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

> Learn how ego-lite subscribes to browser events using subscribeBrowserEvent. Discover the CDP event architecture and listener registration process within the browser-runtime.ts file.

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

---

**Browser events in ego-lite are subscribed to via the `subscribeBrowserEvent` function in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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:

```typescript
{
  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`](https://github.com/citrolabs/ego-lite/blob/main/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:

```typescript
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:

```typescript
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`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)** – Core implementation of the subscription registry, event buffering, and dispatch logic (lines 22-96).
- **[`cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/cdp-eval.ts)** – Provides the `browserCdp` helper function that triggers CDP commands and generates the events consumed by the subscription system.
- **[`driver/page.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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.