From 74aa200d6543d746e6e46026938b2fec218ab6ef Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 12:23:43 +0100 Subject: [PATCH 01/12] Standardise `SnapController` action names and types --- package.json | 6 +- packages/snaps-controllers/package.json | 1 + .../src/cronjob/CronjobController.test.ts | 2 +- .../src/cronjob/CronjobController.ts | 27 +- .../insights/SnapInsightsController.test.ts | 14 +- .../src/insights/SnapInsightsController.ts | 11 +- .../SnapInterfaceController.test.tsx | 4 +- .../src/interface/SnapInterfaceController.ts | 6 +- .../src/multichain/MultichainRouter.test.ts | 48 +- .../src/multichain/MultichainRouter.ts | 11 +- .../node-js/NodeThreadExecutionService.ts | 3 - .../services/proxy/ProxyExecutionService.ts | 3 - .../SnapController-method-action-types.ts | 416 ++++++++++ .../src/snaps/SnapController.test.tsx | 534 ++++++------ .../src/snaps/SnapController.ts | 503 +++--------- packages/snaps-controllers/src/snaps/index.ts | 58 +- .../src/test-utils/controller.tsx | 199 +++-- .../src/test-utils/execution-environment.ts | 9 +- .../src/test-utils/registry.ts | 5 +- .../src/websocket/WebSocketService.ts | 16 +- scripts/generate-method-action-types.mts | 771 ++++++++++++++++++ yarn.lock | 92 +++ 22 files changed, 1913 insertions(+), 826 deletions(-) create mode 100644 packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts create mode 100644 scripts/generate-method-action-types.mts diff --git a/package.json b/package.json index 9d109bdf4c..8b03c43325 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,10 @@ "changelog:validate": "yarn workspaces foreach --all --parallel --interlaced --verbose run changelog:validate", "child-workspace-package-names-as-json": "ts-node scripts/child-workspace-package-names-as-json.ts", "clean": "yarn workspaces foreach --all --parallel --verbose run clean", + "generate-method-action-types": "yarn workspaces foreach --all --parallel --interlaced --verbose run generate-method-action-types", "get-release-tag": "ts-node --swc scripts/get-release-tag.ts", "install-chrome": "./scripts/install-chrome.sh", - "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:tsconfig && yarn constraints && yarn lint:dependencies", + "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:tsconfig && yarn constraints && yarn lint:dependencies && yarn generate-method-action-types --check", "lint:dependencies": "yarn workspaces foreach --all --parallel --verbose run lint:dependencies && yarn dedupe --check", "lint:eslint": "eslint . --cache", "lint:fix": "yarn workspaces foreach --all --parallel run lint:eslint --fix && yarn lint:misc --write && yarn lint:tsconfig && yarn constraints --fix && yarn dedupe", @@ -120,7 +121,8 @@ "tsx": "^4.20.3", "typescript": "~5.3.3", "typescript-eslint": "^8.6.0", - "vite": "^6.4.1" + "vite": "^6.4.1", + "yargs": "^18.0.0" }, "packageManager": "yarn@4.10.3", "engines": { diff --git a/packages/snaps-controllers/package.json b/packages/snaps-controllers/package.json index f981211c60..73b90f4bc3 100644 --- a/packages/snaps-controllers/package.json +++ b/packages/snaps-controllers/package.json @@ -62,6 +62,7 @@ "build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references", "changelog:update": "../../scripts/update-changelog.sh @metamask/snaps-controllers", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/snaps-controllers", + "generate-method-action-types": "tsx ../../scripts/generate-method-action-types.mts", "lint": "yarn lint:eslint && yarn lint:misc --check && yarn changelog:validate && yarn lint:dependencies", "lint:ci": "yarn lint", "lint:dependencies": "depcheck", diff --git a/packages/snaps-controllers/src/cronjob/CronjobController.test.ts b/packages/snaps-controllers/src/cronjob/CronjobController.test.ts index d682f05a70..8389151178 100644 --- a/packages/snaps-controllers/src/cronjob/CronjobController.test.ts +++ b/packages/snaps-controllers/src/cronjob/CronjobController.test.ts @@ -1139,7 +1139,7 @@ describe('CronjobController', () => { deriveStateFromMetadata( controller.state, controller.metadata, - 'anonymous', + 'includeInDebugSnapshot', ), ).toMatchInlineSnapshot(`{}`); }); diff --git a/packages/snaps-controllers/src/cronjob/CronjobController.ts b/packages/snaps-controllers/src/cronjob/CronjobController.ts index df12e9fbb7..c5914ca608 100644 --- a/packages/snaps-controllers/src/cronjob/CronjobController.ts +++ b/packages/snaps-controllers/src/cronjob/CronjobController.ts @@ -23,13 +23,13 @@ import { nanoid } from 'nanoid'; import { getCronjobSpecificationSchedule, getExecutionDate } from './utils'; import type { - HandleSnapRequest, - SnapDisabled, - SnapEnabled, - SnapInstalled, - SnapUninstalled, - SnapUpdated, -} from '..'; + SnapControllerHandleRequestAction, + SnapControllerSnapDisabledEvent, + SnapControllerSnapEnabledEvent, + SnapControllerSnapInstalledEvent, + SnapControllerSnapUninstalledEvent, + SnapControllerSnapUpdatedEvent, +} from '../snaps'; import { METAMASK_ORIGIN } from '../snaps/constants'; import { Timer } from '../snaps/Timer'; @@ -37,6 +37,7 @@ export type CronjobControllerGetStateAction = ControllerGetStateAction< typeof controllerName, CronjobControllerState >; + export type CronjobControllerStateChangeEvent = ControllerStateChangeEvent< typeof controllerName, CronjobControllerState @@ -68,7 +69,7 @@ export type Get = { export type CronjobControllerActions = | CronjobControllerGetStateAction - | HandleSnapRequest + | SnapControllerHandleRequestAction | GetPermissions | Schedule | Cancel @@ -77,11 +78,11 @@ export type CronjobControllerActions = export type CronjobControllerEvents = | CronjobControllerStateChangeEvent - | SnapInstalled - | SnapUninstalled - | SnapUpdated - | SnapEnabled - | SnapDisabled; + | SnapControllerSnapInstalledEvent + | SnapControllerSnapUninstalledEvent + | SnapControllerSnapUpdatedEvent + | SnapControllerSnapEnabledEvent + | SnapControllerSnapDisabledEvent; export type CronjobControllerMessenger = Messenger< typeof controllerName, diff --git a/packages/snaps-controllers/src/insights/SnapInsightsController.test.ts b/packages/snaps-controllers/src/insights/SnapInsightsController.test.ts index cf934e930f..5dfee842aa 100644 --- a/packages/snaps-controllers/src/insights/SnapInsightsController.test.ts +++ b/packages/snaps-controllers/src/insights/SnapInsightsController.test.ts @@ -31,7 +31,7 @@ describe('SnapInsightsController', () => { }, ); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -157,7 +157,7 @@ describe('SnapInsightsController', () => { }, ); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -285,7 +285,7 @@ describe('SnapInsightsController', () => { messenger: controllerMessenger, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -388,7 +388,7 @@ describe('SnapInsightsController', () => { messenger: controllerMessenger, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -456,7 +456,7 @@ describe('SnapInsightsController', () => { it('ignores insight if transaction has already been signed', async () => { const rootMessenger = getRootSnapInsightsControllerMessenger(); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -556,7 +556,7 @@ describe('SnapInsightsController', () => { messenger: controllerMessenger, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -661,7 +661,7 @@ describe('SnapInsightsController', () => { deriveStateFromMetadata( controller.state, controller.metadata, - 'anonymous', + 'includeInDebugSnapshot', ), ).toMatchInlineSnapshot(`{}`); }); diff --git a/packages/snaps-controllers/src/insights/SnapInsightsController.ts b/packages/snaps-controllers/src/insights/SnapInsightsController.ts index 62287a4600..69dda216e8 100644 --- a/packages/snaps-controllers/src/insights/SnapInsightsController.ts +++ b/packages/snaps-controllers/src/insights/SnapInsightsController.ts @@ -19,7 +19,10 @@ import { HandlerType } from '@metamask/snaps-utils'; import { hasProperty, hexToBigInt } from '@metamask/utils'; import type { DeleteInterface } from '../interface'; -import type { GetAllSnaps, HandleSnapRequest } from '../snaps'; +import type { + SnapControllerGetAllSnapsAction, + SnapControllerHandleRequestAction, +} from '../snaps'; import { getRunnableSnaps } from '../snaps'; import type { TransactionControllerUnapprovedTransactionAddedEvent, @@ -33,8 +36,8 @@ import type { const controllerName = 'SnapInsightsController'; export type SnapInsightsControllerAllowedActions = - | HandleSnapRequest - | GetAllSnaps + | SnapControllerHandleRequestAction + | SnapControllerGetAllSnapsAction | GetPermissions | DeleteInterface; @@ -143,7 +146,7 @@ export class SnapInsightsController extends BaseController< * @returns A list of objects containing Snap IDs and the permission object. */ #getSnapsWithPermission(permissionName: string) { - const allSnaps = this.messenger.call('SnapController:getAll'); + const allSnaps = this.messenger.call('SnapController:getAllSnaps'); const filteredSnaps = getRunnableSnaps(allSnaps); return filteredSnaps.reduce((accumulator, snap) => { diff --git a/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx b/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx index 0699bff4a8..afe461771d 100644 --- a/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx +++ b/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx @@ -646,7 +646,7 @@ describe('SnapInterfaceController', () => { ); rootMessenger.registerActionHandler( - 'SnapController:get', + 'SnapController:getSnap', () => undefined, ); @@ -2024,7 +2024,7 @@ describe('SnapInterfaceController', () => { deriveStateFromMetadata( controller.state, controller.metadata, - 'anonymous', + 'includeInDebugSnapshot', ), ).toMatchInlineSnapshot(`{}`); }); diff --git a/packages/snaps-controllers/src/interface/SnapInterfaceController.ts b/packages/snaps-controllers/src/interface/SnapInterfaceController.ts index f80e17c7ad..574eccf619 100644 --- a/packages/snaps-controllers/src/interface/SnapInterfaceController.ts +++ b/packages/snaps-controllers/src/interface/SnapInterfaceController.ts @@ -40,7 +40,7 @@ import { isMatchingChainId, validateInterfaceContext, } from './utils'; -import type { GetSnap } from '../snaps'; +import type { SnapControllerGetSnapAction } from '../snaps'; const MAX_UI_CONTENT_SIZE = 10_000_000; // 10 mb @@ -125,7 +125,7 @@ export type SnapInterfaceControllerAllowedActions = | PhishingControllerTestOrigin | ApprovalControllerHasRequestAction | ApprovalControllerAcceptRequestAction - | GetSnap + | SnapControllerGetSnapAction | MultichainAssetsControllerGetStateAction | AccountsControllerGetSelectedMultichainAccountAction | AccountsControllerGetAccountByAddressAction @@ -598,7 +598,7 @@ export class SnapInterfaceController extends BaseController< * @returns The snap. */ #getSnap(id: string) { - return this.messenger.call('SnapController:get', id); + return this.messenger.call('SnapController:getSnap', id); } #hasPermission(snapId: SnapId, permission: string) { diff --git a/packages/snaps-controllers/src/multichain/MultichainRouter.test.ts b/packages/snaps-controllers/src/multichain/MultichainRouter.test.ts index 8988c4dee3..b0a0895edd 100644 --- a/packages/snaps-controllers/src/multichain/MultichainRouter.test.ts +++ b/packages/snaps-controllers/src/multichain/MultichainRouter.test.ts @@ -7,7 +7,7 @@ import { import { MultichainRouter } from './MultichainRouter'; import { METAMASK_ORIGIN } from '../snaps/constants'; import { - getRootMultichainRouterMessenger, + getMultichainRouterRootMessenger, getRestrictedMultichainRouterMessenger, BTC_CAIP2, BTC_CONNECTED_ACCOUNTS, @@ -22,7 +22,7 @@ import { describe('MultichainRouter', () => { describe('handleRequest', () => { it('can route signing requests to account Snaps without address resolution', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring({ submitRequest: jest.fn().mockResolvedValue({ @@ -71,7 +71,7 @@ describe('MultichainRouter', () => { }); it('can route signing requests to account Snaps using address resolution', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring({ submitRequest: jest.fn().mockResolvedValue({ @@ -123,7 +123,7 @@ describe('MultichainRouter', () => { }); it('disallows routing to unconnected accounts', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -166,7 +166,7 @@ describe('MultichainRouter', () => { }); it('can route protocol requests to protocol Snaps', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -181,7 +181,7 @@ describe('MultichainRouter', () => { () => [], ); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -237,7 +237,7 @@ describe('MultichainRouter', () => { }); it('throws if no suitable Snaps are found', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -252,7 +252,7 @@ describe('MultichainRouter', () => { () => [], ); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return []; }); @@ -271,7 +271,7 @@ describe('MultichainRouter', () => { }); it('throws if address resolution fails', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -320,7 +320,7 @@ describe('MultichainRouter', () => { }); it('throws if address resolution returns an address that isnt available', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -372,7 +372,7 @@ describe('MultichainRouter', () => { }); it(`throws if address resolution returns a lower case address that isn't available`, async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -425,7 +425,7 @@ describe('MultichainRouter', () => { describe('getSupportedMethods', () => { it('returns a set of both protocol and account Snap methods', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -435,7 +435,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -455,7 +455,7 @@ describe('MultichainRouter', () => { }); it('handles lack of protocol Snaps', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -465,7 +465,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -485,7 +485,7 @@ describe('MultichainRouter', () => { }); it('handles lack of account Snaps', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -495,7 +495,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -517,7 +517,7 @@ describe('MultichainRouter', () => { describe('getSupportedAccounts', () => { it('returns a set of accounts for the requested scope', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -542,7 +542,7 @@ describe('MultichainRouter', () => { describe('isSupportedScope', () => { it('returns true if an account Snap exists', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -552,7 +552,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -572,7 +572,7 @@ describe('MultichainRouter', () => { }); it('returns true if a protocol Snap exists', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -582,7 +582,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -602,7 +602,7 @@ describe('MultichainRouter', () => { }); it('returns false if no Snap is found', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -612,7 +612,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return []; }); diff --git a/packages/snaps-controllers/src/multichain/MultichainRouter.ts b/packages/snaps-controllers/src/multichain/MultichainRouter.ts index 87df9a634e..fefa0aa408 100644 --- a/packages/snaps-controllers/src/multichain/MultichainRouter.ts +++ b/packages/snaps-controllers/src/multichain/MultichainRouter.ts @@ -22,8 +22,11 @@ import { } from '@metamask/utils'; import { nanoid } from 'nanoid'; +import type { + SnapControllerGetAllSnapsAction, + SnapControllerHandleRequestAction, +} from '../snaps'; import { getRunnableSnaps } from '../snaps'; -import type { GetAllSnaps, HandleSnapRequest } from '../snaps'; export type MultichainRouterHandleRequestAction = { type: `${typeof name}:handleRequest`; @@ -72,8 +75,8 @@ export type MultichainRouterActions = | MultichainRouterIsSupportedScopeAction; export type MultichainRouterAllowedActions = - | GetAllSnaps - | HandleSnapRequest + | SnapControllerGetAllSnapsAction + | SnapControllerHandleRequestAction | GetPermissions | AccountsControllerListMultichainAccountsAction; @@ -260,7 +263,7 @@ export class MultichainRouter { * @returns A list of all the protocol Snaps available and their RPC methods. */ #getProtocolSnaps(scope: CaipChainId) { - const allSnaps = this.#messenger.call('SnapController:getAll'); + const allSnaps = this.#messenger.call('SnapController:getAllSnaps'); const filteredSnaps = getRunnableSnaps(allSnaps); return filteredSnaps.reduce((accumulator, snap) => { diff --git a/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts b/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts index 223506acf5..9165986784 100644 --- a/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts +++ b/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts @@ -36,9 +36,6 @@ export class NodeThreadExecutionService extends AbstractExecutionService return Promise.resolve({ worker, stream }); } - // TODO: Either fix this lint violation or explain why it's necessary to - // ignore. - // eslint-disable-next-line @typescript-eslint/no-misused-promises protected async terminateJob( jobWrapper: TerminateJobArgs, ): Promise { diff --git a/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts b/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts index 251f7131b9..03d4fe531d 100644 --- a/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts +++ b/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts @@ -47,9 +47,6 @@ export class ProxyExecutionService extends AbstractExecutionService { * * @param job - The job to terminate. */ - // TODO: Either fix this lint violation or explain why it's necessary to - // ignore. - // eslint-disable-next-line @typescript-eslint/no-misused-promises protected async terminateJob(job: TerminateJobArgs) { // The `AbstractExecutionService` will have already closed the job stream, // so we write to the runtime stream directly. diff --git a/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts new file mode 100644 index 0000000000..1ec97d9acc --- /dev/null +++ b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts @@ -0,0 +1,416 @@ +/** + * This file is auto generated by `scripts/generate-method-action-types.ts`. + * Do not edit manually. + */ + +import type { SnapController } from './SnapController'; + +/** + * Initialise the SnapController. + * + * Currently this method sets up the controller and calls the `onStart` lifecycle hook for all + * runnable Snaps. + * + * @param waitForPlatform - Whether to wait for the platform to be ready before returning. + */ +export type SnapControllerInitAction = { + type: `SnapController:init`; + handler: SnapController['init']; +}; + +/** + * Trigger an update of the registry. + * + * As a side-effect of this, preinstalled Snaps may be updated and Snaps may be blocked/unblocked. + */ +export type SnapControllerUpdateRegistryAction = { + type: `SnapController:updateRegistry`; + handler: SnapController['updateRegistry']; +}; + +/** + * Starts the given snap. Throws an error if no such snap exists + * or if it is already running. + * + * @param snapId - The id of the Snap to start. + */ +export type SnapControllerStartSnapAction = { + type: `SnapController:startSnap`; + handler: SnapController['startSnap']; +}; + +/** + * Enables the given snap. A snap can only be started if it is enabled. A snap + * can only be enabled if it isn't blocked. + * + * @param snapId - The id of the Snap to enable. + */ +export type SnapControllerEnableSnapAction = { + type: `SnapController:enableSnap`; + handler: SnapController['enableSnap']; +}; + +/** + * Disables the given snap. A snap can only be started if it is enabled. + * + * @param snapId - The id of the Snap to disable. + * @returns A promise that resolves once the snap has been disabled. + */ +export type SnapControllerDisableSnapAction = { + type: `SnapController:disableSnap`; + handler: SnapController['disableSnap']; +}; + +/** + * Stops the given snap, removes all hooks, closes all connections, and + * terminates its worker. + * + * @param snapId - The id of the Snap to stop. + * @param statusEvent - The Snap status event that caused the snap to be + * stopped. + */ +export type SnapControllerStopSnapAction = { + type: `SnapController:stopSnap`; + handler: SnapController['stopSnap']; +}; + +/** + * Stops all running snaps, removes all hooks, closes all connections, and + * terminates their workers. + * + * @param statusEvent - The Snap status event that caused the snap to be + * stopped. + */ +export type SnapControllerStopAllSnapsAction = { + type: `SnapController:stopAllSnaps`; + handler: SnapController['stopAllSnaps']; +}; + +/** + * Returns whether the given snap is running. + * Throws an error if the snap doesn't exist. + * + * @param snapId - The id of the Snap to check. + * @returns `true` if the snap is running, otherwise `false`. + */ +export type SnapControllerIsSnapRunningAction = { + type: `SnapController:isSnapRunning`; + handler: SnapController['isSnapRunning']; +}; + +/** + * Returns whether the given snap has been added to state. + * + * @param snapId - The id of the Snap to check for. + * @returns `true` if the snap exists in the controller state, otherwise `false`. + */ +export type SnapControllerHasSnapAction = { + type: `SnapController:hasSnap`; + handler: SnapController['hasSnap']; +}; + +/** + * Gets the snap with the given id if it exists, including all data. + * This should not be used if the snap is to be serializable, as e.g. + * the snap sourceCode may be quite large. + * + * @param snapId - The id of the Snap to get. + * @returns The entire snap object from the controller state. + */ +export type SnapControllerGetSnapAction = { + type: `SnapController:getSnap`; + handler: SnapController['getSnap']; +}; + +/** + * Gets the snap with the given id, throws if doesn't. + * This should not be used if the snap is to be serializable, as e.g. + * the snap sourceCode may be quite large. + * + * @see {@link SnapController.getSnap} + * @throws {@link Error}. If the snap doesn't exist + * @param snapId - The id of the snap to get. + * @returns The entire snap object. + */ +export type SnapControllerGetSnapExpectAction = { + type: `SnapController:getSnapExpect`; + handler: SnapController['getSnapExpect']; +}; + +/** + * Gets the snap with the given id if it exists, excluding any + * non-serializable or expensive-to-serialize data. + * + * @param snapId - The id of the Snap to get. + * @returns A truncated version of the snap state, that is less expensive to serialize. + */ +export type SnapControllerGetTruncatedSnapAction = { + type: `SnapController:getTruncatedSnap`; + handler: SnapController['getTruncatedSnap']; +}; + +/** + * Gets the snap with the given id, throw if it doesn't exist. + * + * @throws {@link Error}. If snap doesn't exist + * @param snapId - The id of the snap to get. + * @returns A truncated version of the snap state, that is less expensive to serialize. + */ +export type SnapControllerGetTruncatedSnapExpectAction = { + type: `SnapController:getTruncatedSnapExpect`; + handler: SnapController['getTruncatedSnapExpect']; +}; + +/** + * Updates the own state of the snap with the given id. + * This is distinct from the state MetaMask uses to manage snaps. + * + * @param snapId - The id of the Snap whose state should be updated. + * @param newSnapState - The new state of the snap. + * @param encrypted - A flag to indicate whether to use encrypted storage or not. + */ +export type SnapControllerUpdateSnapStateAction = { + type: `SnapController:updateSnapState`; + handler: SnapController['updateSnapState']; +}; + +/** + * Clears the state of the snap with the given id. + * This is distinct from the state MetaMask uses to manage snaps. + * + * @param snapId - The id of the Snap whose state should be cleared. + * @param encrypted - A flag to indicate whether to use encrypted storage or not. + */ +export type SnapControllerClearSnapStateAction = { + type: `SnapController:clearSnapState`; + handler: SnapController['clearSnapState']; +}; + +/** + * Gets the own state of the snap with the given id. + * This is distinct from the state MetaMask uses to manage snaps. + * + * @param snapId - The id of the Snap whose state to get. + * @param encrypted - A flag to indicate whether to use encrypted storage or not. + * @returns The requested snap state or null if no state exists. + */ +export type SnapControllerGetSnapStateAction = { + type: `SnapController:getSnapState`; + handler: SnapController['getSnapState']; +}; + +/** + * Gets a static auxiliary snap file in a chosen file encoding. + * + * @param snapId - The id of the Snap whose state to get. + * @param path - The path to the requested file. + * @param encoding - An optional requested file encoding. + * @returns The file requested in the chosen file encoding or null if the file is not found. + */ +export type SnapControllerGetSnapFileAction = { + type: `SnapController:getSnapFile`; + handler: SnapController['getSnapFile']; +}; + +/** + * Determine if a given Snap ID supports a given minimum version of the Snaps platform + * by inspecting the platformVersion in the Snap manifest. + * + * @param snapId - The Snap ID. + * @param version - The version. + * @returns True if the platform version is equal or greater to the passed version, false otherwise. + */ +export type SnapControllerIsMinimumPlatformVersionAction = { + type: `SnapController:isMinimumPlatformVersion`; + handler: SnapController['isMinimumPlatformVersion']; +}; + +/** + * Completely clear the controller's state: delete all associated data, + * handlers, event listeners, and permissions; tear down all snap providers. + * Also re-initializes the controller after clearing the state. + */ +export type SnapControllerClearStateAction = { + type: `SnapController:clearState`; + handler: SnapController['clearState']; +}; + +/** + * Removes the given snap from state, and clears all associated handlers + * and listeners. + * + * @param snapId - The id of the Snap. + * @returns A promise that resolves once the snap has been removed. + */ +export type SnapControllerRemoveSnapAction = { + type: `SnapController:removeSnap`; + handler: SnapController['removeSnap']; +}; + +/** + * Stops the given snaps, removes them from state, and clears all associated + * permissions, handlers, and listeners. + * + * @param snapIds - The ids of the Snaps. + */ +export type SnapControllerRemoveSnapsAction = { + type: `SnapController:removeSnaps`; + handler: SnapController['removeSnaps']; +}; + +/** + * Removes a snap's permission (caveat) from the specified subject. + * + * @param origin - The origin from which to remove the snap. + * @param snapId - The id of the snap to remove. + */ +export type SnapControllerRemoveSnapFromSubjectAction = { + type: `SnapController:removeSnapFromSubject`; + handler: SnapController['removeSnapFromSubject']; +}; + +/** + * Checks if a list of permissions are dynamic and allowed to be revoked, if they are they will all be revoked. + * + * @param snapId - The snap ID. + * @param permissionNames - The names of the permissions. + * @throws If non-dynamic permissions are passed. + */ +export type SnapControllerRevokeDynamicSnapPermissionsAction = { + type: `SnapController:revokeDynamicSnapPermissions`; + handler: SnapController['revokeDynamicSnapPermissions']; +}; + +/** + * Handles incrementing the activeReferences counter. + * + * @param snapId - The snap id of the snap that was referenced. + */ +export type SnapControllerIncrementActiveReferencesAction = { + type: `SnapController:incrementActiveReferences`; + handler: SnapController['incrementActiveReferences']; +}; + +/** + * Handles decrement the activeReferences counter. + * + * @param snapId - The snap id of the snap that was referenced.. + */ +export type SnapControllerDecrementActiveReferencesAction = { + type: `SnapController:decrementActiveReferences`; + handler: SnapController['decrementActiveReferences']; +}; + +/** + * Gets all snaps in their truncated format. + * + * @returns All installed snaps in their truncated format. + */ +export type SnapControllerGetAllSnapsAction = { + type: `SnapController:getAllSnaps`; + handler: SnapController['getAllSnaps']; +}; + +/** + * Gets all runnable snaps. + * + * @returns All runnable snaps. + */ +export type SnapControllerGetRunnableSnapsAction = { + type: `SnapController:getRunnableSnaps`; + handler: SnapController['getRunnableSnaps']; +}; + +/** + * Gets the serialized permitted snaps of the given origin, if any. + * + * @param origin - The origin whose permitted snaps to retrieve. + * @returns The serialized permitted snaps for the origin. + */ +export type SnapControllerGetPermittedSnapsAction = { + type: `SnapController:getPermittedSnaps`; + handler: SnapController['getPermittedSnaps']; +}; + +/** + * Installs the snaps requested by the given origin, returning the snap + * object if the origin is permitted to install it, and an authorization error + * otherwise. + * + * @param origin - The origin that requested to install the snaps. + * @param requestedSnaps - The snaps to install. + * @returns An object of snap ids and snap objects, or errors if a + * snap couldn't be installed. + */ +export type SnapControllerInstallSnapsAction = { + type: `SnapController:installSnaps`; + handler: SnapController['installSnaps']; +}; + +export type SnapControllerDestroyAction = { + type: `SnapController:destroy`; + handler: SnapController['destroy']; +}; + +/** + * Passes a JSON-RPC request object to the RPC handler function of a snap. + * + * @param options - A bag of options. + * @param options.snapId - The ID of the recipient snap. + * @param options.origin - The origin of the RPC request. + * @param options.handler - The handler to trigger on the snap for the request. + * @param options.request - The JSON-RPC request object. + * @returns The result of the JSON-RPC request. + */ +export type SnapControllerHandleRequestAction = { + type: `SnapController:handleRequest`; + handler: SnapController['handleRequest']; +}; + +/** + * Set the active state of the client. This will trigger the `onActive` or + * `onInactive` lifecycle hooks for all Snaps. + * + * @param active - A boolean indicating whether the client is active or not. + */ +export type SnapControllerSetClientActiveAction = { + type: `SnapController:setClientActive`; + handler: SnapController['setClientActive']; +}; + +/** + * Union of all SnapController action types. + */ +export type SnapControllerMethodActions = + | SnapControllerInitAction + | SnapControllerUpdateRegistryAction + | SnapControllerStartSnapAction + | SnapControllerEnableSnapAction + | SnapControllerDisableSnapAction + | SnapControllerStopSnapAction + | SnapControllerStopAllSnapsAction + | SnapControllerIsSnapRunningAction + | SnapControllerHasSnapAction + | SnapControllerGetSnapAction + | SnapControllerGetSnapExpectAction + | SnapControllerGetTruncatedSnapAction + | SnapControllerGetTruncatedSnapExpectAction + | SnapControllerUpdateSnapStateAction + | SnapControllerClearSnapStateAction + | SnapControllerGetSnapStateAction + | SnapControllerGetSnapFileAction + | SnapControllerIsMinimumPlatformVersionAction + | SnapControllerClearStateAction + | SnapControllerRemoveSnapAction + | SnapControllerRemoveSnapsAction + | SnapControllerRemoveSnapFromSubjectAction + | SnapControllerRevokeDynamicSnapPermissionsAction + | SnapControllerIncrementActiveReferencesAction + | SnapControllerDecrementActiveReferencesAction + | SnapControllerGetAllSnapsAction + | SnapControllerGetRunnableSnapsAction + | SnapControllerGetPermittedSnapsAction + | SnapControllerInstallSnapsAction + | SnapControllerDestroyAction + | SnapControllerHandleRequestAction + | SnapControllerSetClientActiveAction; diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.tsx b/packages/snaps-controllers/src/snaps/SnapController.test.tsx index cfc481a078..1020ced1a6 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.tsx +++ b/packages/snaps-controllers/src/snaps/SnapController.test.tsx @@ -106,7 +106,7 @@ import { approvalControllerMock, DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, ExecutionEnvironmentStub, - getControllerMessenger, + getRootMessenger, getNodeEES, getNodeEESMessenger, getPersistedSnapsState, @@ -177,7 +177,7 @@ describe('SnapController', () => { }), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); const result = await snapController.handleRequest({ @@ -198,7 +198,7 @@ describe('SnapController', () => { }); it('adds a snap and uses its JSON-RPC API', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const executionEnvironmentStub = new ExecutionEnvironmentStub( getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; @@ -213,7 +213,7 @@ describe('SnapController', () => { executionEnvironmentStub, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); const result = await snapController.handleRequest({ @@ -233,7 +233,7 @@ describe('SnapController', () => { }); it('passes endowments to a snap when executing it', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -252,7 +252,7 @@ describe('SnapController', () => { }, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); @@ -321,7 +321,7 @@ describe('SnapController', () => { const { rootMessenger } = options; const [snapController, service] = await getSnapControllerWithEES(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); // defer @@ -354,7 +354,7 @@ describe('SnapController', () => { }), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); await snapController.handleRequest({ @@ -371,14 +371,14 @@ describe('SnapController', () => { await delay(100); - expect(snapController.isRunning(snap.id)).toBe(false); + expect(snapController.isSnapRunning(snap.id)).toBe(false); snapController.destroy(); await service.terminateAllSnaps(); }); it('terminates a snap even if connection to worker has failed', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const [snapController, service] = await getSnapControllerWithEES( getSnapControllerOptions({ @@ -398,7 +398,7 @@ describe('SnapController', () => { }, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); // @ts-expect-error `maxRequestTime` is a private property. @@ -440,7 +440,7 @@ describe('SnapController', () => { }), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -463,7 +463,7 @@ describe('SnapController', () => { }), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -489,7 +489,7 @@ describe('SnapController', () => { }); it('includes the initialConnections data in the approval requestState when installing a Snap', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -535,7 +535,7 @@ describe('SnapController', () => { }); it('includes the initialConnections data in the requestState when updating a Snap without pre-existing connections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -596,7 +596,7 @@ describe('SnapController', () => { }); it('includes the initialConnections data in the requestState when updating a Snap with pre-existing connections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -645,6 +645,7 @@ describe('SnapController', () => { state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ + // @ts-expect-error: Partial mock. manifest: { initialConnections: { 'https://snaps.metamask.io': {}, @@ -693,7 +694,7 @@ describe('SnapController', () => { }); it('includes the initialConnections data in the requestState when updating a Snap with pre-existing connections where some are revoked', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); // Simulate all permissions being revoked. rootMessenger.registerActionHandler( @@ -723,6 +724,7 @@ describe('SnapController', () => { state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ + // @ts-expect-error: Partial mock. manifest: { initialConnections: { 'https://snaps.metamask.io': {}, @@ -1045,7 +1047,7 @@ describe('SnapController', () => { }); it('throws an error if snap is not on allowlist and allowlisting is required but resolve succeeds', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const controller = await getSnapController( @@ -1074,7 +1076,7 @@ describe('SnapController', () => { }); it('throws an error if the registry is unavailable and allowlisting is required but resolve succeeds', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const controller = await getSnapController( @@ -1138,7 +1140,7 @@ describe('SnapController', () => { }); it('resolves to allowlisted version when allowlisting is required', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const { manifest, sourceCode, svgIcon } = @@ -1171,14 +1173,14 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '^1.0.0' }, }); - expect(controller.get(MOCK_SNAP_ID)?.version).toBe('1.1.0'); + expect(controller.getSnap(MOCK_SNAP_ID)?.version).toBe('1.1.0'); expect(registry.resolveVersion).toHaveBeenCalled(); controller.destroy(); }); it('does not use registry resolving when allowlist is not required', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const controller = await getSnapController( @@ -1211,7 +1213,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '>0.9.0 <1.1.0' }, }); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); expect(newSnap).toStrictEqual(getSnapObject()); expect(options.messenger.call).toHaveBeenCalledTimes(1); @@ -1226,7 +1228,7 @@ describe('SnapController', () => { }); it('fails to install snap if user rejects installation', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, detectSnapLocation: loopbackDetect(), @@ -1297,7 +1299,7 @@ describe('SnapController', () => { MOCK_SNAP_ID, ); - expect(controller.get(MOCK_SNAP_ID)).toBeUndefined(); + expect(controller.getSnap(MOCK_SNAP_ID)).toBeUndefined(); expect(options.messenger.publish).not.toHaveBeenCalledWith( 'SnapController:snapUninstalled', @@ -1308,7 +1310,7 @@ describe('SnapController', () => { }); it('removes a snap that errors during installation after being added', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, detectSnapLocation: loopbackDetect(), @@ -1384,7 +1386,7 @@ describe('SnapController', () => { }), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ @@ -1456,7 +1458,7 @@ describe('SnapController', () => { }); it('times out an RPC request that takes too long', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, idleTimeCheckInterval: 30000, @@ -1468,7 +1470,7 @@ describe('SnapController', () => { }); const snapController = await getSnapController(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( 'ExecutionService:handleRpcRequest', @@ -1517,7 +1519,7 @@ describe('SnapController', () => { }); const snapController = await getSnapController(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -1530,7 +1532,7 @@ describe('SnapController', () => { }); it('uses the execution timeout specified by the snap', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, idleTimeCheckInterval: 30000, @@ -1542,7 +1544,7 @@ describe('SnapController', () => { }); const snapController = await getSnapController(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( 'ExecutionService:handleRpcRequest', @@ -1637,7 +1639,7 @@ describe('SnapController', () => { setupSnapProvider, ); const [snapController] = await getSnapControllerWithEES(options, service); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -1712,7 +1714,7 @@ describe('SnapController', () => { setupSnapProvider, ); const [snapController] = await getSnapControllerWithEES(options, service); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -1766,7 +1768,7 @@ describe('SnapController', () => { }); const [snapController] = await getSnapControllerWithEES(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); const results = (await Promise.allSettled([ snapController.handleRequest({ @@ -1830,7 +1832,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: {}, }); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -1862,7 +1864,7 @@ describe('SnapController', () => { // This test also ensures that we do not throw "Premature close" it('throws if the execution environment fails', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, state: { snaps: getPersistedSnapsState() }, @@ -1933,7 +1935,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: {}, }); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -1979,7 +1981,7 @@ describe('SnapController', () => { `, }); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const [snapController, service] = await getSnapControllerWithEES( getSnapControllerOptions({ maxRequestTime: 50, @@ -1997,14 +1999,14 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: {}, }); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect(snapController.state.snaps[snap.id].status).toBe('running'); // @ts-expect-error Accessing protected value. const originalTerminateFunction = service.terminateJob.bind(service); - let promise: Promise; + let promise: Promise = Promise.resolve(); // Cause a request at termination time. // @ts-expect-error Accessing protected value. @@ -2054,7 +2056,7 @@ describe('SnapController', () => { module.exports.onRpcRequest = () => 'foo bar'; `; - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2073,7 +2075,7 @@ describe('SnapController', () => { }); const [snapController, service] = await getSnapControllerWithEES(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -2117,7 +2119,7 @@ describe('SnapController', () => { }); it(`shouldn't time out a long running snap on start up`, async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2134,7 +2136,7 @@ describe('SnapController', () => { async () => await sleep(300), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); const startPromise = snapController.startSnap(snap.id); const timeoutPromise = sleep(50).then(() => true); @@ -2148,7 +2150,7 @@ describe('SnapController', () => { }); it('removes a snap that is stopped without errors', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2166,7 +2168,7 @@ describe('SnapController', () => { getNodeEESMessenger(options.rootMessenger), ) as unknown as NodeThreadExecutionService, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( 'ExecutionService:handleRpcRequest', @@ -2218,7 +2220,7 @@ describe('SnapController', () => { }); it('clears encrypted state of Snaps when the client is locked', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const state = { myVariable: 1 }; @@ -2306,7 +2308,7 @@ describe('SnapController', () => { )( 'throws if the snap does not have permission for the handler', async (handler) => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2322,7 +2324,7 @@ describe('SnapController', () => { () => ({}), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ snapId: snap.id, @@ -2341,7 +2343,7 @@ describe('SnapController', () => { ); it('does not throw if the snap uses a permitted handler', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2357,7 +2359,7 @@ describe('SnapController', () => { () => false, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2375,7 +2377,7 @@ describe('SnapController', () => { }); it('allows MetaMask to send a JSON-RPC request', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2408,7 +2410,7 @@ describe('SnapController', () => { () => undefined, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2422,7 +2424,7 @@ describe('SnapController', () => { }); it('allows MetaMask to send a keyring request', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2455,7 +2457,7 @@ describe('SnapController', () => { () => undefined, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2469,7 +2471,7 @@ describe('SnapController', () => { }); it('allows a website origin if it is in the `allowedOrigins` list', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2502,7 +2504,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2516,7 +2518,7 @@ describe('SnapController', () => { }); it('allows a website origin if it is in the `allowedOrigins` list for keyring requests', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2549,7 +2551,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2563,7 +2565,7 @@ describe('SnapController', () => { }); it('allows a website origin if `dapps` is `true`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2597,7 +2599,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2611,7 +2613,7 @@ describe('SnapController', () => { }); it('allows a Snap origin if it is in the `allowedOrigins` list', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2644,7 +2646,7 @@ describe('SnapController', () => { () => MOCK_SNAP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2658,7 +2660,7 @@ describe('SnapController', () => { }); it('allows a Snap origin if `snaps` is `true`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2692,7 +2694,7 @@ describe('SnapController', () => { () => MOCK_SNAP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2719,7 +2721,7 @@ describe('SnapController', () => { ])( 'throws if the origin is not in the `allowedOrigins` list (%p)', async (value: RpcOrigins) => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2750,7 +2752,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ snapId: snap.id, @@ -2767,7 +2769,7 @@ describe('SnapController', () => { ); it('ensures onboarding has completed before processing requests', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const { promise, resolve } = createDeferredPromise(); const ensureOnboardingComplete = jest.fn().mockReturnValue(promise); @@ -2786,7 +2788,7 @@ describe('SnapController', () => { const initPromise = options.messenger.call('SnapController:init'); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); const requestPromise = snapController.handleRequest({ snapId: snap.id, @@ -2820,7 +2822,7 @@ describe('SnapController', () => { }); it('throws if the snap does not have permission to handle JSON-RPC requests from dapps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -2844,7 +2846,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ snapId: snap.id, @@ -2860,7 +2862,7 @@ describe('SnapController', () => { }); it('throws if the snap does not have permission to handle JSON-RPC requests from snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -2884,7 +2886,7 @@ describe('SnapController', () => { () => MOCK_SNAP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ snapId: snap.id, @@ -2900,7 +2902,7 @@ describe('SnapController', () => { }); it('throws if the website origin is not in the `allowedOrigins` list for keyring requests', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -2933,7 +2935,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ snapId: snap.id, @@ -2949,7 +2951,7 @@ describe('SnapController', () => { }); it('injects context into onUserInput', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -3021,7 +3023,7 @@ describe('SnapController', () => { }); it('calls `SnapInterfaceController:setInterfaceDisplayed` if the response includes content', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -3078,7 +3080,7 @@ describe('SnapController', () => { }); it('throws if onTransaction handler returns a phishing link', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3143,7 +3145,7 @@ describe('SnapController', () => { }); it('throws if onTransaction returns an invalid value', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3204,7 +3206,7 @@ describe('SnapController', () => { }); it("doesn't throw if onTransaction return value is valid", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3263,7 +3265,7 @@ describe('SnapController', () => { }); it('throws if onTransaction return value is an invalid id', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3317,7 +3319,7 @@ describe('SnapController', () => { }); it("doesn't throw if onTransaction return value is an id", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3355,6 +3357,7 @@ describe('SnapController', () => { rootMessenger.registerActionHandler( 'SnapInterfaceController:getInterface', + // @ts-expect-error: Partial mock. () => ({ snapId: MOCK_SNAP_ID, content: foo, state: {} }), ); @@ -3376,7 +3379,7 @@ describe('SnapController', () => { }); it('throws if onSignature handler returns a phishing link', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3441,7 +3444,7 @@ describe('SnapController', () => { }); it('throws if onSignature returns an invalid value', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3502,7 +3505,7 @@ describe('SnapController', () => { }); it('throws if onSignature return value is an invalid id', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3556,7 +3559,7 @@ describe('SnapController', () => { }); it("doesn't throw if onSignature return value is valid", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3616,7 +3619,7 @@ describe('SnapController', () => { }); it(`doesn't throw if onTransaction handler returns null`, async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3668,7 +3671,7 @@ describe('SnapController', () => { }); it(`doesn't throw if onSignature handler returns null`, async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3720,7 +3723,7 @@ describe('SnapController', () => { }); it('throws if onHomePage handler returns a phishing link', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3785,7 +3788,7 @@ describe('SnapController', () => { }); it('throws if onHomePage return value is an invalid id', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3839,7 +3842,7 @@ describe('SnapController', () => { }); it("doesn't throw if onHomePage return value is valid", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3898,7 +3901,7 @@ describe('SnapController', () => { }); it('throws if onSettingsPage handler returns a phishing link', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3963,7 +3966,7 @@ describe('SnapController', () => { }); it('throws if onSettingsPage return value is an invalid id', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4017,7 +4020,7 @@ describe('SnapController', () => { }); it("doesn't throw if onSettingsPage return value is valid", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4076,7 +4079,7 @@ describe('SnapController', () => { }); it('throws if onNameLookup returns an invalid value', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4133,7 +4136,7 @@ describe('SnapController', () => { }); it("doesn't throw if onNameLookup return value is valid", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4195,7 +4198,7 @@ describe('SnapController', () => { }); it(`doesn't throw if onNameLookup handler returns null`, async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4248,7 +4251,7 @@ describe('SnapController', () => { describe('onAssetsLookup', () => { it('throws if `onAssetsLookup` handler returns an invalid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4310,7 +4313,7 @@ describe('SnapController', () => { }); it('filters out assets that are out of scope for `onAssetsLookup`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4386,7 +4389,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetsLookup` returns a valid response for fungible assets', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4478,7 +4481,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetsLookup` returns a valid response for non-fungible assets', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4600,7 +4603,7 @@ describe('SnapController', () => { describe('onAssetsConversion', () => { it('throws if `onAssetsConversion` handler returns an invalid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4662,7 +4665,7 @@ describe('SnapController', () => { }); it('filters out assets that are out of scope for `onAssetsConversion`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4736,7 +4739,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetsConversion` returns a valid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4821,7 +4824,7 @@ describe('SnapController', () => { describe('onAssetsMarketData', () => { it('throws if `onAssetsMarketData` handler returns an invalid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4883,7 +4886,7 @@ describe('SnapController', () => { }); it('filters out assets that are out of scope for `onAssetsMarketData`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4956,7 +4959,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetsMarketData` returns a valid response for fungible assets', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5041,7 +5044,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetsMarketData` returns a valid response for non-fungible assets', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5165,7 +5168,7 @@ describe('SnapController', () => { describe('onAssetHistoricalPrice', () => { it('throws if `onAssetHistoricalPrice` handler returns an invalid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5227,7 +5230,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetHistoricalPrice` returns a valid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5304,7 +5307,7 @@ describe('SnapController', () => { describe('onClientRequest', () => { it('returns the value when `onClientRequest` returns a valid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5347,7 +5350,7 @@ describe('SnapController', () => { }); it('throws if the origin is not "metamask"', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5391,7 +5394,7 @@ describe('SnapController', () => { describe('getRpcRequestHandler', () => { it('handlers populate the "jsonrpc" property if missing', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -5640,7 +5643,7 @@ describe('SnapController', () => { }); it('reinstalls local snaps even if they are already installed (already stopped)', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapObject = getPersistedSnapObject({ id: MOCK_LOCAL_SNAP_ID, }); @@ -5780,7 +5783,7 @@ describe('SnapController', () => { }); it('reinstalls local snaps even if they are already installed (running)', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const version = '0.0.1'; const newVersion = '0.0.2'; @@ -5822,7 +5825,7 @@ describe('SnapController', () => { await snapController.installSnaps(MOCK_ORIGIN, { [MOCK_LOCAL_SNAP_ID]: {}, }); - expect(snapController.isRunning(MOCK_LOCAL_SNAP_ID)).toBe(true); + expect(snapController.isSnapRunning(MOCK_LOCAL_SNAP_ID)).toBe(true); const result = await snapController.installSnaps(MOCK_ORIGIN, { [MOCK_LOCAL_SNAP_ID]: {}, @@ -6022,7 +6025,7 @@ describe('SnapController', () => { }); it('does not get stuck when re-installing a local snap that fails to install', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapObject = getPersistedSnapObject({ id: MOCK_LOCAL_SNAP_ID, }); @@ -6133,7 +6136,7 @@ describe('SnapController', () => { }); it('grants connection permission to initialConnections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -6192,7 +6195,7 @@ describe('SnapController', () => { }); it('updates existing caveats to satisfy initialConnections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const initialConnections = { 'npm:filsnap': {}, @@ -6241,7 +6244,7 @@ describe('SnapController', () => { }); it('supports preinstalled snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); jest.spyOn(rootMessenger, 'publish'); @@ -6333,11 +6336,12 @@ describe('SnapController', () => { }); it('supports preinstalled snaps with two-way initial connections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', + // @ts-expect-error: Partial mock. (origin) => { if (origin === `${MOCK_SNAP_ID}2`) { return { @@ -6423,7 +6427,7 @@ describe('SnapController', () => { }); it('supports preinstalled snaps with initial connections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permission initially @@ -6507,7 +6511,7 @@ describe('SnapController', () => { }); it('supports preinstalled snaps when Snap installation is disabled', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permission initially @@ -6596,7 +6600,7 @@ describe('SnapController', () => { }); it('supports updating preinstalled snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); jest.spyOn(rootMessenger, 'publish'); @@ -6713,7 +6717,7 @@ describe('SnapController', () => { }); it('skips preinstalling a Snap if a newer version is already installed', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); const preinstalledSnaps = [ @@ -6746,7 +6750,7 @@ describe('SnapController', () => { }); it('supports localized preinstalled snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permission initially @@ -6854,7 +6858,7 @@ describe('SnapController', () => { }); it('disallows manual updates of preinstalled snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permissions initially @@ -6915,7 +6919,7 @@ describe('SnapController', () => { }); it('supports preinstalled Snaps specifying the hidden flag', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permission initially @@ -6963,13 +6967,13 @@ describe('SnapController', () => { snapControllerOptions, ); - expect(snapController.get(MOCK_SNAP_ID)?.hidden).toBe(true); + expect(snapController.getSnap(MOCK_SNAP_ID)?.hidden).toBe(true); snapController.destroy(); }); it('supports preinstalled Snaps specifying the hideSnapBranding flag', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permission initially @@ -7017,13 +7021,13 @@ describe('SnapController', () => { snapControllerOptions, ); - expect(snapController.get(MOCK_SNAP_ID)?.hideSnapBranding).toBe(true); + expect(snapController.getSnap(MOCK_SNAP_ID)?.hideSnapBranding).toBe(true); snapController.destroy(); }); it('recovers if preinstalled permissions are out of sync', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); const log = jest.spyOn(console, 'warn').mockImplementation(); @@ -7114,7 +7118,7 @@ describe('SnapController', () => { }); it('recovers if preinstalled permissions are out of sync when Snap has limited information', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); const log = jest.spyOn(console, 'warn').mockImplementation(); @@ -7204,7 +7208,7 @@ describe('SnapController', () => { }); it('supports onInstall for preinstalled Snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); rootMessenger.registerActionHandler( @@ -7270,7 +7274,7 @@ describe('SnapController', () => { }); it('recovers if preinstalled source code is missing', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); const log = jest.spyOn(console, 'warn').mockImplementation(); @@ -7337,7 +7341,7 @@ describe('SnapController', () => { }); it('supports onUpdate for preinstalled Snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); rootMessenger.registerActionHandler( @@ -7410,7 +7414,7 @@ describe('SnapController', () => { it('authorizes permissions needed for snaps', async () => { const manifest = getSnapManifest(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -7919,7 +7923,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -7999,7 +8003,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -8073,7 +8077,7 @@ describe('SnapController', () => { const newVersion = '1.0.2'; const newVersionRange = '>=1.0.1'; - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ manifest: getSnapManifest({ @@ -8425,7 +8429,7 @@ describe('SnapController', () => { }), ); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); let revokedConnection = false; @@ -8474,8 +8478,8 @@ describe('SnapController', () => { await controller.stopSnap(snapId1); await controller.stopSnap(snapId2); - expect(controller.get(snapId1)).toBeDefined(); - expect(controller.get(snapId2)).toBeDefined(); + expect(controller.getSnap(snapId1)).toBeDefined(); + expect(controller.getSnap(snapId2)).toBeDefined(); ( options.messenger.publish as jest.MockedFn< @@ -8493,11 +8497,11 @@ describe('SnapController', () => { expect(detect).toHaveBeenCalledTimes(5); - expect(controller.get(snapId3)).toBeUndefined(); - expect(controller.get(snapId1)?.manifest.version).toBe(oldVersion); - expect(controller.get(snapId2)?.manifest.version).toBe(oldVersion); - expect(controller.get(snapId1)?.status).toBe('stopped'); - expect(controller.get(snapId2)?.status).toBe('stopped'); + expect(controller.getSnap(snapId3)).toBeUndefined(); + expect(controller.getSnap(snapId1)?.manifest.version).toBe(oldVersion); + expect(controller.getSnap(snapId2)?.manifest.version).toBe(oldVersion); + expect(controller.getSnap(snapId1)?.status).toBe('stopped'); + expect(controller.getSnap(snapId2)?.status).toBe('stopped'); expect(options.messenger.publish).not.toHaveBeenCalledWith( 'SnapController:snapInstalled', @@ -8630,8 +8634,8 @@ describe('SnapController', () => { await controller.stopSnap(snapId1); await controller.stopSnap(snapId2); - expect(controller.get(snapId1)).toBeDefined(); - expect(controller.get(snapId2)).toBeDefined(); + expect(controller.getSnap(snapId1)).toBeDefined(); + expect(controller.getSnap(snapId2)).toBeDefined(); await expect( controller.installSnaps(MOCK_ORIGIN, { @@ -8645,9 +8649,9 @@ describe('SnapController', () => { expect(detect).toHaveBeenCalledTimes(4); - expect(controller.get(snapId3)).toBeUndefined(); - expect(controller.get(snapId1)?.manifest.version).toBe(oldVersion); - expect(controller.get(snapId2)?.manifest.version).toBe(oldVersion); + expect(controller.getSnap(snapId3)).toBeUndefined(); + expect(controller.getSnap(snapId1)?.manifest.version).toBe(oldVersion); + expect(controller.getSnap(snapId2)?.manifest.version).toBe(oldVersion); expect(listener).toHaveBeenCalledTimes(0); controller.destroy(); @@ -8780,12 +8784,12 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: {}, }); - const localSnap = snapController.getExpect(MOCK_LOCAL_SNAP_ID); + const localSnap = snapController.getSnapExpect(MOCK_LOCAL_SNAP_ID); expect(localSnap.preinstalled).toBe(true); expect(localSnap.hideSnapBranding).toBe(true); expect(localSnap.hidden).toBe(false); - const npmSnap = snapController.getExpect(MOCK_SNAP_ID); + const npmSnap = snapController.getSnapExpect(MOCK_SNAP_ID); expect(npmSnap.preinstalled).toBeUndefined(); expect(npmSnap.hideSnapBranding).toBeUndefined(); expect(npmSnap.hidden).toBeUndefined(); @@ -8819,7 +8823,7 @@ describe('SnapController', () => { ); const localSnapBeforeUpdate = - snapController.getExpect(MOCK_LOCAL_SNAP_ID); + snapController.getSnapExpect(MOCK_LOCAL_SNAP_ID); expect(localSnapBeforeUpdate.preinstalled).toBeUndefined(); expect(localSnapBeforeUpdate.hideSnapBranding).toBeUndefined(); expect(localSnapBeforeUpdate.hidden).toBeUndefined(); @@ -8828,7 +8832,7 @@ describe('SnapController', () => { [MOCK_LOCAL_SNAP_ID]: {}, }); - const localSnap = snapController.getExpect(MOCK_LOCAL_SNAP_ID); + const localSnap = snapController.getSnapExpect(MOCK_LOCAL_SNAP_ID); expect(localSnap.preinstalled).toBe(true); expect(localSnap.hideSnapBranding).toBe(true); expect(localSnap.hidden).toBe(false); @@ -8884,11 +8888,11 @@ describe('SnapController', () => { const controller = await getSnapController(options); const onSnapUpdated = jest.fn(); - const snap = controller.getExpect(MOCK_SNAP_ID); + const snap = controller.getSnapExpect(MOCK_SNAP_ID); options.messenger.subscribe('SnapController:snapUpdated', onSnapUpdated); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); await expect( controller.installSnaps(MOCK_ORIGIN, { @@ -8904,7 +8908,7 @@ describe('SnapController', () => { }); it('throws an error if the new version of the snap is blocked', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ @@ -8959,13 +8963,13 @@ describe('SnapController', () => { const controller = await getSnapController(options); const onSnapUpdated = jest.fn(); - const snap = controller.getExpect(MOCK_SNAP_ID); + const snap = controller.getSnapExpect(MOCK_SNAP_ID); options.messenger.subscribe('SnapController:snapUpdated', onSnapUpdated); const publishSpy = jest.spyOn(options.messenger, 'publish'); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); const errorMessage = `Snap "${MOCK_SNAP_ID}@${snap.version}" is already installed. Couldn't update to a version inside requested "0.9.0" range.`; @@ -9027,9 +9031,9 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.1.0' }, }); - const newSnapTruncated = controller.getTruncated(MOCK_SNAP_ID); + const newSnapTruncated = controller.getTruncatedSnap(MOCK_SNAP_ID); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); expect(result).toStrictEqual({ [MOCK_SNAP_ID]: newSnapTruncated }); expect(newSnap?.version).toBe('1.1.0'); @@ -9168,7 +9172,7 @@ describe('SnapController', () => { }), ); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -9283,7 +9287,7 @@ describe('SnapController', () => { }), ); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -9410,9 +9414,9 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.1.0' }, }); - const newSnapTruncated = controller.getTruncated(MOCK_SNAP_ID); + const newSnapTruncated = controller.getTruncatedSnap(MOCK_SNAP_ID); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); expect(result).toStrictEqual({ [MOCK_SNAP_ID]: newSnapTruncated }); expect(newSnap?.version).toBe('1.1.0'); @@ -9460,7 +9464,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.1.0' }, }); - const isRunning = controller.isRunning(MOCK_SNAP_ID); + const isRunning = controller.isSnapRunning(MOCK_SNAP_ID); expect(callActionSpy).toHaveBeenCalledTimes(15); @@ -9562,7 +9566,7 @@ describe('SnapController', () => { }); it('throws on update request denied', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ manifest: getSnapManifest({ version: '1.1.0' as SemVerVersion, @@ -9622,7 +9626,7 @@ describe('SnapController', () => { }), ).rejects.toThrow('User rejected the request.'); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); expect(newSnap?.version).toBe('1.0.0'); expect(callActionSpy).toHaveBeenCalledTimes(6); @@ -9678,7 +9682,7 @@ describe('SnapController', () => { }); it('requests approval for new and already approved permissions and revoke unused permissions', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); /* eslint-disable @typescript-eslint/naming-convention */ const initialPermissions = { @@ -9921,7 +9925,7 @@ describe('SnapController', () => { }); it('supports initialConnections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -10037,7 +10041,7 @@ describe('SnapController', () => { it('assigns the same id to the approval request and the request metadata', async () => { expect.assertions(4); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); /* eslint-disable @typescript-eslint/naming-convention */ const initialPermissions = { @@ -10191,7 +10195,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.2.0' }, }); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); expect(newSnap?.version).toBe('1.2.0'); controller.destroy(); @@ -10410,10 +10414,10 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(false); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(false); snapController.enableSnap(MOCK_SNAP_ID); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(true); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(true); expect(options.messenger.publish).toHaveBeenCalledWith( 'SnapController:snapEnabled', getTruncatedSnap(), @@ -10460,10 +10464,10 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(true); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(true); await snapController.disableSnap(MOCK_SNAP_ID); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(false); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(false); expect(options.messenger.publish).toHaveBeenCalledWith( 'SnapController:snapDisabled', getTruncatedSnap({ enabled: false }), @@ -10481,14 +10485,14 @@ describe('SnapController', () => { }), ); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(true); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(true); await snapController.startSnap(MOCK_SNAP_ID); - expect(snapController.isRunning(MOCK_SNAP_ID)).toBe(true); + expect(snapController.isSnapRunning(MOCK_SNAP_ID)).toBe(true); await snapController.disableSnap(MOCK_SNAP_ID); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(false); - expect(snapController.isRunning(MOCK_SNAP_ID)).toBe(false); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(false); + expect(snapController.isSnapRunning(MOCK_SNAP_ID)).toBe(false); snapController.destroy(); }); @@ -10505,7 +10509,7 @@ describe('SnapController', () => { describe('updateRegistry', () => { it('updates the registry database', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const snapController = await getSnapController( @@ -10524,7 +10528,7 @@ describe('SnapController', () => { }); it('blocks snaps as expected', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const mockSnapA = getMockSnapData({ @@ -10575,12 +10579,12 @@ describe('SnapController', () => { }); // A is blocked and disabled - expect(snapController.get(mockSnapA.id)?.blocked).toBe(true); - expect(snapController.get(mockSnapA.id)?.enabled).toBe(false); + expect(snapController.getSnap(mockSnapA.id)?.blocked).toBe(true); + expect(snapController.getSnap(mockSnapA.id)?.enabled).toBe(false); // B is unblocked and enabled - expect(snapController.get(mockSnapB.id)?.blocked).toBe(false); - expect(snapController.get(mockSnapB.id)?.enabled).toBe(true); + expect(snapController.getSnap(mockSnapB.id)?.blocked).toBe(false); + expect(snapController.getSnap(mockSnapB.id)?.enabled).toBe(true); expect(publishMock).toHaveBeenLastCalledWith( 'SnapController:snapBlocked', @@ -10595,7 +10599,7 @@ describe('SnapController', () => { }); it('stops running snaps when they are blocked', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const mockSnap = getMockSnapData({ @@ -10622,15 +10626,15 @@ describe('SnapController', () => { await waitForStateChange(options.messenger); // The snap is blocked, disabled, and stopped - expect(snapController.get(mockSnap.id)?.blocked).toBe(true); - expect(snapController.get(mockSnap.id)?.enabled).toBe(false); - expect(snapController.isRunning(mockSnap.id)).toBe(false); + expect(snapController.getSnap(mockSnap.id)?.blocked).toBe(true); + expect(snapController.getSnap(mockSnap.id)?.enabled).toBe(false); + expect(snapController.isSnapRunning(mockSnap.id)).toBe(false); snapController.destroy(); }); it('unblocks snaps as expected', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const mockSnapA = getMockSnapData({ @@ -10660,12 +10664,12 @@ describe('SnapController', () => { const snapController = await getSnapController(options); // A is blocked and disabled - expect(snapController.get(mockSnapA.id)?.blocked).toBe(true); - expect(snapController.get(mockSnapA.id)?.enabled).toBe(false); + expect(snapController.getSnap(mockSnapA.id)?.blocked).toBe(true); + expect(snapController.getSnap(mockSnapA.id)?.enabled).toBe(false); // B is unblocked and enabled - expect(snapController.get(mockSnapB.id)?.blocked).toBe(false); - expect(snapController.get(mockSnapB.id)?.enabled).toBe(true); + expect(snapController.getSnap(mockSnapB.id)?.blocked).toBe(false); + expect(snapController.getSnap(mockSnapB.id)?.enabled).toBe(true); // Indicate that both snaps A and B are unblocked, and update blocked // states. @@ -10676,12 +10680,12 @@ describe('SnapController', () => { await snapController.updateRegistry(); // A is unblocked, but still disabled - expect(snapController.get(mockSnapA.id)?.blocked).toBe(false); - expect(snapController.get(mockSnapA.id)?.enabled).toBe(false); + expect(snapController.getSnap(mockSnapA.id)?.blocked).toBe(false); + expect(snapController.getSnap(mockSnapA.id)?.enabled).toBe(false); // B remains unblocked and enabled - expect(snapController.get(mockSnapB.id)?.blocked).toBe(false); - expect(snapController.get(mockSnapB.id)?.enabled).toBe(true); + expect(snapController.getSnap(mockSnapB.id)?.blocked).toBe(false); + expect(snapController.getSnap(mockSnapB.id)?.enabled).toBe(true); expect(publishMock).toHaveBeenLastCalledWith( 'SnapController:snapUnblocked', @@ -10693,7 +10697,7 @@ describe('SnapController', () => { it('updating blocked snaps does not throw if a snap is removed while fetching the blocklist', async () => { const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const mockSnap = getMockSnapData({ @@ -10728,7 +10732,7 @@ describe('SnapController', () => { await updateBlockList; // The snap was removed, no errors were thrown - expect(snapController.has(mockSnap.id)).toBe(false); + expect(snapController.hasSnap(mockSnap.id)).toBe(false); expect(consoleErrorSpy).not.toHaveBeenCalled(); snapController.destroy(); @@ -10736,7 +10740,7 @@ describe('SnapController', () => { it('logs but does not throw unexpected errors while blocking', async () => { const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const mockSnap = getMockSnapData({ @@ -10766,8 +10770,8 @@ describe('SnapController', () => { await snapController.updateRegistry(); // A is blocked and disabled - expect(snapController.get(mockSnap.id)?.blocked).toBe(true); - expect(snapController.get(mockSnap.id)?.enabled).toBe(false); + expect(snapController.getSnap(mockSnap.id)?.blocked).toBe(true); + expect(snapController.getSnap(mockSnap.id)?.enabled).toBe(false); expect(consoleErrorSpy).toHaveBeenCalledTimes(1); expect(consoleErrorSpy).toHaveBeenCalledWith( @@ -10779,7 +10783,7 @@ describe('SnapController', () => { }); it('updates preinstalled Snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); // Simulate previous permissions, some of which will be removed @@ -10834,7 +10838,7 @@ describe('SnapController', () => { await waitForStateChange(options.messenger); await sleep(100); - const updatedSnap = snapController.get(snapId); + const updatedSnap = snapController.getSnap(snapId); assert(updatedSnap); expect(updatedSnap.version).toStrictEqual(updateVersion); @@ -10875,7 +10879,7 @@ describe('SnapController', () => { }); it('does not update preinstalled Snaps when the feature flag is off', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const snapId = 'npm:@metamask/jsx-example-snap' as SnapId; @@ -10903,7 +10907,7 @@ describe('SnapController', () => { await snapController.updateRegistry(); - const snap = snapController.get(snapId); + const snap = snapController.getSnap(snapId); assert(snap); expect(snap.version).toStrictEqual(mockSnap.version); @@ -10940,7 +10944,7 @@ describe('SnapController', () => { const callActionSpy = jest.spyOn(messenger, 'call'); - expect(snapController.has(MOCK_SNAP_ID)).toBe(true); + expect(snapController.hasSnap(MOCK_SNAP_ID)).toBe(true); const requestPromise = snapController.handleRequest({ snapId: MOCK_SNAP_ID, @@ -10953,13 +10957,13 @@ describe('SnapController', () => { await waitForStateChange(messenger); - expect(snapController.isRunning(MOCK_SNAP_ID)).toBe(true); + expect(snapController.isSnapRunning(MOCK_SNAP_ID)).toBe(true); await new Promise((resolve) => setTimeout(resolve, 100)); await snapController.clearState(); - expect(snapController.has(MOCK_SNAP_ID)).toBe(false); + expect(snapController.hasSnap(MOCK_SNAP_ID)).toBe(false); expect(callActionSpy).toHaveBeenCalledWith( 'ExecutionService:terminateSnap', @@ -11010,7 +11014,7 @@ describe('SnapController', () => { }, ]; - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:revokeAllPermissions', @@ -11050,16 +11054,16 @@ describe('SnapController', () => { const callActionSpy = jest.spyOn(options.messenger, 'call'); - expect(snapController.has(MOCK_SNAP_ID)).toBe(true); - expect(snapController.has(preinstalledSnapId)).toBe(true); + expect(snapController.hasSnap(MOCK_SNAP_ID)).toBe(true); + expect(snapController.hasSnap(preinstalledSnapId)).toBe(true); await snapController.startSnap(MOCK_SNAP_ID); await snapController.startSnap(preinstalledSnapId); await snapController.clearState(); - expect(snapController.has(MOCK_SNAP_ID)).toBe(false); - expect(snapController.has(preinstalledSnapId)).toBe(true); + expect(snapController.hasSnap(MOCK_SNAP_ID)).toBe(false); + expect(snapController.hasSnap(preinstalledSnapId)).toBe(true); expect(callActionSpy).toHaveBeenCalledWith( 'ExecutionService:terminateSnap', @@ -11104,7 +11108,7 @@ describe('SnapController', () => { describe('SnapController actions', () => { describe('SnapController:init', () => { it('populates `isReady`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -11122,15 +11126,13 @@ describe('SnapController', () => { }); it('calls `onStart` for all Snaps with the `endowment:lifecycle-hooks` permission', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', ( origin, - ): SubjectPermissions< - ValidPermission - > => { + ): SubjectPermissions> => { if (origin === MOCK_SNAP_ID) { return { [SnapEndowments.LifecycleHooks]: @@ -11205,7 +11207,7 @@ describe('SnapController', () => { .spyOn(console, 'error') .mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -11261,9 +11263,9 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - const getSpy = jest.spyOn(snapController, 'get'); + const getSpy = jest.spyOn(snapController, 'getSnap'); const result = options.messenger.call( - 'SnapController:get', + 'SnapController:getSnap', MOCK_SNAP_ID, ); @@ -11303,7 +11305,7 @@ describe('SnapController', () => { it('should track event for allowed handler', async () => { const mockTrackEvent = jest.fn(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const executionEnvironmentStub = new ExecutionEnvironmentStub( getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; @@ -11319,7 +11321,7 @@ describe('SnapController', () => { executionEnvironmentStub, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); await snapController.handleRequest({ @@ -11353,7 +11355,7 @@ describe('SnapController', () => { it('should not track event for disallowed handler', async () => { const mockTrackEvent = jest.fn(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -11386,7 +11388,7 @@ describe('SnapController', () => { executionEnvironmentStub, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); await snapController.handleRequest({ @@ -11411,7 +11413,7 @@ describe('SnapController', () => { const mockTrackEvent = jest.fn().mockImplementation(() => { throw error; }); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const executionEnvironmentStub = new ExecutionEnvironmentStub( getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; @@ -11427,7 +11429,7 @@ describe('SnapController', () => { executionEnvironmentStub, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); await snapController.handleRequest({ @@ -11453,7 +11455,7 @@ describe('SnapController', () => { it('should not track event for preinstalled snap', async () => { const mockTrackEvent = jest.fn(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const executionEnvironmentStub = new ExecutionEnvironmentStub( getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; @@ -11471,7 +11473,7 @@ describe('SnapController', () => { executionEnvironmentStub, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); await snapController.handleRequest({ @@ -11888,8 +11890,8 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - const hasSpy = jest.spyOn(snapController, 'has'); - const result = options.messenger.call('SnapController:has', id); + const hasSpy = jest.spyOn(snapController, 'hasSnap'); + const result = options.messenger.call('SnapController:hasSnap', id); expect(hasSpy).toHaveBeenCalledTimes(1); expect(result).toBe(true); @@ -12267,7 +12269,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - options.messenger.call('SnapController:enable', mockSnap.id); + options.messenger.call('SnapController:enableSnap', mockSnap.id); expect(snapController.state.snaps[mockSnap.id].enabled).toBe(true); snapController.destroy(); @@ -12290,7 +12292,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - await options.messenger.call('SnapController:disable', mockSnap.id); + await options.messenger.call('SnapController:disableSnap', mockSnap.id); expect(snapController.state.snaps[mockSnap.id].enabled).toBe(false); snapController.destroy(); @@ -12313,7 +12315,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - await options.messenger.call('SnapController:remove', mockSnap.id); + await options.messenger.call('SnapController:removeSnap', mockSnap.id); expect(snapController.state.snaps[mockSnap.id]).toBeUndefined(); snapController.destroy(); @@ -12322,7 +12324,7 @@ describe('SnapController', () => { describe('SnapController:getPermitted', () => { it('calls SnapController.getPermittedSnaps()', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const mockSnap = getMockSnapData({ id: MOCK_SNAP_ID, origin: MOCK_ORIGIN, @@ -12338,7 +12340,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); const result = options.messenger.call( - 'SnapController:getPermitted', + 'SnapController:getPermittedSnaps', mockSnap.origin, ); expect(result).toStrictEqual({ @@ -12364,7 +12366,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - const result = options.messenger.call('SnapController:getAll'); + const result = options.messenger.call('SnapController:getAllSnaps'); expect(result).toStrictEqual([getTruncatedSnap()]); snapController.destroy(); @@ -12412,7 +12414,7 @@ describe('SnapController', () => { .mockImplementation(); const snaps = { [MOCK_SNAP_ID]: {} }; - await options.messenger.call('SnapController:install', 'foo', snaps); + await options.messenger.call('SnapController:installSnaps', 'foo', snaps); expect(installSnapsSpy).toHaveBeenCalledTimes(1); expect(installSnapsSpy).toHaveBeenCalledWith('foo', snaps); @@ -12451,7 +12453,7 @@ describe('SnapController', () => { const callActionSpy = jest.spyOn(options.messenger, 'call'); options.messenger.call( - 'SnapController:disconnectOrigin', + 'SnapController:removeSnapFromSubject', MOCK_ORIGIN, MOCK_SNAP_ID, ); @@ -12487,7 +12489,7 @@ describe('SnapController', () => { const callActionSpy = jest.spyOn(options.messenger, 'call'); options.messenger.call( - 'SnapController:revokeDynamicPermissions', + 'SnapController:revokeDynamicSnapPermissions', MOCK_SNAP_ID, ['endowment:caip25'], ); @@ -12505,9 +12507,9 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - expect(() => + expect(async () => options.messenger.call( - 'SnapController:revokeDynamicPermissions', + 'SnapController:revokeDynamicSnapPermissions', MOCK_SNAP_ID, ['snap_notify'], ), @@ -12545,7 +12547,7 @@ describe('SnapController', () => { expect( await options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './src/foo.json', ), @@ -12595,7 +12597,7 @@ describe('SnapController', () => { expect( await options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './src/foo.json', AuxiliaryFileEncoding.Hex, @@ -12621,7 +12623,7 @@ describe('SnapController', () => { expect( await options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './foo.json', ), @@ -12668,7 +12670,7 @@ describe('SnapController', () => { await expect( options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './src/foo.json', AuxiliaryFileEncoding.Hex, @@ -12683,7 +12685,7 @@ describe('SnapController', () => { describe('SnapController:snapInstalled', () => { it('calls the `onInstall` lifecycle hook', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12714,6 +12716,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12750,7 +12753,7 @@ describe('SnapController', () => { }); it('does not call the `onInstall` lifecycle hook if the snap does not have the `endowment:lifecycle-hooks` permission', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12770,6 +12773,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12795,7 +12799,7 @@ describe('SnapController', () => { it('logs an error if the lifecycle hook call fails', async () => { const log = jest.spyOn(console, 'error').mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12818,6 +12822,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12832,7 +12837,7 @@ describe('SnapController', () => { describe('SnapController:snapUpdated', () => { it('calls the `onUpdate` lifecycle hook', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12864,6 +12869,7 @@ describe('SnapController', () => { getTruncatedSnap(), '0.9.0', MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12900,7 +12906,7 @@ describe('SnapController', () => { }); it('does not call the `onUpdate` lifecycle hook if the snap does not have the `endowment:lifecycle-hooks` permission', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12920,6 +12926,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12945,7 +12952,7 @@ describe('SnapController', () => { it('logs an error if the lifecycle hook call fails', async () => { const log = jest.spyOn(console, 'error').mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12969,6 +12976,7 @@ describe('SnapController', () => { getTruncatedSnap(), '0.9.0', MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -13112,7 +13120,7 @@ describe('SnapController', () => { describe('SnapController:setClientActive', () => { it('calls the `onActive` lifecycle hook for all Snaps when called with `true`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -13199,7 +13207,7 @@ describe('SnapController', () => { }); it('calls the `onInactive` lifecycle hook for all Snaps when called with `false`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -13445,11 +13453,11 @@ describe('SnapController', () => { // create a new controller const secondSnapController = await getSnapController(options); - expect(secondSnapController.isRunning(id)).toBe(false); + expect(secondSnapController.isSnapRunning(id)).toBe(false); await secondSnapController.startSnap(id); expect(secondSnapController.state.snaps[id]).toBeDefined(); - expect(secondSnapController.isRunning(id)).toBe(true); + expect(secondSnapController.isSnapRunning(id)).toBe(true); firstSnapController.destroy(); secondSnapController.destroy(); }); diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 344fe8c012..17b058b1aa 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -171,6 +171,7 @@ import type { } from './registry'; import { SnapsRegistryStatus } from './registry'; import { getRunnableSnaps } from './selectors'; +import type { SnapControllerMethodActions } from './SnapController-method-action-types'; import { Timer } from './Timer'; import { forceStrict, validateMachine } from '../fsm'; import type { @@ -205,6 +206,41 @@ import { export const controllerName = 'SnapController'; +export const MESSENGER_EXPOSED_METHODS = [ + 'init', + 'updateRegistry', + 'startSnap', + 'enableSnap', + 'disableSnap', + 'stopSnap', + 'stopAllSnaps', + 'isSnapRunning', + 'hasSnap', + 'getSnap', + 'getSnapExpect', + 'getTruncatedSnap', + 'getTruncatedSnapExpect', + 'updateSnapState', + 'clearSnapState', + 'getSnapState', + 'getSnapFile', + 'isMinimumPlatformVersion', + 'clearState', + 'removeSnap', + 'removeSnaps', + 'removeSnapFromSubject', + 'revokeDynamicSnapPermissions', + 'incrementActiveReferences', + 'decrementActiveReferences', + 'getAllSnaps', + 'getRunnableSnaps', + 'getPermittedSnaps', + 'installSnaps', + 'destroy', + 'handleRequest', + 'setClientActive', +] as const; + // TODO: Figure out how to name these export const SNAP_APPROVAL_INSTALL = 'wallet_installSnap'; export const SNAP_APPROVAL_UPDATE = 'wallet_updateSnap'; @@ -359,188 +395,21 @@ type PendingApproval = { // Controller Messenger Actions -/** - * Initialise the SnapController. This should be called after all controllers - * are created. - */ -export type SnapControllerInitAction = { - type: `${typeof controllerName}:init`; - handler: SnapController['init']; -}; - -/** - * Gets the specified Snap from state. - */ -export type GetSnap = { - type: `${typeof controllerName}:get`; - handler: SnapController['get']; -}; - -/** - * Handles sending an inbound request to a snap and returns its result. - */ -export type HandleSnapRequest = { - type: `${typeof controllerName}:handleRequest`; - handler: SnapController['handleRequest']; -}; - -/** - * Gets the specified Snap's persisted state. - */ -export type GetSnapState = { - type: `${typeof controllerName}:getSnapState`; - handler: SnapController['getSnapState']; -}; - -/** - * Checks if the specified snap exists in state. - */ -export type HasSnap = { - type: `${typeof controllerName}:has`; - handler: SnapController['has']; -}; - -/** - * Updates the specified Snap's persisted state. - */ -export type UpdateSnapState = { - type: `${typeof controllerName}:updateSnapState`; - handler: SnapController['updateSnapState']; -}; - -/** - * Clears the specified Snap's persisted state. - */ -export type ClearSnapState = { - type: `${typeof controllerName}:clearSnapState`; - handler: SnapController['clearSnapState']; -}; - -/** - * Checks all installed snaps against the blocklist. - */ -export type UpdateRegistry = { - type: `${typeof controllerName}:updateRegistry`; - handler: SnapController['updateRegistry']; -}; - -export type EnableSnap = { - type: `${typeof controllerName}:enable`; - handler: SnapController['enableSnap']; -}; - -export type DisableSnap = { - type: `${typeof controllerName}:disable`; - handler: SnapController['disableSnap']; -}; - -export type RemoveSnap = { - type: `${typeof controllerName}:remove`; - handler: SnapController['removeSnap']; -}; - -export type GetPermittedSnaps = { - type: `${typeof controllerName}:getPermitted`; - handler: SnapController['getPermittedSnaps']; -}; - -export type GetAllSnaps = { - type: `${typeof controllerName}:getAll`; - handler: SnapController['getAllSnaps']; -}; - -export type GetRunnableSnaps = { - type: `${typeof controllerName}:getRunnableSnaps`; - handler: SnapController['getRunnableSnaps']; -}; - -export type StopAllSnaps = { - type: `${typeof controllerName}:stopAllSnaps`; - handler: SnapController['stopAllSnaps']; -}; - -export type IncrementActiveReferences = { - type: `${typeof controllerName}:incrementActiveReferences`; - handler: SnapController['incrementActiveReferences']; -}; - -export type DecrementActiveReferences = { - type: `${typeof controllerName}:decrementActiveReferences`; - handler: SnapController['decrementActiveReferences']; -}; - -export type InstallSnaps = { - type: `${typeof controllerName}:install`; - handler: SnapController['installSnaps']; -}; - -export type DisconnectOrigin = { - type: `${typeof controllerName}:disconnectOrigin`; - handler: SnapController['removeSnapFromSubject']; -}; - -export type RevokeDynamicPermissions = { - type: `${typeof controllerName}:revokeDynamicPermissions`; - handler: SnapController['revokeDynamicSnapPermissions']; -}; - -export type GetSnapFile = { - type: `${typeof controllerName}:getFile`; - handler: SnapController['getSnapFile']; -}; - -export type IsMinimumPlatformVersion = { - type: `${typeof controllerName}:isMinimumPlatformVersion`; - handler: SnapController['isMinimumPlatformVersion']; -}; - -export type SetClientActive = { - type: `${typeof controllerName}:setClientActive`; - handler: SnapController['setClientActive']; -}; - export type SnapControllerGetStateAction = ControllerGetStateAction< typeof controllerName, SnapControllerState >; export type SnapControllerActions = - | SnapControllerInitAction - | ClearSnapState - | GetSnap - | GetSnapState - | HandleSnapRequest - | HasSnap - | UpdateRegistry - | UpdateSnapState - | EnableSnap - | DisableSnap - | RemoveSnap - | GetPermittedSnaps - | InstallSnaps - | GetAllSnaps - | GetRunnableSnaps - | IncrementActiveReferences - | DecrementActiveReferences - | DisconnectOrigin - | RevokeDynamicPermissions - | GetSnapFile | SnapControllerGetStateAction - | StopAllSnaps - | IsMinimumPlatformVersion - | SetClientActive; + | SnapControllerMethodActions; // Controller Messenger Events -export type SnapStateChange = { - type: `${typeof controllerName}:stateChange`; - payload: [SnapControllerState, Patch[]]; -}; - /** * Emitted when an installed snap has been blocked. */ -export type SnapBlocked = { +export type SnapControllerSnapBlockedEvent = { type: `${typeof controllerName}:snapBlocked`; payload: [snapId: string, blockedSnapInfo?: BlockReason]; }; @@ -548,7 +417,7 @@ export type SnapBlocked = { /** * Emitted when a snap installation or update is started. */ -export type SnapInstallStarted = { +export type SnapControllerSnapInstallStartedEvent = { type: `${typeof controllerName}:snapInstallStarted`; payload: [snapId: SnapId, origin: string, isUpdate: boolean]; }; @@ -556,7 +425,7 @@ export type SnapInstallStarted = { /** * Emitted when a snap installation or update failed. */ -export type SnapInstallFailed = { +export type SnapControllerSnapInstallFailedEvent = { type: `${typeof controllerName}:snapInstallFailed`; payload: [snapId: SnapId, origin: string, isUpdate: boolean, error: string]; }; @@ -565,7 +434,7 @@ export type SnapInstallFailed = { * Emitted when a snap has been started after being added and authorized during * installation. */ -export type SnapInstalled = { +export type SnapControllerSnapInstalledEvent = { type: `${typeof controllerName}:snapInstalled`; payload: [snap: TruncatedSnap, origin: string, preinstalled: boolean]; }; @@ -573,7 +442,7 @@ export type SnapInstalled = { /** * Emitted when a snap that has previously been fully installed, is uninstalled. */ -export type SnapUninstalled = { +export type SnapControllerSnapUninstalledEvent = { type: `${typeof controllerName}:snapUninstalled`; payload: [snap: TruncatedSnap]; }; @@ -581,7 +450,7 @@ export type SnapUninstalled = { /** * Emitted when an installed snap has been unblocked. */ -export type SnapUnblocked = { +export type SnapControllerSnapUnblockedEvent = { type: `${typeof controllerName}:snapUnblocked`; payload: [snapId: string]; }; @@ -589,7 +458,7 @@ export type SnapUnblocked = { /** * Emitted when a snap is updated. */ -export type SnapUpdated = { +export type SnapControllerSnapUpdatedEvent = { type: `${typeof controllerName}:snapUpdated`; payload: [ snap: TruncatedSnap, @@ -602,7 +471,7 @@ export type SnapUpdated = { /** * Emitted when a snap is rolled back. */ -export type SnapRolledback = { +export type SnapControllerSnapRolledbackEvent = { type: `${typeof controllerName}:snapRolledback`; payload: [snap: TruncatedSnap, failedVersion: string]; }; @@ -611,7 +480,7 @@ export type SnapRolledback = { * Emitted when a Snap is terminated. This is different from the snap being * stopped as it can also be triggered when a snap fails initialization. */ -export type SnapTerminated = { +export type SnapControllerSnapTerminatedEvent = { type: `${typeof controllerName}:snapTerminated`; payload: [snap: TruncatedSnap]; }; @@ -620,7 +489,7 @@ export type SnapTerminated = { * Emitted when a Snap is enabled by a user. * This is not emitted by default when installing a snap. */ -export type SnapEnabled = { +export type SnapControllerSnapEnabledEvent = { type: `${typeof controllerName}:snapEnabled`; payload: [snap: TruncatedSnap]; }; @@ -628,7 +497,7 @@ export type SnapEnabled = { /** * Emitted when a Snap is disabled by a user. */ -export type SnapDisabled = { +export type SnapControllerSnapDisabledEvent = { type: `${typeof controllerName}:snapDisabled`; payload: [snap: TruncatedSnap]; }; @@ -641,24 +510,23 @@ export type SnapControllerStateChangeEvent = ControllerStateChangeEvent< SnapControllerState >; -type KeyringControllerLock = { +type KeyringControllerLockEvent = { type: 'KeyringController:lock'; payload: []; }; export type SnapControllerEvents = - | SnapBlocked - | SnapInstalled - | SnapUninstalled - | SnapInstallStarted - | SnapInstallFailed - | SnapStateChange - | SnapUnblocked - | SnapUpdated - | SnapRolledback - | SnapTerminated - | SnapEnabled - | SnapDisabled + | SnapControllerSnapBlockedEvent + | SnapControllerSnapInstalledEvent + | SnapControllerSnapUninstalledEvent + | SnapControllerSnapInstallStartedEvent + | SnapControllerSnapInstallFailedEvent + | SnapControllerSnapUnblockedEvent + | SnapControllerSnapUpdatedEvent + | SnapControllerSnapRolledbackEvent + | SnapControllerSnapTerminatedEvent + | SnapControllerSnapEnabledEvent + | SnapControllerSnapDisabledEvent | SnapControllerStateChangeEvent; export type AllowedActions = @@ -693,12 +561,12 @@ export type AllowedActions = export type AllowedEvents = | ExecutionServiceEvents - | SnapInstalled - | SnapUpdated - | KeyringControllerLock + | SnapControllerSnapInstalledEvent + | SnapControllerSnapUpdatedEvent + | KeyringControllerLockEvent | SnapsRegistryStateChangeEvent; -type SnapControllerMessenger = Messenger< +export type SnapControllerMessenger = Messenger< typeof controllerName, SnapControllerActions | AllowedActions, SnapControllerEvents | AllowedEvents @@ -1138,7 +1006,10 @@ export class SnapController extends BaseController< ); this.#initializeStateMachine(); - this.#registerMessageHandlers(); + this.messenger.registerMethodActionHandlers( + this, + MESSENGER_EXPOSED_METHODS, + ); Object.values(this.state?.snaps ?? {}).forEach((snap) => this.#setupRuntime(snap.id), @@ -1178,7 +1049,7 @@ export class SnapController extends BaseController< // In the future, side-effects could be added to the machine during transitions. #initializeStateMachine() { const disableGuard = ({ snapId }: StatusContext) => { - return this.getExpect(snapId).enabled; + return this.getSnapExpect(snapId).enabled; }; const statusConfig: StateMachine.Config< @@ -1235,125 +1106,6 @@ export class SnapController extends BaseController< validateMachine(this.#statusMachine); } - /** - * Constructor helper for registering the controller's messaging system - * actions. - */ - #registerMessageHandlers(): void { - this.messenger.registerActionHandler( - `${controllerName}:init`, - async (...args) => this.init(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:clearSnapState`, - (...args) => this.clearSnapState(...args), - ); - - this.messenger.registerActionHandler(`${controllerName}:get`, (...args) => - this.get(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:getSnapState`, - async (...args) => this.getSnapState(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:handleRequest`, - async (...args) => this.handleRequest(...args), - ); - - this.messenger.registerActionHandler(`${controllerName}:has`, (...args) => - this.has(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:updateRegistry`, - async () => this.updateRegistry(), - ); - - this.messenger.registerActionHandler( - `${controllerName}:updateSnapState`, - async (...args) => this.updateSnapState(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:enable`, - (...args) => this.enableSnap(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:disable`, - async (...args) => this.disableSnap(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:remove`, - async (...args) => this.removeSnap(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:getPermitted`, - (...args) => this.getPermittedSnaps(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:install`, - async (...args) => this.installSnaps(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:getAll`, - (...args) => this.getAllSnaps(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:getRunnableSnaps`, - (...args) => this.getRunnableSnaps(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:incrementActiveReferences`, - (...args) => this.incrementActiveReferences(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:decrementActiveReferences`, - (...args) => this.decrementActiveReferences(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:disconnectOrigin`, - (...args) => this.removeSnapFromSubject(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:revokeDynamicPermissions`, - (...args) => this.revokeDynamicSnapPermissions(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:getFile`, - async (...args) => this.getSnapFile(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:stopAllSnaps`, - async (...args) => this.stopAllSnaps(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:isMinimumPlatformVersion`, - (...args) => this.isMinimumPlatformVersion(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:setClientActive`, - (...args) => this.setClientActive(...args), - ); - } - /** * Initialise the SnapController. * @@ -1393,7 +1145,7 @@ export class SnapController extends BaseController< hidden, hideSnapBranding, } of preinstalledSnaps) { - const existingSnap = this.get(snapId); + const existingSnap = this.getSnap(snapId); const isAlreadyInstalled = existingSnap !== undefined; const isUpdate = isAlreadyInstalled && gtVersion(manifest.version, existingSnap.version); @@ -1505,7 +1257,7 @@ export class SnapController extends BaseController< if (isUpdate) { this.messenger.publish( 'SnapController:snapUpdated', - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), existingSnap.version, METAMASK_ORIGIN, true, @@ -1513,7 +1265,7 @@ export class SnapController extends BaseController< } else if (!isMissingSource) { this.messenger.publish( 'SnapController:snapInstalled', - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), METAMASK_ORIGIN, true, ); @@ -1684,7 +1436,7 @@ export class SnapController extends BaseController< /** * Blocks an installed snap and prevents it from being started again. Emits - * {@link SnapBlocked}. Does nothing if the snap is not installed. + * {@link SnapControllerSnapBlockedEvent}. Does nothing if the snap is not installed. * * @param snapId - The snap to block. * @param blockedSnapInfo - Information detailing why the snap is blocked. @@ -1693,7 +1445,7 @@ export class SnapController extends BaseController< snapId: SnapId, blockedSnapInfo?: BlockReason, ): Promise { - if (!this.has(snapId)) { + if (!this.hasSnap(snapId)) { return; } @@ -1720,13 +1472,13 @@ export class SnapController extends BaseController< /** * Unblocks a snap so that it can be enabled and started again. Emits - * {@link SnapUnblocked}. Does nothing if the snap is not installed or already + * {@link SnapControllerSnapUnblockedEvent}. Does nothing if the snap is not installed or already * unblocked. * * @param snapId - The id of the snap to unblock. */ #unblockSnap(snapId: SnapId) { - if (!this.has(snapId) || !this.state.snaps[snapId].blocked) { + if (!this.hasSnap(snapId) || !this.state.snaps[snapId].blocked) { return; } @@ -1918,7 +1670,7 @@ export class SnapController extends BaseController< * @param snapId - The id of the Snap to enable. */ enableSnap(snapId: SnapId): void { - this.getExpect(snapId); + this.getSnapExpect(snapId); if (this.state.snaps[snapId].blocked) { throw new Error(`Snap "${snapId}" is blocked and cannot be enabled.`); @@ -1930,7 +1682,7 @@ export class SnapController extends BaseController< this.messenger.publish( 'SnapController:snapEnabled', - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), ); } @@ -1941,7 +1693,7 @@ export class SnapController extends BaseController< * @returns A promise that resolves once the snap has been disabled. */ async disableSnap(snapId: SnapId): Promise { - if (!this.has(snapId)) { + if (!this.hasSnap(snapId)) { throw new Error(`Snap "${snapId}" not found.`); } @@ -1949,13 +1701,13 @@ export class SnapController extends BaseController< state.snaps[snapId].enabled = false; }); - if (this.isRunning(snapId)) { + if (this.isSnapRunning(snapId)) { await this.stopSnap(snapId, SnapStatusEvents.Stop); } this.messenger.publish( 'SnapController:snapDisabled', - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), ); } @@ -1990,7 +1742,7 @@ export class SnapController extends BaseController< runtime.stopPromise = promise; try { - if (this.isRunning(snapId)) { + if (this.isSnapRunning(snapId)) { await this.#terminateSnap(snapId); } } finally { @@ -1999,7 +1751,7 @@ export class SnapController extends BaseController< runtime.pendingInboundRequests = []; runtime.pendingOutboundRequests = 0; runtime.stopPromise = null; - if (this.isRunning(snapId)) { + if (this.isSnapRunning(snapId)) { this.#transition(snapId, statusEvent); } resolve(); @@ -2019,7 +1771,7 @@ export class SnapController extends BaseController< | SnapStatusEvents.Crash = SnapStatusEvents.Stop, ): Promise { const snaps = Object.values(this.state.snaps).filter((snap) => - this.isRunning(snap.id), + this.isSnapRunning(snap.id), ); const promises = snaps.map(async (snap) => this.stopSnap(snap.id, statusEvent), @@ -2049,7 +1801,7 @@ export class SnapController extends BaseController< this.messenger.publish( 'SnapController:snapTerminated', - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), ); } @@ -2060,8 +1812,8 @@ export class SnapController extends BaseController< * @param snapId - The id of the Snap to check. * @returns `true` if the snap is running, otherwise `false`. */ - isRunning(snapId: SnapId): boolean { - return this.getExpect(snapId).status === 'running'; + isSnapRunning(snapId: SnapId): boolean { + return this.getSnapExpect(snapId).status === 'running'; } /** @@ -2070,8 +1822,8 @@ export class SnapController extends BaseController< * @param snapId - The id of the Snap to check for. * @returns `true` if the snap exists in the controller state, otherwise `false`. */ - has(snapId: SnapId): boolean { - return Boolean(this.get(snapId)); + hasSnap(snapId: SnapId): boolean { + return Boolean(this.getSnap(snapId)); } /** @@ -2082,7 +1834,7 @@ export class SnapController extends BaseController< * @param snapId - The id of the Snap to get. * @returns The entire snap object from the controller state. */ - get(snapId: string): Snap | undefined { + getSnap(snapId: string): Snap | undefined { return this.state.snaps[snapId as SnapId]; } @@ -2091,13 +1843,13 @@ export class SnapController extends BaseController< * This should not be used if the snap is to be serializable, as e.g. * the snap sourceCode may be quite large. * - * @see {@link SnapController.get} + * @see {@link SnapController.getSnap} * @throws {@link Error}. If the snap doesn't exist * @param snapId - The id of the snap to get. * @returns The entire snap object. */ - getExpect(snapId: SnapId): Snap { - const snap = this.get(snapId); + getSnapExpect(snapId: SnapId): Snap { + const snap = this.getSnap(snapId); assert(snap !== undefined, `Snap "${snapId}" not found.`); return snap; } @@ -2110,8 +1862,8 @@ export class SnapController extends BaseController< * @returns A truncated version of the snap state, that is less expensive to serialize. */ // TODO(ritave): this.get returns undefined, this.getTruncated returns null - getTruncated(snapId: SnapId): TruncatedSnap | null { - const snap = this.get(snapId); + getTruncatedSnap(snapId: SnapId): TruncatedSnap | null { + const snap = this.getSnap(snapId); return snap ? truncateSnap(snap) : null; } @@ -2123,8 +1875,8 @@ export class SnapController extends BaseController< * @param snapId - The id of the snap to get. * @returns A truncated version of the snap state, that is less expensive to serialize. */ - getTruncatedExpect(snapId: SnapId): TruncatedSnap { - return truncateSnap(this.getExpect(snapId)); + getTruncatedSnapExpect(snapId: SnapId): TruncatedSnap { + return truncateSnap(this.getSnapExpect(snapId)); } /** @@ -2430,7 +2182,7 @@ export class SnapController extends BaseController< path: string, encoding: AuxiliaryFileEncoding = AuxiliaryFileEncoding.Base64, ): Promise { - const snap = this.getExpect(snapId); + const snap = this.getSnapExpect(snapId); const normalizedPath = normalizeRelative(path); const value = snap.auxiliaryFiles?.find( (file) => file.path === normalizedPath, @@ -2459,7 +2211,7 @@ export class SnapController extends BaseController< * @returns True if the platform version is equal or greater to the passed version, false otherwise. */ isMinimumPlatformVersion(snapId: SnapId, version: SemVerVersion): boolean { - const snap = this.getExpect(snapId); + const snap = this.getSnapExpect(snapId); const { platformVersion } = snap.manifest; @@ -2522,14 +2274,14 @@ export class SnapController extends BaseController< } snapIds.forEach((snapId) => { - const snap = this.getExpect(snapId); + const snap = this.getSnapExpect(snapId); assert(snap.removable !== false, `${snapId} is not removable.`); }); await Promise.all( snapIds.map(async (snapId) => { - const snap = this.getExpect(snapId); - const truncated = this.getTruncatedExpect(snapId); + const snap = this.getSnapExpect(snapId); + const truncated = this.getTruncatedSnapExpect(snapId); // Disable the snap and revoke all of its permissions before deleting // it. This ensures that the snap will not be restarted or otherwise // affect the host environment while we are deleting it. @@ -2777,8 +2529,8 @@ export class SnapController extends BaseController< )?.value ?? {}; return Object.keys(snaps).reduce( (permittedSnaps, snapId) => { - const snap = this.get(snapId); - const truncatedSnap = this.getTruncated(snapId as SnapId); + const snap = this.getSnap(snapId); + const truncatedSnap = this.getTruncatedSnap(snapId as SnapId); if (truncatedSnap && snap?.status !== SnapStatus.Installing) { permittedSnaps[snapId] = truncatedSnap; @@ -2840,10 +2592,10 @@ export class SnapController extends BaseController< // Existing snaps may need to be updated, unless they should be re-installed (e.g. local snaps) // Everything else is treated as an install - const isUpdate = this.has(snapId) && !location.shouldAlwaysReload; + const isUpdate = this.hasSnap(snapId) && !location.shouldAlwaysReload; if (isUpdate && this.#isValidUpdate(snapId, version)) { - const existingSnap = this.getExpect(snapId); + const existingSnap = this.getSnapExpect(snapId); pendingUpdates.push({ snapId, oldVersion: existingSnap.version }); let rollbackSnapshot = this.#getRollbackSnapshot(snapId); if (rollbackSnapshot === undefined) { @@ -2868,7 +2620,7 @@ export class SnapController extends BaseController< pendingInstalls.forEach((snapId) => this.messenger.publish( `SnapController:snapInstalled`, - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), origin, false, ), @@ -2877,7 +2629,7 @@ export class SnapController extends BaseController< pendingUpdates.forEach(({ snapId, oldVersion }) => this.messenger.publish( `SnapController:snapUpdated`, - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), oldVersion, origin, false, @@ -2886,7 +2638,9 @@ export class SnapController extends BaseController< snapIds.forEach((snapId) => this.#rollbackSnapshots.delete(snapId)); } catch (error) { - const installed = pendingInstalls.filter((snapId) => this.has(snapId)); + const installed = pendingInstalls.filter((snapId) => + this.hasSnap(snapId), + ); await this.removeSnaps(installed); const snapshottedSnaps = [...this.#rollbackSnapshots.keys()]; const snapsToRollback = pendingUpdates @@ -2916,7 +2670,7 @@ export class SnapController extends BaseController< location: SnapLocation, versionRange: SemVerRange, ): Promise { - const existingSnap = this.getTruncated(snapId); + const existingSnap = this.getTruncatedSnap(snapId); // For devX we always re-install local snaps. if (existingSnap && !location.shouldAlwaysReload) { @@ -2948,7 +2702,7 @@ export class SnapController extends BaseController< ); // Existing snaps must be stopped before overwriting - if (existingSnap && this.isRunning(snapId)) { + if (existingSnap && this.isSnapRunning(snapId)) { await this.stopSnap(snapId, SnapStatusEvents.Stop); } @@ -2978,7 +2732,7 @@ export class SnapController extends BaseController< sourceCode, }); - const truncated = this.getTruncatedExpect(snapId); + const truncated = this.getTruncatedSnapExpect(snapId); this.#updateApproval(pendingApproval.id, { loading: false, @@ -3091,7 +2845,7 @@ export class SnapController extends BaseController< } await this.#ensureCanUsePlatform(); - const snap = this.getExpect(snapId); + const snap = this.getSnapExpect(snapId); const { preinstalled, removable, hidden, hideSnapBranding } = snap; @@ -3190,7 +2944,7 @@ export class SnapController extends BaseController< approvedNewPermissions = newPermissions; } - if (this.isRunning(snapId)) { + if (this.isSnapRunning(snapId)) { await this.stopSnap(snapId, SnapStatusEvents.Stop); } @@ -3245,7 +2999,7 @@ export class SnapController extends BaseController< throw new Error(`Snap ${snapId} crashed with updated source code.`); } - const truncatedSnap = this.getTruncatedExpect(snapId); + const truncatedSnap = this.getTruncatedSnapExpect(snapId); if (pendingApproval) { this.#updateApproval(pendingApproval.id, { @@ -3357,7 +3111,7 @@ export class SnapController extends BaseController< async #startSnap(snapData: { snapId: SnapId; sourceCode: string }) { const { snapId } = snapData; - if (this.isRunning(snapId)) { + if (this.isSnapRunning(snapId)) { throw new Error(`Snap "${snapId}" is already started.`); } @@ -3733,7 +3487,7 @@ export class SnapController extends BaseController< }: SnapRpcHookArgs & { snapId: SnapId }): Promise { await this.#ensureCanUsePlatform(); - const snap = this.get(snapId); + const snap = this.getSnap(snapId); assert( snap, @@ -3836,7 +3590,7 @@ export class SnapController extends BaseController< await runtime.stopPromise; } - if (!this.isRunning(snapId)) { + if (!this.isSnapRunning(snapId)) { if (!runtime.startPromise) { runtime.startPromise = this.startSnap(snapId); } @@ -3869,7 +3623,7 @@ export class SnapController extends BaseController< if (result === hasTimedOut) { const stopping = - runtime.stopPromise !== null || !this.isRunning(snapId); + runtime.stopPromise !== null || !this.isSnapRunning(snapId); throw new Error( stopping ? `${snapId} was stopped and the request was cancelled. This is likely because the Snap crashed.` @@ -3925,7 +3679,8 @@ export class SnapController extends BaseController< const [jsonRpcError, handled] = unwrapError(error); - const stopping = runtime.stopPromise !== null || !this.isRunning(snapId); + const stopping = + runtime.stopPromise !== null || !this.isSnapRunning(snapId); if (!handled) { if (!stopping) { @@ -4299,7 +4054,7 @@ export class SnapController extends BaseController< runtime.lastRequest = Date.now(); } - const snap = this.get(snapId); + const snap = this.getSnap(snapId); if (isTrackableHandler(handlerType) && !snap?.preinstalled) { try { @@ -4372,7 +4127,7 @@ export class SnapController extends BaseController< await this.stopSnap(snapId, SnapStatusEvents.Stop); // Always set to stopped even if it wasn't running initially - if (this.get(snapId)?.status !== SnapStatus.Stopped) { + if (this.getSnap(snapId)?.status !== SnapStatus.Stopped) { this.#transition(snapId, SnapStatusEvents.Stop); } @@ -4396,7 +4151,7 @@ export class SnapController extends BaseController< // Reset snap status, as we may have been in another state when we stored state patches // But now we are 100% in a stopped state - if (this.get(snapId)?.status !== SnapStatus.Stopped) { + if (this.getSnap(snapId)?.status !== SnapStatus.Stopped) { this.update((state) => { state.snaps[snapId].status = SnapStatus.Stopped; }); @@ -4416,7 +4171,7 @@ export class SnapController extends BaseController< previousInitialConnections ?? {}, ); - const truncatedSnap = this.getTruncatedExpect(snapId); + const truncatedSnap = this.getTruncatedSnapExpect(snapId); this.messenger.publish( 'SnapController:snapRolledback', @@ -4454,7 +4209,7 @@ export class SnapController extends BaseController< return; } - const snap = this.get(snapId); + const snap = this.getSnap(snapId); const interpreter = interpret(this.#statusMachine); interpreter.start({ context: { snapId }, @@ -4678,7 +4433,7 @@ export class SnapController extends BaseController< * @returns `true` if validation checks pass and `false` if they do not. */ #isValidUpdate(snapId: SnapId, newVersionRange: SemVerRange): boolean { - const existingSnap = this.getExpect(snapId); + const existingSnap = this.getSnapExpect(snapId); if (satisfiesVersionRange(existingSnap.version, newVersionRange)) { return false; diff --git a/packages/snaps-controllers/src/snaps/index.ts b/packages/snaps-controllers/src/snaps/index.ts index 27c90e70fd..99f15f7766 100644 --- a/packages/snaps-controllers/src/snaps/index.ts +++ b/packages/snaps-controllers/src/snaps/index.ts @@ -1,5 +1,61 @@ export * from './constants'; export * from './location'; -export * from './SnapController'; +export type { + SnapControllerGetStateAction, + SnapControllerSnapBlockedEvent, + SnapControllerSnapDisabledEvent, + SnapControllerSnapEnabledEvent, + SnapControllerSnapInstalledEvent, + SnapControllerSnapInstallFailedEvent, + SnapControllerSnapInstallStartedEvent, + SnapControllerSnapRolledbackEvent, + SnapControllerSnapTerminatedEvent, + SnapControllerSnapUnblockedEvent, + SnapControllerSnapUninstalledEvent, + SnapControllerSnapUpdatedEvent, + SnapControllerState, + SnapControllerStateChangeEvent, + SnapError, + SnapRuntimeData, + PendingRequest, + PreinstalledSnapFile, + PreinstalledSnap, + PersistedSnapControllerState, +} from './SnapController'; +export { SnapController } from './SnapController'; +export type { + SnapControllerInitAction, + SnapControllerUpdateRegistryAction, + SnapControllerStartSnapAction, + SnapControllerEnableSnapAction, + SnapControllerDisableSnapAction, + SnapControllerStopSnapAction, + SnapControllerStopAllSnapsAction, + SnapControllerIsSnapRunningAction, + SnapControllerHasSnapAction, + SnapControllerGetSnapAction, + SnapControllerGetSnapExpectAction, + SnapControllerGetTruncatedSnapAction, + SnapControllerGetTruncatedSnapExpectAction, + SnapControllerUpdateSnapStateAction, + SnapControllerClearSnapStateAction, + SnapControllerGetSnapStateAction, + SnapControllerGetSnapFileAction, + SnapControllerIsMinimumPlatformVersionAction, + SnapControllerClearStateAction, + SnapControllerRemoveSnapAction, + SnapControllerRemoveSnapsAction, + SnapControllerRemoveSnapFromSubjectAction, + SnapControllerRevokeDynamicSnapPermissionsAction, + SnapControllerIncrementActiveReferencesAction, + SnapControllerDecrementActiveReferencesAction, + SnapControllerGetAllSnapsAction, + SnapControllerGetRunnableSnapsAction, + SnapControllerGetPermittedSnapsAction, + SnapControllerInstallSnapsAction, + SnapControllerDestroyAction, + SnapControllerHandleRequestAction, + SnapControllerSetClientActiveAction, +} from './SnapController-method-action-types'; export * from './selectors'; export * from './registry'; diff --git a/packages/snaps-controllers/src/test-utils/controller.tsx b/packages/snaps-controllers/src/test-utils/controller.tsx index d8423cc939..c370aab14e 100644 --- a/packages/snaps-controllers/src/test-utils/controller.tsx +++ b/packages/snaps-controllers/src/test-utils/controller.tsx @@ -8,6 +8,11 @@ import { isVaultUpdated, keyFromPassword, } from '@metamask/browser-passworder'; +import type { + MessengerActions, + MessengerEvents, + MockAnyNamespace, +} from '@metamask/messenger'; import { Messenger } from '@metamask/messenger'; import type { Caveat, @@ -49,37 +54,28 @@ import type { Json } from '@metamask/utils'; import { MOCK_CRONJOB_PERMISSION } from './cronjob'; import { getNodeEES, getNodeEESMessenger } from './execution-environment'; import { MockSnapsRegistry } from './registry'; +import type { CronjobControllerMessenger } from '../cronjob'; +import type { SnapInsightsControllerMessenger } from '../insights'; import type { - CronjobControllerActions, - CronjobControllerEvents, -} from '../cronjob'; -import type { - SnapInsightsControllerAllowedActions, - SnapInsightsControllerAllowedEvents, -} from '../insights'; -import type { - SnapInterfaceControllerActions, - SnapInterfaceControllerAllowedActions, - SnapInterfaceControllerEvents, + SnapInterfaceControllerMessenger, StoredInterface, } from '../interface/SnapInterfaceController'; +import type { MultichainRouterMessenger } from '../multichain'; import type { - MultichainRouterActions, - MultichainRouterAllowedActions, - MultichainRouterEvents, -} from '../multichain'; -import type { AbstractExecutionService } from '../services'; + AbstractExecutionService, + ExecutionServiceMessenger, +} from '../services'; import type { - AllowedActions, - AllowedEvents, - PersistedSnapControllerState, - SnapControllerActions, - SnapControllerEvents, - SnapControllerStateChangeEvent, SnapsRegistryActions, SnapsRegistryEvents, + SnapsRegistryMessenger, } from '../snaps'; import { SnapController } from '../snaps'; +import type { + PersistedSnapControllerState, + SnapControllerMessenger, + SnapControllerStateChangeEvent, +} from '../snaps/SnapController'; import type { KeyDerivationOptions } from '../types'; import type { WebSocketServiceActions, @@ -329,11 +325,22 @@ export const MOCK_INSIGHTS_PERMISSIONS_NO_ORIGINS: Record< }, }; -export const getControllerMessenger = () => { - const messenger = new MockControllerMessenger< - SnapControllerActions | AllowedActions, - SnapControllerEvents | AllowedEvents - >(); +/** + * The type of the messenger populated with all external actions and events + * required by the controller under test. + */ +export type RootMessenger = Messenger< + MockAnyNamespace, + MessengerActions< + SnapControllerMessenger | SnapsRegistryMessenger | ExecutionServiceMessenger + >, + MessengerEvents< + SnapControllerMessenger | SnapsRegistryMessenger | ExecutionServiceMessenger + > +>; + +export const getRootMessenger = () => { + const messenger: RootMessenger = new MockControllerMessenger(); messenger.registerActionHandler( 'PermissionController:hasPermission', @@ -463,16 +470,9 @@ export const getControllerMessenger = () => { }; export const getSnapControllerMessenger = ( - messenger: ReturnType< - typeof getControllerMessenger - > = getControllerMessenger(), + messenger: RootMessenger = getRootMessenger(), ) => { - const snapControllerMessenger = new Messenger< - 'SnapController', - SnapControllerActions | AllowedActions, - SnapControllerEvents | AllowedEvents, - any - >({ + const snapControllerMessenger: SnapControllerMessenger = new Messenger({ namespace: 'SnapController', parent: messenger, }); @@ -582,10 +582,10 @@ export const getSnapControllerEncryptor = () => { export type GetSnapControllerOptionsParam = Omit< PartialSnapControllerConstructorParamsWithStorage, 'messenger' -> & { rootMessenger?: ReturnType }; +> & { rootMessenger?: ReturnType }; export const getSnapControllerOptions = ({ - rootMessenger = getControllerMessenger(), + rootMessenger = getRootMessenger(), ...opts }: GetSnapControllerOptionsParam = {}) => { const snapControllerMessenger = getSnapControllerMessenger(rootMessenger); @@ -608,7 +608,7 @@ export const getSnapControllerOptions = ({ ensureOnboardingComplete: jest.fn().mockResolvedValue(undefined), ...opts, } as SnapControllerConstructorParamsWithStorage & { - rootMessenger: ReturnType; + rootMessenger: ReturnType; }; options.state = { @@ -643,7 +643,7 @@ export const extractSourceCodeFromSnapsState = ( }; export const getStorageService = ( - messenger: ReturnType, + messenger: ReturnType, initialData?: InitialStorageData, ) => { const storageServiceMessenger = new Messenger< @@ -696,7 +696,6 @@ export const getSnapControllerWithEES = async ( init = true, ) => { const _service = - // @ts-expect-error: TODO: Investigate type mismatch. service ?? getNodeEES(getNodeEESMessenger(options.rootMessenger)); const { snaps, snapsData } = extractSourceCodeFromSnapsState( @@ -733,12 +732,16 @@ export const getPersistedSnapsState = ( }, {}); }; +type CronjobControllerRootMessenger = Messenger< + MockAnyNamespace, + MessengerActions, + MessengerEvents +>; + // Mock controller messenger for Cronjob Controller export const getRootCronjobControllerMessenger = () => { - const messenger = new MockControllerMessenger< - CronjobControllerActions | AllowedActions, - CronjobControllerEvents | AllowedEvents - >(); + const messenger: CronjobControllerRootMessenger = + new MockControllerMessenger(); jest.spyOn(messenger, 'call'); @@ -746,24 +749,16 @@ export const getRootCronjobControllerMessenger = () => { }; export const getRestrictedCronjobControllerMessenger = ( - messenger: ReturnType< - typeof getRootCronjobControllerMessenger - > = getRootCronjobControllerMessenger(), + messenger: CronjobControllerRootMessenger = getRootCronjobControllerMessenger(), mocked = true, ) => { - const cronjobControllerMessenger = new Messenger< - 'CronjobController', - CronjobControllerActions | AllowedActions, - CronjobControllerEvents | AllowedEvents, - any - >({ + const cronjobControllerMessenger: CronjobControllerMessenger = new Messenger({ namespace: 'CronjobController', parent: messenger, }); messenger.delegate({ actions: [ - 'PermissionController:hasPermission', 'PermissionController:getPermissions', 'SnapController:handleRequest', ], @@ -778,13 +773,6 @@ export const getRestrictedCronjobControllerMessenger = ( }); if (mocked) { - messenger.registerActionHandler( - 'PermissionController:hasPermission', - () => { - return true; - }, - ); - messenger.registerActionHandler( 'PermissionController:getPermissions', () => { @@ -825,12 +813,20 @@ export const getRestrictedSnapsRegistryControllerMessenger = ( >({ namespace: 'SnapsRegistry', parent: messenger }); }; +/** + * The type of the messenger populated with all external actions and events + * required by the controller under test. + */ +type SnapInterfaceControllerRootMessenger = Messenger< + MockAnyNamespace, + MessengerActions, + MessengerEvents +>; + // Mock controller messenger for Interface Controller export const getRootSnapInterfaceControllerMessenger = () => { - const messenger = new MockControllerMessenger< - SnapInterfaceControllerActions | SnapInterfaceControllerAllowedActions, - SnapInterfaceControllerEvents - >(); + const messenger: SnapInterfaceControllerRootMessenger = + new MockControllerMessenger(); jest.spyOn(messenger, 'call'); @@ -843,12 +839,8 @@ export const getRestrictedSnapInterfaceControllerMessenger = ( > = getRootSnapInterfaceControllerMessenger(), mocked = true, ) => { - const snapInterfaceControllerMessenger = new Messenger< - 'SnapInterfaceController', - SnapInterfaceControllerAllowedActions, - SnapInterfaceControllerEvents, - any - >({ namespace: 'SnapInterfaceController', parent: messenger }); + const snapInterfaceControllerMessenger: SnapInterfaceControllerMessenger = + new Messenger({ namespace: 'SnapInterfaceController', parent: messenger }); messenger.delegate({ actions: [ @@ -857,7 +849,7 @@ export const getRestrictedSnapInterfaceControllerMessenger = ( 'ApprovalController:acceptRequest', 'MultichainAssetsController:getState', 'AccountsController:getAccountByAddress', - 'SnapController:get', + 'SnapController:getSnap', 'AccountsController:getSelectedMultichainAccount', 'AccountsController:listMultichainAccounts', 'PermissionController:hasPermission', @@ -920,9 +912,12 @@ export const getRestrictedSnapInterfaceControllerMessenger = ( ], ); - messenger.registerActionHandler('SnapController:get', (snapId: string) => { - return getSnapObject({ id: snapId as SnapId }); - }); + messenger.registerActionHandler( + 'SnapController:getSnap', + (snapId: string) => { + return getSnapObject({ id: snapId as SnapId }); + }, + ); } jest.spyOn(snapInterfaceControllerMessenger, 'call'); @@ -930,12 +925,16 @@ export const getRestrictedSnapInterfaceControllerMessenger = ( return snapInterfaceControllerMessenger; }; +type RootSnapInsightsControllerMessenger = Messenger< + MockAnyNamespace, + MessengerActions, + MessengerEvents +>; + // Mock controller messenger for Insight Controller export const getRootSnapInsightsControllerMessenger = () => { - const messenger = new MockControllerMessenger< - SnapInsightsControllerAllowedActions, - SnapInsightsControllerAllowedEvents - >(); + const messenger: RootSnapInsightsControllerMessenger = + new MockControllerMessenger(); jest.spyOn(messenger, 'call'); @@ -943,16 +942,9 @@ export const getRootSnapInsightsControllerMessenger = () => { }; export const getRestrictedSnapInsightsControllerMessenger = ( - messenger: ReturnType< - typeof getRootSnapInsightsControllerMessenger - > = getRootSnapInsightsControllerMessenger(), + messenger: RootSnapInsightsControllerMessenger = getRootSnapInsightsControllerMessenger(), ) => { - const controllerMessenger = new Messenger< - 'SnapInsightsController', - SnapInsightsControllerAllowedActions, - SnapInsightsControllerAllowedEvents, - any - >({ + const controllerMessenger: SnapInsightsControllerMessenger = new Messenger({ namespace: 'SnapInsightsController', parent: messenger, }); @@ -960,7 +952,7 @@ export const getRestrictedSnapInsightsControllerMessenger = ( messenger.delegate({ actions: [ 'PermissionController:getPermissions', - 'SnapController:getAll', + 'SnapController:getAllSnaps', 'SnapController:handleRequest', 'SnapInterfaceController:deleteInterface', ], @@ -993,12 +985,15 @@ export async function waitForStateChange( }); } +type MultichainRouterRootMessenger = Messenger< + MockAnyNamespace, + MessengerActions +>; + // Mock controller messenger for Multichain Router -export const getRootMultichainRouterMessenger = () => { - const messenger = new MockControllerMessenger< - MultichainRouterActions | MultichainRouterAllowedActions, - MultichainRouterEvents - >(); +export const getMultichainRouterRootMessenger = () => { + const messenger: MultichainRouterRootMessenger = + new MockControllerMessenger(); jest.spyOn(messenger, 'call'); @@ -1006,16 +1001,12 @@ export const getRootMultichainRouterMessenger = () => { }; export const getRestrictedMultichainRouterMessenger = ( - messenger: ReturnType< - typeof getRootMultichainRouterMessenger - > = getRootMultichainRouterMessenger(), + messenger: MultichainRouterRootMessenger = getMultichainRouterRootMessenger(), ) => { - const controllerMessenger = new Messenger< - 'MultichainRouter', - MultichainRouterActions | MultichainRouterAllowedActions, - never, - any - >({ namespace: 'MultichainRouter', parent: messenger }); + const controllerMessenger: MultichainRouterMessenger = new Messenger({ + namespace: 'MultichainRouter', + parent: messenger, + }); messenger.delegate({ actions: [ diff --git a/packages/snaps-controllers/src/test-utils/execution-environment.ts b/packages/snaps-controllers/src/test-utils/execution-environment.ts index f0126f179c..575ebce491 100644 --- a/packages/snaps-controllers/src/test-utils/execution-environment.ts +++ b/packages/snaps-controllers/src/test-utils/execution-environment.ts @@ -2,10 +2,10 @@ import { JsonRpcEngine } from '@metamask/json-rpc-engine'; import { createEngineStream } from '@metamask/json-rpc-middleware-stream'; import { Messenger } from '@metamask/messenger'; import { logError, type SnapRpcHookArgs } from '@metamask/snaps-utils'; -import type { MockControllerMessenger } from '@metamask/snaps-utils/test-utils'; import { pipeline } from 'readable-stream'; import { MOCK_BLOCK_NUMBER } from './constants'; +import type { RootMessenger } from './controller'; import type { ExecutionService, ExecutionServiceActions, @@ -15,12 +15,7 @@ import type { } from '../services'; import { NodeThreadExecutionService, setupMultiplex } from '../services/node'; -export const getNodeEESMessenger = ( - messenger: MockControllerMessenger< - ExecutionServiceActions, - ExecutionServiceEvents - >, -) => { +export const getNodeEESMessenger = (messenger: RootMessenger) => { const executionServiceMessenger = new Messenger< 'ExecutionService', ExecutionServiceActions, diff --git a/packages/snaps-controllers/src/test-utils/registry.ts b/packages/snaps-controllers/src/test-utils/registry.ts index 086c3ffade..ef44f797ff 100644 --- a/packages/snaps-controllers/src/test-utils/registry.ts +++ b/packages/snaps-controllers/src/test-utils/registry.ts @@ -1,12 +1,11 @@ -import type { MockControllerMessenger } from '@metamask/snaps-utils/test-utils'; - +import type { RootMessenger } from './controller'; import type { SnapsRegistry } from '../snaps'; import { SnapsRegistryStatus } from '../snaps'; export class MockSnapsRegistry implements SnapsRegistry { readonly #messenger; - constructor(messenger: MockControllerMessenger) { + constructor(messenger: RootMessenger) { this.#messenger = messenger; this.#messenger.registerActionHandler( diff --git a/packages/snaps-controllers/src/websocket/WebSocketService.ts b/packages/snaps-controllers/src/websocket/WebSocketService.ts index 15addd7c4f..0b077e9ca0 100644 --- a/packages/snaps-controllers/src/websocket/WebSocketService.ts +++ b/packages/snaps-controllers/src/websocket/WebSocketService.ts @@ -10,10 +10,10 @@ import { assert, createDeferredPromise } from '@metamask/utils'; import { nanoid } from 'nanoid'; import type { - HandleSnapRequest, - SnapInstalled, - SnapUninstalled, - SnapUpdated, + SnapControllerHandleRequestAction, + SnapControllerSnapInstalledEvent, + SnapControllerSnapUninstalledEvent, + SnapControllerSnapUpdatedEvent, } from '../snaps'; import { METAMASK_ORIGIN } from '../snaps'; @@ -53,12 +53,12 @@ export type WebSocketServiceActions = | WebSocketServiceSendMessageAction | WebSocketServiceGetAllAction; -export type WebSocketServiceAllowedActions = HandleSnapRequest; +export type WebSocketServiceAllowedActions = SnapControllerHandleRequestAction; export type WebSocketServiceEvents = - | SnapUninstalled - | SnapUpdated - | SnapInstalled; + | SnapControllerSnapUninstalledEvent + | SnapControllerSnapUpdatedEvent + | SnapControllerSnapInstalledEvent; export type WebSocketServiceMessenger = Messenger< 'WebSocketService', diff --git a/scripts/generate-method-action-types.mts b/scripts/generate-method-action-types.mts new file mode 100644 index 0000000000..dfeb7e61f3 --- /dev/null +++ b/scripts/generate-method-action-types.mts @@ -0,0 +1,771 @@ +#!yarn tsx + +/* eslint-disable no-console */ + +import { assert, hasProperty, isObject } from '@metamask/utils'; +import { ESLint } from 'eslint'; +import * as fs from 'fs'; +import * as path from 'path'; +import ts from 'typescript'; +import yargs from 'yargs'; + +type MethodInfo = { + name: string; + jsDoc: string; + signature: string; +}; + +type ControllerInfo = { + name: string; + filePath: string; + exposedMethods: string[]; + methods: MethodInfo[]; +}; + +/** + * The parsed command-line arguments. + */ +type CommandLineArguments = { + /** + * Whether to check if the action types files are up to date. + */ + check: boolean; + /** + * Whether to fix the action types files. + */ + fix: boolean; + /** + * Optional path to a specific controller to process. + */ + controllerPath: string; +}; + +/** + * Uses `yargs` to parse the arguments given to the script. + * + * @returns The command line arguments. + */ +async function parseCommandLineArguments(): Promise { + const { + check, + fix, + path: controllerPath, + } = await yargs(process.argv.slice(2)) + .command( + '$0 [path]', + 'Generate method action types for a controller messenger', + (yargsInstance) => { + yargsInstance.positional('path', { + type: 'string', + description: 'Path to the folder where controllers are located', + default: 'src', + }); + }, + ) + .option('check', { + type: 'boolean', + description: 'Check if generated action type files are up to date', + default: false, + }) + .option('fix', { + type: 'boolean', + description: 'Generate/update action type files', + default: false, + }) + .help() + .check((argv) => { + if (!argv.check && !argv.fix) { + throw new Error('Either --check or --fix must be provided.\n'); + } + return true; + }).argv; + + return { + check, + fix, + // TypeScript doesn't narrow the type of `controllerPath` even though we defined it as a string in yargs, so we need to cast it here. + controllerPath: controllerPath as string, + }; +} + +/** + * Checks if generated action types files are up to date. + * + * @param controllers - Array of controller information objects. + * @param eslint - The ESLint instance to use for formatting. + */ +async function checkActionTypesFiles( + controllers: ControllerInfo[], + eslint: ESLint, +): Promise { + let hasErrors = false; + + // Track files that exist and their corresponding temp files + const fileComparisonJobs: { + expectedTempFile: string; + actualFile: string; + baseFileName: string; + }[] = []; + + try { + // Check each controller and prepare comparison jobs + for (const controller of controllers) { + console.log(`\nšŸ”§ Checking ${controller.name}...`); + const outputDir = path.dirname(controller.filePath); + const baseFileName = path.basename(controller.filePath, '.ts'); + const actualFile = path.join( + outputDir, + `${baseFileName}-method-action-types.ts`, + ); + + const expectedContent = generateActionTypesContent(controller); + const expectedTempFile = actualFile.replace('.ts', '.tmp.ts'); + + try { + // Check if actual file exists first + await fs.promises.access(actualFile); + + // Write expected content to temp file + await fs.promises.writeFile(expectedTempFile, expectedContent, 'utf8'); + + // Add to comparison jobs + fileComparisonJobs.push({ + expectedTempFile, + actualFile, + baseFileName, + }); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + console.error( + `āŒ ${baseFileName}-method-action-types.ts does not exist`, + ); + } else { + console.error( + `āŒ Error reading ${baseFileName}-method-action-types.ts:`, + error, + ); + } + hasErrors = true; + } + } + + // Run ESLint on all files at once if we have comparisons to make + if (fileComparisonJobs.length > 0) { + console.log('\nšŸ“ Running ESLint to compare files...'); + + const results = await eslint.lintFiles( + fileComparisonJobs.map((job) => job.expectedTempFile), + ); + await ESLint.outputFixes(results); + + // Compare expected vs actual content + for (const job of fileComparisonJobs) { + const expectedContent = await fs.promises.readFile( + job.expectedTempFile, + 'utf8', + ); + const actualContent = await fs.promises.readFile( + job.actualFile, + 'utf8', + ); + + if (expectedContent === actualContent) { + console.log( + `āœ… ${job.baseFileName}-method-action-types.ts is up to date`, + ); + } else { + console.error( + `āŒ ${job.baseFileName}-method-action-types.ts is out of date`, + ); + hasErrors = true; + } + } + } + } finally { + // Clean up temp files + for (const job of fileComparisonJobs) { + try { + await fs.promises.unlink(job.expectedTempFile); + } catch { + // Ignore cleanup errors + } + } + } + + if (hasErrors) { + console.error('\nšŸ’„ Some action type files are out of date or missing.'); + console.error( + 'Run `yarn generate-method-action-types --fix` to update them.', + ); + process.exitCode = 1; + } else { + console.log('\nšŸŽ‰ All action type files are up to date!'); + } +} + +/** + * Main entry point for the script. + */ +async function main(): Promise { + const { fix, controllerPath } = await parseCommandLineArguments(); + + console.log('šŸ” Searching for controllers with MESSENGER_EXPOSED_METHODS...'); + + const controllers = await findControllersWithExposedMethods(controllerPath); + + if (controllers.length === 0) { + console.log('āš ļø No controllers found with MESSENGER_EXPOSED_METHODS'); + return; + } + + console.log( + `šŸ“¦ Found ${controllers.length} controller(s) with exposed methods`, + ); + + const eslint = new ESLint({ + fix: true, + errorOnUnmatchedPattern: false, + }); + + if (fix) { + await generateAllActionTypesFiles(controllers, eslint); + console.log('\nšŸŽ‰ All action types generated successfully!'); + } else { + // -check mode: check files + await checkActionTypesFiles(controllers, eslint); + } +} + +/** + * Check if a path is a directory. + * + * @param pathValue - The path to check. + * @returns True if the path is a directory, false otherwise. + * @throws If an error occurs other than the path not existing. + */ +async function isDirectory(pathValue: string): Promise { + try { + const stats = await fs.promises.stat(pathValue); + return stats.isDirectory(); + } catch (error) { + if ( + isObject(error) && + hasProperty(error, 'code') && + error.code === 'ENOENT' + ) { + return false; + } + + throw error; + } +} + +/** + * Recursively get all files in a directory and its subdirectories. + * + * @param directory - The directory to search. + * @returns An array of file paths. + */ +async function getFiles(directory: string): Promise { + const entries = await fs.promises.readdir(directory, { withFileTypes: true }); + const files = await Promise.all( + entries.map(async (entry) => { + const fullPath = path.join(directory, entry.name); + return entry.isDirectory() ? await getFiles(fullPath) : fullPath; + }), + ); + + return files.flat(); +} + +/** + * Finds all controller files that have MESSENGER_EXPOSED_METHODS constants. + * + * @param controllerPath - Path to the folder where controllers are located. + * @returns A list of controller information objects. + */ +async function findControllersWithExposedMethods( + controllerPath: string, +): Promise { + const srcPath = path.resolve(process.cwd(), controllerPath); + const controllers: ControllerInfo[] = []; + + if (!(await isDirectory(srcPath))) { + throw new Error(`The specified path is not a directory: ${srcPath}`); + } + + const srcFiles = await getFiles(srcPath); + + for (const file of srcFiles) { + if (!file.endsWith('.ts') || file.endsWith('.test.ts')) { + continue; + } + + const content = await fs.promises.readFile(file, 'utf8'); + + if (content.includes('MESSENGER_EXPOSED_METHODS')) { + const controllerInfo = await parseControllerFile(file); + if (controllerInfo) { + controllers.push(controllerInfo); + } + } + } + + return controllers; +} + +/** + * Context for AST visiting. + */ +type VisitorContext = { + exposedMethods: string[]; + className: string; + methods: MethodInfo[]; + sourceFile: ts.SourceFile; +}; + +/** + * Visits AST nodes to find exposed methods and controller class. + * + * @param context - The visitor context. + * @returns A function to visit nodes. + */ +function createASTVisitor(context: VisitorContext): (node: ts.Node) => void { + /** + * Visits AST nodes to find exposed methods and controller class. + * + * @param node - The AST node to visit. + */ + function visitNode(node: ts.Node): void { + if (ts.isVariableStatement(node)) { + const declaration = node.declarationList.declarations[0]; + if ( + ts.isIdentifier(declaration.name) && + declaration.name.text === 'MESSENGER_EXPOSED_METHODS' + ) { + if (declaration.initializer) { + let arrayExpression: ts.ArrayLiteralExpression | undefined; + + // Handle direct array literal + if (ts.isArrayLiteralExpression(declaration.initializer)) { + arrayExpression = declaration.initializer; + } + // Handle "as const" assertion: expression is wrapped in type assertion + else if ( + ts.isAsExpression(declaration.initializer) && + ts.isArrayLiteralExpression(declaration.initializer.expression) + ) { + arrayExpression = declaration.initializer.expression; + } + + if (arrayExpression) { + context.exposedMethods = arrayExpression.elements + .filter(ts.isStringLiteral) + .map((element) => element.text); + } + } + } + } + + // Find the controller or service class + if (ts.isClassDeclaration(node) && node.name) { + const classText = node.name.text; + if (classText.includes('Controller') || classText.includes('Service')) { + context.className = classText; + + // Extract method info for exposed methods + const seenMethods = new Set(); + for (const member of node.members) { + if ( + ts.isMethodDeclaration(member) && + member.name && + ts.isIdentifier(member.name) + ) { + const methodName = member.name.text; + if ( + context.exposedMethods.includes(methodName) && + !seenMethods.has(methodName) + ) { + seenMethods.add(methodName); + const jsDoc = extractJSDoc(member, context.sourceFile); + const signature = extractMethodSignature(member); + context.methods.push({ + name: methodName, + jsDoc, + signature, + }); + } + } + } + } + } + + ts.forEachChild(node, visitNode); + } + + return visitNode; +} + +/** + * Create a TypeScript program for the given file by locating the nearest + * tsconfig.json. + * + * @param filePath - Absolute path to the source file. + * @returns A TypeScript program, or null if no tsconfig was found. + */ +function createProgramForFile(filePath: string): ts.Program | null { + const configPath = ts.findConfigFile( + path.dirname(filePath), + ts.sys.fileExists.bind(ts.sys), + 'tsconfig.json', + ); + if (!configPath) { + return null; + } + + const { config, error } = ts.readConfigFile( + configPath, + ts.sys.readFile.bind(ts.sys), + ); + + if (error) { + return null; + } + + const parsedConfig = ts.parseJsonConfigFileContent( + config, + ts.sys, + path.dirname(configPath), + ); + + return ts.createProgram({ + rootNames: parsedConfig.fileNames, + options: parsedConfig.options, + }); +} + +/** + * Find a class declaration with the given name in a source file. + * + * @param sourceFile - The source file to search. + * @param className - The class name to look for. + * @returns The class declaration node, or null if not found. + */ +function findClassInSourceFile( + sourceFile: ts.SourceFile, + className: string, +): ts.ClassDeclaration | null { + return ( + sourceFile.statements.find( + (node): node is ts.ClassDeclaration => + ts.isClassDeclaration(node) && node.name?.text === className, + ) ?? null + ); +} + +/** + * Search through the class hierarchy of a TypeScript type to find the + * declaration of a method with the given name. + * + * @param classType - The class type to search. + * @param methodName - The method name to look for. + * @returns The method declaration node, or null if not found. + */ +function findMethodInHierarchy( + classType: ts.Type, + methodName: string, +): ts.MethodDeclaration | null { + const symbol = classType.getProperty(methodName); + if (!symbol) { + return null; + } + + const declarations = symbol.getDeclarations(); + if (!declarations) { + return null; + } + + for (const declaration of declarations) { + if (ts.isMethodDeclaration(declaration)) { + return declaration; + } + } + + return null; +} + +/** + * Parses a controller file to extract exposed methods and their metadata. + * + * @param filePath - Path to the controller file to parse. + * @returns Controller information or null if parsing fails. + */ +async function parseControllerFile( + filePath: string, +): Promise { + try { + const content = await fs.promises.readFile(filePath, 'utf8'); + const sourceFile = ts.createSourceFile( + filePath, + content, + ts.ScriptTarget.Latest, + true, + ); + + const context: VisitorContext = { + exposedMethods: [], + className: '', + methods: [], + sourceFile, + }; + + createASTVisitor(context)(sourceFile); + + if (context.exposedMethods.length === 0 || !context.className) { + return null; + } + + // For exposed methods not found directly in the class body, attempt to + // locate them in the inheritance hierarchy using the type checker. + const foundMethodNames = new Set( + context.methods.map((method) => method.name), + ); + + const inheritedMethodNames = context.exposedMethods.filter( + (name) => !foundMethodNames.has(name), + ); + + if (inheritedMethodNames.length > 0) { + const program = createProgramForFile(filePath); + const checker = program?.getTypeChecker(); + const programSourceFile = program?.getSourceFile(filePath); + + assert( + checker, + `Type checker could not be created for "${filePath}". Ensure a valid tsconfig.json is present.`, + ); + + assert( + programSourceFile, + `Source file "${filePath}" not found in program.`, + ); + + const classNode = findClassInSourceFile( + programSourceFile, + context.className, + ); + + assert( + classNode, + `Class "${context.className}" not found in "${filePath}".`, + ); + + const classType = checker.getTypeAtLocation(classNode); + for (const methodName of inheritedMethodNames) { + const methodDeclaration = findMethodInHierarchy(classType, methodName); + + const jsDoc = methodDeclaration + ? extractJSDoc(methodDeclaration, methodDeclaration.getSourceFile()) + : ''; + context.methods.push({ name: methodName, jsDoc, signature: '' }); + } + } + + return { + name: context.className, + filePath, + exposedMethods: context.exposedMethods, + methods: context.methods, + }; + } catch (error) { + console.error(`Error parsing ${filePath}:`, error); + return null; + } +} + +/** + * Extracts JSDoc comment from a method declaration. + * + * @param node - The method declaration node. + * @param sourceFile - The source file. + * @returns The JSDoc comment. + */ +function extractJSDoc( + node: ts.MethodDeclaration, + sourceFile: ts.SourceFile, +): string { + const jsDocTags = ts.getJSDocCommentsAndTags(node); + if (jsDocTags.length === 0) { + return ''; + } + + const jsDoc = jsDocTags[0]; + if (ts.isJSDoc(jsDoc)) { + const fullText = sourceFile.getFullText(); + const start = jsDoc.getFullStart(); + const end = jsDoc.getEnd(); + const rawJsDoc = fullText.substring(start, end).trim(); + return formatJSDoc(rawJsDoc); + } + + return ''; +} + +/** + * Formats JSDoc comments to have consistent indentation for the generated file. + * + * @param rawJsDoc - The raw JSDoc comment from the source. + * @returns The formatted JSDoc comment. + */ +function formatJSDoc(rawJsDoc: string): string { + const lines = rawJsDoc.split('\n'); + const formattedLines: string[] = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (i === 0) { + // First line should be /** + formattedLines.push('/**'); + } else if (i === lines.length - 1) { + // Last line should be */ + formattedLines.push(' */'); + } else { + // Middle lines should start with ' * ' + const trimmed = line.trim(); + if (trimmed.startsWith('*')) { + // Remove existing * and normalize + const content = trimmed.substring(1).trim(); + formattedLines.push(content ? ` * ${content}` : ' *'); + } else { + // Handle lines that don't start with * + formattedLines.push(trimmed ? ` * ${trimmed}` : ' *'); + } + } + } + + return formattedLines.join('\n'); +} + +/** + * Extracts method signature as a string for the handler type. + * + * @param node - The method declaration node. + * @returns The method signature. + */ +function extractMethodSignature(node: ts.MethodDeclaration): string { + // Since we're just using the method reference in the handler type, + // we don't need the full signature - just return the method name + // The actual signature will be inferred from the controller class + return node.name ? (node.name as ts.Identifier).text : ''; +} + +/** + * Generates action types files for all controllers. + * + * @param controllers - Array of controller information objects. + * @param eslint - The ESLint instance to use for formatting. + */ +async function generateAllActionTypesFiles( + controllers: ControllerInfo[], + eslint: ESLint, +): Promise { + const outputFiles: string[] = []; + + // Write all files first + for (const controller of controllers) { + console.log(`\nšŸ”§ Processing ${controller.name}...`); + const outputDir = path.dirname(controller.filePath); + const baseFileName = path.basename(controller.filePath, '.ts'); + const outputFile = path.join( + outputDir, + `${baseFileName}-method-action-types.ts`, + ); + + const generatedContent = generateActionTypesContent(controller); + await fs.promises.writeFile(outputFile, generatedContent, 'utf8'); + outputFiles.push(outputFile); + console.log(`āœ… Generated action types for ${controller.name}`); + } + + // Run ESLint on all the actual files + if (outputFiles.length > 0) { + console.log('\nšŸ“ Running ESLint on generated files...'); + + const results = await eslint.lintFiles(outputFiles); + await ESLint.outputFixes(results); + const errors = ESLint.getErrorResults(results); + if (errors.length > 0) { + console.error('āŒ ESLint errors:', errors); + process.exitCode = 1; + } else { + console.log('āœ… ESLint formatting applied'); + } + } +} + +/** + * Generates the content for the action types file. + * + * @param controller - The controller information object. + * @returns The content for the action types file. + */ +function generateActionTypesContent(controller: ControllerInfo): string { + const baseFileName = path.basename(controller.filePath, '.ts'); + const controllerImportPath = `./${baseFileName}`; + + let content = `/** + * This file is auto generated by \`scripts/generate-method-action-types.ts\`. + * Do not edit manually. + */ + +import type { ${controller.name} } from '${controllerImportPath}'; + +`; + + const actionTypeNames: string[] = []; + + // Generate action types for each exposed method + for (const method of controller.methods) { + const actionTypeName = `${controller.name}${capitalize(method.name)}Action`; + const actionString = `${controller.name}:${method.name}`; + + actionTypeNames.push(actionTypeName); + + // Add the JSDoc if available + if (method.jsDoc) { + content += `${method.jsDoc}\n`; + } + + content += `export type ${actionTypeName} = { + type: \`${actionString}\`; + handler: ${controller.name}['${method.name}']; +};\n\n`; + } + + // Generate union type of all action types + if (actionTypeNames.length > 0) { + const unionTypeName = `${controller.name}MethodActions`; + content += `/** + * Union of all ${controller.name} action types. + */ +export type ${unionTypeName} = ${actionTypeNames.join(' | ')};\n`; + } + + return `${content.trimEnd()}\n`; +} + +/** + * Capitalizes the first letter of a string. + * + * @param str - The string to capitalize. + * @returns The capitalized string. + */ +function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +// Error handling wrapper +main().catch((error) => { + console.error('āŒ Script failed:', error); + process.exitCode = 1; +}); diff --git a/yarn.lock b/yarn.lock index 99eb90ff4f..e41192694d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4430,6 +4430,7 @@ __metadata: typescript: "npm:~5.3.3" typescript-eslint: "npm:^8.6.0" vite: "npm:^6.4.1" + yargs: "npm:^18.0.0" languageName: unknown linkType: soft @@ -8296,6 +8297,13 @@ __metadata: languageName: node linkType: hard +"ansi-regex@npm:^6.2.2": + version: 6.2.2 + resolution: "ansi-regex@npm:6.2.2" + checksum: 10/9b17ce2c6daecc75bcd5966b9ad672c23b184dc3ed9bf3c98a0702f0d2f736c15c10d461913568f2cf527a5e64291c7473358885dd493305c84a1cfed66ba94f + languageName: node + linkType: hard + "ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" @@ -8328,6 +8336,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^6.2.1": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10/c49dad7639f3e48859bd51824c93b9eb0db628afc243c51c3dd2410c4a15ede1a83881c6c7341aa2b159c4f90c11befb38f2ba848c07c66c9f9de4bcd7cb9f30 + languageName: node + linkType: hard + "anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": version: 3.1.2 resolution: "anymatch@npm:3.1.2" @@ -9360,6 +9375,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^9.0.1": + version: 9.0.1 + resolution: "cliui@npm:9.0.1" + dependencies: + string-width: "npm:^7.2.0" + strip-ansi: "npm:^7.1.0" + wrap-ansi: "npm:^9.0.0" + checksum: 10/df43d8d1c6e3254cbb64b1905310d5f6672c595496a3cbe76946c6d24777136886470686f2772ac9edfe547a74bb70e8017530b3554715aee119efd7752fc0d9 + languageName: node + linkType: hard + "clone-deep@npm:^0.2.4": version: 0.2.4 resolution: "clone-deep@npm:0.2.4" @@ -10424,6 +10450,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.3.0": + version: 10.6.0 + resolution: "emoji-regex@npm:10.6.0" + checksum: 10/98cc0b0e1daed1ed25afbf69dcb921fee00f712f51aab93aa1547e4e4e8171725cc4f0098aaa645b4f611a19da11ec9f4623eb6ff2b72314b39a8f2ae7c12bf2 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -11757,6 +11790,13 @@ __metadata: languageName: node linkType: hard +"get-east-asian-width@npm:^1.0.0": + version: 1.5.0 + resolution: "get-east-asian-width@npm:1.5.0" + checksum: 10/60bc34cd1e975055ab99f0f177e31bed3e516ff7cee9c536474383954a976abaa6b94a51d99ad158ef1e372790fa096cab7d07f166bb0778f6587954c0fbe946 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.3.0": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" @@ -17527,6 +17567,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.0.0, string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10/42f9e82f61314904a81393f6ef75b832c39f39761797250de68c041d8ba4df2ef80db49ab6cd3a292923a6f0f409b8c9980d120f7d32c820b4a8a84a2598a295 + languageName: node + linkType: hard + "string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" @@ -17563,6 +17614,15 @@ __metadata: languageName: node linkType: hard +"strip-ansi@npm:^7.1.0": + version: 7.2.0 + resolution: "strip-ansi@npm:7.2.0" + dependencies: + ansi-regex: "npm:^6.2.2" + checksum: 10/96da3bc6d73cfba1218625a3d66cf7d37a69bf0920d8735b28f9eeaafcdb6c1fe8440e1ae9eb1ba0ca355dbe8702da872e105e2e939fa93e7851b3cb5dd7d316 + languageName: node + linkType: hard + "strip-bom@npm:^3.0.0": version: 3.0.0 resolution: "strip-bom@npm:3.0.0" @@ -19090,6 +19150,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^9.0.0": + version: 9.0.2 + resolution: "wrap-ansi@npm:9.0.2" + dependencies: + ansi-styles: "npm:^6.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10/f3907e1ea9717404ca53a338fa5a017c2121550c3a5305180e2bc08c03e21aa45068df55b0d7676bf57be1880ba51a84458c17241ebedea485fafa9ef16b4024 + languageName: node + linkType: hard + "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -19244,6 +19315,13 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^22.0.0": + version: 22.0.0 + resolution: "yargs-parser@npm:22.0.0" + checksum: 10/f13c42bad6ebed1a587a72f2db5694f5fa772bcaf409a701691d13cf74eb5adfcf61a2611de08807e319b829d3e5e6e1578b16ebe174cae8e8be3bf7b8e7a19e + languageName: node + linkType: hard + "yargs@npm:17.7.2, yargs@npm:^17.0.1, yargs@npm:^17.3.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" @@ -19274,6 +19352,20 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^18.0.0": + version: 18.0.0 + resolution: "yargs@npm:18.0.0" + dependencies: + cliui: "npm:^9.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + string-width: "npm:^7.2.0" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^22.0.0" + checksum: 10/5af36234871390386b31cac99f00e79fcbc2ead858a61b30a8ca381c5fde5df8af0b407c36b000d3f774bcbe4aec5833f2f1c915f6ddc49ce97b78176b651801 + languageName: node + linkType: hard + "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" From 4a129984a02129feb64878d52f1496473df3a94b Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 12:44:15 +0100 Subject: [PATCH 02/12] Fix Yargs version --- package.json | 2 +- yarn.lock | 93 +--------------------------------------------------- 2 files changed, 2 insertions(+), 93 deletions(-) diff --git a/package.json b/package.json index 8b03c43325..a7cd8198e8 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "typescript": "~5.3.3", "typescript-eslint": "^8.6.0", "vite": "^6.4.1", - "yargs": "^18.0.0" + "yargs": "^17.7.1" }, "packageManager": "yarn@4.10.3", "engines": { diff --git a/yarn.lock b/yarn.lock index e41192694d..67a707b05f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4430,7 +4430,7 @@ __metadata: typescript: "npm:~5.3.3" typescript-eslint: "npm:^8.6.0" vite: "npm:^6.4.1" - yargs: "npm:^18.0.0" + yargs: "npm:^17.7.1" languageName: unknown linkType: soft @@ -8297,13 +8297,6 @@ __metadata: languageName: node linkType: hard -"ansi-regex@npm:^6.2.2": - version: 6.2.2 - resolution: "ansi-regex@npm:6.2.2" - checksum: 10/9b17ce2c6daecc75bcd5966b9ad672c23b184dc3ed9bf3c98a0702f0d2f736c15c10d461913568f2cf527a5e64291c7473358885dd493305c84a1cfed66ba94f - languageName: node - linkType: hard - "ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" @@ -8336,13 +8329,6 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^6.2.1": - version: 6.2.3 - resolution: "ansi-styles@npm:6.2.3" - checksum: 10/c49dad7639f3e48859bd51824c93b9eb0db628afc243c51c3dd2410c4a15ede1a83881c6c7341aa2b159c4f90c11befb38f2ba848c07c66c9f9de4bcd7cb9f30 - languageName: node - linkType: hard - "anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": version: 3.1.2 resolution: "anymatch@npm:3.1.2" @@ -9375,17 +9361,6 @@ __metadata: languageName: node linkType: hard -"cliui@npm:^9.0.1": - version: 9.0.1 - resolution: "cliui@npm:9.0.1" - dependencies: - string-width: "npm:^7.2.0" - strip-ansi: "npm:^7.1.0" - wrap-ansi: "npm:^9.0.0" - checksum: 10/df43d8d1c6e3254cbb64b1905310d5f6672c595496a3cbe76946c6d24777136886470686f2772ac9edfe547a74bb70e8017530b3554715aee119efd7752fc0d9 - languageName: node - linkType: hard - "clone-deep@npm:^0.2.4": version: 0.2.4 resolution: "clone-deep@npm:0.2.4" @@ -10450,13 +10425,6 @@ __metadata: languageName: node linkType: hard -"emoji-regex@npm:^10.3.0": - version: 10.6.0 - resolution: "emoji-regex@npm:10.6.0" - checksum: 10/98cc0b0e1daed1ed25afbf69dcb921fee00f712f51aab93aa1547e4e4e8171725cc4f0098aaa645b4f611a19da11ec9f4623eb6ff2b72314b39a8f2ae7c12bf2 - languageName: node - linkType: hard - "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -11790,13 +11758,6 @@ __metadata: languageName: node linkType: hard -"get-east-asian-width@npm:^1.0.0": - version: 1.5.0 - resolution: "get-east-asian-width@npm:1.5.0" - checksum: 10/60bc34cd1e975055ab99f0f177e31bed3e516ff7cee9c536474383954a976abaa6b94a51d99ad158ef1e372790fa096cab7d07f166bb0778f6587954c0fbe946 - languageName: node - linkType: hard - "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.3.0": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" @@ -17567,17 +17528,6 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^7.0.0, string-width@npm:^7.2.0": - version: 7.2.0 - resolution: "string-width@npm:7.2.0" - dependencies: - emoji-regex: "npm:^10.3.0" - get-east-asian-width: "npm:^1.0.0" - strip-ansi: "npm:^7.1.0" - checksum: 10/42f9e82f61314904a81393f6ef75b832c39f39761797250de68c041d8ba4df2ef80db49ab6cd3a292923a6f0f409b8c9980d120f7d32c820b4a8a84a2598a295 - languageName: node - linkType: hard - "string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" @@ -17614,15 +17564,6 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.1.0": - version: 7.2.0 - resolution: "strip-ansi@npm:7.2.0" - dependencies: - ansi-regex: "npm:^6.2.2" - checksum: 10/96da3bc6d73cfba1218625a3d66cf7d37a69bf0920d8735b28f9eeaafcdb6c1fe8440e1ae9eb1ba0ca355dbe8702da872e105e2e939fa93e7851b3cb5dd7d316 - languageName: node - linkType: hard - "strip-bom@npm:^3.0.0": version: 3.0.0 resolution: "strip-bom@npm:3.0.0" @@ -19150,17 +19091,6 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^9.0.0": - version: 9.0.2 - resolution: "wrap-ansi@npm:9.0.2" - dependencies: - ansi-styles: "npm:^6.2.1" - string-width: "npm:^7.0.0" - strip-ansi: "npm:^7.1.0" - checksum: 10/f3907e1ea9717404ca53a338fa5a017c2121550c3a5305180e2bc08c03e21aa45068df55b0d7676bf57be1880ba51a84458c17241ebedea485fafa9ef16b4024 - languageName: node - linkType: hard - "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -19315,13 +19245,6 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^22.0.0": - version: 22.0.0 - resolution: "yargs-parser@npm:22.0.0" - checksum: 10/f13c42bad6ebed1a587a72f2db5694f5fa772bcaf409a701691d13cf74eb5adfcf61a2611de08807e319b829d3e5e6e1578b16ebe174cae8e8be3bf7b8e7a19e - languageName: node - linkType: hard - "yargs@npm:17.7.2, yargs@npm:^17.0.1, yargs@npm:^17.3.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" @@ -19352,20 +19275,6 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^18.0.0": - version: 18.0.0 - resolution: "yargs@npm:18.0.0" - dependencies: - cliui: "npm:^9.0.1" - escalade: "npm:^3.1.1" - get-caller-file: "npm:^2.0.5" - string-width: "npm:^7.2.0" - y18n: "npm:^5.0.5" - yargs-parser: "npm:^22.0.0" - checksum: 10/5af36234871390386b31cac99f00e79fcbc2ead858a61b30a8ca381c5fde5df8af0b407c36b000d3f774bcbe4aec5833f2f1c915f6ddc49ce97b78176b651801 - languageName: node - linkType: hard - "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" From 2b7e41ceca4b043579afb1375a7a9d128695ad2a Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 12:59:33 +0100 Subject: [PATCH 03/12] Replace `ts` default import with named imports --- scripts/generate-method-action-types.mts | 104 ++++++++++++++--------- 1 file changed, 62 insertions(+), 42 deletions(-) diff --git a/scripts/generate-method-action-types.mts b/scripts/generate-method-action-types.mts index dfeb7e61f3..242fd2c318 100644 --- a/scripts/generate-method-action-types.mts +++ b/scripts/generate-method-action-types.mts @@ -6,7 +6,33 @@ import { assert, hasProperty, isObject } from '@metamask/utils'; import { ESLint } from 'eslint'; import * as fs from 'fs'; import * as path from 'path'; -import ts from 'typescript'; +import { + type ArrayLiteralExpression, + type ClassDeclaration, + type Identifier, + type MethodDeclaration, + type Node, + type Program, + type SourceFile, + type Type, + createProgram, + createSourceFile, + findConfigFile, + forEachChild, + getJSDocCommentsAndTags, + isArrayLiteralExpression, + isAsExpression, + isClassDeclaration, + isIdentifier, + isJSDoc, + isMethodDeclaration, + isStringLiteral, + isVariableStatement, + parseJsonConfigFileContent, + readConfigFile, + ScriptTarget, + sys, +} from 'typescript'; import yargs from 'yargs'; type MethodInfo = { @@ -321,7 +347,7 @@ type VisitorContext = { exposedMethods: string[]; className: string; methods: MethodInfo[]; - sourceFile: ts.SourceFile; + sourceFile: SourceFile; }; /** @@ -330,37 +356,37 @@ type VisitorContext = { * @param context - The visitor context. * @returns A function to visit nodes. */ -function createASTVisitor(context: VisitorContext): (node: ts.Node) => void { +function createASTVisitor(context: VisitorContext): (node: Node) => void { /** * Visits AST nodes to find exposed methods and controller class. * * @param node - The AST node to visit. */ - function visitNode(node: ts.Node): void { - if (ts.isVariableStatement(node)) { + function visitNode(node: Node): void { + if (isVariableStatement(node)) { const declaration = node.declarationList.declarations[0]; if ( - ts.isIdentifier(declaration.name) && + isIdentifier(declaration.name) && declaration.name.text === 'MESSENGER_EXPOSED_METHODS' ) { if (declaration.initializer) { - let arrayExpression: ts.ArrayLiteralExpression | undefined; + let arrayExpression: ArrayLiteralExpression | undefined; // Handle direct array literal - if (ts.isArrayLiteralExpression(declaration.initializer)) { + if (isArrayLiteralExpression(declaration.initializer)) { arrayExpression = declaration.initializer; } // Handle "as const" assertion: expression is wrapped in type assertion else if ( - ts.isAsExpression(declaration.initializer) && - ts.isArrayLiteralExpression(declaration.initializer.expression) + isAsExpression(declaration.initializer) && + isArrayLiteralExpression(declaration.initializer.expression) ) { arrayExpression = declaration.initializer.expression; } if (arrayExpression) { context.exposedMethods = arrayExpression.elements - .filter(ts.isStringLiteral) + .filter(isStringLiteral) .map((element) => element.text); } } @@ -368,7 +394,7 @@ function createASTVisitor(context: VisitorContext): (node: ts.Node) => void { } // Find the controller or service class - if (ts.isClassDeclaration(node) && node.name) { + if (isClassDeclaration(node) && node.name) { const classText = node.name.text; if (classText.includes('Controller') || classText.includes('Service')) { context.className = classText; @@ -377,9 +403,9 @@ function createASTVisitor(context: VisitorContext): (node: ts.Node) => void { const seenMethods = new Set(); for (const member of node.members) { if ( - ts.isMethodDeclaration(member) && + isMethodDeclaration(member) && member.name && - ts.isIdentifier(member.name) + isIdentifier(member.name) ) { const methodName = member.name.text; if ( @@ -400,7 +426,7 @@ function createASTVisitor(context: VisitorContext): (node: ts.Node) => void { } } - ts.forEachChild(node, visitNode); + forEachChild(node, visitNode); } return visitNode; @@ -413,32 +439,29 @@ function createASTVisitor(context: VisitorContext): (node: ts.Node) => void { * @param filePath - Absolute path to the source file. * @returns A TypeScript program, or null if no tsconfig was found. */ -function createProgramForFile(filePath: string): ts.Program | null { - const configPath = ts.findConfigFile( +function createProgramForFile(filePath: string): Program | null { + const configPath = findConfigFile( path.dirname(filePath), - ts.sys.fileExists.bind(ts.sys), + sys.fileExists.bind(sys), 'tsconfig.json', ); if (!configPath) { return null; } - const { config, error } = ts.readConfigFile( - configPath, - ts.sys.readFile.bind(ts.sys), - ); + const { config, error } = readConfigFile(configPath, sys.readFile.bind(sys)); if (error) { return null; } - const parsedConfig = ts.parseJsonConfigFileContent( + const parsedConfig = parseJsonConfigFileContent( config, - ts.sys, + sys, path.dirname(configPath), ); - return ts.createProgram({ + return createProgram({ rootNames: parsedConfig.fileNames, options: parsedConfig.options, }); @@ -452,13 +475,13 @@ function createProgramForFile(filePath: string): ts.Program | null { * @returns The class declaration node, or null if not found. */ function findClassInSourceFile( - sourceFile: ts.SourceFile, + sourceFile: SourceFile, className: string, -): ts.ClassDeclaration | null { +): ClassDeclaration | null { return ( sourceFile.statements.find( - (node): node is ts.ClassDeclaration => - ts.isClassDeclaration(node) && node.name?.text === className, + (node): node is ClassDeclaration => + isClassDeclaration(node) && node.name?.text === className, ) ?? null ); } @@ -472,9 +495,9 @@ function findClassInSourceFile( * @returns The method declaration node, or null if not found. */ function findMethodInHierarchy( - classType: ts.Type, + classType: Type, methodName: string, -): ts.MethodDeclaration | null { +): MethodDeclaration | null { const symbol = classType.getProperty(methodName); if (!symbol) { return null; @@ -486,7 +509,7 @@ function findMethodInHierarchy( } for (const declaration of declarations) { - if (ts.isMethodDeclaration(declaration)) { + if (isMethodDeclaration(declaration)) { return declaration; } } @@ -505,10 +528,10 @@ async function parseControllerFile( ): Promise { try { const content = await fs.promises.readFile(filePath, 'utf8'); - const sourceFile = ts.createSourceFile( + const sourceFile = createSourceFile( filePath, content, - ts.ScriptTarget.Latest, + ScriptTarget.Latest, true, ); @@ -590,17 +613,14 @@ async function parseControllerFile( * @param sourceFile - The source file. * @returns The JSDoc comment. */ -function extractJSDoc( - node: ts.MethodDeclaration, - sourceFile: ts.SourceFile, -): string { - const jsDocTags = ts.getJSDocCommentsAndTags(node); +function extractJSDoc(node: MethodDeclaration, sourceFile: SourceFile): string { + const jsDocTags = getJSDocCommentsAndTags(node); if (jsDocTags.length === 0) { return ''; } const jsDoc = jsDocTags[0]; - if (ts.isJSDoc(jsDoc)) { + if (isJSDoc(jsDoc)) { const fullText = sourceFile.getFullText(); const start = jsDoc.getFullStart(); const end = jsDoc.getEnd(); @@ -652,11 +672,11 @@ function formatJSDoc(rawJsDoc: string): string { * @param node - The method declaration node. * @returns The method signature. */ -function extractMethodSignature(node: ts.MethodDeclaration): string { +function extractMethodSignature(node: MethodDeclaration): string { // Since we're just using the method reference in the handler type, // we don't need the full signature - just return the method name // The actual signature will be inferred from the controller class - return node.name ? (node.name as ts.Identifier).text : ''; + return node.name ? (node.name as Identifier).text : ''; } /** From 67aab043094c8b4194ffc0048e1162769883c920 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 13:49:53 +0100 Subject: [PATCH 04/12] Revert some changes --- .../src/snaps/SnapController.test.tsx | 2 +- .../src/snaps/SnapController.ts | 126 +++++++++++++++++- .../src/test-utils/controller.tsx | 3 +- scripts/generate-method-action-types.mts | 108 +++++++-------- 4 files changed, 170 insertions(+), 69 deletions(-) diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.tsx b/packages/snaps-controllers/src/snaps/SnapController.test.tsx index 1020ced1a6..72fe80f901 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.tsx +++ b/packages/snaps-controllers/src/snaps/SnapController.test.tsx @@ -12507,7 +12507,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - expect(async () => + expect(() => options.messenger.call( 'SnapController:revokeDynamicSnapPermissions', MOCK_SNAP_ID, diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 17b058b1aa..39d0883c08 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -1006,10 +1006,7 @@ export class SnapController extends BaseController< ); this.#initializeStateMachine(); - this.messenger.registerMethodActionHandlers( - this, - MESSENGER_EXPOSED_METHODS, - ); + this.#registerMessageHandlers(); Object.values(this.state?.snaps ?? {}).forEach((snap) => this.#setupRuntime(snap.id), @@ -1106,6 +1103,127 @@ export class SnapController extends BaseController< validateMachine(this.#statusMachine); } + /** + * Constructor helper for registering the controller's messaging system + * actions. + */ + #registerMessageHandlers(): void { + this.messenger.registerActionHandler( + `${controllerName}:init`, + async (...args) => this.init(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:clearSnapState`, + (...args) => this.clearSnapState(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:getSnap`, + (...args) => this.getSnap(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:getSnapState`, + async (...args) => this.getSnapState(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:handleRequest`, + async (...args) => this.handleRequest(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:hasSnap`, + (...args) => this.hasSnap(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:updateRegistry`, + async () => this.updateRegistry(), + ); + + this.messenger.registerActionHandler( + `${controllerName}:updateSnapState`, + async (...args) => this.updateSnapState(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:enableSnap`, + (...args) => this.enableSnap(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:disableSnap`, + async (...args) => this.disableSnap(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:removeSnap`, + async (...args) => this.removeSnap(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:getPermittedSnaps`, + (...args) => this.getPermittedSnaps(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:installSnaps`, + async (...args) => this.installSnaps(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:getAllSnaps`, + (...args) => this.getAllSnaps(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:getRunnableSnaps`, + (...args) => this.getRunnableSnaps(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:incrementActiveReferences`, + (...args) => this.incrementActiveReferences(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:decrementActiveReferences`, + (...args) => this.decrementActiveReferences(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:removeSnapFromSubject`, + (...args) => this.removeSnapFromSubject(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:revokeDynamicSnapPermissions`, + (...args) => this.revokeDynamicSnapPermissions(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:getSnapFile`, + async (...args) => this.getSnapFile(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:stopAllSnaps`, + async (...args) => this.stopAllSnaps(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:isMinimumPlatformVersion`, + (...args) => this.isMinimumPlatformVersion(...args), + ); + + this.messenger.registerActionHandler( + `${controllerName}:setClientActive`, + (...args) => this.setClientActive(...args), + ); + } + /** * Initialise the SnapController. * diff --git a/packages/snaps-controllers/src/test-utils/controller.tsx b/packages/snaps-controllers/src/test-utils/controller.tsx index c370aab14e..ef26f2925d 100644 --- a/packages/snaps-controllers/src/test-utils/controller.tsx +++ b/packages/snaps-controllers/src/test-utils/controller.tsx @@ -687,6 +687,7 @@ export const getSnapController = async ( if (init) { await controller.init(); } + return controller; }; @@ -1011,7 +1012,7 @@ export const getRestrictedMultichainRouterMessenger = ( messenger.delegate({ actions: [ 'PermissionController:getPermissions', - 'SnapController:getAll', + 'SnapController:getAllSnaps', 'SnapController:handleRequest', 'AccountsController:listMultichainAccounts', ], diff --git a/scripts/generate-method-action-types.mts b/scripts/generate-method-action-types.mts index 242fd2c318..27f3a5a436 100644 --- a/scripts/generate-method-action-types.mts +++ b/scripts/generate-method-action-types.mts @@ -1,38 +1,14 @@ #!yarn tsx -/* eslint-disable no-console */ +// ESLint is saying `ts` can be replaced with named imports, but this doesn't +// seem to actually work with the current TypeScript version. +/* eslint-disable no-console, import-x/no-named-as-default-member */ import { assert, hasProperty, isObject } from '@metamask/utils'; import { ESLint } from 'eslint'; import * as fs from 'fs'; import * as path from 'path'; -import { - type ArrayLiteralExpression, - type ClassDeclaration, - type Identifier, - type MethodDeclaration, - type Node, - type Program, - type SourceFile, - type Type, - createProgram, - createSourceFile, - findConfigFile, - forEachChild, - getJSDocCommentsAndTags, - isArrayLiteralExpression, - isAsExpression, - isClassDeclaration, - isIdentifier, - isJSDoc, - isMethodDeclaration, - isStringLiteral, - isVariableStatement, - parseJsonConfigFileContent, - readConfigFile, - ScriptTarget, - sys, -} from 'typescript'; +import ts from 'typescript'; import yargs from 'yargs'; type MethodInfo = { @@ -347,7 +323,7 @@ type VisitorContext = { exposedMethods: string[]; className: string; methods: MethodInfo[]; - sourceFile: SourceFile; + sourceFile: ts.SourceFile; }; /** @@ -356,37 +332,37 @@ type VisitorContext = { * @param context - The visitor context. * @returns A function to visit nodes. */ -function createASTVisitor(context: VisitorContext): (node: Node) => void { +function createASTVisitor(context: VisitorContext): (node: ts.Node) => void { /** * Visits AST nodes to find exposed methods and controller class. * * @param node - The AST node to visit. */ - function visitNode(node: Node): void { - if (isVariableStatement(node)) { + function visitNode(node: ts.Node): void { + if (ts.isVariableStatement(node)) { const declaration = node.declarationList.declarations[0]; if ( - isIdentifier(declaration.name) && + ts.isIdentifier(declaration.name) && declaration.name.text === 'MESSENGER_EXPOSED_METHODS' ) { if (declaration.initializer) { - let arrayExpression: ArrayLiteralExpression | undefined; + let arrayExpression: ts.ArrayLiteralExpression | undefined; // Handle direct array literal - if (isArrayLiteralExpression(declaration.initializer)) { + if (ts.isArrayLiteralExpression(declaration.initializer)) { arrayExpression = declaration.initializer; } // Handle "as const" assertion: expression is wrapped in type assertion else if ( - isAsExpression(declaration.initializer) && - isArrayLiteralExpression(declaration.initializer.expression) + ts.isAsExpression(declaration.initializer) && + ts.isArrayLiteralExpression(declaration.initializer.expression) ) { arrayExpression = declaration.initializer.expression; } if (arrayExpression) { context.exposedMethods = arrayExpression.elements - .filter(isStringLiteral) + .filter(ts.isStringLiteral) .map((element) => element.text); } } @@ -394,7 +370,7 @@ function createASTVisitor(context: VisitorContext): (node: Node) => void { } // Find the controller or service class - if (isClassDeclaration(node) && node.name) { + if (ts.isClassDeclaration(node) && node.name) { const classText = node.name.text; if (classText.includes('Controller') || classText.includes('Service')) { context.className = classText; @@ -403,9 +379,9 @@ function createASTVisitor(context: VisitorContext): (node: Node) => void { const seenMethods = new Set(); for (const member of node.members) { if ( - isMethodDeclaration(member) && + ts.isMethodDeclaration(member) && member.name && - isIdentifier(member.name) + ts.isIdentifier(member.name) ) { const methodName = member.name.text; if ( @@ -426,7 +402,7 @@ function createASTVisitor(context: VisitorContext): (node: Node) => void { } } - forEachChild(node, visitNode); + ts.forEachChild(node, visitNode); } return visitNode; @@ -439,29 +415,32 @@ function createASTVisitor(context: VisitorContext): (node: Node) => void { * @param filePath - Absolute path to the source file. * @returns A TypeScript program, or null if no tsconfig was found. */ -function createProgramForFile(filePath: string): Program | null { - const configPath = findConfigFile( +function createProgramForFile(filePath: string): ts.Program | null { + const configPath = ts.findConfigFile( path.dirname(filePath), - sys.fileExists.bind(sys), + ts.sys.fileExists.bind(ts.sys), 'tsconfig.json', ); if (!configPath) { return null; } - const { config, error } = readConfigFile(configPath, sys.readFile.bind(sys)); + const { config, error } = ts.readConfigFile( + configPath, + ts.sys.readFile.bind(ts.sys), + ); if (error) { return null; } - const parsedConfig = parseJsonConfigFileContent( + const parsedConfig = ts.parseJsonConfigFileContent( config, - sys, + ts.sys, path.dirname(configPath), ); - return createProgram({ + return ts.createProgram({ rootNames: parsedConfig.fileNames, options: parsedConfig.options, }); @@ -475,13 +454,13 @@ function createProgramForFile(filePath: string): Program | null { * @returns The class declaration node, or null if not found. */ function findClassInSourceFile( - sourceFile: SourceFile, + sourceFile: ts.SourceFile, className: string, -): ClassDeclaration | null { +): ts.ClassDeclaration | null { return ( sourceFile.statements.find( - (node): node is ClassDeclaration => - isClassDeclaration(node) && node.name?.text === className, + (node): node is ts.ClassDeclaration => + ts.isClassDeclaration(node) && node.name?.text === className, ) ?? null ); } @@ -495,9 +474,9 @@ function findClassInSourceFile( * @returns The method declaration node, or null if not found. */ function findMethodInHierarchy( - classType: Type, + classType: ts.Type, methodName: string, -): MethodDeclaration | null { +): ts.MethodDeclaration | null { const symbol = classType.getProperty(methodName); if (!symbol) { return null; @@ -509,7 +488,7 @@ function findMethodInHierarchy( } for (const declaration of declarations) { - if (isMethodDeclaration(declaration)) { + if (ts.isMethodDeclaration(declaration)) { return declaration; } } @@ -528,10 +507,10 @@ async function parseControllerFile( ): Promise { try { const content = await fs.promises.readFile(filePath, 'utf8'); - const sourceFile = createSourceFile( + const sourceFile = ts.createSourceFile( filePath, content, - ScriptTarget.Latest, + ts.ScriptTarget.Latest, true, ); @@ -613,14 +592,17 @@ async function parseControllerFile( * @param sourceFile - The source file. * @returns The JSDoc comment. */ -function extractJSDoc(node: MethodDeclaration, sourceFile: SourceFile): string { - const jsDocTags = getJSDocCommentsAndTags(node); +function extractJSDoc( + node: ts.MethodDeclaration, + sourceFile: ts.SourceFile, +): string { + const jsDocTags = ts.getJSDocCommentsAndTags(node); if (jsDocTags.length === 0) { return ''; } const jsDoc = jsDocTags[0]; - if (isJSDoc(jsDoc)) { + if (ts.isJSDoc(jsDoc)) { const fullText = sourceFile.getFullText(); const start = jsDoc.getFullStart(); const end = jsDoc.getEnd(); @@ -672,11 +654,11 @@ function formatJSDoc(rawJsDoc: string): string { * @param node - The method declaration node. * @returns The method signature. */ -function extractMethodSignature(node: MethodDeclaration): string { +function extractMethodSignature(node: ts.MethodDeclaration): string { // Since we're just using the method reference in the handler type, // we don't need the full signature - just return the method name // The actual signature will be inferred from the controller class - return node.name ? (node.name as Identifier).text : ''; + return node.name ? (node.name as ts.Identifier).text : ''; } /** From 3dd5333993d8fa01c4a80cb845ef17dbeaa6a7b5 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 13:58:41 +0100 Subject: [PATCH 05/12] Fix reference to `SnapController:get` --- .../src/interface/SnapInterfaceController.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx b/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx index afe461771d..ece1306f40 100644 --- a/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx +++ b/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx @@ -673,7 +673,7 @@ describe('SnapInterfaceController', () => { expect(controllerMessenger.call).toHaveBeenNthCalledWith( 1, - 'SnapController:get', + 'SnapController:getSnap', MOCK_SNAP_ID, ); }); From 0c3fddd31c8300441efeb8b8751f371437ed5549 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 14:29:14 +0100 Subject: [PATCH 06/12] Remove comment --- packages/snaps-controllers/src/snaps/SnapController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 39d0883c08..9ea71da4a2 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -241,7 +241,6 @@ export const MESSENGER_EXPOSED_METHODS = [ 'setClientActive', ] as const; -// TODO: Figure out how to name these export const SNAP_APPROVAL_INSTALL = 'wallet_installSnap'; export const SNAP_APPROVAL_UPDATE = 'wallet_updateSnap'; export const SNAP_APPROVAL_RESULT = 'wallet_installSnapResult'; From 2f7e862e620d125196508489862620adc4324d20 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 14:31:00 +0100 Subject: [PATCH 07/12] Add changelog entry --- packages/snaps-controllers/CHANGELOG.md | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/snaps-controllers/CHANGELOG.md b/packages/snaps-controllers/CHANGELOG.md index 320e4fa6e0..3a8e839c15 100644 --- a/packages/snaps-controllers/CHANGELOG.md +++ b/packages/snaps-controllers/CHANGELOG.md @@ -7,6 +7,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **BREAKING:** All `SnapController` action types were renamed from `DoSomething` to `SnapControllerDoSomethingAction` ([#3907](https://github.com/MetaMask/snaps/pull/3907)) + - `GetSnap` is now `SnapControllerGetSnapAction`. + - Note: The method is now called `getSnap` instead of `get`. + - `HandleSnapRequest` is now `SnapControllerHandleRequestAction`. + - `GetSnapState` is now `SnapControllerGetSnapStateAction`. + - `HasSnap` is now `SnapControllerHasSnapAction`. + - Note: The method is now called `hasSnap` instead of `has`. + - `UpdateSnapState` is now `SnapControllerUpdateSnapStateAction`. + - `ClearSnapState` is now `SnapControllerClearSnapStateAction`. + - `UpdateRegistry` is now `SnapControllerUpdateRegistryAction`. + - `EnableSnap` is now `SnapControllerEnableSnapAction`. + - Note: The method is now called `enableSnap` instead of `enable`. + - `DisableSnap` is now `SnapControllerDisableSnapAction`. + - Note: The method is now called `disableSnap` instead of `disable`. + - `RemoveSnap` is now `SnapControllerRemoveSnapAction`. + - Note: The method is now called `removeSnap` instead of `remove`. + - `GetPermittedSnaps` is now `SnapControllerGetPermittedSnapsAction`. + - Note: The method is now called `getPermittedSnaps` instead of `getPermitted`. + - `GetAllSnaps` is now `SnapControllerGetAllSnapsAction`. + - Note: The method is now called `getAllSnaps` instead of `getAll`. + - `GetRunnableSnaps` is now `SnapControllerGetRunnableSnapsAction`. + - `StopAllSnaps` is now `SnapControllerStopAllSnapsAction`. + - `IncrementActiveReferences` is now `SnapControllerIncrementActiveReferencesAction`. + - `DecrementActiveReferences` is now `SnapControllerDecrementActiveReferencesAction`. + - `InstallSnaps` is now `SnapControllerInstallSnapsAction`. + - Note: The method is now called `installSnaps` instead of `install`. + - `DisconnectOrigin` is now `SnapControllerRemoveSnapFromSubjectAction`. + - `RevokeDynamicPermissions` is now `SnapControllerRevokeDynamicSnapPermissionsAction`. + - `GetSnapFile` is now `SnapControllerGetSnapFileAction`. + - `IsMinimumPlatformVersion` is now `SnapControllerIsMinimumPlatformVersionAction`. + - `SetClientActive` is now `SnapControllerSetClientActiveAction`. +- **BREAKING:** All `SnapController` event types were renamed from `OnSomething` to `SnapControllerOnSomethingEvent` ([#3907](https://github.com/MetaMask/snaps/pull/3907)) + - `SnapStateChange` was removed in favour of `SnapControllerStateChangeEvent`. + - `SnapBlocked` is now `SnapControllerSnapBlockedEvent`. + - `SnapInstallStarted` is now `SnapControllerSnapInstallStartedEvent`. + - `SnapInstallFailed` is now `SnapControllerSnapInstallFailedEvent`. + - `SnapInstalled` is now `SnapControllerSnapInstalledEvent`. + - `SnapUninstalled` is now `SnapControllerSnapUninstalledEvent`. + - `SnapUnblocked` is now `SnapControllerSnapUnblockedEvent. + - `SnapUpdated` is now `SnapControllerSnapUpdatedEvent`. + - `SnapRolledback` is now `SnapControllerSnapRolledbackEvent`. + - `SnapTerminated` is now `SnapControllerSnapTerminatedEvent`. + - `SnapEnabled` is now `SnapControllerSnapEnabledEvent`. + - `SnapDisabled` is now `SnapControllerSnapDisabledEvent`. + + ## [18.0.4] ### Fixed From 56d9bede44bb55993145d487e873ef566489c489 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 14:31:31 +0100 Subject: [PATCH 08/12] Remove extra whitespace --- packages/snaps-controllers/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/snaps-controllers/CHANGELOG.md b/packages/snaps-controllers/CHANGELOG.md index 3a8e839c15..2060c4f03e 100644 --- a/packages/snaps-controllers/CHANGELOG.md +++ b/packages/snaps-controllers/CHANGELOG.md @@ -54,7 +54,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `SnapEnabled` is now `SnapControllerSnapEnabledEvent`. - `SnapDisabled` is now `SnapControllerSnapDisabledEvent`. - ## [18.0.4] ### Fixed From 95f516b1d1576411f70f2f22030333a64ec754de Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 14:37:32 +0100 Subject: [PATCH 09/12] Remove `destroy` action --- .../src/snaps/SnapController-method-action-types.ts | 6 ------ packages/snaps-controllers/src/snaps/SnapController.ts | 1 - packages/snaps-controllers/src/snaps/index.ts | 1 - 3 files changed, 8 deletions(-) diff --git a/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts index 1ec97d9acc..4a25094ba3 100644 --- a/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts +++ b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts @@ -347,11 +347,6 @@ export type SnapControllerInstallSnapsAction = { handler: SnapController['installSnaps']; }; -export type SnapControllerDestroyAction = { - type: `SnapController:destroy`; - handler: SnapController['destroy']; -}; - /** * Passes a JSON-RPC request object to the RPC handler function of a snap. * @@ -411,6 +406,5 @@ export type SnapControllerMethodActions = | SnapControllerGetRunnableSnapsAction | SnapControllerGetPermittedSnapsAction | SnapControllerInstallSnapsAction - | SnapControllerDestroyAction | SnapControllerHandleRequestAction | SnapControllerSetClientActiveAction; diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 9ea71da4a2..4ce8200b26 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -236,7 +236,6 @@ export const MESSENGER_EXPOSED_METHODS = [ 'getRunnableSnaps', 'getPermittedSnaps', 'installSnaps', - 'destroy', 'handleRequest', 'setClientActive', ] as const; diff --git a/packages/snaps-controllers/src/snaps/index.ts b/packages/snaps-controllers/src/snaps/index.ts index 99f15f7766..6b7c9953ed 100644 --- a/packages/snaps-controllers/src/snaps/index.ts +++ b/packages/snaps-controllers/src/snaps/index.ts @@ -53,7 +53,6 @@ export type { SnapControllerGetRunnableSnapsAction, SnapControllerGetPermittedSnapsAction, SnapControllerInstallSnapsAction, - SnapControllerDestroyAction, SnapControllerHandleRequestAction, SnapControllerSetClientActiveAction, } from './SnapController-method-action-types'; From ca2f5c98003bff03e6d0eb00619e0b8b8a13ef5a Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 14:46:56 +0100 Subject: [PATCH 10/12] Fix `invokeSnap` --- .../src/restricted/invokeSnap.test.ts | 38 ++++++++++++------- .../src/restricted/invokeSnap.ts | 16 ++++---- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/packages/snaps-rpc-methods/src/restricted/invokeSnap.test.ts b/packages/snaps-rpc-methods/src/restricted/invokeSnap.test.ts index e36ebf07d5..44968b375a 100644 --- a/packages/snaps-rpc-methods/src/restricted/invokeSnap.test.ts +++ b/packages/snaps-rpc-methods/src/restricted/invokeSnap.test.ts @@ -10,7 +10,10 @@ import { MOCK_LOCAL_SNAP_ID, } from '@metamask/snaps-utils/test-utils'; -import type { InstallSnaps, GetPermittedSnaps } from './invokeSnap'; +import type { + SnapControllerInstallSnapsAction, + SnapControllerGetPermittedSnapsAction, +} from './invokeSnap'; import { invokeSnapBuilder, getInvokeSnapImplementation, @@ -113,20 +116,23 @@ describe('implementation', () => { describe('handleSnapInstall', () => { it('calls SnapController:install with the right parameters', async () => { const messenger = new MockControllerMessenger< - InstallSnaps | GetPermittedSnaps, + SnapControllerGetPermittedSnapsAction | SnapControllerInstallSnapsAction, never >(); const sideEffectMessenger = new Messenger< 'PermissionController', - InstallSnaps | GetPermittedSnaps, + SnapControllerGetPermittedSnapsAction | SnapControllerInstallSnapsAction, never, any >({ namespace: 'PermissionController', parent: messenger }); messenger.delegate({ messenger: sideEffectMessenger, - actions: ['SnapController:install', 'SnapController:getPermitted'], + actions: [ + 'SnapController:installSnaps', + 'SnapController:getPermittedSnaps', + ], }); const expectedResult = { @@ -134,11 +140,14 @@ describe('handleSnapInstall', () => { }; messenger.registerActionHandler( - 'SnapController:install', + 'SnapController:installSnaps', async () => expectedResult, ); - messenger.registerActionHandler('SnapController:getPermitted', () => ({})); + messenger.registerActionHandler( + 'SnapController:getPermittedSnaps', + () => ({}), + ); jest.spyOn(sideEffectMessenger, 'call'); @@ -166,7 +175,7 @@ describe('handleSnapInstall', () => { }); expect(sideEffectMessenger.call).toHaveBeenCalledWith( - 'SnapController:install', + 'SnapController:installSnaps', MOCK_ORIGIN, requestedSnaps, ); @@ -176,20 +185,23 @@ describe('handleSnapInstall', () => { it('dedupes snaps before calling installSnaps', async () => { const messenger = new MockControllerMessenger< - InstallSnaps | GetPermittedSnaps, + SnapControllerGetPermittedSnapsAction | SnapControllerInstallSnapsAction, never >(); const sideEffectMessenger = new Messenger< 'PermissionController', - InstallSnaps | GetPermittedSnaps, + SnapControllerGetPermittedSnapsAction | SnapControllerInstallSnapsAction, never, any >({ namespace: 'PermissionController', parent: messenger }); messenger.delegate({ messenger: sideEffectMessenger, - actions: ['SnapController:install', 'SnapController:getPermitted'], + actions: [ + 'SnapController:installSnaps', + 'SnapController:getPermittedSnaps', + ], }); const expectedResult = { @@ -197,11 +209,11 @@ describe('handleSnapInstall', () => { }; messenger.registerActionHandler( - 'SnapController:install', + 'SnapController:installSnaps', async () => expectedResult, ); - messenger.registerActionHandler('SnapController:getPermitted', () => ({ + messenger.registerActionHandler('SnapController:getPermittedSnaps', () => ({ [MOCK_SNAP_ID]: getTruncatedSnap(), })); @@ -232,7 +244,7 @@ describe('handleSnapInstall', () => { }); expect(sideEffectMessenger.call).toHaveBeenCalledWith( - 'SnapController:install', + 'SnapController:installSnaps', MOCK_ORIGIN, { [MOCK_LOCAL_SNAP_ID]: {} }, ); diff --git a/packages/snaps-rpc-methods/src/restricted/invokeSnap.ts b/packages/snaps-rpc-methods/src/restricted/invokeSnap.ts index f7448fbe2b..177d4537c7 100644 --- a/packages/snaps-rpc-methods/src/restricted/invokeSnap.ts +++ b/packages/snaps-rpc-methods/src/restricted/invokeSnap.ts @@ -22,20 +22,22 @@ import type { MethodHooksObject } from '../utils'; export const WALLET_SNAP_PERMISSION_KEY = 'wallet_snap'; // Redeclare installSnaps action type to avoid circular dependencies -export type InstallSnaps = { - type: `SnapController:install`; +export type SnapControllerInstallSnapsAction = { + type: `SnapController:installSnaps`; handler: ( origin: string, requestedSnaps: RequestSnapsParams, ) => Promise; }; -export type GetPermittedSnaps = { - type: `SnapController:getPermitted`; +export type SnapControllerGetPermittedSnapsAction = { + type: `SnapController:getPermittedSnaps`; handler: (origin: string) => RequestSnapsResult; }; -type AllowedActions = InstallSnaps | GetPermittedSnaps; +type AllowedActions = + | SnapControllerInstallSnapsAction + | SnapControllerGetPermittedSnapsAction; export type InvokeSnapMethodHooks = { handleSnapRpcRequest: ({ @@ -78,7 +80,7 @@ export const handleSnapInstall: PermissionSideEffect< .value as RequestSnapsParams; const permittedSnaps = messenger.call( - `SnapController:getPermitted`, + `SnapController:getPermittedSnaps`, requestData.metadata.origin, ); @@ -93,7 +95,7 @@ export const handleSnapInstall: PermissionSideEffect< ); return messenger.call( - `SnapController:install`, + `SnapController:installSnaps`, requestData.metadata.origin, dedupedSnaps, ); From bfc276d45b09358fd7b2679a21c9440593ece312 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 15:12:57 +0100 Subject: [PATCH 11/12] Remove some exports --- packages/snaps-controllers/src/snaps/SnapController.ts | 6 ------ packages/snaps-controllers/src/snaps/index.ts | 3 --- 2 files changed, 9 deletions(-) diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 4ce8200b26..27ecbdabd7 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -352,12 +352,6 @@ export type SnapRuntimeData = { getStateMutex: Mutex; }; -export type SnapError = { - message: string; - code: number; - data?: Json; -}; - // Types that probably should be defined elsewhere in prod type StoredSnaps = Record; diff --git a/packages/snaps-controllers/src/snaps/index.ts b/packages/snaps-controllers/src/snaps/index.ts index 6b7c9953ed..3736b0f181 100644 --- a/packages/snaps-controllers/src/snaps/index.ts +++ b/packages/snaps-controllers/src/snaps/index.ts @@ -15,9 +15,6 @@ export type { SnapControllerSnapUpdatedEvent, SnapControllerState, SnapControllerStateChangeEvent, - SnapError, - SnapRuntimeData, - PendingRequest, PreinstalledSnapFile, PreinstalledSnap, PersistedSnapControllerState, From 3b515900aacbaa9dd7a2a5f3ad9fd51e44d9a2b4 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Fri, 20 Mar 2026 15:20:55 +0100 Subject: [PATCH 12/12] Rename `removeSnapFromSubject` to `disconnectOrigin` --- packages/snaps-controllers/CHANGELOG.md | 3 ++- .../snaps/SnapController-method-action-types.ts | 13 +++++++------ .../src/snaps/SnapController.test.tsx | 6 +++--- .../src/snaps/SnapController.ts | 17 +++++++++-------- packages/snaps-controllers/src/snaps/index.ts | 2 +- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/snaps-controllers/CHANGELOG.md b/packages/snaps-controllers/CHANGELOG.md index 2060c4f03e..9f02e28921 100644 --- a/packages/snaps-controllers/CHANGELOG.md +++ b/packages/snaps-controllers/CHANGELOG.md @@ -35,7 +35,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DecrementActiveReferences` is now `SnapControllerDecrementActiveReferencesAction`. - `InstallSnaps` is now `SnapControllerInstallSnapsAction`. - Note: The method is now called `installSnaps` instead of `install`. - - `DisconnectOrigin` is now `SnapControllerRemoveSnapFromSubjectAction`. + - `DisconnectOrigin` is now `SnapControllerDisconnectOriginAction`. + - Note: The method is now called `disconnectOrigin` instead of `removeSnapFromSubject`. - `RevokeDynamicPermissions` is now `SnapControllerRevokeDynamicSnapPermissionsAction`. - `GetSnapFile` is now `SnapControllerGetSnapFileAction`. - `IsMinimumPlatformVersion` is now `SnapControllerIsMinimumPlatformVersionAction`. diff --git a/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts index 4a25094ba3..9caef94965 100644 --- a/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts +++ b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts @@ -259,14 +259,15 @@ export type SnapControllerRemoveSnapsAction = { }; /** - * Removes a snap's permission (caveat) from the specified subject. + * Disconnect the Snap from the given origin, meaning the origin can no longer + * interact with the Snap until it is reconnected. * - * @param origin - The origin from which to remove the snap. + * @param origin - The origin from which to remove the Snap. * @param snapId - The id of the snap to remove. */ -export type SnapControllerRemoveSnapFromSubjectAction = { - type: `SnapController:removeSnapFromSubject`; - handler: SnapController['removeSnapFromSubject']; +export type SnapControllerDisconnectOriginAction = { + type: `SnapController:disconnectOrigin`; + handler: SnapController['disconnectOrigin']; }; /** @@ -398,7 +399,7 @@ export type SnapControllerMethodActions = | SnapControllerClearStateAction | SnapControllerRemoveSnapAction | SnapControllerRemoveSnapsAction - | SnapControllerRemoveSnapFromSubjectAction + | SnapControllerDisconnectOriginAction | SnapControllerRevokeDynamicSnapPermissionsAction | SnapControllerIncrementActiveReferencesAction | SnapControllerDecrementActiveReferencesAction diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.tsx b/packages/snaps-controllers/src/snaps/SnapController.test.tsx index 72fe80f901..0fd670b778 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.tsx +++ b/packages/snaps-controllers/src/snaps/SnapController.test.tsx @@ -12423,7 +12423,7 @@ describe('SnapController', () => { }); describe('SnapController:disconnectOrigin', () => { - it('calls SnapController.removeSnapFromSubject()', async () => { + it('calls SnapController.disconnectOrigin()', async () => { const permittedSnaps = [ MOCK_SNAP_ID, MOCK_LOCAL_SNAP_ID, @@ -12447,13 +12447,13 @@ describe('SnapController', () => { const removeSnapFromSubjectSpy = jest.spyOn( snapController, - 'removeSnapFromSubject', + 'disconnectOrigin', ); const callActionSpy = jest.spyOn(options.messenger, 'call'); options.messenger.call( - 'SnapController:removeSnapFromSubject', + 'SnapController:disconnectOrigin', MOCK_ORIGIN, MOCK_SNAP_ID, ); diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 27ecbdabd7..0b8cf3b86b 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -228,7 +228,7 @@ export const MESSENGER_EXPOSED_METHODS = [ 'clearState', 'removeSnap', 'removeSnaps', - 'removeSnapFromSubject', + 'disconnectOrigin', 'revokeDynamicSnapPermissions', 'incrementActiveReferences', 'decrementActiveReferences', @@ -1186,8 +1186,8 @@ export class SnapController extends BaseController< ); this.messenger.registerActionHandler( - `${controllerName}:removeSnapFromSubject`, - (...args) => this.removeSnapFromSubject(...args), + `${controllerName}:disconnectOrigin`, + (...args) => this.disconnectOrigin(...args), ); this.messenger.registerActionHandler( @@ -2430,7 +2430,7 @@ export class SnapController extends BaseController< ); for (const origin of Object.keys(revokedInitialConnections)) { - this.removeSnapFromSubject(origin, snapId); + this.disconnectOrigin(origin, snapId); } } @@ -2490,12 +2490,13 @@ export class SnapController extends BaseController< } /** - * Removes a snap's permission (caveat) from the specified subject. + * Disconnect the Snap from the given origin, meaning the origin can no longer + * interact with the Snap until it is reconnected. * - * @param origin - The origin from which to remove the snap. + * @param origin - The origin from which to remove the Snap. * @param snapId - The id of the snap to remove. */ - removeSnapFromSubject(origin: string, snapId: SnapId) { + disconnectOrigin(origin: string, snapId: SnapId) { const subjectPermissions = this.messenger.call( 'PermissionController:getPermissions', origin, @@ -2567,7 +2568,7 @@ export class SnapController extends BaseController< 'PermissionController:getSubjectNames', ); for (const subject of subjects) { - this.removeSnapFromSubject(subject, snapId); + this.disconnectOrigin(subject, snapId); } } diff --git a/packages/snaps-controllers/src/snaps/index.ts b/packages/snaps-controllers/src/snaps/index.ts index 3736b0f181..dd132da9df 100644 --- a/packages/snaps-controllers/src/snaps/index.ts +++ b/packages/snaps-controllers/src/snaps/index.ts @@ -42,7 +42,7 @@ export type { SnapControllerClearStateAction, SnapControllerRemoveSnapAction, SnapControllerRemoveSnapsAction, - SnapControllerRemoveSnapFromSubjectAction, + SnapControllerDisconnectOriginAction, SnapControllerRevokeDynamicSnapPermissionsAction, SnapControllerIncrementActiveReferencesAction, SnapControllerDecrementActiveReferencesAction,