1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Warn users when they attempt to add a network that is already configured (#11179)

* warn users when they attempt to add a network that is already configured

* clean up validation logic

* fixing up e2e tests

* Update test/e2e/helpers.js

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

Co-authored-by: Mark Stacey <markjstacey@gmail.com>
This commit is contained in:
Alex Donesky 2021-06-04 08:52:07 -05:00 committed by GitHub
parent a8196c2b21
commit e76762548c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 16 deletions

View File

@ -289,6 +289,9 @@
"chainIdDefinition": { "chainIdDefinition": {
"message": "The chain ID used to sign transactions for this network." "message": "The chain ID used to sign transactions for this network."
}, },
"chainIdExistsErrorMsg": {
"message": "This Chain ID is currently used by the $1 network."
},
"chromeRequiredForHardwareWallets": { "chromeRequiredForHardwareWallets": {
"message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet." "message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet."
}, },
@ -2349,7 +2352,7 @@
"message": "URLs require the appropriate HTTP/HTTPS prefix." "message": "URLs require the appropriate HTTP/HTTPS prefix."
}, },
"urlExistsErrorMsg": { "urlExistsErrorMsg": {
"message": "URL is already present in existing list of networks" "message": "This URL is currently used by the $1 network."
}, },
"usePhishingDetection": { "usePhishingDetection": {
"message": "Use Phishing Detection" "message": "Use Phishing Detection"

View File

@ -27,6 +27,7 @@ async function withFixtures(options, testSuite) {
} = options; } = options;
const fixtureServer = new FixtureServer(); const fixtureServer = new FixtureServer();
const ganacheServer = new Ganache(); const ganacheServer = new Ganache();
let secondaryGanacheServer;
let dappServer; let dappServer;
let segmentServer; let segmentServer;
let segmentStub; let segmentStub;
@ -34,6 +35,16 @@ async function withFixtures(options, testSuite) {
let webDriver; let webDriver;
try { try {
await ganacheServer.start(ganacheOptions); await ganacheServer.start(ganacheOptions);
if (ganacheOptions?.concurrent) {
const { port, chainId } = ganacheOptions.concurrent;
secondaryGanacheServer = new Ganache();
await secondaryGanacheServer.start({
blockTime: 2,
_chainIdRpc: chainId,
port,
vmErrorsOnRPCResponse: false,
});
}
await fixtureServer.start(); await fixtureServer.start();
await fixtureServer.loadState(path.join(__dirname, 'fixtures', fixtures)); await fixtureServer.loadState(path.join(__dirname, 'fixtures', fixtures));
if (dapp) { if (dapp) {
@ -103,6 +114,9 @@ async function withFixtures(options, testSuite) {
} finally { } finally {
await fixtureServer.stop(); await fixtureServer.stop();
await ganacheServer.quit(); await ganacheServer.quit();
if (ganacheOptions?.concurrent) {
await secondaryGanacheServer.quit();
}
if (webDriver) { if (webDriver) {
await webDriver.quit(); await webDriver.quit();
} }

View File

@ -12,6 +12,49 @@ describe('Stores custom RPC history', function () {
], ],
}; };
it(`creates first custom RPC entry`, async function () { it(`creates first custom RPC entry`, async function () {
const port = 8546;
const chainId = 1338;
await withFixtures(
{
fixtures: 'imported-account',
ganacheOptions: { ...ganacheOptions, concurrent: { port, chainId } },
title: this.test.title,
},
async ({ driver }) => {
await driver.navigate();
await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER);
const rpcUrl = `http://127.0.0.1:${port}`;
const networkName = 'Secondary Ganache Testnet';
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 networkNameInput = customRpcInputs[0];
const rpcUrlInput = customRpcInputs[1];
const chainIdInput = customRpcInputs[2];
await networkNameInput.clear();
await networkNameInput.sendKeys(networkName);
await rpcUrlInput.clear();
await rpcUrlInput.sendKeys(rpcUrl);
await chainIdInput.clear();
await chainIdInput.sendKeys(chainId.toString());
await driver.clickElement('.network-form__footer .btn-secondary');
await driver.findElement({ text: networkName, tag: 'div' });
},
);
});
it('warns user when they enter url or chainId for an already configured network', async function () {
await withFixtures( await withFixtures(
{ {
fixtures: 'imported-account', fixtures: 'imported-account',
@ -23,8 +66,9 @@ describe('Stores custom RPC history', function () {
await driver.fill('#password', 'correct horse battery staple'); await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER); await driver.press('#password', driver.Key.ENTER);
const rpcUrl = 'http://127.0.0.1:8545/1'; // duplicate network
const chainId = '0x539'; // Ganache default, decimal 1337 const duplicateRpcUrl = 'http://localhost:8545';
const duplicateChainId = '0x539';
await driver.clickElement('.network-display'); await driver.clickElement('.network-display');
@ -37,13 +81,19 @@ describe('Stores custom RPC history', function () {
const chainIdInput = customRpcInputs[2]; const chainIdInput = customRpcInputs[2];
await rpcUrlInput.clear(); await rpcUrlInput.clear();
await rpcUrlInput.sendKeys(rpcUrl); await rpcUrlInput.sendKeys(duplicateRpcUrl);
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(chainId); await chainIdInput.sendKeys(duplicateChainId);
await driver.findElement({
await driver.clickElement('.network-form__footer .btn-secondary'); text:
await driver.findElement({ text: rpcUrl, tag: 'div' }); 'This Chain ID is currently used by the Localhost 8545 network.',
tag: 'p',
});
}, },
); );
}); });

View File

@ -10,6 +10,7 @@ import {
isSafeChainId, isSafeChainId,
} from '../../../../../shared/modules/network.utils'; } from '../../../../../shared/modules/network.utils';
import { jsonRpcRequest } from '../../../../../shared/modules/rpc.utils'; import { jsonRpcRequest } from '../../../../../shared/modules/rpc.utils';
import { decimalToHex } from '../../../../helpers/utils/conversions.util';
const FORM_STATE_KEYS = [ const FORM_STATE_KEYS = [
'rpcUrl', 'rpcUrl',
@ -39,7 +40,7 @@ export default class NetworkForm extends PureComponent {
isCurrentRpcTarget: PropTypes.bool, isCurrentRpcTarget: PropTypes.bool,
blockExplorerUrl: PropTypes.string, blockExplorerUrl: PropTypes.string,
rpcPrefs: PropTypes.object, rpcPrefs: PropTypes.object,
rpcUrls: PropTypes.array, networksToRender: PropTypes.array,
isFullScreen: PropTypes.bool, isFullScreen: PropTypes.bool,
}; };
@ -332,11 +333,25 @@ export default class NetworkForm extends PureComponent {
}; };
validateChainIdOnChange = (chainIdArg = '') => { validateChainIdOnChange = (chainIdArg = '') => {
const { networksToRender } = this.props;
const chainId = chainIdArg.trim(); const chainId = chainIdArg.trim();
let errorMessage = ''; let errorMessage = '';
let radix = 10; let radix = 10;
const hexChainId = chainId.startsWith('0x')
? chainId
: `0x${decimalToHex(chainId)}`;
const [matchingChainId] = networksToRender.filter(
(e) => e.chainId === hexChainId,
);
if (chainId.startsWith('0x')) { if (chainId === '') {
this.setErrorTo('chainId', '');
return;
} else if (matchingChainId) {
errorMessage = this.context.t('chainIdExistsErrorMsg', [
matchingChainId.label ?? matchingChainId.labelKey,
]);
} 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)) {
errorMessage = this.context.t('invalidHexNumber'); errorMessage = this.context.t('invalidHexNumber');
@ -433,23 +448,29 @@ export default class NetworkForm extends PureComponent {
validateUrlRpcUrl = (url, stateKey) => { validateUrlRpcUrl = (url, stateKey) => {
const { t } = this.context; const { t } = this.context;
const { rpcUrls } = this.props; const { networksToRender } = this.props;
const { chainId: stateChainId } = this.state; const { chainId: stateChainId } = this.state;
const isValidUrl = validUrl.isWebUri(url) && url !== ''; const isValidUrl = validUrl.isWebUri(url);
const chainIdFetchFailed = this.hasError( const chainIdFetchFailed = this.hasError(
'chainId', 'chainId',
t('failedToFetchChainId'), t('failedToFetchChainId'),
); );
const [matchingRPCUrl] = networksToRender.filter((e) => e.rpcUrl === url);
if (!isValidUrl) { if (!isValidUrl && url !== '') {
this.setErrorTo( this.setErrorTo(
stateKey, stateKey,
this.context.t( this.context.t(
this.isValidWhenAppended(url) ? 'urlErrorMsg' : 'invalidRPC', this.isValidWhenAppended(url) ? 'urlErrorMsg' : 'invalidRPC',
), ),
); );
} else if (rpcUrls.includes(url)) { } else if (matchingRPCUrl) {
this.setErrorTo(stateKey, this.context.t('urlExistsErrorMsg')); this.setErrorTo(
stateKey,
this.context.t('urlExistsErrorMsg', [
matchingRPCUrl.label ?? matchingRPCUrl.labelKey,
]),
);
} else { } else {
this.setErrorTo(stateKey, ''); this.setErrorTo(stateKey, '');
} }

View File

@ -202,12 +202,12 @@ export default class NetworksTab extends PureComponent {
{this.renderNetworksList()} {this.renderNetworksList()}
{shouldRenderNetworkForm ? ( {shouldRenderNetworkForm ? (
<NetworkForm <NetworkForm
rpcUrls={networksToRender.map((network) => network.rpcUrl)}
setRpcTarget={setRpcTarget} setRpcTarget={setRpcTarget}
editRpc={editRpc} editRpc={editRpc}
networkName={label || (labelKey && t(labelKey)) || ''} networkName={label || (labelKey && t(labelKey)) || ''}
rpcUrl={rpcUrl} rpcUrl={rpcUrl}
chainId={chainId} chainId={chainId}
networksToRender={networksToRender}
ticker={ticker} ticker={ticker}
onClear={(shouldUpdateHistory = true) => { onClear={(shouldUpdateHistory = true) => {
setNetworksTabAddMode(false); setNetworksTabAddMode(false);