How law-chain-hot/websocket-devtools Handles WebSocket Connection States
The extension proxies the native WebSocket constructor to track connection states through a centralized connectionInfo object that transitions from "connecting" → "open" → "closing" → "closed" (or "error"), with the UI reflecting these states in real-time.
The law-chain-hot/websocket-devtools repository is a browser extension that provides debugging capabilities for WebSocket connections. Understanding how it manages WebSocket connection states reveals a sophisticated proxy pattern that intercepts native browser APIs to monitor lifecycle transitions. This article examines the implementation details of how the extension tracks connecting, open, closing, closed, and error states across the content script and UI components.
The State Tracking Architecture
At the core of the extension's state management is a proxy pattern that wraps the browser's native WebSocket constructor. When a page creates a new WebSocket, the injected script in src/content/injected.js intercepts the call and initializes a state tracking object.
Proxying the Native Constructor
The extension replaces the global WebSocket with a ProxiedWebSocket function that instantiates the original native object while simultaneously creating a connectionInfo entry. This entry stores the current status and metadata in a Map called connections.
function ProxiedWebSocket(url, protocols) {
const connectionId = generateConnectionId();
const ws = new OriginalWebSocket(url, protocols);
const connectionInfo = {
id: connectionId,
url,
ws,
status: "connecting", // ← initial state
// … other bookkeeping fields …
};
connections.set(connectionId, connectionInfo);
// Tell the panel we are connecting
sendEvent({ id: connectionId, type: "connection", status: "connecting", ... });
// Listen for native events
ws.addEventListener("open", () => {
connectionInfo.status = "open";
sendEvent({ id: connectionId, type: "open", status: "open", ... });
});
ws.addEventListener("close", () => {
connectionInfo.status = "closed";
sendEvent({ id: connectionId, type: "close", status: "closed", ... });
connections.delete(connectionId);
});
ws.addEventListener("error", () => {
connectionInfo.status = "error";
sendEvent({ id: connectionId, type: "error", status: "error", ... });
});
}
The connectionInfo Object
Each WebSocket connection maintains a connectionInfo object within the connections Map. The status field is the single source of truth for the connection's lifecycle stage. The proxy updates this field immediately when native events fire (open, close, error) or when user actions trigger state changes.
Mapping the Five Connection States
The extension defines five distinct WebSocket connection states, each with specific entry conditions and UI representations.
Connecting
The "connecting" state is assigned immediately upon proxy instantiation. In src/content/injected.js (lines 95‑103, 112‑119), the status is initialized to "connecting" and a system event is emitted to the UI panel. This state indicates the handshake has started but the connection is not yet established.
Open
When the native socket fires its open event, the proxy listener in src/content/injected.js (lines 158‑167) updates connectionInfo.status to "open". This represents a successfully established WebSocket ready for bidirectional communication.
Closing
The "closing" state occurs when the user initiates a disconnect through the DevTools panel. In src/components/WebSocketList.jsx (lines 56‑63), clicking the close button triggers onSetConnectionClosing(id), which sends a simulate-system-event of type client-close to the background script. The injected script receives this message, sets connectionInfo.status = "closing", and emits a system event before the native close handshake completes.
Closed
After the native socket's close event fires (or a simulated close finishes), the proxy updates connectionInfo.status to "closed" (lines 48‑55, 66‑73 in src/content/injected.js). The entry is then removed from the connections Map to prevent memory leaks.
Error
When the native socket fires an error event or a simulated error is injected, the proxy changes connectionInfo.status to "error" (lines 48‑55 in src/content/injected.js). This state indicates the connection failed to establish or encountered a runtime error.
State Transitions in the UI
The React components in src/components/WebSocketList.jsx consume the status field to render appropriate visual feedback and group connections logically.
Visual Indicators
The UI maps each status to specific icons and labels:
const renderStatusIcon = () => {
if (connection.status === "connecting") return <Loader size={14} color="#f59e0b" />;
if (connection.status === "closing") return <Loader size={14} color="#ef4444" />;
if (isActive) return <CheckCircle size={14} color="#10b981" />;
return <XCircle size={14} color="#ef4444" />;
};
const getStatusText = () => {
if (connection.status === "connecting") return t("panel.connectionList.status.connecting");
if (connection.status === "closing") return t("panel.connectionList.status.closing");
if (isActive) return t("panel.connectionList.status.connected");
return t("panel.connectionList.status.disconnected");
};
- Connecting: Yellow spinner with "Connecting" text
- Open: Green check-mark with "Connected" text
- Closing: Red spinner with "Closing" text
- Closed/Error: Red X icon with "Disconnected" or "Error" text
Grouping Active vs Inactive Connections
The UI separates connections into two categories based on status:
- Active Connections: Status is
"open","connecting", or"closing"(connections that currently exist or are in transition) - Inactive Connections: Status is
"closed"or"error"(terminated connections shown for historical debugging)
Key Implementation Files
The state management system spans three primary files:
src/content/injected.js: Proxies the nativeWebSocket, maintains theconnectionsMap, updatesstatuson native events, and emits system events to the panel.src/components/WebSocketList.jsx: Readsstatusfrom connection objects, renders status indicators, and sends close commands to trigger the "closing" state.src/background/background.js: Routes messages between the UI and content script, forwardingsimulate-system-eventrequests for user-initiated state changes.src/utils/wsHistoryService.js: Persists connection metadata including final status for the history view.
Summary
- The extension uses a proxy pattern in
src/content/injected.jsto intercept native WebSocket creation and monitor lifecycle events. - Connection states are stored in a
connectionInfoobject within aMap, withstatusfields tracking"connecting","open","closing","closed", and"error". - State transitions follow the flow: create → connecting → open → closing → closed (with error as a potential branch from connecting or open).
- The UI in
src/components/WebSocketList.jsxmaps these states to visual indicators (spinners, check-marks, X icons) and separates active from inactive connections. - User-initiated closes trigger a "closing" intermediate state through a message passing system involving the background script.
Frequently Asked Questions
How does the extension detect when a WebSocket connection is established?
When the native WebSocket fires its open event, the proxy listener in src/content/injected.js updates the connectionInfo.status to "open" and emits a system event. The UI receives this event and displays a green check-mark icon with "Connected" status.
What happens to the connection state when a user clicks the close button?
The UI sends a simulate-system-event message of type client-close through src/background/background.js to the content script. The injected script sets the status to "closing" immediately, shows a red spinner in the UI, then transitions to "closed" when the native socket's close event fires.
Where is the connection state data stored during the WebSocket lifecycle?
Each connection's state is stored in the connections Map within src/content/injected.js. The Map keys are unique connection IDs, and the values are connectionInfo objects containing the status field and other metadata. This Map is cleaned up when connections reach the "closed" or "error" state.
Can the extension track WebSocket errors separately from normal closures?
Yes. The proxy specifically listens for native error events in src/content/injected.js and sets connectionInfo.status = "error". This distinguishes error terminations from graceful closures ("closed"), and the UI renders a red X icon with "Error" text rather than "Disconnected".
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 →