diff --git a/src/App.svelte b/src/App.svelte index 4f0e9a1..2f3d341 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -41,7 +41,7 @@ handleToggleServerSettings(true)} /> -{#if connectionState.isDisconnected || (connectionState.isConnecting && connectionState.hasConnectedOnce)} +{#if connectionState.shouldShowDisconnectedModal} {}} zIndex={9999} @@ -52,6 +52,9 @@

Connection Lost

Your connection to the SpacetimeDB server was interrupted. + {#if connectionState.lastDisconnectAt} +
Offline for {Math.round((Date.now() - connectionState.lastDisconnectAt) / 1000)}s + {/if}

diff --git a/src/InnerSpacetimeDBProvider.svelte b/src/InnerSpacetimeDBProvider.svelte index 0415cf3..feb2316 100644 --- a/src/InnerSpacetimeDBProvider.svelte +++ b/src/InnerSpacetimeDBProvider.svelte @@ -19,17 +19,19 @@ // We untrack the builder here because SpacetimeProvider handles remounting on builder changes. const db = createSpacetimeDBProvider(untrack(() => builder)); - // 1.1 Connection Timeout - // If we stay in "connecting" state for too long without an identity or error, - // we'll force a timeout error to trigger recovery/logout. + // 1.1 Connection Timeout (Initial connection only) + // If the very first connection attempt hangs without identity or error, + // surface a timeout so the user isn't staring at a spinner forever. + // During reconnection, the backoff loop in SpacetimeProvider handles retries. let connectionTimeout = $state(null); $effect(() => { - if (!$db.identity && !$db.error) { + const isInitialAttempt = !wasActive && !connectionState.hasConnectedOnce; + if (isInitialAttempt && !$db.identity && !$db.error) { if (!connectionTimeout) { connectionTimeout = setTimeout(() => { - console.warn("[Handshake] Connection attempt timed out after 10s."); - handleConnectError(new Error("Connection timeout: Server did not respond to handshake. This may be due to an expired token or network issues.")); + console.warn("[Handshake] Initial connection attempt timed out after 10s."); + connectionState.error = "Connection timeout: Server did not respond to handshake."; }, 10000); } } else { @@ -95,12 +97,21 @@ } }); - // Update global connection status for UI visibility + // 5. Connection State Tracking + // Track whether we have ever been active so we can distinguish + // "initial connecting" from "was connected then dropped". + let wasActive = $state(false); + $effect(() => { - if ($db.isActive) { - connectionState.status = "connected"; - } else { - connectionState.status = "connecting"; + const isActiveNow = $db.isActive; + + if (isActiveNow) { + wasActive = true; + connectionState.markConnected(); + } else if (wasActive) { + // We HAD a connection and lost it. + connectionState.markDisconnected(); + wasActive = false; } }); diff --git a/src/SpacetimeProvider.svelte b/src/SpacetimeProvider.svelte index 50169fc..7047eec 100644 --- a/src/SpacetimeProvider.svelte +++ b/src/SpacetimeProvider.svelte @@ -1,6 +1,7 @@
+ + {#if connectionState.isReconnecting && !connectionState.shouldShowDisconnectedModal} +
+ + + Reconnecting + {#if connectionState.reconnectAttempts > 0} + (attempt {connectionState.reconnectAttempts}) + {/if}... + +
+ {/if} +