mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Allowing custom rpc form submission when chainId is a duplicate (#11363)
This commit is contained in:
parent
4818ddad25
commit
73200a7876
@ -54,7 +54,7 @@ describe('Stores custom RPC history', function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('warns user when they enter url or chainId for an already configured network', async function () {
|
it('warns user when they enter url for an already configured network', async function () {
|
||||||
await withFixtures(
|
await withFixtures(
|
||||||
{
|
{
|
||||||
fixtures: 'imported-account',
|
fixtures: 'imported-account',
|
||||||
@ -68,6 +68,40 @@ describe('Stores custom RPC history', function () {
|
|||||||
|
|
||||||
// duplicate network
|
// duplicate network
|
||||||
const duplicateRpcUrl = 'http://localhost:8545';
|
const duplicateRpcUrl = 'http://localhost:8545';
|
||||||
|
|
||||||
|
await driver.clickElement('.network-display');
|
||||||
|
|
||||||
|
await driver.clickElement({ text: 'Custom RPC', tag: 'span' });
|
||||||
|
|
||||||
|
await driver.findElement('.settings-page__sub-header-text');
|
||||||
|
|
||||||
|
const customRpcInputs = await driver.findElements('input[type="text"]');
|
||||||
|
const rpcUrlInput = customRpcInputs[1];
|
||||||
|
|
||||||
|
await rpcUrlInput.clear();
|
||||||
|
await rpcUrlInput.sendKeys(duplicateRpcUrl);
|
||||||
|
await driver.findElement({
|
||||||
|
text: 'This URL is currently used by the Localhost 8545 network.',
|
||||||
|
tag: 'p',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('warns user when they enter chainId for an already configured network', async function () {
|
||||||
|
await withFixtures(
|
||||||
|
{
|
||||||
|
fixtures: 'imported-account',
|
||||||
|
ganacheOptions,
|
||||||
|
title: this.test.title,
|
||||||
|
},
|
||||||
|
async ({ driver }) => {
|
||||||
|
await driver.navigate();
|
||||||
|
await driver.fill('#password', 'correct horse battery staple');
|
||||||
|
await driver.press('#password', driver.Key.ENTER);
|
||||||
|
|
||||||
|
// duplicate network
|
||||||
|
const newRpcUrl = 'http://localhost:8544';
|
||||||
const duplicateChainId = '0x539';
|
const duplicateChainId = '0x539';
|
||||||
|
|
||||||
await driver.clickElement('.network-display');
|
await driver.clickElement('.network-display');
|
||||||
@ -81,11 +115,7 @@ describe('Stores custom RPC history', function () {
|
|||||||
const chainIdInput = customRpcInputs[2];
|
const chainIdInput = customRpcInputs[2];
|
||||||
|
|
||||||
await rpcUrlInput.clear();
|
await rpcUrlInput.clear();
|
||||||
await rpcUrlInput.sendKeys(duplicateRpcUrl);
|
await rpcUrlInput.sendKeys(newRpcUrl);
|
||||||
await driver.findElement({
|
|
||||||
text: 'This URL is currently used by the Localhost 8545 network.',
|
|
||||||
tag: 'p',
|
|
||||||
});
|
|
||||||
|
|
||||||
await chainIdInput.clear();
|
await chainIdInput.clear();
|
||||||
await chainIdInput.sendKeys(duplicateChainId);
|
await chainIdInput.sendKeys(duplicateChainId);
|
||||||
|
@ -280,6 +280,7 @@ export default class NetworkForm extends PureComponent {
|
|||||||
}) {
|
}) {
|
||||||
const { errors } = this.state;
|
const { errors } = this.state;
|
||||||
const { viewOnly } = this.props;
|
const { viewOnly } = this.props;
|
||||||
|
const errorMessage = errors[fieldKey]?.msg || '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="networks-tab__network-form-row">
|
<div className="networks-tab__network-form-row">
|
||||||
@ -305,7 +306,7 @@ export default class NetworkForm extends PureComponent {
|
|||||||
margin="dense"
|
margin="dense"
|
||||||
value={value}
|
value={value}
|
||||||
disabled={viewOnly}
|
disabled={viewOnly}
|
||||||
error={errors[fieldKey]}
|
error={errorMessage}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -328,45 +329,78 @@ export default class NetworkForm extends PureComponent {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
hasError = (errorKey, errorVal) => {
|
setErrorEmpty = (errorKey) => {
|
||||||
return this.state.errors[errorKey] === errorVal;
|
this.setState({
|
||||||
|
errors: {
|
||||||
|
...this.state.errors,
|
||||||
|
[errorKey]: {
|
||||||
|
msg: '',
|
||||||
|
key: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
validateChainIdOnChange = (chainIdArg = '') => {
|
hasError = (errorKey, errorKeyVal) => {
|
||||||
|
return this.state.errors[errorKey]?.key === errorKeyVal;
|
||||||
|
};
|
||||||
|
|
||||||
|
hasErrors = () => {
|
||||||
|
const { errors } = this.state;
|
||||||
|
return Object.keys(errors).some((key) => {
|
||||||
|
const error = errors[key];
|
||||||
|
// Do not factor in duplicate chain id error for submission disabling
|
||||||
|
if (key === 'chainId' && error.key === 'chainIdExistsErrorMsg') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return error.key && error.msg;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
validateChainIdOnChange = (selfRpcUrl, chainIdArg = '') => {
|
||||||
const { networksToRender } = this.props;
|
const { networksToRender } = this.props;
|
||||||
const chainId = chainIdArg.trim();
|
const chainId = chainIdArg.trim();
|
||||||
|
let errorKey = '';
|
||||||
let errorMessage = '';
|
let errorMessage = '';
|
||||||
let radix = 10;
|
let radix = 10;
|
||||||
const hexChainId = chainId.startsWith('0x')
|
const hexChainId = chainId.startsWith('0x')
|
||||||
? chainId
|
? chainId
|
||||||
: `0x${decimalToHex(chainId)}`;
|
: `0x${decimalToHex(chainId)}`;
|
||||||
const [matchingChainId] = networksToRender.filter(
|
const [matchingChainId] = networksToRender.filter(
|
||||||
(e) => e.chainId === hexChainId,
|
(e) => e.chainId === hexChainId && e.rpcUrl !== selfRpcUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (chainId === '') {
|
if (chainId === '') {
|
||||||
this.setErrorTo('chainId', '');
|
this.setErrorEmpty('chainId');
|
||||||
return;
|
return;
|
||||||
} else if (matchingChainId) {
|
} else if (matchingChainId) {
|
||||||
|
errorKey = 'chainIdExistsErrorMsg';
|
||||||
errorMessage = this.context.t('chainIdExistsErrorMsg', [
|
errorMessage = this.context.t('chainIdExistsErrorMsg', [
|
||||||
matchingChainId.label ?? matchingChainId.labelKey,
|
matchingChainId.label ?? matchingChainId.labelKey,
|
||||||
]);
|
]);
|
||||||
} else if (chainId.startsWith('0x')) {
|
} else if (chainId.startsWith('0x')) {
|
||||||
radix = 16;
|
radix = 16;
|
||||||
if (!/^0x[0-9a-f]+$/iu.test(chainId)) {
|
if (!/^0x[0-9a-f]+$/iu.test(chainId)) {
|
||||||
|
errorKey = 'invalidHexNumber';
|
||||||
errorMessage = this.context.t('invalidHexNumber');
|
errorMessage = this.context.t('invalidHexNumber');
|
||||||
} else if (!isPrefixedFormattedHexString(chainId)) {
|
} else if (!isPrefixedFormattedHexString(chainId)) {
|
||||||
errorMessage = this.context.t('invalidHexNumberLeadingZeros');
|
errorMessage = this.context.t('invalidHexNumberLeadingZeros');
|
||||||
}
|
}
|
||||||
} else if (!/^[0-9]+$/u.test(chainId)) {
|
} else if (!/^[0-9]+$/u.test(chainId)) {
|
||||||
|
errorKey = 'invalidNumber';
|
||||||
errorMessage = this.context.t('invalidNumber');
|
errorMessage = this.context.t('invalidNumber');
|
||||||
} else if (chainId.startsWith('0')) {
|
} else if (chainId.startsWith('0')) {
|
||||||
|
errorKey = 'invalidNumberLeadingZeros';
|
||||||
errorMessage = this.context.t('invalidNumberLeadingZeros');
|
errorMessage = this.context.t('invalidNumberLeadingZeros');
|
||||||
} else if (!isSafeChainId(parseInt(chainId, radix))) {
|
} else if (!isSafeChainId(parseInt(chainId, radix))) {
|
||||||
|
errorKey = 'invalidChainIdTooBig';
|
||||||
errorMessage = this.context.t('invalidChainIdTooBig');
|
errorMessage = this.context.t('invalidChainIdTooBig');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setErrorTo('chainId', errorMessage);
|
this.setErrorTo('chainId', {
|
||||||
|
key: errorKey,
|
||||||
|
msg: errorMessage,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -381,6 +415,7 @@ export default class NetworkForm extends PureComponent {
|
|||||||
*/
|
*/
|
||||||
validateChainIdOnSubmit = async (formChainId, parsedChainId, rpcUrl) => {
|
validateChainIdOnSubmit = async (formChainId, parsedChainId, rpcUrl) => {
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
|
let errorKey;
|
||||||
let errorMessage;
|
let errorMessage;
|
||||||
let endpointChainId;
|
let endpointChainId;
|
||||||
let providerError;
|
let providerError;
|
||||||
@ -393,6 +428,7 @@ export default class NetworkForm extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (providerError || typeof endpointChainId !== 'string') {
|
if (providerError || typeof endpointChainId !== 'string') {
|
||||||
|
errorKey = 'failedToFetchChainId';
|
||||||
errorMessage = t('failedToFetchChainId');
|
errorMessage = t('failedToFetchChainId');
|
||||||
} else if (parsedChainId !== endpointChainId) {
|
} else if (parsedChainId !== endpointChainId) {
|
||||||
// Here, we are in an error state. The endpoint should always return a
|
// Here, we are in an error state. The endpoint should always return a
|
||||||
@ -410,6 +446,7 @@ export default class NetworkForm extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorKey = 'endpointReturnedDifferentChainId';
|
||||||
errorMessage = t('endpointReturnedDifferentChainId', [
|
errorMessage = t('endpointReturnedDifferentChainId', [
|
||||||
endpointChainId.length <= 12
|
endpointChainId.length <= 12
|
||||||
? endpointChainId
|
? endpointChainId
|
||||||
@ -417,12 +454,15 @@ export default class NetworkForm extends PureComponent {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMessage) {
|
if (errorKey) {
|
||||||
this.setErrorTo('chainId', errorMessage);
|
this.setErrorTo('chainId', {
|
||||||
|
key: errorKey,
|
||||||
|
msg: errorMessage,
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setErrorTo('chainId', '');
|
this.setErrorEmpty('chainId');
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -432,17 +472,25 @@ export default class NetworkForm extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
validateBlockExplorerURL = (url, stateKey) => {
|
validateBlockExplorerURL = (url, stateKey) => {
|
||||||
|
const { t } = this.context;
|
||||||
if (!validUrl.isWebUri(url) && url !== '') {
|
if (!validUrl.isWebUri(url) && url !== '') {
|
||||||
this.setErrorTo(
|
let errorKey;
|
||||||
stateKey,
|
let errorMessage;
|
||||||
this.context.t(
|
|
||||||
this.isValidWhenAppended(url)
|
if (this.isValidWhenAppended(url)) {
|
||||||
? 'urlErrorMsg'
|
errorKey = 'urlErrorMsg';
|
||||||
: 'invalidBlockExplorerURL',
|
errorMessage = t('urlErrorMsg');
|
||||||
),
|
} else {
|
||||||
);
|
errorKey = 'invalidBlockExplorerURL';
|
||||||
|
errorMessage = t('invalidBlockExplorerURL');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setErrorTo(stateKey, {
|
||||||
|
key: errorKey,
|
||||||
|
msg: errorMessage,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setErrorTo(stateKey, '');
|
this.setErrorEmpty(stateKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -451,28 +499,32 @@ export default class NetworkForm extends PureComponent {
|
|||||||
const { networksToRender } = this.props;
|
const { networksToRender } = this.props;
|
||||||
const { chainId: stateChainId } = this.state;
|
const { chainId: stateChainId } = this.state;
|
||||||
const isValidUrl = validUrl.isWebUri(url);
|
const isValidUrl = validUrl.isWebUri(url);
|
||||||
const chainIdFetchFailed = this.hasError(
|
const chainIdFetchFailed = this.hasError('chainId', 'failedToFetchChainId');
|
||||||
'chainId',
|
|
||||||
t('failedToFetchChainId'),
|
|
||||||
);
|
|
||||||
const [matchingRPCUrl] = networksToRender.filter((e) => e.rpcUrl === url);
|
const [matchingRPCUrl] = networksToRender.filter((e) => e.rpcUrl === url);
|
||||||
|
|
||||||
if (!isValidUrl && url !== '') {
|
if (!isValidUrl && url !== '') {
|
||||||
this.setErrorTo(
|
let errorKey;
|
||||||
stateKey,
|
let errorMessage;
|
||||||
this.context.t(
|
if (this.isValidWhenAppended(url)) {
|
||||||
this.isValidWhenAppended(url) ? 'urlErrorMsg' : 'invalidRPC',
|
errorKey = 'urlErrorMsg';
|
||||||
),
|
errorMessage = t('urlErrorMsg');
|
||||||
);
|
} else {
|
||||||
|
errorKey = 'invalidRPC';
|
||||||
|
errorMessage = t('invalidRPC');
|
||||||
|
}
|
||||||
|
this.setErrorTo(stateKey, {
|
||||||
|
key: errorKey,
|
||||||
|
msg: errorMessage,
|
||||||
|
});
|
||||||
} else if (matchingRPCUrl) {
|
} else if (matchingRPCUrl) {
|
||||||
this.setErrorTo(
|
this.setErrorTo(stateKey, {
|
||||||
stateKey,
|
key: 'urlExistsErrorMsg',
|
||||||
this.context.t('urlExistsErrorMsg', [
|
msg: t('urlExistsErrorMsg', [
|
||||||
matchingRPCUrl.label ?? matchingRPCUrl.labelKey,
|
matchingRPCUrl.label ?? matchingRPCUrl.labelKey,
|
||||||
]),
|
]),
|
||||||
);
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setErrorTo(stateKey, '');
|
this.setErrorEmpty(stateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-validate the chain id if it could not be found with previous rpc url
|
// Re-validate the chain id if it could not be found with previous rpc url
|
||||||
@ -501,18 +553,16 @@ export default class NetworkForm extends PureComponent {
|
|||||||
chainId = '',
|
chainId = '',
|
||||||
ticker,
|
ticker,
|
||||||
blockExplorerUrl,
|
blockExplorerUrl,
|
||||||
errors,
|
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const deletable =
|
const deletable =
|
||||||
!networksTabIsInAddMode && !isCurrentRpcTarget && !viewOnly;
|
!networksTabIsInAddMode && !isCurrentRpcTarget && !viewOnly;
|
||||||
|
|
||||||
const isSubmitDisabled =
|
const isSubmitDisabled =
|
||||||
|
this.hasErrors() ||
|
||||||
this.isSubmitting() ||
|
this.isSubmitting() ||
|
||||||
this.stateIsUnchanged() ||
|
this.stateIsUnchanged() ||
|
||||||
!rpcUrl ||
|
!rpcUrl ||
|
||||||
!chainId ||
|
!chainId;
|
||||||
Object.values(errors).some((x) => x);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="networks-tab__network-form">
|
<div className="networks-tab__network-form">
|
||||||
@ -535,7 +585,7 @@ export default class NetworkForm extends PureComponent {
|
|||||||
textFieldId: 'chainId',
|
textFieldId: 'chainId',
|
||||||
onChange: this.setStateWithValue(
|
onChange: this.setStateWithValue(
|
||||||
'chainId',
|
'chainId',
|
||||||
this.validateChainIdOnChange,
|
this.validateChainIdOnChange.bind(this, rpcUrl),
|
||||||
),
|
),
|
||||||
value: chainId,
|
value: chainId,
|
||||||
tooltipText: viewOnly ? null : t('networkSettingsChainIdDescription'),
|
tooltipText: viewOnly ? null : t('networkSettingsChainIdDescription'),
|
||||||
|
Loading…
Reference in New Issue
Block a user