rn-swiftauth-sdk 1.0.2 → 1.0.4
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 +235 -35
- package/dist/components/LoginForm.js +74 -29
- package/dist/core/AuthProvider.js +44 -49
- package/dist/errors/errorMapper.d.ts +10 -2
- package/dist/errors/errorMapper.js +61 -99
- package/dist/errors/exceptions.d.ts +82 -0
- package/dist/errors/exceptions.js +134 -0
- package/dist/errors/index.d.ts +2 -1
- package/dist/errors/index.js +19 -15
- package/dist/types/auth.types.d.ts +3 -2
- package/dist/types/error.types.d.ts +9 -8
- package/dist/types/error.types.js +22 -15
- package/package.json +1 -1
- package/src/components/LoginForm.tsx +128 -48
- package/src/core/AuthProvider.tsx +75 -65
- package/src/errors/errorMapper.ts +97 -105
- package/src/errors/exceptions.ts +174 -0
- package/src/errors/index.ts +22 -1
- package/src/types/auth.types.ts +3 -3
- package/src/types/error.types.ts +29 -21
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
StyleSheet,
|
|
8
8
|
ActivityIndicator,
|
|
9
|
-
Platform
|
|
9
|
+
Platform,
|
|
10
|
+
Alert
|
|
10
11
|
} from 'react-native';
|
|
11
12
|
import { useAuth } from '../hooks/useAuth';
|
|
12
13
|
import { AuthScreenStyles } from '../types';
|
|
@@ -22,24 +23,49 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
22
23
|
signInWithEmail,
|
|
23
24
|
signInWithGoogle,
|
|
24
25
|
signInWithApple,
|
|
26
|
+
sendPasswordReset,
|
|
25
27
|
isLoading,
|
|
26
28
|
error,
|
|
27
|
-
config
|
|
29
|
+
config,
|
|
30
|
+
clearError
|
|
28
31
|
} = useAuth();
|
|
29
32
|
|
|
30
33
|
const [email, setEmail] = useState('');
|
|
31
34
|
const [password, setPassword] = useState('');
|
|
35
|
+
const [isResetMode, setIsResetMode] = useState(false); // ✅ 2. Mode Toggle State
|
|
32
36
|
|
|
33
37
|
const [validationErrors, setValidationErrors] = useState<{ email?: string; password?: string }>({});
|
|
34
38
|
|
|
35
|
-
//
|
|
36
|
-
const isFormFilled =
|
|
39
|
+
// ✅ 3. Dynamic check: If resetting, we only need Email. If logging in, we need both.
|
|
40
|
+
const isFormFilled = isResetMode
|
|
41
|
+
? email.length > 0
|
|
42
|
+
: (email.length > 0 && password.length > 0);
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
// ✅ 4. New Handler for Password Reset
|
|
45
|
+
const handleResetPassword = async () => {
|
|
40
46
|
setValidationErrors({});
|
|
47
|
+
const emailErr = validateEmail(email);
|
|
48
|
+
if (emailErr) {
|
|
49
|
+
setValidationErrors({ email: emailErr });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await sendPasswordReset(email);
|
|
55
|
+
Alert.alert(
|
|
56
|
+
"Check your email",
|
|
57
|
+
"If an account exists with this email, a password reset link has been sent."
|
|
58
|
+
);
|
|
59
|
+
setIsResetMode(false); // Go back to login screen
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.log('Reset failed:', e);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
41
64
|
|
|
42
|
-
|
|
65
|
+
const handleLogin = async () => {
|
|
66
|
+
setValidationErrors({});
|
|
67
|
+
|
|
68
|
+
// Validate Inputs
|
|
43
69
|
const emailErr = validateEmail(email);
|
|
44
70
|
const passErr = validatePasswordLogin(password);
|
|
45
71
|
|
|
@@ -48,16 +74,12 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
48
74
|
email: emailErr || undefined,
|
|
49
75
|
password: passErr || undefined
|
|
50
76
|
});
|
|
51
|
-
return;
|
|
77
|
+
return;
|
|
52
78
|
}
|
|
53
79
|
|
|
54
|
-
// 3. Attempt Login
|
|
55
80
|
try {
|
|
56
|
-
//UPDATED: Clean Object Syntax
|
|
57
81
|
await signInWithEmail({ email, password });
|
|
58
82
|
} catch (e) {
|
|
59
|
-
// Auth errors handled by global state
|
|
60
|
-
// DX: Log it for the developer (Optional but helpful for debugging)
|
|
61
83
|
console.log('Login failed:', e);
|
|
62
84
|
}
|
|
63
85
|
};
|
|
@@ -78,16 +100,30 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
78
100
|
}
|
|
79
101
|
};
|
|
80
102
|
|
|
103
|
+
|
|
104
|
+
const toggleResetMode = () => {
|
|
105
|
+
setIsResetMode(!isResetMode);
|
|
106
|
+
clearError();
|
|
107
|
+
setValidationErrors({});
|
|
108
|
+
|
|
109
|
+
setPassword('');
|
|
110
|
+
};
|
|
111
|
+
|
|
81
112
|
return (
|
|
82
113
|
<View style={[defaultStyles.container, userStyles?.container]}>
|
|
83
|
-
|
|
114
|
+
|
|
84
115
|
{error && (
|
|
85
116
|
<Text style={[defaultStyles.errorText, userStyles?.errorText]}>
|
|
86
117
|
{error.message}
|
|
87
118
|
</Text>
|
|
88
119
|
)}
|
|
89
120
|
|
|
90
|
-
|
|
121
|
+
|
|
122
|
+
{isResetMode && (
|
|
123
|
+
<Text style={defaultStyles.modeTitle}>Reset Password</Text>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
|
|
91
127
|
<TextInput
|
|
92
128
|
style={[
|
|
93
129
|
defaultStyles.input,
|
|
@@ -109,44 +145,69 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
109
145
|
<Text style={defaultStyles.validationText}>{validationErrors.email}</Text>
|
|
110
146
|
)}
|
|
111
147
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
148
|
+
|
|
149
|
+
{!isResetMode && (
|
|
150
|
+
<>
|
|
151
|
+
<PasswordInput
|
|
152
|
+
styles={userStyles}
|
|
153
|
+
placeholder="Password"
|
|
154
|
+
value={password}
|
|
155
|
+
onChangeText={(text) => {
|
|
156
|
+
setPassword(text);
|
|
157
|
+
if (validationErrors.password) setValidationErrors({...validationErrors, password: undefined});
|
|
158
|
+
}}
|
|
159
|
+
editable={!isLoading}
|
|
160
|
+
/>
|
|
161
|
+
{validationErrors.password && (
|
|
162
|
+
<Text style={defaultStyles.validationText}>{validationErrors.password}</Text>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
<TouchableOpacity
|
|
167
|
+
style={defaultStyles.forgotPasswordContainer}
|
|
168
|
+
onPress={toggleResetMode}
|
|
169
|
+
disabled={isLoading}
|
|
170
|
+
>
|
|
171
|
+
<Text style={[defaultStyles.forgotPasswordText, userStyles?.linkText]}>
|
|
172
|
+
Forgot Password?
|
|
173
|
+
</Text>
|
|
174
|
+
</TouchableOpacity>
|
|
175
|
+
</>
|
|
125
176
|
)}
|
|
126
177
|
|
|
127
|
-
|
|
178
|
+
|
|
128
179
|
<TouchableOpacity
|
|
129
180
|
style={[
|
|
130
181
|
defaultStyles.button,
|
|
131
|
-
// Disable style if loading OR form is incomplete
|
|
132
182
|
(isLoading || !isFormFilled) && defaultStyles.buttonDisabled,
|
|
133
183
|
userStyles?.button
|
|
134
184
|
]}
|
|
135
|
-
|
|
136
|
-
|
|
185
|
+
|
|
186
|
+
onPress={isResetMode ? handleResetPassword : handleLogin}
|
|
137
187
|
disabled={isLoading || !isFormFilled}
|
|
138
188
|
>
|
|
139
189
|
{isLoading ? (
|
|
140
190
|
<ActivityIndicator color={userStyles?.loadingIndicatorColor || "#fff"} />
|
|
141
191
|
) : (
|
|
142
192
|
<Text style={[defaultStyles.buttonText, userStyles?.buttonText]}>
|
|
143
|
-
Sign In
|
|
193
|
+
{isResetMode ? "Send Reset Link" : "Sign In"}
|
|
144
194
|
</Text>
|
|
145
195
|
)}
|
|
146
196
|
</TouchableOpacity>
|
|
147
197
|
|
|
148
|
-
|
|
149
|
-
{
|
|
198
|
+
|
|
199
|
+
{isResetMode && (
|
|
200
|
+
<TouchableOpacity
|
|
201
|
+
style={defaultStyles.cancelButton}
|
|
202
|
+
onPress={toggleResetMode}
|
|
203
|
+
disabled={isLoading}
|
|
204
|
+
>
|
|
205
|
+
<Text style={defaultStyles.cancelButtonText}>Back to Sign In</Text>
|
|
206
|
+
</TouchableOpacity>
|
|
207
|
+
)}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
{!isResetMode && (config.enableGoogle || config.enableApple) && !isLoading && (
|
|
150
211
|
<>
|
|
151
212
|
<View style={defaultStyles.dividerContainer}>
|
|
152
213
|
<View style={defaultStyles.divider} />
|
|
@@ -157,30 +218,20 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
|
|
|
157
218
|
{/* Google Button */}
|
|
158
219
|
{config.enableGoogle && (
|
|
159
220
|
<TouchableOpacity
|
|
160
|
-
style={[
|
|
161
|
-
defaultStyles.oauthButton,
|
|
162
|
-
defaultStyles.googleButton,
|
|
163
|
-
]}
|
|
221
|
+
style={[defaultStyles.oauthButton, defaultStyles.googleButton]}
|
|
164
222
|
onPress={handleGoogleSignIn}
|
|
165
223
|
>
|
|
166
|
-
<Text style={defaultStyles.googleButtonText}>
|
|
167
|
-
Continue with Google
|
|
168
|
-
</Text>
|
|
224
|
+
<Text style={defaultStyles.googleButtonText}>Continue with Google</Text>
|
|
169
225
|
</TouchableOpacity>
|
|
170
226
|
)}
|
|
171
227
|
|
|
172
228
|
{/* Apple Button (iOS Only) */}
|
|
173
229
|
{config.enableApple && Platform.OS === 'ios' && (
|
|
174
230
|
<TouchableOpacity
|
|
175
|
-
style={[
|
|
176
|
-
defaultStyles.oauthButton,
|
|
177
|
-
defaultStyles.appleButton,
|
|
178
|
-
]}
|
|
231
|
+
style={[defaultStyles.oauthButton, defaultStyles.appleButton]}
|
|
179
232
|
onPress={handleAppleSignIn}
|
|
180
233
|
>
|
|
181
|
-
<Text style={defaultStyles.appleButtonText}>
|
|
182
|
-
Continue with Apple
|
|
183
|
-
</Text>
|
|
234
|
+
<Text style={defaultStyles.appleButtonText}>Continue with Apple</Text>
|
|
184
235
|
</TouchableOpacity>
|
|
185
236
|
)}
|
|
186
237
|
</>
|
|
@@ -252,4 +303,33 @@ const defaultStyles = StyleSheet.create({
|
|
|
252
303
|
fontSize: 16,
|
|
253
304
|
fontWeight: '600',
|
|
254
305
|
},
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
forgotPasswordContainer: {
|
|
309
|
+
alignSelf: 'flex-end',
|
|
310
|
+
marginBottom: 12,
|
|
311
|
+
padding: 4,
|
|
312
|
+
},
|
|
313
|
+
forgotPasswordText: {
|
|
314
|
+
color: '#007AFF',
|
|
315
|
+
fontSize: 14,
|
|
316
|
+
fontWeight: '500',
|
|
317
|
+
},
|
|
318
|
+
modeTitle: {
|
|
319
|
+
fontSize: 18,
|
|
320
|
+
fontWeight: 'bold',
|
|
321
|
+
marginBottom: 16,
|
|
322
|
+
color: '#333',
|
|
323
|
+
textAlign: 'center',
|
|
324
|
+
},
|
|
325
|
+
cancelButton: {
|
|
326
|
+
marginTop: 16,
|
|
327
|
+
alignItems: 'center',
|
|
328
|
+
padding: 8,
|
|
329
|
+
},
|
|
330
|
+
cancelButtonText: {
|
|
331
|
+
color: '#666',
|
|
332
|
+
fontSize: 14,
|
|
333
|
+
fontWeight: '500',
|
|
334
|
+
},
|
|
255
335
|
});
|
|
@@ -2,22 +2,30 @@ 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
4
|
import * as FirebaseAuth from 'firebase/auth';
|
|
5
|
+
|
|
6
|
+
import { sendPasswordResetEmail } from 'firebase/auth';
|
|
5
7
|
import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';
|
|
6
8
|
import { GoogleSignin } from '@react-native-google-signin/google-signin';
|
|
7
9
|
import * as AppleAuthentication from 'expo-apple-authentication';
|
|
8
10
|
import * as Crypto from 'expo-crypto';
|
|
9
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
AuthException,
|
|
13
|
+
mapFirebaseError,
|
|
14
|
+
ConfigurationException,
|
|
15
|
+
AppleSignInNotSupportedException,
|
|
16
|
+
AppleSignInCancelledException,
|
|
17
|
+
GoogleSignInCancelledException,
|
|
18
|
+
} from '../errors';
|
|
10
19
|
import { AuthContext } from './AuthContext';
|
|
11
|
-
import {
|
|
12
|
-
AuthConfig,
|
|
13
|
-
AuthStatus,
|
|
14
|
-
User,
|
|
15
|
-
|
|
16
|
-
AuthErrorCode,
|
|
17
|
-
ProviderErrorCodes,
|
|
20
|
+
import {
|
|
21
|
+
AuthConfig,
|
|
22
|
+
AuthStatus,
|
|
23
|
+
User,
|
|
24
|
+
ProviderErrorCodes,
|
|
18
25
|
EmailSignInOptions,
|
|
19
|
-
EmailSignUpOptions
|
|
26
|
+
EmailSignUpOptions
|
|
20
27
|
} from '../types';
|
|
28
|
+
|
|
21
29
|
const getReactNativePersistence = (FirebaseAuth as any).getReactNativePersistence;
|
|
22
30
|
|
|
23
31
|
interface AuthProviderProps {
|
|
@@ -28,7 +36,7 @@ interface AuthProviderProps {
|
|
|
28
36
|
export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children }) => {
|
|
29
37
|
const [user, setUser] = useState<User | null>(null);
|
|
30
38
|
const [status, setStatus] = useState<AuthStatus>(AuthStatus.LOADING);
|
|
31
|
-
const [error, setError] = useState<
|
|
39
|
+
const [error, setError] = useState<AuthException | null>(null);
|
|
32
40
|
const [firebaseAuthInstance, setFirebaseAuthInstance] = useState<FirebaseAuth.Auth | null>(null);
|
|
33
41
|
const [isDataLoading, setIsDataLoading] = useState(true);
|
|
34
42
|
|
|
@@ -47,8 +55,8 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
47
55
|
appId: config.appId,
|
|
48
56
|
});
|
|
49
57
|
|
|
50
|
-
const selectedPersistence = config.persistence === 'memory'
|
|
51
|
-
? FirebaseAuth.inMemoryPersistence
|
|
58
|
+
const selectedPersistence = config.persistence === 'memory'
|
|
59
|
+
? FirebaseAuth.inMemoryPersistence
|
|
52
60
|
: getReactNativePersistence(ReactNativeAsyncStorage);
|
|
53
61
|
|
|
54
62
|
auth = FirebaseAuth.initializeAuth(app, {
|
|
@@ -66,7 +74,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
66
74
|
GoogleSignin.configure({
|
|
67
75
|
webClientId: config.googleWebClientId,
|
|
68
76
|
offlineAccess: true,
|
|
69
|
-
iosClientId: config.googleIOSClientId,
|
|
77
|
+
iosClientId: config.googleIOSClientId,
|
|
70
78
|
});
|
|
71
79
|
console.log('Google Sign-In configured successfully');
|
|
72
80
|
} catch (err) {
|
|
@@ -90,11 +98,13 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
90
98
|
setStatus(AuthStatus.AUTHENTICATED);
|
|
91
99
|
} catch (tokenError: any) {
|
|
92
100
|
console.error('Token retrieval error:', tokenError);
|
|
93
|
-
if (tokenError.code === ProviderErrorCodes.USER_TOKEN_EXPIRED ||
|
|
101
|
+
if (tokenError.code === ProviderErrorCodes.USER_TOKEN_EXPIRED ||
|
|
94
102
|
tokenError.code === ProviderErrorCodes.NULL_USER) {
|
|
95
103
|
setStatus(AuthStatus.TOKEN_EXPIRED);
|
|
104
|
+
setError(mapFirebaseError(tokenError));
|
|
96
105
|
} else {
|
|
97
106
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
107
|
+
setError(mapFirebaseError(tokenError));
|
|
98
108
|
}
|
|
99
109
|
setUser(null);
|
|
100
110
|
}
|
|
@@ -105,6 +115,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
105
115
|
} catch (err) {
|
|
106
116
|
console.error("Auth State Error:", err);
|
|
107
117
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
118
|
+
setError(mapFirebaseError(err));
|
|
108
119
|
} finally {
|
|
109
120
|
setIsDataLoading(false);
|
|
110
121
|
}
|
|
@@ -122,10 +133,10 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
122
133
|
setStatus(AuthStatus.LOADING);
|
|
123
134
|
await FirebaseAuth.signInWithEmailAndPassword(firebaseAuthInstance, email, password);
|
|
124
135
|
} catch (err: any) {
|
|
125
|
-
const
|
|
126
|
-
setError(
|
|
136
|
+
const mappedException = mapFirebaseError(err);
|
|
137
|
+
setError(mappedException);
|
|
127
138
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
128
|
-
throw
|
|
139
|
+
throw mappedException;
|
|
129
140
|
}
|
|
130
141
|
};
|
|
131
142
|
|
|
@@ -136,18 +147,41 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
136
147
|
setStatus(AuthStatus.LOADING);
|
|
137
148
|
await FirebaseAuth.createUserWithEmailAndPassword(firebaseAuthInstance, email, password);
|
|
138
149
|
} catch (err: any) {
|
|
139
|
-
const
|
|
140
|
-
setError(
|
|
150
|
+
const mappedException = mapFirebaseError(err);
|
|
151
|
+
setError(mappedException);
|
|
152
|
+
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
153
|
+
throw mappedException;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
const sendPasswordReset = async (email: string) => {
|
|
159
|
+
if (!firebaseAuthInstance) return;
|
|
160
|
+
try {
|
|
161
|
+
setError(null);
|
|
162
|
+
setStatus(AuthStatus.LOADING);
|
|
163
|
+
|
|
164
|
+
await sendPasswordResetEmail(firebaseAuthInstance, email);
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
168
|
+
console.log(`Password reset email sent to ${email}`);
|
|
169
|
+
} catch (err: any) {
|
|
170
|
+
const mappedException = mapFirebaseError(err);
|
|
171
|
+
setError(mappedException);
|
|
141
172
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
142
|
-
throw
|
|
173
|
+
throw mappedException;
|
|
143
174
|
}
|
|
144
175
|
};
|
|
145
176
|
|
|
146
177
|
const signInWithGoogle = async () => {
|
|
147
178
|
if (!firebaseAuthInstance) throw new Error('Firebase not initialized');
|
|
148
179
|
if (!config.enableGoogle || !config.googleWebClientId) {
|
|
149
|
-
|
|
150
|
-
|
|
180
|
+
const configError = new ConfigurationException(
|
|
181
|
+
'Google Auth not configured. Missing googleWebClientId.'
|
|
182
|
+
);
|
|
183
|
+
setError(configError);
|
|
184
|
+
throw configError;
|
|
151
185
|
}
|
|
152
186
|
|
|
153
187
|
try {
|
|
@@ -166,56 +200,33 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
166
200
|
|
|
167
201
|
} catch (err: any) {
|
|
168
202
|
console.error('Google Sign-In Error:', err);
|
|
169
|
-
let mappedError: AuthError;
|
|
170
203
|
|
|
171
204
|
if (err.code === ProviderErrorCodes.GOOGLE_CANCELLED) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
};
|
|
177
|
-
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
178
|
-
return;
|
|
179
|
-
} else if (err.code === ProviderErrorCodes.GOOGLE_IN_PROGRESS) {
|
|
180
|
-
mappedError = {
|
|
181
|
-
code: AuthErrorCode.GOOGLE_SIGN_IN_IN_PROGRESS,
|
|
182
|
-
message: 'Google Sign-In is already in progress',
|
|
183
|
-
originalError: err
|
|
184
|
-
};
|
|
185
|
-
} else if (err.code === ProviderErrorCodes.GOOGLE_PLAY_UNAVAILABLE) {
|
|
186
|
-
mappedError = {
|
|
187
|
-
code: AuthErrorCode.GOOGLE_PLAY_SERVICES_NOT_AVAILABLE,
|
|
188
|
-
message: 'Google Play Services are not available. Please update Google Play Services.',
|
|
189
|
-
originalError: err
|
|
190
|
-
};
|
|
191
|
-
} else {
|
|
192
|
-
mappedError = mapFirebaseError(err);
|
|
205
|
+
const cancelError = new GoogleSignInCancelledException(err);
|
|
206
|
+
setError(cancelError);
|
|
207
|
+
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
208
|
+
return;
|
|
193
209
|
}
|
|
194
210
|
|
|
195
|
-
|
|
211
|
+
const mappedException = mapFirebaseError(err);
|
|
212
|
+
setError(mappedException);
|
|
196
213
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
197
|
-
throw
|
|
214
|
+
throw mappedException;
|
|
198
215
|
}
|
|
199
216
|
};
|
|
200
217
|
|
|
201
218
|
const signInWithApple = async () => {
|
|
202
219
|
if (!firebaseAuthInstance) throw new Error('Firebase not initialized');
|
|
203
|
-
|
|
220
|
+
|
|
204
221
|
if (Platform.OS !== 'ios') {
|
|
205
|
-
const platformError
|
|
206
|
-
code: AuthErrorCode.APPLE_SIGN_IN_NOT_SUPPORTED,
|
|
207
|
-
message: 'Apple Sign-In is only available on iOS devices',
|
|
208
|
-
};
|
|
222
|
+
const platformError = new AppleSignInNotSupportedException();
|
|
209
223
|
setError(platformError);
|
|
210
224
|
throw platformError;
|
|
211
225
|
}
|
|
212
226
|
|
|
213
227
|
const isAvailable = await AppleAuthentication.isAvailableAsync();
|
|
214
228
|
if (!isAvailable) {
|
|
215
|
-
const availabilityError
|
|
216
|
-
code: AuthErrorCode.APPLE_SIGN_IN_NOT_SUPPORTED,
|
|
217
|
-
message: 'Apple Sign-In is not available on this device (requires iOS 13+)',
|
|
218
|
-
};
|
|
229
|
+
const availabilityError = new AppleSignInNotSupportedException();
|
|
219
230
|
setError(availabilityError);
|
|
220
231
|
throw availabilityError;
|
|
221
232
|
}
|
|
@@ -223,10 +234,10 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
223
234
|
try {
|
|
224
235
|
setError(null);
|
|
225
236
|
setStatus(AuthStatus.LOADING);
|
|
226
|
-
|
|
237
|
+
|
|
227
238
|
const nonce = Math.random().toString(36).substring(2, 10);
|
|
228
239
|
const hashedNonce = await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, nonce);
|
|
229
|
-
|
|
240
|
+
|
|
230
241
|
const appleCredential = await AppleAuthentication.signInAsync({
|
|
231
242
|
requestedScopes: [
|
|
232
243
|
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
|
@@ -252,20 +263,16 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
252
263
|
console.error('Apple Sign-In Error:', err);
|
|
253
264
|
|
|
254
265
|
if (err.code === ProviderErrorCodes.APPLE_CANCELLED) {
|
|
255
|
-
const cancelError
|
|
256
|
-
code: AuthErrorCode.APPLE_SIGN_IN_CANCELLED,
|
|
257
|
-
message: 'Apple Sign-In was cancelled',
|
|
258
|
-
originalError: err
|
|
259
|
-
};
|
|
266
|
+
const cancelError = new AppleSignInCancelledException(err);
|
|
260
267
|
setError(cancelError);
|
|
261
268
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
262
269
|
return;
|
|
263
270
|
}
|
|
264
271
|
|
|
265
|
-
const
|
|
266
|
-
setError(
|
|
272
|
+
const mappedException = mapFirebaseError(err);
|
|
273
|
+
setError(mappedException);
|
|
267
274
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
268
|
-
throw
|
|
275
|
+
throw mappedException;
|
|
269
276
|
}
|
|
270
277
|
};
|
|
271
278
|
|
|
@@ -284,6 +291,8 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
284
291
|
console.log('Sign out successful');
|
|
285
292
|
} catch (err) {
|
|
286
293
|
console.error('Sign out error:', err);
|
|
294
|
+
const signOutError = mapFirebaseError(err);
|
|
295
|
+
setError(signOutError);
|
|
287
296
|
setUser(null);
|
|
288
297
|
setStatus(AuthStatus.UNAUTHENTICATED);
|
|
289
298
|
}
|
|
@@ -299,6 +308,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
|
|
|
299
308
|
config,
|
|
300
309
|
signInWithEmail,
|
|
301
310
|
signUpWithEmail,
|
|
311
|
+
sendPasswordReset,
|
|
302
312
|
signInWithGoogle,
|
|
303
313
|
signInWithApple,
|
|
304
314
|
signOut,
|