Files
wownerogue/src/network/broadcastManager.js
T
jwinterm 7581ba79cb refactor: frontend de-spaghetti (dup declares, dead code, log gating)
- Remove the duplicate _gameMode declaration in the client SocketHandlers
  object literal — it was declared twice, so the later `null` silently
  shadowed the earlier `'free'`. Now declared once with the effective value.
- Remove the dead SocketHandlers._appendMessage calls in smirkAuth.js; that
  method never existed, so the guard always fell through to the working
  .text() append. Keep the safe append.
- Gate broadcastManager's high-volume per-event logs (user count, chat message
  incl. message/socketId PII, game update, game start) behind debug mode;
  previously they logged on every event regardless of NODE_ENV.

Full suite: 21 suites / 155 tests.
2026-05-31 14:39:12 -04:00

229 lines
7.7 KiB
JavaScript

/**
* Broadcast Manager Module
* Handles all socket.io event broadcasting for the Wownerogue game server
* Supports current player-specific events and future spectator broadcasting
*/
class BroadcastManager {
constructor(io, debugManager = null) {
this.io = io;
this.debugManager = debugManager;
this._lastBroadcastBlock = null;
this._lastUserCount = -1;
}
setDebugManager(debugManager) {
this.debugManager = debugManager;
}
// ====== GLOBAL BROADCASTS (All Clients) ======
/**
* Broadcast connected user count to all clients
*/
broadcastUserCount() {
// Try multiple ways to get the count
let count = 0;
if (this.io.sockets && this.io.sockets.sockets) {
count = this.io.sockets.sockets.size || 0;
} else if (this.io.engine) {
count = this.io.engine.clientsCount || 0;
}
if (this.debugManager?.CONSOLE_LOGGING) {
console.log(`👥 Broadcasting user count: ${count} (last was ${this._lastUserCount})`);
}
// Always broadcast to ensure new clients get the count
this._lastUserCount = count;
this.io.emit('user_count', { count });
}
/**
* Broadcast block height to all connected clients
* @param {number} blockHeight - Current block height
*/
broadcastBlockHeight(blockHeight) {
// Only log when the height actually changes to avoid per-client spam
if (blockHeight !== this._lastBroadcastBlock) {
if (this.debugManager?.CONSOLE_LOGGING) {
console.log(`📡 Broadcasting block height ${blockHeight} to all clients`);
}
this._lastBroadcastBlock = blockHeight;
}
this.io.emit('blockheight', { blockHeight });
}
/**
* Broadcast chat message to all connected clients
* @param {string} username - Username of the sender
* @param {string} message - Chat message content
* @param {number} timestamp - Message timestamp
* @param {string} socketId - Socket ID of the sender
*/
broadcastChatMessage(username, message, timestamp, socketId) {
if (this.debugManager?.CONSOLE_LOGGING) {
console.log(`💬 Broadcasting chat message from ${socketId}: "${message}"`);
}
this.io.emit('chat_broadcast', {
username: username,
message: message,
timestamp: timestamp,
socketId: socketId
});
}
// ====== PLAYER-SPECIFIC BROADCASTS ======
/**
* Send status update to a specific player
* @param {string} socketId - The socket ID of the player
* @param {string} type - Type of status (info, error, warning, connection, help, etc.)
* @param {string} message - The status message
*/
sendStatusUpdate(socketId, type, message) {
this.io.to(socketId).emit('status_update', {
type: type,
message: message,
timestamp: Date.now()
});
}
/**
* Send welcome message to a newly connected client
* @param {string} clientId - The client ID
*/
sendWelcome(clientId) {
this.io.to(clientId).emit('welcome', clientId);
}
/**
* Send game state update to player and any spectators
* @param {string} playerSocketId - The socket ID of the player
* @param {object} gameState - The game state to broadcast
*/
sendGameUpdate(playerSocketId, gameState) {
if (this.debugManager?.CONSOLE_LOGGING) {
console.log(`📡 sendGameUpdate to ${playerSocketId}:`);
console.log(` - Lighting data: ${!!gameState.lighting} (${gameState.lighting ? Object.keys(gameState.lighting).length : 0} rows)`);
console.log(` - Torch data: ${!!gameState.torches} (${gameState.torches ? gameState.torches.length : 0} torches)`);
}
// Send to the player
this.io.to(playerSocketId).emit('game_update', gameState);
// TODO: Future spectator support
// Get list of spectators for this game and broadcast to them too
// const spectators = getSpectatorsForPlayer(playerSocketId);
// spectators.forEach(spectatorId => {
// this.io.to(spectatorId).emit('spectator_update', {
// playerSocketId: playerSocketId,
// gameState: gameState
// });
// });
}
/**
* Send game start event to player
* @param {string} socketId - The socket ID of the player
* @param {object} gameState - Initial game state
*/
sendGameStart(socketId, gameState) {
if (this.debugManager?.CONSOLE_LOGGING) {
console.log(`🎮 SENDING GAME_START to ${socketId}`);
}
this.io.to(socketId).emit('game_start', gameState);
}
/**
* Send game over event to player
* @param {string} socketId - The socket ID of the player
* @param {object} gameOverData - Game over information
*/
sendGameOver(socketId, gameOverData) {
this.io.to(socketId).emit('game_over', gameOverData);
}
/**
* Send waiting status to player (for queue system)
* @param {string} socketId - The socket ID of the player
* @param {object} waitingData - Waiting status information
*/
sendWaitingStatus(socketId, waitingData) {
this.io.to(socketId).emit('waiting_status', waitingData);
}
/**
* Send queue cancelled event to player
* @param {string} socketId - The socket ID of the player
*/
sendQueueCancelled(socketId) {
this.io.to(socketId).emit('queue_cancelled');
}
/**
* Send simple message to player
* @param {string} socketId - The socket ID of the player
* @param {string} message - The message content
*/
sendMessage(socketId, message) {
this.io.to(socketId).emit('message', message);
}
/**
* Send socket registration confirmation
* @param {string} socketId - The socket ID
* @param {object} registrationData - Registration confirmation data
*/
sendSocketRegistered(socketId, registrationData) {
this.io.to(socketId).emit('socket_registered', registrationData);
}
/**
* Send debug pong response
* @param {string} socketId - The socket ID
* @param {object} pongData - Debug pong data
*/
sendDebugPong(socketId, pongData) {
this.io.to(socketId).emit('debug_pong', pongData);
}
// ====== FUTURE SPECTATOR SUPPORT ======
/**
* Get spectators for a specific player's game
* @param {string} playerSocketId - The player's socket ID
* @returns {string[]} Array of spectator socket IDs
* TODO: Implement spectator tracking system
*/
getSpectatorsForPlayer(playerSocketId) {
// Future implementation: return array of spectator socket IDs
return [];
}
/**
* Add spectator to a player's game
* @param {string} playerSocketId - The player's socket ID
* @param {string} spectatorSocketId - The spectator's socket ID
* TODO: Implement spectator tracking system
*/
addSpectator(playerSocketId, spectatorSocketId) {
// Future implementation: track spectators per game
console.log(`TODO: Add spectator ${spectatorSocketId} to watch player ${playerSocketId}`);
}
/**
* Remove spectator from a player's game
* @param {string} playerSocketId - The player's socket ID
* @param {string} spectatorSocketId - The spectator's socket ID
* TODO: Implement spectator tracking system
*/
removeSpectator(playerSocketId, spectatorSocketId) {
// Future implementation: remove spectator tracking
console.log(`TODO: Remove spectator ${spectatorSocketId} from watching player ${playerSocketId}`);
}
}
module.exports = BroadcastManager;