svelte-firekit 0.2.3 → 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.
@@ -45,9 +45,11 @@
45
45
  return firekitAuth.signOut();
46
46
  }
47
47
 
48
- // React to auth state changes runs whenever isAuthenticated or loading changes.
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.loading) return;
52
+ if (!firekitUser.initialized) return;
51
53
 
52
54
  const isAuth = firekitUser.isAuthenticated;
53
55
  const shouldBlock = requireAuth ? !isAuth : isAuth;
@@ -55,7 +57,7 @@
55
57
  });
56
58
  </script>
57
59
 
58
- {#if firekitUser.loading}
60
+ {#if !firekitUser.initialized}
59
61
  {#if fallback}
60
62
  {@render fallback()}
61
63
  {/if}
@@ -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.loading) return;
60
+ if (!firekitUser.initialized) return;
58
61
 
59
62
  const isAuth = firekitUser.isAuthenticated;
60
63
  const shouldBlock = requireAuth ? !isAuth : isAuth;
@@ -78,7 +81,7 @@
78
81
  });
79
82
  </script>
80
83
 
81
- {#if firekitUser.loading || isVerifying}
84
+ {#if !firekitUser.initialized || isVerifying}
82
85
  {#if fallback}
83
86
  {@render fallback()}
84
87
  {/if}
@@ -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
 
@@ -27,7 +27,7 @@
27
27
  } = $props();
28
28
  </script>
29
29
 
30
- {#if firekitUser.loading}
30
+ {#if !firekitUser.initialized}
31
31
  {#if fallback}
32
32
  {@render fallback()}
33
33
  {/if}
@@ -35,7 +35,7 @@
35
35
  }
36
36
  </script>
37
37
 
38
- {#if firekitUser.loading}
38
+ {#if !firekitUser.initialized}
39
39
  {#if fallback}
40
40
  {@render fallback()}
41
41
  {/if}
@@ -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
- private bootstrap;
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
- if (typeof window !== 'undefined') {
39
- this.bootstrap();
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
- bootstrap() {
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 (err) {
60
- this._error = err instanceof Error ? err : new Error(String(err));
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
- get user() { return this._user; }
89
- get loading() { return this._loading; }
90
- get initialized() { return this._initialized; }
91
- get error() { return this._error; }
92
- get isAuthenticated() { return this._isAuthenticated; }
93
- get isAnonymous() { return this._isAnonymous; }
94
- get isEmailVerified() { return this._isEmailVerified; }
95
- get email() { return this._email; }
96
- get displayName() { return this._displayName; }
97
- get photoURL() { return this._photoURL; }
98
- get uid() { return this._uid; }
99
- get phoneNumber() { return this._phoneNumber; }
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
- // $effect.root creates a reactive scope outside of component initialization,
262
- // so this works safely whether called inside or outside a Svelte component.
263
- return new Promise((resolve) => {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-firekit",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "A Svelte library for Firebase integration",
5
5
  "license": "MIT",
6
6
  "author": "Giovani Rodriguez",