# How the State Singleton in ego-browser Manages Session and Target Information

> Discover how the state singleton in ego-browser manages session and target information. Centralize mutable CDP data for a consistent runtime view of your browser attachment.

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

---

**The state singleton exported from [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) centralizes all mutable Chrome DevTools Protocol (CDP) session data—including the active session ID, target ID, and timestamps—allowing the entire ego-browser runtime to share a consistent view of the current browser attachment.**

The `citrolabs/ego-lite` repository provides a lightweight browser automation harness that relies on a single shared state object to coordinate CDP sessions. This **state singleton in ego-browser** eliminates race conditions and duplicate attachments by tracking session lifecycle metadata in one location, ensuring that every module from element resolvers to network interceptors references the same target context.

## Core State Fields in the Singleton

The singleton defined in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) exports a mutable object that acts as the single source of truth for the runtime. According to the source code, it maintains the following fields:

- **`sessionId`**: The CDP session identifier attached to the currently active tab. It remains `null` until a session is explicitly created.
- **`sessionTargetId`**: The target (tab) ID that the current session is attached to. Changing tabs forces a new attachment sequence.
- **`sessionAt`**: Timestamp of the last successful session acquisition. The runtime uses this to enforce a TTL (2 seconds) after which the session is considered stale.
- **`sessionInflight`**: Holds a pending Promise while a new session is being created, allowing concurrent callers to await the same operation without issuing duplicate attach calls.
- **`preferredTargetId`**: Optional override pointing to a specific tab that callers want to attach to. When set, `ensureSession` prioritizes this target over the currently active tab.
- **`networkDomainEnabled`**: Tracks the last known state of the Network domain on the active session, used by other parts of the runtime to conditionally enable network interception.

## Session Lifecycle and TTL Management

The `ensureSession()` function in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) orchestrates the session lifecycle by reading from and writing to the state singleton. The process follows a strict sequence to minimize CDP overhead and prevent attachment conflicts.

### Requesting a Session with ensureSession()

Any helper that requires a CDP session invokes `ensureSession()`. This function checks the singleton’s current state to determine whether it can reuse an existing session or must create a new one.

### TTL Check and Caching

To avoid excessive re-attachment, the runtime implements a 2-second TTL (time-to-live) cache. If `state.sessionId` exists and the elapsed time since `state.sessionAt` is less than the TTL, the cached ID returns immediately (see lines 7‑10 of [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)):

```typescript
if (state.sessionId && Date.now() - state.sessionAt < SESSION_TTL_MS) {
    return state.sessionId;
}

```

This logic ensures that rapid successive calls within the same 2-second window reuse the active session without querying the browser.

### Inflight Deduplication

When a fresh session is required—either because none exists or the TTL has expired—the runtime stores the creation Promise in `state.sessionInflight`. This prevents concurrent callers from triggering multiple attach operations for the same target (lines 11‑13):

```typescript
if (state.sessionInflight) return state.sessionInflight;
state.sessionInflight = (async () => { … })();

```

Once the Promise resolves, the singleton clears `sessionInflight` and populates `sessionId` with the new identifier.

### Target Selection Strategy

Before attaching, `ensureSession` queries the browser via `listTabs` and selects a target in the following priority order:

1. The `preferredTargetId` if explicitly set in the singleton.
2. The currently active tab reported by the browser.
3. The last tab in the list as a fallback.

The chosen target’s ID is stored in `state.sessionTargetId`, and the runtime compares it against any previously attached target to determine if re-attachment is necessary.

### Attachment and Event Enablement

If the selected `targetId` differs from the previously attached one, or if no session exists, the runtime issues `Target.attachToTarget` and stores the returned `sessionId` in `state.sessionId`. Immediately after attachment, it calls `enablePageEvents(state.sessionId)` to route page-level events such as dialog tracking to the runtime. Finally, it updates `state.sessionAt` to the current timestamp, resetting the TTL.

### Session Invalidation

When the underlying CDP connection reports a lost session (matched by the `SESSION_LOST` regex), the `invalidateSession()` function clears all session-related fields in the singleton (lines 46‑53):

```typescript
export function invalidateSession() {
    if (state.sessionId) {
        pageEnabledSessions.delete(state.sessionId);
        pendingDialogs.delete(state.sessionId);
    }
    state.sessionId = null;
    state.sessionTargetId = null;
    state.sessionAt = 0;
}

```

This forces the next call to `ensureSession` to repeat the entire discovery and attach flow.

## Integration with CDP and Driver Modules

The state singleton integrates transparently with the evaluation layer and driver helpers. The `cdp()` function in [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts) forwards calls to `browserCdp()`, which automatically injects the current session ID when callers omit it (lines 90‑92):

```typescript
if (!explicit && !BROWSER_LEVEL(method)) {
    effective = await ensureSession();
}

```

This ensures that CDP commands like `Runtime.evaluate` or `DOM.querySelector` always execute within the correct session context without requiring manual ID management.

Element resolvers and driver utilities—such as those in [`src/driver/pointer.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/pointer.ts)—receive a `{ sessionId }` tuple from locator functions that ultimately rely on the singleton’s current session. By centralizing state, the runtime allows any helper or skill to work with the correct target transparently.

## Practical Code Examples

The following examples demonstrate how to interact with the state singleton to control session behavior.

### Force Attachment to a Specific Tab

To override automatic target selection and force the runtime to attach to a specific tab, use `setPreferredTarget()` before issuing CDP commands:

```typescript
import { setPreferredTarget } from "./browser-runtime.js";
import { cdp } from "./cdp-eval.js";

async function clickFirstButtonInTab(targetId) {
  // Tell the runtime which tab we want to work with
  setPreferredTarget(targetId);

  // The next CDP call will automatically attach to that tab
  const { result } = await cdp("Runtime.evaluate", {
    expression: "document.querySelector('button').click()",
  });
  return result;
}

```

### Inspect Current Session Information

You can read the singleton directly to debug the current attachment state:

```typescript
import { state } from "./state.js";

function dumpSessionInfo() {
  console.log("Current session ID:", state.sessionId);
  console.log("Attached target ID :", state.sessionTargetId);
  console.log("Acquired at:", new Date(state.sessionAt).toISOString());
}

```

### Reset to Default Target Behavior

To clear a preferred target and return to the default behavior (attaching to the active tab), call `clearPreferredTarget()`:

```typescript
import { clearPreferredTarget } from "./browser-runtime.js";

clearPreferredTarget(); // subsequent calls will use the active tab again

```

## Summary

- The **state singleton** in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) centralizes all mutable CDP session data, including `sessionId`, `sessionTargetId`, and `sessionAt`.
- A **2-second TTL** cache minimizes redundant re-attachments by reusing active sessions stored in the singleton.
- **Inflight deduplication** via `sessionInflight` prevents race conditions when multiple callers request sessions simultaneously.
- The **preferred target override** (`preferredTargetId`) allows explicit control over which tab the runtime attaches to.
- **Automatic invalidation** clears the singleton’s fields when the CDP connection is lost, triggering a fresh discovery flow on the next call.

## Frequently Asked Questions

### How does the state singleton prevent duplicate CDP attachment requests?

The singleton stores a pending Promise in `state.sessionInflight` while a new session is being created. Concurrent callers check this field and await the same Promise instead of initiating their own attach operations, eliminating duplicate requests to the browser.

### What triggers a session invalidation in ego-browser?

When the underlying CDP connection reports a lost session—detected via the `SESSION_LOST` regex—the `invalidateSession()` function nullifies `state.sessionId`, `state.sessionTargetId`, and `state.sessionAt`. This forces the next call to `ensureSession()` to perform a full target discovery and re-attachment.

### Can I force the runtime to use a specific browser tab instead of the active one?

Yes. By calling `setPreferredTarget(targetId)` from [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts), you store a target ID in `state.preferredTargetId`. The next invocation of `ensureSession()` prioritizes this ID over the currently active tab, attaching the CDP session to your specified target.

### What is the purpose of the sessionAt timestamp?

The `sessionAt` field records the Unix timestamp of the last successful session acquisition. The runtime compares this value against the current time to enforce a 2-second TTL; if the cached session is younger than this threshold, `ensureSession()` returns the existing `sessionId` immediately without querying the browser.