From 0ac506be85f91fc156929e2fbd73404926bba22c Mon Sep 17 00:00:00 2001 From: Florian Klampfer Date: Sat, 31 Aug 2024 13:04:42 +0700 Subject: [PATCH] Commit --- src/caplink.ts | 21 +++++++++++---------- src/node-adapter.ts | 42 +++++++++++++++++------------------------- src/protocol.ts | 3 ++- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/caplink.ts b/src/caplink.ts index 19177a4..3a94be7 100644 --- a/src/caplink.ts +++ b/src/caplink.ts @@ -465,9 +465,12 @@ export function wrap(ep: Endpoint, target?: any): Remote { return createProxy(ep, [], target) as any; } -function throwIfProxyReleased(isReleased: boolean) { +function throwIfProxyReleased(isReleased: boolean|string|Error) { if (isReleased) { - throw new Error("Proxy has been released and is not useable"); + throw new Error( + "Proxy has been released and is not useable" + (typeof isReleased === 'string' ? `: ${isReleased}` : ''), + isReleased instanceof Error ? { cause: isReleased } : {}, + ); } } @@ -475,9 +478,7 @@ async function releaseEndpoint(ep: Endpoint, force = false) { if (endpointState.has(ep)) { const { resolvers, messageHandler } = endpointState.get(ep)!; try { - const releasedPromise = !force ? requestResponseMessage(ep, { - type: MessageType.RELEASE, - }) : null; + const releasedPromise = !force && requestResponseMessage(ep, { type: MessageType.RELEASE }); endpointState.delete(ep); // prevent reentry await releasedPromise; // now save to await } finally { @@ -518,7 +519,7 @@ function createProxy( path: PropertyKey[] = [], target: object = function () {} ): Remote { - let isProxyReleased = false; + let isProxyReleased: boolean|string|Error = false; const proxy = new Proxy(target, { get(_target, prop) { if (prop === Symbol.dispose || prop === releaseProxy) { @@ -632,16 +633,16 @@ function createProxy( // If the endpoint gets closed on us, we should mark the proxy as released and reject all pending promises. // This shouldn't really happen since the proxy must be closed from this side, either through manual dispose or finalization registry. // Also note that support for the `close` event is unclear (MDN doesn't document it, spec says it should be there...), so this is a last resort. - ep.addEventListener("close", async () => { - isProxyReleased = true; + ep.addEventListener("close", async (ev) => { + isProxyReleased = ev.reason ?? 'closed'; unregisterProxy(proxy); // Passing the force flag to skip sending a release message, since the endpoint is already closed. await releaseEndpoint(ep, true); }); // Similarly, if the endpoint errors for any reason, we should mark the proxy as released and reject all pending promises. - ep.addEventListener("error", async () => { - isProxyReleased = true; + ep.addEventListener("error", async (ev) => { + isProxyReleased = ev.error instanceof Error ? ev.error : 'errored'; unregisterProxy(proxy); await releaseEndpoint(ep, true); }); diff --git a/src/node-adapter.ts b/src/node-adapter.ts index 5f6ca58..24724a9 100644 --- a/src/node-adapter.ts +++ b/src/node-adapter.ts @@ -21,43 +21,35 @@ import type { Endpoint } from "./protocol.ts"; export interface NodeEndpoint { postMessage(message: any, transfer?: any[]): void; - on( - type: string, - listener: EventListenerOrEventListenerObject, - options?: {} - ): void; - off( - type: string, - listener: EventListenerOrEventListenerObject, - options?: {} - ): void; + on(type: string, listener: (value: any) => void): void; + off(type: string, listener: (value: any) => void): void; start?: () => void; } +const mkl = (eh: EventListenerOrEventListenerObject) => (data: any) => { + if ("handleEvent" in eh) { + eh.handleEvent({ data } as MessageEvent); // XXX: doesn't work for non-MessageEvent + } else { + eh({ data } as MessageEvent); // XXX: doesn't work for non-MessageEvent + } +}; + export default function nodeEndpoint(nep: Endpoint|NodeEndpoint): Endpoint { if (!('on' in nep) || !('off' in nep)) return nep; const listeners = new WeakMap(); return { postMessage: nep.postMessage.bind(nep), - addEventListener: (_: string, eh: any) => { - const l = (data: any) => { - if ("handleEvent" in eh) { - eh.handleEvent({ data } as MessageEvent); - } else { - eh({ data } as MessageEvent); - } - }; - nep.on("message", l); + addEventListener: (name: string, eh: EventListenerOrEventListenerObject) => { + const l = mkl(eh); + nep.on(name, l); listeners.set(eh, l); }, - removeEventListener: (_: string, eh: any) => { + removeEventListener: (name: string, eh: EventListenerOrEventListenerObject) => { const l = listeners.get(eh); - if (!l) { - return; - } - nep.off("message", l); + if (!l) return; + nep.off(name, l); listeners.delete(eh); }, - start: nep.start && nep.start.bind(nep), + ...nep.start && { start: nep.start.bind(nep) }, }; } diff --git a/src/protocol.ts b/src/protocol.ts index 0d170bf..fab1e96 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -6,7 +6,8 @@ import type { TypedEventTarget } from "@workers/typed-event-target"; -export type MessageEventTarget = Pick, "addEventListener"|"removeEventListener">; +export type MessageEventTargetEventMap = MessagePortEventMap & { error: ErrorEvent, close: CloseEvent }; +export type MessageEventTarget = Pick, "addEventListener"|"removeEventListener">; export const messageChannel = Symbol('Caplink.messageChannel'); export const adoptNative = Symbol('Caplink.adaptNative');