svelte-firekit 0.2.3 → 0.2.5
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 +5 -3
- package/dist/components/CustomGuard.svelte +5 -2
- package/dist/components/FirebaseApp.svelte +4 -0
- package/dist/components/SignedIn.svelte +1 -1
- package/dist/components/SignedOut.svelte +1 -1
- package/dist/services/ai.svelte.js +13 -1
- package/dist/services/analytics.js +5 -1
- package/dist/services/auth.d.ts +1 -1
- package/dist/services/auth.js +29 -25
- package/dist/services/messaging.svelte.d.ts +1 -0
- package/dist/services/messaging.svelte.js +5 -0
- package/dist/services/mutations.js +1 -1
- package/dist/services/presence.svelte.d.ts +1 -0
- package/dist/services/presence.svelte.js +12 -4
- package/dist/services/remote-config.svelte.js +3 -0
- package/dist/services/storage.svelte.js +1 -0
- package/dist/services/user.svelte.d.ts +14 -2
- package/dist/services/user.svelte.js +48 -24
- package/package.json +1 -1
|
@@ -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,7 +57,7 @@
|
|
|
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}
|
|
@@ -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,7 +81,7 @@
|
|
|
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}
|
|
@@ -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
|
|
|
@@ -210,7 +210,19 @@ export class FirekitChat {
|
|
|
210
210
|
// Seed history from initialHistory
|
|
211
211
|
for (const turn of initialHistory) {
|
|
212
212
|
const text = turn.parts
|
|
213
|
-
.map((p) =>
|
|
213
|
+
.map((p) => {
|
|
214
|
+
if ('text' in p)
|
|
215
|
+
return p.text;
|
|
216
|
+
if ('inlineData' in p)
|
|
217
|
+
return '[image]';
|
|
218
|
+
if ('fileData' in p)
|
|
219
|
+
return '[file]';
|
|
220
|
+
if ('functionCall' in p)
|
|
221
|
+
return `[function: ${p.functionCall.name}]`;
|
|
222
|
+
if ('functionResponse' in p)
|
|
223
|
+
return `[function response: ${p.functionResponse.name}]`;
|
|
224
|
+
return '[unknown part]';
|
|
225
|
+
})
|
|
214
226
|
.filter(Boolean)
|
|
215
227
|
.join('');
|
|
216
228
|
this._history.push({
|
|
@@ -41,7 +41,11 @@ class FirekitAnalytics {
|
|
|
41
41
|
if (!supported)
|
|
42
42
|
return;
|
|
43
43
|
this._analytics = getAnalytics(getApp());
|
|
44
|
-
})()
|
|
44
|
+
})().catch((err) => {
|
|
45
|
+
// Reset so next call retries instead of returning a rejected promise forever
|
|
46
|
+
this._initPromise = null;
|
|
47
|
+
throw err;
|
|
48
|
+
});
|
|
45
49
|
return this._initPromise;
|
|
46
50
|
}
|
|
47
51
|
async _get() {
|
package/dist/services/auth.d.ts
CHANGED
|
@@ -18,8 +18,8 @@ declare class FirekitAuth {
|
|
|
18
18
|
private recaptchaVerifiers;
|
|
19
19
|
private constructor();
|
|
20
20
|
static getInstance(): FirekitAuth;
|
|
21
|
-
private bootstrap;
|
|
22
21
|
private getAuth;
|
|
22
|
+
private getFirestore;
|
|
23
23
|
private syncToFirestore;
|
|
24
24
|
private profile;
|
|
25
25
|
signInWithEmail(email: string, password: string): Promise<SignInResult>;
|
package/dist/services/auth.js
CHANGED
|
@@ -20,9 +20,8 @@ class FirekitAuth {
|
|
|
20
20
|
firestore = null;
|
|
21
21
|
recaptchaVerifiers = new Map();
|
|
22
22
|
constructor() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
23
|
+
// Do NOT bootstrap here — Firebase config may not be set yet.
|
|
24
|
+
// Auth and Firestore instances are resolved lazily via getAuth() / getFirestore().
|
|
26
25
|
}
|
|
27
26
|
static getInstance() {
|
|
28
27
|
if (!FirekitAuth.instance) {
|
|
@@ -30,20 +29,6 @@ class FirekitAuth {
|
|
|
30
29
|
}
|
|
31
30
|
return FirekitAuth.instance;
|
|
32
31
|
}
|
|
33
|
-
bootstrap() {
|
|
34
|
-
try {
|
|
35
|
-
this.auth = firebaseService.getAuthInstance();
|
|
36
|
-
try {
|
|
37
|
-
this.firestore = firebaseService.getDbInstance();
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
this.firestore = null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
// Firebase not yet configured — services will be accessed lazily
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
32
|
getAuth() {
|
|
48
33
|
if (!this.auth) {
|
|
49
34
|
this.auth = firebaseService.getAuthInstance();
|
|
@@ -53,10 +38,22 @@ class FirekitAuth {
|
|
|
53
38
|
}
|
|
54
39
|
return this.auth;
|
|
55
40
|
}
|
|
41
|
+
getFirestore() {
|
|
42
|
+
if (!this.firestore) {
|
|
43
|
+
try {
|
|
44
|
+
this.firestore = firebaseService.getDbInstance();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
this.firestore = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return this.firestore;
|
|
51
|
+
}
|
|
56
52
|
async syncToFirestore(user) {
|
|
57
|
-
|
|
53
|
+
const fs = this.getFirestore();
|
|
54
|
+
if (!fs)
|
|
58
55
|
return;
|
|
59
|
-
await updateUserInFirestore(
|
|
56
|
+
await updateUserInFirestore(fs, user);
|
|
60
57
|
}
|
|
61
58
|
profile(user) {
|
|
62
59
|
return mapFirebaseUserToProfile(user);
|
|
@@ -327,9 +324,10 @@ class FirekitAuth {
|
|
|
327
324
|
if (currentPassword)
|
|
328
325
|
await this.reauthenticate(currentPassword);
|
|
329
326
|
// Soft-delete record in Firestore before removing auth
|
|
330
|
-
|
|
327
|
+
const fs = this.getFirestore();
|
|
328
|
+
if (fs) {
|
|
331
329
|
try {
|
|
332
|
-
await setDoc(doc(
|
|
330
|
+
await setDoc(doc(fs, 'users', user.uid), { deleted: true, deletedAt: serverTimestamp() }, { merge: true });
|
|
333
331
|
}
|
|
334
332
|
catch {
|
|
335
333
|
// Non-blocking
|
|
@@ -560,16 +558,22 @@ class FirekitAuth {
|
|
|
560
558
|
}
|
|
561
559
|
// ─── Utility getters ─────────────────────────────────────────────────────────
|
|
562
560
|
getCurrentUser() {
|
|
563
|
-
|
|
561
|
+
try {
|
|
562
|
+
return this.getAuth().currentUser;
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
564
567
|
}
|
|
565
568
|
isAuthenticated() {
|
|
566
|
-
|
|
569
|
+
const user = this.getCurrentUser();
|
|
570
|
+
return user !== null && !user.isAnonymous;
|
|
567
571
|
}
|
|
568
572
|
isAnonymous() {
|
|
569
|
-
return this.
|
|
573
|
+
return this.getCurrentUser()?.isAnonymous ?? false;
|
|
570
574
|
}
|
|
571
575
|
isEmailVerified() {
|
|
572
|
-
return this.
|
|
576
|
+
return this.getCurrentUser()?.emailVerified ?? false;
|
|
573
577
|
}
|
|
574
578
|
async cleanup() {
|
|
575
579
|
this.recaptchaVerifiers.forEach((v) => v.clear());
|
|
@@ -40,6 +40,7 @@ class FirekitMessaging {
|
|
|
40
40
|
_messaging = null;
|
|
41
41
|
_unsubscribeMessage = null;
|
|
42
42
|
_requesting = false;
|
|
43
|
+
_listening = false;
|
|
43
44
|
constructor() {
|
|
44
45
|
this._initPermissionState();
|
|
45
46
|
}
|
|
@@ -161,7 +162,10 @@ class FirekitMessaging {
|
|
|
161
162
|
}
|
|
162
163
|
// ── Foreground messages ───────────────────────────────────────────────────
|
|
163
164
|
_listenForMessages(msg) {
|
|
165
|
+
if (this._listening)
|
|
166
|
+
return;
|
|
164
167
|
this._unsubscribeMessage?.();
|
|
168
|
+
this._listening = true;
|
|
165
169
|
this._unsubscribeMessage = onMessage(msg, (payload) => {
|
|
166
170
|
this._lastMessage = payload;
|
|
167
171
|
this._messages = [...this._messages, payload];
|
|
@@ -185,6 +189,7 @@ class FirekitMessaging {
|
|
|
185
189
|
dispose() {
|
|
186
190
|
this._unsubscribeMessage?.();
|
|
187
191
|
this._unsubscribeMessage = null;
|
|
192
|
+
this._listening = false;
|
|
188
193
|
}
|
|
189
194
|
}
|
|
190
195
|
export const firekitMessaging = FirekitMessaging.getInstance();
|
|
@@ -174,9 +174,11 @@ class FirekitPresence {
|
|
|
174
174
|
_geo = null;
|
|
175
175
|
_connectedUnsub = null;
|
|
176
176
|
_currentUser = null;
|
|
177
|
+
_visibilityListenerAdded = false;
|
|
177
178
|
constructor() {
|
|
178
179
|
if (typeof window !== 'undefined') {
|
|
179
180
|
document.addEventListener('visibilitychange', this._onVisibilityChange);
|
|
181
|
+
this._visibilityListenerAdded = true;
|
|
180
182
|
}
|
|
181
183
|
}
|
|
182
184
|
static getInstance() {
|
|
@@ -236,10 +238,15 @@ class FirekitPresence {
|
|
|
236
238
|
if (!db)
|
|
237
239
|
throw new PresenceError(PresenceErrorCode.DATABASE_ERROR, 'Realtime Database is not initialized.');
|
|
238
240
|
const connectedRef = ref(db, '.info/connected');
|
|
239
|
-
this._connectedUnsub = onValue(connectedRef,
|
|
241
|
+
this._connectedUnsub = onValue(connectedRef, (snap) => {
|
|
240
242
|
if (snap.val() === true) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
+
this.setPresence('online')
|
|
244
|
+
.then(() => this._setupDisconnectHandler())
|
|
245
|
+
.catch((err) => {
|
|
246
|
+
this._error = err instanceof PresenceError
|
|
247
|
+
? err
|
|
248
|
+
: new PresenceError(PresenceErrorCode.DATABASE_ERROR, err.message, err);
|
|
249
|
+
});
|
|
243
250
|
}
|
|
244
251
|
else {
|
|
245
252
|
this._status = 'offline';
|
|
@@ -377,8 +384,9 @@ class FirekitPresence {
|
|
|
377
384
|
this._geo?.dispose();
|
|
378
385
|
this._connectedUnsub?.();
|
|
379
386
|
this._connectedUnsub = null;
|
|
380
|
-
if (
|
|
387
|
+
if (this._visibilityListenerAdded) {
|
|
381
388
|
document.removeEventListener('visibilitychange', this._onVisibilityChange);
|
|
389
|
+
this._visibilityListenerAdded = false;
|
|
382
390
|
}
|
|
383
391
|
this._initialized = false;
|
|
384
392
|
this._status = 'offline';
|
|
@@ -92,6 +92,9 @@ export class FirekitRemoteConfig {
|
|
|
92
92
|
},
|
|
93
93
|
error: (err) => {
|
|
94
94
|
this._error = err instanceof Error ? err : new Error(String(err));
|
|
95
|
+
// Clean up the broken subscription so it doesn't keep firing errors
|
|
96
|
+
this._unsubscribe?.();
|
|
97
|
+
this._unsubscribe = null;
|
|
95
98
|
},
|
|
96
99
|
complete: () => { }
|
|
97
100
|
});
|
|
@@ -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
|
}
|