WebSocket, SSE & XHR
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.
new WebSocket(url)
Works, with caveatsBoth 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.
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() }- The client offers no
permessage-deflateextension (Node's does), sows.extensionsis always''. - klain:ws's server-side
.close()ends the connection without waiting for the peer's close frame. Its message events carryev.isBinaryand a byte-exactev.dataBytes()(klain:ws's own API, not the client's).
new EventSource(url)
ExactServer-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 }`.
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() }new XMLHttpRequest()
Works, with caveatsA 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.
ReturnsXMLHttpRequest — 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- 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 optionalasyncflag:async === falseis exact (the request is already synchronous);async === truealso blocks (no main-thread event loop to defer.onloadonto) — a documented narrowing. Theuser/passwordargs are accepted but not yet wired to HTTP auth..setRequestHeader(name, value)accumulates headers, but names are **not** lowercased here (unlikeHeaders' own methods) — a separate V1 narrowing..readyStateis 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/.responseare identical values — only a textresponseTypeis supported; noresponseTypeother than the default..onreadystatechange/.onload/.onerrorcallbacks are zero-argument (deliberately — a payload-carrying callback isn't representable for this particular type)..abort()is a best-effortreadyStatereset — 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.