Protocols

The wire protocols that connect the VM to the outside — the service-worker bridge, the tunnel frames, and I/O streaming.

Lifo is mostly in-process, but a few seams cross a boundary: the page ↔ service worker, the service worker ↔ VM, the VM ↔ relay, and the shell ↔ terminal. Each has a small, deliberate protocol.

VirtualRequest / VirtualResponse

The common currency is an HTTP-like message pair defined by the kernel: a VirtualRequest (method, url, headers, body) and a VirtualResponse (status, headers, body). Everything that wants to talk to an in-VM server produces a VirtualRequest and gets a VirtualResponse back — the service worker, a tunnel, and in-VM clients all speak it.

Service worker ↔ page bridge

The ServiceWorkerBridge connects the page's registered `/sw.js` to the box. The worker:

  • Intercepts fetch for `/_sw/<boxId>/<port>/…` and forwards a VirtualRequest into the VM handler for <port>, streaming the response back.
  • Routes a client's later requests by client id (a clientPorts map), so only the entry URL carries the `/_sw/<boxId>/<port>` prefix.
  • Injects two shims into VM-served HTML: a path shim (strips the `/_sw/<boxId>/<port>` prefix via history.replaceState before app code runs, and restores it on reload via sessionStorage) and a WebSocket shim (so in-app sockets route through the worker).

Because a service worker update swaps control, a client needs one reload after sw.js changes for the new worker to claim the page.

Tunnel frames

A WebSocketTunnel multiplexes traffic between an in-VM port and the relay over a single WebSocket. Frames are Buffer-based (the ws receiver reads them as binary), carrying both HTTP request/response bodies and WebSocket-upgrade traffic. When the relay runs path-based, it prefixes `/<port>` on the way in and strips it before the box sees the request, so public URLs stay clean while the box still serves at its own paths.

HMR over WebSocket

Dev-server hot updates (Vite's /hot, Metro's socket) are WebSocket upgrades. They ride the same bridge/tunnel: the upgrade is proxied to the in-VM server's socket, and frames are relayed as Buffers in both directions, so Fast Refresh and HMR work inside a preview or over a tunnel.

I/O streaming

Inside a box, a command's stdout/stderr are streamed incrementally (the shell threads per-execution stream defaults so concurrent runs don't clobber each other). At the edge, sandbox.commands.run() surfaces this as onStdout / onStderr callbacks, and stdin is fed either as a string or, on an interactive terminal, keystroke-by-keystroke — including raw mode for prompt-driven CLIs. See The Node runtime for how a script's process.stdin/stdout bind to this.