summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/ws.svelte.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/lib/ws.svelte.ts')
-rw-r--r--packages/frontend/src/lib/ws.svelte.ts89
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);