Game-Server/GameServer/socket/socketHandlers.js
2026-01-24 16:47:19 -04:00

394 lines
14 KiB
JavaScript

/**
* Socket Handlers - Manages real-time socket connections for game server
*/
const jwt = require('jsonwebtoken');
const logger = require('../utils/logger');
const { getGameSystem } = require('../systems/GameSystem');
class SocketHandlers {
constructor(io, gameServers, connectedPlayers) {
this.io = io;
this.gameServers = gameServers;
this.connectedPlayers = connectedPlayers; // Track actual player connections
this.gameSystem = null;
// Track connected users to prevent duplicate connections
this.connectedUsers = new Map(); // userId -> socket.id
this.userSockets = new Map(); // socket.id -> userId
// Add connection cleanup interval
this.startConnectionCleanup();
}
startConnectionCleanup() {
// Clean up stale connections every 30 seconds
setInterval(() => {
this.cleanupStaleConnections();
}, 30000);
}
cleanupStaleConnections() {
logger.info(`[GAME SERVER] Checking ${this.connectedUsers.size} active connections...`);
logger.info(`[GAME SERVER] Current tracked players: ${Array.from(this.connectedPlayers)}`);
let playersRemoved = 0;
for (const [userId, socketId] of this.connectedUsers.entries()) {
const socket = this.io.sockets.sockets.get(socketId);
if (!socket || !socket.connected) {
logger.warn(`[GAME SERVER] Cleaning up stale connection for user ${userId} (socket: ${socketId})`);
this.connectedUsers.delete(userId);
this.userSockets.delete(socketId);
if (this.connectedPlayers.has(userId)) {
this.connectedPlayers.delete(userId);
playersRemoved++;
logger.info(`[GAME SERVER] Removed stale player ${userId}. Players removed: ${playersRemoved}`);
}
}
}
logger.info(`[GAME SERVER] Cleanup complete. Players removed: ${playersRemoved}, Total players now: ${this.connectedPlayers.size}`);
// Update player count on API if players were removed
if (playersRemoved > 0 && this.serverRegistration) {
logger.info(`[GAME SERVER] Updating API player count after cleanup to: ${this.connectedPlayers.size}`);
this.serverRegistration.updatePlayerCount(this.connectedPlayers.size);
}
}
async initializeGameSystem() {
const { initializeGameSystems } = require('../systems/GameSystem');
this.gameSystem = await initializeGameSystems();
}
handleConnection(socket) {
logger.info(`Game Server: Socket connected - ${socket.id}`);
// Authentication middleware
socket.use(async (packet, next) => {
try {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication required'));
}
// Verify JWT token
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret');
socket.userId = decoded.userId;
socket.email = decoded.email;
// Check if user is already connected from another client
const existingSocketId = this.connectedUsers.get(decoded.userId);
const wasAlreadyConnected = this.connectedPlayers.has(decoded.userId);
logger.info(`[GAME SERVER] User ${decoded.userId} (${decoded.email}) connecting. Was already connected: ${wasAlreadyConnected}, Existing socket: ${existingSocketId}`);
if (existingSocketId && existingSocketId !== socket.id) {
logger.warn(`[GAME SERVER] User ${decoded.userId} attempting to connect from multiple clients. Disconnecting previous client.`);
logger.warn(`[GAME SERVER] Existing socket: ${existingSocketId}, New socket: ${socket.id}`);
// Disconnect the previous client
const previousSocket = this.io.sockets.sockets.get(existingSocketId);
if (previousSocket) {
logger.info(`[GAME SERVER] Force disconnecting previous socket: ${existingSocketId}`);
previousSocket.emit('force_disconnect', {
reason: 'Another client connected with your account',
newSocketId: socket.id
});
previousSocket.disconnect(true);
} else {
logger.warn(`[GAME SERVER] Previous socket ${existingSocketId} not found in active connections`);
// Clean up the stale mapping
this.connectedUsers.delete(decoded.userId);
this.userSockets.delete(existingSocketId);
if (!wasAlreadyConnected) {
this.connectedPlayers.delete(decoded.userId);
}
}
} else {
logger.info(`[GAME SERVER] New connection for user ${decoded.userId} (socket: ${socket.id})`);
}
// Store user connection
this.connectedUsers.set(decoded.userId, socket.id);
this.userSockets.set(socket.id, decoded.userId);
// Add to connected players tracking only if not already there
const wasNotAlreadyConnected = !this.connectedPlayers.has(decoded.userId);
this.connectedPlayers.add(decoded.userId);
logger.info(`[GAME SERVER] User ${decoded.userId} added to tracking. Was not already connected: ${wasNotAlreadyConnected}, Total players: ${this.connectedPlayers.size}`);
// Update player count on API only if this is a new unique user
if (this.serverRegistration && wasNotAlreadyConnected) {
logger.info(`[GAME SERVER] Updating API player count to: ${this.connectedPlayers.size}`);
this.serverRegistration.updatePlayerCount(this.connectedPlayers.size);
}
next();
} catch (error) {
logger.error(`Socket authentication failed: ${error.message}`);
socket.emit('authError', { error: 'Authentication failed' });
socket.disconnect();
}
});
// Event handlers
socket.on('joinServer', (data) => this.handleJoinServer(socket, data));
socket.on('leaveServer', (data) => this.handleLeaveServer(socket, data));
socket.on('gameAction', (data) => this.handleGameAction(socket, data));
socket.on('chatMessage', (data) => this.handleChatMessage(socket, data));
socket.on('getPlayerList', (data) => this.handleGetPlayerList(socket, data));
socket.on('disconnect', () => this.handleDisconnect(socket));
}
async handleJoinServer(socket, data) {
try {
const { serverId, userId, username } = data;
// Verify user matches socket authentication
if (socket.userId !== userId) {
socket.emit('error', { message: 'User authentication mismatch' });
return;
}
// Create or get game instance
let gameInstance = this.gameSystem.getGameInstance(serverId);
if (!gameInstance) {
// This should ideally be handled by the API server
// But for now, create a basic game instance
gameInstance = this.gameSystem.createGameInstance({
id: serverId,
name: `Game ${serverId}`,
type: 'public',
region: 'us-east',
maxPlayers: 10
});
}
// Join player to game instance
const playerData = {
userId: userId,
username: username,
currentShip: data.currentShip,
stats: data.stats
};
const joinedGame = this.gameSystem.joinGameInstance(socket, serverId, playerData);
if (joinedGame) {
// Notify player of successful join
socket.emit('joinedServer', {
serverId: serverId,
gameInstance: {
id: joinedGame.id,
name: joinedGame.name
}
});
// Notify other players
socket.to(`game_${gameId}`).emit('playerJoined', {
userId: socket.userId,
username: socket.email || 'Player'
});
logger.info(`Player ${socket.userId} joined game ${gameId}`);
} else {
socket.emit('error', { message: 'Failed to join game' });
}
} catch (error) {
logger.error(`Join game error: ${error.message}`);
socket.emit('error', { message: 'Failed to join game' });
}
}
async handleLeaveGame(socket, data) {
try {
const { gameId } = data;
const leftGame = this.gameSystem.leaveGameInstance(socket, gameId);
if (leftGame) {
// Notify player of successful leave
socket.emit('leftGame', { gameId: gameId });
// Notify other players
socket.to(`game_${gameId}`).emit('playerLeft', {
userId: socket.userId,
currentPlayers: leftGame.currentPlayers
});
logger.info(`Player left game ${gameId}`);
} else {
socket.emit('error', { message: 'Failed to leave game' });
}
} catch (error) {
logger.error(`Leave game error: ${error.message}`);
socket.emit('error', { message: 'Failed to leave game' });
}
}
async handleGameAction(socket, data) {
try {
const { type, actionData } = data;
// Handle game action through game system
const success = this.gameSystem.handlePlayerAction(socket, type, actionData);
if (!success) {
socket.emit('error', { message: 'Failed to process game action' });
}
} catch (error) {
logger.error(`Game action error: ${error.message}`);
socket.emit('error', { message: 'Failed to process game action' });
}
}
async handleChatMessage(socket, data) {
try {
const { message } = data;
// Handle chat through game system
const success = this.gameSystem.handlePlayerChat(socket, { message });
if (!success) {
socket.emit('error', { message: 'Failed to send chat message' });
}
} catch (error) {
logger.error(`Chat message error: ${error.message}`);
socket.emit('error', { message: 'Failed to send chat message' });
}
}
async handleGetPlayerList(socket, data) {
try {
const { serverId } = data;
this.sendPlayerList(socket, serverId);
} catch (error) {
logger.error(`Get player list error: ${error.message}`);
socket.emit('error', { message: 'Failed to get player list' });
}
}
async sendPlayerList(socket, serverId) {
const gameInstance = this.gameSystem.getGameInstance(serverId);
if (gameInstance) {
const players = Array.from(gameInstance.players.values()).map(player => ({
userId: player.userId,
username: player.username,
joinedAt: player.joinedAt,
isReady: player.isReady,
stats: player.stats
}));
socket.emit('playerList', {
serverId: serverId,
players: players,
currentPlayers: gameInstance.currentPlayers,
maxPlayers: gameInstance.maxPlayers
});
} else {
socket.emit('error', { message: 'Game instance not found' });
}
}
async handleDisconnect(socket) {
try {
logger.info(`Game Server: Socket disconnected - ${socket.id}`);
// Get user ID from socket
const userId = this.userSockets.get(socket.id);
if (userId) {
logger.info(`[GAME SERVER] User ${userId} disconnecting (socket: ${socket.id})`);
// Remove from tracking maps
this.connectedUsers.delete(userId);
this.userSockets.delete(socket.id);
// Remove from connected players tracking
const wasTracked = this.connectedPlayers.has(userId);
this.connectedPlayers.delete(userId);
logger.info(`[GAME SERVER] User ${userId} removed from tracking. Was tracked: ${wasTracked}, Total players: ${this.connectedPlayers.size}`);
// Update player count on API only if user was being tracked
if (this.serverRegistration && wasTracked) {
logger.info(`[GAME SERVER] Updating API player count to: ${this.connectedPlayers.size}`);
this.serverRegistration.updatePlayerCount(this.connectedPlayers.size);
}
// Get player connection info
const connection = this.gameSystem.getPlayerConnection(socket.id);
if (connection && connection.gameId) {
const gameId = connection.gameId;
// Remove player from game
const success = this.gameSystem.removePlayerFromGame(gameId, userId);
if (success) {
// Notify other players in the game
const game = this.gameSystem.getGame(gameId);
if (game) {
this.io.to(gameId).emit('playerLeft', {
userId,
username: socket.email || 'Unknown',
gameId,
currentPlayers: game.currentPlayers
});
}
logger.info(`Player ${userId} disconnected from game ${gameId}`);
}
if (success) {
// Notify other players
this.io.to(`game_${gameId}`).emit('playerLeft', {
userId: socket.userId,
currentPlayers: game.currentPlayers
});
logger.info(`Player ${socket.userId} disconnected from game ${gameId}`);
}
}
logger.info(`[GAME SERVER] User ${userId} fully disconnected and cleaned up`);
} else {
logger.warn(`[GAME SERVER] Unknown socket ${socket.id} disconnected without user mapping`);
}
} catch (error) {
logger.error(`[GAME SERVER] Disconnect error: ${error.message}`);
}
}
// Server management methods
getServerStatus() {
const gameInstances = this.gameSystem.getAllGameInstances();
return {
activeServers: gameInstances.length,
connectedPlayers: this.connectedPlayers.size,
};
}
getConnectedUsers() {
return Array.from(this.connectedUsers.keys());
}
getUserCount() {
return this.connectedUsers.size;
}
broadcastToAll(event, data) {
this.io.emit(event, data);
}
broadcastToServer(serverId, event, data) {
this.io.to(`game_${serverId}`).emit(event, data);
}
}
module.exports = SocketHandlers;