Decouple room monitoring from player count, fix Jackbox API fetch
Extracts checkRoomStatus into shared jackbox-api.js with proper User-Agent header (bare fetch was silently rejected by Jackbox API) and always-on error logging (previously gated behind DEBUG flag). Splits room-start detection (room-monitor.js) from audience-based player counting (player-count-checker.js) to eliminate circular dependency and allow immediate game.started detection. Room monitor now polls immediately instead of waiting 10 seconds for first check. Made-with: Cursor
This commit is contained in:
42
backend/utils/jackbox-api.js
Normal file
42
backend/utils/jackbox-api.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const JACKBOX_API_BASE = 'https://ecast.jackboxgames.com/api/v2';
|
||||
|
||||
const DEFAULT_HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; GamePicker/1.0)'
|
||||
};
|
||||
|
||||
/**
|
||||
* Check room status via the Jackbox ecast REST API.
|
||||
* Shared by room-monitor (polling for lock) and player-count-checker (room existence).
|
||||
*/
|
||||
async function checkRoomStatus(roomCode) {
|
||||
try {
|
||||
const response = await fetch(`${JACKBOX_API_BASE}/rooms/${roomCode}`, {
|
||||
headers: DEFAULT_HEADERS
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(`[Jackbox API] Room ${roomCode}: HTTP ${response.status}`);
|
||||
return { exists: false };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const roomData = data.body || data;
|
||||
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('[Jackbox API] Room data:', JSON.stringify(roomData, null, 2));
|
||||
}
|
||||
|
||||
return {
|
||||
exists: true,
|
||||
locked: roomData.locked || false,
|
||||
full: roomData.full || false,
|
||||
maxPlayers: roomData.maxPlayers || 8,
|
||||
minPlayers: roomData.minPlayers || 0
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(`[Jackbox API] Error checking room ${roomCode}:`, e.message);
|
||||
return { exists: false };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { checkRoomStatus };
|
||||
@@ -1,40 +1,11 @@
|
||||
const puppeteer = require('puppeteer');
|
||||
const db = require('../database');
|
||||
const { getWebSocketManager } = require('./websocket-manager');
|
||||
const { checkRoomStatus } = require('./jackbox-api');
|
||||
|
||||
// Store active check jobs
|
||||
const activeChecks = new Map();
|
||||
|
||||
/**
|
||||
* Check room status via Jackbox API
|
||||
*/
|
||||
async function checkRoomStatus(roomCode) {
|
||||
try {
|
||||
const response = await fetch(`https://ecast.jackboxgames.com/api/v2/rooms/${roomCode}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const roomData = data.body || data;
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
console.log('[API] Room data:', JSON.stringify(roomData, null, 2));
|
||||
}
|
||||
return {
|
||||
exists: true,
|
||||
locked: roomData.locked || false,
|
||||
full: roomData.full || false,
|
||||
maxPlayers: roomData.maxPlayers || 8,
|
||||
minPlayers: roomData.minPlayers || 0
|
||||
};
|
||||
}
|
||||
return { exists: false };
|
||||
} catch (e) {
|
||||
if (process.env.DEBUG) {
|
||||
console.error('[API] Error checking room:', e.message);
|
||||
}
|
||||
return { exists: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch a game from start to finish as audience member
|
||||
* Collects analytics throughout the entire game lifecycle
|
||||
@@ -67,7 +38,7 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
let bestPlayerCount = null;
|
||||
let startPlayerCount = null; // Authoritative count from 'start' action
|
||||
let gameEnded = false;
|
||||
let audienceJoined = false; // Track whether we've confirmed audience join
|
||||
let audienceJoined = false;
|
||||
let frameCount = 0;
|
||||
|
||||
// Enable CDP and listen for WebSocket frames BEFORE navigating
|
||||
@@ -98,7 +69,6 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
audienceJoined = true;
|
||||
console.log(`[Audience] Successfully joined room ${roomCode} as audience`);
|
||||
|
||||
// Broadcast audience.joined event via WebSocket
|
||||
const wsManager = getWebSocketManager();
|
||||
if (wsManager) {
|
||||
wsManager.broadcastEvent('audience.joined', {
|
||||
@@ -121,7 +91,6 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
if (process.env.DEBUG) {
|
||||
console.log(`[Frame ${frameCount}] 🎉 GAME ENDED - Final count: ${finalCount} players`);
|
||||
|
||||
// Verify it matches start count if we had one
|
||||
if (startPlayerCount !== null && startPlayerCount !== finalCount) {
|
||||
console.log(`[Frame ${frameCount}] ⚠️ WARNING: Start count (${startPlayerCount}) != Final count (${finalCount})`);
|
||||
} else if (startPlayerCount !== null) {
|
||||
@@ -130,7 +99,6 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
}
|
||||
bestPlayerCount = finalCount;
|
||||
gameEnded = true;
|
||||
// Update immediately with final count
|
||||
updatePlayerCount(sessionId, gameId, finalCount, 'completed');
|
||||
return;
|
||||
}
|
||||
@@ -138,7 +106,6 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
// Extract player counts from analytics (game in progress)
|
||||
if (roomVal.analytics && Array.isArray(roomVal.analytics)) {
|
||||
for (const analytic of roomVal.analytics) {
|
||||
// Check for 'start' action - this is authoritative
|
||||
if (analytic.action === 'start' && analytic.value && typeof analytic.value === 'number') {
|
||||
if (startPlayerCount === null) {
|
||||
startPlayerCount = analytic.value;
|
||||
@@ -146,25 +113,20 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
if (process.env.DEBUG) {
|
||||
console.log(`[Frame ${frameCount}] 🎯 Found 'start' action: ${analytic.value} players (authoritative)`);
|
||||
}
|
||||
// Update UI with authoritative start count
|
||||
updatePlayerCount(sessionId, gameId, startPlayerCount, 'checking');
|
||||
}
|
||||
continue; // Skip to next analytic
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we already have start count, we don't need to keep counting
|
||||
if (startPlayerCount !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, look for any numeric value that could be a player count
|
||||
if (analytic.value && typeof analytic.value === 'number' && analytic.value > 0 && analytic.value <= 100) {
|
||||
seenPlayerCounts.add(analytic.value);
|
||||
|
||||
// Clamp to maxPlayers to avoid cumulative stats inflating count
|
||||
const clampedValue = Math.min(analytic.value, maxPlayers);
|
||||
|
||||
// Update best guess (highest count seen so far, clamped to maxPlayers)
|
||||
if (bestPlayerCount === null || clampedValue > bestPlayerCount) {
|
||||
bestPlayerCount = clampedValue;
|
||||
if (process.env.DEBUG) {
|
||||
@@ -174,7 +136,6 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
console.log(`[Frame ${frameCount}] 📊 Found player count ${analytic.value} in action '${analytic.action}' (best so far)`);
|
||||
}
|
||||
}
|
||||
// Update UI with current best guess
|
||||
updatePlayerCount(sessionId, gameId, bestPlayerCount, 'checking');
|
||||
}
|
||||
}
|
||||
@@ -237,9 +198,7 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
if (process.env.DEBUG) console.log('[Audience] 👀 Watching game... (will monitor until game ends)');
|
||||
|
||||
// Keep watching until game ends or we're told to stop
|
||||
// Check every 5 seconds if we should still be watching
|
||||
const checkInterval = setInterval(async () => {
|
||||
// Check if we should stop
|
||||
const game = db.prepare(`
|
||||
SELECT status, player_count_check_status
|
||||
FROM session_games
|
||||
@@ -256,14 +215,12 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if game ended
|
||||
if (gameEnded) {
|
||||
clearInterval(checkInterval);
|
||||
if (browser) await browser.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if room still exists
|
||||
const roomStatus = await checkRoomStatus(roomCode);
|
||||
if (!roomStatus.exists) {
|
||||
if (process.env.DEBUG) {
|
||||
@@ -281,7 +238,6 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// Store the interval so we can clean it up
|
||||
const check = activeChecks.get(checkKey);
|
||||
if (check) {
|
||||
check.watchInterval = checkInterval;
|
||||
@@ -293,7 +249,6 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
}
|
||||
// If we had a best guess, use it; otherwise fail
|
||||
if (bestPlayerCount !== null) {
|
||||
updatePlayerCount(sessionId, gameId, bestPlayerCount, 'completed');
|
||||
} else {
|
||||
@@ -303,27 +258,7 @@ async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(`[Player Count] Broadcasted game.started for room ${roomCode} (max: ${maxPlayers})`);
|
||||
} catch (error) {
|
||||
console.error('[Player Count] Failed to broadcast game.started:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update player count in database
|
||||
* Update player count in database and broadcast via WebSocket
|
||||
*/
|
||||
function updatePlayerCount(sessionId, gameId, playerCount, status) {
|
||||
try {
|
||||
@@ -333,7 +268,6 @@ function updatePlayerCount(sessionId, gameId, playerCount, status) {
|
||||
WHERE session_id = ? AND id = ?
|
||||
`).run(playerCount, status, sessionId, gameId);
|
||||
|
||||
// Broadcast via WebSocket
|
||||
const wsManager = getWebSocketManager();
|
||||
if (wsManager) {
|
||||
wsManager.broadcastEvent('player-count.updated', {
|
||||
@@ -351,25 +285,18 @@ function updatePlayerCount(sessionId, gameId, playerCount, status) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start checking player count for a game
|
||||
* Strategy:
|
||||
* 1. Wait 10 seconds for initial room setup
|
||||
* 2. Poll every 10 seconds until game is locked (started)
|
||||
* 3. Broadcast game.started event when locked detected
|
||||
* 4. Join audience and watch entire game
|
||||
* 5. Update UI as we learn more
|
||||
* 6. Finalize when game ends
|
||||
* Start player count checking for a game.
|
||||
* Called by room-monitor once the game is confirmed started (room locked).
|
||||
* Goes straight to joining the audience — no polling needed.
|
||||
*/
|
||||
async function startPlayerCountCheck(sessionId, gameId, roomCode, maxPlayers = 8) {
|
||||
const checkKey = `${sessionId}-${gameId}`;
|
||||
|
||||
// If already checking, don't start again
|
||||
if (activeChecks.has(checkKey)) {
|
||||
console.log(`[Player Count] Already checking ${checkKey}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if already completed (but allow retrying failed checks)
|
||||
const game = db.prepare(`
|
||||
SELECT player_count_check_status
|
||||
FROM session_games
|
||||
@@ -381,122 +308,27 @@ async function startPlayerCountCheck(sessionId, gameId, roomCode, maxPlayers = 8
|
||||
return;
|
||||
}
|
||||
|
||||
// If retrying a failed check, reset the status
|
||||
if (game && game.player_count_check_status === 'failed') {
|
||||
console.log(`[Player Count] Retrying failed check for ${checkKey}`);
|
||||
}
|
||||
|
||||
console.log(`[Player Count] Starting check for game ${gameId} with room code ${roomCode}`);
|
||||
console.log(`[Player Count] Starting audience watch for game ${gameId} (room ${roomCode}, max ${maxPlayers})`);
|
||||
|
||||
// Update status to waiting
|
||||
db.prepare(`
|
||||
UPDATE session_games
|
||||
SET player_count_check_status = 'waiting'
|
||||
SET player_count_check_status = 'checking'
|
||||
WHERE session_id = ? AND id = ?
|
||||
`).run(sessionId, gameId);
|
||||
|
||||
// Function to check if game is ready (locked)
|
||||
const waitForGameStart = async () => {
|
||||
const roomStatus = await checkRoomStatus(roomCode);
|
||||
|
||||
if (!roomStatus.exists) {
|
||||
console.log(`[Player Count] Room ${roomCode} does not exist`);
|
||||
updatePlayerCount(sessionId, gameId, null, 'failed');
|
||||
stopPlayerCountCheck(sessionId, gameId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If locked, game has started - ready to watch
|
||||
if (roomStatus.locked) {
|
||||
console.log(`[Player Count] Room is LOCKED - game in progress, starting watch`);
|
||||
return { ready: true, maxPlayers: roomStatus.maxPlayers };
|
||||
}
|
||||
|
||||
// Log if full but not yet started
|
||||
if (roomStatus.full) {
|
||||
console.log(`[Player Count] Room is FULL but not locked yet - waiting for game start`);
|
||||
} else {
|
||||
console.log(`[Player Count] Room not ready yet (lobby still open)`);
|
||||
}
|
||||
|
||||
return null; // Not ready, keep polling
|
||||
};
|
||||
|
||||
// Wait 10 seconds before first check
|
||||
const initialTimeout = setTimeout(async () => {
|
||||
try {
|
||||
// Update status to checking
|
||||
db.prepare(`
|
||||
UPDATE session_games
|
||||
SET player_count_check_status = 'checking'
|
||||
WHERE session_id = ? AND id = ?
|
||||
`).run(sessionId, gameId);
|
||||
|
||||
console.log(`[Player Count] Initial check after 10s for ${checkKey}`);
|
||||
const result = await waitForGameStart();
|
||||
|
||||
if (result && result.ready === true) {
|
||||
// Game is locked, broadcast game.started and start watching
|
||||
const realMaxPlayers = result.maxPlayers;
|
||||
broadcastGameStarted(sessionId, gameId, roomCode, realMaxPlayers);
|
||||
console.log(`[Player Count] Using real maxPlayers from Jackbox: ${realMaxPlayers} (database had: ${maxPlayers})`);
|
||||
await watchGameAsAudience(sessionId, gameId, roomCode, realMaxPlayers);
|
||||
} else if (result === null) {
|
||||
// Not ready yet, poll every 10 seconds
|
||||
const checkInterval = setInterval(async () => {
|
||||
// Check if we should stop
|
||||
const game = db.prepare(`
|
||||
SELECT status, player_count_check_status
|
||||
FROM session_games
|
||||
WHERE session_id = ? AND id = ?
|
||||
`).get(sessionId, gameId);
|
||||
|
||||
if (!game || game.status === 'skipped' || game.status === 'played' || game.player_count_check_status === 'stopped' || game.player_count_check_status === 'completed') {
|
||||
console.log(`[Player Count] Stopping check for ${checkKey} - game status changed`);
|
||||
stopPlayerCountCheck(sessionId, gameId);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await waitForGameStart();
|
||||
if (result && result.ready === true) {
|
||||
// Game is now locked, stop interval, broadcast game.started, and start watching
|
||||
clearInterval(checkInterval);
|
||||
const check = activeChecks.get(checkKey);
|
||||
if (check) check.interval = null;
|
||||
const realMaxPlayers = result.maxPlayers;
|
||||
broadcastGameStarted(sessionId, gameId, roomCode, realMaxPlayers);
|
||||
console.log(`[Player Count] Using real maxPlayers from Jackbox: ${realMaxPlayers} (database had: ${maxPlayers})`);
|
||||
await watchGameAsAudience(sessionId, gameId, roomCode, realMaxPlayers);
|
||||
} else if (result === false) {
|
||||
// Check failed or completed, stop
|
||||
clearInterval(checkInterval);
|
||||
stopPlayerCountCheck(sessionId, gameId);
|
||||
}
|
||||
}, 10000); // Poll every 10 seconds
|
||||
|
||||
// Store the interval
|
||||
const check = activeChecks.get(checkKey);
|
||||
if (check) check.interval = checkInterval;
|
||||
}
|
||||
// If ready === false, check already stopped/completed
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Player Count] Error starting check for ${checkKey}:`, error.message);
|
||||
updatePlayerCount(sessionId, gameId, null, 'failed');
|
||||
stopPlayerCountCheck(sessionId, gameId);
|
||||
}
|
||||
}, 10000); // Wait 10 seconds before first check
|
||||
|
||||
// Store the check references
|
||||
activeChecks.set(checkKey, {
|
||||
sessionId,
|
||||
gameId,
|
||||
roomCode,
|
||||
initialTimeout,
|
||||
interval: null,
|
||||
watchInterval: null,
|
||||
browser: null
|
||||
});
|
||||
|
||||
await watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -507,12 +339,6 @@ async function stopPlayerCountCheck(sessionId, gameId) {
|
||||
const check = activeChecks.get(checkKey);
|
||||
|
||||
if (check) {
|
||||
if (check.initialTimeout) {
|
||||
clearTimeout(check.initialTimeout);
|
||||
}
|
||||
if (check.interval) {
|
||||
clearInterval(check.interval);
|
||||
}
|
||||
if (check.watchInterval) {
|
||||
clearInterval(check.watchInterval);
|
||||
}
|
||||
@@ -525,7 +351,6 @@ async function stopPlayerCountCheck(sessionId, gameId) {
|
||||
}
|
||||
activeChecks.delete(checkKey);
|
||||
|
||||
// Update status to stopped if not already completed or failed
|
||||
const game = db.prepare(`
|
||||
SELECT player_count_check_status
|
||||
FROM session_games
|
||||
@@ -548,13 +373,7 @@ async function stopPlayerCountCheck(sessionId, gameId) {
|
||||
* Clean up all active checks (for graceful shutdown)
|
||||
*/
|
||||
async function cleanupAllChecks() {
|
||||
for (const [checkKey, check] of activeChecks.entries()) {
|
||||
if (check.initialTimeout) {
|
||||
clearTimeout(check.initialTimeout);
|
||||
}
|
||||
if (check.interval) {
|
||||
clearInterval(check.interval);
|
||||
}
|
||||
for (const [, check] of activeChecks.entries()) {
|
||||
if (check.watchInterval) {
|
||||
clearInterval(check.watchInterval);
|
||||
}
|
||||
|
||||
135
backend/utils/room-monitor.js
Normal file
135
backend/utils/room-monitor.js
Normal file
@@ -0,0 +1,135 @@
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user