From 5cb893af62e27aaa3e8055b878e51cad5599de09 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Sun, 25 Oct 2026 04:42:25 +0100 Subject: [PATCH] feat: connect client to realtime WebSocket --- src/App.svelte | 34 +++++++++++++++++++++++++++++++++- src/styles.css | 4 ++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/App.svelte b/src/App.svelte index 6e8d2e7..9215b76 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -13,6 +13,8 @@ let authError = ''; let authBusy = false; let connectionNotice = ''; + let realtimeState = 'DISCONNECTED'; + let realtimeSocket: WebSocket | undefined; let oauthLink = ''; let oauthIdentity: 'streamer' | 'bot' = 'streamer'; let oauthBusy = false; @@ -60,6 +62,7 @@ authenticated = true; mustReplaceCredentials = result.must_replace_credentials; connectionState = 'CONNECTED'; + connectRealtime(); if (!mustReplaceCredentials) password = ''; if (mustReplaceCredentials) newUsername = username; } catch (error) { @@ -95,6 +98,9 @@ } async function logout() { + realtimeSocket?.close(); + realtimeSocket = undefined; + realtimeState = 'DISCONNECTED'; if (sessionToken) { await fetch(`${serverAddress}/api/v1/auth/logout`, { method: 'POST', headers: { authorization: `Bearer ${sessionToken}` } }).catch(() => undefined); } @@ -104,6 +110,32 @@ oauthLink = ''; } + function connectRealtime() { + realtimeSocket?.close(); + const socketUrl = serverAddress.replace(/^http/, 'ws') + '/api/v1/ws'; + realtimeState = 'CONNECTING'; + const socket = new WebSocket(socketUrl); + realtimeSocket = socket; + socket.onopen = () => { + socket.send(JSON.stringify({ frame_type: 'authenticate', token: sessionToken })); + }; + socket.onmessage = (message) => { + try { + const event: { event_type?: string } = JSON.parse(message.data); + if (event.event_type === 'connection.ready') realtimeState = 'CONNECTED'; + } catch { + realtimeState = 'DISCONNECTED'; + } + }; + socket.onclose = () => { + realtimeState = 'DISCONNECTED'; + if (realtimeSocket === socket) realtimeSocket = undefined; + }; + socket.onerror = () => { + realtimeState = 'DISCONNECTED'; + }; + } + async function createOAuthLink(identity: 'streamer' | 'bot') { oauthBusy = true; oauthError = ''; @@ -188,7 +220,7 @@
Sign in as administrator to connect accounts.
{/if} - +
diff --git a/src/styles.css b/src/styles.css index 1261855..56edfe0 100644 --- a/src/styles.css +++ b/src/styles.css @@ -326,6 +326,10 @@ label { margin-top: auto; border-bottom: 0; } +.version-label { + display: block; + margin-top: 13px; +} .content-area { min-width: 0; padding: 8px;