- CSP: Allow WebSocket/HTTP connections to any domain (for production) - Nakama: Detect host/SSL from browser location instead of hardcoded localhost - WebSocket: Dynamic protocol/host detection for stream and watch sync - HLS/LLHLS/WebRTC: Dynamic URLs in live page and stream components - RTMP/SRT: Show actual domain in my-realms settings page - Forums: Use numeric forum ID for banner/title-color API calls 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
104 lines
No EOL
3 KiB
JavaScript
104 lines
No EOL
3 KiB
JavaScript
import { browser } from '$app/environment';
|
|
|
|
let ws = null;
|
|
let reconnectTimeout = null;
|
|
let reconnectAttempts = 0;
|
|
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
const BASE_RECONNECT_DELAY = 1000; // 1 second
|
|
const MAX_RECONNECT_DELAY = 30000; // 30 seconds
|
|
|
|
// Dynamically detect WebSocket URL from browser location
|
|
// This ensures production uses wss:// and the correct host
|
|
function getWebSocketURL() {
|
|
if (!browser) {
|
|
return import.meta.env.VITE_WS_URL || 'ws://localhost/ws';
|
|
}
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
return `${protocol}//${window.location.host}/ws`;
|
|
}
|
|
|
|
const WS_URL = getWebSocketURL();
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
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');
|
|
reconnectAttempts = 0; // Reset on successful connection
|
|
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');
|
|
|
|
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})`);
|
|
reconnectTimeout = setTimeout(() => {
|
|
connectWebSocket(onMessage);
|
|
}, delay);
|
|
};
|
|
}
|
|
|
|
export function disconnectWebSocket() {
|
|
if (reconnectTimeout) {
|
|
clearTimeout(reconnectTimeout);
|
|
reconnectTimeout = null;
|
|
}
|
|
|
|
if (ws) {
|
|
ws.close();
|
|
ws = null;
|
|
}
|
|
|
|
reconnectAttempts = 0; // Reset attempts on explicit disconnect
|
|
}
|
|
|
|
/**
|
|
* Reset reconnect attempts counter (call this to allow reconnection after max attempts)
|
|
*/
|
|
export function resetReconnectAttempts() {
|
|
reconnectAttempts = 0;
|
|
}
|
|
|
|
export function sendMessage(message) {
|
|
if (ws?.readyState === WebSocket.OPEN) {
|
|
ws.send(JSON.stringify(message));
|
|
}
|
|
} |