Files
zep/src/SpacetimeProvider.svelte
T
2026-04-20 20:50:37 -04:00

139 lines
4.0 KiB
Svelte

<script lang="ts">
import { untrack } from "svelte";
import { auth } from "./auth/auth.svelte";
import { connectionState } from "./connection.svelte";
import { connectionBuilder, getStdbHost, getStdbDbName } from "./config";
import InnerSpacetimeDBProvider from "./InnerSpacetimeDBProvider.svelte";
let { children, onCancel } = $props<{
children: any,
onCancel?: () => void
}>();
// 1. Connection Builder Lifecycle
// We MUST wait for OIDC to finish its loading/silent-renew phase
// before we construct the initial SpacetimeDB connection.
let builder = $state<any>(null);
let lastUsedOidcToken = $state<string | undefined>(undefined);
let providerKey = $state(0);
$effect(() => {
// Hold off until OIDC is settled for the first time
if (auth.isLoading) return;
const currentToken = auth.user?.id_token;
// 1. Initial creation
if (!builder) {
console.log(`[SpacetimeProvider] Initializing connection builder. OIDC present: ${!!currentToken}`);
untrack(() => {
builder = connectionBuilder(currentToken);
lastUsedOidcToken = currentToken;
providerKey += 1;
});
return;
}
// 2. Identity transition (Logged out -> Logged in)
// If we were a guest (or null) and now have a token, we SHOULD remount
// to ensure the OIDC credentials take over completely.
if (currentToken && !lastUsedOidcToken) {
console.log("[SpacetimeProvider] Transitioning from Guest/None to OIDC session. Remounting...");
untrack(() => {
builder = connectionBuilder(currentToken);
lastUsedOidcToken = currentToken;
providerKey += 1;
});
return;
}
// 3. Background Refresh (Token -> New Token)
// If it's just a refresh, we DON'T remount. We let InnerSpacetimeDBProvider
// handle the in-place upgrade via withToken.
if (currentToken && currentToken !== lastUsedOidcToken) {
console.log("[SpacetimeProvider] Background token refresh detected. Upgrading in-place.");
untrack(() => {
lastUsedOidcToken = currentToken;
// Notice we DON'T increment providerKey here
});
}
// 4. Logout (Token -> Null)
if (!currentToken && lastUsedOidcToken) {
console.log("[SpacetimeProvider] User logged out. Remounting for Guest mode.");
untrack(() => {
builder = connectionBuilder(undefined);
lastUsedOidcToken = undefined;
providerKey += 1;
});
}
});
// Reactive labels for the loading screen
const host = getStdbHost();
const dbName = getStdbDbName();
</script>
{#if !builder || (auth.isLoading && !auth.user)}
<div class="login-screen">
<div class="login-card" style="text-align: center;">
<i class="fas fa-id-card fa-spin" style="font-size: 3rem; color: var(--brand); margin-bottom: 20px;"></i>
<h1>Authenticating...</h1>
<p style="color: var(--text-muted); margin-top: 8px; margin-bottom: 24px;">Synchronizing your session credentials.</p>
</div>
</div>
{:else}
{#key providerKey}
<InnerSpacetimeDBProvider
{builder}
{onCancel}
{host}
{dbName}
oidcToken={auth.user?.id_token}
>
{@render children()}
</InnerSpacetimeDBProvider>
{/key}
{/if}
<style>
.login-screen {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
width: 100vw;
background: radial-gradient(
circle at center,
var(--background-secondary) 0%,
var(--background-tertiary) 100%
);
background-color: var(--background-tertiary);
}
.login-card {
background-color: var(--background-primary);
padding: 32px;
border-radius: 8px;
width: 480px;
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.login-card h1 {
color: var(--header-primary);
margin-bottom: 8px;
font-size: 24px;
font-weight: 600;
}
.login-card p {
color: var(--text-normal);
margin-bottom: 24px;
font-size: 16px;
}
</style>