API reference

WebSocket, SSE & XHR

3/3Coverage1/3Strict (zero caveats)

The realtime and legacy corners of the Networking surface — WebSocket, EventSource (SSE), and XMLHttpRequest. WebSocket and EventSource are Node's own (undici's), written in TypeScript; XMLHttpRequest is modeled as the spec's legacy synchronous mode. The fetch/Response/Request/Headers rows of this same status page are documented separately under the fetch surface.

Exact Works, with caveats Not supported

new WebSocket(url)

Works, with caveats
new WebSocket(url: string | URL, protocols?: string | string[]): WebSocket

Both a client and a server-side WebSocket. The client is the global WebSocket as Node's (undici's): the constructor returns CONNECTING and the handshake runs on the event loop; messages arrive as MessageEvents whose binary data is a Blob or an ArrayBuffer per binaryType, and close()/onclose carry a CloseEvent (code, reason, wasClean). Server-side, attach klain:ws's `new WebSocketServer({ server })` to an http.createServer (ws://) or https.createServer (wss://) server and `wss.on('connection', socket => …)` hands each upgraded connection a WSConnection that coexists with ordinary HTTP. The raw Node `server.on('upgrade', (req, socket, head) => …)` event is also available for hand-rolled or non-WebSocket upgrades.

Parameters
urlstring | URL
The ws:// or wss:// endpoint (http:/https: are rewritten, as Node does).
protocolsstring | string[]
Subprotocols to offer; the server's choice is ws.protocol.
Returns

WebSocket — The client socket, an EventTarget with onopen/onmessage/onclose/onerror handlers.

const ws = new WebSocket('ws://127.0.0.1:8083/')
ws.onopen = () => { ws.send('ping') }
ws.onmessage = (ev) => { console.log(ev.data); ws.close() }
See it in a full example →
⚠ Differences from standard
  • The client offers no permessage-deflate extension (Node's does), so ws.extensions is always ''.
  • klain:ws's server-side .close() ends the connection without waiting for the peer's close frame. Its message events carry ev.isBinary and a byte-exact ev.dataBytes() (klain:ws's own API, not the client's).

new EventSource(url)

Exact
new EventSource(url: string | URL, init?: EventSourceInit): EventSource

Server-sent events (SSE) as Node's (undici's) EventSource, over fetch — behind `--experimental-eventsource` in Node. Covers readyState/close(), onmessage/onopen/onerror, and addEventListener/removeEventListener for named (event: name) records — data/event/id/retry field parsing, multi-line data: concatenation, the CRLF/LF/CR line endings, and auto-reconnect honoring a server-sent retry: delay while replaying Last-Event-ID. A response that arrives with the wrong shape (non-200, or without text/event-stream) fails permanently instead of retrying.

Parameters
urlstring | URL
The SSE endpoint to connect to.
initEventSourceInit
`{ withCredentials }`.
Returns

EventSource — The event source, with readyState, onmessage/onopen/onerror and addEventListener.

const messages = new EventSource('http://127.0.0.1:8765/stream')
messages.onopen = (ev) => { console.log(ev.type) }   // open
messages.onmessage = (ev) => { console.log(ev.data); messages.close() }
See it in a full example →

new XMLHttpRequest()

Works, with caveats
new XMLHttpRequest(): XMLHttpRequest

A legacy client modeled as the spec's synchronous mode: .send() runs the request to completion before returning, then fires every registered callback once. It's not actually blocking — .send() is built on the same non-blocking primitive fetch() uses, so it still yields the current fiber when called from inside an http.listen connection handler or a klain:sync worker goroutine (making pooled goroutines a real closed-loop load generator). Supports .open(method, url, async?, user?, password?) with async: false exact, .setRequestHeader(), .send()/.send(body), .abort(), .readyState, .status, .responseText/.response, .getResponseHeader()/.getAllResponseHeaders(), and the zero-argument callbacks. A network-level failure fires .onerror rather than throwing.

Returns

XMLHttpRequest — The request object, with onreadystatechange/onload/onerror callbacks.

const xhr = new XMLHttpRequest()
xhr.onload = () => { console.log('loaded') }
xhr.open('GET', 'http://127.0.0.1:8765/get')
xhr.send()
console.log(xhr.status)  // 200
See it in a full example →
⚠ Differences from standard
  • Modeled as the real spec's legacy *synchronous* mode: .send()/.send(body) runs the request to completion before returning, then fires every registered callback once, synchronously — there's no default-async, callback-interleaved mode (this compiler has no re-entrant-into-already-returned-caller callback machinery to support one).
  • .open(method, url, async?, user?, password?) accepts the optional async flag: async === false is exact (the request is already synchronous); async === true also blocks (no main-thread event loop to defer .onload onto) — a documented narrowing. The user/password args are accepted but not yet wired to HTTP auth.
  • .setRequestHeader(name, value) accumulates headers, but names are **not** lowercased here (unlike Headers' own methods) — a separate V1 narrowing.
  • .readyState is 0/1/4 only (UNSENT/OPENED/DONE); the real spec's HEADERS_RECEIVED/LOADING have no meaning for a transfer that always runs to completion in one call.
  • .responseText/.response are identical values — only a text responseType is supported; no responseType other than the default.
  • .onreadystatechange/.onload/.onerror callbacks are zero-argument (deliberately — a payload-carrying callback isn't representable for this particular type).
  • .abort() is a best-effort readyState reset — there's nothing to actually interrupt once a synchronous .send() has returned.
  • .getAllResponseHeaders() returns headers in arrival order, not the spec's lowercase-sorted order.