svelte-firekit 0.0.25 → 0.1.0

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.
Files changed (95) hide show
  1. package/README.md +445 -213
  2. package/dist/components/Collection.svelte +150 -0
  3. package/dist/components/Collection.svelte.d.ts +27 -0
  4. package/dist/components/Ddoc.svelte +131 -0
  5. package/dist/components/Ddoc.svelte.d.ts +28 -0
  6. package/dist/components/Node.svelte +97 -0
  7. package/dist/components/Node.svelte.d.ts +23 -0
  8. package/dist/components/auth-guard.svelte +89 -0
  9. package/dist/components/auth-guard.svelte.d.ts +26 -0
  10. package/dist/components/custom-guard.svelte +122 -0
  11. package/dist/components/custom-guard.svelte.d.ts +31 -0
  12. package/dist/components/download-url.svelte +92 -0
  13. package/dist/components/download-url.svelte.d.ts +19 -0
  14. package/dist/components/firebase-app.svelte +30 -0
  15. package/dist/components/firebase-app.svelte.d.ts +7 -0
  16. package/dist/components/node-list.svelte +102 -0
  17. package/dist/components/node-list.svelte.d.ts +27 -0
  18. package/dist/components/signed-in.svelte +42 -0
  19. package/dist/components/signed-in.svelte.d.ts +11 -0
  20. package/dist/components/signed-out.svelte +42 -0
  21. package/dist/components/signed-out.svelte.d.ts +11 -0
  22. package/dist/components/storage-list.svelte +97 -0
  23. package/dist/components/storage-list.svelte.d.ts +26 -0
  24. package/dist/components/upload-task.svelte +108 -0
  25. package/dist/components/upload-task.svelte.d.ts +24 -0
  26. package/dist/config.js +17 -39
  27. package/dist/firebase.d.ts +43 -21
  28. package/dist/firebase.js +121 -35
  29. package/dist/index.d.ts +21 -13
  30. package/dist/index.js +27 -15
  31. package/dist/services/auth.d.ts +389 -0
  32. package/dist/services/auth.js +824 -0
  33. package/dist/services/collection.svelte.d.ts +286 -0
  34. package/dist/services/collection.svelte.js +871 -0
  35. package/dist/services/document.svelte.d.ts +288 -0
  36. package/dist/services/document.svelte.js +555 -0
  37. package/dist/services/mutations.d.ts +336 -0
  38. package/dist/services/mutations.js +1079 -0
  39. package/dist/services/presence.svelte.d.ts +141 -0
  40. package/dist/services/presence.svelte.js +727 -0
  41. package/dist/{realtime → services}/realtime.svelte.d.ts +3 -1
  42. package/dist/{realtime → services}/realtime.svelte.js +13 -7
  43. package/dist/services/storage.svelte.d.ts +257 -0
  44. package/dist/services/storage.svelte.js +374 -0
  45. package/dist/services/user.svelte.d.ts +290 -0
  46. package/dist/services/user.svelte.js +533 -0
  47. package/dist/types/auth.d.ts +158 -0
  48. package/dist/types/auth.js +106 -0
  49. package/dist/types/collection.d.ts +360 -0
  50. package/dist/types/collection.js +167 -0
  51. package/dist/types/document.d.ts +342 -0
  52. package/dist/types/document.js +148 -0
  53. package/dist/types/firebase.d.ts +44 -0
  54. package/dist/types/firebase.js +33 -0
  55. package/dist/types/index.d.ts +6 -0
  56. package/dist/types/index.js +4 -0
  57. package/dist/types/mutations.d.ts +387 -0
  58. package/dist/types/mutations.js +205 -0
  59. package/dist/types/presence.d.ts +282 -0
  60. package/dist/types/presence.js +80 -0
  61. package/dist/utils/errors.d.ts +21 -0
  62. package/dist/utils/errors.js +35 -0
  63. package/dist/utils/firestore.d.ts +9 -0
  64. package/dist/utils/firestore.js +33 -0
  65. package/dist/utils/index.d.ts +4 -0
  66. package/dist/utils/index.js +8 -0
  67. package/dist/utils/providers.d.ts +16 -0
  68. package/dist/utils/providers.js +30 -0
  69. package/dist/utils/user.d.ts +8 -0
  70. package/dist/utils/user.js +29 -0
  71. package/package.json +65 -65
  72. package/dist/auth/auth.d.ts +0 -117
  73. package/dist/auth/auth.js +0 -194
  74. package/dist/auth/presence.svelte.d.ts +0 -139
  75. package/dist/auth/presence.svelte.js +0 -373
  76. package/dist/auth/user.svelte.d.ts +0 -112
  77. package/dist/auth/user.svelte.js +0 -155
  78. package/dist/firestore/awaitable-doc.svelte.d.ts +0 -141
  79. package/dist/firestore/awaitable-doc.svelte.js +0 -183
  80. package/dist/firestore/batch-mutations.svelte.d.ts +0 -140
  81. package/dist/firestore/batch-mutations.svelte.js +0 -218
  82. package/dist/firestore/collection-group.svelte.d.ts +0 -78
  83. package/dist/firestore/collection-group.svelte.js +0 -120
  84. package/dist/firestore/collection.svelte.d.ts +0 -96
  85. package/dist/firestore/collection.svelte.js +0 -137
  86. package/dist/firestore/doc.svelte.d.ts +0 -90
  87. package/dist/firestore/doc.svelte.js +0 -131
  88. package/dist/firestore/document-mutations.svelte.d.ts +0 -164
  89. package/dist/firestore/document-mutations.svelte.js +0 -273
  90. package/dist/storage/download-url.svelte.d.ts +0 -83
  91. package/dist/storage/download-url.svelte.js +0 -114
  92. package/dist/storage/storage-list.svelte.d.ts +0 -89
  93. package/dist/storage/storage-list.svelte.js +0 -123
  94. package/dist/storage/upload-task.svelte.d.ts +0 -94
  95. package/dist/storage/upload-task.svelte.js +0 -138
@@ -0,0 +1,824 @@
1
+ /**
2
+ * @fileoverview FirekitAuth - Complete Firebase Authentication Service for Svelte
3
+ * @module FirekitAuth
4
+ * @version 1.0.0
5
+ */
6
+ import { EmailAuthProvider, PhoneAuthProvider, RecaptchaVerifier, signInWithEmailAndPassword, signInWithPopup, signInWithPhoneNumber as firebaseSignInWithPhoneNumber, signInWithCredential, signInAnonymously as firebaseSignInAnonymously, createUserWithEmailAndPassword, signOut, sendPasswordResetEmail, confirmPasswordReset, sendEmailVerification, updateProfile, updateEmail, updatePassword, reauthenticateWithCredential, deleteUser, reload, getIdToken, onAuthStateChanged } from 'firebase/auth';
7
+ import { doc, setDoc, serverTimestamp } from 'firebase/firestore';
8
+ import { firebaseService } from '../firebase.js';
9
+ import { AuthErrorCode, FirekitAuthError } from '../types/auth.js';
10
+ import { mapFirebaseUserToProfile, updateUserInFirestore, createGoogleProvider, createFacebookProvider, createAppleProvider, handleAuthError } from '../utils/index.js';
11
+ /**
12
+ * Comprehensive Firebase Authentication service for Svelte applications.
13
+ * Provides a complete authentication solution with automatic Firestore integration,
14
+ * error handling, and support for all major authentication methods.
15
+ *
16
+ * @class FirekitAuth
17
+ * @example
18
+ * ```typescript
19
+ * import { firekitAuth } from 'svelte-firekit';
20
+ *
21
+ * // Sign in with Google
22
+ * await firekitAuth.signInWithGoogle();
23
+ *
24
+ * // Register new user
25
+ * await firekitAuth.registerWithEmail("user@example.com", "password123", "John Doe");
26
+ *
27
+ * // Listen to auth state changes
28
+ * const unsubscribe = firekitAuth.onAuthStateChanged((user) => {
29
+ * console.log('User:', user);
30
+ * });
31
+ * ```
32
+ */
33
+ class FirekitAuth {
34
+ static instance;
35
+ auth = firebaseService.getAuthInstance();
36
+ firestore = firebaseService.getDbInstance();
37
+ authState = {
38
+ user: null,
39
+ loading: true,
40
+ initialized: false
41
+ };
42
+ stateListeners = new Set();
43
+ recaptchaVerifiers = new Map();
44
+ constructor() {
45
+ if (!this.auth) {
46
+ throw new Error('Firebase Auth instance not available');
47
+ }
48
+ if (!this.firestore) {
49
+ throw new Error('Firestore instance not available');
50
+ }
51
+ this.initializeAuthStateListener();
52
+ }
53
+ /**
54
+ * Gets singleton instance of FirekitAuth
55
+ * @returns {FirekitAuth} The FirekitAuth instance
56
+ */
57
+ static getInstance() {
58
+ if (!FirekitAuth.instance) {
59
+ FirekitAuth.instance = new FirekitAuth();
60
+ }
61
+ return FirekitAuth.instance;
62
+ }
63
+ /**
64
+ * Initializes the authentication state listener
65
+ * @private
66
+ */
67
+ initializeAuthStateListener() {
68
+ onAuthStateChanged(this.auth, (user) => {
69
+ this.authState = {
70
+ user: user ? this.mapFirebaseUserToProfile(user) : null,
71
+ loading: false,
72
+ initialized: true
73
+ };
74
+ this.notifyStateListeners();
75
+ }, (error) => {
76
+ console.error('Auth state change error:', error);
77
+ this.authState = {
78
+ user: null,
79
+ loading: false,
80
+ initialized: true
81
+ };
82
+ this.notifyStateListeners();
83
+ });
84
+ }
85
+ /**
86
+ * Notifies all state listeners of auth state changes
87
+ * @private
88
+ */
89
+ notifyStateListeners() {
90
+ this.stateListeners.forEach((listener) => listener(this.authState));
91
+ }
92
+ /**
93
+ * Maps Firebase User to UserProfile interface
94
+ * @private
95
+ */
96
+ mapFirebaseUserToProfile(user) {
97
+ return mapFirebaseUserToProfile(user);
98
+ }
99
+ /**
100
+ * Updates user data in Firestore with comprehensive profile information
101
+ * @private
102
+ */
103
+ async updateUserInFirestore(user) {
104
+ await updateUserInFirestore(this.firestore, user);
105
+ }
106
+ /**
107
+ * Handles Firebase authentication errors and throws FirekitAuthError
108
+ * @private
109
+ */
110
+ handleAuthError(error) {
111
+ handleAuthError(error);
112
+ }
113
+ /**
114
+ * Gets the current authentication state
115
+ * @returns {AuthState} Current authentication state
116
+ */
117
+ getState() {
118
+ return { ...this.authState };
119
+ }
120
+ /**
121
+ * Gets the current authenticated user
122
+ * @returns {User | null} Current Firebase user or null
123
+ */
124
+ getCurrentUser() {
125
+ return this.auth.currentUser;
126
+ }
127
+ /**
128
+ * Gets the current user profile
129
+ * @returns {UserProfile | null} Current user profile or null
130
+ */
131
+ getCurrentUserProfile() {
132
+ return this.authState.user;
133
+ }
134
+ /**
135
+ * Waits for auth initialization to complete
136
+ * @returns {Promise<UserProfile | null>} Promise that resolves when auth is initialized
137
+ */
138
+ async waitForAuth() {
139
+ return new Promise((resolve) => {
140
+ if (this.authState.initialized) {
141
+ resolve(this.authState.user);
142
+ return;
143
+ }
144
+ const unsubscribe = this.onAuthStateChanged((state) => {
145
+ if (state.initialized) {
146
+ unsubscribe();
147
+ resolve(state.user);
148
+ }
149
+ });
150
+ });
151
+ }
152
+ /**
153
+ * Subscribes to authentication state changes
154
+ * @param {Function} callback Callback function to handle state changes
155
+ * @returns {Function} Unsubscribe function
156
+ */
157
+ onAuthStateChanged(callback) {
158
+ this.stateListeners.add(callback);
159
+ // Immediately call with current state
160
+ callback(this.authState);
161
+ return () => {
162
+ this.stateListeners.delete(callback);
163
+ };
164
+ }
165
+ // ========================================
166
+ // SIGN IN METHODS
167
+ // ========================================
168
+ /**
169
+ * Signs in user with email and password
170
+ * @param {string} email User's email address
171
+ * @param {string} password User's password
172
+ * @returns {Promise<UserProfile>} Promise resolving to user profile
173
+ * @throws {FirekitAuthError} If sign-in fails
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * try {
178
+ * const user = await firekitAuth.signInWithEmail("user@example.com", "password123");
179
+ * console.log("Signed in:", user.displayName);
180
+ * } catch (error) {
181
+ * console.error("Sign-in failed:", error.message);
182
+ * }
183
+ * ```
184
+ */
185
+ async signInWithEmail(email, password) {
186
+ try {
187
+ this.authState.loading = true;
188
+ this.notifyStateListeners();
189
+ const userCredential = await signInWithEmailAndPassword(this.auth, email, password);
190
+ await this.updateUserInFirestore(userCredential.user);
191
+ return this.mapFirebaseUserToProfile(userCredential.user);
192
+ }
193
+ catch (error) {
194
+ this.handleAuthError(error);
195
+ }
196
+ finally {
197
+ this.authState.loading = false;
198
+ this.notifyStateListeners();
199
+ }
200
+ }
201
+ /**
202
+ * Signs in user with Google popup
203
+ * @returns {Promise<UserProfile>} Promise resolving to user profile
204
+ * @throws {FirekitAuthError} If sign-in fails
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * try {
209
+ * const user = await firekitAuth.signInWithGoogle();
210
+ * console.log("Signed in with Google:", user.email);
211
+ * } catch (error) {
212
+ * if (error.code === 'auth/popup-closed-by-user') {
213
+ * console.log("User cancelled sign-in");
214
+ * }
215
+ * }
216
+ * ```
217
+ */
218
+ async signInWithGoogle() {
219
+ try {
220
+ this.authState.loading = true;
221
+ this.notifyStateListeners();
222
+ const provider = createGoogleProvider();
223
+ const result = await signInWithPopup(this.auth, provider);
224
+ await this.updateUserInFirestore(result.user);
225
+ return this.mapFirebaseUserToProfile(result.user);
226
+ }
227
+ catch (error) {
228
+ this.handleAuthError(error);
229
+ }
230
+ finally {
231
+ this.authState.loading = false;
232
+ this.notifyStateListeners();
233
+ }
234
+ }
235
+ /**
236
+ * Signs in user with Facebook popup
237
+ * @returns {Promise<UserProfile>} Promise resolving to user profile
238
+ * @throws {FirekitAuthError} If sign-in fails
239
+ */
240
+ async signInWithFacebook() {
241
+ try {
242
+ this.authState.loading = true;
243
+ this.notifyStateListeners();
244
+ const provider = createFacebookProvider();
245
+ const result = await signInWithPopup(this.auth, provider);
246
+ await this.updateUserInFirestore(result.user);
247
+ return this.mapFirebaseUserToProfile(result.user);
248
+ }
249
+ catch (error) {
250
+ this.handleAuthError(error);
251
+ }
252
+ finally {
253
+ this.authState.loading = false;
254
+ this.notifyStateListeners();
255
+ }
256
+ }
257
+ /**
258
+ * Signs in user with Apple popup
259
+ * @returns {Promise<UserProfile>} Promise resolving to user profile
260
+ * @throws {FirekitAuthError} If sign-in fails
261
+ */
262
+ async signInWithApple() {
263
+ try {
264
+ this.authState.loading = true;
265
+ this.notifyStateListeners();
266
+ const provider = createAppleProvider();
267
+ const result = await signInWithPopup(this.auth, provider);
268
+ await this.updateUserInFirestore(result.user);
269
+ return this.mapFirebaseUserToProfile(result.user);
270
+ }
271
+ catch (error) {
272
+ this.handleAuthError(error);
273
+ }
274
+ finally {
275
+ this.authState.loading = false;
276
+ this.notifyStateListeners();
277
+ }
278
+ }
279
+ /**
280
+ * Signs in user anonymously
281
+ * @returns {Promise<UserProfile>} Promise resolving to user profile
282
+ * @throws {FirekitAuthError} If sign-in fails
283
+ *
284
+ * @example
285
+ * ```typescript
286
+ * const user = await firekitAuth.signInAnonymously();
287
+ * console.log("Anonymous user:", user.uid);
288
+ * ```
289
+ */
290
+ async signInAnonymously() {
291
+ try {
292
+ this.authState.loading = true;
293
+ this.notifyStateListeners();
294
+ const result = await firebaseSignInAnonymously(this.auth);
295
+ await this.updateUserInFirestore(result.user);
296
+ return this.mapFirebaseUserToProfile(result.user);
297
+ }
298
+ catch (error) {
299
+ this.handleAuthError(error);
300
+ }
301
+ finally {
302
+ this.authState.loading = false;
303
+ this.notifyStateListeners();
304
+ }
305
+ }
306
+ /**
307
+ * Initiates phone number sign-in process
308
+ * @param {string} phoneNumber Phone number in international format
309
+ * @param {string} recaptchaContainerId ID of the reCAPTCHA container element
310
+ * @returns {Promise<PhoneVerificationResult>} Promise resolving to verification result
311
+ * @throws {FirekitAuthError} If verification initiation fails
312
+ *
313
+ * @example
314
+ * ```typescript
315
+ * const verification = await firekitAuth.signInWithPhoneNumber("+1234567890", "recaptcha-container");
316
+ * const user = await verification.confirm("123456");
317
+ * ```
318
+ */
319
+ async signInWithPhoneNumber(phoneNumber, recaptchaContainerId) {
320
+ try {
321
+ this.authState.loading = true;
322
+ this.notifyStateListeners();
323
+ // Clean up existing verifier if any
324
+ const existingVerifier = this.recaptchaVerifiers.get(recaptchaContainerId);
325
+ if (existingVerifier) {
326
+ existingVerifier.clear();
327
+ this.recaptchaVerifiers.delete(recaptchaContainerId);
328
+ }
329
+ const recaptchaVerifier = new RecaptchaVerifier(this.auth, recaptchaContainerId, {
330
+ size: 'normal',
331
+ callback: () => {
332
+ console.log('reCAPTCHA solved');
333
+ },
334
+ 'expired-callback': () => {
335
+ console.log('reCAPTCHA expired');
336
+ }
337
+ });
338
+ this.recaptchaVerifiers.set(recaptchaContainerId, recaptchaVerifier);
339
+ const confirmationResult = await firebaseSignInWithPhoneNumber(this.auth, phoneNumber, recaptchaVerifier);
340
+ return {
341
+ verificationId: confirmationResult.verificationId,
342
+ confirm: async (code) => {
343
+ try {
344
+ const result = await confirmationResult.confirm(code);
345
+ await this.updateUserInFirestore(result.user);
346
+ return this.mapFirebaseUserToProfile(result.user);
347
+ }
348
+ catch (error) {
349
+ this.handleAuthError(error);
350
+ }
351
+ finally {
352
+ // Clean up verifier after use
353
+ recaptchaVerifier.clear();
354
+ this.recaptchaVerifiers.delete(recaptchaContainerId);
355
+ }
356
+ }
357
+ };
358
+ }
359
+ catch (error) {
360
+ this.handleAuthError(error);
361
+ }
362
+ finally {
363
+ this.authState.loading = false;
364
+ this.notifyStateListeners();
365
+ }
366
+ }
367
+ // ========================================
368
+ // REGISTRATION METHODS
369
+ // ========================================
370
+ /**
371
+ * Registers new user with email and password
372
+ * @param {string} email User's email address
373
+ * @param {string} password User's password
374
+ * @param {string} [displayName] User's display name
375
+ * @param {boolean} [sendVerification=true] Whether to send email verification
376
+ * @returns {Promise<UserProfile>} Promise resolving to user profile
377
+ * @throws {FirekitAuthError} If registration fails
378
+ *
379
+ * @example
380
+ * ```typescript
381
+ * const user = await firekitAuth.registerWithEmail(
382
+ * "user@example.com",
383
+ * "password123",
384
+ * "John Doe"
385
+ * );
386
+ * console.log("Registered:", user.displayName);
387
+ * ```
388
+ */
389
+ async registerWithEmail(email, password, displayName, sendVerification = true) {
390
+ try {
391
+ this.authState.loading = true;
392
+ this.notifyStateListeners();
393
+ const userCredential = await createUserWithEmailAndPassword(this.auth, email, password);
394
+ const user = userCredential.user;
395
+ // Update profile if displayName provided
396
+ if (displayName) {
397
+ await updateProfile(user, { displayName });
398
+ }
399
+ // Send email verification
400
+ if (sendVerification) {
401
+ await sendEmailVerification(user);
402
+ }
403
+ await this.updateUserInFirestore(user);
404
+ return this.mapFirebaseUserToProfile(user);
405
+ }
406
+ catch (error) {
407
+ this.handleAuthError(error);
408
+ }
409
+ finally {
410
+ this.authState.loading = false;
411
+ this.notifyStateListeners();
412
+ }
413
+ }
414
+ // ========================================
415
+ // PASSWORD METHODS
416
+ // ========================================
417
+ /**
418
+ * Sends password reset email
419
+ * @param {string} email User's email address
420
+ * @returns {Promise<void>} Promise that resolves when email is sent
421
+ * @throws {FirekitAuthError} If sending fails
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * await firekitAuth.sendPasswordReset("user@example.com");
426
+ * console.log("Password reset email sent");
427
+ * ```
428
+ */
429
+ async sendPasswordReset(email) {
430
+ try {
431
+ await sendPasswordResetEmail(this.auth, email);
432
+ }
433
+ catch (error) {
434
+ this.handleAuthError(error);
435
+ }
436
+ }
437
+ /**
438
+ * Confirms password reset with code
439
+ * @param {string} code Password reset code from email
440
+ * @param {string} newPassword New password
441
+ * @returns {Promise<void>} Promise that resolves when password is reset
442
+ * @throws {FirekitAuthError} If reset fails
443
+ */
444
+ async confirmPasswordReset(code, newPassword) {
445
+ try {
446
+ await confirmPasswordReset(this.auth, code, newPassword);
447
+ }
448
+ catch (error) {
449
+ this.handleAuthError(error);
450
+ }
451
+ }
452
+ /**
453
+ * Updates user password with reauthentication
454
+ * @param {string} newPassword New password
455
+ * @param {string} currentPassword Current password for reauthentication
456
+ * @returns {Promise<PasswordUpdateResult>} Promise resolving to update result
457
+ *
458
+ * @example
459
+ * ```typescript
460
+ * const result = await firekitAuth.updatePassword("newPassword123", "oldPassword123");
461
+ * if (result.success) {
462
+ * console.log("Password updated successfully");
463
+ * } else {
464
+ * console.error("Update failed:", result.message);
465
+ * }
466
+ * ```
467
+ */
468
+ async updatePassword(newPassword, currentPassword) {
469
+ if (!this.auth.currentUser) {
470
+ return {
471
+ success: false,
472
+ message: 'No authenticated user found.',
473
+ code: 'auth/no-current-user'
474
+ };
475
+ }
476
+ try {
477
+ await this.reauthenticateUser(currentPassword);
478
+ await updatePassword(this.auth.currentUser, newPassword);
479
+ return {
480
+ success: true,
481
+ message: 'Password successfully updated.'
482
+ };
483
+ }
484
+ catch (error) {
485
+ const code = error.code;
486
+ if (code === AuthErrorCode.WRONG_PASSWORD) {
487
+ return {
488
+ success: false,
489
+ code,
490
+ message: 'Current password is incorrect.'
491
+ };
492
+ }
493
+ return {
494
+ success: false,
495
+ code: code || 'unknown_error',
496
+ message: error.message || 'Failed to update password.'
497
+ };
498
+ }
499
+ }
500
+ // ========================================
501
+ // PROFILE METHODS
502
+ // ========================================
503
+ /**
504
+ * Updates user profile
505
+ * @param {Object} profile Profile update data
506
+ * @param {string} [profile.displayName] New display name
507
+ * @param {string} [profile.photoURL] New photo URL
508
+ * @returns {Promise<void>} Promise that resolves when profile is updated
509
+ * @throws {FirekitAuthError} If update fails
510
+ *
511
+ * @example
512
+ * ```typescript
513
+ * await firekitAuth.updateUserProfile({
514
+ * displayName: "John Smith",
515
+ * photoURL: "https://example.com/photo.jpg"
516
+ * });
517
+ * ```
518
+ */
519
+ async updateUserProfile(profile) {
520
+ if (!this.auth.currentUser) {
521
+ throw new FirekitAuthError('auth/no-current-user', 'No authenticated user found.');
522
+ }
523
+ try {
524
+ await updateProfile(this.auth.currentUser, profile);
525
+ await this.updateUserInFirestore(this.auth.currentUser);
526
+ }
527
+ catch (error) {
528
+ this.handleAuthError(error);
529
+ }
530
+ }
531
+ /**
532
+ * Updates user email address
533
+ * @param {string} newEmail New email address
534
+ * @returns {Promise<void>} Promise that resolves when email is updated
535
+ * @throws {FirekitAuthError} If update fails
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * await firekitAuth.updateEmail("newemail@example.com");
540
+ * ```
541
+ */
542
+ async updateEmail(newEmail) {
543
+ if (!this.auth.currentUser) {
544
+ throw new FirekitAuthError('auth/no-current-user', 'No authenticated user found.');
545
+ }
546
+ try {
547
+ await updateEmail(this.auth.currentUser, newEmail);
548
+ await this.updateUserInFirestore(this.auth.currentUser);
549
+ }
550
+ catch (error) {
551
+ this.handleAuthError(error);
552
+ }
553
+ }
554
+ // ========================================
555
+ // EMAIL VERIFICATION METHODS
556
+ // ========================================
557
+ /**
558
+ * Sends email verification to current user
559
+ * @returns {Promise<void>} Promise that resolves when verification email is sent
560
+ * @throws {FirekitAuthError} If sending fails
561
+ *
562
+ * @example
563
+ * ```typescript
564
+ * await firekitAuth.sendEmailVerification();
565
+ * console.log("Verification email sent");
566
+ * ```
567
+ */
568
+ async sendEmailVerification() {
569
+ if (!this.auth.currentUser) {
570
+ throw new FirekitAuthError('auth/no-current-user', 'No authenticated user found.');
571
+ }
572
+ try {
573
+ await sendEmailVerification(this.auth.currentUser);
574
+ }
575
+ catch (error) {
576
+ this.handleAuthError(error);
577
+ }
578
+ }
579
+ /**
580
+ * Reloads current user to get updated email verification status
581
+ * @returns {Promise<void>} Promise that resolves when user is reloaded
582
+ * @throws {FirekitAuthError} If reload fails
583
+ */
584
+ async reloadUser() {
585
+ if (!this.auth.currentUser) {
586
+ throw new FirekitAuthError('auth/no-current-user', 'No authenticated user found.');
587
+ }
588
+ try {
589
+ await reload(this.auth.currentUser);
590
+ await this.updateUserInFirestore(this.auth.currentUser);
591
+ }
592
+ catch (error) {
593
+ this.handleAuthError(error);
594
+ }
595
+ }
596
+ // ========================================
597
+ // TOKEN METHODS
598
+ // ========================================
599
+ /**
600
+ * Gets the current user's ID token
601
+ * @param {boolean} [forceRefresh=false] Whether to force token refresh
602
+ * @returns {Promise<string>} Promise resolving to ID token
603
+ * @throws {FirekitAuthError} If getting token fails
604
+ *
605
+ * @example
606
+ * ```typescript
607
+ * const token = await firekitAuth.getIdToken();
608
+ * // Use token for authenticated API calls
609
+ * ```
610
+ */
611
+ async getIdToken(forceRefresh = false) {
612
+ if (!this.auth.currentUser) {
613
+ throw new FirekitAuthError('auth/no-current-user', 'No authenticated user found.');
614
+ }
615
+ try {
616
+ return await getIdToken(this.auth.currentUser, forceRefresh);
617
+ }
618
+ catch (error) {
619
+ this.handleAuthError(error);
620
+ }
621
+ }
622
+ // ========================================
623
+ // ACCOUNT MANAGEMENT
624
+ // ========================================
625
+ /**
626
+ * Reauthenticates current user with email/password
627
+ * @param {string} currentPassword Current password
628
+ * @returns {Promise<void>} Promise that resolves when reauthentication succeeds
629
+ * @throws {FirekitAuthError} If reauthentication fails
630
+ * @private
631
+ */
632
+ async reauthenticateUser(currentPassword) {
633
+ if (!this.auth.currentUser || !this.auth.currentUser.email) {
634
+ throw new FirekitAuthError('auth/no-current-user', 'No authenticated user or email unavailable.');
635
+ }
636
+ try {
637
+ const credential = EmailAuthProvider.credential(this.auth.currentUser.email, currentPassword);
638
+ await reauthenticateWithCredential(this.auth.currentUser, credential);
639
+ }
640
+ catch (error) {
641
+ throw new FirekitAuthError(error.code || 'auth/reauthentication-failed', `Reauthentication failed: ${error.message || 'Unknown error occurred.'}`);
642
+ }
643
+ }
644
+ /**
645
+ * Deletes user account and associated data
646
+ * @param {string} [currentPassword] Current password for reauthentication (required for email/password accounts)
647
+ * @returns {Promise<AccountDeletionResult>} Promise resolving to deletion result
648
+ *
649
+ * @example
650
+ * ```typescript
651
+ * const result = await firekitAuth.deleteAccount("currentPassword123");
652
+ * if (result.success) {
653
+ * console.log("Account deleted successfully");
654
+ * }
655
+ * ```
656
+ */
657
+ async deleteAccount(currentPassword) {
658
+ if (!this.auth.currentUser) {
659
+ return {
660
+ success: false,
661
+ message: 'No authenticated user found.'
662
+ };
663
+ }
664
+ try {
665
+ const user = this.auth.currentUser;
666
+ // Reauthenticate if password provided (required for email/password accounts)
667
+ if (currentPassword) {
668
+ await this.reauthenticateUser(currentPassword);
669
+ }
670
+ // Delete user data from Firestore first
671
+ try {
672
+ const userRef = doc(this.firestore, 'users', user.uid);
673
+ await setDoc(userRef, { deleted: true, deletedAt: serverTimestamp() }, { merge: true });
674
+ }
675
+ catch (firestoreError) {
676
+ console.error('Failed to mark user as deleted in Firestore:', firestoreError);
677
+ }
678
+ // Delete the Firebase Auth account
679
+ await deleteUser(user);
680
+ return {
681
+ success: true,
682
+ message: 'Account successfully deleted.'
683
+ };
684
+ }
685
+ catch (error) {
686
+ const code = error.code;
687
+ if (code === AuthErrorCode.REQUIRES_RECENT_LOGIN) {
688
+ return {
689
+ success: false,
690
+ message: 'Please sign in again before deleting your account.'
691
+ };
692
+ }
693
+ return {
694
+ success: false,
695
+ message: error.message || 'Failed to delete account.'
696
+ };
697
+ }
698
+ }
699
+ // ========================================
700
+ // SIGN OUT
701
+ // ========================================
702
+ /**
703
+ * Signs out current user
704
+ * @returns {Promise<void>} Promise that resolves when sign-out completes
705
+ * @throws {FirekitAuthError} If sign-out fails
706
+ *
707
+ * @example
708
+ * ```typescript
709
+ * await firekitAuth.signOut();
710
+ * console.log("User signed out");
711
+ * ```
712
+ */
713
+ async signOut() {
714
+ try {
715
+ this.authState.loading = true;
716
+ this.notifyStateListeners();
717
+ // Clear all reCAPTCHA verifiers
718
+ this.recaptchaVerifiers.forEach((verifier) => verifier.clear());
719
+ this.recaptchaVerifiers.clear();
720
+ await signOut(this.auth);
721
+ }
722
+ catch (error) {
723
+ this.handleAuthError(error);
724
+ }
725
+ finally {
726
+ this.authState.loading = false;
727
+ this.notifyStateListeners();
728
+ }
729
+ }
730
+ // ========================================
731
+ // UTILITY METHODS
732
+ // ========================================
733
+ /**
734
+ * Checks if user is authenticated
735
+ * @returns {boolean} True if user is authenticated
736
+ */
737
+ isAuthenticated() {
738
+ return !!this.authState.user && !this.authState.user.isAnonymous;
739
+ }
740
+ /**
741
+ * Checks if user is anonymous
742
+ * @returns {boolean} True if user is anonymous
743
+ */
744
+ isAnonymous() {
745
+ return !!this.authState.user?.isAnonymous;
746
+ }
747
+ /**
748
+ * Checks if user's email is verified
749
+ * @returns {boolean} True if email is verified
750
+ */
751
+ isEmailVerified() {
752
+ return !!this.authState.user?.emailVerified;
753
+ }
754
+ /**
755
+ * Gets user's primary email address
756
+ * @returns {string | null} User's email or null
757
+ */
758
+ getUserEmail() {
759
+ return this.authState.user?.email || null;
760
+ }
761
+ /**
762
+ * Gets user's display name
763
+ * @returns {string | null} User's display name or null
764
+ */
765
+ getUserDisplayName() {
766
+ return this.authState.user?.displayName || null;
767
+ }
768
+ /**
769
+ * Gets user's photo URL
770
+ * @returns {string | null} User's photo URL or null
771
+ */
772
+ getUserPhotoURL() {
773
+ return this.authState.user?.photoURL || null;
774
+ }
775
+ /**
776
+ * Gets user's UID
777
+ * @returns {string | null} User's UID or null
778
+ */
779
+ getUserId() {
780
+ return this.authState.user?.uid || null;
781
+ }
782
+ /**
783
+ * Cleans up all resources
784
+ * @returns {Promise<void>} Promise that resolves when cleanup is complete
785
+ */
786
+ async cleanup() {
787
+ // Clear all listeners
788
+ this.stateListeners.clear();
789
+ // Clear all reCAPTCHA verifiers
790
+ this.recaptchaVerifiers.forEach((verifier) => verifier.clear());
791
+ this.recaptchaVerifiers.clear();
792
+ // Reset state
793
+ this.authState = {
794
+ user: null,
795
+ loading: false,
796
+ initialized: false
797
+ };
798
+ }
799
+ }
800
+ /**
801
+ * Pre-initialized singleton instance of FirekitAuth.
802
+ * This is the main export that should be used throughout your application.
803
+ *
804
+ * @example
805
+ * ```typescript
806
+ * import { firekitAuth } from 'svelte-firekit';
807
+ *
808
+ * // Sign in with email
809
+ * await firekitAuth.signInWithEmail("user@example.com", "password");
810
+ *
811
+ * // Listen to auth state
812
+ * const unsubscribe = firekitAuth.onAuthStateChanged((state) => {
813
+ * if (state.user) {
814
+ * console.log("User is signed in:", state.user.email);
815
+ * } else {
816
+ * console.log("User is signed out");
817
+ * }
818
+ * });
819
+ *
820
+ * // Clean up listener
821
+ * unsubscribe();
822
+ * ```
823
+ */
824
+ export const firekitAuth = FirekitAuth.getInstance();