rn-swiftauth-sdk 1.0.1 → 1.0.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/README.md +147 -374
- package/dist/components/LoginForm.js +19 -11
- package/dist/components/SignUpForm.js +17 -16
- package/dist/core/AuthProvider.js +45 -75
- package/dist/types/auth.types.d.ts +11 -3
- package/dist/types/auth.types.js +0 -1
- package/dist/types/error.types.d.ts +10 -1
- package/dist/types/error.types.js +16 -1
- package/package.json +1 -1
- package/src/components/LoginForm.tsx +21 -12
- package/src/components/SignUpForm.tsx +20 -31
- package/src/core/AuthProvider.tsx +67 -116
- package/src/types/auth.types.ts +20 -17
- package/src/types/error.types.ts +20 -1
|
@@ -22,17 +22,19 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
22
22
|
signInWithEmail,
|
|
23
23
|
signInWithGoogle,
|
|
24
24
|
signInWithApple,
|
|
25
|
-
isLoading,
|
|
25
|
+
isLoading,
|
|
26
26
|
error,
|
|
27
|
-
config
|
|
27
|
+
config
|
|
28
28
|
} = useAuth();
|
|
29
29
|
|
|
30
30
|
const [email, setEmail] = useState('');
|
|
31
31
|
const [password, setPassword] = useState('');
|
|
32
32
|
|
|
33
|
-
// ✅ Validation Error State
|
|
34
33
|
const [validationErrors, setValidationErrors] = useState<{ email?: string; password?: string }>({});
|
|
35
34
|
|
|
35
|
+
//Check if form is filled to enable button
|
|
36
|
+
const isFormFilled = email.length > 0 && password.length > 0;
|
|
37
|
+
|
|
36
38
|
const handleLogin = async () => {
|
|
37
39
|
// 1. Reset previous errors
|
|
38
40
|
setValidationErrors({});
|
|
@@ -46,14 +48,17 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
46
48
|
email: emailErr || undefined,
|
|
47
49
|
password: passErr || undefined
|
|
48
50
|
});
|
|
49
|
-
return; //
|
|
51
|
+
return; //Stop if invalid
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
// 3. Attempt Login
|
|
53
55
|
try {
|
|
54
|
-
|
|
56
|
+
//UPDATED: Clean Object Syntax
|
|
57
|
+
await signInWithEmail({ email, password });
|
|
55
58
|
} catch (e) {
|
|
56
59
|
// Auth errors handled by global state
|
|
60
|
+
// DX: Log it for the developer (Optional but helpful for debugging)
|
|
61
|
+
console.log('Login failed:', e);
|
|
57
62
|
}
|
|
58
63
|
};
|
|
59
64
|
|
|
@@ -87,7 +92,7 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
87
92
|
style={[
|
|
88
93
|
defaultStyles.input,
|
|
89
94
|
userStyles?.input,
|
|
90
|
-
validationErrors.email ? { borderColor: 'red' } : {}
|
|
95
|
+
validationErrors.email ? { borderColor: 'red' } : {}
|
|
91
96
|
]}
|
|
92
97
|
placeholder="Email"
|
|
93
98
|
value={email}
|
|
@@ -100,7 +105,6 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
100
105
|
placeholderTextColor="#999"
|
|
101
106
|
editable={!isLoading}
|
|
102
107
|
/>
|
|
103
|
-
{/* Validation Message */}
|
|
104
108
|
{validationErrors.email && (
|
|
105
109
|
<Text style={defaultStyles.validationText}>{validationErrors.email}</Text>
|
|
106
110
|
)}
|
|
@@ -124,11 +128,13 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
124
128
|
<TouchableOpacity
|
|
125
129
|
style={[
|
|
126
130
|
defaultStyles.button,
|
|
127
|
-
|
|
131
|
+
// Disable style if loading OR form is incomplete
|
|
132
|
+
(isLoading || !isFormFilled) && defaultStyles.buttonDisabled,
|
|
128
133
|
userStyles?.button
|
|
129
134
|
]}
|
|
130
135
|
onPress={handleLogin}
|
|
131
|
-
|
|
136
|
+
// Disable interaction if loading OR form is incomplete
|
|
137
|
+
disabled={isLoading || !isFormFilled}
|
|
132
138
|
>
|
|
133
139
|
{isLoading ? (
|
|
134
140
|
<ActivityIndicator color={userStyles?.loadingIndicatorColor || "#fff"} />
|
|
@@ -139,7 +145,7 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
139
145
|
)}
|
|
140
146
|
</TouchableOpacity>
|
|
141
147
|
|
|
142
|
-
{/* OAuth Section
|
|
148
|
+
{/* OAuth Section */}
|
|
143
149
|
{(config.enableGoogle || config.enableApple) && !isLoading && (
|
|
144
150
|
<>
|
|
145
151
|
<View style={defaultStyles.dividerContainer}>
|
|
@@ -190,7 +196,7 @@ const defaultStyles = StyleSheet.create({
|
|
|
190
196
|
backgroundColor: '#f5f5f5',
|
|
191
197
|
padding: 15,
|
|
192
198
|
borderRadius: 8,
|
|
193
|
-
marginBottom: 8,
|
|
199
|
+
marginBottom: 8,
|
|
194
200
|
borderWidth: 1,
|
|
195
201
|
borderColor: '#e0e0e0',
|
|
196
202
|
fontSize: 16,
|
|
@@ -203,7 +209,10 @@ const defaultStyles = StyleSheet.create({
|
|
|
203
209
|
alignItems: 'center',
|
|
204
210
|
marginTop: 8,
|
|
205
211
|
},
|
|
206
|
-
buttonDisabled: {
|
|
212
|
+
buttonDisabled: {
|
|
213
|
+
backgroundColor: '#a0cfff',
|
|
214
|
+
opacity: 0.7
|
|
215
|
+
},
|
|
207
216
|
buttonText: { color: '#fff', fontWeight: '600', fontSize: 16 },
|
|
208
217
|
|
|
209
218
|
errorText: { color: 'red', marginBottom: 12, fontSize: 14, textAlign: 'center' },
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
StyleSheet,
|
|
8
8
|
ActivityIndicator,
|
|
9
|
-
Platform
|
|
9
|
+
Platform,
|
|
10
10
|
} from 'react-native';
|
|
11
11
|
|
|
12
12
|
import { useAuth } from '../hooks/useAuth';
|
|
@@ -24,19 +24,20 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
|
|
|
24
24
|
signUpWithEmail,
|
|
25
25
|
signInWithGoogle,
|
|
26
26
|
signInWithApple,
|
|
27
|
-
isLoading,
|
|
27
|
+
isLoading,
|
|
28
28
|
error,
|
|
29
|
-
config
|
|
29
|
+
config
|
|
30
30
|
} = useAuth();
|
|
31
31
|
|
|
32
32
|
const [email, setEmail] = useState('');
|
|
33
33
|
const [password, setPassword] = useState('');
|
|
34
34
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
35
|
-
|
|
36
|
-
// ✅ Proper Validation State
|
|
37
35
|
const [validationErrors, setValidationErrors] = useState<{ email?: string; password?: string; confirm?: string }>({});
|
|
38
36
|
|
|
39
|
-
//
|
|
37
|
+
// 1. Smart Button Logic: Check if all fields have content
|
|
38
|
+
const isFormFilled = email.length > 0 && password.length > 0 && confirmPassword.length > 0;
|
|
39
|
+
|
|
40
|
+
// Password Requirements Logic
|
|
40
41
|
const requirements = [
|
|
41
42
|
{ label: "At least 6 characters", met: password.length >= 6 },
|
|
42
43
|
{ label: "Contains a number", met: /\d/.test(password) },
|
|
@@ -44,19 +45,16 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
|
|
|
44
45
|
];
|
|
45
46
|
|
|
46
47
|
const handleSignUp = async () => {
|
|
47
|
-
// 1. Reset Errors
|
|
48
48
|
setValidationErrors({});
|
|
49
49
|
|
|
50
50
|
// 2. Validate Inputs
|
|
51
51
|
const emailErr = validateEmail(email);
|
|
52
52
|
const passErr = validatePasswordSignup(password);
|
|
53
|
-
|
|
54
53
|
let confirmErr;
|
|
55
54
|
if (password !== confirmPassword) {
|
|
56
55
|
confirmErr = "Passwords do not match.";
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
// 3. Check if any errors exist
|
|
60
58
|
if (emailErr || passErr || confirmErr) {
|
|
61
59
|
setValidationErrors({
|
|
62
60
|
email: emailErr || undefined,
|
|
@@ -66,11 +64,11 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
|
|
|
66
64
|
return;
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
// 4. Attempt Sign Up
|
|
70
67
|
try {
|
|
71
|
-
|
|
68
|
+
// ✅ UPDATED: New Object Syntax
|
|
69
|
+
await signUpWithEmail({ email, password });
|
|
72
70
|
} catch (e) {
|
|
73
|
-
|
|
71
|
+
console.error('Sign Up Failed:', e);
|
|
74
72
|
}
|
|
75
73
|
};
|
|
76
74
|
|
|
@@ -92,7 +90,6 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
|
|
|
92
90
|
|
|
93
91
|
return (
|
|
94
92
|
<View style={[defaultStyles.container, userStyles?.container]}>
|
|
95
|
-
{/* Global API Error */}
|
|
96
93
|
{error && (
|
|
97
94
|
<Text style={[defaultStyles.globalError, userStyles?.errorText]}>
|
|
98
95
|
{error.message}
|
|
@@ -177,11 +174,13 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
|
|
|
177
174
|
<TouchableOpacity
|
|
178
175
|
style={[
|
|
179
176
|
defaultStyles.button,
|
|
180
|
-
|
|
177
|
+
// Disable style if loading OR form incomplete
|
|
178
|
+
(isLoading || !isFormFilled) && defaultStyles.buttonDisabled,
|
|
181
179
|
userStyles?.button
|
|
182
180
|
]}
|
|
183
181
|
onPress={handleSignUp}
|
|
184
|
-
|
|
182
|
+
// Disable interaction if loading OR form incomplete
|
|
183
|
+
disabled={isLoading || !isFormFilled}
|
|
185
184
|
>
|
|
186
185
|
{isLoading ? (
|
|
187
186
|
<ActivityIndicator color={userStyles?.loadingIndicatorColor || "#fff"} />
|
|
@@ -192,7 +191,7 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
|
|
|
192
191
|
)}
|
|
193
192
|
</TouchableOpacity>
|
|
194
193
|
|
|
195
|
-
{/* OAuth Section
|
|
194
|
+
{/* OAuth Section */}
|
|
196
195
|
{(config.enableGoogle || config.enableApple) && !isLoading && (
|
|
197
196
|
<>
|
|
198
197
|
<View style={defaultStyles.dividerContainer}>
|
|
@@ -201,7 +200,6 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
|
|
|
201
200
|
<View style={defaultStyles.divider} />
|
|
202
201
|
</View>
|
|
203
202
|
|
|
204
|
-
{/* Google */}
|
|
205
203
|
{config.enableGoogle && (
|
|
206
204
|
<TouchableOpacity
|
|
207
205
|
style={[defaultStyles.oauthButton, defaultStyles.googleButton]}
|
|
@@ -213,7 +211,6 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
|
|
|
213
211
|
</TouchableOpacity>
|
|
214
212
|
)}
|
|
215
213
|
|
|
216
|
-
{/* Apple */}
|
|
217
214
|
{config.enableApple && Platform.OS === 'ios' && (
|
|
218
215
|
<TouchableOpacity
|
|
219
216
|
style={[defaultStyles.oauthButton, defaultStyles.appleButton]}
|
|
@@ -232,17 +229,15 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
|
|
|
232
229
|
|
|
233
230
|
const defaultStyles = StyleSheet.create({
|
|
234
231
|
container: { width: '100%', marginVertical: 10 },
|
|
235
|
-
|
|
236
232
|
input: {
|
|
237
233
|
backgroundColor: '#f5f5f5',
|
|
238
234
|
padding: 15,
|
|
239
235
|
borderRadius: 8,
|
|
240
|
-
marginBottom: 8,
|
|
236
|
+
marginBottom: 8,
|
|
241
237
|
borderWidth: 1,
|
|
242
238
|
borderColor: '#e0e0e0',
|
|
243
239
|
fontSize: 16,
|
|
244
240
|
},
|
|
245
|
-
|
|
246
241
|
button: {
|
|
247
242
|
backgroundColor: '#34C759',
|
|
248
243
|
padding: 15,
|
|
@@ -250,15 +245,13 @@ const defaultStyles = StyleSheet.create({
|
|
|
250
245
|
alignItems: 'center',
|
|
251
246
|
marginTop: 8,
|
|
252
247
|
},
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
248
|
+
buttonDisabled: {
|
|
249
|
+
backgroundColor: '#9ce4ae',
|
|
250
|
+
opacity: 0.7
|
|
251
|
+
},
|
|
256
252
|
buttonText: { color: '#fff', fontWeight: '600', fontSize: 16 },
|
|
257
|
-
|
|
258
253
|
globalError: { color: 'red', marginBottom: 12, fontSize: 14, textAlign: 'center' },
|
|
259
254
|
validationText: { color: 'red', fontSize: 12, marginBottom: 10, marginLeft: 4, marginTop: -4 },
|
|
260
|
-
|
|
261
|
-
// OAuth Styles
|
|
262
255
|
dividerContainer: {
|
|
263
256
|
flexDirection: 'row',
|
|
264
257
|
alignItems: 'center',
|
|
@@ -266,7 +259,6 @@ const defaultStyles = StyleSheet.create({
|
|
|
266
259
|
},
|
|
267
260
|
divider: { flex: 1, height: 1, backgroundColor: '#e0e0e0' },
|
|
268
261
|
dividerText: { marginHorizontal: 16, color: '#666', fontSize: 14 },
|
|
269
|
-
|
|
270
262
|
oauthButton: {
|
|
271
263
|
padding: 15,
|
|
272
264
|
borderRadius: 8,
|
|
@@ -281,11 +273,8 @@ const defaultStyles = StyleSheet.create({
|
|
|
281
273
|
borderColor: '#e0e0e0',
|
|
282
274
|
},
|
|
283
275
|
googleButtonText: { color: '#000', fontSize: 16, fontWeight: '500' },
|
|
284
|
-
|
|
285
276
|
appleButton: { backgroundColor: '#000' },
|
|
286
277
|
appleButtonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
|
|
287
|
-
|
|
288
|
-
// Password Hint Styles
|
|
289
278
|
hintContainer: { marginBottom: 15, paddingLeft: 5 },
|
|
290
279
|
hintRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 4 },
|
|
291
280
|
hintText: { color: '#666', fontSize: 12 },
|
|
@@ -1,39 +1,24 @@
|
|
|
1
1
|
import React, { useEffect, useState, ReactNode, useMemo } from 'react';
|
|
2
2
|
import { Platform } from 'react-native';
|
|
3
3
|
import { initializeApp, getApps, getApp, FirebaseApp } from 'firebase/app';
|
|
4
|
-
|
|
5
|
-
// Firebase Auth
|
|
6
|
-
import {
|
|
7
|
-
getAuth,
|
|
8
|
-
initializeAuth,
|
|
9
|
-
onAuthStateChanged,
|
|
10
|
-
inMemoryPersistence,
|
|
11
|
-
User as FirebaseUser,
|
|
12
|
-
Auth as FirebaseAuth,
|
|
13
|
-
createUserWithEmailAndPassword,
|
|
14
|
-
signInWithEmailAndPassword,
|
|
15
|
-
GoogleAuthProvider,
|
|
16
|
-
OAuthProvider,
|
|
17
|
-
signInWithCredential,
|
|
18
|
-
} from 'firebase/auth';
|
|
19
|
-
|
|
20
|
-
// The Hack: Import for getReactNativePersistence
|
|
21
|
-
import * as firebaseAuth from 'firebase/auth';
|
|
22
|
-
// @ts-ignore
|
|
23
|
-
const getReactNativePersistence = (firebaseAuth as any).getReactNativePersistence;
|
|
24
|
-
|
|
4
|
+
import * as FirebaseAuth from 'firebase/auth';
|
|
25
5
|
import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';
|
|
26
|
-
|
|
27
|
-
// PROPER GOOGLE SIGN-IN
|
|
28
6
|
import { GoogleSignin } from '@react-native-google-signin/google-signin';
|
|
29
|
-
|
|
30
|
-
// Apple Sign-In (Expo)
|
|
31
7
|
import * as AppleAuthentication from 'expo-apple-authentication';
|
|
32
8
|
import * as Crypto from 'expo-crypto';
|
|
33
|
-
|
|
34
9
|
import { mapFirebaseError } from '../errors';
|
|
35
10
|
import { AuthContext } from './AuthContext';
|
|
36
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
AuthConfig,
|
|
13
|
+
AuthStatus,
|
|
14
|
+
User,
|
|
15
|
+
AuthError,
|
|
16
|
+
AuthErrorCode,
|
|
17
|
+
ProviderErrorCodes,
|
|
18
|
+
EmailSignInOptions,
|
|
19
|
+
EmailSignUpOptions
|
|
20
|
+
} from '../types';
|
|
21
|
+
const getReactNativePersistence = (FirebaseAuth as any).getReactNativePersistence;
|
|
37
22
|
|
|
38
23
|
interface AuthProviderProps {
|
|
39
24
|
config: AuthConfig;
|
|
@@ -44,17 +29,15 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
44
29
|
const [user, setUser] = useState<User | null>(null);
|
|
45
30
|
const [status, setStatus] = useState<AuthStatus>(AuthStatus.LOADING);
|
|
46
31
|
const [error, setError] = useState<AuthError | null>(null);
|
|
47
|
-
const [firebaseAuthInstance, setFirebaseAuthInstance] = useState<FirebaseAuth | null>(null);
|
|
48
|
-
|
|
49
|
-
// ✅ NEW: Explicit loading state for initial app load vs action loading
|
|
32
|
+
const [firebaseAuthInstance, setFirebaseAuthInstance] = useState<FirebaseAuth.Auth | null>(null);
|
|
50
33
|
const [isDataLoading, setIsDataLoading] = useState(true);
|
|
51
34
|
|
|
35
|
+
// --- INITIALIZATION ---
|
|
52
36
|
useEffect(() => {
|
|
53
37
|
let app: FirebaseApp;
|
|
54
|
-
let auth: FirebaseAuth;
|
|
38
|
+
let auth: FirebaseAuth.Auth;
|
|
55
39
|
|
|
56
40
|
if (!getApps().length) {
|
|
57
|
-
// 1. Initialize App
|
|
58
41
|
app = initializeApp({
|
|
59
42
|
apiKey: config.apiKey,
|
|
60
43
|
authDomain: config.authDomain,
|
|
@@ -64,42 +47,37 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
64
47
|
appId: config.appId,
|
|
65
48
|
});
|
|
66
49
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
? inMemoryPersistence
|
|
50
|
+
const selectedPersistence = config.persistence === 'memory'
|
|
51
|
+
? FirebaseAuth.inMemoryPersistence
|
|
70
52
|
: getReactNativePersistence(ReactNativeAsyncStorage);
|
|
71
53
|
|
|
72
|
-
|
|
73
|
-
auth = initializeAuth(app, {
|
|
54
|
+
auth = FirebaseAuth.initializeAuth(app, {
|
|
74
55
|
persistence: selectedPersistence
|
|
75
56
|
});
|
|
76
|
-
|
|
77
57
|
} else {
|
|
78
58
|
app = getApp();
|
|
79
|
-
auth = getAuth(app);
|
|
59
|
+
auth = FirebaseAuth.getAuth(app);
|
|
80
60
|
}
|
|
81
61
|
|
|
82
62
|
setFirebaseAuthInstance(auth);
|
|
83
63
|
|
|
84
|
-
// 4. Configure Google Sign-In if enabled
|
|
85
64
|
if (config.enableGoogle && config.googleWebClientId) {
|
|
86
65
|
try {
|
|
87
66
|
GoogleSignin.configure({
|
|
88
67
|
webClientId: config.googleWebClientId,
|
|
89
68
|
offlineAccess: true,
|
|
90
|
-
iosClientId: config.googleIOSClientId,
|
|
69
|
+
iosClientId: config.googleIOSClientId,
|
|
91
70
|
});
|
|
92
|
-
console.log('
|
|
71
|
+
console.log('Google Sign-In configured successfully');
|
|
93
72
|
} catch (err) {
|
|
94
|
-
console.error('
|
|
73
|
+
console.error('Google Sign-In configuration failed:', err);
|
|
95
74
|
}
|
|
96
75
|
}
|
|
97
76
|
|
|
98
|
-
const unsubscribe = onAuthStateChanged(auth, async (fbUser
|
|
77
|
+
const unsubscribe = FirebaseAuth.onAuthStateChanged(auth, async (fbUser) => {
|
|
99
78
|
try {
|
|
100
79
|
if (fbUser) {
|
|
101
80
|
try {
|
|
102
|
-
// Force token refresh to ensure validity on load
|
|
103
81
|
const token = await fbUser.getIdToken(true);
|
|
104
82
|
setUser({
|
|
105
83
|
uid: fbUser.uid,
|
|
@@ -112,7 +90,8 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
112
90
|
setStatus(AuthStatus.AUTHENTICATED);
|
|
113
91
|
} catch (tokenError: any) {
|
|
114
92
|
console.error('Token retrieval error:', tokenError);
|
|
115
|
-
if (tokenError.code ===
|
|
93
|
+
if (tokenError.code === ProviderErrorCodes.USER_TOKEN_EXPIRED ||
|
|
94
|
+
tokenError.code === ProviderErrorCodes.NULL_USER) {
|
|
116
95
|
setStatus(AuthStatus.TOKEN_EXPIRED);
|
|
117
96
|
} else {
|
|
118
97
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
@@ -127,61 +106,48 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
127
106
|
console.error("Auth State Error:", err);
|
|
128
107
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
129
108
|
} finally {
|
|
130
|
-
// ✅ Stop initial loading spinner once Firebase has checked storage
|
|
131
109
|
setIsDataLoading(false);
|
|
132
110
|
}
|
|
133
|
-
}, (err) => {
|
|
134
|
-
console.error("Auth State Error:", err);
|
|
135
|
-
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
136
|
-
setIsDataLoading(false);
|
|
137
111
|
});
|
|
138
112
|
|
|
139
113
|
return () => unsubscribe();
|
|
140
114
|
}, [config]);
|
|
141
115
|
|
|
142
|
-
//
|
|
143
|
-
|
|
116
|
+
// --- ACTIONS ---
|
|
117
|
+
|
|
118
|
+
const signInWithEmail = async ({ email, password }: EmailSignInOptions) => {
|
|
144
119
|
if (!firebaseAuthInstance) return;
|
|
145
120
|
try {
|
|
146
121
|
setError(null);
|
|
147
122
|
setStatus(AuthStatus.LOADING);
|
|
148
|
-
await signInWithEmailAndPassword(firebaseAuthInstance, email,
|
|
149
|
-
} catch (err) {
|
|
150
|
-
const
|
|
151
|
-
setError(
|
|
123
|
+
await FirebaseAuth.signInWithEmailAndPassword(firebaseAuthInstance, email, password);
|
|
124
|
+
} catch (err: any) {
|
|
125
|
+
const mapped = mapFirebaseError(err);
|
|
126
|
+
setError({ ...mapped, originalError: err });
|
|
152
127
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
153
|
-
throw
|
|
128
|
+
throw err;
|
|
154
129
|
}
|
|
155
130
|
};
|
|
156
131
|
|
|
157
|
-
|
|
158
|
-
const signUpWithEmail = async (email: string, pass: string) => {
|
|
132
|
+
const signUpWithEmail = async ({ email, password }: EmailSignUpOptions) => {
|
|
159
133
|
if (!firebaseAuthInstance) return;
|
|
160
134
|
try {
|
|
161
135
|
setError(null);
|
|
162
136
|
setStatus(AuthStatus.LOADING);
|
|
163
|
-
await createUserWithEmailAndPassword(firebaseAuthInstance, email,
|
|
164
|
-
} catch (err) {
|
|
165
|
-
const
|
|
166
|
-
setError(
|
|
137
|
+
await FirebaseAuth.createUserWithEmailAndPassword(firebaseAuthInstance, email, password);
|
|
138
|
+
} catch (err: any) {
|
|
139
|
+
const mapped = mapFirebaseError(err);
|
|
140
|
+
setError({ ...mapped, originalError: err });
|
|
167
141
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
168
|
-
throw
|
|
142
|
+
throw err;
|
|
169
143
|
}
|
|
170
144
|
};
|
|
171
145
|
|
|
172
|
-
// PROPER GOOGLE SIGN-IN using @react-native-google-signin/google-signin
|
|
173
146
|
const signInWithGoogle = async () => {
|
|
174
|
-
if (!firebaseAuthInstance)
|
|
175
|
-
throw new Error('Firebase not initialized');
|
|
176
|
-
}
|
|
177
|
-
|
|
147
|
+
if (!firebaseAuthInstance) throw new Error('Firebase not initialized');
|
|
178
148
|
if (!config.enableGoogle || !config.googleWebClientId) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
message: 'Google Sign-In is not enabled or configured. Please add googleWebClientId to your AuthConfig.',
|
|
182
|
-
};
|
|
183
|
-
setError(configError);
|
|
184
|
-
throw configError;
|
|
149
|
+
setError({ code: AuthErrorCode.CONFIG_ERROR, message: 'Google Auth not configured. Missing googleWebClientId.' });
|
|
150
|
+
return;
|
|
185
151
|
}
|
|
186
152
|
|
|
187
153
|
try {
|
|
@@ -192,35 +158,31 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
192
158
|
const userInfo = await GoogleSignin.signIn();
|
|
193
159
|
const idToken = userInfo.data?.idToken;
|
|
194
160
|
|
|
195
|
-
if (!idToken)
|
|
196
|
-
throw new Error('No ID token received from Google Sign-In');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const credential = GoogleAuthProvider.credential(idToken);
|
|
200
|
-
await signInWithCredential(firebaseAuthInstance, credential);
|
|
161
|
+
if (!idToken) throw new Error('No ID token received from Google Sign-In');
|
|
201
162
|
|
|
202
|
-
|
|
163
|
+
const credential = FirebaseAuth.GoogleAuthProvider.credential(idToken);
|
|
164
|
+
await FirebaseAuth.signInWithCredential(firebaseAuthInstance, credential);
|
|
165
|
+
console.log('Google Sign-In successful');
|
|
203
166
|
|
|
204
167
|
} catch (err: any) {
|
|
205
|
-
console.error('
|
|
168
|
+
console.error('Google Sign-In Error:', err);
|
|
206
169
|
let mappedError: AuthError;
|
|
207
170
|
|
|
208
|
-
if (err.code ===
|
|
171
|
+
if (err.code === ProviderErrorCodes.GOOGLE_CANCELLED) {
|
|
209
172
|
mappedError = {
|
|
210
173
|
code: AuthErrorCode.GOOGLE_SIGN_IN_CANCELLED,
|
|
211
174
|
message: 'Google Sign-In was cancelled',
|
|
212
175
|
originalError: err
|
|
213
176
|
};
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
} else if (err.code === 'IN_PROGRESS') {
|
|
177
|
+
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
178
|
+
return;
|
|
179
|
+
} else if (err.code === ProviderErrorCodes.GOOGLE_IN_PROGRESS) {
|
|
218
180
|
mappedError = {
|
|
219
181
|
code: AuthErrorCode.GOOGLE_SIGN_IN_IN_PROGRESS,
|
|
220
182
|
message: 'Google Sign-In is already in progress',
|
|
221
183
|
originalError: err
|
|
222
184
|
};
|
|
223
|
-
} else if (err.code ===
|
|
185
|
+
} else if (err.code === ProviderErrorCodes.GOOGLE_PLAY_UNAVAILABLE) {
|
|
224
186
|
mappedError = {
|
|
225
187
|
code: AuthErrorCode.GOOGLE_PLAY_SERVICES_NOT_AVAILABLE,
|
|
226
188
|
message: 'Google Play Services are not available. Please update Google Play Services.',
|
|
@@ -230,18 +192,15 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
230
192
|
mappedError = mapFirebaseError(err);
|
|
231
193
|
}
|
|
232
194
|
|
|
233
|
-
setError(mappedError);
|
|
195
|
+
setError({ ...mappedError, originalError: err });
|
|
234
196
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
235
197
|
throw mappedError;
|
|
236
198
|
}
|
|
237
199
|
};
|
|
238
200
|
|
|
239
|
-
// Apple Sign-In using expo-apple-authentication
|
|
240
201
|
const signInWithApple = async () => {
|
|
241
|
-
if (!firebaseAuthInstance)
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
202
|
+
if (!firebaseAuthInstance) throw new Error('Firebase not initialized');
|
|
203
|
+
|
|
245
204
|
if (Platform.OS !== 'ios') {
|
|
246
205
|
const platformError: AuthError = {
|
|
247
206
|
code: AuthErrorCode.APPLE_SIGN_IN_NOT_SUPPORTED,
|
|
@@ -264,13 +223,10 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
264
223
|
try {
|
|
265
224
|
setError(null);
|
|
266
225
|
setStatus(AuthStatus.LOADING);
|
|
267
|
-
|
|
226
|
+
|
|
268
227
|
const nonce = Math.random().toString(36).substring(2, 10);
|
|
269
|
-
const hashedNonce = await Crypto.digestStringAsync(
|
|
270
|
-
|
|
271
|
-
nonce
|
|
272
|
-
);
|
|
273
|
-
|
|
228
|
+
const hashedNonce = await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, nonce);
|
|
229
|
+
|
|
274
230
|
const appleCredential = await AppleAuthentication.signInAsync({
|
|
275
231
|
requestedScopes: [
|
|
276
232
|
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
|
@@ -281,23 +237,21 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
281
237
|
|
|
282
238
|
const { identityToken } = appleCredential;
|
|
283
239
|
|
|
284
|
-
if (!identityToken)
|
|
285
|
-
throw new Error('No identity token received from Apple');
|
|
286
|
-
}
|
|
240
|
+
if (!identityToken) throw new Error('No identity token received from Apple');
|
|
287
241
|
|
|
288
|
-
const provider = new OAuthProvider('apple.com');
|
|
242
|
+
const provider = new FirebaseAuth.OAuthProvider('apple.com');
|
|
289
243
|
const credential = provider.credential({
|
|
290
244
|
idToken: identityToken,
|
|
291
245
|
rawNonce: nonce,
|
|
292
246
|
});
|
|
293
247
|
|
|
294
|
-
await signInWithCredential(firebaseAuthInstance, credential);
|
|
295
|
-
console.log('
|
|
248
|
+
await FirebaseAuth.signInWithCredential(firebaseAuthInstance, credential);
|
|
249
|
+
console.log('Apple Sign-In successful');
|
|
296
250
|
|
|
297
251
|
} catch (err: any) {
|
|
298
|
-
console.error('
|
|
252
|
+
console.error('Apple Sign-In Error:', err);
|
|
299
253
|
|
|
300
|
-
if (err.code ===
|
|
254
|
+
if (err.code === ProviderErrorCodes.APPLE_CANCELLED) {
|
|
301
255
|
const cancelError: AuthError = {
|
|
302
256
|
code: AuthErrorCode.APPLE_SIGN_IN_CANCELLED,
|
|
303
257
|
message: 'Apple Sign-In was cancelled',
|
|
@@ -309,13 +263,12 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
309
263
|
}
|
|
310
264
|
|
|
311
265
|
const mappedError = mapFirebaseError(err);
|
|
312
|
-
setError(mappedError);
|
|
266
|
+
setError({ ...mappedError, originalError: err });
|
|
313
267
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
314
268
|
throw mappedError;
|
|
315
269
|
}
|
|
316
270
|
};
|
|
317
271
|
|
|
318
|
-
// Sign Out
|
|
319
272
|
const signOut = async () => {
|
|
320
273
|
try {
|
|
321
274
|
if (firebaseAuthInstance) {
|
|
@@ -328,9 +281,9 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
328
281
|
console.log('Google sign-out skipped or failed:', googleSignOutError);
|
|
329
282
|
}
|
|
330
283
|
}
|
|
331
|
-
console.log('
|
|
284
|
+
console.log('Sign out successful');
|
|
332
285
|
} catch (err) {
|
|
333
|
-
console.error('
|
|
286
|
+
console.error('Sign out error:', err);
|
|
334
287
|
setUser(null);
|
|
335
288
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
336
289
|
}
|
|
@@ -341,10 +294,8 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
341
294
|
const value = useMemo(() => ({
|
|
342
295
|
user,
|
|
343
296
|
status,
|
|
344
|
-
// ✅ NEW: Combine internal loading with AuthStatus
|
|
345
297
|
isLoading: isDataLoading || status === AuthStatus.LOADING,
|
|
346
298
|
error,
|
|
347
|
-
// ✅ NEW: Expose config for UI to read
|
|
348
299
|
config,
|
|
349
300
|
signInWithEmail,
|
|
350
301
|
signUpWithEmail,
|