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 <noreply@anthropic.com>
This commit is contained in:
Frank
2026-07-04 16:26:47 +02:00
co-authored by Claude Sonnet 5
parent b7693bd9d0
commit e76d0b0f07
2 changed files with 25 additions and 10 deletions
+10 -5
View File
@@ -326,11 +326,16 @@ final class GameDashboardService
$this->checkAllPlayersReady($session); $this->checkAllPlayersReady($session);
$this->entityManager->flush(); $this->entityManager->flush();
try { // If this toggle just made everyone ready, checkAllPlayersReady() already
$topic = '/game/hub/' . $session->getId(); // transitioned the session out of READY and published 'all_ready' —
$this->hub->publish(new Update($topic, json_encode(['type' => 'player_ready', 'player' => $player->getScreen(), 'ready' => !$setting]))); // don't also publish a redundant 'player_ready'.
} catch (\Exception $e) { if ($session->getStatus() === SessionStatus::READY) {
// Mercure might be down, but we don't want to crash the game 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; return true;
+15 -5
View File
@@ -91,6 +91,18 @@
const topic = config.dataset.topic; const topic = config.dataset.topic;
const readyAt = config.dataset.readyAt; 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) { if (publicUrl && topic) {
const url = new URL(publicUrl); const url = new URL(publicUrl);
url.searchParams.append('topic', topic); url.searchParams.append('topic', topic);
@@ -99,7 +111,7 @@
eventSource.onmessage = event => { eventSource.onmessage = event => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.type === 'all_ready' || data.type === 'player_ready') { if (data.type === 'all_ready' || data.type === 'player_ready') {
window.location.reload(); reloadOnce(eventSource);
} }
}; };
} }
@@ -113,12 +125,10 @@
const timeLeft = timeoutMs - timeElapsed; const timeLeft = timeoutMs - timeElapsed;
if (timeLeft > 0) { if (timeLeft > 0) {
setTimeout(() => { setTimeout(() => reloadOnce(), timeLeft);
window.location.reload();
}, timeLeft);
} else { } else {
// Already timed out, reload to sync with server // Already timed out, reload to sync with server
window.location.reload(); reloadOnce();
} }
} }
</script> </script>