From e76d0b0f072785f1085804d9a5bf4d0d2fd25de3 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 4 Jul 2026 16:26:47 +0200 Subject: [PATCH] Fix Mercure reload spam on waiting page in Chrome When the last player clicked ready, toggleReady() published a redundant 'player_ready' event on top of checkAllPlayersReady()'s 'all_ready', and the client reloaded on every message with no guard and without closing the EventSource. Chrome kept the old page's script (and its EventSource) alive across the overlapping reload calls, causing repeated reconnects to the Mercure hub; Firefox apparently tore the page down fast enough to mask it. Skip the redundant publish server-side, and make the client reload idempotent by tracking whether it already fired and closing the EventSource before reloading. Co-Authored-By: Claude Sonnet 5 --- src/Game/Service/GameDashboardService.php | 15 ++++++++++----- templates/game/waiting.html.twig | 20 +++++++++++++++----- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Game/Service/GameDashboardService.php b/src/Game/Service/GameDashboardService.php index 76de3de..6907e6a 100644 --- a/src/Game/Service/GameDashboardService.php +++ b/src/Game/Service/GameDashboardService.php @@ -326,11 +326,16 @@ final class GameDashboardService $this->checkAllPlayersReady($session); $this->entityManager->flush(); - try { - $topic = '/game/hub/' . $session->getId(); - $this->hub->publish(new Update($topic, json_encode(['type' => 'player_ready', 'player' => $player->getScreen(), 'ready' => !$setting]))); - } catch (\Exception $e) { - // Mercure might be down, but we don't want to crash the game + // If this toggle just made everyone ready, checkAllPlayersReady() already + // transitioned the session out of READY and published 'all_ready' — + // don't also publish a redundant 'player_ready'. + if ($session->getStatus() === SessionStatus::READY) { + try { + $topic = '/game/hub/' . $session->getId(); + $this->hub->publish(new Update($topic, json_encode(['type' => 'player_ready', 'player' => $player->getScreen(), 'ready' => !$setting]))); + } catch (\Exception $e) { + // Mercure might be down, but we don't want to crash the game + } } return true; diff --git a/templates/game/waiting.html.twig b/templates/game/waiting.html.twig index b6b184c..6fc6a6b 100644 --- a/templates/game/waiting.html.twig +++ b/templates/game/waiting.html.twig @@ -91,6 +91,18 @@ const topic = config.dataset.topic; const readyAt = config.dataset.readyAt; + let reloading = false; + const reloadOnce = (eventSource) => { + if (reloading) { + return; + } + reloading = true; + if (eventSource) { + eventSource.close(); + } + window.location.reload(); + }; + if (publicUrl && topic) { const url = new URL(publicUrl); url.searchParams.append('topic', topic); @@ -99,7 +111,7 @@ eventSource.onmessage = event => { const data = JSON.parse(event.data); if (data.type === 'all_ready' || data.type === 'player_ready') { - window.location.reload(); + reloadOnce(eventSource); } }; } @@ -113,12 +125,10 @@ const timeLeft = timeoutMs - timeElapsed; if (timeLeft > 0) { - setTimeout(() => { - window.location.reload(); - }, timeLeft); + setTimeout(() => reloadOnce(), timeLeft); } else { // Already timed out, reload to sync with server - window.location.reload(); + reloadOnce(); } }