From 9b0a6ecc90abc1fc024176becc7f7ee6db59e623 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 30 Mar 2023 12:39:36 -0600 Subject: [PATCH] NetworkController: Use messenger for events (#18041) Currently, the network controller notifies consumers about events by emitting them directly from the controller. In order to migrate the controller to the core repo, where controllers use the BaseControllerV2 interface, events should be emitted via a messenger object. This commit updates the network controller to use a messenger, and then updates all of the controllers that listen for network events to use the messenger as well. --- app/scripts/controllers/detect-tokens.test.js | 35 +- app/scripts/controllers/metametrics.test.js | 13 +- app/scripts/controllers/network/index.js | 2 +- .../controllers/network/network-controller.js | 81 ++- .../network/network-controller.test.js | 495 ++++++++++++------ app/scripts/controllers/preferences.js | 9 +- app/scripts/controllers/preferences.test.js | 4 + app/scripts/controllers/swaps.js | 4 +- app/scripts/controllers/swaps.test.js | 24 +- app/scripts/metamask-controller.js | 94 ++-- 10 files changed, 508 insertions(+), 253 deletions(-) diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js index 8a3685f22..cd8040a68 100644 --- a/app/scripts/controllers/detect-tokens.test.js +++ b/app/scripts/controllers/detect-tokens.test.js @@ -13,7 +13,7 @@ import { convertHexToDecimal } from '@metamask/controller-utils'; import { NETWORK_TYPES } from '../../../shared/constants/network'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; import DetectTokensController from './detect-tokens'; -import NetworkController, { NETWORK_EVENTS } from './network'; +import NetworkController, { NetworkControllerEventTypes } from './network'; import PreferencesController from './preferences'; describe('DetectTokensController', function () { @@ -34,7 +34,11 @@ describe('DetectTokensController', function () { beforeEach(async function () { keyringMemStore = new ObservableStore({ isUnlocked: false }); - network = new NetworkController({ infuraProjectId: 'foo' }); + const networkControllerMessenger = new ControllerMessenger(); + network = new NetworkController({ + messenger: networkControllerMessenger, + infuraProjectId: 'foo', + }); network.initializeProvider(networkControllerProviderConfig); provider = network.getProviderAndBlockTracker().provider; @@ -54,6 +58,8 @@ describe('DetectTokensController', function () { network, provider, tokenListController, + onInfuraIsBlocked: sinon.stub(), + onInfuraIsUnblocked: sinon.stub(), }); preferences.setAddresses([ '0x7e57e2', @@ -82,17 +88,20 @@ describe('DetectTokensController', function () { preferences.store, ), onNetworkStateChange: (cb) => - network.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => { - const networkState = network.store.getState(); - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.provider, - chainId: convertHexToDecimal(networkState.provider.chainId), - }, - }; - return cb(modifiedNetworkState); - }), + networkControllerMessenger.subscribe( + NetworkControllerEventTypes.NetworkDidChange, + () => { + const networkState = network.store.getState(); + const modifiedNetworkState = { + ...networkState, + providerConfig: { + ...networkState.provider, + chainId: convertHexToDecimal(networkState.provider.chainId), + }, + }; + return cb(modifiedNetworkState); + }, + ), }); sandbox diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index f3969a7b3..18535c1d7 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -15,7 +15,6 @@ import { } from '../../../shared/constants/network'; import * as Utils from '../lib/util'; import MetaMetricsController from './metametrics'; -import { NETWORK_EVENTS } from './network'; const segment = createSegmentMock(2, 10000); @@ -72,17 +71,17 @@ function getMockNetworkController() { }, network: 'loading', }; - const on = sinon.stub().withArgs(NETWORK_EVENTS.NETWORK_DID_CHANGE); + const onNetworkDidChange = sinon.stub(); const updateState = (newState) => { state = { ...state, ...newState }; - on.getCall(0).args[1](); + onNetworkDidChange.getCall(0).args[0](); }; return { store: { getState: () => state, updateState, }, - on, + onNetworkDidChange, }; } @@ -136,10 +135,8 @@ function getMetaMetricsController({ segment: segmentInstance || segment, getCurrentChainId: () => networkController.store.getState().provider.chainId, - onNetworkDidChange: networkController.on.bind( - networkController, - NETWORK_EVENTS.NETWORK_DID_CHANGE, - ), + onNetworkDidChange: + networkController.onNetworkDidChange.bind(networkController), preferencesStore, version: '0.0.1', environment: 'test', diff --git a/app/scripts/controllers/network/index.js b/app/scripts/controllers/network/index.js index dbb003ff1..b91e16698 100644 --- a/app/scripts/controllers/network/index.js +++ b/app/scripts/controllers/network/index.js @@ -1 +1 @@ -export { default, NETWORK_EVENTS } from './network-controller'; +export { default, NetworkControllerEventTypes } from './network-controller'; diff --git a/app/scripts/controllers/network/network-controller.js b/app/scripts/controllers/network/network-controller.js index 06d236e26..0fa749d36 100644 --- a/app/scripts/controllers/network/network-controller.js +++ b/app/scripts/controllers/network/network-controller.js @@ -7,6 +7,9 @@ import { createEventEmitterProxy, } from 'swappable-obj-proxy'; import EthQuery from 'eth-query'; +// ControllerMessenger is referred to in the JSDocs +// eslint-disable-next-line no-unused-vars +import { ControllerMessenger } from '@metamask/base-controller'; import { v4 as random } from 'uuid'; import { INFURA_PROVIDER_TYPES, @@ -36,6 +39,8 @@ import { createNetworkClient } from './create-network-client'; const env = process.env.METAMASK_ENV; const fetchWithTimeout = getFetchWithTimeout(); +const name = 'NetworkController'; + let defaultProviderConfigOpts; if (process.env.IN_TEST) { defaultProviderConfigOpts = { @@ -66,15 +71,30 @@ const defaultNetworkDetailsState = { EIPS: { 1559: undefined }, }; -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', - // Fired when Infura returns an error indicating no support - INFURA_IS_BLOCKED: 'infuraIsBlocked', - // Fired when not using an Infura network or when Infura returns no error, indicating support - INFURA_IS_UNBLOCKED: 'infuraIsUnblocked', +/** + * The set of event types that this controller can publish via its messenger. + */ +export const NetworkControllerEventTypes = { + /** + * Fired after the current network is changed. + */ + NetworkDidChange: `${name}:networkDidChange`, + /** + * Fired when there is a request to change the current network, but no state + * changes have occurred yet. + */ + NetworkWillChange: `${name}:networkWillChange`, + /** + * Fired after the network is changed to an Infura network, but when Infura + * returns an error denying support for the user's location. + */ + InfuraIsBlocked: `${name}:infuraIsBlocked`, + /** + * Fired after the network is changed to an Infura network and Infura does not + * return an error denying support for the user's location, or after the + * network is changed to a custom network. + */ + InfuraIsUnblocked: `${name}:infuraIsUnblocked`, }; export default class NetworkController extends EventEmitter { @@ -83,14 +103,22 @@ export default class NetworkController extends EventEmitter { /** * Construct a NetworkController. * - * @param {object} [options] - NetworkController options. + * @param {object} options - Options for this controller. + * @param {ControllerMessenger} options.messenger - The controller messenger. * @param {object} [options.state] - Initial controller state. * @param {string} [options.infuraProjectId] - The Infura project ID. * @param {string} [options.trackMetaMetricsEvent] - A method to forward events to the MetaMetricsController */ - constructor({ state = {}, infuraProjectId, trackMetaMetricsEvent } = {}) { + constructor({ + messenger, + state = {}, + infuraProjectId, + trackMetaMetricsEvent, + } = {}) { super(); + this.messenger = messenger; + // create stores this.providerStore = new ObservableStore( state.provider || { ...defaultProviderConfig }, @@ -134,11 +162,8 @@ export default class NetworkController extends EventEmitter { throw new Error('Invalid Infura project ID'); } this._infuraProjectId = infuraProjectId; - this._trackMetaMetricsEvent = trackMetaMetricsEvent; - this.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => { - this.lookupNetwork(); - }); + this._trackMetaMetricsEvent = trackMetaMetricsEvent; } /** @@ -210,7 +235,7 @@ export default class NetworkController extends EventEmitter { if (isInfura) { this._checkInfuraAvailability(type); } else { - this.emit(NETWORK_EVENTS.INFURA_IS_UNBLOCKED); + this.messenger.publish(NetworkControllerEventTypes.InfuraIsUnblocked); } let networkVersion; @@ -373,9 +398,17 @@ export default class NetworkController extends EventEmitter { const rpcUrl = `https://${network}.infura.io/v3/${this._infuraProjectId}`; let networkChanged = false; - this.once(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => { + const listener = () => { networkChanged = true; - }); + this.messenger.unsubscribe( + NetworkControllerEventTypes.NetworkDidChange, + listener, + ); + }; + this.messenger.subscribe( + NetworkControllerEventTypes.NetworkDidChange, + listener, + ); try { const response = await fetchWithTimeout(rpcUrl, { @@ -393,14 +426,14 @@ export default class NetworkController extends EventEmitter { } if (response.ok) { - this.emit(NETWORK_EVENTS.INFURA_IS_UNBLOCKED); + this.messenger.publish(NetworkControllerEventTypes.InfuraIsUnblocked); } else { const responseMessage = await response.json(); if (networkChanged) { return; } if (responseMessage.error === INFURA_BLOCKED_KEY) { - this.emit(NETWORK_EVENTS.INFURA_IS_BLOCKED); + this.messenger.publish(NetworkControllerEventTypes.InfuraIsBlocked); } } } catch (err) { @@ -410,7 +443,7 @@ export default class NetworkController extends EventEmitter { _switchNetwork(opts) { // Indicate to subscribers that network is about to change - this.emit(NETWORK_EVENTS.NETWORK_WILL_CHANGE); + this.messenger.publish(NetworkControllerEventTypes.NetworkWillChange); // Set loading state this._setNetworkState('loading'); // Reset network details @@ -418,7 +451,11 @@ export default class NetworkController extends EventEmitter { // Configure the provider appropriately this._configureProvider(opts); // Notify subscribers that network has changed - this.emit(NETWORK_EVENTS.NETWORK_DID_CHANGE, opts.type); + this.messenger.publish( + NetworkControllerEventTypes.NetworkDidChange, + opts.type, + ); + this.lookupNetwork(); } _configureProvider({ type, rpcUrl, chainId }) { diff --git a/app/scripts/controllers/network/network-controller.test.js b/app/scripts/controllers/network/network-controller.test.js index cd4e9a782..ce160a65a 100644 --- a/app/scripts/controllers/network/network-controller.test.js +++ b/app/scripts/controllers/network/network-controller.test.js @@ -4,6 +4,7 @@ import { v4 } from 'uuid'; import nock from 'nock'; import sinon from 'sinon'; import * as ethJsonRpcProvider from '@metamask/eth-json-rpc-provider'; +import { ControllerMessenger } from '@metamask/base-controller'; import { BUILT_IN_NETWORKS } from '../../../../shared/constants/network'; import { EVENT } from '../../../../shared/constants/metametrics'; import NetworkController from './network-controller'; @@ -111,16 +112,6 @@ const BLOCK = POST_1559_BLOCK; */ const DEFAULT_INFURA_PROJECT_ID = 'fake-infura-project-id'; -/** - * Despite the signature of its constructor, NetworkController must take an - * Infura project ID. This object is mixed into the options first when a - * NetworkController is instantiated in tests. - */ -const DEFAULT_CONTROLLER_OPTIONS = { - infuraProjectId: DEFAULT_INFURA_PROJECT_ID, - trackMetaMetricsEvent: jest.fn(), -}; - /** * The set of properties allowed in a valid JSON-RPC response object. */ @@ -536,7 +527,9 @@ describe('NetworkController', () => { describe('destroy', () => { it('does not throw if called before the provider is initialized', async () => { - const controller = new NetworkController(DEFAULT_CONTROLLER_OPTIONS); + const controller = new NetworkController( + buildDefaultNetworkControllerOptions(), + ); expect(await controller.destroy()).toBeUndefined(); }); @@ -619,8 +612,11 @@ describe('NetworkController', () => { }); it('emits infuraIsUnblocked (assuming that the request to eth_blockNumber responds successfully)', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -633,15 +629,15 @@ describe('NetworkController', () => { async ({ controller, network }) => { network.mockEssentialRpcCalls(); - const infuraIsUnblocked = await waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: async () => { await controller.initializeProvider(); }, }); - expect(infuraIsUnblocked).toBe(true); + expect(infuraIsUnblocked).toBeTruthy(); }, ); }); @@ -755,8 +751,11 @@ describe('NetworkController', () => { }); it('emits infuraIsUnblocked', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: 'rpc', @@ -778,15 +777,15 @@ describe('NetworkController', () => { async ({ controller, network }) => { network.mockEssentialRpcCalls(); - const infuraIsUnblocked = await waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: async () => { await controller.initializeProvider(); }, }); - expect(infuraIsUnblocked).toBe(true); + expect(infuraIsUnblocked).toBeTruthy(); }, ); }); @@ -1161,12 +1160,14 @@ describe('NetworkController', () => { }); it('does not emit infuraIsUnblocked', async () => { - await withController(async ({ controller, network }) => { + const messenger = buildMessenger(); + + await withController({ messenger }, async ({ controller, network }) => { network.mockEssentialRpcCalls(); - const promiseForInfuraIsUnblocked = waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const promiseForInfuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: async () => { await controller.lookupNetwork(); }, @@ -1177,12 +1178,14 @@ describe('NetworkController', () => { }); it('does not emit infuraIsBlocked', async () => { - await withController(async ({ controller, network }) => { + const messenger = buildMessenger(); + + await withController({ messenger }, async ({ controller, network }) => { network.mockEssentialRpcCalls(); - const promiseForInfuraIsBlocked = waitForEvent({ - controller, - eventName: 'infuraIsBlocked', + const promiseForInfuraIsBlocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', operation: async () => { await controller.lookupNetwork(); }, @@ -1197,8 +1200,11 @@ describe('NetworkController', () => { describe(`when the type in the provider configuration is "${networkType}"`, () => { describe('if the request for eth_blockNumber responds successfully', () => { it('emits infuraIsUnblocked as long as the network has not changed by the time the request ends', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -1223,15 +1229,15 @@ describe('NetworkController', () => { }, }); - const infuraIsUnblocked = await waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: async () => { await controller.lookupNetwork(); }, }); - expect(infuraIsUnblocked).toBe(true); + expect(infuraIsUnblocked).toBeTruthy(); }, ); }); @@ -1247,8 +1253,11 @@ describe('NetworkController', () => { ); } + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -1265,9 +1274,9 @@ describe('NetworkController', () => { result: '0x42', }, beforeCompleting: async () => { - await waitForEvent({ - controller, - eventName: 'networkDidChange', + await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', operation: async () => { await withoutCallingLookupNetwork({ controller, @@ -1295,9 +1304,9 @@ describe('NetworkController', () => { }, }); - const promiseForInfuraIsUnblocked = waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const promiseForInfuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: async () => { await controller.lookupNetwork(); }, @@ -1311,8 +1320,11 @@ describe('NetworkController', () => { describe('if the request for eth_blockNumber responds with a "countryBlocked" error', () => { it('emits infuraIsBlocked as long as the network has not changed by the time the request ends', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -1338,22 +1350,25 @@ describe('NetworkController', () => { }, }); - const infuraIsBlocked = await waitForEvent({ - controller, - eventName: 'infuraIsBlocked', + const infuraIsBlocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', operation: async () => { await controller.lookupNetwork(); }, }); - expect(infuraIsBlocked).toBe(true); + expect(infuraIsBlocked).toBeTruthy(); }, ); }); it('does not emit infuraIsBlocked if the network has changed by the time the request ends', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -1382,9 +1397,9 @@ describe('NetworkController', () => { await withoutCallingLookupNetwork({ controller, operation: async () => { - await waitForEvent({ - controller, - eventName: 'networkDidChange', + await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', operation: () => { controller.setActiveNetwork( 'testNetworkConfigurationId', @@ -1399,7 +1414,7 @@ describe('NetworkController', () => { const network2 = new NetworkCommunications({ networkClientType: 'rpc', networkClientOptions: { - customRpcUrl: 'http://some-rpc-url', + customRpcUrl: 'https://mock-rpc-url', }, }); network2.mockEssentialRpcCalls(); @@ -1410,9 +1425,9 @@ describe('NetworkController', () => { }, }); - const promiseForInfuraIsBlocked = waitForEvent({ - controller, - eventName: 'infuraIsBlocked', + const promiseForInfuraIsBlocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', operation: async () => { await controller.lookupNetwork(); }, @@ -1426,8 +1441,11 @@ describe('NetworkController', () => { describe('if the request for eth_blockNumber responds with a generic error', () => { it('does not emit infuraIsUnblocked', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -1453,9 +1471,9 @@ describe('NetworkController', () => { }, }); - const promiseForInfuraIsUnblocked = waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const promiseForInfuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: async () => { await controller.lookupNetwork(); }, @@ -1906,8 +1924,11 @@ describe('NetworkController', () => { describe(`when the type in the provider configuration is "rpc"`, () => { it('emits infuraIsUnblocked', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: 'rpc', @@ -1935,15 +1956,15 @@ describe('NetworkController', () => { }, }); - const infuraIsUnblocked = await waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: async () => { await controller.lookupNetwork(); }, }); - expect(infuraIsUnblocked).toBe(true); + expect(infuraIsUnblocked).toBeTruthy(); }, ); }); @@ -2646,8 +2667,11 @@ describe('NetworkController', () => { }); it('emits networkWillChange before making any changes to the network store', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: 'rpc', @@ -2702,9 +2726,9 @@ describe('NetworkController', () => { const initialNetwork = controller.store.getState().network; expect(initialNetwork).toBe('42'); - const networkWillChange = await waitForEvent({ - controller, - eventName: 'networkWillChange', + const networkWillChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkWillChange', operation: () => { controller.setActiveNetwork('testNetworkConfigurationId2'); }, @@ -2712,7 +2736,7 @@ describe('NetworkController', () => { expect(controller.store.getState().network).toBe(initialNetwork); }, }); - expect(networkWillChange).toBe(true); + expect(networkWillChange).toBeTruthy(); }, ); }); @@ -2927,8 +2951,11 @@ describe('NetworkController', () => { }); it('emits networkDidChange', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { networkConfigurations: { testNetworkConfigurationId: { @@ -2949,22 +2976,25 @@ describe('NetworkController', () => { }); network.mockEssentialRpcCalls(); - const networkDidChange = await waitForEvent({ - controller, - eventName: 'networkDidChange', + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', operation: () => { controller.setActiveNetwork('testNetworkConfigurationId'); }, }); - expect(networkDidChange).toBe(true); + expect(networkDidChange).toBeTruthy(); }, ); }); it('emits infuraIsUnblocked', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { networkConfigurations: { testNetworkConfigurationId: { @@ -2985,15 +3015,15 @@ describe('NetworkController', () => { }); network.mockEssentialRpcCalls(); - const infuraIsUnblocked = await waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: () => { controller.setActiveNetwork('testNetworkConfigurationId'); }, }); - expect(infuraIsUnblocked).toBe(true); + expect(infuraIsUnblocked).toBeTruthy(); }, ); }); @@ -3224,7 +3254,9 @@ describe('NetworkController', () => { }); it('emits networkWillChange', async () => { - await withController(async ({ controller }) => { + const messenger = buildMessenger(); + + await withController({ messenger }, async ({ controller }) => { const network = new NetworkCommunications({ networkClientType: 'infura', networkClientOptions: { @@ -3233,15 +3265,15 @@ describe('NetworkController', () => { }); network.mockEssentialRpcCalls(); - const networkWillChange = await waitForEvent({ - controller, - eventName: 'networkWillChange', + const networkWillChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkWillChange', operation: () => { controller.setProviderType(networkType); }, }); - expect(networkWillChange).toBe(true); + expect(networkWillChange).toBeTruthy(); }); }); @@ -3406,7 +3438,9 @@ describe('NetworkController', () => { }); it('emits networkDidChange', async () => { - await withController(async ({ controller }) => { + const messenger = buildMessenger(); + + await withController({ messenger }, async ({ controller }) => { const network = new NetworkCommunications({ networkClientType: 'infura', networkClientOptions: { @@ -3415,20 +3449,22 @@ describe('NetworkController', () => { }); network.mockEssentialRpcCalls(); - const networkDidChange = await waitForEvent({ - controller, - eventName: 'networkDidChange', + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', operation: () => { controller.setProviderType(networkType); }, }); - expect(networkDidChange).toBe(true); + expect(networkDidChange).toBeTruthy(); }); }); it('emits infuraIsUnblocked (assuming that the request for eth_blockNumber responds successfully)', async () => { - await withController(async ({ controller }) => { + const messenger = buildMessenger(); + + await withController({ messenger }, async ({ controller }) => { const network = new NetworkCommunications({ networkClientType: 'infura', networkClientOptions: { @@ -3437,15 +3473,15 @@ describe('NetworkController', () => { }); network.mockEssentialRpcCalls(); - const infuraIsUnblocked = await waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', - operation: () => { + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + operation: async () => { controller.setProviderType(networkType); }, }); - expect(infuraIsUnblocked).toBe(true); + expect(infuraIsUnblocked).toBeTruthy(); }); }); @@ -3541,8 +3577,11 @@ describe('NetworkController', () => { } of INFURA_NETWORKS) { describe(`when the type in the provider configuration is "${networkType}"`, () => { it('emits networkWillChange', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -3555,15 +3594,15 @@ describe('NetworkController', () => { async ({ controller, network }) => { network.mockEssentialRpcCalls(); - const networkWillChange = await waitForEvent({ - controller, - eventName: 'networkWillChange', + const networkWillChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkWillChange', operation: () => { controller.resetConnection(); }, }); - expect(networkWillChange).toBe(true); + expect(networkWillChange).toBeTruthy(); }, ); }); @@ -3708,8 +3747,11 @@ describe('NetworkController', () => { }); it('emits networkDidChange', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -3722,22 +3764,25 @@ describe('NetworkController', () => { async ({ controller, network }) => { network.mockEssentialRpcCalls(); - const networkDidChange = await waitForEvent({ - controller, - eventName: 'networkDidChange', + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', operation: () => { controller.resetConnection(); }, }); - expect(networkDidChange).toBe(true); + expect(networkDidChange).toBeTruthy(); }, ); }); it('emits infuraIsUnblocked (assuming that the request for eth_blockNumber responds successfully)', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -3750,15 +3795,15 @@ describe('NetworkController', () => { async ({ controller, network }) => { network.mockEssentialRpcCalls(); - const infuraIsUnblocked = await waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: () => { controller.resetConnection(); }, }); - expect(infuraIsUnblocked).toBe(true); + expect(infuraIsUnblocked).toBeTruthy(); }, ); }); @@ -3825,8 +3870,11 @@ describe('NetworkController', () => { describe(`when the type in the provider configuration is "rpc"`, () => { it('emits networkWillChange', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: 'rpc', @@ -3848,15 +3896,15 @@ describe('NetworkController', () => { async ({ controller, network }) => { network.mockEssentialRpcCalls(); - const networkWillChange = await waitForEvent({ - controller, - eventName: 'networkWillChange', + const networkWillChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkWillChange', operation: () => { controller.resetConnection(); }, }); - expect(networkWillChange).toBe(true); + expect(networkWillChange).toBeTruthy(); }, ); }); @@ -4047,8 +4095,11 @@ describe('NetworkController', () => { }); it('emits networkDidChange', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: 'rpc', @@ -4070,22 +4121,25 @@ describe('NetworkController', () => { async ({ controller, network }) => { network.mockEssentialRpcCalls(); - const networkDidChange = await waitForEvent({ - controller, - eventName: 'networkDidChange', + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', operation: () => { controller.resetConnection(); }, }); - expect(networkDidChange).toBe(true); + expect(networkDidChange).toBeTruthy(); }, ); }); it('emits infuraIsUnblocked', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: 'rpc', @@ -4107,15 +4161,15 @@ describe('NetworkController', () => { async ({ controller, network }) => { network.mockEssentialRpcCalls(); - const infuraIsUnblocked = await waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: () => { controller.resetConnection(); }, }); - expect(infuraIsUnblocked).toBe(true); + expect(infuraIsUnblocked).toBeTruthy(); }, ); }); @@ -4304,8 +4358,11 @@ describe('NetworkController', () => { }); it('emits networkWillChange', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -4347,15 +4404,15 @@ describe('NetworkController', () => { await waitForLookupNetworkToComplete({ controller, operation: async () => { - const networkWillChange = await waitForEvent({ - controller, - eventName: 'networkWillChange', + const networkWillChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkWillChange', operation: () => { controller.rollbackToPreviousProvider(); }, }); - expect(networkWillChange).toBe(true); + expect(networkWillChange).toBeTruthy(); }, }); }, @@ -4624,8 +4681,11 @@ describe('NetworkController', () => { }); it('emits networkDidChange', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -4668,14 +4728,14 @@ describe('NetworkController', () => { await waitForLookupNetworkToComplete({ controller, operation: async () => { - const networkDidChange = await waitForEvent({ - controller, - eventName: 'networkDidChange', + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', operation: () => { controller.rollbackToPreviousProvider(); }, }); - expect(networkDidChange).toBe(true); + expect(networkDidChange).toBeTruthy(); }, }); }, @@ -4683,8 +4743,11 @@ describe('NetworkController', () => { }); it('emits infuraIsUnblocked (assuming that the request for eth_blockNumber responds successfully)', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: networkType, @@ -4727,15 +4790,15 @@ describe('NetworkController', () => { await waitForLookupNetworkToComplete({ controller, operation: async () => { - const infuraIsUnblocked = await waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: () => { controller.rollbackToPreviousProvider(); }, }); - expect(infuraIsUnblocked).toBe(true); + expect(infuraIsUnblocked).toBeTruthy(); }, }); }, @@ -4962,8 +5025,11 @@ describe('NetworkController', () => { }); it('emits networkWillChange', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: 'rpc', @@ -5007,15 +5073,15 @@ describe('NetworkController', () => { await waitForLookupNetworkToComplete({ controller, operation: async () => { - const networkWillChange = await waitForEvent({ - controller, - eventName: 'networkWillChange', + const networkWillChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkWillChange', operation: () => { controller.rollbackToPreviousProvider(); }, }); - expect(networkWillChange).toBe(true); + expect(networkWillChange).toBeTruthy(); }, }); }, @@ -5262,8 +5328,11 @@ describe('NetworkController', () => { }); it('emits networkDidChange', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: 'rpc', @@ -5302,14 +5371,14 @@ describe('NetworkController', () => { await waitForLookupNetworkToComplete({ controller, operation: async () => { - const networkDidChange = await waitForEvent({ - controller, - eventName: 'networkDidChange', + const networkDidChange = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', operation: () => { controller.rollbackToPreviousProvider(); }, }); - expect(networkDidChange).toBe(true); + expect(networkDidChange).toBeTruthy(); }, }); }, @@ -5317,8 +5386,11 @@ describe('NetworkController', () => { }); it('emits infuraIsUnblocked', async () => { + const messenger = buildMessenger(); + await withController( { + messenger, state: { provider: { type: 'rpc', @@ -5357,15 +5429,15 @@ describe('NetworkController', () => { await waitForLookupNetworkToComplete({ controller, operation: async () => { - const infuraIsUnblocked = await waitForEvent({ - controller, - eventName: 'infuraIsUnblocked', + const infuraIsUnblocked = await waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: () => { controller.rollbackToPreviousProvider(); }, }); - expect(infuraIsUnblocked).toBe(true); + expect(infuraIsUnblocked).toBeTruthy(); }, }); }, @@ -6097,6 +6169,40 @@ describe('NetworkController', () => { }); }); +/** + * Builds the controller messenger that NetworkController is designed to work + * with. + * + * @returns The controller messenger. + */ +function buildMessenger() { + return new ControllerMessenger().getRestricted({ + name: 'NetworkController', + allowedActions: [], + allowedEvents: [ + 'NetworkController:networkDidChange', + 'NetworkController:networkWillChange', + 'NetworkController:infuraIsBlocked', + 'NetworkController:infuraIsUnblocked', + ], + }); +} + +/** + * Despite the signature of its constructor, NetworkController must take an + * Infura project ID. The object that this function returns is mixed into the + * options first when a NetworkController is instantiated in tests. + * + * @returns {object} The controller options. + */ +function buildDefaultNetworkControllerOptions() { + return { + messenger: buildMessenger(), + infuraProjectId: DEFAULT_INFURA_PROJECT_ID, + trackMetaMetricsEvent: jest.fn(), + }; +} + /** * Builds a controller based on the given options, and calls the given function * with that controller. @@ -6108,11 +6214,11 @@ describe('NetworkController', () => { * @returns Whatever the callback returns. */ async function withController(...args) { - const [givenConstructorOptions, fn] = + const [givenNetworkControllerOptions, fn] = args.length === 2 ? args : [{}, args[0]]; const constructorOptions = { - ...DEFAULT_CONTROLLER_OPTIONS, - ...givenConstructorOptions, + ...buildDefaultNetworkControllerOptions(), + ...givenNetworkControllerOptions, }; const controller = new NetworkController(constructorOptions); @@ -6323,43 +6429,116 @@ async function waitForStateChanges({ } /** - * Waits for an event to occur on the controller before proceeding. + * Waits for controller events to be emitted before proceeding. * - * @param {{controller: NetworkController, eventName: string, operation: (() => void | Promise), beforeResolving?: (() => void | Promise)}} args - The arguments. - * @param {NetworkController} args.controller - The network controller - * @param {string} args.eventName - The name of the event. - * @param {() => void | Promise} args.operation - A function that will - * presumably produce the event in question. - * @param {() => void | Promise} [args.beforeResolving] - In some tests, - * state updates happen so fast, we need to make an assertion immediately after - * the event in question occurs. However, if we wait until the promise this - * function returns resolves to do so, some other state update to the same + * @param {object} options - An options bag. + * @param {ControllerMessenger} options.messenger - The messenger suited for + * NetworkController. + * @param {string} options.eventType - The type of NetworkController event you + * want to wait for. + * @param {number} options.count - The number of events you expect to occur + * (default: 1). + * @param {(payload: any) => boolean} options.filter - A function used to + * discard events that are not of interest. + * @param {number} options.wait - The amount of time in milliseconds to wait for + * the expected number of filtered events to occur before resolving the promise + * that this function returns (default: 150). + * @param {() => void | Promise} options.operation - A function to run + * that will presumably produce the events in question. + * @param {() => void | Promise} [options.beforeResolving] - In some + * tests, state updates happen so fast, we need to make an assertion immediately + * after the event in question occurs. However, if we wait until the promise + * this function returns resolves to do so, some other state update to the same * property may have happened. This option allows you to make an assertion * _before_ the promise resolves. This has the added benefit of allowing you to - * maintain the "arrange, act, assert" ordering in your test, meaning that - * you can still call the method that kicks off the event and then make the + * maintain the "arrange, act, assert" ordering in your test, meaning that you + * can still call the method that kicks off the event and then make the * assertion afterward instead of the other way around. - * @returns {Promise} + * @returns A promise that resolves to the list of payloads for the set of + * events, optionally filtered, when a specific number of them have occurred. */ -async function waitForEvent({ - controller, - eventName, - operation, +async function waitForPublishedEvents({ + messenger, + eventType, + count: expectedNumberOfEvents = 1, + filter: isEventPayloadInteresting = () => true, + wait: timeBeforeAssumingNoMoreEvents = 150, + operation = () => { + // do nothing + }, beforeResolving = async () => { // do nothing }, }) { - const promise = new Promise((resolve) => { - controller.once(eventName, () => { - Promise.resolve(beforeResolving()).then(() => { - resolve(true); - }); - }); + const promiseForEventPayloads = new Promise((resolve, reject) => { + // We need to declare this variable first, then assign it later, so that + // ESLint won't complain that resetTimer is referring to this variable + // before it's declared. And we need to use let so that we can assign it + // below. + /* eslint-disable-next-line prefer-const */ + let eventListener; + let timer; + const allEventPayloads = []; + const interestingEventPayloads = []; + let alreadyEnded = false; + + const end = () => { + if (!alreadyEnded) { + alreadyEnded = true; + messenger.unsubscribe(eventType.toString(), eventListener); + Promise.resolve(beforeResolving()).then(() => { + if (interestingEventPayloads.length === expectedNumberOfEvents) { + resolve(interestingEventPayloads); + } else { + // Using a string instead of an Error leads to better backtraces. + /* eslint-disable-next-line prefer-promise-reject-errors */ + reject( + `Expected to receive ${expectedNumberOfEvents} ${eventType} event(s), but received ${ + interestingEventPayloads.length + } after ${timeBeforeAssumingNoMoreEvents}ms.\n\nAll payloads:\n\n${inspect( + allEventPayloads, + { depth: null }, + )}`, + ); + } + }); + } + }; + + const stopTimer = () => { + if (timer) { + clearTimeout(timer); + } + }; + + const resetTimer = () => { + stopTimer(); + timer = originalSetTimeout(() => { + end(); + }, timeBeforeAssumingNoMoreEvents); + }; + + eventListener = (...payload) => { + allEventPayloads.push(payload); + + if (isEventPayloadInteresting(payload)) { + interestingEventPayloads.push(payload); + if (interestingEventPayloads.length === expectedNumberOfEvents) { + stopTimer(); + end(); + } else { + resetTimer(); + } + } + }; + + messenger.subscribe(eventType.toString(), eventListener); + resetTimer(); }); await operation(); - return await promise; + return await promiseForEventPayloads; } /** diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index c33e0f12a..101c37258 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -3,7 +3,6 @@ import { normalize as normalizeAddress } from 'eth-sig-util'; import { IPFS_DEFAULT_GATEWAY_URL } from '../../../shared/constants/network'; import { LedgerTransportTypes } from '../../../shared/constants/hardware-wallets'; import { ThemeType } from '../../../shared/constants/preferences'; -import { NETWORK_EVENTS } from './network'; export default class PreferencesController { /** @@ -70,7 +69,8 @@ export default class PreferencesController { ...opts.initState, }; - this.network = opts.network; + this._onInfuraIsBlocked = opts.onInfuraIsBlocked; + this._onInfuraIsUnblocked = opts.onInfuraIsUnblocked; this.store = new ObservableStore(initState); this.store.setMaxListeners(13); this.openPopup = opts.openPopup; @@ -511,10 +511,11 @@ export default class PreferencesController { // _subscribeToInfuraAvailability() { - this.network.on(NETWORK_EVENTS.INFURA_IS_BLOCKED, () => { + this._onInfuraIsBlocked(() => { this._setInfuraBlocked(true); }); - this.network.on(NETWORK_EVENTS.INFURA_IS_UNBLOCKED, () => { + + this._onInfuraIsUnblocked(() => { this._setInfuraBlocked(false); }); } diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index 21267195c..51f2ba5a0 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -19,8 +19,10 @@ describe('preferences controller', function () { const networkControllerProviderConfig = { getAccounts: () => undefined, }; + const networkControllerMessenger = new ControllerMessenger(); network = new NetworkController({ infuraProjectId: 'foo', + messenger: networkControllerMessenger, state: { provider: { type: 'mainnet', @@ -50,6 +52,8 @@ describe('preferences controller', function () { network, provider, tokenListController, + onInfuraIsBlocked: sinon.spy(), + onInfuraIsUnblocked: sinon.spy(), }); }); diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index 534aa54b7..f9737ef3a 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -41,7 +41,6 @@ import fetchEstimatedL1Fee from '../../../ui/helpers/utils/optimism/fetchEstimat import { Numeric } from '../../../shared/modules/Numeric'; import { EtherDenomination } from '../../../shared/constants/common'; -import { NETWORK_EVENTS } from './network'; // The MAX_GAS_LIMIT is a number that is higher than the maximum gas costs we have observed on any aggregator const MAX_GAS_LIMIT = 2500000; @@ -114,6 +113,7 @@ export default class SwapsController { fetchTradesInfo = defaultFetchTradesInfo, getCurrentChainId, getEIP1559GasFeeEstimates, + onNetworkDidChange, }) { this.store = new ObservableStore({ swapsState: { ...initialState.swapsState }, @@ -137,7 +137,7 @@ export default class SwapsController { this.ethersProvider = new Web3Provider(provider); this._currentNetwork = networkController.store.getState().network; - networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, (network) => { + onNetworkDidChange((network) => { if (network !== 'loading' && network !== this._currentNetwork) { this._currentNetwork = network; this.ethersProvider = new Web3Provider(provider); diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps.test.js index 065540985..cebf695ae 100644 --- a/app/scripts/controllers/swaps.test.js +++ b/app/scripts/controllers/swaps.test.js @@ -14,7 +14,6 @@ import { FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, } from '../../../shared/constants/smartTransactions'; import SwapsController, { utils } from './swaps'; -import { NETWORK_EVENTS } from './network'; const MOCK_FETCH_PARAMS = { slippage: 3, @@ -104,10 +103,6 @@ function getMockNetworkController() { }; }, }, - on: sinon - .stub() - .withArgs(NETWORK_EVENTS.NETWORK_DID_CHANGE) - .callsArgAsync(1), }; } @@ -162,6 +157,7 @@ describe('SwapsController', function () { return new SwapsController({ getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, networkController: getMockNetworkController(), + onNetworkDidChange: sinon.stub(), provider, getProviderConfig: MOCK_GET_PROVIDER_CONFIG, getTokenRatesState: MOCK_TOKEN_RATES_STORE, @@ -209,9 +205,11 @@ describe('SwapsController', function () { it('should replace ethers instance when network changes', function () { const networkController = getMockNetworkController(); + const onNetworkDidChange = sinon.stub(); const swapsController = new SwapsController({ getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, networkController, + onNetworkDidChange, provider, getProviderConfig: MOCK_GET_PROVIDER_CONFIG, getTokenRatesState: MOCK_TOKEN_RATES_STORE, @@ -219,9 +217,9 @@ describe('SwapsController', function () { getCurrentChainId: getCurrentChainIdStub, }); const currentEthersInstance = swapsController.ethersProvider; - const onNetworkDidChange = networkController.on.getCall(0).args[1]; + const changeNetwork = onNetworkDidChange.getCall(0).args[0]; - onNetworkDidChange(NETWORK_IDS.MAINNET); + changeNetwork(NETWORK_IDS.MAINNET); const newEthersInstance = swapsController.ethersProvider; assert.notStrictEqual( @@ -233,9 +231,11 @@ describe('SwapsController', function () { it('should not replace ethers instance when network changes to loading', function () { const networkController = getMockNetworkController(); + const onNetworkDidChange = sinon.stub(); const swapsController = new SwapsController({ getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, networkController, + onNetworkDidChange, provider, getProviderConfig: MOCK_GET_PROVIDER_CONFIG, getTokenRatesState: MOCK_TOKEN_RATES_STORE, @@ -243,9 +243,9 @@ describe('SwapsController', function () { getCurrentChainId: getCurrentChainIdStub, }); const currentEthersInstance = swapsController.ethersProvider; - const onNetworkDidChange = networkController.on.getCall(0).args[1]; + const changeNetwork = onNetworkDidChange.getCall(0).args[0]; - onNetworkDidChange('loading'); + changeNetwork('loading'); const newEthersInstance = swapsController.ethersProvider; assert.strictEqual( @@ -257,9 +257,11 @@ describe('SwapsController', function () { it('should not replace ethers instance when network changes to the same network', function () { const networkController = getMockNetworkController(); + const onNetworkDidChange = sinon.stub(); const swapsController = new SwapsController({ getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, networkController, + onNetworkDidChange, provider, getProviderConfig: MOCK_GET_PROVIDER_CONFIG, getTokenRatesState: MOCK_TOKEN_RATES_STORE, @@ -267,9 +269,9 @@ describe('SwapsController', function () { getCurrentChainId: getCurrentChainIdStub, }); const currentEthersInstance = swapsController.ethersProvider; - const onNetworkDidChange = networkController.on.getCall(0).args[1]; + const changeNetwork = onNetworkDidChange.getCall(0).args[0]; - onNetworkDidChange(NETWORK_IDS.GOERLI); + changeNetwork(NETWORK_IDS.GOERLI); const newEthersInstance = swapsController.ethersProvider; assert.strictEqual( diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2d210f2a1..70b05b8fe 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -135,7 +135,9 @@ import createTabIdMiddleware from './lib/createTabIdMiddleware'; import createOnboardingMiddleware from './lib/createOnboardingMiddleware'; import { setupMultiplex } from './lib/stream-utils'; import EnsController from './controllers/ens'; -import NetworkController, { NETWORK_EVENTS } from './controllers/network'; +import NetworkController, { + NetworkControllerEventTypes, +} from './controllers/network'; import PreferencesController from './controllers/preferences'; import AppStateController from './controllers/app-state'; import CachedBalancesController from './controllers/cached-balances'; @@ -249,7 +251,12 @@ export default class MetamaskController extends EventEmitter { showApprovalRequest: opts.showUserConfirmation, }); + const networkControllerMessenger = this.controllerMessenger.getRestricted({ + name: 'NetworkController', + allowedEvents: Object.values(NetworkControllerEventTypes), + }); this.networkController = new NetworkController({ + messenger: networkControllerMessenger, state: initState.NetworkController, infuraProjectId: opts.infuraProjectId, trackMetaMetricsEvent: (...args) => @@ -292,7 +299,14 @@ export default class MetamaskController extends EventEmitter { initState: initState.PreferencesController, initLangCode: opts.initLangCode, openPopup: opts.openPopup, - network: this.networkController, + onInfuraIsBlocked: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + NetworkControllerEventTypes.InfuraIsBlocked, + ), + onInfuraIsUnblocked: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + NetworkControllerEventTypes.InfuraIsUnblocked, + ), tokenListController: this.tokenListController, provider: this.provider, }); @@ -320,8 +334,7 @@ export default class MetamaskController extends EventEmitter { onPreferencesStateChange: (listener) => this.preferencesController.store.subscribe(listener), onNetworkStateChange: (cb) => - this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => { - const networkState = this.networkController.store.getState(); + this.networkController.store.subscribe((networkState) => { const modifiedNetworkState = { ...networkState, providerConfig: { @@ -427,9 +440,9 @@ export default class MetamaskController extends EventEmitter { this.metaMetricsController = new MetaMetricsController({ segment, preferencesStore: this.preferencesController.store, - onNetworkDidChange: this.networkController.on.bind( - this.networkController, - NETWORK_EVENTS.NETWORK_DID_CHANGE, + onNetworkDidChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + NetworkControllerEventTypes.NetworkDidChange, ), getNetworkIdentifier: () => { const { type, rpcUrl } = @@ -464,9 +477,9 @@ export default class MetamaskController extends EventEmitter { clientId: SWAPS_CLIENT_ID, getProvider: () => this.networkController.getProviderAndBlockTracker().provider, - onNetworkStateChange: this.networkController.on.bind( - this.networkController, - NETWORK_EVENTS.NETWORK_DID_CHANGE, + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + NetworkControllerEventTypes.NetworkDidChange, ), getCurrentNetworkEIP1559Compatibility: this.networkController.getEIP1559Compatibility.bind( @@ -578,9 +591,9 @@ export default class MetamaskController extends EventEmitter { provider: this.provider, getCurrentChainId: () => this.networkController.store.getState().provider.chainId, - onNetworkDidChange: this.networkController.on.bind( - this.networkController, - NETWORK_EVENTS.NETWORK_DID_CHANGE, + onNetworkDidChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + NetworkControllerEventTypes.NetworkDidChange, ), }); @@ -590,9 +603,9 @@ export default class MetamaskController extends EventEmitter { this.incomingTransactionsController = new IncomingTransactionsController({ blockTracker: this.blockTracker, - onNetworkDidChange: this.networkController.on.bind( - this.networkController, - NETWORK_EVENTS.NETWORK_DID_CHANGE, + onNetworkDidChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + NetworkControllerEventTypes.NetworkDidChange, ), getCurrentChainId: () => this.networkController.store.getState().provider.chainId, @@ -1041,15 +1054,18 @@ export default class MetamaskController extends EventEmitter { } }); - this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, async () => { - const { ticker } = this.networkController.store.getState().provider; - try { - await this.currencyRateController.setNativeCurrency(ticker); - } catch (error) { - // TODO: Handle failure to get conversion rate more gracefully - console.error(error); - } - }); + networkControllerMessenger.subscribe( + NetworkControllerEventTypes.NetworkDidChange, + async () => { + const { ticker } = this.networkController.store.getState().provider; + try { + await this.currencyRateController.setNativeCurrency(ticker); + } catch (error) { + // TODO: Handle failure to get conversion rate more gracefully + console.error(error); + } + }, + ); this.networkController.lookupNetwork(); this.decryptMessageManager = new DecryptMessageManager({ @@ -1083,6 +1099,10 @@ export default class MetamaskController extends EventEmitter { this.txController.txGasUtil, ), networkController: this.networkController, + onNetworkDidChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + NetworkControllerEventTypes.NetworkDidChange, + ), provider: this.provider, getProviderConfig: () => this.networkController.store.getState().provider, getTokenRatesState: () => this.tokenRatesController.state, @@ -1122,17 +1142,23 @@ export default class MetamaskController extends EventEmitter { ); // ensure accountTracker updates balances after network change - this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => { - this.accountTracker._updateAccounts(); - }); + networkControllerMessenger.subscribe( + NetworkControllerEventTypes.NetworkDidChange, + () => { + 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.decryptMessageManager.clearUnapproved(); - this.signController.clearUnapproved(); - }); + networkControllerMessenger.subscribe( + NetworkControllerEventTypes.NetworkWillChange, + () => { + this.txController.txStateManager.clearUnapprovedTxs(); + this.encryptionPublicKeyManager.clearUnapproved(); + this.decryptMessageManager.clearUnapproved(); + this.signController.clearUnapproved(); + }, + ); this.metamaskMiddleware = createMetamaskMiddleware({ static: {