What Is the State Singleton in Ego-Lite? Centralized Runtime Management Explained
The state singleton is a centralized mutable store exported from 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 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 identifiersessionTargetId– Target ID for the current sessionsessionAt– Timestamp of the last session updatesessionInflight– Pending operation counterpreferredTargetId– Preferred target override
These fields enable src/ref-state.ts to resolve element references consistently while 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.tscallsstate.sendto communicate with the underlying CDP transport.src/helpers.tsretrieves timestamps, sleeps, and other utilities from the singleton.src/ref-state.tsaccessesstate.sessionIdand related fields to resolve element references against the current session.src/helpers.test.mjsdemonstratessetOverridesusage 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:
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:
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:
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.tsserves 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. setOverridesenables 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,helpers.ts, andref-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 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.
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 →