137 lines
3.7 KiB
JavaScript
137 lines
3.7 KiB
JavaScript
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,
|
||
};
|
||
};
|