Spaces:
Sleeping
Sleeping
| import { WebSocketServer, WebSocket } from 'ws'; | |
| import { IncomingMessage } from 'http'; | |
| import { parse } from 'url'; | |
| interface ExtendedWebSocket extends WebSocket { | |
| tenantId?: string; | |
| sessionToken?: string; | |
| isAlive?: boolean; | |
| } | |
| export function setupWebSocket(wss: WebSocketServer) { | |
| const clients = new Map<string, ExtendedWebSocket>(); | |
| wss.on('connection', (ws: ExtendedWebSocket, req: IncomingMessage) => { | |
| // Parse query parameters | |
| const query = parse(req.url || '', true).query; | |
| const tenantId = query.tenantId as string; | |
| const sessionToken = query.sessionToken as string; | |
| if (!tenantId || !sessionToken) { | |
| ws.close(1008, 'Missing tenantId or sessionToken'); | |
| return; | |
| } | |
| // Set up connection | |
| ws.tenantId = tenantId; | |
| ws.sessionToken = sessionToken; | |
| ws.isAlive = true; | |
| // Store client connection | |
| const clientKey = `${tenantId}:${sessionToken}`; | |
| clients.set(clientKey, ws); | |
| console.log(`WebSocket client connected: ${clientKey}`); | |
| // Handle ping/pong for connection health | |
| ws.on('pong', () => { | |
| ws.isAlive = true; | |
| }); | |
| ws.on('message', (data: Buffer) => { | |
| try { | |
| const message = JSON.parse(data.toString()); | |
| handleMessage(ws, message); | |
| } catch (error) { | |
| console.error('Invalid WebSocket message:', error); | |
| ws.send(JSON.stringify({ error: 'Invalid message format' })); | |
| } | |
| }); | |
| ws.on('close', () => { | |
| console.log(`WebSocket client disconnected: ${clientKey}`); | |
| clients.delete(clientKey); | |
| }); | |
| ws.on('error', (error) => { | |
| console.error('WebSocket error:', error); | |
| clients.delete(clientKey); | |
| }); | |
| // Send welcome message | |
| ws.send(JSON.stringify({ | |
| type: 'connection', | |
| message: 'Connected to chat server', | |
| timestamp: new Date().toISOString() | |
| })); | |
| }); | |
| // Health check - ping clients every 30 seconds | |
| const interval = setInterval(() => { | |
| wss.clients.forEach((ws: ExtendedWebSocket) => { | |
| if (ws.isAlive === false) { | |
| return ws.terminate(); | |
| } | |
| ws.isAlive = false; | |
| ws.ping(); | |
| }); | |
| }, 30000); | |
| wss.on('close', () => { | |
| clearInterval(interval); | |
| }); | |
| function handleMessage(ws: ExtendedWebSocket, message: any) { | |
| switch (message.type) { | |
| case 'ping': | |
| ws.send(JSON.stringify({ type: 'pong', timestamp: new Date().toISOString() })); | |
| break; | |
| case 'typing': | |
| // Broadcast typing indicator to other participants (if multi-user chat) | |
| broadcastToSession(ws.tenantId!, ws.sessionToken!, { | |
| type: 'typing', | |
| sender: 'user', | |
| timestamp: new Date().toISOString() | |
| }, ws); | |
| break; | |
| case 'stop_typing': | |
| broadcastToSession(ws.tenantId!, ws.sessionToken!, { | |
| type: 'stop_typing', | |
| sender: 'user', | |
| timestamp: new Date().toISOString() | |
| }, ws); | |
| break; | |
| default: | |
| ws.send(JSON.stringify({ error: 'Unknown message type' })); | |
| } | |
| } | |
| function broadcastToSession(tenantId: string, sessionToken: string, message: any, sender?: WebSocket) { | |
| const clientKey = `${tenantId}:${sessionToken}`; | |
| const client = clients.get(clientKey); | |
| if (client && client !== sender && client.readyState === WebSocket.OPEN) { | |
| client.send(JSON.stringify(message)); | |
| } | |
| } | |
| // Utility function to send message to specific session | |
| function sendToSession(tenantId: string, sessionToken: string, message: any) { | |
| const clientKey = `${tenantId}:${sessionToken}`; | |
| const client = clients.get(clientKey); | |
| if (client && client.readyState === WebSocket.OPEN) { | |
| client.send(JSON.stringify(message)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| return { | |
| broadcastToSession, | |
| sendToSession, | |
| getConnectedClients: () => clients.size | |
| }; | |
| } |