WebSocket
Fairyfly speaks WebSocket on both sides: upgrade HTTP clients with the websocket option (server), or dial out with new WebSocket(url) (client). Server and client share the frame protocol but have different APIs — this guide covers both.
Quick look: echo server + client
Server (server.js) — the websocket block makes upgrades automatic; echo everything back:
Client (client.js) — connect, send, print:
Server: upgrading connections
Add a websocket block to the options. Any request carrying the handshake headers (Connection: Upgrade, Upgrade: websocket, Sec-WebSocket-Key) is upgraded before your handler runs — the handler never sees that request, and no { status: 101 } branch is needed (a handler that would return 404 still gets the 101). The handler serves plain HTTP only:
Always check the type of data first — text and binary arrive differently:
Server: sending and broadcast
Server sockets have exactly two methods — send (text) and sendBinary (bytes). There is no socket.id, no readyState, and no server-side close. Identify peers by object reference in a Set:
Send binary with sendBinary:
Caution:
socket.sendafter the client disconnected fails — always remove sockets inclose, and never store an index or id on them (none exists).
Client: connecting and messaging
Only ws:// and wss:// URLs are accepted. For wss:// to private CAs, the same trust rules as fetch apply (--ca for scripts, FF_CA_FILE for ff start).
Proxies
The WebSocket client shares the runtime's HTTP client, so the proxy
environment variables apply exactly as they do for fetch (see
fetch Proxies: HTTP_PROXY / HTTPS_PROXY /
ALL_PROXY / NO_PROXY, read once at process start. wss:// tunnels
through CONNECT. ws:// also needs a proxy that accepts CONNECT for the
target port — a forward-only HTTP proxy will reject the connection.
Client errors
Two shapes, depending on when the failure happens. Synchronous mistakes
throw immediately — every message starts with a WebSocket: prefix:
A failed connection at runtime (refused, unreachable) does not throw
and does not fire onerror — you get a bare onclose. When the close
does carry an error, onerror fires first with an event whose .message
is a string (not an Error), immediately followed by onclose, whose
event carries numeric .code and string .reason:
Practical example: presence counter
Track who's online and push the count to every client on join/leave:
Reference: limits
Troubleshooting
Upgrade never fires — the websocket block must be present, and the client must send the handshake headers (Connection: Upgrade, Upgrade: websocket, Sec-WebSocket-Key — any real WS client does). No handler branch is needed: the upgrade happens before the handler runs.
socket.id is undefined — by design. Use a Set of socket objects; peers.has(socket) is the identity check.
Binary arrives as Uint8Array, not string — check typeof data === "string" first, per the table above.
Client connect fails — confirm the ws:///wss:// scheme, the 512-byte path limit, and (for wss://) the CA trust setup from the fetch guide.
