How Browser Events Are Subscribed to in Ego-Lite: CDP Event Architecture

Browser events in ego-lite are subscribed to via the subscribeBrowserEvent function in browser-runtime.ts, which registers listeners in a central Set registry and dispatches Chrome DevTools Protocol (CDP) events based on method name and optional session ID filtering.

Ego-lite, developed by citrolabs, provides a streamlined TypeScript interface for Chrome DevTools Protocol automation. Understanding how browser events are subscribed to in ego-lite enables developers to build responsive automation scripts that react to DOM mutations, network activity, and page lifecycle events in real-time.

The Central Subscription Registry

At the core of ego-lite's event system lies a centralized registry defined in browser-runtime.ts. The runtime maintains a subscription registry named eventSubscribers (lines 22-23), implemented as a Set<BrowserEventSubscriber>.

Each subscriber object follows the structure:

{
  method: string,      // The CDP event method name (e.g., 'DOM.nodeInserted')
  sessionId?: string,  // Optional target session for multi-session filtering
  listener: Function   // Callback invoked when matching events arrive
}

This design allows multiple components to listen for identical CDP events simultaneously without interference, supporting both global listeners and session-specific subscriptions.

Registering Event Listeners

To subscribe to browser events, helper modules call subscribeBrowserEvent(method, sessionId?, listener) (lines 88-96). This function creates a subscriber entry containing the method name, optional session identifier, and callback function, then adds the entry to the eventSubscribers set.

The function returns an unsubscribe function that removes the entry from the registry when invoked, preventing memory leaks and enabling clean resource management. If sessionId is undefined, the subscription listens to events across the current default session.

Event Dispatch and Filtering

All incoming CDP messages flow through the handleMessage function in browser-runtime.ts. After processing responses and special protocol cases, the dispatcher iterates over the eventSubscribers collection (lines 77-85).

For each subscriber, the system performs matching logic:

  • The subscriber's method must match data.method from the CDP event
  • If the subscriber specifies a sessionId, it must match the event's session identifier

When both conditions satisfy, the runtime invokes the listener with the event payload.

Event Buffering for Late Consumption

Even when no active subscriber handles a specific event, ego-lite preserves the message in a general events buffer. This buffering mechanism supports drainBrowserEvents, allowing scripts to retrieve historical events that arrived before subscription establishment.

This pattern proves essential for debugging scenarios or when initializing listeners after page navigation, ensuring no critical CDP notifications are lost due to timing race conditions.

Practical Implementation Examples

Listening to DOM Mutations

Subscribe to DOM insertion events on the current session:

import { subscribeBrowserEvent, browserCdp } from 'ego-browser';

// Enable DOM domain events first
await browserCdp('DOM.enable');

// Subscribe to DOM.nodeInserted events
const unsubscribe = subscribeBrowserEvent(
  'DOM.nodeInserted',
  undefined,               // No specific sessionId → uses current session
  (event) => {
    console.log('Node inserted:', event);
  }
);

// Cleanup when finished
unsubscribe();

Session-Specific Page Load Monitoring

Listen for page load completion on a specific browser session:

import { subscribeBrowserEvent, ensureSession } from 'ego-browser';

const sessionId = await ensureSession();

const stop = subscribeBrowserEvent(
  'Page.loadEventFired',
  sessionId,
  (e) => console.log('Page finished loading:', e)
);

// Stop listening when done
stop();

Key Source Files

The event subscription architecture spans three primary files in the citrolabs/ego-lite repository:

  • browser-runtime.ts – Core implementation of the subscription registry, event buffering, and dispatch logic (lines 22-96).
  • cdp-eval.ts – Provides the browserCdp helper function that triggers CDP commands and generates the events consumed by the subscription system.
  • driver/page.ts – Contains high-level page automation helpers that internally leverage subscribeBrowserEvent for state management.

Summary

  • Subscription Registry: A Set<BrowserEventSubscriber> in browser-runtime.ts (lines 22-23) stores all active event listeners.
  • Registration API: The subscribeBrowserEvent(method, sessionId?, listener) function (lines 88-96) adds subscribers and returns cleanup functions.
  • Dispatch Logic: The handleMessage function (lines 77-85) filters events by method name and optional session ID before invoking listeners.
  • Message Buffering: Unhandled events persist in the events buffer for later retrieval via drainBrowserEvents.
  • Session Support: Subscriptions can target specific CDP sessions or listen globally across the current session.

Frequently Asked Questions

How do I unsubscribe from browser events in ego-lite?

The subscribeBrowserEvent function returns a cleanup function that removes the subscriber from the eventSubscribers registry. Store this return value and call it when your component unmounts or no longer requires the event stream. According to the source code in browser-runtime.ts (lines 88-96), this immediately removes the listener reference and prevents further invocations.

Can I filter events by specific browser sessions?

Yes. The subscribeBrowserEvent function accepts an optional sessionId parameter as its second argument. When provided, the dispatcher in handleMessage (lines 77-85) validates that the event's session identifier matches the subscriber's sessionId before invoking the callback. Omitting this parameter subscribes to events from the current default session.

What happens to CDP events if no listener is active?

The event system stores all incoming CDP messages in a general events buffer regardless of subscription status. This design ensures that scripts calling drainBrowserEvents can retrieve historical messages that arrived prior to subscription establishment, preventing data loss during initialization timing gaps.

Where is the event subscription logic implemented?

All subscription management, dispatch, and buffering logic resides in browser-runtime.ts within the ego-browser package. This file defines the eventSubscribers Set, implements the subscribeBrowserEvent registration function (lines 88-96), and contains the handleMessage dispatcher (lines 77-85) that routes CDP events to matching listeners.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →