CDP Connection

View as Markdown

How to connect to a Chaser session, what works, and what to avoid.

The endpoint

Every ready session exposes a standard Chrome DevTools Protocol WebSocket:

wss://cdp.chaser.sh/sessions/{session_id}

This is the same protocol your local browser uses. Any CDP-compatible client works.

1import { chromium } from "patchright";
2
3const browser = await chromium.connectOverCDP(cdpUrl);
4const context = browser.contexts()[0];
5const page = context.pages()[0] ?? await context.newPage();
6
7await page.goto("https://www.cloudflare.com");
8console.log(await page.title());

connectOverCDP attaches to the existing browser process. Do not call chromium.launch() — that starts a local browser, which is not what you want.

Connecting with Puppeteer

1import puppeteer from "puppeteer-core";
2
3const browser = await puppeteer.connect({ browserWSEndpoint: cdpUrl });
4const [page] = await browser.pages();
5
6await page.goto("https://www.cloudflare.com");
7console.log(await page.title());

Connecting with raw CDP

Any WebSocket client that speaks CDP works. Here’s a minimal example using the ws package:

1import WebSocket from "ws";
2
3const ws = new WebSocket(cdpUrl);
4
5ws.on("open", () => {
6 // Navigate
7 ws.send(JSON.stringify({
8 id: 1,
9 method: "Page.navigate",
10 params: { url: "https://www.cloudflare.com" },
11 }));
12});
13
14ws.on("message", (data) => {
15 const msg = JSON.parse(data.toString());
16 console.log(msg));
17});

For production use, prefer a full CDP client library. The protocol is message-based and asynchronous — you send commands with incrementing id fields and match responses by id.

The default context

Each session has one browser context with pre-warmed state: cookies, localStorage, and navigation history that a real browser would have. Use it:

1const context = browser.contexts()[0];

Do not call browser.createBrowserContext() or browser.newContext(). Additional contexts are not supported and will error.

Pages

The default context may have a blank page open. You can use it or create a new one:

1const pages = context.pages();
2const page = pages.length > 0 ? pages[0] : await context.newPage();

Timeouts

CDP connections to Chaser sessions are subject to the session’s TTL. If the session expires while you’re connected, the WebSocket closes. Handle the close event and re-create the session if needed:

1browser.on("disconnected", () => {
2 console.log("Session ended — create a new one to continue");
3});

What you can do

The full CDP surface is available:

  • Page.navigate, Page.reload, Page.goBack
  • Runtime.evaluate (run JavaScript in the page)
  • DOM.getDocument, DOM.querySelector
  • Input.dispatchKeyEvent, Input.dispatchMouseEvent
  • Network.setCookie, Network.getCookies
  • Emulation.setDeviceMetricsOverride (avoid — it breaks fingerprint consistency)

What to avoid

ActionWhy
Emulation.setDeviceMetricsOverrideOverrides the screen metrics, breaking fingerprint consistency
Emulation.setUserAgentOverrideOverrides the user-agent, breaking client-hint consistency
Network.setBlockedURLsUnnecessary — the session already routes through residential egress
Creating new browser contextsNot supported; use the default context
browser.close()This closes your local connection, not the remote session. Use DELETE /v1/sessions/{id} to end the session.