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.
- 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 +389 -0
- package/dist/services/auth.js +824 -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 +290 -0
- package/dist/services/user.svelte.js +533 -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 +65 -65
- 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,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();
|