UNPKG

message-port-rpc

Version:

Turns a MessagePort into an remote procedure call (RPC) stub.

207 lines (204 loc) 7.29 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { forGenerator: () => forGenerator, messagePortRPC: () => messagePortRPC }); module.exports = __toCommonJS(src_exports); // src/messagePortRPC.ts var ABORT = "ABORT"; var CALL = "CALL"; var REJECT = "REJECT"; var RESOLVE = "RESOLVE"; function messagePortRPC(port, fn) { const handleMessage = (event) => { const data = event.data; if (Array.isArray(data) && data[0] === CALL) { event.stopImmediatePropagation(); const [returnPort] = event.ports; if (!returnPort) { throw new Error("RPCCallMessage must contains a port."); } if (fn) { (async function() { const abortController = new AbortController(); try { returnPort.onmessage = ({ data: data2 }) => Array.isArray(data2) && data2[0] === ABORT && abortController.abort(); returnPort.postMessage([ RESOLVE, await fn.call({ signal: abortController.signal }, ...data.slice(1)) ]); } catch (error) { returnPort.postMessage([REJECT, error]); } finally { returnPort.close(); } })(); } else { returnPort.postMessage([ REJECT, new Error( "No function was registered on this RPC. This is probably calling a client which do not implement the function." ) ]); returnPort.close(); } } }; port.addEventListener("message", handleMessage); port.start(); const createWithOptions = (init) => (...args) => { return new Promise((resolve, reject) => { const { port1, port2 } = new MessageChannel(); port1.onmessage = (event) => { const data = event.data; if (data[0] === RESOLVE) { resolve(data[1]); } else { reject(data[1]); } port1.close(); }; init?.signal?.addEventListener("abort", () => { port1.postMessage([ABORT]); port1.close(); reject(new Error("Aborted.")); }); port.postMessage([CALL, ...args], [port2, ...init.transfer || []]); }); }; const stub = createWithOptions({}); stub.withOptions = createWithOptions; return stub; } // src/forGenerator.ts var GENERATE = "GENERATOR_GENERATE"; function forGenerator(port, fn) { const handleMessage = (event) => { const data = event.data; if (Array.isArray(data) && data[0] === GENERATE) { event.stopImmediatePropagation(); if (!fn) { throw new Error( "No function was registered on this RPC. This is probably calling a client which do not implement the function." ); } const [_, messagePorts, ...args] = data; const generator = fn(...args); messagePortRPC(messagePorts.next, generator.next.bind(generator)); messagePortRPC(messagePorts.return, (value) => generator.return?.(value) || { done: true }); messagePortRPC(messagePorts.throw, (error) => generator.throw?.(error) || Promise.reject(error)); messagePortRPC(messagePorts.asyncDispose, async () => { const symbolAsyncDispose = Symbol.asyncDispose || /* @__PURE__ */ Symbol.for("Symbol.asyncDispose"); const symbolDispose = Symbol.dispose || /* @__PURE__ */ Symbol.for("Symbol.dispose"); symbolAsyncDispose in generator ? await generator[symbolAsyncDispose]() : symbolDispose in generator && generator[symbolDispose](); }); } }; port.addEventListener("message", handleMessage); port.start(); const createWithOptions = (init) => { let checkAborted; return (...args) => { const { port1: asyncDisposePort1, port2: asyncDisposePort2 } = new MessageChannel(); const { port1: nextPort1, port2: nextPort2 } = new MessageChannel(); const { port1: returnPort1, port2: returnPort2 } = new MessageChannel(); const { port1: throwPort1, port2: throwPort2 } = new MessageChannel(); const subInit = { signal: init.signal }; const closePorts = () => { asyncDisposePort1.close(); asyncDisposePort2.close(); nextPort1.close(); nextPort2.close(); returnPort1.close(); returnPort2.close(); throwPort1.close(); throwPort2.close(); }; const asyncDisposeRPC = messagePortRPC(asyncDisposePort1).withOptions(subInit); const nextRPC = messagePortRPC(nextPort1).withOptions( subInit ); const returnRPC = messagePortRPC( returnPort1 ).withOptions(subInit); const throwRPC = messagePortRPC(throwPort1).withOptions(subInit); let finished = false; const callGenerator = async (fn2) => { checkAborted?.(); if (finished) { return { done: true }; } const result = await fn2(); if (result.done) { finished = true; closePorts(); } return result; }; const asyncDisposeGenerator = async (fn2) => { checkAborted?.(); await fn2(); finished = true; closePorts(); checkAborted = () => { throw new Error("This generator has been disposed."); }; }; const generator = { next: (value) => callGenerator(() => nextRPC(value)), return: (value) => callGenerator(() => returnRPC(value)), throw: (error) => callGenerator(() => throwRPC(error)), // Ponyfills for Symbol.asyncDispose [Symbol.asyncDispose || /* @__PURE__ */ Symbol.for("Symbol.asyncDispose")]: () => asyncDisposeGenerator(() => asyncDisposeRPC()), [Symbol.asyncIterator]: () => generator }; port.postMessage( [ GENERATE, { asyncDispose: asyncDisposePort2, next: nextPort2, return: returnPort2, throw: throwPort2 }, ...args ], [...init.transfer || [], asyncDisposePort2, nextPort2, returnPort2, throwPort2] ); init.signal?.addEventListener( "abort", () => { checkAborted = () => { throw new Error("This generator has been aborted."); }; closePorts(); }, { once: true } ); return generator; }; }; const stub = createWithOptions({}); stub.withOptions = createWithOptions; return stub; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { forGenerator, messagePortRPC }); //# sourceMappingURL=message-port-rpc.js.map