fix: restore lobby state on refresh, handle game.status heartbeat
- Extract maxPlayers from game object in #applyGameAdded so the meter works immediately when a game is added - Read playerName field in lobby.player-joined (matches API payload) - Handle game.status 20s heartbeat to keep overlay in sync - Restore in-progress game on page refresh using status-live endpoint for full shard state including player names Made-with: Cursor
This commit is contained in:
@@ -217,6 +217,15 @@ export class OverlayManager {
|
||||
this.#transitionTo('idle');
|
||||
break;
|
||||
|
||||
case 'game.status':
|
||||
this.#applyGameStatus(d);
|
||||
if (this.#state === 'idle' && d.gameState === 'Lobby') {
|
||||
this.#transitionTo('lobby');
|
||||
} else {
|
||||
this.#broadcastUpdate();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'player-count.updated':
|
||||
this.#applyPlayerCountUpdated(d);
|
||||
this.#broadcastUpdate();
|
||||
@@ -286,6 +295,8 @@ export class OverlayManager {
|
||||
const code =
|
||||
game.room_code ?? game.roomCode ?? game.code;
|
||||
if (code != null) this.#context.roomCode = String(code);
|
||||
const mp = game.max_players ?? game.maxPlayers;
|
||||
if (mp != null) this.#context.maxPlayers = Number(mp);
|
||||
this.#context.game = { ...game };
|
||||
}
|
||||
|
||||
@@ -306,8 +317,8 @@ export class OverlayManager {
|
||||
if (d.maxPlayers != null) this.#context.maxPlayers = Number(d.maxPlayers);
|
||||
if (d.playerCount != null) this.#context.playerCount = Number(d.playerCount);
|
||||
if (Array.isArray(d.players)) this.#context.players = [...d.players];
|
||||
if (d.player !== undefined) this.#context.lastJoinedPlayer = d.player;
|
||||
if (d.lastJoinedPlayer !== undefined) this.#context.lastJoinedPlayer = d.lastJoinedPlayer;
|
||||
const joined = d.playerName ?? d.player ?? d.lastJoinedPlayer;
|
||||
if (joined !== undefined) this.#context.lastJoinedPlayer = joined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,6 +329,17 @@ export class OverlayManager {
|
||||
if (d.playerCount != null) this.#context.playerCount = Number(d.playerCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} d
|
||||
*/
|
||||
#applyGameStatus(d) {
|
||||
if (d.roomCode != null) this.#context.roomCode = String(d.roomCode);
|
||||
if (d.maxPlayers != null) this.#context.maxPlayers = Number(d.maxPlayers);
|
||||
if (d.playerCount != null) this.#context.playerCount = Number(d.playerCount);
|
||||
if (Array.isArray(d.players)) this.#context.players = [...d.players];
|
||||
if (d.lobbyState !== undefined) this.#context.lobbyState = d.lobbyState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} d
|
||||
*/
|
||||
|
||||
@@ -307,31 +307,125 @@ export class WebSocketClient {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const session = await response.json();
|
||||
if (session && session.id !== undefined && session.id !== null) {
|
||||
console.log(
|
||||
'[WebSocketClient] Found active session:',
|
||||
session.id,
|
||||
'— subscribing',
|
||||
);
|
||||
this.subscribeToSession(session.id);
|
||||
} else {
|
||||
console.log(
|
||||
'[WebSocketClient] No active session found; waiting for session.started',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
console.log(
|
||||
'[WebSocketClient] Could not fetch active session:',
|
||||
response.status,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await response.json();
|
||||
if (!session || session.id === undefined || session.id === null) {
|
||||
console.log(
|
||||
'[WebSocketClient] No active session found; waiting for session.started',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = session.id;
|
||||
console.log(
|
||||
'[WebSocketClient] Found active session:',
|
||||
sessionId,
|
||||
'— subscribing',
|
||||
);
|
||||
this.subscribeToSession(sessionId);
|
||||
|
||||
await this._restoreCurrentGame(sessionId);
|
||||
} catch (err) {
|
||||
console.error('[WebSocketClient] Error fetching active session:', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After subscribing, fetch the session's games to find the currently playing
|
||||
* game, then hit status-live for full shard state (players, lobby, etc.).
|
||||
* @param {string | number} sessionId
|
||||
*/
|
||||
async _restoreCurrentGame(sessionId) {
|
||||
const apiUrl = this._apiUrl;
|
||||
const token = this._jwtToken;
|
||||
if (!apiUrl || !token) return;
|
||||
|
||||
try {
|
||||
const gamesRes = await fetch(`${apiUrl}/api/sessions/${sessionId}/games`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (!gamesRes.ok) {
|
||||
console.log('[WebSocketClient] Could not fetch session games:', gamesRes.status);
|
||||
return;
|
||||
}
|
||||
|
||||
const games = await gamesRes.json();
|
||||
if (!Array.isArray(games) || games.length === 0) return;
|
||||
|
||||
const playing = games.find((g) => g.status === 'playing');
|
||||
if (!playing) return;
|
||||
|
||||
console.log(
|
||||
'[WebSocketClient] Restoring in-progress game:',
|
||||
playing.title,
|
||||
'(room:', playing.room_code, ')',
|
||||
);
|
||||
|
||||
this._onEvent('game.added', {
|
||||
session: { id: sessionId },
|
||||
game: {
|
||||
id: playing.game_id,
|
||||
title: playing.title,
|
||||
pack_name: playing.pack_name,
|
||||
min_players: playing.min_players,
|
||||
max_players: playing.max_players,
|
||||
room_code: playing.room_code,
|
||||
manually_added: playing.manually_added,
|
||||
},
|
||||
});
|
||||
|
||||
const sessionGameId = playing.id;
|
||||
const statusRes = await fetch(
|
||||
`${apiUrl}/api/sessions/${sessionId}/games/${sessionGameId}/status-live`,
|
||||
);
|
||||
|
||||
if (statusRes.ok) {
|
||||
const live = await statusRes.json();
|
||||
console.log(
|
||||
'[WebSocketClient] Restored live status — players:',
|
||||
live.playerCount,
|
||||
'state:', live.gameState,
|
||||
);
|
||||
|
||||
this._onEvent('game.status', {
|
||||
sessionId,
|
||||
gameId: live.gameId ?? playing.game_id,
|
||||
roomCode: live.roomCode ?? playing.room_code,
|
||||
appTag: live.appTag,
|
||||
maxPlayers: live.maxPlayers ?? playing.max_players,
|
||||
playerCount: live.playerCount ?? playing.player_count,
|
||||
players: Array.isArray(live.players) ? live.players : [],
|
||||
lobbyState: live.lobbyState,
|
||||
gameState: live.gameState,
|
||||
gameStarted: live.gameStarted,
|
||||
gameFinished: live.gameFinished,
|
||||
monitoring: live.monitoring,
|
||||
});
|
||||
} else if (playing.player_count != null && playing.max_players != null) {
|
||||
this._onEvent('room.connected', {
|
||||
sessionId,
|
||||
gameId: playing.game_id,
|
||||
roomCode: playing.room_code,
|
||||
maxPlayers: playing.max_players,
|
||||
playerCount: playing.player_count,
|
||||
players: [],
|
||||
lobbyState: 'Unknown',
|
||||
gameState: 'Unknown',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[WebSocketClient] Error restoring current game:', err);
|
||||
}
|
||||
}
|
||||
|
||||
_startHeartbeat() {
|
||||
this._stopHeartbeat();
|
||||
this._heartbeatInterval = setInterval(() => {
|
||||
|
||||
Reference in New Issue
Block a user