Warn about multiple MetaMask instances running (#13836)

* Add text warning on startup page

* Try to detect extensions with browser API

* Setup messaging between different versions of extension

* Cleanup

* Cleanup

* Simplify check for multiple instances running

* Fix a doc string + use webextension-polyfill

* Fix test

* Mock webextension-polyfill

* Mock correctly

* Catch error and show warning in both extensions

* Mock as promise

* Address comments

* Rename build ids

* Run detection code only if Chrome

* Add Firefox warnings

* Cleanup imports

* Update connection ids

* Run detection code for Firefox

* Add test

* Add missing await

* Update tests

* Cleanup

* Cleanup

* Improve testing

* Improve tests

* Log errors from sendMessage

* Cleanup

Co-authored-by: Frederik Bolding <frederik.bolding@gmail.com>
feature/default_network_editable
Hennadii Ivtushok 3 years ago committed by GitHub
parent 74b5401302
commit 72d2977e72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/_locales/en/messages.json
  2. 55
      app/scripts/detect-multiple-instances.js
  3. 104
      app/scripts/detect-multiple-instances.test.js
  4. 9
      app/scripts/metamask-controller.js
  5. 8
      app/scripts/metamask-controller.test.js
  6. 20
      shared/constants/app.js
  7. 2
      ui/components/app/flask/experimental-area/experimental-area.js

@ -1252,6 +1252,10 @@
"message": "All Flask APIs are experimental. They may be changed or removed without notice, or they might stay on Flask indefinitely without ever being migrated to stable MetaMask. Use them at your own risk.",
"description": "This message warns developers about unstable Flask APIs"
},
"flaskWelcomeWarning4": {
"message": "Make sure to disable your regular MetaMask extension when using Flask.",
"description": "This message calls to pay attention about multiple versions of MetaMask running on the same site (Flask + Prod)"
},
"flaskWelcomeWarningAcceptButton": {
"message": "I accept the risks",
"description": "this text is shown on a button, which the user presses to confirm they understand the risks of using Flask"

@ -0,0 +1,55 @@
/**
* Sets up two-way communication between the
* mainline version of extension and Flask build
* in order to detect & warn if there are two different
* versions running simultaneously.
*/
import browser from 'webextension-polyfill';
import {
PLATFORM_CHROME,
PLATFORM_FIREFOX,
CHROME_BUILD_IDS,
FIREFOX_BUILD_IDS,
} from '../../shared/constants/app';
import { getPlatform } from './lib/util';
const MESSAGE_TEXT = 'isRunning';
const showWarning = () =>
console.warn('Warning! You have multiple instances of MetaMask running!');
/**
* Handles the ping message sent from other extension.
* Displays console warning if it's active.
*
* @param message - The message received from the other extension
*/
export const onMessageReceived = (message) => {
if (message === MESSAGE_TEXT) {
showWarning();
}
};
/**
* Sends the ping message sent to other extensions to detect whether it's active or not.
*/
export const checkForMultipleVersionsRunning = async () => {
if (getPlatform() !== PLATFORM_CHROME && getPlatform() !== PLATFORM_FIREFOX) {
return;
}
const buildIds =
getPlatform() === PLATFORM_CHROME ? CHROME_BUILD_IDS : FIREFOX_BUILD_IDS;
const thisBuild = browser.runtime.id;
for (const id of buildIds) {
if (id !== thisBuild) {
try {
await browser.runtime.sendMessage(id, MESSAGE_TEXT);
} catch (error) {
// Should do nothing if receiving end was not reached (no other instances running)
}
}
}
};

@ -0,0 +1,104 @@
import { strict as assert } from 'assert';
import browser from 'webextension-polyfill';
import sinon from 'sinon';
import {
PLATFORM_CHROME,
PLATFORM_EDGE,
METAMASK_BETA_CHROME_ID,
METAMASK_PROD_CHROME_ID,
METAMASK_FLASK_CHROME_ID,
} from '../../shared/constants/app';
import {
checkForMultipleVersionsRunning,
onMessageReceived,
} from './detect-multiple-instances';
import * as util from './lib/util';
describe('multiple instances running detector', function () {
const PING_MESSAGE = 'isRunning';
let sendMessageStub = sinon.stub();
beforeEach(async function () {
sinon.replace(browser, 'runtime', {
sendMessage: sendMessageStub,
id: METAMASK_BETA_CHROME_ID,
});
sinon.stub(util, 'getPlatform').callsFake((_) => {
return PLATFORM_CHROME;
});
});
afterEach(function () {
sinon.restore();
});
describe('checkForMultipleVersionsRunning', function () {
it('should send ping message to multiple instances', async function () {
await checkForMultipleVersionsRunning();
assert(sendMessageStub.calledTwice);
assert(
sendMessageStub
.getCall(0)
.calledWithExactly(METAMASK_PROD_CHROME_ID, PING_MESSAGE),
);
assert(
sendMessageStub
.getCall(1)
.calledWithExactly(METAMASK_FLASK_CHROME_ID, PING_MESSAGE),
);
});
it('should not send ping message if platform is not Chrome or Firefox', async function () {
util.getPlatform.restore();
sendMessageStub = sinon.stub();
sinon.stub(util, 'getPlatform').callsFake((_) => {
return PLATFORM_EDGE;
});
await checkForMultipleVersionsRunning();
assert(sendMessageStub.notCalled);
});
it('should not expose an error outside if sendMessage throws', async function () {
sinon.restore();
sinon.replace(browser, 'runtime', {
sendMessage: sinon.stub().throws(),
id: METAMASK_BETA_CHROME_ID,
});
const spy = sinon.spy(checkForMultipleVersionsRunning);
await checkForMultipleVersionsRunning();
assert(!spy.threw());
});
});
describe('onMessageReceived', function () {
beforeEach(function () {
sinon.spy(console, 'warn');
});
it('should print warning message to on ping message received', async function () {
onMessageReceived(PING_MESSAGE);
assert(
console.warn.calledWithExactly(
'Warning! You have multiple instances of MetaMask running!',
),
);
});
it('should not print warning message if wrong message received', async function () {
onMessageReceived(PING_MESSAGE.concat('wrong'));
assert(console.warn.notCalled);
});
});
});

@ -88,6 +88,10 @@ import { hexToDecimal } from '../../ui/helpers/utils/conversions.util';
import { getTokenValueParam } from '../../ui/helpers/utils/token-util';
import { isEqualCaseInsensitive } from '../../shared/modules/string-utils';
import { parseStandardTokenTransactionData } from '../../shared/modules/transaction.utils';
import {
onMessageReceived,
checkForMultipleVersionsRunning,
} from './detect-multiple-instances';
import ComposableObservableStore from './lib/ComposableObservableStore';
import AccountTracker from './lib/account-tracker';
import createLoggerMiddleware from './lib/createLoggerMiddleware';
@ -1062,6 +1066,11 @@ export default class MetamaskController extends EventEmitter {
// TODO:LegacyProvider: Delete
this.publicConfigStore = this.createPublicConfigStore();
// Multiple MetaMask instances launched warning
this.extension.runtime.onMessageExternal.addListener(onMessageReceived);
// Fire a ping message to check if other extensions are running
checkForMultipleVersionsRunning();
}
///: BEGIN:ONLY_INCLUDE_IN(flask)

@ -6,6 +6,7 @@ import { pubToAddress, bufferToHex } from 'ethereumjs-util';
import { obj as createThoughStream } from 'through2';
import EthQuery from 'eth-query';
import proxyquire from 'proxyquire';
import browser from 'webextension-polyfill';
import { TRANSACTION_STATUSES } from '../../shared/constants/transaction';
import createTxMeta from '../../test/lib/createTxMeta';
import { NETWORK_TYPE_RPC } from '../../shared/constants/network';
@ -71,6 +72,9 @@ const browserPolyfillMock = {
onInstalled: {
addListener: () => undefined,
},
onMessageExternal: {
addListener: () => undefined,
},
getPlatformInfo: async () => 'mac',
},
};
@ -131,6 +135,10 @@ describe('MetaMaskController', function () {
.get(/.*/u)
.reply(200, '{"JPY":12415.9}');
sandbox.replace(browser, 'runtime', {
sendMessage: sandbox.stub().rejects(),
});
metamaskController = new MetaMaskController({
showUserConfirmation: noop,
encryptor: {

@ -72,3 +72,23 @@ export const POLLING_TOKEN_ENVIRONMENT_TYPES = {
};
export const ORIGIN_METAMASK = 'metamask';
export const METAMASK_BETA_CHROME_ID = 'pbbkamfgmaedccnfkmjcofcecjhfgldn';
export const METAMASK_PROD_CHROME_ID = 'nkbihfbeogaeaoehlefnkodbefgpgknn';
export const METAMASK_FLASK_CHROME_ID = 'ljfoeinjpaedjfecbmggjgodbgkmjkjk';
export const CHROME_BUILD_IDS = [
METAMASK_BETA_CHROME_ID,
METAMASK_PROD_CHROME_ID,
METAMASK_FLASK_CHROME_ID,
];
const METAMASK_BETA_FIREFOX_ID = 'webextension-beta@metamask.io';
const METAMASK_PROD_FIREFOX_ID = 'webextension@metamask.io';
const METAMASK_FLASK_FIREFOX_ID = 'webextension-flask@metamask.io';
export const FIREFOX_BUILD_IDS = [
METAMASK_BETA_FIREFOX_ID,
METAMASK_PROD_FIREFOX_ID,
METAMASK_FLASK_FIREFOX_ID,
];

@ -86,6 +86,8 @@ export default function ExperimentalArea({ redirectTo }) {
<p>{t('flaskWelcomeWarning2')}</p>
<br />
<p>{t('flaskWelcomeWarning3')}</p>
<br />
<p>{t('flaskWelcomeWarning4')}</p>
</div>
<Button type="primary" onClick={onClick}>
{t('flaskWelcomeWarningAcceptButton')}

Loading…
Cancel
Save