v0.8.0.12 — put a player back in their seat after losing their browser storage
Gitea#33. A session token is the only identity the game has, and it lives in exactly one place the player controls: their browser's localStorage, scoped to the origin they joined at. Lose it — a cleared profile, a private window, a different browser — and the seat is unreachable while the game runs on and the session sits intact on disk. Reported from the table: of two humans in one game the host reloaded straight back in, the joiner met an empty lobby. Diagnosed before it was fixed, and two server-side theories of mine were retracted on the evidence: no storage key changed in 0.8.0.11, nothing in the app deletes the secret or name, create and join both call persistSession, that game's sessions.json held both seats, and it resumed with 80 intents replayed. Both players used the same URL, so it was not a second origin either. The fix is a recovery link. An administrator mints a code for a named seat (admin-gated: deciding somebody lost a seat is a judgement no route can make); the player opens the link and the page trades the code for the token over a POST, then strips it from the address bar. The link never carries the token — lobby-and-sessions.md §1 says keep it out of URLs, and a recovery link is exactly what gets pasted into a chat. Single use, 30-minute expiry, held in memory because a restart dropping them is the right failure. server/claims.ts is a pure store, so single use, lazy expiry and one identical answer for unknown/spent/expired codes are tested rather than asserted. The admin game listing gained seatedPlayers — the seats a human holds a token for, read from the session map rather than guessed from player names — so the StartOS action can offer real players instead of bot chairs. No rule changed: `git diff v0.8.0.11..v0.8.0.12 -- src/engine/` is empty, so games in progress resume. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017nnuCv8UodHucFfx3LWEoX
This commit is contained in:
co-authored by
Claude Opus 5
parent
9a9e50b3c6
commit
7c9ef8797d
@@ -19,6 +19,66 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
|
||||
|
||||
---
|
||||
|
||||
## 0.8.0.12 — 2026-09-16
|
||||
|
||||
A player who has lost their browser storage can be put back in their seat (Gitea#33). No rule
|
||||
changed: `git diff v0.8.0.11..v0.8.0.12 -- src/engine/` is empty, so games in progress resume.
|
||||
|
||||
### The failure this fixes, and the four things it was not
|
||||
|
||||
Reported from the table after the 0.8.0.11 update: of two humans in one game, the host reloaded
|
||||
straight back into it and the player who had JOINED found an empty lobby — no join secret, no display
|
||||
name, no game code. Their seat was never lost. `sessions.json` for that game held both seats, and the
|
||||
server logged it resuming with 80 intents replayed.
|
||||
|
||||
Four explanations were ruled out with evidence before any code was written, and two of them were
|
||||
theories of mine that had to be retracted:
|
||||
|
||||
- **Not the update.** `git diff v0.8.0.10..v0.8.0.11 -- src/web/` contains no storage change at all;
|
||||
both tags declare identical `SECRET_KEY` and `NAME_KEY`.
|
||||
- **Not a create-vs-join asymmetry in the client.** `lb-secret` and `lb-name` sit above both doors in
|
||||
`play.html`, so a joiner writes the same three keys a host does.
|
||||
- **Not the server forgetting joiners.** Create calls `persistSession` and so does join; it writes
|
||||
every session for the game. Two-seat session files plainly work.
|
||||
- **Not a second origin.** Both players used the identical URL.
|
||||
|
||||
What is left is the thing §1 has always said: the token lives in one browser's `localStorage`, scoped
|
||||
to the origin. A cleared profile, a private window or a different browser ends the seat while the game
|
||||
runs on without it. Nothing in the client can detect that — origin isolation is the point — and
|
||||
nothing in it can repair it either.
|
||||
|
||||
### A recovery link carries a code, never the token
|
||||
|
||||
`lobby-and-sessions.md` §1: *"Keep it out of URLs so it is not shoulder-surfed or pasted into a
|
||||
chat."* A recovery link is precisely what gets pasted into a chat, so the URL carries a **single-use
|
||||
code that expires in 30 minutes** and the page trades it for the real token over a POST, then strips
|
||||
it from the address bar. A spent code is worth nothing; a token in a chat log is the seat for the rest
|
||||
of the game.
|
||||
|
||||
```
|
||||
POST /api/games/<id>/claim { player } → { code, expiresAt, … } admin secret
|
||||
POST /api/claim { code } → { token, gameId, player, gameCode }
|
||||
```
|
||||
|
||||
**Minting is administrative; spending is not.** Deciding that a particular person has lost a
|
||||
particular seat is a judgement no route can make safely — anyone able to mint their own code could
|
||||
take any chair at the table. Spending needs no secret because the player following the link is the one
|
||||
person in the story who holds none: the code *is* the authorisation, unguessable and one-time, which
|
||||
is the same shape as the token it returns.
|
||||
|
||||
`server/claims.ts` is a pure store — no clock, no sockets, no disk — so its rules are actually tested
|
||||
rather than asserted: single use, lazy expiry, and one identical answer for unknown, spent and expired
|
||||
codes so it cannot be probed. The codes are held in memory on purpose. They are minted on demand and
|
||||
spent within minutes with the administrator present, so a restart dropping them is the right failure;
|
||||
persisting them would put a credential-equivalent on disk to solve a problem measured in seconds.
|
||||
|
||||
### The administrator picks a seat, not a string
|
||||
|
||||
The admin game listing now reports `seatedPlayers` — the seats a HUMAN holds a token for, read from
|
||||
the server's session map rather than guessed by matching "Bot 1" against a display name. That is what
|
||||
lets the StartOS side offer real players to choose from instead of chairs no token was ever issued
|
||||
for.
|
||||
|
||||
## 0.8.0.11 — 2026-09-16
|
||||
|
||||
Fourteen reports from the second multiplayer playtest of v0.8.0.10, the WHISTLE-6945 table. Eleven are
|
||||
|
||||
Reference in New Issue
Block a user