# How ego-browser Uses Chrome DevTools Protocol (CDP) for Communication

> Discover how ego-browser leverages Chrome DevTools Protocol (CDP) for communication through a three-layer architecture. Learn about raw transport, session management, and evaluation helpers.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: internals
- Published: 2026-08-16

---

**`ego-browser` communicates with Chrome DevTools Protocol via a three-layer architecture wrapped around `globalThis.ego.sendCDPMessage`, providing raw transport handling, automatic session management, and high-level evaluation helpers.**

The `ego-browser` package in the [citrolabs/ego-lite](https://github.com/citrolabs/ego-lite) repository implements a lightweight, resilient client for **Chrome DevTools Protocol (CDP)**. It abstracts the complexity of direct message passing while maintaining full access to browser automation capabilities, allowing scripts to execute commands, evaluate JavaScript, and subscribe to browser events.

## CDP Communication Architecture

The implementation separates concerns into three distinct layers, each handling specific aspects of the protocol lifecycle.

### Raw Transport Layer

At the foundation, `rawCdp()` in [`package/ego-browser/src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts) (lines 38-77) manages the direct protocol communication. This function constructs JSON payloads containing `{id, method, params, sessionId?}`, assigns unique message IDs, and maintains a `pending` Map to correlate asynchronous responses. It calls the runtime-injected `globalThis.ego.sendCDPMessage(payload)` to transmit requests and registers a 15-second timeout (`RESPONSE_TIMEOUT_MS`) to prevent hanging promises.

### Session Management Layer

The middle layer handles **CDP session lifecycle** through `browserCdp()` and `ensureSession()` (lines 79-104 and 107-142 in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)). When a command requires page-level context (non-browser methods), `browserCdp()` checks for an active session. If missing, `ensureSession()` discovers the active tab via `Target.getTargets`, attaches using `Target.attachToTarget`, and enables page events. This layer automatically injects the `sessionId` into subsequent commands and detects session loss via regex matching (`SESSION_LOST`), triggering `invalidateSession()` and retry logic when connections drop.

### High-Level API Layer

The top layer exposes ergonomic methods in [`package/ego-browser/src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/cdp-eval.ts). The `cdp()` function (lines 12-27) forwards requests through `state.send` (defaulting to `defaultSend()` in [`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts) lines 10-21), while `evaluate()` (lines 36-65) constructs `Runtime.evaluate` commands, optionally attaches to specific targets, and extracts return values. These helpers shield callers from message ID generation, timeout handling, and session details.

## Message Flow and Request Lifecycle

Understanding the exact path of a CDP request reveals how the layers coordinate:

1. **Runtime Detection** – `isBrowserRuntime()` verifies the presence of `globalThis.ego.sendCDPMessage` when the harness loads inside the ego-lite host.

2. **Request Initiation** – A script calls `cdp('Page.navigate', {url})`, which invokes `state.send()`.

3. **Session Resolution** – `browserCdp()` inspects the method name. Commands starting with `Target.` or `Browser.` bypass session injection; others trigger `ensureSession()` to obtain a valid `sessionId`.

4. **Payload Construction** – `rawCdp()` generates a unique ID, stores the resolver in the `pending` Map, and calls `ego.sendCDPMessage()`.

5. **Response Handling** – The runtime delivers CDP responses to `onCDPMessage`, where `handleMessage` (in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)) parses JSON, matches IDs against the `pending` Map, and resolves promises. Errors are normalized via `buildEgoError()`.

6. **Event Processing** – Unsolicited CDP events (e.g., `Page.screencastFrame`) enter a bounded `events` buffer and dispatch to registered subscribers via `subscribeBrowserEvent()`, enabling `waitForEvent()` and `drainBrowserEvents()` helpers.

## Handling Edge Cases and Resilience

The architecture includes specific safeguards for production reliability:

- **Automatic Session Recovery** – When `browserCdp()` detects "Session not found" errors using the `SESSION_LOST` regex, it automatically invalidates the cached session and retries the command on a fresh connection.

- **Request Timeouts** – Every request includes a 15-second timeout that cleans up the `pending` Map entry and rejects the promise if the runtime fails to respond.

- **Browser-Level Commands** – Methods prefixed with `Target.` or `Browser.` skip session attachment, allowing direct control of the browser process (such as `Target.getTargets`) without page context requirements.

## Practical Code Examples

Send arbitrary CDP commands or evaluate JavaScript using the high-level API:

```typescript
// Enable network tracking
import { cdp } from 'ego-browser';
await cdp('Network.enable');

// Navigate to a URL
await cdp('Page.navigate', { url: 'https://example.com' });

// Evaluate JavaScript on the current page
import { evaluate } from 'ego-browser';
const title = await evaluate(() => document.title);
console.log('Page title →', title);

// Evaluate on a specific target (legacy string form)
const targetId = 'target-1234';
const href = await evaluate('document.location.href', targetId);

```

## Summary

- **`ego-browser`** wraps CDP communication through `globalThis.ego.sendCDPMessage` provided by the ego-lite runtime.
- **Three-layer architecture:** Raw transport (`rawCdp`), session management (`browserCdp`/`ensureSession`), and high-level helpers (`cdp`/`evaluate`).
- **Automatic resilience:** Handles session loss detection, automatic reconnection, and 15-second request timeouts.
- **Direct browser control:** Browser-level commands bypass session injection while page-level commands automatically manage tab attachment.
- **Event subscription:** Unsolicited CDP events are buffered and dispatched to registered listeners for reactive automation.

## Frequently Asked Questions

### What is the entry point for CDP communication in ego-browser?

The entry point is the globally injected `ego` object provided by the ego-lite host runtime. The function `isBrowserRuntime()` checks for `globalThis.ego.sendCDPMessage`, and all subsequent communication flows through this injected interface rather than a WebSocket or external debugger connection.

### How does ego-browser handle lost CDP sessions?

When a command returns a "Session not found" error (matched via `SESSION_LOST` regex in `browserCdp`), the client automatically calls `invalidateSession()` to clear the cached session ID, then retries the original command. This triggers `ensureSession()` to create a fresh attachment to the active tab, ensuring resilience against page navigations or disconnections.

### What timeout applies to CDP requests?

All CDP requests use a **15-second timeout** defined by `RESPONSE_TIMEOUT_MS`. If the runtime does not return a response within this window, the promise is rejected and the pending request is removed from the internal Map to prevent memory leaks.

### How can I evaluate JavaScript on a specific target?

Use the `evaluate()` function from [`cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/cdp-eval.ts) with a target ID string as the second argument: `await evaluate('document.title', 'target-1234')`. Alternatively, pass a function reference for execution in the current session context, which automatically handles `Runtime.evaluate` construction and result extraction.