@chem-po/firebase-native 0.0.52 → 0.0.53

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 (37) hide show
  1. package/lib/commonjs/hooks/useAuthenticatorVerify.js +14 -5
  2. package/lib/commonjs/hooks/useAuthenticatorVerify.js.map +1 -1
  3. package/lib/module/hooks/useAuthenticatorVerify.js +15 -6
  4. package/lib/module/hooks/useAuthenticatorVerify.js.map +1 -1
  5. package/lib/typescript/hooks/useAuthenticatorVerify.d.ts.map +1 -1
  6. package/package.json +6 -21
  7. package/src/adapter/auth.ts +0 -562
  8. package/src/adapter/db.ts +0 -146
  9. package/src/adapter/index.ts +0 -32
  10. package/src/adapter/storage.ts +0 -58
  11. package/src/auth/functions.ts +0 -7
  12. package/src/auth/index.ts +0 -1
  13. package/src/components/AuthenticatorVerify.tsx +0 -76
  14. package/src/components/FirebaseSignIn.tsx +0 -234
  15. package/src/components/PhoneVerify.tsx +0 -115
  16. package/src/components/TwoFactorAuthModal.tsx +0 -198
  17. package/src/components/index.ts +0 -2
  18. package/src/contexts/FirebaseContext.tsx +0 -50
  19. package/src/contexts/index.ts +0 -1
  20. package/src/db/index.ts +0 -1
  21. package/src/db/utils.ts +0 -142
  22. package/src/hooks/backend.ts +0 -4
  23. package/src/hooks/index.ts +0 -1
  24. package/src/hooks/useAuthenticatorVerify.ts +0 -39
  25. package/src/hooks/usePhoneVerify.ts +0 -87
  26. package/src/icons/Apple.tsx +0 -12
  27. package/src/icons/Google.tsx +0 -24
  28. package/src/index.ts +0 -9
  29. package/src/storage/index.ts +0 -1
  30. package/src/storage/utils.ts +0 -29
  31. package/src/types/adapter.ts +0 -13
  32. package/src/types/auth.ts +0 -13
  33. package/src/types/db.ts +0 -10
  34. package/src/types/functions.ts +0 -3
  35. package/src/types/index.ts +0 -29
  36. package/src/types/storage.ts +0 -3
  37. package/src/utils/validation.ts +0 -92
@@ -1,562 +0,0 @@
1
- import {
2
- AuthAdapter,
3
- BaseAuthProvider,
4
- BaseUserData,
5
- BaseUserRole,
6
- EnrollmentFactor,
7
- EnrollmentFactorsResult,
8
- LoginResult,
9
- MultiFactorVerification,
10
- WithMultiFactorVerified,
11
- } from '@chem-po/core'
12
- import {
13
- AppleAuthProvider,
14
- FirebaseAuthTypes,
15
- getIdTokenResult,
16
- getMultiFactorResolver,
17
- GoogleAuthProvider,
18
- onAuthStateChanged,
19
- PhoneAuthProvider,
20
- PhoneMultiFactorGenerator,
21
- sendPasswordResetEmail,
22
- signInWithCredential,
23
- signInWithCustomToken,
24
- signInWithEmailAndPassword,
25
- signOut,
26
- TotpMultiFactorGenerator,
27
- } from '@react-native-firebase/auth'
28
-
29
- import { appleAuth } from '@invertase/react-native-apple-authentication'
30
- import { GoogleSignin } from '@react-native-google-signin/google-signin'
31
- import { Platform } from 'react-native'
32
- import { Auth, User } from '../types/auth'
33
-
34
- const isDebug =
35
- (typeof process !== 'undefined' && process.env?.EXPO_PUBLIC_DEBUG === 'true') || false
36
-
37
- const debugLog = (message: string, data?: any) => {
38
- if (isDebug) {
39
- console.log(`[FirebaseAuth Debug] ${message}`, data || '')
40
- }
41
- }
42
-
43
- const providerInitialized: Record<string, boolean> = {}
44
-
45
- const getUserWithRole = async <UserData extends BaseUserData>(
46
- user: FirebaseAuthTypes.User
47
- ): Promise<WithMultiFactorVerified<UserData>> => {
48
- debugLog('Getting user with role', { uid: user.uid, email: user.email })
49
-
50
- try {
51
- const { claims } = await getIdTokenResult(user)
52
- debugLog('Retrieved ID token claims', { role: claims.role, customClaims: Object.keys(claims) })
53
-
54
- const userWithRole = {
55
- ...(user as any),
56
- role: claims.role ?? ('user' as BaseUserRole),
57
- multiFactorVerified: !!user.multiFactor?.enrolledFactors.length,
58
- } as WithMultiFactorVerified<UserData>
59
-
60
- debugLog('User with role created', {
61
- uid: userWithRole.uid,
62
- role: userWithRole.role,
63
- multiFactorVerified: userWithRole.multiFactorVerified,
64
- enrolledFactorsCount: user.multiFactor?.enrolledFactors.length || 0,
65
- })
66
-
67
- return userWithRole
68
- } catch (error) {
69
- debugLog('Error getting user with role', error)
70
- throw error
71
- }
72
- }
73
-
74
- const toEnrollmentFactor = (hint: FirebaseAuthTypes.MultiFactorInfo): EnrollmentFactor => {
75
- if (hint.factorId === PhoneMultiFactorGenerator.FACTOR_ID) {
76
- return {
77
- type: 'phone',
78
- phoneNumber: (hint as FirebaseAuthTypes.PhoneMultiFactorInfo).phoneNumber,
79
- enrollmentTime: hint.enrollmentTime,
80
- displayName: (hint as FirebaseAuthTypes.PhoneMultiFactorInfo).displayName,
81
- uid: hint.uid,
82
- }
83
- }
84
- if (hint.factorId === 'totp') {
85
- return {
86
- type: 'totp',
87
- enrollmentTime: hint.enrollmentTime,
88
- displayName: hint.displayName,
89
- uid: hint.uid,
90
- }
91
- }
92
- throw new Error('Unsupported factor type: ' + hint.factorId)
93
- }
94
-
95
- const toFirebaseFactor = (factor: EnrollmentFactor): FirebaseAuthTypes.MultiFactorInfo => {
96
- if (factor.type === 'phone') {
97
- return {
98
- factorId: PhoneMultiFactorGenerator.FACTOR_ID,
99
- phoneNumber: factor.phoneNumber,
100
- enrollmentTime: factor.enrollmentTime,
101
- uid: factor.uid,
102
- displayName: factor.displayName,
103
- }
104
- }
105
- if (factor.type === 'totp') {
106
- return {
107
- factorId: 'totp',
108
- enrollmentTime: factor.enrollmentTime,
109
- uid: factor.uid,
110
- displayName: factor.displayName,
111
- }
112
- }
113
- throw new Error(
114
- `Unsupported factor type: ${(factor as FirebaseAuthTypes.MultiFactorInfo).factorId ?? 'Missing factor type'}`
115
- )
116
- }
117
-
118
- const sendMultiFactorCode = async (
119
- auth: Auth,
120
- factor: EnrollmentFactor,
121
- resolver: FirebaseAuthTypes.MultiFactorResolver
122
- ): Promise<MultiFactorVerification> => {
123
- debugLog('Sending multi-factor code', { factorType: factor.type, factorUid: factor.uid })
124
-
125
- const sessionId = resolver.session
126
- // const verificationId = await new PhoneAuthProvider(auth).verifyPhoneNumber(phoneSignInFactor, sessionId)
127
- if (factor.type === 'phone') {
128
- try {
129
- const verificationId = await auth.verifyPhoneNumberWithMultiFactorInfo(
130
- toFirebaseFactor(factor),
131
- sessionId
132
- )
133
- debugLog('Multi-factor code sent successfully', {
134
- verificationId: verificationId.substring(0, 10) + '...',
135
- })
136
- return {
137
- verificationId,
138
- factor,
139
- resolver,
140
- }
141
- } catch (error) {
142
- debugLog('Error sending multi-factor code', error)
143
- throw error
144
- }
145
- }
146
- throw new Error(`Unsupported factor type: ${factor.type ?? 'Missing factor type'}`)
147
- }
148
-
149
- const getEnrolledFactors = async (auth: Auth, error: any): Promise<EnrollmentFactorsResult> => {
150
- debugLog('Getting enrolled factors from error', { errorCode: error?.code })
151
-
152
- try {
153
- const resolver = getMultiFactorResolver(auth, error)
154
- debugLog('Multi-factor resolver created', { hintsCount: resolver.hints.length })
155
-
156
- if (resolver.hints.length === 0) {
157
- debugLog('No enrolled factors found')
158
- throw new Error(
159
- 'No multi-factor verification methods found, please enroll one on the website'
160
- )
161
- }
162
-
163
- const enrollmentFactors = resolver.hints.map(toEnrollmentFactor)
164
- debugLog('Enrollment factors mapped', {
165
- factors: enrollmentFactors.map((f) => ({ type: f.type, uid: f.uid })),
166
- })
167
-
168
- return {
169
- enrollmentFactors,
170
- multiFactorResolver: resolver,
171
- }
172
- } catch (error) {
173
- debugLog('Error getting enrolled factors', error)
174
- throw error
175
- }
176
- }
177
-
178
- const verifyMultiFactor = async (
179
- verification: MultiFactorVerification,
180
- code: string
181
- ): Promise<LoginResult<User>> => {
182
- debugLog('Verifying multi-factor code', {
183
- factorType: verification.factor.type,
184
- codeLength: code.length,
185
- })
186
-
187
- try {
188
- let assertion: FirebaseAuthTypes.MultiFactorAssertion
189
- if (verification.factor.type === 'totp') {
190
- debugLog('Creating TOTP assertion', { factorUid: verification.factor.uid })
191
- assertion = TotpMultiFactorGenerator.assertionForSignIn(verification.factor.uid, code)
192
- } else {
193
- const credential = await PhoneAuthProvider.credential(verification.verificationId, code)
194
- debugLog('Phone credential created')
195
- assertion = PhoneMultiFactorGenerator.assertion(credential)
196
- }
197
- debugLog('Multi-factor assertion created')
198
-
199
- const resolver = (verification as any).resolver
200
- if (!resolver) {
201
- debugLog('Multi-factor resolver not found')
202
- throw new Error('Internal error signing in with two factor: resolver not found')
203
- }
204
-
205
- const userCredential = await resolver.resolveSignIn(assertion)
206
- debugLog('Multi-factor sign-in resolved', { uid: userCredential.user?.uid })
207
-
208
- const user = userCredential.user
209
- if (!user) {
210
- debugLog('No user found after multi-factor resolution')
211
- throw new Error('No user found')
212
- }
213
-
214
- const userWithRole = await getUserWithRole(user)
215
- debugLog('Multi-factor verification completed successfully')
216
-
217
- return { user: userWithRole }
218
- } catch (error) {
219
- debugLog('Error verifying multi-factor', error)
220
- throw error
221
- }
222
- }
223
-
224
- const handleSignInError = async (error: any): Promise<LoginResult<User>> => {
225
- debugLog('Handling sign-in error', { code: error?.code, message: error?.message })
226
-
227
- if (error.code === 'auth/multi-factor-auth-required') {
228
- debugLog('Multi-factor authentication required')
229
- return { requestArgs: error }
230
- }
231
-
232
- debugLog('Re-throwing sign-in error')
233
- throw error
234
- }
235
-
236
- const initializeProvider = async (provider: BaseAuthProvider) => {
237
- debugLog('Initializing provider', {
238
- name: provider.name,
239
- alreadyInitialized: providerInitialized[provider.name],
240
- })
241
-
242
- if (providerInitialized[provider.name]) {
243
- debugLog('Provider already initialized, skipping')
244
- return
245
- }
246
-
247
- switch (provider.name) {
248
- case 'google':
249
- const { webClientId, iosClientId } = provider as GoogleAuthProvider
250
- debugLog('Initializing Google provider', {
251
- webClientId,
252
- iosClientId,
253
- })
254
-
255
- if (!webClientId) {
256
- debugLog('Google web client ID missing')
257
- throw new Error(
258
- 'Google web client ID is required when using Google Auth. ' +
259
- 'Get your webClientId from Firebase Console > Authentication > Sign-in method > Google > Web SDK configuration. ' +
260
- 'Then provide it in your GoogleAuthProvider: { name: "google", webClientId: "YOUR_CLIENT_ID" }'
261
- )
262
- }
263
-
264
- try {
265
- await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true })
266
- debugLog('Google Play Services available')
267
-
268
- GoogleSignin.configure({
269
- webClientId,
270
- iosClientId,
271
- })
272
- debugLog('Google Sign-In configured successfully')
273
- } catch (error) {
274
- debugLog('Error initializing Google provider', error)
275
- if (error instanceof Error && error.message.includes('UNAVAILABLE')) {
276
- throw new Error(
277
- 'Google Play Services is not available or needs to be updated. ' +
278
- 'Please ensure Google Play Services is installed and up to date on this device.'
279
- )
280
- }
281
- throw error
282
- }
283
- break
284
- case 'apple':
285
- debugLog('Apple provider initialization')
286
- try {
287
- const isAppleAuthAvailable = appleAuth.isSupported
288
- debugLog('Apple authentication supported:', isAppleAuthAvailable)
289
- if (!isAppleAuthAvailable) {
290
- throw new Error('Apple authentication is not supported on this device')
291
- }
292
- debugLog('Apple authentication is supported')
293
- } catch (error) {
294
- debugLog('Error checking Apple authentication support', error)
295
- throw error
296
- }
297
- break
298
-
299
- case 'email':
300
- debugLog('Email provider initialization (no special setup required)')
301
- // Native SDK doesn't need special initialization
302
- break
303
- default:
304
- debugLog('Unsupported provider', { name: provider.name })
305
- throw new Error(`Unsupported provider for initialization: ${provider.name}`)
306
- }
307
-
308
- providerInitialized[provider.name] = true
309
- debugLog('Provider initialization completed', { name: provider.name })
310
- }
311
-
312
- const handleInitialLogin = async (
313
- userCredential: FirebaseAuthTypes.UserCredential,
314
- twoFactorRequired: boolean
315
- ): Promise<LoginResult<User>> => {
316
- debugLog('Handling initial login', {
317
- uid: userCredential.user?.uid,
318
- twoFactorRequired,
319
- enrolledFactorsCount: userCredential.user?.multiFactor?.enrolledFactors.length || 0,
320
- })
321
-
322
- const user = userCredential.user
323
- if (!user) {
324
- debugLog('No user found in credential')
325
- throw new Error('No user found')
326
- }
327
-
328
- if (twoFactorRequired) {
329
- debugLog('Two-factor authentication is required but user has no enrolled factors')
330
- throw new Error(
331
- 'This app requires two factor authentication, please enroll a factor on the website and try again'
332
- )
333
- }
334
-
335
- const userWithRole = await getUserWithRole(user)
336
- debugLog('Initial login completed successfully')
337
-
338
- return { user: userWithRole }
339
- }
340
-
341
- const loginWithApple = async (
342
- auth: Auth,
343
- twoFactorRequired: boolean
344
- ): Promise<LoginResult<User>> => {
345
- debugLog('Starting Apple sign-in', { twoFactorRequired })
346
-
347
- try {
348
- // Start the sign-in request
349
- const appleAuthRequestResponse = await appleAuth.performRequest({
350
- requestedOperation: appleAuth.Operation.LOGIN,
351
- requestedScopes: [appleAuth.Scope.FULL_NAME, appleAuth.Scope.EMAIL],
352
- })
353
-
354
- // Ensure Apple returned a user identityToken
355
- if (!appleAuthRequestResponse.identityToken) {
356
- debugLog('No identity token returned from Apple')
357
- throw new Error('Apple Sign-In failed - no identity token returned')
358
- }
359
-
360
- debugLog('Apple sign-in response received', {
361
- hasIdentityToken: appleAuthRequestResponse.identityToken,
362
- hasNonce: appleAuthRequestResponse.nonce,
363
- })
364
-
365
- // Create a Firebase credential from the response
366
- const { identityToken, nonce } = appleAuthRequestResponse
367
- const appleCredential = AppleAuthProvider.credential(identityToken, nonce)
368
-
369
- debugLog('Signing in with Apple credential')
370
- const userCredential = await signInWithCredential(auth, appleCredential)
371
-
372
- return await handleInitialLogin(userCredential, twoFactorRequired)
373
- } catch (error) {
374
- debugLog('Error in Apple sign-in', error)
375
- return await handleSignInError(error)
376
- }
377
- }
378
-
379
- const loginWithGoogle = async (
380
- auth: Auth,
381
- twoFactorRequired: boolean
382
- ): Promise<LoginResult<User>> => {
383
- debugLog('Starting Google sign-in', { twoFactorRequired })
384
-
385
- try {
386
- debugLog('Calling GoogleSignin.signIn()')
387
- const signInResult = await GoogleSignin.signIn()
388
- debugLog('Google sign-in result received', { hasIdToken: !!signInResult.data?.idToken })
389
-
390
- const idToken = signInResult.data?.idToken
391
- if (!idToken) {
392
- debugLog('No ID token found in Google sign-in result')
393
- throw new Error('No ID token found')
394
- }
395
-
396
- debugLog('Creating Google credential')
397
- const googleCredential = GoogleAuthProvider.credential(idToken)
398
-
399
- debugLog('Signing in with Google credential')
400
- const userCredential = await signInWithCredential(auth, googleCredential)
401
-
402
- return await handleInitialLogin(userCredential, twoFactorRequired)
403
- } catch (error) {
404
- debugLog('Error in Google sign-in', error)
405
- return await handleSignInError(error)
406
- }
407
- }
408
-
409
- const getLoginWithPassword =
410
- (auth: Auth, twoFactorRequired: boolean) =>
411
- async (
412
- provider: BaseAuthProvider,
413
- { email, password }: { email: string; password: string }
414
- ): Promise<LoginResult<User>> => {
415
- debugLog('Starting email/password sign-in', {
416
- provider: provider.name,
417
- email,
418
- twoFactorRequired,
419
- })
420
-
421
- await initializeProvider(provider)
422
- try {
423
- debugLog('Calling signInWithEmailAndPassword')
424
- const userCredential = await signInWithEmailAndPassword(auth, email, password)
425
- debugLog('Email/password sign-in successful', { uid: userCredential.user?.uid })
426
-
427
- return await handleInitialLogin(userCredential, twoFactorRequired)
428
- } catch (error) {
429
- debugLog('Error in email/password sign-in', error)
430
- return await handleSignInError(error)
431
- }
432
- }
433
-
434
- const getLoginWithPopup =
435
- (auth: Auth, twoFactorRequired: boolean) =>
436
- async (provider: BaseAuthProvider): Promise<LoginResult<User>> => {
437
- debugLog('Starting popup sign-in', { provider: provider.name, twoFactorRequired })
438
-
439
- await initializeProvider(provider)
440
- switch (provider.name) {
441
- case 'google':
442
- return loginWithGoogle(auth, twoFactorRequired)
443
- case 'apple':
444
- return loginWithApple(auth, twoFactorRequired)
445
- default:
446
- debugLog('Unsupported popup provider', provider)
447
- throw new Error(`Unsupported provider for login with popup: ${provider.name}`)
448
- }
449
- }
450
-
451
- const getLoginWithRedirect =
452
- (auth: Auth, twoFactorRequired: boolean) =>
453
- async (provider: BaseAuthProvider): Promise<LoginResult<User>> => {
454
- debugLog('Starting redirect sign-in', { provider: provider.name, twoFactorRequired })
455
-
456
- // In React Native, redirect is the same as popup since we're using native SDKs
457
- return getLoginWithPopup(auth, twoFactorRequired)(provider)
458
- }
459
-
460
- const getLoginWithToken =
461
- (auth: Auth, twoFactorRequired: boolean) =>
462
- async (provider: BaseAuthProvider, token: string): Promise<LoginResult<User>> => {
463
- debugLog('Starting token sign-in', {
464
- provider: provider.name,
465
- tokenLength: token.length,
466
- twoFactorRequired,
467
- })
468
-
469
- await initializeProvider(provider)
470
- try {
471
- debugLog('Calling signInWithCustomToken')
472
- const userCredential = await signInWithCustomToken(auth, token)
473
- debugLog('Token sign-in successful', { uid: userCredential.user?.uid })
474
-
475
- return await handleInitialLogin(userCredential, twoFactorRequired)
476
- } catch (error) {
477
- debugLog('Error in token sign-in', error)
478
- return await handleSignInError(error)
479
- }
480
- }
481
-
482
- const resetPassword = async (
483
- auth: Auth,
484
- provider: BaseAuthProvider,
485
- usernameOrEmail: string
486
- ): Promise<void> => {
487
- debugLog('Starting password reset', { provider: provider.name, email: usernameOrEmail })
488
-
489
- await initializeProvider(provider)
490
- try {
491
- await sendPasswordResetEmail(auth, usernameOrEmail)
492
- debugLog('Password reset email sent successfully')
493
- } catch (error) {
494
- debugLog('Error sending password reset email', error)
495
- throw error
496
- }
497
- }
498
-
499
- export const getFirebaseAuthAdapter = (
500
- auth: Auth,
501
- twoFactorRequired: boolean
502
- ): AuthAdapter<BaseAuthProvider, User, { email: string; password: string }> => {
503
- debugLog('Creating Firebase Auth Adapter', { twoFactorRequired })
504
-
505
- return {
506
- twoFactorRequired,
507
- getCurrentUser: async () => {
508
- debugLog('Getting current user')
509
- const user = auth.currentUser
510
- if (!user) {
511
- debugLog('No current user found')
512
- return null
513
- }
514
- debugLog('Current user found', { uid: user.uid, email: user.email })
515
- return getUserWithRole(user)
516
- },
517
- loginWithPassword: getLoginWithPassword(auth, twoFactorRequired),
518
- loginWithPopup: getLoginWithPopup(auth, twoFactorRequired),
519
- loginWithRedirect: getLoginWithRedirect(auth, twoFactorRequired),
520
- loginWithToken: getLoginWithToken(auth, twoFactorRequired),
521
- resetPassword: (...args) => {
522
- return resetPassword(auth, ...args)
523
- },
524
- verifyMultiFactor,
525
- sendMultiFactorCode: (...args) => {
526
- return sendMultiFactorCode(auth, ...args)
527
- },
528
- getEnrolledFactors: (...args) => {
529
- return getEnrolledFactors(auth, ...args)
530
- },
531
- logout: async () => {
532
- debugLog('Starting logout')
533
- try {
534
- await signOut(auth)
535
- debugLog('Logout completed successfully')
536
- } catch (error) {
537
- debugLog('Error during logout', error)
538
- throw error
539
- }
540
- },
541
- subscribeToUser: (callback) => {
542
- debugLog('Setting up user subscription')
543
- return onAuthStateChanged(auth, (user) => {
544
- if (!user) {
545
- debugLog('User state changed: signed out')
546
- callback(null)
547
- return
548
- }
549
- debugLog('User state changed: user signed in', { uid: user.uid, email: user.email })
550
- getUserWithRole(user)
551
- .then((result) => {
552
- debugLog('User subscription callback completed', { uid: result.uid, role: result.role })
553
- callback(result)
554
- })
555
- .catch((error) => {
556
- debugLog('Error in user subscription callback', error)
557
- callback(null)
558
- })
559
- })
560
- },
561
- }
562
- }
package/src/adapter/db.ts DELETED
@@ -1,146 +0,0 @@
1
- import {
2
- addMetadata,
3
- AnyObject,
4
- BaseError,
5
- DatabaseAdapter,
6
- DBItem,
7
- OnError,
8
- OnItemData,
9
- Unsubscribe,
10
- WithMetadata,
11
- } from '@chem-po/core'
12
- import { useAuth } from '@chem-po/react'
13
- import {
14
- deleteDoc,
15
- doc,
16
- FirebaseFirestoreTypes,
17
- getDoc,
18
- getDocs,
19
- onSnapshot,
20
- setDoc,
21
- } from '@react-native-firebase/firestore'
22
- import { httpsCallable } from '@react-native-firebase/functions'
23
- import { toCursorQuery, toFirestoreQuery } from '../db/utils'
24
- import { Firestore, FirestoreBaseQuery, FirestoreCursor, FirestoreOnItemsData } from '../types/db'
25
- import { Functions } from '../types/functions'
26
- // import { BaseQuery, FirestoreCursor } from '../types/db'
27
-
28
- const handleFirestoreError = (error: any): BaseError => ({
29
- code: error?.code ?? 'unknown',
30
- message: error?.name ?? 'Unknown Error',
31
- description: error?.message ?? 'An unknown error occurred',
32
- })
33
-
34
- const toDBItem = <T extends AnyObject>(
35
- doc: FirebaseFirestoreTypes.DocumentSnapshot<T>,
36
- ): DBItem<T> => ({
37
- ...(doc.data() as WithMetadata<T>),
38
- id: doc.id,
39
- })
40
-
41
- const getSubscribeToQuery =
42
- (db: Firestore) =>
43
- <T extends AnyObject>(
44
- baseQuery: FirestoreBaseQuery<T>,
45
- onData: FirestoreOnItemsData<T>,
46
- onError: OnError,
47
- ): Unsubscribe => {
48
- const q = toFirestoreQuery<T>(db, baseQuery)
49
- return onSnapshot(
50
- q,
51
- (snapshot: FirebaseFirestoreTypes.QuerySnapshot<T>) => {
52
- const items = snapshot.docs.map(doc => toDBItem(doc))
53
- onData(items, snapshot.docs[snapshot.docs.length - 1] as FirestoreCursor)
54
- },
55
- error => onError(handleFirestoreError(error)),
56
- )
57
- }
58
-
59
- const getFetchNextCursor =
60
- (db: Firestore) =>
61
- async <T extends AnyObject>(
62
- baseQuery: FirestoreBaseQuery<T>,
63
- ): Promise<FirestoreCursor | null> => {
64
- const { cursor } = baseQuery
65
- const cursorDoc = cursor ? await getDoc(doc(db, baseQuery.collection, cursor.id)) : null
66
- const q = toCursorQuery<T>(db, baseQuery, cursorDoc as FirestoreCursor | null)
67
- const snapshot = await getDocs(q)
68
- return (snapshot.docs[0] ?? null) as FirestoreCursor | null
69
- }
70
-
71
- const getFetchCount =
72
- (functions: Functions) =>
73
- async <T extends AnyObject>(baseQuery: FirestoreBaseQuery<T>): Promise<number> => {
74
- const fetchCountFunc = httpsCallable(functions, 'getQueryCount')
75
- const { data } = await fetchCountFunc(baseQuery)
76
- return (data as { count: number }).count
77
- }
78
-
79
- const getFetchItem =
80
- (db: Firestore) =>
81
- async <T extends AnyObject>(path: string): Promise<DBItem<T> | null> => {
82
- const docRef = doc(db, path)
83
- const docSnap = await getDoc(docRef)
84
- const data = docSnap.data()
85
- return data ? ({ ...data, id: docSnap.id } as DBItem<T>) : null
86
- }
87
-
88
- const getFetchItems =
89
- (db: Firestore) =>
90
- async <T extends AnyObject>(baseQuery: FirestoreBaseQuery<T>): Promise<DBItem<T>[]> => {
91
- const q = toFirestoreQuery<T>(db, baseQuery)
92
- const snapshot = (await getDocs(q)) as FirebaseFirestoreTypes.QuerySnapshot<T>
93
- return snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id }) as DBItem<T>)
94
- }
95
-
96
- const getCreateItem =
97
- (db: Firestore) =>
98
- async (path: string, item: AnyObject): Promise<string> => {
99
- const docRef = doc(db, path)
100
- const data = addMetadata(useAuth.getState().user, item, true)
101
- await setDoc(docRef, data, { merge: true })
102
- return docRef.id
103
- }
104
-
105
- const getDeleteItem =
106
- (db: Firestore) =>
107
- async (path: string): Promise<void> => {
108
- const docRef = doc(db, path)
109
- await deleteDoc(docRef)
110
- }
111
-
112
- const getUpdateItem =
113
- (db: Firestore) =>
114
- async <T extends AnyObject>(path: string, item: Partial<T>): Promise<void> => {
115
- const docRef = doc(db, path)
116
- await setDoc(docRef, item, { merge: true })
117
- }
118
-
119
- const getSubscribeToItem =
120
- (db: Firestore) =>
121
- <T extends AnyObject>(path: string, onData: OnItemData<T>, onError: OnError): Unsubscribe => {
122
- const docRef = doc(db, path)
123
- return onSnapshot(
124
- docRef,
125
- (snapshot: FirebaseFirestoreTypes.DocumentSnapshot<T>) => {
126
- const data = snapshot.data()
127
- onData(data ? ({ ...data, id: snapshot.id } as DBItem<T>) : null)
128
- },
129
- error => onError(handleFirestoreError(error)),
130
- )
131
- }
132
-
133
- export const getFirebaseDatabaseAdapter = (
134
- db: Firestore,
135
- functions: Functions,
136
- ): DatabaseAdapter<FirestoreCursor> => ({
137
- fetchNextCursor: getFetchNextCursor(db),
138
- fetchCount: getFetchCount(functions),
139
- fetchItem: getFetchItem(db),
140
- fetchItems: getFetchItems(db),
141
- createItem: getCreateItem(db),
142
- deleteItem: getDeleteItem(db),
143
- updateItem: getUpdateItem(db),
144
- subscribeToItem: getSubscribeToItem(db),
145
- subscribeToQuery: getSubscribeToQuery(db),
146
- })