Files
Samuel Attard cdf7ac51c5 feat: add net.WebSocket (#51593)
* feat: add net.WebSocket

Add a WHATWG-compatible WebSocket client to the net module, backed by
Chromium's network stack via NetworkContext::CreateWebSocket(). This
gives main-process code a WebSocket that goes through the session's
proxy configuration, certificate verification, and cookie store, with
the same drop-in API shape as the browser/Node global WebSocket.

The implementation has two pieces:

- shell/browser/api/electron_api_web_socket.{h,cc}: a gin-wrapped object
  that implements network.mojom.{WebSocketHandshakeClient, WebSocketClient,
  WebSocketAuthenticationHandler}, drives the mojo data pipes with
  back-pressure on both sides, reassembles fragmented frames, tracks
  bufferedAmount, and emits low-level events.
- lib/browser/api/net-websocket.ts: a class WebSocket extends EventTarget
  that implements the WHATWG surface (addEventListener, on{open,message,
  error,close}, readyState, bufferedAmount, protocol, extensions,
  binaryType, send(), close(), CONNECTING/OPEN/CLOSING/CLOSED) plus
  Electron-specific options (headers, origin, useSessionCookies, session,
  partition).

binaryType supports 'nodebuffer' (default), 'arraybuffer', and 'blob'.
useSessionCookies defaults to false, matching net.request(). The Origin
header defaults to the http(s) equivalent of the WebSocket URL's origin
so that the connection looks first-party for SameSite cookie purposes;
it can be overridden with the origin option.

Main process only for now; webRequest interception is a known follow-up.

* fix: address review findings and migrate WebSocketWrapper to gin::Wrappable

Migrate WebSocketWrapper from gin_helper::DeprecatedWrappable to
gin::Wrappable (cppgc-based, electron/electron#47922):

- Add kElectronWebSocket wrappable pointer tag.
- Replace Pin()/Unpin() with gin_helper::SelfKeepAlive, cleared on teardown.
- Replace base::WeakPtrFactory with gin::WeakCellFactory + gin::WrapPersistent
  for callbacks; add Trace() to visit the weak cell.
- Annotate mojo::Receiver/Remote fields with GC_PLUGIN_IGNORE (browser process
  doesn't need execution-context tracking), matching UtilityProcessWrapper.
- Public constructor for cppgc::MakeGarbageCollected; Create() returns T*.

Spec and correctness fixes:

- Drop the unused WebSocketAuthenticationHandler. The 'login' event it emitted
  was never bridged to the WHATWG-only TS surface, leaving the mojo
  OnAuthRequired callback orphaned and the connection hung on a 401/407
  challenge until V8 GC. Pass NullRemote() so the network service fails the
  challenge fast (matching WebSocketConnectorImpl). Drop the 'authenticated
  proxies' claim from the docs.
- Serialize the send queue so a Blob send (which must read asynchronously)
  doesn't let a later string/ArrayBuffer send overtake it on the wire.
- Set readyState to CLOSED before dispatching the 'error' event, per the
  WebSocket spec ordering rules.
- Stop nulling the wrapper on close so bufferedAmount reflects untransmitted
  bytes (it must not reset to zero) and continues to count post-close sends.
- Use WebIDL [Clamp] semantics for the close() code argument.
- Disable permessage-deflate on the test ws server; it isn't the subject under
  test and ws@7's deflate close path can race a server-initiated close.

* chore: correct copyright holder in new files

* fix: address review feedback

- Track all bytes queued in the JS send chain (not just Blob reads) so
  bufferedAmount is accurate while a non-Blob send is waiting behind an
  asynchronous Blob read.
- Surface the network failure message and net error in CloseEvent.reason
  for connection failures (the WHATWG 'error' Event has no payload). Failure
  reasons from the network service already embed the net error name; only
  append it for internally-generated failures.
- Emit a Node Buffer from the C++ side via electron::Buffer::Copy() instead
  of a raw ArrayBuffer, avoiding a TS-side wrap on the default 'nodebuffer'
  path.
- Drop the redundant disconnect handler on the WebSocket remote; the
  WebSocketClient receiver and the remote terminate at the same WebSocketImpl
  in the network process.

* chore: remove accidentally committed lock file
2026-05-20 10:03:40 -04:00

6.3 KiB
Raw Permalink Blame History

Class: WebSocket extends EventTarget

Create WebSocket connections from the main process using Chromium's native networking library

Process: Main

net.WebSocket is a drop-in replacement for the WHATWG WebSocket interface that routes the connection through Chromium's network stack rather than Node.js. Use it when you want a main-process WebSocket connection that:

  • Uses the system or session proxy configuration (PAC, WPAD).
  • Validates TLS certificates against the platform trust store and the session's certificate verification policy.
  • Honors session-level configuration (custom CA, host resolution rules, etc.).
  • Sends the session's cookies (when useSessionCookies is enabled).

The class implements the standard WebSocket interface (an EventTarget), so code written against the browser or Node.js global WebSocket works without changes:

const { app, net } = require('electron')

app.whenReady().then(() => {
  const ws = new net.WebSocket('wss://echo.websocket.events')
  ws.onopen = () => ws.send('hello')
  ws.onmessage = (event) => {
    console.log('received', event.data)
    ws.close()
  }
})

net.WebSocket can only be used after the application emits the ready event.

new WebSocket(url[, protocols])

  • url string - The URL to connect to. The scheme must be ws: or wss: (http: and https: are accepted and rewritten to their WebSocket equivalents, as in the browser).
  • protocols string | string[] | WebSocketOptions (optional) - One or more WebSocket subprotocols, or an Electron-specific options object.

Passing an options object as the second argument is an Electron extension; the two-argument form new net.WebSocket(url, protocols) is fully compatible with the WHATWG constructor.

Static Properties

WebSocket.CONNECTING Readonly

An Integer constant equal to 0, the readyState value while the opening handshake is in progress.

WebSocket.OPEN Readonly

An Integer constant equal to 1, the readyState value once the connection is established.

WebSocket.CLOSING Readonly

An Integer constant equal to 2, the readyState value while the closing handshake is in progress.

WebSocket.CLOSED Readonly

An Integer constant equal to 3, the readyState value once the connection is closed.

Instance Properties

ws.url Readonly

A string representing the resolved URL of the connection.

ws.readyState Readonly

An Integer representing the current state of the connection: one of WebSocket.CONNECTING (0), WebSocket.OPEN (1), WebSocket.CLOSING (2), or WebSocket.CLOSED (3).

ws.bufferedAmount Readonly

An Integer representing the number of bytes of application data that have been queued via send() but not yet handed off to the network.

ws.protocol Readonly

A string containing the subprotocol selected by the server. The empty string until the connection is open or if the server did not select a subprotocol.

ws.extensions Readonly

A string containing the extensions negotiated by the server (for example permessage-deflate).

ws.binaryType

A string controlling how incoming binary messages are exposed on the message event. Can be nodebuffer, arraybuffer, or blob. The default is nodebuffer.

'nodebuffer' is an Electron extension that delivers binary messages as Buffer objects, which is generally the most convenient representation in the main process. Set binaryType to 'arraybuffer' or 'blob' for behavior identical to the renderer WebSocket.

ws.onopen

A Function | null event handler for the open event. Equivalent to calling addEventListener('open', ...).

ws.onmessage

A Function | null event handler for the message event. Equivalent to calling addEventListener('message', ...).

ws.onerror

A Function | null event handler for the error event. Equivalent to calling addEventListener('error', ...).

ws.onclose

A Function | null event handler for the close event. Equivalent to calling addEventListener('close', ...).

Instance Methods

ws.send(data)

  • data string | ArrayBufferLike | ArrayBufferView | Blob - The data to send. Strings are sent as text frames; everything else is sent as a binary frame.

Enqueues data to be transmitted to the server. Throws an InvalidStateError DOMException if readyState is CONNECTING.

ws.close([code][, reason])

  • code Integer (optional) - The WebSocket close code. Must be 1000 or in the range 3000–4999.
  • reason string (optional) - A human-readable close reason. Must encode to no more than 123 bytes of UTF-8.

Closes the connection. Calling close() while still CONNECTING aborts the handshake.

Events

net.WebSocket is an EventTarget, not an EventEmitter. Listen with addEventListener() or the corresponding on* event handler property:

  • open - Emitted when the connection is established and the opening handshake has completed. After this event, protocol and extensions reflect the values negotiated with the server.
  • message - Emitted with a MessageEvent when a message arrives. event.data is a string for text frames, or a Buffer, ArrayBuffer, or Blob (per binaryType) for binary frames.
  • error - Emitted when the connection fails. Always followed by a close event.
  • close - Emitted with a CloseEvent (code, reason, wasClean) when the connection is closed for any reason. When the connection fails (for example, the handshake is rejected or the network is unreachable), code is 1006 and Electron sets reason to a short description of the underlying network error so that the failure is diagnosable without a debugger attached.