svelte-firekit 0.1.0 → 0.1.2
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/auth-guard.svelte +8 -2
- package/dist/firebase.js +15 -3
- package/dist/services/auth.d.ts +22 -14
- package/dist/services/auth.js +157 -89
- package/dist/services/user.svelte.d.ts +6 -0
- package/dist/services/user.svelte.js +86 -10
- package/package.json +1 -1
|
@@ -36,8 +36,14 @@
|
|
|
36
36
|
fallback?: Snippet<[]>;
|
|
37
37
|
} = $props();
|
|
38
38
|
|
|
39
|
-
// Get Firebase Auth instance
|
|
40
|
-
|
|
39
|
+
// Get Firebase Auth instance with error handling
|
|
40
|
+
let auth: Auth | null = null;
|
|
41
|
+
try {
|
|
42
|
+
auth = firebaseService.getAuthInstance();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.warn('Firebase Auth not available:', error);
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
if (!auth) {
|
|
42
48
|
throw new Error('Firebase Auth instance not available');
|
|
43
49
|
}
|
package/dist/firebase.js
CHANGED
|
@@ -129,9 +129,21 @@ class FirebaseService {
|
|
|
129
129
|
*/
|
|
130
130
|
getDbInstance() {
|
|
131
131
|
if (!this.db) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
try {
|
|
133
|
+
this.getFirebaseApp();
|
|
134
|
+
if (!this.db) {
|
|
135
|
+
// If we're not in a browser environment, Firestore won't be available
|
|
136
|
+
if (!this.isBrowser) {
|
|
137
|
+
throw new FirebaseServiceError('Firestore is not available in server environment', 'firestore');
|
|
138
|
+
}
|
|
139
|
+
throw new FirebaseServiceError('Firestore instance not available', 'firestore');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (error instanceof FirebaseServiceError) {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
throw new FirebaseServiceError('Failed to initialize Firestore', 'firestore');
|
|
135
147
|
}
|
|
136
148
|
}
|
|
137
149
|
return this.db;
|
package/dist/services/auth.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ declare class FirekitAuth {
|
|
|
31
31
|
private static instance;
|
|
32
32
|
private auth;
|
|
33
33
|
private firestore;
|
|
34
|
+
private _servicesInitialized;
|
|
34
35
|
private authState;
|
|
35
36
|
private stateListeners;
|
|
36
37
|
private recaptchaVerifiers;
|
|
@@ -40,6 +41,11 @@ declare class FirekitAuth {
|
|
|
40
41
|
* @returns {FirekitAuth} The FirekitAuth instance
|
|
41
42
|
*/
|
|
42
43
|
static getInstance(): FirekitAuth;
|
|
44
|
+
/**
|
|
45
|
+
* Initializes Firebase services and auth state listener
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
private initializeServices;
|
|
43
49
|
/**
|
|
44
50
|
* Initializes the authentication state listener
|
|
45
51
|
* @private
|
|
@@ -263,14 +269,18 @@ declare class FirekitAuth {
|
|
|
263
269
|
* @example
|
|
264
270
|
* ```typescript
|
|
265
271
|
* await firekitAuth.sendEmailVerification();
|
|
266
|
-
* console.log("Verification email sent");
|
|
267
272
|
* ```
|
|
268
273
|
*/
|
|
269
274
|
sendEmailVerification(): Promise<void>;
|
|
270
275
|
/**
|
|
271
|
-
* Reloads
|
|
276
|
+
* Reloads user to get updated data
|
|
272
277
|
* @returns {Promise<void>} Promise that resolves when user is reloaded
|
|
273
278
|
* @throws {FirekitAuthError} If reload fails
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```typescript
|
|
282
|
+
* await firekitAuth.reloadUser();
|
|
283
|
+
* ```
|
|
274
284
|
*/
|
|
275
285
|
reloadUser(): Promise<void>;
|
|
276
286
|
/**
|
|
@@ -282,21 +292,17 @@ declare class FirekitAuth {
|
|
|
282
292
|
* @example
|
|
283
293
|
* ```typescript
|
|
284
294
|
* const token = await firekitAuth.getIdToken();
|
|
285
|
-
* // Use token for authenticated API calls
|
|
286
295
|
* ```
|
|
287
296
|
*/
|
|
288
297
|
getIdToken(forceRefresh?: boolean): Promise<string>;
|
|
289
298
|
/**
|
|
290
|
-
* Reauthenticates
|
|
291
|
-
* @param {string} currentPassword Current password
|
|
292
|
-
* @returns {Promise<void>} Promise that resolves when reauthentication succeeds
|
|
293
|
-
* @throws {FirekitAuthError} If reauthentication fails
|
|
299
|
+
* Reauthenticates user with current password
|
|
294
300
|
* @private
|
|
295
301
|
*/
|
|
296
302
|
private reauthenticateUser;
|
|
297
303
|
/**
|
|
298
|
-
* Deletes user account
|
|
299
|
-
* @param {string} [currentPassword] Current password for reauthentication
|
|
304
|
+
* Deletes user account
|
|
305
|
+
* @param {string} [currentPassword] Current password for reauthentication
|
|
300
306
|
* @returns {Promise<AccountDeletionResult>} Promise resolving to deletion result
|
|
301
307
|
*
|
|
302
308
|
* @example
|
|
@@ -304,12 +310,14 @@ declare class FirekitAuth {
|
|
|
304
310
|
* const result = await firekitAuth.deleteAccount("currentPassword123");
|
|
305
311
|
* if (result.success) {
|
|
306
312
|
* console.log("Account deleted successfully");
|
|
313
|
+
* } else {
|
|
314
|
+
* console.error("Deletion failed:", result.message);
|
|
307
315
|
* }
|
|
308
316
|
* ```
|
|
309
317
|
*/
|
|
310
318
|
deleteAccount(currentPassword?: string): Promise<AccountDeletionResult>;
|
|
311
319
|
/**
|
|
312
|
-
* Signs out current user
|
|
320
|
+
* Signs out the current user
|
|
313
321
|
* @returns {Promise<void>} Promise that resolves when sign-out completes
|
|
314
322
|
* @throws {FirekitAuthError} If sign-out fails
|
|
315
323
|
*
|
|
@@ -336,7 +344,7 @@ declare class FirekitAuth {
|
|
|
336
344
|
*/
|
|
337
345
|
isEmailVerified(): boolean;
|
|
338
346
|
/**
|
|
339
|
-
* Gets user's
|
|
347
|
+
* Gets user's email address
|
|
340
348
|
* @returns {string | null} User's email or null
|
|
341
349
|
*/
|
|
342
350
|
getUserEmail(): string | null;
|
|
@@ -351,13 +359,13 @@ declare class FirekitAuth {
|
|
|
351
359
|
*/
|
|
352
360
|
getUserPhotoURL(): string | null;
|
|
353
361
|
/**
|
|
354
|
-
* Gets user's
|
|
362
|
+
* Gets user's unique ID
|
|
355
363
|
* @returns {string | null} User's UID or null
|
|
356
364
|
*/
|
|
357
365
|
getUserId(): string | null;
|
|
358
366
|
/**
|
|
359
|
-
* Cleans up
|
|
360
|
-
* @returns {Promise<void>} Promise that resolves when cleanup
|
|
367
|
+
* Cleans up resources and listeners
|
|
368
|
+
* @returns {Promise<void>} Promise that resolves when cleanup completes
|
|
361
369
|
*/
|
|
362
370
|
cleanup(): Promise<void>;
|
|
363
371
|
}
|
package/dist/services/auth.js
CHANGED
|
@@ -32,8 +32,9 @@ import { mapFirebaseUserToProfile, updateUserInFirestore, createGoogleProvider,
|
|
|
32
32
|
*/
|
|
33
33
|
class FirekitAuth {
|
|
34
34
|
static instance;
|
|
35
|
-
auth =
|
|
36
|
-
firestore =
|
|
35
|
+
auth = null;
|
|
36
|
+
firestore = null;
|
|
37
|
+
_servicesInitialized = false;
|
|
37
38
|
authState = {
|
|
38
39
|
user: null,
|
|
39
40
|
loading: true,
|
|
@@ -42,13 +43,8 @@ class FirekitAuth {
|
|
|
42
43
|
stateListeners = new Set();
|
|
43
44
|
recaptchaVerifiers = new Map();
|
|
44
45
|
constructor() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
if (!this.firestore) {
|
|
49
|
-
throw new Error('Firestore instance not available');
|
|
50
|
-
}
|
|
51
|
-
this.initializeAuthStateListener();
|
|
46
|
+
// Don't initialize Firebase services in constructor
|
|
47
|
+
// They will be initialized lazily when needed
|
|
52
48
|
}
|
|
53
49
|
/**
|
|
54
50
|
* Gets singleton instance of FirekitAuth
|
|
@@ -60,11 +56,45 @@ class FirekitAuth {
|
|
|
60
56
|
}
|
|
61
57
|
return FirekitAuth.instance;
|
|
62
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Initializes Firebase services and auth state listener
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
initializeServices() {
|
|
64
|
+
if (this._servicesInitialized)
|
|
65
|
+
return;
|
|
66
|
+
try {
|
|
67
|
+
this.auth = firebaseService.getAuthInstance();
|
|
68
|
+
// Try to get Firestore instance, but don't fail if it's not available
|
|
69
|
+
try {
|
|
70
|
+
this.firestore = firebaseService.getDbInstance();
|
|
71
|
+
}
|
|
72
|
+
catch (firestoreError) {
|
|
73
|
+
console.warn('Firestore not available, continuing without Firestore integration:', firestoreError);
|
|
74
|
+
this.firestore = null;
|
|
75
|
+
}
|
|
76
|
+
this._servicesInitialized = true;
|
|
77
|
+
this.initializeAuthStateListener();
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error('Failed to initialize Firebase services:', error);
|
|
81
|
+
this.authState = {
|
|
82
|
+
user: null,
|
|
83
|
+
loading: false,
|
|
84
|
+
initialized: true
|
|
85
|
+
};
|
|
86
|
+
this.notifyStateListeners();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
63
89
|
/**
|
|
64
90
|
* Initializes the authentication state listener
|
|
65
91
|
* @private
|
|
66
92
|
*/
|
|
67
93
|
initializeAuthStateListener() {
|
|
94
|
+
if (!this.auth) {
|
|
95
|
+
console.error('Auth instance not available');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
68
98
|
onAuthStateChanged(this.auth, (user) => {
|
|
69
99
|
this.authState = {
|
|
70
100
|
user: user ? this.mapFirebaseUserToProfile(user) : null,
|
|
@@ -101,6 +131,10 @@ class FirekitAuth {
|
|
|
101
131
|
* @private
|
|
102
132
|
*/
|
|
103
133
|
async updateUserInFirestore(user) {
|
|
134
|
+
if (!this.firestore) {
|
|
135
|
+
console.warn('Firestore not available, skipping user update in Firestore');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
104
138
|
await updateUserInFirestore(this.firestore, user);
|
|
105
139
|
}
|
|
106
140
|
/**
|
|
@@ -115,6 +149,7 @@ class FirekitAuth {
|
|
|
115
149
|
* @returns {AuthState} Current authentication state
|
|
116
150
|
*/
|
|
117
151
|
getState() {
|
|
152
|
+
this.initializeServices();
|
|
118
153
|
return { ...this.authState };
|
|
119
154
|
}
|
|
120
155
|
/**
|
|
@@ -122,13 +157,15 @@ class FirekitAuth {
|
|
|
122
157
|
* @returns {User | null} Current Firebase user or null
|
|
123
158
|
*/
|
|
124
159
|
getCurrentUser() {
|
|
125
|
-
|
|
160
|
+
this.initializeServices();
|
|
161
|
+
return this.auth?.currentUser ?? null;
|
|
126
162
|
}
|
|
127
163
|
/**
|
|
128
164
|
* Gets the current user profile
|
|
129
165
|
* @returns {UserProfile | null} Current user profile or null
|
|
130
166
|
*/
|
|
131
167
|
getCurrentUserProfile() {
|
|
168
|
+
this.initializeServices();
|
|
132
169
|
return this.authState.user;
|
|
133
170
|
}
|
|
134
171
|
/**
|
|
@@ -136,6 +173,7 @@ class FirekitAuth {
|
|
|
136
173
|
* @returns {Promise<UserProfile | null>} Promise that resolves when auth is initialized
|
|
137
174
|
*/
|
|
138
175
|
async waitForAuth() {
|
|
176
|
+
this.initializeServices();
|
|
139
177
|
return new Promise((resolve) => {
|
|
140
178
|
if (this.authState.initialized) {
|
|
141
179
|
resolve(this.authState.user);
|
|
@@ -155,6 +193,7 @@ class FirekitAuth {
|
|
|
155
193
|
* @returns {Function} Unsubscribe function
|
|
156
194
|
*/
|
|
157
195
|
onAuthStateChanged(callback) {
|
|
196
|
+
this.initializeServices();
|
|
158
197
|
this.stateListeners.add(callback);
|
|
159
198
|
// Immediately call with current state
|
|
160
199
|
callback(this.authState);
|
|
@@ -183,6 +222,10 @@ class FirekitAuth {
|
|
|
183
222
|
* ```
|
|
184
223
|
*/
|
|
185
224
|
async signInWithEmail(email, password) {
|
|
225
|
+
this.initializeServices();
|
|
226
|
+
if (!this.auth) {
|
|
227
|
+
throw new Error('Auth instance not available');
|
|
228
|
+
}
|
|
186
229
|
try {
|
|
187
230
|
this.authState.loading = true;
|
|
188
231
|
this.notifyStateListeners();
|
|
@@ -216,6 +259,10 @@ class FirekitAuth {
|
|
|
216
259
|
* ```
|
|
217
260
|
*/
|
|
218
261
|
async signInWithGoogle() {
|
|
262
|
+
this.initializeServices();
|
|
263
|
+
if (!this.auth) {
|
|
264
|
+
throw new Error('Auth instance not available');
|
|
265
|
+
}
|
|
219
266
|
try {
|
|
220
267
|
this.authState.loading = true;
|
|
221
268
|
this.notifyStateListeners();
|
|
@@ -238,6 +285,10 @@ class FirekitAuth {
|
|
|
238
285
|
* @throws {FirekitAuthError} If sign-in fails
|
|
239
286
|
*/
|
|
240
287
|
async signInWithFacebook() {
|
|
288
|
+
this.initializeServices();
|
|
289
|
+
if (!this.auth) {
|
|
290
|
+
throw new Error('Auth instance not available');
|
|
291
|
+
}
|
|
241
292
|
try {
|
|
242
293
|
this.authState.loading = true;
|
|
243
294
|
this.notifyStateListeners();
|
|
@@ -260,6 +311,10 @@ class FirekitAuth {
|
|
|
260
311
|
* @throws {FirekitAuthError} If sign-in fails
|
|
261
312
|
*/
|
|
262
313
|
async signInWithApple() {
|
|
314
|
+
this.initializeServices();
|
|
315
|
+
if (!this.auth) {
|
|
316
|
+
throw new Error('Auth instance not available');
|
|
317
|
+
}
|
|
263
318
|
try {
|
|
264
319
|
this.authState.loading = true;
|
|
265
320
|
this.notifyStateListeners();
|
|
@@ -288,6 +343,10 @@ class FirekitAuth {
|
|
|
288
343
|
* ```
|
|
289
344
|
*/
|
|
290
345
|
async signInAnonymously() {
|
|
346
|
+
this.initializeServices();
|
|
347
|
+
if (!this.auth) {
|
|
348
|
+
throw new Error('Auth instance not available');
|
|
349
|
+
}
|
|
291
350
|
try {
|
|
292
351
|
this.authState.loading = true;
|
|
293
352
|
this.notifyStateListeners();
|
|
@@ -317,6 +376,10 @@ class FirekitAuth {
|
|
|
317
376
|
* ```
|
|
318
377
|
*/
|
|
319
378
|
async signInWithPhoneNumber(phoneNumber, recaptchaContainerId) {
|
|
379
|
+
this.initializeServices();
|
|
380
|
+
if (!this.auth) {
|
|
381
|
+
throw new Error('Auth instance not available');
|
|
382
|
+
}
|
|
320
383
|
try {
|
|
321
384
|
this.authState.loading = true;
|
|
322
385
|
this.notifyStateListeners();
|
|
@@ -339,11 +402,11 @@ class FirekitAuth {
|
|
|
339
402
|
const confirmationResult = await firebaseSignInWithPhoneNumber(this.auth, phoneNumber, recaptchaVerifier);
|
|
340
403
|
return {
|
|
341
404
|
verificationId: confirmationResult.verificationId,
|
|
342
|
-
confirm: async (
|
|
405
|
+
confirm: async (verificationCode) => {
|
|
343
406
|
try {
|
|
344
|
-
const
|
|
345
|
-
await this.updateUserInFirestore(
|
|
346
|
-
return this.mapFirebaseUserToProfile(
|
|
407
|
+
const userCredential = await confirmationResult.confirm(verificationCode);
|
|
408
|
+
await this.updateUserInFirestore(userCredential.user);
|
|
409
|
+
return this.mapFirebaseUserToProfile(userCredential.user);
|
|
347
410
|
}
|
|
348
411
|
catch (error) {
|
|
349
412
|
this.handleAuthError(error);
|
|
@@ -387,6 +450,10 @@ class FirekitAuth {
|
|
|
387
450
|
* ```
|
|
388
451
|
*/
|
|
389
452
|
async registerWithEmail(email, password, displayName, sendVerification = true) {
|
|
453
|
+
this.initializeServices();
|
|
454
|
+
if (!this.auth) {
|
|
455
|
+
throw new Error('Auth instance not available');
|
|
456
|
+
}
|
|
390
457
|
try {
|
|
391
458
|
this.authState.loading = true;
|
|
392
459
|
this.notifyStateListeners();
|
|
@@ -427,6 +494,10 @@ class FirekitAuth {
|
|
|
427
494
|
* ```
|
|
428
495
|
*/
|
|
429
496
|
async sendPasswordReset(email) {
|
|
497
|
+
this.initializeServices();
|
|
498
|
+
if (!this.auth) {
|
|
499
|
+
throw new Error('Auth instance not available');
|
|
500
|
+
}
|
|
430
501
|
try {
|
|
431
502
|
await sendPasswordResetEmail(this.auth, email);
|
|
432
503
|
}
|
|
@@ -442,6 +513,10 @@ class FirekitAuth {
|
|
|
442
513
|
* @throws {FirekitAuthError} If reset fails
|
|
443
514
|
*/
|
|
444
515
|
async confirmPasswordReset(code, newPassword) {
|
|
516
|
+
this.initializeServices();
|
|
517
|
+
if (!this.auth) {
|
|
518
|
+
throw new Error('Auth instance not available');
|
|
519
|
+
}
|
|
445
520
|
try {
|
|
446
521
|
await confirmPasswordReset(this.auth, code, newPassword);
|
|
447
522
|
}
|
|
@@ -466,7 +541,8 @@ class FirekitAuth {
|
|
|
466
541
|
* ```
|
|
467
542
|
*/
|
|
468
543
|
async updatePassword(newPassword, currentPassword) {
|
|
469
|
-
|
|
544
|
+
this.initializeServices();
|
|
545
|
+
if (!this.auth?.currentUser) {
|
|
470
546
|
return {
|
|
471
547
|
success: false,
|
|
472
548
|
message: 'No authenticated user found.',
|
|
@@ -517,7 +593,8 @@ class FirekitAuth {
|
|
|
517
593
|
* ```
|
|
518
594
|
*/
|
|
519
595
|
async updateUserProfile(profile) {
|
|
520
|
-
|
|
596
|
+
this.initializeServices();
|
|
597
|
+
if (!this.auth?.currentUser) {
|
|
521
598
|
throw new FirekitAuthError('auth/no-current-user', 'No authenticated user found.');
|
|
522
599
|
}
|
|
523
600
|
try {
|
|
@@ -540,7 +617,8 @@ class FirekitAuth {
|
|
|
540
617
|
* ```
|
|
541
618
|
*/
|
|
542
619
|
async updateEmail(newEmail) {
|
|
543
|
-
|
|
620
|
+
this.initializeServices();
|
|
621
|
+
if (!this.auth?.currentUser) {
|
|
544
622
|
throw new FirekitAuthError('auth/no-current-user', 'No authenticated user found.');
|
|
545
623
|
}
|
|
546
624
|
try {
|
|
@@ -551,9 +629,6 @@ class FirekitAuth {
|
|
|
551
629
|
this.handleAuthError(error);
|
|
552
630
|
}
|
|
553
631
|
}
|
|
554
|
-
// ========================================
|
|
555
|
-
// EMAIL VERIFICATION METHODS
|
|
556
|
-
// ========================================
|
|
557
632
|
/**
|
|
558
633
|
* Sends email verification to current user
|
|
559
634
|
* @returns {Promise<void>} Promise that resolves when verification email is sent
|
|
@@ -562,11 +637,11 @@ class FirekitAuth {
|
|
|
562
637
|
* @example
|
|
563
638
|
* ```typescript
|
|
564
639
|
* await firekitAuth.sendEmailVerification();
|
|
565
|
-
* console.log("Verification email sent");
|
|
566
640
|
* ```
|
|
567
641
|
*/
|
|
568
642
|
async sendEmailVerification() {
|
|
569
|
-
|
|
643
|
+
this.initializeServices();
|
|
644
|
+
if (!this.auth?.currentUser) {
|
|
570
645
|
throw new FirekitAuthError('auth/no-current-user', 'No authenticated user found.');
|
|
571
646
|
}
|
|
572
647
|
try {
|
|
@@ -577,12 +652,18 @@ class FirekitAuth {
|
|
|
577
652
|
}
|
|
578
653
|
}
|
|
579
654
|
/**
|
|
580
|
-
* Reloads
|
|
655
|
+
* Reloads user to get updated data
|
|
581
656
|
* @returns {Promise<void>} Promise that resolves when user is reloaded
|
|
582
657
|
* @throws {FirekitAuthError} If reload fails
|
|
658
|
+
*
|
|
659
|
+
* @example
|
|
660
|
+
* ```typescript
|
|
661
|
+
* await firekitAuth.reloadUser();
|
|
662
|
+
* ```
|
|
583
663
|
*/
|
|
584
664
|
async reloadUser() {
|
|
585
|
-
|
|
665
|
+
this.initializeServices();
|
|
666
|
+
if (!this.auth?.currentUser) {
|
|
586
667
|
throw new FirekitAuthError('auth/no-current-user', 'No authenticated user found.');
|
|
587
668
|
}
|
|
588
669
|
try {
|
|
@@ -593,9 +674,6 @@ class FirekitAuth {
|
|
|
593
674
|
this.handleAuthError(error);
|
|
594
675
|
}
|
|
595
676
|
}
|
|
596
|
-
// ========================================
|
|
597
|
-
// TOKEN METHODS
|
|
598
|
-
// ========================================
|
|
599
677
|
/**
|
|
600
678
|
* Gets the current user's ID token
|
|
601
679
|
* @param {boolean} [forceRefresh=false] Whether to force token refresh
|
|
@@ -605,11 +683,11 @@ class FirekitAuth {
|
|
|
605
683
|
* @example
|
|
606
684
|
* ```typescript
|
|
607
685
|
* const token = await firekitAuth.getIdToken();
|
|
608
|
-
* // Use token for authenticated API calls
|
|
609
686
|
* ```
|
|
610
687
|
*/
|
|
611
688
|
async getIdToken(forceRefresh = false) {
|
|
612
|
-
|
|
689
|
+
this.initializeServices();
|
|
690
|
+
if (!this.auth?.currentUser) {
|
|
613
691
|
throw new FirekitAuthError('auth/no-current-user', 'No authenticated user found.');
|
|
614
692
|
}
|
|
615
693
|
try {
|
|
@@ -619,31 +697,26 @@ class FirekitAuth {
|
|
|
619
697
|
this.handleAuthError(error);
|
|
620
698
|
}
|
|
621
699
|
}
|
|
622
|
-
// ========================================
|
|
623
|
-
// ACCOUNT MANAGEMENT
|
|
624
|
-
// ========================================
|
|
625
700
|
/**
|
|
626
|
-
* Reauthenticates
|
|
627
|
-
* @param {string} currentPassword Current password
|
|
628
|
-
* @returns {Promise<void>} Promise that resolves when reauthentication succeeds
|
|
629
|
-
* @throws {FirekitAuthError} If reauthentication fails
|
|
701
|
+
* Reauthenticates user with current password
|
|
630
702
|
* @private
|
|
631
703
|
*/
|
|
632
704
|
async reauthenticateUser(currentPassword) {
|
|
633
|
-
|
|
634
|
-
|
|
705
|
+
this.initializeServices();
|
|
706
|
+
if (!this.auth?.currentUser || !this.auth.currentUser.email) {
|
|
707
|
+
throw new FirekitAuthError('auth/no-current-user', 'No authenticated user with email found.');
|
|
635
708
|
}
|
|
636
709
|
try {
|
|
637
710
|
const credential = EmailAuthProvider.credential(this.auth.currentUser.email, currentPassword);
|
|
638
711
|
await reauthenticateWithCredential(this.auth.currentUser, credential);
|
|
639
712
|
}
|
|
640
713
|
catch (error) {
|
|
641
|
-
|
|
714
|
+
this.handleAuthError(error);
|
|
642
715
|
}
|
|
643
716
|
}
|
|
644
717
|
/**
|
|
645
|
-
* Deletes user account
|
|
646
|
-
* @param {string} [currentPassword] Current password for reauthentication
|
|
718
|
+
* Deletes user account
|
|
719
|
+
* @param {string} [currentPassword] Current password for reauthentication
|
|
647
720
|
* @returns {Promise<AccountDeletionResult>} Promise resolving to deletion result
|
|
648
721
|
*
|
|
649
722
|
* @example
|
|
@@ -651,11 +724,14 @@ class FirekitAuth {
|
|
|
651
724
|
* const result = await firekitAuth.deleteAccount("currentPassword123");
|
|
652
725
|
* if (result.success) {
|
|
653
726
|
* console.log("Account deleted successfully");
|
|
727
|
+
* } else {
|
|
728
|
+
* console.error("Deletion failed:", result.message);
|
|
654
729
|
* }
|
|
655
730
|
* ```
|
|
656
731
|
*/
|
|
657
732
|
async deleteAccount(currentPassword) {
|
|
658
|
-
|
|
733
|
+
this.initializeServices();
|
|
734
|
+
if (!this.auth?.currentUser) {
|
|
659
735
|
return {
|
|
660
736
|
success: false,
|
|
661
737
|
message: 'No authenticated user found.'
|
|
@@ -663,19 +739,21 @@ class FirekitAuth {
|
|
|
663
739
|
}
|
|
664
740
|
try {
|
|
665
741
|
const user = this.auth.currentUser;
|
|
666
|
-
// Reauthenticate if password provided
|
|
742
|
+
// Reauthenticate if password provided
|
|
667
743
|
if (currentPassword) {
|
|
668
744
|
await this.reauthenticateUser(currentPassword);
|
|
669
745
|
}
|
|
670
|
-
// Delete user data from Firestore first
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
746
|
+
// Delete user data from Firestore first (if available)
|
|
747
|
+
if (this.firestore) {
|
|
748
|
+
try {
|
|
749
|
+
const userRef = doc(this.firestore, 'users', user.uid);
|
|
750
|
+
await setDoc(userRef, { deleted: true, deletedAt: serverTimestamp() }, { merge: true });
|
|
751
|
+
}
|
|
752
|
+
catch (firestoreError) {
|
|
753
|
+
console.warn('Failed to update Firestore before account deletion:', firestoreError);
|
|
754
|
+
}
|
|
677
755
|
}
|
|
678
|
-
// Delete the
|
|
756
|
+
// Delete the user account
|
|
679
757
|
await deleteUser(user);
|
|
680
758
|
return {
|
|
681
759
|
success: true,
|
|
@@ -683,24 +761,14 @@ class FirekitAuth {
|
|
|
683
761
|
};
|
|
684
762
|
}
|
|
685
763
|
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
764
|
return {
|
|
694
765
|
success: false,
|
|
695
766
|
message: error.message || 'Failed to delete account.'
|
|
696
767
|
};
|
|
697
768
|
}
|
|
698
769
|
}
|
|
699
|
-
// ========================================
|
|
700
|
-
// SIGN OUT
|
|
701
|
-
// ========================================
|
|
702
770
|
/**
|
|
703
|
-
* Signs out current user
|
|
771
|
+
* Signs out the current user
|
|
704
772
|
* @returns {Promise<void>} Promise that resolves when sign-out completes
|
|
705
773
|
* @throws {FirekitAuthError} If sign-out fails
|
|
706
774
|
*
|
|
@@ -711,10 +779,12 @@ class FirekitAuth {
|
|
|
711
779
|
* ```
|
|
712
780
|
*/
|
|
713
781
|
async signOut() {
|
|
782
|
+
this.initializeServices();
|
|
783
|
+
if (!this.auth) {
|
|
784
|
+
throw new Error('Auth instance not available');
|
|
785
|
+
}
|
|
714
786
|
try {
|
|
715
|
-
|
|
716
|
-
this.notifyStateListeners();
|
|
717
|
-
// Clear all reCAPTCHA verifiers
|
|
787
|
+
// Clear reCAPTCHA verifiers
|
|
718
788
|
this.recaptchaVerifiers.forEach((verifier) => verifier.clear());
|
|
719
789
|
this.recaptchaVerifiers.clear();
|
|
720
790
|
await signOut(this.auth);
|
|
@@ -722,10 +792,6 @@ class FirekitAuth {
|
|
|
722
792
|
catch (error) {
|
|
723
793
|
this.handleAuthError(error);
|
|
724
794
|
}
|
|
725
|
-
finally {
|
|
726
|
-
this.authState.loading = false;
|
|
727
|
-
this.notifyStateListeners();
|
|
728
|
-
}
|
|
729
795
|
}
|
|
730
796
|
// ========================================
|
|
731
797
|
// UTILITY METHODS
|
|
@@ -735,66 +801,68 @@ class FirekitAuth {
|
|
|
735
801
|
* @returns {boolean} True if user is authenticated
|
|
736
802
|
*/
|
|
737
803
|
isAuthenticated() {
|
|
738
|
-
|
|
804
|
+
this.initializeServices();
|
|
805
|
+
return this.authState.user !== null && !this.authState.user.isAnonymous;
|
|
739
806
|
}
|
|
740
807
|
/**
|
|
741
808
|
* Checks if user is anonymous
|
|
742
809
|
* @returns {boolean} True if user is anonymous
|
|
743
810
|
*/
|
|
744
811
|
isAnonymous() {
|
|
745
|
-
|
|
812
|
+
this.initializeServices();
|
|
813
|
+
return this.authState.user?.isAnonymous ?? false;
|
|
746
814
|
}
|
|
747
815
|
/**
|
|
748
816
|
* Checks if user's email is verified
|
|
749
817
|
* @returns {boolean} True if email is verified
|
|
750
818
|
*/
|
|
751
819
|
isEmailVerified() {
|
|
752
|
-
|
|
820
|
+
this.initializeServices();
|
|
821
|
+
return this.authState.user?.emailVerified ?? false;
|
|
753
822
|
}
|
|
754
823
|
/**
|
|
755
|
-
* Gets user's
|
|
824
|
+
* Gets user's email address
|
|
756
825
|
* @returns {string | null} User's email or null
|
|
757
826
|
*/
|
|
758
827
|
getUserEmail() {
|
|
759
|
-
|
|
828
|
+
this.initializeServices();
|
|
829
|
+
return this.authState.user?.email ?? null;
|
|
760
830
|
}
|
|
761
831
|
/**
|
|
762
832
|
* Gets user's display name
|
|
763
833
|
* @returns {string | null} User's display name or null
|
|
764
834
|
*/
|
|
765
835
|
getUserDisplayName() {
|
|
766
|
-
|
|
836
|
+
this.initializeServices();
|
|
837
|
+
return this.authState.user?.displayName ?? null;
|
|
767
838
|
}
|
|
768
839
|
/**
|
|
769
840
|
* Gets user's photo URL
|
|
770
841
|
* @returns {string | null} User's photo URL or null
|
|
771
842
|
*/
|
|
772
843
|
getUserPhotoURL() {
|
|
773
|
-
|
|
844
|
+
this.initializeServices();
|
|
845
|
+
return this.authState.user?.photoURL ?? null;
|
|
774
846
|
}
|
|
775
847
|
/**
|
|
776
|
-
* Gets user's
|
|
848
|
+
* Gets user's unique ID
|
|
777
849
|
* @returns {string | null} User's UID or null
|
|
778
850
|
*/
|
|
779
851
|
getUserId() {
|
|
780
|
-
|
|
852
|
+
this.initializeServices();
|
|
853
|
+
return this.authState.user?.uid ?? null;
|
|
781
854
|
}
|
|
782
855
|
/**
|
|
783
|
-
* Cleans up
|
|
784
|
-
* @returns {Promise<void>} Promise that resolves when cleanup
|
|
856
|
+
* Cleans up resources and listeners
|
|
857
|
+
* @returns {Promise<void>} Promise that resolves when cleanup completes
|
|
785
858
|
*/
|
|
786
859
|
async cleanup() {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
// Clear all reCAPTCHA verifiers
|
|
860
|
+
this.initializeServices();
|
|
861
|
+
// Clear reCAPTCHA verifiers
|
|
790
862
|
this.recaptchaVerifiers.forEach((verifier) => verifier.clear());
|
|
791
863
|
this.recaptchaVerifiers.clear();
|
|
792
|
-
//
|
|
793
|
-
this.
|
|
794
|
-
user: null,
|
|
795
|
-
loading: false,
|
|
796
|
-
initialized: false
|
|
797
|
-
};
|
|
864
|
+
// Clear state listeners
|
|
865
|
+
this.stateListeners.clear();
|
|
798
866
|
}
|
|
799
867
|
}
|
|
800
868
|
/**
|
|
@@ -48,6 +48,7 @@ declare class FirekitUserStore {
|
|
|
48
48
|
private static instance;
|
|
49
49
|
private auth;
|
|
50
50
|
private firestore;
|
|
51
|
+
private _servicesInitialized;
|
|
51
52
|
/** Current user profile state */
|
|
52
53
|
private _user;
|
|
53
54
|
/** Loading state indicator */
|
|
@@ -78,6 +79,11 @@ declare class FirekitUserStore {
|
|
|
78
79
|
* @returns {FirekitUserStore} The FirekitUserStore instance
|
|
79
80
|
*/
|
|
80
81
|
static getInstance(): FirekitUserStore;
|
|
82
|
+
/**
|
|
83
|
+
* Initializes Firebase services and auth state listener
|
|
84
|
+
* @private
|
|
85
|
+
*/
|
|
86
|
+
private initializeServices;
|
|
81
87
|
/**
|
|
82
88
|
* Initializes the authentication state listener
|
|
83
89
|
* @private
|
|
@@ -31,8 +31,9 @@ import { mapFirebaseUserToProfile, updateUserInFirestore, createAuthError, valid
|
|
|
31
31
|
*/
|
|
32
32
|
class FirekitUserStore {
|
|
33
33
|
static instance;
|
|
34
|
-
auth =
|
|
35
|
-
firestore =
|
|
34
|
+
auth = null;
|
|
35
|
+
firestore = null;
|
|
36
|
+
_servicesInitialized = false;
|
|
36
37
|
// ========================================
|
|
37
38
|
// REACTIVE STATE (Svelte 5 Runes)
|
|
38
39
|
// ========================================
|
|
@@ -64,13 +65,8 @@ class FirekitUserStore {
|
|
|
64
65
|
/** Derived: User's phone number */
|
|
65
66
|
_userPhoneNumber = $derived(this._user?.phoneNumber ?? null);
|
|
66
67
|
constructor() {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
if (!this.firestore) {
|
|
71
|
-
throw new Error('Firestore instance not available');
|
|
72
|
-
}
|
|
73
|
-
this.initializeAuthStateListener();
|
|
68
|
+
// Don't initialize Firebase services in constructor
|
|
69
|
+
// They will be initialized lazily when needed
|
|
74
70
|
}
|
|
75
71
|
/**
|
|
76
72
|
* Gets singleton instance of FirekitUserStore
|
|
@@ -82,11 +78,35 @@ class FirekitUserStore {
|
|
|
82
78
|
}
|
|
83
79
|
return FirekitUserStore.instance;
|
|
84
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
|
+
}
|
|
85
101
|
/**
|
|
86
102
|
* Initializes the authentication state listener
|
|
87
103
|
* @private
|
|
88
104
|
*/
|
|
89
105
|
initializeAuthStateListener() {
|
|
106
|
+
if (!this.auth) {
|
|
107
|
+
console.error('Auth instance not available');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
90
110
|
onAuthStateChanged(this.auth, (firebaseUser) => {
|
|
91
111
|
this._user = firebaseUser ? this.mapFirebaseUserToProfile(firebaseUser) : null;
|
|
92
112
|
this._loading = false;
|
|
@@ -111,6 +131,9 @@ class FirekitUserStore {
|
|
|
111
131
|
* @private
|
|
112
132
|
*/
|
|
113
133
|
async updateUserInFirestore(user) {
|
|
134
|
+
if (!this.firestore) {
|
|
135
|
+
throw new Error('Firestore instance not available');
|
|
136
|
+
}
|
|
114
137
|
await updateUserInFirestore(this.firestore, user);
|
|
115
138
|
}
|
|
116
139
|
// ========================================
|
|
@@ -118,50 +141,62 @@ class FirekitUserStore {
|
|
|
118
141
|
// ========================================
|
|
119
142
|
/** Current user profile */
|
|
120
143
|
get user() {
|
|
144
|
+
this.initializeServices();
|
|
121
145
|
return this._user;
|
|
122
146
|
}
|
|
123
147
|
/** Current loading state */
|
|
124
148
|
get loading() {
|
|
149
|
+
this.initializeServices();
|
|
125
150
|
return this._loading;
|
|
126
151
|
}
|
|
127
152
|
/** Whether auth has been initialized */
|
|
128
153
|
get initialized() {
|
|
154
|
+
this.initializeServices();
|
|
129
155
|
return this._initialized;
|
|
130
156
|
}
|
|
131
157
|
/** Current error state */
|
|
132
158
|
get error() {
|
|
159
|
+
this.initializeServices();
|
|
133
160
|
return this._error;
|
|
134
161
|
}
|
|
135
162
|
/** Whether user is authenticated (not anonymous) */
|
|
136
163
|
get isAuthenticated() {
|
|
164
|
+
this.initializeServices();
|
|
137
165
|
return this._isAuthenticated;
|
|
138
166
|
}
|
|
139
167
|
/** Whether user is anonymous */
|
|
140
168
|
get isAnonymous() {
|
|
169
|
+
this.initializeServices();
|
|
141
170
|
return this._isAnonymous;
|
|
142
171
|
}
|
|
143
172
|
/** Whether user's email is verified */
|
|
144
173
|
get isEmailVerified() {
|
|
174
|
+
this.initializeServices();
|
|
145
175
|
return this._isEmailVerified;
|
|
146
176
|
}
|
|
147
177
|
/** User's email address */
|
|
148
178
|
get email() {
|
|
179
|
+
this.initializeServices();
|
|
149
180
|
return this._userEmail;
|
|
150
181
|
}
|
|
151
182
|
/** User's display name */
|
|
152
183
|
get displayName() {
|
|
184
|
+
this.initializeServices();
|
|
153
185
|
return this._userDisplayName;
|
|
154
186
|
}
|
|
155
187
|
/** User's photo URL */
|
|
156
188
|
get photoURL() {
|
|
189
|
+
this.initializeServices();
|
|
157
190
|
return this._userPhotoURL;
|
|
158
191
|
}
|
|
159
192
|
/** User's unique ID */
|
|
160
193
|
get uid() {
|
|
194
|
+
this.initializeServices();
|
|
161
195
|
return this._userId;
|
|
162
196
|
}
|
|
163
197
|
/** User's phone number */
|
|
164
198
|
get phoneNumber() {
|
|
199
|
+
this.initializeServices();
|
|
165
200
|
return this._userPhoneNumber;
|
|
166
201
|
}
|
|
167
202
|
// ========================================
|
|
@@ -179,6 +214,10 @@ class FirekitUserStore {
|
|
|
179
214
|
* ```
|
|
180
215
|
*/
|
|
181
216
|
async updateDisplayName(displayName) {
|
|
217
|
+
this.initializeServices();
|
|
218
|
+
if (!this.auth) {
|
|
219
|
+
throw new Error('Auth instance not available');
|
|
220
|
+
}
|
|
182
221
|
const currentUser = validateCurrentUser(this.auth);
|
|
183
222
|
try {
|
|
184
223
|
this._loading = true;
|
|
@@ -209,6 +248,10 @@ class FirekitUserStore {
|
|
|
209
248
|
* ```
|
|
210
249
|
*/
|
|
211
250
|
async updatePhotoURL(photoURL) {
|
|
251
|
+
this.initializeServices();
|
|
252
|
+
if (!this.auth) {
|
|
253
|
+
throw new Error('Auth instance not available');
|
|
254
|
+
}
|
|
212
255
|
const currentUser = validateCurrentUser(this.auth);
|
|
213
256
|
try {
|
|
214
257
|
this._loading = true;
|
|
@@ -242,6 +285,10 @@ class FirekitUserStore {
|
|
|
242
285
|
* ```
|
|
243
286
|
*/
|
|
244
287
|
async updateProfile(profileData) {
|
|
288
|
+
this.initializeServices();
|
|
289
|
+
if (!this.auth) {
|
|
290
|
+
throw new Error('Auth instance not available');
|
|
291
|
+
}
|
|
245
292
|
const currentUser = validateCurrentUser(this.auth);
|
|
246
293
|
try {
|
|
247
294
|
this._loading = true;
|
|
@@ -276,6 +323,10 @@ class FirekitUserStore {
|
|
|
276
323
|
* ```
|
|
277
324
|
*/
|
|
278
325
|
async updateEmail(newEmail) {
|
|
326
|
+
this.initializeServices();
|
|
327
|
+
if (!this.auth) {
|
|
328
|
+
throw new Error('Auth instance not available');
|
|
329
|
+
}
|
|
279
330
|
const currentUser = validateCurrentUser(this.auth);
|
|
280
331
|
try {
|
|
281
332
|
this._loading = true;
|
|
@@ -306,6 +357,10 @@ class FirekitUserStore {
|
|
|
306
357
|
* ```
|
|
307
358
|
*/
|
|
308
359
|
async updatePassword(newPassword) {
|
|
360
|
+
this.initializeServices();
|
|
361
|
+
if (!this.auth) {
|
|
362
|
+
throw new Error('Auth instance not available');
|
|
363
|
+
}
|
|
309
364
|
const currentUser = validateCurrentUser(this.auth);
|
|
310
365
|
try {
|
|
311
366
|
this._loading = true;
|
|
@@ -333,6 +388,10 @@ class FirekitUserStore {
|
|
|
333
388
|
* ```
|
|
334
389
|
*/
|
|
335
390
|
async sendEmailVerification() {
|
|
391
|
+
this.initializeServices();
|
|
392
|
+
if (!this.auth) {
|
|
393
|
+
throw new Error('Auth instance not available');
|
|
394
|
+
}
|
|
336
395
|
const currentUser = validateCurrentUser(this.auth);
|
|
337
396
|
try {
|
|
338
397
|
await sendEmailVerification(currentUser);
|
|
@@ -353,6 +412,10 @@ class FirekitUserStore {
|
|
|
353
412
|
* ```
|
|
354
413
|
*/
|
|
355
414
|
async reloadUser() {
|
|
415
|
+
this.initializeServices();
|
|
416
|
+
if (!this.auth) {
|
|
417
|
+
throw new Error('Auth instance not available');
|
|
418
|
+
}
|
|
356
419
|
const currentUser = validateCurrentUser(this.auth);
|
|
357
420
|
try {
|
|
358
421
|
this._loading = true;
|
|
@@ -384,6 +447,10 @@ class FirekitUserStore {
|
|
|
384
447
|
* ```
|
|
385
448
|
*/
|
|
386
449
|
async getIdToken(forceRefresh = false) {
|
|
450
|
+
this.initializeServices();
|
|
451
|
+
if (!this.auth) {
|
|
452
|
+
throw new Error('Auth instance not available');
|
|
453
|
+
}
|
|
387
454
|
const currentUser = validateCurrentUser(this.auth);
|
|
388
455
|
try {
|
|
389
456
|
return await getIdToken(currentUser, forceRefresh);
|
|
@@ -407,7 +474,8 @@ class FirekitUserStore {
|
|
|
407
474
|
* ```
|
|
408
475
|
*/
|
|
409
476
|
async getExtendedUserData() {
|
|
410
|
-
|
|
477
|
+
this.initializeServices();
|
|
478
|
+
if (!this._user?.uid || !this.firestore) {
|
|
411
479
|
return null;
|
|
412
480
|
}
|
|
413
481
|
try {
|
|
@@ -438,9 +506,13 @@ class FirekitUserStore {
|
|
|
438
506
|
* ```
|
|
439
507
|
*/
|
|
440
508
|
async updateExtendedUserData(data) {
|
|
509
|
+
this.initializeServices();
|
|
441
510
|
if (!this._user?.uid) {
|
|
442
511
|
throw new FirekitAuthError(AuthErrorCode.USER_NOT_FOUND, 'No authenticated user found.');
|
|
443
512
|
}
|
|
513
|
+
if (!this.firestore) {
|
|
514
|
+
throw new Error('Firestore instance not available');
|
|
515
|
+
}
|
|
444
516
|
try {
|
|
445
517
|
const userRef = doc(this.firestore, 'users', this._user.uid);
|
|
446
518
|
await setDoc(userRef, {
|
|
@@ -469,6 +541,7 @@ class FirekitUserStore {
|
|
|
469
541
|
* ```
|
|
470
542
|
*/
|
|
471
543
|
async waitForAuth() {
|
|
544
|
+
this.initializeServices();
|
|
472
545
|
return new Promise((resolve) => {
|
|
473
546
|
if (this._initialized) {
|
|
474
547
|
resolve(this._user);
|
|
@@ -508,6 +581,9 @@ class FirekitUserStore {
|
|
|
508
581
|
this._loading = true;
|
|
509
582
|
this._initialized = false;
|
|
510
583
|
this._error = null;
|
|
584
|
+
this._servicesInitialized = false;
|
|
585
|
+
this.auth = null;
|
|
586
|
+
this.firestore = null;
|
|
511
587
|
}
|
|
512
588
|
}
|
|
513
589
|
/**
|