diff --git a/package.json b/package.json index 9d109bdf4c..a7cd8198e8 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,10 @@ "changelog:validate": "yarn workspaces foreach --all --parallel --interlaced --verbose run changelog:validate", "child-workspace-package-names-as-json": "ts-node scripts/child-workspace-package-names-as-json.ts", "clean": "yarn workspaces foreach --all --parallel --verbose run clean", + "generate-method-action-types": "yarn workspaces foreach --all --parallel --interlaced --verbose run generate-method-action-types", "get-release-tag": "ts-node --swc scripts/get-release-tag.ts", "install-chrome": "./scripts/install-chrome.sh", - "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:tsconfig && yarn constraints && yarn lint:dependencies", + "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:tsconfig && yarn constraints && yarn lint:dependencies && yarn generate-method-action-types --check", "lint:dependencies": "yarn workspaces foreach --all --parallel --verbose run lint:dependencies && yarn dedupe --check", "lint:eslint": "eslint . --cache", "lint:fix": "yarn workspaces foreach --all --parallel run lint:eslint --fix && yarn lint:misc --write && yarn lint:tsconfig && yarn constraints --fix && yarn dedupe", @@ -120,7 +121,8 @@ "tsx": "^4.20.3", "typescript": "~5.3.3", "typescript-eslint": "^8.6.0", - "vite": "^6.4.1" + "vite": "^6.4.1", + "yargs": "^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..9f02e28921 100644 --- a/packages/snaps-controllers/CHANGELOG.md +++ b/packages/snaps-controllers/CHANGELOG.md @@ -7,6 +7,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **BREAKING:** All `SnapController` action types were renamed from `DoSomething` to `SnapControllerDoSomethingAction` ([#3907](https://github.com/MetaMask/snaps/pull/3907)) + - `GetSnap` is now `SnapControllerGetSnapAction`. + - Note: The method is now called `getSnap` instead of `get`. + - `HandleSnapRequest` is now `SnapControllerHandleRequestAction`. + - `GetSnapState` is now `SnapControllerGetSnapStateAction`. + - `HasSnap` is now `SnapControllerHasSnapAction`. + - Note: The method is now called `hasSnap` instead of `has`. + - `UpdateSnapState` is now `SnapControllerUpdateSnapStateAction`. + - `ClearSnapState` is now `SnapControllerClearSnapStateAction`. + - `UpdateRegistry` is now `SnapControllerUpdateRegistryAction`. + - `EnableSnap` is now `SnapControllerEnableSnapAction`. + - Note: The method is now called `enableSnap` instead of `enable`. + - `DisableSnap` is now `SnapControllerDisableSnapAction`. + - Note: The method is now called `disableSnap` instead of `disable`. + - `RemoveSnap` is now `SnapControllerRemoveSnapAction`. + - Note: The method is now called `removeSnap` instead of `remove`. + - `GetPermittedSnaps` is now `SnapControllerGetPermittedSnapsAction`. + - Note: The method is now called `getPermittedSnaps` instead of `getPermitted`. + - `GetAllSnaps` is now `SnapControllerGetAllSnapsAction`. + - Note: The method is now called `getAllSnaps` instead of `getAll`. + - `GetRunnableSnaps` is now `SnapControllerGetRunnableSnapsAction`. + - `StopAllSnaps` is now `SnapControllerStopAllSnapsAction`. + - `IncrementActiveReferences` is now `SnapControllerIncrementActiveReferencesAction`. + - `DecrementActiveReferences` is now `SnapControllerDecrementActiveReferencesAction`. + - `InstallSnaps` is now `SnapControllerInstallSnapsAction`. + - Note: The method is now called `installSnaps` instead of `install`. + - `DisconnectOrigin` is now `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`. +- **BREAKING:** All `SnapController` event types were renamed from `OnSomething` to `SnapControllerOnSomethingEvent` ([#3907](https://github.com/MetaMask/snaps/pull/3907)) + - `SnapStateChange` was removed in favour of `SnapControllerStateChangeEvent`. + - `SnapBlocked` is now `SnapControllerSnapBlockedEvent`. + - `SnapInstallStarted` is now `SnapControllerSnapInstallStartedEvent`. + - `SnapInstallFailed` is now `SnapControllerSnapInstallFailedEvent`. + - `SnapInstalled` is now `SnapControllerSnapInstalledEvent`. + - `SnapUninstalled` is now `SnapControllerSnapUninstalledEvent`. + - `SnapUnblocked` is now `SnapControllerSnapUnblockedEvent. + - `SnapUpdated` is now `SnapControllerSnapUpdatedEvent`. + - `SnapRolledback` is now `SnapControllerSnapRolledbackEvent`. + - `SnapTerminated` is now `SnapControllerSnapTerminatedEvent`. + - `SnapEnabled` is now `SnapControllerSnapEnabledEvent`. + - `SnapDisabled` is now `SnapControllerSnapDisabledEvent`. + ## [18.0.4] ### Fixed diff --git a/packages/snaps-controllers/package.json b/packages/snaps-controllers/package.json index f981211c60..73b90f4bc3 100644 --- a/packages/snaps-controllers/package.json +++ b/packages/snaps-controllers/package.json @@ -62,6 +62,7 @@ "build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references", "changelog:update": "../../scripts/update-changelog.sh @metamask/snaps-controllers", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/snaps-controllers", + "generate-method-action-types": "tsx ../../scripts/generate-method-action-types.mts", "lint": "yarn lint:eslint && yarn lint:misc --check && yarn changelog:validate && yarn lint:dependencies", "lint:ci": "yarn lint", "lint:dependencies": "depcheck", diff --git a/packages/snaps-controllers/src/cronjob/CronjobController.test.ts b/packages/snaps-controllers/src/cronjob/CronjobController.test.ts index d682f05a70..8389151178 100644 --- a/packages/snaps-controllers/src/cronjob/CronjobController.test.ts +++ b/packages/snaps-controllers/src/cronjob/CronjobController.test.ts @@ -1139,7 +1139,7 @@ describe('CronjobController', () => { deriveStateFromMetadata( controller.state, controller.metadata, - 'anonymous', + 'includeInDebugSnapshot', ), ).toMatchInlineSnapshot(`{}`); }); diff --git a/packages/snaps-controllers/src/cronjob/CronjobController.ts b/packages/snaps-controllers/src/cronjob/CronjobController.ts index df12e9fbb7..c5914ca608 100644 --- a/packages/snaps-controllers/src/cronjob/CronjobController.ts +++ b/packages/snaps-controllers/src/cronjob/CronjobController.ts @@ -23,13 +23,13 @@ import { nanoid } from 'nanoid'; import { getCronjobSpecificationSchedule, getExecutionDate } from './utils'; import type { - HandleSnapRequest, - SnapDisabled, - SnapEnabled, - SnapInstalled, - SnapUninstalled, - SnapUpdated, -} from '..'; + SnapControllerHandleRequestAction, + SnapControllerSnapDisabledEvent, + SnapControllerSnapEnabledEvent, + SnapControllerSnapInstalledEvent, + SnapControllerSnapUninstalledEvent, + SnapControllerSnapUpdatedEvent, +} from '../snaps'; import { METAMASK_ORIGIN } from '../snaps/constants'; import { Timer } from '../snaps/Timer'; @@ -37,6 +37,7 @@ export type CronjobControllerGetStateAction = ControllerGetStateAction< typeof controllerName, CronjobControllerState >; + export type CronjobControllerStateChangeEvent = ControllerStateChangeEvent< typeof controllerName, CronjobControllerState @@ -68,7 +69,7 @@ export type Get = { export type CronjobControllerActions = | CronjobControllerGetStateAction - | HandleSnapRequest + | SnapControllerHandleRequestAction | GetPermissions | Schedule | Cancel @@ -77,11 +78,11 @@ export type CronjobControllerActions = export type CronjobControllerEvents = | CronjobControllerStateChangeEvent - | SnapInstalled - | SnapUninstalled - | SnapUpdated - | SnapEnabled - | SnapDisabled; + | SnapControllerSnapInstalledEvent + | SnapControllerSnapUninstalledEvent + | SnapControllerSnapUpdatedEvent + | SnapControllerSnapEnabledEvent + | SnapControllerSnapDisabledEvent; export type CronjobControllerMessenger = Messenger< typeof controllerName, diff --git a/packages/snaps-controllers/src/insights/SnapInsightsController.test.ts b/packages/snaps-controllers/src/insights/SnapInsightsController.test.ts index cf934e930f..5dfee842aa 100644 --- a/packages/snaps-controllers/src/insights/SnapInsightsController.test.ts +++ b/packages/snaps-controllers/src/insights/SnapInsightsController.test.ts @@ -31,7 +31,7 @@ describe('SnapInsightsController', () => { }, ); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -157,7 +157,7 @@ describe('SnapInsightsController', () => { }, ); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -285,7 +285,7 @@ describe('SnapInsightsController', () => { messenger: controllerMessenger, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -388,7 +388,7 @@ describe('SnapInsightsController', () => { messenger: controllerMessenger, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -456,7 +456,7 @@ describe('SnapInsightsController', () => { it('ignores insight if transaction has already been signed', async () => { const rootMessenger = getRootSnapInsightsControllerMessenger(); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -556,7 +556,7 @@ describe('SnapInsightsController', () => { messenger: controllerMessenger, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap(), getTruncatedSnap({ id: MOCK_LOCAL_SNAP_ID })]; }); @@ -661,7 +661,7 @@ describe('SnapInsightsController', () => { deriveStateFromMetadata( controller.state, controller.metadata, - 'anonymous', + 'includeInDebugSnapshot', ), ).toMatchInlineSnapshot(`{}`); }); diff --git a/packages/snaps-controllers/src/insights/SnapInsightsController.ts b/packages/snaps-controllers/src/insights/SnapInsightsController.ts index 62287a4600..69dda216e8 100644 --- a/packages/snaps-controllers/src/insights/SnapInsightsController.ts +++ b/packages/snaps-controllers/src/insights/SnapInsightsController.ts @@ -19,7 +19,10 @@ import { HandlerType } from '@metamask/snaps-utils'; import { hasProperty, hexToBigInt } from '@metamask/utils'; import type { DeleteInterface } from '../interface'; -import type { GetAllSnaps, HandleSnapRequest } from '../snaps'; +import type { + SnapControllerGetAllSnapsAction, + SnapControllerHandleRequestAction, +} from '../snaps'; import { getRunnableSnaps } from '../snaps'; import type { TransactionControllerUnapprovedTransactionAddedEvent, @@ -33,8 +36,8 @@ import type { const controllerName = 'SnapInsightsController'; export type SnapInsightsControllerAllowedActions = - | HandleSnapRequest - | GetAllSnaps + | SnapControllerHandleRequestAction + | SnapControllerGetAllSnapsAction | GetPermissions | DeleteInterface; @@ -143,7 +146,7 @@ export class SnapInsightsController extends BaseController< * @returns A list of objects containing Snap IDs and the permission object. */ #getSnapsWithPermission(permissionName: string) { - const allSnaps = this.messenger.call('SnapController:getAll'); + const allSnaps = this.messenger.call('SnapController:getAllSnaps'); const filteredSnaps = getRunnableSnaps(allSnaps); return filteredSnaps.reduce((accumulator, snap) => { diff --git a/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx b/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx index 0699bff4a8..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..574eccf619 100644 --- a/packages/snaps-controllers/src/interface/SnapInterfaceController.ts +++ b/packages/snaps-controllers/src/interface/SnapInterfaceController.ts @@ -40,7 +40,7 @@ import { isMatchingChainId, validateInterfaceContext, } from './utils'; -import type { GetSnap } from '../snaps'; +import type { SnapControllerGetSnapAction } from '../snaps'; const MAX_UI_CONTENT_SIZE = 10_000_000; // 10 mb @@ -125,7 +125,7 @@ export type SnapInterfaceControllerAllowedActions = | PhishingControllerTestOrigin | ApprovalControllerHasRequestAction | ApprovalControllerAcceptRequestAction - | GetSnap + | SnapControllerGetSnapAction | MultichainAssetsControllerGetStateAction | AccountsControllerGetSelectedMultichainAccountAction | AccountsControllerGetAccountByAddressAction @@ -598,7 +598,7 @@ export class SnapInterfaceController extends BaseController< * @returns The snap. */ #getSnap(id: string) { - return this.messenger.call('SnapController:get', id); + return this.messenger.call('SnapController:getSnap', id); } #hasPermission(snapId: SnapId, permission: string) { diff --git a/packages/snaps-controllers/src/multichain/MultichainRouter.test.ts b/packages/snaps-controllers/src/multichain/MultichainRouter.test.ts index 8988c4dee3..b0a0895edd 100644 --- a/packages/snaps-controllers/src/multichain/MultichainRouter.test.ts +++ b/packages/snaps-controllers/src/multichain/MultichainRouter.test.ts @@ -7,7 +7,7 @@ import { import { MultichainRouter } from './MultichainRouter'; import { METAMASK_ORIGIN } from '../snaps/constants'; import { - getRootMultichainRouterMessenger, + getMultichainRouterRootMessenger, getRestrictedMultichainRouterMessenger, BTC_CAIP2, BTC_CONNECTED_ACCOUNTS, @@ -22,7 +22,7 @@ import { describe('MultichainRouter', () => { describe('handleRequest', () => { it('can route signing requests to account Snaps without address resolution', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring({ submitRequest: jest.fn().mockResolvedValue({ @@ -71,7 +71,7 @@ describe('MultichainRouter', () => { }); it('can route signing requests to account Snaps using address resolution', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring({ submitRequest: jest.fn().mockResolvedValue({ @@ -123,7 +123,7 @@ describe('MultichainRouter', () => { }); it('disallows routing to unconnected accounts', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -166,7 +166,7 @@ describe('MultichainRouter', () => { }); it('can route protocol requests to protocol Snaps', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -181,7 +181,7 @@ describe('MultichainRouter', () => { () => [], ); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -237,7 +237,7 @@ describe('MultichainRouter', () => { }); it('throws if no suitable Snaps are found', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -252,7 +252,7 @@ describe('MultichainRouter', () => { () => [], ); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return []; }); @@ -271,7 +271,7 @@ describe('MultichainRouter', () => { }); it('throws if address resolution fails', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -320,7 +320,7 @@ describe('MultichainRouter', () => { }); it('throws if address resolution returns an address that isnt available', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -372,7 +372,7 @@ describe('MultichainRouter', () => { }); it(`throws if address resolution returns a lower case address that isn't available`, async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -425,7 +425,7 @@ describe('MultichainRouter', () => { describe('getSupportedMethods', () => { it('returns a set of both protocol and account Snap methods', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -435,7 +435,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -455,7 +455,7 @@ describe('MultichainRouter', () => { }); it('handles lack of protocol Snaps', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -465,7 +465,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -485,7 +485,7 @@ describe('MultichainRouter', () => { }); it('handles lack of account Snaps', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -495,7 +495,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -517,7 +517,7 @@ describe('MultichainRouter', () => { describe('getSupportedAccounts', () => { it('returns a set of accounts for the requested scope', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -542,7 +542,7 @@ describe('MultichainRouter', () => { describe('isSupportedScope', () => { it('returns true if an account Snap exists', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -552,7 +552,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -572,7 +572,7 @@ describe('MultichainRouter', () => { }); it('returns true if a protocol Snap exists', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -582,7 +582,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return [getTruncatedSnap()]; }); @@ -602,7 +602,7 @@ describe('MultichainRouter', () => { }); it('returns false if no Snap is found', async () => { - const rootMessenger = getRootMultichainRouterMessenger(); + const rootMessenger = getMultichainRouterRootMessenger(); const messenger = getRestrictedMultichainRouterMessenger(rootMessenger); const withSnapKeyring = getMockWithSnapKeyring(); @@ -612,7 +612,7 @@ describe('MultichainRouter', () => { withSnapKeyring, }); - rootMessenger.registerActionHandler('SnapController:getAll', () => { + rootMessenger.registerActionHandler('SnapController:getAllSnaps', () => { return []; }); diff --git a/packages/snaps-controllers/src/multichain/MultichainRouter.ts b/packages/snaps-controllers/src/multichain/MultichainRouter.ts index 87df9a634e..fefa0aa408 100644 --- a/packages/snaps-controllers/src/multichain/MultichainRouter.ts +++ b/packages/snaps-controllers/src/multichain/MultichainRouter.ts @@ -22,8 +22,11 @@ import { } from '@metamask/utils'; import { nanoid } from 'nanoid'; +import type { + SnapControllerGetAllSnapsAction, + SnapControllerHandleRequestAction, +} from '../snaps'; import { getRunnableSnaps } from '../snaps'; -import type { GetAllSnaps, HandleSnapRequest } from '../snaps'; export type MultichainRouterHandleRequestAction = { type: `${typeof name}:handleRequest`; @@ -72,8 +75,8 @@ export type MultichainRouterActions = | MultichainRouterIsSupportedScopeAction; export type MultichainRouterAllowedActions = - | GetAllSnaps - | HandleSnapRequest + | SnapControllerGetAllSnapsAction + | SnapControllerHandleRequestAction | GetPermissions | AccountsControllerListMultichainAccountsAction; @@ -260,7 +263,7 @@ export class MultichainRouter { * @returns A list of all the protocol Snaps available and their RPC methods. */ #getProtocolSnaps(scope: CaipChainId) { - const allSnaps = this.#messenger.call('SnapController:getAll'); + const allSnaps = this.#messenger.call('SnapController:getAllSnaps'); const filteredSnaps = getRunnableSnaps(allSnaps); return filteredSnaps.reduce((accumulator, snap) => { diff --git a/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts b/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts index 223506acf5..9165986784 100644 --- a/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts +++ b/packages/snaps-controllers/src/services/node-js/NodeThreadExecutionService.ts @@ -36,9 +36,6 @@ export class NodeThreadExecutionService extends AbstractExecutionService return Promise.resolve({ worker, stream }); } - // TODO: Either fix this lint violation or explain why it's necessary to - // ignore. - // eslint-disable-next-line @typescript-eslint/no-misused-promises protected async terminateJob( jobWrapper: TerminateJobArgs, ): Promise { diff --git a/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts b/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts index 251f7131b9..03d4fe531d 100644 --- a/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts +++ b/packages/snaps-controllers/src/services/proxy/ProxyExecutionService.ts @@ -47,9 +47,6 @@ export class ProxyExecutionService extends AbstractExecutionService { * * @param job - The job to terminate. */ - // TODO: Either fix this lint violation or explain why it's necessary to - // ignore. - // eslint-disable-next-line @typescript-eslint/no-misused-promises protected async terminateJob(job: TerminateJobArgs) { // The `AbstractExecutionService` will have already closed the job stream, // so we write to the runtime stream directly. diff --git a/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts new file mode 100644 index 0000000000..9caef94965 --- /dev/null +++ b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts @@ -0,0 +1,411 @@ +/** + * This file is auto generated by `scripts/generate-method-action-types.ts`. + * Do not edit manually. + */ + +import type { SnapController } from './SnapController'; + +/** + * Initialise the SnapController. + * + * Currently this method sets up the controller and calls the `onStart` lifecycle hook for all + * runnable Snaps. + * + * @param waitForPlatform - Whether to wait for the platform to be ready before returning. + */ +export type SnapControllerInitAction = { + type: `SnapController:init`; + handler: SnapController['init']; +}; + +/** + * Trigger an update of the registry. + * + * As a side-effect of this, preinstalled Snaps may be updated and Snaps may be blocked/unblocked. + */ +export type SnapControllerUpdateRegistryAction = { + type: `SnapController:updateRegistry`; + handler: SnapController['updateRegistry']; +}; + +/** + * Starts the given snap. Throws an error if no such snap exists + * or if it is already running. + * + * @param snapId - The id of the Snap to start. + */ +export type SnapControllerStartSnapAction = { + type: `SnapController:startSnap`; + handler: SnapController['startSnap']; +}; + +/** + * Enables the given snap. A snap can only be started if it is enabled. A snap + * can only be enabled if it isn't blocked. + * + * @param snapId - The id of the Snap to enable. + */ +export type SnapControllerEnableSnapAction = { + type: `SnapController:enableSnap`; + handler: SnapController['enableSnap']; +}; + +/** + * Disables the given snap. A snap can only be started if it is enabled. + * + * @param snapId - The id of the Snap to disable. + * @returns A promise that resolves once the snap has been disabled. + */ +export type SnapControllerDisableSnapAction = { + type: `SnapController:disableSnap`; + handler: SnapController['disableSnap']; +}; + +/** + * Stops the given snap, removes all hooks, closes all connections, and + * terminates its worker. + * + * @param snapId - The id of the Snap to stop. + * @param statusEvent - The Snap status event that caused the snap to be + * stopped. + */ +export type SnapControllerStopSnapAction = { + type: `SnapController:stopSnap`; + handler: SnapController['stopSnap']; +}; + +/** + * Stops all running snaps, removes all hooks, closes all connections, and + * terminates their workers. + * + * @param statusEvent - The Snap status event that caused the snap to be + * stopped. + */ +export type SnapControllerStopAllSnapsAction = { + type: `SnapController:stopAllSnaps`; + handler: SnapController['stopAllSnaps']; +}; + +/** + * Returns whether the given snap is running. + * Throws an error if the snap doesn't exist. + * + * @param snapId - The id of the Snap to check. + * @returns `true` if the snap is running, otherwise `false`. + */ +export type SnapControllerIsSnapRunningAction = { + type: `SnapController:isSnapRunning`; + handler: SnapController['isSnapRunning']; +}; + +/** + * Returns whether the given snap has been added to state. + * + * @param snapId - The id of the Snap to check for. + * @returns `true` if the snap exists in the controller state, otherwise `false`. + */ +export type SnapControllerHasSnapAction = { + type: `SnapController:hasSnap`; + handler: SnapController['hasSnap']; +}; + +/** + * Gets the snap with the given id if it exists, including all data. + * This should not be used if the snap is to be serializable, as e.g. + * the snap sourceCode may be quite large. + * + * @param snapId - The id of the Snap to get. + * @returns The entire snap object from the controller state. + */ +export type SnapControllerGetSnapAction = { + type: `SnapController:getSnap`; + handler: SnapController['getSnap']; +}; + +/** + * Gets the snap with the given id, throws if doesn't. + * This should not be used if the snap is to be serializable, as e.g. + * the snap sourceCode may be quite large. + * + * @see {@link SnapController.getSnap} + * @throws {@link Error}. If the snap doesn't exist + * @param snapId - The id of the snap to get. + * @returns The entire snap object. + */ +export type SnapControllerGetSnapExpectAction = { + type: `SnapController:getSnapExpect`; + handler: SnapController['getSnapExpect']; +}; + +/** + * Gets the snap with the given id if it exists, excluding any + * non-serializable or expensive-to-serialize data. + * + * @param snapId - The id of the Snap to get. + * @returns A truncated version of the snap state, that is less expensive to serialize. + */ +export type SnapControllerGetTruncatedSnapAction = { + type: `SnapController:getTruncatedSnap`; + handler: SnapController['getTruncatedSnap']; +}; + +/** + * Gets the snap with the given id, throw if it doesn't exist. + * + * @throws {@link Error}. If snap doesn't exist + * @param snapId - The id of the snap to get. + * @returns A truncated version of the snap state, that is less expensive to serialize. + */ +export type SnapControllerGetTruncatedSnapExpectAction = { + type: `SnapController:getTruncatedSnapExpect`; + handler: SnapController['getTruncatedSnapExpect']; +}; + +/** + * Updates the own state of the snap with the given id. + * This is distinct from the state MetaMask uses to manage snaps. + * + * @param snapId - The id of the Snap whose state should be updated. + * @param newSnapState - The new state of the snap. + * @param encrypted - A flag to indicate whether to use encrypted storage or not. + */ +export type SnapControllerUpdateSnapStateAction = { + type: `SnapController:updateSnapState`; + handler: SnapController['updateSnapState']; +}; + +/** + * Clears the state of the snap with the given id. + * This is distinct from the state MetaMask uses to manage snaps. + * + * @param snapId - The id of the Snap whose state should be cleared. + * @param encrypted - A flag to indicate whether to use encrypted storage or not. + */ +export type SnapControllerClearSnapStateAction = { + type: `SnapController:clearSnapState`; + handler: SnapController['clearSnapState']; +}; + +/** + * Gets the own state of the snap with the given id. + * This is distinct from the state MetaMask uses to manage snaps. + * + * @param snapId - The id of the Snap whose state to get. + * @param encrypted - A flag to indicate whether to use encrypted storage or not. + * @returns The requested snap state or null if no state exists. + */ +export type SnapControllerGetSnapStateAction = { + type: `SnapController:getSnapState`; + handler: SnapController['getSnapState']; +}; + +/** + * Gets a static auxiliary snap file in a chosen file encoding. + * + * @param snapId - The id of the Snap whose state to get. + * @param path - The path to the requested file. + * @param encoding - An optional requested file encoding. + * @returns The file requested in the chosen file encoding or null if the file is not found. + */ +export type SnapControllerGetSnapFileAction = { + type: `SnapController:getSnapFile`; + handler: SnapController['getSnapFile']; +}; + +/** + * Determine if a given Snap ID supports a given minimum version of the Snaps platform + * by inspecting the platformVersion in the Snap manifest. + * + * @param snapId - The Snap ID. + * @param version - The version. + * @returns True if the platform version is equal or greater to the passed version, false otherwise. + */ +export type SnapControllerIsMinimumPlatformVersionAction = { + type: `SnapController:isMinimumPlatformVersion`; + handler: SnapController['isMinimumPlatformVersion']; +}; + +/** + * Completely clear the controller's state: delete all associated data, + * handlers, event listeners, and permissions; tear down all snap providers. + * Also re-initializes the controller after clearing the state. + */ +export type SnapControllerClearStateAction = { + type: `SnapController:clearState`; + handler: SnapController['clearState']; +}; + +/** + * Removes the given snap from state, and clears all associated handlers + * and listeners. + * + * @param snapId - The id of the Snap. + * @returns A promise that resolves once the snap has been removed. + */ +export type SnapControllerRemoveSnapAction = { + type: `SnapController:removeSnap`; + handler: SnapController['removeSnap']; +}; + +/** + * Stops the given snaps, removes them from state, and clears all associated + * permissions, handlers, and listeners. + * + * @param snapIds - The ids of the Snaps. + */ +export type SnapControllerRemoveSnapsAction = { + type: `SnapController:removeSnaps`; + handler: SnapController['removeSnaps']; +}; + +/** + * 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']; +}; + +/** + * Handles incrementing the activeReferences counter. + * + * @param snapId - The snap id of the snap that was referenced. + */ +export type SnapControllerIncrementActiveReferencesAction = { + type: `SnapController:incrementActiveReferences`; + handler: SnapController['incrementActiveReferences']; +}; + +/** + * Handles decrement the activeReferences counter. + * + * @param snapId - The snap id of the snap that was referenced.. + */ +export type SnapControllerDecrementActiveReferencesAction = { + type: `SnapController:decrementActiveReferences`; + handler: SnapController['decrementActiveReferences']; +}; + +/** + * Gets all snaps in their truncated format. + * + * @returns All installed snaps in their truncated format. + */ +export type SnapControllerGetAllSnapsAction = { + type: `SnapController:getAllSnaps`; + handler: SnapController['getAllSnaps']; +}; + +/** + * Gets all runnable snaps. + * + * @returns All runnable snaps. + */ +export type SnapControllerGetRunnableSnapsAction = { + type: `SnapController:getRunnableSnaps`; + handler: SnapController['getRunnableSnaps']; +}; + +/** + * Gets the serialized permitted snaps of the given origin, if any. + * + * @param origin - The origin whose permitted snaps to retrieve. + * @returns The serialized permitted snaps for the origin. + */ +export type SnapControllerGetPermittedSnapsAction = { + type: `SnapController:getPermittedSnaps`; + handler: SnapController['getPermittedSnaps']; +}; + +/** + * Installs the snaps requested by the given origin, returning the snap + * object if the origin is permitted to install it, and an authorization error + * otherwise. + * + * @param origin - The origin that requested to install the snaps. + * @param requestedSnaps - The snaps to install. + * @returns An object of snap ids and snap objects, or errors if a + * snap couldn't be installed. + */ +export type SnapControllerInstallSnapsAction = { + type: `SnapController:installSnaps`; + handler: SnapController['installSnaps']; +}; + +/** + * Passes a JSON-RPC request object to the RPC handler function of a snap. + * + * @param options - A bag of options. + * @param options.snapId - The ID of the recipient snap. + * @param options.origin - The origin of the RPC request. + * @param options.handler - The handler to trigger on the snap for the request. + * @param options.request - The JSON-RPC request object. + * @returns The result of the JSON-RPC request. + */ +export type SnapControllerHandleRequestAction = { + type: `SnapController:handleRequest`; + handler: SnapController['handleRequest']; +}; + +/** + * Set the active state of the client. This will trigger the `onActive` or + * `onInactive` lifecycle hooks for all Snaps. + * + * @param active - A boolean indicating whether the client is active or not. + */ +export type SnapControllerSetClientActiveAction = { + type: `SnapController:setClientActive`; + handler: SnapController['setClientActive']; +}; + +/** + * Union of all SnapController action types. + */ +export type SnapControllerMethodActions = + | SnapControllerInitAction + | SnapControllerUpdateRegistryAction + | SnapControllerStartSnapAction + | SnapControllerEnableSnapAction + | SnapControllerDisableSnapAction + | SnapControllerStopSnapAction + | SnapControllerStopAllSnapsAction + | SnapControllerIsSnapRunningAction + | SnapControllerHasSnapAction + | SnapControllerGetSnapAction + | SnapControllerGetSnapExpectAction + | SnapControllerGetTruncatedSnapAction + | SnapControllerGetTruncatedSnapExpectAction + | SnapControllerUpdateSnapStateAction + | SnapControllerClearSnapStateAction + | SnapControllerGetSnapStateAction + | SnapControllerGetSnapFileAction + | SnapControllerIsMinimumPlatformVersionAction + | SnapControllerClearStateAction + | SnapControllerRemoveSnapAction + | SnapControllerRemoveSnapsAction + | SnapControllerDisconnectOriginAction + | SnapControllerRevokeDynamicSnapPermissionsAction + | SnapControllerIncrementActiveReferencesAction + | SnapControllerDecrementActiveReferencesAction + | 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..0fd670b778 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.tsx +++ b/packages/snaps-controllers/src/snaps/SnapController.test.tsx @@ -106,7 +106,7 @@ import { approvalControllerMock, DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, ExecutionEnvironmentStub, - getControllerMessenger, + getRootMessenger, getNodeEES, getNodeEESMessenger, getPersistedSnapsState, @@ -177,7 +177,7 @@ describe('SnapController', () => { }), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); const result = await snapController.handleRequest({ @@ -198,7 +198,7 @@ describe('SnapController', () => { }); it('adds a snap and uses its JSON-RPC API', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const executionEnvironmentStub = new ExecutionEnvironmentStub( getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; @@ -213,7 +213,7 @@ describe('SnapController', () => { executionEnvironmentStub, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); const result = await snapController.handleRequest({ @@ -233,7 +233,7 @@ describe('SnapController', () => { }); it('passes endowments to a snap when executing it', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -252,7 +252,7 @@ describe('SnapController', () => { }, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); @@ -321,7 +321,7 @@ describe('SnapController', () => { const { rootMessenger } = options; const [snapController, service] = await getSnapControllerWithEES(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); // defer @@ -354,7 +354,7 @@ describe('SnapController', () => { }), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); await snapController.handleRequest({ @@ -371,14 +371,14 @@ describe('SnapController', () => { await delay(100); - expect(snapController.isRunning(snap.id)).toBe(false); + expect(snapController.isSnapRunning(snap.id)).toBe(false); snapController.destroy(); await service.terminateAllSnaps(); }); it('terminates a snap even if connection to worker has failed', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const [snapController, service] = await getSnapControllerWithEES( getSnapControllerOptions({ @@ -398,7 +398,7 @@ describe('SnapController', () => { }, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); // @ts-expect-error `maxRequestTime` is a private property. @@ -440,7 +440,7 @@ describe('SnapController', () => { }), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -463,7 +463,7 @@ describe('SnapController', () => { }), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -489,7 +489,7 @@ describe('SnapController', () => { }); it('includes the initialConnections data in the approval requestState when installing a Snap', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -535,7 +535,7 @@ describe('SnapController', () => { }); it('includes the initialConnections data in the requestState when updating a Snap without pre-existing connections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -596,7 +596,7 @@ describe('SnapController', () => { }); it('includes the initialConnections data in the requestState when updating a Snap with pre-existing connections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -645,6 +645,7 @@ describe('SnapController', () => { state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ + // @ts-expect-error: Partial mock. manifest: { initialConnections: { 'https://snaps.metamask.io': {}, @@ -693,7 +694,7 @@ describe('SnapController', () => { }); it('includes the initialConnections data in the requestState when updating a Snap with pre-existing connections where some are revoked', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); // Simulate all permissions being revoked. rootMessenger.registerActionHandler( @@ -723,6 +724,7 @@ describe('SnapController', () => { state: { snaps: getPersistedSnapsState( getPersistedSnapObject({ + // @ts-expect-error: Partial mock. manifest: { initialConnections: { 'https://snaps.metamask.io': {}, @@ -1045,7 +1047,7 @@ describe('SnapController', () => { }); it('throws an error if snap is not on allowlist and allowlisting is required but resolve succeeds', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const controller = await getSnapController( @@ -1074,7 +1076,7 @@ describe('SnapController', () => { }); it('throws an error if the registry is unavailable and allowlisting is required but resolve succeeds', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const controller = await getSnapController( @@ -1138,7 +1140,7 @@ describe('SnapController', () => { }); it('resolves to allowlisted version when allowlisting is required', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const { manifest, sourceCode, svgIcon } = @@ -1171,14 +1173,14 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '^1.0.0' }, }); - expect(controller.get(MOCK_SNAP_ID)?.version).toBe('1.1.0'); + expect(controller.getSnap(MOCK_SNAP_ID)?.version).toBe('1.1.0'); expect(registry.resolveVersion).toHaveBeenCalled(); controller.destroy(); }); it('does not use registry resolving when allowlist is not required', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const controller = await getSnapController( @@ -1211,7 +1213,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '>0.9.0 <1.1.0' }, }); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); expect(newSnap).toStrictEqual(getSnapObject()); expect(options.messenger.call).toHaveBeenCalledTimes(1); @@ -1226,7 +1228,7 @@ describe('SnapController', () => { }); it('fails to install snap if user rejects installation', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, detectSnapLocation: loopbackDetect(), @@ -1297,7 +1299,7 @@ describe('SnapController', () => { MOCK_SNAP_ID, ); - expect(controller.get(MOCK_SNAP_ID)).toBeUndefined(); + expect(controller.getSnap(MOCK_SNAP_ID)).toBeUndefined(); expect(options.messenger.publish).not.toHaveBeenCalledWith( 'SnapController:snapUninstalled', @@ -1308,7 +1310,7 @@ describe('SnapController', () => { }); it('removes a snap that errors during installation after being added', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, detectSnapLocation: loopbackDetect(), @@ -1384,7 +1386,7 @@ describe('SnapController', () => { }), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ @@ -1456,7 +1458,7 @@ describe('SnapController', () => { }); it('times out an RPC request that takes too long', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, idleTimeCheckInterval: 30000, @@ -1468,7 +1470,7 @@ describe('SnapController', () => { }); const snapController = await getSnapController(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( 'ExecutionService:handleRpcRequest', @@ -1517,7 +1519,7 @@ describe('SnapController', () => { }); const snapController = await getSnapController(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -1530,7 +1532,7 @@ describe('SnapController', () => { }); it('uses the execution timeout specified by the snap', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, idleTimeCheckInterval: 30000, @@ -1542,7 +1544,7 @@ describe('SnapController', () => { }); const snapController = await getSnapController(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( 'ExecutionService:handleRpcRequest', @@ -1637,7 +1639,7 @@ describe('SnapController', () => { setupSnapProvider, ); const [snapController] = await getSnapControllerWithEES(options, service); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -1712,7 +1714,7 @@ describe('SnapController', () => { setupSnapProvider, ); const [snapController] = await getSnapControllerWithEES(options, service); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -1766,7 +1768,7 @@ describe('SnapController', () => { }); const [snapController] = await getSnapControllerWithEES(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); const results = (await Promise.allSettled([ snapController.handleRequest({ @@ -1830,7 +1832,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: {}, }); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -1862,7 +1864,7 @@ describe('SnapController', () => { // This test also ensures that we do not throw "Premature close" it('throws if the execution environment fails', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, state: { snaps: getPersistedSnapsState() }, @@ -1933,7 +1935,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: {}, }); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -1979,7 +1981,7 @@ describe('SnapController', () => { `, }); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const [snapController, service] = await getSnapControllerWithEES( getSnapControllerOptions({ maxRequestTime: 50, @@ -1997,14 +1999,14 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: {}, }); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect(snapController.state.snaps[snap.id].status).toBe('running'); // @ts-expect-error Accessing protected value. const originalTerminateFunction = service.terminateJob.bind(service); - let promise: Promise; + let promise: Promise = Promise.resolve(); // Cause a request at termination time. // @ts-expect-error Accessing protected value. @@ -2054,7 +2056,7 @@ describe('SnapController', () => { module.exports.onRpcRequest = () => 'foo bar'; `; - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2073,7 +2075,7 @@ describe('SnapController', () => { }); const [snapController, service] = await getSnapControllerWithEES(options); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); expect(snapController.state.snaps[snap.id].status).toBe('running'); @@ -2117,7 +2119,7 @@ describe('SnapController', () => { }); it(`shouldn't time out a long running snap on start up`, async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2134,7 +2136,7 @@ describe('SnapController', () => { async () => await sleep(300), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); const startPromise = snapController.startSnap(snap.id); const timeoutPromise = sleep(50).then(() => true); @@ -2148,7 +2150,7 @@ describe('SnapController', () => { }); it('removes a snap that is stopped without errors', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2166,7 +2168,7 @@ describe('SnapController', () => { getNodeEESMessenger(options.rootMessenger), ) as unknown as NodeThreadExecutionService, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); rootMessenger.registerActionHandler( 'ExecutionService:handleRpcRequest', @@ -2218,7 +2220,7 @@ describe('SnapController', () => { }); it('clears encrypted state of Snaps when the client is locked', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const state = { myVariable: 1 }; @@ -2306,7 +2308,7 @@ describe('SnapController', () => { )( 'throws if the snap does not have permission for the handler', async (handler) => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2322,7 +2324,7 @@ describe('SnapController', () => { () => ({}), ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ snapId: snap.id, @@ -2341,7 +2343,7 @@ describe('SnapController', () => { ); it('does not throw if the snap uses a permitted handler', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2357,7 +2359,7 @@ describe('SnapController', () => { () => false, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2375,7 +2377,7 @@ describe('SnapController', () => { }); it('allows MetaMask to send a JSON-RPC request', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2408,7 +2410,7 @@ describe('SnapController', () => { () => undefined, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2422,7 +2424,7 @@ describe('SnapController', () => { }); it('allows MetaMask to send a keyring request', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2455,7 +2457,7 @@ describe('SnapController', () => { () => undefined, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2469,7 +2471,7 @@ describe('SnapController', () => { }); it('allows a website origin if it is in the `allowedOrigins` list', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2502,7 +2504,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2516,7 +2518,7 @@ describe('SnapController', () => { }); it('allows a website origin if it is in the `allowedOrigins` list for keyring requests', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2549,7 +2551,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2563,7 +2565,7 @@ describe('SnapController', () => { }); it('allows a website origin if `dapps` is `true`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2597,7 +2599,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2611,7 +2613,7 @@ describe('SnapController', () => { }); it('allows a Snap origin if it is in the `allowedOrigins` list', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2644,7 +2646,7 @@ describe('SnapController', () => { () => MOCK_SNAP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2658,7 +2660,7 @@ describe('SnapController', () => { }); it('allows a Snap origin if `snaps` is `true`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2692,7 +2694,7 @@ describe('SnapController', () => { () => MOCK_SNAP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); expect( await snapController.handleRequest({ snapId: snap.id, @@ -2719,7 +2721,7 @@ describe('SnapController', () => { ])( 'throws if the origin is not in the `allowedOrigins` list (%p)', async (value: RpcOrigins) => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -2750,7 +2752,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ snapId: snap.id, @@ -2767,7 +2769,7 @@ describe('SnapController', () => { ); it('ensures onboarding has completed before processing requests', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const { promise, resolve } = createDeferredPromise(); const ensureOnboardingComplete = jest.fn().mockReturnValue(promise); @@ -2786,7 +2788,7 @@ describe('SnapController', () => { const initPromise = options.messenger.call('SnapController:init'); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); const requestPromise = snapController.handleRequest({ snapId: snap.id, @@ -2820,7 +2822,7 @@ describe('SnapController', () => { }); it('throws if the snap does not have permission to handle JSON-RPC requests from dapps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -2844,7 +2846,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ snapId: snap.id, @@ -2860,7 +2862,7 @@ describe('SnapController', () => { }); it('throws if the snap does not have permission to handle JSON-RPC requests from snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -2884,7 +2886,7 @@ describe('SnapController', () => { () => MOCK_SNAP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ snapId: snap.id, @@ -2900,7 +2902,7 @@ describe('SnapController', () => { }); it('throws if the website origin is not in the `allowedOrigins` list for keyring requests', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -2933,7 +2935,7 @@ describe('SnapController', () => { () => MOCK_DAPP_SUBJECT_METADATA, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await expect( snapController.handleRequest({ snapId: snap.id, @@ -2949,7 +2951,7 @@ describe('SnapController', () => { }); it('injects context into onUserInput', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -3021,7 +3023,7 @@ describe('SnapController', () => { }); it('calls `SnapInterfaceController:setInterfaceDisplayed` if the response includes content', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -3078,7 +3080,7 @@ describe('SnapController', () => { }); it('throws if onTransaction handler returns a phishing link', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3143,7 +3145,7 @@ describe('SnapController', () => { }); it('throws if onTransaction returns an invalid value', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3204,7 +3206,7 @@ describe('SnapController', () => { }); it("doesn't throw if onTransaction return value is valid", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3263,7 +3265,7 @@ describe('SnapController', () => { }); it('throws if onTransaction return value is an invalid id', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3317,7 +3319,7 @@ describe('SnapController', () => { }); it("doesn't throw if onTransaction return value is an id", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3355,6 +3357,7 @@ describe('SnapController', () => { rootMessenger.registerActionHandler( 'SnapInterfaceController:getInterface', + // @ts-expect-error: Partial mock. () => ({ snapId: MOCK_SNAP_ID, content: foo, state: {} }), ); @@ -3376,7 +3379,7 @@ describe('SnapController', () => { }); it('throws if onSignature handler returns a phishing link', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3441,7 +3444,7 @@ describe('SnapController', () => { }); it('throws if onSignature returns an invalid value', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3502,7 +3505,7 @@ describe('SnapController', () => { }); it('throws if onSignature return value is an invalid id', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3556,7 +3559,7 @@ describe('SnapController', () => { }); it("doesn't throw if onSignature return value is valid", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3616,7 +3619,7 @@ describe('SnapController', () => { }); it(`doesn't throw if onTransaction handler returns null`, async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3668,7 +3671,7 @@ describe('SnapController', () => { }); it(`doesn't throw if onSignature handler returns null`, async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3720,7 +3723,7 @@ describe('SnapController', () => { }); it('throws if onHomePage handler returns a phishing link', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3785,7 +3788,7 @@ describe('SnapController', () => { }); it('throws if onHomePage return value is an invalid id', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3839,7 +3842,7 @@ describe('SnapController', () => { }); it("doesn't throw if onHomePage return value is valid", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3898,7 +3901,7 @@ describe('SnapController', () => { }); it('throws if onSettingsPage handler returns a phishing link', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -3963,7 +3966,7 @@ describe('SnapController', () => { }); it('throws if onSettingsPage return value is an invalid id', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4017,7 +4020,7 @@ describe('SnapController', () => { }); it("doesn't throw if onSettingsPage return value is valid", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4076,7 +4079,7 @@ describe('SnapController', () => { }); it('throws if onNameLookup returns an invalid value', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4133,7 +4136,7 @@ describe('SnapController', () => { }); it("doesn't throw if onNameLookup return value is valid", async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4195,7 +4198,7 @@ describe('SnapController', () => { }); it(`doesn't throw if onNameLookup handler returns null`, async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4248,7 +4251,7 @@ describe('SnapController', () => { describe('onAssetsLookup', () => { it('throws if `onAssetsLookup` handler returns an invalid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4310,7 +4313,7 @@ describe('SnapController', () => { }); it('filters out assets that are out of scope for `onAssetsLookup`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4386,7 +4389,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetsLookup` returns a valid response for fungible assets', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4478,7 +4481,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetsLookup` returns a valid response for non-fungible assets', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4600,7 +4603,7 @@ describe('SnapController', () => { describe('onAssetsConversion', () => { it('throws if `onAssetsConversion` handler returns an invalid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4662,7 +4665,7 @@ describe('SnapController', () => { }); it('filters out assets that are out of scope for `onAssetsConversion`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4736,7 +4739,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetsConversion` returns a valid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4821,7 +4824,7 @@ describe('SnapController', () => { describe('onAssetsMarketData', () => { it('throws if `onAssetsMarketData` handler returns an invalid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4883,7 +4886,7 @@ describe('SnapController', () => { }); it('filters out assets that are out of scope for `onAssetsMarketData`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -4956,7 +4959,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetsMarketData` returns a valid response for fungible assets', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5041,7 +5044,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetsMarketData` returns a valid response for non-fungible assets', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5165,7 +5168,7 @@ describe('SnapController', () => { describe('onAssetHistoricalPrice', () => { it('throws if `onAssetHistoricalPrice` handler returns an invalid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5227,7 +5230,7 @@ describe('SnapController', () => { }); it('returns the value when `onAssetHistoricalPrice` returns a valid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5304,7 +5307,7 @@ describe('SnapController', () => { describe('onClientRequest', () => { it('returns the value when `onClientRequest` returns a valid response', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5347,7 +5350,7 @@ describe('SnapController', () => { }); it('throws if the origin is not "metamask"', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapController = await getSnapController( getSnapControllerOptions({ @@ -5391,7 +5394,7 @@ describe('SnapController', () => { describe('getRpcRequestHandler', () => { it('handlers populate the "jsonrpc" property if missing', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -5640,7 +5643,7 @@ describe('SnapController', () => { }); it('reinstalls local snaps even if they are already installed (already stopped)', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapObject = getPersistedSnapObject({ id: MOCK_LOCAL_SNAP_ID, }); @@ -5780,7 +5783,7 @@ describe('SnapController', () => { }); it('reinstalls local snaps even if they are already installed (running)', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const version = '0.0.1'; const newVersion = '0.0.2'; @@ -5822,7 +5825,7 @@ describe('SnapController', () => { await snapController.installSnaps(MOCK_ORIGIN, { [MOCK_LOCAL_SNAP_ID]: {}, }); - expect(snapController.isRunning(MOCK_LOCAL_SNAP_ID)).toBe(true); + expect(snapController.isSnapRunning(MOCK_LOCAL_SNAP_ID)).toBe(true); const result = await snapController.installSnaps(MOCK_ORIGIN, { [MOCK_LOCAL_SNAP_ID]: {}, @@ -6022,7 +6025,7 @@ describe('SnapController', () => { }); it('does not get stuck when re-installing a local snap that fails to install', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const snapObject = getPersistedSnapObject({ id: MOCK_LOCAL_SNAP_ID, }); @@ -6133,7 +6136,7 @@ describe('SnapController', () => { }); it('grants connection permission to initialConnections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -6192,7 +6195,7 @@ describe('SnapController', () => { }); it('updates existing caveats to satisfy initialConnections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const initialConnections = { 'npm:filsnap': {}, @@ -6241,7 +6244,7 @@ describe('SnapController', () => { }); it('supports preinstalled snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); jest.spyOn(rootMessenger, 'publish'); @@ -6333,11 +6336,12 @@ describe('SnapController', () => { }); it('supports preinstalled snaps with two-way initial connections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', + // @ts-expect-error: Partial mock. (origin) => { if (origin === `${MOCK_SNAP_ID}2`) { return { @@ -6423,7 +6427,7 @@ describe('SnapController', () => { }); it('supports preinstalled snaps with initial connections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permission initially @@ -6507,7 +6511,7 @@ describe('SnapController', () => { }); it('supports preinstalled snaps when Snap installation is disabled', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permission initially @@ -6596,7 +6600,7 @@ describe('SnapController', () => { }); it('supports updating preinstalled snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); jest.spyOn(rootMessenger, 'publish'); @@ -6713,7 +6717,7 @@ describe('SnapController', () => { }); it('skips preinstalling a Snap if a newer version is already installed', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); const preinstalledSnaps = [ @@ -6746,7 +6750,7 @@ describe('SnapController', () => { }); it('supports localized preinstalled snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permission initially @@ -6854,7 +6858,7 @@ describe('SnapController', () => { }); it('disallows manual updates of preinstalled snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permissions initially @@ -6915,7 +6919,7 @@ describe('SnapController', () => { }); it('supports preinstalled Snaps specifying the hidden flag', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permission initially @@ -6963,13 +6967,13 @@ describe('SnapController', () => { snapControllerOptions, ); - expect(snapController.get(MOCK_SNAP_ID)?.hidden).toBe(true); + expect(snapController.getSnap(MOCK_SNAP_ID)?.hidden).toBe(true); snapController.destroy(); }); it('supports preinstalled Snaps specifying the hideSnapBranding flag', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); // The snap should not have permission initially @@ -7017,13 +7021,13 @@ describe('SnapController', () => { snapControllerOptions, ); - expect(snapController.get(MOCK_SNAP_ID)?.hideSnapBranding).toBe(true); + expect(snapController.getSnap(MOCK_SNAP_ID)?.hideSnapBranding).toBe(true); snapController.destroy(); }); it('recovers if preinstalled permissions are out of sync', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); const log = jest.spyOn(console, 'warn').mockImplementation(); @@ -7114,7 +7118,7 @@ describe('SnapController', () => { }); it('recovers if preinstalled permissions are out of sync when Snap has limited information', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); const log = jest.spyOn(console, 'warn').mockImplementation(); @@ -7204,7 +7208,7 @@ describe('SnapController', () => { }); it('supports onInstall for preinstalled Snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); rootMessenger.registerActionHandler( @@ -7270,7 +7274,7 @@ describe('SnapController', () => { }); it('recovers if preinstalled source code is missing', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); const log = jest.spyOn(console, 'warn').mockImplementation(); @@ -7337,7 +7341,7 @@ describe('SnapController', () => { }); it('supports onUpdate for preinstalled Snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); jest.spyOn(rootMessenger, 'call'); rootMessenger.registerActionHandler( @@ -7410,7 +7414,7 @@ describe('SnapController', () => { it('authorizes permissions needed for snaps', async () => { const manifest = getSnapManifest(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -7919,7 +7923,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -7999,7 +8003,7 @@ describe('SnapController', () => { manifest: manifest.result, }); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -8073,7 +8077,7 @@ describe('SnapController', () => { const newVersion = '1.0.2'; const newVersionRange = '>=1.0.1'; - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ manifest: getSnapManifest({ @@ -8425,7 +8429,7 @@ describe('SnapController', () => { }), ); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); let revokedConnection = false; @@ -8474,8 +8478,8 @@ describe('SnapController', () => { await controller.stopSnap(snapId1); await controller.stopSnap(snapId2); - expect(controller.get(snapId1)).toBeDefined(); - expect(controller.get(snapId2)).toBeDefined(); + expect(controller.getSnap(snapId1)).toBeDefined(); + expect(controller.getSnap(snapId2)).toBeDefined(); ( options.messenger.publish as jest.MockedFn< @@ -8493,11 +8497,11 @@ describe('SnapController', () => { expect(detect).toHaveBeenCalledTimes(5); - expect(controller.get(snapId3)).toBeUndefined(); - expect(controller.get(snapId1)?.manifest.version).toBe(oldVersion); - expect(controller.get(snapId2)?.manifest.version).toBe(oldVersion); - expect(controller.get(snapId1)?.status).toBe('stopped'); - expect(controller.get(snapId2)?.status).toBe('stopped'); + expect(controller.getSnap(snapId3)).toBeUndefined(); + expect(controller.getSnap(snapId1)?.manifest.version).toBe(oldVersion); + expect(controller.getSnap(snapId2)?.manifest.version).toBe(oldVersion); + expect(controller.getSnap(snapId1)?.status).toBe('stopped'); + expect(controller.getSnap(snapId2)?.status).toBe('stopped'); expect(options.messenger.publish).not.toHaveBeenCalledWith( 'SnapController:snapInstalled', @@ -8630,8 +8634,8 @@ describe('SnapController', () => { await controller.stopSnap(snapId1); await controller.stopSnap(snapId2); - expect(controller.get(snapId1)).toBeDefined(); - expect(controller.get(snapId2)).toBeDefined(); + expect(controller.getSnap(snapId1)).toBeDefined(); + expect(controller.getSnap(snapId2)).toBeDefined(); await expect( controller.installSnaps(MOCK_ORIGIN, { @@ -8645,9 +8649,9 @@ describe('SnapController', () => { expect(detect).toHaveBeenCalledTimes(4); - expect(controller.get(snapId3)).toBeUndefined(); - expect(controller.get(snapId1)?.manifest.version).toBe(oldVersion); - expect(controller.get(snapId2)?.manifest.version).toBe(oldVersion); + expect(controller.getSnap(snapId3)).toBeUndefined(); + expect(controller.getSnap(snapId1)?.manifest.version).toBe(oldVersion); + expect(controller.getSnap(snapId2)?.manifest.version).toBe(oldVersion); expect(listener).toHaveBeenCalledTimes(0); controller.destroy(); @@ -8780,12 +8784,12 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: {}, }); - const localSnap = snapController.getExpect(MOCK_LOCAL_SNAP_ID); + const localSnap = snapController.getSnapExpect(MOCK_LOCAL_SNAP_ID); expect(localSnap.preinstalled).toBe(true); expect(localSnap.hideSnapBranding).toBe(true); expect(localSnap.hidden).toBe(false); - const npmSnap = snapController.getExpect(MOCK_SNAP_ID); + const npmSnap = snapController.getSnapExpect(MOCK_SNAP_ID); expect(npmSnap.preinstalled).toBeUndefined(); expect(npmSnap.hideSnapBranding).toBeUndefined(); expect(npmSnap.hidden).toBeUndefined(); @@ -8819,7 +8823,7 @@ describe('SnapController', () => { ); const localSnapBeforeUpdate = - snapController.getExpect(MOCK_LOCAL_SNAP_ID); + snapController.getSnapExpect(MOCK_LOCAL_SNAP_ID); expect(localSnapBeforeUpdate.preinstalled).toBeUndefined(); expect(localSnapBeforeUpdate.hideSnapBranding).toBeUndefined(); expect(localSnapBeforeUpdate.hidden).toBeUndefined(); @@ -8828,7 +8832,7 @@ describe('SnapController', () => { [MOCK_LOCAL_SNAP_ID]: {}, }); - const localSnap = snapController.getExpect(MOCK_LOCAL_SNAP_ID); + const localSnap = snapController.getSnapExpect(MOCK_LOCAL_SNAP_ID); expect(localSnap.preinstalled).toBe(true); expect(localSnap.hideSnapBranding).toBe(true); expect(localSnap.hidden).toBe(false); @@ -8884,11 +8888,11 @@ describe('SnapController', () => { const controller = await getSnapController(options); const onSnapUpdated = jest.fn(); - const snap = controller.getExpect(MOCK_SNAP_ID); + const snap = controller.getSnapExpect(MOCK_SNAP_ID); options.messenger.subscribe('SnapController:snapUpdated', onSnapUpdated); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); await expect( controller.installSnaps(MOCK_ORIGIN, { @@ -8904,7 +8908,7 @@ describe('SnapController', () => { }); it('throws an error if the new version of the snap is blocked', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ @@ -8959,13 +8963,13 @@ describe('SnapController', () => { const controller = await getSnapController(options); const onSnapUpdated = jest.fn(); - const snap = controller.getExpect(MOCK_SNAP_ID); + const snap = controller.getSnapExpect(MOCK_SNAP_ID); options.messenger.subscribe('SnapController:snapUpdated', onSnapUpdated); const publishSpy = jest.spyOn(options.messenger, 'publish'); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); const errorMessage = `Snap "${MOCK_SNAP_ID}@${snap.version}" is already installed. Couldn't update to a version inside requested "0.9.0" range.`; @@ -9027,9 +9031,9 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.1.0' }, }); - const newSnapTruncated = controller.getTruncated(MOCK_SNAP_ID); + const newSnapTruncated = controller.getTruncatedSnap(MOCK_SNAP_ID); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); expect(result).toStrictEqual({ [MOCK_SNAP_ID]: newSnapTruncated }); expect(newSnap?.version).toBe('1.1.0'); @@ -9168,7 +9172,7 @@ describe('SnapController', () => { }), ); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -9283,7 +9287,7 @@ describe('SnapController', () => { }), ); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -9410,9 +9414,9 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.1.0' }, }); - const newSnapTruncated = controller.getTruncated(MOCK_SNAP_ID); + const newSnapTruncated = controller.getTruncatedSnap(MOCK_SNAP_ID); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); expect(result).toStrictEqual({ [MOCK_SNAP_ID]: newSnapTruncated }); expect(newSnap?.version).toBe('1.1.0'); @@ -9460,7 +9464,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.1.0' }, }); - const isRunning = controller.isRunning(MOCK_SNAP_ID); + const isRunning = controller.isSnapRunning(MOCK_SNAP_ID); expect(callActionSpy).toHaveBeenCalledTimes(15); @@ -9562,7 +9566,7 @@ describe('SnapController', () => { }); it('throws on update request denied', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ manifest: getSnapManifest({ version: '1.1.0' as SemVerVersion, @@ -9622,7 +9626,7 @@ describe('SnapController', () => { }), ).rejects.toThrow('User rejected the request.'); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); expect(newSnap?.version).toBe('1.0.0'); expect(callActionSpy).toHaveBeenCalledTimes(6); @@ -9678,7 +9682,7 @@ describe('SnapController', () => { }); it('requests approval for new and already approved permissions and revoke unused permissions', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); /* eslint-disable @typescript-eslint/naming-convention */ const initialPermissions = { @@ -9921,7 +9925,7 @@ describe('SnapController', () => { }); it('supports initialConnections', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -10037,7 +10041,7 @@ describe('SnapController', () => { it('assigns the same id to the approval request and the request metadata', async () => { expect.assertions(4); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); /* eslint-disable @typescript-eslint/naming-convention */ const initialPermissions = { @@ -10191,7 +10195,7 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: { version: '1.2.0' }, }); - const newSnap = controller.get(MOCK_SNAP_ID); + const newSnap = controller.getSnap(MOCK_SNAP_ID); expect(newSnap?.version).toBe('1.2.0'); controller.destroy(); @@ -10410,10 +10414,10 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(false); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(false); snapController.enableSnap(MOCK_SNAP_ID); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(true); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(true); expect(options.messenger.publish).toHaveBeenCalledWith( 'SnapController:snapEnabled', getTruncatedSnap(), @@ -10460,10 +10464,10 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(true); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(true); await snapController.disableSnap(MOCK_SNAP_ID); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(false); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(false); expect(options.messenger.publish).toHaveBeenCalledWith( 'SnapController:snapDisabled', getTruncatedSnap({ enabled: false }), @@ -10481,14 +10485,14 @@ describe('SnapController', () => { }), ); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(true); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(true); await snapController.startSnap(MOCK_SNAP_ID); - expect(snapController.isRunning(MOCK_SNAP_ID)).toBe(true); + expect(snapController.isSnapRunning(MOCK_SNAP_ID)).toBe(true); await snapController.disableSnap(MOCK_SNAP_ID); - expect(snapController.get(MOCK_SNAP_ID)?.enabled).toBe(false); - expect(snapController.isRunning(MOCK_SNAP_ID)).toBe(false); + expect(snapController.getSnap(MOCK_SNAP_ID)?.enabled).toBe(false); + expect(snapController.isSnapRunning(MOCK_SNAP_ID)).toBe(false); snapController.destroy(); }); @@ -10505,7 +10509,7 @@ describe('SnapController', () => { describe('updateRegistry', () => { it('updates the registry database', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const snapController = await getSnapController( @@ -10524,7 +10528,7 @@ describe('SnapController', () => { }); it('blocks snaps as expected', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const mockSnapA = getMockSnapData({ @@ -10575,12 +10579,12 @@ describe('SnapController', () => { }); // A is blocked and disabled - expect(snapController.get(mockSnapA.id)?.blocked).toBe(true); - expect(snapController.get(mockSnapA.id)?.enabled).toBe(false); + expect(snapController.getSnap(mockSnapA.id)?.blocked).toBe(true); + expect(snapController.getSnap(mockSnapA.id)?.enabled).toBe(false); // B is unblocked and enabled - expect(snapController.get(mockSnapB.id)?.blocked).toBe(false); - expect(snapController.get(mockSnapB.id)?.enabled).toBe(true); + expect(snapController.getSnap(mockSnapB.id)?.blocked).toBe(false); + expect(snapController.getSnap(mockSnapB.id)?.enabled).toBe(true); expect(publishMock).toHaveBeenLastCalledWith( 'SnapController:snapBlocked', @@ -10595,7 +10599,7 @@ describe('SnapController', () => { }); it('stops running snaps when they are blocked', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const mockSnap = getMockSnapData({ @@ -10622,15 +10626,15 @@ describe('SnapController', () => { await waitForStateChange(options.messenger); // The snap is blocked, disabled, and stopped - expect(snapController.get(mockSnap.id)?.blocked).toBe(true); - expect(snapController.get(mockSnap.id)?.enabled).toBe(false); - expect(snapController.isRunning(mockSnap.id)).toBe(false); + expect(snapController.getSnap(mockSnap.id)?.blocked).toBe(true); + expect(snapController.getSnap(mockSnap.id)?.enabled).toBe(false); + expect(snapController.isSnapRunning(mockSnap.id)).toBe(false); snapController.destroy(); }); it('unblocks snaps as expected', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const mockSnapA = getMockSnapData({ @@ -10660,12 +10664,12 @@ describe('SnapController', () => { const snapController = await getSnapController(options); // A is blocked and disabled - expect(snapController.get(mockSnapA.id)?.blocked).toBe(true); - expect(snapController.get(mockSnapA.id)?.enabled).toBe(false); + expect(snapController.getSnap(mockSnapA.id)?.blocked).toBe(true); + expect(snapController.getSnap(mockSnapA.id)?.enabled).toBe(false); // B is unblocked and enabled - expect(snapController.get(mockSnapB.id)?.blocked).toBe(false); - expect(snapController.get(mockSnapB.id)?.enabled).toBe(true); + expect(snapController.getSnap(mockSnapB.id)?.blocked).toBe(false); + expect(snapController.getSnap(mockSnapB.id)?.enabled).toBe(true); // Indicate that both snaps A and B are unblocked, and update blocked // states. @@ -10676,12 +10680,12 @@ describe('SnapController', () => { await snapController.updateRegistry(); // A is unblocked, but still disabled - expect(snapController.get(mockSnapA.id)?.blocked).toBe(false); - expect(snapController.get(mockSnapA.id)?.enabled).toBe(false); + expect(snapController.getSnap(mockSnapA.id)?.blocked).toBe(false); + expect(snapController.getSnap(mockSnapA.id)?.enabled).toBe(false); // B remains unblocked and enabled - expect(snapController.get(mockSnapB.id)?.blocked).toBe(false); - expect(snapController.get(mockSnapB.id)?.enabled).toBe(true); + expect(snapController.getSnap(mockSnapB.id)?.blocked).toBe(false); + expect(snapController.getSnap(mockSnapB.id)?.enabled).toBe(true); expect(publishMock).toHaveBeenLastCalledWith( 'SnapController:snapUnblocked', @@ -10693,7 +10697,7 @@ describe('SnapController', () => { it('updating blocked snaps does not throw if a snap is removed while fetching the blocklist', async () => { const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const mockSnap = getMockSnapData({ @@ -10728,7 +10732,7 @@ describe('SnapController', () => { await updateBlockList; // The snap was removed, no errors were thrown - expect(snapController.has(mockSnap.id)).toBe(false); + expect(snapController.hasSnap(mockSnap.id)).toBe(false); expect(consoleErrorSpy).not.toHaveBeenCalled(); snapController.destroy(); @@ -10736,7 +10740,7 @@ describe('SnapController', () => { it('logs but does not throw unexpected errors while blocking', async () => { const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const mockSnap = getMockSnapData({ @@ -10766,8 +10770,8 @@ describe('SnapController', () => { await snapController.updateRegistry(); // A is blocked and disabled - expect(snapController.get(mockSnap.id)?.blocked).toBe(true); - expect(snapController.get(mockSnap.id)?.enabled).toBe(false); + expect(snapController.getSnap(mockSnap.id)?.blocked).toBe(true); + expect(snapController.getSnap(mockSnap.id)?.enabled).toBe(false); expect(consoleErrorSpy).toHaveBeenCalledTimes(1); expect(consoleErrorSpy).toHaveBeenCalledWith( @@ -10779,7 +10783,7 @@ describe('SnapController', () => { }); it('updates preinstalled Snaps', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); // Simulate previous permissions, some of which will be removed @@ -10834,7 +10838,7 @@ describe('SnapController', () => { await waitForStateChange(options.messenger); await sleep(100); - const updatedSnap = snapController.get(snapId); + const updatedSnap = snapController.getSnap(snapId); assert(updatedSnap); expect(updatedSnap.version).toStrictEqual(updateVersion); @@ -10875,7 +10879,7 @@ describe('SnapController', () => { }); it('does not update preinstalled Snaps when the feature flag is off', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const registry = new MockSnapsRegistry(rootMessenger); const snapId = 'npm:@metamask/jsx-example-snap' as SnapId; @@ -10903,7 +10907,7 @@ describe('SnapController', () => { await snapController.updateRegistry(); - const snap = snapController.get(snapId); + const snap = snapController.getSnap(snapId); assert(snap); expect(snap.version).toStrictEqual(mockSnap.version); @@ -10940,7 +10944,7 @@ describe('SnapController', () => { const callActionSpy = jest.spyOn(messenger, 'call'); - expect(snapController.has(MOCK_SNAP_ID)).toBe(true); + expect(snapController.hasSnap(MOCK_SNAP_ID)).toBe(true); const requestPromise = snapController.handleRequest({ snapId: MOCK_SNAP_ID, @@ -10953,13 +10957,13 @@ describe('SnapController', () => { await waitForStateChange(messenger); - expect(snapController.isRunning(MOCK_SNAP_ID)).toBe(true); + expect(snapController.isSnapRunning(MOCK_SNAP_ID)).toBe(true); await new Promise((resolve) => setTimeout(resolve, 100)); await snapController.clearState(); - expect(snapController.has(MOCK_SNAP_ID)).toBe(false); + expect(snapController.hasSnap(MOCK_SNAP_ID)).toBe(false); expect(callActionSpy).toHaveBeenCalledWith( 'ExecutionService:terminateSnap', @@ -11010,7 +11014,7 @@ describe('SnapController', () => { }, ]; - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:revokeAllPermissions', @@ -11050,16 +11054,16 @@ describe('SnapController', () => { const callActionSpy = jest.spyOn(options.messenger, 'call'); - expect(snapController.has(MOCK_SNAP_ID)).toBe(true); - expect(snapController.has(preinstalledSnapId)).toBe(true); + expect(snapController.hasSnap(MOCK_SNAP_ID)).toBe(true); + expect(snapController.hasSnap(preinstalledSnapId)).toBe(true); await snapController.startSnap(MOCK_SNAP_ID); await snapController.startSnap(preinstalledSnapId); await snapController.clearState(); - expect(snapController.has(MOCK_SNAP_ID)).toBe(false); - expect(snapController.has(preinstalledSnapId)).toBe(true); + expect(snapController.hasSnap(MOCK_SNAP_ID)).toBe(false); + expect(snapController.hasSnap(preinstalledSnapId)).toBe(true); expect(callActionSpy).toHaveBeenCalledWith( 'ExecutionService:terminateSnap', @@ -11104,7 +11108,7 @@ describe('SnapController', () => { describe('SnapController actions', () => { describe('SnapController:init', () => { it('populates `isReady`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -11122,15 +11126,13 @@ describe('SnapController', () => { }); it('calls `onStart` for all Snaps with the `endowment:lifecycle-hooks` permission', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', ( origin, - ): SubjectPermissions< - ValidPermission - > => { + ): SubjectPermissions> => { if (origin === MOCK_SNAP_ID) { return { [SnapEndowments.LifecycleHooks]: @@ -11205,7 +11207,7 @@ describe('SnapController', () => { .spyOn(console, 'error') .mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -11261,9 +11263,9 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - const getSpy = jest.spyOn(snapController, 'get'); + const getSpy = jest.spyOn(snapController, 'getSnap'); const result = options.messenger.call( - 'SnapController:get', + 'SnapController:getSnap', MOCK_SNAP_ID, ); @@ -11303,7 +11305,7 @@ describe('SnapController', () => { it('should track event for allowed handler', async () => { const mockTrackEvent = jest.fn(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const executionEnvironmentStub = new ExecutionEnvironmentStub( getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; @@ -11319,7 +11321,7 @@ describe('SnapController', () => { executionEnvironmentStub, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); await snapController.handleRequest({ @@ -11353,7 +11355,7 @@ describe('SnapController', () => { it('should not track event for disallowed handler', async () => { const mockTrackEvent = jest.fn(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:getPermissions', @@ -11386,7 +11388,7 @@ describe('SnapController', () => { executionEnvironmentStub, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); await snapController.handleRequest({ @@ -11411,7 +11413,7 @@ describe('SnapController', () => { const mockTrackEvent = jest.fn().mockImplementation(() => { throw error; }); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const executionEnvironmentStub = new ExecutionEnvironmentStub( getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; @@ -11427,7 +11429,7 @@ describe('SnapController', () => { executionEnvironmentStub, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); await snapController.handleRequest({ @@ -11453,7 +11455,7 @@ describe('SnapController', () => { it('should not track event for preinstalled snap', async () => { const mockTrackEvent = jest.fn(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const executionEnvironmentStub = new ExecutionEnvironmentStub( getNodeEESMessenger(rootMessenger), ) as unknown as NodeThreadExecutionService; @@ -11471,7 +11473,7 @@ describe('SnapController', () => { executionEnvironmentStub, ); - const snap = snapController.getExpect(MOCK_SNAP_ID); + const snap = snapController.getSnapExpect(MOCK_SNAP_ID); await snapController.startSnap(snap.id); await snapController.handleRequest({ @@ -11888,8 +11890,8 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - const hasSpy = jest.spyOn(snapController, 'has'); - const result = options.messenger.call('SnapController:has', id); + const hasSpy = jest.spyOn(snapController, 'hasSnap'); + const result = options.messenger.call('SnapController:hasSnap', id); expect(hasSpy).toHaveBeenCalledTimes(1); expect(result).toBe(true); @@ -12267,7 +12269,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - options.messenger.call('SnapController:enable', mockSnap.id); + options.messenger.call('SnapController:enableSnap', mockSnap.id); expect(snapController.state.snaps[mockSnap.id].enabled).toBe(true); snapController.destroy(); @@ -12290,7 +12292,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - await options.messenger.call('SnapController:disable', mockSnap.id); + await options.messenger.call('SnapController:disableSnap', mockSnap.id); expect(snapController.state.snaps[mockSnap.id].enabled).toBe(false); snapController.destroy(); @@ -12313,7 +12315,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - await options.messenger.call('SnapController:remove', mockSnap.id); + await options.messenger.call('SnapController:removeSnap', mockSnap.id); expect(snapController.state.snaps[mockSnap.id]).toBeUndefined(); snapController.destroy(); @@ -12322,7 +12324,7 @@ describe('SnapController', () => { describe('SnapController:getPermitted', () => { it('calls SnapController.getPermittedSnaps()', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const mockSnap = getMockSnapData({ id: MOCK_SNAP_ID, origin: MOCK_ORIGIN, @@ -12338,7 +12340,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); const result = options.messenger.call( - 'SnapController:getPermitted', + 'SnapController:getPermittedSnaps', mockSnap.origin, ); expect(result).toStrictEqual({ @@ -12364,7 +12366,7 @@ describe('SnapController', () => { const snapController = await getSnapController(options); - const result = options.messenger.call('SnapController:getAll'); + const result = options.messenger.call('SnapController:getAllSnaps'); expect(result).toStrictEqual([getTruncatedSnap()]); snapController.destroy(); @@ -12412,7 +12414,7 @@ describe('SnapController', () => { .mockImplementation(); const snaps = { [MOCK_SNAP_ID]: {} }; - await options.messenger.call('SnapController:install', 'foo', snaps); + await options.messenger.call('SnapController:installSnaps', 'foo', snaps); expect(installSnapsSpy).toHaveBeenCalledTimes(1); expect(installSnapsSpy).toHaveBeenCalledWith('foo', snaps); @@ -12421,7 +12423,7 @@ describe('SnapController', () => { }); describe('SnapController:disconnectOrigin', () => { - it('calls SnapController.removeSnapFromSubject()', async () => { + it('calls SnapController.disconnectOrigin()', async () => { const permittedSnaps = [ MOCK_SNAP_ID, MOCK_LOCAL_SNAP_ID, @@ -12445,7 +12447,7 @@ describe('SnapController', () => { const removeSnapFromSubjectSpy = jest.spyOn( snapController, - 'removeSnapFromSubject', + 'disconnectOrigin', ); const callActionSpy = jest.spyOn(options.messenger, 'call'); @@ -12487,7 +12489,7 @@ describe('SnapController', () => { const callActionSpy = jest.spyOn(options.messenger, 'call'); options.messenger.call( - 'SnapController:revokeDynamicPermissions', + 'SnapController:revokeDynamicSnapPermissions', MOCK_SNAP_ID, ['endowment:caip25'], ); @@ -12507,7 +12509,7 @@ describe('SnapController', () => { expect(() => options.messenger.call( - 'SnapController:revokeDynamicPermissions', + 'SnapController:revokeDynamicSnapPermissions', MOCK_SNAP_ID, ['snap_notify'], ), @@ -12545,7 +12547,7 @@ describe('SnapController', () => { expect( await options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './src/foo.json', ), @@ -12595,7 +12597,7 @@ describe('SnapController', () => { expect( await options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './src/foo.json', AuxiliaryFileEncoding.Hex, @@ -12621,7 +12623,7 @@ describe('SnapController', () => { expect( await options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './foo.json', ), @@ -12668,7 +12670,7 @@ describe('SnapController', () => { await expect( options.messenger.call( - 'SnapController:getFile', + 'SnapController:getSnapFile', MOCK_SNAP_ID, './src/foo.json', AuxiliaryFileEncoding.Hex, @@ -12683,7 +12685,7 @@ describe('SnapController', () => { describe('SnapController:snapInstalled', () => { it('calls the `onInstall` lifecycle hook', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12714,6 +12716,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12750,7 +12753,7 @@ describe('SnapController', () => { }); it('does not call the `onInstall` lifecycle hook if the snap does not have the `endowment:lifecycle-hooks` permission', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12770,6 +12773,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12795,7 +12799,7 @@ describe('SnapController', () => { it('logs an error if the lifecycle hook call fails', async () => { const log = jest.spyOn(console, 'error').mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12818,6 +12822,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12832,7 +12837,7 @@ describe('SnapController', () => { describe('SnapController:snapUpdated', () => { it('calls the `onUpdate` lifecycle hook', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12864,6 +12869,7 @@ describe('SnapController', () => { getTruncatedSnap(), '0.9.0', MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12900,7 +12906,7 @@ describe('SnapController', () => { }); it('does not call the `onUpdate` lifecycle hook if the snap does not have the `endowment:lifecycle-hooks` permission', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12920,6 +12926,7 @@ describe('SnapController', () => { 'SnapController:snapInstalled', getTruncatedSnap(), MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -12945,7 +12952,7 @@ describe('SnapController', () => { it('logs an error if the lifecycle hook call fails', async () => { const log = jest.spyOn(console, 'error').mockImplementation(); - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); const options = getSnapControllerOptions({ rootMessenger, @@ -12969,6 +12976,7 @@ describe('SnapController', () => { getTruncatedSnap(), '0.9.0', MOCK_ORIGIN, + false, ); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -13112,7 +13120,7 @@ describe('SnapController', () => { describe('SnapController:setClientActive', () => { it('calls the `onActive` lifecycle hook for all Snaps when called with `true`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -13199,7 +13207,7 @@ describe('SnapController', () => { }); it('calls the `onInactive` lifecycle hook for all Snaps when called with `false`', async () => { - const rootMessenger = getControllerMessenger(); + const rootMessenger = getRootMessenger(); rootMessenger.registerActionHandler( 'PermissionController:hasPermission', @@ -13445,11 +13453,11 @@ describe('SnapController', () => { // create a new controller const secondSnapController = await getSnapController(options); - expect(secondSnapController.isRunning(id)).toBe(false); + expect(secondSnapController.isSnapRunning(id)).toBe(false); await secondSnapController.startSnap(id); expect(secondSnapController.state.snaps[id]).toBeDefined(); - expect(secondSnapController.isRunning(id)).toBe(true); + expect(secondSnapController.isSnapRunning(id)).toBe(true); firstSnapController.destroy(); secondSnapController.destroy(); }); diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 344fe8c012..0b8cf3b86b 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -171,6 +171,7 @@ import type { } from './registry'; import { SnapsRegistryStatus } from './registry'; import { getRunnableSnaps } from './selectors'; +import type { SnapControllerMethodActions } from './SnapController-method-action-types'; import { Timer } from './Timer'; import { forceStrict, validateMachine } from '../fsm'; import type { @@ -205,7 +206,40 @@ import { export const controllerName = 'SnapController'; -// TODO: Figure out how to name these +export const MESSENGER_EXPOSED_METHODS = [ + 'init', + 'updateRegistry', + 'startSnap', + 'enableSnap', + 'disableSnap', + 'stopSnap', + 'stopAllSnaps', + 'isSnapRunning', + 'hasSnap', + 'getSnap', + 'getSnapExpect', + 'getTruncatedSnap', + 'getTruncatedSnapExpect', + 'updateSnapState', + 'clearSnapState', + 'getSnapState', + 'getSnapFile', + 'isMinimumPlatformVersion', + 'clearState', + 'removeSnap', + 'removeSnaps', + 'disconnectOrigin', + 'revokeDynamicSnapPermissions', + 'incrementActiveReferences', + 'decrementActiveReferences', + '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 +352,6 @@ export type SnapRuntimeData = { getStateMutex: Mutex; }; -export type SnapError = { - message: string; - code: number; - data?: Json; -}; - // Types that probably should be defined elsewhere in prod type StoredSnaps = Record; @@ -359,188 +387,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 +409,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 +417,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 +426,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 +434,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 +442,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 +450,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 +463,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 +472,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 +481,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 +489,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 +502,23 @@ export type SnapControllerStateChangeEvent = ControllerStateChangeEvent< SnapControllerState >; -type KeyringControllerLock = { +type KeyringControllerLockEvent = { type: 'KeyringController:lock'; payload: []; }; export type SnapControllerEvents = - | SnapBlocked - | SnapInstalled - | SnapUninstalled - | SnapInstallStarted - | SnapInstallFailed - | SnapStateChange - | SnapUnblocked - | SnapUpdated - | SnapRolledback - | SnapTerminated - | SnapEnabled - | SnapDisabled + | SnapControllerSnapBlockedEvent + | SnapControllerSnapInstalledEvent + | SnapControllerSnapUninstalledEvent + | SnapControllerSnapInstallStartedEvent + | SnapControllerSnapInstallFailedEvent + | SnapControllerSnapUnblockedEvent + | SnapControllerSnapUpdatedEvent + | SnapControllerSnapRolledbackEvent + | SnapControllerSnapTerminatedEvent + | SnapControllerSnapEnabledEvent + | SnapControllerSnapDisabledEvent | SnapControllerStateChangeEvent; export type AllowedActions = @@ -693,12 +553,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 +1038,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 +1110,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 +1125,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 +1141,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), ); @@ -1325,16 +1187,16 @@ export class SnapController extends BaseController< 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 +1255,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 +1367,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 +1375,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 +1546,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 +1555,7 @@ export class SnapController extends BaseController< snapId: SnapId, blockedSnapInfo?: BlockReason, ): Promise { - if (!this.has(snapId)) { + if (!this.hasSnap(snapId)) { return; } @@ -1720,13 +1582,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 +1780,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 +1792,7 @@ export class SnapController extends BaseController< this.messenger.publish( 'SnapController:snapEnabled', - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), ); } @@ -1941,7 +1803,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 +1811,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 +1852,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 +1861,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 +1881,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 +1911,7 @@ export class SnapController extends BaseController< this.messenger.publish( 'SnapController:snapTerminated', - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), ); } @@ -2060,8 +1922,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 +1932,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 +1944,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 +1953,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 +1972,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 +1985,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 +2292,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 +2321,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 +2384,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 +2430,7 @@ export class SnapController extends BaseController< ); for (const origin of Object.keys(revokedInitialConnections)) { - this.removeSnapFromSubject(origin, snapId); + this.disconnectOrigin(origin, snapId); } } @@ -2628,12 +2490,13 @@ export class SnapController extends BaseController< } /** - * Removes a snap's permission (caveat) from the specified subject. + * Disconnect the Snap from the given origin, meaning the origin can no longer + * interact with the Snap until it is reconnected. * - * @param origin - The origin from which to remove the snap. + * @param origin - The origin from which to remove the Snap. * @param snapId - The id of the snap to remove. */ - removeSnapFromSubject(origin: string, snapId: SnapId) { + disconnectOrigin(origin: string, snapId: SnapId) { const subjectPermissions = this.messenger.call( 'PermissionController:getPermissions', origin, @@ -2705,7 +2568,7 @@ export class SnapController extends BaseController< 'PermissionController:getSubjectNames', ); for (const subject of subjects) { - this.removeSnapFromSubject(subject, snapId); + this.disconnectOrigin(subject, snapId); } } @@ -2777,8 +2640,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 +2703,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 +2731,7 @@ export class SnapController extends BaseController< pendingInstalls.forEach((snapId) => this.messenger.publish( `SnapController:snapInstalled`, - this.getTruncatedExpect(snapId), + this.getTruncatedSnapExpect(snapId), origin, false, ), @@ -2877,7 +2740,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 +2749,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 +2781,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 +2813,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 +2843,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 +2956,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 +3055,7 @@ export class SnapController extends BaseController< approvedNewPermissions = newPermissions; } - if (this.isRunning(snapId)) { + if (this.isSnapRunning(snapId)) { await this.stopSnap(snapId, SnapStatusEvents.Stop); } @@ -3245,7 +3110,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 +3222,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 +3598,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 +3701,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 +3734,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 +3790,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 +4165,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 +4238,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 +4262,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 +4282,7 @@ export class SnapController extends BaseController< previousInitialConnections ?? {}, ); - const truncatedSnap = this.getTruncatedExpect(snapId); + const truncatedSnap = this.getTruncatedSnapExpect(snapId); this.messenger.publish( 'SnapController:snapRolledback', @@ -4454,7 +4320,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 +4544,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..dd132da9df 100644 --- a/packages/snaps-controllers/src/snaps/index.ts +++ b/packages/snaps-controllers/src/snaps/index.ts @@ -1,5 +1,57 @@ 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, + SnapControllerStartSnapAction, + SnapControllerEnableSnapAction, + SnapControllerDisableSnapAction, + SnapControllerStopSnapAction, + SnapControllerStopAllSnapsAction, + SnapControllerIsSnapRunningAction, + SnapControllerHasSnapAction, + SnapControllerGetSnapAction, + SnapControllerGetSnapExpectAction, + SnapControllerGetTruncatedSnapAction, + SnapControllerGetTruncatedSnapExpectAction, + SnapControllerUpdateSnapStateAction, + SnapControllerClearSnapStateAction, + SnapControllerGetSnapStateAction, + SnapControllerGetSnapFileAction, + SnapControllerIsMinimumPlatformVersionAction, + SnapControllerClearStateAction, + SnapControllerRemoveSnapAction, + SnapControllerRemoveSnapsAction, + SnapControllerDisconnectOriginAction, + SnapControllerRevokeDynamicSnapPermissionsAction, + SnapControllerIncrementActiveReferencesAction, + SnapControllerDecrementActiveReferencesAction, + 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/scripts/generate-method-action-types.mts b/scripts/generate-method-action-types.mts new file mode 100644 index 0000000000..27f3a5a436 --- /dev/null +++ b/scripts/generate-method-action-types.mts @@ -0,0 +1,773 @@ +#!yarn tsx + +// ESLint is saying `ts` can be replaced with named imports, but this doesn't +// seem to actually work with the current TypeScript version. +/* eslint-disable no-console, import-x/no-named-as-default-member */ + +import { assert, hasProperty, isObject } from '@metamask/utils'; +import { ESLint } from 'eslint'; +import * as fs from 'fs'; +import * as path from 'path'; +import ts from 'typescript'; +import yargs from 'yargs'; + +type MethodInfo = { + name: string; + jsDoc: string; + signature: string; +}; + +type ControllerInfo = { + name: string; + filePath: string; + exposedMethods: string[]; + methods: MethodInfo[]; +}; + +/** + * The parsed command-line arguments. + */ +type CommandLineArguments = { + /** + * Whether to check if the action types files are up to date. + */ + check: boolean; + /** + * Whether to fix the action types files. + */ + fix: boolean; + /** + * Optional path to a specific controller to process. + */ + controllerPath: string; +}; + +/** + * Uses `yargs` to parse the arguments given to the script. + * + * @returns The command line arguments. + */ +async function parseCommandLineArguments(): Promise { + const { + check, + fix, + path: controllerPath, + } = await yargs(process.argv.slice(2)) + .command( + '$0 [path]', + 'Generate method action types for a controller messenger', + (yargsInstance) => { + yargsInstance.positional('path', { + type: 'string', + description: 'Path to the folder where controllers are located', + default: 'src', + }); + }, + ) + .option('check', { + type: 'boolean', + description: 'Check if generated action type files are up to date', + default: false, + }) + .option('fix', { + type: 'boolean', + description: 'Generate/update action type files', + default: false, + }) + .help() + .check((argv) => { + if (!argv.check && !argv.fix) { + throw new Error('Either --check or --fix must be provided.\n'); + } + return true; + }).argv; + + return { + check, + fix, + // TypeScript doesn't narrow the type of `controllerPath` even though we defined it as a string in yargs, so we need to cast it here. + controllerPath: controllerPath as string, + }; +} + +/** + * Checks if generated action types files are up to date. + * + * @param controllers - Array of controller information objects. + * @param eslint - The ESLint instance to use for formatting. + */ +async function checkActionTypesFiles( + controllers: ControllerInfo[], + eslint: ESLint, +): Promise { + let hasErrors = false; + + // Track files that exist and their corresponding temp files + const fileComparisonJobs: { + expectedTempFile: string; + actualFile: string; + baseFileName: string; + }[] = []; + + try { + // Check each controller and prepare comparison jobs + for (const controller of controllers) { + console.log(`\nšŸ”§ Checking ${controller.name}...`); + const outputDir = path.dirname(controller.filePath); + const baseFileName = path.basename(controller.filePath, '.ts'); + const actualFile = path.join( + outputDir, + `${baseFileName}-method-action-types.ts`, + ); + + const expectedContent = generateActionTypesContent(controller); + const expectedTempFile = actualFile.replace('.ts', '.tmp.ts'); + + try { + // Check if actual file exists first + await fs.promises.access(actualFile); + + // Write expected content to temp file + await fs.promises.writeFile(expectedTempFile, expectedContent, 'utf8'); + + // Add to comparison jobs + fileComparisonJobs.push({ + expectedTempFile, + actualFile, + baseFileName, + }); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + console.error( + `āŒ ${baseFileName}-method-action-types.ts does not exist`, + ); + } else { + console.error( + `āŒ Error reading ${baseFileName}-method-action-types.ts:`, + error, + ); + } + hasErrors = true; + } + } + + // Run ESLint on all files at once if we have comparisons to make + if (fileComparisonJobs.length > 0) { + console.log('\nšŸ“ Running ESLint to compare files...'); + + const results = await eslint.lintFiles( + fileComparisonJobs.map((job) => job.expectedTempFile), + ); + await ESLint.outputFixes(results); + + // Compare expected vs actual content + for (const job of fileComparisonJobs) { + const expectedContent = await fs.promises.readFile( + job.expectedTempFile, + 'utf8', + ); + const actualContent = await fs.promises.readFile( + job.actualFile, + 'utf8', + ); + + if (expectedContent === actualContent) { + console.log( + `āœ… ${job.baseFileName}-method-action-types.ts is up to date`, + ); + } else { + console.error( + `āŒ ${job.baseFileName}-method-action-types.ts is out of date`, + ); + hasErrors = true; + } + } + } + } finally { + // Clean up temp files + for (const job of fileComparisonJobs) { + try { + await fs.promises.unlink(job.expectedTempFile); + } catch { + // Ignore cleanup errors + } + } + } + + if (hasErrors) { + console.error('\nšŸ’„ Some action type files are out of date or missing.'); + console.error( + 'Run `yarn generate-method-action-types --fix` to update them.', + ); + process.exitCode = 1; + } else { + console.log('\nšŸŽ‰ All action type files are up to date!'); + } +} + +/** + * Main entry point for the script. + */ +async function main(): Promise { + const { fix, controllerPath } = await parseCommandLineArguments(); + + console.log('šŸ” Searching for controllers with MESSENGER_EXPOSED_METHODS...'); + + const controllers = await findControllersWithExposedMethods(controllerPath); + + if (controllers.length === 0) { + console.log('āš ļø No controllers found with MESSENGER_EXPOSED_METHODS'); + return; + } + + console.log( + `šŸ“¦ Found ${controllers.length} controller(s) with exposed methods`, + ); + + const eslint = new ESLint({ + fix: true, + errorOnUnmatchedPattern: false, + }); + + if (fix) { + await generateAllActionTypesFiles(controllers, eslint); + console.log('\nšŸŽ‰ All action types generated successfully!'); + } else { + // -check mode: check files + await checkActionTypesFiles(controllers, eslint); + } +} + +/** + * Check if a path is a directory. + * + * @param pathValue - The path to check. + * @returns True if the path is a directory, false otherwise. + * @throws If an error occurs other than the path not existing. + */ +async function isDirectory(pathValue: string): Promise { + try { + const stats = await fs.promises.stat(pathValue); + return stats.isDirectory(); + } catch (error) { + if ( + isObject(error) && + hasProperty(error, 'code') && + error.code === 'ENOENT' + ) { + return false; + } + + throw error; + } +} + +/** + * Recursively get all files in a directory and its subdirectories. + * + * @param directory - The directory to search. + * @returns An array of file paths. + */ +async function getFiles(directory: string): Promise { + const entries = await fs.promises.readdir(directory, { withFileTypes: true }); + const files = await Promise.all( + entries.map(async (entry) => { + const fullPath = path.join(directory, entry.name); + return entry.isDirectory() ? await getFiles(fullPath) : fullPath; + }), + ); + + return files.flat(); +} + +/** + * Finds all controller files that have MESSENGER_EXPOSED_METHODS constants. + * + * @param controllerPath - Path to the folder where controllers are located. + * @returns A list of controller information objects. + */ +async function findControllersWithExposedMethods( + controllerPath: string, +): Promise { + const srcPath = path.resolve(process.cwd(), controllerPath); + const controllers: ControllerInfo[] = []; + + if (!(await isDirectory(srcPath))) { + throw new Error(`The specified path is not a directory: ${srcPath}`); + } + + const srcFiles = await getFiles(srcPath); + + for (const file of srcFiles) { + if (!file.endsWith('.ts') || file.endsWith('.test.ts')) { + continue; + } + + const content = await fs.promises.readFile(file, 'utf8'); + + if (content.includes('MESSENGER_EXPOSED_METHODS')) { + const controllerInfo = await parseControllerFile(file); + if (controllerInfo) { + controllers.push(controllerInfo); + } + } + } + + return controllers; +} + +/** + * Context for AST visiting. + */ +type VisitorContext = { + exposedMethods: string[]; + className: string; + methods: MethodInfo[]; + sourceFile: ts.SourceFile; +}; + +/** + * Visits AST nodes to find exposed methods and controller class. + * + * @param context - The visitor context. + * @returns A function to visit nodes. + */ +function createASTVisitor(context: VisitorContext): (node: ts.Node) => void { + /** + * Visits AST nodes to find exposed methods and controller class. + * + * @param node - The AST node to visit. + */ + function visitNode(node: ts.Node): void { + if (ts.isVariableStatement(node)) { + const declaration = node.declarationList.declarations[0]; + if ( + ts.isIdentifier(declaration.name) && + declaration.name.text === 'MESSENGER_EXPOSED_METHODS' + ) { + if (declaration.initializer) { + let arrayExpression: ts.ArrayLiteralExpression | undefined; + + // Handle direct array literal + if (ts.isArrayLiteralExpression(declaration.initializer)) { + arrayExpression = declaration.initializer; + } + // Handle "as const" assertion: expression is wrapped in type assertion + else if ( + ts.isAsExpression(declaration.initializer) && + ts.isArrayLiteralExpression(declaration.initializer.expression) + ) { + arrayExpression = declaration.initializer.expression; + } + + if (arrayExpression) { + context.exposedMethods = arrayExpression.elements + .filter(ts.isStringLiteral) + .map((element) => element.text); + } + } + } + } + + // Find the controller or service class + if (ts.isClassDeclaration(node) && node.name) { + const classText = node.name.text; + if (classText.includes('Controller') || classText.includes('Service')) { + context.className = classText; + + // Extract method info for exposed methods + const seenMethods = new Set(); + for (const member of node.members) { + if ( + ts.isMethodDeclaration(member) && + member.name && + ts.isIdentifier(member.name) + ) { + const methodName = member.name.text; + if ( + context.exposedMethods.includes(methodName) && + !seenMethods.has(methodName) + ) { + seenMethods.add(methodName); + const jsDoc = extractJSDoc(member, context.sourceFile); + const signature = extractMethodSignature(member); + context.methods.push({ + name: methodName, + jsDoc, + signature, + }); + } + } + } + } + } + + ts.forEachChild(node, visitNode); + } + + return visitNode; +} + +/** + * Create a TypeScript program for the given file by locating the nearest + * tsconfig.json. + * + * @param filePath - Absolute path to the source file. + * @returns A TypeScript program, or null if no tsconfig was found. + */ +function createProgramForFile(filePath: string): ts.Program | null { + const configPath = ts.findConfigFile( + path.dirname(filePath), + ts.sys.fileExists.bind(ts.sys), + 'tsconfig.json', + ); + if (!configPath) { + return null; + } + + const { config, error } = ts.readConfigFile( + configPath, + ts.sys.readFile.bind(ts.sys), + ); + + if (error) { + return null; + } + + const parsedConfig = ts.parseJsonConfigFileContent( + config, + ts.sys, + path.dirname(configPath), + ); + + return ts.createProgram({ + rootNames: parsedConfig.fileNames, + options: parsedConfig.options, + }); +} + +/** + * Find a class declaration with the given name in a source file. + * + * @param sourceFile - The source file to search. + * @param className - The class name to look for. + * @returns The class declaration node, or null if not found. + */ +function findClassInSourceFile( + sourceFile: ts.SourceFile, + className: string, +): ts.ClassDeclaration | null { + return ( + sourceFile.statements.find( + (node): node is ts.ClassDeclaration => + ts.isClassDeclaration(node) && node.name?.text === className, + ) ?? null + ); +} + +/** + * Search through the class hierarchy of a TypeScript type to find the + * declaration of a method with the given name. + * + * @param classType - The class type to search. + * @param methodName - The method name to look for. + * @returns The method declaration node, or null if not found. + */ +function findMethodInHierarchy( + classType: ts.Type, + methodName: string, +): ts.MethodDeclaration | null { + const symbol = classType.getProperty(methodName); + if (!symbol) { + return null; + } + + const declarations = symbol.getDeclarations(); + if (!declarations) { + return null; + } + + for (const declaration of declarations) { + if (ts.isMethodDeclaration(declaration)) { + return declaration; + } + } + + return null; +} + +/** + * Parses a controller file to extract exposed methods and their metadata. + * + * @param filePath - Path to the controller file to parse. + * @returns Controller information or null if parsing fails. + */ +async function parseControllerFile( + filePath: string, +): Promise { + try { + const content = await fs.promises.readFile(filePath, 'utf8'); + const sourceFile = ts.createSourceFile( + filePath, + content, + ts.ScriptTarget.Latest, + true, + ); + + const context: VisitorContext = { + exposedMethods: [], + className: '', + methods: [], + sourceFile, + }; + + createASTVisitor(context)(sourceFile); + + if (context.exposedMethods.length === 0 || !context.className) { + return null; + } + + // For exposed methods not found directly in the class body, attempt to + // locate them in the inheritance hierarchy using the type checker. + const foundMethodNames = new Set( + context.methods.map((method) => method.name), + ); + + const inheritedMethodNames = context.exposedMethods.filter( + (name) => !foundMethodNames.has(name), + ); + + if (inheritedMethodNames.length > 0) { + const program = createProgramForFile(filePath); + const checker = program?.getTypeChecker(); + const programSourceFile = program?.getSourceFile(filePath); + + assert( + checker, + `Type checker could not be created for "${filePath}". Ensure a valid tsconfig.json is present.`, + ); + + assert( + programSourceFile, + `Source file "${filePath}" not found in program.`, + ); + + const classNode = findClassInSourceFile( + programSourceFile, + context.className, + ); + + assert( + classNode, + `Class "${context.className}" not found in "${filePath}".`, + ); + + const classType = checker.getTypeAtLocation(classNode); + for (const methodName of inheritedMethodNames) { + const methodDeclaration = findMethodInHierarchy(classType, methodName); + + const jsDoc = methodDeclaration + ? extractJSDoc(methodDeclaration, methodDeclaration.getSourceFile()) + : ''; + context.methods.push({ name: methodName, jsDoc, signature: '' }); + } + } + + return { + name: context.className, + filePath, + exposedMethods: context.exposedMethods, + methods: context.methods, + }; + } catch (error) { + console.error(`Error parsing ${filePath}:`, error); + return null; + } +} + +/** + * Extracts JSDoc comment from a method declaration. + * + * @param node - The method declaration node. + * @param sourceFile - The source file. + * @returns The JSDoc comment. + */ +function extractJSDoc( + node: ts.MethodDeclaration, + sourceFile: ts.SourceFile, +): string { + const jsDocTags = ts.getJSDocCommentsAndTags(node); + if (jsDocTags.length === 0) { + return ''; + } + + const jsDoc = jsDocTags[0]; + if (ts.isJSDoc(jsDoc)) { + const fullText = sourceFile.getFullText(); + const start = jsDoc.getFullStart(); + const end = jsDoc.getEnd(); + const rawJsDoc = fullText.substring(start, end).trim(); + return formatJSDoc(rawJsDoc); + } + + return ''; +} + +/** + * Formats JSDoc comments to have consistent indentation for the generated file. + * + * @param rawJsDoc - The raw JSDoc comment from the source. + * @returns The formatted JSDoc comment. + */ +function formatJSDoc(rawJsDoc: string): string { + const lines = rawJsDoc.split('\n'); + const formattedLines: string[] = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (i === 0) { + // First line should be /** + formattedLines.push('/**'); + } else if (i === lines.length - 1) { + // Last line should be */ + formattedLines.push(' */'); + } else { + // Middle lines should start with ' * ' + const trimmed = line.trim(); + if (trimmed.startsWith('*')) { + // Remove existing * and normalize + const content = trimmed.substring(1).trim(); + formattedLines.push(content ? ` * ${content}` : ' *'); + } else { + // Handle lines that don't start with * + formattedLines.push(trimmed ? ` * ${trimmed}` : ' *'); + } + } + } + + return formattedLines.join('\n'); +} + +/** + * Extracts method signature as a string for the handler type. + * + * @param node - The method declaration node. + * @returns The method signature. + */ +function extractMethodSignature(node: ts.MethodDeclaration): string { + // Since we're just using the method reference in the handler type, + // we don't need the full signature - just return the method name + // The actual signature will be inferred from the controller class + return node.name ? (node.name as ts.Identifier).text : ''; +} + +/** + * Generates action types files for all controllers. + * + * @param controllers - Array of controller information objects. + * @param eslint - The ESLint instance to use for formatting. + */ +async function generateAllActionTypesFiles( + controllers: ControllerInfo[], + eslint: ESLint, +): Promise { + const outputFiles: string[] = []; + + // Write all files first + for (const controller of controllers) { + console.log(`\nšŸ”§ Processing ${controller.name}...`); + const outputDir = path.dirname(controller.filePath); + const baseFileName = path.basename(controller.filePath, '.ts'); + const outputFile = path.join( + outputDir, + `${baseFileName}-method-action-types.ts`, + ); + + const generatedContent = generateActionTypesContent(controller); + await fs.promises.writeFile(outputFile, generatedContent, 'utf8'); + outputFiles.push(outputFile); + console.log(`āœ… Generated action types for ${controller.name}`); + } + + // Run ESLint on all the actual files + if (outputFiles.length > 0) { + console.log('\nšŸ“ Running ESLint on generated files...'); + + const results = await eslint.lintFiles(outputFiles); + await ESLint.outputFixes(results); + const errors = ESLint.getErrorResults(results); + if (errors.length > 0) { + console.error('āŒ ESLint errors:', errors); + process.exitCode = 1; + } else { + console.log('āœ… ESLint formatting applied'); + } + } +} + +/** + * Generates the content for the action types file. + * + * @param controller - The controller information object. + * @returns The content for the action types file. + */ +function generateActionTypesContent(controller: ControllerInfo): string { + const baseFileName = path.basename(controller.filePath, '.ts'); + const controllerImportPath = `./${baseFileName}`; + + let content = `/** + * This file is auto generated by \`scripts/generate-method-action-types.ts\`. + * Do not edit manually. + */ + +import type { ${controller.name} } from '${controllerImportPath}'; + +`; + + const actionTypeNames: string[] = []; + + // Generate action types for each exposed method + for (const method of controller.methods) { + const actionTypeName = `${controller.name}${capitalize(method.name)}Action`; + const actionString = `${controller.name}:${method.name}`; + + actionTypeNames.push(actionTypeName); + + // Add the JSDoc if available + if (method.jsDoc) { + content += `${method.jsDoc}\n`; + } + + content += `export type ${actionTypeName} = { + type: \`${actionString}\`; + handler: ${controller.name}['${method.name}']; +};\n\n`; + } + + // Generate union type of all action types + if (actionTypeNames.length > 0) { + const unionTypeName = `${controller.name}MethodActions`; + content += `/** + * Union of all ${controller.name} action types. + */ +export type ${unionTypeName} = ${actionTypeNames.join(' | ')};\n`; + } + + return `${content.trimEnd()}\n`; +} + +/** + * Capitalizes the first letter of a string. + * + * @param str - The string to capitalize. + * @returns The capitalized string. + */ +function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +// Error handling wrapper +main().catch((error) => { + console.error('āŒ Script failed:', error); + process.exitCode = 1; +}); diff --git a/yarn.lock b/yarn.lock index 99eb90ff4f..67a707b05f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4430,6 +4430,7 @@ __metadata: typescript: "npm:~5.3.3" typescript-eslint: "npm:^8.6.0" vite: "npm:^6.4.1" + yargs: "npm:^17.7.1" languageName: unknown linkType: soft