Tunnelling & proxies

Reaching an in-VM port from outside the tab — the WebSocket tunnel and relay, and the CORS proxy for outbound requests.

Previews reach an in-VM port from the same tab. Two other mechanisms cross a real network boundary: a tunnel (expose an in-VM port to the outside), and a CORS proxy (let the VM reach hosts a tab can't).

The WebSocket tunnel

WebSocketTunnel connects a box's port to a relay over a WebSocket, so something outside the tab — a phone running Expo Go, a teammate's browser — can reach a dev server inside the VM. The relay (apps/tunnel-server) accepts connections and forwards HTTP and WebSocket traffic in both directions.

  • Framing — tunnel frames are Buffer-based (the ws receiver reads binary frames), carrying request/response and WebSocket-upgrade traffic.
  • Path-based multiplexing — run the relay without a fixed port and it routes by a path prefix (`/<port>/…`), stripping the prefix before handing the request to the box, so tunnelled URLs stay clean.
  • Headers preserved — the relay forwards headers (including Host), which matters for dev servers that build URLs from req.headers.host.

A typical native-device setup: run the relay on your machine, start a tunnel from the VM, and point the dev server's public URL at the relay:

# on your machine
node apps/tunnel-server/server.js --port 8081

# inside the VM
tunnel --server ws://localhost:3005 &

The tunnel, forward, and ports commands manage this from the shell.

The CORS proxy

Outbound requests are the mirror image. A browser tab can't fetch arbitrary cross-origin hosts, so requests to known non-CORS hosts (e.g. api.expo.dev, exp.host, u.expo.dev) are routed through a same-origin CORS proxy`/_cors?url=…` — which fetches server-side and returns the response with permissive CORS headers.

The Node layer wires this in transparently: the fetch a module receives (and require("fetch-nodeshim")) proxies those hosts automatically, so packages that call them work inside a tab without any code change. Configure it with LIFO_CORS_PROXY (the proxy base) and LIFO_CORS_PROXY_HOSTS.

Note

The relay is per-session and manual today. A hosted tunnel/proxy — so exposing a port doesn't require running your own relay — is on the roadmap, along with hardening the relay (auth, reconnect, multiplexing).