svelte-firekit 0.0.25 → 0.1.1
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/README.md +445 -213
- package/dist/components/Collection.svelte +150 -0
- package/dist/components/Collection.svelte.d.ts +27 -0
- package/dist/components/Ddoc.svelte +131 -0
- package/dist/components/Ddoc.svelte.d.ts +28 -0
- package/dist/components/Node.svelte +97 -0
- package/dist/components/Node.svelte.d.ts +23 -0
- package/dist/components/auth-guard.svelte +89 -0
- package/dist/components/auth-guard.svelte.d.ts +26 -0
- package/dist/components/custom-guard.svelte +122 -0
- package/dist/components/custom-guard.svelte.d.ts +31 -0
- package/dist/components/download-url.svelte +92 -0
- package/dist/components/download-url.svelte.d.ts +19 -0
- package/dist/components/firebase-app.svelte +30 -0
- package/dist/components/firebase-app.svelte.d.ts +7 -0
- package/dist/components/node-list.svelte +102 -0
- package/dist/components/node-list.svelte.d.ts +27 -0
- package/dist/components/signed-in.svelte +42 -0
- package/dist/components/signed-in.svelte.d.ts +11 -0
- package/dist/components/signed-out.svelte +42 -0
- package/dist/components/signed-out.svelte.d.ts +11 -0
- package/dist/components/storage-list.svelte +97 -0
- package/dist/components/storage-list.svelte.d.ts +26 -0
- package/dist/components/upload-task.svelte +108 -0
- package/dist/components/upload-task.svelte.d.ts +24 -0
- package/dist/config.js +17 -39
- package/dist/firebase.d.ts +43 -21
- package/dist/firebase.js +121 -35
- package/dist/index.d.ts +21 -13
- package/dist/index.js +27 -15
- package/dist/services/auth.d.ts +397 -0
- package/dist/services/auth.js +882 -0
- package/dist/services/collection.svelte.d.ts +286 -0
- package/dist/services/collection.svelte.js +871 -0
- package/dist/services/document.svelte.d.ts +288 -0
- package/dist/services/document.svelte.js +555 -0
- package/dist/services/mutations.d.ts +336 -0
- package/dist/services/mutations.js +1079 -0
- package/dist/services/presence.svelte.d.ts +141 -0
- package/dist/services/presence.svelte.js +727 -0
- package/dist/{realtime → services}/realtime.svelte.d.ts +3 -1
- package/dist/{realtime → services}/realtime.svelte.js +13 -7
- package/dist/services/storage.svelte.d.ts +257 -0
- package/dist/services/storage.svelte.js +374 -0
- package/dist/services/user.svelte.d.ts +296 -0
- package/dist/services/user.svelte.js +609 -0
- package/dist/types/auth.d.ts +158 -0
- package/dist/types/auth.js +106 -0
- package/dist/types/collection.d.ts +360 -0
- package/dist/types/collection.js +167 -0
- package/dist/types/document.d.ts +342 -0
- package/dist/types/document.js +148 -0
- package/dist/types/firebase.d.ts +44 -0
- package/dist/types/firebase.js +33 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +4 -0
- package/dist/types/mutations.d.ts +387 -0
- package/dist/types/mutations.js +205 -0
- package/dist/types/presence.d.ts +282 -0
- package/dist/types/presence.js +80 -0
- package/dist/utils/errors.d.ts +21 -0
- package/dist/utils/errors.js +35 -0
- package/dist/utils/firestore.d.ts +9 -0
- package/dist/utils/firestore.js +33 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/providers.d.ts +16 -0
- package/dist/utils/providers.js +30 -0
- package/dist/utils/user.d.ts +8 -0
- package/dist/utils/user.js +29 -0
- package/package.json +64 -64
- package/dist/auth/auth.d.ts +0 -117
- package/dist/auth/auth.js +0 -194
- package/dist/auth/presence.svelte.d.ts +0 -139
- package/dist/auth/presence.svelte.js +0 -373
- package/dist/auth/user.svelte.d.ts +0 -112
- package/dist/auth/user.svelte.js +0 -155
- package/dist/firestore/awaitable-doc.svelte.d.ts +0 -141
- package/dist/firestore/awaitable-doc.svelte.js +0 -183
- package/dist/firestore/batch-mutations.svelte.d.ts +0 -140
- package/dist/firestore/batch-mutations.svelte.js +0 -218
- package/dist/firestore/collection-group.svelte.d.ts +0 -78
- package/dist/firestore/collection-group.svelte.js +0 -120
- package/dist/firestore/collection.svelte.d.ts +0 -96
- package/dist/firestore/collection.svelte.js +0 -137
- package/dist/firestore/doc.svelte.d.ts +0 -90
- package/dist/firestore/doc.svelte.js +0 -131
- package/dist/firestore/document-mutations.svelte.d.ts +0 -164
- package/dist/firestore/document-mutations.svelte.js +0 -273
- package/dist/storage/download-url.svelte.d.ts +0 -83
- package/dist/storage/download-url.svelte.js +0 -114
- package/dist/storage/storage-list.svelte.d.ts +0 -89
- package/dist/storage/storage-list.svelte.js +0 -123
- package/dist/storage/upload-task.svelte.d.ts +0 -94
- package/dist/storage/upload-task.svelte.js +0 -138
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview FirekitUser Store - Reactive user state management using Svelte 5 runes
|
|
3
|
+
* @module FirekitUserStore
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
import { onAuthStateChanged, updateProfile, updateEmail, updatePassword, reload, sendEmailVerification, getIdToken } from 'firebase/auth';
|
|
7
|
+
import { doc, setDoc, getDoc, serverTimestamp } from 'firebase/firestore';
|
|
8
|
+
import { firebaseService } from '../firebase.js';
|
|
9
|
+
import { FirekitAuthError, AuthErrorCode } from '../types/auth.js';
|
|
10
|
+
import { mapFirebaseUserToProfile, updateUserInFirestore, createAuthError, validateCurrentUser } from '../utils/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* Reactive user store using Svelte 5 runes for state management.
|
|
13
|
+
* Provides real-time authentication state and user profile management.
|
|
14
|
+
*
|
|
15
|
+
* @class FirekitUserStore
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { firekitUser } from 'svelte-firekit';
|
|
19
|
+
*
|
|
20
|
+
* // Access reactive state
|
|
21
|
+
* $: if (firekitUser.isAuthenticated) {
|
|
22
|
+
* console.log("User:", firekitUser.user);
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* // Update profile
|
|
26
|
+
* await firekitUser.updateDisplayName("John Doe");
|
|
27
|
+
*
|
|
28
|
+
* // Get extended user data
|
|
29
|
+
* const userData = await firekitUser.getExtendedUserData();
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
class FirekitUserStore {
|
|
33
|
+
static instance;
|
|
34
|
+
auth = null;
|
|
35
|
+
firestore = null;
|
|
36
|
+
_servicesInitialized = false;
|
|
37
|
+
// ========================================
|
|
38
|
+
// REACTIVE STATE (Svelte 5 Runes)
|
|
39
|
+
// ========================================
|
|
40
|
+
/** Current user profile state */
|
|
41
|
+
_user = $state(null);
|
|
42
|
+
/** Loading state indicator */
|
|
43
|
+
_loading = $state(true);
|
|
44
|
+
/** Authentication initialization state */
|
|
45
|
+
_initialized = $state(false);
|
|
46
|
+
/** Current error state */
|
|
47
|
+
_error = $state(null);
|
|
48
|
+
// ========================================
|
|
49
|
+
// DERIVED STATE
|
|
50
|
+
// ========================================
|
|
51
|
+
/** Derived: Whether user is authenticated (not anonymous) */
|
|
52
|
+
_isAuthenticated = $derived(this._user !== null && !this._user.isAnonymous);
|
|
53
|
+
/** Derived: Whether user is anonymous */
|
|
54
|
+
_isAnonymous = $derived(this._user?.isAnonymous ?? false);
|
|
55
|
+
/** Derived: Whether user's email is verified */
|
|
56
|
+
_isEmailVerified = $derived(this._user?.emailVerified ?? false);
|
|
57
|
+
/** Derived: User's primary email address */
|
|
58
|
+
_userEmail = $derived(this._user?.email ?? null);
|
|
59
|
+
/** Derived: User's display name */
|
|
60
|
+
_userDisplayName = $derived(this._user?.displayName ?? null);
|
|
61
|
+
/** Derived: User's photo URL */
|
|
62
|
+
_userPhotoURL = $derived(this._user?.photoURL ?? null);
|
|
63
|
+
/** Derived: User's unique ID */
|
|
64
|
+
_userId = $derived(this._user?.uid ?? null);
|
|
65
|
+
/** Derived: User's phone number */
|
|
66
|
+
_userPhoneNumber = $derived(this._user?.phoneNumber ?? null);
|
|
67
|
+
constructor() {
|
|
68
|
+
// Don't initialize Firebase services in constructor
|
|
69
|
+
// They will be initialized lazily when needed
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Gets singleton instance of FirekitUserStore
|
|
73
|
+
* @returns {FirekitUserStore} The FirekitUserStore instance
|
|
74
|
+
*/
|
|
75
|
+
static getInstance() {
|
|
76
|
+
if (!FirekitUserStore.instance) {
|
|
77
|
+
FirekitUserStore.instance = new FirekitUserStore();
|
|
78
|
+
}
|
|
79
|
+
return FirekitUserStore.instance;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Initializes Firebase services and auth state listener
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
initializeServices() {
|
|
86
|
+
if (this._servicesInitialized)
|
|
87
|
+
return;
|
|
88
|
+
try {
|
|
89
|
+
this.auth = firebaseService.getAuthInstance();
|
|
90
|
+
this.firestore = firebaseService.getDbInstance();
|
|
91
|
+
this._servicesInitialized = true;
|
|
92
|
+
this.initializeAuthStateListener();
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error('Failed to initialize Firebase services:', error);
|
|
96
|
+
this._error = error instanceof Error ? error : new Error(String(error));
|
|
97
|
+
this._loading = false;
|
|
98
|
+
this._initialized = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Initializes the authentication state listener
|
|
103
|
+
* @private
|
|
104
|
+
*/
|
|
105
|
+
initializeAuthStateListener() {
|
|
106
|
+
if (!this.auth) {
|
|
107
|
+
console.error('Auth instance not available');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
onAuthStateChanged(this.auth, (firebaseUser) => {
|
|
111
|
+
this._user = firebaseUser ? this.mapFirebaseUserToProfile(firebaseUser) : null;
|
|
112
|
+
this._loading = false;
|
|
113
|
+
this._initialized = true;
|
|
114
|
+
this._error = null;
|
|
115
|
+
}, (error) => {
|
|
116
|
+
console.error('Auth state change error:', error);
|
|
117
|
+
this._error = error;
|
|
118
|
+
this._loading = false;
|
|
119
|
+
this._initialized = true;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Maps Firebase User to UserProfile interface
|
|
124
|
+
* @private
|
|
125
|
+
*/
|
|
126
|
+
mapFirebaseUserToProfile(user) {
|
|
127
|
+
return mapFirebaseUserToProfile(user);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Updates user data in Firestore
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
async updateUserInFirestore(user) {
|
|
134
|
+
if (!this.firestore) {
|
|
135
|
+
throw new Error('Firestore instance not available');
|
|
136
|
+
}
|
|
137
|
+
await updateUserInFirestore(this.firestore, user);
|
|
138
|
+
}
|
|
139
|
+
// ========================================
|
|
140
|
+
// PUBLIC GETTERS (Reactive State)
|
|
141
|
+
// ========================================
|
|
142
|
+
/** Current user profile */
|
|
143
|
+
get user() {
|
|
144
|
+
this.initializeServices();
|
|
145
|
+
return this._user;
|
|
146
|
+
}
|
|
147
|
+
/** Current loading state */
|
|
148
|
+
get loading() {
|
|
149
|
+
this.initializeServices();
|
|
150
|
+
return this._loading;
|
|
151
|
+
}
|
|
152
|
+
/** Whether auth has been initialized */
|
|
153
|
+
get initialized() {
|
|
154
|
+
this.initializeServices();
|
|
155
|
+
return this._initialized;
|
|
156
|
+
}
|
|
157
|
+
/** Current error state */
|
|
158
|
+
get error() {
|
|
159
|
+
this.initializeServices();
|
|
160
|
+
return this._error;
|
|
161
|
+
}
|
|
162
|
+
/** Whether user is authenticated (not anonymous) */
|
|
163
|
+
get isAuthenticated() {
|
|
164
|
+
this.initializeServices();
|
|
165
|
+
return this._isAuthenticated;
|
|
166
|
+
}
|
|
167
|
+
/** Whether user is anonymous */
|
|
168
|
+
get isAnonymous() {
|
|
169
|
+
this.initializeServices();
|
|
170
|
+
return this._isAnonymous;
|
|
171
|
+
}
|
|
172
|
+
/** Whether user's email is verified */
|
|
173
|
+
get isEmailVerified() {
|
|
174
|
+
this.initializeServices();
|
|
175
|
+
return this._isEmailVerified;
|
|
176
|
+
}
|
|
177
|
+
/** User's email address */
|
|
178
|
+
get email() {
|
|
179
|
+
this.initializeServices();
|
|
180
|
+
return this._userEmail;
|
|
181
|
+
}
|
|
182
|
+
/** User's display name */
|
|
183
|
+
get displayName() {
|
|
184
|
+
this.initializeServices();
|
|
185
|
+
return this._userDisplayName;
|
|
186
|
+
}
|
|
187
|
+
/** User's photo URL */
|
|
188
|
+
get photoURL() {
|
|
189
|
+
this.initializeServices();
|
|
190
|
+
return this._userPhotoURL;
|
|
191
|
+
}
|
|
192
|
+
/** User's unique ID */
|
|
193
|
+
get uid() {
|
|
194
|
+
this.initializeServices();
|
|
195
|
+
return this._userId;
|
|
196
|
+
}
|
|
197
|
+
/** User's phone number */
|
|
198
|
+
get phoneNumber() {
|
|
199
|
+
this.initializeServices();
|
|
200
|
+
return this._userPhoneNumber;
|
|
201
|
+
}
|
|
202
|
+
// ========================================
|
|
203
|
+
// PROFILE UPDATE METHODS
|
|
204
|
+
// ========================================
|
|
205
|
+
/**
|
|
206
|
+
* Updates user's display name
|
|
207
|
+
* @param {string} displayName New display name
|
|
208
|
+
* @returns {Promise<void>} Promise that resolves when update completes
|
|
209
|
+
* @throws {FirekitAuthError} If update fails
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* await firekitUser.updateDisplayName("John Doe");
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
async updateDisplayName(displayName) {
|
|
217
|
+
this.initializeServices();
|
|
218
|
+
if (!this.auth) {
|
|
219
|
+
throw new Error('Auth instance not available');
|
|
220
|
+
}
|
|
221
|
+
const currentUser = validateCurrentUser(this.auth);
|
|
222
|
+
try {
|
|
223
|
+
this._loading = true;
|
|
224
|
+
await updateProfile(currentUser, { displayName });
|
|
225
|
+
await this.updateUserInFirestore(currentUser);
|
|
226
|
+
// Update local state
|
|
227
|
+
if (this._user) {
|
|
228
|
+
this._user = { ...this._user, displayName };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
this._error = error;
|
|
233
|
+
throw createAuthError(error, 'update display name');
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
this._loading = false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Updates user's photo URL
|
|
241
|
+
* @param {string} photoURL New photo URL
|
|
242
|
+
* @returns {Promise<void>} Promise that resolves when update completes
|
|
243
|
+
* @throws {FirekitAuthError} If update fails
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* await firekitUser.updatePhotoURL("https://example.com/photo.jpg");
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
async updatePhotoURL(photoURL) {
|
|
251
|
+
this.initializeServices();
|
|
252
|
+
if (!this.auth) {
|
|
253
|
+
throw new Error('Auth instance not available');
|
|
254
|
+
}
|
|
255
|
+
const currentUser = validateCurrentUser(this.auth);
|
|
256
|
+
try {
|
|
257
|
+
this._loading = true;
|
|
258
|
+
await updateProfile(currentUser, { photoURL });
|
|
259
|
+
await this.updateUserInFirestore(currentUser);
|
|
260
|
+
// Update local state
|
|
261
|
+
if (this._user) {
|
|
262
|
+
this._user = { ...this._user, photoURL };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
this._error = error;
|
|
267
|
+
throw createAuthError(error, 'update photo URL');
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
this._loading = false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Updates user's profile data
|
|
275
|
+
* @param {UserProfileUpdateData} profileData Profile data to update
|
|
276
|
+
* @returns {Promise<void>} Promise that resolves when update completes
|
|
277
|
+
* @throws {FirekitAuthError} If update fails
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```typescript
|
|
281
|
+
* await firekitUser.updateProfile({
|
|
282
|
+
* displayName: "John Smith",
|
|
283
|
+
* photoURL: "https://example.com/new-photo.jpg"
|
|
284
|
+
* });
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
async updateProfile(profileData) {
|
|
288
|
+
this.initializeServices();
|
|
289
|
+
if (!this.auth) {
|
|
290
|
+
throw new Error('Auth instance not available');
|
|
291
|
+
}
|
|
292
|
+
const currentUser = validateCurrentUser(this.auth);
|
|
293
|
+
try {
|
|
294
|
+
this._loading = true;
|
|
295
|
+
await updateProfile(currentUser, profileData);
|
|
296
|
+
await this.updateUserInFirestore(currentUser);
|
|
297
|
+
// Update local state
|
|
298
|
+
if (this._user) {
|
|
299
|
+
this._user = {
|
|
300
|
+
...this._user,
|
|
301
|
+
displayName: profileData.displayName ?? this._user.displayName,
|
|
302
|
+
photoURL: profileData.photoURL ?? this._user.photoURL
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
this._error = error;
|
|
308
|
+
throw createAuthError(error, 'update profile');
|
|
309
|
+
}
|
|
310
|
+
finally {
|
|
311
|
+
this._loading = false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Updates user's email address
|
|
316
|
+
* @param {string} newEmail New email address
|
|
317
|
+
* @returns {Promise<void>} Promise that resolves when update completes
|
|
318
|
+
* @throws {FirekitAuthError} If update fails
|
|
319
|
+
*
|
|
320
|
+
* @example
|
|
321
|
+
* ```typescript
|
|
322
|
+
* await firekitUser.updateEmail("newemail@example.com");
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
async updateEmail(newEmail) {
|
|
326
|
+
this.initializeServices();
|
|
327
|
+
if (!this.auth) {
|
|
328
|
+
throw new Error('Auth instance not available');
|
|
329
|
+
}
|
|
330
|
+
const currentUser = validateCurrentUser(this.auth);
|
|
331
|
+
try {
|
|
332
|
+
this._loading = true;
|
|
333
|
+
await updateEmail(currentUser, newEmail);
|
|
334
|
+
await this.updateUserInFirestore(currentUser);
|
|
335
|
+
// Update local state
|
|
336
|
+
if (this._user) {
|
|
337
|
+
this._user = { ...this._user, email: newEmail, emailVerified: false };
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
this._error = error;
|
|
342
|
+
throw createAuthError(error, 'update email');
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
this._loading = false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Updates user's password
|
|
350
|
+
* @param {string} newPassword New password
|
|
351
|
+
* @returns {Promise<void>} Promise that resolves when update completes
|
|
352
|
+
* @throws {FirekitAuthError} If update fails
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```typescript
|
|
356
|
+
* await firekitUser.updatePassword("newSecurePassword123");
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
async updatePassword(newPassword) {
|
|
360
|
+
this.initializeServices();
|
|
361
|
+
if (!this.auth) {
|
|
362
|
+
throw new Error('Auth instance not available');
|
|
363
|
+
}
|
|
364
|
+
const currentUser = validateCurrentUser(this.auth);
|
|
365
|
+
try {
|
|
366
|
+
this._loading = true;
|
|
367
|
+
await updatePassword(currentUser, newPassword);
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
this._error = error;
|
|
371
|
+
throw createAuthError(error, 'update password');
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
this._loading = false;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// ========================================
|
|
378
|
+
// EMAIL VERIFICATION METHODS
|
|
379
|
+
// ========================================
|
|
380
|
+
/**
|
|
381
|
+
* Sends email verification to current user
|
|
382
|
+
* @returns {Promise<void>} Promise that resolves when verification email is sent
|
|
383
|
+
* @throws {FirekitAuthError} If sending fails
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```typescript
|
|
387
|
+
* await firekitUser.sendEmailVerification();
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
async sendEmailVerification() {
|
|
391
|
+
this.initializeServices();
|
|
392
|
+
if (!this.auth) {
|
|
393
|
+
throw new Error('Auth instance not available');
|
|
394
|
+
}
|
|
395
|
+
const currentUser = validateCurrentUser(this.auth);
|
|
396
|
+
try {
|
|
397
|
+
await sendEmailVerification(currentUser);
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
this._error = error;
|
|
401
|
+
throw createAuthError(error, 'send email verification');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Reloads user to get updated email verification status
|
|
406
|
+
* @returns {Promise<void>} Promise that resolves when user is reloaded
|
|
407
|
+
* @throws {FirekitAuthError} If reload fails
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* ```typescript
|
|
411
|
+
* await firekitUser.reloadUser();
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
async reloadUser() {
|
|
415
|
+
this.initializeServices();
|
|
416
|
+
if (!this.auth) {
|
|
417
|
+
throw new Error('Auth instance not available');
|
|
418
|
+
}
|
|
419
|
+
const currentUser = validateCurrentUser(this.auth);
|
|
420
|
+
try {
|
|
421
|
+
this._loading = true;
|
|
422
|
+
await reload(currentUser);
|
|
423
|
+
await this.updateUserInFirestore(currentUser);
|
|
424
|
+
// Update local state with reloaded data
|
|
425
|
+
this._user = this.mapFirebaseUserToProfile(currentUser);
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
this._error = error;
|
|
429
|
+
throw createAuthError(error, 'reload user');
|
|
430
|
+
}
|
|
431
|
+
finally {
|
|
432
|
+
this._loading = false;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// ========================================
|
|
436
|
+
// TOKEN METHODS
|
|
437
|
+
// ========================================
|
|
438
|
+
/**
|
|
439
|
+
* Gets the current user's ID token
|
|
440
|
+
* @param {boolean} [forceRefresh=false] Whether to force token refresh
|
|
441
|
+
* @returns {Promise<string>} Promise resolving to ID token
|
|
442
|
+
* @throws {FirekitAuthError} If getting token fails
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* ```typescript
|
|
446
|
+
* const token = await firekitUser.getIdToken();
|
|
447
|
+
* ```
|
|
448
|
+
*/
|
|
449
|
+
async getIdToken(forceRefresh = false) {
|
|
450
|
+
this.initializeServices();
|
|
451
|
+
if (!this.auth) {
|
|
452
|
+
throw new Error('Auth instance not available');
|
|
453
|
+
}
|
|
454
|
+
const currentUser = validateCurrentUser(this.auth);
|
|
455
|
+
try {
|
|
456
|
+
return await getIdToken(currentUser, forceRefresh);
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
this._error = error;
|
|
460
|
+
throw createAuthError(error, 'get ID token');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// ========================================
|
|
464
|
+
// FIRESTORE EXTENDED DATA METHODS
|
|
465
|
+
// ========================================
|
|
466
|
+
/**
|
|
467
|
+
* Gets extended user data from Firestore
|
|
468
|
+
* @returns {Promise<ExtendedUserData | null>} Promise resolving to extended user data
|
|
469
|
+
*
|
|
470
|
+
* @example
|
|
471
|
+
* ```typescript
|
|
472
|
+
* const userData = await firekitUser.getExtendedUserData();
|
|
473
|
+
* console.log(userData?.preferences);
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
async getExtendedUserData() {
|
|
477
|
+
this.initializeServices();
|
|
478
|
+
if (!this._user?.uid || !this.firestore) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const userRef = doc(this.firestore, 'users', this._user.uid);
|
|
483
|
+
const userDoc = await getDoc(userRef);
|
|
484
|
+
if (userDoc.exists()) {
|
|
485
|
+
return userDoc.data();
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
console.error('Failed to get extended user data:', error);
|
|
491
|
+
this._error = error;
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Updates extended user data in Firestore
|
|
497
|
+
* @param {Partial<ExtendedUserData>} data Data to update
|
|
498
|
+
* @returns {Promise<void>} Promise that resolves when update completes
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```typescript
|
|
502
|
+
* await firekitUser.updateExtendedUserData({
|
|
503
|
+
* preferences: { theme: 'dark', language: 'en' },
|
|
504
|
+
* settings: { notifications: true }
|
|
505
|
+
* });
|
|
506
|
+
* ```
|
|
507
|
+
*/
|
|
508
|
+
async updateExtendedUserData(data) {
|
|
509
|
+
this.initializeServices();
|
|
510
|
+
if (!this._user?.uid) {
|
|
511
|
+
throw new FirekitAuthError(AuthErrorCode.USER_NOT_FOUND, 'No authenticated user found.');
|
|
512
|
+
}
|
|
513
|
+
if (!this.firestore) {
|
|
514
|
+
throw new Error('Firestore instance not available');
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
const userRef = doc(this.firestore, 'users', this._user.uid);
|
|
518
|
+
await setDoc(userRef, {
|
|
519
|
+
...data,
|
|
520
|
+
updatedAt: serverTimestamp()
|
|
521
|
+
}, { merge: true });
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
this._error = error;
|
|
525
|
+
throw createAuthError(error, 'update user data');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// ========================================
|
|
529
|
+
// UTILITY METHODS
|
|
530
|
+
// ========================================
|
|
531
|
+
/**
|
|
532
|
+
* Waits for authentication to initialize
|
|
533
|
+
* @returns {Promise<UserProfile | null>} Promise that resolves when auth is initialized
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```typescript
|
|
537
|
+
* const user = await firekitUser.waitForAuth();
|
|
538
|
+
* if (user) {
|
|
539
|
+
* console.log("User is authenticated");
|
|
540
|
+
* }
|
|
541
|
+
* ```
|
|
542
|
+
*/
|
|
543
|
+
async waitForAuth() {
|
|
544
|
+
this.initializeServices();
|
|
545
|
+
return new Promise((resolve) => {
|
|
546
|
+
if (this._initialized) {
|
|
547
|
+
resolve(this._user);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
// Create a one-time watcher
|
|
551
|
+
const checkInitialized = () => {
|
|
552
|
+
if (this._initialized) {
|
|
553
|
+
resolve(this._user);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
// Check again in next tick
|
|
557
|
+
setTimeout(checkInitialized, 10);
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
checkInitialized();
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Clears any error state
|
|
565
|
+
*/
|
|
566
|
+
clearError() {
|
|
567
|
+
this._error = null;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Manually sets loading state (use with caution)
|
|
571
|
+
* @param {boolean} loading Loading state
|
|
572
|
+
*/
|
|
573
|
+
setLoading(loading) {
|
|
574
|
+
this._loading = loading;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Resets the store to initial state
|
|
578
|
+
*/
|
|
579
|
+
reset() {
|
|
580
|
+
this._user = null;
|
|
581
|
+
this._loading = true;
|
|
582
|
+
this._initialized = false;
|
|
583
|
+
this._error = null;
|
|
584
|
+
this._servicesInitialized = false;
|
|
585
|
+
this.auth = null;
|
|
586
|
+
this.firestore = null;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Pre-initialized singleton instance of FirekitUserStore.
|
|
591
|
+
* Provides reactive user state management for Svelte applications.
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
* ```typescript
|
|
595
|
+
* import { firekitUser } from 'svelte-firekit';
|
|
596
|
+
*
|
|
597
|
+
* // In a Svelte component
|
|
598
|
+
* $: if (firekitUser.isAuthenticated) {
|
|
599
|
+
* console.log("Welcome:", firekitUser.displayName);
|
|
600
|
+
* }
|
|
601
|
+
*
|
|
602
|
+
* // Update profile
|
|
603
|
+
* await firekitUser.updateDisplayName("John Doe");
|
|
604
|
+
*
|
|
605
|
+
* // Get extended data
|
|
606
|
+
* const userData = await firekitUser.getExtendedUserData();
|
|
607
|
+
* ```
|
|
608
|
+
*/
|
|
609
|
+
export const firekitUser = FirekitUserStore.getInstance();
|