Skip to main content
These are the browser APIs, not lookalikes: fetch with Headers/Request/ Response/FormData, WebSocket, EventSource and XMLHttpRequest, on a native HTTP/1.1 + WebSocket transport with TLS. Which means browser HTTP libraries work unmodified - ky, axios, ofetch, jose, reconnecting-websocket, centrifuge, any SDK written for a browser or a service worker.
Nothing here blocks the game: a request in flight costs you no frames, and every callback still runs on the script thread, so there is no concurrency to reason about in your own code.

Overview

fetch

Requests, responses, bodies, streaming, aborting.

WebSocket

Full-duplex sockets with subprotocols and close codes.

EventSource

Server-sent events with automatic reconnect.

Limits & TLS

What the transport does not do, and who decides trust.
These follow the specs, so MDN is the reference for every argument and edge case. This page covers the parts that are specific to running inside the game.

fetch

The promise resolves as soon as the headers arrive. A 404 is a successful fetch - only transport failures reject, so check res.ok yourself.

Bodies

string, Blob, ArrayBuffer, any typed array, FormData, URLSearchParams and ReadableStream (with duplex: "half") are all accepted, and the implied content-type is filled in when you leave it out.
Read a response with text(), json(), bytes(), arrayBuffer(), blob() or formData() - once each. res.clone() before reading if you need it twice.

Streaming

res.body is a real ReadableStream with working backpressure, so a large download never has to be held in memory at once:
Reading pauses on its own when a slow consumer lets chunks pile up, so a body you consume lazily is not silently buffered whole. gzip, deflate and br arrive decoded, and the body is a normal stream - pipe it through a TextDecoderStream or a DecompressionStream if that suits you better.

Aborting & timeouts

The transport has deadlines of its own, and hitting one rejects with a DOMException named TimeoutError:

Redirects

Followed by the transport, up to 20, with the spec’s method rewrite (a 303, and a 301/302 on a request with a body, continue as GET). Authorization and Cookie are dropped when a redirect crosses to another origin.
redirect: "manual" deviates from the spec on purpose: a browser hands back an opaque response with no headers, and a script fetches a 3xx precisely to read its Location.

data: and blob: URLs

Resolved locally - no socket involved.
URLs must be absolute. There is no document to be relative to, so fetch("/api/status") fails with Failed to fetch: the scheme 'spurdo' is not supported. Keep the base URL in a constant.

Headers

Case-insensitive, sorted iteration, getSetCookie() for the one header that is never merged - all as specified. Response headers are immutable. There is no forbidden-header list: User-Agent, Host, Referer, Cookie and Origin all go out as you set them. Three are filled in when you leave them out - host, accept-encoding (gzip, deflate, br), and a desktop-Chrome user-agent, because plenty of sites answer 403 to anything they do not recognise.

WebSocket

Subprotocols, binaryType, bufferedAmount, CloseEvent with the peer’s code and reason, and the spec’s failure order (error, then a close that was not clean). http/https URLs are mapped onto ws/wss; anything else throws a SyntaxError.
Message compression (permessage-deflate) is offered to the server, and keep-alive pings plus an idle timeout come for free - a peer that dies quietly is noticed and reported as a close, not waited on forever.
WebSocket only exists when the host has the transport, so typeof WebSocket === "function" is an honest feature check rather than a stub that fails later.

Staying connected

Reconnect logic is not the socket’s job, so bring a library or a few lines:
reconnecting-websocket, socket.io-client and centrifuge all work as they are - they only ever needed a real WebSocket global.

EventSource

Server-sent events, for the one-directional case. Reconnects on its own, resumes with Last-Event-ID, honours a retry: field and treats a 204 as “do not come back”.
Default reconnect delay is 3 s until the server says otherwise. The server must answer 200 with content-type: text/event-stream; anything else is a hard failure and is not retried.

XMLHttpRequest

Also here, in full - because libraries look for it. axios’ default browser adapter is XHR, and so is every SDK written before fetch existed.
Events, readyState, timeout, upload, overrideMimeType and the responseType values ""/text/json/arraybuffer/blob all behave as specified. Two gaps:
  • open(..., false) - a synchronous request - throws. There is one script thread, and blocking it until a socket answers would freeze the game.
  • No DOM, so responseXML and responseType: "document" are always null. responseText still has the raw body.
Upload progress is one loadstart → progress → load → loadend run rather than byte-by-byte - the request body is sent in one piece, so there is nothing in between to report honestly.
Prefer fetch in your own code. Bundling axios for this runtime? Build with platform: "browser" and define process.env.NODE_ENV so it takes the XHR adapter instead of the Node http one.

TLS & certificates

Certificates are checked by Windows itself, against the same trust store a browser on that machine uses, and the verdict lands before a single byte of your request goes out. So a host is trusted here exactly when it is trusted in your browser - cross-signed and incomplete chains, revocation and the hostname check all included. Expired, wrong-host, self-signed, untrusted-root and revoked certificates are rejected with a readable reason:
A revocation check that cannot be reached is not treated as a bad certificate - otherwise a flaky network would look like a compromised server.
There is no way to skip verification and no pinning hook. If a host fails, its certificate really is unacceptable to the machine the script runs on.

Limits

The transport is deliberately small. What it does not do: mode, cache, referrerPolicy and credentials are stored and echoed back untouched, because libraries read them, but nothing acts on them.

Recipes

A small API client

Retry with backoff

Download to disk with progress


Error handling

Only transport-level problems reject:
A WebSocket never throws for a connection problem: it fires error and then close with wasClean === false. For the rest of the browser surface these build on - streams, Blob, crypto.subtle, TextDecoder - see Web APIs.