import { toError } from "./toError.js"; /** Owns the parent half of the iframe fetch transport. */ export class ParentFetchBridge { port; #parentPort; #fetch; #requests = new Map(); #closed = false; constructor(fetchFn) { this.#fetch = fetchFn; const channel = new MessageChannel(); this.port = channel.port2; channel.port1.onmessage = this.#handleMessage; channel.port1.start(); this.#parentPort = channel.port1; } close() { if (this.#closed) { return; } this.#closed = true; for (const [port, controller] of this.#requests) { controller.abort(); port.close(); } this.#requests.clear(); this.#parentPort.close(); } #handleMessage = (event) => { if (this.#closed) { return; } void this.#fetchStream(event.data.url, event.data.options, event.data.port); }; async #fetchStream(url, options, port) { const controller = new AbortController(); this.#requests.set(port, controller); port.onmessage = (event) => { if (event.data.type === "abort") { controller.abort(); } this.#finishRequest(port); }; port.start(); try { const response = await this.#fetch(url, { ...options, signal: controller.signal, }); const message = { type: "response", status: response.status, statusText: response.statusText, headers: [...response.headers.entries()], body: response.body, }; port.postMessage(message, response.body ? [response.body] : []); } catch (err) { const error = toError(err); port.postMessage({ type: "error", name: error.name, message: error.message, }); this.#finishRequest(port); } } #finishRequest(port) { this.#requests.delete(port); port.close(); } }