@metamask/snaps-controllers 0.37.2-flask.1 → 0.38.0-flask.1
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/CHANGELOG.md +10 -1
- package/dist/cjs/services/AbstractExecutionService.js +1 -19
- package/dist/cjs/services/AbstractExecutionService.js.map +1 -1
- package/dist/cjs/snaps/SnapController.js +66 -54
- package/dist/cjs/snaps/SnapController.js.map +1 -1
- package/dist/cjs/snaps/endowments/enum.js +1 -0
- package/dist/cjs/snaps/endowments/enum.js.map +1 -1
- package/dist/cjs/snaps/endowments/index.js +6 -2
- package/dist/cjs/snaps/endowments/index.js.map +1 -1
- package/dist/cjs/snaps/endowments/lifecycle-hooks.js +37 -0
- package/dist/cjs/snaps/endowments/lifecycle-hooks.js.map +1 -0
- package/dist/cjs/snaps/registry/json.js +6 -5
- package/dist/cjs/snaps/registry/json.js.map +1 -1
- package/dist/cjs/snaps/registry/registry.js.map +1 -1
- package/dist/esm/services/AbstractExecutionService.js +1 -19
- package/dist/esm/services/AbstractExecutionService.js.map +1 -1
- package/dist/esm/snaps/SnapController.js +67 -55
- package/dist/esm/snaps/SnapController.js.map +1 -1
- package/dist/esm/snaps/endowments/enum.js +1 -0
- package/dist/esm/snaps/endowments/enum.js.map +1 -1
- package/dist/esm/snaps/endowments/index.js +6 -2
- package/dist/esm/snaps/endowments/index.js.map +1 -1
- package/dist/esm/snaps/endowments/lifecycle-hooks.js +27 -0
- package/dist/esm/snaps/endowments/lifecycle-hooks.js.map +1 -0
- package/dist/esm/snaps/registry/json.js +6 -5
- package/dist/esm/snaps/registry/json.js.map +1 -1
- package/dist/esm/snaps/registry/registry.js.map +1 -1
- package/dist/types/snaps/SnapController.d.ts +5 -14
- package/dist/types/snaps/endowments/enum.d.ts +2 -1
- package/dist/types/snaps/endowments/index.d.ts +9 -0
- package/dist/types/snaps/endowments/lifecycle-hooks.d.ts +15 -0
- package/dist/types/snaps/registry/json.d.ts +5 -1
- package/dist/types/snaps/registry/registry.d.ts +1 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,11 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.38.0-flask.1]
|
|
10
|
+
### Added
|
|
11
|
+
- Add `onInstall` and `onUpdate` lifecycle hooks ([#1643](https://github.com/MetaMask/snaps/pull/1643))
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Make `updateBlockedSnaps` update the registry ([#1625](https://github.com/MetaMask/snaps/pull/1625))
|
|
15
|
+
- Move source code and snap state back to controller state ([#1634](https://github.com/MetaMask/snaps/pull/1634))
|
|
16
|
+
|
|
9
17
|
## [0.37.2-flask.1]
|
|
10
18
|
### Changed
|
|
11
19
|
- Release package independently ([#1600](https://github.com/MetaMask/snaps/pull/1600))
|
|
12
20
|
- The version of the package no longer needs to match the version of all other
|
|
13
21
|
MetaMask Snaps packages.
|
|
14
22
|
|
|
15
|
-
[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@0.
|
|
23
|
+
[Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@0.38.0-flask.1...HEAD
|
|
24
|
+
[0.38.0-flask.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@0.37.2-flask.1...@metamask/snaps-controllers@0.38.0-flask.1
|
|
16
25
|
[0.37.2-flask.1]: https://github.com/MetaMask/snaps/releases/tag/@metamask/snaps-controllers@0.37.2-flask.1
|
|
@@ -94,17 +94,7 @@ function _interop_require_default(obj) {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
const controllerName = 'ExecutionService';
|
|
97
|
-
var _snapRpcHooks = /*#__PURE__*/ new WeakMap(), _snapToJobMap = /*#__PURE__*/ new WeakMap(), _jobToSnapMap = /*#__PURE__*/ new WeakMap(), _messenger = /*#__PURE__*/ new WeakMap(), _terminationTimeout = /*#__PURE__*/ new WeakMap(), _removeSnapHooks = /*#__PURE__*/ new WeakSet(), _createSnapHooks = /*#__PURE__*/ new WeakSet(),
|
|
98
|
-
* Gets the job id for a given snap.
|
|
99
|
-
*
|
|
100
|
-
* @param snapId - A given snap id.
|
|
101
|
-
* @returns The ID of the snap's job.
|
|
102
|
-
*/ _getJobForSnap = /*#__PURE__*/ new WeakSet(), /**
|
|
103
|
-
* Gets the snap id for a given job.
|
|
104
|
-
*
|
|
105
|
-
* @param jobId - A given job id.
|
|
106
|
-
* @returns The ID of the snap that is running the job.
|
|
107
|
-
*/ _getSnapForJob = /*#__PURE__*/ new WeakSet(), _mapSnapAndJob = /*#__PURE__*/ new WeakSet(), _removeSnapAndJobMapping = /*#__PURE__*/ new WeakSet();
|
|
97
|
+
var _snapRpcHooks = /*#__PURE__*/ new WeakMap(), _snapToJobMap = /*#__PURE__*/ new WeakMap(), _jobToSnapMap = /*#__PURE__*/ new WeakMap(), _messenger = /*#__PURE__*/ new WeakMap(), _terminationTimeout = /*#__PURE__*/ new WeakMap(), _removeSnapHooks = /*#__PURE__*/ new WeakSet(), _createSnapHooks = /*#__PURE__*/ new WeakSet(), _mapSnapAndJob = /*#__PURE__*/ new WeakSet(), _removeSnapAndJobMapping = /*#__PURE__*/ new WeakSet();
|
|
108
98
|
class AbstractExecutionService {
|
|
109
99
|
/**
|
|
110
100
|
* Constructor helper for registering the controller's messaging system
|
|
@@ -314,8 +304,6 @@ class AbstractExecutionService {
|
|
|
314
304
|
constructor({ setupSnapProvider, messenger, terminationTimeout = _utils.Duration.Second }){
|
|
315
305
|
_class_private_method_init(this, _removeSnapHooks);
|
|
316
306
|
_class_private_method_init(this, _createSnapHooks);
|
|
317
|
-
_class_private_method_init(this, _getJobForSnap);
|
|
318
|
-
_class_private_method_init(this, _getSnapForJob);
|
|
319
307
|
_class_private_method_init(this, _mapSnapAndJob);
|
|
320
308
|
_class_private_method_init(this, _removeSnapAndJobMapping);
|
|
321
309
|
_class_private_field_init(this, _snapRpcHooks, {
|
|
@@ -371,12 +359,6 @@ function createSnapHooks(snapId, workerId) {
|
|
|
371
359
|
};
|
|
372
360
|
_class_private_field_get(this, _snapRpcHooks).set(snapId, rpcHook);
|
|
373
361
|
}
|
|
374
|
-
function getJobForSnap(snapId) {
|
|
375
|
-
return _class_private_field_get(this, _snapToJobMap).get(snapId);
|
|
376
|
-
}
|
|
377
|
-
function getSnapForJob(jobId) {
|
|
378
|
-
return _class_private_field_get(this, _jobToSnapMap).get(jobId);
|
|
379
|
-
}
|
|
380
362
|
function mapSnapAndJob(snapId, jobId) {
|
|
381
363
|
_class_private_field_get(this, _snapToJobMap).set(snapId, jobId);
|
|
382
364
|
_class_private_field_get(this, _jobToSnapMap).set(jobId, snapId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/AbstractExecutionService.ts"],"sourcesContent":["import ObjectMultiplex from '@metamask/object-multiplex';\nimport type { BasePostMessageStream } from '@metamask/post-message-stream';\nimport type { SnapRpcHook, SnapRpcHookArgs } from '@metamask/snaps-utils';\nimport { SNAP_STREAM_NAMES, logError } from '@metamask/snaps-utils';\nimport type { Json, JsonRpcNotification } from '@metamask/utils';\nimport { Duration, isJsonRpcNotification, isObject } from '@metamask/utils';\nimport type {\n // TODO: Replace with @metamask/utils version after bumping json-rpc-engine\n JsonRpcRequest,\n PendingJsonRpcResponse,\n} from 'json-rpc-engine';\nimport { JsonRpcEngine } from 'json-rpc-engine';\nimport { createStreamMiddleware } from 'json-rpc-middleware-stream';\nimport { nanoid } from 'nanoid';\nimport pump from 'pump';\nimport type { Duplex } from 'stream';\n\nimport { log } from '../logging';\nimport { hasTimedOut, withTimeout } from '../utils';\nimport type {\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"],"names":["AbstractExecutionService","setupMultiplex","controllerName","registerMessageHandlers","messenger","registerActionHandler","snapId","options","handleRpcRequest","snapData","executeSnap","terminateSnap","terminateAllSnaps","terminate","jobId","jobWrapper","jobs","get","Error","result","withTimeout","command","jsonrpc","method","params","id","nanoid","terminationTimeout","hasTimedOut","logError","Object","values","streams","forEach","stream","destroyed","destroy","removeAllListeners","error","terminateJob","removeSnapAndJobMapping","delete","log","initJob","worker","initStreams","rpcEngine","JsonRpcEngine","jsonRpcConnection","createStreamMiddleware","pump","push","middleware","envMetadata","set","envStream","initEnvStream","mux","commandStream","createStream","SNAP_STREAM_NAMES","COMMAND","notificationHandler","message","isJsonRpcNotification","jobToSnapMap","publish","isObject","removeListener","on","rpcStream","JSON_RPC","rpc","_connection","snapToJobMap","Promise","all","keys","map","snapRpcHooks","clear","getRpcRequestHandler","has","job","mapSnapAndJob","setupSnapProvider","createSnapHooks","response","handle","rpcRequestHandler","constructor","Duration","Second","Map","workerId","rpcHook","origin","handler","request","target","removeSnapHooks","connectionStream","streamName","ObjectMultiplex"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/services/AbstractExecutionService.ts"],"sourcesContent":["import ObjectMultiplex from '@metamask/object-multiplex';\nimport type { BasePostMessageStream } from '@metamask/post-message-stream';\nimport type { SnapRpcHook, SnapRpcHookArgs } from '@metamask/snaps-utils';\nimport { SNAP_STREAM_NAMES, logError } from '@metamask/snaps-utils';\nimport type { Json, JsonRpcNotification } from '@metamask/utils';\nimport { Duration, isJsonRpcNotification, isObject } from '@metamask/utils';\nimport type {\n // TODO: Replace with @metamask/utils version after bumping json-rpc-engine\n JsonRpcRequest,\n PendingJsonRpcResponse,\n} from 'json-rpc-engine';\nimport { JsonRpcEngine } from 'json-rpc-engine';\nimport { createStreamMiddleware } from 'json-rpc-middleware-stream';\nimport { nanoid } from 'nanoid';\nimport pump from 'pump';\nimport type { Duplex } from 'stream';\n\nimport { log } from '../logging';\nimport { hasTimedOut, withTimeout } from '../utils';\nimport type {\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 #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"],"names":["AbstractExecutionService","setupMultiplex","controllerName","registerMessageHandlers","messenger","registerActionHandler","snapId","options","handleRpcRequest","snapData","executeSnap","terminateSnap","terminateAllSnaps","terminate","jobId","jobWrapper","jobs","get","Error","result","withTimeout","command","jsonrpc","method","params","id","nanoid","terminationTimeout","hasTimedOut","logError","Object","values","streams","forEach","stream","destroyed","destroy","removeAllListeners","error","terminateJob","removeSnapAndJobMapping","delete","log","initJob","worker","initStreams","rpcEngine","JsonRpcEngine","jsonRpcConnection","createStreamMiddleware","pump","push","middleware","envMetadata","set","envStream","initEnvStream","mux","commandStream","createStream","SNAP_STREAM_NAMES","COMMAND","notificationHandler","message","isJsonRpcNotification","jobToSnapMap","publish","isObject","removeListener","on","rpcStream","JSON_RPC","rpc","_connection","snapToJobMap","Promise","all","keys","map","snapRpcHooks","clear","getRpcRequestHandler","has","job","mapSnapAndJob","setupSnapProvider","createSnapHooks","response","handle","rpcRequestHandler","constructor","Duration","Second","Map","workerId","rpcHook","origin","handler","request","target","removeSnapHooks","connectionStream","streamName","ObjectMultiplex"],"mappings":"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"}
|
|
@@ -138,7 +138,7 @@ const defaultState = {
|
|
|
138
138
|
return truncatedSnap;
|
|
139
139
|
}
|
|
140
140
|
const name = 'SnapController';
|
|
141
|
-
var _closeAllConnections = /*#__PURE__*/ new WeakMap(), _dynamicPermissions = /*#__PURE__*/ new WeakMap(), _environmentEndowmentPermissions = /*#__PURE__*/ new WeakMap(), _excludedPermissions = /*#__PURE__*/ new WeakMap(), _featureFlags = /*#__PURE__*/ new WeakMap(), _fetchFunction = /*#__PURE__*/ new WeakMap(), _idleTimeCheckInterval = /*#__PURE__*/ new WeakMap(), _maxIdleTime = /*#__PURE__*/ new WeakMap(), _detectSnapLocation = /*#__PURE__*/ new WeakMap(), _rollbackSnapshots = /*#__PURE__*/ new WeakMap(), _timeoutForLastRequestStatus = /*#__PURE__*/ new WeakMap(), _statusMachine = /*#__PURE__*/ new WeakMap(), /**
|
|
141
|
+
var _closeAllConnections = /*#__PURE__*/ new WeakMap(), _dynamicPermissions = /*#__PURE__*/ new WeakMap(), _environmentEndowmentPermissions = /*#__PURE__*/ new WeakMap(), _excludedPermissions = /*#__PURE__*/ new WeakMap(), _featureFlags = /*#__PURE__*/ new WeakMap(), _fetchFunction = /*#__PURE__*/ new WeakMap(), _idleTimeCheckInterval = /*#__PURE__*/ new WeakMap(), _maxIdleTime = /*#__PURE__*/ new WeakMap(), _detectSnapLocation = /*#__PURE__*/ new WeakMap(), _snapsRuntimeData = /*#__PURE__*/ new WeakMap(), _rollbackSnapshots = /*#__PURE__*/ new WeakMap(), _timeoutForLastRequestStatus = /*#__PURE__*/ new WeakMap(), _statusMachine = /*#__PURE__*/ new WeakMap(), /**
|
|
142
142
|
* We track status of a Snap using a finite-state-machine.
|
|
143
143
|
* It keeps track of whether the snap is started / stopped / etc.
|
|
144
144
|
*
|
|
@@ -218,13 +218,14 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
|
|
|
218
218
|
* @param snapId - The snap id.
|
|
219
219
|
* @param newVersionRange - The new version range being requsted.
|
|
220
220
|
* @returns `true` if validation checks pass and `false` if they do not.
|
|
221
|
-
*/ _isValidUpdate = /*#__PURE__*/ new WeakSet();
|
|
221
|
+
*/ _isValidUpdate = /*#__PURE__*/ new WeakSet(), _callLifecycleHook = /*#__PURE__*/ new WeakSet();
|
|
222
222
|
class SnapController extends _basecontroller.BaseControllerV2 {
|
|
223
223
|
/**
|
|
224
224
|
* Checks all installed snaps against the block list and
|
|
225
225
|
* blocks/unblocks snaps as appropriate. See {@link SnapController.blockSnap}
|
|
226
226
|
* for more information.
|
|
227
227
|
*/ async updateBlockedSnaps() {
|
|
228
|
+
await this.messagingSystem.call('SnapsRegistry:update');
|
|
228
229
|
const blockedSnaps = await this.messagingSystem.call('SnapsRegistry:get', Object.values(this.state.snaps).reduce((blockListArg, snap)=>{
|
|
229
230
|
blockListArg[snap.id] = {
|
|
230
231
|
version: snap.version,
|
|
@@ -265,14 +266,13 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
265
266
|
*
|
|
266
267
|
* @param snapId - The id of the Snap to start.
|
|
267
268
|
*/ async startSnap(snapId) {
|
|
268
|
-
const
|
|
269
|
-
if (
|
|
269
|
+
const snap = this.state.snaps[snapId];
|
|
270
|
+
if (snap.enabled === false) {
|
|
270
271
|
throw new Error(`Snap "${snapId}" is disabled.`);
|
|
271
272
|
}
|
|
272
|
-
(0, _utils.assert)(runtime.sourceCode);
|
|
273
273
|
await _class_private_method_get(this, _startSnap, startSnap).call(this, {
|
|
274
274
|
snapId,
|
|
275
|
-
sourceCode:
|
|
275
|
+
sourceCode: snap.sourceCode
|
|
276
276
|
});
|
|
277
277
|
}
|
|
278
278
|
/**
|
|
@@ -401,8 +401,9 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
401
401
|
* @param snapId - The id of the Snap whose state should be updated.
|
|
402
402
|
* @param newSnapState - The new state of the snap.
|
|
403
403
|
*/ async updateSnapState(snapId, newSnapState) {
|
|
404
|
-
|
|
405
|
-
|
|
404
|
+
this.update((state)=>{
|
|
405
|
+
state.snapStates[snapId] = newSnapState;
|
|
406
|
+
});
|
|
406
407
|
}
|
|
407
408
|
/**
|
|
408
409
|
* Clears the state of the snap with the given id.
|
|
@@ -410,8 +411,9 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
410
411
|
*
|
|
411
412
|
* @param snapId - The id of the Snap whose state should be cleared.
|
|
412
413
|
*/ clearSnapState(snapId) {
|
|
413
|
-
|
|
414
|
-
|
|
414
|
+
this.update((state)=>{
|
|
415
|
+
state.snapStates[snapId] = null;
|
|
416
|
+
});
|
|
415
417
|
}
|
|
416
418
|
/**
|
|
417
419
|
* Adds error from a snap to the SnapController state.
|
|
@@ -450,7 +452,7 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
450
452
|
* @returns A promise that resolves with the decrypted snap state or null if no state exists.
|
|
451
453
|
* @throws If the snap state decryption fails.
|
|
452
454
|
*/ async getSnapState(snapId) {
|
|
453
|
-
const
|
|
455
|
+
const state = this.state.snapStates[snapId];
|
|
454
456
|
return state ?? null;
|
|
455
457
|
}
|
|
456
458
|
/**
|
|
@@ -496,7 +498,7 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
496
498
|
await this.disableSnap(snapId);
|
|
497
499
|
_class_private_method_get(this, _revokeAllSnapPermissions, revokeAllSnapPermissions).call(this, snapId);
|
|
498
500
|
_class_private_method_get(this, _removeSnapFromSubjects, removeSnapFromSubjects).call(this, snapId);
|
|
499
|
-
this.
|
|
501
|
+
_class_private_field_get(this, _snapsRuntimeData).delete(snapId);
|
|
500
502
|
this.update((state)=>{
|
|
501
503
|
delete state.snaps[snapId];
|
|
502
504
|
delete state.snapStates[snapId];
|
|
@@ -618,9 +620,7 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
618
620
|
pendingUpdates.push(snapId);
|
|
619
621
|
let rollbackSnapshot = _class_private_method_get(this, _getRollbackSnapshot, getRollbackSnapshot).call(this, snapId);
|
|
620
622
|
if (rollbackSnapshot === undefined) {
|
|
621
|
-
const prevSourceCode = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId).sourceCode;
|
|
622
623
|
rollbackSnapshot = _class_private_method_get(this, _createRollbackSnapshot, createRollbackSnapshot).call(this, snapId);
|
|
623
|
-
rollbackSnapshot.sourceCode = prevSourceCode;
|
|
624
624
|
rollbackSnapshot.newVersion = version;
|
|
625
625
|
} else {
|
|
626
626
|
throw new Error('This snap is already being updated.');
|
|
@@ -936,12 +936,7 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
936
936
|
anonymous: false
|
|
937
937
|
},
|
|
938
938
|
snapStates: {
|
|
939
|
-
persist:
|
|
940
|
-
return Object.keys(this.state.snaps).reduce((acc, cur)=>{
|
|
941
|
-
acc[cur] = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, cur).state;
|
|
942
|
-
return acc;
|
|
943
|
-
}, {});
|
|
944
|
-
},
|
|
939
|
+
persist: true,
|
|
945
940
|
anonymous: false
|
|
946
941
|
},
|
|
947
942
|
snaps: {
|
|
@@ -949,7 +944,6 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
949
944
|
return Object.values(snaps).map((snap)=>{
|
|
950
945
|
return {
|
|
951
946
|
...snap,
|
|
952
|
-
sourceCode: _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snap.id).sourceCode,
|
|
953
947
|
// At the time state is rehydrated, no snap will be running.
|
|
954
948
|
status: _snapsutils.SnapStatus.Stopped
|
|
955
949
|
};
|
|
@@ -964,18 +958,7 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
964
958
|
name,
|
|
965
959
|
state: {
|
|
966
960
|
...defaultState,
|
|
967
|
-
...
|
|
968
|
-
...state,
|
|
969
|
-
snaps: Object.values(state?.snaps ?? {}).reduce((memo, snap)=>{
|
|
970
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
971
|
-
// sourceCode is stripped out to prevent piping to MetaMask UI,
|
|
972
|
-
// it is stored in the runtime while we're running a snap and then
|
|
973
|
-
// persisted to state when needed.
|
|
974
|
-
const { sourceCode, ...rest } = snap;
|
|
975
|
-
memo[snap.id] = rest;
|
|
976
|
-
return memo;
|
|
977
|
-
}, {})
|
|
978
|
-
}
|
|
961
|
+
...state
|
|
979
962
|
}
|
|
980
963
|
});
|
|
981
964
|
_class_private_method_init(this, _initializeStateMachine);
|
|
@@ -1070,6 +1053,16 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
1070
1053
|
_class_private_method_init(this, _setupRuntime);
|
|
1071
1054
|
_class_private_method_init(this, _calculatePermissionsChange);
|
|
1072
1055
|
_class_private_method_init(this, _isValidUpdate);
|
|
1056
|
+
/**
|
|
1057
|
+
* Call a lifecycle hook on a snap, if the snap has the
|
|
1058
|
+
* `endowment:lifecycle-hooks` permission. If the snap does not have the
|
|
1059
|
+
* permission, nothing happens.
|
|
1060
|
+
*
|
|
1061
|
+
* @param snapId - The snap ID.
|
|
1062
|
+
* @param handler - The lifecycle hook to call. This should be one of the
|
|
1063
|
+
* supported lifecycle hooks.
|
|
1064
|
+
* @private
|
|
1065
|
+
*/ _class_private_method_init(this, _callLifecycleHook);
|
|
1073
1066
|
_class_private_field_init(this, _closeAllConnections, {
|
|
1074
1067
|
writable: true,
|
|
1075
1068
|
value: void 0
|
|
@@ -1108,8 +1101,10 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
1108
1101
|
writable: true,
|
|
1109
1102
|
value: void 0
|
|
1110
1103
|
});
|
|
1111
|
-
|
|
1112
|
-
|
|
1104
|
+
_class_private_field_init(this, _snapsRuntimeData, {
|
|
1105
|
+
writable: true,
|
|
1106
|
+
value: void 0
|
|
1107
|
+
});
|
|
1113
1108
|
_class_private_field_init(this, _rollbackSnapshots, {
|
|
1114
1109
|
writable: true,
|
|
1115
1110
|
value: void 0
|
|
@@ -1136,12 +1131,22 @@ class SnapController extends _basecontroller.BaseControllerV2 {
|
|
|
1136
1131
|
this._onOutboundRequest = this._onOutboundRequest.bind(this);
|
|
1137
1132
|
this._onOutboundResponse = this._onOutboundResponse.bind(this);
|
|
1138
1133
|
_class_private_field_set(this, _rollbackSnapshots, new Map());
|
|
1139
|
-
this
|
|
1134
|
+
_class_private_field_set(this, _snapsRuntimeData, new Map());
|
|
1140
1135
|
_class_private_method_get(this, _pollForLastRequestStatus, pollForLastRequestStatus).call(this);
|
|
1141
1136
|
/* eslint-disable @typescript-eslint/unbound-method */ this.messagingSystem.subscribe('ExecutionService:unhandledError', this._onUnhandledSnapError);
|
|
1142
1137
|
this.messagingSystem.subscribe('ExecutionService:outboundRequest', this._onOutboundRequest);
|
|
1143
1138
|
this.messagingSystem.subscribe('ExecutionService:outboundResponse', this._onOutboundResponse);
|
|
1144
|
-
/* eslint-enable @typescript-eslint/unbound-method */
|
|
1139
|
+
/* eslint-enable @typescript-eslint/unbound-method */ this.messagingSystem.subscribe('SnapController:snapInstalled', ({ id })=>{
|
|
1140
|
+
_class_private_method_get(this, _callLifecycleHook, callLifecycleHook).call(this, id, _snapsutils.HandlerType.OnInstall).catch((error)=>{
|
|
1141
|
+
(0, _snapsutils.logError)(`Error when calling \`onInstall\` lifecycle hook for snap "${id}": ${(0, _snapsutils.getErrorMessage)(error)}`);
|
|
1142
|
+
});
|
|
1143
|
+
});
|
|
1144
|
+
this.messagingSystem.subscribe('SnapController:snapUpdated', ({ id })=>{
|
|
1145
|
+
_class_private_method_get(this, _callLifecycleHook, callLifecycleHook).call(this, id, _snapsutils.HandlerType.OnUpdate).catch((error)=>{
|
|
1146
|
+
(0, _snapsutils.logError)(`Error when calling \`onUpdate\` lifecycle hook for snap "${id}": ${(0, _snapsutils.getErrorMessage)(error)}`);
|
|
1147
|
+
});
|
|
1148
|
+
});
|
|
1149
|
+
_class_private_method_get(this, _initializeStateMachine, initializeStateMachine).call(this);
|
|
1145
1150
|
_class_private_method_get(this, _registerMessageHandlers, registerMessageHandlers).call(this);
|
|
1146
1151
|
Object.values(state?.snaps ?? {}).forEach((snap)=>_class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snap.id, {
|
|
1147
1152
|
sourceCode: snap.sourceCode,
|
|
@@ -1270,7 +1275,7 @@ async function assertIsInstallAllowed(snapId, snapInfo) {
|
|
|
1270
1275
|
}
|
|
1271
1276
|
async function stopSnapsLastRequestPastMax() {
|
|
1272
1277
|
const entries = [
|
|
1273
|
-
...this.
|
|
1278
|
+
..._class_private_field_get(this, _snapsRuntimeData).entries()
|
|
1274
1279
|
];
|
|
1275
1280
|
return Promise.all(entries.filter(([_snapId, runtime])=>runtime.activeReferences === 0 && runtime.pendingInboundRequests.length === 0 && // lastRequest should always be set here but TypeScript wants this check
|
|
1276
1281
|
runtime.lastRequest && _class_private_field_get(this, _maxIdleTime) && (0, _utils.timeSince)(runtime.lastRequest) > _class_private_field_get(this, _maxIdleTime)).map(async ([snapId])=>this.stopSnap(snapId, _snapsutils.SnapStatusEvents.Stop)));
|
|
@@ -1445,6 +1450,7 @@ function set(args) {
|
|
|
1445
1450
|
initialPermissions: manifest.result.initialPermissions,
|
|
1446
1451
|
manifest: manifest.result,
|
|
1447
1452
|
status: _class_private_field_get(this, _statusMachine).config.initial,
|
|
1453
|
+
sourceCode,
|
|
1448
1454
|
version,
|
|
1449
1455
|
versionHistory
|
|
1450
1456
|
};
|
|
@@ -1462,8 +1468,6 @@ function set(args) {
|
|
|
1462
1468
|
rollbackSnapshot.statePatches = inversePatches;
|
|
1463
1469
|
}
|
|
1464
1470
|
}
|
|
1465
|
-
const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
|
|
1466
|
-
runtime.sourceCode = sourceCode;
|
|
1467
1471
|
this.messagingSystem.publish(`SnapController:snapAdded`, snap, svgIcon?.toString());
|
|
1468
1472
|
return {
|
|
1469
1473
|
...snap,
|
|
@@ -1493,15 +1497,12 @@ async function fetchSnap(snapId, location) {
|
|
|
1493
1497
|
location
|
|
1494
1498
|
};
|
|
1495
1499
|
} catch (error) {
|
|
1496
|
-
|
|
1497
|
-
// https://github.com/MetaMask/utils/blob/62d022ef83c91fa4d150e51913be4441508a0ab1/src/assert.ts
|
|
1498
|
-
const message = error instanceof Error ? error.message : error.toString();
|
|
1499
|
-
throw new Error(`Failed to fetch Snap "${snapId}": ${message}.`);
|
|
1500
|
+
throw new Error(`Failed to fetch snap "${snapId}": ${(0, _snapsutils.getErrorMessage)(error)}.`);
|
|
1500
1501
|
}
|
|
1501
1502
|
}
|
|
1502
1503
|
function validateSnapPermissions(processedPermissions) {
|
|
1503
1504
|
const permissionKeys = Object.keys(processedPermissions);
|
|
1504
|
-
const handlerPermissions = Object.values(_endowments.handlerEndowments);
|
|
1505
|
+
const handlerPermissions = Array.from(new Set(Object.values(_endowments.handlerEndowments)));
|
|
1505
1506
|
(0, _utils.assert)(permissionKeys.some((key)=>handlerPermissions.includes(key)), `A snap must request at least one of the following permissions: ${handlerPermissions.join(', ')}.`);
|
|
1506
1507
|
const excludedPermissionErrors = permissionKeys.reduce((errors, permission)=>{
|
|
1507
1508
|
if ((0, _utils.hasProperty)(_class_private_field_get(this, _excludedPermissions), permission)) {
|
|
@@ -1602,7 +1603,6 @@ function createRollbackSnapshot(snapId) {
|
|
|
1602
1603
|
(0, _utils.assert)(_class_private_field_get(this, _rollbackSnapshots).get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
|
|
1603
1604
|
_class_private_field_get(this, _rollbackSnapshots).set(snapId, {
|
|
1604
1605
|
statePatches: [],
|
|
1605
|
-
sourceCode: '',
|
|
1606
1606
|
permissions: {
|
|
1607
1607
|
revoked: null,
|
|
1608
1608
|
granted: [],
|
|
@@ -1624,7 +1624,7 @@ async function rollbackSnap(snapId) {
|
|
|
1624
1624
|
if (this.get(snapId)?.status !== _snapsutils.SnapStatus.Stopped) {
|
|
1625
1625
|
_class_private_method_get(this, _transition, transition).call(this, snapId, _snapsutils.SnapStatusEvents.Stop);
|
|
1626
1626
|
}
|
|
1627
|
-
const { statePatches,
|
|
1627
|
+
const { statePatches, permissions } = rollbackSnapshot;
|
|
1628
1628
|
if (statePatches?.length) {
|
|
1629
1629
|
this.applyPatches(statePatches);
|
|
1630
1630
|
}
|
|
@@ -1635,10 +1635,6 @@ async function rollbackSnap(snapId) {
|
|
|
1635
1635
|
state.snaps[snapId].status = _snapsutils.SnapStatus.Stopped;
|
|
1636
1636
|
});
|
|
1637
1637
|
}
|
|
1638
|
-
if (sourceCode) {
|
|
1639
|
-
const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
|
|
1640
|
-
runtime.sourceCode = sourceCode;
|
|
1641
|
-
}
|
|
1642
1638
|
if (permissions.revoked && Object.keys(permissions.revoked).length) {
|
|
1643
1639
|
this.messagingSystem.call('PermissionController:grantPermissions', {
|
|
1644
1640
|
approvedPermissions: permissions.revoked,
|
|
@@ -1663,7 +1659,7 @@ async function rollbackSnaps(snapIds) {
|
|
|
1663
1659
|
}
|
|
1664
1660
|
}
|
|
1665
1661
|
function getRuntime(snapId) {
|
|
1666
|
-
return this.
|
|
1662
|
+
return _class_private_field_get(this, _snapsRuntimeData).get(snapId);
|
|
1667
1663
|
}
|
|
1668
1664
|
function getRuntimeExpect(snapId) {
|
|
1669
1665
|
const runtime = _class_private_method_get(this, _getRuntime, getRuntime).call(this, snapId);
|
|
@@ -1671,7 +1667,7 @@ function getRuntimeExpect(snapId) {
|
|
|
1671
1667
|
return runtime;
|
|
1672
1668
|
}
|
|
1673
1669
|
function setupRuntime(snapId, data) {
|
|
1674
|
-
if (this.
|
|
1670
|
+
if (_class_private_field_get(this, _snapsRuntimeData).has(snapId)) {
|
|
1675
1671
|
return;
|
|
1676
1672
|
}
|
|
1677
1673
|
const snap = this.get(snapId);
|
|
@@ -1683,7 +1679,7 @@ function setupRuntime(snapId, data) {
|
|
|
1683
1679
|
value: snap?.status ?? _class_private_field_get(this, _statusMachine).config.initial
|
|
1684
1680
|
});
|
|
1685
1681
|
(0, _fsm1.forceStrict)(interpreter);
|
|
1686
|
-
this.
|
|
1682
|
+
_class_private_field_get(this, _snapsRuntimeData).set(snapId, {
|
|
1687
1683
|
lastRequest: null,
|
|
1688
1684
|
rpcHandler: null,
|
|
1689
1685
|
installPromise: null,
|
|
@@ -1719,5 +1715,21 @@ function isValidUpdate(snapId, newVersionRange) {
|
|
|
1719
1715
|
}
|
|
1720
1716
|
return true;
|
|
1721
1717
|
}
|
|
1718
|
+
async function callLifecycleHook(snapId, handler) {
|
|
1719
|
+
const permissionName = _endowments.handlerEndowments[handler];
|
|
1720
|
+
const hasPermission = this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName);
|
|
1721
|
+
if (!hasPermission) {
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
await this.handleRequest({
|
|
1725
|
+
snapId,
|
|
1726
|
+
handler,
|
|
1727
|
+
origin: '',
|
|
1728
|
+
request: {
|
|
1729
|
+
jsonrpc: '2.0',
|
|
1730
|
+
method: handler
|
|
1731
|
+
}
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1722
1734
|
|
|
1723
1735
|
//# sourceMappingURL=SnapController.js.map
|