How WebSocket Message Blocking Works in law-chain-hot/websocket-devtools: Complete Architecture Guide
The WebSocket message blocking system intercepts native WebSocket API calls through an injected page proxy, allowing selective suppression of outgoing sends and incoming message events while maintaining full visibility in the DevTools panel.
The law-chain-hot/websocket-devtools extension provides developers with granular control over bidirectional WebSocket traffic through its message blocking functionality. This feature enables selective suppression of outgoing messages from the page or incoming messages from the server, creating a robust debugging environment for testing network failure scenarios. The implementation relies on a coordinated three-layer architecture spanning the DevTools UI, background script routing, and an in-page proxy that intercepts native WebSocket operations in src/content/injected.js.
Three-Layer Architecture for WebSocket Message Blocking
The extension implements blocking through three coordinated layers that bridge user interaction with native API interception.
UI Control Layer in ControlPanel.jsx
The user interface in src/components/ControlPanel.jsx provides toggle switches that dispatch Chrome runtime messages to enable or disable blocking for each direction. When a user toggles the switch, the component sends a message with the type block-outgoing or block-incoming along with the enabled state and target tab ID.
chrome.runtime.sendMessage({
type: "block-outgoing", // or "block-incoming"
enabled: newState,
tabId: currentTabId,
});
Background Routing in background.js
The background script in src/background/background.js listens for these block commands and forwards them to the appropriate content scripts. The handler calls notifyAllTabs to propagate the state change to every active tab, ensuring the blocking preference applies across the browser session.
case "block-outgoing":
case "block-incoming":
notifyAllTabs(request.type, request.enabled, request.tabId);
break;
In-Page Proxy Layer in injected.js
The content script in src/content/content.js receives these commands and relays them to the injected proxy via window.postMessage. The proxy in src/content/injected.js maintains blocking state in a shared proxyState object that controls the interception logic.
let proxyState = {
isMonitoring: true,
blockOutgoing: false,
blockIncoming: false,
};
Blocking Outgoing WebSocket Messages
The proxy overrides WebSocket.prototype.send to intercept all outgoing traffic. When proxyState.blockOutgoing is enabled and monitoring is active, the wrapper prevents the original native send from executing, effectively dropping the message before it reaches the network stack.
ws.send = function (data) {
const eventData = { /* common fields … */ };
// Outgoing blocking check
if (proxyState.blockOutgoing && proxyState.isMonitoring) {
eventData.blocked = true;
eventData.reason = "Outgoing messages blocked";
connectionInfo.blockedMessages.push({
data,
timestamp: Date.now(),
direction: "outgoing",
});
sendEvent(eventData); // Report to DevTools panel
return; // Abort: do not call originalSend
}
// Normal flow: forward to real WebSocket and log
if (proxyState.isMonitoring) sendEvent(eventData);
return originalSend(data);
};
When blocking is active, the function returns early without invoking originalSend, ensuring the message never leaves the browser. The proxy still reports the blocked event to the DevTools panel via sendEvent, allowing developers to see the suppressed message with a "blocked" tag.
Blocking Incoming WebSocket Messages
For incoming traffic, the proxy registers a message event listener with capture: true to intercept events during the capture phase before they reach any user-registered handlers. When proxyState.blockIncoming is active, the listener swallows the event entirely.
const ourMessageListener = function (event) {
// Simulated messages bypass blocking
if (event._isSimulated) { forwardToUser(event); return; }
// Incoming blocking check
if (proxyState.blockIncoming && proxyState.isMonitoring) {
connectionInfo.blockedMessages.push({
data: event.data,
timestamp: Date.now(),
direction: "incoming",
});
sendEvent({
blocked: true,
reason: "Incoming messages blocked",
/* … other fields … */
});
return; // Swallow: do not forward to page listeners
}
// Normal processing: log then forward to user handlers
if (proxyState.isMonitoring) { /* sendEvent */ }
forwardToUser(event);
};
By stopping execution before calling forwardToUser, the proxy ensures that user code (such as ws.onmessage callbacks) never receives the blocked message. As with outgoing blocking, the event is still reported to the DevTools panel with blocked: true metadata for debugging visibility.
Simulated Messages Bypass Blocking
Both outgoing and incoming simulation paths intentionally ignore block settings to allow developers to force test messages through the UI. The proxy checks for the _isSimulated flag at the entry point of both interception handlers, causing it to skip blocking logic and forward simulated traffic regardless of the current proxyState configuration.
Summary
- The WebSocket message blocking system uses a three-layer flow: UI toggles → background routing (
notifyAllTabs) → in-page proxy state updates - Outgoing blocking wraps
WebSocket.sendinsrc/content/injected.jsand aborts before calling the native implementation, preventing network transmission - Incoming blocking uses capture-phase event listeners to swallow messages before they reach page handlers, ensuring user code never processes blocked data
- Blocked events still emit to the DevTools panel via
sendEventwithblocked: trueand reason metadata, maintaining full debugging visibility - Simulated messages bypass all blocking logic via the
_isSimulatedflag, allowing forced testing regardless of current block settings
Frequently Asked Questions
How does the extension intercept WebSocket traffic without modifying the server?
The extension injects a proxy script into the page context via the content script, which overrides the native WebSocket constructor and its prototype methods. This client-side interception happens entirely within the browser, requiring no server-side changes or network-level proxy configuration.
Why do blocked messages still appear in the DevTools panel?
Even when blocking is active, the proxy calls sendEvent with blocked: true and a descriptive reason string before aborting the operation. This ensures developers can see exactly which messages were suppressed, when they were blocked, and in which direction, preserving debugging visibility while preventing actual data transmission.
Can I block messages for specific WebSocket connections only?
The current implementation in src/content/injected.js applies blocking globally through the shared proxyState object. The proxy checks proxyState.blockOutgoing and proxyState.blockIncoming flags for every WebSocket instance in the page, meaning all connections are affected uniformly by the toggle settings.
Do simulated messages respect the blocking settings?
No. Simulated messages explicitly set an _isSimulated flag on the event object. Both the outgoing send wrapper and incoming message listener check for this flag first and bypass all blocking logic when present, allowing developers to force test messages through regardless of current block configurations.
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 →