fix(multi): prevent player kick on slow load and background tab throttling

This commit is contained in:
jessy-david-dev
2026-04-11 22:12:39 +02:00
parent 6be8db9145
commit 1153f574a4
2 changed files with 27 additions and 2 deletions
+2 -2
View File
@@ -44,8 +44,8 @@ function generatePlayerId(): string {
return crypto.randomUUID(); return crypto.randomUUID();
} }
// Timeout joueur inactif : 15s // Timeout joueur inactif : 45s (heartbeat toutes les 2s, on laisse large pour les tabs en arrière-plan)
const PLAYER_TIMEOUT_MS = 15_000; const PLAYER_TIMEOUT_MS = 45_000;
function prunePlayers(players: Player[]): Player[] { function prunePlayers(players: Player[]): Player[] {
const now = Date.now(); const now = Date.now();
+25
View File
@@ -74,11 +74,29 @@ export function useMultiGame() {
} catch { /* ignore */ } } catch { /* ignore */ }
} }
const pollCodeRef = useRef<string | null>(null);
const pollPidRef = useRef<string | null>(null);
function startPolling(code: string, pid: string) { function startPolling(code: string, pid: string) {
stopPolling(); stopPolling();
pollCodeRef.current = code;
pollPidRef.current = pid;
pollRef.current = setInterval(() => poll(code, pid), POLL_INTERVAL); pollRef.current = setInterval(() => poll(code, pid), POLL_INTERVAL);
} }
// Relance le polling quand le tab redevient visible (les setInterval sont throttlés en arrière-plan)
useEffect(() => {
function onVisible() {
if (document.visibilityState === "visible" && pollCodeRef.current && pollPidRef.current) {
poll(pollCodeRef.current, pollPidRef.current);
startPolling(pollCodeRef.current, pollPidRef.current);
}
}
document.addEventListener("visibilitychange", onVisible);
return () => document.removeEventListener("visibilitychange", onVisible);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Phase sync // Phase sync
useEffect(() => { useEffect(() => {
@@ -131,6 +149,13 @@ export function useMultiGame() {
clicksRef.current += 1; setClicksDisplay(clicksRef.current); clicksRef.current += 1; setClicksDisplay(clicksRef.current);
if (!timerStartedRef.current) { timer.start(); timerStartedRef.current = true; } if (!timerStartedRef.current) { timer.start(); timerStartedRef.current = true; }
// Heartbeat optimiste pour éviter le kick pendant le chargement de l'article
fetch(`/api/rooms/${room.code}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "heartbeat", playerId }),
}).catch(() => {});
const canonical = await loadArticle(t); const canonical = await loadArticle(t);
if (!canonical) return; if (!canonical) return;