import { useEffect, useRef, useCallback, useState } from "react"; export const useWebSocket = (url, onMessage) => { const [isConnected, setIsConnected] = useState(false); const isConnectedRef = useRef(false); const [error, setError] = useState(null); const wsRef = useRef(null); const reconnectTimeoutRef = useRef(null); const reconnectAttempts = useRef(0); useEffect(() => { isConnectedRef.current = isConnected; }, [isConnected]); const connect = useCallback(() => { if (wsRef.current?.readyState === WebSocket.OPEN) return; try { console.log("Connecting to WebSocket:", url); const ws = new WebSocket(url); wsRef.current = ws; ws.onopen = () => { console.log("WebSocket connected successfully"); setIsConnected(true); setError(null); reconnectAttempts.current = 0; }; ws.onmessage = (event) => { try { const data = JSON.parse(event.data); console.log("WebSocket message received:", data); onMessage(data); } catch (err) { console.error("Failed to parse WebSocket message:", err); } }; ws.onerror = (event) => { console.error("WebSocket error:", event); console.error("WebSocket readyState:", ws.readyState); setError("Connection error occurred"); }; ws.onclose = (event) => { console.log( "WebSocket closed:", event.code, event.reason, "Clean:", event.wasClean, ); setIsConnected(false); // Не переподключаемся если закрыто intentionally или с ошибкой авторизации if (event.code === 1008 || event.code === 1000) { console.log( "Connection closed intentionally or auth error, not reconnecting", ); return; } // Reconnect with exponential backoff if (reconnectAttempts.current < 5) { const delay = Math.min( 1000 * Math.pow(2, reconnectAttempts.current), 30000, ); console.log(`Reconnecting in ${delay}ms...`); reconnectTimeoutRef.current = setTimeout(() => { reconnectAttempts.current++; connect(); }, delay); } else { setError("Max reconnection attempts reached"); } }; } catch (err) { console.error("Failed to create WebSocket:", err); setError(err.message); } }, [url, onMessage]); const disconnect = useCallback(() => { if (reconnectTimeoutRef.current) { clearTimeout(reconnectTimeoutRef.current); } if (wsRef.current) { wsRef.current.close(1000, "Intentional disconnect"); wsRef.current = null; } setIsConnected(false); }, []); const sendMessage = useCallback((message) => { if (wsRef.current?.readyState === WebSocket.OPEN) { wsRef.current.send(JSON.stringify(message)); console.log("Message sent:", message); return true; } console.warn( "WebSocket is not connected, readyState:", wsRef.current?.readyState, ); setError("Cannot send message: WebSocket not connected"); return false; }, []); const login = useCallback( (token) => { if (!token) { console.warn("No token provided for login"); return false; } return sendMessage({ event: "user_login", data: { token }, }); }, [sendMessage], ); useEffect(() => { connect(); return () => disconnect(); }, [connect, disconnect]); return { isConnected: isConnectedRef, error, sendMessage, login, disconnect, }; };