@metamask/snaps-controllers 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cronjob/CronjobController.js +3 -3
- package/dist/cronjob/CronjobController.js.map +1 -1
- package/dist/logging.d.ts +8 -0
- package/dist/logging.js +13 -0
- package/dist/logging.js.map +1 -0
- package/dist/multichain/MultiChainController.js +11 -12
- package/dist/multichain/MultiChainController.js.map +1 -1
- package/dist/services/AbstractExecutionService.js +9 -8
- package/dist/services/AbstractExecutionService.js.map +1 -1
- package/dist/snaps/SnapController.d.ts +16 -4
- package/dist/snaps/SnapController.js +69 -40
- package/dist/snaps/SnapController.js.map +1 -1
- package/dist/snaps/endowments/enum.d.ts +2 -1
- package/dist/snaps/endowments/enum.js +1 -0
- package/dist/snaps/endowments/enum.js.map +1 -1
- package/dist/snaps/endowments/index.d.ts +9 -0
- package/dist/snaps/endowments/index.js +2 -0
- package/dist/snaps/endowments/index.js.map +1 -1
- package/dist/snaps/endowments/web-assembly.d.ts +13 -0
- package/dist/snaps/endowments/web-assembly.js +30 -0
- package/dist/snaps/endowments/web-assembly.js.map +1 -0
- package/dist/snaps/location/npm.js +7 -2
- package/dist/snaps/location/npm.js.map +1 -1
- package/dist/snaps/registry/json.d.ts +9 -1
- package/dist/snaps/registry/json.js +12 -0
- package/dist/snaps/registry/json.js.map +1 -1
- package/dist/snaps/registry/registry.d.ts +10 -1
- package/dist/snaps/registry/registry.js.map +1 -1
- package/package.json +9 -9
|
@@ -53,7 +53,7 @@ class CronjobController extends base_controller_1.BaseControllerV2 {
|
|
|
53
53
|
this.messagingSystem.subscribe('SnapController:snapUpdated', this._handleEventSnapUpdated);
|
|
54
54
|
/* eslint-enable @typescript-eslint/unbound-method */
|
|
55
55
|
this.dailyCheckIn().catch((error) => {
|
|
56
|
-
|
|
56
|
+
(0, snaps_utils_1.logError)(error);
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
@@ -118,7 +118,7 @@ class CronjobController extends base_controller_1.BaseControllerV2 {
|
|
|
118
118
|
timer.start(() => {
|
|
119
119
|
this.executeCronjob(job).catch((error) => {
|
|
120
120
|
// TODO: Decide how to handle errors.
|
|
121
|
-
|
|
121
|
+
(0, snaps_utils_1.logError)(error);
|
|
122
122
|
});
|
|
123
123
|
__classPrivateFieldGet(this, _CronjobController_timers, "f").delete(job.id);
|
|
124
124
|
this.schedule(job);
|
|
@@ -196,7 +196,7 @@ class CronjobController extends base_controller_1.BaseControllerV2 {
|
|
|
196
196
|
__classPrivateFieldGet(this, _CronjobController_dailyTimer, "f").start(() => {
|
|
197
197
|
this.dailyCheckIn().catch((error) => {
|
|
198
198
|
// TODO: Decide how to handle errors.
|
|
199
|
-
|
|
199
|
+
(0, snaps_utils_1.logError)(error);
|
|
200
200
|
});
|
|
201
201
|
});
|
|
202
202
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CronjobController.js","sourceRoot":"","sources":["../../src/cronjob/CronjobController.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BaseControllerV2 as BaseController,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport { GetPermissions } from '@metamask/permission-controller';\nimport {\n HandlerType,\n SnapId,\n TruncatedSnap,\n CronjobSpecification,\n flatten,\n parseCronExpression,\n} from '@metamask/snaps-utils';\nimport { Duration, inMilliseconds } from '@metamask/utils';\n\nimport {\n GetAllSnaps,\n getRunnableSnaps,\n HandleSnapRequest,\n SnapEndowments,\n SnapInstalled,\n SnapRemoved,\n SnapUpdated,\n} from '..';\nimport { getCronjobCaveatJobs } from '../snaps/endowments/cronjob';\nimport { Timer } from '../snaps/Timer';\n\nexport type CronjobControllerActions =\n | GetAllSnaps\n | HandleSnapRequest\n | GetPermissions;\n\nexport type CronjobControllerEvents = SnapInstalled | SnapRemoved | SnapUpdated;\n\nexport type CronjobControllerMessenger = RestrictedControllerMessenger<\n 'CronjobController',\n CronjobControllerActions,\n CronjobControllerEvents,\n CronjobControllerActions['type'],\n CronjobControllerEvents['type']\n>;\n\nexport const DAILY_TIMEOUT = inMilliseconds(24, Duration.Hour);\n\nexport type CronjobControllerArgs = {\n messenger: CronjobControllerMessenger;\n /**\n * Persisted state that will be used for rehydration.\n */\n state?: CronjobControllerState;\n};\n\nexport type Cronjob = {\n timer?: Timer;\n id: string;\n snapId: SnapId;\n} & CronjobSpecification;\n\nexport type StoredJobInformation = {\n lastRun: number;\n};\n\nexport type CronjobControllerState = {\n jobs: Record<string, StoredJobInformation>;\n};\n\nconst controllerName = 'CronjobController';\n\n/**\n * Use this controller to register and schedule periodically executed jobs\n * using RPC method hooks.\n */\nexport class CronjobController extends BaseController<\n typeof controllerName,\n CronjobControllerState,\n CronjobControllerMessenger\n> {\n #messenger: CronjobControllerMessenger;\n\n #dailyTimer!: Timer;\n\n #timers: Map<string, Timer>;\n\n // Mapping from jobId to snapId\n #snapIds: Map<string, string>;\n\n constructor({ messenger, state }: CronjobControllerArgs) {\n super({\n messenger,\n metadata: {\n jobs: { persist: true, anonymous: false },\n },\n name: controllerName,\n state: {\n jobs: {},\n ...state,\n },\n });\n this.#timers = new Map();\n this.#snapIds = new Map();\n this.#messenger = messenger;\n\n this._handleEventSnapInstalled = this._handleEventSnapInstalled.bind(this);\n this._handleEventSnapRemoved = this._handleEventSnapRemoved.bind(this);\n this._handleEventSnapUpdated = this._handleEventSnapUpdated.bind(this);\n\n // Subscribe to Snap events\n /* eslint-disable @typescript-eslint/unbound-method */\n this.messagingSystem.subscribe(\n 'SnapController:snapInstalled',\n this._handleEventSnapInstalled,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapRemoved',\n this._handleEventSnapRemoved,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapUpdated',\n this._handleEventSnapUpdated,\n );\n /* eslint-enable @typescript-eslint/unbound-method */\n\n this.dailyCheckIn().catch((error) => {\n console.error(error);\n });\n }\n\n /**\n * Retrieve all cronjob specifications for all runnable snaps.\n *\n * @returns Array of Cronjob specifications.\n */\n private getAllJobs(): Cronjob[] {\n const snaps = this.messagingSystem.call('SnapController:getAll');\n const filteredSnaps = getRunnableSnaps(snaps);\n\n const jobs = filteredSnaps.map((snap) => this.getSnapJobs(snap.id));\n return flatten(jobs).filter((job) => job !== undefined) as Cronjob[];\n }\n\n /**\n * Retrieve all Cronjob specifications for a Snap.\n *\n * @param snapId - ID of a Snap.\n * @returns Array of Cronjob specifications.\n */\n private getSnapJobs(snapId: SnapId): Cronjob[] | undefined {\n const permissions = this.#messenger.call(\n 'PermissionController:getPermissions',\n snapId,\n );\n\n const permission = permissions?.[SnapEndowments.Cronjob];\n const definitions = getCronjobCaveatJobs(permission);\n\n return definitions?.map((definition, idx) => {\n return { ...definition, id: `${snapId}-${idx}`, snapId };\n });\n }\n\n /**\n * Register cron jobs for a given snap by getting specification from a permission caveats.\n * Once registered, each job will be scheduled.\n *\n * @param snapId - ID of a snap.\n */\n register(snapId: SnapId) {\n const jobs = this.getSnapJobs(snapId);\n jobs?.forEach((job) => this.schedule(job));\n }\n\n /**\n * Schedule a new job.\n * This will interpret the cron expression and tell the timer to execute the job\n * at the next suitable point in time.\n * Job last run state will be initialized afterwards.\n *\n * Note: Schedule will be skipped if the job's execution time is too far in the future and\n * will be revisited on a daily check.\n *\n * @param job - Cronjob specification.\n */\n private schedule(job: Cronjob) {\n if (this.#timers.has(job.id)) {\n return;\n }\n\n const parsed = parseCronExpression(job.expression);\n const next = parsed.next();\n const now = new Date();\n const ms = next.getTime() - now.getTime();\n\n // Don't schedule this job yet as it is too far in the future\n if (ms > DAILY_TIMEOUT) {\n return;\n }\n\n const timer = new Timer(ms);\n timer.start(() => {\n this.executeCronjob(job).catch((error) => {\n // TODO: Decide how to handle errors.\n console.error(error);\n });\n\n this.#timers.delete(job.id);\n this.schedule(job);\n });\n\n this.updateJobLastRunState(job.id, 0); // 0 for init, never ran actually\n this.#timers.set(job.id, timer);\n this.#snapIds.set(job.id, job.snapId);\n }\n\n /**\n * Execute job.\n *\n * @param job - Cronjob specification.\n */\n private async executeCronjob(job: Cronjob) {\n this.updateJobLastRunState(job.id, Date.now());\n await this.#messenger.call('SnapController:handleRequest', {\n snapId: job.snapId,\n origin: '',\n handler: HandlerType.OnCronjob,\n request: job.request,\n });\n }\n\n /**\n * Unregister all jobs related to the given snapId.\n *\n * @param snapId - ID of a snap.\n */\n unregister(snapId: SnapId) {\n const jobs = [...this.#snapIds.entries()].filter(\n ([_, jobSnapId]) => jobSnapId === snapId,\n );\n\n if (jobs.length) {\n jobs.forEach(([id]) => {\n const timer = this.#timers.get(id);\n if (timer) {\n timer.cancel();\n this.#timers.delete(id);\n this.#snapIds.delete(id);\n }\n });\n }\n }\n\n /**\n * Update time of a last run for the Cronjob specified by ID.\n *\n * @param jobId - ID of a cron job.\n * @param lastRun - Unix timestamp when the job was last ran.\n */\n private updateJobLastRunState(jobId: string, lastRun: number) {\n this.update((state) => {\n state.jobs[jobId] = {\n lastRun,\n };\n });\n }\n\n /**\n * Runs every 24 hours to check if new jobs need to be scheduled.\n *\n * This is necesary for longer running jobs that execute with more than 24 hours between them.\n */\n async dailyCheckIn() {\n const jobs = this.getAllJobs();\n\n for (const job of jobs) {\n const parsed = parseCronExpression(job.expression);\n const lastRun = this.state.jobs[job.id]?.lastRun;\n // If a job was supposed to run while we were shut down but wasn't we run it now\n if (\n lastRun !== undefined &&\n parsed.hasPrev() &&\n parsed.prev().getTime() > lastRun\n ) {\n await this.executeCronjob(job);\n }\n\n // Try scheduling, will fail if an existing scheduled job is found\n this.schedule(job);\n }\n\n this.#dailyTimer = new Timer(DAILY_TIMEOUT);\n this.#dailyTimer.start(() => {\n this.dailyCheckIn().catch((error) => {\n // TODO: Decide how to handle errors.\n console.error(error);\n });\n });\n }\n\n /**\n * Run controller teardown process and unsubscribe from Snap events.\n */\n destroy() {\n super.destroy();\n\n /* eslint-disable @typescript-eslint/unbound-method */\n this.messagingSystem.unsubscribe(\n 'SnapController:snapInstalled',\n this._handleEventSnapInstalled,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapRemoved',\n this._handleEventSnapRemoved,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapUpdated',\n this._handleEventSnapUpdated,\n );\n /* eslint-enable @typescript-eslint/unbound-method */\n\n this.#snapIds.forEach((snapId) => {\n this.unregister(snapId);\n });\n }\n\n /**\n * Handle cron jobs on 'snapInstalled' event.\n *\n * @param snap - Basic Snap information.\n */\n private _handleEventSnapInstalled(snap: TruncatedSnap) {\n this.register(snap.id);\n }\n\n /**\n * Handle cron jobs on 'snapRemoved' event.\n *\n * @param snap - Basic Snap information.\n */\n private _handleEventSnapRemoved(snap: TruncatedSnap) {\n this.unregister(snap.id);\n }\n\n /**\n * Handle cron jobs on 'snapUpdated' event.\n *\n * @param snap - Basic Snap information.\n */\n private _handleEventSnapUpdated(snap: TruncatedSnap) {\n this.unregister(snap.id);\n this.register(snap.id);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"CronjobController.js","sourceRoot":"","sources":["../../src/cronjob/CronjobController.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BaseControllerV2 as BaseController,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport { GetPermissions } from '@metamask/permission-controller';\nimport {\n HandlerType,\n SnapId,\n TruncatedSnap,\n CronjobSpecification,\n flatten,\n parseCronExpression,\n logError,\n} from '@metamask/snaps-utils';\nimport { Duration, inMilliseconds } from '@metamask/utils';\n\nimport {\n GetAllSnaps,\n getRunnableSnaps,\n HandleSnapRequest,\n SnapEndowments,\n SnapInstalled,\n SnapRemoved,\n SnapUpdated,\n} from '..';\nimport { getCronjobCaveatJobs } from '../snaps/endowments/cronjob';\nimport { Timer } from '../snaps/Timer';\n\nexport type CronjobControllerActions =\n | GetAllSnaps\n | HandleSnapRequest\n | GetPermissions;\n\nexport type CronjobControllerEvents = SnapInstalled | SnapRemoved | SnapUpdated;\n\nexport type CronjobControllerMessenger = RestrictedControllerMessenger<\n 'CronjobController',\n CronjobControllerActions,\n CronjobControllerEvents,\n CronjobControllerActions['type'],\n CronjobControllerEvents['type']\n>;\n\nexport const DAILY_TIMEOUT = inMilliseconds(24, Duration.Hour);\n\nexport type CronjobControllerArgs = {\n messenger: CronjobControllerMessenger;\n /**\n * Persisted state that will be used for rehydration.\n */\n state?: CronjobControllerState;\n};\n\nexport type Cronjob = {\n timer?: Timer;\n id: string;\n snapId: SnapId;\n} & CronjobSpecification;\n\nexport type StoredJobInformation = {\n lastRun: number;\n};\n\nexport type CronjobControllerState = {\n jobs: Record<string, StoredJobInformation>;\n};\n\nconst controllerName = 'CronjobController';\n\n/**\n * Use this controller to register and schedule periodically executed jobs\n * using RPC method hooks.\n */\nexport class CronjobController extends BaseController<\n typeof controllerName,\n CronjobControllerState,\n CronjobControllerMessenger\n> {\n #messenger: CronjobControllerMessenger;\n\n #dailyTimer!: Timer;\n\n #timers: Map<string, Timer>;\n\n // Mapping from jobId to snapId\n #snapIds: Map<string, string>;\n\n constructor({ messenger, state }: CronjobControllerArgs) {\n super({\n messenger,\n metadata: {\n jobs: { persist: true, anonymous: false },\n },\n name: controllerName,\n state: {\n jobs: {},\n ...state,\n },\n });\n this.#timers = new Map();\n this.#snapIds = new Map();\n this.#messenger = messenger;\n\n this._handleEventSnapInstalled = this._handleEventSnapInstalled.bind(this);\n this._handleEventSnapRemoved = this._handleEventSnapRemoved.bind(this);\n this._handleEventSnapUpdated = this._handleEventSnapUpdated.bind(this);\n\n // Subscribe to Snap events\n /* eslint-disable @typescript-eslint/unbound-method */\n this.messagingSystem.subscribe(\n 'SnapController:snapInstalled',\n this._handleEventSnapInstalled,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapRemoved',\n this._handleEventSnapRemoved,\n );\n\n this.messagingSystem.subscribe(\n 'SnapController:snapUpdated',\n this._handleEventSnapUpdated,\n );\n /* eslint-enable @typescript-eslint/unbound-method */\n\n this.dailyCheckIn().catch((error) => {\n logError(error);\n });\n }\n\n /**\n * Retrieve all cronjob specifications for all runnable snaps.\n *\n * @returns Array of Cronjob specifications.\n */\n private getAllJobs(): Cronjob[] {\n const snaps = this.messagingSystem.call('SnapController:getAll');\n const filteredSnaps = getRunnableSnaps(snaps);\n\n const jobs = filteredSnaps.map((snap) => this.getSnapJobs(snap.id));\n return flatten(jobs).filter((job) => job !== undefined) as Cronjob[];\n }\n\n /**\n * Retrieve all Cronjob specifications for a Snap.\n *\n * @param snapId - ID of a Snap.\n * @returns Array of Cronjob specifications.\n */\n private getSnapJobs(snapId: SnapId): Cronjob[] | undefined {\n const permissions = this.#messenger.call(\n 'PermissionController:getPermissions',\n snapId,\n );\n\n const permission = permissions?.[SnapEndowments.Cronjob];\n const definitions = getCronjobCaveatJobs(permission);\n\n return definitions?.map((definition, idx) => {\n return { ...definition, id: `${snapId}-${idx}`, snapId };\n });\n }\n\n /**\n * Register cron jobs for a given snap by getting specification from a permission caveats.\n * Once registered, each job will be scheduled.\n *\n * @param snapId - ID of a snap.\n */\n register(snapId: SnapId) {\n const jobs = this.getSnapJobs(snapId);\n jobs?.forEach((job) => this.schedule(job));\n }\n\n /**\n * Schedule a new job.\n * This will interpret the cron expression and tell the timer to execute the job\n * at the next suitable point in time.\n * Job last run state will be initialized afterwards.\n *\n * Note: Schedule will be skipped if the job's execution time is too far in the future and\n * will be revisited on a daily check.\n *\n * @param job - Cronjob specification.\n */\n private schedule(job: Cronjob) {\n if (this.#timers.has(job.id)) {\n return;\n }\n\n const parsed = parseCronExpression(job.expression);\n const next = parsed.next();\n const now = new Date();\n const ms = next.getTime() - now.getTime();\n\n // Don't schedule this job yet as it is too far in the future\n if (ms > DAILY_TIMEOUT) {\n return;\n }\n\n const timer = new Timer(ms);\n timer.start(() => {\n this.executeCronjob(job).catch((error) => {\n // TODO: Decide how to handle errors.\n logError(error);\n });\n\n this.#timers.delete(job.id);\n this.schedule(job);\n });\n\n this.updateJobLastRunState(job.id, 0); // 0 for init, never ran actually\n this.#timers.set(job.id, timer);\n this.#snapIds.set(job.id, job.snapId);\n }\n\n /**\n * Execute job.\n *\n * @param job - Cronjob specification.\n */\n private async executeCronjob(job: Cronjob) {\n this.updateJobLastRunState(job.id, Date.now());\n await this.#messenger.call('SnapController:handleRequest', {\n snapId: job.snapId,\n origin: '',\n handler: HandlerType.OnCronjob,\n request: job.request,\n });\n }\n\n /**\n * Unregister all jobs related to the given snapId.\n *\n * @param snapId - ID of a snap.\n */\n unregister(snapId: SnapId) {\n const jobs = [...this.#snapIds.entries()].filter(\n ([_, jobSnapId]) => jobSnapId === snapId,\n );\n\n if (jobs.length) {\n jobs.forEach(([id]) => {\n const timer = this.#timers.get(id);\n if (timer) {\n timer.cancel();\n this.#timers.delete(id);\n this.#snapIds.delete(id);\n }\n });\n }\n }\n\n /**\n * Update time of a last run for the Cronjob specified by ID.\n *\n * @param jobId - ID of a cron job.\n * @param lastRun - Unix timestamp when the job was last ran.\n */\n private updateJobLastRunState(jobId: string, lastRun: number) {\n this.update((state) => {\n state.jobs[jobId] = {\n lastRun,\n };\n });\n }\n\n /**\n * Runs every 24 hours to check if new jobs need to be scheduled.\n *\n * This is necesary for longer running jobs that execute with more than 24 hours between them.\n */\n async dailyCheckIn() {\n const jobs = this.getAllJobs();\n\n for (const job of jobs) {\n const parsed = parseCronExpression(job.expression);\n const lastRun = this.state.jobs[job.id]?.lastRun;\n // If a job was supposed to run while we were shut down but wasn't we run it now\n if (\n lastRun !== undefined &&\n parsed.hasPrev() &&\n parsed.prev().getTime() > lastRun\n ) {\n await this.executeCronjob(job);\n }\n\n // Try scheduling, will fail if an existing scheduled job is found\n this.schedule(job);\n }\n\n this.#dailyTimer = new Timer(DAILY_TIMEOUT);\n this.#dailyTimer.start(() => {\n this.dailyCheckIn().catch((error) => {\n // TODO: Decide how to handle errors.\n logError(error);\n });\n });\n }\n\n /**\n * Run controller teardown process and unsubscribe from Snap events.\n */\n destroy() {\n super.destroy();\n\n /* eslint-disable @typescript-eslint/unbound-method */\n this.messagingSystem.unsubscribe(\n 'SnapController:snapInstalled',\n this._handleEventSnapInstalled,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapRemoved',\n this._handleEventSnapRemoved,\n );\n\n this.messagingSystem.unsubscribe(\n 'SnapController:snapUpdated',\n this._handleEventSnapUpdated,\n );\n /* eslint-enable @typescript-eslint/unbound-method */\n\n this.#snapIds.forEach((snapId) => {\n this.unregister(snapId);\n });\n }\n\n /**\n * Handle cron jobs on 'snapInstalled' event.\n *\n * @param snap - Basic Snap information.\n */\n private _handleEventSnapInstalled(snap: TruncatedSnap) {\n this.register(snap.id);\n }\n\n /**\n * Handle cron jobs on 'snapRemoved' event.\n *\n * @param snap - Basic Snap information.\n */\n private _handleEventSnapRemoved(snap: TruncatedSnap) {\n this.unregister(snap.id);\n }\n\n /**\n * Handle cron jobs on 'snapUpdated' event.\n *\n * @param snap - Basic Snap information.\n */\n private _handleEventSnapUpdated(snap: TruncatedSnap) {\n this.unregister(snap.id);\n this.register(snap.id);\n }\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="debug" />
|
|
2
|
+
/**
|
|
3
|
+
* A logging function specific to this package. The log messages don't show up
|
|
4
|
+
* by default, but they can be enabled by setting the environment variable:
|
|
5
|
+
* - `DEBUG=metamask:snaps:snaps-controllers`, or
|
|
6
|
+
* - `DEBUG=metamask:snaps:*` to enable all logs from `@metamask/snaps-*`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const log: import("debug").Debugger;
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.log = void 0;
|
|
4
|
+
const snaps_utils_1 = require("@metamask/snaps-utils");
|
|
5
|
+
const utils_1 = require("@metamask/utils");
|
|
6
|
+
/**
|
|
7
|
+
* A logging function specific to this package. The log messages don't show up
|
|
8
|
+
* by default, but they can be enabled by setting the environment variable:
|
|
9
|
+
* - `DEBUG=metamask:snaps:snaps-controllers`, or
|
|
10
|
+
* - `DEBUG=metamask:snaps:*` to enable all logs from `@metamask/snaps-*`.
|
|
11
|
+
*/
|
|
12
|
+
exports.log = (0, utils_1.createModuleLogger)(snaps_utils_1.snapsLogger, 'snaps-controllers');
|
|
13
|
+
//# sourceMappingURL=logging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":";;;AAAA,uDAAoD;AACpD,2CAAqD;AAErD;;;;;GAKG;AACU,QAAA,GAAG,GAAG,IAAA,0BAAkB,EAAC,yBAAW,EAAE,mBAAmB,CAAC,CAAC","sourcesContent":["import { snapsLogger } from '@metamask/snaps-utils';\nimport { createModuleLogger } from '@metamask/utils';\n\n/**\n * A logging function specific to this package. The log messages don't show up\n * by default, but they can be enabled by setting the environment variable:\n * - `DEBUG=metamask:snaps:snaps-controllers`, or\n * - `DEBUG=metamask:snaps:*` to enable all logs from `@metamask/snaps-*`.\n */\nexport const log = createModuleLogger(snapsLogger, 'snaps-controllers');\n"]}
|
|
@@ -14,6 +14,7 @@ var _MultiChainController_notify;
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.MultiChainController = void 0;
|
|
16
16
|
const base_controller_1 = require("@metamask/base-controller");
|
|
17
|
+
const rpc_methods_1 = require("@metamask/rpc-methods");
|
|
17
18
|
const snaps_utils_1 = require("@metamask/snaps-utils");
|
|
18
19
|
const utils_1 = require("@metamask/utils");
|
|
19
20
|
const nanoid_1 = require("nanoid");
|
|
@@ -96,11 +97,10 @@ class MultiChainController extends base_controller_1.BaseControllerV2 {
|
|
|
96
97
|
// The magical matching algorithm specified in SIP-2.
|
|
97
98
|
const namespaceToSnaps = (0, matching_1.findMatchingKeyringSnaps)(connection.requiredNamespaces, availableNamespaces);
|
|
98
99
|
const permissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
|
|
100
|
+
(0, utils_1.assert)(permissions !== undefined, `${origin} does not have any permissions.`);
|
|
99
101
|
// Find namespaces that can be satisfied with existing approved Snaps.
|
|
100
102
|
const approvedNamespacesAndSnaps = Object.entries(namespaceToSnaps).reduce((acc, [namespace, snapIds]) => {
|
|
101
|
-
const approvedSnaps = snapIds.filter((snapId) =>
|
|
102
|
-
return (permissions && (0, utils_1.hasProperty)(permissions, (0, snaps_utils_1.getSnapPermissionName)(snapId)));
|
|
103
|
-
});
|
|
103
|
+
const approvedSnaps = snapIds.filter((snapId) => (0, snaps_utils_1.isSnapPermitted)(permissions, snapId));
|
|
104
104
|
if (approvedSnaps.length > 0) {
|
|
105
105
|
acc[namespace] = approvedSnaps;
|
|
106
106
|
}
|
|
@@ -190,13 +190,12 @@ class MultiChainController extends base_controller_1.BaseControllerV2 {
|
|
|
190
190
|
(0, utils_1.assert)((_b = sessionNamespace === null || sessionNamespace === void 0 ? void 0 : sessionNamespace.methods) === null || _b === void 0 ? void 0 : _b.includes(method), `Session for "${origin}" does not support ${method}`);
|
|
191
191
|
const snapId = session.handlingSnaps[namespace];
|
|
192
192
|
(0, utils_1.assert)(snapId !== undefined);
|
|
193
|
-
const permissionName = (0, snaps_utils_1.getSnapPermissionName)(snapId);
|
|
194
|
-
// Check if origin has permission to communicate with this Snap.
|
|
195
|
-
const hasPermission = this.messagingSystem.call('PermissionController:hasPermission', origin, permissionName);
|
|
196
193
|
// TODO: Get permission for origin connecting to snap, or get user approval.
|
|
197
194
|
// In the future this is where we should prompt for this permission.
|
|
198
195
|
// In this iteration, we will grant this permission in `onConnect`.
|
|
199
|
-
|
|
196
|
+
const permissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
|
|
197
|
+
(0, utils_1.assert)(permissions !== undefined, `${origin} does not have any permissions.`);
|
|
198
|
+
(0, utils_1.assert)((0, snaps_utils_1.isSnapPermitted)(permissions, snapId), `${origin} does not have permission to communicate with ${snapId}.`);
|
|
200
199
|
return this.snapRequest({
|
|
201
200
|
snapId,
|
|
202
201
|
origin,
|
|
@@ -247,7 +246,7 @@ class MultiChainController extends base_controller_1.BaseControllerV2 {
|
|
|
247
246
|
}
|
|
248
247
|
catch (error) {
|
|
249
248
|
// Ignore errors for now
|
|
250
|
-
|
|
249
|
+
(0, snaps_utils_1.logError)(error);
|
|
251
250
|
}
|
|
252
251
|
return null;
|
|
253
252
|
}
|
|
@@ -330,12 +329,12 @@ class MultiChainController extends base_controller_1.BaseControllerV2 {
|
|
|
330
329
|
// Instead we should give origin only a read-only access to list of accounts
|
|
331
330
|
// without allowing provider.request() talking to a snap before additional
|
|
332
331
|
// user approval. The additional approval would be requested in `onRequest`.
|
|
333
|
-
const approvedPermissions = Object.values(resolvedAccounts).reduce((acc,
|
|
334
|
-
if (
|
|
335
|
-
acc[
|
|
332
|
+
const approvedPermissions = Object.values(resolvedAccounts).reduce((acc, curr) => {
|
|
333
|
+
if (curr !== null) {
|
|
334
|
+
acc[rpc_methods_1.WALLET_SNAP_PERMISSION_KEY][curr.snapId] = {};
|
|
336
335
|
}
|
|
337
336
|
return acc;
|
|
338
|
-
}, {});
|
|
337
|
+
}, { [rpc_methods_1.WALLET_SNAP_PERMISSION_KEY]: {} });
|
|
339
338
|
this.messagingSystem.call('PermissionController:grantPermissions', {
|
|
340
339
|
approvedPermissions,
|
|
341
340
|
subject: { origin },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiChainController.js","sourceRoot":"","sources":["../../src/multichain/MultiChainController.ts"],"names":[],"mappings":"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","sourcesContent":["import { AddApprovalRequest } from '@metamask/approval-controller';\nimport {\n BaseControllerV2 as BaseController,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport {\n GetPermissions,\n GrantPermissions,\n HasPermission,\n PermissionConstraint,\n} from '@metamask/permission-controller';\nimport {\n SnapKeyring,\n parseAccountId,\n AccountId,\n parseChainId,\n ChainId,\n ConnectArguments,\n HandlerType,\n NamespaceId,\n RequestArguments,\n RequestNamespace,\n Session,\n TruncatedSnap,\n SnapId,\n fromEntries,\n SessionNamespace,\n flatten,\n getSnapPermissionName,\n isAccountIdArray,\n Namespaces,\n} from '@metamask/snaps-utils';\nimport { hasProperty, assert } from '@metamask/utils';\nimport { nanoid } from 'nanoid';\n\nimport {\n GetAllSnaps,\n HandleSnapRequest,\n IncrementActiveReferences,\n DecrementActiveReferences,\n SnapEndowments,\n getRunnableSnaps,\n} from '../snaps';\nimport { getKeyringCaveatNamespaces } from '../snaps/endowments/keyring';\nimport { findMatchingKeyringSnaps } from './matching';\n\nconst controllerName = 'MultiChainController';\n\nconst defaultState: MultiChainControllerState = {\n sessions: {},\n};\n\ntype AllowedActions =\n | GetAllSnaps\n | IncrementActiveReferences\n | DecrementActiveReferences\n | HandleSnapRequest\n | GetPermissions\n | HasPermission\n | AddApprovalRequest\n | GrantPermissions;\n\ntype MultiChainControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n AllowedActions,\n never,\n AllowedActions['type'],\n never\n>;\n\ntype SessionData = {\n origin: string;\n requestedNamespaces: Record<NamespaceId, RequestNamespace>;\n providedNamespaces: Record<NamespaceId, RequestNamespace>;\n handlingSnaps: Record<NamespaceId, SnapId>;\n};\n\ntype MultiChainControllerState = {\n sessions: { [origin: string]: SessionData };\n};\n\ntype Notify = (\n origin: string,\n data: { method: string; params?: Record<string, unknown> },\n) => Promise<void>;\n\ntype MultiChainControllerArgs = {\n notify: Notify;\n messenger: MultiChainControllerMessenger;\n};\n\n// TODO(ritave): Support for legacy ethereum operations, not just snaps\nexport class MultiChainController extends BaseController<\n typeof controllerName,\n MultiChainControllerState,\n MultiChainControllerMessenger\n> {\n readonly #notify: Notify;\n\n /**\n * Construct a new {@link MultiChainController} instance.\n *\n * @param args - The arguments to construct the controller with.\n * @param args.messenger - The controller messenger to use.\n * @param args.notify - A function that should handle JSON-RPC notifications.\n */\n constructor({ messenger, notify }: MultiChainControllerArgs) {\n super({\n messenger,\n metadata: {\n sessions: { persist: false, anonymous: false },\n },\n name: controllerName,\n state: defaultState,\n });\n\n this.#notify = notify;\n }\n\n /**\n * Get an open session for the given origin.\n *\n * @param origin - The origin to get the session for.\n * @returns The session, if it exists, or `undefined` otherwise.\n */\n getSession(origin: string): SessionData | undefined {\n return this.state.sessions[origin];\n }\n\n /**\n * Close a session for the given origin.\n *\n * @param origin - The origin to close the session for.\n * @throws If the session does not exist.\n */\n async closeSession(origin: string): Promise<void> {\n const session = this.getSession(origin);\n assert(session, 'No session to close.');\n\n await this.#notify(origin, {\n method: 'multichainHack_metamask_disconnect',\n });\n\n this.update((state) => {\n delete state.sessions[origin];\n });\n\n await Promise.all(\n Object.values(session.handlingSnaps).map((snapId) =>\n this.messagingSystem.call(\n 'SnapController:decrementActiveReferences',\n snapId,\n ),\n ),\n );\n }\n\n /**\n * Handles a new connection from the given origin. This will create a new\n * session, and close any existing session for the origin.\n *\n * @param origin - The origin to create the session for.\n * @param connection - The connection arguments.\n * @param connection.requiredNamespaces - The namespaces that the origin\n * requires.\n * @returns The session that was created.\n */\n async onConnect(\n origin: string,\n connection: ConnectArguments,\n ): Promise<Session> {\n const existingSession = this.getSession(origin);\n if (existingSession) {\n await this.closeSession(origin);\n }\n\n const snaps = this.messagingSystem.call('SnapController:getAll');\n const filteredSnaps = getRunnableSnaps(snaps);\n\n // Get available namespaces supported by currently installed Snaps.\n const availableNamespaces = fromEntries(\n await Promise.all(\n filteredSnaps.map(async (snap) => [\n snap.id,\n await this.snapToNamespaces(snap),\n ]),\n ),\n );\n\n // The magical matching algorithm specified in SIP-2.\n const namespaceToSnaps = findMatchingKeyringSnaps(\n connection.requiredNamespaces,\n availableNamespaces,\n );\n\n const permissions = this.messagingSystem.call(\n 'PermissionController:getPermissions',\n origin,\n );\n\n // Find namespaces that can be satisfied with existing approved Snaps.\n const approvedNamespacesAndSnaps = Object.entries(namespaceToSnaps).reduce<\n Record<NamespaceId, SnapId[]>\n >((acc, [namespace, snapIds]) => {\n const approvedSnaps = snapIds.filter((snapId) => {\n return (\n permissions && hasProperty(permissions, getSnapPermissionName(snapId))\n );\n });\n\n if (approvedSnaps.length > 0) {\n acc[namespace] = approvedSnaps;\n }\n return acc;\n }, {});\n\n // If we either don't have a snap to handle a namespace or we have multiple we have conflicts\n const hasConflicts = Object.keys(namespaceToSnaps).some(\n (namespace) =>\n !hasProperty(approvedNamespacesAndSnaps, namespace) ||\n approvedNamespacesAndSnaps[namespace]?.length > 1,\n );\n\n // Use already approved snaps if they satisfy the requested namespaces.\n const filteredNamespacesAndSnaps = hasConflicts\n ? namespaceToSnaps\n : approvedNamespacesAndSnaps;\n\n // Fetch possible accounts from snaps.\n const possibleAccounts = await this.namespacesToAccounts(\n origin,\n filteredNamespacesAndSnaps,\n connection.requiredNamespaces,\n );\n\n // For now we fail here if no namespaces could be matched to a snap.\n // We don't fail if at least one namespace is matched to a snap.\n // TODO: Decide whether this is what we want\n assert(\n Object.values(possibleAccounts).some(\n (possibleAccount) => possibleAccount.length > 0,\n ),\n 'No installed snaps found for any requested namespace.',\n );\n\n // If currently installed Snaps / configuration doesn't solve request, we\n // need to show a prompt. This is handled by `resolveConflicts`.\n const resolvedAccounts = hasConflicts\n ? await this.resolveConflicts(origin, possibleAccounts)\n : fromEntries(\n Object.entries(possibleAccounts).map(\n ([namespace, snapAndAccounts]) => [\n namespace,\n snapAndAccounts[0] ?? null,\n ],\n ),\n );\n\n // Aggregate information about session namespaces.\n const providedNamespaces = Object.entries(\n connection.requiredNamespaces,\n ).reduce<Record<NamespaceId, SessionNamespace>>(\n (acc, [namespaceId, namespace]) => {\n const accounts = resolvedAccounts[namespaceId]?.accounts;\n if (accounts) {\n acc[namespaceId] = {\n accounts,\n chains: namespace.chains,\n events: namespace.events,\n methods: namespace.methods,\n };\n }\n return acc;\n },\n {},\n );\n\n // Collect information about handler Snaps for each namespace.\n const handlingSnaps = Object.entries(resolvedAccounts).reduce<\n Record<NamespaceId, SnapId>\n >((acc, [namespaceId, accountsAndSnap]) => {\n if (accountsAndSnap) {\n acc[namespaceId] = accountsAndSnap.snapId;\n }\n return acc;\n }, {});\n\n const session: SessionData = {\n origin,\n requestedNamespaces: connection.requiredNamespaces,\n providedNamespaces,\n handlingSnaps,\n };\n\n // Makes sure used Snaps aren't killed while they are serving the session.\n await Promise.all(\n Object.values(session.handlingSnaps).map((snapId) =>\n this.messagingSystem.call(\n 'SnapController:incrementActiveReferences',\n snapId,\n ),\n ),\n );\n\n this.update((state) => {\n state.sessions[origin] = session;\n });\n\n return { namespaces: providedNamespaces };\n }\n\n /**\n * Handle an incoming multichain request from the given origin. This will\n * forward the request to the appropriate Snap, and return the response.\n *\n * @param origin - The origin to handle the request for.\n * @param data - The request data.\n * @param data.chainId - The chain ID for the request.\n * @param data.request - The request arguments, i.e., the method and params.\n * @returns The response from the Snap.\n * @throws If the session does not exist, or the session does not provide the\n * requested namespace.\n */\n async onRequest(\n origin: string,\n data: { chainId: ChainId; request: RequestArguments },\n ): Promise<unknown> {\n const session = this.getSession(origin);\n assert(session, `Session for \"${origin}\" doesn't exist.`);\n\n const { namespace } = parseChainId(data.chainId);\n const sessionNamespace = session.providedNamespaces[namespace];\n assert(\n session.providedNamespaces[namespace]?.chains.includes(data.chainId),\n `Session for \"${origin}\" is not connected to \"${data.chainId}\" chain.`,\n );\n\n const { method } = data.request;\n assert(\n sessionNamespace?.methods?.includes(method),\n `Session for \"${origin}\" does not support ${method}`,\n );\n\n const snapId = session.handlingSnaps[namespace];\n assert(snapId !== undefined);\n\n const permissionName = getSnapPermissionName(snapId);\n\n // Check if origin has permission to communicate with this Snap.\n const hasPermission = this.messagingSystem.call(\n 'PermissionController:hasPermission',\n origin,\n permissionName,\n );\n\n // TODO: Get permission for origin connecting to snap, or get user approval.\n // In the future this is where we should prompt for this permission.\n // In this iteration, we will grant this permission in `onConnect`.\n assert(\n hasPermission,\n `${origin} does not have permission to communicate with ${snapId}.`,\n );\n\n return this.snapRequest({\n snapId,\n origin,\n method: 'handleRequest',\n args: data,\n });\n }\n\n /**\n * Send a request to the given Snap. This calls the given method with the\n * given arguments on the keyring class in the given Snap.\n *\n * @param options - The request options.\n * @param options.snapId - The ID of the Snap to send the request to.\n * @param options.origin - The origin of the request.\n * @param options.method - The request method.\n * @param options.args - The request params.\n * @returns The response from the Snap.\n */\n private async snapRequest({\n snapId,\n origin,\n method,\n args,\n }: {\n snapId: SnapId;\n origin: string;\n method: keyof SnapKeyring;\n args?: unknown;\n }) {\n return this.messagingSystem.call('SnapController:handleRequest', {\n snapId,\n origin,\n handler: HandlerType.SnapKeyring,\n request: { method, params: args ? [args] : [] },\n });\n }\n\n /**\n * Get the accounts exposed by the Snap's keyring.\n *\n * This also verifies that the accounts returned by the snap are valid CAIP-10\n * account IDs.\n *\n * @param origin - The origin of the request.\n * @param snapId - The ID of the Snap to get the accounts from.\n * @returns The accounts, or `null` if the Snap does not have any accounts, or\n * the accounts are invalid (i.e., not valid CAIP-10 account IDs).\n */\n private async getSnapAccounts(\n origin: string,\n snapId: SnapId,\n ): Promise<AccountId[] | null> {\n try {\n const result = await this.snapRequest({\n snapId,\n origin,\n method: 'getAccounts',\n });\n\n if (isAccountIdArray(result)) {\n return result;\n }\n } catch (error) {\n // Ignore errors for now\n console.error(error);\n }\n\n return null;\n }\n\n /**\n * Get the namespaces for the given Snap, as described in the Snap's manifest.\n *\n * @param snap - The Snap to get the namespaces for.\n * @returns The namespaces, or `null` if the Snap does not have any\n * namespaces.\n */\n private snapToNamespaces(snap: TruncatedSnap): Namespaces | null {\n const permissions = this.messagingSystem.call(\n 'PermissionController:getPermissions',\n snap.id,\n );\n\n const keyringPermission = permissions?.[SnapEndowments.Keyring];\n return getKeyringCaveatNamespaces(keyringPermission);\n }\n\n /**\n * Maps from an object of namespace IDs and Snap IDs, and an object of\n * namespace IDs and requested namespaces, to an object of namespace IDs and\n * resolved accounts, with the Snap ID providing the accounts.\n *\n * @param origin - The origin of the request.\n * @param namespacesAndSnaps - An object of namespace IDs and Snap IDs\n * providing the namespace.\n * @param requestedNamespaces - An object of namespace IDs and requested\n * namespaces.\n * @returns An object of namespace IDs and resolved accounts, with the Snap ID\n * providing the accounts.\n */\n private async namespacesToAccounts(\n origin: string,\n namespacesAndSnaps: Record<NamespaceId, SnapId[]>,\n requestedNamespaces: Record<NamespaceId, RequestNamespace>,\n ): Promise<Record<NamespaceId, { snapId: SnapId; accounts: AccountId[] }[]>> {\n const dedupedSnaps = [\n ...new Set(flatten(Object.values(namespacesAndSnaps))),\n ] as SnapId[];\n\n const allAccounts = await dedupedSnaps.reduce<\n Promise<Record<string, AccountId[]>>\n >(async (previousPromise, snapId) => {\n const result = await this.getSnapAccounts(origin, snapId);\n const acc = await previousPromise;\n if (result) {\n acc[snapId] = result;\n }\n return acc;\n }, Promise.resolve({}));\n\n return Object.keys(namespacesAndSnaps).reduce<\n Record<NamespaceId, { snapId: SnapId; accounts: AccountId[] }[]>\n >((acc, namespaceId) => {\n const { chains } = requestedNamespaces[namespaceId];\n\n const accountInAnyRequestedChain = (account: AccountId) => {\n const { chainId: parsedChainId } = parseAccountId(account);\n return chains.some((chainId) => chainId === parsedChainId);\n };\n\n const result = Object.entries(allAccounts)\n .map(([snapId, accounts]) => ({\n snapId,\n accounts: accounts.filter(accountInAnyRequestedChain),\n }))\n .filter(({ accounts }) => accounts.length > 0);\n\n acc[namespaceId] = result;\n\n return acc;\n }, {});\n }\n\n /**\n * If multiple Snap IDs are provided for a namespace, this method will\n * determine which Snap ID to use for the namespace, by showing the user a\n * prompt.\n *\n * @param origin - The origin of the request.\n * @param possibleAccounts - An object containing the accounts provided by\n * each Snap ID for each namespace.\n * @returns An object containing the Snap ID to use for each namespace.\n */\n private async resolveConflicts(\n origin: string,\n possibleAccounts: Record<\n NamespaceId,\n { snapId: SnapId; accounts: AccountId[] }[]\n >,\n ): Promise<\n Record<NamespaceId, { snapId: SnapId; accounts: AccountId[] } | null>\n > {\n // Get user approval for connection.\n const id = nanoid();\n const resolvedAccounts = (await this.messagingSystem.call(\n 'ApprovalController:addRequest',\n {\n origin,\n id,\n type: 'multichain_connect',\n requestData: {\n possibleAccounts,\n },\n },\n true,\n )) as Record<NamespaceId, { snapId: SnapId; accounts: AccountId[] } | null>;\n\n // TODO: In the future, use another permission here to not give full\n // permission after handshake.\n // Instead we should give origin only a read-only access to list of accounts\n // without allowing provider.request() talking to a snap before additional\n // user approval. The additional approval would be requested in `onRequest`.\n const approvedPermissions = Object.values(resolvedAccounts).reduce<\n Record<string, Partial<PermissionConstraint>>\n >((acc, cur) => {\n if (cur !== null) {\n acc[getSnapPermissionName(cur.snapId)] = {};\n }\n return acc;\n }, {});\n\n this.messagingSystem.call('PermissionController:grantPermissions', {\n approvedPermissions,\n subject: { origin },\n });\n\n return resolvedAccounts;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"MultiChainController.js","sourceRoot":"","sources":["../../src/multichain/MultiChainController.ts"],"names":[],"mappings":"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","sourcesContent":["import { AddApprovalRequest } from '@metamask/approval-controller';\nimport {\n BaseControllerV2 as BaseController,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport {\n GetPermissions,\n GrantPermissions,\n HasPermission,\n PermissionConstraint,\n} from '@metamask/permission-controller';\nimport { WALLET_SNAP_PERMISSION_KEY } from '@metamask/rpc-methods';\nimport {\n SnapKeyring,\n parseAccountId,\n AccountId,\n parseChainId,\n ChainId,\n ConnectArguments,\n isSnapPermitted,\n HandlerType,\n NamespaceId,\n RequestArguments,\n RequestNamespace,\n Session,\n TruncatedSnap,\n SnapId,\n fromEntries,\n SessionNamespace,\n flatten,\n isAccountIdArray,\n Namespaces,\n logError,\n} from '@metamask/snaps-utils';\nimport { hasProperty, assert } from '@metamask/utils';\nimport { nanoid } from 'nanoid';\n\nimport {\n GetAllSnaps,\n HandleSnapRequest,\n IncrementActiveReferences,\n DecrementActiveReferences,\n SnapEndowments,\n getRunnableSnaps,\n} from '../snaps';\nimport { getKeyringCaveatNamespaces } from '../snaps/endowments/keyring';\nimport { findMatchingKeyringSnaps } from './matching';\n\nconst controllerName = 'MultiChainController';\n\nconst defaultState: MultiChainControllerState = {\n sessions: {},\n};\n\ntype AllowedActions =\n | GetAllSnaps\n | IncrementActiveReferences\n | DecrementActiveReferences\n | HandleSnapRequest\n | GetPermissions\n | HasPermission\n | AddApprovalRequest\n | GrantPermissions;\n\ntype MultiChainControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n AllowedActions,\n never,\n AllowedActions['type'],\n never\n>;\n\ntype SessionData = {\n origin: string;\n requestedNamespaces: Record<NamespaceId, RequestNamespace>;\n providedNamespaces: Record<NamespaceId, RequestNamespace>;\n handlingSnaps: Record<NamespaceId, SnapId>;\n};\n\ntype MultiChainControllerState = {\n sessions: { [origin: string]: SessionData };\n};\n\ntype Notify = (\n origin: string,\n data: { method: string; params?: Record<string, unknown> },\n) => Promise<void>;\n\ntype MultiChainControllerArgs = {\n notify: Notify;\n messenger: MultiChainControllerMessenger;\n};\n\n// TODO(ritave): Support for legacy ethereum operations, not just snaps\nexport class MultiChainController extends BaseController<\n typeof controllerName,\n MultiChainControllerState,\n MultiChainControllerMessenger\n> {\n readonly #notify: Notify;\n\n /**\n * Construct a new {@link MultiChainController} instance.\n *\n * @param args - The arguments to construct the controller with.\n * @param args.messenger - The controller messenger to use.\n * @param args.notify - A function that should handle JSON-RPC notifications.\n */\n constructor({ messenger, notify }: MultiChainControllerArgs) {\n super({\n messenger,\n metadata: {\n sessions: { persist: false, anonymous: false },\n },\n name: controllerName,\n state: defaultState,\n });\n\n this.#notify = notify;\n }\n\n /**\n * Get an open session for the given origin.\n *\n * @param origin - The origin to get the session for.\n * @returns The session, if it exists, or `undefined` otherwise.\n */\n getSession(origin: string): SessionData | undefined {\n return this.state.sessions[origin];\n }\n\n /**\n * Close a session for the given origin.\n *\n * @param origin - The origin to close the session for.\n * @throws If the session does not exist.\n */\n async closeSession(origin: string): Promise<void> {\n const session = this.getSession(origin);\n assert(session, 'No session to close.');\n\n await this.#notify(origin, {\n method: 'multichainHack_metamask_disconnect',\n });\n\n this.update((state) => {\n delete state.sessions[origin];\n });\n\n await Promise.all(\n Object.values(session.handlingSnaps).map((snapId) =>\n this.messagingSystem.call(\n 'SnapController:decrementActiveReferences',\n snapId,\n ),\n ),\n );\n }\n\n /**\n * Handles a new connection from the given origin. This will create a new\n * session, and close any existing session for the origin.\n *\n * @param origin - The origin to create the session for.\n * @param connection - The connection arguments.\n * @param connection.requiredNamespaces - The namespaces that the origin\n * requires.\n * @returns The session that was created.\n */\n async onConnect(\n origin: string,\n connection: ConnectArguments,\n ): Promise<Session> {\n const existingSession = this.getSession(origin);\n if (existingSession) {\n await this.closeSession(origin);\n }\n\n const snaps = this.messagingSystem.call('SnapController:getAll');\n const filteredSnaps = getRunnableSnaps(snaps);\n\n // Get available namespaces supported by currently installed Snaps.\n const availableNamespaces = fromEntries(\n await Promise.all(\n filteredSnaps.map(async (snap) => [\n snap.id,\n await this.snapToNamespaces(snap),\n ]),\n ),\n );\n\n // The magical matching algorithm specified in SIP-2.\n const namespaceToSnaps = findMatchingKeyringSnaps(\n connection.requiredNamespaces,\n availableNamespaces,\n );\n\n const permissions = this.messagingSystem.call(\n 'PermissionController:getPermissions',\n origin,\n );\n\n assert(\n permissions !== undefined,\n `${origin} does not have any permissions.`,\n );\n\n // Find namespaces that can be satisfied with existing approved Snaps.\n const approvedNamespacesAndSnaps = Object.entries(namespaceToSnaps).reduce<\n Record<NamespaceId, SnapId[]>\n >((acc, [namespace, snapIds]) => {\n const approvedSnaps = snapIds.filter((snapId) =>\n isSnapPermitted(permissions, snapId),\n );\n\n if (approvedSnaps.length > 0) {\n acc[namespace] = approvedSnaps;\n }\n return acc;\n }, {});\n\n // If we either don't have a snap to handle a namespace or we have multiple we have conflicts\n const hasConflicts = Object.keys(namespaceToSnaps).some(\n (namespace) =>\n !hasProperty(approvedNamespacesAndSnaps, namespace) ||\n approvedNamespacesAndSnaps[namespace]?.length > 1,\n );\n\n // Use already approved snaps if they satisfy the requested namespaces.\n const filteredNamespacesAndSnaps = hasConflicts\n ? namespaceToSnaps\n : approvedNamespacesAndSnaps;\n\n // Fetch possible accounts from snaps.\n const possibleAccounts = await this.namespacesToAccounts(\n origin,\n filteredNamespacesAndSnaps,\n connection.requiredNamespaces,\n );\n\n // For now we fail here if no namespaces could be matched to a snap.\n // We don't fail if at least one namespace is matched to a snap.\n // TODO: Decide whether this is what we want\n assert(\n Object.values(possibleAccounts).some(\n (possibleAccount) => possibleAccount.length > 0,\n ),\n 'No installed snaps found for any requested namespace.',\n );\n\n // If currently installed Snaps / configuration doesn't solve request, we\n // need to show a prompt. This is handled by `resolveConflicts`.\n const resolvedAccounts = hasConflicts\n ? await this.resolveConflicts(origin, possibleAccounts)\n : fromEntries(\n Object.entries(possibleAccounts).map(\n ([namespace, snapAndAccounts]) => [\n namespace,\n snapAndAccounts[0] ?? null,\n ],\n ),\n );\n\n // Aggregate information about session namespaces.\n const providedNamespaces = Object.entries(\n connection.requiredNamespaces,\n ).reduce<Record<NamespaceId, SessionNamespace>>(\n (acc, [namespaceId, namespace]) => {\n const accounts = resolvedAccounts[namespaceId]?.accounts;\n if (accounts) {\n acc[namespaceId] = {\n accounts,\n chains: namespace.chains,\n events: namespace.events,\n methods: namespace.methods,\n };\n }\n return acc;\n },\n {},\n );\n\n // Collect information about handler Snaps for each namespace.\n const handlingSnaps = Object.entries(resolvedAccounts).reduce<\n Record<NamespaceId, SnapId>\n >((acc, [namespaceId, accountsAndSnap]) => {\n if (accountsAndSnap) {\n acc[namespaceId] = accountsAndSnap.snapId;\n }\n return acc;\n }, {});\n\n const session: SessionData = {\n origin,\n requestedNamespaces: connection.requiredNamespaces,\n providedNamespaces,\n handlingSnaps,\n };\n\n // Makes sure used Snaps aren't killed while they are serving the session.\n await Promise.all(\n Object.values(session.handlingSnaps).map((snapId) =>\n this.messagingSystem.call(\n 'SnapController:incrementActiveReferences',\n snapId,\n ),\n ),\n );\n\n this.update((state) => {\n state.sessions[origin] = session;\n });\n\n return { namespaces: providedNamespaces };\n }\n\n /**\n * Handle an incoming multichain request from the given origin. This will\n * forward the request to the appropriate Snap, and return the response.\n *\n * @param origin - The origin to handle the request for.\n * @param data - The request data.\n * @param data.chainId - The chain ID for the request.\n * @param data.request - The request arguments, i.e., the method and params.\n * @returns The response from the Snap.\n * @throws If the session does not exist, or the session does not provide the\n * requested namespace.\n */\n async onRequest(\n origin: string,\n data: { chainId: ChainId; request: RequestArguments },\n ): Promise<unknown> {\n const session = this.getSession(origin);\n assert(session, `Session for \"${origin}\" doesn't exist.`);\n\n const { namespace } = parseChainId(data.chainId);\n const sessionNamespace = session.providedNamespaces[namespace];\n assert(\n session.providedNamespaces[namespace]?.chains.includes(data.chainId),\n `Session for \"${origin}\" is not connected to \"${data.chainId}\" chain.`,\n );\n\n const { method } = data.request;\n assert(\n sessionNamespace?.methods?.includes(method),\n `Session for \"${origin}\" does not support ${method}`,\n );\n\n const snapId = session.handlingSnaps[namespace];\n assert(snapId !== undefined);\n\n // TODO: Get permission for origin connecting to snap, or get user approval.\n // In the future this is where we should prompt for this permission.\n // In this iteration, we will grant this permission in `onConnect`.\n const permissions = this.messagingSystem.call(\n 'PermissionController:getPermissions',\n origin,\n );\n\n assert(\n permissions !== undefined,\n `${origin} does not have any permissions.`,\n );\n\n assert(\n isSnapPermitted(permissions, snapId),\n `${origin} does not have permission to communicate with ${snapId}.`,\n );\n\n return this.snapRequest({\n snapId,\n origin,\n method: 'handleRequest',\n args: data,\n });\n }\n\n /**\n * Send a request to the given Snap. This calls the given method with the\n * given arguments on the keyring class in the given Snap.\n *\n * @param options - The request options.\n * @param options.snapId - The ID of the Snap to send the request to.\n * @param options.origin - The origin of the request.\n * @param options.method - The request method.\n * @param options.args - The request params.\n * @returns The response from the Snap.\n */\n private async snapRequest({\n snapId,\n origin,\n method,\n args,\n }: {\n snapId: SnapId;\n origin: string;\n method: keyof SnapKeyring;\n args?: unknown;\n }) {\n return this.messagingSystem.call('SnapController:handleRequest', {\n snapId,\n origin,\n handler: HandlerType.SnapKeyring,\n request: { method, params: args ? [args] : [] },\n });\n }\n\n /**\n * Get the accounts exposed by the Snap's keyring.\n *\n * This also verifies that the accounts returned by the snap are valid CAIP-10\n * account IDs.\n *\n * @param origin - The origin of the request.\n * @param snapId - The ID of the Snap to get the accounts from.\n * @returns The accounts, or `null` if the Snap does not have any accounts, or\n * the accounts are invalid (i.e., not valid CAIP-10 account IDs).\n */\n private async getSnapAccounts(\n origin: string,\n snapId: SnapId,\n ): Promise<AccountId[] | null> {\n try {\n const result = await this.snapRequest({\n snapId,\n origin,\n method: 'getAccounts',\n });\n\n if (isAccountIdArray(result)) {\n return result;\n }\n } catch (error) {\n // Ignore errors for now\n logError(error);\n }\n\n return null;\n }\n\n /**\n * Get the namespaces for the given Snap, as described in the Snap's manifest.\n *\n * @param snap - The Snap to get the namespaces for.\n * @returns The namespaces, or `null` if the Snap does not have any\n * namespaces.\n */\n private snapToNamespaces(snap: TruncatedSnap): Namespaces | null {\n const permissions = this.messagingSystem.call(\n 'PermissionController:getPermissions',\n snap.id,\n );\n\n const keyringPermission = permissions?.[SnapEndowments.Keyring];\n return getKeyringCaveatNamespaces(keyringPermission);\n }\n\n /**\n * Maps from an object of namespace IDs and Snap IDs, and an object of\n * namespace IDs and requested namespaces, to an object of namespace IDs and\n * resolved accounts, with the Snap ID providing the accounts.\n *\n * @param origin - The origin of the request.\n * @param namespacesAndSnaps - An object of namespace IDs and Snap IDs\n * providing the namespace.\n * @param requestedNamespaces - An object of namespace IDs and requested\n * namespaces.\n * @returns An object of namespace IDs and resolved accounts, with the Snap ID\n * providing the accounts.\n */\n private async namespacesToAccounts(\n origin: string,\n namespacesAndSnaps: Record<NamespaceId, SnapId[]>,\n requestedNamespaces: Record<NamespaceId, RequestNamespace>,\n ): Promise<Record<NamespaceId, { snapId: SnapId; accounts: AccountId[] }[]>> {\n const dedupedSnaps = [\n ...new Set(flatten(Object.values(namespacesAndSnaps))),\n ] as SnapId[];\n\n const allAccounts = await dedupedSnaps.reduce<\n Promise<Record<string, AccountId[]>>\n >(async (previousPromise, snapId) => {\n const result = await this.getSnapAccounts(origin, snapId);\n const acc = await previousPromise;\n if (result) {\n acc[snapId] = result;\n }\n return acc;\n }, Promise.resolve({}));\n\n return Object.keys(namespacesAndSnaps).reduce<\n Record<NamespaceId, { snapId: SnapId; accounts: AccountId[] }[]>\n >((acc, namespaceId) => {\n const { chains } = requestedNamespaces[namespaceId];\n\n const accountInAnyRequestedChain = (account: AccountId) => {\n const { chainId: parsedChainId } = parseAccountId(account);\n return chains.some((chainId) => chainId === parsedChainId);\n };\n\n const result = Object.entries(allAccounts)\n .map(([snapId, accounts]) => ({\n snapId,\n accounts: accounts.filter(accountInAnyRequestedChain),\n }))\n .filter(({ accounts }) => accounts.length > 0);\n\n acc[namespaceId] = result;\n\n return acc;\n }, {});\n }\n\n /**\n * If multiple Snap IDs are provided for a namespace, this method will\n * determine which Snap ID to use for the namespace, by showing the user a\n * prompt.\n *\n * @param origin - The origin of the request.\n * @param possibleAccounts - An object containing the accounts provided by\n * each Snap ID for each namespace.\n * @returns An object containing the Snap ID to use for each namespace.\n */\n private async resolveConflicts(\n origin: string,\n possibleAccounts: Record<\n NamespaceId,\n { snapId: SnapId; accounts: AccountId[] }[]\n >,\n ): Promise<\n Record<NamespaceId, { snapId: SnapId; accounts: AccountId[] } | null>\n > {\n // Get user approval for connection.\n const id = nanoid();\n const resolvedAccounts = (await this.messagingSystem.call(\n 'ApprovalController:addRequest',\n {\n origin,\n id,\n type: 'multichain_connect',\n requestData: {\n possibleAccounts,\n },\n },\n true,\n )) as Record<NamespaceId, { snapId: SnapId; accounts: AccountId[] } | null>;\n\n // TODO: In the future, use another permission here to not give full\n // permission after handshake.\n // Instead we should give origin only a read-only access to list of accounts\n // without allowing provider.request() talking to a snap before additional\n // user approval. The additional approval would be requested in `onRequest`.\n\n const approvedPermissions = Object.values(resolvedAccounts).reduce<\n Record<string, Record<string, Partial<PermissionConstraint>>>\n >(\n (acc, curr) => {\n if (curr !== null) {\n acc[WALLET_SNAP_PERMISSION_KEY][curr.snapId] = {};\n }\n return acc;\n },\n { [WALLET_SNAP_PERMISSION_KEY]: {} },\n );\n\n this.messagingSystem.call('PermissionController:grantPermissions', {\n approvedPermissions,\n subject: { origin },\n });\n\n return resolvedAccounts;\n }\n}\n"]}
|
|
@@ -23,6 +23,7 @@ const json_rpc_engine_1 = require("json-rpc-engine");
|
|
|
23
23
|
const json_rpc_middleware_stream_1 = require("json-rpc-middleware-stream");
|
|
24
24
|
const nanoid_1 = require("nanoid");
|
|
25
25
|
const pump_1 = __importDefault(require("pump"));
|
|
26
|
+
const logging_1 = require("../logging");
|
|
26
27
|
const utils_2 = require("../utils");
|
|
27
28
|
const controllerName = 'ExecutionService';
|
|
28
29
|
class AbstractExecutionService {
|
|
@@ -78,7 +79,7 @@ class AbstractExecutionService {
|
|
|
78
79
|
// TODO(ritave): It might be doing weird things such as posting a lot of setTimeouts. Add a test to ensure that this behaviour
|
|
79
80
|
// doesn't leak into other workers. Especially important in IframeExecutionEnvironment since they all share the same
|
|
80
81
|
// JS process.
|
|
81
|
-
|
|
82
|
+
(0, snaps_utils_1.logError)(`Job "${jobId}" failed to terminate gracefully.`, result);
|
|
82
83
|
}
|
|
83
84
|
Object.values(jobWrapper.streams).forEach((stream) => {
|
|
84
85
|
try {
|
|
@@ -86,13 +87,13 @@ class AbstractExecutionService {
|
|
|
86
87
|
stream.removeAllListeners();
|
|
87
88
|
}
|
|
88
89
|
catch (error) {
|
|
89
|
-
|
|
90
|
+
(0, snaps_utils_1.logError)('Error while destroying stream', error);
|
|
90
91
|
}
|
|
91
92
|
});
|
|
92
93
|
this.terminateJob(jobWrapper);
|
|
93
94
|
__classPrivateFieldGet(this, _AbstractExecutionService_instances, "m", _AbstractExecutionService_removeSnapAndJobMapping).call(this, jobId);
|
|
94
95
|
this.jobs.delete(jobId);
|
|
95
|
-
|
|
96
|
+
(0, logging_1.log)(`Job "${jobId}" terminated.`);
|
|
96
97
|
}
|
|
97
98
|
/**
|
|
98
99
|
* Initiates a job for a snap.
|
|
@@ -150,11 +151,11 @@ class AbstractExecutionService {
|
|
|
150
151
|
commandStream.removeListener('data', notificationHandler);
|
|
151
152
|
}
|
|
152
153
|
else {
|
|
153
|
-
|
|
154
|
+
(0, snaps_utils_1.logError)(new Error(`Received malformed "${message.method}" command stream notification.`));
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
else {
|
|
157
|
-
|
|
158
|
+
(0, snaps_utils_1.logError)(new Error(`Received unexpected command stream notification "${message.method}".`));
|
|
158
159
|
}
|
|
159
160
|
};
|
|
160
161
|
commandStream.on('data', notificationHandler);
|
|
@@ -237,7 +238,7 @@ class AbstractExecutionService {
|
|
|
237
238
|
if (!job) {
|
|
238
239
|
throw new Error(`Job with id "${jobId}" not found.`);
|
|
239
240
|
}
|
|
240
|
-
|
|
241
|
+
(0, logging_1.log)('Parent: Sending Command', message);
|
|
241
242
|
const response = await job.rpcEngine.handle(message);
|
|
242
243
|
if (response.error) {
|
|
243
244
|
throw new Error(response.error.message);
|
|
@@ -307,8 +308,8 @@ function setupMultiplex(connectionStream, streamName) {
|
|
|
307
308
|
mux, connectionStream, (error) => {
|
|
308
309
|
if (error) {
|
|
309
310
|
streamName
|
|
310
|
-
?
|
|
311
|
-
:
|
|
311
|
+
? (0, snaps_utils_1.logError)(`"${streamName}" stream failure.`, error)
|
|
312
|
+
: (0, snaps_utils_1.logError)(error);
|
|
312
313
|
}
|
|
313
314
|
});
|
|
314
315
|
return mux;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbstractExecutionService.js","sourceRoot":"","sources":["../../src/services/AbstractExecutionService.ts"],"names":[],"mappings":"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","sourcesContent":["import ObjectMultiplex from '@metamask/object-multiplex';\nimport { BasePostMessageStream } from '@metamask/post-message-stream';\nimport {\n SnapRpcHook,\n SnapRpcHookArgs,\n SNAP_STREAM_NAMES,\n} from '@metamask/snaps-utils';\nimport {\n Duration,\n isJsonRpcNotification,\n isObject,\n Json,\n JsonRpcNotification,\n} from '@metamask/utils';\nimport {\n JsonRpcEngine,\n // TODO: Replace with @metamask/utils version after bumping json-rpc-engine\n JsonRpcRequest,\n PendingJsonRpcResponse,\n} from 'json-rpc-engine';\nimport { createStreamMiddleware } from 'json-rpc-middleware-stream';\nimport { nanoid } from 'nanoid';\nimport pump from 'pump';\nimport { Duplex } from 'stream';\n\nimport { hasTimedOut, withTimeout } from '../utils';\nimport {\n ExecutionService,\n ExecutionServiceMessenger,\n SnapErrorJson,\n SnapExecutionData,\n} from './ExecutionService';\n\nconst controllerName = 'ExecutionService';\n\nexport type SetupSnapProvider = (snapId: string, stream: Duplex) => void;\n\nexport type ExecutionServiceArgs = {\n setupSnapProvider: SetupSnapProvider;\n messenger: ExecutionServiceMessenger;\n terminationTimeout?: number;\n};\n\nexport type JobStreams = {\n command: Duplex;\n rpc: Duplex;\n _connection: BasePostMessageStream;\n};\n\nexport type Job<WorkerType> = {\n id: string;\n streams: JobStreams;\n rpcEngine: JsonRpcEngine;\n worker: WorkerType;\n};\n\nexport abstract class AbstractExecutionService<WorkerType>\n implements ExecutionService\n{\n #snapRpcHooks: Map<string, SnapRpcHook>;\n\n // Cannot be hash private yet because of tests.\n protected jobs: Map<string, Job<WorkerType>>;\n\n // Cannot be hash private yet because of tests.\n private readonly setupSnapProvider: SetupSnapProvider;\n\n #snapToJobMap: Map<string, string>;\n\n #jobToSnapMap: Map<string, string>;\n\n #messenger: ExecutionServiceMessenger;\n\n #terminationTimeout: number;\n\n constructor({\n setupSnapProvider,\n messenger,\n terminationTimeout = Duration.Second,\n }: ExecutionServiceArgs) {\n this.#snapRpcHooks = new Map();\n this.jobs = new Map();\n this.setupSnapProvider = setupSnapProvider;\n this.#snapToJobMap = new Map();\n this.#jobToSnapMap = new Map();\n this.#messenger = messenger;\n this.#terminationTimeout = terminationTimeout;\n\n this.registerMessageHandlers();\n }\n\n /**\n * Constructor helper for registering the controller's messaging system\n * actions.\n */\n private registerMessageHandlers(): void {\n this.#messenger.registerActionHandler(\n `${controllerName}:handleRpcRequest`,\n async (snapId: string, options: SnapRpcHookArgs) =>\n this.handleRpcRequest(snapId, options),\n );\n\n this.#messenger.registerActionHandler(\n `${controllerName}:executeSnap`,\n async (snapData: SnapExecutionData) => this.executeSnap(snapData),\n );\n\n this.#messenger.registerActionHandler(\n `${controllerName}:terminateSnap`,\n async (snapId: string) => this.terminateSnap(snapId),\n );\n\n this.#messenger.registerActionHandler(\n `${controllerName}:terminateAllSnaps`,\n async () => this.terminateAllSnaps(),\n );\n }\n\n /**\n * Performs additional necessary work during job termination. **MUST** be\n * implemented by concrete implementations. See\n * {@link AbstractExecutionService.terminate} for details.\n *\n * @param job - The object corresponding to the job to be terminated.\n */\n protected abstract terminateJob(job: Job<WorkerType>): void;\n\n /**\n * Terminates the job with the specified ID and deletes all its associated\n * data. Any subsequent messages targeting the job will fail with an error.\n * Throws an error if the specified job does not exist, or if termination\n * fails unexpectedly.\n *\n * @param jobId - The id of the job to be terminated.\n */\n public async terminate(jobId: string): Promise<void> {\n const jobWrapper = this.jobs.get(jobId);\n if (!jobWrapper) {\n throw new Error(`Job with id \"${jobId}\" not found.`);\n }\n\n // Ping worker and tell it to run teardown, continue with termination if it takes too long\n const result = await withTimeout(\n this.command(jobId, {\n jsonrpc: '2.0',\n method: 'terminate',\n params: [],\n id: nanoid(),\n }),\n this.#terminationTimeout,\n );\n\n if (result === hasTimedOut || result !== 'OK') {\n // We tried to shutdown gracefully but failed. This probably means the Snap is in infinite loop and\n // hogging down the whole JS process.\n // TODO(ritave): It might be doing weird things such as posting a lot of setTimeouts. Add a test to ensure that this behaviour\n // doesn't leak into other workers. Especially important in IframeExecutionEnvironment since they all share the same\n // JS process.\n console.error(`Job \"${jobId}\" failed to terminate gracefully.`, result);\n }\n\n Object.values(jobWrapper.streams).forEach((stream) => {\n try {\n !stream.destroyed && stream.destroy();\n stream.removeAllListeners();\n } catch (error) {\n console.error('Error while destroying stream', error);\n }\n });\n\n this.terminateJob(jobWrapper);\n\n this.#removeSnapAndJobMapping(jobId);\n this.jobs.delete(jobId);\n console.log(`Job \"${jobId}\" terminated.`);\n }\n\n /**\n * Initiates a job for a snap.\n *\n * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout.\n *\n * @returns Information regarding the created job.\n */\n protected async initJob(): Promise<Job<WorkerType>> {\n const jobId = nanoid();\n const { streams, worker } = await this.initStreams(jobId);\n const rpcEngine = new JsonRpcEngine();\n\n const jsonRpcConnection = createStreamMiddleware();\n\n pump(jsonRpcConnection.stream, streams.command, jsonRpcConnection.stream);\n\n rpcEngine.push(jsonRpcConnection.middleware);\n\n const envMetadata = {\n id: jobId,\n streams,\n rpcEngine,\n worker,\n };\n this.jobs.set(jobId, envMetadata);\n\n return envMetadata;\n }\n\n /**\n * Sets up the streams for an initiated job.\n *\n * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout.\n *\n * @param jobId - The id of the job.\n * @returns The streams to communicate with the worker and the worker itself.\n */\n protected async initStreams(\n jobId: string,\n ): Promise<{ streams: JobStreams; worker: WorkerType }> {\n const { worker, stream: envStream } = await this.initEnvStream(jobId);\n // Typecast justification: stream type mismatch\n const mux = setupMultiplex(\n envStream as unknown as Duplex,\n `Job: \"${jobId}\"`,\n );\n\n const commandStream = mux.createStream(SNAP_STREAM_NAMES.COMMAND);\n\n // Handle out-of-band errors, i.e. errors thrown from the snap outside of the req/res cycle.\n // Also keep track of outbound request/responses\n const notificationHandler = (\n message:\n | JsonRpcRequest<unknown>\n | JsonRpcNotification<Json[] | Record<string, Json>>,\n ) => {\n if (!isJsonRpcNotification(message)) {\n return;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const snapId = this.#jobToSnapMap.get(jobId)!;\n if (message.method === 'OutboundRequest') {\n this.#messenger.publish('ExecutionService:outboundRequest', snapId);\n } else if (message.method === 'OutboundResponse') {\n this.#messenger.publish('ExecutionService:outboundResponse', snapId);\n } else if (message.method === 'UnhandledError') {\n if (isObject(message.params) && message.params.error) {\n this.#messenger.publish(\n 'ExecutionService:unhandledError',\n snapId,\n message.params.error as SnapErrorJson,\n );\n commandStream.removeListener('data', notificationHandler);\n } else {\n console.error(\n new Error(\n `Received malformed \"${message.method}\" command stream notification.`,\n ),\n );\n }\n } else {\n console.error(\n new Error(\n `Received unexpected command stream notification \"${message.method}\".`,\n ),\n );\n }\n };\n\n commandStream.on('data', notificationHandler);\n const rpcStream = mux.createStream(SNAP_STREAM_NAMES.JSON_RPC);\n\n // Typecast: stream type mismatch\n return {\n streams: {\n command: commandStream as unknown as Duplex,\n rpc: rpcStream,\n // eslint-disable-next-line @typescript-eslint/naming-convention\n _connection: envStream,\n },\n worker,\n };\n }\n\n /**\n * Abstract function implemented by implementing class that spins up a new worker for a job.\n *\n * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout.\n */\n protected abstract initEnvStream(jobId: string): Promise<{\n worker: WorkerType;\n stream: BasePostMessageStream;\n }>;\n\n /**\n * Terminates the Snap with the specified ID. May throw an error if\n * termination unexpectedly fails, but will not fail if no job for the snap\n * with the specified ID is found.\n *\n * @param snapId - The ID of the snap to terminate.\n */\n async terminateSnap(snapId: string) {\n const jobId = this.#snapToJobMap.get(snapId);\n if (jobId) {\n await this.terminate(jobId);\n }\n }\n\n async terminateAllSnaps() {\n await Promise.all(\n [...this.jobs.keys()].map(async (jobId) => this.terminate(jobId)),\n );\n this.#snapRpcHooks.clear();\n }\n\n /**\n * Gets the RPC request handler for the given snap.\n *\n * @param snapId - The id of the Snap whose message handler to get.\n * @returns The RPC request handler for the snap.\n */\n private getRpcRequestHandler(snapId: string) {\n return this.#snapRpcHooks.get(snapId);\n }\n\n /**\n * Initializes and executes a snap, setting up the communication channels to the snap etc.\n *\n * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout.\n *\n * @param snapData - Data needed for Snap execution.\n * @returns A string `OK` if execution succeeded.\n * @throws If the execution service returns an error.\n */\n async executeSnap(snapData: SnapExecutionData): Promise<string> {\n if (this.#snapToJobMap.has(snapData.snapId)) {\n throw new Error(`Snap \"${snapData.snapId}\" is already being executed.`);\n }\n\n const job = await this.initJob();\n this.#mapSnapAndJob(snapData.snapId, job.id);\n\n // Ping the worker to ensure that it started up\n await this.command(job.id, {\n jsonrpc: '2.0',\n method: 'ping',\n id: nanoid(),\n });\n\n const rpcStream = job.streams.rpc as unknown as Duplex;\n\n this.setupSnapProvider(snapData.snapId, rpcStream);\n\n const result = await this.command(job.id, {\n jsonrpc: '2.0',\n method: 'executeSnap',\n params: snapData,\n id: nanoid(),\n });\n this.#createSnapHooks(snapData.snapId, job.id);\n return result as string;\n }\n\n // Cannot be hash private yet because of tests.\n private async command(\n jobId: string,\n message: JsonRpcRequest<unknown>,\n ): Promise<unknown> {\n if (typeof message !== 'object') {\n throw new Error('Must send object.');\n }\n\n const job = this.jobs.get(jobId);\n if (!job) {\n throw new Error(`Job with id \"${jobId}\" not found.`);\n }\n\n console.log('Parent: Sending Command', message);\n const response: PendingJsonRpcResponse<unknown> =\n await job.rpcEngine.handle(message);\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result;\n }\n\n #removeSnapHooks(snapId: string) {\n this.#snapRpcHooks.delete(snapId);\n }\n\n #createSnapHooks(snapId: string, workerId: string) {\n const rpcHook = async ({ origin, handler, request }: SnapRpcHookArgs) => {\n return await this.command(workerId, {\n id: nanoid(),\n jsonrpc: '2.0',\n method: 'snapRpc',\n params: {\n origin,\n handler,\n request,\n target: snapId,\n },\n });\n };\n\n this.#snapRpcHooks.set(snapId, rpcHook);\n }\n\n /**\n * Gets the job id for a given snap.\n *\n * @param snapId - A given snap id.\n * @returns The ID of the snap's job.\n */\n #getJobForSnap(snapId: string): string | undefined {\n return this.#snapToJobMap.get(snapId);\n }\n\n /**\n * Gets the snap id for a given job.\n *\n * @param jobId - A given job id.\n * @returns The ID of the snap that is running the job.\n */\n #getSnapForJob(jobId: string): string | undefined {\n return this.#jobToSnapMap.get(jobId);\n }\n\n #mapSnapAndJob(snapId: string, jobId: string): void {\n this.#snapToJobMap.set(snapId, jobId);\n this.#jobToSnapMap.set(jobId, snapId);\n }\n\n #removeSnapAndJobMapping(jobId: string): void {\n const snapId = this.#jobToSnapMap.get(jobId);\n if (!snapId) {\n throw new Error(`job: \"${jobId}\" has no mapped snap.`);\n }\n\n this.#jobToSnapMap.delete(jobId);\n this.#snapToJobMap.delete(snapId);\n this.#removeSnapHooks(snapId);\n }\n\n /**\n * Handle RPC request.\n *\n * @param snapId - The ID of the recipient snap.\n * @param options - Bag of options to pass to the RPC handler.\n * @returns Promise that can handle the request.\n */\n public async handleRpcRequest(\n snapId: string,\n options: SnapRpcHookArgs,\n ): Promise<unknown> {\n const rpcRequestHandler = await this.getRpcRequestHandler(snapId);\n\n if (!rpcRequestHandler) {\n throw new Error(\n `Snap execution service returned no RPC handler for running snap \"${snapId}\".`,\n );\n }\n\n return rpcRequestHandler(options);\n }\n}\n\n/**\n * Sets up stream multiplexing for the given stream.\n *\n * @param connectionStream - The stream to mux.\n * @param streamName - The name of the stream, for identification in errors.\n * @returns The multiplexed stream.\n */\nexport function setupMultiplex(\n connectionStream: Duplex,\n streamName: string,\n): ObjectMultiplex {\n const mux = new ObjectMultiplex();\n pump(\n connectionStream,\n // Typecast: stream type mismatch\n mux as unknown as Duplex,\n connectionStream,\n (error) => {\n if (error) {\n streamName\n ? console.error(`\"${streamName}\" stream failure.`, error)\n : console.error(error);\n }\n },\n );\n return mux;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"AbstractExecutionService.js","sourceRoot":"","sources":["../../src/services/AbstractExecutionService.ts"],"names":[],"mappings":"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","sourcesContent":["import ObjectMultiplex from '@metamask/object-multiplex';\nimport { BasePostMessageStream } from '@metamask/post-message-stream';\nimport {\n SnapRpcHook,\n SnapRpcHookArgs,\n SNAP_STREAM_NAMES,\n logError,\n} from '@metamask/snaps-utils';\nimport {\n Duration,\n isJsonRpcNotification,\n isObject,\n Json,\n JsonRpcNotification,\n} from '@metamask/utils';\nimport {\n JsonRpcEngine,\n // TODO: Replace with @metamask/utils version after bumping json-rpc-engine\n JsonRpcRequest,\n PendingJsonRpcResponse,\n} from 'json-rpc-engine';\nimport { createStreamMiddleware } from 'json-rpc-middleware-stream';\nimport { nanoid } from 'nanoid';\nimport pump from 'pump';\nimport { Duplex } from 'stream';\n\nimport { log } from '../logging';\nimport { hasTimedOut, withTimeout } from '../utils';\nimport {\n ExecutionService,\n ExecutionServiceMessenger,\n SnapErrorJson,\n SnapExecutionData,\n} from './ExecutionService';\n\nconst controllerName = 'ExecutionService';\n\nexport type SetupSnapProvider = (snapId: string, stream: Duplex) => void;\n\nexport type ExecutionServiceArgs = {\n setupSnapProvider: SetupSnapProvider;\n messenger: ExecutionServiceMessenger;\n terminationTimeout?: number;\n};\n\nexport type JobStreams = {\n command: Duplex;\n rpc: Duplex;\n _connection: BasePostMessageStream;\n};\n\nexport type Job<WorkerType> = {\n id: string;\n streams: JobStreams;\n rpcEngine: JsonRpcEngine;\n worker: WorkerType;\n};\n\nexport abstract class AbstractExecutionService<WorkerType>\n implements ExecutionService\n{\n #snapRpcHooks: Map<string, SnapRpcHook>;\n\n // Cannot be hash private yet because of tests.\n protected jobs: Map<string, Job<WorkerType>>;\n\n // Cannot be hash private yet because of tests.\n private readonly setupSnapProvider: SetupSnapProvider;\n\n #snapToJobMap: Map<string, string>;\n\n #jobToSnapMap: Map<string, string>;\n\n #messenger: ExecutionServiceMessenger;\n\n #terminationTimeout: number;\n\n constructor({\n setupSnapProvider,\n messenger,\n terminationTimeout = Duration.Second,\n }: ExecutionServiceArgs) {\n this.#snapRpcHooks = new Map();\n this.jobs = new Map();\n this.setupSnapProvider = setupSnapProvider;\n this.#snapToJobMap = new Map();\n this.#jobToSnapMap = new Map();\n this.#messenger = messenger;\n this.#terminationTimeout = terminationTimeout;\n\n this.registerMessageHandlers();\n }\n\n /**\n * Constructor helper for registering the controller's messaging system\n * actions.\n */\n private registerMessageHandlers(): void {\n this.#messenger.registerActionHandler(\n `${controllerName}:handleRpcRequest`,\n async (snapId: string, options: SnapRpcHookArgs) =>\n this.handleRpcRequest(snapId, options),\n );\n\n this.#messenger.registerActionHandler(\n `${controllerName}:executeSnap`,\n async (snapData: SnapExecutionData) => this.executeSnap(snapData),\n );\n\n this.#messenger.registerActionHandler(\n `${controllerName}:terminateSnap`,\n async (snapId: string) => this.terminateSnap(snapId),\n );\n\n this.#messenger.registerActionHandler(\n `${controllerName}:terminateAllSnaps`,\n async () => this.terminateAllSnaps(),\n );\n }\n\n /**\n * Performs additional necessary work during job termination. **MUST** be\n * implemented by concrete implementations. See\n * {@link AbstractExecutionService.terminate} for details.\n *\n * @param job - The object corresponding to the job to be terminated.\n */\n protected abstract terminateJob(job: Job<WorkerType>): void;\n\n /**\n * Terminates the job with the specified ID and deletes all its associated\n * data. Any subsequent messages targeting the job will fail with an error.\n * Throws an error if the specified job does not exist, or if termination\n * fails unexpectedly.\n *\n * @param jobId - The id of the job to be terminated.\n */\n public async terminate(jobId: string): Promise<void> {\n const jobWrapper = this.jobs.get(jobId);\n if (!jobWrapper) {\n throw new Error(`Job with id \"${jobId}\" not found.`);\n }\n\n // Ping worker and tell it to run teardown, continue with termination if it takes too long\n const result = await withTimeout(\n this.command(jobId, {\n jsonrpc: '2.0',\n method: 'terminate',\n params: [],\n id: nanoid(),\n }),\n this.#terminationTimeout,\n );\n\n if (result === hasTimedOut || result !== 'OK') {\n // We tried to shutdown gracefully but failed. This probably means the Snap is in infinite loop and\n // hogging down the whole JS process.\n // TODO(ritave): It might be doing weird things such as posting a lot of setTimeouts. Add a test to ensure that this behaviour\n // doesn't leak into other workers. Especially important in IframeExecutionEnvironment since they all share the same\n // JS process.\n logError(`Job \"${jobId}\" failed to terminate gracefully.`, result);\n }\n\n Object.values(jobWrapper.streams).forEach((stream) => {\n try {\n !stream.destroyed && stream.destroy();\n stream.removeAllListeners();\n } catch (error) {\n logError('Error while destroying stream', error);\n }\n });\n\n this.terminateJob(jobWrapper);\n\n this.#removeSnapAndJobMapping(jobId);\n this.jobs.delete(jobId);\n log(`Job \"${jobId}\" terminated.`);\n }\n\n /**\n * Initiates a job for a snap.\n *\n * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout.\n *\n * @returns Information regarding the created job.\n */\n protected async initJob(): Promise<Job<WorkerType>> {\n const jobId = nanoid();\n const { streams, worker } = await this.initStreams(jobId);\n const rpcEngine = new JsonRpcEngine();\n\n const jsonRpcConnection = createStreamMiddleware();\n\n pump(jsonRpcConnection.stream, streams.command, jsonRpcConnection.stream);\n\n rpcEngine.push(jsonRpcConnection.middleware);\n\n const envMetadata = {\n id: jobId,\n streams,\n rpcEngine,\n worker,\n };\n this.jobs.set(jobId, envMetadata);\n\n return envMetadata;\n }\n\n /**\n * Sets up the streams for an initiated job.\n *\n * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout.\n *\n * @param jobId - The id of the job.\n * @returns The streams to communicate with the worker and the worker itself.\n */\n protected async initStreams(\n jobId: string,\n ): Promise<{ streams: JobStreams; worker: WorkerType }> {\n const { worker, stream: envStream } = await this.initEnvStream(jobId);\n // Typecast justification: stream type mismatch\n const mux = setupMultiplex(\n envStream as unknown as Duplex,\n `Job: \"${jobId}\"`,\n );\n\n const commandStream = mux.createStream(SNAP_STREAM_NAMES.COMMAND);\n\n // Handle out-of-band errors, i.e. errors thrown from the snap outside of the req/res cycle.\n // Also keep track of outbound request/responses\n const notificationHandler = (\n message:\n | JsonRpcRequest<unknown>\n | JsonRpcNotification<Json[] | Record<string, Json>>,\n ) => {\n if (!isJsonRpcNotification(message)) {\n return;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const snapId = this.#jobToSnapMap.get(jobId)!;\n if (message.method === 'OutboundRequest') {\n this.#messenger.publish('ExecutionService:outboundRequest', snapId);\n } else if (message.method === 'OutboundResponse') {\n this.#messenger.publish('ExecutionService:outboundResponse', snapId);\n } else if (message.method === 'UnhandledError') {\n if (isObject(message.params) && message.params.error) {\n this.#messenger.publish(\n 'ExecutionService:unhandledError',\n snapId,\n message.params.error as SnapErrorJson,\n );\n commandStream.removeListener('data', notificationHandler);\n } else {\n logError(\n new Error(\n `Received malformed \"${message.method}\" command stream notification.`,\n ),\n );\n }\n } else {\n logError(\n new Error(\n `Received unexpected command stream notification \"${message.method}\".`,\n ),\n );\n }\n };\n\n commandStream.on('data', notificationHandler);\n const rpcStream = mux.createStream(SNAP_STREAM_NAMES.JSON_RPC);\n\n // Typecast: stream type mismatch\n return {\n streams: {\n command: commandStream as unknown as Duplex,\n rpc: rpcStream,\n // eslint-disable-next-line @typescript-eslint/naming-convention\n _connection: envStream,\n },\n worker,\n };\n }\n\n /**\n * Abstract function implemented by implementing class that spins up a new worker for a job.\n *\n * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout.\n */\n protected abstract initEnvStream(jobId: string): Promise<{\n worker: WorkerType;\n stream: BasePostMessageStream;\n }>;\n\n /**\n * Terminates the Snap with the specified ID. May throw an error if\n * termination unexpectedly fails, but will not fail if no job for the snap\n * with the specified ID is found.\n *\n * @param snapId - The ID of the snap to terminate.\n */\n async terminateSnap(snapId: string) {\n const jobId = this.#snapToJobMap.get(snapId);\n if (jobId) {\n await this.terminate(jobId);\n }\n }\n\n async terminateAllSnaps() {\n await Promise.all(\n [...this.jobs.keys()].map(async (jobId) => this.terminate(jobId)),\n );\n this.#snapRpcHooks.clear();\n }\n\n /**\n * Gets the RPC request handler for the given snap.\n *\n * @param snapId - The id of the Snap whose message handler to get.\n * @returns The RPC request handler for the snap.\n */\n private getRpcRequestHandler(snapId: string) {\n return this.#snapRpcHooks.get(snapId);\n }\n\n /**\n * Initializes and executes a snap, setting up the communication channels to the snap etc.\n *\n * Depending on the execution environment, this may run forever if the Snap fails to start up properly, therefore any call to this function should be wrapped in a timeout.\n *\n * @param snapData - Data needed for Snap execution.\n * @returns A string `OK` if execution succeeded.\n * @throws If the execution service returns an error.\n */\n async executeSnap(snapData: SnapExecutionData): Promise<string> {\n if (this.#snapToJobMap.has(snapData.snapId)) {\n throw new Error(`Snap \"${snapData.snapId}\" is already being executed.`);\n }\n\n const job = await this.initJob();\n this.#mapSnapAndJob(snapData.snapId, job.id);\n\n // Ping the worker to ensure that it started up\n await this.command(job.id, {\n jsonrpc: '2.0',\n method: 'ping',\n id: nanoid(),\n });\n\n const rpcStream = job.streams.rpc as unknown as Duplex;\n\n this.setupSnapProvider(snapData.snapId, rpcStream);\n\n const result = await this.command(job.id, {\n jsonrpc: '2.0',\n method: 'executeSnap',\n params: snapData,\n id: nanoid(),\n });\n this.#createSnapHooks(snapData.snapId, job.id);\n return result as string;\n }\n\n // Cannot be hash private yet because of tests.\n private async command(\n jobId: string,\n message: JsonRpcRequest<unknown>,\n ): Promise<unknown> {\n if (typeof message !== 'object') {\n throw new Error('Must send object.');\n }\n\n const job = this.jobs.get(jobId);\n if (!job) {\n throw new Error(`Job with id \"${jobId}\" not found.`);\n }\n\n log('Parent: Sending Command', message);\n const response: PendingJsonRpcResponse<unknown> =\n await job.rpcEngine.handle(message);\n if (response.error) {\n throw new Error(response.error.message);\n }\n return response.result;\n }\n\n #removeSnapHooks(snapId: string) {\n this.#snapRpcHooks.delete(snapId);\n }\n\n #createSnapHooks(snapId: string, workerId: string) {\n const rpcHook = async ({ origin, handler, request }: SnapRpcHookArgs) => {\n return await this.command(workerId, {\n id: nanoid(),\n jsonrpc: '2.0',\n method: 'snapRpc',\n params: {\n origin,\n handler,\n request,\n target: snapId,\n },\n });\n };\n\n this.#snapRpcHooks.set(snapId, rpcHook);\n }\n\n /**\n * Gets the job id for a given snap.\n *\n * @param snapId - A given snap id.\n * @returns The ID of the snap's job.\n */\n #getJobForSnap(snapId: string): string | undefined {\n return this.#snapToJobMap.get(snapId);\n }\n\n /**\n * Gets the snap id for a given job.\n *\n * @param jobId - A given job id.\n * @returns The ID of the snap that is running the job.\n */\n #getSnapForJob(jobId: string): string | undefined {\n return this.#jobToSnapMap.get(jobId);\n }\n\n #mapSnapAndJob(snapId: string, jobId: string): void {\n this.#snapToJobMap.set(snapId, jobId);\n this.#jobToSnapMap.set(jobId, snapId);\n }\n\n #removeSnapAndJobMapping(jobId: string): void {\n const snapId = this.#jobToSnapMap.get(jobId);\n if (!snapId) {\n throw new Error(`job: \"${jobId}\" has no mapped snap.`);\n }\n\n this.#jobToSnapMap.delete(jobId);\n this.#snapToJobMap.delete(snapId);\n this.#removeSnapHooks(snapId);\n }\n\n /**\n * Handle RPC request.\n *\n * @param snapId - The ID of the recipient snap.\n * @param options - Bag of options to pass to the RPC handler.\n * @returns Promise that can handle the request.\n */\n public async handleRpcRequest(\n snapId: string,\n options: SnapRpcHookArgs,\n ): Promise<unknown> {\n const rpcRequestHandler = await this.getRpcRequestHandler(snapId);\n\n if (!rpcRequestHandler) {\n throw new Error(\n `Snap execution service returned no RPC handler for running snap \"${snapId}\".`,\n );\n }\n\n return rpcRequestHandler(options);\n }\n}\n\n/**\n * Sets up stream multiplexing for the given stream.\n *\n * @param connectionStream - The stream to mux.\n * @param streamName - The name of the stream, for identification in errors.\n * @returns The multiplexed stream.\n */\nexport function setupMultiplex(\n connectionStream: Duplex,\n streamName: string,\n): ObjectMultiplex {\n const mux = new ObjectMultiplex();\n pump(\n connectionStream,\n // Typecast: stream type mismatch\n mux as unknown as Duplex,\n connectionStream,\n (error) => {\n if (error) {\n streamName\n ? logError(`\"${streamName}\" stream failure.`, error)\n : logError(error);\n }\n },\n );\n return mux;\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AddApprovalRequest } from '@metamask/approval-controller';
|
|
2
2
|
import { BaseControllerV2 as BaseController, RestrictedControllerMessenger } from '@metamask/base-controller';
|
|
3
|
-
import { GetEndowments, GetPermissions, GrantPermissions, HasPermission, HasPermissions, RevokeAllPermissions, RevokePermissionForAllSubjects, RevokePermissions } from '@metamask/permission-controller';
|
|
3
|
+
import { GetEndowments, GetPermissions, GetSubjects, GrantPermissions, HasPermission, HasPermissions, RevokeAllPermissions, RevokePermissionForAllSubjects, RevokePermissions, UpdateCaveat } from '@metamask/permission-controller';
|
|
4
4
|
import { BlockReason } from '@metamask/snaps-registry';
|
|
5
5
|
import { InstallSnapsResult, PersistedSnap, RequestedSnapPermissions, Snap, SnapId, SnapRpcHook, SnapRpcHookArgs, SnapStatusEvents, StatusContext, StatusEvents, StatusStates, TruncatedSnap, ValidatedSnapId } from '@metamask/snaps-utils';
|
|
6
6
|
import { GetSubjectMetadata } from '@metamask/subject-metadata-controller';
|
|
@@ -9,7 +9,7 @@ import { StateMachine } from '@xstate/fsm';
|
|
|
9
9
|
import type { Patch } from 'immer';
|
|
10
10
|
import { ExecuteSnapAction, ExecutionServiceEvents, HandleRpcRequestAction, SnapErrorJson, TerminateAllSnapsAction, TerminateSnapAction } from '../services';
|
|
11
11
|
import { detectSnapLocation, SnapLocation } from './location';
|
|
12
|
-
import { SnapsRegistry } from './registry';
|
|
12
|
+
import { SnapsRegistry, SnapsRegistryMetadata } from './registry';
|
|
13
13
|
import { Timer } from './Timer';
|
|
14
14
|
export declare const controllerName = "SnapController";
|
|
15
15
|
export declare const SNAP_APPROVAL_INSTALL = "wallet_installSnap";
|
|
@@ -170,7 +170,11 @@ export declare type RemoveSnapError = {
|
|
|
170
170
|
type: `${typeof controllerName}:removeSnapError`;
|
|
171
171
|
handler: SnapController['removeSnapError'];
|
|
172
172
|
};
|
|
173
|
-
export declare type
|
|
173
|
+
export declare type GetRegistryMetadata = {
|
|
174
|
+
type: `${typeof controllerName}:getRegistryMetadata`;
|
|
175
|
+
handler: SnapController['getRegistryMetadata'];
|
|
176
|
+
};
|
|
177
|
+
export declare type SnapControllerActions = ClearSnapState | GetSnap | GetSnapState | HandleSnapRequest | HasSnap | UpdateBlockedSnaps | UpdateSnapState | EnableSnap | DisableSnap | RemoveSnap | GetPermittedSnaps | InstallSnaps | RemoveSnapError | GetAllSnaps | IncrementActiveReferences | DecrementActiveReferences | GetRegistryMetadata;
|
|
174
178
|
export declare type SnapStateChange = {
|
|
175
179
|
type: `${typeof controllerName}:stateChange`;
|
|
176
180
|
payload: [SnapControllerState, Patch[]];
|
|
@@ -234,7 +238,7 @@ export declare type SnapTerminated = {
|
|
|
234
238
|
payload: [snap: TruncatedSnap];
|
|
235
239
|
};
|
|
236
240
|
export declare type SnapControllerEvents = SnapAdded | SnapBlocked | SnapInstalled | SnapRemoved | SnapStateChange | SnapUnblocked | SnapUpdated | SnapRolledback | SnapTerminated;
|
|
237
|
-
export declare type AllowedActions = GetEndowments | GetPermissions | GetSubjectMetadata | HasPermission | HasPermissions | RevokePermissions | RevokeAllPermissions | RevokePermissionForAllSubjects | GrantPermissions | AddApprovalRequest | HandleRpcRequestAction | ExecuteSnapAction | TerminateAllSnapsAction | TerminateSnapAction;
|
|
241
|
+
export declare type AllowedActions = GetEndowments | GetPermissions | GetSubjects | GetSubjectMetadata | HasPermission | HasPermissions | RevokePermissions | RevokeAllPermissions | RevokePermissionForAllSubjects | GrantPermissions | AddApprovalRequest | HandleRpcRequestAction | ExecuteSnapAction | TerminateAllSnapsAction | TerminateSnapAction | UpdateCaveat;
|
|
238
242
|
export declare type AllowedEvents = ExecutionServiceEvents;
|
|
239
243
|
declare type SnapControllerMessenger = RestrictedControllerMessenger<typeof controllerName, SnapControllerActions | AllowedActions, SnapControllerEvents | AllowedEvents, AllowedActions['type'], AllowedEvents['type']>;
|
|
240
244
|
declare type FeatureFlags = {
|
|
@@ -536,6 +540,14 @@ export declare class SnapController extends BaseController<string, SnapControlle
|
|
|
536
540
|
* @returns The snap metadata if updated, `null` otherwise.
|
|
537
541
|
*/
|
|
538
542
|
updateSnap(origin: string, snapId: ValidatedSnapId, location: SnapLocation, newVersionRange?: string): Promise<TruncatedSnap | null>;
|
|
543
|
+
/**
|
|
544
|
+
* Get metadata for the given snap ID.
|
|
545
|
+
*
|
|
546
|
+
* @param snapId - The ID of the snap to get metadata for.
|
|
547
|
+
* @returns The metadata for the given snap ID, or `null` if the snap is not
|
|
548
|
+
* verified.
|
|
549
|
+
*/
|
|
550
|
+
getRegistryMetadata(snapId: SnapId): Promise<SnapsRegistryMetadata | null>;
|
|
539
551
|
/**
|
|
540
552
|
* Initiates a request for the given snap's initial permissions.
|
|
541
553
|
* Must be called in order. See processRequestedSnap.
|