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(); | |