Skip to content

Commit

Permalink
Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
qwtel committed Aug 31, 2024
1 parent 2555334 commit 0ac506b
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 36 deletions.
21 changes: 11 additions & 10 deletions src/caplink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,19 +465,20 @@ export function wrap<T>(ep: Endpoint, target?: any): Remote<T> {
return createProxy<T>(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 } : {},
);
}
}

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 {
Expand Down Expand Up @@ -518,7 +519,7 @@ function createProxy<T>(
path: PropertyKey[] = [],
target: object = function () {}
): Remote<T> {
let isProxyReleased = false;
let isProxyReleased: boolean|string|Error = false;
const proxy = new Proxy(target, {
get(_target, prop) {
if (prop === Symbol.dispose || prop === releaseProxy) {
Expand Down Expand Up @@ -632,16 +633,16 @@ function createProxy<T>(
// 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);
});
Expand Down
42 changes: 17 additions & 25 deletions src/node-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
};
}
3 changes: 2 additions & 1 deletion src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

import type { TypedEventTarget } from "@workers/typed-event-target";

export type MessageEventTarget = Pick<TypedEventTarget<MessagePortEventMap>, "addEventListener"|"removeEventListener">;
export type MessageEventTargetEventMap = MessagePortEventMap & { error: ErrorEvent, close: CloseEvent };
export type MessageEventTarget = Pick<TypedEventTarget<MessageEventTargetEventMap>, "addEventListener"|"removeEventListener">;

export const messageChannel = Symbol('Caplink.messageChannel');
export const adoptNative = Symbol('Caplink.adaptNative');
Expand Down

0 comments on commit 0ac506b

Please sign in to comment.