# What Is the State Singleton in Ego-Lite? Centralized Runtime Management Explained

> Explore the Ego-Lite state singleton, the centralized store for CDP communication, session tracking, and runtime configuration. Understand its purpose and function in browser automation.

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

---

**The state singleton is a centralized mutable store exported from [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) that acts as the single source of truth for CDP communication, session tracking, and runtime configuration across the Ego-Lite browser automation framework.**

The `citrolabs/ego-lite` repository implements a lightweight browser automation runtime that relies on a single shared object to coordinate process-wide state. This **state singleton** consolidates Chrome DevTools Protocol (CDP) request handling, session bookkeeping, and utility functions into one mutable instance that every module imports and mutates.

## Core Responsibilities of the Runtime State

The singleton defined in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) consolidates diverse concerns into a single mutable object. Because every module receives the same instance, changes propagate instantly across the entire runtime.

### CDP Request Handling and Defaults

At its foundation, the singleton provides **`send`**, which points to `defaultSend` (lines 10‑21). This function forwards requests to the browser via the internal `browserCdp` transport. The exported `send(req)` wrapper (lines 42‑44) simply forwards to `state.send(req)`, allowing the rest of the codebase to issue CDP commands without knowing the underlying transport implementation.

### Session Bookkeeping

The singleton maintains critical session state fields that track the lifecycle of the browser connection:

- **`sessionId`** – Active CDP session identifier
- **`sessionTargetId`** – Target ID for the current session
- **`sessionAt`** – Timestamp of the last session update
- **`sessionInflight`** – Pending operation counter
- **`preferredTargetId`** – Preferred target override

These fields enable [`src/ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-state.ts) to resolve element references consistently while [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) manages target lifecycle.

### Time Utilities and Platform Information

The singleton exposes **`now`** (current timestamp) and **`sleep`** (promise-based delay) for time-sensitive operations (lines 27‑28). It also provides **`platform`** (exposing `process.platform`) and **`agentWorkspace`** (resolving the agent’s workspace directory) to abstract environment details away from helper modules (lines 29‑30).

### File System Access

Rather than requiring every helper to import Node’s `fs` module, the singleton directly re-exports **`writeFile`** (line 31), letting modules persist data while maintaining a centralized dependency graph.

### Configuration and Capability Detection

Runtime configuration is centralized through **`defaultTimeout`**, which supplies fallback values for helper calls, and **`networkDomainEnabled`**, which mirrors the last observed Network domain state (lines 37‑40). The **`cdpAvailable()`** function (lines 46‑48) tells callers whether a custom CDP implementation is present, enabling conditional logic for capability detection.

## Pluggable Overrides and Testability

A key design feature is the **`setOverrides`** helper (lines 50‑55), which temporarily replaces any property on the singleton—such as `send`, `now`, or session fields—and returns a restore function that safely resets the state to its previous snapshot.

This mechanism powers the extensive test suite in `src/helpers.test.mjs`. Tests can inject mocked CDP responses or frozen timestamps without mutating global state permanently, ensuring isolation between test cases.

## Source File Integration

The state singleton is consumed across the codebase:

- **[`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts)** calls `state.send` to communicate with the underlying CDP transport.
- **[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)** retrieves timestamps, sleeps, and other utilities from the singleton.
- **[`src/ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-state.ts)** accesses `state.sessionId` and related fields to resolve element references against the current session.
- **`src/helpers.test.mjs`** demonstrates `setOverrides` usage for mocking dependencies.

## Practical Usage Examples

The following examples demonstrate how to interact with the state singleton in production code and tests.

### Issuing CDP Commands

Import the `send` wrapper to issue commands to the browser:

```javascript
import { send } from "ego-browser/src/state.js";

await send({ method: "Page.navigate", params: { url: "https://example.com" } });

```

### Mocking Dependencies in Tests

Use `setOverrides` to inject test doubles and restore the original implementation afterward:

```javascript
import { setOverrides, send } from "ego-browser/src/state.js";

const restore = setOverrides({
  send: async (req) => ({ result: { mocked: true } }),
});

const resp = await send({ method: "Runtime.evaluate", params: { expression: "1+1" } });
console.log(resp.result.mocked); // true

restore(); // Returns state to the real implementation

```

### Accessing Session State

Directly manipulate session bookkeeping fields when managing complex navigation flows:

```javascript
import { state } from "ego-browser/src/state.js";

state.sessionId = "abcd1234";
console.log(state.sessionAt); // Timestamp of the last session update

```

## Summary

- The **state singleton** in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) serves as the centralized mutable store for the entire Ego-Lite runtime.
- It consolidates **CDP request handling**, **session bookkeeping** (`sessionId`, `sessionTargetId`, etc.), **time utilities** (`now`, `sleep`), and **platform information**.
- **`setOverrides`** enables safe, temporary mutation for testing without permanent side effects.
- Every module that imports the singleton receives the same instance, ensuring **consistent session data** across [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts), [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts), and [`ref-state.ts`](https://github.com/citrolabs/ego-lite/blob/main/ref-state.ts).

## Frequently Asked Questions

### How does the state singleton differ from a standard global variable?

Unlike a plain global variable, the state singleton is a structured object exported from [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) that implements specific methods like `setOverrides` for safe mutation and restoration. This design allows the runtime to maintain type safety while providing a centralized, mockable dependency injection point for the entire `citrolabs/ego-lite` codebase.

### Can I use the state singleton in a multi-session environment?

Yes, the singleton tracks multiple session-related fields (`sessionId`, `sessionTargetId`, `sessionAt`, `sessionInflight`) that can be updated dynamically. However, because it is a singleton, only one set of session values exists at a time; concurrent sessions require careful management of these fields or separate process isolation.

### What happens if I call `setOverrides` but forget to run the restore function?

Without calling the returned restore function, the mock implementation remains active for the remainder of the process lifetime. In production code this could break CDP communication, but in test suites this is often acceptable if the process exits after the test completes. The `src/helpers.test.mjs` file demonstrates patterns for ensuring restoration via `afterEach` hooks or `try/finally` blocks.

### Is the `send` function on the singleton the same as the exported `send` wrapper?

They are effectively aliases. The exported `send` function (lines 42‑44) is a convenience wrapper that delegates to `state.send`, which in turn points to `defaultSend` unless overridden. This indirection allows `setOverrides` to replace the implementation globally while keeping import statements simple for consuming modules.