How ego-browser Manages CDP Session Transport and Timeouts: A Deep Dive into the Runtime Layer
ego-browser handles Chrome DevTools Protocol (CDP) communication through a self-healing transport layer that automatically manages session attachment, enforces 15-second response timeouts, and retries failed requests when sessions are lost. This architecture lets you call CDP methods without manually tracking session IDs or handling transient connection failures.
The ego-lite repository provides a lightweight browser automation toolkit where ego-browser wraps Chromium's raw CDP in a TypeScript runtime. The transport and timeout logic lives primarily in src/browser-runtime.ts, which exposes a single browserCdp() function that powers all higher-level APIs. Here's how it works under the hood.
CDP Transport Layer: Message Routing with Promise Matching
Every CDP request flows through rawCdp() in browser-runtime.ts. The runtime converts your method call into a JSON payload containing four fields: an incrementing message-id, the CDP method name, optional parameters, and an optional sessionId. This payload passes to globalThis.ego.sendCDPMessage, the bridge injected by the ego host environment.
// Conceptual flow inside rawCdp (browser-runtime.ts#L38-L70)
const id = ++messageIdCounter;
const payload = { id, method, params, sessionId };
globalThis.ego.sendCDPMessage(JSON.stringify(payload));
// Response handling via handleMessage
const pending = pendingPromises.get(id);
if (pending) {
pending.resolve(result);
clearTimeout(pending.timeout);
}
Incoming messages route to handleMessage(), which parses the JSON and matches responses to pending promises by id. The runtime rejects promises for CDP errors and resolves them with the result field on success.
Session Management: Automatic Attachment with 2-Second TTL
For page-level CDP methods, ego-browser eliminates manual session handling. The runtime maintains a cached session ID with a 2-second time-to-live (SESSION_TTL_MS = 2000).
How ensureSession() Works
import { ensureSession, invalidateSession } from "ego-browser";
// Automatically creates or returns a fresh session
const sessionId = await ensureSession(); // browser-runtime.ts#L107-L136
The ensureSession() function implements this logic:
- Cache check – Returns the cached
state.sessionIdifstate.sessionTimestampis within the 2-second TTL - Target discovery – Lists tabs via
Target.getTargets, selects the active tab or preferred target - Session acquisition – Calls
Target.attachToTargetto obtain a new session ID - State update – Stores the ID in
state.sessionIdwith a fresh timestamp
Session Invalidation
When the browser reports a detached target or other fatal conditions, invalidateSession() clears state.sessionId and state.sessionTimestamp. This forces ensureSession() to re-attach on the next call.
Timeout Handling and Error Recovery
The runtime implements two layers of failure protection to keep CDP calls reliable.
Response Timeouts (15 Seconds Default)
Each rawCdp call starts a setTimeout using RESPONSE_TIMEOUT_MS (15000 ms). If the timeout fires before a response arrives, the pending promise is removed and rejected:
try {
await browserCdp("Runtime.evaluate", { expression: "document.title" });
} catch (e) {
// Error: "CDP request timed out: Runtime.evaluate"
}
Override per-call:
await browserCdp("Network.enable", {}, undefined, 5000); // 5 second timeout
Transport Failure Handling
If sendCDPMessage throws synchronously, the timeout clears immediately and the promise rejects with the thrown error. This prevents hanging promises when the bridge itself is unavailable.
Automatic Retry on Session Loss
The runtime detects session loss through regex matching against SESSION_LOST patterns ("Session not found", "Target closed", etc.). When a raw request fails with a matching error—and the request wasn't explicitly targeting a specific session—the runtime:
- Calls
invalidateSession()to purge stale state - Invokes
ensureSession()to obtain a fresh session - Retries the request once with the new session ID
This self-healing behavior covers transient detachments without surfacing errors to your code.
// This call recovers automatically even if the session died mid-request
await browserCdp("Runtime.evaluate", { expression: "1+1" });
// Session lost → invalidated → re-attached → retried → returns 2
Source File Reference
| File | Purpose |
|---|---|
src/browser-runtime.ts |
Core transport (rawCdp), session management (ensureSession, invalidateSession), timeout logic, event routing |
src/state.ts |
Mutable runtime state: sessionId, sessionTimestamp, configuration overrides |
src/cdp-eval.ts |
Convenience wrappers (cdp(), js()) delegating to browserCdp |
src/driver/*.ts |
Higher-level APIs (pointer, keyboard, etc.) consuming browserCdp |
Summary
- Transport: JSON payloads via
globalThis.ego.sendCDPMessagewith promise-based response matching inhandleMessage - Sessions: Automatic attachment with 2-second TTL caching in
ensureSession(); explicit invalidation viainvalidateSession() - Timeouts: 15-second default per-request, configurable per call, cleared on response or synchronous failure
- Recovery: Single automatic retry when
SESSION_LOSTpatterns match, triggered transparently inrawCdp
Frequently Asked Questions
How does ego-browser handle CDP session timeouts?
Each CDP request starts a setTimeout with RESPONSE_TIMEOUT_MS (15000 ms by default). If no response arrives before the timer fires, the pending promise rejects with CDP request timed out: <method>. You can override this per-call by passing a fourth argument to browserCdp().
What happens when a CDP session is lost mid-request?
The runtime detects session loss through regex matching against common error patterns. If detected and the request wasn't explicitly bound to a specific session, invalidateSession() clears stale state, ensureSession() creates a fresh session, and the request retries once automatically.
Can I manually control the CDP session in ego-browser?
Yes, though it's rarely necessary. Import ensureSession() to force session creation or retrieval, or invalidateSession() to explicitly clear cached state. You can also pass a sessionId directly to browserCdp() to bypass automatic session management.
Where is the CDP transport logic implemented in ego-lite?
The core implementation resides in package/ego-browser/src/browser-runtime.ts, specifically lines 38-70 for message routing and 107-136 for session management. Global state lives in src/state.ts, and all public APIs ultimately delegate through this runtime layer.
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 →