// The worker half of the standalone deployment (improvise-9x6): strings only, // every decision in Rust. The two rules it must not break: context/code/clients.md. import init, { start } from "./worker-pkg/improvise_worker_server.js"; import { serialize } from "./serial.js"; const params = new URLSearchParams(self.location.search); const DOC = params.get("doc") || "demo.improv"; // Where the first copy of the document comes from on a fresh origin: fetched // and WRITTEN through the VFS, never injected past storage. const SEED = params.get("seed") || "demo.improv"; // Fetch the seed. A failure is NOT decided here: the bytes and the reason both // go to Rust, which knows whether the document needed a first copy at all. async function seedBytes() { try { const res = await fetch(SEED); if (!res.ok) return { error: `${SEED} answered ${res.status} ${res.statusText}` }; return { bytes: new Uint8Array(await res.arrayBuffer()) }; } catch (err) { return { error: `${SEED} could not be fetched: ${err?.message ?? err}` }; } } let server = null; function post(replies) { for (const reply of replies) self.postMessage(reply); } /** A shell notice, posted as an OBJECT so the page can tell it from a protocol * frame without parsing one. See `shell.js::classifyWorkerMessage`. */ function notify(notice) { self.postMessage(notice); } async function handle(data) { const replies = server.on_message(data); post(replies); await server.flush(); // A persist nobody asked for that did not happen (improvise-zre1). Drained // per message, so one failure is reported once. const raised = server.take_notice(); if (raised) notify(JSON.parse(raised)); } // Startup, and the head of the chain: frames arrive before this settles, and // chaining onto it holds them in arrival order with no queue to drain. const started = (async () => { await init(); const seed = await seedBytes(); server = await start(DOC, seed.bytes, seed.error); // Read once, straight after start: read-only, or an empty document whose // seed never arrived. Both are open sessions, so this is not a failure. const boot = server.boot_notice(); if (boot) notify(JSON.parse(boot)); })(); started.catch((err) => { console.error("improvise worker: failed to start", err); // A worker that rejects in `start()` fires NO error on the main thread, so // without this the page has only a watchdog and no reason (improvise-1wft). notify({ kind: "BootFailed", document: DOC, detail: String(err?.message ?? err), }); }); // One frame at a time, in arrival order, and the listener is deliberately NOT // `async`: the chaining is `serial.js`'s and is tested there. const onFrame = serialize( started, async (data) => { // `start()` failed, and the page has already been told why by the // `BootFailed` notice above. There is nothing to answer with. if (!server) return; await handle(data); }, // An unhandled rejection here reaches the devtools console and nothing // else. Caught, so one bad frame does not poison every later one. (err, data) => console.error("improvise worker: frame rejected", data, err), ); self.onmessage = (event) => { onFrame(event.data); };