export type WebSocketMessage = { type: string; payload: Record; timestamp?: number; }; export type MessageHandler = (message: WebSocketMessage) => void; export type ConnectionHandler = (connected: boolean) => void; export class WebSocketService { private ws: WebSocket | null = null; private messageHandlers = new Set(); private connectionHandlers = new Set(); private reconnectTimer: ReturnType | null = null; private url: string; constructor() { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; this.url = `${protocol}//${window.location.host}/ws`; } connect(): void { if (this.ws?.readyState === WebSocket.OPEN) return; this.ws = new WebSocket(this.url); this.ws.onopen = () => { this.notifyConnection(true); if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } }; this.ws.onmessage = (event) => { try { const message = JSON.parse(event.data) as WebSocketMessage; this.notifyMessage(message); } catch (error) { console.error("Error parsing WebSocket message:", error); } }; this.ws.onerror = (error) => { console.error("WebSocket error:", error); }; this.ws.onclose = () => { this.notifyConnection(false); this.scheduleReconnect(); }; } disconnect(): void { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } if (this.ws) { this.ws.close(); this.ws = null; } } send(message: WebSocketMessage): void { if (this.ws?.readyState !== WebSocket.OPEN) { throw new Error("WebSocket not connected"); } this.ws.send(JSON.stringify(message)); } isConnected(): boolean { return this.ws?.readyState === WebSocket.OPEN; } onMessage(handler: MessageHandler): () => void { this.messageHandlers.add(handler); return () => this.messageHandlers.delete(handler); } onConnection(handler: ConnectionHandler): () => void { this.connectionHandlers.add(handler); return () => this.connectionHandlers.delete(handler); } private notifyMessage(message: WebSocketMessage): void { this.messageHandlers.forEach((handler) => handler(message)); } private notifyConnection(connected: boolean): void { this.connectionHandlers.forEach((handler) => handler(connected)); } private scheduleReconnect(): void { if (!this.reconnectTimer) { this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; this.connect(); }, 3000); } } } export const websocketService = new WebSocketService();