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.connectionsarray, removing all WebSocket connection records associated with that specifictabId. - It broadcasts a
page-refreshmessage 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.connectionsare preserved. - The background script sends a
url-updatemessage to the DevTools panel containing both thepreviousUrlandnewUrl, 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
connectionevent viasendEvent. It registers listeners for nativeopen,close, anderrorevents to update theconnectionInfo.statusfield in real time. - Incoming traffic: A capturing
messagelistener (ourMessageListener) intercepts all incoming data, optionally blocks messages, decodes binary payloads, and forwards the information to the DevTools panel. - Outgoing traffic: The
ws.sendmethod 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.onUpdatedmonitoring base URL changes insrc/background/background.js. - Connection cleanup occurs immediately on full refreshes, removing all entries from the global
websocketData.connectionsarray for the affected tab. - State preservation applies only to hash or query changes, triggering a
url-updateevent instead of clearing data. - Proxy reinjection happens via
src/content/injected.jsredefiningwindow.WebSocketwithProxiedWebSocket, generating fresh connection IDs viagenerateConnectionId(). - Message interception uses wrapped
sendmethods and capturing listeners, with control exposed through the_proxyControlobject. - Background coordination forwards events through
forwardToDevToolsand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →