A player who loses their browser storage is locked out of their seat, with no way back #33

Open
opened 2026-09-16 21:22:33 +00:00 by Jesse.Markowitz · 0 comments
Owner

Symptom

A seated player reloads the play page and finds the lobby with join secret, display name and game code all blank — they are a stranger to their own table. Seen 2026-09-16 after the 0.8.0.11 update: of two humans in WHISTLE-6945, the host came straight back into the game and the joining player did not.

What it is NOT

Investigated at length; all of these were ruled out with evidence:

  • Not the update. git diff v0.8.0.10..v0.8.0.11 -- src/web/ contains no localStorage or key changes; both tags declare identical SECRET_KEY/NAME_KEY.
  • Not a create-vs-join asymmetry. Create calls persistSession (http.ts:425), join calls it (http.ts:454), and it writes every session for the game. The lb-secret and lb-name inputs sit above both panels in play.html, so a joiner writes the same three keys a host does.
  • Not the server losing anything. That game's sessions.json holds both seats, and the server logged it resuming cleanly (80 intents replayed).
  • Not a different origin. Both players used the identical URL.
  • Nothing in the app deletes the secret or name. No localStorage.clear(), no service worker; abandonRemote() removes only one game record, and it never fired here because the game was never refused.

Cause

The seat token exists in exactly one place a player controls: localStorage under station-master.remote.v1, per-origin. Lose that — a different browser or profile, a private window, "clear site data on close", storage eviction — and the token is gone. The token is the identity (lobby-and-sessions.md §1), so there is no second factor to fall back on.

Why it matters

The failure is silent and total. The player sees an ordinary empty lobby with no hint that they hold a seat, the page cannot detect the situation (that is what origin isolation means), and the only recovery today is an administrator reading sessions.json off the data volume and the player pasting a token into localStorage via a devtools console. At a table of four this will recur.

Proposed

  1. A supported restore path. An admin-run action that produces a link the player opens to be put back in their seat, instead of hand-editing browser storage. Worth deciding whether the link carries the raw token or a short-lived single-use code the client exchanges — a durable credential in a URL lands in history, logs and chat apps, which is exactly why the invite link deliberately omits the join secret.
  2. Say it out loud in the lobby. When there is no stored secret and no known games, note that a seat is tied to the browser it was joined from, so a returning player understands what happened rather than assuming the game vanished.
## Symptom A seated player reloads the play page and finds the lobby with **join secret, display name and game code all blank** — they are a stranger to their own table. Seen 2026-09-16 after the 0.8.0.11 update: of two humans in WHISTLE-6945, the host came straight back into the game and the joining player did not. ## What it is NOT Investigated at length; all of these were ruled out with evidence: - **Not the update.** `git diff v0.8.0.10..v0.8.0.11 -- src/web/` contains no `localStorage` or key changes; both tags declare identical `SECRET_KEY`/`NAME_KEY`. - **Not a create-vs-join asymmetry.** Create calls `persistSession` (http.ts:425), join calls it (http.ts:454), and it writes every session for the game. The `lb-secret` and `lb-name` inputs sit above both panels in `play.html`, so a joiner writes the same three keys a host does. - **Not the server losing anything.** That game's `sessions.json` holds both seats, and the server logged it resuming cleanly (80 intents replayed). - **Not a different origin.** Both players used the identical URL. - **Nothing in the app deletes the secret or name.** No `localStorage.clear()`, no service worker; `abandonRemote()` removes only one game record, and it never fired here because the game was never refused. ## Cause The seat token exists in exactly one place a player controls: `localStorage` under `station-master.remote.v1`, per-origin. Lose that — a different browser or profile, a private window, "clear site data on close", storage eviction — and the token is gone. The token **is** the identity (`lobby-and-sessions.md` §1), so there is no second factor to fall back on. ## Why it matters The failure is silent and total. The player sees an ordinary empty lobby with no hint that they hold a seat, the page cannot detect the situation (that is what origin isolation means), and the only recovery today is an administrator reading `sessions.json` off the data volume and the player pasting a token into `localStorage` via a devtools console. At a table of four this will recur. ## Proposed 1. **A supported restore path.** An admin-run action that produces a link the player opens to be put back in their seat, instead of hand-editing browser storage. Worth deciding whether the link carries the raw token or a short-lived single-use code the client exchanges — a durable credential in a URL lands in history, logs and chat apps, which is exactly why the invite link deliberately omits the join secret. 2. **Say it out loud in the lobby.** When there is no stored secret and no known games, note that a seat is tied to the browser it was joined from, so a returning player understands what happened rather than assuming the game vanished.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Jesse.Markowitz/station-master#33