From 63249aed9dd62e59aa302ddcf7c3a419effc43b3 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 7 Mar 2026 10:39:57 -0800 Subject: improve message delivery with websocket reconnect and push data transport - Enable auto-reconnect on relay connections so dropped WebSockets recover - Connect relays in parallel with 5s timeout for faster startup - Add visibility change handler to catch up missed messages on tab return - Forward kind 445 events via push notifications as a data channel - Show "reconnecting..." status in chat UI when connections are down Co-Authored-By: Claude Opus 4.6 --- src/routes/(app)/messages/+page.svelte | 28 +++++++++++++++++++++++++++- src/routes/(app)/messages/[id]/+page.svelte | 23 ++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) (limited to 'src/routes') diff --git a/src/routes/(app)/messages/+page.svelte b/src/routes/(app)/messages/+page.svelte index 1981b5f1..215f07b0 100644 --- a/src/routes/(app)/messages/+page.svelte +++ b/src/routes/(app)/messages/+page.svelte @@ -4,6 +4,7 @@ import { getGroups, subscribeToMessages, fetchGroupHistory, searchMlsUsers, preloadMlsUsers, resolveUser, displayName, + isConnected, onConnectionChange, } from "$lib/messaging"; import type { GroupInfo } from "$lib/messaging"; import { t } from "$lib/translations"; @@ -22,6 +23,7 @@ let groups: GroupInfo[] = $state([]); let loaded = $state(false); + let connected = $state(isConnected()); let memberInfos = $state(new Map()); // Search state @@ -68,8 +70,10 @@ }; let closeSub: (() => void) | undefined; + let closeConnectionListener: (() => void) | undefined; onMount(() => { + closeConnectionListener = onConnectionChange((c) => (connected = c)); // Show cached groups immediately, then fetch history in background refreshGroups().then(() => { loaded = true; }); fetchGroupHistory(user).then(refreshGroups); @@ -77,9 +81,16 @@ subscribeToMessages(user, refreshGroups).then((close) => (closeSub = close)); }); - onDestroy(() => closeSub?.()); + onDestroy(() => { + closeSub?.(); + closeConnectionListener?.(); + }); +{#if !connected} +
Reconnecting...
+{/if} +
+ + diff --git a/src/routes/(app)/messages/[id]/+page.svelte b/src/routes/(app)/messages/[id]/+page.svelte index a5768df7..4af0def5 100644 --- a/src/routes/(app)/messages/[id]/+page.svelte +++ b/src/routes/(app)/messages/[id]/+page.svelte @@ -12,6 +12,7 @@ findOrCreateDmGroup, resolveUser, displayName, decryptMediaUrl, + isConnected, onConnectionChange, } from "$lib/messaging"; import type { GroupInfo } from "$lib/messaging"; import { t } from "$lib/translations"; @@ -48,6 +49,7 @@ let dates: number[] = $state([]); let sendError = $state(""); let uploading = $state(false); + let connected = $state(isConnected()); let senderInfos = $state(new Map()); const groupDisplayName = (g: GroupInfo | undefined): string => { @@ -243,8 +245,10 @@ }; let closeSub: (() => void) | undefined; + let closeConnectionListener: (() => void) | undefined; onMount(() => { + closeConnectionListener = onConnectionChange((c) => (connected = c)); loadGroup(); const vv = window.visualViewport; @@ -272,7 +276,10 @@ }).then((close) => (closeSub = close)); }); - onDestroy(() => closeSub?.()); + onDestroy(() => { + closeSub?.(); + closeConnectionListener?.(); + });
@@ -281,6 +288,9 @@ {#if groupInfo && groupInfo.members.length > 2} {groupInfo.members.length} members {/if} + {#if !connected} + reconnecting... + {/if}
@@ -394,6 +404,17 @@ flex-shrink: 0; } + .connection-status { + font-size: 0.75rem; + color: #f59e0b; + animation: pulse-text 1.5s ease-in-out infinite; + } + + @keyframes pulse-text { + 0%, 100% { opacity: 0.6; } + 50% { opacity: 1; } + } + .warning { color: #ff7f00; } -- cgit v1.2.3