import { PROTOCOL_VERSION, type ClientMessage, type DeltaMsg, type ServerMessage, type SnapshotMsg, type WelcomeMsg } from '@idle/shared'; export type NetStatus = 'connecting' | 'online' | 'reconnecting'; export function wsUrl(): string { const fromEnv = import.meta.env.VITE_WS_URL; if (typeof fromEnv === 'string' && fromEnv.length > 0) return fromEnv; // сборка раздаётся самим сервером — тот же origin const scheme = location.protocol === 'https:' ? 'wss' : 'ws'; return `${scheme}://${location.host}/ws`; } export interface ConnectHandlers { onStatus: (status: NetStatus) => void; onWelcome: (msg: WelcomeMsg) => void; onSnapshot: (msg: SnapshotMsg) => void; onDelta: (msg: DeltaMsg) => void; } /** Подключение к миру с автопереподключением; возвращает функцию закрытия. */ export function connectWorld(handlers: ConnectHandlers): () => void { let ws: WebSocket | null = null; let disposed = false; let attempt = 0; const open = (): void => { if (disposed) return; handlers.onStatus(attempt === 0 ? 'connecting' : 'reconnecting'); ws = new WebSocket(wsUrl()); ws.onopen = () => { attempt = 0; const hello: ClientMessage = { t: 'hello', v: PROTOCOL_VERSION }; ws?.send(JSON.stringify(hello)); }; ws.onmessage = (ev) => { let msg: ServerMessage; try { msg = JSON.parse(String(ev.data)) as ServerMessage; } catch { return; } if (msg.t === 'welcome') { handlers.onStatus('online'); handlers.onWelcome(msg); } else if (msg.t === 'snapshot') { handlers.onSnapshot(msg); } else if (msg.t === 'delta') { handlers.onDelta(msg); } }; ws.onclose = () => { if (disposed) return; attempt += 1; setTimeout(open, Math.min(500 * 2 ** Math.min(attempt, 4), 8000)); }; ws.onerror = () => ws?.close(); }; open(); return () => { disposed = true; ws?.close(); }; }