@metamask/snaps-controllers 2.0.2 → 3.1.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/dist/cjs/cronjob/CronjobController.js +1 -1
  3. package/dist/cjs/cronjob/CronjobController.js.map +1 -1
  4. package/dist/cjs/fsm.js.map +1 -1
  5. package/dist/cjs/services/AbstractExecutionService.js +6 -7
  6. package/dist/cjs/services/AbstractExecutionService.js.map +1 -1
  7. package/dist/cjs/snaps/SnapController.js +80 -89
  8. package/dist/cjs/snaps/SnapController.js.map +1 -1
  9. package/dist/cjs/snaps/endowments/cronjob.js +4 -4
  10. package/dist/cjs/snaps/endowments/cronjob.js.map +1 -1
  11. package/dist/cjs/snaps/endowments/enum.js +1 -0
  12. package/dist/cjs/snaps/endowments/enum.js.map +1 -1
  13. package/dist/cjs/snaps/endowments/index.js +12 -4
  14. package/dist/cjs/snaps/endowments/index.js.map +1 -1
  15. package/dist/cjs/snaps/endowments/keyring.js +100 -0
  16. package/dist/cjs/snaps/endowments/keyring.js.map +1 -0
  17. package/dist/cjs/snaps/endowments/name-lookup.js +3 -3
  18. package/dist/cjs/snaps/endowments/name-lookup.js.map +1 -1
  19. package/dist/cjs/snaps/endowments/rpc.js +4 -4
  20. package/dist/cjs/snaps/endowments/rpc.js.map +1 -1
  21. package/dist/cjs/snaps/endowments/transaction-insight.js +3 -3
  22. package/dist/cjs/snaps/endowments/transaction-insight.js.map +1 -1
  23. package/dist/cjs/snaps/location/npm.js +41 -14
  24. package/dist/cjs/snaps/location/npm.js.map +1 -1
  25. package/dist/cjs/snaps/permissions.js +5 -5
  26. package/dist/cjs/snaps/permissions.js.map +1 -1
  27. package/dist/cjs/snaps/registry/json.js +30 -1
  28. package/dist/cjs/snaps/registry/json.js.map +1 -1
  29. package/dist/cjs/snaps/registry/registry.js.map +1 -1
  30. package/dist/esm/cronjob/CronjobController.js +1 -1
  31. package/dist/esm/cronjob/CronjobController.js.map +1 -1
  32. package/dist/esm/fsm.js.map +1 -1
  33. package/dist/esm/services/AbstractExecutionService.js +6 -7
  34. package/dist/esm/services/AbstractExecutionService.js.map +1 -1
  35. package/dist/esm/snaps/SnapController.js +77 -86
  36. package/dist/esm/snaps/SnapController.js.map +1 -1
  37. package/dist/esm/snaps/endowments/cronjob.js +4 -4
  38. package/dist/esm/snaps/endowments/cronjob.js.map +1 -1
  39. package/dist/esm/snaps/endowments/enum.js +1 -0
  40. package/dist/esm/snaps/endowments/enum.js.map +1 -1
  41. package/dist/esm/snaps/endowments/index.js +10 -4
  42. package/dist/esm/snaps/endowments/index.js.map +1 -1
  43. package/dist/esm/snaps/endowments/keyring.js +91 -0
  44. package/dist/esm/snaps/endowments/keyring.js.map +1 -0
  45. package/dist/esm/snaps/endowments/name-lookup.js +3 -3
  46. package/dist/esm/snaps/endowments/name-lookup.js.map +1 -1
  47. package/dist/esm/snaps/endowments/rpc.js +4 -4
  48. package/dist/esm/snaps/endowments/rpc.js.map +1 -1
  49. package/dist/esm/snaps/endowments/transaction-insight.js +3 -3
  50. package/dist/esm/snaps/endowments/transaction-insight.js.map +1 -1
  51. package/dist/esm/snaps/location/npm.js +42 -15
  52. package/dist/esm/snaps/location/npm.js.map +1 -1
  53. package/dist/esm/snaps/permissions.js +1 -1
  54. package/dist/esm/snaps/permissions.js.map +1 -1
  55. package/dist/esm/snaps/registry/json.js +31 -2
  56. package/dist/esm/snaps/registry/json.js.map +1 -1
  57. package/dist/esm/snaps/registry/registry.js.map +1 -1
  58. package/dist/types/cronjob/CronjobController.d.ts +1 -1
  59. package/dist/types/services/AbstractExecutionService.d.ts +1 -1
  60. package/dist/types/snaps/SnapController.d.ts +20 -32
  61. package/dist/types/snaps/endowments/enum.d.ts +2 -1
  62. package/dist/types/snaps/endowments/index.d.ts +13 -0
  63. package/dist/types/snaps/endowments/keyring.d.ts +39 -0
  64. package/dist/types/snaps/location/npm.d.ts +1 -1
  65. package/dist/types/snaps/registry/json.d.ts +5 -1
  66. package/dist/types/snaps/registry/registry.d.ts +11 -1
  67. package/package.json +32 -25
@@ -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 { pipeline } from 'stream';\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 pipeline(\n jsonRpcConnection.stream,\n streams.command,\n jsonRpcConnection.stream,\n (error) => {\n if (error) {\n logError(`Command stream failure.`, error);\n }\n },\n );\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 pipeline(\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":["ObjectMultiplex","SNAP_STREAM_NAMES","logError","Duration","isJsonRpcNotification","isObject","JsonRpcEngine","createStreamMiddleware","nanoid","pipeline","log","hasTimedOut","withTimeout","controllerName","AbstractExecutionService","registerMessageHandlers","messenger","registerActionHandler","snapId","options","handleRpcRequest","snapData","executeSnap","terminateSnap","terminateAllSnaps","terminate","jobId","jobWrapper","jobs","get","Error","result","command","jsonrpc","method","params","id","terminationTimeout","Object","values","streams","forEach","stream","destroyed","destroy","removeAllListeners","error","terminateJob","removeSnapAndJobMapping","delete","initJob","worker","initStreams","rpcEngine","jsonRpcConnection","push","middleware","envMetadata","set","envStream","initEnvStream","mux","setupMultiplex","commandStream","createStream","COMMAND","notificationHandler","message","jobToSnapMap","publish","removeListener","on","rpcStream","JSON_RPC","rpc","_connection","snapToJobMap","Promise","all","keys","map","snapRpcHooks","clear","getRpcRequestHandler","has","job","mapSnapAndJob","setupSnapProvider","createSnapHooks","response","handle","rpcRequestHandler","constructor","Second","Map","workerId","rpcHook","origin","handler","request","target","removeSnapHooks","connectionStream","streamName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/services/AbstractExecutionService.ts"],"sourcesContent":["import { JsonRpcEngine } from '@metamask/json-rpc-engine';\nimport ObjectMultiplex from '@metamask/object-multiplex';\nimport type { BasePostMessageStream } from '@metamask/post-message-stream';\nimport { JsonRpcError } from '@metamask/rpc-errors';\nimport type { SnapRpcHook, SnapRpcHookArgs } from '@metamask/snaps-utils';\nimport { SNAP_STREAM_NAMES, logError } from '@metamask/snaps-utils';\nimport type {\n Json,\n JsonRpcNotification,\n JsonRpcRequest,\n PendingJsonRpcResponse,\n} from '@metamask/utils';\nimport { Duration, isJsonRpcNotification, isObject } from '@metamask/utils';\nimport { createStreamMiddleware } from 'json-rpc-middleware-stream';\nimport { nanoid } from 'nanoid';\nimport { pipeline } from 'stream';\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 pipeline(\n jsonRpcConnection.stream,\n streams.command,\n jsonRpcConnection.stream,\n (error) => {\n if (error) {\n logError(`Command stream failure.`, error);\n }\n },\n );\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 const mux = setupMultiplex(envStream, `Job: \"${jobId}\"`);\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\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,\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;\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,\n ): Promise<Json | undefined> {\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<Json> = await job.rpcEngine.handle(\n message,\n );\n\n if (response.error) {\n throw new JsonRpcError(\n response.error.code,\n response.error.message,\n response.error.data,\n );\n }\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: request as JsonRpcRequest,\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 = 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 pipeline(connectionStream, mux, connectionStream, (error) => {\n if (error) {\n streamName\n ? logError(`\"${streamName}\" stream failure.`, error)\n : logError(error);\n }\n });\n return mux;\n}\n"],"names":["JsonRpcEngine","ObjectMultiplex","JsonRpcError","SNAP_STREAM_NAMES","logError","Duration","isJsonRpcNotification","isObject","createStreamMiddleware","nanoid","pipeline","log","hasTimedOut","withTimeout","controllerName","AbstractExecutionService","registerMessageHandlers","messenger","registerActionHandler","snapId","options","handleRpcRequest","snapData","executeSnap","terminateSnap","terminateAllSnaps","terminate","jobId","jobWrapper","jobs","get","Error","result","command","jsonrpc","method","params","id","terminationTimeout","Object","values","streams","forEach","stream","destroyed","destroy","removeAllListeners","error","terminateJob","removeSnapAndJobMapping","delete","initJob","worker","initStreams","rpcEngine","jsonRpcConnection","push","middleware","envMetadata","set","envStream","initEnvStream","mux","setupMultiplex","commandStream","createStream","COMMAND","notificationHandler","message","jobToSnapMap","publish","removeListener","on","rpcStream","JSON_RPC","rpc","_connection","snapToJobMap","Promise","all","keys","map","snapRpcHooks","clear","getRpcRequestHandler","has","job","mapSnapAndJob","setupSnapProvider","createSnapHooks","response","handle","code","data","rpcRequestHandler","constructor","Second","Map","workerId","rpcHook","origin","handler","request","target","removeSnapHooks","connectionStream","streamName"],"mappings":"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"}
@@ -63,16 +63,17 @@ function _define_property(obj, key, value) {
63
63
  }
64
64
  import { BaseControllerV2 as BaseController } from '@metamask/base-controller';
65
65
  import { SubjectType } from '@metamask/permission-controller';
66
- import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/rpc-methods';
67
- import { assertIsSnapManifest, assertIsValidSnapId, DEFAULT_ENDOWMENTS, DEFAULT_REQUESTED_SNAP_VERSION, getErrorMessage, HandlerType, logError, normalizeRelative, resolveVersionRange, SnapCaveatType, SnapStatus, SnapStatusEvents, validateFetchedSnap } from '@metamask/snaps-utils';
66
+ import { rpcErrors } from '@metamask/rpc-errors';
67
+ import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-rpc-methods';
68
+ import { assertIsSnapManifest, assertIsValidSnapId, AuxiliaryFileEncoding, DEFAULT_ENDOWMENTS, DEFAULT_REQUESTED_SNAP_VERSION, encodeAuxiliaryFile, getErrorMessage, HandlerType, isOriginAllowed, logError, normalizeRelative, resolveVersionRange, SnapCaveatType, SnapStatus, SnapStatusEvents, validateFetchedSnap, unwrapError } from '@metamask/snaps-utils';
68
69
  import { assert, assertIsJsonRpcRequest, Duration, gtRange, gtVersion, hasProperty, inMilliseconds, isNonEmptyArray, isValidSemVerRange, satisfiesVersionRange, timeSince } from '@metamask/utils';
69
70
  import { createMachine, interpret } from '@xstate/fsm';
70
- import { ethErrors } from 'eth-rpc-errors';
71
71
  import { nanoid } from 'nanoid';
72
72
  import { forceStrict, validateMachine } from '../fsm';
73
73
  import { log } from '../logging';
74
74
  import { hasTimedOut, setDiff, withTimeout } from '../utils';
75
75
  import { handlerEndowments, SnapEndowments } from './endowments';
76
+ import { getKeyringCaveatOrigins } from './endowments/keyring';
76
77
  import { getRpcCaveatOrigins } from './endowments/rpc';
77
78
  import { detectSnapLocation } from './location';
78
79
  import { processSnapPermissions } from './permissions';
@@ -92,7 +93,6 @@ const TRUNCATED_SNAP_PROPERTIES = new Set([
92
93
  'blocked'
93
94
  ]);
94
95
  const defaultState = {
95
- snapErrors: {},
96
96
  snaps: {},
97
97
  snapStates: {}
98
98
  };
@@ -148,7 +148,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
148
148
  * Safely revokes all permissions granted to a Snap.
149
149
  *
150
150
  * @param snapId - The snap ID.
151
- */ _revokeAllSnapPermissions = /*#__PURE__*/ new WeakSet(), _createApproval = /*#__PURE__*/ new WeakSet(), _updateApproval = /*#__PURE__*/ new WeakSet(), _add = /*#__PURE__*/ new WeakSet(), _startSnap = /*#__PURE__*/ new WeakSet(), _getEndowments = /*#__PURE__*/ new WeakSet(), /**
151
+ */ _revokeAllSnapPermissions = /*#__PURE__*/ new WeakSet(), _createApproval = /*#__PURE__*/ new WeakSet(), _updateApproval = /*#__PURE__*/ new WeakSet(), _resolveAllowlistVersion = /*#__PURE__*/ new WeakSet(), _add = /*#__PURE__*/ new WeakSet(), _startSnap = /*#__PURE__*/ new WeakSet(), _getEndowments = /*#__PURE__*/ new WeakSet(), /**
152
152
  * Sets a snap in state. Called when a snap is installed or updated. Performs
153
153
  * various validation checks on the received arguments, and will throw if
154
154
  * validation fails.
@@ -219,8 +219,8 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
219
219
  return _class_private_method_get(this, _unblockSnap, unblockSnap).call(this, snapId);
220
220
  }));
221
221
  }
222
- _onUnhandledSnapError(snapId, error) {
223
- this.stopSnap(snapId, SnapStatusEvents.Crash).then(()=>this.addSnapError(error)).catch((stopSnapError)=>{
222
+ _onUnhandledSnapError(snapId, _error) {
223
+ this.stopSnap(snapId, SnapStatusEvents.Crash).catch((stopSnapError)=>{
224
224
  // TODO: Decide how to handle errors.
225
225
  logError(stopSnapError);
226
226
  });
@@ -396,35 +396,6 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
396
396
  });
397
397
  }
398
398
  /**
399
- * Adds error from a snap to the SnapController state.
400
- *
401
- * @param snapError - The error to store on the SnapController.
402
- */ addSnapError(snapError) {
403
- this.update((state)=>{
404
- const id = nanoid();
405
- state.snapErrors[id] = {
406
- ...snapError,
407
- internalID: id
408
- };
409
- });
410
- }
411
- /**
412
- * Removes an error by internalID from the SnapControllers state.
413
- *
414
- * @param internalID - The internal error ID to remove on the SnapController.
415
- */ removeSnapError(internalID) {
416
- this.update((state)=>{
417
- delete state.snapErrors[internalID];
418
- });
419
- }
420
- /**
421
- * Clears all errors from the SnapControllers state.
422
- */ clearSnapErrors() {
423
- this.update((state)=>{
424
- state.snapErrors = {};
425
- });
426
- }
427
- /**
428
399
  * Gets the own state of the snap with the given id.
429
400
  * This is distinct from the state MetaMask uses to manage snaps.
430
401
  *
@@ -436,6 +407,22 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
436
407
  return state ?? null;
437
408
  }
438
409
  /**
410
+ * Gets a static auxiliary snap file in a chosen file encoding.
411
+ *
412
+ * @param snapId - The id of the Snap whose state to get.
413
+ * @param path - The path to the requested file.
414
+ * @param encoding - An optional requested file encoding.
415
+ * @returns The file requested in the chosen file encoding or null if the file is not found.
416
+ */ getSnapFile(snapId, path, encoding = AuxiliaryFileEncoding.Base64) {
417
+ const snap = this.getExpect(snapId);
418
+ const normalizedPath = normalizeRelative(path);
419
+ const value = snap.auxiliaryFiles?.find((file)=>file.path === normalizedPath)?.value;
420
+ if (!value) {
421
+ return null;
422
+ }
423
+ return encodeAuxiliaryFile(value, encoding);
424
+ }
425
+ /**
439
426
  * Completely clear the controller's state: delete all associated data,
440
427
  * handlers, event listeners, and permissions; tear down all snap providers.
441
428
  */ async clearState() {
@@ -589,10 +576,12 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
589
576
  try {
590
577
  for (const [snapId, { version: rawVersion }] of Object.entries(requestedSnaps)){
591
578
  assertIsValidSnapId(snapId);
592
- const [error, version] = resolveVersionRange(rawVersion);
579
+ const [error, resolvedVersion] = resolveVersionRange(rawVersion);
593
580
  if (error) {
594
- throw ethErrors.rpc.invalidParams(`The "version" field must be a valid SemVer version range if specified. Received: "${rawVersion}".`);
581
+ throw rpcErrors.invalidParams(`The "version" field must be a valid SemVer version range if specified. Received: "${rawVersion}".`);
595
582
  }
583
+ // If we are running in allowlist mode, try to match the version with an allowlist version.
584
+ const version = _class_private_field_get(this, _featureFlags).requireAllowlist ? await _class_private_method_get(this, _resolveAllowlistVersion, resolveAllowlistVersion).call(this, snapId, resolvedVersion) : resolvedVersion;
596
585
  const location = _class_private_field_get(this, _detectSnapLocation).call(this, snapId, {
597
586
  versionRange: version,
598
587
  fetch: _class_private_field_get(this, _fetchFunction),
@@ -620,8 +609,8 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
620
609
  result[snapId] = await this.processRequestedSnap(origin, snapId, location, version);
621
610
  }
622
611
  // Once we finish all installs / updates, emit events.
623
- pendingInstalls.forEach((snapId)=>this.messagingSystem.publish(`SnapController:snapInstalled`, this.getTruncatedExpect(snapId)));
624
- pendingUpdates.forEach(({ snapId, oldVersion })=>this.messagingSystem.publish(`SnapController:snapUpdated`, this.getTruncatedExpect(snapId), oldVersion));
612
+ pendingInstalls.forEach((snapId)=>this.messagingSystem.publish(`SnapController:snapInstalled`, this.getTruncatedExpect(snapId), origin));
613
+ pendingUpdates.forEach(({ snapId, oldVersion })=>this.messagingSystem.publish(`SnapController:snapUpdated`, this.getTruncatedExpect(snapId), oldVersion, origin));
625
614
  snapIds.forEach((snapId)=>_class_private_field_get(this, _rollbackSnapshots).delete(snapId));
626
615
  } catch (error) {
627
616
  const installed = pendingInstalls.filter((snapId)=>this.has(snapId));
@@ -654,7 +643,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
654
643
  return await this.updateSnap(origin, snapId, location, versionRange, // Since we are requesting an update from within processRequestedSnap,
655
644
  // we disable the emitting of the snapUpdated event and rely on the caller
656
645
  // to publish this event after the update is complete.
657
- // This is necesary as installSnaps may be installing multiple snaps
646
+ // This is necessary as installSnaps may be installing multiple snaps
658
647
  // and we don't want to emit events prematurely.
659
648
  false);
660
649
  }
@@ -734,23 +723,25 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
734
723
  try {
735
724
  const snap = this.getExpect(snapId);
736
725
  const newSnap = await _class_private_method_get(this, _fetchSnap, fetchSnap).call(this, snapId, location);
737
- const newVersion = newSnap.manifest.result.version;
726
+ const { sourceCode: sourceCodeFile, manifest: manifestFile } = newSnap.files;
727
+ const manifest = manifestFile.result;
728
+ const newVersion = manifest.version;
738
729
  if (!gtVersion(newVersion, snap.version)) {
739
- throw ethErrors.rpc.invalidParams(`Snap "${snapId}@${snap.version}" is already installed. Couldn't update to a version inside requested "${newVersionRange}" range.`);
730
+ throw rpcErrors.invalidParams(`Snap "${snapId}@${snap.version}" is already installed. Couldn't update to a version inside requested "${newVersionRange}" range.`);
740
731
  }
741
732
  if (!satisfiesVersionRange(newVersion, newVersionRange)) {
742
733
  throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${newVersion}" which doesn't satisfy requested version range "${newVersionRange}".`);
743
734
  }
744
735
  await _class_private_method_get(this, _assertIsInstallAllowed, assertIsInstallAllowed).call(this, snapId, {
745
736
  version: newVersion,
746
- checksum: newSnap.manifest.result.source.shasum
737
+ checksum: manifest.source.shasum
747
738
  });
748
- const processedPermissions = processSnapPermissions(newSnap.manifest.result.initialPermissions);
739
+ const processedPermissions = processSnapPermissions(manifest.initialPermissions);
749
740
  _class_private_method_get(this, _validateSnapPermissions, validateSnapPermissions).call(this, processedPermissions);
750
741
  const { newPermissions, unusedPermissions, approvedPermissions } = _class_private_method_get(this, _calculatePermissionsChange, calculatePermissionsChange).call(this, snapId, processedPermissions);
751
742
  _class_private_method_get(this, _updateApproval, updateApproval).call(this, pendingApproval.id, {
752
743
  permissions: newPermissions,
753
- newVersion: newSnap.manifest.result.version,
744
+ newVersion: manifest.version,
754
745
  newPermissions,
755
746
  approvedPermissions,
756
747
  unusedPermissions,
@@ -769,7 +760,6 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
769
760
  _class_private_method_get(this, _set, set).call(this, {
770
761
  origin,
771
762
  id: snapId,
772
- manifest: newSnap.manifest,
773
763
  files: newSnap.files,
774
764
  isUpdate: true
775
765
  });
@@ -794,8 +784,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
794
784
  rollbackSnapshot.permissions.granted = Object.keys(approvedNewPermissions);
795
785
  rollbackSnapshot.permissions.requestData = requestData;
796
786
  }
797
- const normalizedSourcePath = normalizeRelative(newSnap.manifest.result.source.location.npm.filePath);
798
- const sourceCode = newSnap.files.find((file)=>file.path === normalizedSourcePath)?.toString();
787
+ const sourceCode = sourceCodeFile.toString();
799
788
  assert(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
800
789
  try {
801
790
  await _class_private_method_get(this, _startSnap, startSnap).call(this, {
@@ -807,7 +796,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
807
796
  }
808
797
  const truncatedSnap = this.getTruncatedExpect(snapId);
809
798
  if (emitEvent) {
810
- this.messagingSystem.publish('SnapController:snapUpdated', truncatedSnap, snap.version);
799
+ this.messagingSystem.publish('SnapController:snapUpdated', truncatedSnap, snap.version, origin);
811
800
  }
812
801
  _class_private_method_get(this, _updateApproval, updateApproval).call(this, pendingApproval.id, {
813
802
  loading: false,
@@ -877,6 +866,8 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
877
866
  /* eslint-disable @typescript-eslint/unbound-method */ this.messagingSystem.unsubscribe('ExecutionService:unhandledError', this._onUnhandledSnapError);
878
867
  this.messagingSystem.unsubscribe('ExecutionService:outboundRequest', this._onOutboundRequest);
879
868
  this.messagingSystem.unsubscribe('ExecutionService:outboundResponse', this._onOutboundResponse);
869
+ this.messagingSystem.clearEventSubscriptions('SnapController:snapInstalled');
870
+ this.messagingSystem.clearEventSubscriptions('SnapController:snapUpdated');
880
871
  /* eslint-enable @typescript-eslint/unbound-method */ }
881
872
  /**
882
873
  * Passes a JSON-RPC request object to the RPC handler function of a snap.
@@ -899,16 +890,15 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
899
890
  if (!hasPermission) {
900
891
  throw new Error(`Snap "${snapId}" is not permitted to use "${permissionName}".`);
901
892
  }
902
- if (permissionName === SnapEndowments.Rpc) {
893
+ if (permissionName === SnapEndowments.Rpc || permissionName === SnapEndowments.Keyring) {
903
894
  const subject = this.messagingSystem.call('SubjectMetadataController:getSubjectMetadata', origin);
904
- const isSnap = subject?.subjectType === SubjectType.Snap;
905
895
  const permissions = this.messagingSystem.call('PermissionController:getPermissions', snapId);
906
- const rpcPermission = permissions?.[SnapEndowments.Rpc];
907
- assert(rpcPermission);
908
- const origins = getRpcCaveatOrigins(rpcPermission);
896
+ const handlerPermissions = permissions?.[permissionName];
897
+ assert(handlerPermissions);
898
+ const origins = permissionName === SnapEndowments.Rpc ? getRpcCaveatOrigins(handlerPermissions) : getKeyringCaveatOrigins(handlerPermissions);
909
899
  assert(origins);
910
- if (isSnap && !origins.snaps || !isSnap && !origins.dapps) {
911
- throw new Error(`Snap "${snapId}" is not permitted to handle JSON-RPC requests from "${origin}".`);
900
+ if (!isOriginAllowed(origins, subject?.subjectType ?? SubjectType.Website, origin)) {
901
+ throw new Error(`Snap "${snapId}" is not permitted to handle requests from "${origin}".`);
912
902
  }
913
903
  }
914
904
  const handler = await _class_private_method_get(this, _getRpcRequestHandler, getRpcRequestHandler).call(this, snapId);
@@ -927,17 +917,15 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
927
917
  super({
928
918
  messenger,
929
919
  metadata: {
930
- snapErrors: {
931
- persist: false,
932
- anonymous: false
933
- },
934
920
  snapStates: {
935
921
  persist: true,
936
922
  anonymous: false
937
923
  },
938
924
  snaps: {
939
925
  persist: (snaps)=>{
940
- return Object.values(snaps).map((snap)=>{
926
+ return Object.values(snaps)// We should not persist snaps that are in the installing state,
927
+ // since they haven't completed installation and would be unusable
928
+ .filter((snap)=>snap.status !== SnapStatus.Installing).map((snap)=>{
941
929
  return {
942
930
  ...snap,
943
931
  // At the time state is rehydrated, no snap will be running.
@@ -980,6 +968,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
980
968
  _class_private_method_init(this, _revokeAllSnapPermissions);
981
969
  _class_private_method_init(this, _createApproval);
982
970
  _class_private_method_init(this, _updateApproval);
971
+ _class_private_method_init(this, _resolveAllowlistVersion);
983
972
  /**
984
973
  * Returns a promise representing the complete installation of the requested snap.
985
974
  * If the snap is already being installed, the previously pending promise will be returned.
@@ -1005,8 +994,6 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1005
994
  /**
1006
995
  * Fetches the manifest and source code of a snap.
1007
996
  *
1008
- * This function is not hash private yet because of tests.
1009
- *
1010
997
  * @param snapId - The id of the Snap.
1011
998
  * @param location - Source from which snap will be fetched.
1012
999
  * @returns A tuple of the Snap manifest object and the Snap source code.
@@ -1215,13 +1202,13 @@ function registerMessageHandlers() {
1215
1202
  this.messagingSystem.registerActionHandler(`${controllerName}:remove`, async (...args)=>this.removeSnap(...args));
1216
1203
  this.messagingSystem.registerActionHandler(`${controllerName}:getPermitted`, (...args)=>this.getPermittedSnaps(...args));
1217
1204
  this.messagingSystem.registerActionHandler(`${controllerName}:install`, async (...args)=>this.installSnaps(...args));
1218
- this.messagingSystem.registerActionHandler(`${controllerName}:removeSnapError`, (...args)=>this.removeSnapError(...args));
1219
1205
  this.messagingSystem.registerActionHandler(`${controllerName}:getAll`, (...args)=>this.getAllSnaps(...args));
1220
1206
  this.messagingSystem.registerActionHandler(`${controllerName}:incrementActiveReferences`, (...args)=>this.incrementActiveReferences(...args));
1221
1207
  this.messagingSystem.registerActionHandler(`${controllerName}:decrementActiveReferences`, (...args)=>this.decrementActiveReferences(...args));
1222
1208
  this.messagingSystem.registerActionHandler(`${controllerName}:getRegistryMetadata`, async (...args)=>this.getRegistryMetadata(...args));
1223
1209
  this.messagingSystem.registerActionHandler(`${controllerName}:disconnectOrigin`, (...args)=>this.removeSnapFromSubject(...args));
1224
1210
  this.messagingSystem.registerActionHandler(`${controllerName}:revokeDynamicPermissions`, (...args)=>this.revokeDynamicSnapPermissions(...args));
1211
+ this.messagingSystem.registerActionHandler(`${controllerName}:getFile`, (...args)=>this.getSnapFile(...args));
1225
1212
  }
1226
1213
  function pollForLastRequestStatus() {
1227
1214
  _class_private_field_set(this, _timeoutForLastRequestStatus, setTimeout(()=>{
@@ -1265,7 +1252,7 @@ async function assertIsInstallAllowed(snapId, snapInfo) {
1265
1252
  if (result.status === SnapsRegistryStatus.Blocked) {
1266
1253
  throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": The version is blocked. ${result.reason?.explanation ?? ''}`);
1267
1254
  } else if (_class_private_field_get(this, _featureFlags).requireAllowlist && result.status !== SnapsRegistryStatus.Verified) {
1268
- throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": The snap is not on the allow list.`);
1255
+ throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": The snap is not on the allowlist.`);
1269
1256
  }
1270
1257
  }
1271
1258
  async function stopSnapsLastRequestPastMax() {
@@ -1331,6 +1318,9 @@ function updateApproval(id, requestState) {
1331
1318
  // Do nothing
1332
1319
  }
1333
1320
  }
1321
+ async function resolveAllowlistVersion(snapId, versionRange) {
1322
+ return await this.messagingSystem.call('SnapsRegistry:resolveVersion', snapId, versionRange);
1323
+ }
1334
1324
  async function add(args) {
1335
1325
  const { id: snapId, location, versionRange } = args;
1336
1326
  _class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snapId, {
@@ -1344,7 +1334,7 @@ async function add(args) {
1344
1334
  // to null in the authorize() method.
1345
1335
  runtime.installPromise = (async ()=>{
1346
1336
  const fetchedSnap = await _class_private_method_get(this, _fetchSnap, fetchSnap).call(this, snapId, location);
1347
- const manifest = fetchedSnap.manifest.result;
1337
+ const manifest = fetchedSnap.files.manifest.result;
1348
1338
  if (!satisfiesVersionRange(manifest.version, versionRange)) {
1349
1339
  throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${manifest.version}" which doesn't satisfy requested version range "${versionRange}".`);
1350
1340
  }
@@ -1414,15 +1404,16 @@ async function getEndowments(snapId) {
1414
1404
  return dedupedEndowments;
1415
1405
  }
1416
1406
  function set(args) {
1417
- const { id: snapId, origin, manifest, files, isUpdate = false } = args;
1407
+ const { id: snapId, origin, files, isUpdate = false } = args;
1408
+ const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles } = files;
1418
1409
  assertIsSnapManifest(manifest.result);
1419
1410
  const { version } = manifest.result;
1420
- const normalizedSourcePath = normalizeRelative(manifest.result.source.location.npm.filePath);
1421
- const { iconPath } = manifest.result.source.location.npm;
1422
- const normalizedIconPath = iconPath && normalizeRelative(iconPath);
1423
- const sourceCode = files.find((file)=>file.path === normalizedSourcePath)?.toString();
1424
- const svgIcon = normalizedIconPath ? files.find((file)=>file.path === normalizedIconPath) : undefined;
1411
+ const sourceCode = sourceCodeFile.toString();
1425
1412
  assert(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
1413
+ const auxiliaryFiles = rawAuxiliaryFiles.map((file)=>({
1414
+ path: file.path,
1415
+ value: file.toString('base64')
1416
+ }));
1426
1417
  const snapsState = this.state.snaps;
1427
1418
  const existingSnap = snapsState[snapId];
1428
1419
  const previousVersionHistory = existingSnap?.versionHistory ?? [];
@@ -1447,7 +1438,8 @@ function set(args) {
1447
1438
  status: _class_private_field_get(this, _statusMachine).config.initial,
1448
1439
  sourceCode,
1449
1440
  version,
1450
- versionHistory
1441
+ versionHistory,
1442
+ auxiliaryFiles
1451
1443
  };
1452
1444
  // If the snap was blocked, it isn't any longer
1453
1445
  delete snap.blockInformation;
@@ -1475,19 +1467,15 @@ async function fetchSnap(snapId, location) {
1475
1467
  const sourceCode = await location.fetch(manifest.result.source.location.npm.filePath);
1476
1468
  const { iconPath } = manifest.result.source.location.npm;
1477
1469
  const svgIcon = iconPath ? await location.fetch(iconPath) : undefined;
1478
- const files = [
1479
- sourceCode
1480
- ];
1481
- if (svgIcon) {
1482
- files.push(svgIcon);
1483
- }
1484
- validateFetchedSnap({
1470
+ const auxiliaryFiles = manifest.result.source.files ? await Promise.all(manifest.result.source.files.map(async (filePath)=>location.fetch(filePath))) : [];
1471
+ const files = {
1485
1472
  manifest,
1486
1473
  sourceCode,
1487
- svgIcon
1488
- });
1474
+ svgIcon,
1475
+ auxiliaryFiles
1476
+ };
1477
+ validateFetchedSnap(files);
1489
1478
  return {
1490
- manifest,
1491
1479
  files,
1492
1480
  location
1493
1481
  };
@@ -1556,8 +1544,11 @@ function getRpcRequestHandler(snapId) {
1556
1544
  _class_private_method_get(this, _recordSnapRpcRequestFinish, recordSnapRpcRequestFinish).call(this, snapId, request.id);
1557
1545
  return result;
1558
1546
  } catch (error) {
1559
- await this.stopSnap(snapId, SnapStatusEvents.Crash);
1560
- throw error;
1547
+ const [jsonRpcError, handled] = unwrapError(error);
1548
+ if (!handled) {
1549
+ await this.stopSnap(snapId, SnapStatusEvents.Crash);
1550
+ }
1551
+ throw jsonRpcError;
1561
1552
  }
1562
1553
  };
1563
1554
  runtime.rpcHandler = rpcHandler;