message-port-rpc
Version:
Turns a MessagePort into an remote procedure call (RPC) stub.
207 lines (204 loc) • 7.29 kB
JavaScript
;
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