From 08f67ee4f52b224f2718f309965681df96f09378 Mon Sep 17 00:00:00 2001 From: jessy-david-dev Date: Fri, 17 Apr 2026 15:35:13 +0200 Subject: [PATCH] fix(multi): prevent host pruning and increase timeout to 90s - Exempt host from inactivity pruning to prevent room corruption - Increase player timeout from 45s to 90s for slower connections - Reduce heartbeat interval from 20s to 15s for better coverage - Refresh all players' lastSeen on nextRound/resetGame to avoid immediate pruning --- app/api/rooms/[code]/route.ts | 10 +++++++--- lib/badges.ts | 20 ++++++++++---------- lib/useGameEffects.ts | 3 ++- lib/useMultiGame.ts | 2 +- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/api/rooms/[code]/route.ts b/app/api/rooms/[code]/route.ts index 0a2905e..0adc1f3 100644 --- a/app/api/rooms/[code]/route.ts +++ b/app/api/rooms/[code]/route.ts @@ -47,12 +47,12 @@ function generatePlayerId(): string { return crypto.randomUUID(); } -// Timeout joueur inactif : 45s (heartbeat toutes les 2s, on laisse large pour les tabs en arrière-plan) -const PLAYER_TIMEOUT_MS = 45_000; +// Timeout joueur inactif : 90s (heartbeat toutes les 15s, x6 de marge pour les connexions lentes) +const PLAYER_TIMEOUT_MS = 90_000; function prunePlayers(players: Player[]): Player[] { const now = Date.now(); - return players.filter((p) => now - p.lastSeen < PLAYER_TIMEOUT_MS); + return players.filter((p) => p.isHost || now - p.lastSeen < PLAYER_TIMEOUT_MS); } // Calcule les points d'un joueur selon ses clics et son temps @@ -455,11 +455,13 @@ export async function PATCH( room.roundWinner = null; room.countdownStart = null; room.roundStart = null; + const now = Date.now(); for (const p of room.players) { p.hasWon = false; p.hasSurrendered = false; p.currentArticle = ""; p.wonAt = null; + p.lastSeen = now; // rafraîchit tous les joueurs pour éviter le pruning immédiat } await saveRoom(room); return Response.json({ room }); @@ -479,12 +481,14 @@ export async function PATCH( room.roundWinner = null; room.countdownStart = null; room.roundStart = null; + const nowReset = Date.now(); for (const p of room.players) { p.score = 0; p.hasWon = false; p.hasSurrendered = false; p.currentArticle = ""; p.wonAt = null; + p.lastSeen = nowReset; // rafraîchit tous les joueurs pour éviter le pruning immédiat } await saveRoom(room); return Response.json({ room }); diff --git a/lib/badges.ts b/lib/badges.ts index ce65fdc..b24df9c 100644 --- a/lib/badges.ts +++ b/lib/badges.ts @@ -1,16 +1,16 @@ export type Badge = { name: string; file: string; threshold: number }; export const BADGES: Badge[] = [ - { name: "Omniscient", file: "omniscient", threshold: 10000 }, - { name: "Transcendant", file: "transcendant", threshold: 5000 }, - { name: "Mythique", file: "mythique", threshold: 2500 }, - { name: "Légende", file: "légende", threshold: 1000 }, - { name: "Maître", file: "maître", threshold: 500 }, - { name: "Expert", file: "expert", threshold: 150 }, - { name: "Adepte", file: "adepte", threshold: 50 }, - { name: "Initié", file: "initié", threshold: 20 }, - { name: "Apprenti", file: "apprenti", threshold: 5 }, - { name: "Novice", file: "novice", threshold: 0 }, + { name: "Omniscient", file: "omniscient", threshold: 10000 }, + { name: "Transcendant", file: "transcendant", threshold: 5000 }, + { name: "Mythique", file: "mythique", threshold: 2500 }, + { name: "Légende", file: "légende", threshold: 1000 }, + { name: "Maître", file: "maître", threshold: 500 }, + { name: "Expert", file: "expert", threshold: 150 }, + { name: "Adepte", file: "adepte", threshold: 50 }, + { name: "Initié", file: "initié", threshold: 20 }, + { name: "Apprenti", file: "apprenti", threshold: 5 }, + { name: "Novice", file: "novice", threshold: 0 }, ]; export function getBadge(wins: number): Badge { diff --git a/lib/useGameEffects.ts b/lib/useGameEffects.ts index b604b6d..586f7b3 100644 --- a/lib/useGameEffects.ts +++ b/lib/useGameEffects.ts @@ -61,7 +61,8 @@ export function useGameEffects({ multi.restore(saved.multiRoomCode, saved.multiPlayerId).then((ok) => { // si une nouvelle room a été rejointe pendant le restore, on laisse const current = loadSession(); - if (current?.multiRoomCode && current.multiRoomCode !== savedCode) return; + if (current?.multiRoomCode && current.multiRoomCode !== savedCode) + return; if (ok) setScreen("lobby"); else { clearSession(); diff --git a/lib/useMultiGame.ts b/lib/useMultiGame.ts index cde11ce..6427139 100644 --- a/lib/useMultiGame.ts +++ b/lib/useMultiGame.ts @@ -116,7 +116,7 @@ export function useMultiGame() { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "heartbeat", playerId: ssePidRef.current }), }).catch(() => {}); - }, 20_000); + }, 15_000); return () => clearInterval(id); }, []);