@chem-po/firebase-native 0.0.31 → 0.0.33

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@chem-po/firebase-native",
3
3
  "author": "Elan Canfield",
4
4
  "license": "MIT",
5
- "version": "0.0.31",
5
+ "version": "0.0.33",
6
6
  "main": "lib/commonjs/index.js",
7
7
  "types": "lib/typescript/index.d.ts",
8
8
  "source": "src/index.ts",
@@ -32,6 +32,7 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@react-native-google-signin/google-signin": "^14.0.1",
35
+ "@invertase/react-native-apple-authentication": "^2.4.1",
35
36
  "@react-native-firebase/app": "^22.4.0",
36
37
  "@react-native-firebase/auth": "^22.4.0",
37
38
  "@react-native-firebase/firestore": "^22.4.0",
@@ -43,9 +44,9 @@
43
44
  "react-native-paper": "^5.14.3",
44
45
  "react-native-svg": "15.11.2",
45
46
  "zustand": "^4.3.3",
46
- "@chem-po/core": "0.0.31",
47
- "@chem-po/react": "0.0.31",
48
- "@chem-po/react-native": "0.0.31"
47
+ "@chem-po/core": "0.0.33",
48
+ "@chem-po/react-native": "0.0.33",
49
+ "@chem-po/react": "0.0.33"
49
50
  },
50
51
  "devDependencies": {
51
52
  "@babel/core": "^7.26.0",
@@ -7,9 +7,10 @@ import {
7
7
  EnrollmentFactorsResult,
8
8
  LoginResult,
9
9
  MultiFactorVerification,
10
- WithMultiFactorVerified,
10
+ WithMultiFactorVerified
11
11
  } from '@chem-po/core'
12
12
  import {
13
+ AppleAuthProvider,
13
14
  FirebaseAuthTypes,
14
15
  getIdTokenResult,
15
16
  getMultiFactorResolver,
@@ -24,6 +25,7 @@ import {
24
25
  signOut,
25
26
  } from '@react-native-firebase/auth'
26
27
 
28
+ import { appleAuth } from '@invertase/react-native-apple-authentication'
27
29
  import { GoogleSignin } from '@react-native-google-signin/google-signin'
28
30
  import { Auth, User } from '../types/auth'
29
31
 
@@ -260,13 +262,28 @@ const initializeProvider = async (provider: BaseAuthProvider) => {
260
262
  throw error
261
263
  }
262
264
  break
265
+ case 'apple':
266
+ debugLog('Apple provider initialization')
267
+ try {
268
+ const isAppleAuthAvailable = appleAuth.isSupported
269
+ debugLog('Apple authentication supported:', isAppleAuthAvailable)
270
+ if (!isAppleAuthAvailable) {
271
+ throw new Error('Apple authentication is not supported on this device')
272
+ }
273
+ debugLog('Apple authentication is supported')
274
+ } catch (error) {
275
+ debugLog('Error checking Apple authentication support', error)
276
+ throw error
277
+ }
278
+ break
279
+
263
280
  case 'email':
264
281
  debugLog('Email provider initialization (no special setup required)')
265
282
  // Native SDK doesn't need special initialization
266
283
  break
267
284
  default:
268
285
  debugLog('Unsupported provider', { name: provider.name })
269
- throw new Error(`Unsupported provider: ${provider.name}`)
286
+ throw new Error(`Unsupported provider for initialization: ${provider.name}`)
270
287
  }
271
288
 
272
289
  providerInitialized[provider.name] = true
@@ -302,6 +319,44 @@ const handleInitialLogin = async (
302
319
  return { user: userWithRole }
303
320
  }
304
321
 
322
+ const loginWithApple = async (
323
+ auth: Auth,
324
+ twoFactorRequired: boolean
325
+ ): Promise<LoginResult<User>> => {
326
+ debugLog('Starting Apple sign-in', { twoFactorRequired })
327
+
328
+ try {
329
+ // Start the sign-in request
330
+ const appleAuthRequestResponse = await appleAuth.performRequest({
331
+ requestedOperation: appleAuth.Operation.LOGIN,
332
+ requestedScopes: [appleAuth.Scope.FULL_NAME, appleAuth.Scope.EMAIL],
333
+ })
334
+
335
+ // Ensure Apple returned a user identityToken
336
+ if (!appleAuthRequestResponse.identityToken) {
337
+ debugLog('No identity token returned from Apple')
338
+ throw new Error('Apple Sign-In failed - no identity token returned')
339
+ }
340
+
341
+ debugLog('Apple sign-in response received', {
342
+ hasIdentityToken: appleAuthRequestResponse.identityToken,
343
+ hasNonce: appleAuthRequestResponse.nonce
344
+ })
345
+
346
+ // Create a Firebase credential from the response
347
+ const { identityToken, nonce } = appleAuthRequestResponse
348
+ const appleCredential = AppleAuthProvider.credential(identityToken, nonce)
349
+
350
+ debugLog('Signing in with Apple credential')
351
+ const userCredential = await signInWithCredential(auth, appleCredential)
352
+
353
+ return await handleInitialLogin(userCredential, twoFactorRequired)
354
+ } catch (error) {
355
+ debugLog('Error in Apple sign-in', error)
356
+ return await handleSignInError(error)
357
+ }
358
+ }
359
+
305
360
  const loginWithGoogle = async (
306
361
  auth: Auth,
307
362
  twoFactorRequired: boolean
@@ -366,12 +421,23 @@ const getLoginWithPopup =
366
421
  switch (provider.name) {
367
422
  case 'google':
368
423
  return loginWithGoogle(auth, twoFactorRequired)
424
+ case 'apple':
425
+ return loginWithApple(auth, twoFactorRequired)
369
426
  default:
370
- debugLog('Unsupported popup provider', { name: provider.name })
371
- throw new Error(`Unsupported provider: ${provider.name}`)
427
+ debugLog('Unsupported popup provider', provider)
428
+ throw new Error(`Unsupported provider for login with popup: ${provider.name}`)
372
429
  }
373
430
  }
374
431
 
432
+ const getLoginWithRedirect =
433
+ (auth: Auth, twoFactorRequired: boolean) =>
434
+ async (provider: BaseAuthProvider): Promise<LoginResult<User>> => {
435
+ debugLog('Starting redirect sign-in', { provider: provider.name, twoFactorRequired })
436
+
437
+ // In React Native, redirect is the same as popup since we're using native SDKs
438
+ return getLoginWithPopup(auth, twoFactorRequired)(provider)
439
+ }
440
+
375
441
  const getLoginWithToken =
376
442
  (auth: Auth, twoFactorRequired: boolean) =>
377
443
  async (provider: BaseAuthProvider, token: string): Promise<LoginResult<User>> => {
@@ -431,6 +497,7 @@ export const getFirebaseAuthAdapter = (
431
497
  },
432
498
  loginWithPassword: getLoginWithPassword(auth, twoFactorRequired),
433
499
  loginWithPopup: getLoginWithPopup(auth, twoFactorRequired),
500
+ loginWithRedirect: getLoginWithRedirect(auth, twoFactorRequired),
434
501
  loginWithToken: getLoginWithToken(auth, twoFactorRequired),
435
502
  resetPassword: (...args) => {
436
503
  return resetPassword(auth, ...args)
@@ -1,11 +1,13 @@
1
1
  import { useBackend } from '../hooks'
2
2
 
3
- import { GoogleAuthProvider } from '@chem-po/core'
3
+ import { AppleAuthProvider, GoogleAuthProvider, ThirdPartyAuthProvider } from '@chem-po/core'
4
4
  import { TextField, useBorderColor, useToast } from '@chem-po/react'
5
5
  import { LoadingButton, StandaloneInput, Txt } from '@chem-po/react-native'
6
+ import appleAuth from '@invertase/react-native-apple-authentication'
6
7
  import React, { useCallback, useState } from 'react'
7
8
  import { StyleSheet, View } from 'react-native'
8
9
  import { Divider } from 'react-native-paper'
10
+ import SvgApple from '../icons/Apple'
9
11
  import SvgGoogle from '../icons/Google'
10
12
 
11
13
  const emailField: TextField = {
@@ -20,7 +22,15 @@ const passwordField: TextField = {
20
22
  placeholder: 'Password',
21
23
  }
22
24
 
23
- const GoogleLogin = ({ provider }: { provider: GoogleAuthProvider }) => {
25
+ const thirdPartyInfo: Record<
26
+ ThirdPartyAuthProvider['name'],
27
+ { icon: React.ReactNode; name: string }
28
+ > = {
29
+ google: { icon: <SvgGoogle width={24} />, name: 'Google' },
30
+ apple: { icon: <SvgApple width={24} />, name: 'Apple' },
31
+ }
32
+
33
+ const ThirdPartyLogin = ({ provider }: { provider: GoogleAuthProvider | AppleAuthProvider }) => {
24
34
  const { auth } = useBackend()
25
35
  const { showError } = useToast()
26
36
 
@@ -30,12 +40,14 @@ const GoogleLogin = ({ provider }: { provider: GoogleAuthProvider }) => {
30
40
  })
31
41
  }, [auth, provider, showError])
32
42
 
43
+ const borderColor = useBorderColor()
44
+
33
45
  return (
34
46
  <View style={styles.row}>
35
- <LoadingButton style={styles.googleLoginButton} onPress={handleSignIn}>
47
+ <LoadingButton style={[styles.googleLoginButton, { borderColor }]} onPress={handleSignIn}>
36
48
  <View style={styles.googleLogin}>
37
- <SvgGoogle width={24} />
38
- <Txt>Log In with Google</Txt>
49
+ {thirdPartyInfo[provider.name].icon}
50
+ <Txt>Log In with {thirdPartyInfo[provider.name].name}</Txt>
39
51
  </View>
40
52
  </LoadingButton>
41
53
  </View>
@@ -88,21 +100,42 @@ const EmailPasswordLogin = () => {
88
100
  )
89
101
  }
90
102
 
91
- export const FirebaseSignIn = ({
103
+ const ThirdPartyLogins = ({
104
+ appleProvider,
92
105
  googleProvider,
93
106
  }: {
94
- googleProvider: GoogleAuthProvider | null
107
+ appleProvider?: AppleAuthProvider
108
+ googleProvider?: GoogleAuthProvider
95
109
  }) => {
96
110
  const borderColor = useBorderColor()
111
+ console.log('appleAuth.isSupported', appleAuth.isSupported)
112
+ const hasApple = appleProvider && appleAuth.isSupported
113
+ if (!googleProvider && !hasApple) return null
114
+ return (
115
+ <>
116
+ <Divider style={{ backgroundColor: borderColor, width: '100%' }} />
117
+ <View style={styles.thirdPartyLogins}>
118
+ {googleProvider ? <ThirdPartyLogin provider={googleProvider} /> : null}
119
+ {appleProvider && appleAuth.isSupported ? (
120
+ <ThirdPartyLogin provider={appleProvider} />
121
+ ) : null}
122
+ </View>
123
+ </>
124
+ )
125
+ }
126
+
127
+ export const FirebaseSignIn = ({
128
+ googleProvider,
129
+ appleProvider,
130
+ }: {
131
+ googleProvider?: GoogleAuthProvider
132
+ appleProvider?: AppleAuthProvider
133
+ }) => {
134
+ console.log('hello')
97
135
  return (
98
136
  <View style={styles.signInContent}>
99
137
  <EmailPasswordLogin />
100
- {googleProvider ? (
101
- <>
102
- <Divider style={{ backgroundColor: borderColor, width: '100%' }} />
103
- <GoogleLogin provider={googleProvider} />
104
- </>
105
- ) : null}
138
+ <ThirdPartyLogins appleProvider={appleProvider} googleProvider={googleProvider} />
106
139
  </View>
107
140
  )
108
141
  }
@@ -122,13 +155,20 @@ const styles = StyleSheet.create({
122
155
  alignItems: 'center',
123
156
  justifyContent: 'center',
124
157
  },
158
+ thirdPartyLogins: {
159
+ flexDirection: 'column',
160
+ gap: 10,
161
+ flex: 1,
162
+ width: '100%',
163
+ alignItems: 'center',
164
+ justifyContent: 'center',
165
+ },
125
166
  colorModeToggle: {
126
167
  paddingVertical: 20,
127
168
  },
128
169
  signInContent: {
129
170
  paddingVertical: 20,
130
171
  paddingHorizontal: 20,
131
- height: 300,
132
172
  gap: 12,
133
173
  justifyContent: 'center',
134
174
  alignItems: 'center',
@@ -0,0 +1,12 @@
1
+ import * as React from 'react'
2
+ import Svg, { Path, SvgProps } from 'react-native-svg'
3
+
4
+ const SvgApple = (props: SvgProps) => {
5
+ return (
6
+ <Svg width={24} height={24} viewBox="0 0 814 1000" {...props}>
7
+ <Path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z" />
8
+ </Svg>
9
+ )
10
+ }
11
+
12
+ export default SvgApple