{"version":3,"file":"ChromeRuntimeStream.mjs","sourceRoot":"","sources":["../../src/browser/ChromeRuntimeStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;;;;;;;;;;;;;AAEH,OAAO,EAAE,SAAS,EAAE,+BAA+B;AAInD,OAAO,EACL,gBAAgB,EAChB,8BAA8B,EAC/B,gCAA+B;AAOhC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,0BAAyB;AAW1D,MAAM,iBAAiB,GAAG,CACxB,OAAgB,EACqB,EAAE,CACvC,OAAO,OAAO,KAAK,QAAQ;IAC3B,OAAO,KAAK,IAAI;IAChB,QAAQ,IAAI,OAAO;IACnB,QAAQ,IAAI,OAAO;IACnB,SAAS,IAAI,OAAO,CAAC;AAEvB;;;;;;;;;GASG;AACH,MAAM,OAAO,mBAAuC,SAAQ,UAAgB;IAS1E;;;;;;;;;OASG;IACH,YACE,OAAsB,EACtB,MAA2B,EAC3B,MAA2B,EAC3B,EAAE,aAAa,EAAE,KAAK,KAA2B,EAAE;QAEnD,wCAAwC;QACxC,IAAI,eAGK,CAAC;QAEV,KAAK,CAAC;YACJ,aAAa;YACb,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;gBACrB,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC;gBAClD,MAAM,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC;SACF,CAAC,CAAC;;QApCI,oDAA4B;QAE5B,8CAA6B;QAE7B,8CAA6B;QAE7B,mDAAqB;QAgC5B,uBAAA,IAAI,qCAAiB,KAAK,CAAC,eAAe,EAAE,MAAA,CAAC;QAC7C,uBAAA,IAAI,+BAAW,MAAM,MAAA,CAAC;QACtB,uBAAA,IAAI,+BAAW,MAAM,MAAA,CAAC;QACtB,uBAAA,IAAI,oCAAgB,OAAO,CAAC,EAAE,MAAA,CAAC;QAE/B,eAAe,GAAG,uBAAA,IAAI,sEAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,wDAAwD;QACxD,OAAO,CAAC,SAAS,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;QAE/C,MAAM,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;CAuCF;wTA/BY,OAAgB,EAAE,MAA2B;IACtD,IAAI,MAAM,CAAC,EAAE,KAAK,uBAAA,IAAI,wCAAa,EAAE,CAAC;QACpC,OAAO;IACT,CAAC;IAED,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,kCAAkC;QAClC,sCAAsC;QACtC,OAAO,CAAC,KAAK,CACX,oDAAoD,SAAS,CAC3D,OAAO,CACR,EAAE,CACJ,CAAC;QACF,OAAO;IACT,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,uBAAA,IAAI,mCAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,uBAAA,IAAI,mCAAQ,EAAE,CAAC;QACvE,kCAAkC;QAClC,sCAAsC;QACtC,OAAO,CAAC,KAAK,CACX,yEAAyE,SAAS,CAAC,OAAO,CAAC,EAAE,EAC7F,oBAAoB,uBAAA,IAAI,mCAAQ,EAAE,EAClC,oBAAoB,uBAAA,IAAI,mCAAQ,EAAE,CACnC,CAAC;QACF,OAAO;IACT,CAAC;IAED,uBAAA,IAAI,yCAAc,MAAlB,IAAI,EAAe,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CACxD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAClB,CAAC;AACJ,CAAC;AAEH,MAAM,CAAC,mBAAmB,CAAC,CAAC;AAE5B;;;;;;;;GAQG;AACH,MAAM,OAAO,mBAAwC,SAAQ,UAAiB;IAC5E;;;;;;;;;OASG;IACH,YACE,OAAsB,EACtB,MAA2B,EAC3B,MAA2B,EAC3B,EAAE,IAAI,EAAE,KAAK,KAAgD,EAAE;QAE/D,KAAK,CAAC;YACJ,IAAI;YACJ,UAAU,EAAE,KAAK,EAAE,KAA0B,EAAE,EAAE;gBAC/C,MAAM,OAAO,CAAC,WAAW,CAAC;oBACxB,MAAM;oBACN,MAAM;oBACN,OAAO,EAAE,KAAK;iBACf,CAAC,CAAC;YACL,CAAC;YACD,KAAK;SACN,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;CACF;AACD,MAAM,CAAC,mBAAmB,CAAC,CAAC;AAE5B;;;;;;;;GAQG;AACH,MAAM,OAAO,yBAGX,SAAQ,gBAKT;IACC;;;;;;;OAOG;IACH,YACE,OAAsB,EACtB,WAAgC,EAChC,YAAiC,EACjC,aAAmC;QAEnC,IAAI,MAAkC,CAAC,CAAC,mCAAmC;QAC3E,MAAM,MAAM,GAAG,IAAI,mBAAmB,CACpC,OAAO,EACP,WAAW,EACX,YAAY,EACZ;YACE,IAAI,EAAE,2BAA2B;YACjC,aAAa,EAAE,8BAA8B,CAAC,aAAa,CAAC;YAC5D,KAAK,EAAE,KAAK,IAAI,EAAE;gBAChB,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;YACxB,CAAC;SACF,CACF,CAAC;QACF,MAAM,GAAG,IAAI,mBAAmB,CAC9B,OAAO,EACP,YAAY,EACZ,WAAW,EACX;YACE,IAAI,EAAE,2BAA2B;YACjC,KAAK,EAAE,KAAK,IAAI,EAAE;gBAChB,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;YACxB,CAAC;SACF,CACF,CAAC;QACF,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtB,MAAM,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,CACf,OAAsB,EACtB,WAAgC,EAChC,YAAiC,EACjC,aAAmC;QAEnC,IAAI,WAAW,KAAK,YAAY,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,yBAAyB,CAC1C,OAAO,EACP,WAAW,EACX,YAAY,EACZ,aAAa,CACd,CAAC;QACF,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;QAC3B,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AACD,MAAM,CAAC,yBAAyB,CAAC,CAAC","sourcesContent":["/**\n * This module provides a pair of classes for creating readable and writable streams\n * over the Chrome Extension Runtime messaging API.\n *\n * These streams utilize `chrome.runtime.sendMessage` for sending data and\n * `chrome.runtime.onMessage.addListener` for receiving data. This allows for\n * communication between different parts of a Chrome extension (e.g., background scripts,\n * content scripts, and popup pages).\n *\n * Note that unlike e.g. the `MessagePort` API, the Chrome Extension Runtime messaging API\n * doesn't have a built-in way to close the connection. The streams will continue to operate\n * as long as the extension is running, unless manually ended.\n *\n * @module ChromeRuntime streams\n */\n\nimport { stringify } from '@metamask/kernel-utils';\nimport type { Json } from '@metamask/utils';\n\nimport type { ChromeRuntime, ChromeMessageSender } from './chrome.d.ts';\nimport {\n  BaseDuplexStream,\n  makeDuplexStreamInputValidator,\n} from '../BaseDuplexStream.ts';\nimport type {\n  BaseReaderArgs,\n  ValidateInput,\n  BaseWriterArgs,\n  ReceiveInput,\n} from '../BaseStream.ts';\nimport { BaseReader, BaseWriter } from '../BaseStream.ts';\nimport type { Dispatchable } from '../utils.ts';\n\nexport type ChromeRuntimeTarget = 'background' | 'offscreen' | 'popup';\n\nexport type MessageEnvelope<Payload> = {\n  target: ChromeRuntimeTarget;\n  source: ChromeRuntimeTarget;\n  payload: Payload;\n};\n\nconst isMessageEnvelope = (\n  message: unknown,\n): message is MessageEnvelope<unknown> =>\n  typeof message === 'object' &&\n  message !== null &&\n  'target' in message &&\n  'source' in message &&\n  'payload' in message;\n\n/**\n * A readable stream over the Chrome Extension Runtime messaging API.\n *\n * This class is a naive passthrough mechanism for data using `chrome.runtime.onMessage`.\n * Expects exclusive read access to the messaging API.\n *\n * @see\n * - {@link ChromeRuntimeWriter} for the corresponding writable stream.\n * - The module-level documentation for more details.\n */\nexport class ChromeRuntimeReader<Read extends Json> extends BaseReader<Read> {\n  readonly #receiveInput: ReceiveInput;\n\n  readonly #target: ChromeRuntimeTarget;\n\n  readonly #source: ChromeRuntimeTarget;\n\n  readonly #extensionId: string;\n\n  /**\n   * Constructs a new {@link ChromeRuntimeReader}.\n   *\n   * @param runtime - The Chrome runtime API object.\n   * @param target - The target context (e.g., 'background', 'offscreen', 'popup').\n   * @param source - The source context that messages are expected from.\n   * @param options - Options bag for configuring the reader.\n   * @param options.validateInput - A function that validates input from the transport.\n   * @param options.onEnd - A function that is called when the stream ends.\n   */\n  constructor(\n    runtime: ChromeRuntime,\n    target: ChromeRuntimeTarget,\n    source: ChromeRuntimeTarget,\n    { validateInput, onEnd }: BaseReaderArgs<Read> = {},\n  ) {\n    // eslint-disable-next-line prefer-const\n    let messageListener: (\n      message: unknown,\n      sender: ChromeMessageSender,\n    ) => void;\n\n    super({\n      validateInput,\n      onEnd: async (error) => {\n        runtime.onMessage.removeListener(messageListener);\n        await onEnd?.(error);\n      },\n    });\n\n    this.#receiveInput = super.getReceiveInput();\n    this.#target = target;\n    this.#source = source;\n    this.#extensionId = runtime.id;\n\n    messageListener = this.#onMessage.bind(this);\n    // Begin listening for messages from the Chrome runtime.\n    runtime.onMessage.addListener(messageListener);\n\n    harden(this);\n  }\n\n  /**\n   * Handles incoming messages from the Chrome runtime.\n   *\n   * @param message - The message received from the Chrome runtime.\n   * @param sender - The sender information for the message.\n   */\n  #onMessage(message: unknown, sender: ChromeMessageSender): void {\n    if (sender.id !== this.#extensionId) {\n      return;\n    }\n\n    if (!isMessageEnvelope(message)) {\n      // TODO(#562): Use logger instead.\n      // eslint-disable-next-line no-console\n      console.debug(\n        `ChromeRuntimeReader received unexpected message: ${stringify(\n          message,\n        )}`,\n      );\n      return;\n    }\n\n    if (message.target !== this.#target || message.source !== this.#source) {\n      // TODO(#562): Use logger instead.\n      // eslint-disable-next-line no-console\n      console.debug(\n        `ChromeRuntimeReader received message with incorrect target or source: ${stringify(message)}`,\n        `Expected target: ${this.#target}`,\n        `Expected source: ${this.#source}`,\n      );\n      return;\n    }\n\n    this.#receiveInput(message.payload).catch(async (error) =>\n      this.throw(error),\n    );\n  }\n}\nharden(ChromeRuntimeReader);\n\n/**\n * A writable stream over the Chrome Extension Runtime messaging API.\n *\n * This class is a naive passthrough mechanism for data using `chrome.runtime.sendMessage`.\n *\n * @see\n * - {@link ChromeRuntimeReader} for the corresponding readable stream.\n * - The module-level documentation for more details.\n */\nexport class ChromeRuntimeWriter<Write extends Json> extends BaseWriter<Write> {\n  /**\n   * Constructs a new {@link ChromeRuntimeWriter}.\n   *\n   * @param runtime - The Chrome runtime API object.\n   * @param target - The target context to send messages to.\n   * @param source - The source context identifying where messages originate.\n   * @param options - Options bag for configuring the writer.\n   * @param options.name - The name of the stream, for logging purposes.\n   * @param options.onEnd - A function that is called when the stream ends.\n   */\n  constructor(\n    runtime: ChromeRuntime,\n    target: ChromeRuntimeTarget,\n    source: ChromeRuntimeTarget,\n    { name, onEnd }: Omit<BaseWriterArgs<Write>, 'onDispatch'> = {},\n  ) {\n    super({\n      name,\n      onDispatch: async (value: Dispatchable<Write>) => {\n        await runtime.sendMessage({\n          target,\n          source,\n          payload: value,\n        });\n      },\n      onEnd,\n    });\n    harden(this);\n  }\n}\nharden(ChromeRuntimeWriter);\n\n/**\n * A duplex stream over the Chrome Extension Runtime messaging API.\n *\n * This class is a naive passthrough mechanism for data using `chrome.runtime.onMessage`.\n *\n * @see\n * - {@link ChromeRuntimeReader} for the corresponding readable stream.\n * - {@link ChromeRuntimeWriter} for the corresponding writable stream.\n */\nexport class ChromeRuntimeDuplexStream<\n  Read extends Json,\n  Write extends Json = Read,\n> extends BaseDuplexStream<\n  Read,\n  ChromeRuntimeReader<Read>,\n  Write,\n  ChromeRuntimeWriter<Write>\n> {\n  /**\n   * Constructs a new {@link ChromeRuntimeDuplexStream}.\n   *\n   * @param runtime - The Chrome runtime API object.\n   * @param localTarget - The local target context for this stream.\n   * @param remoteTarget - The remote target context to communicate with.\n   * @param validateInput - A function that validates input from the transport.\n   */\n  constructor(\n    runtime: ChromeRuntime,\n    localTarget: ChromeRuntimeTarget,\n    remoteTarget: ChromeRuntimeTarget,\n    validateInput?: ValidateInput<Read>,\n  ) {\n    let writer: ChromeRuntimeWriter<Write>; // eslint-disable-line prefer-const\n    const reader = new ChromeRuntimeReader<Read>(\n      runtime,\n      localTarget,\n      remoteTarget,\n      {\n        name: 'ChromeRuntimeDuplexStream',\n        validateInput: makeDuplexStreamInputValidator(validateInput),\n        onEnd: async () => {\n          await writer.return();\n        },\n      },\n    );\n    writer = new ChromeRuntimeWriter<Write>(\n      runtime,\n      remoteTarget,\n      localTarget,\n      {\n        name: 'ChromeRuntimeDuplexStream',\n        onEnd: async () => {\n          await reader.return();\n        },\n      },\n    );\n    super(reader, writer);\n    harden(this);\n  }\n\n  /**\n   * Creates and synchronizes a new {@link ChromeRuntimeDuplexStream}.\n   *\n   * @param runtime - The Chrome runtime API object.\n   * @param localTarget - The local target context for this stream.\n   * @param remoteTarget - The remote target context to communicate with.\n   * @param validateInput - A function that validates input from the transport.\n   * @returns A synchronized duplex stream.\n   */\n  static async make<Read extends Json, Write extends Json = Read>(\n    runtime: ChromeRuntime,\n    localTarget: ChromeRuntimeTarget,\n    remoteTarget: ChromeRuntimeTarget,\n    validateInput?: ValidateInput<Read>,\n  ): Promise<ChromeRuntimeDuplexStream<Read, Write>> {\n    if (localTarget === remoteTarget) {\n      throw new Error('localTarget and remoteTarget must be different');\n    }\n\n    const stream = new ChromeRuntimeDuplexStream<Read, Write>(\n      runtime,\n      localTarget,\n      remoteTarget,\n      validateInput,\n    );\n    await stream.synchronize();\n    return stream;\n  }\n}\nharden(ChromeRuntimeDuplexStream);\n"]}