How to Manually Initiate a WebSocket Connection Using the Manual Connection Feature in law-chain-hot/websocket-devtools

Developers can manually initiate a WebSocket connection by opening the Manual Connect modal in the DevTools panel, entering a valid ws:// or wss:// URL, and triggering a four-layer message flow that instantiates the socket directly in the target page's JavaScript context.

The manual connection feature in law-chain-hot/websocket-devtools allows developers to create WebSocket connections from the Chrome DevTools panel even when the inspected page does not establish one natively. This feature routes requests through the extension's architecture—from the UI modal through the background script to the content script—where the actual WebSocket object is constructed and monitored.

Understanding the Manual Connection Architecture

The manual connection workflow operates across four distinct layers of the Chrome extension architecture. Each layer handles a specific responsibility in the request chain.

  • UI Layer (src/components/ManualConnectModal.jsx): Presents a modal interface where developers input the target WebSocket URL. The component validates the URL scheme (restricting input to ws:// or wss:// protocols) and invokes the onConnect callback with the sanitized URL.

  • Panel Logic Layer (src/devtools/panel.jsx): Implements the handleManualConnect function that creates a Promise-based listener to await the connection outcome. This layer sends the creation request to the background script using chrome.runtime.sendMessage with type "create-manual-websocket".

  • Background Script Layer (src/background/background.js): Receives the panel's request and forwards it to the specific tab's content script via chrome.tabs.sendMessage. This layer acts as a message broker between the DevTools panel and the page context.

  • Content Script Layer (src/content/injected.js): Executes inside the inspected page's JavaScript context. This layer constructs the actual new window.WebSocket(url) instance, attaches event listeners, and reports success or failure back to the background script.

Step-by-Step Connection Flow

Step 1: Modal Input and Validation

When a developer clicks to open a manual connection, src/components/ManualConnectModal.jsx renders an input field for the WebSocket URL. The component validates that the URL begins with ws: or wss: before enabling the Connect button. Upon confirmation, the modal calls onConnect(wsUrl), passing the validated string to the panel's handler.

Step 2: Panel-Side Request and Promise Creation

In src/devtools/panel.jsx, the handleManualConnect function orchestrates the request lifecycle. The function first establishes a Promise that listens for Chrome runtime messages with types "manual-connection-created" or "manual-connection-error", implementing a 10-second timeout to prevent indefinite waiting.

The handler then dispatches the creation request:

const response = await chrome.runtime.sendMessage({
  type: "create-manual-websocket",
  data: { url: wsUrl, tabId: currentTabId },
});

The function awaits both the background script's acknowledgment and the content script's completion event before resolving with the connectionId.

Step 3: Background Script Forwarding

The src/background/background.js file processes the "create-manual-websocket" message by extracting the tabId from the message payload. According to the source code, the background script validates the presence of tabId and forwards the request:

case "create-manual-websocket": {
  const tabId = message.data.tabId;
  if (tabId) {
    chrome.tabs.sendMessage(tabId, {
      type: "create-manual-websocket",
      url: message.data.url,
    })
    .then(() => sendResponse({ success: true }))
    .catch(err => sendResponse({ success: false, error: err.message }));
  }
  break;
}

This bridges the communication gap between the isolated DevTools panel and the target page's content script.

Step 4: Content Script Instantiation

The src/content/injected.js file receives the forwarded message and executes within the page's JavaScript context. The script constructs a native WebSocket instance and assigns a unique connectionId derived from the internal proxy wrapper:

const manualWs = new window.WebSocket(event.data.url);
const connectionId = manualWs._connectionId;

The script immediately attaches event listeners for open, error, and close events. Upon successful connection, it dispatches a "websocket-event" message with subtype "manual-connection-created":

chrome.runtime.sendMessage({
  type: "websocket-event",
  data: {
    type: "manual-connection-created",
    url: event.data.url,
    connectionId,
  },
});

If instantiation fails, the script sends "manual-connection-error" with the error message. The background script relays these events back to the DevTools panel, where the Promise created in Step 2 resolves and updates the UI.

Implementation Code Examples

Panel Handler with Promise-Based Response

The handleManualConnect function in src/devtools/panel.jsx demonstrates the complete request-response cycle with timeout handling:

const handleManualConnect = async (wsUrl) => {
  try {
    const connectionCreatedPromise = new Promise((resolve, reject) => {
      const timeout = setTimeout(() => 
        reject(new Error("Connection creation timeout")), 10000);

      const listener = (msg, _, sendResponse) => {
        if (msg.type === "websocket-event") {
          const d = msg.data;
          if (d.type === "manual-connection-created" && d.url === wsUrl) {
            clearTimeout(timeout);
            chrome.runtime.onMessage.removeListener(listener);
            resolve(d.connectionId);
          } else if (d.type === "manual-connection-error" && d.url === wsUrl) {
            clearTimeout(timeout);
            chrome.runtime.onMessage.removeListener(listener);
            reject(new Error(d.error || "Manual connection failed"));
          }
        }
        if (typeof sendResponse === "function") sendResponse({ received: true });
      };

      chrome.runtime.onMessage.addListener(listener);
    });

    const response = await chrome.runtime.sendMessage({
      type: "create-manual-websocket",
      data: { url: wsUrl, tabId: currentTabId },
    });

    if (!response?.success) throw new Error(response?.error);
    const connectionId = await connectionCreatedPromise;
    return { success: true, connectionId };
  } catch (e) {
    throw e;
  }
};

Content Script Socket Creation

The content script in src/content/injected.js handles the actual WebSocket construction and error reporting:

case "create-manual-websocket": {
  try {
    const manualWs = new window.WebSocket(event.data.url);
    const connectionId = manualWs._connectionId;

    manualWs.addEventListener('open', () => {
      chrome.runtime.sendMessage({
        type: "websocket-event",
        data: {
          type: "manual-connection-created",
          url: event.data.url,
          connectionId,
        },
      });
    });

    manualWs.addEventListener('error', (e) => {
      chrome.runtime.sendMessage({
        type: "websocket-event",
        data: {
          type: "manual-connection-error",
          url: event.data.url,
          error: e.message || "WebSocket error",
        },
      });
    });
  } catch (err) {
    chrome.runtime.sendMessage({
      type: "websocket-event",
      data: {
        type: "manual-connection-error",
        url: event.data.url,
        error: err.message,
      },
    });
  }
  break;
}

Key Source Files

Summary

  • The manual connection feature creates WebSocket instances in the target page's JavaScript context, enabling inspection of connections that the page itself does not initiate.

  • The implementation uses a four-layer architecture: UI modal → DevTools panel → Background script → Content script.

  • Message types "create-manual-websocket", "manual-connection-created", and "manual-connection-error" coordinate the cross-context communication.

  • The panel implements a 10-second timeout to handle unresponsive connection attempts gracefully.

  • Once established, manual connections are monitored identically to native WebSocket connections through the extension's proxy layer.

Frequently Asked Questions

How does the extension handle manual connection errors?

The content script in src/content/injected.js catches instantiation failures and WebSocket error events, dispatching "manual-connection-error" messages to the background script. The panel's handleManualConnect function rejects its Promise upon receiving this event, while a 10-second timeout prevents indefinite waiting if the content script fails to respond.

Can developers initiate manual connections to any tab from the DevTools panel?

Yes, provided the extension has access to the target tab. The handleManualConnect function requires a currentTabId parameter that specifies which tab's content script should receive the "create-manual-websocket" message. The background script validates this tabId before forwarding the request via chrome.tabs.sendMessage.

What WebSocket URL schemes are supported for manual connections?

The extension supports standard WebSocket protocols. The ManualConnectModal.jsx component validates that URLs begin with ws:// or wss:// before enabling the Connect button, ensuring only secure and unencrypted WebSocket schemes are accepted.

Are manually created WebSocket connections monitored differently than native ones?

No. Because the content script creates the WebSocket using new window.WebSocket(url) inside the page's actual JavaScript context, the extension's proxy layer automatically intercepts all subsequent traffic. Developers can inspect, simulate, or block messages on manual connections using the same tools available for page-native WebSocket connections.

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 →