beeta/frontend/src/lib/websocket.js

92 lines
2.6 KiB
JavaScript
Raw Normal View History

2025-08-03 21:53:15 -04:00
let ws = null;
let reconnectTimeout = null;
2026-01-05 22:54:27 -05:00
let reconnectAttempts = 0;
const MAX_RECONNECT_ATTEMPTS = 10;
const BASE_RECONNECT_DELAY = 1000; // 1 second
const MAX_RECONNECT_DELAY = 30000; // 30 seconds
2025-08-03 21:53:15 -04:00
const WS_URL = import.meta.env.VITE_WS_URL || 'ws://localhost/ws';
2026-01-05 22:54:27 -05:00
/**
* Calculate exponential backoff delay with jitter
* @returns {number} Delay in milliseconds
*/
function getReconnectDelay() {
// Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s, 30s...
const exponentialDelay = BASE_RECONNECT_DELAY * Math.pow(2, reconnectAttempts);
const cappedDelay = Math.min(exponentialDelay, MAX_RECONNECT_DELAY);
// Add random jitter (±20%) to prevent thundering herd
const jitter = cappedDelay * 0.2 * (Math.random() - 0.5);
return Math.floor(cappedDelay + jitter);
}
2025-08-03 21:53:15 -04:00
export function connectWebSocket(onMessage) {
if (ws?.readyState === WebSocket.OPEN) return;
// WebSocket doesn't support withCredentials, but cookies are sent automatically
// on same-origin requests
ws = new WebSocket(`${WS_URL}/stream`);
ws.onopen = () => {
console.log('WebSocket connected');
2026-01-05 22:54:27 -05:00
reconnectAttempts = 0; // Reset on successful connection
2025-08-03 21:53:15 -04:00
ws?.send(JSON.stringify({ type: 'subscribe' }));
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
onMessage(data);
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('WebSocket disconnected');
2026-01-05 22:54:27 -05:00
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
console.error('Max reconnect attempts reached, giving up');
return;
}
const delay = getReconnectDelay();
reconnectAttempts++;
console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
2025-08-03 21:53:15 -04:00
reconnectTimeout = setTimeout(() => {
connectWebSocket(onMessage);
2026-01-05 22:54:27 -05:00
}, delay);
2025-08-03 21:53:15 -04:00
};
}
export function disconnectWebSocket() {
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
reconnectTimeout = null;
}
if (ws) {
ws.close();
ws = null;
}
2026-01-05 22:54:27 -05:00
reconnectAttempts = 0; // Reset attempts on explicit disconnect
}
/**
* Reset reconnect attempts counter (call this to allow reconnection after max attempts)
*/
export function resetReconnectAttempts() {
reconnectAttempts = 0;
2025-08-03 21:53:15 -04:00
}
export function sendMessage(message) {
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(message));
}
}