diff --git a/public/js/main.js b/public/js/main.js index f1ba194..c3a0bf8 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -19,7 +19,11 @@ function showToast(msg) { function handleConnected(msg) { state.playerId = msg.playerId; if (msg.sessionId) { - cacheSessionId(msg.sessionId); + // Don't clobber an existing session on reconnect; only set if none exists yet. + const existing = localStorage.getItem('sessionId'); + if (!existing) { + cacheSessionId(msg.sessionId); + } } const stored = localStorage.getItem('playerName'); if (stored) { diff --git a/src/server/game.js b/src/server/game.js index cf44918..09c38d7 100644 --- a/src/server/game.js +++ b/src/server/game.js @@ -20,11 +20,24 @@ function drawNextTrack(room) { export function setupWebSocket(server) { const wss = new WebSocketServer({ server }); wss.on('connection', (ws) => { - const id = uuidv4(); - const sessionId = uuidv4(); - let player = { id, sessionId, name: `Player-${id.slice(0, 4)}`, ws, connected: true, roomId: null }; + // Create a tentative player identity, but don't immediately commit or send it. + const newId = uuidv4(); + const newSessionId = uuidv4(); + let player = { id: newId, sessionId: newSessionId, name: `Player-${newId.slice(0, 4)}`, ws, connected: true, roomId: null }; const send = (type, payload) => { try { ws.send(JSON.stringify({ type, ...payload })); } catch {} }; - send('connected', { playerId: id, sessionId }); + + // To avoid overwriting an existing session on reconnect, delay the initial + // welcome until we see if the client sends a resume message. + let helloSent = false; + const sendHello = (p) => { + if (helloSent) return; + helloSent = true; + send('connected', { playerId: p.id, sessionId: p.sessionId }); + }; + // Fallback: if no resume arrives shortly, treat as a fresh connection. + const helloTimer = setTimeout(() => { + sendHello(player); + }, 400); function isParticipant(room, pid) { if (!room) return false; @@ -39,6 +52,7 @@ export function setupWebSocket(server) { // Allow client to resume by session token if (msg.type === 'resume') { + clearTimeout(helloTimer); const reqSession = String(msg.sessionId || ''); if (!reqSession) { send('resume_result', { ok: false, reason: 'no_session' }); return; } let found = null; let foundRoom = null; @@ -61,7 +75,10 @@ export function setupWebSocket(server) { // Notify room broadcast(foundRoom, 'room_update', { room: roomSummary(foundRoom) }); } - send('resume_result', { ok: true, playerId: found.id, roomId: foundRoom?.id }); + // Send resume result and an explicit connected that preserves their original session. + send('resume_result', { ok: true, playerId: found.id, roomId: foundRoom?.id }); + helloSent = true; + send('connected', { playerId: found.id, sessionId: found.sessionId }); return; } @@ -109,6 +126,13 @@ export function setupWebSocket(server) { if (msg.type === 'create_room') { const room = createRoom(msg.name, player); player.roomId = room.id; broadcast(room, 'room_update', { room: roomSummary(room) }); return; } if (msg.type === 'join_room') { const code = String(msg.code || '').toUpperCase(); const room = rooms.get(code); if (!room) return send('error', { message: 'Room not found' }); + // If there's an existing player in this room with the same session, merge to avoid duplicates. + let existing = null; + for (const p of room.players.values()) { if (p.sessionId && p.sessionId === player.sessionId && p.id !== player.id) { existing = p; break; } } + if (existing) { + try { if (existing.ws && existing.ws !== ws) { try { existing.ws.terminate(); } catch {} } } catch {} + existing.ws = ws; existing.connected = true; existing.roomId = room.id; player = existing; + } room.players.set(player.id, player); player.roomId = room.id; if (!room.state.ready) room.state.ready = {}; if (room.state.ready[player.id] == null) room.state.ready[player.id] = false; const inProgress = room.state.status === 'playing' || room.state.status === 'ended'; const wasParticipant = isParticipant(room, player.id); @@ -161,6 +185,7 @@ export function setupWebSocket(server) { }); ws.on('close', () => { + clearTimeout(helloTimer); // Mark player disconnected but keep them in the room for resume try { if (player) { @@ -172,7 +197,5 @@ export function setupWebSocket(server) { } } catch {} }); - - ws.on('close', () => { player.connected = false; if (player.roomId && rooms.has(player.roomId)) { const room = rooms.get(player.roomId); broadcast(room, 'room_update', { room: roomSummary(room) }); } }); }); }