UNPKG

@cks-systems/manifest-sdk

Version:
113 lines (95 loc) 3.11 kB
import WebSocket from 'ws'; /** * Manages WebSocket server with client heartbeat functionality */ export class WebSocketManager { private wss: WebSocket.Server; private clientHeartbeats: Map<WebSocket, NodeJS.Timeout> = new Map(); private heartbeatInterval: number; constructor(port: number, heartbeatInterval: number = 30000) { this.heartbeatInterval = heartbeatInterval; this.wss = new WebSocket.Server({ port }); this.wss.on('connection', (ws: WebSocket) => { console.log('New client connected'); // Start heartbeat for this client this.startClientHeartbeat(ws); ws.on('message', (message: string) => { console.log(`Received message: ${message}`); }); ws.on('pong', () => { // Client is still alive, reset the heartbeat timer this.resetClientHeartbeat(ws); }); ws.on('close', () => { console.log('Client disconnected'); this.stopClientHeartbeat(ws); }); ws.on('error', (error) => { console.error('WebSocket error:', error); this.stopClientHeartbeat(ws); }); }); } /** * Broadcast a message to all connected clients */ public broadcast(message: string): void { this.wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(message); } }); } /** * Close the WebSocket server and clean up */ public close(): void { // Clean up all heartbeats before closing this.clientHeartbeats.forEach((timeout, ws) => { clearTimeout(timeout); ws.close(); }); this.clientHeartbeats.clear(); this.wss.close(); } private startClientHeartbeat(ws: WebSocket): void { const timeout = setTimeout(() => { console.log('Client heartbeat timeout, closing connection'); ws.terminate(); this.clientHeartbeats.delete(ws); }, this.heartbeatInterval * 2); // Wait for 2x heartbeat interval before considering dead this.clientHeartbeats.set(ws, timeout); // Send initial ping if (ws.readyState === WebSocket.OPEN) { ws.ping(); } } private resetClientHeartbeat(ws: WebSocket): void { // Clear existing timeout const existingTimeout = this.clientHeartbeats.get(ws); if (existingTimeout) { clearTimeout(existingTimeout); } // Set new timeout and send next ping const timeout = setTimeout(() => { if (ws.readyState === WebSocket.OPEN) { ws.ping(); // Set another timeout for the pong response const pongTimeout = setTimeout(() => { console.log('Client pong timeout, closing connection'); ws.terminate(); this.clientHeartbeats.delete(ws); }, this.heartbeatInterval); this.clientHeartbeats.set(ws, pongTimeout); } }, this.heartbeatInterval); this.clientHeartbeats.set(ws, timeout); } private stopClientHeartbeat(ws: WebSocket): void { const timeout = this.clientHeartbeats.get(ws); if (timeout) { clearTimeout(timeout); this.clientHeartbeats.delete(ws); } } }