UNPKG

comlink-electron-endpoint

Version:

Use Comlink to communicate between main and renderer.

122 lines (114 loc) 3.9 kB
import { MessageType, WireValueType } from "comlink/src/protocol"; import type { Endpoint, Message, WireValue } from "comlink/src/protocol"; // MessagePortMain is not StructuredCloneable but the proxy transferHandler // relies on being able to pass a MessagePort via the postMessage body. // Instead, we swap out the passed port for an index into transfers and set the // message type to a proxy token. // if val is transferred endpoint, remove it from the WireValue, and mark with // our own WireValueType (null "SHOULD" be a safe sentinel value for WireValueType) const proxyToken = null as unknown as WireValueType.HANDLER; function packWireValue(val: WireValue, transfers: Transferable[] | undefined) { if (transfers && val.type === WireValueType.HANDLER) { const index = transfers.findIndex((p) => val.value === p); if (index >= 0) { val.type = proxyToken; val.value = index; } } return val; } // check for proxyToken sentinel value and swap out the index for the endpoint function unpackWireValue( val: WireValue, transfers: Transferable[] | undefined, electronEndpoint: (port: MessagePort) => Endpoint ) { if (val.type === proxyToken) { val.type = WireValueType.HANDLER; val.value = electronEndpoint( transfers![val.value as number] as MessagePort ); } return val; } function isMessage(val: Message | WireValue): val is Message { return ( val.type === MessageType.APPLY || val.type === MessageType.CONSTRUCT || val.type === MessageType.ENDPOINT || val.type === MessageType.GET || val.type === MessageType.RELEASE || val.type === MessageType.SET ); } // pack all the transferrables and properly transfer ports function packMessage( message: Message | WireValue, transfers: Transferable[] | undefined ) { if (isMessage(message)) { if (message.type === MessageType.SET) message.value = packWireValue(message.value, transfers); else if ( message.type === MessageType.APPLY || message.type === MessageType.CONSTRUCT ) message.argumentList = message.argumentList.map((v) => packWireValue(v, transfers) ); } else { message = packWireValue(message, transfers); } return message; } // unpack the transferred ports function unpackMessage( message: Message | WireValue, transfers: Transferable[] | undefined, electronEndpoint: (port: MessagePort) => Endpoint ) { if (isMessage(message)) { if (message.type === MessageType.SET) message.value = unpackWireValue( message.value, transfers, electronEndpoint ); else if ( message.type === MessageType.APPLY || message.type === MessageType.CONSTRUCT ) message.argumentList = message.argumentList.map((v) => unpackWireValue(v, transfers, electronEndpoint) ); } else { message = unpackWireValue(message, transfers, electronEndpoint); } return message; } export function electronEndpoint(port: MessagePort): Endpoint { const listeners = new WeakMap(); return { postMessage(message, ports) { // shim for comlink proxy message = packMessage(message, ports); port.postMessage(message, ports as any); }, addEventListener: (type, listener: any) => { const l = ({ data, ports }: any) => { // shim for comlink proxy data = unpackMessage(data, ports, electronEndpoint); listener({ data, ports }); }; port.addEventListener("message", l); listeners.set(listener, l); }, removeEventListener: (type, listener) => { const l = listeners.get(listener); if (!l) return; port.removeEventListener("message", l); listeners.delete(listener); }, start: port.start.bind(port), }; }