How ego-browser Uses Chrome DevTools Protocol (CDP) for Communication
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 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 (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). 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. The cdp() function (lines 12-27) forwards requests through state.send (defaulting to defaultSend() in 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:
-
Runtime Detection –
isBrowserRuntime()verifies the presence ofglobalThis.ego.sendCDPMessagewhen the harness loads inside the ego-lite host. -
Request Initiation – A script calls
cdp('Page.navigate', {url}), which invokesstate.send(). -
Session Resolution –
browserCdp()inspects the method name. Commands starting withTarget.orBrowser.bypass session injection; others triggerensureSession()to obtain a validsessionId. -
Payload Construction –
rawCdp()generates a unique ID, stores the resolver in thependingMap, and callsego.sendCDPMessage(). -
Response Handling – The runtime delivers CDP responses to
onCDPMessage, wherehandleMessage(inbrowser-runtime.ts) parses JSON, matches IDs against thependingMap, and resolves promises. Errors are normalized viabuildEgoError(). -
Event Processing – Unsolicited CDP events (e.g.,
Page.screencastFrame) enter a boundedeventsbuffer and dispatch to registered subscribers viasubscribeBrowserEvent(), enablingwaitForEvent()anddrainBrowserEvents()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 theSESSION_LOSTregex, 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
pendingMap entry and rejects the promise if the runtime fails to respond. -
Browser-Level Commands – Methods prefixed with
Target.orBrowser.skip session attachment, allowing direct control of the browser process (such asTarget.getTargets) without page context requirements.
Practical Code Examples
Send arbitrary CDP commands or evaluate JavaScript using the high-level API:
// 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-browserwraps CDP communication throughglobalThis.ego.sendCDPMessageprovided 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →