diff --git a/backend/src/PROTOCOL.md b/backend/src/PROTOCOL.md index 53e0384..9753874 100644 --- a/backend/src/PROTOCOL.md +++ b/backend/src/PROTOCOL.md @@ -25,8 +25,10 @@ message-specific fields — no envelope wrapper, no request/response ids. There is no account system and none is planned — adding auth is out of scope for multiplayer entirely. Instead, the client generates a -`playerId` (a `crypto.randomUUID()` v4 string) the first time it loads, -persists it in `localStorage`, and reuses it on every future visit. The +`playerId` (a `crypto.randomUUID()` v4 string) the first time a tab +loads, persists it in `sessionStorage`, and reuses it for the lifetime +of that tab (a reload keeps the same identity; a new tab gets a new +one, so two tabs of the same browser act as two distinct players). The player also picks/keeps a display `name` (also persisted client-side, no server-side uniqueness check). The very first message a client sends after the socket opens registers both with the server: diff --git a/frontend/src/net/identity.ts b/frontend/src/net/identity.ts index d8eb81c..c91b946 100644 --- a/frontend/src/net/identity.ts +++ b/frontend/src/net/identity.ts @@ -1,18 +1,21 @@ -// Per-browser player identity. There is no account system (see +// Per-tab player identity. There is no account system (see // backend/src/PROTOCOL.md's "Identity" section) — just a stable UUID // generated once and reused, plus a freely editable display name. Both -// are persisted in localStorage so they survive reloads and reconnects. +// are persisted in sessionStorage so they survive reloads/reconnects +// within a tab but don't leak across tabs — two tabs of the same +// browser (e.g. testing matchmaking/private rooms locally) are two +// distinct players, the same as two separate browsers would be. const PLAYER_ID_KEY = 'themeGuess.playerId'; const PLAYER_NAME_KEY = 'themeGuess.playerName'; /** Returns the persisted `playerId`, generating and storing a new - * `crypto.randomUUID()` the first time this browser is seen. */ + * `crypto.randomUUID()` the first time this tab is seen. */ export function getOrCreatePlayerId(): string { - const existing = localStorage.getItem(PLAYER_ID_KEY); + const existing = sessionStorage.getItem(PLAYER_ID_KEY); if (existing) return existing; const created = crypto.randomUUID(); - localStorage.setItem(PLAYER_ID_KEY, created); + sessionStorage.setItem(PLAYER_ID_KEY, created); return created; } @@ -20,14 +23,14 @@ export function getOrCreatePlayerId(): string { * a generated default derived from the player's id so `identify` never * has to send an empty `name`. */ export function getPlayerName(): string { - const existing = localStorage.getItem(PLAYER_NAME_KEY); + const existing = sessionStorage.getItem(PLAYER_NAME_KEY); if (existing) return existing; const fallback = `Player${getOrCreatePlayerId().slice(0, 4)}`; - localStorage.setItem(PLAYER_NAME_KEY, fallback); + sessionStorage.setItem(PLAYER_NAME_KEY, fallback); return fallback; } /** Overwrites the persisted display name. */ export function setPlayerName(name: string): void { - localStorage.setItem(PLAYER_NAME_KEY, name); + sessionStorage.setItem(PLAYER_NAME_KEY, name); }