Transports
Devframe serves live RPC over two interchangeable transports, WebSocket and SSE, so an RPC client connects even where the WebSocket upgrade is unavailable (serverless, buffering proxies). Both speak the identical birpc wire protocol, transparent to your RPC code.
What the node side binds
A live instance binds both by default:
- WebSocket at
<base>__ws: primary, one full-duplex socket. - SSE at
<base>__sse, one method-dispatched route:GETopens the server→client stream,POSTcarries client→server frames. It rides the same HTTP routes as__connection.json, so wherever discovery works SSE works, including middleware-only host frameworks (the Vite bridge,initDevframe'shandler/nodeMiddleware).
__connection.json advertises what's bound; backend is the primary:
{
"backend": "websocket",
"websocket": { "path": "__ws" },
"sse": { "path": "__sse" }
}The SSE stream sends a keep-alive comment every 30 seconds. Both endpoints share one session space, identical for auth, shared-state, and streaming replay.
Configuring
// SSE-only: host frameworks/proxies where the upgrade can't happen.
// RPC clients connect over SSE automatically (backend: 'sse').
initDevframe(def, { base: '/__my-tool/', ws: false })
// WebSocket-only: opt out of the SSE endpoint.
initDevframe(def, { base: '/__my-tool/', server, sse: false })
// Rename the SSE route.
initDevframe(def, { base: '/__my-tool/', server, sse: { route: '__events' } })ws: false + sse: false runs RPC-less (backend: 'none'); the SPA, discovery, and MCP routes still serve. The same options apply to createDevServer, initHub, and cli.ws / cli.sse defaults.
What the RPC client picks
connectDevframe connects over the declared primary, preferring WebSocket when both are present; a socket-less server advertises SSE as primary, so the RPC client lands there directly.
Pin a transport when you know better, e.g. an intermediary that silently strips WS upgrades:
const client = await connectDevframe({ transport: 'sse' })
client.transport // 'websocket' | 'sse' | 'static': what actually connectedPinning an unadvertised transport rejects. SSE follows the same proxy-safe rules as WebSocket: relative paths against __connection.json's URL, explicit host/port only for a cross-origin endpoint.
A dropped SSE stream ends the RPC client like a closed socket: pending calls reject, status moves to disconnected, reconnect via connectDevframe.
In-Page Channel
The in-page channel connects a devframe's page script to its panels entirely in the browser: typed events, calls, and page-script-authoritative shared state, with no server involved.
Security
Devframe tools are secure by default: connections bind to localhost, and dev-mode RPC requires a trust handshake before accepting a browser.