From f48618f94b5e9627bb0836d92f1e6909790cbf91 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Sun, 25 Oct 2026 04:44:43 +0100 Subject: [PATCH] feat: render activity history and live events --- src/App.svelte | 22 +++++++++++++++++++++- src/styles.css | 18 ++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/App.svelte b/src/App.svelte index 9215b76..7156117 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -15,6 +15,7 @@ let connectionNotice = ''; let realtimeState = 'DISCONNECTED'; let realtimeSocket: WebSocket | undefined; + let activityEvents: Array<{ event_id: string; event_type: string; occurred_at: string }> = []; let oauthLink = ''; let oauthIdentity: 'streamer' | 'bot' = 'streamer'; let oauthBusy = false; @@ -62,6 +63,7 @@ authenticated = true; mustReplaceCredentials = result.must_replace_credentials; connectionState = 'CONNECTED'; + await loadActivity(); connectRealtime(); if (!mustReplaceCredentials) password = ''; if (mustReplaceCredentials) newUsername = username; @@ -123,6 +125,12 @@ try { const event: { event_type?: string } = JSON.parse(message.data); if (event.event_type === 'connection.ready') realtimeState = 'CONNECTED'; + else if ('event_id' in event && event.event_type) { + const activity = event as { event_id: string; event_type: string; occurred_at: string }; + if (!activityEvents.some((item) => item.event_id === activity.event_id)) { + activityEvents = [activity, ...activityEvents].slice(0, 100); + } + } } catch { realtimeState = 'DISCONNECTED'; } @@ -136,6 +144,18 @@ }; } + async function loadActivity() { + try { + const response = await fetch(`${serverAddress}/api/v1/activity?limit=100`, { + headers: { authorization: `Bearer ${sessionToken}` }, + }); + if (!response.ok) return; + activityEvents = await response.json(); + } catch { + activityEvents = []; + } + } + async function createOAuthLink(identity: 'streamer' | 'bot') { oauthBusy = true; oauthError = ''; @@ -243,7 +263,7 @@ {:else}
STREAM PREVIEW⋮
STREAM OFFLINE
CONNECT TWITCH TO BEGIN
-
ACTIVITY FEED⋮
◌
Nothing here yetFollows, subscriptions, raids, and server events will appear here.
+
ACTIVITY FEED⋮
{#if activityEvents.length}
{#each activityEvents as event}
{event.event_type}{new Date(event.occurred_at).toLocaleTimeString()}
{/each}
{:else}
◌
Nothing here yetFollows, subscriptions, raids, and server events will appear here.
{/if}
MY CHAT⋮
⌁
Chat is waitingConnect a streamer or bot account to begin.
QUICK ACTIONS⋮
diff --git a/src/styles.css b/src/styles.css index 56edfe0..554643c 100644 --- a/src/styles.css +++ b/src/styles.css @@ -425,6 +425,24 @@ label { color: var(--ed-accent); font-size: 28px; } +.activity-list { + height: calc(100% - 70px); + overflow: auto; +} +.activity-row { + display: flex; + justify-content: space-between; + gap: 8px; + padding: 9px 12px; + border-bottom: 1px solid var(--ui-divider); + font: 10px var(--font-data); +} +.activity-type { + color: var(--ed-fg); +} +.activity-time { + color: var(--ed-dim); +} .chat-composer { display: flex; gap: 5px;