export type Result = | { status: "ok"; value: A } | { status: "err"; error: string }; export interface API { save(fileName: string, content: string): string; typeCheck(fileName: string): string; compile(fileName: string): string; } export interface Message { id: number; key: K; args: Parameters; } export interface ResponseMSG { id: number; result: string; } type Suspense = { resolve: (value: any | PromiseLike) => void; reject: (reason?: any) => void; }; export class IPC { callbacks: Record = {}; _postMessage: (msg: Message) => void; lastID = 1; constructor() { const newtWorker = new Worker("worker.js"); this._postMessage = (msg: Message) => newtWorker.postMessage(msg); // Safari/MobileSafari have small stacks in webworkers. if (navigator.vendor.includes("Apple")) { const workerFrame = document.createElement("iframe"); workerFrame.src = "worker.html"; workerFrame.style.display = "none"; document.body.appendChild(workerFrame); this._postMessage = (msg: any) => workerFrame.contentWindow?.postMessage(msg, "*"); window.addEventListener("message", (ev) => this.onmessage(ev)); } else { newtWorker.onmessage = this.onmessage } // Need to handle messages from the other iframe too? Or at least ignore them. } onmessage = (ev: MessageEvent) => { console.log("GET", ev.data); // Maybe key off of type if (ev.data.id) { let suspense = this.callbacks[ev.data.id]; if (suspense) { suspense.resolve(ev.data.result); delete this.callbacks[ev.data.id]; } console.log("result", ev.data, "suspense", suspense); } } async sendMessage( key: K, args: Parameters ): Promise> { return new Promise((resolve, reject) => { let id = this.lastID++; this.callbacks[id] = { resolve, reject }; console.log("POST", { id, key, args }); this._postMessage({ id, key, args }); }); } } class IPCClient {}