/** * WebSocket Plugin for Real-time Alerts * * Broadcasts environment alerts to connected clients. */ import { FastifyInstance } from 'fastify'; import websocket from '@fastify/websocket'; interface AlertMessage { type: 'ALERT' | 'READING' | 'HEARTBEAT'; data: any; timestamp: string; } // Connected clients (storing the raw WebSocket) const clients: Map = new Map(); export async function websocketPlugin(fastify: FastifyInstance) { await fastify.register(websocket); /** * WebSocket endpoint for real-time alerts */ fastify.get('/api/ws/alerts', { websocket: true }, (connection, request) => { const clientId = `${Date.now()}-${Math.random().toString(36).slice(2)}`; // Get the raw WebSocket from the SocketStream const socket = connection.socket; clients.set(clientId, socket); fastify.log.debug(`WebSocket client connected: ${clientId}`); // Send welcome message socket.send(JSON.stringify({ type: 'CONNECTED', clientId, timestamp: new Date().toISOString() })); socket.on('message', (message: Buffer) => { try { const data = JSON.parse(message.toString()); // Handle ping/pong for keepalive if (data.type === 'PING') { socket.send(JSON.stringify({ type: 'PONG', timestamp: new Date().toISOString() })); } } catch { // Ignore invalid messages } }); socket.on('close', () => { clients.delete(clientId); fastify.log.debug(`WebSocket client disconnected: ${clientId}`); }); socket.on('error', (error: Error) => { fastify.log.error(`WebSocket error for ${clientId}: ${error.message}`); clients.delete(clientId); }); }); } /** * Broadcast an alert to all connected clients */ export function broadcastAlert(alert: any): void { const message: AlertMessage = { type: 'ALERT', data: alert, timestamp: new Date().toISOString() }; const payload = JSON.stringify(message); clients.forEach((socket, clientId) => { try { if (socket.readyState === 1) { // OPEN socket.send(payload); } } catch (error) { console.error(`Failed to broadcast to ${clientId}:`, error); } }); } /** * Broadcast a sensor reading update */ export function broadcastReading(reading: any): void { const message: AlertMessage = { type: 'READING', data: reading, timestamp: new Date().toISOString() }; const payload = JSON.stringify(message); clients.forEach((socket, clientId) => { try { if (socket.readyState === 1) { socket.send(payload); } } catch (error) { console.error(`Failed to broadcast reading to ${clientId}:`, error); } }); } /** * Get count of connected clients */ export function getConnectedClientCount(): number { return clients.size; }