2 Commits
Author SHA1 Message Date
FrankandClaude Sonnet 5 e76d0b0f07 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>
2026-07-04 16:26:47 +02:00
FrankandClaude Sonnet 5 b7693bd9d0 Remove quotes around MERCURE_CORS_ALLOWED_ORIGINS in docker/.env.dist
docker/.env feeds MERCURE_EXTRA_DIRECTIVES, a Caddyfile-style config
block, via Compose variable substitution. Quoting a space-separated
value there makes Caddy treat both origins as one single malformed
token rather than two arguments, which crash-loops the Mercure
container on startup. Compose's .env parsing for values with spaces
doesn't require quotes (unlike Symfony's Dotenv), so drop them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 15:43:03 +02:00
3 changed files with 26 additions and 11 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ MYSQL_ROOT_PASSWORD=CHANGEME_MYSQL_ROOT_PASSWORD
MERCURE_URL=http://mercure/.well-known/mercure
MERCURE_PUBLIC_URL=https://mercure.escapepage.com/.well-known/mercure
MERCURE_JWT_SECRET=CHANGEME_MERCURE_JWT_SECRET
MERCURE_CORS_ALLOWED_ORIGINS="https://www.escapepage.com https://escapepage.com"
MERCURE_CORS_ALLOWED_ORIGINS=https://www.escapepage.com https://escapepage.com
MERCURE_TOPIC_BASE=https://escapepage.com
# Recaptcha
+10 -5
View File
@@ -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;
+15 -5
View File
@@ -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();
}
}
</script>