Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mini-ws

WebSocket from scratch — RFC 6455 handshake, framing, and close handshake in dependency-free TypeScript, tested against native clients.

Every realtime feature I'd shipped treated new WebSocket(url) as magic. So I read the RFC and built the protocol layer by hand: the HTTP upgrade and its SHA-1 challenge, the 2–14 byte frame headers, client masking, fragment reassembly, ping/pong, and the close handshake — then proved it interoperates with the WebSocket client shipped inside Node itself. The study notes in docs/ are the other half of the repo.

import { createWsServer, connect } from "./src/index.js";

const ws = createWsServer((conn) => {
  conn.send("welcome");                    // works with any browser
  conn.onMessage = (data) => conn.send(data); // WebSocket client
});
ws.server.listen(8080);

const conn = await connect({ host: "localhost", port: 8080 });
conn.onMessage = (data) => console.log(data);   // → "welcome"

What "from scratch" covers

   one HTTP request…            …then never HTTP again
  ┌──────────────────┐        ┌─────────────────────────────────┐
  │ GET / HTTP/1.1   │        │ ┌─┬─────────┬─────────────────┐ │
  │ Upgrade: websocket│       │ │F│ opcode  │ len (7/16/64)   │ │
  │ Sec-WebSocket-Key│  ───▶  │ ├─┴─────────┴─────────────────┤ │
  ├──────────────────┤        │ │ mask-key (client→server)    │ │
  │ 101 + Accept =   │        │ ├─────────────────────────────┤ │
  │ base64(sha1(key+ │        │ │ payload (XOR-masked if client)│ │
  │ GUID))           │        │ └─────────────────────────────┘ │
  └──────────────────┘        │   text · binary · ping · pong · │
                              │   close (a handshake of its own)│
                              └─────────────────────────────────┘
Layer File What's in it
Frames src/frame.ts encode + streaming parse, masking XOR, minimal length encoding, control-frame rules
Handshake src/handshake.ts Key↔Accept (RFC sample vector pinned), request validation, subprotocols
Connection src/connection.ts fragment reassembly, interleaved control frames, UTF-8 validation (1007), close handshake, close codes
Server src/server.ts http.Server upgrade handling, connection set, broadcast, the pause/unshift/resume handoff
Client src/client.ts raw TCP (node:net), speaks the handshake itself, masked frames
Resiliency src/resiliency.ts what the browser API doesn't give you: reconnect (full-jitter backoff) + heartbeat

Try it

git clone https://github.com/wjdjdakf17/mini-ws && cd mini-ws
npm install
npm test        # 35 tests: unit (in-memory duplex pairs) + real-TCP e2e
npm run demo    # → http://localhost:8080 — a browser page (native WebSocket)
                #   chatting with this from-scratch server, RTT probe included

The e2e suite includes the compliance proof: Node's built-in (undici) WebSocket client connecting, exchanging messages, and closing cleanly against this server. If the handshake math or framing were off by a byte, it would refuse us.

Three of those tests exist because the bugs were real: a first-frame loss at the HTTP→WS handoff, a close-handshake deadlock (the server must hang up TCP first — RFC §7.1.1), and http.Server.closeAllConnections() silently skipping upgraded sockets. Each story is in docs/05.

Study notes

  1. Why WebSockets exist — polling → long-poll → SSE → WS, and the cost of full-duplex
  2. The handshake — Upgrade mechanics, why SHA-1 isn't for security, subprotocols
  3. Framing — byte-level anatomy, why clients must mask, fragmentation, close codes
  4. What the browser adds (and doesn't) — API limits, Socket.IO's trade, WebSocketStream, WebTransport Baseline 2026
  5. Production realities — heartbeats, jittered backoff, LB/proxy quirks, the three bugs this repo's tests caught
  6. Design decisions — ADRs, and what to build next
  7. References

What this is not

No permessage-deflate, no extensions negotiation, no HTTP/2 bootstrap (RFC 8441), no rooms/pub-sub, no reconnect UI — each is a deliberate, documented next step (docs/06). For production use ws or Socket.IO; read this first so you know what they're doing for you.

Companion repo: mini-harness — the same from-scratch treatment for AI agent harnesses.

License

MIT

About

WebSocket from scratch — RFC 6455 handshake, framing, and close handshake in dependency-free TypeScript, tested against native clients.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages