feat: support custom request headers for WsProvider - #752
Merged
Conversation
Add a `headers` option to `WsProviderOptions` so custom HTTP headers (e.g: an auth token for a private RPC endpoint or proxy) can be sent along with the websocket opening handshake. Headers can be provided either as a static map or as a function that is resolved on every connection attempt (including reconnects), which is helpful to refresh short-lived tokens or to use different credentials per endpoint. The headers are passed as an options object in the second constructor argument, which is supported by both the `ws` package (used by `@polkadot/x-ws` on Node.js < 22) and the native WebSocket implementation (Node.js >= 22, Bun). Browsers and Deno follow the WHATWG spec where the second argument is a list of subprotocols, so headers are ignored there with a warning. Closes #751 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpx2P4VETQHZ3hXGphLUAy
`@polkadot/x-ws` only ships a websocket client, so the previous test relied on the `ws` package for a server. Use a minimal handshake-only server built on `node:http` instead, keeping the test dependency-free while still exercising the real websocket implementation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpx2P4VETQHZ3hXGphLUAy
Verified across Node.js v18.20.8, v20.10.0, v20.20.2, v21.0.0, v21.7.3, v22.0.0, v22.23.2, v24.20.0, v26.8.1, Bun 1.4.0 and Deno 1.46/2.0/2.9: - A global WebSocket is only defined by default from Node.js v22 onwards (v20.10+ define one when started with `--experimental-websocket`), so the boundary between the `ws` package and the native implementation is not strictly the major version. - Passing the options as the second constructor argument sends the headers on every tested Node.js version and on Bun, while the third argument only works for the `ws` package. - Deno shims `process.versions.node`, but only very recent versions accept the options object, older ones throw `Invalid protocol value`, hence keeping it out of the supported runtimes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cpx2P4VETQHZ3hXGphLUAy
Member
Author
|
bugbot run |
|
Skipping Bugbot: Bugbot is disabled for this repository. Visit the Bugbot dashboard to update your settings. |
Member
Author
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 0d1f55d. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #751
Adds a
headersoption toWsProviderOptionsso custom HTTP headers (e.g: an auth token for a private RPC endpoint or proxy) can be sent along with the websocket opening handshake.Headers can also be resolved on each connection attempt (including reconnects), which is helpful to refresh short-lived tokens or to use different credentials per endpoint. The selector receives the same
WsConnectionStateas the endpoint selector, so the endpoint about to be connected to is available:No dependency changes — the websocket is still created through
@polkadot/x-wsas before, andyarn.lockis untouched.Why the headers are passed as the second constructor argument
@polkadot/x-wsresolves itsWebSocketexport viaextractGlobal('WebSocket', ws), so the underlying implementation depends on the runtime:wspackageWebSocket, since a globalWebSocketis definedPassing the options as a third argument (
new WebSocket(url, undefined, { headers })) only works for thewspackage and silently drops the headers on Node.js >= 22. Both implementations do accept an options object as the second constructor argument, which is what this PR uses — verified against both implementations, the header reaches the server and no bogusSec-WebSocket-Protocolis sent.Browsers and Deno follow the WHATWG spec where the second argument is a list of subprotocols, so headers cannot be sent there. In those environments the headers are ignored and a warning is logged once per provider instance (the headers selector is not invoked at all, to avoid a pointless token fetch). This is checked by a new
canSendRequestHeaders()helper. Headers are never logged.When no headers are configured, the websocket is constructed exactly as before.
Tests
WsProvider.spec.ts: the@polkadot/x-wsmock now records constructor arguments. New cases cover: no options passed when no headers are configured (guards the existing behavior), static headers, the selector receiving the connection state, headers being re-resolved on reconnection, an empty headers map being ignored, and headers being dropped with a warning when the environment does not support them.WsProviderHeaders.spec.ts(new): runs against a real websocket handshake without mocking the websocket implementation (// @vitest-environment node), asserting the headers actually arrive over the wire. This is what verifies the argument shape against whichever implementation the runtime picks — CI runs Node 18 (ws), locally verified on Node 22 (native). The server is a minimal handshake-onlynode:httpserver, so no extra dependency is needed (@polkadot/x-wsonly ships a client). Cross-checked by mutation: switching the implementation to the third-argument form makes this test fail on Node 22, which is exactly the regression it guards.utils.spec.ts: cases forcanSendRequestHeaderson Node.js, Bun, Deno and browsers.Docs are covered by the
WsProviderJSDoc examples and a section inpackages/providers/README.md; the user-facing docs site would need a matching follow-up.Note
Medium Risk
Changes how WebSockets are opened and may carry auth secrets, but behavior is unchanged when
headersis omitted and unsupported environments fail safe with warnings.Overview
Adds a
headersoption onWsProviderso callers can attach custom HTTP headers to the WebSocket opening handshake (e.g. auth for private RPCs). Values can be a static map or a selector invoked on each connect/reconnect with the sameWsConnectionStateas endpoint selection, for token refresh or per-endpoint credentials.Connection logic resolves headers via new
#getHeaders(), usescanSendRequestHeaders()to enable this only on Node.js/Bun, and passes{ headers }as the secondWebSocketconstructor argument so headers work with bothwsand native Node WebSocket (avoids silent drops on newer Node). Unsupported runtimes ignore headers, log a one-time warning, and skip the selector so token fetches are not wasted.README and JSDoc document usage; tests cover constructor args (mocked), on-the-wire handshake (Node integration), runtime detection, reconnect re-resolution, and empty-header behavior.
Reviewed by Cursor Bugbot for commit 0d1f55d. Bugbot is set up for automated code reviews on this repo. Configure here.