Spaces:
Running
Running
| export type WebSocketMessage = { | |
| type: string; | |
| payload: Record<string, unknown>; | |
| 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<MessageHandler>(); | |
| private connectionHandlers = new Set<ConnectionHandler>(); | |
| private reconnectTimer: ReturnType<typeof setTimeout> | 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(); | |