diff --git a/package.json b/package.json index 9d109bdf4c..ef227d66e0 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", @@ -58,6 +59,7 @@ ] }, "resolutions": { + "@metamask/messenger": "npm:@metamask-previews/messenger@0.3.0-preview-a462582", "@esbuild-plugins/node-modules-polyfill@^0.2.2": "patch:@esbuild-plugins/node-modules-polyfill@npm%3A0.2.2#./.yarn/patches/@esbuild-plugins-node-modules-polyfill-npm-0.2.2-f612681798.patch", "@puppeteer/browsers@1.4.6": "patch:@puppeteer/browsers@npm%3A1.7.0#./.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch", "@puppeteer/browsers@^1.6.0": "patch:@puppeteer/browsers@npm%3A1.7.0#./.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch", @@ -120,7 +122,8 @@ "tsx": "^4.20.3", "typescript": "~5.3.3", "typescript-eslint": "^8.6.0", - "vite": "^6.4.1" + "vite": "^6.4.1", + "yargs": "^17.7.1" }, "packageManager": "yarn@4.10.3", "engines": { diff --git a/packages/snaps-controllers/CHANGELOG.md b/packages/snaps-controllers/CHANGELOG.md index 320e4fa6e0..dd6302c997 100644 --- a/packages/snaps-controllers/CHANGELOG.md +++ b/packages/snaps-controllers/CHANGELOG.md @@ -7,6 +7,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **BREAKING:** All action types were renamed from `DoSomething` to `ControllerNameDoSomethingAction` ([#3907](https://github.com/MetaMask/snaps/pull/3907), [#3911](https://github.com/MetaMask/snaps/pull/3911), [#3912](https://github.com/MetaMask/snaps/pull/3912)) + - `SnapController` actions: + - `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`. + - `InstallSnaps` is now `SnapControllerInstallSnapsAction`. + - Note: The method is now called `installSnaps` instead of `install`. + - `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`. + - `SetClientActive` is now `SnapControllerSetClientActiveAction`. + - `CronjobController` actions: + - `Schedule` is now `CronjobControllerScheduleAction`. + - `Cancel` is now `CronjobControllerCancelAction`. + - `Get` is now `CronjobControllerGetAction`. + - `SnapInterfaceController` actions: + - `CreateInterface` is now `SnapInterfaceControllerCreateInterfaceAction`. + - `GetInterface` is now `SnapInterfaceControllerGetInterfaceAction`. + - `UpdateInterface` is now `SnapInterfaceControllerUpdateInterfaceAction`. + - `DeleteInterface` is now `SnapInterfaceControllerDeleteInterfaceAction`. + - `UpdateInterfaceState` is now `SnapInterfaceControllerUpdateInterfaceStateAction`. + - `ResolveInterface` is now `SnapInterfaceControllerResolveInterfaceAction`. +- **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`. + +### Removed + +- **BREAKING:** `incrementActiveReferences` and `decrementActiveReferences` actions were removed ([#3907](https://github.com/MetaMask/snaps/pull/3907)) + ## [18.0.4] ### Fixed diff --git a/packages/snaps-controllers/package.json b/packages/snaps-controllers/package.json index f981211c60..4214bd0b85 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": "messenger-generate-action-types", "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-method-action-types.ts b/packages/snaps-controllers/src/cronjob/CronjobController-method-action-types.ts new file mode 100644 index 0000000000..f11a9f0fe2 --- /dev/null +++ b/packages/snaps-controllers/src/cronjob/CronjobController-method-action-types.ts @@ -0,0 +1,60 @@ +/** + * This file is auto generated by `@metamask/messenger/generate-action-types`. + * Do not edit manually. + */ + +import type { CronjobController } from './CronjobController'; + +/** + * Initialize the CronjobController. + * + * This starts the daily timer, clears out expired events + * and reschedules any remaining events. + */ +export type CronjobControllerInitAction = { + type: `CronjobController:init`; + handler: CronjobController['init']; +}; + +/** + * Schedule a non-recurring background event. + * + * @param event - The event to schedule. + * @returns The ID of the scheduled event. + */ +export type CronjobControllerScheduleAction = { + type: `CronjobController:schedule`; + handler: CronjobController['schedule']; +}; + +/** + * Cancel an event. + * + * @param origin - The origin making the cancel call. + * @param id - The id of the event to cancel. + * @throws If the event does not exist. + */ +export type CronjobControllerCancelAction = { + type: `CronjobController:cancel`; + handler: CronjobController['cancel']; +}; + +/** + * Get a list of a Snap's background events. + * + * @param snapId - The id of the Snap to fetch background events for. + * @returns An array of background events. + */ +export type CronjobControllerGetAction = { + type: `CronjobController:get`; + handler: CronjobController['get']; +}; + +/** + * Union of all CronjobController action types. + */ +export type CronjobControllerMethodActions = + | CronjobControllerInitAction + | CronjobControllerScheduleAction + | CronjobControllerCancelAction + | CronjobControllerGetAction; 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..c59f4e2f7b 100644 --- a/packages/snaps-controllers/src/cronjob/CronjobController.ts +++ b/packages/snaps-controllers/src/cronjob/CronjobController.ts @@ -21,15 +21,16 @@ import { castDraft } from 'immer'; import { DateTime } from 'luxon'; import { nanoid } from 'nanoid'; +import type { CronjobControllerMethodActions } from './CronjobController-method-action-types'; 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,56 +38,31 @@ export type CronjobControllerGetStateAction = ControllerGetStateAction< typeof controllerName, CronjobControllerState >; + export type CronjobControllerStateChangeEvent = ControllerStateChangeEvent< typeof controllerName, CronjobControllerState >; -/** - * Initialise the CronjobController. This should be called after all controllers - * are created. - */ -export type CronjobControllerInitAction = { - type: `${typeof controllerName}:init`; - handler: CronjobController['init']; -}; +export type CronjobControllerActions = + | CronjobControllerGetStateAction + | CronjobControllerMethodActions; -export type Schedule = { - type: `${typeof controllerName}:schedule`; - handler: CronjobController['schedule']; -}; +export type CronjobControllerEvents = CronjobControllerStateChangeEvent; -export type Cancel = { - type: `${typeof controllerName}:cancel`; - handler: CronjobController['cancel']; -}; +type AllowedActions = GetPermissions | SnapControllerHandleRequestAction; -export type Get = { - type: `${typeof controllerName}:get`; - handler: CronjobController['get']; -}; - -export type CronjobControllerActions = - | CronjobControllerGetStateAction - | HandleSnapRequest - | GetPermissions - | Schedule - | Cancel - | Get - | CronjobControllerInitAction; - -export type CronjobControllerEvents = - | CronjobControllerStateChangeEvent - | SnapInstalled - | SnapUninstalled - | SnapUpdated - | SnapEnabled - | SnapDisabled; +type AllowedEvents = + | SnapControllerSnapInstalledEvent + | SnapControllerSnapUninstalledEvent + | SnapControllerSnapUpdatedEvent + | SnapControllerSnapEnabledEvent + | SnapControllerSnapDisabledEvent; export type CronjobControllerMessenger = Messenger< typeof controllerName, - CronjobControllerActions, - CronjobControllerEvents + CronjobControllerActions | AllowedActions, + CronjobControllerEvents | AllowedEvents >; export const DAILY_TIMEOUT = inMilliseconds(24, Duration.Hour); @@ -156,6 +132,13 @@ export type CronjobControllerState = { const controllerName = 'CronjobController'; +const MESSENGER_EXPOSED_METHODS = [ + 'init', + 'schedule', + 'cancel', + 'get', +] as const; + /** * The cronjob controller is responsible for managing cronjobs and background * events for Snaps. It allows Snaps to schedule events that will be executed @@ -219,22 +202,9 @@ export class CronjobController extends BaseController< this.#handleSnapUpdatedEvent, ); - this.messenger.registerActionHandler(`${controllerName}:init`, (...args) => - this.init(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:schedule`, - (...args) => this.schedule(...args), - ); - - this.messenger.registerActionHandler( - `${controllerName}:cancel`, - (...args) => this.cancel(...args), - ); - - this.messenger.registerActionHandler(`${controllerName}:get`, (...args) => - this.get(...args), + this.messenger.registerMethodActionHandlers( + this, + MESSENGER_EXPOSED_METHODS, ); } diff --git a/packages/snaps-controllers/src/cronjob/index.ts b/packages/snaps-controllers/src/cronjob/index.ts index ed316ed002..ad8d078a66 100644 --- a/packages/snaps-controllers/src/cronjob/index.ts +++ b/packages/snaps-controllers/src/cronjob/index.ts @@ -1 +1,13 @@ -export * from './CronjobController'; +export type { + CronjobControllerGetStateAction, + CronjobControllerState, + CronjobControllerStateChangeEvent, + CronjobControllerStateManager, +} from './CronjobController'; +export { CronjobController } from './CronjobController'; +export type { + CronjobControllerInitAction, + CronjobControllerScheduleAction, + CronjobControllerCancelAction, + CronjobControllerGetAction, +} from './CronjobController-method-action-types'; 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..057dd2860b 100644 --- a/packages/snaps-controllers/src/insights/SnapInsightsController.ts +++ b/packages/snaps-controllers/src/insights/SnapInsightsController.ts @@ -18,8 +18,11 @@ import type { Json, SnapId } from '@metamask/snaps-sdk'; 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 { SnapInterfaceControllerDeleteInterfaceAction } from '../interface'; +import type { + SnapControllerGetAllSnapsAction, + SnapControllerHandleRequestAction, +} from '../snaps'; import { getRunnableSnaps } from '../snaps'; import type { TransactionControllerUnapprovedTransactionAddedEvent, @@ -33,10 +36,10 @@ import type { const controllerName = 'SnapInsightsController'; export type SnapInsightsControllerAllowedActions = - | HandleSnapRequest - | GetAllSnaps + | SnapControllerHandleRequestAction + | SnapControllerGetAllSnapsAction | GetPermissions - | DeleteInterface; + | SnapInterfaceControllerDeleteInterfaceAction; export type SnapInsightsControllerGetStateAction = ControllerGetStateAction< typeof controllerName, @@ -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-method-action-types.ts b/packages/snaps-controllers/src/interface/SnapInterfaceController-method-action-types.ts new file mode 100644 index 0000000000..f0d0fc6945 --- /dev/null +++ b/packages/snaps-controllers/src/interface/SnapInterfaceController-method-action-types.ts @@ -0,0 +1,116 @@ +/** + * This file is auto generated by `@metamask/messenger/generate-action-types`. + * Do not edit manually. + */ + +import type { SnapInterfaceController } from './SnapInterfaceController'; + +/** + * Create an interface in the controller state with the associated data. + * + * @param snapId - The snap id that created the interface. + * @param content - The interface content. + * @param context - An optional interface context object. + * @param contentType - The type of content. + * @returns The newly interface id. + */ +export type SnapInterfaceControllerCreateInterfaceAction = { + type: `SnapInterfaceController:createInterface`; + handler: SnapInterfaceController['createInterface']; +}; + +/** + * Get the data of a given interface id. + * + * @param snapId - The snap id requesting the interface data. + * @param id - The interface id. + * @returns The interface state. + */ +export type SnapInterfaceControllerGetInterfaceAction = { + type: `SnapInterfaceController:getInterface`; + handler: SnapInterfaceController['getInterface']; +}; + +/** + * Get the state of a given interface ID, if the interface has been displayed + * at least once. + * + * @param snapId - The snap ID requesting the interface state. + * @param id - The interface ID. + * @returns The interface state. + */ +export type SnapInterfaceControllerGetInterfaceStateAction = { + type: `SnapInterfaceController:getInterfaceState`; + handler: SnapInterfaceController['getInterfaceState']; +}; + +/** + * Update the interface with the given content. + * + * @param snapId - The snap id requesting the update. + * @param id - The interface id. + * @param content - The new content. + * @param context - An optional interface context object. + */ +export type SnapInterfaceControllerUpdateInterfaceAction = { + type: `SnapInterfaceController:updateInterface`; + handler: SnapInterfaceController['updateInterface']; +}; + +/** + * Delete an interface from state. + * + * @param id - The interface id. + */ +export type SnapInterfaceControllerDeleteInterfaceAction = { + type: `SnapInterfaceController:deleteInterface`; + handler: SnapInterfaceController['deleteInterface']; +}; + +/** + * Update the interface state. + * + * @param id - The interface id. + * @param state - The new state. + */ +export type SnapInterfaceControllerUpdateInterfaceStateAction = { + type: `SnapInterfaceController:updateInterfaceState`; + handler: SnapInterfaceController['updateInterfaceState']; +}; + +/** + * Resolve the promise of a given interface approval request. + * The approval needs to have the same ID as the interface. + * + * @param snapId - The snap id. + * @param id - The interface id. + * @param value - The value to resolve the promise with. + */ +export type SnapInterfaceControllerResolveInterfaceAction = { + type: `SnapInterfaceController:resolveInterface`; + handler: SnapInterfaceController['resolveInterface']; +}; + +/** + * Set the interface as displayed. + * + * @param snapId - The snap ID requesting the update. + * @param id - The interface ID. + */ +export type SnapInterfaceControllerSetInterfaceDisplayedAction = { + type: `SnapInterfaceController:setInterfaceDisplayed`; + handler: SnapInterfaceController['setInterfaceDisplayed']; +}; + +/** + * Union of all SnapInterfaceController action types. + */ +export type SnapInterfaceControllerMethodActions = + | SnapInterfaceControllerCreateInterfaceAction + | SnapInterfaceControllerGetInterfaceAction + | SnapInterfaceControllerGetInterfaceStateAction + | SnapInterfaceControllerUpdateInterfaceAction + | SnapInterfaceControllerDeleteInterfaceAction + | SnapInterfaceControllerUpdateInterfaceStateAction + | SnapInterfaceControllerResolveInterfaceAction + | SnapInterfaceControllerSetInterfaceDisplayedAction; diff --git a/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx b/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx index 0699bff4a8..ece1306f40 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, ); @@ -673,7 +673,7 @@ describe('SnapInterfaceController', () => { expect(controllerMessenger.call).toHaveBeenNthCalledWith( 1, - 'SnapController:get', + 'SnapController:getSnap', MOCK_SNAP_ID, ); }); @@ -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..d992660767 100644 --- a/packages/snaps-controllers/src/interface/SnapInterfaceController.ts +++ b/packages/snaps-controllers/src/interface/SnapInterfaceController.ts @@ -34,57 +34,29 @@ import { assert, hasProperty, parseCaipAccountId } from '@metamask/utils'; import { castDraft } from 'immer'; import { nanoid } from 'nanoid'; +import type { SnapInterfaceControllerMethodActions } from './SnapInterfaceController-method-action-types'; import { constructState, getJsxInterface, isMatchingChainId, validateInterfaceContext, } from './utils'; -import type { GetSnap } from '../snaps'; +import type { SnapControllerGetSnapAction } from '../snaps'; const MAX_UI_CONTENT_SIZE = 10_000_000; // 10 mb const controllerName = 'SnapInterfaceController'; -export type CreateInterface = { - type: `${typeof controllerName}:createInterface`; - handler: SnapInterfaceController['createInterface']; -}; - -export type GetInterface = { - type: `${typeof controllerName}:getInterface`; - handler: SnapInterfaceController['getInterface']; -}; - -export type UpdateInterface = { - type: `${typeof controllerName}:updateInterface`; - handler: SnapInterfaceController['updateInterface']; -}; - -export type DeleteInterface = { - type: `${typeof controllerName}:deleteInterface`; - handler: SnapInterfaceController['deleteInterface']; -}; - -export type UpdateInterfaceState = { - type: `${typeof controllerName}:updateInterfaceState`; - handler: SnapInterfaceController['updateInterfaceState']; -}; - -export type ResolveInterface = { - type: `${typeof controllerName}:resolveInterface`; - handler: SnapInterfaceController['resolveInterface']; -}; - -export type SnapInterfaceControllerGetInterfaceStateAction = { - type: `${typeof controllerName}:getInterfaceState`; - handler: SnapInterfaceController['getInterfaceState']; -}; - -export type SnapInterfaceControllerSetInterfaceDisplayedAction = { - type: `${typeof controllerName}:setInterfaceDisplayed`; - handler: SnapInterfaceController['setInterfaceDisplayed']; -}; +const MESSENGER_EXPOSED_METHODS = [ + 'createInterface', + 'getInterface', + 'getInterfaceState', + 'updateInterface', + 'deleteInterface', + 'updateInterfaceState', + 'resolveInterface', + 'setInterfaceDisplayed', +] as const; type AccountsControllerGetAccountByAddressAction = { type: `AccountsController:getAccountByAddress`; @@ -125,7 +97,7 @@ export type SnapInterfaceControllerAllowedActions = | PhishingControllerTestOrigin | ApprovalControllerHasRequestAction | ApprovalControllerAcceptRequestAction - | GetSnap + | SnapControllerGetSnapAction | MultichainAssetsControllerGetStateAction | AccountsControllerGetSelectedMultichainAccountAction | AccountsControllerGetAccountByAddressAction @@ -133,15 +105,8 @@ export type SnapInterfaceControllerAllowedActions = | HasPermission; export type SnapInterfaceControllerActions = - | CreateInterface - | GetInterface - | UpdateInterface - | DeleteInterface - | UpdateInterfaceState - | ResolveInterface - | SnapInterfaceControllerGetInterfaceStateAction - | SnapInterfaceControllerSetInterfaceDisplayedAction - | SnapInterfaceControllerGetStateAction; + | SnapInterfaceControllerGetStateAction + | SnapInterfaceControllerMethodActions; export type SnapInterfaceControllerStateChangeEvent = ControllerStateChangeEvent< @@ -151,7 +116,7 @@ export type SnapInterfaceControllerStateChangeEvent = type OtherNotification = { type: string; [key: string]: unknown }; -export type ExpandedView = { +type ExpandedView = { title: string; interfaceId: string; footerLink?: { href: string; text: string }; @@ -250,52 +215,9 @@ export class SnapInterfaceController extends BaseController< this.#onNotificationsListUpdated.bind(this), ); - this.#registerMessageHandlers(); - } - - /** - * Constructor helper for registering this controller's messaging system - * actions. - */ - #registerMessageHandlers() { - this.messenger.registerActionHandler( - `${controllerName}:createInterface`, - this.createInterface.bind(this), - ); - - this.messenger.registerActionHandler( - `${controllerName}:getInterface`, - this.getInterface.bind(this), - ); - - this.messenger.registerActionHandler( - `${controllerName}:getInterfaceState`, - this.getInterfaceState.bind(this), - ); - - this.messenger.registerActionHandler( - `${controllerName}:updateInterface`, - this.updateInterface.bind(this), - ); - - this.messenger.registerActionHandler( - `${controllerName}:deleteInterface`, - this.deleteInterface.bind(this), - ); - - this.messenger.registerActionHandler( - `${controllerName}:updateInterfaceState`, - this.updateInterfaceState.bind(this), - ); - - this.messenger.registerActionHandler( - `${controllerName}:resolveInterface`, - this.resolveInterface.bind(this), - ); - - this.messenger.registerActionHandler( - `${controllerName}:setInterfaceDisplayed`, - this.setInterfaceDisplayed.bind(this), + this.messenger.registerMethodActionHandlers( + this, + MESSENGER_EXPOSED_METHODS, ); } @@ -598,7 +520,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/interface/index.ts b/packages/snaps-controllers/src/interface/index.ts index e3f329b66e..3abc18faf2 100644 --- a/packages/snaps-controllers/src/interface/index.ts +++ b/packages/snaps-controllers/src/interface/index.ts @@ -1 +1,20 @@ -export * from './SnapInterfaceController'; +export type { + SnapInterfaceControllerActions, + SnapInterfaceControllerAllowedActions, + SnapInterfaceControllerGetStateAction, + SnapInterfaceControllerState, + SnapInterfaceControllerStateChangeEvent, + StoredInterface, +} from './SnapInterfaceController'; +export { SnapInterfaceController } from './SnapInterfaceController'; +export type { + SnapInterfaceControllerCreateInterfaceAction, + SnapInterfaceControllerDeleteInterfaceAction, + SnapInterfaceControllerGetInterfaceAction, + SnapInterfaceControllerGetInterfaceStateAction, + SnapInterfaceControllerMethodActions, + SnapInterfaceControllerResolveInterfaceAction, + SnapInterfaceControllerSetInterfaceDisplayedAction, + SnapInterfaceControllerUpdateInterfaceAction, + SnapInterfaceControllerUpdateInterfaceStateAction, +} from './SnapInterfaceController-method-action-types'; 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..6abc6c22ec --- /dev/null +++ b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts @@ -0,0 +1,335 @@ +/** + * This file is auto generated by `@metamask/messenger/generate-action-types`. + * 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']; +}; + +/** + * 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']; +}; + +/** + * 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']; +}; + +/** + * 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 snapId - The id of the snap to remove. + */ +export type SnapControllerDisconnectOriginAction = { + type: `SnapController:disconnectOrigin`; + handler: SnapController['disconnectOrigin']; +}; + +/** + * 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']; +}; + +/** + * 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']; +}; + +/** + * 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 + | SnapControllerEnableSnapAction + | SnapControllerDisableSnapAction + | SnapControllerStopSnapAction + | SnapControllerStopAllSnapsAction + | SnapControllerIsSnapRunningAction + | SnapControllerHasSnapAction + | SnapControllerGetSnapAction + | SnapControllerUpdateSnapStateAction + | SnapControllerClearSnapStateAction + | SnapControllerGetSnapStateAction + | SnapControllerGetSnapFileAction + | SnapControllerIsMinimumPlatformVersionAction + | SnapControllerClearStateAction + | SnapControllerRemoveSnapAction + | SnapControllerRemoveSnapsAction + | SnapControllerDisconnectOriginAction + | SnapControllerRevokeDynamicSnapPermissionsAction + | SnapControllerGetAllSnapsAction + | SnapControllerGetRunnableSnapsAction + | SnapControllerGetPermittedSnapsAction + | SnapControllerInstallSnapsAction + | SnapControllerHandleRequestAction + | SnapControllerSetClientActiveAction; diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.tsx b/packages/snaps-controllers/src/snaps/SnapController.test.tsx index cfc481a078..94ec443b68 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,15 +2075,12 @@ 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'); - rootMessenger.call( - 'SnapController:incrementActiveReferences', - MOCK_SNAP_ID, - ); + snapController.incrementActiveReferences(snap.id); expect( await snapController.handleRequest({ @@ -2102,10 +2101,7 @@ describe('SnapController', () => { // Should still be running after idle timeout expect(snapController.state.snaps[snap.id].status).toBe('running'); - options.rootMessenger.call( - 'SnapController:decrementActiveReferences', - MOCK_SNAP_ID, - ); + snapController.decrementActiveReferences(snap.id); await sleep(100); @@ -2117,7 +2113,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 +2130,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 +2144,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 +2162,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 +2214,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 +2302,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 +2318,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 +2337,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 +2353,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 +2371,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 +2404,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 +2418,7 @@ describe('SnapController', () => { }); it('allows MetaMask to send a keyring request', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2455,7 +2451,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 +2465,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 +2498,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 +2512,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 +2545,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 +2559,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 +2593,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 +2607,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 +2640,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 +2654,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 +2688,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 +2715,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 +2746,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 +2763,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 +2782,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 +2816,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 +2840,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 +2856,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 +2880,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 +2896,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 +2929,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 +2945,7 @@ describe('SnapController', () => { }); it('injects context into onUserInput', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -3021,7 +3017,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 +3074,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 +3139,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 +3200,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 +3259,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 +3313,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 +3351,7 @@ describe('SnapController', () => { rootMessenger.registerActionHandler( 'SnapInterfaceController:getInterface', + // @ts-expect-error: Partial mock. () => ({ snapId: MOCK_SNAP_ID, content: foo, state: {} }), ); @@ -3376,7 +3373,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 +3438,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 +3499,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 +3553,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 +3613,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 +3665,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 +3717,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 +3782,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 +3836,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 +3895,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 +3960,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 +4014,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 +4073,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 +4130,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 +4192,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 +4245,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 +4307,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 +4383,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 +4475,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 +4597,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 +4659,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 +4733,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 +4818,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 +4880,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 +4953,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 +5038,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 +5162,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 +5224,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 +5301,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 +5344,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 +5388,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 +5637,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 +5777,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 +5819,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 +6019,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 +6130,7 @@ describe('SnapController', () => { }); it('grants connection permission to initialConnections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -6192,7 +6189,7 @@ describe('SnapController', () => { }); it('updates existing caveats to satisfy initialConnections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const initialConnections = { 'npm:filsnap': {}, @@ -6241,7 +6238,7 @@ describe('SnapController', () => { }); it('supports preinstalled snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); jest.spyOn(rootMessenger, 'publish'); @@ -6333,11 +6330,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 +6421,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 +6505,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 +6594,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 +6711,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 +6744,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 +6852,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 +6913,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 +6961,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 +7015,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 +7112,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 +7202,7 @@ describe('SnapController', () => { }); it('supports onInstall for preinstalled Snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); rootMessenger.registerActionHandler( @@ -7270,7 +7268,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 +7335,7 @@ describe('SnapController', () => { }); it('supports onUpdate for preinstalled Snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); rootMessenger.registerActionHandler( @@ -7410,7 +7408,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 +7917,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -7999,7 +7997,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -8073,7 +8071,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 +8423,7 @@ describe('SnapController', () => { }), ); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); let revokedConnection = false; @@ -8474,8 +8472,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 +8491,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 +8628,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 +8643,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 +8778,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 +8817,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 +8826,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 +8882,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 +8902,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 +8957,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 +9025,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 +9166,7 @@ describe('SnapController', () => { }), ); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -9283,7 +9281,7 @@ describe('SnapController', () => { }), ); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -9410,9 +9408,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 +9458,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 +9560,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 +9620,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 +9676,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 +9919,7 @@ describe('SnapController', () => { }); it('supports initialConnections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -10037,7 +10035,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 +10189,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 +10408,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 +10458,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 +10479,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 +10503,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 +10522,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 +10573,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 +10593,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 +10620,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 +10658,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 +10674,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 +10691,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 +10726,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 +10734,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 +10764,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 +10777,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 +10832,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 +10873,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 +10901,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 +10938,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 +10951,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 +11008,7 @@ describe('SnapController', () => { }, ]; - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:revokeAllPermissions', @@ -11050,16 +11048,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 +11102,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 +11120,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 +11201,7 @@ describe('SnapController', () => { .spyOn(console, 'error') .mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -11261,9 +11257,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 +11299,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 +11315,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 +11349,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 +11382,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 +11407,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 +11423,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 +11449,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 +11467,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 +11884,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 +12263,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 +12286,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 +12309,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 +12318,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 +12334,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); const result = options.messenger.call( - 'SnapController:getPermitted', + 'SnapController:getPermittedSnaps', mockSnap.origin, ); expect(result).toStrictEqual({ @@ -12364,7 +12360,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 +12408,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); @@ -12421,7 +12417,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, @@ -12445,7 +12441,7 @@ describe('SnapController', () => { const removeSnapFromSubjectSpy = jest.spyOn( snapController, - 'removeSnapFromSubject', + 'disconnectOrigin', ); const callActionSpy = jest.spyOn(options.messenger, 'call'); @@ -12487,7 +12483,7 @@ describe('SnapController', () => { const callActionSpy = jest.spyOn(options.messenger, 'call'); options.messenger.call( - 'SnapController:revokeDynamicPermissions', + 'SnapController:revokeDynamicSnapPermissions', MOCK_SNAP_ID, ['endowment:caip25'], ); @@ -12507,7 +12503,7 @@ describe('SnapController', () => { expect(() => options.messenger.call( - 'SnapController:revokeDynamicPermissions', + 'SnapController:revokeDynamicSnapPermissions', MOCK_SNAP_ID, ['snap_notify'], ), @@ -12545,7 +12541,7 @@ describe('SnapController', () => { expect( await options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './src/foo.json', ), @@ -12595,7 +12591,7 @@ describe('SnapController', () => { expect( await options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './src/foo.json', AuxiliaryFileEncoding.Hex, @@ -12621,7 +12617,7 @@ describe('SnapController', () => { expect( await options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './foo.json', ), @@ -12668,7 +12664,7 @@ describe('SnapController', () => { await expect( options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './src/foo.json', AuxiliaryFileEncoding.Hex, @@ -12683,7 +12679,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 +12710,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12750,7 +12747,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 +12767,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12795,7 +12793,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 +12816,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12832,7 +12831,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 +12863,7 @@ describe('SnapController', () => { getTruncatedSnap(), '0.9.0', MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12900,7 +12900,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 +12920,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12945,7 +12946,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 +12970,7 @@ describe('SnapController', () => { getTruncatedSnap(), '0.9.0', MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -13112,7 +13114,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 +13201,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 +13447,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..9a3b7e4aaf 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -171,11 +171,12 @@ 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 { - CreateInterface, - GetInterface, + SnapInterfaceControllerCreateInterfaceAction, + SnapInterfaceControllerGetInterfaceAction, SnapInterfaceControllerSetInterfaceDisplayedAction, } from '../interface'; import { log } from '../logging'; @@ -205,7 +206,36 @@ import { export const controllerName = 'SnapController'; -// TODO: Figure out how to name these +// This is used by the `generate-method-action-types` script. +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const MESSENGER_EXPOSED_METHODS = [ + 'init', + 'updateRegistry', + 'enableSnap', + 'disableSnap', + 'stopSnap', + 'stopAllSnaps', + 'isSnapRunning', + 'hasSnap', + 'getSnap', + 'updateSnapState', + 'clearSnapState', + 'getSnapState', + 'getSnapFile', + 'isMinimumPlatformVersion', + 'clearState', + 'removeSnap', + 'removeSnaps', + 'disconnectOrigin', + 'revokeDynamicSnapPermissions', + 'getAllSnaps', + 'getRunnableSnaps', + 'getPermittedSnaps', + 'installSnaps', + 'handleRequest', + 'setClientActive', +] as const; + export const SNAP_APPROVAL_INSTALL = 'wallet_installSnap'; export const SNAP_APPROVAL_UPDATE = 'wallet_updateSnap'; export const SNAP_APPROVAL_RESULT = 'wallet_installSnapResult'; @@ -318,12 +348,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; @@ -359,188 +383,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 +405,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 +413,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 +422,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 +430,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 +438,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 +446,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 +459,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 +468,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 +477,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 +485,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 +498,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 = @@ -683,8 +539,8 @@ export type AllowedActions = | GetMetadata | Update | ResolveVersion - | CreateInterface - | GetInterface + | SnapInterfaceControllerCreateInterfaceAction + | SnapInterfaceControllerGetInterfaceAction | SnapInterfaceControllerSetInterfaceDisplayedAction | StorageServiceSetItemAction | StorageServiceGetItemAction @@ -693,12 +549,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 @@ -1178,7 +1034,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< @@ -1250,8 +1106,9 @@ export class SnapController extends BaseController< (...args) => this.clearSnapState(...args), ); - this.messenger.registerActionHandler(`${controllerName}:get`, (...args) => - this.get(...args), + this.messenger.registerActionHandler( + `${controllerName}:getSnap`, + (...args) => this.getSnap(...args), ); this.messenger.registerActionHandler( @@ -1264,8 +1121,9 @@ export class SnapController extends BaseController< async (...args) => this.handleRequest(...args), ); - this.messenger.registerActionHandler(`${controllerName}:has`, (...args) => - this.has(...args), + this.messenger.registerActionHandler( + `${controllerName}:hasSnap`, + (...args) => this.hasSnap(...args), ); this.messenger.registerActionHandler( @@ -1279,32 +1137,32 @@ export class SnapController extends BaseController< ); this.messenger.registerActionHandler( - `${controllerName}:enable`, + `${controllerName}:enableSnap`, (...args) => this.enableSnap(...args), ); this.messenger.registerActionHandler( - `${controllerName}:disable`, + `${controllerName}:disableSnap`, async (...args) => this.disableSnap(...args), ); this.messenger.registerActionHandler( - `${controllerName}:remove`, + `${controllerName}:removeSnap`, async (...args) => this.removeSnap(...args), ); this.messenger.registerActionHandler( - `${controllerName}:getPermitted`, + `${controllerName}:getPermittedSnaps`, (...args) => this.getPermittedSnaps(...args), ); this.messenger.registerActionHandler( - `${controllerName}:install`, + `${controllerName}:installSnaps`, async (...args) => this.installSnaps(...args), ); this.messenger.registerActionHandler( - `${controllerName}:getAll`, + `${controllerName}:getAllSnaps`, (...args) => this.getAllSnaps(...args), ); @@ -1313,28 +1171,18 @@ export class SnapController extends BaseController< (...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), + (...args) => this.disconnectOrigin(...args), ); this.messenger.registerActionHandler( - `${controllerName}:revokeDynamicPermissions`, + `${controllerName}:revokeDynamicSnapPermissions`, (...args) => this.revokeDynamicSnapPermissions(...args), ); this.messenger.registerActionHandler( - `${controllerName}:getFile`, + `${controllerName}:getSnapFile`, async (...args) => this.getSnapFile(...args), ); @@ -1393,7 +1241,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 +1353,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 +1361,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 +1532,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 +1541,7 @@ export class SnapController extends BaseController< snapId: SnapId, blockedSnapInfo?: BlockReason, ): Promise { - if (!this.has(snapId)) { + if (!this.hasSnap(snapId)) { return; } @@ -1720,13 +1568,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 +1766,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 +1778,7 @@ export class SnapController extends BaseController< this.messenger.publish( 'SnapController:snapEnabled', - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), ); } @@ -1941,7 +1789,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 +1797,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 +1838,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 +1847,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 +1867,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 +1897,7 @@ export class SnapController extends BaseController< this.messenger.publish( 'SnapController:snapTerminated', - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), ); } @@ -2060,8 +1908,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 +1918,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 +1930,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 +1939,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 +1958,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 +1971,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 +2278,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 +2307,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 +2370,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. @@ -2568,7 +2416,7 @@ export class SnapController extends BaseController< ); for (const origin of Object.keys(revokedInitialConnections)) { - this.removeSnapFromSubject(origin, snapId); + this.disconnectOrigin(origin, snapId); } } @@ -2628,12 +2476,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, @@ -2705,7 +2554,7 @@ export class SnapController extends BaseController< 'PermissionController:getSubjectNames', ); for (const subject of subjects) { - this.removeSnapFromSubject(subject, snapId); + this.disconnectOrigin(subject, snapId); } } @@ -2777,8 +2626,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 +2689,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 +2717,7 @@ export class SnapController extends BaseController< pendingInstalls.forEach((snapId) => this.messenger.publish( `SnapController:snapInstalled`, - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), origin, false, ), @@ -2877,7 +2726,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 +2735,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 +2767,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 +2799,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 +2829,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 +2942,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 +3041,7 @@ export class SnapController extends BaseController< approvedNewPermissions = newPermissions; } - if (this.isRunning(snapId)) { + if (this.isSnapRunning(snapId)) { await this.stopSnap(snapId, SnapStatusEvents.Stop); } @@ -3245,7 +3096,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 +3208,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 +3584,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 +3687,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 +3720,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 +3776,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 +4151,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 +4224,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 +4248,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 +4268,7 @@ export class SnapController extends BaseController< previousInitialConnections ?? {}, ); - const truncatedSnap = this.getTruncatedExpect(snapId); + const truncatedSnap = this.getTruncatedSnapExpect(snapId); this.messenger.publish( 'SnapController:snapRolledback', @@ -4454,7 +4306,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 +4530,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..d744095d96 100644 --- a/packages/snaps-controllers/src/snaps/index.ts +++ b/packages/snaps-controllers/src/snaps/index.ts @@ -1,5 +1,51 @@ 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, + PreinstalledSnapFile, + PreinstalledSnap, + PersistedSnapControllerState, +} from './SnapController'; +export { SnapController } from './SnapController'; +export type { + SnapControllerInitAction, + SnapControllerUpdateRegistryAction, + SnapControllerEnableSnapAction, + SnapControllerDisableSnapAction, + SnapControllerStopSnapAction, + SnapControllerStopAllSnapsAction, + SnapControllerIsSnapRunningAction, + SnapControllerHasSnapAction, + SnapControllerGetSnapAction, + SnapControllerUpdateSnapStateAction, + SnapControllerClearSnapStateAction, + SnapControllerGetSnapStateAction, + SnapControllerGetSnapFileAction, + SnapControllerIsMinimumPlatformVersionAction, + SnapControllerClearStateAction, + SnapControllerRemoveSnapAction, + SnapControllerRemoveSnapsAction, + SnapControllerDisconnectOriginAction, + SnapControllerRevokeDynamicSnapPermissionsAction, + SnapControllerGetAllSnapsAction, + SnapControllerGetRunnableSnapsAction, + SnapControllerGetPermittedSnapsAction, + SnapControllerInstallSnapsAction, + 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..ef26f2925d 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< @@ -687,6 +687,7 @@ export const getSnapController = async ( if (init) { await controller.init(); } + return controller; }; @@ -696,7 +697,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 +733,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 +750,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 +774,6 @@ export const getRestrictedCronjobControllerMessenger = ( }); if (mocked) { - messenger.registerActionHandler( - 'PermissionController:hasPermission', - () => { - return true; - }, - ); - messenger.registerActionHandler( 'PermissionController:getPermissions', () => { @@ -825,12 +814,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 +840,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 +850,7 @@ export const getRestrictedSnapInterfaceControllerMessenger = ( 'ApprovalController:acceptRequest', 'MultichainAssetsController:getState', 'AccountsController:getAccountByAddress', - 'SnapController:get', + 'SnapController:getSnap', 'AccountsController:getSelectedMultichainAccount', 'AccountsController:listMultichainAccounts', 'PermissionController:hasPermission', @@ -920,9 +913,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 +926,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 +943,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 +953,7 @@ export const getRestrictedSnapInsightsControllerMessenger = ( messenger.delegate({ actions: [ 'PermissionController:getPermissions', - 'SnapController:getAll', + 'SnapController:getAllSnaps', 'SnapController:handleRequest', 'SnapInterfaceController:deleteInterface', ], @@ -993,12 +986,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,21 +1002,17 @@ 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: [ 'PermissionController:getPermissions', - 'SnapController:getAll', + 'SnapController:getAllSnaps', 'SnapController:handleRequest', 'AccountsController:listMultichainAccounts', ], 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/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, ); diff --git a/packages/snaps-simulation/src/controllers.ts b/packages/snaps-simulation/src/controllers.ts index 1f415bef17..4710aa642c 100644 --- a/packages/snaps-simulation/src/controllers.ts +++ b/packages/snaps-simulation/src/controllers.ts @@ -17,9 +17,9 @@ import { import { SnapInterfaceController } from '@metamask/snaps-controllers'; import type { ExecutionServiceActions, + SnapInterfaceControllerStateChangeEvent, SnapInterfaceControllerActions, SnapInterfaceControllerAllowedActions, - SnapInterfaceControllerStateChangeEvent, } from '@metamask/snaps-controllers'; import { caveatSpecifications as snapsCaveatsSpecifications, diff --git a/yarn.lock b/yarn.lock index 99eb90ff4f..3484b1fecb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3770,10 +3770,26 @@ __metadata: languageName: unknown linkType: soft -"@metamask/messenger@npm:^0.3.0": - version: 0.3.0 - resolution: "@metamask/messenger@npm:0.3.0" - checksum: 10/84e9f4193646d749c7260a4958b13974b3c8738cc2e414116279ed31734e1edba687ff56ddbfdb75033bce30aaa9eeb7c391bccb87a66dbc99a902882271f673 +"@metamask/messenger@npm:@metamask-previews/messenger@0.3.0-preview-a462582": + version: 0.3.0-preview-a462582 + resolution: "@metamask-previews/messenger@npm:0.3.0-preview-a462582" + peerDependencies: + "@metamask/utils": ^11.9.0 + eslint: ">=8" + typescript: ~5.3.3 + yargs: ^17.7.2 + peerDependenciesMeta: + "@metamask/utils": + optional: true + eslint: + optional: true + typescript: + optional: true + yargs: + optional: true + bin: + messenger-generate-action-types: ./dist/generate-action-types/cli.mjs + checksum: 10/210e6f1e05178b8778ed2799ca0ad32936cceb7c191b86bfb105e312f78187b48129bad229e841e6c1851babefed5c7b81d7377bdcf5461c8fbd8ad0890399dd languageName: node linkType: hard @@ -4430,6 +4446,7 @@ __metadata: typescript: "npm:~5.3.3" typescript-eslint: "npm:^8.6.0" vite: "npm:^6.4.1" + yargs: "npm:^17.7.1" languageName: unknown linkType: soft