# How law-chain-hot/websocket-devtools Handles WebSocket Connection States

> Discover how law-chain-hot/websocket-devtools manages WebSocket connection states. Learn about its proxying technique and real-time UI updates for connecting, open, closing, and error states.

- Repository: [Brian 阿布/websocket-devtools](https://github.com/law-chain-hot/websocket-devtools)
- Tags: deep-dive
- Published: 2026-03-05

---

**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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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`.

```javascript
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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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:

```jsx
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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/content/injected.js)**: Proxies the native `WebSocket`, maintains the `connections` Map, updates `status` on native events, and emits system events to the panel.
- **[`src/components/WebSocketList.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/components/WebSocketList.jsx)**: Reads `status` from connection objects, renders status indicators, and sends close commands to trigger the "closing" state.
- **[`src/background/background.js`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/background/background.js)**: Routes messages between the UI and content script, forwarding `simulate-system-event` requests for user-initiated state changes.
- **[`src/utils/wsHistoryService.js`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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.js`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/content/injected.js) to intercept native WebSocket creation and monitor lifecycle events.
- Connection states are stored in a **`connectionInfo` object** within a `Map`, with `status` fields 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.jsx`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/components/WebSocketList.jsx) maps 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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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`](https://github.com/law-chain-hot/websocket-devtools/blob/main/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".