skip to content

What does React Native's WebSocket constructor accept that the browser's does not, and when would you use its third argument?

level: middleimportance: should knowfreq 36%

answer

  1. three arguments, not two
  2. an options object with headers
  3. Authorization on the upgrade request
  4. native cookies attached automatically
  5. plus a non-standard ping()

basics

~20 s

React Native's WebSocket takes a third argument, { headers }, sent on the upgrade request, so an app can set Authorization or custom headers a browser WebSocket cannot. Native cookies are attached too, and a non-standard ping() method exists.

solid answer

~50 s

The browser constructor is `new WebSocket(url, protocols)`. React Native's is `new WebSocket(url, protocols, options)`, where `options.headers` is a map of header names to strings that the native module adds to the HTTP upgrade request. That lets an app send `Authorization: Bearer ...`, an API version or a device id without query-string tricks. Other keys in `options` are ignored with a warning asking whether you meant to put them under `headers`, and a top-level `origin` is deprecated in favour of `headers.origin`. The native layer also attaches cookies from the shared native cookie store, and on Android it adds an `Origin` header derived from the socket URL unless you set one. React Native additionally exposes a non-standard `ws.ping()` that sends a protocol ping. The catch: code shared with React Native Web cannot rely on any of this, because the browser ignores the third argument.

code

typescript · 15 lines
typescript
import { Platform } from 'react-native';

export function openBidStream(token: string): WebSocket {
  const url = 'wss://auctions.example.com/bids';
  if (Platform.OS === 'web') {
    // Browsers ignore a third argument; use a scheme the server accepts from web
    return new WebSocket(url);
  }
  return new WebSocket(url, null, {
    headers: {
      Authorization: `Bearer ${token}`,
      'X-App-Version': '4.2.0',
    },
  });
}

go deeper

for a junior

Recall that React Native's WebSocket takes a third argument with a headers map, which a browser WebSocket does not.

for a middle

Explain what the native module adds for you: headers on the upgrade, cookies from the shared store, a default Origin on Android, and the non-standard ping().

for a senior

Plan for token rotation on long-lived sockets, keep secrets out of logs, and handle code shared with React Native Web where the headers option is ignored.

for a principal

Align one socket authentication scheme across web and native clients with the backend, weighing header-based auth on mobile against a single browser-compatible mechanism.

## Two constructors, one name The browser's `WebSocket` constructor takes a URL and optional subprotocols. The page cannot add arbitrary HTTP headers to the upgrade request; the browser builds that request itself. React Native's `WebSocket` is implemented by native code (OkHttp on Android, SocketRocket on iOS), and its constructor has a **third parameter**: ```ts new WebSocket(url, protocols, { headers: { Authorization: `Bearer ${token}` } }); ``` The TypeScript declaration shipped with React Native types it as `options?: { headers: { [headerName: string]: string } }`. The JavaScript class passes `{ headers }` to the native module's `connect`, and native code adds each entry to the upgrade request. ## Details the source makes explicit - **Only `headers` is recognised.** Any other key in the options object triggers a warning asking whether you meant to put it under `headers`. - **`origin` moved.** A top-level `origin` option still works but is deprecated with a warning; put it in `headers.origin`. - **Values must be strings.** On Android, a non-string header value is ignored and logged. - **Default `Origin` on Android.** If you do not pass an origin, Android's module adds one derived from the socket URL (`https://host` for `wss://`). - **Cookies come along.** Both platforms load cookies for the host from the native cookie store shared with `fetch` and `XMLHttpRequest` and send them on the upgrade. - **Subprotocols** passed as the second argument become the `Sec-WebSocket-Protocol` header. ## When the third argument is the right tool | Need | With React Native's headers option | Without it | |---|---|---| | Bearer token for the socket | `Authorization` header on the upgrade | Token in the URL or a first message | | API or app version negotiation | A custom header such as `X-App-Version` | A query parameter | | Server expects an `Origin` allow-list | Set `headers.origin` explicitly | Rely on the platform default | | Code shared with React Native Web | Not available in the browser | Needs a browser-compatible scheme | Which token-placement scheme a backend should accept is a protocol and security design question in its own right; the React Native point is simply that the mobile client **can** send headers, so it is not forced into the browser's workarounds. ## The non-standard ping() React Native's `WebSocket` also has a `ping()` method (declared in its TypeScript types) that asks the native module to send a protocol ping frame. It throws while the socket is still connecting. Two limits keep it from being a health check: JavaScript receives **no pong event**, and a browser `WebSocket` has no such method at all. It can keep intermediaries from idling the connection, but detecting a dead socket needs an application-level heartbeat. ## Checking what the server received When a header seems to be missing, verify from both sides before blaming the client: 1. Log the header names (never the values of secrets) you pass in `options.headers`. 2. Confirm on the server, or in a proxy in front of it, which headers arrived on the upgrade request. 3. Remember that a reverse proxy or load balancer may strip or rename headers before they reach the application. 4. Check that the key really sits under `headers`; a top-level key produces only a console warning and never reaches the network. ## Pitfalls - **Token refresh.** Headers are sent once, on the upgrade. When the access token rotates, the open socket keeps the old authentication until you reconnect with a new header, so plan a reconnect or an in-band re-auth. - **Cross-platform code.** If the same module runs on React Native Web, the third argument is ignored there; branch with `Platform.OS` or keep a browser-compatible fallback. - **Logging.** Headers with tokens should never be written to logs when you debug the connection. ## Interview framing Say that React Native's constructor is `(url, protocols, { headers })`, give the Authorization use case, mention that native cookies and a default `Origin` (on Android) are added for you, and call out the React Native Web limitation. Mentioning `ping()` and its missing pong event shows you read the implementation rather than the browser docs.

  • The access token rotates every 15 minutes, but the auction socket stays open for hours. What happens?
    Headers are sent only on the upgrade request, so the open socket stays authenticated with the old token as far as the handshake is concerned. Either reconnect with the new header when the token refreshes, or have the server accept an in-band re-authentication message and close sockets whose credential expired.
  • Can ws.ping() tell your React Native code that the server is still alive?
    No. React Native's `ping()` sends a protocol ping through the native module, but no pong event reaches JavaScript, so your code learns nothing from it. Liveness needs an application-level heartbeat message that the server answers and your code times.

saying these in an interview costs you the question

  • React Native's WebSocket accepts the same two arguments as the browser's, nothing more.
  • Any option passed in the third argument is forwarded as a header.
  • The headers option also works when the code runs on React Native Web.
  • Headers set at connect time are re-sent automatically after a token refresh.
  • ws.ping() reports a pong back to JavaScript, so it doubles as a health check.