message-port-rpc
Version:
Turns a MessagePort into an remote procedure call (RPC) stub.
1 lines • 21.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/messagePortRPC.ts","../src/forGenerator.ts"],"sourcesContent":["// Naming is from https://www.w3.org/History/1992/nfs_dxcern_mirror/rpc/doc/Introduction/HowItWorks.html.\n\nimport { type ReturnValueOfPromise } from './private/types/ReturnValueOfPromise.ts';\n\nconst ABORT = 'ABORT';\nconst CALL = 'CALL';\nconst REJECT = 'REJECT';\nconst RESOLVE = 'RESOLVE';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Subroutine = (...args: any[]) => Promise<unknown> | unknown;\ntype RPCCallMessage<T extends Subroutine> = [typeof CALL, ...Parameters<T>];\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype RPCRejectMessage = [typeof REJECT, any];\ntype RPCResolveMessage<T extends Subroutine> = [typeof RESOLVE, ReturnValueOfPromise<ReturnType<T>>];\n\ntype CallInit = {\n signal?: AbortSignal | undefined;\n transfer?: readonly Transferable[] | undefined;\n};\n\n// Regardless whether T returns Promise or not, the client stub must return Promise.\ntype ClientStub<T extends Subroutine> = (...args: Parameters<T>) => Promise<ReturnValueOfPromise<ReturnType<T>>>;\n\ntype ClientStubWithExtra<T extends Subroutine> = ClientStub<T> & {\n /**\n * Creates a new stub with options.\n *\n * @param {AbortSignal} init.signal - Abort signal to abort the call to the stub.\n * @param {Transferable[]} init.transfer - Transfer ownership of objects specified in `args`.\n */\n withOptions: (init: CallInit) => ClientStub<T>;\n};\n\ntype ServerStub<T extends Subroutine> = (this: { signal: AbortSignal }, ...args: Parameters<T>) => ReturnType<T>;\n\n/**\n * Binds a function to a `MessagePort` in RPC fashion and/or create a RPC function stub connected to a `MessagePort`.\n *\n * In a traditional RPC setting:\n *\n * - server should call this function with `fn` argument, the returned function should be ignored;\n * - client should call this function without `fn` argument, the returned function is the stub to call the server.\n *\n * This function supports bidirectional RPC when both sides are passing the `fn` argument.\n *\n * When calling the returned function stub, the arguments and return value are transferred over `MessagePort`.\n * Thus, they will be cloned by the underlying structured clone algorithm.\n *\n * The returned stub has a variant `withOptions` for passing transferables and abort signal.\n *\n * @param {MessagePort} port - The `MessagePort` object to send the calls. The underlying `MessageChannel` must be exclusively used by this function only.\n * @param {Function} fn - The function to invoke. If not set, this RPC cannot be invoked by the other side of `MessagePort`.\n *\n * @returns An asynchronous function, when called, will invoke the function on the other side of `MessagePort`.\n */\nexport default function messagePortRPC<C extends Subroutine>(port: MessagePort): ClientStubWithExtra<C>;\n\nexport default function messagePortRPC<C extends Subroutine, S extends Subroutine = C>(\n port: MessagePort,\n fn: ServerStub<S>\n): ClientStubWithExtra<C>;\n\nexport default function messagePortRPC<C extends Subroutine, S extends Subroutine = C>(\n port: MessagePort,\n fn: ServerStub<S>,\n options: { signal: AbortSignal }\n): ClientStubWithExtra<C>;\n\nexport default function messagePortRPC<C extends Subroutine, S extends Subroutine = C>(\n port: MessagePort,\n fn?: ServerStub<S>\n): ClientStubWithExtra<C> {\n // We cannot neuter the input port because it would cause memory leak:\n // - We can neuter a port by passing it through Structured Clone Algorithm so the input port will become non-functional\n // - After a port is neutered, closing the neutered port will not close the cloned port\n // - Thus, the port owner will no longer able to close the port\n // - This defeated our philosophy: whoever pass a resources to a function, should own the resources unless it is intentional and no other workarounds\n\n type ClientSubroutineParameters = Parameters<C>;\n type ClientSubroutineReturnValue = ReturnValueOfPromise<ReturnType<C>>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const handleMessage = (event: MessageEvent<RPCCallMessage<S>>): void => {\n const data = event.data as RPCCallMessage<S> | undefined;\n\n if (Array.isArray(data) && data[0] === CALL) {\n event.stopImmediatePropagation();\n\n const [returnPort] = event.ports;\n\n if (!returnPort) {\n throw new Error('RPCCallMessage must contains a port.');\n }\n\n if (fn) {\n (async function () {\n const abortController = new AbortController();\n\n try {\n returnPort.onmessage = ({ data }) => Array.isArray(data) && data[0] === ABORT && abortController.abort();\n\n returnPort.postMessage([\n RESOLVE,\n await fn.call({ signal: abortController.signal }, ...(data.slice(1) as Parameters<S>))\n ]);\n } catch (error) {\n returnPort.postMessage([REJECT, error]);\n } finally {\n returnPort.close();\n }\n })();\n } else {\n returnPort.postMessage([\n REJECT,\n new Error(\n 'No function was registered on this RPC. This is probably calling a client which do not implement the function.'\n )\n ]);\n returnPort.close();\n }\n }\n };\n\n port.addEventListener('message', handleMessage);\n port.start();\n\n const createWithOptions =\n (init: CallInit): ((...args: ClientSubroutineParameters) => Promise<ClientSubroutineReturnValue>) =>\n (...args) => {\n return new Promise<ClientSubroutineReturnValue>((resolve, reject) => {\n const { port1, port2 } = new MessageChannel();\n\n port1.onmessage = event => {\n const data = event.data as RPCRejectMessage | RPCResolveMessage<C>;\n\n if (data[0] === RESOLVE) {\n resolve(data[1]);\n } else {\n reject(data[1]);\n }\n\n port1.close();\n };\n\n init?.signal?.addEventListener('abort', () => {\n port1.postMessage([ABORT]);\n port1.close();\n\n reject(new Error('Aborted.'));\n });\n\n port.postMessage([CALL, ...args] satisfies RPCCallMessage<C>, [port2, ...(init.transfer || [])]);\n });\n };\n\n const stub = createWithOptions({}) as ClientStubWithExtra<C>;\n\n stub.withOptions = createWithOptions;\n\n return stub;\n}\n","// Naming is from https://www.w3.org/History/1992/nfs_dxcern_mirror/rpc/doc/Introduction/HowItWorks.html.\n\nimport messagePortRPC from './messagePortRPC.ts';\n\nconst GENERATE = 'GENERATOR_GENERATE';\n\n// type GeneratorSubroutine<TArgs extends unknown[] = any[], T = unknown, TReturn = any, TNext = unknown> = (\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype GeneratorSubroutine = (...args: any[]) => AsyncGenerator | Generator | AsyncIterator<unknown> | Iterator<unknown>;\ntype RPCGeneratorGenerateMessage<T extends GeneratorSubroutine> = [\n typeof GENERATE,\n Readonly<{\n asyncDispose: MessagePort;\n next: MessagePort;\n return: MessagePort;\n throw: MessagePort;\n }>,\n ...Parameters<T>\n];\n\ntype CallInit = {\n signal?: AbortSignal;\n transfer?: Transferable[];\n};\n\ntype NextOfGenerator<T extends AsyncGenerator | Generator | AsyncIterator<unknown> | Iterator<unknown>> =\n T extends AsyncGenerator<unknown, unknown, infer U> ? U : T extends Generator<unknown, unknown, infer V> ? V : never;\ntype ReturnOfGenerator<T extends AsyncGenerator | Generator | AsyncIterator<unknown> | Iterator<unknown>> =\n T extends AsyncGenerator<unknown, infer U> ? U : T extends Generator<unknown, infer V> ? V : never;\ntype YieldOfGenerator<T extends AsyncGenerator | Generator | AsyncIterator<unknown> | Iterator<unknown>> =\n T extends AsyncGenerator<infer U>\n ? U\n : T extends Generator<infer U>\n ? U\n : T extends AsyncIterator<infer U>\n ? U\n : T extends Iterator<infer U>\n ? U\n : never;\n\n// Regardless whether T returns Promise or not, the client stub must return Promise.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ClientGeneratorStub<T extends GeneratorSubroutine> = (\n ...args: Parameters<T>\n) => AsyncGenerator<YieldOfGenerator<ReturnType<T>>, ReturnOfGenerator<ReturnType<T>>, NextOfGenerator<ReturnType<T>>>;\n\ntype ClientGeneratorStubWithExtra<T extends GeneratorSubroutine> = ClientGeneratorStub<T> & {\n /**\n * Creates a new stub with options.\n *\n * @param {AbortSignal} init.signal - Abort signal to abort the call to the stub.\n * @param {Transferable[]} init.transfer - Transfer ownership of objects specified in `args`.\n */\n withOptions: (init: CallInit) => ClientGeneratorStub<T>;\n};\n\ntype ServerStub<T extends GeneratorSubroutine> = (...args: Parameters<T>) => ReturnType<T>;\n\n/**\n * Binds a generator function to a `MessagePort` in RPC fashion and/or create a RPC function stub connected to a `MessagePort`.\n *\n * In a traditional RPC setting:\n *\n * - server should call this generator function with `fn` argument, the returned function should be ignored;\n * - client should call this generator function without `fn` argument, the returned function is the stub to call the server.\n *\n * This function supports bidirectional RPC when both sides are passing the `fn` argument.\n *\n * When calling the returned function stub, the arguments and return value are transferred over `MessagePort`.\n * Thus, they will be cloned by the underlying structured clone algorithm.\n *\n * The returned stub has a variant `withOptions` for passing transferables and abort signal.\n *\n * Notes: if `next()` is used on the client stub and did not iterate until `{ done: true }`, caller must use the `withOptions({ signal: AbortSignal })`\n * to release resources.\n *\n * @param {MessagePort} port - The `MessagePort` object to send the calls. The underlying `MessageChannel` must be exclusively used by this function only.\n * @param {Function} fn - The generator function to invoke. If not set, this RPC cannot be invoked by the other side of `MessagePort`.\n *\n * @returns An asynchronous generator function, when called, will invoke the generator function on the other side of `MessagePort`.\n */\nexport default function forGenerator<C extends GeneratorSubroutine>(port: MessagePort): ClientGeneratorStubWithExtra<C>;\n\nexport default function forGenerator<C extends GeneratorSubroutine, S extends GeneratorSubroutine = C>(\n port: MessagePort,\n fn: ServerStub<S>\n): ClientGeneratorStubWithExtra<C>;\n\nexport default function forGenerator<C extends GeneratorSubroutine, S extends GeneratorSubroutine = C>(\n port: MessagePort,\n fn: ServerStub<S>,\n options: { signal: AbortSignal }\n): ClientGeneratorStubWithExtra<C>;\n\nexport default function forGenerator<C extends GeneratorSubroutine, S extends GeneratorSubroutine = C>(\n port: MessagePort,\n fn?: ServerStub<S>\n): ClientGeneratorStubWithExtra<C> {\n // We cannot neuter the input port because it would cause memory leak:\n // - We can neuter a port by passing it through Structured Clone Algorithm so the input port will become non-functional\n // - After a port is neutered, closing the neutered port will not close the cloned port\n // - Thus, the port owner will no longer able to close the port\n // - This defeated our philosophy: whoever pass a resources to a function, should own the resources unless it is intentional and no other workarounds\n\n type ClientSubroutineParameters = Parameters<C>;\n type ClientSubroutineYield = YieldOfGenerator<ReturnType<C>>;\n type ClientSubroutineReturn = ReturnOfGenerator<ReturnType<C>>;\n type ClientSubroutineNext = NextOfGenerator<ReturnType<C>>;\n type ClientSubroutineIteratorResult = IteratorResult<ClientSubroutineYield, ClientSubroutineReturn>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const handleMessage = (event: MessageEvent<RPCGeneratorGenerateMessage<S>>): void => {\n const data = event.data as RPCGeneratorGenerateMessage<S> | undefined;\n\n if (Array.isArray(data) && data[0] === GENERATE) {\n event.stopImmediatePropagation();\n\n if (!fn) {\n throw new Error(\n 'No function was registered on this RPC. This is probably calling a client which do not implement the function.'\n );\n }\n\n const [_, messagePorts, ...args] = data;\n\n const generator = fn(...args);\n\n messagePortRPC(messagePorts.next, generator.next.bind(generator));\n\n // This is a slight deviation from the actual `Iterator`.\n // In the original approach, `Iterator.return` is not defined.\n // In our approach, the client stub does not know if the server stub has `Iterator.return` defined or not, instead, we simply return `{ done: true }`.\n messagePortRPC(messagePorts.return, value => generator.return?.(value) || { done: true });\n messagePortRPC(messagePorts.throw, error => generator.throw?.(error) || Promise.reject(error));\n messagePortRPC(messagePorts.asyncDispose, async (): Promise<void> => {\n const symbolAsyncDispose: typeof Symbol.asyncDispose = Symbol.asyncDispose || Symbol.for('Symbol.asyncDispose');\n const symbolDispose: typeof Symbol.dispose = Symbol.dispose || Symbol.for('Symbol.dispose');\n\n symbolAsyncDispose in generator\n ? await generator[symbolAsyncDispose]()\n : symbolDispose in generator && generator[symbolDispose]();\n });\n }\n };\n\n port.addEventListener('message', handleMessage);\n port.start();\n\n const createWithOptions = (\n init: CallInit\n ): ((\n ...args: ClientSubroutineParameters\n ) => AsyncGenerator<ClientSubroutineYield, ClientSubroutineReturn, ClientSubroutineNext>) => {\n let checkAborted: (() => void) | undefined;\n\n return (...args: ClientSubroutineParameters) => {\n const { port1: asyncDisposePort1, port2: asyncDisposePort2 } = new MessageChannel();\n const { port1: nextPort1, port2: nextPort2 } = new MessageChannel();\n const { port1: returnPort1, port2: returnPort2 } = new MessageChannel();\n const { port1: throwPort1, port2: throwPort2 } = new MessageChannel();\n\n const subInit = { signal: init.signal };\n\n const closePorts = () => {\n asyncDisposePort1.close();\n asyncDisposePort2.close();\n nextPort1.close();\n nextPort2.close();\n returnPort1.close();\n returnPort2.close();\n throwPort1.close();\n throwPort2.close();\n };\n\n const asyncDisposeRPC = messagePortRPC<() => void>(asyncDisposePort1).withOptions(subInit);\n\n const nextRPC =\n messagePortRPC<(next: ClientSubroutineNext | void) => ClientSubroutineIteratorResult>(nextPort1).withOptions(\n subInit\n );\n\n const returnRPC =\n messagePortRPC<(returnValue: ClientSubroutineReturn) => ClientSubroutineIteratorResult>(\n returnPort1\n ).withOptions(subInit);\n\n const throwRPC =\n messagePortRPC<(error: unknown) => ClientSubroutineIteratorResult>(throwPort1).withOptions(subInit);\n\n let finished = false;\n const callGenerator = async (\n fn: () => Promise<IteratorResult<ClientSubroutineYield, ClientSubroutineReturn>>\n ): Promise<IteratorResult<ClientSubroutineYield, ClientSubroutineReturn>> => {\n checkAborted?.();\n\n // After the generator returned { done: true, value: any } once, all subsequent calls will be { done: true }.\n if (finished) {\n // It is okay to return without \"value\" property.\n return { done: true } as IteratorResult<ClientSubroutineYield, ClientSubroutineReturn>;\n }\n\n const result = await fn();\n\n if (result.done) {\n finished = true;\n\n closePorts();\n }\n\n return result;\n };\n\n const asyncDisposeGenerator = async (fn: () => Promise<void>): Promise<void> => {\n checkAborted?.();\n\n await fn();\n\n finished = true;\n closePorts();\n\n checkAborted = () => {\n throw new Error('This generator has been disposed.');\n };\n };\n\n const generator: AsyncGenerator<ClientSubroutineYield, ClientSubroutineReturn, ClientSubroutineNext> = {\n next: (value: NextOfGenerator<ReturnType<C>> | void) => callGenerator(() => nextRPC(value)),\n return: (value: ReturnOfGenerator<ReturnType<C>>) => callGenerator(() => returnRPC(value)),\n throw: (error: unknown) => callGenerator(() => throwRPC(error)),\n // Ponyfills for Symbol.asyncDispose\n [Symbol.asyncDispose || Symbol.for('Symbol.asyncDispose')]: () =>\n asyncDisposeGenerator(() => asyncDisposeRPC()),\n [Symbol.asyncIterator]: () => generator\n };\n\n port.postMessage(\n [\n GENERATE,\n { asyncDispose: asyncDisposePort2, next: nextPort2, return: returnPort2, throw: throwPort2 },\n ...args\n ] satisfies RPCGeneratorGenerateMessage<C>,\n [...(init.transfer || []), asyncDisposePort2, nextPort2, returnPort2, throwPort2]\n );\n\n init.signal?.addEventListener(\n 'abort',\n () => {\n checkAborted = () => {\n throw new Error('This generator has been aborted.');\n };\n\n closePorts();\n },\n { once: true }\n );\n\n return generator;\n };\n };\n\n const stub = createWithOptions({}) as ClientGeneratorStubWithExtra<C>;\n\n stub.withOptions = createWithOptions;\n\n return stub;\n}\n"],"mappings":"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","names":["data","fn"]}