# WebSocket Message Events Format in law-chain-hot/websocket-devtools

> Explore the WebSocket message events format in law-chain-hot/websocket-devtools. Understand the standardized event object with type, data, messageId, timestamp and more.

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

---

**The extension routes all WebSocket activity through a standardized event object containing `type`, `data`, `messageId`, `timestamp`, `source`, and `frameContext` fields that are enriched at each layer of the architecture.**

The law-chain-hot/websocket-devtools extension intercepts native browser WebSocket traffic using a three-stage pipeline that relies on a canonical message format to maintain data integrity across contexts. Understanding the exact structure of these WebSocket message events is essential for debugging, extending, or integrating with the tool. This article breaks down the event schema, field semantics, and transmission flow using the actual source implementation from the repository.

## Event Structure and Schema

The extension transmits data using two distinct schema variants depending on whether events are sent individually or buffered for performance.

### Single Event Format

A solitary WebSocket event travels between components as a JSON object with the following structure:

```json
{
  "type": "websocket-event",
  "data": {
    "type": "message",
    "id": "ws_abc123",
    "direction": "incoming",
    "data": "raw payload or decoded protobuf"
  },
  "messageId": "msg_1657389123456_1_xk7y9",
  "timestamp": 1657389123478,
  "source": "content-script",
  "frameContext": {
    "url": "https://example.com/page",
    "stableId": "https://example.com/page",
    "isIframe": false,
    "frameId": null
  }
}

```

### Batched Event Format

When high-frequency traffic occurs, the injected script buffers multiple events into a single payload to reduce overhead:

```json
{
  "type": "websocket-event-batch",
  "payload": [
    {
      "type": "websocket-event",
      "data": { /* event 1 data */ },
      "messageId": "msg_...",
      "timestamp": 1657389123478
    },
    {
      "type": "websocket-event",
      "data": { /* event 2 data */ },
      "messageId": "msg_...",
      "timestamp": 1657389123480
    }
  ]
}

```

The content script iterates over the batch, injects missing `messageId` and `timestamp` values, and normalizes the `source` field before forwarding to the background script.

## Core Field Definitions

Each field in the event object serves a specific purpose in the routing and display pipeline:

- **type**: Identifies the payload variant. Individual events use `"websocket-event"` while buffered arrays use `"websocket-event-batch"`.
- **data**: Contains the raw payload generated by the injected script, including WebSocket direction, connection ID, and message content.
- **messageId**: A UUID-like string formatted as `msg_<timestamp>_<counter>_<random>` generated by the content script to enable deduplication and tracing.
- **timestamp**: Milliseconds since epoch (`Date.now()`) captured when the content script creates the wrapper.
- **source**: Normalized to `"content-script"` after processing to ensure the background script can reliably filter events. The injected script initially uses `"websocket-proxy-injected"`.
- **frameContext**: An object containing `url`, `stableId`, `isIframe`, and `frameId` that identifies the specific frame that generated the event, allowing the DevTools UI to group events per-frame.
- **tabId**: Added exclusively by the background script (`sender.tab.id`) to isolate data between browser tabs.

## Cross-Component Communication Flow

The event format evolves as it traverses from the page context to the DevTools panel.

### Injected Script (Page Context)

Located in [`src/content/injected.js`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/content/injected.js), this script intercepts native `WebSocket` calls and emits events via `window.postMessage`:

```javascript
window.postMessage(
  {
    source: "websocket-proxy-injected",
    type: "websocket-event",
    payload: {
      type: "message",
      id: generateConnectionId(),
      direction: "incoming",
      data: rawMessageData
    }
  },
  "*"
);

```

This occurs at lines 438-447 of the injected script, where the payload contains the raw WebSocket activity before any extension-side enrichment.

### Content Script (Extension Context)

The content script ([`src/content/content.js`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/content/content.js)) listens for these messages and transforms them into the canonical format at lines 1110-1139:

```javascript
const messageId = generateMessageId();
const messageWithId = {
  type: "websocket-event",
  data: event.data.payload,
  messageId,
  timestamp: Date.now(),
  source: "content-script",
  frameContext: {
    url: window.location.href,
    stableId: (() => {
      try { 
        const u = new URL(window.location.href); 
        return `${u.origin}${u.pathname}`; 
      } catch { 
        return window.location.href; 
      }
    })(),
    isIframe: window !== window.top,
    frameId: window !== window.top ? (() => {
      try { 
        const u = new URL(window.location.href); 
        return `${u.origin}${u.pathname}`; 
      } catch { 
        return window.location.href; 
      }
    })() : null
  }
};

chrome.runtime.sendMessage(messageWithId);

```

The content script also handles batch normalization at lines 65-73, ensuring every array item has a unique `messageId` and `timestamp` before forwarding.

### Background Script (Central Hub)

The background script ([`src/background/background.js`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/background/background.js)) receives the enriched message at lines 31-44, adds the `tabId`, persists the data, and forwards it to the DevTools UI:

```javascript
case "websocket-event": {
  message.data.tabId = sender.tab.id;
  message.tabId = sender.tab.id;

  websocketData.connections.push(message.data);
  forwardToDevTools(message);
  sendResponse({ received: true });
  break;
}

```

The `tabId` field is critical for the DevTools panel to display only events relevant to the currently inspected tab.

## Implementation Examples

### Emitting Events from Injected Code

When the proxy intercepts a WebSocket message, it constructs the initial payload:

```javascript
// src/content/injected.js
window.postMessage({
  source: "websocket-proxy-injected",
  type: "websocket-event",
  payload: {
    type: "message",
    id: connectionId,
    direction: "outgoing",
    data: encodedPayload
  }
}, "*");

```

### Processing Batched Events

For high-throughput scenarios, the content script handles batch arrays by mapping over the payload and injecting missing metadata:

```javascript
// src/content/content.js
const batchWithIds = batch.map(item => ({
  ...item,
  messageId: item.messageId || generateMessageId(),
  timestamp: item.timestamp || Date.now(),
  source: "content-script"
}));

chrome.runtime.sendMessage({
  type: "websocket-event-batch",
  data: batchWithIds,
  timestamp: Date.now(),
  source: "content-script"
});

```

### Storing Events in Background Memory

The background script maintains an in-memory store of connections, appending each new event to the appropriate connection array:

```javascript
// src/background/background.js
websocketData.connections.push(message.data);
forwardToDevTools(message);

```

## Summary

- The **canonical event object** includes `type`, `data`, `messageId`, `timestamp`, `source`, and `frameContext` fields.
- **Injected scripts** emit raw events with `source: "websocket-proxy-injected"` using `window.postMessage`.
- **Content scripts** normalize events by adding unique IDs, timestamps, and frame context before forwarding via `chrome.runtime.sendMessage`.
- **Background scripts** append `tabId` to isolate data per tab and forward events to the DevTools panel.
- **Batch processing** uses the `websocket-event-batch` type to optimize high-frequency message flows.

## Frequently Asked Questions

### What is the exact structure of a WebSocket event message in the extension?

The event is a JSON object with a fixed top-level structure containing `type` (set to `"websocket-event"`), `data` (the WebSocket payload), `messageId` (unique identifier), `timestamp` (epoch milliseconds), `source` (normalized to `"content-script"`), and `frameContext` (frame metadata). The background script later adds a `tabId` field to route the event to the correct DevTools instance.

### How does the content script modify events from the injected script?

The content script intercepts messages via `window.addEventListener('message', ...)` and enriches them by generating a `messageId`, capturing a `timestamp`, setting `source` to `"content-script"`, and constructing a `frameContext` object containing the current URL and iframe status. This normalization occurs in [`src/content/content.js`](https://github.com/law-chain-hot/websocket-devtools/blob/main/src/content/content.js) before transmission to the background script.

### What is the purpose of the `frameContext` field in WebSocket events?

The `frameContext` field provides structural metadata about the source frame, including `url`, `stableId`, `isIframe`, and `frameId`. This allows the DevTools panel to group and filter WebSocket traffic by specific frames within a tab, distinguishing between main document requests and those originating from nested iframes.

### Are events sent individually or in batches between components?

Both modes are supported. Individual events use the `websocket-event` type. For high-frequency traffic, the injected script buffers multiple events and sends them as a `websocket-event-batch` payload. The content script then decomposes these batches, ensures each item has proper metadata, and forwards them individually to the background script for storage.