Spaces:
Configuration error
Configuration error
File size: 2,938 Bytes
78013c4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | "use client";
import { useAuthStore } from "@/store/authStore";
type MessageHandler = (data: Record<string, unknown>) => void;
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api/v1";
class WebSocketClient {
private ws: WebSocket | null = null;
private handlers = new Map<string, Set<MessageHandler>>();
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
private pingTimer: ReturnType<typeof setInterval> | null = null;
connect(): void {
if (this.ws?.readyState === WebSocket.OPEN) return;
const token = useAuthStore.getState().token;
if (!token) return;
const wsUrl = API_BASE_URL.replace("http://", "ws://").replace("https://", "wss://");
const socket = new WebSocket(`${wsUrl}/ws/connect`);
this.ws = socket;
socket.onopen = () => {
if (this.ws !== socket) {
socket.close();
return;
}
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: "auth", token }));
this.reconnectAttempts = 0;
this.startPing();
}
};
socket.onmessage = (event) => {
if (this.ws !== socket) return;
try {
const data = JSON.parse(event.data) as Record<string, unknown>;
if (data.type === "pong") return;
const typeHandlers = this.handlers.get(String(data.type));
typeHandlers?.forEach((handler) => handler(data));
} catch {
// ignore parse errors
}
};
socket.onclose = () => {
if (this.ws !== socket) return;
this.stopPing();
this.scheduleReconnect();
};
socket.onerror = () => {
socket.close();
};
}
on(type: string, handler: MessageHandler): () => void {
if (!this.handlers.has(type)) {
this.handlers.set(type, new Set());
}
this.handlers.get(type)!.add(handler);
return () => this.handlers.get(type)?.delete(handler);
}
disconnect(): void {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
this.stopPing();
this.ws?.close();
this.ws = null;
this.reconnectAttempts = 0;
}
private scheduleReconnect(): void {
if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
this.reconnectAttempts++;
this.reconnectTimer = setTimeout(() => this.connect(), 3000 * this.reconnectAttempts);
}
private startPing(): void {
this.pingTimer = setInterval(() => {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: "ping" }));
}
}, 30000);
}
private stopPing(): void {
if (this.pingTimer) clearInterval(this.pingTimer);
this.pingTimer = null;
}
}
let instance: WebSocketClient | null = null;
export function getWebSocket(): WebSocketClient {
if (!instance) {
instance = new WebSocketClient();
}
return instance;
}
|