# Architecture of Ego-Browser's CDP Transport Layer: Inside the Chrome DevTools Protocol Implementation

> Explore the architecture of Ego-Browser's CDP transport layer. Discover its resilient, session-aware design handling message framing and request tracking for seamless Chrome DevTools Protocol integration via a Promise-based int...

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: architecture
- Published: 2026-07-27

---

**Ego-Browser's CDP transport layer is a resilient, session-aware wrapper around the host-provided `globalThis.ego.sendCDPMessage` API that manages message framing, pending request tracking, and automatic session recovery to provide a Promise-based interface for Chrome DevTools Protocol commands.**

The `citrolabs/ego-lite` repository implements a lightweight browser automation SDK that abstracts raw CDP complexity through a specialized transport architecture. This system handles the complete lifecycle of protocol messages—from ID assignment and timeout management to session attachment and error propagation—enabling reliable communication between automation scripts and the browser host.

## Message Framing and ID Management

In [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts), every outgoing CDP request is assigned a monotonically increasing numeric identifier via `nextMessageId` (lines 38‑53). The transport wraps each command in a JSON payload structure that includes the method name, parameters, and optionally a `sessionId` when targeting specific browser tabs. This framing ensures that responses can be correctly correlated with their originating requests across the asynchronous boundary.

## Pending Request Tracking

The transport maintains an in-flight request registry using a `Map` named `pending` (lines 39‑50, 59‑65, 71‑77). When `sendCDPMessage` dispatches a command, it stores a promise resolver in this map, keyed by the message ID. Upon receiving a response, the `handleMessage` function extracts the matching `id`, retrieves the corresponding resolver from `pending`, and resolves or rejects the promise based on the response payload. This mechanism guarantees one-to-one request-response correlation even under high concurrency.

## Timeout Handling

To prevent indefinite blocking, each CDP call is wrapped in a `Promise` that automatically rejects after `RESPONSE_TIMEOUT_MS` (default 15 seconds) as implemented in lines 55‑58 of [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts). If the host fails to respond within this window, the pending resolver is removed from the map and rejected with a timeout error, ensuring that automation scripts fail fast rather than hanging indefinitely.

## Session Management Architecture

The transport layer implements intelligent session handling for page-level commands—those not prefixed with `Target.` or `Browser.`—ensuring that automation targets the correct execution context.

### Session Attachment and Caching

The `ensureSession` function inspects cached state in `state.sessionId` and `state.sessionAt` (lines 7‑23, 84‑103). If the cached session exceeds `SESSION_TTL_MS` (2 seconds), the transport enumerates available tabs via `ego.listTabs()`, selects the preferred or most recent tab, and executes `Target.attachToTarget` to obtain a fresh `sessionId`. This TTL-based caching minimizes redundant attachment operations while ensuring that commands execute against the current active tab.

### Automatic Session Recovery

When a CDP request fails with a "session lost" error (detected via the `SESSION_LOST` regex), the transport automatically clears `state.sessionId` and invokes `ensureSession` to establish a new session before transparently retrying the original request (lines 96‑103). This recovery mechanism handles scenarios where the target page navigates or crashes, providing fault tolerance without requiring manual session management in user scripts.

## Event Buffering and Subscription

Incoming CDP events are parsed in `handleMessage` and stored in a bounded circular buffer configured with `MAX_BUFFERED_EVENTS` (10,000 entries) to prevent unbounded memory growth during long-running automation sessions (lines 84‑92, 164‑172). Users can subscribe to specific protocol events via `subscribeBrowserEvent(eventName, sessionId?, callback)`, which routes matching events from the buffer to registered listeners. The function returns an unsubscribe handler for cleanup.

## Error Propagation

Local send failures—such as when the host task becomes inactive—are caught by `handleSendError` in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) (lines 24‑31). This function iterates through the `pending` map and rejects all unresolved promises with a normalized `EgoError` object defined in [`src/ego-errors.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ego-errors.ts). This ensures that network interruptions or host crashes propagate as structured exceptions rather than silent failures.

## Practical Usage Examples

The following patterns demonstrate how the transport layer functions are used in practice:

```javascript
// Example: sending a CDP command that automatically gets a page session.
import { browserCdp } from 'ego-browser';

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

// Evaluate an expression in the page context
const result = await browserCdp('Runtime.evaluate', {
  expression: 'document.title',
});
console.log('Page title:', result.result.value);

```

```javascript
// Example: subscribing to a CDP event (e.g., console messages)
import { subscribeBrowserEvent } from 'ego-browser';

const unsubscribe = subscribeBrowserEvent(
  'Runtime.consoleAPICalled',
  undefined,               // listen on any session
  (event) => console.log('Console:', event.params.args)
);

// Later – stop listening
unsubscribe();

```

```javascript
// Example: manually creating a session for a specific tab (advanced)
import { ensureSession, browserCdp } from 'ego-browser';

// Force a new session (useful for a non‑active tab)
const sessionId = await ensureSession();
await browserCdp('Runtime.enable', {}, sessionId);

```

## Summary

- The transport layer in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) wraps `globalThis.ego.sendCDPMessage` to provide a Promise-based CDP interface with automatic ID management via `nextMessageId`.
- In-flight requests are tracked in a `pending` Map with a 15-second timeout (`RESPONSE_TIMEOUT_MS`) to prevent hanging operations.
- Session management caches `sessionId` for 2 seconds (`SESSION_TTL_MS`) and automatically recovers from "session lost" errors by re-attaching to targets.
- Incoming events are buffered in a circular queue limited to 10,000 entries (`MAX_BUFFERED_EVENTS`) and exposed through `subscribeBrowserEvent`.
- All transport errors are normalized to `EgoError` objects via `handleSendError` for consistent error handling across the SDK.

## Frequently Asked Questions

### How does ego-browser handle CDP session timeouts?

The transport implements a time-to-live (TTL) mechanism where `state.sessionId` remains valid for `SESSION_TTL_MS` (2 seconds). When invoking page-level commands, `ensureSession` checks this cache and only re-executes `Target.attachToTarget` if the session has expired, minimizing protocol overhead while ensuring fresh execution contexts.

### What happens when a CDP request fails with a session error?

If a response matches the `SESSION_LOST` regex, the transport clears the cached session ID and invokes `ensureSession` to create a new session, then automatically retries the original request without throwing to the caller. This transparent recovery handles page navigations and target detaches seamlessly (lines 96‑103 in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts)).

### How does the transport prevent memory leaks during long-running scripts?

The `handleMessage` function stores CDP events in a bounded circular buffer with a capacity of `MAX_BUFFERED_EVENTS` (10,000). Once the buffer reaches capacity, oldest events are overwritten, ensuring that memory usage remains constant regardless of how long the automation runtime executes.

### Can I subscribe to specific CDP events like console logs?

Yes. Import `subscribeBrowserEvent` from `ego-browser` to register callbacks for specific protocol events such as `Runtime.consoleAPICalled`. The function accepts an optional `sessionId` filter (pass `undefined` to listen globally) and returns an unsubscribe function to stop listening and free resources.