Public: anyone can read this; the crew can change it.

Socket: the connection, its rules and its limits, before any frame goes through it #23

shipped production is serving it opened this has it
opened this

Split out of #9 so the socket's hard half — who may open one, what closes it, and what a flood costs — is built and tested against a stub source while the round (#8) is still being written. #9 keeps only the frames.

Done looks like

  • [ ] src/socket.ts (new): GET /ws upgraded through Bun.serve's built-in websocket support, no

dependency, with one row in src/routes.ts so the table stays the map of every path.

  • [ ] The upgrade is refused unless it passes socketOrigin from #17 — Origin present and equal to

publicOrigin. A missing Origin is refused here even though a POST may pass without one, and the code says why in a line.

  • [ ] Identity is fixed at upgrade time: who(request, key) plus playerKey from #19. The socket

remembers who it is; no inbound message ever names its own sender.

  • [ ] Every inbound message is validated by parseSteer from #16 and by nothing else: at most 256

bytes, an object, and a named failure closes the socket with 1008 while throwing nothing.

  • [ ] Rationed per socket: a normal 20 steers a second passes, a flood is dropped rather than queued,

and a socket that keeps flooding is closed. A socket silent for 90 s is closed, and a ping goes out inside the 60 s idleTimeout in src/server.ts.

  • [ ] A seat is a person, not a tab: the pull decides and writes down what a second socket on the same

player key does — newest wins, or both drive one mote — so one person cannot hold 24 of 24 seats.

  • [ ] Backpressure is a named function #9 can call per frame: a socket whose send buffer is backed up is

skipped, never awaited, so a slow client degrades alone.

  • [ ] Sockets are handed to a publish(payload) that the round will drive later; in this issue a test

drives it, so this lands with no loop in the tree.

  • [ ] src/socket.test.ts: a foreign Origin and an absent Origin are both refused; an oversized

message and a malformed one each close with 1008; valid steers arrive in order; a flood is dropped and the socket survives; two sockets on one player key do what the pull says they do.

Files src/socket.ts (new), src/socket.test.ts (new), src/routes.ts, src/server.ts.

Risks Websockets bypass allow() entirely, which is why the per-socket ration is acceptance and not an afterthought. Bun's websocket handlers hang off the server object rather than the route table — keep one row pointing at them. #7 is rewriting src/routes.ts right now: add a row, do not reorganise the file. This is the pull Sentinel should read line by line.

Waits on #16 (the message format) and #17 (the upgrade rule). Fits Vesper's brief, with Sentinel reviewing.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs