update dashboards over websocket and remove database path from GameplayDatabase output
Build and Push Docker Container / build-and-push (push) Failing after 13m18s
Build and Push Docker Container / build-and-push (push) Failing after 13m18s
This commit is contained in:
+150
-12
@@ -750,6 +750,10 @@
|
||||
let activeGameId = String(initialGameId || "");
|
||||
let gamesWebSocket = null;
|
||||
let gamesWebSocketReconnectTimer = null;
|
||||
let gamesWebSocketServerShuttingDown = false;
|
||||
let hasLoadedReplayOnce = false;
|
||||
let replayRequestSeq = 0;
|
||||
const pendingReplayRequests = new Map();
|
||||
let selectedSnakeId = null;
|
||||
const svgCache = new Map();
|
||||
|
||||
@@ -1373,6 +1377,7 @@
|
||||
}
|
||||
|
||||
function scheduleDashboardGamesWebSocketReconnect() {
|
||||
if (gamesWebSocketServerShuttingDown) return;
|
||||
if (gamesWebSocketReconnectTimer) return;
|
||||
gamesWebSocketReconnectTimer = window.setTimeout(() => {
|
||||
gamesWebSocketReconnectTimer = null;
|
||||
@@ -1380,6 +1385,14 @@
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function rejectPendingReplayRequests(message) {
|
||||
for (const pending of pendingReplayRequests.values()) {
|
||||
window.clearTimeout(pending.timeoutId);
|
||||
pending.reject(new Error(message));
|
||||
}
|
||||
pendingReplayRequests.clear();
|
||||
}
|
||||
|
||||
function applyDashboardGamesUpdate(payload) {
|
||||
const nextSummary = payload && payload.summary && typeof payload.summary === "object"
|
||||
? payload.summary
|
||||
@@ -1407,9 +1420,8 @@
|
||||
clearActiveGame();
|
||||
}
|
||||
|
||||
const payloadGameId = payload && payload.game_id ? String(payload.game_id) : "";
|
||||
if (payloadGameId && payloadGameId === activeGameId) {
|
||||
loadReplay(payloadGameId);
|
||||
if (activeGameId && gameIds.has(activeGameId) && payload && payload.trigger === "game_saved") {
|
||||
loadReplay(activeGameId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1421,6 +1433,10 @@
|
||||
}
|
||||
|
||||
function connectDashboardGamesWebSocket() {
|
||||
if (gamesWebSocketServerShuttingDown) return;
|
||||
if (gamesWebSocket && (gamesWebSocket.readyState === WebSocket.OPEN || gamesWebSocket.readyState === WebSocket.CONNECTING)) {
|
||||
return;
|
||||
}
|
||||
const wsUrl = dashboardGamesWebSocketUrl();
|
||||
try {
|
||||
gamesWebSocket = new WebSocket(wsUrl);
|
||||
@@ -1436,13 +1452,47 @@
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!payload || payload.type !== "dashboard_games_update") return;
|
||||
applyDashboardGamesUpdate(payload);
|
||||
if (!payload || !payload.type) return;
|
||||
|
||||
if (payload.type === "dashboard_ws_shutdown") {
|
||||
gamesWebSocketServerShuttingDown = true;
|
||||
if (gamesWebSocketReconnectTimer) {
|
||||
clearTimeout(gamesWebSocketReconnectTimer);
|
||||
gamesWebSocketReconnectTimer = null;
|
||||
}
|
||||
rejectPendingReplayRequests("Server shutting down");
|
||||
if (gamesWebSocket) {
|
||||
gamesWebSocket.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === "dashboard_game_replay") {
|
||||
const requestId = String(payload.request_id || "");
|
||||
if (!requestId) return;
|
||||
const pending = pendingReplayRequests.get(requestId);
|
||||
if (!pending) return;
|
||||
pendingReplayRequests.delete(requestId);
|
||||
window.clearTimeout(pending.timeoutId);
|
||||
if (payload.error) {
|
||||
pending.reject(new Error(String(payload.error)));
|
||||
return;
|
||||
}
|
||||
pending.resolve(payload.replay || null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === "dashboard_games_update") {
|
||||
applyDashboardGamesUpdate(payload);
|
||||
}
|
||||
});
|
||||
|
||||
gamesWebSocket.addEventListener("close", () => {
|
||||
gamesWebSocket = null;
|
||||
scheduleDashboardGamesWebSocketReconnect();
|
||||
rejectPendingReplayRequests("Dashboard websocket disconnected");
|
||||
if (!gamesWebSocketServerShuttingDown) {
|
||||
scheduleDashboardGamesWebSocketReconnect();
|
||||
}
|
||||
});
|
||||
|
||||
gamesWebSocket.addEventListener("error", () => {
|
||||
@@ -1452,6 +1502,79 @@
|
||||
});
|
||||
}
|
||||
|
||||
function waitForDashboardGamesWebSocketOpen(timeoutMs = 4000) {
|
||||
if (gamesWebSocketServerShuttingDown) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if (!gamesWebSocket || gamesWebSocket.readyState === WebSocket.CLOSED) {
|
||||
connectDashboardGamesWebSocket();
|
||||
}
|
||||
|
||||
if (gamesWebSocket && gamesWebSocket.readyState === WebSocket.OPEN) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
if (!gamesWebSocket) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const socketRef = gamesWebSocket;
|
||||
let settled = false;
|
||||
const cleanup = () => {
|
||||
if (!socketRef) return;
|
||||
socketRef.removeEventListener("open", onOpen);
|
||||
socketRef.removeEventListener("close", onClose);
|
||||
socketRef.removeEventListener("error", onError);
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
const finish = (value) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
cleanup();
|
||||
resolve(value);
|
||||
};
|
||||
const onOpen = () => finish(true);
|
||||
const onClose = () => finish(false);
|
||||
const onError = () => finish(false);
|
||||
const timeoutId = window.setTimeout(() => finish(false), timeoutMs);
|
||||
|
||||
socketRef.addEventListener("open", onOpen);
|
||||
socketRef.addEventListener("close", onClose);
|
||||
socketRef.addEventListener("error", onError);
|
||||
});
|
||||
}
|
||||
|
||||
async function requestReplayOverWebSocket(gameId) {
|
||||
const isOpen = await waitForDashboardGamesWebSocketOpen();
|
||||
if (!isOpen || !gamesWebSocket || gamesWebSocket.readyState !== WebSocket.OPEN) {
|
||||
throw new Error("Dashboard websocket unavailable");
|
||||
}
|
||||
|
||||
const requestId = `replay-${Date.now()}-${replayRequestSeq++}`;
|
||||
return await new Promise((resolve, reject) => {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
pendingReplayRequests.delete(requestId);
|
||||
reject(new Error(`Replay websocket timeout for ${gameId}`));
|
||||
}, 4000);
|
||||
|
||||
pendingReplayRequests.set(requestId, { resolve, reject, timeoutId });
|
||||
try {
|
||||
gamesWebSocket.send(JSON.stringify({
|
||||
type: "dashboard_game_replay_request",
|
||||
request_id: requestId,
|
||||
game_id: gameId,
|
||||
}));
|
||||
} catch (error) {
|
||||
window.clearTimeout(timeoutId);
|
||||
pendingReplayRequests.delete(requestId);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearActiveGame() {
|
||||
for (const row of gamesBodyEl.querySelectorAll("tr")) {
|
||||
row.classList.remove("active");
|
||||
@@ -1609,12 +1732,24 @@
|
||||
}
|
||||
|
||||
async function loadReplay(gameId) {
|
||||
const response = await fetch(`/dashboard/game/${gameId}`);
|
||||
if (!response.ok) {
|
||||
renderThinking({ my_move: "-", my_thinking: { error: `Replay load failed for ${gameId}` } });
|
||||
return;
|
||||
let nextReplay = null;
|
||||
try {
|
||||
nextReplay = await requestReplayOverWebSocket(gameId);
|
||||
} catch {
|
||||
if (!hasLoadedReplayOnce) {
|
||||
renderThinking({ my_move: "-", my_thinking: { error: `Replay websocket unavailable for ${gameId}` } });
|
||||
return;
|
||||
}
|
||||
const response = await fetch(`/dashboard/game/${gameId}`);
|
||||
if (!response.ok) {
|
||||
renderThinking({ my_move: "-", my_thinking: { error: `Replay load failed for ${gameId}` } });
|
||||
return;
|
||||
}
|
||||
nextReplay = await response.json();
|
||||
}
|
||||
replay = await response.json();
|
||||
|
||||
replay = nextReplay;
|
||||
hasLoadedReplayOnce = true;
|
||||
activeGameId = String(gameId || "");
|
||||
await preloadReplaySvgs();
|
||||
turnIndex = 0;
|
||||
@@ -1749,8 +1884,9 @@
|
||||
async function boot() {
|
||||
renderThinking(null);
|
||||
loadSummary();
|
||||
await loadGames();
|
||||
connectDashboardGamesWebSocket();
|
||||
await waitForDashboardGamesWebSocketOpen(2500);
|
||||
await loadGames();
|
||||
syncMonoOffset();
|
||||
}
|
||||
|
||||
@@ -1780,10 +1916,12 @@
|
||||
});
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
gamesWebSocketServerShuttingDown = true;
|
||||
if (gamesWebSocketReconnectTimer) {
|
||||
clearTimeout(gamesWebSocketReconnectTimer);
|
||||
gamesWebSocketReconnectTimer = null;
|
||||
}
|
||||
rejectPendingReplayRequests("Dashboard unloading");
|
||||
if (gamesWebSocket) {
|
||||
gamesWebSocket.close();
|
||||
gamesWebSocket = null;
|
||||
|
||||
Reference in New Issue
Block a user