chore: remove Puppeteer and old room-monitor/player-count-checker modules
Made-with: Cursor
This commit is contained in:
974
backend/package-lock.json
generated
974
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,6 @@
|
|||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"puppeteer": "^24.0.0",
|
|
||||||
"ws": "^8.14.0"
|
"ws": "^8.14.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,396 +0,0 @@
|
|||||||
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();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch a game from start to finish as audience member
|
|
||||||
* Collects analytics throughout the entire game lifecycle
|
|
||||||
*/
|
|
||||||
async function watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers) {
|
|
||||||
let browser;
|
|
||||||
const checkKey = `${sessionId}-${gameId}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log(`[Player Count] Opening audience connection for ${checkKey} (max: ${maxPlayers})`);
|
|
||||||
|
|
||||||
browser = await puppeteer.launch({
|
|
||||||
headless: 'new',
|
|
||||||
args: [
|
|
||||||
'--no-sandbox',
|
|
||||||
'--disable-setuid-sandbox',
|
|
||||||
'--disable-dev-shm-usage',
|
|
||||||
'--disable-accelerated-2d-canvas',
|
|
||||||
'--no-first-run',
|
|
||||||
'--no-zygote',
|
|
||||||
'--disable-gpu'
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
const page = await browser.newPage();
|
|
||||||
await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
|
||||||
|
|
||||||
// Track all player counts we've seen
|
|
||||||
const seenPlayerCounts = new Set();
|
|
||||||
let bestPlayerCount = null;
|
|
||||||
let startPlayerCount = null; // Authoritative count from 'start' action
|
|
||||||
let gameEnded = false;
|
|
||||||
let audienceJoined = false;
|
|
||||||
let frameCount = 0;
|
|
||||||
|
|
||||||
// Enable CDP and listen for WebSocket frames BEFORE navigating
|
|
||||||
const client = await page.target().createCDPSession();
|
|
||||||
await client.send('Network.enable');
|
|
||||||
|
|
||||||
client.on('Network.webSocketFrameReceived', ({ response }) => {
|
|
||||||
if (response.payloadData && !gameEnded) {
|
|
||||||
frameCount++;
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(response.payloadData);
|
|
||||||
|
|
||||||
if (process.env.DEBUG && frameCount % 10 === 0) {
|
|
||||||
console.log(`[Frame ${frameCount}] opcode: ${data.opcode}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for bc:room with player count data
|
|
||||||
let roomVal = null;
|
|
||||||
|
|
||||||
if (data.opcode === 'client/welcome' && data.result?.entities?.['bc:room']) {
|
|
||||||
roomVal = data.result.entities['bc:room'][1]?.val;
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
console.log(`[Frame ${frameCount}] Found bc:room in client/welcome`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// First client/welcome means Jackbox accepted our audience join
|
|
||||||
if (!audienceJoined) {
|
|
||||||
audienceJoined = true;
|
|
||||||
console.log(`[Audience] Successfully joined room ${roomCode} as audience`);
|
|
||||||
|
|
||||||
const wsManager = getWebSocketManager();
|
|
||||||
if (wsManager) {
|
|
||||||
wsManager.broadcastEvent('audience.joined', {
|
|
||||||
sessionId,
|
|
||||||
gameId,
|
|
||||||
roomCode
|
|
||||||
}, parseInt(sessionId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.opcode === 'object' && data.result?.key === 'bc:room') {
|
|
||||||
roomVal = data.result.val;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (roomVal) {
|
|
||||||
// Check if game has ended
|
|
||||||
if (roomVal.gameResults?.players) {
|
|
||||||
const finalCount = roomVal.gameResults.players.length;
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
console.log(`[Frame ${frameCount}] 🎉 GAME ENDED - Final count: ${finalCount} players`);
|
|
||||||
|
|
||||||
if (startPlayerCount !== null && startPlayerCount !== finalCount) {
|
|
||||||
console.log(`[Frame ${frameCount}] ⚠️ WARNING: Start count (${startPlayerCount}) != Final count (${finalCount})`);
|
|
||||||
} else if (startPlayerCount !== null) {
|
|
||||||
console.log(`[Frame ${frameCount}] ✓ Verified: Start count matches final count (${finalCount})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bestPlayerCount = finalCount;
|
|
||||||
gameEnded = true;
|
|
||||||
updatePlayerCount(sessionId, gameId, finalCount, 'completed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract player counts from analytics (game in progress)
|
|
||||||
if (roomVal.analytics && Array.isArray(roomVal.analytics)) {
|
|
||||||
for (const analytic of roomVal.analytics) {
|
|
||||||
if (analytic.action === 'start' && analytic.value && typeof analytic.value === 'number') {
|
|
||||||
if (startPlayerCount === null) {
|
|
||||||
startPlayerCount = analytic.value;
|
|
||||||
bestPlayerCount = analytic.value;
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
console.log(`[Frame ${frameCount}] 🎯 Found 'start' action: ${analytic.value} players (authoritative)`);
|
|
||||||
}
|
|
||||||
updatePlayerCount(sessionId, gameId, startPlayerCount, 'checking');
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startPlayerCount !== null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (analytic.value && typeof analytic.value === 'number' && analytic.value > 0 && analytic.value <= 100) {
|
|
||||||
seenPlayerCounts.add(analytic.value);
|
|
||||||
|
|
||||||
const clampedValue = Math.min(analytic.value, maxPlayers);
|
|
||||||
|
|
||||||
if (bestPlayerCount === null || clampedValue > bestPlayerCount) {
|
|
||||||
bestPlayerCount = clampedValue;
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
if (analytic.value > maxPlayers) {
|
|
||||||
console.log(`[Frame ${frameCount}] 📊 Found player count ${analytic.value} in action '${analytic.action}' (clamped to ${clampedValue})`);
|
|
||||||
} else {
|
|
||||||
console.log(`[Frame ${frameCount}] 📊 Found player count ${analytic.value} in action '${analytic.action}' (best so far)`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updatePlayerCount(sessionId, gameId, bestPlayerCount, 'checking');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if room is no longer locked (game ended another way)
|
|
||||||
if (roomVal.locked === false && bestPlayerCount !== null) {
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
console.log(`[Frame ${frameCount}] Room unlocked, game likely ended. Final count: ${bestPlayerCount}`);
|
|
||||||
}
|
|
||||||
gameEnded = true;
|
|
||||||
updatePlayerCount(sessionId, gameId, bestPlayerCount, 'completed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (process.env.DEBUG && frameCount % 50 === 0) {
|
|
||||||
console.log(`[Frame ${frameCount}] Parse error:`, e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Navigate and join audience
|
|
||||||
if (process.env.DEBUG) console.log('[Audience] Navigating to jackbox.tv...');
|
|
||||||
await page.goto('https://jackbox.tv/', { waitUntil: 'networkidle2', timeout: 30000 });
|
|
||||||
|
|
||||||
if (process.env.DEBUG) console.log('[Audience] Waiting for form...');
|
|
||||||
await page.waitForSelector('input#roomcode', { timeout: 10000 });
|
|
||||||
|
|
||||||
await page.evaluate(() => {
|
|
||||||
localStorage.clear();
|
|
||||||
sessionStorage.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (process.env.DEBUG) console.log('[Audience] Typing room code:', roomCode);
|
|
||||||
const roomInput = await page.$('input#roomcode');
|
|
||||||
await roomInput.type(roomCode.toUpperCase(), { delay: 50 });
|
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
|
|
||||||
if (process.env.DEBUG) console.log('[Audience] Typing name...');
|
|
||||||
const nameInput = await page.$('input#username');
|
|
||||||
await nameInput.type('CountBot', { delay: 30 });
|
|
||||||
|
|
||||||
if (process.env.DEBUG) console.log('[Audience] Waiting for JOIN AUDIENCE button...');
|
|
||||||
await page.waitForFunction(() => {
|
|
||||||
const buttons = Array.from(document.querySelectorAll('button'));
|
|
||||||
return buttons.some(b => b.textContent.toUpperCase().includes('JOIN AUDIENCE') && !b.disabled);
|
|
||||||
}, { timeout: 10000 });
|
|
||||||
|
|
||||||
if (process.env.DEBUG) console.log('[Audience] Clicking JOIN AUDIENCE...');
|
|
||||||
await page.evaluate(() => {
|
|
||||||
const buttons = Array.from(document.querySelectorAll('button'));
|
|
||||||
const btn = buttons.find(b => b.textContent.toUpperCase().includes('JOIN AUDIENCE') && !b.disabled);
|
|
||||||
if (btn) btn.click();
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
|
||||||
const checkInterval = setInterval(async () => {
|
|
||||||
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') {
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
console.log(`[Audience] Stopping watch - game status changed`);
|
|
||||||
}
|
|
||||||
clearInterval(checkInterval);
|
|
||||||
gameEnded = true;
|
|
||||||
if (browser) await browser.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameEnded) {
|
|
||||||
clearInterval(checkInterval);
|
|
||||||
if (browser) await browser.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const roomStatus = await checkRoomStatus(roomCode);
|
|
||||||
if (!roomStatus.exists) {
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
console.log(`[Audience] Room no longer exists - game ended`);
|
|
||||||
}
|
|
||||||
gameEnded = true;
|
|
||||||
clearInterval(checkInterval);
|
|
||||||
if (bestPlayerCount !== null) {
|
|
||||||
updatePlayerCount(sessionId, gameId, bestPlayerCount, 'completed');
|
|
||||||
} else {
|
|
||||||
updatePlayerCount(sessionId, gameId, null, 'failed');
|
|
||||||
}
|
|
||||||
if (browser) await browser.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
const check = activeChecks.get(checkKey);
|
|
||||||
if (check) {
|
|
||||||
check.watchInterval = checkInterval;
|
|
||||||
check.browser = browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Audience] Error watching game:', error.message);
|
|
||||||
if (browser) {
|
|
||||||
await browser.close();
|
|
||||||
}
|
|
||||||
if (bestPlayerCount !== null) {
|
|
||||||
updatePlayerCount(sessionId, gameId, bestPlayerCount, 'completed');
|
|
||||||
} else {
|
|
||||||
updatePlayerCount(sessionId, gameId, null, 'failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update player count in database and broadcast via WebSocket
|
|
||||||
*/
|
|
||||||
function updatePlayerCount(sessionId, gameId, playerCount, status) {
|
|
||||||
try {
|
|
||||||
db.prepare(`
|
|
||||||
UPDATE session_games
|
|
||||||
SET player_count = ?, player_count_check_status = ?
|
|
||||||
WHERE session_id = ? AND id = ?
|
|
||||||
`).run(playerCount, status, sessionId, gameId);
|
|
||||||
|
|
||||||
const wsManager = getWebSocketManager();
|
|
||||||
if (wsManager) {
|
|
||||||
wsManager.broadcastEvent('player-count.updated', {
|
|
||||||
sessionId,
|
|
||||||
gameId,
|
|
||||||
playerCount,
|
|
||||||
status
|
|
||||||
}, parseInt(sessionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[Player Count] Updated game ${gameId}: ${playerCount} players (${status})`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Player Count] Failed to update database:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 (activeChecks.has(checkKey)) {
|
|
||||||
console.log(`[Player Count] Already checking ${checkKey}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const game = db.prepare(`
|
|
||||||
SELECT player_count_check_status
|
|
||||||
FROM session_games
|
|
||||||
WHERE session_id = ? AND id = ?
|
|
||||||
`).get(sessionId, gameId);
|
|
||||||
|
|
||||||
if (game && game.player_count_check_status === 'completed') {
|
|
||||||
console.log(`[Player Count] Check already completed for ${checkKey}, skipping`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (game && game.player_count_check_status === 'failed') {
|
|
||||||
console.log(`[Player Count] Retrying failed check for ${checkKey}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[Player Count] Starting audience watch for game ${gameId} (room ${roomCode}, max ${maxPlayers})`);
|
|
||||||
|
|
||||||
db.prepare(`
|
|
||||||
UPDATE session_games
|
|
||||||
SET player_count_check_status = 'checking'
|
|
||||||
WHERE session_id = ? AND id = ?
|
|
||||||
`).run(sessionId, gameId);
|
|
||||||
|
|
||||||
activeChecks.set(checkKey, {
|
|
||||||
sessionId,
|
|
||||||
gameId,
|
|
||||||
roomCode,
|
|
||||||
watchInterval: null,
|
|
||||||
browser: null
|
|
||||||
});
|
|
||||||
|
|
||||||
await watchGameAsAudience(sessionId, gameId, roomCode, maxPlayers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop checking player count for a game
|
|
||||||
*/
|
|
||||||
async function stopPlayerCountCheck(sessionId, gameId) {
|
|
||||||
const checkKey = `${sessionId}-${gameId}`;
|
|
||||||
const check = activeChecks.get(checkKey);
|
|
||||||
|
|
||||||
if (check) {
|
|
||||||
if (check.watchInterval) {
|
|
||||||
clearInterval(check.watchInterval);
|
|
||||||
}
|
|
||||||
if (check.browser) {
|
|
||||||
try {
|
|
||||||
await check.browser.close();
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore errors closing browser
|
|
||||||
}
|
|
||||||
}
|
|
||||||
activeChecks.delete(checkKey);
|
|
||||||
|
|
||||||
const game = db.prepare(`
|
|
||||||
SELECT player_count_check_status
|
|
||||||
FROM session_games
|
|
||||||
WHERE session_id = ? AND id = ?
|
|
||||||
`).get(sessionId, gameId);
|
|
||||||
|
|
||||||
if (game && game.player_count_check_status !== 'completed' && game.player_count_check_status !== 'failed') {
|
|
||||||
db.prepare(`
|
|
||||||
UPDATE session_games
|
|
||||||
SET player_count_check_status = 'stopped'
|
|
||||||
WHERE session_id = ? AND id = ?
|
|
||||||
`).run(sessionId, gameId);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[Player Count] Stopped check for ${checkKey}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clean up all active checks (for graceful shutdown)
|
|
||||||
*/
|
|
||||||
async function cleanupAllChecks() {
|
|
||||||
for (const [, check] of activeChecks.entries()) {
|
|
||||||
if (check.watchInterval) {
|
|
||||||
clearInterval(check.watchInterval);
|
|
||||||
}
|
|
||||||
if (check.browser) {
|
|
||||||
try {
|
|
||||||
await check.browser.close();
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
activeChecks.clear();
|
|
||||||
console.log('[Player Count] Cleaned up all active checks');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
startPlayerCountCheck,
|
|
||||||
stopPlayerCountCheck,
|
|
||||||
cleanupAllChecks
|
|
||||||
};
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
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