fix(multi): prevent player kick on slow load and background tab throttling
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user