diff options
Diffstat (limited to 'packages/frontend/src/lib/ws.svelte.ts')
| -rw-r--r-- | packages/frontend/src/lib/ws.svelte.ts | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/packages/frontend/src/lib/ws.svelte.ts b/packages/frontend/src/lib/ws.svelte.ts new file mode 100644 index 0000000..76c7ef5 --- /dev/null +++ b/packages/frontend/src/lib/ws.svelte.ts @@ -0,0 +1,89 @@ +import { config } from "./config.js"; +import type { AgentEvent, ConnectionStatus } from "./types.js"; + +type EventCallback = (event: AgentEvent) => void; + +function createWebSocketClient(url: string) { + let connectionStatus: ConnectionStatus = $state("disconnected"); + let ws: WebSocket | null = null; + let reconnectDelay = 1000; + let reconnectTimer: ReturnType<typeof setTimeout> | null = null; + let manualDisconnect = false; + const callbacks: EventCallback[] = []; + + function connect() { + if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { + return; + } + manualDisconnect = false; + connectionStatus = "connecting"; + ws = new WebSocket(url); + + ws.onopen = () => { + connectionStatus = "connected"; + reconnectDelay = 1000; + }; + + ws.onmessage = (event: MessageEvent) => { + try { + const data = JSON.parse(event.data as string) as AgentEvent; + for (const cb of callbacks) { + cb(data); + } + } catch { + // ignore malformed messages + } + }; + + ws.onclose = () => { + connectionStatus = "disconnected"; + ws = null; + if (!manualDisconnect) { + scheduleReconnect(); + } + }; + + ws.onerror = () => { + ws?.close(); + }; + } + + function scheduleReconnect() { + if (reconnectTimer) clearTimeout(reconnectTimer); + reconnectTimer = setTimeout(() => { + reconnectTimer = null; + connect(); + }, reconnectDelay); + reconnectDelay = Math.min(reconnectDelay * 2, 10000); + } + + function disconnect() { + manualDisconnect = true; + if (reconnectTimer) { + clearTimeout(reconnectTimer); + reconnectTimer = null; + } + ws?.close(); + ws = null; + connectionStatus = "disconnected"; + } + + function onEvent(callback: EventCallback) { + callbacks.push(callback); + return () => { + const idx = callbacks.indexOf(callback); + if (idx !== -1) callbacks.splice(idx, 1); + }; + } + + return { + get connectionStatus() { + return connectionStatus; + }, + connect, + disconnect, + onEvent, + }; +} + +export const wsClient = createWebSocketClient(config.wsUrl); |
