From ce43e6f6aabcf417df24a2a98d9f7ea1ea13d55e Mon Sep 17 00:00:00 2001 From: PM Date: Mon, 4 May 2026 15:32:03 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20per-socket=20state=20with=20drawer-only?= =?UTF-8?q?=20wordChoices=20to=20prevent=20race=20on=20lobby=E2=86=92play?= =?UTF-8?q?=20nav?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/game-state.js | 16 +++++++++++++++- server/socket-handlers.js | 10 +++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/server/game-state.js b/server/game-state.js index 7577e5e..49b0431 100644 --- a/server/game-state.js +++ b/server/game-state.js @@ -59,7 +59,8 @@ function makeRoom({ code, hostId, mode, settings }) { }; } -function publicRoomState(room) { +function publicRoomState(room, viewerId) { + const isViewerDrawer = !!(viewerId && room.skribbl && room.skribbl.drawerId === viewerId); return { code: room.code, hostId: room.hostId, @@ -84,6 +85,19 @@ function publicRoomState(room) { phase: room.skribbl.phase, // 'choosing' | 'drawing' | 'between' endsAt: room.skribbl.endsAt, solvedIds: [...(room.skribbl.solvedIds || [])], + // Drawer-only: surface current word choices in the snapshot so a + // late-mounted play page (after lobby→/play navigation) can render + // the pick modal even if the original skribbl:wordChoices event + // was emitted before the listener was attached. + wordChoices: + isViewerDrawer && room.skribbl.phase === "choosing" + ? room.skribbl.wordChoices + : null, + // Drawer-only: reveal the actual word so they know what to draw. + word: + isViewerDrawer && room.skribbl.phase === "drawing" + ? room.skribbl.word + : null, } : null, gartic: room.gartic diff --git a/server/socket-handlers.js b/server/socket-handlers.js index 0e63c76..dee6d0b 100644 --- a/server/socket-handlers.js +++ b/server/socket-handlers.js @@ -10,7 +10,15 @@ function escape(text) { } function broadcastState(io, room) { - io.to(room.code).emit("room:state", G.publicRoomState(room)); + // Per-socket emission so we can include drawer-only data + // (wordChoices during choosing, secret word during drawing) in the + // snapshot of the right viewer without leaking it to others. + for (const player of room.players) { + if (!player.socketId) continue; + const sock = io.sockets.sockets.get(player.socketId); + if (!sock) continue; + sock.emit("room:state", G.publicRoomState(room, player.id)); + } } function chatSystem(io, room, text) {