@metamask/snaps-controllers 3.4.0 → 3.4.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 CHANGED
@@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.4.1]
10
+ ### Changed
11
+ - Bump several MetaMask dependencies ([#1964](https://github.com/MetaMask/snaps/pull/1964), [#1961](https://github.com/MetaMask/snaps/pull/1961))
12
+
13
+ ### Fixed
14
+ - Fix a few issues with passing non-JSON-serializable values ([#1974](https://github.com/MetaMask/snaps/pull/1974))
15
+
9
16
  ## [3.4.0]
10
17
  ### Changed
11
18
  - Use `SubtleCrypto` for checksum calculation if available ([#1953](https://github.com/MetaMask/snaps/pull/1953))
@@ -119,7 +126,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
119
126
  - The version of the package no longer needs to match the version of all other
120
127
  MetaMask Snaps packages.
121
128
 
122
- [Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.4.0...HEAD
129
+ [Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.4.1...HEAD
130
+ [3.4.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.4.0...@metamask/snaps-controllers@3.4.1
123
131
  [3.4.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.3.0...@metamask/snaps-controllers@3.4.0
124
132
  [3.3.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.2.0...@metamask/snaps-controllers@3.3.0
125
133
  [3.2.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.1.1...@metamask/snaps-controllers@3.2.0
@@ -23,7 +23,7 @@ const _snapsutils = require("@metamask/snaps-utils");
23
23
  const _utils = require("@metamask/utils");
24
24
  const _jsonrpcmiddlewarestream = require("json-rpc-middleware-stream");
25
25
  const _nanoid = require("nanoid");
26
- const _stream = require("stream");
26
+ const _readablestream = require("readable-stream");
27
27
  const _logging = require("../logging");
28
28
  const _utils1 = require("../utils");
29
29
  function _check_private_redeclaration(obj, privateCollection) {
@@ -157,7 +157,7 @@ class AbstractExecutionService {
157
157
  const { streams, worker } = await this.initStreams(jobId);
158
158
  const rpcEngine = new _jsonrpcengine.JsonRpcEngine();
159
159
  const jsonRpcConnection = (0, _jsonrpcmiddlewarestream.createStreamMiddleware)();
160
- (0, _stream.pipeline)(jsonRpcConnection.stream, streams.command, jsonRpcConnection.stream, (error)=>{
160
+ (0, _readablestream.pipeline)(jsonRpcConnection.stream, streams.command, jsonRpcConnection.stream, (error)=>{
161
161
  if (error) {
162
162
  (0, _snapsutils.logError)(`Command stream failure.`, error);
163
163
  }
@@ -278,9 +278,7 @@ class AbstractExecutionService {
278
278
  }
279
279
  // Cannot be hash private yet because of tests.
280
280
  async command(jobId, message) {
281
- if (typeof message !== 'object') {
282
- throw new Error('Must send object.');
283
- }
281
+ (0, _utils.assertIsJsonRpcRequest)(message);
284
282
  const job = this.jobs.get(jobId);
285
283
  if (!job) {
286
284
  throw new Error(`Job with id "${jobId}" not found.`);
@@ -378,7 +376,7 @@ function removeSnapAndJobMapping(jobId) {
378
376
  }
379
377
  function setupMultiplex(connectionStream, streamName) {
380
378
  const mux = new _objectmultiplex.default();
381
- (0, _stream.pipeline)(connectionStream, mux, connectionStream, (error)=>{
379
+ (0, _readablestream.pipeline)(connectionStream, mux, connectionStream, (error)=>{
382
380
  if (error) {
383
381
  streamName ? (0, _snapsutils.logError)(`"${streamName}" stream failure.`, error) : (0, _snapsutils.logError)(error);
384
382
  }
@@ -1 +1 @@
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":["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","pipeline","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","JsonRpcError","code","data","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 { 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 {\n Duration,\n assertIsJsonRpcRequest,\n isJsonRpcNotification,\n isObject,\n} from '@metamask/utils';\nimport { createStreamMiddleware } from 'json-rpc-middleware-stream';\nimport { nanoid } from 'nanoid';\nimport { pipeline } from 'readable-stream';\nimport type { Duplex } from 'readable-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 assertIsJsonRpcRequest(message);\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":["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","pipeline","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","assertIsJsonRpcRequest","response","handle","JsonRpcError","code","data","rpcRequestHandler","constructor","Duration","Second","Map","workerId","rpcHook","origin","handler","request","target","removeSnapHooks","connectionStream","streamName","ObjectMultiplex"],"mappings":";;;;;;;;;;;IAuDsBA,wBAAwB;eAAxBA;;IAqZNC,cAAc;eAAdA;;;+BA5cc;wEACF;2BAEC;4BAEe;uBAYrC;yCACgC;wBAChB;gCACE;yBAGL;wBACqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQzC,MAAMC,iBAAiB;IA0BrB,6CAQA,6CAEA,6CAEA,0CAEA,mDAgUA,gDAIA,gDAkBA,8CAKA;AA5WK,MAAeF;IAmCpB;;;GAGC,GACD,AAAQG,0BAAgC;QACtC,yBAAA,IAAI,EAAEC,YAAUC,qBAAqB,CACnC,CAAC,EAAEH,eAAe,iBAAiB,CAAC,EACpC,OAAOI,QAAgBC,UACrB,IAAI,CAACC,gBAAgB,CAACF,QAAQC;QAGlC,yBAAA,IAAI,EAAEH,YAAUC,qBAAqB,CACnC,CAAC,EAAEH,eAAe,YAAY,CAAC,EAC/B,OAAOO,WAAgC,IAAI,CAACC,WAAW,CAACD;QAG1D,yBAAA,IAAI,EAAEL,YAAUC,qBAAqB,CACnC,CAAC,EAAEH,eAAe,cAAc,CAAC,EACjC,OAAOI,SAAmB,IAAI,CAACK,aAAa,CAACL;QAG/C,yBAAA,IAAI,EAAEF,YAAUC,qBAAqB,CACnC,CAAC,EAAEH,eAAe,kBAAkB,CAAC,EACrC,UAAY,IAAI,CAACU,iBAAiB;IAEtC;IAWA;;;;;;;GAOC,GACD,MAAaC,UAAUC,KAAa,EAAiB;QACnD,MAAMC,aAAa,IAAI,CAACC,IAAI,CAACC,GAAG,CAACH;QACjC,IAAI,CAACC,YAAY;YACf,MAAM,IAAIG,MAAM,CAAC,aAAa,EAAEJ,MAAM,YAAY,CAAC;QACrD;QAEA,0FAA0F;QAC1F,MAAMK,SAAS,MAAMC,IAAAA,mBAAW,EAC9B,IAAI,CAACC,OAAO,CAACP,OAAO;YAClBQ,SAAS;YACTC,QAAQ;YACRC,QAAQ,EAAE;YACVC,IAAIC,IAAAA,cAAM;QACZ,6BACA,IAAI,EAAEC;QAGR,IAAIR,WAAWS,mBAAW,IAAIT,WAAW,MAAM;YAC7C,mGAAmG;YACnG,qCAAqC;YACrC,8HAA8H;YAC9H,kIAAkI;YAClI,4BAA4B;YAC5BU,IAAAA,oBAAQ,EAAC,CAAC,KAAK,EAAEf,MAAM,iCAAiC,CAAC,EAAEK;QAC7D;QAEAW,OAAOC,MAAM,CAAChB,WAAWiB,OAAO,EAAEC,OAAO,CAAC,CAACC;YACzC,IAAI;gBACF,CAACA,OAAOC,SAAS,IAAID,OAAOE,OAAO;gBACnCF,OAAOG,kBAAkB;YAC3B,EAAE,OAAOC,OAAO;gBACdT,IAAAA,oBAAQ,EAAC,iCAAiCS;YAC5C;QACF;QAEA,IAAI,CAACC,YAAY,CAACxB;QAElB,0BAAA,IAAI,EAAEyB,0BAAAA,8BAAN,IAAI,EAA0B1B;QAC9B,IAAI,CAACE,IAAI,CAACyB,MAAM,CAAC3B;QACjB4B,IAAAA,YAAG,EAAC,CAAC,KAAK,EAAE5B,MAAM,aAAa,CAAC;IAClC;IAEA;;;;;;GAMC,GACD,MAAgB6B,UAAoC;QAClD,MAAM7B,QAAQY,IAAAA,cAAM;QACpB,MAAM,EAAEM,OAAO,EAAEY,MAAM,EAAE,GAAG,MAAM,IAAI,CAACC,WAAW,CAAC/B;QACnD,MAAMgC,YAAY,IAAIC,4BAAa;QAEnC,MAAMC,oBAAoBC,IAAAA,+CAAsB;QAEhDC,IAAAA,wBAAQ,EACNF,kBAAkBd,MAAM,EACxBF,QAAQX,OAAO,EACf2B,kBAAkBd,MAAM,EACxB,CAACI;YACC,IAAIA,OAAO;gBACTT,IAAAA,oBAAQ,EAAC,CAAC,uBAAuB,CAAC,EAAES;YACtC;QACF;QAGFQ,UAAUK,IAAI,CAACH,kBAAkBI,UAAU;QAE3C,MAAMC,cAAc;YAClB5B,IAAIX;YACJkB;YACAc;YACAF;QACF;QACA,IAAI,CAAC5B,IAAI,CAACsC,GAAG,CAACxC,OAAOuC;QAErB,OAAOA;IACT;IAEA;;;;;;;GAOC,GACD,MAAgBR,YACd/B,KAAa,EACyC;QACtD,MAAM,EAAE8B,MAAM,EAAEV,QAAQqB,SAAS,EAAE,GAAG,MAAM,IAAI,CAACC,aAAa,CAAC1C;QAC/D,MAAM2C,MAAMxD,eAAesD,WAAW,CAAC,MAAM,EAAEzC,MAAM,CAAC,CAAC;QACvD,MAAM4C,gBAAgBD,IAAIE,YAAY,CAACC,6BAAiB,CAACC,OAAO;QAEhE,4FAA4F;QAC5F,gDAAgD;QAChD,MAAMC,sBAAsB,CAC1BC;YAIA,IAAI,CAACC,IAAAA,4BAAqB,EAACD,UAAU;gBACnC;YACF;YAEA,oEAAoE;YACpE,MAAMzD,SAAS,yBAAA,IAAI,EAAE2D,eAAahD,GAAG,CAACH;YACtC,IAAIiD,QAAQxC,MAAM,KAAK,mBAAmB;gBACxC,yBAAA,IAAI,EAAEnB,YAAU8D,OAAO,CAAC,oCAAoC5D;YAC9D,OAAO,IAAIyD,QAAQxC,MAAM,KAAK,oBAAoB;gBAChD,yBAAA,IAAI,EAAEnB,YAAU8D,OAAO,CAAC,qCAAqC5D;YAC/D,OAAO,IAAIyD,QAAQxC,MAAM,KAAK,kBAAkB;gBAC9C,IAAI4C,IAAAA,eAAQ,EAACJ,QAAQvC,MAAM,KAAKuC,QAAQvC,MAAM,CAACc,KAAK,EAAE;oBACpD,yBAAA,IAAI,EAAElC,YAAU8D,OAAO,CACrB,mCACA5D,QACAyD,QAAQvC,MAAM,CAACc,KAAK;oBAEtBoB,cAAcU,cAAc,CAAC,QAAQN;gBACvC,OAAO;oBACLjC,IAAAA,oBAAQ,EACN,IAAIX,MACF,CAAC,oBAAoB,EAAE6C,QAAQxC,MAAM,CAAC,8BAA8B,CAAC;gBAG3E;YACF,OAAO;gBACLM,IAAAA,oBAAQ,EACN,IAAIX,MACF,CAAC,iDAAiD,EAAE6C,QAAQxC,MAAM,CAAC,EAAE,CAAC;YAG5E;QACF;QAEAmC,cAAcW,EAAE,CAAC,QAAQP;QACzB,MAAMQ,YAAYb,IAAIE,YAAY,CAACC,6BAAiB,CAACW,QAAQ;QAE7D,iCAAiC;QACjC,OAAO;YACLvC,SAAS;gBACPX,SAASqC;gBACTc,KAAKF;gBACL,gEAAgE;gBAChEG,aAAalB;YACf;YACAX;QACF;IACF;IAYA;;;;;;GAMC,GACD,MAAMjC,cAAcL,MAAc,EAAE;QAClC,MAAMQ,QAAQ,yBAAA,IAAI,EAAE4D,eAAazD,GAAG,CAACX;QACrC,IAAIQ,OAAO;YACT,MAAM,IAAI,CAACD,SAAS,CAACC;QACvB;IACF;IAEA,MAAMF,oBAAoB;QACxB,MAAM+D,QAAQC,GAAG,CACf;eAAI,IAAI,CAAC5D,IAAI,CAAC6D,IAAI;SAAG,CAACC,GAAG,CAAC,OAAOhE,QAAU,IAAI,CAACD,SAAS,CAACC;QAE5D,yBAAA,IAAI,EAAEiE,eAAaC,KAAK;IAC1B;IAEA;;;;;GAKC,GACD,AAAQC,qBAAqB3E,MAAc,EAAE;QAC3C,OAAO,yBAAA,IAAI,EAAEyE,eAAa9D,GAAG,CAACX;IAChC;IAEA;;;;;;;;GAQC,GACD,MAAMI,YAAYD,QAA2B,EAAmB;QAC9D,IAAI,yBAAA,IAAI,EAAEiE,eAAaQ,GAAG,CAACzE,SAASH,MAAM,GAAG;YAC3C,MAAM,IAAIY,MAAM,CAAC,MAAM,EAAET,SAASH,MAAM,CAAC,4BAA4B,CAAC;QACxE;QAEA,MAAM6E,MAAM,MAAM,IAAI,CAACxC,OAAO;QAC9B,0BAAA,IAAI,EAAEyC,gBAAAA,oBAAN,IAAI,EAAgB3E,SAASH,MAAM,EAAE6E,IAAI1D,EAAE;QAE3C,+CAA+C;QAC/C,MAAM,IAAI,CAACJ,OAAO,CAAC8D,IAAI1D,EAAE,EAAE;YACzBH,SAAS;YACTC,QAAQ;YACRE,IAAIC,IAAAA,cAAM;QACZ;QAEA,MAAM4C,YAAYa,IAAInD,OAAO,CAACwC,GAAG;QAEjC,IAAI,CAACa,iBAAiB,CAAC5E,SAASH,MAAM,EAAEgE;QAExC,MAAMnD,SAAS,MAAM,IAAI,CAACE,OAAO,CAAC8D,IAAI1D,EAAE,EAAE;YACxCH,SAAS;YACTC,QAAQ;YACRC,QAAQf;YACRgB,IAAIC,IAAAA,cAAM;QACZ;QACA,0BAAA,IAAI,EAAE4D,kBAAAA,sBAAN,IAAI,EAAkB7E,SAASH,MAAM,EAAE6E,IAAI1D,EAAE;QAC7C,OAAON;IACT;IAEA,+CAA+C;IAC/C,MAAcE,QACZP,KAAa,EACbiD,OAAuB,EACI;QAC3BwB,IAAAA,6BAAsB,EAACxB;QAEvB,MAAMoB,MAAM,IAAI,CAACnE,IAAI,CAACC,GAAG,CAACH;QAC1B,IAAI,CAACqE,KAAK;YACR,MAAM,IAAIjE,MAAM,CAAC,aAAa,EAAEJ,MAAM,YAAY,CAAC;QACrD;QAEA4B,IAAAA,YAAG,EAAC,2BAA2BqB;QAC/B,MAAMyB,WAAyC,MAAML,IAAIrC,SAAS,CAAC2C,MAAM,CACvE1B;QAGF,IAAIyB,SAASlD,KAAK,EAAE;YAClB,MAAM,IAAIoD,uBAAY,CACpBF,SAASlD,KAAK,CAACqD,IAAI,EACnBH,SAASlD,KAAK,CAACyB,OAAO,EACtByB,SAASlD,KAAK,CAACsD,IAAI;QAEvB;QAEA,OAAOJ,SAASrE,MAAM;IACxB;IAwCA;;;;;;GAMC,GACD,MAAaX,iBACXF,MAAc,EACdC,OAAwB,EACN;QAClB,MAAMsF,oBAAoB,IAAI,CAACZ,oBAAoB,CAAC3E;QAEpD,IAAI,CAACuF,mBAAmB;YACtB,MAAM,IAAI3E,MACR,CAAC,iEAAiE,EAAEZ,OAAO,EAAE,CAAC;QAElF;QAEA,OAAOuF,kBAAkBtF;IAC3B;IAxXAuF,YAAY,EACVT,iBAAiB,EACjBjF,SAAS,EACTuB,qBAAqBoE,eAAQ,CAACC,MAAM,EACf,CAAE;QA0TzB,iCAAA;QAIA,iCAAA;QAkBA,iCAAA;QAKA,iCAAA;QAzWA,gCAAA;;mBAAA,KAAA;;QAEA,+CAA+C;QAC/C,uBAAUhF,QAAV,KAAA;QAEA,+CAA+C;QAC/C,uBAAiBqE,qBAAjB,KAAA;QAEA,gCAAA;;mBAAA,KAAA;;QAEA,gCAAA;;mBAAA,KAAA;;QAEA,gCAAA;;mBAAA,KAAA;;QAEA,gCAAA;;mBAAA,KAAA;;uCAOQN,eAAe,IAAIkB;QACzB,IAAI,CAACjF,IAAI,GAAG,IAAIiF;QAChB,IAAI,CAACZ,iBAAiB,GAAGA;uCACnBX,eAAe,IAAIuB;uCACnBhC,eAAe,IAAIgC;uCACnB7F,YAAYA;uCACZuB,qBAAqBA;QAE3B,IAAI,CAACxB,uBAAuB;IAC9B;AA2WF;AA3DE,SAAA,gBAAiBG,MAAc;IAC7B,yBAAA,IAAI,EAAEyE,eAAatC,MAAM,CAACnC;AAC5B;AAEA,SAAA,gBAAiBA,MAAc,EAAE4F,QAAgB;IAC/C,MAAMC,UAAU,OAAO,EAAEC,MAAM,EAAEC,OAAO,EAAEC,OAAO,EAAmB;QAClE,OAAO,MAAM,IAAI,CAACjF,OAAO,CAAC6E,UAAU;YAClCzE,IAAIC,IAAAA,cAAM;YACVJ,SAAS;YACTC,QAAQ;YACRC,QAAQ;gBACN4E;gBACAC;gBACAC,SAASA;gBACTC,QAAQjG;YACV;QACF;IACF;IAEA,yBAAA,IAAI,EAAEyE,eAAazB,GAAG,CAAChD,QAAQ6F;AACjC;AAEA,SAAA,cAAe7F,MAAc,EAAEQ,KAAa;IAC1C,yBAAA,IAAI,EAAE4D,eAAapB,GAAG,CAAChD,QAAQQ;IAC/B,yBAAA,IAAI,EAAEmD,eAAaX,GAAG,CAACxC,OAAOR;AAChC;AAEA,SAAA,wBAAyBQ,KAAa;IACpC,MAAMR,SAAS,yBAAA,IAAI,EAAE2D,eAAahD,GAAG,CAACH;IACtC,IAAI,CAACR,QAAQ;QACX,MAAM,IAAIY,MAAM,CAAC,MAAM,EAAEJ,MAAM,qBAAqB,CAAC;IACvD;IAEA,yBAAA,IAAI,EAAEmD,eAAaxB,MAAM,CAAC3B;IAC1B,yBAAA,IAAI,EAAE4D,eAAajC,MAAM,CAACnC;IAC1B,0BAAA,IAAI,EAAEkG,kBAAAA,sBAAN,IAAI,EAAkBlG;AACxB;AAgCK,SAASL,eACdwG,gBAAwB,EACxBC,UAAkB;IAElB,MAAMjD,MAAM,IAAIkD,wBAAe;IAC/BzD,IAAAA,wBAAQ,EAACuD,kBAAkBhD,KAAKgD,kBAAkB,CAACnE;QACjD,IAAIA,OAAO;YACToE,aACI7E,IAAAA,oBAAQ,EAAC,CAAC,CAAC,EAAE6E,WAAW,iBAAiB,CAAC,EAAEpE,SAC5CT,IAAAA,oBAAQ,EAACS;QACf;IACF;IACA,OAAOmB;AACT"}
@@ -24,8 +24,8 @@ const _utils = require("@metamask/utils");
24
24
  const _concatstream = /*#__PURE__*/ _interop_require_default(require("concat-stream"));
25
25
  const _getnpmtarballurl = /*#__PURE__*/ _interop_require_default(require("get-npm-tarball-url"));
26
26
  const _gunzipmaybe = /*#__PURE__*/ _interop_require_default(require("gunzip-maybe"));
27
+ const _readablestream = require("readable-stream");
27
28
  const _readablewebtonodestream = require("readable-web-to-node-stream");
28
- const _stream = require("stream");
29
29
  const _tarstream = require("tar-stream");
30
30
  function _check_private_redeclaration(obj, privateCollection) {
31
31
  if (privateCollection.has(obj)) {
@@ -156,7 +156,7 @@ async function lazyInit() {
156
156
  // We would need to replace tar-stream package because it requires immediate consumption of streams.
157
157
  await new Promise((resolve, reject)=>{
158
158
  this.files = new Map();
159
- (0, _stream.pipeline)(getNodeStream(tarballResponse), // The "gz" in "tgz" stands for "gzip". The tarball needs to be decompressed
159
+ (0, _readablestream.pipeline)(getNodeStream(tarballResponse), // The "gz" in "tgz" stands for "gzip". The tarball needs to be decompressed
160
160
  // before we can actually grab any files from it.
161
161
  // To prevent recursion-based zip bombs, we set a maximum recursion depth of 1.
162
162
  (0, _gunzipmaybe.default)(1), createTarballStream(`${canonicalBase}/${this.meta.packageName}/`, this.files), (error)=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/snaps/location/npm.ts"],"sourcesContent":["import type { SnapManifest } from '@metamask/snaps-utils';\nimport {\n createSnapManifest,\n DEFAULT_REQUESTED_SNAP_VERSION,\n getTargetVersion,\n isValidUrl,\n NpmSnapIdStruct,\n VirtualFile,\n normalizeRelative,\n parseJson,\n} from '@metamask/snaps-utils';\nimport type { SemVerRange, SemVerVersion } from '@metamask/utils';\nimport {\n assert,\n assertIsSemVerVersion,\n assertStruct,\n isObject,\n isValidSemVerVersion,\n} from '@metamask/utils';\nimport concat from 'concat-stream';\nimport getNpmTarballUrl from 'get-npm-tarball-url';\nimport createGunzipStream from 'gunzip-maybe';\nimport { ReadableWebToNodeStream } from 'readable-web-to-node-stream';\nimport { pipeline } from 'stream';\nimport type { Readable, Writable } from 'stream';\nimport { extract as tarExtract } from 'tar-stream';\n\nimport type { DetectSnapLocationOptions, SnapLocation } from './location';\n\nexport const DEFAULT_NPM_REGISTRY = new URL('https://registry.npmjs.org');\n\ninterface NpmMeta {\n registry: URL;\n packageName: string;\n requestedRange: SemVerRange;\n version?: string;\n fetch: typeof fetch;\n resolveVersion: (range: SemVerRange) => Promise<SemVerRange>;\n}\nexport interface NpmOptions {\n /**\n * @default DEFAULT_REQUESTED_SNAP_VERSION\n */\n versionRange?: SemVerRange;\n /**\n * Whether to allow custom NPM registries outside of {@link DEFAULT_NPM_REGISTRY}.\n *\n * @default false\n */\n allowCustomRegistries?: boolean;\n}\n\nexport class NpmLocation implements SnapLocation {\n private readonly meta: NpmMeta;\n\n private validatedManifest?: VirtualFile<SnapManifest>;\n\n private files?: Map<string, VirtualFile>;\n\n constructor(url: URL, opts: DetectSnapLocationOptions = {}) {\n const allowCustomRegistries = opts.allowCustomRegistries ?? false;\n const fetchFunction = opts.fetch ?? globalThis.fetch.bind(globalThis);\n const requestedRange = opts.versionRange ?? DEFAULT_REQUESTED_SNAP_VERSION;\n const defaultResolve = async (range: SemVerRange) => range;\n const resolveVersion = opts.resolveVersion ?? defaultResolve;\n\n assertStruct(url.toString(), NpmSnapIdStruct, 'Invalid Snap Id: ');\n\n let registry: string | URL;\n if (\n url.host === '' &&\n url.port === '' &&\n url.username === '' &&\n url.password === ''\n ) {\n registry = DEFAULT_NPM_REGISTRY;\n } else {\n registry = 'https://';\n if (url.username) {\n registry += url.username;\n if (url.password) {\n registry += `:${url.password}`;\n }\n registry += '@';\n }\n registry += url.host;\n registry = new URL(registry);\n assert(\n allowCustomRegistries,\n new TypeError(\n `Custom NPM registries are disabled, tried to use \"${registry.toString()}\".`,\n ),\n );\n }\n\n assert(\n registry.pathname === '/' &&\n registry.search === '' &&\n registry.hash === '',\n );\n\n assert(\n url.pathname !== '' && url.pathname !== '/',\n new TypeError('The package name in NPM location is empty.'),\n );\n let packageName = url.pathname;\n if (packageName.startsWith('/')) {\n packageName = packageName.slice(1);\n }\n\n this.meta = {\n requestedRange,\n registry,\n packageName,\n fetch: fetchFunction,\n resolveVersion,\n };\n }\n\n async manifest(): Promise<VirtualFile<SnapManifest>> {\n if (this.validatedManifest) {\n return this.validatedManifest.clone();\n }\n\n const vfile = await this.fetch('snap.manifest.json');\n const result = parseJson(vfile.toString());\n vfile.result = createSnapManifest(result);\n this.validatedManifest = vfile as VirtualFile<SnapManifest>;\n\n return this.manifest();\n }\n\n async fetch(path: string): Promise<VirtualFile> {\n const relativePath = normalizeRelative(path);\n if (!this.files) {\n await this.#lazyInit();\n assert(this.files !== undefined);\n }\n const vfile = this.files.get(relativePath);\n assert(\n vfile !== undefined,\n new TypeError(`File \"${path}\" not found in package.`),\n );\n return vfile.clone();\n }\n\n get packageName(): string {\n return this.meta.packageName;\n }\n\n get version(): string {\n assert(\n this.meta.version !== undefined,\n 'Tried to access version without first fetching NPM package.',\n );\n return this.meta.version;\n }\n\n get registry(): URL {\n return this.meta.registry;\n }\n\n get versionRange(): SemVerRange {\n return this.meta.requestedRange;\n }\n\n async #lazyInit() {\n assert(this.files === undefined);\n const resolvedVersion = await this.meta.resolveVersion(\n this.meta.requestedRange,\n );\n const [tarballResponse, actualVersion] = await fetchNpmTarball(\n this.meta.packageName,\n resolvedVersion,\n this.meta.registry,\n this.meta.fetch,\n );\n this.meta.version = actualVersion;\n\n let canonicalBase = 'npm://';\n if (this.meta.registry.username !== '') {\n canonicalBase += this.meta.registry.username;\n if (this.meta.registry.password !== '') {\n canonicalBase += `:${this.meta.registry.password}`;\n }\n canonicalBase += '@';\n }\n canonicalBase += this.meta.registry.host;\n\n // TODO(ritave): Lazily extract files instead of up-front extracting all of them\n // We would need to replace tar-stream package because it requires immediate consumption of streams.\n await new Promise<void>((resolve, reject) => {\n this.files = new Map();\n pipeline(\n getNodeStream(tarballResponse),\n // The \"gz\" in \"tgz\" stands for \"gzip\". The tarball needs to be decompressed\n // before we can actually grab any files from it.\n // To prevent recursion-based zip bombs, we set a maximum recursion depth of 1.\n createGunzipStream(1),\n createTarballStream(\n `${canonicalBase}/${this.meta.packageName}/`,\n this.files,\n ),\n (error) => {\n error ? reject(error) : resolve();\n },\n );\n });\n }\n}\n\n// Safety limit for tarballs, 250 MB in bytes\nconst TARBALL_SIZE_SAFETY_LIMIT = 262144000;\n\n// Incomplete type\nexport type PartialNpmMetadata = {\n versions: Record<string, { dist: { tarball: string } }>;\n};\n\n/**\n * Fetches the NPM metadata of the specified package from\n * the public npm registry.\n *\n * @param packageName - The name of the package whose metadata to fetch.\n * @param registryUrl - The URL of the npm registry to fetch the metadata from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns The NPM metadata object.\n * @throws If fetching the metadata fails.\n */\nexport async function fetchNpmMetadata(\n packageName: string,\n registryUrl: URL | string,\n fetchFunction: typeof fetch,\n): Promise<PartialNpmMetadata> {\n const packageResponse = await fetchFunction(\n new URL(packageName, registryUrl).toString(),\n );\n if (!packageResponse.ok) {\n throw new Error(\n `Failed to fetch NPM registry entry. Status code: ${packageResponse.status}.`,\n );\n }\n const packageMetadata = await packageResponse.json();\n\n if (!isObject(packageMetadata)) {\n throw new Error(\n `Failed to fetch package \"${packageName}\" metadata from npm.`,\n );\n }\n\n return packageMetadata as PartialNpmMetadata;\n}\n\n/**\n * Resolves a version range to a version using the NPM registry.\n *\n * Unless the version range is already a version, then the NPM registry is skipped.\n *\n * @param packageName - The name of the package whose metadata to fetch.\n * @param versionRange - The version range of the package.\n * @param registryUrl - The URL of the npm registry to fetch the metadata from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns An object containing the resolved version and a URL for its tarball.\n * @throws If fetching the metadata fails.\n */\nasync function resolveNpmVersion(\n packageName: string,\n versionRange: SemVerRange,\n registryUrl: URL,\n fetchFunction: typeof fetch,\n): Promise<{ tarballURL: string; targetVersion: SemVerVersion }> {\n // If the version range is already a static version we don't need to look for the metadata.\n if (\n registryUrl.toString() === DEFAULT_NPM_REGISTRY.toString() &&\n isValidSemVerVersion(versionRange)\n ) {\n return {\n tarballURL: getNpmTarballUrl(packageName, versionRange),\n targetVersion: versionRange,\n };\n }\n\n const packageMetadata = await fetchNpmMetadata(\n packageName,\n registryUrl,\n fetchFunction,\n );\n\n const versions = Object.keys(packageMetadata?.versions ?? {}).map(\n (version) => {\n assertIsSemVerVersion(version);\n return version;\n },\n );\n\n const targetVersion = getTargetVersion(versions, versionRange);\n\n if (targetVersion === null) {\n throw new Error(\n `Failed to find a matching version in npm metadata for package \"${packageName}\" and requested semver range \"${versionRange}\".`,\n );\n }\n\n const tarballURL = packageMetadata?.versions?.[targetVersion]?.dist?.tarball;\n\n return { tarballURL, targetVersion };\n}\n\n/**\n * Fetches the tarball (`.tgz` file) of the specified package and version from\n * the public npm registry.\n *\n * @param packageName - The name of the package whose tarball to fetch.\n * @param versionRange - The SemVer range of the package to fetch. The highest\n * version satisfying the range will be fetched.\n * @param registryUrl - The URL of the npm registry to fetch the tarball from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns A tuple of the {@link Response} for the package tarball and the\n * actual version of the package.\n * @throws If fetching the tarball fails.\n */\nasync function fetchNpmTarball(\n packageName: string,\n versionRange: SemVerRange,\n registryUrl: URL,\n fetchFunction: typeof fetch,\n): Promise<[ReadableStream, SemVerVersion]> {\n const { tarballURL, targetVersion } = await resolveNpmVersion(\n packageName,\n versionRange,\n registryUrl,\n fetchFunction,\n );\n\n if (!isValidUrl(tarballURL) || !tarballURL.toString().endsWith('.tgz')) {\n throw new Error(\n `Failed to find valid tarball URL in NPM metadata for package \"${packageName}\".`,\n );\n }\n\n // Override the tarball hostname/protocol with registryUrl hostname/protocol\n const newRegistryUrl = new URL(registryUrl);\n const newTarballUrl = new URL(tarballURL);\n newTarballUrl.hostname = newRegistryUrl.hostname;\n newTarballUrl.protocol = newRegistryUrl.protocol;\n\n // Perform a raw fetch because we want the Response object itself.\n const tarballResponse = await fetchFunction(newTarballUrl.toString());\n if (!tarballResponse.ok || !tarballResponse.body) {\n throw new Error(`Failed to fetch tarball for package \"${packageName}\".`);\n }\n // We assume that NPM is a good actor and provides us with a valid `content-length` header.\n const tarballSizeString = tarballResponse.headers.get('content-length');\n assert(tarballSizeString, 'Snap tarball has invalid content-length');\n const tarballSize = parseInt(tarballSizeString, 10);\n assert(\n tarballSize <= TARBALL_SIZE_SAFETY_LIMIT,\n 'Snap tarball exceeds size limit',\n );\n return [tarballResponse.body, targetVersion];\n}\n\n/**\n * The paths of files within npm tarballs appear to always be prefixed with\n * \"package/\".\n */\nconst NPM_TARBALL_PATH_PREFIX = /^package\\//u;\n\n/**\n * Converts a {@link ReadableStream} to a Node.js {@link Readable}\n * stream. Returns the stream directly if it is already a Node.js stream.\n * We can't use the native Web {@link ReadableStream} directly because the\n * other stream libraries we use expect Node.js streams.\n *\n * @param stream - The stream to convert.\n * @returns The given stream as a Node.js Readable stream.\n */\nfunction getNodeStream(stream: ReadableStream): Readable {\n if (typeof stream.getReader !== 'function') {\n return stream as unknown as Readable;\n }\n\n return new ReadableWebToNodeStream(stream);\n}\n\n/**\n * Creates a `tar-stream` that will get the necessary files from an npm Snap\n * package tarball (`.tgz` file).\n *\n * @param canonicalBase - A base URI as specified in {@link https://github.com/MetaMask/SIPs/blob/main/SIPS/sip-8.md SIP-8}. Starting with 'npm:'. Will be used for canonicalPath vfile argument.\n * @param files - An object to write target file contents to.\n * @returns The {@link Writable} tarball extraction stream.\n */\nfunction createTarballStream(\n canonicalBase: string,\n files: Map<string, VirtualFile>,\n): Writable {\n assert(\n canonicalBase.endsWith('/'),\n \"Base needs to end with '/' for relative paths to be added as children instead of siblings.\",\n );\n\n assert(\n canonicalBase.startsWith('npm:'),\n 'Protocol mismatch, expected \"npm:\".',\n );\n // `tar-stream` is pretty old-school, so we create it first and then\n // instrument it by adding event listeners.\n const extractStream = tarExtract();\n\n let totalSize = 0;\n\n // \"entry\" is fired for every discreet entity in the tarball. This includes\n // files and folders.\n extractStream.on('entry', (header, entryStream, next) => {\n const { name: headerName, type: headerType } = header;\n if (headerType === 'file') {\n // The name is a path if the header type is \"file\".\n const path = headerName.replace(NPM_TARBALL_PATH_PREFIX, '');\n return entryStream.pipe(\n concat({ encoding: 'uint8array' }, (data) => {\n try {\n totalSize += data.byteLength;\n // To prevent zip bombs, we set a safety limit for the total size of tarballs.\n assert(\n totalSize < TARBALL_SIZE_SAFETY_LIMIT,\n `Snap tarball exceeds limit of ${TARBALL_SIZE_SAFETY_LIMIT} bytes.`,\n );\n const vfile = new VirtualFile({\n value: data,\n path,\n data: {\n canonicalPath: new URL(path, canonicalBase).toString(),\n },\n });\n // We disallow files having identical paths as it may confuse our checksum calculations.\n assert(\n !files.has(path),\n 'Malformed tarball, multiple files with the same path.',\n );\n files.set(path, vfile);\n return next();\n } catch (error) {\n return extractStream.destroy(error);\n }\n }),\n );\n }\n\n // If we get here, the entry is not a file, and we want to ignore. The entry\n // stream must be drained, or the extractStream will stop reading. This is\n // effectively a no-op for the current entry.\n entryStream.on('end', () => next());\n return entryStream.resume();\n });\n return extractStream;\n}\n"],"names":["DEFAULT_NPM_REGISTRY","NpmLocation","fetchNpmMetadata","URL","manifest","validatedManifest","clone","vfile","fetch","result","parseJson","toString","createSnapManifest","path","relativePath","normalizeRelative","files","lazyInit","assert","undefined","get","TypeError","packageName","meta","version","registry","versionRange","requestedRange","constructor","url","opts","allowCustomRegistries","fetchFunction","globalThis","bind","DEFAULT_REQUESTED_SNAP_VERSION","defaultResolve","range","resolveVersion","assertStruct","NpmSnapIdStruct","host","port","username","password","pathname","search","hash","startsWith","slice","resolvedVersion","tarballResponse","actualVersion","fetchNpmTarball","canonicalBase","Promise","resolve","reject","Map","pipeline","getNodeStream","createGunzipStream","createTarballStream","error","TARBALL_SIZE_SAFETY_LIMIT","registryUrl","packageResponse","ok","Error","status","packageMetadata","json","isObject","resolveNpmVersion","isValidSemVerVersion","tarballURL","getNpmTarballUrl","targetVersion","versions","Object","keys","map","assertIsSemVerVersion","getTargetVersion","dist","tarball","isValidUrl","endsWith","newRegistryUrl","newTarballUrl","hostname","protocol","body","tarballSizeString","headers","tarballSize","parseInt","NPM_TARBALL_PATH_PREFIX","stream","getReader","ReadableWebToNodeStream","extractStream","tarExtract","totalSize","on","header","entryStream","next","name","headerName","type","headerType","replace","pipe","concat","encoding","data","byteLength","VirtualFile","value","canonicalPath","has","set","destroy","resume"],"mappings":";;;;;;;;;;;IA6BaA,oBAAoB;eAApBA;;IAuBAC,WAAW;eAAXA;;IAkLSC,gBAAgB;eAAhBA;;;4BA5Nf;uBAQA;qEACY;yEACU;oEACE;yCACS;wBACf;2BAEa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAI/B,MAAMF,uBAAuB,IAAIG,IAAI;IAyIpC;AAlHD,MAAMF;IAmEX,MAAMG,WAA+C;QACnD,IAAI,IAAI,CAACC,iBAAiB,EAAE;YAC1B,OAAO,IAAI,CAACA,iBAAiB,CAACC,KAAK;QACrC;QAEA,MAAMC,QAAQ,MAAM,IAAI,CAACC,KAAK,CAAC;QAC/B,MAAMC,SAASC,IAAAA,qBAAS,EAACH,MAAMI,QAAQ;QACvCJ,MAAME,MAAM,GAAGG,IAAAA,8BAAkB,EAACH;QAClC,IAAI,CAACJ,iBAAiB,GAAGE;QAEzB,OAAO,IAAI,CAACH,QAAQ;IACtB;IAEA,MAAMI,MAAMK,IAAY,EAAwB;QAC9C,MAAMC,eAAeC,IAAAA,6BAAiB,EAACF;QACvC,IAAI,CAAC,IAAI,CAACG,KAAK,EAAE;YACf,MAAM,0BAAA,IAAI,EAAEC,WAAAA,eAAN,IAAI;YACVC,IAAAA,aAAM,EAAC,IAAI,CAACF,KAAK,KAAKG;QACxB;QACA,MAAMZ,QAAQ,IAAI,CAACS,KAAK,CAACI,GAAG,CAACN;QAC7BI,IAAAA,aAAM,EACJX,UAAUY,WACV,IAAIE,UAAU,CAAC,MAAM,EAAER,KAAK,uBAAuB,CAAC;QAEtD,OAAON,MAAMD,KAAK;IACpB;IAEA,IAAIgB,cAAsB;QACxB,OAAO,IAAI,CAACC,IAAI,CAACD,WAAW;IAC9B;IAEA,IAAIE,UAAkB;QACpBN,IAAAA,aAAM,EACJ,IAAI,CAACK,IAAI,CAACC,OAAO,KAAKL,WACtB;QAEF,OAAO,IAAI,CAACI,IAAI,CAACC,OAAO;IAC1B;IAEA,IAAIC,WAAgB;QAClB,OAAO,IAAI,CAACF,IAAI,CAACE,QAAQ;IAC3B;IAEA,IAAIC,eAA4B;QAC9B,OAAO,IAAI,CAACH,IAAI,CAACI,cAAc;IACjC;IAzGAC,YAAYC,GAAQ,EAAEC,OAAkC,CAAC,CAAC,CAAE;QA2G5D,iCAAM;QAjHN,uBAAiBP,QAAjB,KAAA;QAEA,uBAAQlB,qBAAR,KAAA;QAEA,uBAAQW,SAAR,KAAA;QAGE,MAAMe,wBAAwBD,KAAKC,qBAAqB,IAAI;QAC5D,MAAMC,gBAAgBF,KAAKtB,KAAK,IAAIyB,WAAWzB,KAAK,CAAC0B,IAAI,CAACD;QAC1D,MAAMN,iBAAiBG,KAAKJ,YAAY,IAAIS,0CAA8B;QAC1E,MAAMC,iBAAiB,OAAOC,QAAuBA;QACrD,MAAMC,iBAAiBR,KAAKQ,cAAc,IAAIF;QAE9CG,IAAAA,mBAAY,EAACV,IAAIlB,QAAQ,IAAI6B,2BAAe,EAAE;QAE9C,IAAIf;QACJ,IACEI,IAAIY,IAAI,KAAK,MACbZ,IAAIa,IAAI,KAAK,MACbb,IAAIc,QAAQ,KAAK,MACjBd,IAAIe,QAAQ,KAAK,IACjB;YACAnB,WAAWzB;QACb,OAAO;YACLyB,WAAW;YACX,IAAII,IAAIc,QAAQ,EAAE;gBAChBlB,YAAYI,IAAIc,QAAQ;gBACxB,IAAId,IAAIe,QAAQ,EAAE;oBAChBnB,YAAY,CAAC,CAAC,EAAEI,IAAIe,QAAQ,CAAC,CAAC;gBAChC;gBACAnB,YAAY;YACd;YACAA,YAAYI,IAAIY,IAAI;YACpBhB,WAAW,IAAItB,IAAIsB;YACnBP,IAAAA,aAAM,EACJa,uBACA,IAAIV,UACF,CAAC,kDAAkD,EAAEI,SAASd,QAAQ,GAAG,EAAE,CAAC;QAGlF;QAEAO,IAAAA,aAAM,EACJO,SAASoB,QAAQ,KAAK,OACpBpB,SAASqB,MAAM,KAAK,MACpBrB,SAASsB,IAAI,KAAK;QAGtB7B,IAAAA,aAAM,EACJW,IAAIgB,QAAQ,KAAK,MAAMhB,IAAIgB,QAAQ,KAAK,KACxC,IAAIxB,UAAU;QAEhB,IAAIC,cAAcO,IAAIgB,QAAQ;QAC9B,IAAIvB,YAAY0B,UAAU,CAAC,MAAM;YAC/B1B,cAAcA,YAAY2B,KAAK,CAAC;QAClC;QAEA,IAAI,CAAC1B,IAAI,GAAG;YACVI;YACAF;YACAH;YACAd,OAAOwB;YACPM;QACF;IACF;AA4FF;AA3CE,eAAA;IACEpB,IAAAA,aAAM,EAAC,IAAI,CAACF,KAAK,KAAKG;IACtB,MAAM+B,kBAAkB,MAAM,IAAI,CAAC3B,IAAI,CAACe,cAAc,CACpD,IAAI,CAACf,IAAI,CAACI,cAAc;IAE1B,MAAM,CAACwB,iBAAiBC,cAAc,GAAG,MAAMC,gBAC7C,IAAI,CAAC9B,IAAI,CAACD,WAAW,EACrB4B,iBACA,IAAI,CAAC3B,IAAI,CAACE,QAAQ,EAClB,IAAI,CAACF,IAAI,CAACf,KAAK;IAEjB,IAAI,CAACe,IAAI,CAACC,OAAO,GAAG4B;IAEpB,IAAIE,gBAAgB;IACpB,IAAI,IAAI,CAAC/B,IAAI,CAACE,QAAQ,CAACkB,QAAQ,KAAK,IAAI;QACtCW,iBAAiB,IAAI,CAAC/B,IAAI,CAACE,QAAQ,CAACkB,QAAQ;QAC5C,IAAI,IAAI,CAACpB,IAAI,CAACE,QAAQ,CAACmB,QAAQ,KAAK,IAAI;YACtCU,iBAAiB,CAAC,CAAC,EAAE,IAAI,CAAC/B,IAAI,CAACE,QAAQ,CAACmB,QAAQ,CAAC,CAAC;QACpD;QACAU,iBAAiB;IACnB;IACAA,iBAAiB,IAAI,CAAC/B,IAAI,CAACE,QAAQ,CAACgB,IAAI;IAExC,gFAAgF;IAChF,kHAAkH;IAClH,MAAM,IAAIc,QAAc,CAACC,SAASC;QAChC,IAAI,CAACzC,KAAK,GAAG,IAAI0C;QACjBC,IAAAA,gBAAQ,EACNC,cAAcT,kBACd,4EAA4E;QAC5E,iDAAiD;QACjD,+EAA+E;QAC/EU,IAAAA,oBAAkB,EAAC,IACnBC,oBACE,CAAC,EAAER,cAAc,CAAC,EAAE,IAAI,CAAC/B,IAAI,CAACD,WAAW,CAAC,CAAC,CAAC,EAC5C,IAAI,CAACN,KAAK,GAEZ,CAAC+C;YACCA,QAAQN,OAAOM,SAASP;QAC1B;IAEJ;AACF;AAGF,6CAA6C;AAC7C,MAAMQ,4BAA4B;AAkB3B,eAAe9D,iBACpBoB,WAAmB,EACnB2C,WAAyB,EACzBjC,aAA2B;IAE3B,MAAMkC,kBAAkB,MAAMlC,cAC5B,IAAI7B,IAAImB,aAAa2C,aAAatD,QAAQ;IAE5C,IAAI,CAACuD,gBAAgBC,EAAE,EAAE;QACvB,MAAM,IAAIC,MACR,CAAC,iDAAiD,EAAEF,gBAAgBG,MAAM,CAAC,CAAC,CAAC;IAEjF;IACA,MAAMC,kBAAkB,MAAMJ,gBAAgBK,IAAI;IAElD,IAAI,CAACC,IAAAA,eAAQ,EAACF,kBAAkB;QAC9B,MAAM,IAAIF,MACR,CAAC,yBAAyB,EAAE9C,YAAY,oBAAoB,CAAC;IAEjE;IAEA,OAAOgD;AACT;AAEA;;;;;;;;;;;;CAYC,GACD,eAAeG,kBACbnD,WAAmB,EACnBI,YAAyB,EACzBuC,WAAgB,EAChBjC,aAA2B;IAE3B,2FAA2F;IAC3F,IACEiC,YAAYtD,QAAQ,OAAOX,qBAAqBW,QAAQ,MACxD+D,IAAAA,2BAAoB,EAAChD,eACrB;QACA,OAAO;YACLiD,YAAYC,IAAAA,yBAAgB,EAACtD,aAAaI;YAC1CmD,eAAenD;QACjB;IACF;IAEA,MAAM4C,kBAAkB,MAAMpE,iBAC5BoB,aACA2C,aACAjC;IAGF,MAAM8C,WAAWC,OAAOC,IAAI,CAACV,iBAAiBQ,YAAY,CAAC,GAAGG,GAAG,CAC/D,CAACzD;QACC0D,IAAAA,4BAAqB,EAAC1D;QACtB,OAAOA;IACT;IAGF,MAAMqD,gBAAgBM,IAAAA,4BAAgB,EAACL,UAAUpD;IAEjD,IAAImD,kBAAkB,MAAM;QAC1B,MAAM,IAAIT,MACR,CAAC,+DAA+D,EAAE9C,YAAY,8BAA8B,EAAEI,aAAa,EAAE,CAAC;IAElI;IAEA,MAAMiD,aAAaL,iBAAiBQ,UAAU,CAACD,cAAc,EAAEO,MAAMC;IAErE,OAAO;QAAEV;QAAYE;IAAc;AACrC;AAEA;;;;;;;;;;;;;CAaC,GACD,eAAexB,gBACb/B,WAAmB,EACnBI,YAAyB,EACzBuC,WAAgB,EAChBjC,aAA2B;IAE3B,MAAM,EAAE2C,UAAU,EAAEE,aAAa,EAAE,GAAG,MAAMJ,kBAC1CnD,aACAI,cACAuC,aACAjC;IAGF,IAAI,CAACsD,IAAAA,sBAAU,EAACX,eAAe,CAACA,WAAWhE,QAAQ,GAAG4E,QAAQ,CAAC,SAAS;QACtE,MAAM,IAAInB,MACR,CAAC,8DAA8D,EAAE9C,YAAY,EAAE,CAAC;IAEpF;IAEA,4EAA4E;IAC5E,MAAMkE,iBAAiB,IAAIrF,IAAI8D;IAC/B,MAAMwB,gBAAgB,IAAItF,IAAIwE;IAC9Bc,cAAcC,QAAQ,GAAGF,eAAeE,QAAQ;IAChDD,cAAcE,QAAQ,GAAGH,eAAeG,QAAQ;IAEhD,kEAAkE;IAClE,MAAMxC,kBAAkB,MAAMnB,cAAcyD,cAAc9E,QAAQ;IAClE,IAAI,CAACwC,gBAAgBgB,EAAE,IAAI,CAAChB,gBAAgByC,IAAI,EAAE;QAChD,MAAM,IAAIxB,MAAM,CAAC,qCAAqC,EAAE9C,YAAY,EAAE,CAAC;IACzE;IACA,2FAA2F;IAC3F,MAAMuE,oBAAoB1C,gBAAgB2C,OAAO,CAAC1E,GAAG,CAAC;IACtDF,IAAAA,aAAM,EAAC2E,mBAAmB;IAC1B,MAAME,cAAcC,SAASH,mBAAmB;IAChD3E,IAAAA,aAAM,EACJ6E,eAAe/B,2BACf;IAEF,OAAO;QAACb,gBAAgByC,IAAI;QAAEf;KAAc;AAC9C;AAEA;;;CAGC,GACD,MAAMoB,0BAA0B;AAEhC;;;;;;;;CAQC,GACD,SAASrC,cAAcsC,MAAsB;IAC3C,IAAI,OAAOA,OAAOC,SAAS,KAAK,YAAY;QAC1C,OAAOD;IACT;IAEA,OAAO,IAAIE,gDAAuB,CAACF;AACrC;AAEA;;;;;;;CAOC,GACD,SAASpC,oBACPR,aAAqB,EACrBtC,KAA+B;IAE/BE,IAAAA,aAAM,EACJoC,cAAciC,QAAQ,CAAC,MACvB;IAGFrE,IAAAA,aAAM,EACJoC,cAAcN,UAAU,CAAC,SACzB;IAEF,oEAAoE;IACpE,2CAA2C;IAC3C,MAAMqD,gBAAgBC,IAAAA,kBAAU;IAEhC,IAAIC,YAAY;IAEhB,2EAA2E;IAC3E,qBAAqB;IACrBF,cAAcG,EAAE,CAAC,SAAS,CAACC,QAAQC,aAAaC;QAC9C,MAAM,EAAEC,MAAMC,UAAU,EAAEC,MAAMC,UAAU,EAAE,GAAGN;QAC/C,IAAIM,eAAe,QAAQ;YACzB,mDAAmD;YACnD,MAAMlG,OAAOgG,WAAWG,OAAO,CAACf,yBAAyB;YACzD,OAAOS,YAAYO,IAAI,CACrBC,IAAAA,qBAAM,EAAC;gBAAEC,UAAU;YAAa,GAAG,CAACC;gBAClC,IAAI;oBACFb,aAAaa,KAAKC,UAAU;oBAC5B,8EAA8E;oBAC9EnG,IAAAA,aAAM,EACJqF,YAAYvC,2BACZ,CAAC,8BAA8B,EAAEA,0BAA0B,OAAO,CAAC;oBAErE,MAAMzD,QAAQ,IAAI+G,uBAAW,CAAC;wBAC5BC,OAAOH;wBACPvG;wBACAuG,MAAM;4BACJI,eAAe,IAAIrH,IAAIU,MAAMyC,eAAe3C,QAAQ;wBACtD;oBACF;oBACA,wFAAwF;oBACxFO,IAAAA,aAAM,EACJ,CAACF,MAAMyG,GAAG,CAAC5G,OACX;oBAEFG,MAAM0G,GAAG,CAAC7G,MAAMN;oBAChB,OAAOoG;gBACT,EAAE,OAAO5C,OAAO;oBACd,OAAOsC,cAAcsB,OAAO,CAAC5D;gBAC/B;YACF;QAEJ;QAEA,4EAA4E;QAC5E,0EAA0E;QAC1E,6CAA6C;QAC7C2C,YAAYF,EAAE,CAAC,OAAO,IAAMG;QAC5B,OAAOD,YAAYkB,MAAM;IAC3B;IACA,OAAOvB;AACT"}
1
+ {"version":3,"sources":["../../../../src/snaps/location/npm.ts"],"sourcesContent":["import type { SnapManifest } from '@metamask/snaps-utils';\nimport {\n createSnapManifest,\n DEFAULT_REQUESTED_SNAP_VERSION,\n getTargetVersion,\n isValidUrl,\n NpmSnapIdStruct,\n VirtualFile,\n normalizeRelative,\n parseJson,\n} from '@metamask/snaps-utils';\nimport type { SemVerRange, SemVerVersion } from '@metamask/utils';\nimport {\n assert,\n assertIsSemVerVersion,\n assertStruct,\n isObject,\n isValidSemVerVersion,\n} from '@metamask/utils';\nimport concat from 'concat-stream';\nimport getNpmTarballUrl from 'get-npm-tarball-url';\nimport createGunzipStream from 'gunzip-maybe';\nimport { pipeline } from 'readable-stream';\nimport type { Readable, Writable } from 'readable-stream';\nimport { ReadableWebToNodeStream } from 'readable-web-to-node-stream';\nimport { extract as tarExtract } from 'tar-stream';\n\nimport type { DetectSnapLocationOptions, SnapLocation } from './location';\n\nexport const DEFAULT_NPM_REGISTRY = new URL('https://registry.npmjs.org');\n\ninterface NpmMeta {\n registry: URL;\n packageName: string;\n requestedRange: SemVerRange;\n version?: string;\n fetch: typeof fetch;\n resolveVersion: (range: SemVerRange) => Promise<SemVerRange>;\n}\nexport interface NpmOptions {\n /**\n * @default DEFAULT_REQUESTED_SNAP_VERSION\n */\n versionRange?: SemVerRange;\n /**\n * Whether to allow custom NPM registries outside of {@link DEFAULT_NPM_REGISTRY}.\n *\n * @default false\n */\n allowCustomRegistries?: boolean;\n}\n\nexport class NpmLocation implements SnapLocation {\n private readonly meta: NpmMeta;\n\n private validatedManifest?: VirtualFile<SnapManifest>;\n\n private files?: Map<string, VirtualFile>;\n\n constructor(url: URL, opts: DetectSnapLocationOptions = {}) {\n const allowCustomRegistries = opts.allowCustomRegistries ?? false;\n const fetchFunction = opts.fetch ?? globalThis.fetch.bind(globalThis);\n const requestedRange = opts.versionRange ?? DEFAULT_REQUESTED_SNAP_VERSION;\n const defaultResolve = async (range: SemVerRange) => range;\n const resolveVersion = opts.resolveVersion ?? defaultResolve;\n\n assertStruct(url.toString(), NpmSnapIdStruct, 'Invalid Snap Id: ');\n\n let registry: string | URL;\n if (\n url.host === '' &&\n url.port === '' &&\n url.username === '' &&\n url.password === ''\n ) {\n registry = DEFAULT_NPM_REGISTRY;\n } else {\n registry = 'https://';\n if (url.username) {\n registry += url.username;\n if (url.password) {\n registry += `:${url.password}`;\n }\n registry += '@';\n }\n registry += url.host;\n registry = new URL(registry);\n assert(\n allowCustomRegistries,\n new TypeError(\n `Custom NPM registries are disabled, tried to use \"${registry.toString()}\".`,\n ),\n );\n }\n\n assert(\n registry.pathname === '/' &&\n registry.search === '' &&\n registry.hash === '',\n );\n\n assert(\n url.pathname !== '' && url.pathname !== '/',\n new TypeError('The package name in NPM location is empty.'),\n );\n let packageName = url.pathname;\n if (packageName.startsWith('/')) {\n packageName = packageName.slice(1);\n }\n\n this.meta = {\n requestedRange,\n registry,\n packageName,\n fetch: fetchFunction,\n resolveVersion,\n };\n }\n\n async manifest(): Promise<VirtualFile<SnapManifest>> {\n if (this.validatedManifest) {\n return this.validatedManifest.clone();\n }\n\n const vfile = await this.fetch('snap.manifest.json');\n const result = parseJson(vfile.toString());\n vfile.result = createSnapManifest(result);\n this.validatedManifest = vfile as VirtualFile<SnapManifest>;\n\n return this.manifest();\n }\n\n async fetch(path: string): Promise<VirtualFile> {\n const relativePath = normalizeRelative(path);\n if (!this.files) {\n await this.#lazyInit();\n assert(this.files !== undefined);\n }\n const vfile = this.files.get(relativePath);\n assert(\n vfile !== undefined,\n new TypeError(`File \"${path}\" not found in package.`),\n );\n return vfile.clone();\n }\n\n get packageName(): string {\n return this.meta.packageName;\n }\n\n get version(): string {\n assert(\n this.meta.version !== undefined,\n 'Tried to access version without first fetching NPM package.',\n );\n return this.meta.version;\n }\n\n get registry(): URL {\n return this.meta.registry;\n }\n\n get versionRange(): SemVerRange {\n return this.meta.requestedRange;\n }\n\n async #lazyInit() {\n assert(this.files === undefined);\n const resolvedVersion = await this.meta.resolveVersion(\n this.meta.requestedRange,\n );\n const [tarballResponse, actualVersion] = await fetchNpmTarball(\n this.meta.packageName,\n resolvedVersion,\n this.meta.registry,\n this.meta.fetch,\n );\n this.meta.version = actualVersion;\n\n let canonicalBase = 'npm://';\n if (this.meta.registry.username !== '') {\n canonicalBase += this.meta.registry.username;\n if (this.meta.registry.password !== '') {\n canonicalBase += `:${this.meta.registry.password}`;\n }\n canonicalBase += '@';\n }\n canonicalBase += this.meta.registry.host;\n\n // TODO(ritave): Lazily extract files instead of up-front extracting all of them\n // We would need to replace tar-stream package because it requires immediate consumption of streams.\n await new Promise<void>((resolve, reject) => {\n this.files = new Map();\n pipeline(\n getNodeStream(tarballResponse),\n // The \"gz\" in \"tgz\" stands for \"gzip\". The tarball needs to be decompressed\n // before we can actually grab any files from it.\n // To prevent recursion-based zip bombs, we set a maximum recursion depth of 1.\n createGunzipStream(1),\n createTarballStream(\n `${canonicalBase}/${this.meta.packageName}/`,\n this.files,\n ),\n (error: unknown) => {\n error ? reject(error) : resolve();\n },\n );\n });\n }\n}\n\n// Safety limit for tarballs, 250 MB in bytes\nconst TARBALL_SIZE_SAFETY_LIMIT = 262144000;\n\n// Incomplete type\nexport type PartialNpmMetadata = {\n versions: Record<string, { dist: { tarball: string } }>;\n};\n\n/**\n * Fetches the NPM metadata of the specified package from\n * the public npm registry.\n *\n * @param packageName - The name of the package whose metadata to fetch.\n * @param registryUrl - The URL of the npm registry to fetch the metadata from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns The NPM metadata object.\n * @throws If fetching the metadata fails.\n */\nexport async function fetchNpmMetadata(\n packageName: string,\n registryUrl: URL | string,\n fetchFunction: typeof fetch,\n): Promise<PartialNpmMetadata> {\n const packageResponse = await fetchFunction(\n new URL(packageName, registryUrl).toString(),\n );\n if (!packageResponse.ok) {\n throw new Error(\n `Failed to fetch NPM registry entry. Status code: ${packageResponse.status}.`,\n );\n }\n const packageMetadata = await packageResponse.json();\n\n if (!isObject(packageMetadata)) {\n throw new Error(\n `Failed to fetch package \"${packageName}\" metadata from npm.`,\n );\n }\n\n return packageMetadata as PartialNpmMetadata;\n}\n\n/**\n * Resolves a version range to a version using the NPM registry.\n *\n * Unless the version range is already a version, then the NPM registry is skipped.\n *\n * @param packageName - The name of the package whose metadata to fetch.\n * @param versionRange - The version range of the package.\n * @param registryUrl - The URL of the npm registry to fetch the metadata from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns An object containing the resolved version and a URL for its tarball.\n * @throws If fetching the metadata fails.\n */\nasync function resolveNpmVersion(\n packageName: string,\n versionRange: SemVerRange,\n registryUrl: URL,\n fetchFunction: typeof fetch,\n): Promise<{ tarballURL: string; targetVersion: SemVerVersion }> {\n // If the version range is already a static version we don't need to look for the metadata.\n if (\n registryUrl.toString() === DEFAULT_NPM_REGISTRY.toString() &&\n isValidSemVerVersion(versionRange)\n ) {\n return {\n tarballURL: getNpmTarballUrl(packageName, versionRange),\n targetVersion: versionRange,\n };\n }\n\n const packageMetadata = await fetchNpmMetadata(\n packageName,\n registryUrl,\n fetchFunction,\n );\n\n const versions = Object.keys(packageMetadata?.versions ?? {}).map(\n (version) => {\n assertIsSemVerVersion(version);\n return version;\n },\n );\n\n const targetVersion = getTargetVersion(versions, versionRange);\n\n if (targetVersion === null) {\n throw new Error(\n `Failed to find a matching version in npm metadata for package \"${packageName}\" and requested semver range \"${versionRange}\".`,\n );\n }\n\n const tarballURL = packageMetadata?.versions?.[targetVersion]?.dist?.tarball;\n\n return { tarballURL, targetVersion };\n}\n\n/**\n * Fetches the tarball (`.tgz` file) of the specified package and version from\n * the public npm registry.\n *\n * @param packageName - The name of the package whose tarball to fetch.\n * @param versionRange - The SemVer range of the package to fetch. The highest\n * version satisfying the range will be fetched.\n * @param registryUrl - The URL of the npm registry to fetch the tarball from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns A tuple of the {@link Response} for the package tarball and the\n * actual version of the package.\n * @throws If fetching the tarball fails.\n */\nasync function fetchNpmTarball(\n packageName: string,\n versionRange: SemVerRange,\n registryUrl: URL,\n fetchFunction: typeof fetch,\n): Promise<[ReadableStream, SemVerVersion]> {\n const { tarballURL, targetVersion } = await resolveNpmVersion(\n packageName,\n versionRange,\n registryUrl,\n fetchFunction,\n );\n\n if (!isValidUrl(tarballURL) || !tarballURL.toString().endsWith('.tgz')) {\n throw new Error(\n `Failed to find valid tarball URL in NPM metadata for package \"${packageName}\".`,\n );\n }\n\n // Override the tarball hostname/protocol with registryUrl hostname/protocol\n const newRegistryUrl = new URL(registryUrl);\n const newTarballUrl = new URL(tarballURL);\n newTarballUrl.hostname = newRegistryUrl.hostname;\n newTarballUrl.protocol = newRegistryUrl.protocol;\n\n // Perform a raw fetch because we want the Response object itself.\n const tarballResponse = await fetchFunction(newTarballUrl.toString());\n if (!tarballResponse.ok || !tarballResponse.body) {\n throw new Error(`Failed to fetch tarball for package \"${packageName}\".`);\n }\n // We assume that NPM is a good actor and provides us with a valid `content-length` header.\n const tarballSizeString = tarballResponse.headers.get('content-length');\n assert(tarballSizeString, 'Snap tarball has invalid content-length');\n const tarballSize = parseInt(tarballSizeString, 10);\n assert(\n tarballSize <= TARBALL_SIZE_SAFETY_LIMIT,\n 'Snap tarball exceeds size limit',\n );\n return [tarballResponse.body, targetVersion];\n}\n\n/**\n * The paths of files within npm tarballs appear to always be prefixed with\n * \"package/\".\n */\nconst NPM_TARBALL_PATH_PREFIX = /^package\\//u;\n\n/**\n * Converts a {@link ReadableStream} to a Node.js {@link Readable}\n * stream. Returns the stream directly if it is already a Node.js stream.\n * We can't use the native Web {@link ReadableStream} directly because the\n * other stream libraries we use expect Node.js streams.\n *\n * @param stream - The stream to convert.\n * @returns The given stream as a Node.js Readable stream.\n */\nfunction getNodeStream(stream: ReadableStream): Readable {\n if (typeof stream.getReader !== 'function') {\n return stream as unknown as Readable;\n }\n\n return new ReadableWebToNodeStream(stream);\n}\n\n/**\n * Creates a `tar-stream` that will get the necessary files from an npm Snap\n * package tarball (`.tgz` file).\n *\n * @param canonicalBase - A base URI as specified in {@link https://github.com/MetaMask/SIPs/blob/main/SIPS/sip-8.md SIP-8}. Starting with 'npm:'. Will be used for canonicalPath vfile argument.\n * @param files - An object to write target file contents to.\n * @returns The {@link Writable} tarball extraction stream.\n */\nfunction createTarballStream(\n canonicalBase: string,\n files: Map<string, VirtualFile>,\n): Writable {\n assert(\n canonicalBase.endsWith('/'),\n \"Base needs to end with '/' for relative paths to be added as children instead of siblings.\",\n );\n\n assert(\n canonicalBase.startsWith('npm:'),\n 'Protocol mismatch, expected \"npm:\".',\n );\n // `tar-stream` is pretty old-school, so we create it first and then\n // instrument it by adding event listeners.\n const extractStream = tarExtract();\n\n let totalSize = 0;\n\n // \"entry\" is fired for every discreet entity in the tarball. This includes\n // files and folders.\n extractStream.on('entry', (header, entryStream, next) => {\n const { name: headerName, type: headerType } = header;\n if (headerType === 'file') {\n // The name is a path if the header type is \"file\".\n const path = headerName.replace(NPM_TARBALL_PATH_PREFIX, '');\n return entryStream.pipe(\n concat({ encoding: 'uint8array' }, (data) => {\n try {\n totalSize += data.byteLength;\n // To prevent zip bombs, we set a safety limit for the total size of tarballs.\n assert(\n totalSize < TARBALL_SIZE_SAFETY_LIMIT,\n `Snap tarball exceeds limit of ${TARBALL_SIZE_SAFETY_LIMIT} bytes.`,\n );\n const vfile = new VirtualFile({\n value: data,\n path,\n data: {\n canonicalPath: new URL(path, canonicalBase).toString(),\n },\n });\n // We disallow files having identical paths as it may confuse our checksum calculations.\n assert(\n !files.has(path),\n 'Malformed tarball, multiple files with the same path.',\n );\n files.set(path, vfile);\n return next();\n } catch (error) {\n return extractStream.destroy(error);\n }\n }),\n );\n }\n\n // If we get here, the entry is not a file, and we want to ignore. The entry\n // stream must be drained, or the extractStream will stop reading. This is\n // effectively a no-op for the current entry.\n entryStream.on('end', () => next());\n return entryStream.resume();\n });\n return extractStream;\n}\n"],"names":["DEFAULT_NPM_REGISTRY","NpmLocation","fetchNpmMetadata","URL","manifest","validatedManifest","clone","vfile","fetch","result","parseJson","toString","createSnapManifest","path","relativePath","normalizeRelative","files","lazyInit","assert","undefined","get","TypeError","packageName","meta","version","registry","versionRange","requestedRange","constructor","url","opts","allowCustomRegistries","fetchFunction","globalThis","bind","DEFAULT_REQUESTED_SNAP_VERSION","defaultResolve","range","resolveVersion","assertStruct","NpmSnapIdStruct","host","port","username","password","pathname","search","hash","startsWith","slice","resolvedVersion","tarballResponse","actualVersion","fetchNpmTarball","canonicalBase","Promise","resolve","reject","Map","pipeline","getNodeStream","createGunzipStream","createTarballStream","error","TARBALL_SIZE_SAFETY_LIMIT","registryUrl","packageResponse","ok","Error","status","packageMetadata","json","isObject","resolveNpmVersion","isValidSemVerVersion","tarballURL","getNpmTarballUrl","targetVersion","versions","Object","keys","map","assertIsSemVerVersion","getTargetVersion","dist","tarball","isValidUrl","endsWith","newRegistryUrl","newTarballUrl","hostname","protocol","body","tarballSizeString","headers","tarballSize","parseInt","NPM_TARBALL_PATH_PREFIX","stream","getReader","ReadableWebToNodeStream","extractStream","tarExtract","totalSize","on","header","entryStream","next","name","headerName","type","headerType","replace","pipe","concat","encoding","data","byteLength","VirtualFile","value","canonicalPath","has","set","destroy","resume"],"mappings":"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"}
@@ -65,10 +65,10 @@ import { JsonRpcEngine } from '@metamask/json-rpc-engine';
65
65
  import ObjectMultiplex from '@metamask/object-multiplex';
66
66
  import { JsonRpcError } from '@metamask/rpc-errors';
67
67
  import { SNAP_STREAM_NAMES, logError } from '@metamask/snaps-utils';
68
- import { Duration, isJsonRpcNotification, isObject } from '@metamask/utils';
68
+ import { Duration, assertIsJsonRpcRequest, isJsonRpcNotification, isObject } from '@metamask/utils';
69
69
  import { createStreamMiddleware } from 'json-rpc-middleware-stream';
70
70
  import { nanoid } from 'nanoid';
71
- import { pipeline } from 'stream';
71
+ import { pipeline } from 'readable-stream';
72
72
  import { log } from '../logging';
73
73
  import { hasTimedOut, withTimeout } from '../utils';
74
74
  const controllerName = 'ExecutionService';
@@ -255,9 +255,7 @@ export class AbstractExecutionService {
255
255
  }
256
256
  // Cannot be hash private yet because of tests.
257
257
  async command(jobId, message) {
258
- if (typeof message !== 'object') {
259
- throw new Error('Must send object.');
260
- }
258
+ assertIsJsonRpcRequest(message);
261
259
  const job = this.jobs.get(jobId);
262
260
  if (!job) {
263
261
  throw new Error(`Job with id "${jobId}" not found.`);
@@ -1 +1 @@
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAASA,aAAa,QAAQ,4BAA4B;AAC1D,OAAOC,qBAAqB,6BAA6B;AAEzD,SAASC,YAAY,QAAQ,uBAAuB;AAEpD,SAASC,iBAAiB,EAAEC,QAAQ,QAAQ,wBAAwB;AAOpE,SAASC,QAAQ,EAAEC,qBAAqB,EAAEC,QAAQ,QAAQ,kBAAkB;AAC5E,SAASC,sBAAsB,QAAQ,6BAA6B;AACpE,SAASC,MAAM,QAAQ,SAAS;AAChC,SAASC,QAAQ,QAAQ,SAAS;AAGlC,SAASC,GAAG,QAAQ,aAAa;AACjC,SAASC,WAAW,EAAEC,WAAW,QAAQ,WAAW;AAQpD,MAAMC,iBAAiB;IA0BrB,6CAQA,6CAEA,6CAEA,0CAEA,mDAkUA,gDAIA,gDAkBA,8CAKA;AA9WF,OAAO,MAAeC;IAmCpB;;;GAGC,GACD,AAAQC,0BAAgC;QACtC,yBAAA,IAAI,EAAEC,YAAUC,qBAAqB,CACnC,CAAC,EAAEJ,eAAe,iBAAiB,CAAC,EACpC,OAAOK,QAAgBC,UACrB,IAAI,CAACC,gBAAgB,CAACF,QAAQC;QAGlC,yBAAA,IAAI,EAAEH,YAAUC,qBAAqB,CACnC,CAAC,EAAEJ,eAAe,YAAY,CAAC,EAC/B,OAAOQ,WAAgC,IAAI,CAACC,WAAW,CAACD;QAG1D,yBAAA,IAAI,EAAEL,YAAUC,qBAAqB,CACnC,CAAC,EAAEJ,eAAe,cAAc,CAAC,EACjC,OAAOK,SAAmB,IAAI,CAACK,aAAa,CAACL;QAG/C,yBAAA,IAAI,EAAEF,YAAUC,qBAAqB,CACnC,CAAC,EAAEJ,eAAe,kBAAkB,CAAC,EACrC,UAAY,IAAI,CAACW,iBAAiB;IAEtC;IAWA;;;;;;;GAOC,GACD,MAAaC,UAAUC,KAAa,EAAiB;QACnD,MAAMC,aAAa,IAAI,CAACC,IAAI,CAACC,GAAG,CAACH;QACjC,IAAI,CAACC,YAAY;YACf,MAAM,IAAIG,MAAM,CAAC,aAAa,EAAEJ,MAAM,YAAY,CAAC;QACrD;QAEA,0FAA0F;QAC1F,MAAMK,SAAS,MAAMnB,YACnB,IAAI,CAACoB,OAAO,CAACN,OAAO;YAClBO,SAAS;YACTC,QAAQ;YACRC,QAAQ,EAAE;YACVC,IAAI5B;QACN,6BACA,IAAI,EAAE6B;QAGR,IAAIN,WAAWpB,eAAeoB,WAAW,MAAM;YAC7C,mGAAmG;YACnG,qCAAqC;YACrC,8HAA8H;YAC9H,kIAAkI;YAClI,4BAA4B;YAC5B5B,SAAS,CAAC,KAAK,EAAEuB,MAAM,iCAAiC,CAAC,EAAEK;QAC7D;QAEAO,OAAOC,MAAM,CAACZ,WAAWa,OAAO,EAAEC,OAAO,CAAC,CAACC;YACzC,IAAI;gBACF,CAACA,OAAOC,SAAS,IAAID,OAAOE,OAAO;gBACnCF,OAAOG,kBAAkB;YAC3B,EAAE,OAAOC,OAAO;gBACd3C,SAAS,iCAAiC2C;YAC5C;QACF;QAEA,IAAI,CAACC,YAAY,CAACpB;QAElB,0BAAA,IAAI,EAAEqB,0BAAAA,8BAAN,IAAI,EAA0BtB;QAC9B,IAAI,CAACE,IAAI,CAACqB,MAAM,CAACvB;QACjBhB,IAAI,CAAC,KAAK,EAAEgB,MAAM,aAAa,CAAC;IAClC;IAEA;;;;;;GAMC,GACD,MAAgBwB,UAAoC;QAClD,MAAMxB,QAAQlB;QACd,MAAM,EAAEgC,OAAO,EAAEW,MAAM,EAAE,GAAG,MAAM,IAAI,CAACC,WAAW,CAAC1B;QACnD,MAAM2B,YAAY,IAAItD;QAEtB,MAAMuD,oBAAoB/C;QAE1BE,SACE6C,kBAAkBZ,MAAM,EACxBF,QAAQR,OAAO,EACfsB,kBAAkBZ,MAAM,EACxB,CAACI;YACC,IAAIA,OAAO;gBACT3C,SAAS,CAAC,uBAAuB,CAAC,EAAE2C;YACtC;QACF;QAGFO,UAAUE,IAAI,CAACD,kBAAkBE,UAAU;QAE3C,MAAMC,cAAc;YAClBrB,IAAIV;YACJc;YACAa;YACAF;QACF;QACA,IAAI,CAACvB,IAAI,CAAC8B,GAAG,CAAChC,OAAO+B;QAErB,OAAOA;IACT;IAEA;;;;;;;GAOC,GACD,MAAgBL,YACd1B,KAAa,EACyC;QACtD,MAAM,EAAEyB,MAAM,EAAET,QAAQiB,SAAS,EAAE,GAAG,MAAM,IAAI,CAACC,aAAa,CAAClC;QAC/D,MAAMmC,MAAMC,eAAeH,WAAW,CAAC,MAAM,EAAEjC,MAAM,CAAC,CAAC;QACvD,MAAMqC,gBAAgBF,IAAIG,YAAY,CAAC9D,kBAAkB+D,OAAO;QAEhE,4FAA4F;QAC5F,gDAAgD;QAChD,MAAMC,sBAAsB,CAC1BC;YAIA,IAAI,CAAC9D,sBAAsB8D,UAAU;gBACnC;YACF;YAEA,oEAAoE;YACpE,MAAMjD,SAAS,yBAAA,IAAI,EAAEkD,eAAavC,GAAG,CAACH;YACtC,IAAIyC,QAAQjC,MAAM,KAAK,mBAAmB;gBACxC,yBAAA,IAAI,EAAElB,YAAUqD,OAAO,CAAC,oCAAoCnD;YAC9D,OAAO,IAAIiD,QAAQjC,MAAM,KAAK,oBAAoB;gBAChD,yBAAA,IAAI,EAAElB,YAAUqD,OAAO,CAAC,qCAAqCnD;YAC/D,OAAO,IAAIiD,QAAQjC,MAAM,KAAK,kBAAkB;gBAC9C,IAAI5B,SAAS6D,QAAQhC,MAAM,KAAKgC,QAAQhC,MAAM,CAACW,KAAK,EAAE;oBACpD,yBAAA,IAAI,EAAE9B,YAAUqD,OAAO,CACrB,mCACAnD,QACAiD,QAAQhC,MAAM,CAACW,KAAK;oBAEtBiB,cAAcO,cAAc,CAAC,QAAQJ;gBACvC,OAAO;oBACL/D,SACE,IAAI2B,MACF,CAAC,oBAAoB,EAAEqC,QAAQjC,MAAM,CAAC,8BAA8B,CAAC;gBAG3E;YACF,OAAO;gBACL/B,SACE,IAAI2B,MACF,CAAC,iDAAiD,EAAEqC,QAAQjC,MAAM,CAAC,EAAE,CAAC;YAG5E;QACF;QAEA6B,cAAcQ,EAAE,CAAC,QAAQL;QACzB,MAAMM,YAAYX,IAAIG,YAAY,CAAC9D,kBAAkBuE,QAAQ;QAE7D,iCAAiC;QACjC,OAAO;YACLjC,SAAS;gBACPR,SAAS+B;gBACTW,KAAKF;gBACL,gEAAgE;gBAChEG,aAAahB;YACf;YACAR;QACF;IACF;IAYA;;;;;;GAMC,GACD,MAAM5B,cAAcL,MAAc,EAAE;QAClC,MAAMQ,QAAQ,yBAAA,IAAI,EAAEkD,eAAa/C,GAAG,CAACX;QACrC,IAAIQ,OAAO;YACT,MAAM,IAAI,CAACD,SAAS,CAACC;QACvB;IACF;IAEA,MAAMF,oBAAoB;QACxB,MAAMqD,QAAQC,GAAG,CACf;eAAI,IAAI,CAAClD,IAAI,CAACmD,IAAI;SAAG,CAACC,GAAG,CAAC,OAAOtD,QAAU,IAAI,CAACD,SAAS,CAACC;QAE5D,yBAAA,IAAI,EAAEuD,eAAaC,KAAK;IAC1B;IAEA;;;;;GAKC,GACD,AAAQC,qBAAqBjE,MAAc,EAAE;QAC3C,OAAO,yBAAA,IAAI,EAAE+D,eAAapD,GAAG,CAACX;IAChC;IAEA;;;;;;;;GAQC,GACD,MAAMI,YAAYD,QAA2B,EAAmB;QAC9D,IAAI,yBAAA,IAAI,EAAEuD,eAAaQ,GAAG,CAAC/D,SAASH,MAAM,GAAG;YAC3C,MAAM,IAAIY,MAAM,CAAC,MAAM,EAAET,SAASH,MAAM,CAAC,4BAA4B,CAAC;QACxE;QAEA,MAAMmE,MAAM,MAAM,IAAI,CAACnC,OAAO;QAC9B,0BAAA,IAAI,EAAEoC,gBAAAA,oBAAN,IAAI,EAAgBjE,SAASH,MAAM,EAAEmE,IAAIjD,EAAE;QAE3C,+CAA+C;QAC/C,MAAM,IAAI,CAACJ,OAAO,CAACqD,IAAIjD,EAAE,EAAE;YACzBH,SAAS;YACTC,QAAQ;YACRE,IAAI5B;QACN;QAEA,MAAMgE,YAAYa,IAAI7C,OAAO,CAACkC,GAAG;QAEjC,IAAI,CAACa,iBAAiB,CAAClE,SAASH,MAAM,EAAEsD;QAExC,MAAMzC,SAAS,MAAM,IAAI,CAACC,OAAO,CAACqD,IAAIjD,EAAE,EAAE;YACxCH,SAAS;YACTC,QAAQ;YACRC,QAAQd;YACRe,IAAI5B;QACN;QACA,0BAAA,IAAI,EAAEgF,kBAAAA,sBAAN,IAAI,EAAkBnE,SAASH,MAAM,EAAEmE,IAAIjD,EAAE;QAC7C,OAAOL;IACT;IAEA,+CAA+C;IAC/C,MAAcC,QACZN,KAAa,EACbyC,OAAuB,EACI;QAC3B,IAAI,OAAOA,YAAY,UAAU;YAC/B,MAAM,IAAIrC,MAAM;QAClB;QAEA,MAAMuD,MAAM,IAAI,CAACzD,IAAI,CAACC,GAAG,CAACH;QAC1B,IAAI,CAAC2D,KAAK;YACR,MAAM,IAAIvD,MAAM,CAAC,aAAa,EAAEJ,MAAM,YAAY,CAAC;QACrD;QAEAhB,IAAI,2BAA2ByD;QAC/B,MAAMsB,WAAyC,MAAMJ,IAAIhC,SAAS,CAACqC,MAAM,CACvEvB;QAGF,IAAIsB,SAAS3C,KAAK,EAAE;YAClB,MAAM,IAAI7C,aACRwF,SAAS3C,KAAK,CAAC6C,IAAI,EACnBF,SAAS3C,KAAK,CAACqB,OAAO,EACtBsB,SAAS3C,KAAK,CAAC8C,IAAI;QAEvB;QAEA,OAAOH,SAAS1D,MAAM;IACxB;IAwCA;;;;;;GAMC,GACD,MAAaX,iBACXF,MAAc,EACdC,OAAwB,EACN;QAClB,MAAM0E,oBAAoB,IAAI,CAACV,oBAAoB,CAACjE;QAEpD,IAAI,CAAC2E,mBAAmB;YACtB,MAAM,IAAI/D,MACR,CAAC,iEAAiE,EAAEZ,OAAO,EAAE,CAAC;QAElF;QAEA,OAAO2E,kBAAkB1E;IAC3B;IA1XA2E,YAAY,EACVP,iBAAiB,EACjBvE,SAAS,EACTqB,qBAAqBjC,SAAS2F,MAAM,EACf,CAAE;QA4TzB,iCAAA;QAIA,iCAAA;QAkBA,iCAAA;QAKA,iCAAA;QA3WA,gCAAA;;mBAAA,KAAA;;QAEA,+CAA+C;QAC/C,uBAAUnE,QAAV,KAAA;QAEA,+CAA+C;QAC/C,uBAAiB2D,qBAAjB,KAAA;QAEA,gCAAA;;mBAAA,KAAA;;QAEA,gCAAA;;mBAAA,KAAA;;QAEA,gCAAA;;mBAAA,KAAA;;QAEA,gCAAA;;mBAAA,KAAA;;uCAOQN,eAAe,IAAIe;QACzB,IAAI,CAACpE,IAAI,GAAG,IAAIoE;QAChB,IAAI,CAACT,iBAAiB,GAAGA;uCACnBX,eAAe,IAAIoB;uCACnB5B,eAAe,IAAI4B;uCACnBhF,YAAYA;uCACZqB,qBAAqBA;QAE3B,IAAI,CAACtB,uBAAuB;IAC9B;AA6WF;AA3DE,SAAA,gBAAiBG,MAAc;IAC7B,yBAAA,IAAI,EAAE+D,eAAahC,MAAM,CAAC/B;AAC5B;AAEA,SAAA,gBAAiBA,MAAc,EAAE+E,QAAgB;IAC/C,MAAMC,UAAU,OAAO,EAAEC,MAAM,EAAEC,OAAO,EAAEC,OAAO,EAAmB;QAClE,OAAO,MAAM,IAAI,CAACrE,OAAO,CAACiE,UAAU;YAClC7D,IAAI5B;YACJyB,SAAS;YACTC,QAAQ;YACRC,QAAQ;gBACNgE;gBACAC;gBACAC,SAASA;gBACTC,QAAQpF;YACV;QACF;IACF;IAEA,yBAAA,IAAI,EAAE+D,eAAavB,GAAG,CAACxC,QAAQgF;AACjC;AAEA,SAAA,cAAehF,MAAc,EAAEQ,KAAa;IAC1C,yBAAA,IAAI,EAAEkD,eAAalB,GAAG,CAACxC,QAAQQ;IAC/B,yBAAA,IAAI,EAAE0C,eAAaV,GAAG,CAAChC,OAAOR;AAChC;AAEA,SAAA,wBAAyBQ,KAAa;IACpC,MAAMR,SAAS,yBAAA,IAAI,EAAEkD,eAAavC,GAAG,CAACH;IACtC,IAAI,CAACR,QAAQ;QACX,MAAM,IAAIY,MAAM,CAAC,MAAM,EAAEJ,MAAM,qBAAqB,CAAC;IACvD;IAEA,yBAAA,IAAI,EAAE0C,eAAanB,MAAM,CAACvB;IAC1B,yBAAA,IAAI,EAAEkD,eAAa3B,MAAM,CAAC/B;IAC1B,0BAAA,IAAI,EAAEqF,kBAAAA,sBAAN,IAAI,EAAkBrF;AACxB;AAyBF;;;;;;CAMC,GACD,OAAO,SAAS4C,eACd0C,gBAAwB,EACxBC,UAAkB;IAElB,MAAM5C,MAAM,IAAI7D;IAChBS,SAAS+F,kBAAkB3C,KAAK2C,kBAAkB,CAAC1D;QACjD,IAAIA,OAAO;YACT2D,aACItG,SAAS,CAAC,CAAC,EAAEsG,WAAW,iBAAiB,CAAC,EAAE3D,SAC5C3C,SAAS2C;QACf;IACF;IACA,OAAOe;AACT"}
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 {\n Duration,\n assertIsJsonRpcRequest,\n isJsonRpcNotification,\n isObject,\n} from '@metamask/utils';\nimport { createStreamMiddleware } from 'json-rpc-middleware-stream';\nimport { nanoid } from 'nanoid';\nimport { pipeline } from 'readable-stream';\nimport type { Duplex } from 'readable-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 assertIsJsonRpcRequest(message);\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","assertIsJsonRpcRequest","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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"}
@@ -31,8 +31,8 @@ import { assert, assertIsSemVerVersion, assertStruct, isObject, isValidSemVerVer
31
31
  import concat from 'concat-stream';
32
32
  import getNpmTarballUrl from 'get-npm-tarball-url';
33
33
  import createGunzipStream from 'gunzip-maybe';
34
+ import { pipeline } from 'readable-stream';
34
35
  import { ReadableWebToNodeStream } from 'readable-web-to-node-stream';
35
- import { pipeline } from 'stream';
36
36
  import { extract as tarExtract } from 'tar-stream';
37
37
  export const DEFAULT_NPM_REGISTRY = new URL('https://registry.npmjs.org');
38
38
  var _lazyInit = /*#__PURE__*/ new WeakSet();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/snaps/location/npm.ts"],"sourcesContent":["import type { SnapManifest } from '@metamask/snaps-utils';\nimport {\n createSnapManifest,\n DEFAULT_REQUESTED_SNAP_VERSION,\n getTargetVersion,\n isValidUrl,\n NpmSnapIdStruct,\n VirtualFile,\n normalizeRelative,\n parseJson,\n} from '@metamask/snaps-utils';\nimport type { SemVerRange, SemVerVersion } from '@metamask/utils';\nimport {\n assert,\n assertIsSemVerVersion,\n assertStruct,\n isObject,\n isValidSemVerVersion,\n} from '@metamask/utils';\nimport concat from 'concat-stream';\nimport getNpmTarballUrl from 'get-npm-tarball-url';\nimport createGunzipStream from 'gunzip-maybe';\nimport { ReadableWebToNodeStream } from 'readable-web-to-node-stream';\nimport { pipeline } from 'stream';\nimport type { Readable, Writable } from 'stream';\nimport { extract as tarExtract } from 'tar-stream';\n\nimport type { DetectSnapLocationOptions, SnapLocation } from './location';\n\nexport const DEFAULT_NPM_REGISTRY = new URL('https://registry.npmjs.org');\n\ninterface NpmMeta {\n registry: URL;\n packageName: string;\n requestedRange: SemVerRange;\n version?: string;\n fetch: typeof fetch;\n resolveVersion: (range: SemVerRange) => Promise<SemVerRange>;\n}\nexport interface NpmOptions {\n /**\n * @default DEFAULT_REQUESTED_SNAP_VERSION\n */\n versionRange?: SemVerRange;\n /**\n * Whether to allow custom NPM registries outside of {@link DEFAULT_NPM_REGISTRY}.\n *\n * @default false\n */\n allowCustomRegistries?: boolean;\n}\n\nexport class NpmLocation implements SnapLocation {\n private readonly meta: NpmMeta;\n\n private validatedManifest?: VirtualFile<SnapManifest>;\n\n private files?: Map<string, VirtualFile>;\n\n constructor(url: URL, opts: DetectSnapLocationOptions = {}) {\n const allowCustomRegistries = opts.allowCustomRegistries ?? false;\n const fetchFunction = opts.fetch ?? globalThis.fetch.bind(globalThis);\n const requestedRange = opts.versionRange ?? DEFAULT_REQUESTED_SNAP_VERSION;\n const defaultResolve = async (range: SemVerRange) => range;\n const resolveVersion = opts.resolveVersion ?? defaultResolve;\n\n assertStruct(url.toString(), NpmSnapIdStruct, 'Invalid Snap Id: ');\n\n let registry: string | URL;\n if (\n url.host === '' &&\n url.port === '' &&\n url.username === '' &&\n url.password === ''\n ) {\n registry = DEFAULT_NPM_REGISTRY;\n } else {\n registry = 'https://';\n if (url.username) {\n registry += url.username;\n if (url.password) {\n registry += `:${url.password}`;\n }\n registry += '@';\n }\n registry += url.host;\n registry = new URL(registry);\n assert(\n allowCustomRegistries,\n new TypeError(\n `Custom NPM registries are disabled, tried to use \"${registry.toString()}\".`,\n ),\n );\n }\n\n assert(\n registry.pathname === '/' &&\n registry.search === '' &&\n registry.hash === '',\n );\n\n assert(\n url.pathname !== '' && url.pathname !== '/',\n new TypeError('The package name in NPM location is empty.'),\n );\n let packageName = url.pathname;\n if (packageName.startsWith('/')) {\n packageName = packageName.slice(1);\n }\n\n this.meta = {\n requestedRange,\n registry,\n packageName,\n fetch: fetchFunction,\n resolveVersion,\n };\n }\n\n async manifest(): Promise<VirtualFile<SnapManifest>> {\n if (this.validatedManifest) {\n return this.validatedManifest.clone();\n }\n\n const vfile = await this.fetch('snap.manifest.json');\n const result = parseJson(vfile.toString());\n vfile.result = createSnapManifest(result);\n this.validatedManifest = vfile as VirtualFile<SnapManifest>;\n\n return this.manifest();\n }\n\n async fetch(path: string): Promise<VirtualFile> {\n const relativePath = normalizeRelative(path);\n if (!this.files) {\n await this.#lazyInit();\n assert(this.files !== undefined);\n }\n const vfile = this.files.get(relativePath);\n assert(\n vfile !== undefined,\n new TypeError(`File \"${path}\" not found in package.`),\n );\n return vfile.clone();\n }\n\n get packageName(): string {\n return this.meta.packageName;\n }\n\n get version(): string {\n assert(\n this.meta.version !== undefined,\n 'Tried to access version without first fetching NPM package.',\n );\n return this.meta.version;\n }\n\n get registry(): URL {\n return this.meta.registry;\n }\n\n get versionRange(): SemVerRange {\n return this.meta.requestedRange;\n }\n\n async #lazyInit() {\n assert(this.files === undefined);\n const resolvedVersion = await this.meta.resolveVersion(\n this.meta.requestedRange,\n );\n const [tarballResponse, actualVersion] = await fetchNpmTarball(\n this.meta.packageName,\n resolvedVersion,\n this.meta.registry,\n this.meta.fetch,\n );\n this.meta.version = actualVersion;\n\n let canonicalBase = 'npm://';\n if (this.meta.registry.username !== '') {\n canonicalBase += this.meta.registry.username;\n if (this.meta.registry.password !== '') {\n canonicalBase += `:${this.meta.registry.password}`;\n }\n canonicalBase += '@';\n }\n canonicalBase += this.meta.registry.host;\n\n // TODO(ritave): Lazily extract files instead of up-front extracting all of them\n // We would need to replace tar-stream package because it requires immediate consumption of streams.\n await new Promise<void>((resolve, reject) => {\n this.files = new Map();\n pipeline(\n getNodeStream(tarballResponse),\n // The \"gz\" in \"tgz\" stands for \"gzip\". The tarball needs to be decompressed\n // before we can actually grab any files from it.\n // To prevent recursion-based zip bombs, we set a maximum recursion depth of 1.\n createGunzipStream(1),\n createTarballStream(\n `${canonicalBase}/${this.meta.packageName}/`,\n this.files,\n ),\n (error) => {\n error ? reject(error) : resolve();\n },\n );\n });\n }\n}\n\n// Safety limit for tarballs, 250 MB in bytes\nconst TARBALL_SIZE_SAFETY_LIMIT = 262144000;\n\n// Incomplete type\nexport type PartialNpmMetadata = {\n versions: Record<string, { dist: { tarball: string } }>;\n};\n\n/**\n * Fetches the NPM metadata of the specified package from\n * the public npm registry.\n *\n * @param packageName - The name of the package whose metadata to fetch.\n * @param registryUrl - The URL of the npm registry to fetch the metadata from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns The NPM metadata object.\n * @throws If fetching the metadata fails.\n */\nexport async function fetchNpmMetadata(\n packageName: string,\n registryUrl: URL | string,\n fetchFunction: typeof fetch,\n): Promise<PartialNpmMetadata> {\n const packageResponse = await fetchFunction(\n new URL(packageName, registryUrl).toString(),\n );\n if (!packageResponse.ok) {\n throw new Error(\n `Failed to fetch NPM registry entry. Status code: ${packageResponse.status}.`,\n );\n }\n const packageMetadata = await packageResponse.json();\n\n if (!isObject(packageMetadata)) {\n throw new Error(\n `Failed to fetch package \"${packageName}\" metadata from npm.`,\n );\n }\n\n return packageMetadata as PartialNpmMetadata;\n}\n\n/**\n * Resolves a version range to a version using the NPM registry.\n *\n * Unless the version range is already a version, then the NPM registry is skipped.\n *\n * @param packageName - The name of the package whose metadata to fetch.\n * @param versionRange - The version range of the package.\n * @param registryUrl - The URL of the npm registry to fetch the metadata from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns An object containing the resolved version and a URL for its tarball.\n * @throws If fetching the metadata fails.\n */\nasync function resolveNpmVersion(\n packageName: string,\n versionRange: SemVerRange,\n registryUrl: URL,\n fetchFunction: typeof fetch,\n): Promise<{ tarballURL: string; targetVersion: SemVerVersion }> {\n // If the version range is already a static version we don't need to look for the metadata.\n if (\n registryUrl.toString() === DEFAULT_NPM_REGISTRY.toString() &&\n isValidSemVerVersion(versionRange)\n ) {\n return {\n tarballURL: getNpmTarballUrl(packageName, versionRange),\n targetVersion: versionRange,\n };\n }\n\n const packageMetadata = await fetchNpmMetadata(\n packageName,\n registryUrl,\n fetchFunction,\n );\n\n const versions = Object.keys(packageMetadata?.versions ?? {}).map(\n (version) => {\n assertIsSemVerVersion(version);\n return version;\n },\n );\n\n const targetVersion = getTargetVersion(versions, versionRange);\n\n if (targetVersion === null) {\n throw new Error(\n `Failed to find a matching version in npm metadata for package \"${packageName}\" and requested semver range \"${versionRange}\".`,\n );\n }\n\n const tarballURL = packageMetadata?.versions?.[targetVersion]?.dist?.tarball;\n\n return { tarballURL, targetVersion };\n}\n\n/**\n * Fetches the tarball (`.tgz` file) of the specified package and version from\n * the public npm registry.\n *\n * @param packageName - The name of the package whose tarball to fetch.\n * @param versionRange - The SemVer range of the package to fetch. The highest\n * version satisfying the range will be fetched.\n * @param registryUrl - The URL of the npm registry to fetch the tarball from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns A tuple of the {@link Response} for the package tarball and the\n * actual version of the package.\n * @throws If fetching the tarball fails.\n */\nasync function fetchNpmTarball(\n packageName: string,\n versionRange: SemVerRange,\n registryUrl: URL,\n fetchFunction: typeof fetch,\n): Promise<[ReadableStream, SemVerVersion]> {\n const { tarballURL, targetVersion } = await resolveNpmVersion(\n packageName,\n versionRange,\n registryUrl,\n fetchFunction,\n );\n\n if (!isValidUrl(tarballURL) || !tarballURL.toString().endsWith('.tgz')) {\n throw new Error(\n `Failed to find valid tarball URL in NPM metadata for package \"${packageName}\".`,\n );\n }\n\n // Override the tarball hostname/protocol with registryUrl hostname/protocol\n const newRegistryUrl = new URL(registryUrl);\n const newTarballUrl = new URL(tarballURL);\n newTarballUrl.hostname = newRegistryUrl.hostname;\n newTarballUrl.protocol = newRegistryUrl.protocol;\n\n // Perform a raw fetch because we want the Response object itself.\n const tarballResponse = await fetchFunction(newTarballUrl.toString());\n if (!tarballResponse.ok || !tarballResponse.body) {\n throw new Error(`Failed to fetch tarball for package \"${packageName}\".`);\n }\n // We assume that NPM is a good actor and provides us with a valid `content-length` header.\n const tarballSizeString = tarballResponse.headers.get('content-length');\n assert(tarballSizeString, 'Snap tarball has invalid content-length');\n const tarballSize = parseInt(tarballSizeString, 10);\n assert(\n tarballSize <= TARBALL_SIZE_SAFETY_LIMIT,\n 'Snap tarball exceeds size limit',\n );\n return [tarballResponse.body, targetVersion];\n}\n\n/**\n * The paths of files within npm tarballs appear to always be prefixed with\n * \"package/\".\n */\nconst NPM_TARBALL_PATH_PREFIX = /^package\\//u;\n\n/**\n * Converts a {@link ReadableStream} to a Node.js {@link Readable}\n * stream. Returns the stream directly if it is already a Node.js stream.\n * We can't use the native Web {@link ReadableStream} directly because the\n * other stream libraries we use expect Node.js streams.\n *\n * @param stream - The stream to convert.\n * @returns The given stream as a Node.js Readable stream.\n */\nfunction getNodeStream(stream: ReadableStream): Readable {\n if (typeof stream.getReader !== 'function') {\n return stream as unknown as Readable;\n }\n\n return new ReadableWebToNodeStream(stream);\n}\n\n/**\n * Creates a `tar-stream` that will get the necessary files from an npm Snap\n * package tarball (`.tgz` file).\n *\n * @param canonicalBase - A base URI as specified in {@link https://github.com/MetaMask/SIPs/blob/main/SIPS/sip-8.md SIP-8}. Starting with 'npm:'. Will be used for canonicalPath vfile argument.\n * @param files - An object to write target file contents to.\n * @returns The {@link Writable} tarball extraction stream.\n */\nfunction createTarballStream(\n canonicalBase: string,\n files: Map<string, VirtualFile>,\n): Writable {\n assert(\n canonicalBase.endsWith('/'),\n \"Base needs to end with '/' for relative paths to be added as children instead of siblings.\",\n );\n\n assert(\n canonicalBase.startsWith('npm:'),\n 'Protocol mismatch, expected \"npm:\".',\n );\n // `tar-stream` is pretty old-school, so we create it first and then\n // instrument it by adding event listeners.\n const extractStream = tarExtract();\n\n let totalSize = 0;\n\n // \"entry\" is fired for every discreet entity in the tarball. This includes\n // files and folders.\n extractStream.on('entry', (header, entryStream, next) => {\n const { name: headerName, type: headerType } = header;\n if (headerType === 'file') {\n // The name is a path if the header type is \"file\".\n const path = headerName.replace(NPM_TARBALL_PATH_PREFIX, '');\n return entryStream.pipe(\n concat({ encoding: 'uint8array' }, (data) => {\n try {\n totalSize += data.byteLength;\n // To prevent zip bombs, we set a safety limit for the total size of tarballs.\n assert(\n totalSize < TARBALL_SIZE_SAFETY_LIMIT,\n `Snap tarball exceeds limit of ${TARBALL_SIZE_SAFETY_LIMIT} bytes.`,\n );\n const vfile = new VirtualFile({\n value: data,\n path,\n data: {\n canonicalPath: new URL(path, canonicalBase).toString(),\n },\n });\n // We disallow files having identical paths as it may confuse our checksum calculations.\n assert(\n !files.has(path),\n 'Malformed tarball, multiple files with the same path.',\n );\n files.set(path, vfile);\n return next();\n } catch (error) {\n return extractStream.destroy(error);\n }\n }),\n );\n }\n\n // If we get here, the entry is not a file, and we want to ignore. The entry\n // stream must be drained, or the extractStream will stop reading. This is\n // effectively a no-op for the current entry.\n entryStream.on('end', () => next());\n return entryStream.resume();\n });\n return extractStream;\n}\n"],"names":["createSnapManifest","DEFAULT_REQUESTED_SNAP_VERSION","getTargetVersion","isValidUrl","NpmSnapIdStruct","VirtualFile","normalizeRelative","parseJson","assert","assertIsSemVerVersion","assertStruct","isObject","isValidSemVerVersion","concat","getNpmTarballUrl","createGunzipStream","ReadableWebToNodeStream","pipeline","extract","tarExtract","DEFAULT_NPM_REGISTRY","URL","NpmLocation","manifest","validatedManifest","clone","vfile","fetch","result","toString","path","relativePath","files","lazyInit","undefined","get","TypeError","packageName","meta","version","registry","versionRange","requestedRange","constructor","url","opts","allowCustomRegistries","fetchFunction","globalThis","bind","defaultResolve","range","resolveVersion","host","port","username","password","pathname","search","hash","startsWith","slice","resolvedVersion","tarballResponse","actualVersion","fetchNpmTarball","canonicalBase","Promise","resolve","reject","Map","getNodeStream","createTarballStream","error","TARBALL_SIZE_SAFETY_LIMIT","fetchNpmMetadata","registryUrl","packageResponse","ok","Error","status","packageMetadata","json","resolveNpmVersion","tarballURL","targetVersion","versions","Object","keys","map","dist","tarball","endsWith","newRegistryUrl","newTarballUrl","hostname","protocol","body","tarballSizeString","headers","tarballSize","parseInt","NPM_TARBALL_PATH_PREFIX","stream","getReader","extractStream","totalSize","on","header","entryStream","next","name","headerName","type","headerType","replace","pipe","encoding","data","byteLength","value","canonicalPath","has","set","destroy","resume"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/snaps/location/npm.ts"],"sourcesContent":["import type { SnapManifest } from '@metamask/snaps-utils';\nimport {\n createSnapManifest,\n DEFAULT_REQUESTED_SNAP_VERSION,\n getTargetVersion,\n isValidUrl,\n NpmSnapIdStruct,\n VirtualFile,\n normalizeRelative,\n parseJson,\n} from '@metamask/snaps-utils';\nimport type { SemVerRange, SemVerVersion } from '@metamask/utils';\nimport {\n assert,\n assertIsSemVerVersion,\n assertStruct,\n isObject,\n isValidSemVerVersion,\n} from '@metamask/utils';\nimport concat from 'concat-stream';\nimport getNpmTarballUrl from 'get-npm-tarball-url';\nimport createGunzipStream from 'gunzip-maybe';\nimport { pipeline } from 'readable-stream';\nimport type { Readable, Writable } from 'readable-stream';\nimport { ReadableWebToNodeStream } from 'readable-web-to-node-stream';\nimport { extract as tarExtract } from 'tar-stream';\n\nimport type { DetectSnapLocationOptions, SnapLocation } from './location';\n\nexport const DEFAULT_NPM_REGISTRY = new URL('https://registry.npmjs.org');\n\ninterface NpmMeta {\n registry: URL;\n packageName: string;\n requestedRange: SemVerRange;\n version?: string;\n fetch: typeof fetch;\n resolveVersion: (range: SemVerRange) => Promise<SemVerRange>;\n}\nexport interface NpmOptions {\n /**\n * @default DEFAULT_REQUESTED_SNAP_VERSION\n */\n versionRange?: SemVerRange;\n /**\n * Whether to allow custom NPM registries outside of {@link DEFAULT_NPM_REGISTRY}.\n *\n * @default false\n */\n allowCustomRegistries?: boolean;\n}\n\nexport class NpmLocation implements SnapLocation {\n private readonly meta: NpmMeta;\n\n private validatedManifest?: VirtualFile<SnapManifest>;\n\n private files?: Map<string, VirtualFile>;\n\n constructor(url: URL, opts: DetectSnapLocationOptions = {}) {\n const allowCustomRegistries = opts.allowCustomRegistries ?? false;\n const fetchFunction = opts.fetch ?? globalThis.fetch.bind(globalThis);\n const requestedRange = opts.versionRange ?? DEFAULT_REQUESTED_SNAP_VERSION;\n const defaultResolve = async (range: SemVerRange) => range;\n const resolveVersion = opts.resolveVersion ?? defaultResolve;\n\n assertStruct(url.toString(), NpmSnapIdStruct, 'Invalid Snap Id: ');\n\n let registry: string | URL;\n if (\n url.host === '' &&\n url.port === '' &&\n url.username === '' &&\n url.password === ''\n ) {\n registry = DEFAULT_NPM_REGISTRY;\n } else {\n registry = 'https://';\n if (url.username) {\n registry += url.username;\n if (url.password) {\n registry += `:${url.password}`;\n }\n registry += '@';\n }\n registry += url.host;\n registry = new URL(registry);\n assert(\n allowCustomRegistries,\n new TypeError(\n `Custom NPM registries are disabled, tried to use \"${registry.toString()}\".`,\n ),\n );\n }\n\n assert(\n registry.pathname === '/' &&\n registry.search === '' &&\n registry.hash === '',\n );\n\n assert(\n url.pathname !== '' && url.pathname !== '/',\n new TypeError('The package name in NPM location is empty.'),\n );\n let packageName = url.pathname;\n if (packageName.startsWith('/')) {\n packageName = packageName.slice(1);\n }\n\n this.meta = {\n requestedRange,\n registry,\n packageName,\n fetch: fetchFunction,\n resolveVersion,\n };\n }\n\n async manifest(): Promise<VirtualFile<SnapManifest>> {\n if (this.validatedManifest) {\n return this.validatedManifest.clone();\n }\n\n const vfile = await this.fetch('snap.manifest.json');\n const result = parseJson(vfile.toString());\n vfile.result = createSnapManifest(result);\n this.validatedManifest = vfile as VirtualFile<SnapManifest>;\n\n return this.manifest();\n }\n\n async fetch(path: string): Promise<VirtualFile> {\n const relativePath = normalizeRelative(path);\n if (!this.files) {\n await this.#lazyInit();\n assert(this.files !== undefined);\n }\n const vfile = this.files.get(relativePath);\n assert(\n vfile !== undefined,\n new TypeError(`File \"${path}\" not found in package.`),\n );\n return vfile.clone();\n }\n\n get packageName(): string {\n return this.meta.packageName;\n }\n\n get version(): string {\n assert(\n this.meta.version !== undefined,\n 'Tried to access version without first fetching NPM package.',\n );\n return this.meta.version;\n }\n\n get registry(): URL {\n return this.meta.registry;\n }\n\n get versionRange(): SemVerRange {\n return this.meta.requestedRange;\n }\n\n async #lazyInit() {\n assert(this.files === undefined);\n const resolvedVersion = await this.meta.resolveVersion(\n this.meta.requestedRange,\n );\n const [tarballResponse, actualVersion] = await fetchNpmTarball(\n this.meta.packageName,\n resolvedVersion,\n this.meta.registry,\n this.meta.fetch,\n );\n this.meta.version = actualVersion;\n\n let canonicalBase = 'npm://';\n if (this.meta.registry.username !== '') {\n canonicalBase += this.meta.registry.username;\n if (this.meta.registry.password !== '') {\n canonicalBase += `:${this.meta.registry.password}`;\n }\n canonicalBase += '@';\n }\n canonicalBase += this.meta.registry.host;\n\n // TODO(ritave): Lazily extract files instead of up-front extracting all of them\n // We would need to replace tar-stream package because it requires immediate consumption of streams.\n await new Promise<void>((resolve, reject) => {\n this.files = new Map();\n pipeline(\n getNodeStream(tarballResponse),\n // The \"gz\" in \"tgz\" stands for \"gzip\". The tarball needs to be decompressed\n // before we can actually grab any files from it.\n // To prevent recursion-based zip bombs, we set a maximum recursion depth of 1.\n createGunzipStream(1),\n createTarballStream(\n `${canonicalBase}/${this.meta.packageName}/`,\n this.files,\n ),\n (error: unknown) => {\n error ? reject(error) : resolve();\n },\n );\n });\n }\n}\n\n// Safety limit for tarballs, 250 MB in bytes\nconst TARBALL_SIZE_SAFETY_LIMIT = 262144000;\n\n// Incomplete type\nexport type PartialNpmMetadata = {\n versions: Record<string, { dist: { tarball: string } }>;\n};\n\n/**\n * Fetches the NPM metadata of the specified package from\n * the public npm registry.\n *\n * @param packageName - The name of the package whose metadata to fetch.\n * @param registryUrl - The URL of the npm registry to fetch the metadata from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns The NPM metadata object.\n * @throws If fetching the metadata fails.\n */\nexport async function fetchNpmMetadata(\n packageName: string,\n registryUrl: URL | string,\n fetchFunction: typeof fetch,\n): Promise<PartialNpmMetadata> {\n const packageResponse = await fetchFunction(\n new URL(packageName, registryUrl).toString(),\n );\n if (!packageResponse.ok) {\n throw new Error(\n `Failed to fetch NPM registry entry. Status code: ${packageResponse.status}.`,\n );\n }\n const packageMetadata = await packageResponse.json();\n\n if (!isObject(packageMetadata)) {\n throw new Error(\n `Failed to fetch package \"${packageName}\" metadata from npm.`,\n );\n }\n\n return packageMetadata as PartialNpmMetadata;\n}\n\n/**\n * Resolves a version range to a version using the NPM registry.\n *\n * Unless the version range is already a version, then the NPM registry is skipped.\n *\n * @param packageName - The name of the package whose metadata to fetch.\n * @param versionRange - The version range of the package.\n * @param registryUrl - The URL of the npm registry to fetch the metadata from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns An object containing the resolved version and a URL for its tarball.\n * @throws If fetching the metadata fails.\n */\nasync function resolveNpmVersion(\n packageName: string,\n versionRange: SemVerRange,\n registryUrl: URL,\n fetchFunction: typeof fetch,\n): Promise<{ tarballURL: string; targetVersion: SemVerVersion }> {\n // If the version range is already a static version we don't need to look for the metadata.\n if (\n registryUrl.toString() === DEFAULT_NPM_REGISTRY.toString() &&\n isValidSemVerVersion(versionRange)\n ) {\n return {\n tarballURL: getNpmTarballUrl(packageName, versionRange),\n targetVersion: versionRange,\n };\n }\n\n const packageMetadata = await fetchNpmMetadata(\n packageName,\n registryUrl,\n fetchFunction,\n );\n\n const versions = Object.keys(packageMetadata?.versions ?? {}).map(\n (version) => {\n assertIsSemVerVersion(version);\n return version;\n },\n );\n\n const targetVersion = getTargetVersion(versions, versionRange);\n\n if (targetVersion === null) {\n throw new Error(\n `Failed to find a matching version in npm metadata for package \"${packageName}\" and requested semver range \"${versionRange}\".`,\n );\n }\n\n const tarballURL = packageMetadata?.versions?.[targetVersion]?.dist?.tarball;\n\n return { tarballURL, targetVersion };\n}\n\n/**\n * Fetches the tarball (`.tgz` file) of the specified package and version from\n * the public npm registry.\n *\n * @param packageName - The name of the package whose tarball to fetch.\n * @param versionRange - The SemVer range of the package to fetch. The highest\n * version satisfying the range will be fetched.\n * @param registryUrl - The URL of the npm registry to fetch the tarball from.\n * @param fetchFunction - The fetch function to use. Defaults to the global\n * {@link fetch}. Useful for Node.js compatibility.\n * @returns A tuple of the {@link Response} for the package tarball and the\n * actual version of the package.\n * @throws If fetching the tarball fails.\n */\nasync function fetchNpmTarball(\n packageName: string,\n versionRange: SemVerRange,\n registryUrl: URL,\n fetchFunction: typeof fetch,\n): Promise<[ReadableStream, SemVerVersion]> {\n const { tarballURL, targetVersion } = await resolveNpmVersion(\n packageName,\n versionRange,\n registryUrl,\n fetchFunction,\n );\n\n if (!isValidUrl(tarballURL) || !tarballURL.toString().endsWith('.tgz')) {\n throw new Error(\n `Failed to find valid tarball URL in NPM metadata for package \"${packageName}\".`,\n );\n }\n\n // Override the tarball hostname/protocol with registryUrl hostname/protocol\n const newRegistryUrl = new URL(registryUrl);\n const newTarballUrl = new URL(tarballURL);\n newTarballUrl.hostname = newRegistryUrl.hostname;\n newTarballUrl.protocol = newRegistryUrl.protocol;\n\n // Perform a raw fetch because we want the Response object itself.\n const tarballResponse = await fetchFunction(newTarballUrl.toString());\n if (!tarballResponse.ok || !tarballResponse.body) {\n throw new Error(`Failed to fetch tarball for package \"${packageName}\".`);\n }\n // We assume that NPM is a good actor and provides us with a valid `content-length` header.\n const tarballSizeString = tarballResponse.headers.get('content-length');\n assert(tarballSizeString, 'Snap tarball has invalid content-length');\n const tarballSize = parseInt(tarballSizeString, 10);\n assert(\n tarballSize <= TARBALL_SIZE_SAFETY_LIMIT,\n 'Snap tarball exceeds size limit',\n );\n return [tarballResponse.body, targetVersion];\n}\n\n/**\n * The paths of files within npm tarballs appear to always be prefixed with\n * \"package/\".\n */\nconst NPM_TARBALL_PATH_PREFIX = /^package\\//u;\n\n/**\n * Converts a {@link ReadableStream} to a Node.js {@link Readable}\n * stream. Returns the stream directly if it is already a Node.js stream.\n * We can't use the native Web {@link ReadableStream} directly because the\n * other stream libraries we use expect Node.js streams.\n *\n * @param stream - The stream to convert.\n * @returns The given stream as a Node.js Readable stream.\n */\nfunction getNodeStream(stream: ReadableStream): Readable {\n if (typeof stream.getReader !== 'function') {\n return stream as unknown as Readable;\n }\n\n return new ReadableWebToNodeStream(stream);\n}\n\n/**\n * Creates a `tar-stream` that will get the necessary files from an npm Snap\n * package tarball (`.tgz` file).\n *\n * @param canonicalBase - A base URI as specified in {@link https://github.com/MetaMask/SIPs/blob/main/SIPS/sip-8.md SIP-8}. Starting with 'npm:'. Will be used for canonicalPath vfile argument.\n * @param files - An object to write target file contents to.\n * @returns The {@link Writable} tarball extraction stream.\n */\nfunction createTarballStream(\n canonicalBase: string,\n files: Map<string, VirtualFile>,\n): Writable {\n assert(\n canonicalBase.endsWith('/'),\n \"Base needs to end with '/' for relative paths to be added as children instead of siblings.\",\n );\n\n assert(\n canonicalBase.startsWith('npm:'),\n 'Protocol mismatch, expected \"npm:\".',\n );\n // `tar-stream` is pretty old-school, so we create it first and then\n // instrument it by adding event listeners.\n const extractStream = tarExtract();\n\n let totalSize = 0;\n\n // \"entry\" is fired for every discreet entity in the tarball. This includes\n // files and folders.\n extractStream.on('entry', (header, entryStream, next) => {\n const { name: headerName, type: headerType } = header;\n if (headerType === 'file') {\n // The name is a path if the header type is \"file\".\n const path = headerName.replace(NPM_TARBALL_PATH_PREFIX, '');\n return entryStream.pipe(\n concat({ encoding: 'uint8array' }, (data) => {\n try {\n totalSize += data.byteLength;\n // To prevent zip bombs, we set a safety limit for the total size of tarballs.\n assert(\n totalSize < TARBALL_SIZE_SAFETY_LIMIT,\n `Snap tarball exceeds limit of ${TARBALL_SIZE_SAFETY_LIMIT} bytes.`,\n );\n const vfile = new VirtualFile({\n value: data,\n path,\n data: {\n canonicalPath: new URL(path, canonicalBase).toString(),\n },\n });\n // We disallow files having identical paths as it may confuse our checksum calculations.\n assert(\n !files.has(path),\n 'Malformed tarball, multiple files with the same path.',\n );\n files.set(path, vfile);\n return next();\n } catch (error) {\n return extractStream.destroy(error);\n }\n }),\n );\n }\n\n // If we get here, the entry is not a file, and we want to ignore. The entry\n // stream must be drained, or the extractStream will stop reading. This is\n // effectively a no-op for the current entry.\n entryStream.on('end', () => next());\n return entryStream.resume();\n });\n return extractStream;\n}\n"],"names":["createSnapManifest","DEFAULT_REQUESTED_SNAP_VERSION","getTargetVersion","isValidUrl","NpmSnapIdStruct","VirtualFile","normalizeRelative","parseJson","assert","assertIsSemVerVersion","assertStruct","isObject","isValidSemVerVersion","concat","getNpmTarballUrl","createGunzipStream","pipeline","ReadableWebToNodeStream","extract","tarExtract","DEFAULT_NPM_REGISTRY","URL","NpmLocation","manifest","validatedManifest","clone","vfile","fetch","result","toString","path","relativePath","files","lazyInit","undefined","get","TypeError","packageName","meta","version","registry","versionRange","requestedRange","constructor","url","opts","allowCustomRegistries","fetchFunction","globalThis","bind","defaultResolve","range","resolveVersion","host","port","username","password","pathname","search","hash","startsWith","slice","resolvedVersion","tarballResponse","actualVersion","fetchNpmTarball","canonicalBase","Promise","resolve","reject","Map","getNodeStream","createTarballStream","error","TARBALL_SIZE_SAFETY_LIMIT","fetchNpmMetadata","registryUrl","packageResponse","ok","Error","status","packageMetadata","json","resolveNpmVersion","tarballURL","targetVersion","versions","Object","keys","map","dist","tarball","endsWith","newRegistryUrl","newTarballUrl","hostname","protocol","body","tarballSizeString","headers","tarballSize","parseInt","NPM_TARBALL_PATH_PREFIX","stream","getReader","extractStream","totalSize","on","header","entryStream","next","name","headerName","type","headerType","replace","pipe","encoding","data","byteLength","value","canonicalPath","has","set","destroy","resume"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { JsonRpcEngine } from '@metamask/json-rpc-engine';
3
3
  import ObjectMultiplex from '@metamask/object-multiplex';
4
4
  import type { BasePostMessageStream } from '@metamask/post-message-stream';
5
5
  import type { SnapRpcHookArgs } from '@metamask/snaps-utils';
6
- import type { Duplex } from 'stream';
6
+ import type { Duplex } from 'readable-stream';
7
7
  import type { ExecutionService, ExecutionServiceMessenger, SnapExecutionData } from './ExecutionService';
8
8
  export declare type SetupSnapProvider = (snapId: string, stream: Duplex) => void;
9
9
  export declare type ExecutionServiceArgs = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask/snaps-controllers",
3
- "version": "3.4.0",
3
+ "version": "3.4.1",
4
4
  "description": "Controllers for MetaMask Snaps.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -42,19 +42,19 @@
42
42
  "lint:dependencies": "depcheck"
43
43
  },
44
44
  "dependencies": {
45
- "@metamask/approval-controller": "^4.0.0",
45
+ "@metamask/approval-controller": "^4.1.0",
46
46
  "@metamask/base-controller": "^3.2.0",
47
- "@metamask/json-rpc-engine": "^7.1.1",
48
- "@metamask/object-multiplex": "^1.2.0",
49
- "@metamask/permission-controller": "^5.0.0",
47
+ "@metamask/json-rpc-engine": "^7.3.0",
48
+ "@metamask/object-multiplex": "^2.0.0",
49
+ "@metamask/permission-controller": "^5.0.1",
50
50
  "@metamask/phishing-controller": "^7.0.0",
51
51
  "@metamask/post-message-stream": "^7.0.0",
52
52
  "@metamask/rpc-errors": "^6.1.0",
53
53
  "@metamask/snaps-registry": "^2.1.0",
54
- "@metamask/snaps-rpc-methods": "^4.0.0",
55
- "@metamask/snaps-sdk": "^1.0.0",
56
- "@metamask/snaps-utils": "^4.0.0",
57
- "@metamask/utils": "^8.1.0",
54
+ "@metamask/snaps-rpc-methods": "^4.0.1",
55
+ "@metamask/snaps-sdk": "^1.2.0",
56
+ "@metamask/snaps-utils": "^5.0.0",
57
+ "@metamask/utils": "^8.2.1",
58
58
  "@xstate/fsm": "^2.0.0",
59
59
  "concat-stream": "^2.0.0",
60
60
  "get-npm-tarball-url": "^2.0.3",
@@ -62,6 +62,7 @@
62
62
  "immer": "^9.0.6",
63
63
  "json-rpc-middleware-stream": "^5.0.0",
64
64
  "nanoid": "^3.1.31",
65
+ "readable-stream": "^3.6.2",
65
66
  "readable-web-to-node-stream": "^3.0.2",
66
67
  "tar-stream": "^3.1.6"
67
68
  },
@@ -124,7 +125,7 @@
124
125
  "webdriverio": "^8.19.0"
125
126
  },
126
127
  "peerDependencies": {
127
- "@metamask/snaps-execution-environments": "^3.3.0"
128
+ "@metamask/snaps-execution-environments": "^3.4.1"
128
129
  },
129
130
  "peerDependenciesMeta": {
130
131
  "@metamask/snaps-execution-environments": {