svelte-firekit 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AuthGuard.svelte +8 -6
- package/dist/components/AuthGuard.svelte.d.ts +2 -2
- package/dist/components/CustomGuard.svelte +7 -4
- package/dist/components/CustomGuard.svelte.d.ts +1 -1
- package/dist/components/FirebaseApp.svelte +4 -0
- package/dist/components/SignedIn.svelte +17 -3
- package/dist/components/SignedIn.svelte.d.ts +2 -0
- package/dist/components/SignedOut.svelte +16 -4
- package/dist/components/SignedOut.svelte.d.ts +2 -0
- package/dist/services/user.svelte.d.ts +14 -2
- package/dist/services/user.svelte.js +48 -24
- package/package.json +1 -1
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
onUnauthorized,
|
|
32
32
|
fallback
|
|
33
33
|
}: {
|
|
34
|
-
/** Content shown when the auth requirement is satisfied. */
|
|
35
|
-
children: Snippet<[UserProfile, () => Promise<void>]>;
|
|
34
|
+
/** Content shown when the auth requirement is satisfied. User is null when requireAuth is false. */
|
|
35
|
+
children: Snippet<[UserProfile | null, () => Promise<void>]>;
|
|
36
36
|
/** When true (default), requires a signed-in user. When false, requires no user. */
|
|
37
37
|
requireAuth?: boolean;
|
|
38
38
|
/** Called when the auth state does not meet the requirement. Use for navigation. */
|
|
@@ -45,9 +45,11 @@
|
|
|
45
45
|
return firekitAuth.signOut();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// Only react after Firebase Auth has fully initialized (first onAuthStateChanged callback).
|
|
49
|
+
// Uses `initialized` instead of `loading` because `loading` gets reused for profile
|
|
50
|
+
// updates and could cause false redirects mid-operation.
|
|
49
51
|
$effect(() => {
|
|
50
|
-
if (firekitUser.
|
|
52
|
+
if (!firekitUser.initialized) return;
|
|
51
53
|
|
|
52
54
|
const isAuth = firekitUser.isAuthenticated;
|
|
53
55
|
const shouldBlock = requireAuth ? !isAuth : isAuth;
|
|
@@ -55,10 +57,10 @@
|
|
|
55
57
|
});
|
|
56
58
|
</script>
|
|
57
59
|
|
|
58
|
-
{#if firekitUser.
|
|
60
|
+
{#if !firekitUser.initialized}
|
|
59
61
|
{#if fallback}
|
|
60
62
|
{@render fallback()}
|
|
61
63
|
{/if}
|
|
62
|
-
{:else if firekitUser.isAuthenticated === requireAuth
|
|
64
|
+
{:else if firekitUser.isAuthenticated === requireAuth}
|
|
63
65
|
{@render children(firekitUser.user, signOut)}
|
|
64
66
|
{/if}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { UserProfile } from '../types/auth.js';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
|
-
/** Content shown when the auth requirement is satisfied. */
|
|
5
|
-
children: Snippet<[UserProfile, () => Promise<void>]>;
|
|
4
|
+
/** Content shown when the auth requirement is satisfied. User is null when requireAuth is false. */
|
|
5
|
+
children: Snippet<[UserProfile | null, () => Promise<void>]>;
|
|
6
6
|
/** When true (default), requires a signed-in user. When false, requires no user. */
|
|
7
7
|
requireAuth?: boolean;
|
|
8
8
|
/** Called when the auth state does not meet the requirement. Use for navigation. */
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
fallback,
|
|
29
29
|
verificationChecks = []
|
|
30
30
|
}: {
|
|
31
|
-
children: Snippet<[UserProfile, () => Promise<void>]>;
|
|
31
|
+
children: Snippet<[UserProfile | null, () => Promise<void>]>;
|
|
32
32
|
requireAuth?: boolean;
|
|
33
33
|
onUnauthorized?: () => void;
|
|
34
34
|
fallback?: Snippet;
|
|
@@ -53,8 +53,11 @@
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// Only react after Firebase Auth has fully initialized (first onAuthStateChanged callback).
|
|
57
|
+
// Uses `initialized` instead of `loading` because `loading` gets reused for profile
|
|
58
|
+
// updates and could cause false redirects mid-operation.
|
|
56
59
|
$effect(() => {
|
|
57
|
-
if (firekitUser.
|
|
60
|
+
if (!firekitUser.initialized) return;
|
|
58
61
|
|
|
59
62
|
const isAuth = firekitUser.isAuthenticated;
|
|
60
63
|
const shouldBlock = requireAuth ? !isAuth : isAuth;
|
|
@@ -78,10 +81,10 @@
|
|
|
78
81
|
});
|
|
79
82
|
</script>
|
|
80
83
|
|
|
81
|
-
{#if firekitUser.
|
|
84
|
+
{#if !firekitUser.initialized || isVerifying}
|
|
82
85
|
{#if fallback}
|
|
83
86
|
{@render fallback()}
|
|
84
87
|
{/if}
|
|
85
|
-
{:else if firekitUser.isAuthenticated === requireAuth && verificationPassed
|
|
88
|
+
{:else if firekitUser.isAuthenticated === requireAuth && verificationPassed}
|
|
86
89
|
{@render children(firekitUser.user, signOut)}
|
|
87
90
|
{/if}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { UserProfile } from '../types/auth.js';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
|
-
children: Snippet<[UserProfile, () => Promise<void>]>;
|
|
4
|
+
children: Snippet<[UserProfile | null, () => Promise<void>]>;
|
|
5
5
|
requireAuth?: boolean;
|
|
6
6
|
onUnauthorized?: () => void;
|
|
7
7
|
fallback?: Snippet;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { firekitAppCheck } from '../services/app-check.svelte.js';
|
|
9
9
|
import { firekitAnalytics } from '../services/analytics.js';
|
|
10
10
|
import { firekitMessaging } from '../services/messaging.svelte.js';
|
|
11
|
+
import { firekitUser } from '../services/user.svelte.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Root provider component. Initializes Firebase (and optionally App Check) and
|
|
@@ -75,6 +76,9 @@
|
|
|
75
76
|
setContext('firebase/app-check', firekitAppCheck.initialized ? firebaseService.appCheck : null);
|
|
76
77
|
setContext('firebase/analytics', firekitAnalytics);
|
|
77
78
|
setContext('firebase/messaging', firekitMessaging);
|
|
79
|
+
|
|
80
|
+
// Now that Firebase is configured, start the auth state listener.
|
|
81
|
+
firekitUser.initialize();
|
|
78
82
|
}
|
|
79
83
|
</script>
|
|
80
84
|
|
|
@@ -5,18 +5,32 @@
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Renders `children` only when a non-anonymous user is signed in.
|
|
8
|
-
*
|
|
8
|
+
* Optionally renders a `fallback` while auth state is loading.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* <SignedIn>
|
|
12
12
|
* {#snippet children(user)}
|
|
13
13
|
* <p>Hello {user.displayName}</p>
|
|
14
14
|
* {/snippet}
|
|
15
|
+
* {#snippet fallback()}
|
|
16
|
+
* <p>Loading…</p>
|
|
17
|
+
* {/snippet}
|
|
15
18
|
* </SignedIn>
|
|
16
19
|
*/
|
|
17
|
-
let {
|
|
20
|
+
let {
|
|
21
|
+
children,
|
|
22
|
+
fallback
|
|
23
|
+
}: {
|
|
24
|
+
children: Snippet<[UserProfile]>;
|
|
25
|
+
/** Shown while auth state is being determined. */
|
|
26
|
+
fallback?: Snippet;
|
|
27
|
+
} = $props();
|
|
18
28
|
</script>
|
|
19
29
|
|
|
20
|
-
{#if firekitUser.
|
|
30
|
+
{#if !firekitUser.initialized}
|
|
31
|
+
{#if fallback}
|
|
32
|
+
{@render fallback()}
|
|
33
|
+
{/if}
|
|
34
|
+
{:else if firekitUser.isAuthenticated && firekitUser.user}
|
|
21
35
|
{@render children(firekitUser.user)}
|
|
22
36
|
{/if}
|
|
@@ -2,6 +2,8 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { UserProfile } from '../types/auth.js';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
children: Snippet<[UserProfile]>;
|
|
5
|
+
/** Shown while auth state is being determined. */
|
|
6
|
+
fallback?: Snippet;
|
|
5
7
|
};
|
|
6
8
|
declare const SignedIn: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
9
|
type SignedIn = ReturnType<typeof SignedIn>;
|
|
@@ -3,23 +3,31 @@
|
|
|
3
3
|
import { firekitUser } from '../services/user.svelte.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Renders `children` only when no authenticated user exists
|
|
7
|
-
*
|
|
6
|
+
* Renders `children` only when no authenticated user exists.
|
|
7
|
+
* Waits for auth to initialize before rendering to avoid flashing
|
|
8
|
+
* a login form to users who are already signed in.
|
|
9
|
+
* Optionally renders a `fallback` while auth state is loading.
|
|
8
10
|
*
|
|
9
11
|
* @example
|
|
10
12
|
* <SignedOut>
|
|
11
13
|
* {#snippet children(signIn)}
|
|
12
14
|
* <button onclick={signIn}>Sign in with Google</button>
|
|
13
15
|
* {/snippet}
|
|
16
|
+
* {#snippet fallback()}
|
|
17
|
+
* <p>Checking auth…</p>
|
|
18
|
+
* {/snippet}
|
|
14
19
|
* </SignedOut>
|
|
15
20
|
*/
|
|
16
21
|
let {
|
|
17
22
|
children,
|
|
18
|
-
onSignIn
|
|
23
|
+
onSignIn,
|
|
24
|
+
fallback
|
|
19
25
|
}: {
|
|
20
26
|
children: Snippet<[() => void]>;
|
|
21
27
|
/** Optional callback invoked when the user triggers sign-in from the snippet. */
|
|
22
28
|
onSignIn?: () => void;
|
|
29
|
+
/** Shown while auth state is being determined. */
|
|
30
|
+
fallback?: Snippet;
|
|
23
31
|
} = $props();
|
|
24
32
|
|
|
25
33
|
function triggerSignIn() {
|
|
@@ -27,6 +35,10 @@
|
|
|
27
35
|
}
|
|
28
36
|
</script>
|
|
29
37
|
|
|
30
|
-
{#if !firekitUser.
|
|
38
|
+
{#if !firekitUser.initialized}
|
|
39
|
+
{#if fallback}
|
|
40
|
+
{@render fallback()}
|
|
41
|
+
{/if}
|
|
42
|
+
{:else if !firekitUser.isAuthenticated}
|
|
31
43
|
{@render children(triggerSignIn)}
|
|
32
44
|
{/if}
|
|
@@ -3,6 +3,8 @@ type $$ComponentProps = {
|
|
|
3
3
|
children: Snippet<[() => void]>;
|
|
4
4
|
/** Optional callback invoked when the user triggers sign-in from the snippet. */
|
|
5
5
|
onSignIn?: () => void;
|
|
6
|
+
/** Shown while auth state is being determined. */
|
|
7
|
+
fallback?: Snippet;
|
|
6
8
|
};
|
|
7
9
|
declare const SignedOut: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
10
|
type SignedOut = ReturnType<typeof SignedOut>;
|
|
@@ -36,9 +36,20 @@ declare class FirekitUserStore {
|
|
|
36
36
|
private _photoURL;
|
|
37
37
|
private _uid;
|
|
38
38
|
private _phoneNumber;
|
|
39
|
+
private _listening;
|
|
39
40
|
private constructor();
|
|
40
41
|
static getInstance(): FirekitUserStore;
|
|
41
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Called by FirebaseApp after initFirekit() to start the auth listener.
|
|
44
|
+
* Safe to call multiple times — only the first call has an effect.
|
|
45
|
+
*/
|
|
46
|
+
initialize(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Ensures the onAuthStateChanged listener is registered.
|
|
49
|
+
* Called lazily from initialize() or from any public getter/method
|
|
50
|
+
* so the store self-heals if Firebase was configured after import.
|
|
51
|
+
*/
|
|
52
|
+
private ensureListening;
|
|
42
53
|
private listenToAuthState;
|
|
43
54
|
private syncToFirestore;
|
|
44
55
|
private currentFirebaseUser;
|
|
@@ -66,9 +77,10 @@ declare class FirekitUserStore {
|
|
|
66
77
|
updateExtendedData(data: Partial<ExtendedUserData>): Promise<void>;
|
|
67
78
|
/**
|
|
68
79
|
* Resolves once Firebase Auth has initialized (first `onAuthStateChanged` callback).
|
|
80
|
+
* Rejects after `timeoutMs` (default 10 000 ms) if auth never initializes.
|
|
69
81
|
* Safe to call server-side — will resolve immediately with null.
|
|
70
82
|
*/
|
|
71
|
-
waitForAuth(): Promise<UserProfile | null>;
|
|
83
|
+
waitForAuth(timeoutMs?: number): Promise<UserProfile | null>;
|
|
72
84
|
clearError(): void;
|
|
73
85
|
}
|
|
74
86
|
export declare const firekitUser: FirekitUserStore;
|
|
@@ -34,10 +34,10 @@ class FirekitUserStore {
|
|
|
34
34
|
_photoURL = $derived(this._user?.photoURL ?? null);
|
|
35
35
|
_uid = $derived(this._user?.uid ?? null);
|
|
36
36
|
_phoneNumber = $derived(this._user?.phoneNumber ?? null);
|
|
37
|
+
_listening = false;
|
|
37
38
|
constructor() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
39
|
+
// Do NOT bootstrap here — Firebase config may not be set yet.
|
|
40
|
+
// Auth listener is set up lazily via initialize() or ensureListening().
|
|
41
41
|
}
|
|
42
42
|
static getInstance() {
|
|
43
43
|
if (!FirekitUserStore.instance) {
|
|
@@ -45,7 +45,23 @@ class FirekitUserStore {
|
|
|
45
45
|
}
|
|
46
46
|
return FirekitUserStore.instance;
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Called by FirebaseApp after initFirekit() to start the auth listener.
|
|
50
|
+
* Safe to call multiple times — only the first call has an effect.
|
|
51
|
+
*/
|
|
52
|
+
initialize() {
|
|
53
|
+
if (typeof window === 'undefined')
|
|
54
|
+
return;
|
|
55
|
+
this.ensureListening();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Ensures the onAuthStateChanged listener is registered.
|
|
59
|
+
* Called lazily from initialize() or from any public getter/method
|
|
60
|
+
* so the store self-heals if Firebase was configured after import.
|
|
61
|
+
*/
|
|
62
|
+
ensureListening() {
|
|
63
|
+
if (this._listening)
|
|
64
|
+
return;
|
|
49
65
|
try {
|
|
50
66
|
this.auth = firebaseService.getAuthInstance();
|
|
51
67
|
try {
|
|
@@ -54,12 +70,11 @@ class FirekitUserStore {
|
|
|
54
70
|
catch {
|
|
55
71
|
this.firestore = null;
|
|
56
72
|
}
|
|
73
|
+
this._listening = true;
|
|
57
74
|
this.listenToAuthState();
|
|
58
75
|
}
|
|
59
|
-
catch
|
|
60
|
-
|
|
61
|
-
this._loading = false;
|
|
62
|
-
this._initialized = true;
|
|
76
|
+
catch {
|
|
77
|
+
// Firebase not yet configured — will retry on next access
|
|
63
78
|
}
|
|
64
79
|
}
|
|
65
80
|
listenToAuthState() {
|
|
@@ -85,18 +100,20 @@ class FirekitUserStore {
|
|
|
85
100
|
return validateCurrentUser(this.auth);
|
|
86
101
|
}
|
|
87
102
|
// ── Public getters (reactive) ────────────────────────────────────────────────
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
get
|
|
91
|
-
get
|
|
92
|
-
get
|
|
93
|
-
get
|
|
94
|
-
get
|
|
95
|
-
get
|
|
96
|
-
get
|
|
97
|
-
get
|
|
98
|
-
get
|
|
99
|
-
get
|
|
103
|
+
// Each getter calls ensureListening() so the auth listener is registered
|
|
104
|
+
// on first access, even if initialize() hasn't been called yet.
|
|
105
|
+
get user() { this.ensureListening(); return this._user; }
|
|
106
|
+
get loading() { this.ensureListening(); return this._loading; }
|
|
107
|
+
get initialized() { this.ensureListening(); return this._initialized; }
|
|
108
|
+
get error() { this.ensureListening(); return this._error; }
|
|
109
|
+
get isAuthenticated() { this.ensureListening(); return this._isAuthenticated; }
|
|
110
|
+
get isAnonymous() { this.ensureListening(); return this._isAnonymous; }
|
|
111
|
+
get isEmailVerified() { this.ensureListening(); return this._isEmailVerified; }
|
|
112
|
+
get email() { this.ensureListening(); return this._email; }
|
|
113
|
+
get displayName() { this.ensureListening(); return this._displayName; }
|
|
114
|
+
get photoURL() { this.ensureListening(); return this._photoURL; }
|
|
115
|
+
get uid() { this.ensureListening(); return this._uid; }
|
|
116
|
+
get phoneNumber() { this.ensureListening(); return this._phoneNumber; }
|
|
100
117
|
// ── Profile updates ──────────────────────────────────────────────────────────
|
|
101
118
|
async updateDisplayName(displayName) {
|
|
102
119
|
const user = this.currentFirebaseUser();
|
|
@@ -253,17 +270,24 @@ class FirekitUserStore {
|
|
|
253
270
|
// ── Utility ──────────────────────────────────────────────────────────────────
|
|
254
271
|
/**
|
|
255
272
|
* Resolves once Firebase Auth has initialized (first `onAuthStateChanged` callback).
|
|
273
|
+
* Rejects after `timeoutMs` (default 10 000 ms) if auth never initializes.
|
|
256
274
|
* Safe to call server-side — will resolve immediately with null.
|
|
257
275
|
*/
|
|
258
|
-
waitForAuth() {
|
|
276
|
+
waitForAuth(timeoutMs = 10_000) {
|
|
277
|
+
if (typeof window === 'undefined')
|
|
278
|
+
return Promise.resolve(null);
|
|
279
|
+
this.ensureListening();
|
|
259
280
|
if (this._initialized)
|
|
260
281
|
return Promise.resolve(this._user);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
const timer = setTimeout(() => {
|
|
284
|
+
stop();
|
|
285
|
+
reject(new Error('waitForAuth timed out — Firebase Auth did not initialize.'));
|
|
286
|
+
}, timeoutMs);
|
|
264
287
|
const stop = $effect.root(() => {
|
|
265
288
|
$effect(() => {
|
|
266
289
|
if (this._initialized) {
|
|
290
|
+
clearTimeout(timer);
|
|
267
291
|
stop();
|
|
268
292
|
resolve(this._user);
|
|
269
293
|
}
|