Run a Relay for external CDP tools
You have a Host with paired Targets and want an external CDP tool to drive them over WebSocket. The Host has no listening socket. The Relay supplies a browser-level CDP endpoint, one direct endpoint per Target, and a separate raw-message uplink for the Host.
This guide assumes you already have a running Host that has paired at least one Target. If you do not, see Pair an iframe.
Start the Relay
The Node adapter, @olimsaidov/icdp/relay/node, runs on node:http + ws and needs Node >= 22.
import { serveRelay } from "@olimsaidov/icdp/relay/node";
const relay = await serveRelay({ hostPort: 3000, browserPort: 9229 });
console.log(relay.hostWsUrl); // ws://127.0.0.1:3000/icdp/host <- the Host uplinks here
console.log(relay.browserWsUrl); // ws://127.0.0.1:9229/devtools/browser <- Clients connect here
console.log(relay.targetWsUrl("preview")); // ws://127.0.0.1:9229/devtools/page/previewserveRelay resolves to a RelayServer with these endpoint helpers:
relay.hostWsUrl— the bridge endpoint. Hand this tohost.connectRelay.relay.browserWsUrl— the browser-level CDP endpoint.relay.targetWsUrl(targetId)— the direct endpoint advertised for one Target.
The default hostHostname and browserHostname are 127.0.0.1. The default paths are /icdp/host, /devtools/browser, and /devtools/page/<targetId>.
TIP
Pass hostPort: 0 or browserPort: 0 (the defaults) to let the OS assign free ports, then read the real values back from relay.hostPort and relay.browserPort.
Uplink the Host
The Relay serves exactly one Host. Point the Host at relay.hostWsUrl:
const disconnect = host.connectRelay({ url: relay.hostWsUrl });The uplink carries Target summaries, Client-id snapshots, and raw CDP frames. A local console panel and remote Clients can attach to the same Targets at once. connectRelay returns a disconnect function and replaces any existing uplink. After an unexpected close the uplink reconnects (default 500 ms; override with reconnectDelayMs).
One validated Host at a time
A newly connected Host is inert until its ready frame passes bridge validation. It then takes over from the old Host: the Relay drops the previous socket (close code 1008) and replaces the cached Target set. If its stable Host instance id differs, the Relay also closes connected Clients (code 1012) so they reconnect, rediscover, and attach without carrying stale Session ids into the replacement Host. A transient reconnect by the same Host keeps Client sockets and Sessions. Run a single Host per Relay.
Connect a Client
A multi-Target Client connects to relay.browserWsUrl and uses the flat-session protocol: Target.getTargets, then Target.attachToTarget and sessionId routing. Clients that expect a page socket can connect to the per-Target webSocketDebuggerUrl returned by /json/list. See Connect a CDP Client for both forms.
Discover Targets over HTTP
The Relay answers Chrome's HTTP discovery routes on the browser/CDP server, so existing CDP tooling can find the endpoint without exposing the Host uplink:
/json/version— protocol and product info, including thewebSocketDebuggerUrl./jsonand/json/list— the current Target list./icdp/status— the Relay's Host/Client/Target state.
Anything else on the browser/CDP server returns 404. The optional fallback handler is only for ordinary HTTP requests on the Host server, useful when the same public port also serves your shell page. See HTTP endpoints for the exact payloads.
Stop the Relay
await relay.stop();stop() terminates open WebSockets and closes both HTTP servers. It resolves once both servers have shut down.
Debugging
Set ICDP_DEBUG=1 to log every HTTP request, WebSocket upgrade, and frame the Relay handles:
ICDP_DEBUG=1 node server.jsNext steps
- Connect a CDP Client — discover, attach, and run commands against a Target.
- Embed a Relay in another runtime — use the runtime-agnostic
RelayCoreoutside Node. - Relay reference — the transport and discovery surface.