import { getToken } from '@/utils/auth' let ws = null let reconnectTimer = null let manualClose = false let currentOnMessage = null let retryCount = 0 const WS_PATH = '/ws' const MAX_RECONNECT_DELAY = 30000 function getWsBaseUrl() { const baseApi = import.meta.env.VITE_APP_BASE_API || '' if (/^https?:\/\//.test(baseApi)) { return baseApi.replace(/^http/, 'ws').replace(/\/$/, '') } const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' return `${protocol}//${window.location.host}${baseApi}`.replace(/\/$/, '') } function buildWsUrl() { const url = new URL(`${getWsBaseUrl()}${WS_PATH}`) const token = getToken() if (token) { url.searchParams.set('token', token) } return url.toString() } export function connectWs(onMessage) { if (!getToken()) { console.warn('[WebSocket] missing token') return } currentOnMessage = onMessage manualClose = false clearReconnectTimer() if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { return } ws = new WebSocket(buildWsUrl()) ws.onopen = () => { retryCount = 0 console.log('[WebSocket] connected') } ws.onmessage = (event) => { let data = event.data try { data = JSON.parse(event.data) } catch (e) { // Keep plain text messages as-is. } currentOnMessage && currentOnMessage(data, event) } ws.onclose = () => { console.log('[WebSocket] closed') ws = null if (!manualClose) { reconnectTimer = setTimeout(() => { console.log('[WebSocket] reconnecting') connectWs(currentOnMessage) }, getReconnectDelay()) } } ws.onerror = (error) => { console.error('[WebSocket] error', error) } } export function closeWs() { manualClose = true retryCount = 0 clearReconnectTimer() if (ws) { ws.close() ws = null } } export function reconnectWs(onMessage = currentOnMessage) { closeWs() manualClose = false connectWs(onMessage) } export function sendWs(data) { if (!ws || ws.readyState !== WebSocket.OPEN) { console.warn('[WebSocket] send failed, socket is not open') return false } ws.send(typeof data === 'string' ? data : JSON.stringify(data)) return true } export function getWsState() { return ws ? ws.readyState : WebSocket.CLOSED } function getReconnectDelay() { retryCount += 1 return Math.min(5000 * retryCount, MAX_RECONNECT_DELAY) } function clearReconnectTimer() { if (reconnectTimer) { clearTimeout(reconnectTimer) reconnectTimer = null } }