comlink-electron-endpoint
Version:
Use Comlink to communicate between main and renderer.
122 lines (114 loc) • 3.9 kB
text/typescript
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),
};
}