{"version":3,"file":"ProxySnapExecutor.mjs","sourceRoot":"","sources":["../../src/proxy/ProxySnapExecutor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BasePostMessageStream } from '@metamask/post-message-stream';\nimport { WindowPostMessageStream } from '@metamask/post-message-stream';\nimport { createWindow, logError } from '@metamask/snaps-utils';\nimport type { JsonRpcRequest } from '@metamask/utils';\nimport { assert } from '@metamask/utils';\n\nimport packageJson from '@metamask/snaps-execution-environments/package.json';\n\ntype ExecutorJob = {\n  id: string;\n  window: Window;\n  stream: WindowPostMessageStream;\n};\n\nconst IFRAME_URL = `https://execution.metamask.io/iframe/${packageJson.version}/index.html`;\n\n/**\n * A \"proxy\" snap executor that uses a level of indirection to execute snaps.\n *\n * Useful for multiple execution environments.\n *\n * This is not a traditional snap executor, as it does not execute snaps itself.\n * Instead, it creates an iframe window for each snap execution, and sends the\n * snap execution request to the iframe window. The iframe window is responsible\n * for executing the snap.\n *\n * This executor is persisted between snap executions. The executor essentially\n * acts as a proxy between the client and the iframe execution environment.\n */\nexport class ProxySnapExecutor {\n  readonly #stream: BasePostMessageStream;\n\n  readonly #frameUrl: string;\n\n  readonly jobs: Record<string, ExecutorJob> = {};\n\n  /**\n   * Initialize the executor with the given stream. This is a wrapper around the\n   * constructor.\n   *\n   * @param stream - The stream to use for communication.\n   * @param frameUrl - An optional URL for the iframe to use.\n   * @returns The initialized executor.\n   */\n  static initialize(stream: BasePostMessageStream, frameUrl = IFRAME_URL) {\n    return new ProxySnapExecutor(stream, frameUrl);\n  }\n\n  constructor(stream: BasePostMessageStream, frameUrl: string) {\n    this.#stream = stream;\n    this.#stream.on('data', this.#onData.bind(this));\n    this.#frameUrl = frameUrl;\n  }\n\n  /**\n   * Handle an incoming message from a `ProxyExecutionService`. This\n   * assumes that the message contains a `jobId` property, and a JSON-RPC\n   * request in the `data` property.\n   *\n   * @param data - The message data.\n   * @param data.data - The JSON-RPC request.\n   * @param data.jobId - The job ID.\n   */\n  #onData(data: { data: JsonRpcRequest; jobId: string }) {\n    const { jobId, data: request } = data;\n\n    if (!this.jobs[jobId]) {\n      // This ensures that a job is initialized before it is used. To avoid\n      // code duplication, we call the `#onData` method again, which will\n      // run the rest of the logic after initialization.\n      this.#initializeJob(jobId)\n        .then(() => {\n          this.#onData(data);\n        })\n        .catch((error) => {\n          logError('[Worker] Error initializing job:', error);\n        });\n\n      return;\n    }\n\n    // This is a method specific to the `OffscreenSnapExecutor`, as the service\n    // itself does not have access to the iframes directly.\n    if (request.method === 'terminateJob') {\n      this.#terminateJob(jobId);\n      return;\n    }\n\n    this.jobs[jobId].stream.write(request);\n  }\n\n  /**\n   * Create a new iframe and set up a stream to communicate with it.\n   *\n   * @param jobId - The job ID.\n   * @returns The executor job object.\n   */\n  async #initializeJob(jobId: string): Promise<ExecutorJob> {\n    const window = await createWindow({ uri: this.#frameUrl, id: jobId });\n    const jobStream = new WindowPostMessageStream({\n      name: 'parent',\n      target: 'child',\n      targetWindow: window, // iframe's internal window\n      targetOrigin: '*',\n    });\n\n    // Write messages from the iframe to the parent, wrapped with the job ID.\n    jobStream.on('data', (data) => {\n      this.#stream.write({ data, jobId });\n    });\n\n    this.jobs[jobId] = { id: jobId, window, stream: jobStream };\n    return this.jobs[jobId];\n  }\n\n  /**\n   * Terminate the job with the given ID. This will close the iframe and delete\n   * the job from the internal job map.\n   *\n   * @param jobId - The job ID.\n   */\n  #terminateJob(jobId: string) {\n    assert(this.jobs[jobId], `Job \"${jobId}\" not found.`);\n\n    const iframe = document.getElementById(jobId);\n    assert(iframe, `Iframe with ID \"${jobId}\" not found.`);\n\n    iframe.remove();\n    this.jobs[jobId].stream.destroy();\n    delete this.jobs[jobId];\n  }\n}\n"]}