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
26 lines
822 B
JSON
26 lines
822 B
JSON
{
|
|
"name": "station-master",
|
|
"version": "0.8.0.12",
|
|
"private": true,
|
|
"type": "module",
|
|
"description": "Station Master — a railroad operations game",
|
|
"engines": {
|
|
"node": ">=22.18"
|
|
},
|
|
"scripts": {
|
|
"typecheck": "tsc --noEmit",
|
|
"pretest": "tsc --noEmit && node scripts/build-web.ts",
|
|
"test": "node --test $(ls test/*.test.ts test/**/*.test.ts | grep -v '^test/sim.test.ts$')",
|
|
"pretest:sim": "tsc --noEmit",
|
|
"test:sim": "node --test test/sim.test.ts",
|
|
"build:web": "node scripts/build-web.ts",
|
|
"serve:web": "node scripts/build-web.ts && npx --yes http-server dist -p 8080 -c-1",
|
|
"deploy:web": "node scripts/deploy-web.ts",
|
|
"build:cards": "node scripts/build-card-reference.ts"
|
|
},
|
|
"devDependencies": {
|
|
"@types/node": "^26.1.2",
|
|
"typescript": "^7.0.2"
|
|
}
|
|
}
|