127 lines
3.6 KiB
TypeScript
127 lines
3.6 KiB
TypeScript
import { WebSocketMessage } from '../types';
|
||
|
||
export class WebSocketClient {
|
||
private ws: WebSocket | null = null;
|
||
private listeners: Map<string, Set<(data: any) => void>> = new Map();
|
||
private reconnectAttempts = 0;
|
||
private maxReconnectAttempts = 5;
|
||
private reconnectDelay = 3000;
|
||
|
||
private getWebSocketUrl(): string {
|
||
// Если задан VITE_WS_URL, используем его
|
||
if (import.meta.env.VITE_WS_URL) {
|
||
return import.meta.env.VITE_WS_URL;
|
||
}
|
||
|
||
// Иначе определяем автоматически на основе текущего location
|
||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||
const host = window.location.host;
|
||
return `${protocol}//${host}`;
|
||
}
|
||
|
||
connect() {
|
||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const wsUrl = this.getWebSocketUrl();
|
||
this.ws = new WebSocket(`${wsUrl}/ws/reviews`);
|
||
|
||
this.ws.onopen = () => {
|
||
console.log('WebSocket connected');
|
||
this.reconnectAttempts = 0;
|
||
this.notifyListeners('connection', { status: 'connected' });
|
||
};
|
||
|
||
this.ws.onmessage = (event) => {
|
||
try {
|
||
const message: WebSocketMessage = JSON.parse(event.data);
|
||
this.notifyListeners(message.type, message);
|
||
} catch (error) {
|
||
console.error('Failed to parse WebSocket message:', error);
|
||
}
|
||
};
|
||
|
||
this.ws.onerror = (error) => {
|
||
console.error('WebSocket error:', error);
|
||
this.notifyListeners('connection', { status: 'error', error });
|
||
};
|
||
|
||
this.ws.onclose = () => {
|
||
console.log('WebSocket disconnected');
|
||
this.notifyListeners('connection', { status: 'disconnected' });
|
||
this.reconnect();
|
||
};
|
||
} catch (error) {
|
||
console.error('Failed to create WebSocket:', error);
|
||
this.reconnect();
|
||
}
|
||
}
|
||
|
||
disconnect() {
|
||
if (this.ws) {
|
||
this.ws.close();
|
||
this.ws = null;
|
||
}
|
||
}
|
||
|
||
private reconnect() {
|
||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||
this.reconnectAttempts++;
|
||
console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
|
||
setTimeout(() => this.connect(), this.reconnectDelay);
|
||
}
|
||
}
|
||
|
||
on(event: string, callback: (data: any) => void) {
|
||
if (!this.listeners.has(event)) {
|
||
this.listeners.set(event, new Set());
|
||
}
|
||
this.listeners.get(event)!.add(callback);
|
||
|
||
// Return unsubscribe function
|
||
return () => {
|
||
const callbacks = this.listeners.get(event);
|
||
if (callbacks) {
|
||
callbacks.delete(callback);
|
||
}
|
||
};
|
||
}
|
||
|
||
private notifyListeners(event: string, data: any) {
|
||
const callbacks = this.listeners.get(event);
|
||
if (callbacks) {
|
||
callbacks.forEach((callback) => callback(data));
|
||
}
|
||
}
|
||
|
||
send(message: any) {
|
||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||
this.ws.send(JSON.stringify(message));
|
||
} else {
|
||
console.warn('WebSocket is not connected');
|
||
}
|
||
}
|
||
}
|
||
|
||
// Create singleton instance
|
||
export const wsClient = new WebSocketClient();
|
||
|
||
// Export helper to get WS URL
|
||
export const getWebSocketUrl = (): string => {
|
||
// Если задан VITE_WS_URL, используем его
|
||
if (import.meta.env.VITE_WS_URL) {
|
||
return import.meta.env.VITE_WS_URL;
|
||
}
|
||
|
||
// Иначе определяем автоматически на основе текущего location
|
||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||
const host = window.location.host;
|
||
return `${protocol}//${host}`;
|
||
};
|
||
|
||
// Export WS_URL for direct usage
|
||
export const WS_URL = `${getWebSocketUrl()}/ws/reviews`;
|
||
|