2023-05-08 16:36:10 +00:00
|
|
|
import { nanoid } from 'nanoid';
|
2023-05-22 21:24:41 +00:00
|
|
|
import { DecryptError } from '~/decrypt-worker/util/DecryptError';
|
2023-05-08 16:36:10 +00:00
|
|
|
|
2023-05-17 23:18:22 +00:00
|
|
|
type WorkerServerHandler<P, R> = (payload: P) => R | Promise<R>;
|
|
|
|
|
|
|
|
interface SerializedError {
|
|
|
|
message: string;
|
|
|
|
stack?: string;
|
2023-05-22 21:24:41 +00:00
|
|
|
code?: string;
|
2023-05-17 23:18:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
interface WorkerClientRequestPayload<P = unknown> {
|
|
|
|
id: string;
|
|
|
|
action: string;
|
|
|
|
payload: P;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface WorkerServerResponsePayload<R = unknown> {
|
|
|
|
id: string;
|
|
|
|
result: R;
|
|
|
|
error: SerializedError;
|
|
|
|
}
|
2023-05-14 23:09:38 +00:00
|
|
|
|
|
|
|
export class WorkerClientBus<T = string> {
|
2023-05-17 23:18:22 +00:00
|
|
|
private idPromiseMap = new Map<string, [(data: never) => void, (error: Error) => void]>();
|
2023-05-08 16:36:10 +00:00
|
|
|
|
|
|
|
constructor(private worker: Worker) {
|
2023-05-17 23:18:22 +00:00
|
|
|
worker.addEventListener('message', this.eventHandler);
|
2023-05-08 16:36:10 +00:00
|
|
|
}
|
|
|
|
|
2023-05-17 23:18:22 +00:00
|
|
|
eventHandler = (e: MessageEvent<WorkerServerResponsePayload>) => {
|
|
|
|
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;
|
2023-05-22 21:24:41 +00:00
|
|
|
Object.assign(wrappedError, { code: error.code ?? null });
|
2023-05-17 23:18:22 +00:00
|
|
|
reject(wrappedError);
|
|
|
|
} else {
|
|
|
|
resolve(result as never);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
async request<R, P>(actionName: T, payload: P): Promise<R> {
|
2023-05-08 16:36:10 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2023-05-18 21:12:45 +00:00
|
|
|
const id = `request://${actionName}/${nanoid()}`;
|
2023-05-08 16:36:10 +00:00
|
|
|
this.idPromiseMap.set(id, [resolve, reject]);
|
|
|
|
this.worker.postMessage({
|
|
|
|
id,
|
|
|
|
action: actionName,
|
|
|
|
payload,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class WorkerServerBus {
|
2023-05-17 23:18:22 +00:00
|
|
|
private handlers = new Map<string, WorkerServerHandler<unknown, unknown>>();
|
2023-05-08 16:36:10 +00:00
|
|
|
|
2023-05-17 23:18:22 +00:00
|
|
|
addEventHandler<R, P>(actionName: string, handler: WorkerServerHandler<P, R>) {
|
|
|
|
this.handlers.set(actionName, handler as WorkerServerHandler<unknown, unknown>);
|
2023-05-08 16:36:10 +00:00
|
|
|
}
|
|
|
|
|
2023-05-17 23:18:22 +00:00
|
|
|
onmessage = async (e: MessageEvent<WorkerClientRequestPayload>) => {
|
2023-05-08 16:36:10 +00:00
|
|
|
const { id, action, payload } = e.data;
|
|
|
|
const handler = this.handlers.get(action);
|
2023-05-17 23:21:11 +00:00
|
|
|
|
|
|
|
let result = null;
|
|
|
|
let error = null;
|
|
|
|
|
2023-05-08 16:36:10 +00:00
|
|
|
if (!handler) {
|
2023-05-17 23:21:11 +00:00
|
|
|
error = new Error('Handler missing for action ' + action);
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
result = await handler(payload);
|
2023-05-22 21:24:41 +00:00
|
|
|
} catch (err: unknown) {
|
|
|
|
if (err instanceof DecryptError) {
|
|
|
|
error = err.toJSON();
|
|
|
|
} else {
|
|
|
|
error = err;
|
|
|
|
}
|
2023-05-17 23:21:11 +00:00
|
|
|
}
|
2023-05-08 16:36:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
postMessage({ id, result, error });
|
|
|
|
};
|
|
|
|
}
|