UNPKG

@utsp/network-server

Version:

UTSP Network Server - Server-side communication adapters

572 lines (568 loc) 17.8 kB
import { INetworkServer, NetworkServerOptions, ClientInfo, ServerEventHandler, ConnectionHandler, DisconnectionHandler } from '@utsp/types'; export { AnyNetworkMessage, ChatMessage, ClientInfo, ConnectionHandler, DisconnectionHandler, ErrorMessage, INetworkServer, InputMessage, JoinMessage, JoinResponseMessage, LeaveMessage, LoadMessage, MessageType, NetworkMessage, NetworkServerOptions, PingMessage, PongMessage, ServerEventHandler, UpdateMessage } from '@utsp/types'; /** * Socket.IO Server Implementation * * Production-ready Socket.IO server for UTSP real-time networking. * Implements the INetworkServer interface with room management, client tracking, * and comprehensive event handling. Node.js only. * * @example Basic Server * ```typescript * import { SocketIOServer } from '@utsp/network-server'; * * const server = new SocketIOServer({ * port: 3000, * host: '0.0.0.0', * debug: false * }); * * await server.start(); * console.log('Server running on port 3000'); * * server.onConnect((clientId) => { * console.log('Client connected:', clientId); * server.sendToClient(clientId, 'welcome', { message: 'Hello!' }); * }); * * server.on('input', (clientId, data) => { * console.log('Input from', clientId, data); * server.broadcast('update', data); * }); * ``` * * @example With Rooms * ```typescript * server.onConnect((clientId) => { * server.joinRoom(clientId, 'lobby'); * server.sendToRoom('lobby', 'playerJoined', { clientId }); * }); * * server.on('joinGame', (clientId, { gameId }) => { * server.leaveRoom(clientId, 'lobby'); * server.joinRoom(clientId, `game-${gameId}`); * }); * ``` * * @example Client Data Storage * ```typescript * server.onConnect((clientId) => { * server.setClientData(clientId, 'username', 'Player1'); * server.setClientData(clientId, 'score', 0); * }); * * server.on('updateScore', (clientId, { points }) => { * const currentScore = server.getClientData(clientId, 'score') || 0; * server.setClientData(clientId, 'score', currentScore + points); * }); * ``` */ /** * Socket.IO server implementation for UTSP network communication * * Features: * - Connection management with max connections limit * - Room-based broadcasting * - Client data storage per connection * - Volatile messaging for high-frequency updates * - Comprehensive event handling system * - Connection/disconnection lifecycle hooks * - Server statistics tracking * - CORS configuration * * @implements {INetworkServer} */ declare class SocketIOServer implements INetworkServer { private io; private httpServer; private options; private running; private clients; private clientData; private connectionHandlers; private disconnectionHandlers; private eventHandlers; private stats; /** * Create a new Socket.IO server instance * * @param options - Server configuration options * @throws {Error} If port is invalid or options are malformed * * @example * ```typescript * const server = new SocketIOServer({ * port: 3000, * host: '0.0.0.0', * cors: { origin: '*' }, * maxConnections: 1000, * pingInterval: 25000, * pingTimeout: 5000, * debug: true * }); * ``` */ constructor(options: NetworkServerOptions); /** * Check if server is currently running * * @returns true if server is running and accepting connections * * @example * ```typescript * if (server.isRunning()) { * console.log('Server is active'); * } * ``` */ isRunning(): boolean; /** * Start the server and begin accepting connections * * Creates an HTTP server, initializes Socket.IO, and starts listening * on the configured port and host. * * @returns Promise resolving when server is ready to accept connections * @throws {Error} If server fails to start (port in use, permission denied, etc.) * * @example * ```typescript * try { * await server.start(); * console.log('Server started successfully'); * } catch (error) { * console.error('Failed to start server:', error); * } * ``` */ start(): Promise<void>; /** * Stop the server and disconnect all clients * * Gracefully shuts down the server by disconnecting all clients, * closing Socket.IO, and stopping the HTTP server. * * @returns Promise resolving when server is fully stopped * * @example * ```typescript * await server.stop(); * console.log('Server stopped'); * ``` */ stop(): Promise<void>; /** * Get list of all connected client IDs * * @returns Array of client socket IDs * * @example * ```typescript * const clients = server.getClients(); * console.log(`${clients.length} clients connected`); * ``` */ getClients(): string[]; /** * Get information about a specific client * * @param clientId - Client socket ID * @returns Client information object, or null if client not found * * @example * ```typescript * const info = server.getClientInfo(clientId); * if (info) { * console.log('Client:', info.id); * console.log('Connected at:', new Date(info.connectedAt)); * console.log('Address:', info.address); * console.log('Data:', info.data); * } * ``` */ getClientInfo(clientId: string): ClientInfo | null; /** * Send an event to a specific client * * Sends a message to a single client identified by their socket ID. * Message is silently dropped if client is not connected. * * @param clientId - Client socket ID * @param event - Event name * @param data - Event payload (any serializable data) * * @example * ```typescript * server.sendToClient(clientId, 'notification', { * type: 'info', * message: 'You won!' * }); * ``` */ sendToClient(clientId: string, event: string, data: any): void; /** * Send volatile message to a specific client * * Sends a "volatile" message that can be dropped if the network is congested. * Perfect for high-frequency updates (game state, animations, dynamic layers) * where missing a frame is acceptable. * * **Note:** Uses `.compress(false)` instead of `.volatile` due to Socket.IO limitations. * This disables compression for better performance with frequent updates. * * @param clientId - Client socket ID * @param event - Event name * @param data - Event payload * * @example * ```typescript * // Send 60 updates per second - some can be dropped * setInterval(() => { * server.sendToClientVolatile(clientId, 'gameState', { * position: player.position, * velocity: player.velocity * }); * }, 16); // ~60 FPS * ``` */ sendToClientVolatile(clientId: string, event: string, data: any): void; /** * Broadcast an event to all connected clients * * Sends a message to every connected client simultaneously. * * @param event - Event name * @param data - Event payload * * @example * ```typescript * server.broadcast('serverMessage', { * type: 'announcement', * message: 'Server restarting in 5 minutes' * }); * ``` */ broadcast(event: string, data: any): void; /** * Broadcast volatile message to all clients * * Sends a volatile message to all clients. Messages can be dropped * if the network is congested. Useful for high-frequency broadcasts. * * @param event - Event name * @param data - Event payload * * @example * ```typescript * // Broadcast game tick (60 times per second) * setInterval(() => { * server.broadcastVolatile('tick', { * timestamp: Date.now(), * frame: frameCount++ * }); * }, 16); * ``` */ broadcastVolatile(event: string, data: any): void; /** * Broadcast to all clients except one * * Sends a message to all connected clients except the specified one. * Useful for broadcasting player actions to other players. * * @param excludeClientId - Client ID to exclude from broadcast * @param event - Event name * @param data - Event payload * * @example * ```typescript * // When a player moves, tell everyone else * server.on('playerMove', (clientId, position) => { * server.broadcastExcept(clientId, 'playerMoved', { * playerId: clientId, * position * }); * }); * ``` */ broadcastExcept(excludeClientId: string, event: string, data: any): void; /** * Send event to all clients in a room * * Broadcasts a message to all clients that have joined the specified room. * * @param room - Room name * @param event - Event name * @param data - Event payload * * @example * ```typescript * // Send message to all players in a game * server.sendToRoom('game-123', 'gameUpdate', { * score: { team1: 10, team2: 8 } * }); * ``` */ sendToRoom(room: string, event: string, data: any): void; /** * Add a client to a room * * Rooms allow grouping clients for targeted broadcasts. A client * can be in multiple rooms simultaneously. * * @param clientId - Client socket ID * @param room - Room name * * @example * ```typescript * // When player joins a game * server.on('joinGame', (clientId, { gameId }) => { * server.joinRoom(clientId, `game-${gameId}`); * server.sendToRoom(`game-${gameId}`, 'playerJoined', { * clientId, * playerCount: server.getRoomClients(`game-${gameId}`).length * }); * }); * ``` */ joinRoom(clientId: string, room: string): void; /** * Remove a client from a room * * @param clientId - Client socket ID * @param room - Room name * * @example * ```typescript * server.on('leaveGame', (clientId) => { * server.leaveRoom(clientId, 'game-123'); * server.joinRoom(clientId, 'lobby'); * }); * ``` */ leaveRoom(clientId: string, room: string): void; /** * Get list of all client IDs in a room * * @param room - Room name * @returns Array of client socket IDs in the room * * @example * ```typescript * const players = server.getRoomClients('game-123'); * console.log(`${players.length} players in game`); * ``` */ getRoomClients(room: string): string[]; /** * Forcefully disconnect a client * * Immediately closes the client's connection. Use sparingly as it * does not allow graceful cleanup on the client side. * * @param clientId - Client socket ID * @param reason - Reason for disconnection (default: 'Server disconnect') * * @example * ```typescript * // Kick idle players * if (isIdle(clientId)) { * server.disconnectClient(clientId, 'Idle timeout'); * } * ``` */ disconnectClient(clientId: string, reason?: string): void; /** * Register an event listener for client messages * * Handles events sent from clients. Multiple handlers can be registered * for the same event. Handlers receive the client ID and event data. * * @param event - Event name to listen for * @param handler - Callback function (clientId, data) => void * * @example * ```typescript * server.on<InputMessage>('input', (clientId, data) => { * console.log('Input from', clientId, data); * // Process input and broadcast to others * server.broadcastExcept(clientId, 'playerAction', data); * }); * * server.on('chat', (clientId, message) => { * const username = server.getClientData(clientId, 'username'); * server.broadcast('chatMessage', { username, message }); * }); * ``` */ on<T = any>(event: string, handler: ServerEventHandler<T>): void; /** * Register a connection handler * * Called whenever a new client successfully connects. Use this to * initialize client state, send welcome messages, etc. * * @param handler - Callback function receiving client ID * * @example * ```typescript * server.onConnect((clientId) => { * console.log('Client connected:', clientId); * server.setClientData(clientId, 'joinedAt', Date.now()); * server.joinRoom(clientId, 'lobby'); * server.sendToClient(clientId, 'welcome', { * message: 'Welcome to the server!', * serverId: 'game-server-1' * }); * }); * ``` */ onConnect(handler: ConnectionHandler): void; /** * Register a disconnection handler * * Called whenever a client disconnects (gracefully or due to error). * Use this for cleanup and notifying other clients. * * @param handler - Callback function receiving client ID and reason * * @example * ```typescript * server.onDisconnect((clientId, reason) => { * console.log('Client disconnected:', clientId, reason); * const username = server.getClientData(clientId, 'username'); * server.broadcast('playerLeft', { username, reason }); * }); * ``` */ onDisconnect(handler: DisconnectionHandler): void; /** * Unregister a specific event handler * * Removes a previously registered handler for an event. The handler * reference must be the exact same function object that was registered. * * @param event - Event name * @param handler - Handler function to remove (must be same reference) * * @example * ```typescript * const handler = (clientId, data) => { ... }; * server.on('input', handler); * // Later... * server.off('input', handler); * ``` */ off<T = any>(event: string, handler: ServerEventHandler<T>): void; /** * Store arbitrary data for a client * * Associates key-value data with a client connection. Data is * automatically cleared when the client disconnects. * * @param clientId - Client socket ID * @param key - Data key * @param value - Data value (any serializable type) * * @example * ```typescript * server.onConnect((clientId) => { * server.setClientData(clientId, 'username', 'Player1'); * server.setClientData(clientId, 'score', 0); * server.setClientData(clientId, 'team', 'red'); * }); * * server.on('scorePoint', (clientId) => { * const score = server.getClientData(clientId, 'score') || 0; * server.setClientData(clientId, 'score', score + 1); * }); * ``` */ setClientData(clientId: string, key: string, value: any): void; /** * Retrieve stored data for a client * * @param clientId - Client socket ID * @param key - Data key * @returns Stored value, or undefined if not found * * @example * ```typescript * const username = server.getClientData(clientId, 'username'); * const score = server.getClientData(clientId, 'score') || 0; * ``` */ getClientData(clientId: string, key: string): any; /** * Get server statistics * * Returns current server metrics including connected clients, * total connections since start, and uptime. * * @returns Object containing server stats * * @example * ```typescript * const stats = server.getStats(); * console.log('Connected:', stats.connectedClients); * console.log('Total connections:', stats.totalConnections); * console.log('Uptime:', Math.floor(stats.uptime / 1000), 'seconds'); * ``` */ getStats(): { connectedClients: number; totalConnections: number; uptime: number; }; /** * Destroy the server and clean up all resources * * Stops the server, disconnects all clients, and clears all handlers. * Server instance cannot be reused after calling destroy(). * * @returns Promise resolving when cleanup is complete * * @example * ```typescript * // Graceful shutdown * process.on('SIGTERM', async () => { * console.log('Shutting down...'); * await server.destroy(); * process.exit(0); * }); * ``` */ destroy(): Promise<void>; /** * Handle new client connection * * Called when a client successfully connects. Checks max connections limit, * stores client data, sets up event handlers, and calls connection handlers. * * @param socket - Socket.IO socket instance * @private */ private handleConnection; /** * Setup event handlers for a client socket * * Registers disconnect handler and all custom event handlers for this client. * Called once when client connects. * * @param socket - Socket.IO socket instance * @private */ private setupClientHandlers; /** * Debug logging utility * * Logs messages when debug mode is enabled. Uses console.warn * to ensure visibility in production environments. * * @param message - Log message * @param data - Optional data to log * @private */ private log; } export { SocketIOServer, SocketIOServer as SocketIOServerType };