Files
jackboxpartypack-gamepicker/backend/utils/room-monitor.js

136 lines
4.0 KiB
JavaScript
Raw Normal View History

const db = require('../database');
const { getWebSocketManager } = require('./websocket-manager');
const { checkRoomStatus } = require('./jackbox-api');
const POLL_INTERVAL_MS = 10000;
// Active room monitors, keyed by "{sessionId}-{gameId}"
const activeMonitors = new Map();
/**
* Broadcast game.started event when room becomes locked
*/
function broadcastGameStarted(sessionId, gameId, roomCode, maxPlayers) {
try {
const wsManager = getWebSocketManager();
if (wsManager) {
wsManager.broadcastEvent('game.started', {
sessionId,
gameId,
roomCode,
maxPlayers
}, parseInt(sessionId));
}
console.log(`[Room Monitor] Broadcasted game.started for room ${roomCode} (max: ${maxPlayers})`);
} catch (error) {
console.error('[Room Monitor] Failed to broadcast game.started:', error.message);
}
}
/**
* Start monitoring a Jackbox room for game start (locked state).
*
* Polls the Jackbox REST API every 10 seconds. When the room becomes
* locked, broadcasts a game.started WebSocket event and then hands off
* to the player-count-checker to join as audience.
*/
async function startRoomMonitor(sessionId, gameId, roomCode, maxPlayers = 8) {
const monitorKey = `${sessionId}-${gameId}`;
if (activeMonitors.has(monitorKey)) {
console.log(`[Room Monitor] Already monitoring ${monitorKey}`);
return;
}
console.log(`[Room Monitor] Starting monitor for room ${roomCode} (${monitorKey})`);
const onGameStarted = (realMaxPlayers) => {
broadcastGameStarted(sessionId, gameId, roomCode, realMaxPlayers);
// Lazy require breaks the circular dependency with player-count-checker
const { startPlayerCountCheck } = require('./player-count-checker');
console.log(`[Room Monitor] Room ${roomCode} locked — handing off to player count checker`);
startPlayerCountCheck(sessionId, gameId, roomCode, realMaxPlayers);
};
const pollRoom = async () => {
const game = db.prepare(`
SELECT status FROM session_games
WHERE session_id = ? AND id = ?
`).get(sessionId, gameId);
if (!game || game.status === 'skipped' || game.status === 'played') {
console.log(`[Room Monitor] Stopping — game status changed for ${monitorKey}`);
stopRoomMonitor(sessionId, gameId);
return;
}
const roomStatus = await checkRoomStatus(roomCode);
if (!roomStatus.exists) {
console.log(`[Room Monitor] Room ${roomCode} does not exist — stopping`);
stopRoomMonitor(sessionId, gameId);
return;
}
if (roomStatus.locked) {
stopRoomMonitor(sessionId, gameId);
onGameStarted(roomStatus.maxPlayers);
return;
}
if (roomStatus.full) {
console.log(`[Room Monitor] Room ${roomCode} is full but not locked yet — waiting`);
} else {
console.log(`[Room Monitor] Room ${roomCode} lobby still open — waiting`);
}
};
// Poll immediately, then every POLL_INTERVAL_MS
activeMonitors.set(monitorKey, {
sessionId,
gameId,
roomCode,
interval: null
});
await pollRoom();
// If the monitor was already stopped (room locked or gone on first check), bail
if (!activeMonitors.has(monitorKey)) return;
const interval = setInterval(() => pollRoom(), POLL_INTERVAL_MS);
const monitor = activeMonitors.get(monitorKey);
if (monitor) monitor.interval = interval;
}
/**
* Stop monitoring a room
*/
function stopRoomMonitor(sessionId, gameId) {
const monitorKey = `${sessionId}-${gameId}`;
const monitor = activeMonitors.get(monitorKey);
if (monitor) {
if (monitor.interval) clearInterval(monitor.interval);
activeMonitors.delete(monitorKey);
console.log(`[Room Monitor] Stopped monitor for ${monitorKey}`);
}
}
/**
* Clean up all active monitors (for graceful shutdown)
*/
function cleanupAllMonitors() {
for (const [, monitor] of activeMonitors.entries()) {
if (monitor.interval) clearInterval(monitor.interval);
}
activeMonitors.clear();
console.log('[Room Monitor] Cleaned up all active monitors');
}
module.exports = {
startRoomMonitor,
stopRoomMonitor,
cleanupAllMonitors
};