feat: add periodic game.status broadcast and live status REST endpoint
Add 20-second game.status WebSocket heartbeat from active shard monitors containing full game state, and GET /status-live REST endpoint for on-demand polling. Fix missing token destructuring in SessionInfo causing crash. Relax frontend polling from 3s to 60s since WebSocket events now cover real-time updates. Bump version to 0.6.0. Made-with: Cursor
This commit is contained in:
@@ -4,7 +4,7 @@ const { authenticateToken } = require('../middleware/auth');
|
||||
const db = require('../database');
|
||||
const { triggerWebhook } = require('../utils/webhooks');
|
||||
const { getWebSocketManager } = require('../utils/websocket-manager');
|
||||
const { startMonitor, stopMonitor } = require('../utils/ecast-shard-client');
|
||||
const { startMonitor, stopMonitor, getMonitorSnapshot } = require('../utils/ecast-shard-client');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -838,6 +838,55 @@ router.get('/:id/export', authenticateToken, (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Get live game status from shard monitor or DB fallback
|
||||
router.get('/:sessionId/games/:gameId/status-live', (req, res) => {
|
||||
try {
|
||||
const { sessionId, gameId } = req.params;
|
||||
|
||||
const snapshot = getMonitorSnapshot(sessionId, gameId);
|
||||
if (snapshot) {
|
||||
return res.json(snapshot);
|
||||
}
|
||||
|
||||
const game = db.prepare(`
|
||||
SELECT
|
||||
sg.room_code,
|
||||
sg.player_count,
|
||||
sg.player_count_check_status,
|
||||
g.title,
|
||||
g.pack_name,
|
||||
g.max_players
|
||||
FROM session_games sg
|
||||
JOIN games g ON sg.game_id = g.id
|
||||
WHERE sg.session_id = ? AND sg.id = ?
|
||||
`).get(sessionId, gameId);
|
||||
|
||||
if (!game) {
|
||||
return res.status(404).json({ error: 'Session game not found' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
sessionId: parseInt(sessionId, 10),
|
||||
gameId: parseInt(gameId, 10),
|
||||
roomCode: game.room_code,
|
||||
appTag: null,
|
||||
maxPlayers: game.max_players,
|
||||
playerCount: game.player_count,
|
||||
players: [],
|
||||
lobbyState: null,
|
||||
gameState: null,
|
||||
gameStarted: false,
|
||||
gameFinished: game.player_count_check_status === 'completed',
|
||||
monitoring: false,
|
||||
title: game.title,
|
||||
packName: game.pack_name,
|
||||
status: game.player_count_check_status,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Start player count check for a session game (admin only)
|
||||
router.post('/:sessionId/games/:gameId/start-player-check', authenticateToken, (req, res) => {
|
||||
try {
|
||||
|
||||
@@ -90,6 +90,38 @@ class EcastShardClient {
|
||||
this.seq = 0;
|
||||
this.appTag = null;
|
||||
this.reconnecting = false;
|
||||
this.statusInterval = null;
|
||||
}
|
||||
|
||||
getSnapshot() {
|
||||
return {
|
||||
sessionId: this.sessionId,
|
||||
gameId: this.gameId,
|
||||
roomCode: this.roomCode,
|
||||
appTag: this.appTag,
|
||||
maxPlayers: this.maxPlayers,
|
||||
playerCount: this.playerCount,
|
||||
players: [...this.playerNames],
|
||||
lobbyState: this.lobbyState,
|
||||
gameState: this.gameState,
|
||||
gameStarted: this.gameStarted,
|
||||
gameFinished: this.gameFinished,
|
||||
monitoring: true,
|
||||
};
|
||||
}
|
||||
|
||||
startStatusBroadcast() {
|
||||
this.stopStatusBroadcast();
|
||||
this.statusInterval = setInterval(() => {
|
||||
this.onEvent('game.status', this.getSnapshot());
|
||||
}, 20000);
|
||||
}
|
||||
|
||||
stopStatusBroadcast() {
|
||||
if (this.statusInterval) {
|
||||
clearInterval(this.statusInterval);
|
||||
this.statusInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
buildReconnectUrl() {
|
||||
@@ -152,6 +184,8 @@ class EcastShardClient {
|
||||
lobbyState: this.lobbyState,
|
||||
gameState: this.gameState,
|
||||
});
|
||||
|
||||
this.startStatusBroadcast();
|
||||
}
|
||||
|
||||
handleEntityUpdate(result) {
|
||||
@@ -406,6 +440,7 @@ class EcastShardClient {
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.stopStatusBroadcast();
|
||||
if (this.ws) {
|
||||
try {
|
||||
this.ws.close(1000, 'Monitor stopped');
|
||||
@@ -569,4 +604,9 @@ async function cleanupAllShards() {
|
||||
console.log('[Shard Monitor] Cleaned up all active shards');
|
||||
}
|
||||
|
||||
module.exports = { EcastShardClient, startMonitor, stopMonitor, cleanupAllShards };
|
||||
function getMonitorSnapshot(sessionId, gameId) {
|
||||
const client = activeShards.get(`${sessionId}-${gameId}`);
|
||||
return client ? client.getSnapshot() : null;
|
||||
}
|
||||
|
||||
module.exports = { EcastShardClient, startMonitor, stopMonitor, cleanupAllShards, getMonitorSnapshot };
|
||||
|
||||
Reference in New Issue
Block a user