import { nanoid } from 'nanoid';
import { DecryptError } from '~/decrypt-worker/util/DecryptError';
type WorkerServerHandler
= (payload: P) => R | Promise;
interface SerializedError {
message: string;
stack?: string;
code?: string;
}
interface WorkerClientRequestPayload {
id: string;
action: string;
payload: P;
}
interface WorkerServerResponsePayload {
id: string;
result: R;
error: SerializedError;
}
export class WorkerClientBus {
private idPromiseMap = new Map void, (error: Error) => void]>();
constructor(private worker: Worker) {
worker.addEventListener('message', this.eventHandler);
}
eventHandler = (e: MessageEvent) => {
const { id, result, error } = e.data;
const actionPromise = this.idPromiseMap.get(id);
if (!actionPromise) {
console.error('cound not fetch worker promise for action: %s', id);
return;
}
this.idPromiseMap.delete(id);
const [resolve, reject] = actionPromise;
if (error) {
const wrappedError = new Error(error.message, { cause: error });
wrappedError.stack = error.stack;
Object.assign(wrappedError, { code: error.code ?? null });
reject(wrappedError);
} else {
resolve(result as never);
}
};
async request(actionName: T, payload: P): Promise {
return new Promise((resolve, reject) => {
const id = `request://${actionName}/${nanoid()}`;
this.idPromiseMap.set(id, [resolve, reject]);
this.worker.postMessage({
id,
action: actionName,
payload,
});
});
}
}
export class WorkerServerBus {
private handlers = new Map>();
addEventHandler(actionName: string, handler: WorkerServerHandler) {
this.handlers.set(actionName, handler as WorkerServerHandler);
}
onmessage = async (e: MessageEvent) => {
const { id, action, payload } = e.data;
const handler = this.handlers.get(action);
let result = null;
let error = null;
if (!handler) {
error = new Error('Handler missing for action ' + action);
} else {
try {
result = await handler(payload);
} catch (err: unknown) {
if (err instanceof DecryptError) {
error = err.toJSON();
} else {
error = err;
}
}
}
postMessage({ id, result, error });
};
}