diff --git a/app/scripts/background.js b/app/scripts/background.js index 55a8f5a63..ca7c04951 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -32,7 +32,9 @@ import LocalStore from './lib/local-store'; import ReadOnlyNetworkStore from './lib/network-store'; import createStreamSink from './lib/createStreamSink'; import NotificationManager from './lib/notification-manager'; -import MetamaskController from './metamask-controller'; +import MetamaskController, { + METAMASK_CONTROLLER_EVENTS, +} from './metamask-controller'; import rawFirstTimeState from './first-time-state'; import getFirstPreferredLangCode from './lib/get-first-preferred-lang-code'; import getObjStructure from './lib/getObjStructure'; @@ -396,14 +398,35 @@ function setupController(initState, initLangCode) { // updateBadge(); - controller.txController.on('update:badge', updateBadge); - controller.messageManager.on('updateBadge', updateBadge); - controller.personalMessageManager.on('updateBadge', updateBadge); - controller.decryptMessageManager.on('updateBadge', updateBadge); - controller.encryptionPublicKeyManager.on('updateBadge', updateBadge); - controller.typedMessageManager.on('updateBadge', updateBadge); + controller.txController.on( + METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, + updateBadge, + ); + controller.messageManager.on( + METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, + updateBadge, + ); + controller.personalMessageManager.on( + METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, + updateBadge, + ); + controller.decryptMessageManager.on( + METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, + updateBadge, + ); + controller.encryptionPublicKeyManager.on( + METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, + updateBadge, + ); + controller.typedMessageManager.on( + METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, + updateBadge, + ); controller.approvalController.subscribe(updateBadge); - controller.appStateController.on('updateBadge', updateBadge); + controller.appStateController.on( + METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, + updateBadge, + ); /** * Updates the Web Extension's "badge" number, on the little fox in the toolbar. diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 0bf791821..73173bf68 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -1,5 +1,6 @@ import EventEmitter from 'events'; import { ObservableStore } from '@metamask/obs-store'; +import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller'; export default class AppStateController extends EventEmitter { /** @@ -74,7 +75,7 @@ export default class AppStateController extends EventEmitter { */ waitForUnlock(resolve, shouldShowUnlockRequest) { this.waitingForUnlock.push({ resolve }); - this.emit('updateBadge'); + this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE); if (shouldShowUnlockRequest) { this._showUnlockRequest(); } @@ -88,7 +89,7 @@ export default class AppStateController extends EventEmitter { while (this.waitingForUnlock.length > 0) { this.waitingForUnlock.shift().resolve(); } - this.emit('updateBadge'); + this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE); } } diff --git a/app/scripts/controllers/incoming-transactions.js b/app/scripts/controllers/incoming-transactions.js index 481810ec1..471a41872 100644 --- a/app/scripts/controllers/incoming-transactions.js +++ b/app/scripts/controllers/incoming-transactions.js @@ -23,6 +23,7 @@ import { ROPSTEN, ROPSTEN_CHAIN_ID, } from '../../../shared/constants/network'; +import { NETWORK_EVENTS } from './network'; const fetchWithTimeout = getFetchWithTimeout(30000); @@ -111,7 +112,7 @@ export default class IncomingTransactionsController { }), ); - this.networkController.on('networkDidChange', async () => { + this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, async () => { const address = this.preferencesController.getSelectedAddress(); await this._update({ address, diff --git a/app/scripts/controllers/network/index.js b/app/scripts/controllers/network/index.js index a0a43faa5..fa027c5ab 100644 --- a/app/scripts/controllers/network/index.js +++ b/app/scripts/controllers/network/index.js @@ -1 +1 @@ -export { default } from './network'; +export { default, NETWORK_EVENTS } from './network'; diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 605b0063a..7315e060d 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -47,6 +47,13 @@ const defaultProviderConfig = { ...defaultProviderConfigOpts, }; +export const NETWORK_EVENTS = { + // Fired after the actively selected network is changed + NETWORK_DID_CHANGE: 'networkDidChange', + // Fired when the actively selected network *will* change + NETWORK_WILL_CHANGE: 'networkWillChange', +}; + export default class NetworkController extends EventEmitter { constructor(opts = {}) { super(); @@ -73,7 +80,7 @@ export default class NetworkController extends EventEmitter { this._providerProxy = null; this._blockTrackerProxy = null; - this.on('networkDidChange', this.lookupNetwork); + this.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, this.lookupNetwork); } /** @@ -229,9 +236,10 @@ export default class NetworkController extends EventEmitter { // _switchNetwork(opts) { + this.emit(NETWORK_EVENTS.NETWORK_WILL_CHANGE); this.setNetworkState('loading'); this._configureProvider(opts); - this.emit('networkDidChange', opts.type); + this.emit(NETWORK_EVENTS.NETWORK_DID_CHANGE, opts.type); } _configureProvider({ type, rpcUrl, chainId }) { diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index 992af5578..d82ac305f 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -19,6 +19,7 @@ import { fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness, fetchSwapsQuoteRefreshTime as defaultFetchSwapsQuoteRefreshTime, } from '../../../ui/app/pages/swaps/swaps.util'; +import { NETWORK_EVENTS } from './network'; const METASWAP_ADDRESS = '0x881d40237659c251811cec9c364ef91dc08d300c'; @@ -103,7 +104,7 @@ export default class SwapsController { this.ethersProvider = new ethers.providers.Web3Provider(provider); this._currentNetwork = networkController.store.getState().network; - networkController.on('networkDidChange', (network) => { + networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, (network) => { if (network !== 'loading' && network !== this._currentNetwork) { this._currentNetwork = network; this.ethersProvider = new ethers.providers.Web3Provider(provider); diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 36864c509..093c3badb 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -23,6 +23,7 @@ import { TRANSACTION_STATUSES, TRANSACTION_TYPES, } from '../../../../shared/constants/transaction'; +import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; import TransactionStateManager from './tx-state-manager'; import TxGasUtil from './tx-gas-utils'; import PendingTransactionTracker from './pending-tx-tracker'; @@ -113,7 +114,9 @@ export default class TransactionController extends EventEmitter { ), }); - this.txStateManager.store.subscribe(() => this.emit('update:badge')); + this.txStateManager.store.subscribe(() => + this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE), + ); this._setupListeners(); // memstore is computed from a few different stores this._updateMemstore(); diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 492ea4dde..ffa49e2bc 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -3,6 +3,7 @@ import { ObservableStore } from '@metamask/obs-store'; import log from 'loglevel'; import createId from '../../lib/random-id'; import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction'; +import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; import { generateHistoryEntry, replayHistory, @@ -474,7 +475,7 @@ export default class TransactionStateManager extends EventEmitter { * @param {TransactionStatuses[keyof TransactionStatuses]} status - the status to set on the txMeta * @emits tx:status-update - passes txId and status * @emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta - * @emits update:badge + * @emits 'updateBadge' */ _setTxStatus(txId, status) { const txMeta = this.getTx(txId); @@ -497,7 +498,7 @@ export default class TransactionStateManager extends EventEmitter { ) { this.emit(`${txMeta.id}:finished`, txMeta); } - this.emit('update:badge'); + this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE); } catch (error) { log.error(error); } diff --git a/app/scripts/lib/decrypt-message-manager.js b/app/scripts/lib/decrypt-message-manager.js index 39f2a0e42..32ae13626 100644 --- a/app/scripts/lib/decrypt-message-manager.js +++ b/app/scripts/lib/decrypt-message-manager.js @@ -4,6 +4,7 @@ import ethUtil from 'ethereumjs-util'; import { ethErrors } from 'eth-rpc-errors'; import log from 'loglevel'; import { MESSAGE_TYPE } from '../../../shared/constants/app'; +import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller'; import { addHexPrefix } from './util'; import createId from './random-id'; @@ -253,6 +254,14 @@ export default class DecryptMessageManager extends EventEmitter { this._setMsgStatus(msgId, 'errored'); } + /** + * Clears all unapproved messages from memory. + */ + clearUnapproved() { + this.messages = this.messages.filter((msg) => msg.status !== 'unapproved'); + this._saveMsgList(); + } + /** * Updates the status of a DecryptMessage in this.messages via a call to this._updateMsg * @@ -316,7 +325,7 @@ export default class DecryptMessageManager extends EventEmitter { unapprovedDecryptMsgs, unapprovedDecryptMsgCount, }); - this.emit('updateBadge'); + this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE); } /** diff --git a/app/scripts/lib/encryption-public-key-manager.js b/app/scripts/lib/encryption-public-key-manager.js index 6fc213b19..e29723b5b 100644 --- a/app/scripts/lib/encryption-public-key-manager.js +++ b/app/scripts/lib/encryption-public-key-manager.js @@ -3,6 +3,7 @@ import { ObservableStore } from '@metamask/obs-store'; import { ethErrors } from 'eth-rpc-errors'; import log from 'loglevel'; import { MESSAGE_TYPE } from '../../../shared/constants/app'; +import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller'; import createId from './random-id'; /** @@ -242,6 +243,14 @@ export default class EncryptionPublicKeyManager extends EventEmitter { this._setMsgStatus(msgId, 'errored'); } + /** + * Clears all unapproved messages from memory. + */ + clearUnapproved() { + this.messages = this.messages.filter((msg) => msg.status !== 'unapproved'); + this._saveMsgList(); + } + /** * Updates the status of a EncryptionPublicKey in this.messages via a call to this._updateMsg * @@ -303,6 +312,6 @@ export default class EncryptionPublicKeyManager extends EventEmitter { unapprovedEncryptionPublicKeyMsgs, unapprovedEncryptionPublicKeyMsgCount, }); - this.emit('updateBadge'); + this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE); } } diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index 796971208..7f4218f8b 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -3,6 +3,7 @@ import { ObservableStore } from '@metamask/obs-store'; import ethUtil from 'ethereumjs-util'; import { ethErrors } from 'eth-rpc-errors'; import { MESSAGE_TYPE } from '../../../shared/constants/app'; +import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller'; import createId from './random-id'; /** @@ -220,6 +221,14 @@ export default class MessageManager extends EventEmitter { this._setMsgStatus(msgId, 'rejected'); } + /** + * Clears all unapproved messages from memory. + */ + clearUnapproved() { + this.messages = this.messages.filter((msg) => msg.status !== 'unapproved'); + this._saveMsgList(); + } + /** * Updates the status of a Message in this.messages via a call to this._updateMsg * @@ -272,7 +281,7 @@ export default class MessageManager extends EventEmitter { const unapprovedMsgs = this.getUnapprovedMsgs(); const unapprovedMsgCount = Object.keys(unapprovedMsgs).length; this.memStore.updateState({ unapprovedMsgs, unapprovedMsgCount }); - this.emit('updateBadge'); + this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE); } } diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 66474c8c4..1fb59144b 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -4,6 +4,7 @@ import ethUtil from 'ethereumjs-util'; import { ethErrors } from 'eth-rpc-errors'; import log from 'loglevel'; import { MESSAGE_TYPE } from '../../../shared/constants/app'; +import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller'; import { addHexPrefix } from './util'; import createId from './random-id'; @@ -241,6 +242,14 @@ export default class PersonalMessageManager extends EventEmitter { this._setMsgStatus(msgId, 'rejected'); } + /** + * Clears all unapproved messages from memory. + */ + clearUnapproved() { + this.messages = this.messages.filter((msg) => msg.status !== 'unapproved'); + this._saveMsgList(); + } + /** * Updates the status of a PersonalMessage in this.messages via a call to this._updateMsg * @@ -301,7 +310,7 @@ export default class PersonalMessageManager extends EventEmitter { unapprovedPersonalMsgs, unapprovedPersonalMsgCount, }); - this.emit('updateBadge'); + this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE); } /** diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 630e6029c..d1ea6491b 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -7,6 +7,7 @@ import { isValidAddress } from 'ethereumjs-util'; import log from 'loglevel'; import jsonschema from 'jsonschema'; import { MESSAGE_TYPE } from '../../../shared/constants/app'; +import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller'; import createId from './random-id'; /** @@ -313,6 +314,14 @@ export default class TypedMessageManager extends EventEmitter { this._setMsgStatus(msgId, 'errored'); } + /** + * Clears all unapproved messages from memory. + */ + clearUnapproved() { + this.messages = this.messages.filter((msg) => msg.status !== 'unapproved'); + this._saveMsgList(); + } + // // PRIVATE METHODS // @@ -377,6 +386,6 @@ export default class TypedMessageManager extends EventEmitter { unapprovedTypedMessages, unapprovedTypedMessagesCount, }); - this.emit('updateBadge'); + this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE); } } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2eff30355..93404fd39 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -35,7 +35,7 @@ import createTabIdMiddleware from './lib/createTabIdMiddleware'; import createOnboardingMiddleware from './lib/createOnboardingMiddleware'; import { setupMultiplex } from './lib/stream-utils'; import EnsController from './controllers/ens'; -import NetworkController from './controllers/network'; +import NetworkController, { NETWORK_EVENTS } from './controllers/network'; import PreferencesController from './controllers/preferences'; import AppStateController from './controllers/app-state'; import CachedBalancesController from './controllers/cached-balances'; @@ -61,6 +61,12 @@ import seedPhraseVerifier from './lib/seed-phrase-verifier'; import MetaMetricsController from './controllers/metametrics'; import { segment, segmentLegacy } from './lib/segment'; +export const METAMASK_CONTROLLER_EVENTS = { + // Fired after state changes that impact the extension badge (unapproved msg count) + // The process of updating the badge happens in app/scripts/background.js. + UPDATE_BADGE: 'updateBadge', +}; + export default class MetamaskController extends EventEmitter { /** * @constructor @@ -127,7 +133,7 @@ export default class MetamaskController extends EventEmitter { preferencesStore: this.preferencesController.store, onNetworkDidChange: this.networkController.on.bind( this.networkController, - 'networkDidChange', + NETWORK_EVENTS.NETWORK_DID_CHANGE, ), getNetworkIdentifier: this.networkController.getNetworkIdentifier.bind( this.networkController, @@ -214,11 +220,6 @@ export default class MetamaskController extends EventEmitter { preferencesController: this.preferencesController, }); - // ensure accountTracker updates balances after network change - this.networkController.on('networkDidChange', () => { - this.accountTracker._updateAccounts(); - }); - const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]; this.keyringController = new KeyringController({ keyringTypes: additionalKeyrings, @@ -323,7 +324,7 @@ export default class MetamaskController extends EventEmitter { } }); - this.networkController.on('networkDidChange', () => { + this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => { this.setCurrentCurrency( this.currencyRateController.state.currentCurrency, (error) => { @@ -357,6 +358,21 @@ export default class MetamaskController extends EventEmitter { tokenRatesStore: this.tokenRatesController.store, }); + // ensure accountTracker updates balances after network change + this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => { + this.accountTracker._updateAccounts(); + }); + + // clear unapproved transactions and messages when the network will change + this.networkController.on(NETWORK_EVENTS.NETWORK_WILL_CHANGE, () => { + this.txController.txStateManager.clearUnapprovedTxs(); + this.encryptionPublicKeyManager.clearUnapproved(); + this.personalMessageManager.clearUnapproved(); + this.typedMessageManager.clearUnapproved(); + this.decryptMessageManager.clearUnapproved(); + this.messageManager.clearUnapproved(); + }); + // ensure isClientOpenAndUnlocked is updated when memState updates this.on('update', (memState) => this._onStateUpdate(memState)); diff --git a/test/unit/app/controllers/incoming-transactions-test.js b/test/unit/app/controllers/incoming-transactions-test.js index 86e629609..d0f3d3e3b 100644 --- a/test/unit/app/controllers/incoming-transactions-test.js +++ b/test/unit/app/controllers/incoming-transactions-test.js @@ -19,6 +19,7 @@ import { TRANSACTION_CATEGORIES, TRANSACTION_STATUSES, } from '../../../../shared/constants/transaction'; +import { NETWORK_EVENTS } from '../../../../app/scripts/controllers/network'; const IncomingTransactionsController = proxyquire( '../../../../app/scripts/controllers/incoming-transactions', @@ -161,7 +162,7 @@ describe('IncomingTransactionsController', function () { assert(incomingTransactionsController.networkController.on.calledOnce); assert.equal( incomingTransactionsController.networkController.on.getCall(0).args[0], - 'networkDidChange', + NETWORK_EVENTS.NETWORK_DID_CHANGE, ); const networkControllerListenerCallback = incomingTransactionsController.networkController.on.getCall( 0, diff --git a/test/unit/app/controllers/metametrics-test.js b/test/unit/app/controllers/metametrics-test.js index 8f2de0c02..839b323c4 100644 --- a/test/unit/app/controllers/metametrics-test.js +++ b/test/unit/app/controllers/metametrics-test.js @@ -8,6 +8,7 @@ import { METAMETRICS_BACKGROUND_PAGE_OBJECT, } from '../../../../shared/constants/metametrics'; import waitUntilCalled from '../../../lib/wait-until-called'; +import { NETWORK_EVENTS } from '../../../../app/scripts/controllers/network'; const segment = createSegmentMock(2, 10000); const segmentLegacy = createSegmentMock(2, 10000); @@ -49,7 +50,7 @@ function getMockNetworkController( provider = { type: NETWORK }, ) { let networkStore = { chainId, provider }; - const on = sinon.stub().withArgs('networkDidChange'); + const on = sinon.stub().withArgs(NETWORK_EVENTS.NETWORK_DID_CHANGE); const updateState = (newState) => { networkStore = { ...networkStore, ...newState }; on.getCall(0).args[1](); @@ -99,7 +100,7 @@ function getMetaMetricsController({ ), onNetworkDidChange: networkController.on.bind( networkController, - 'networkDidChange', + NETWORK_EVENTS.NETWORK_DID_CHANGE, ), preferencesStore, version: '0.0.1', diff --git a/test/unit/app/controllers/swaps-test.js b/test/unit/app/controllers/swaps-test.js index 5bd1c8529..299f99229 100644 --- a/test/unit/app/controllers/swaps-test.js +++ b/test/unit/app/controllers/swaps-test.js @@ -14,6 +14,7 @@ import { createTestProviderTools } from '../../../stub/provider'; import SwapsController, { utils, } from '../../../../app/scripts/controllers/swaps'; +import { NETWORK_EVENTS } from '../../../../app/scripts/controllers/network'; const MOCK_FETCH_PARAMS = { slippage: 3, @@ -101,7 +102,10 @@ function getMockNetworkController() { }; }, }, - on: sinon.stub().withArgs('networkDidChange').callsArgAsync(1), + on: sinon + .stub() + .withArgs(NETWORK_EVENTS.NETWORK_DID_CHANGE) + .callsArgAsync(1), }; } diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index ca7bfede6..ed84b3bcc 100644 --- a/test/unit/app/controllers/transactions/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -15,6 +15,7 @@ import { TRANSACTION_STATUSES, TRANSACTION_TYPES, } from '../../../../../shared/constants/transaction'; +import { METAMASK_CONTROLLER_EVENTS } from '../../../../../app/scripts/metamask-controller'; const noop = () => true; const currentNetworkId = '42'; @@ -395,7 +396,10 @@ describe('Transaction Controller', function () { txParams: {}, }; - const eventNames = ['update:badge', '1:unapproved']; + const eventNames = [ + METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, + '1:unapproved', + ]; const listeners = []; eventNames.forEach((eventName) => { listeners.push(