@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 +5 -4
- package/src/adapter/auth.ts +71 -4
- package/src/components/FirebaseSignIn.tsx +54 -14
- package/src/icons/Apple.tsx +12 -0
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.
|
|
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.
|
|
47
|
-
"@chem-po/react": "0.0.
|
|
48
|
-
"@chem-po/react
|
|
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",
|
package/src/adapter/auth.ts
CHANGED
|
@@ -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',
|
|
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
|
|
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
|
-
|
|
38
|
-
<Txt>Log In with
|
|
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
|
-
|
|
103
|
+
const ThirdPartyLogins = ({
|
|
104
|
+
appleProvider,
|
|
92
105
|
googleProvider,
|
|
93
106
|
}: {
|
|
94
|
-
|
|
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
|