diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index c26c00fe90..71a27f1284 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -17,5 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Overrides the `fetch` implementation used by `NetworkController`'s RPC service. Defaults to `globalThis.fetch`. Allows platform-specific fetch implementations (e.g. React Native) to be injected. - Add optional `logger` field to `WalletOptions` ([#XXXX](https://github.com/MetaMask/core/pull/XXXX)) - When provided, emits an `info`-level log after each controller finishes initializing: `[wallet] ${ControllerName}: initialized`. Defaults to no-op (no output). Pass `console` during development to observe initialization order. +- Accept optional `initializationConfigurations` in the `Wallet` constructor ([#XXXX](https://github.com/MetaMask/core/pull/XXXX)) + - Allows callers to override or extend the default set of controller configurations. [Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/wallet/src/Wallet.test.ts b/packages/wallet/src/Wallet.test.ts index d5a8b2201a..b5af3b1602 100644 --- a/packages/wallet/src/Wallet.test.ts +++ b/packages/wallet/src/Wallet.test.ts @@ -210,6 +210,27 @@ describe('Wallet', () => { expect(Object.keys(wallet.state)).toStrictEqual(['WithMeta', 'NoMeta']); }); + it('initializes additional controllers passed via initializationConfigurations', () => { + const customInit = jest + .fn() + .mockReturnValue({ instance: {} as never }); + const customMessenger = jest.fn().mockReturnValue({} as never); + + wallet = new Wallet({ + ...options, + initializationConfigurations: [ + { + name: 'CustomController', + init: customInit, + messenger: customMessenger, + }, + ], + }); + + expect(customInit).toHaveBeenCalledTimes(1); + expect(customMessenger).toHaveBeenCalledTimes(1); + }); + it('publishes Wallet:destroyed exactly once on destroy', async () => { wallet = new Wallet(options); diff --git a/packages/wallet/src/Wallet.ts b/packages/wallet/src/Wallet.ts index da3b2d7d3d..b5e6f39786 100644 --- a/packages/wallet/src/Wallet.ts +++ b/packages/wallet/src/Wallet.ts @@ -10,9 +10,12 @@ import type { RootMessenger, } from './initialization'; import { initialize } from './initialization'; +import type { InitializationConfiguration } from './initialization/types'; import type { WalletOptions } from './types'; export class Wallet { + // messenger is typed against the default controller set. Action/event types for + // any additional configurations passed to the constructor are not reflected here. public readonly messenger: RootMessenger; readonly #instances: DefaultInstances; @@ -23,7 +26,13 @@ export class Wallet { #destroyed = false; - constructor({ state, ...options }: WalletOptions) { + constructor({ + state, + initializationConfigurations, + ...options + }: WalletOptions & { + initializationConfigurations?: InitializationConfiguration[]; + }) { this.messenger = new Messenger({ namespace: 'Wallet', }); @@ -31,6 +40,7 @@ export class Wallet { this.#instances = initialize({ state: state ?? {}, messenger: this.messenger, + initializationConfigurations, options, }); diff --git a/packages/wallet/src/initialization/initialization.ts b/packages/wallet/src/initialization/initialization.ts index 49b4e7b37c..073bd88b46 100644 --- a/packages/wallet/src/initialization/initialization.ts +++ b/packages/wallet/src/initialization/initialization.ts @@ -8,17 +8,32 @@ import type { InitializationConfiguration } from './types'; export type InitializeArgs = { state: Record; messenger: RootMessenger; + initializationConfigurations?: InitializationConfiguration< + unknown, + unknown + >[]; options: WalletOptions; }; export function initialize({ state, messenger, + initializationConfigurations = [], options, }: InitializeArgs): DefaultInstances { + const overriddenConfiguration = initializationConfigurations.map( + (config) => config.name, + ); + + const configurationEntries = initializationConfigurations.concat( + Object.values(defaultConfigurations).filter( + (config) => !overriddenConfiguration.includes(config.name), + ), + ); + const instances: Record = {}; - for (const config of Object.values(defaultConfigurations) as InitializationConfiguration[]) { + for (const config of configurationEntries) { const { name } = config; const instanceState = state[name];