2026-06-26 17:48:22 +03:00

137 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};
};