From cf72603484e341f7c47235a7b4c0f2711355ae11 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Thu, 21 May 2026 14:38:40 +0200 Subject: [PATCH 1/2] refactor(snap-account-service)!: make init sync + use it in ctor --- .../src/SnapAccountService.test.ts | 34 +----- .../src/SnapAccountService.ts | 11 -- .../src/SnapTracker.test.ts | 113 +++--------------- .../snap-account-service/src/SnapTracker.ts | 31 +---- 4 files changed, 21 insertions(+), 168 deletions(-) diff --git a/packages/snap-account-service/src/SnapAccountService.test.ts b/packages/snap-account-service/src/SnapAccountService.test.ts index ec00232431..6f0475964d 100644 --- a/packages/snap-account-service/src/SnapAccountService.test.ts +++ b/packages/snap-account-service/src/SnapAccountService.test.ts @@ -472,22 +472,12 @@ function setup({ const MOCK_SNAP_ID = 'npm:@metamask/mock-snap' as SnapId; describe('SnapAccountService', () => { - describe('init', () => { - it('resolves without throwing', async () => { - const { service } = setup(); - - expect(await service.init()).toBeUndefined(); - }); - }); - describe('getSnaps', () => { - it('exposes tracked Snaps seeded by init', async () => { + it('exposes tracked Snaps seeded from construction', () => { const { service } = setup({ runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], }); - await service.init(); - expect(service.getSnaps()).toStrictEqual([MOCK_SNAP_ID]); }); }); @@ -496,18 +486,6 @@ describe('SnapAccountService', () => { it('throws when the Snap is not tracked', async () => { const { service } = setup(); - await service.init(); - - await expect(service.ensureReady(MOCK_SNAP_ID)).rejects.toThrow( - `Unknown snap: "${MOCK_SNAP_ID}"`, - ); - }); - - it('throws before init even for runnable Snaps', async () => { - const { service } = setup({ - runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], - }); - await expect(service.ensureReady(MOCK_SNAP_ID)).rejects.toThrow( `Unknown snap: "${MOCK_SNAP_ID}"`, ); @@ -518,8 +496,6 @@ describe('SnapAccountService', () => { runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], }); - await service.init(); - expect(await service.ensureReady(MOCK_SNAP_ID)).toBeUndefined(); }); @@ -529,8 +505,6 @@ describe('SnapAccountService', () => { runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], }); - await service.init(); - let resolved = false; const ensurePromise = service.ensureReady(MOCK_SNAP_ID).then(() => { resolved = true; @@ -551,8 +525,6 @@ describe('SnapAccountService', () => { runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], }); - await service.init(); - let resolved = false; const ensurePromise = service.ensureReady(MOCK_SNAP_ID).then(() => { resolved = true; @@ -580,8 +552,6 @@ describe('SnapAccountService', () => { }, }); - await service.init(); - jest.useFakeTimers(); const ensurePromise = service.ensureReady(MOCK_SNAP_ID); // Attach rejection handler before advancing timers to avoid unhandled rejection. @@ -610,8 +580,6 @@ describe('SnapAccountService', () => { config: { snapPlatformWatcher: { ensureOnboardingComplete } }, }); - await service.init(); - let resolved = false; const ensurePromise = service.ensureReady(MOCK_SNAP_ID).then(() => { resolved = true; diff --git a/packages/snap-account-service/src/SnapAccountService.ts b/packages/snap-account-service/src/SnapAccountService.ts index 6cd1bba9e2..4a3bf8eaa2 100644 --- a/packages/snap-account-service/src/SnapAccountService.ts +++ b/packages/snap-account-service/src/SnapAccountService.ts @@ -267,17 +267,6 @@ export class SnapAccountService { } } - /** - * Initializes the snap account service. - * - * Seeds the internal set of account-management Snaps from - * `SnapController:getRunnableSnaps`, then starts processing lifecycle - * events. - */ - async init(): Promise { - await this.#tracker.init(); - } - /** * Returns the IDs of all currently tracked account-management Snaps — * Snaps that are installed, enabled, not blocked, and have the diff --git a/packages/snap-account-service/src/SnapTracker.test.ts b/packages/snap-account-service/src/SnapTracker.test.ts index 64b13fa450..5d5be4c310 100644 --- a/packages/snap-account-service/src/SnapTracker.test.ts +++ b/packages/snap-account-service/src/SnapTracker.test.ts @@ -206,24 +206,8 @@ const MOCK_SNAP_ID = 'npm:@metamask/mock-snap' as SnapId; const MOCK_OTHER_SNAP_ID = 'npm:@metamask/other-snap' as SnapId; describe('SnapTracker', () => { - describe('init', () => { - it('resolves without throwing', async () => { - const { tracker } = setup(); - - expect(await tracker.init()).toBeUndefined(); - }); - - it('does not re-init if already initialized', async () => { - const { tracker, mocks } = setup(); - - expect(await tracker.init()).toBeUndefined(); - expect(mocks.SnapController.getRunnableSnaps).toHaveBeenCalledTimes(1); - - expect(await tracker.init()).toBeUndefined(); - expect(mocks.SnapController.getRunnableSnaps).toHaveBeenCalledTimes(1); // Still only called once. - }); - - it('seeds tracked Snaps from getRunnableSnaps, filtering out non-keyring Snaps', async () => { + describe('getSnaps', () => { + it('returns seeded Snaps from construction, filtering out non-keyring Snaps', () => { const { tracker } = setup({ runnableSnaps: [ buildSnap(MOCK_SNAP_ID, true), @@ -231,106 +215,62 @@ describe('SnapTracker', () => { ], }); - await tracker.init(); - expect(tracker.getSnaps()).toStrictEqual([MOCK_SNAP_ID]); }); - }); - describe('getSnaps', () => { - it('returns an empty array before init', () => { - const { tracker } = setup({ - runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], - }); + it('returns an empty array when there are no runnable account-management Snaps', () => { + const { tracker } = setup(); expect(tracker.getSnaps()).toStrictEqual([]); }); }); describe('canUse', () => { - it('returns false before init', () => { - const { tracker } = setup({ - runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], - }); - - expect(tracker.canUse(MOCK_SNAP_ID)).toBe(false); - }); - - it('returns true for a tracked Snap', async () => { + it('returns true for a Snap seeded during construction', () => { const { tracker } = setup({ runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], }); - await tracker.init(); - expect(tracker.canUse(MOCK_SNAP_ID)).toBe(true); }); - it('returns false for an untracked Snap', async () => { + it('returns false for an untracked Snap', () => { const { tracker } = setup(); - await tracker.init(); - expect(tracker.canUse(MOCK_SNAP_ID)).toBe(false); }); }); describe('lifecycle events', () => { - it('ignores add events received before init', async () => { - const { tracker, rootMessenger } = setup(); - - publishSnapInstalled(rootMessenger, buildSnap(MOCK_SNAP_ID, true)); - - await tracker.init(); - - expect(tracker.getSnaps()).toStrictEqual([]); - }); - - it('ignores remove events received before init', async () => { - const { tracker, rootMessenger } = setup({ - runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], - }); - - publishSnapUninstalled(rootMessenger, buildSnap(MOCK_SNAP_ID, true)); - - await tracker.init(); - - expect(tracker.getSnaps()).toStrictEqual([MOCK_SNAP_ID]); - }); - - it('adds a Snap on snapInstalled when it has the keyring endowment', async () => { + it('adds a Snap on snapInstalled when it has the keyring endowment', () => { const { tracker, rootMessenger } = setup(); - await tracker.init(); publishSnapInstalled(rootMessenger, buildSnap(MOCK_SNAP_ID, true)); expect(tracker.getSnaps()).toStrictEqual([MOCK_SNAP_ID]); }); - it('does not add a Snap on snapInstalled when it lacks the keyring endowment', async () => { + it('does not add a Snap on snapInstalled when it lacks the keyring endowment', () => { const { tracker, rootMessenger } = setup(); - await tracker.init(); publishSnapInstalled(rootMessenger, buildSnap(MOCK_SNAP_ID, false)); expect(tracker.getSnaps()).toStrictEqual([]); }); - it('adds a Snap on snapEnabled when it has the keyring endowment', async () => { + it('adds a Snap on snapEnabled when it has the keyring endowment', () => { const { tracker, rootMessenger } = setup(); - await tracker.init(); publishSnapEnabled(rootMessenger, buildSnap(MOCK_SNAP_ID, true)); expect(tracker.getSnaps()).toStrictEqual([MOCK_SNAP_ID]); }); - it('removes a Snap on snapDisabled', async () => { + it('removes a Snap on snapDisabled', () => { const { tracker, rootMessenger } = setup({ runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], }); - await tracker.init(); expect(tracker.getSnaps()).toStrictEqual([MOCK_SNAP_ID]); publishSnapDisabled(rootMessenger, buildSnap(MOCK_SNAP_ID, true)); @@ -338,36 +278,19 @@ describe('SnapTracker', () => { expect(tracker.getSnaps()).toStrictEqual([]); }); - it('removes a Snap on snapBlocked', async () => { + it('removes a Snap on snapBlocked', () => { const { tracker, rootMessenger } = setup({ runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], }); - await tracker.init(); publishSnapBlocked(rootMessenger, MOCK_SNAP_ID); expect(tracker.getSnaps()).toStrictEqual([]); }); - it('ignores snapUnblocked received before init', async () => { - const { tracker, rootMessenger, mocks } = setup(); - - mocks.SnapController.getSnap.mockReturnValue({ - ...buildSnap(MOCK_SNAP_ID, true), - enabled: true, - blocked: false, - } as TruncatedSnap); - publishSnapUnblocked(rootMessenger, MOCK_SNAP_ID); - - await tracker.init(); - - expect(tracker.getSnaps()).toStrictEqual([]); - }); - - it('re-adds a Snap on snapUnblocked when it is enabled and has the keyring endowment', async () => { + it('re-adds a Snap on snapUnblocked when it is enabled and has the keyring endowment', () => { const { tracker, rootMessenger, mocks } = setup(); - await tracker.init(); mocks.SnapController.getSnap.mockReturnValue({ ...buildSnap(MOCK_SNAP_ID, true), enabled: true, @@ -379,10 +302,9 @@ describe('SnapTracker', () => { expect(tracker.getSnaps()).toStrictEqual([MOCK_SNAP_ID]); }); - it('does not re-add a Snap on snapUnblocked when it is disabled', async () => { + it('does not re-add a Snap on snapUnblocked when it is disabled', () => { const { tracker, rootMessenger, mocks } = setup(); - await tracker.init(); mocks.SnapController.getSnap.mockReturnValue({ ...buildSnap(MOCK_SNAP_ID, true), enabled: false, @@ -394,10 +316,9 @@ describe('SnapTracker', () => { expect(tracker.getSnaps()).toStrictEqual([]); }); - it('does not re-add a Snap on snapUnblocked when it lacks the keyring endowment', async () => { + it('does not re-add a Snap on snapUnblocked when it lacks the keyring endowment', () => { const { tracker, rootMessenger, mocks } = setup(); - await tracker.init(); mocks.SnapController.getSnap.mockReturnValue({ ...buildSnap(MOCK_SNAP_ID, false), enabled: true, @@ -409,21 +330,19 @@ describe('SnapTracker', () => { expect(tracker.getSnaps()).toStrictEqual([]); }); - it('does not re-add a Snap on snapUnblocked when getSnap returns null', async () => { + it('does not re-add a Snap on snapUnblocked when getSnap returns null', () => { const { tracker, rootMessenger } = setup(); - await tracker.init(); publishSnapUnblocked(rootMessenger, MOCK_SNAP_ID); expect(tracker.getSnaps()).toStrictEqual([]); }); - it('removes a Snap on snapUninstalled', async () => { + it('removes a Snap on snapUninstalled', () => { const { tracker, rootMessenger } = setup({ runnableSnaps: [buildSnap(MOCK_SNAP_ID, true)], }); - await tracker.init(); publishSnapUninstalled(rootMessenger, buildSnap(MOCK_SNAP_ID, true)); expect(tracker.getSnaps()).toStrictEqual([]); diff --git a/packages/snap-account-service/src/SnapTracker.ts b/packages/snap-account-service/src/SnapTracker.ts index 4bd13e7b28..b824892c4b 100644 --- a/packages/snap-account-service/src/SnapTracker.ts +++ b/packages/snap-account-service/src/SnapTracker.ts @@ -25,8 +25,6 @@ export class SnapTracker { readonly #snaps: Set = new Set(); - #initialized = false; - constructor(messenger: SnapAccountServiceMessenger) { this.#messenger = messenger; @@ -48,22 +46,15 @@ export class SnapTracker { this.#messenger.subscribe('SnapController:snapUnblocked', (snapId) => this.#handleSnapUnblocked(snapId as SnapId), ); + + this.#init(); } /** * Seeds the internal set of account-management Snaps from - * `SnapController:getRunnableSnaps`, then starts processing lifecycle - * events. + * `SnapController:getRunnableSnaps`. */ - async init(): Promise { - if (this.#initialized) { - // Do not re-init, once setup we only rely on lifecycle events to update the set of - // tracked Snaps. - return; - } - - this.#snaps.clear(); - + #init(): void { const runnable = this.#messenger.call('SnapController:getRunnableSnaps'); for (const snap of runnable) { if (isAccountManagementSnap(snap)) { @@ -71,8 +62,6 @@ export class SnapTracker { this.#snaps.add(snap.id); } } - - this.#initialized = true; } /** @@ -102,10 +91,6 @@ export class SnapTracker { * @param reason - The reason the Snap was added. */ #handleSnapAdded(snap: TruncatedSnap, reason: string): void { - if (!this.#initialized) { - return; - } - if (!snap.enabled || snap.blocked) { return; } @@ -124,10 +109,6 @@ export class SnapTracker { * @param snapId - The Snap ID that was unblocked. */ #handleSnapUnblocked(snapId: SnapId): void { - if (!this.#initialized) { - return; - } - const snap = this.#messenger.call('SnapController:getSnap', snapId); if (snap) { this.#handleSnapAdded(snap, 'unblocked'); @@ -142,10 +123,6 @@ export class SnapTracker { * @param reason - The reason the Snap was removed. */ #handleSnapRemoved(snapId: SnapId, reason: string): void { - if (!this.#initialized) { - return; - } - if (this.#snaps.has(snapId)) { log(`Removed account management Snap: ${snapId} (${reason})`); From 9fb8d00e74a6c37510e977f5b8c5018dfefb9407 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Thu, 21 May 2026 14:41:25 +0200 Subject: [PATCH 2/2] chore: changelog --- packages/snap-account-service/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/snap-account-service/CHANGELOG.md b/packages/snap-account-service/CHANGELOG.md index 6d2621b9a7..fb4a3878ae 100644 --- a/packages/snap-account-service/CHANGELOG.md +++ b/packages/snap-account-service/CHANGELOG.md @@ -20,6 +20,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - We now check if the keyring is available before delegating those messages. - We still auto-create the keyring in some specific calls (e.g `notify:accountCreated`). +## Removed + +- Removed `init` in favor of synchronous initialization when constructing the service ([#8877](https://github.com/MetaMask/core/pull/8877)) + ## [0.2.0] ### Added