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
e10ddbe3a3
commit
a171ac7b34
@ -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(
|
||||
{
|
||||
fixtures: 'imported-account',
|
||||
@ -68,6 +68,40 @@ describe('Stores custom RPC history', function () {
|
||||
|
||||
// duplicate network
|
||||
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';
|
||||
|
||||
await driver.clickElement('.network-display');
|
||||
@ -81,11 +115,7 @@ describe('Stores custom RPC history', function () {
|
||||
const chainIdInput = customRpcInputs[2];
|
||||
|
||||
await rpcUrlInput.clear();
|
||||
await rpcUrlInput.sendKeys(duplicateRpcUrl);
|
||||
await driver.findElement({
|
||||
text: 'This URL is currently used by the Localhost 8545 network.',
|
||||
tag: 'p',
|
||||
});
|
||||
await rpcUrlInput.sendKeys(newRpcUrl);
|
||||
|
||||
await chainIdInput.clear();
|
||||
await chainIdInput.sendKeys(duplicateChainId);
|
||||
|
@ -280,6 +280,7 @@ export default class NetworkForm extends PureComponent {
|
||||
}) {
|
||||
const { errors } = this.state;
|
||||
const { viewOnly } = this.props;
|
||||
const errorMessage = errors[fieldKey]?.msg || '';
|
||||
|
||||
return (
|
||||
<div className="networks-tab__network-form-row">
|
||||
@ -305,7 +306,7 @@ export default class NetworkForm extends PureComponent {
|
||||
margin="dense"
|
||||
value={value}
|
||||
disabled={viewOnly}
|
||||
error={errors[fieldKey]}
|
||||
error={errorMessage}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
</div>
|
||||
@ -328,45 +329,78 @@ export default class NetworkForm extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
hasError = (errorKey, errorVal) => {
|
||||
return this.state.errors[errorKey] === errorVal;
|
||||
setErrorEmpty = (errorKey) => {
|
||||
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 chainId = chainIdArg.trim();
|
||||
let errorKey = '';
|
||||
let errorMessage = '';
|
||||
let radix = 10;
|
||||
const hexChainId = chainId.startsWith('0x')
|
||||
? chainId
|
||||
: `0x${decimalToHex(chainId)}`;
|
||||
const [matchingChainId] = networksToRender.filter(
|
||||
(e) => e.chainId === hexChainId,
|
||||
(e) => e.chainId === hexChainId && e.rpcUrl !== selfRpcUrl,
|
||||
);
|
||||
|
||||
if (chainId === '') {
|
||||
this.setErrorTo('chainId', '');
|
||||
this.setErrorEmpty('chainId');
|
||||
return;
|
||||
} else if (matchingChainId) {
|
||||
errorKey = 'chainIdExistsErrorMsg';
|
||||
errorMessage = this.context.t('chainIdExistsErrorMsg', [
|
||||
matchingChainId.label ?? matchingChainId.labelKey,
|
||||
]);
|
||||
} else if (chainId.startsWith('0x')) {
|
||||
radix = 16;
|
||||
if (!/^0x[0-9a-f]+$/iu.test(chainId)) {
|
||||
errorKey = 'invalidHexNumber';
|
||||
errorMessage = this.context.t('invalidHexNumber');
|
||||
} else if (!isPrefixedFormattedHexString(chainId)) {
|
||||
errorMessage = this.context.t('invalidHexNumberLeadingZeros');
|
||||
}
|
||||
} else if (!/^[0-9]+$/u.test(chainId)) {
|
||||
errorKey = 'invalidNumber';
|
||||
errorMessage = this.context.t('invalidNumber');
|
||||
} else if (chainId.startsWith('0')) {
|
||||
errorKey = 'invalidNumberLeadingZeros';
|
||||
errorMessage = this.context.t('invalidNumberLeadingZeros');
|
||||
} else if (!isSafeChainId(parseInt(chainId, radix))) {
|
||||
errorKey = '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) => {
|
||||
const { t } = this.context;
|
||||
let errorKey;
|
||||
let errorMessage;
|
||||
let endpointChainId;
|
||||
let providerError;
|
||||
@ -393,6 +428,7 @@ export default class NetworkForm extends PureComponent {
|
||||
}
|
||||
|
||||
if (providerError || typeof endpointChainId !== 'string') {
|
||||
errorKey = 'failedToFetchChainId';
|
||||
errorMessage = t('failedToFetchChainId');
|
||||
} else if (parsedChainId !== endpointChainId) {
|
||||
// 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', [
|
||||
endpointChainId.length <= 12
|
||||
? endpointChainId
|
||||
@ -417,12 +454,15 @@ export default class NetworkForm extends PureComponent {
|
||||
]);
|
||||
}
|
||||
|
||||
if (errorMessage) {
|
||||
this.setErrorTo('chainId', errorMessage);
|
||||
if (errorKey) {
|
||||
this.setErrorTo('chainId', {
|
||||
key: errorKey,
|
||||
msg: errorMessage,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setErrorTo('chainId', '');
|
||||
this.setErrorEmpty('chainId');
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -432,17 +472,25 @@ export default class NetworkForm extends PureComponent {
|
||||
};
|
||||
|
||||
validateBlockExplorerURL = (url, stateKey) => {
|
||||
const { t } = this.context;
|
||||
if (!validUrl.isWebUri(url) && url !== '') {
|
||||
this.setErrorTo(
|
||||
stateKey,
|
||||
this.context.t(
|
||||
this.isValidWhenAppended(url)
|
||||
? 'urlErrorMsg'
|
||||
: 'invalidBlockExplorerURL',
|
||||
),
|
||||
);
|
||||
let errorKey;
|
||||
let errorMessage;
|
||||
|
||||
if (this.isValidWhenAppended(url)) {
|
||||
errorKey = 'urlErrorMsg';
|
||||
errorMessage = t('urlErrorMsg');
|
||||
} else {
|
||||
errorKey = 'invalidBlockExplorerURL';
|
||||
errorMessage = t('invalidBlockExplorerURL');
|
||||
}
|
||||
|
||||
this.setErrorTo(stateKey, {
|
||||
key: errorKey,
|
||||
msg: errorMessage,
|
||||
});
|
||||
} else {
|
||||
this.setErrorTo(stateKey, '');
|
||||
this.setErrorEmpty(stateKey);
|
||||
}
|
||||
};
|
||||
|
||||
@ -451,28 +499,32 @@ export default class NetworkForm extends PureComponent {
|
||||
const { networksToRender } = this.props;
|
||||
const { chainId: stateChainId } = this.state;
|
||||
const isValidUrl = validUrl.isWebUri(url);
|
||||
const chainIdFetchFailed = this.hasError(
|
||||
'chainId',
|
||||
t('failedToFetchChainId'),
|
||||
);
|
||||
const chainIdFetchFailed = this.hasError('chainId', 'failedToFetchChainId');
|
||||
const [matchingRPCUrl] = networksToRender.filter((e) => e.rpcUrl === url);
|
||||
|
||||
if (!isValidUrl && url !== '') {
|
||||
this.setErrorTo(
|
||||
stateKey,
|
||||
this.context.t(
|
||||
this.isValidWhenAppended(url) ? 'urlErrorMsg' : 'invalidRPC',
|
||||
),
|
||||
);
|
||||
let errorKey;
|
||||
let errorMessage;
|
||||
if (this.isValidWhenAppended(url)) {
|
||||
errorKey = 'urlErrorMsg';
|
||||
errorMessage = t('urlErrorMsg');
|
||||
} else {
|
||||
errorKey = 'invalidRPC';
|
||||
errorMessage = t('invalidRPC');
|
||||
}
|
||||
this.setErrorTo(stateKey, {
|
||||
key: errorKey,
|
||||
msg: errorMessage,
|
||||
});
|
||||
} else if (matchingRPCUrl) {
|
||||
this.setErrorTo(
|
||||
stateKey,
|
||||
this.context.t('urlExistsErrorMsg', [
|
||||
this.setErrorTo(stateKey, {
|
||||
key: 'urlExistsErrorMsg',
|
||||
msg: t('urlExistsErrorMsg', [
|
||||
matchingRPCUrl.label ?? matchingRPCUrl.labelKey,
|
||||
]),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
this.setErrorTo(stateKey, '');
|
||||
this.setErrorEmpty(stateKey);
|
||||
}
|
||||
|
||||
// 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 = '',
|
||||
ticker,
|
||||
blockExplorerUrl,
|
||||
errors,
|
||||
} = this.state;
|
||||
|
||||
const deletable =
|
||||
!networksTabIsInAddMode && !isCurrentRpcTarget && !viewOnly;
|
||||
|
||||
const isSubmitDisabled =
|
||||
this.hasErrors() ||
|
||||
this.isSubmitting() ||
|
||||
this.stateIsUnchanged() ||
|
||||
!rpcUrl ||
|
||||
!chainId ||
|
||||
Object.values(errors).some((x) => x);
|
||||
!chainId;
|
||||
|
||||
return (
|
||||
<div className="networks-tab__network-form">
|
||||
@ -535,7 +585,7 @@ export default class NetworkForm extends PureComponent {
|
||||
textFieldId: 'chainId',
|
||||
onChange: this.setStateWithValue(
|
||||
'chainId',
|
||||
this.validateChainIdOnChange,
|
||||
this.validateChainIdOnChange.bind(this, rpcUrl),
|
||||
),
|
||||
value: chainId,
|
||||
tooltipText: viewOnly ? null : t('networkSettingsChainIdDescription'),
|
||||
|
Loading…
Reference in New Issue
Block a user