fix(frontend): scope player identity to a tab, not the browser
Two tabs of the same browser share localStorage, so both got the same persisted playerId and stepped on each other: matchmaking's waiting queue is keyed by playerId and dedupes a second queue:join as a no-op, so two tabs searching never both count as waiting; and re-identifying with the same playerId hijacks the ws registry's socket binding, tearing down the first tab's connection (surfacing as room:join replying 'room already has two players' once its creator's socket is dead). Move playerId/name from localStorage to sessionStorage: still stable across reloads within a tab (reconnect-by-refresh keeps the same identity), but each new tab gets a fresh UUID, so two tabs of one browser behave like two separate players. Not IP-related; no such restriction existed.
This commit is contained in:
parent
16d36328ec
commit
42d1732816
2 changed files with 15 additions and 10 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue