seizing the means of shared worker production
A POC demonstrating multi-tab communication entirely through browser
SharedWorker instances — no server-side state, no WebSocket, no
BroadcastChannel. Pages discover each other through a shared "register"
worker and exchange messages through per-session "inbox" workers.
- Vite + React 19 + TypeScript for the client
- Tailwind CSS v4 + shadcn/ui (Base UI primitives via the
base-novastyle) - Bun as the dev runtime and static file server
Live deploy: https://compunism.fly.dev/
Dev (HMR):
bun install
bun run devProduction preview (build + serve via Bun):
bun run serveBoth serve at http://localhost:4137. Open additional tabs at the same URL
to spawn more sessions; use the pop out button to launch a window that
shares the original tab's session id (carried via the URL hash,
#sess=<id>).
Containerized with Dockerfile, deployed to Fly:
fly deploy --remote-onlyThe server reads PORT from the env (defaults to 4137 locally; 8080 in
the container, which Fly's proxy fronts on 443). Browser auto-open is
skipped when FLY_APP_NAME or CI is set.
Two SharedWorker scripts, instantiated by name:
register-worker.js(single instance, name"register"): the discovery hub. Every page connects to it, announces itssessionId, and receives the current list of live sessions. The register pushes the updated list to every connected page whenever a session joins or leaves — no polling.session-worker.js(one instance per session, name"sess-<id>"): the per-session inbox. The session owner connects to receive incoming messages; senders connect to the same name to deliver. Sibling windows of the same session (via pop-out) all connect to the same instance.
sessionId— addressable identity. Shared across pop-out windows opened from the same origin page.windowId— unique per page load. Used by the session worker to know which port to skip when broadcasting, so an outgoing message doesn't echo back to its originator but does reach sibling windows.
The register tracks each session's ports and only drops the session when its last port unregisters — this prevents a closing pop-out from removing a session that the parent window still owns. Two cleanup signals:
beforeunloadsends an explicitunregister(covers normal tab close).- Heartbeat (every 5s) + stale prune (15s) covers crashes and force-kills
where
beforeunloaddoesn't fire.
Sending text from (session A, window α) to peer session B:
- Post
{from: A, windowId: α, text}tosess-B.sess-Bbroadcasts to all of its ports except the one tagged with windowIdα— every window of session B (including pop-outs) renders it as incoming. - Render
me → B: textlocally for immediate feedback. - Post
{from: A, windowId: α, text, to: B}tosess-A. Sibling windows of session A receive it and renderme (α) → B: text, keeping every window of a shared session in sync.
| File | Role |
|---|---|
server.ts |
Bun static file server (serves dist/) + browser launcher |
vite.config.ts |
Vite + Tailwind v4 + path alias config |
index.html |
Vite entry HTML |
src/main.tsx |
React entry |
src/App.tsx |
UI composed from shadcn (Base UI) components |
src/hooks/useSession.ts |
Session/window id, SharedWorker wiring, send/popout |
src/components/ui/* |
shadcn Base UI components (base-nova style) |
public/register-worker.js |
Discovery hub with refcounted sessions |
public/session-worker.js |
Per-session inbox; windowId-based routing |