Shatteredvoid/src/config/websocket.js
MegaProxy d41d1e8125 feat: implement complete Phase 2 frontend foundation with React 18
Major milestone: Frontend implementation complete for Shattered Void MMO

FRONTEND IMPLEMENTATION:
- React 18 + TypeScript + Vite development environment
- Tailwind CSS with custom dark theme for sci-fi aesthetic
- Zustand state management with authentication persistence
- Socket.io WebSocket client with auto-reconnection
- Protected routing with authentication guards
- Responsive design with mobile-first approach

AUTHENTICATION SYSTEM:
- Login/register forms with comprehensive validation
- JWT token management with localStorage persistence
- Password strength validation and user feedback
- Protected routes and authentication guards

CORE GAME INTERFACE:
- Colony management dashboard with real-time updates
- Resource display with live production tracking
- WebSocket integration for real-time game events
- Navigation with connection status indicator
- Toast notifications for user feedback

BACKEND ENHANCEMENTS:
- Complete Research System with technology tree (23 technologies)
- Fleet Management System with ship designs and movement
- Enhanced Authentication with email verification and password reset
- Complete game tick integration for all systems
- Advanced WebSocket events for real-time updates

ARCHITECTURE FEATURES:
- Type-safe TypeScript throughout
- Component-based architecture with reusable UI elements
- API client with request/response interceptors
- Error handling and loading states
- Performance optimized builds with code splitting

Phase 2 Status: Frontend foundation complete (Week 1-2 objectives met)
Ready for: Colony management, fleet operations, research interface
Next: Enhanced gameplay features and admin interface

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-02 18:36:06 +00:00

321 lines
8.3 KiB
JavaScript

/**
* WebSocket Configuration and Connection Management
* Handles Socket.IO server initialization and connection management
*/
const { Server } = require('socket.io');
const logger = require('../utils/logger');
// Configuration
const WEBSOCKET_CONFIG = {
cors: {
origin: process.env.WEBSOCKET_CORS_ORIGIN?.split(',') || ['http://localhost:3000', 'http://localhost:3001'],
methods: ['GET', 'POST'],
credentials: true,
},
pingTimeout: parseInt(process.env.WEBSOCKET_PING_TIMEOUT) || 20000,
pingInterval: parseInt(process.env.WEBSOCKET_PING_INTERVAL) || 25000,
maxHttpBufferSize: parseInt(process.env.WEBSOCKET_MAX_BUFFER_SIZE) || 1e6, // 1MB
transports: ['websocket', 'polling'],
allowEIO3: true,
compression: true,
httpCompression: true,
};
let io = null;
let connectionCount = 0;
const connectedClients = new Map();
/**
* Initialize WebSocket server
* @param {Object} server - HTTP server instance
* @returns {Promise<Object>} Socket.IO server instance
*/
async function initializeWebSocket(server) {
try {
if (io) {
logger.info('WebSocket server already initialized');
return io;
}
// Create Socket.IO server
io = new Server(server, WEBSOCKET_CONFIG);
// Set up middleware for authentication and logging
io.use(async (socket, next) => {
const correlationId = socket.handshake.query.correlationId || require('uuid').v4();
socket.correlationId = correlationId;
logger.info('WebSocket connection attempt', {
correlationId,
socketId: socket.id,
ip: socket.handshake.address,
userAgent: socket.handshake.headers['user-agent'],
});
next();
});
// Connection event handler
io.on('connection', (socket) => {
connectionCount++;
connectedClients.set(socket.id, {
connectedAt: new Date(),
ip: socket.handshake.address,
userAgent: socket.handshake.headers['user-agent'],
playerId: null, // Will be set after authentication
rooms: new Set(),
});
logger.info('WebSocket client connected', {
correlationId: socket.correlationId,
socketId: socket.id,
totalConnections: connectionCount,
ip: socket.handshake.address,
});
// Set up event handlers
setupSocketEventHandlers(socket);
// Handle disconnection
socket.on('disconnect', (reason) => {
connectionCount--;
const clientInfo = connectedClients.get(socket.id);
connectedClients.delete(socket.id);
logger.info('WebSocket client disconnected', {
correlationId: socket.correlationId,
socketId: socket.id,
reason,
totalConnections: connectionCount,
playerId: clientInfo?.playerId,
connectionDuration: clientInfo ? Date.now() - clientInfo.connectedAt : 0,
});
});
// Handle connection errors
socket.on('error', (error) => {
logger.error('WebSocket connection error', {
correlationId: socket.correlationId,
socketId: socket.id,
error: error.message,
stack: error.stack,
});
});
});
// Server-level error handling
io.engine.on('connection_error', (error) => {
logger.error('WebSocket connection error:', {
message: error.message,
code: error.code,
context: error.context,
});
});
logger.info('WebSocket server initialized successfully', {
maxConnections: process.env.WEBSOCKET_MAX_CONNECTIONS || 'unlimited',
pingTimeout: WEBSOCKET_CONFIG.pingTimeout,
pingInterval: WEBSOCKET_CONFIG.pingInterval,
});
return io;
} catch (error) {
logger.error('Failed to initialize WebSocket server:', error);
throw error;
}
}
/**
* Set up event handlers for individual socket connections
* @param {Object} socket - Socket.IO socket instance
*/
function setupSocketEventHandlers(socket) {
// Player authentication
socket.on('authenticate', async (data) => {
try {
logger.info('WebSocket authentication attempt', {
correlationId: socket.correlationId,
socketId: socket.id,
playerId: data?.playerId,
});
// TODO: Implement JWT token validation
// For now, just acknowledge
socket.emit('authenticated', {
success: true,
message: 'Authentication successful',
});
// Update client information
if (connectedClients.has(socket.id)) {
connectedClients.get(socket.id).playerId = data?.playerId;
}
} catch (error) {
logger.error('WebSocket authentication error', {
correlationId: socket.correlationId,
socketId: socket.id,
error: error.message,
});
socket.emit('authentication_error', {
success: false,
message: 'Authentication failed',
});
}
});
// Join room (for game features like galaxy regions, player groups, etc.)
socket.on('join_room', (roomName) => {
if (typeof roomName !== 'string' || roomName.length > 50) {
socket.emit('error', { message: 'Invalid room name' });
return;
}
socket.join(roomName);
const clientInfo = connectedClients.get(socket.id);
if (clientInfo) {
clientInfo.rooms.add(roomName);
}
logger.info('Client joined room', {
correlationId: socket.correlationId,
socketId: socket.id,
room: roomName,
playerId: clientInfo?.playerId,
});
socket.emit('room_joined', { room: roomName });
});
// Leave room
socket.on('leave_room', (roomName) => {
socket.leave(roomName);
const clientInfo = connectedClients.get(socket.id);
if (clientInfo) {
clientInfo.rooms.delete(roomName);
}
logger.info('Client left room', {
correlationId: socket.correlationId,
socketId: socket.id,
room: roomName,
playerId: clientInfo?.playerId,
});
socket.emit('room_left', { room: roomName });
});
// Ping/pong for connection testing
socket.on('ping', () => {
socket.emit('pong', { timestamp: Date.now() });
});
// Generic message handler (for debugging)
socket.on('message', (data) => {
logger.debug('WebSocket message received', {
correlationId: socket.correlationId,
socketId: socket.id,
data: typeof data === 'object' ? JSON.stringify(data) : data,
});
});
}
/**
* Get WebSocket server instance
* @returns {Object|null} Socket.IO server instance
*/
function getWebSocketServer() {
return io;
}
/**
* Get connection statistics
* @returns {Object} Connection statistics
*/
function getConnectionStats() {
return {
totalConnections: connectionCount,
authenticatedConnections: Array.from(connectedClients.values())
.filter(client => client.playerId).length,
anonymousConnections: Array.from(connectedClients.values())
.filter(client => !client.playerId).length,
rooms: io ? Array.from(io.sockets.adapter.rooms.keys()) : [],
};
}
/**
* Broadcast message to all connected clients
* @param {string} event - Event name
* @param {Object} data - Data to broadcast
*/
function broadcastToAll(event, data) {
if (!io) {
logger.warn('Attempted to broadcast but WebSocket server not initialized');
return;
}
io.emit(event, data);
logger.info('Broadcast sent to all clients', {
event,
recipientCount: connectionCount,
});
}
/**
* Broadcast message to specific room
* @param {string} room - Room name
* @param {string} event - Event name
* @param {Object} data - Data to broadcast
*/
function broadcastToRoom(room, event, data) {
if (!io) {
logger.warn('Attempted to broadcast to room but WebSocket server not initialized');
return;
}
io.to(room).emit(event, data);
logger.info('Broadcast sent to room', {
room,
event,
recipientCount: io.sockets.adapter.rooms.get(room)?.size || 0,
});
}
/**
* Close WebSocket server gracefully
* @returns {Promise<void>}
*/
async function closeWebSocket() {
if (!io) return;
try {
// Disconnect all clients
io.disconnectSockets();
// Close server
io.close();
io = null;
connectionCount = 0;
connectedClients.clear();
logger.info('WebSocket server closed gracefully');
} catch (error) {
logger.error('Error closing WebSocket server:', error);
throw error;
}
}
module.exports = {
initializeWebSocket,
getWebSocketServer,
getConnectionStats,
broadcastToAll,
broadcastToRoom,
closeWebSocket,
};