How law-chain-hot/websocket-devtools Handles Page Refreshes and Manages WebSocket Connections

The extension uses a Chrome background service worker to detect navigation events, clears all WebSocket connection records on full page reloads while preserving them during hash or query string changes, and re-injects a proxied WebSocket constructor into the refreshed page to maintain continuous monitoring.

The law-chain-hot/websocket-devtools repository provides a Chrome DevTools extension that intercepts WebSocket traffic by injecting a proxy into every monitored page. Understanding its behavior during page refreshes requires examining how the background worker coordinates with content scripts to manage connection state and lifecycle events.

Detecting Page Refreshes via the Background Service Worker

The extension’s background script (src/background/background.js) listens for browser navigation events to determine when a user reloads a page or navigates to a new URL.

Identifying Real Navigation Events

The script registers a listener on chrome.tabs.onUpdated and filters for events where changeInfo.status === "loading" and a changeInfo.url is present. It extracts the base URL—defined as the combination of protocol, host, and pathname—from the new location and compares it against the previously stored base URL for the same tabId.

If the base URL differs from the previous value, the extension treats the event as a real navigation or full page refresh. This distinction prevents the extension from clearing state during minor URL updates.

Clearing Connection Records on Refresh

When a real navigation is detected, the background script performs two critical actions:

  • It filters the global websocketData.connections array, removing all WebSocket connection records associated with that specific tabId.
  • It broadcasts a page-refresh message to every DevTools panel attached to the tab, including a count of the removed connections so the UI can reset its state accordingly.

Preserving WebSocket Connections During URL Updates

If the base URL remains identical but only the query string or hash fragment changes, the extension classifies this as a URL update rather than a navigation event. In this scenario:

  • Existing connection records in websocketData.connections are preserved.
  • The background script sends a url-update message to the DevTools panel containing both the previousUrl and newUrl, allowing the UI to update its display without discarding active connection histories.

Re-injecting the WebSocket Proxy After Page Reload

When a page refreshes, the content script (src/content/injected.js) executes again and immediately replaces the native window.WebSocket constructor with a custom ProxiedWebSocket class.

Connection Initialization and Unique Identification

Each new WebSocket instance created after a refresh receives a unique identifier generated by the generateConnectionId() function. The proxy maintains a per-connection record in a JavaScript Map named connections, tracking metadata and message history for that specific socket.

Intercepting Messages and Events

The ProxiedWebSocket implementation wraps the native WebSocket to provide comprehensive monitoring:

  • Lifecycle events: On construction, it immediately emits a connection event via sendEvent. It registers listeners for native open, close, and error events to update the connectionInfo.status field in real time.
  • Incoming traffic: A capturing message listener (ourMessageListener) intercepts all incoming data, optionally blocks messages, decodes binary payloads, and forwards the information to the DevTools panel.
  • Outgoing traffic: The ws.send method is wrapped to log outgoing messages, apply blocking rules, and emit corresponding events before passing data to the native socket.

Developer Control Interface

Every proxied WebSocket instance exposes a _proxyControl object that provides programmatic access to the interception layer:

// Access blocked messages for a specific WebSocket instance
const blocked = ws._proxyControl.getBlockedMessages();
console.log('Blocked queue:', blocked);

// Clear the blocked message history
ws._proxyControl.clearBlockedMessages();

Coordinating State Between Background and DevTools Panels

Communication between the injected proxy and the DevTools UI flows through multiple message channels managed by the background worker.

Event Forwarding and Keep-Alive

The injected script transmits events to the content script using window.postMessage, which the background script receives as type websocket-event or websocket-event-batch. The background script maintains a devtoolsPorts map to route these events to the correct DevTools panel via the forwardToDevTools function.

To prevent message channel timeouts during long debugging sessions, the background script implements a keep-alive timer that periodically pings connected panels, ensuring the communication port remains active even during idle periods.

Refresh Behavior Summary

Situation Connection State Message to DevTools
Full page reload or navigation to different base URL All tab connections cleared from websocketData.connections page-refresh (includes removed count)
Hash or query string change only Connections preserved url-update (with previous and new URLs)
Same-page reload via cache Connections cleared (base URL change detected) page-refresh

Handling Refresh Events in DevTools Panels

Developers building extensions on top of this tool can listen for refresh notifications to synchronize UI state:

chrome.runtime.onConnect.addListener(port => {
  if (port.name !== 'devtools') return;

  port.onMessage.addListener(msg => {
    if (msg.type === 'page-refresh') {
      console.log('Page refreshed, resetting connection list');
      // Clear UI state and discard stale connections
    }
    if (msg.type === 'url-update') {
      console.log(`URL updated: ${msg.data.previousUrl} → ${msg.data.newUrl}`);
    }
  });
});

Summary

  • Page refresh detection relies on chrome.tabs.onUpdated monitoring base URL changes in src/background/background.js.
  • Connection cleanup occurs immediately on full refreshes, removing all entries from the global websocketData.connections array for the affected tab.
  • State preservation applies only to hash or query changes, triggering a url-update event instead of clearing data.
  • Proxy reinjection happens via src/content/injected.js redefining window.WebSocket with ProxiedWebSocket, generating fresh connection IDs via generateConnectionId().
  • Message interception uses wrapped send methods and capturing listeners, with control exposed through the _proxyControl object.
  • Background coordination forwards events through forwardToDevTools and maintains channels using a keep-alive mechanism.

Frequently Asked Questions

Does WebSocket DevTools lose connection history on a hard refresh?

Yes. When a full page reload occurs or the user navigates to a different base URL, the extension removes all WebSocket connection records associated with that tab from the websocketData.connections array and emits a page-refresh event to clear the DevTools panel. Connection history is only preserved if the URL change is limited to the hash or query string.

How does the extension distinguish between a page reload and a hash change?

The background script compares the base URL (protocol + host + pathname) of the new location against the previous value stored for the tabId. If the base URL matches and only the hash or query differs, it treats the change as a URL update; otherwise, it triggers a full refresh cleanup.

Can I access the proxied WebSocket control object from the browser console?

Yes. Every WebSocket instance created while the extension is active exposes a _proxyControl object that allows you to retrieve blocked messages, clear the blocked queue, and access connection metadata. This interface is injected by the ProxiedWebSocket class defined in src/content/injected.js.

What message types does the DevTools panel receive during navigation?

The panel receives two primary messages from the background script: page-refresh when the base URL changes (triggering UI reset) and url-update when only the hash or query changes (allowing the panel to update the URL display while preserving connection data). Both messages are sent via the devtoolsPorts communication channel.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →