rn-swiftauth-sdk 1.0.1 → 1.0.3

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.
@@ -22,17 +22,19 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
22
22
  signInWithEmail,
23
23
  signInWithGoogle,
24
24
  signInWithApple,
25
- isLoading, // ✅ Using the simplified boolean we added to context
25
+ isLoading,
26
26
  error,
27
- config // ✅ We need this to check if social buttons are enabled
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; // 🛑 Stop if invalid
51
+ return; //Stop if invalid
50
52
  }
51
53
 
52
54
  // 3. Attempt Login
53
55
  try {
54
- await signInWithEmail(email, password);
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' } : {} // Highlight on error
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
- isLoading && defaultStyles.buttonDisabled,
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
- disabled={isLoading}
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 - Conditionally Rendered based on Config */}
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, // Reduced slightly to make room for validation text
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: { backgroundColor: '#a0cfff' },
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, // ✅ Use boolean loading state
27
+ isLoading,
28
28
  error,
29
- config // ✅ Use config for conditional rendering
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
- // Password Requirements Logic for Visual Hints
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
- await signUpWithEmail(email, password);
68
+ // UPDATED: New Object Syntax
69
+ await signUpWithEmail({ email, password });
72
70
  } catch (e) {
73
- // Global error handled by useAuth
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
- isLoading && defaultStyles.buttonDisabled,
177
+ // Disable style if loading OR form incomplete
178
+ (isLoading || !isFormFilled) && defaultStyles.buttonDisabled,
181
179
  userStyles?.button
182
180
  ]}
183
181
  onPress={handleSignUp}
184
- disabled={isLoading}
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 - Conditional Rendering */}
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, // Reduced for validation text space
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
- buttonDisabled: { backgroundColor: '#9ce4ae' },
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,30 @@
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
- import { mapFirebaseError } from '../errors';
9
+ import {
10
+ AuthException,
11
+ mapFirebaseError,
12
+ ConfigurationException,
13
+ AppleSignInNotSupportedException,
14
+ AppleSignInCancelledException,
15
+ GoogleSignInCancelledException,
16
+ } from '../errors';
35
17
  import { AuthContext } from './AuthContext';
36
- import { AuthConfig, AuthStatus, User, AuthError, AuthErrorCode } from '../types';
18
+ import {
19
+ AuthConfig,
20
+ AuthStatus,
21
+ User,
22
+ ProviderErrorCodes,
23
+ EmailSignInOptions,
24
+ EmailSignUpOptions
25
+ } from '../types';
26
+
27
+ const getReactNativePersistence = (FirebaseAuth as any).getReactNativePersistence;
37
28
 
38
29
  interface AuthProviderProps {
39
30
  config: AuthConfig;
@@ -43,18 +34,16 @@ interface AuthProviderProps {
43
34
  export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children }) => {
44
35
  const [user, setUser] = useState<User | null>(null);
45
36
  const [status, setStatus] = useState<AuthStatus>(AuthStatus.LOADING);
46
- 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
37
+ const [error, setError] = useState<AuthException | null>(null);
38
+ const [firebaseAuthInstance, setFirebaseAuthInstance] = useState<FirebaseAuth.Auth | null>(null);
50
39
  const [isDataLoading, setIsDataLoading] = useState(true);
51
40
 
41
+ // --- INITIALIZATION ---
52
42
  useEffect(() => {
53
43
  let app: FirebaseApp;
54
- let auth: FirebaseAuth;
44
+ let auth: FirebaseAuth.Auth;
55
45
 
56
46
  if (!getApps().length) {
57
- // 1. Initialize App
58
47
  app = initializeApp({
59
48
  apiKey: config.apiKey,
60
49
  authDomain: config.authDomain,
@@ -64,42 +53,37 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
64
53
  appId: config.appId,
65
54
  });
66
55
 
67
- // 2. Select Persistence Strategy
68
56
  const selectedPersistence = config.persistence === 'memory'
69
- ? inMemoryPersistence
57
+ ? FirebaseAuth.inMemoryPersistence
70
58
  : getReactNativePersistence(ReactNativeAsyncStorage);
71
59
 
72
- // 3. Initialize Auth
73
- auth = initializeAuth(app, {
60
+ auth = FirebaseAuth.initializeAuth(app, {
74
61
  persistence: selectedPersistence
75
62
  });
76
-
77
63
  } else {
78
64
  app = getApp();
79
- auth = getAuth(app);
65
+ auth = FirebaseAuth.getAuth(app);
80
66
  }
81
67
 
82
68
  setFirebaseAuthInstance(auth);
83
69
 
84
- // 4. Configure Google Sign-In if enabled
85
70
  if (config.enableGoogle && config.googleWebClientId) {
86
71
  try {
87
72
  GoogleSignin.configure({
88
73
  webClientId: config.googleWebClientId,
89
74
  offlineAccess: true,
90
- iosClientId: config.googleIOSClientId, // Optional
75
+ iosClientId: config.googleIOSClientId,
91
76
  });
92
- console.log('Google Sign-In configured successfully');
77
+ console.log('Google Sign-In configured successfully');
93
78
  } catch (err) {
94
- console.error('Google Sign-In configuration failed:', err);
79
+ console.error('Google Sign-In configuration failed:', err);
95
80
  }
96
81
  }
97
82
 
98
- const unsubscribe = onAuthStateChanged(auth, async (fbUser: FirebaseUser | null) => {
83
+ const unsubscribe = FirebaseAuth.onAuthStateChanged(auth, async (fbUser) => {
99
84
  try {
100
85
  if (fbUser) {
101
86
  try {
102
- // Force token refresh to ensure validity on load
103
87
  const token = await fbUser.getIdToken(true);
104
88
  setUser({
105
89
  uid: fbUser.uid,
@@ -112,10 +96,13 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
112
96
  setStatus(AuthStatus.AUTHENTICATED);
113
97
  } catch (tokenError: any) {
114
98
  console.error('Token retrieval error:', tokenError);
115
- if (tokenError.code === 'auth/user-token-expired' || tokenError.code === 'auth/null-user') {
99
+ if (tokenError.code === ProviderErrorCodes.USER_TOKEN_EXPIRED ||
100
+ tokenError.code === ProviderErrorCodes.NULL_USER) {
116
101
  setStatus(AuthStatus.TOKEN_EXPIRED);
102
+ setError(mapFirebaseError(tokenError));
117
103
  } else {
118
104
  setStatus(AuthStatus.UNAUTHENTICATED);
105
+ setError(mapFirebaseError(tokenError));
119
106
  }
120
107
  setUser(null);
121
108
  }
@@ -126,60 +113,51 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
126
113
  } catch (err) {
127
114
  console.error("Auth State Error:", err);
128
115
  setStatus(AuthStatus.UNAUTHENTICATED);
116
+ setError(mapFirebaseError(err));
129
117
  } finally {
130
- // ✅ Stop initial loading spinner once Firebase has checked storage
131
118
  setIsDataLoading(false);
132
119
  }
133
- }, (err) => {
134
- console.error("Auth State Error:", err);
135
- setStatus(AuthStatus.UNAUTHENTICATED);
136
- setIsDataLoading(false);
137
120
  });
138
121
 
139
122
  return () => unsubscribe();
140
123
  }, [config]);
141
124
 
142
- // Email/Password Sign In
143
- const signInWithEmail = async (email: string, pass: string) => {
125
+ // --- ACTIONS ---
126
+
127
+ const signInWithEmail = async ({ email, password }: EmailSignInOptions) => {
144
128
  if (!firebaseAuthInstance) return;
145
129
  try {
146
130
  setError(null);
147
131
  setStatus(AuthStatus.LOADING);
148
- await signInWithEmailAndPassword(firebaseAuthInstance, email, pass);
149
- } catch (err) {
150
- const mappedError = mapFirebaseError(err);
151
- setError(mappedError);
132
+ await FirebaseAuth.signInWithEmailAndPassword(firebaseAuthInstance, email, password);
133
+ } catch (err: any) {
134
+ const mappedException = mapFirebaseError(err);
135
+ setError(mappedException);
152
136
  setStatus(AuthStatus.UNAUTHENTICATED);
153
- throw mappedError;
137
+ throw mappedException;
154
138
  }
155
139
  };
156
140
 
157
- // Email/Password Sign Up
158
- const signUpWithEmail = async (email: string, pass: string) => {
141
+ const signUpWithEmail = async ({ email, password }: EmailSignUpOptions) => {
159
142
  if (!firebaseAuthInstance) return;
160
143
  try {
161
144
  setError(null);
162
145
  setStatus(AuthStatus.LOADING);
163
- await createUserWithEmailAndPassword(firebaseAuthInstance, email, pass);
164
- } catch (err) {
165
- const mappedError = mapFirebaseError(err);
166
- setError(mappedError);
146
+ await FirebaseAuth.createUserWithEmailAndPassword(firebaseAuthInstance, email, password);
147
+ } catch (err: any) {
148
+ const mappedException = mapFirebaseError(err);
149
+ setError(mappedException);
167
150
  setStatus(AuthStatus.UNAUTHENTICATED);
168
- throw mappedError;
151
+ throw mappedException;
169
152
  }
170
153
  };
171
154
 
172
- // PROPER GOOGLE SIGN-IN using @react-native-google-signin/google-signin
173
155
  const signInWithGoogle = async () => {
174
- if (!firebaseAuthInstance) {
175
- throw new Error('Firebase not initialized');
176
- }
177
-
156
+ if (!firebaseAuthInstance) throw new Error('Firebase not initialized');
178
157
  if (!config.enableGoogle || !config.googleWebClientId) {
179
- const configError: AuthError = {
180
- code: AuthErrorCode.CONFIG_ERROR,
181
- message: 'Google Sign-In is not enabled or configured. Please add googleWebClientId to your AuthConfig.',
182
- };
158
+ const configError = new ConfigurationException(
159
+ 'Google Auth not configured. Missing googleWebClientId.'
160
+ );
183
161
  setError(configError);
184
162
  throw configError;
185
163
  }
@@ -192,71 +170,41 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
192
170
  const userInfo = await GoogleSignin.signIn();
193
171
  const idToken = userInfo.data?.idToken;
194
172
 
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);
173
+ if (!idToken) throw new Error('No ID token received from Google Sign-In');
201
174
 
202
- console.log('✅ Google Sign-In successful');
175
+ const credential = FirebaseAuth.GoogleAuthProvider.credential(idToken);
176
+ await FirebaseAuth.signInWithCredential(firebaseAuthInstance, credential);
177
+ console.log('Google Sign-In successful');
203
178
 
204
179
  } catch (err: any) {
205
- console.error('Google Sign-In Error:', err);
206
- let mappedError: AuthError;
207
-
208
- if (err.code === 'SIGN_IN_CANCELLED') {
209
- mappedError = {
210
- code: AuthErrorCode.GOOGLE_SIGN_IN_CANCELLED,
211
- message: 'Google Sign-In was cancelled',
212
- originalError: err
213
- };
214
- // Reset status if cancelled, don't leave it loading
180
+ console.error('Google Sign-In Error:', err);
181
+
182
+ if (err.code === ProviderErrorCodes.GOOGLE_CANCELLED) {
183
+ const cancelError = new GoogleSignInCancelledException(err);
184
+ setError(cancelError);
215
185
  setStatus(AuthStatus.UNAUTHENTICATED);
216
186
  return;
217
- } else if (err.code === 'IN_PROGRESS') {
218
- mappedError = {
219
- code: AuthErrorCode.GOOGLE_SIGN_IN_IN_PROGRESS,
220
- message: 'Google Sign-In is already in progress',
221
- originalError: err
222
- };
223
- } else if (err.code === 'PLAY_SERVICES_NOT_AVAILABLE') {
224
- mappedError = {
225
- code: AuthErrorCode.GOOGLE_PLAY_SERVICES_NOT_AVAILABLE,
226
- message: 'Google Play Services are not available. Please update Google Play Services.',
227
- originalError: err
228
- };
229
- } else {
230
- mappedError = mapFirebaseError(err);
231
187
  }
232
188
 
233
- setError(mappedError);
189
+ const mappedException = mapFirebaseError(err);
190
+ setError(mappedException);
234
191
  setStatus(AuthStatus.UNAUTHENTICATED);
235
- throw mappedError;
192
+ throw mappedException;
236
193
  }
237
194
  };
238
195
 
239
- // Apple Sign-In using expo-apple-authentication
240
196
  const signInWithApple = async () => {
241
- if (!firebaseAuthInstance) {
242
- throw new Error('Firebase not initialized');
243
- }
197
+ if (!firebaseAuthInstance) throw new Error('Firebase not initialized');
244
198
 
245
199
  if (Platform.OS !== 'ios') {
246
- const platformError: AuthError = {
247
- code: AuthErrorCode.APPLE_SIGN_IN_NOT_SUPPORTED,
248
- message: 'Apple Sign-In is only available on iOS devices',
249
- };
200
+ const platformError = new AppleSignInNotSupportedException();
250
201
  setError(platformError);
251
202
  throw platformError;
252
203
  }
253
204
 
254
205
  const isAvailable = await AppleAuthentication.isAvailableAsync();
255
206
  if (!isAvailable) {
256
- const availabilityError: AuthError = {
257
- code: AuthErrorCode.APPLE_SIGN_IN_NOT_SUPPORTED,
258
- message: 'Apple Sign-In is not available on this device (requires iOS 13+)',
259
- };
207
+ const availabilityError = new AppleSignInNotSupportedException();
260
208
  setError(availabilityError);
261
209
  throw availabilityError;
262
210
  }
@@ -266,10 +214,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
266
214
  setStatus(AuthStatus.LOADING);
267
215
 
268
216
  const nonce = Math.random().toString(36).substring(2, 10);
269
- const hashedNonce = await Crypto.digestStringAsync(
270
- Crypto.CryptoDigestAlgorithm.SHA256,
271
- nonce
272
- );
217
+ const hashedNonce = await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, nonce);
273
218
 
274
219
  const appleCredential = await AppleAuthentication.signInAsync({
275
220
  requestedScopes: [
@@ -281,41 +226,34 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
281
226
 
282
227
  const { identityToken } = appleCredential;
283
228
 
284
- if (!identityToken) {
285
- throw new Error('No identity token received from Apple');
286
- }
229
+ if (!identityToken) throw new Error('No identity token received from Apple');
287
230
 
288
- const provider = new OAuthProvider('apple.com');
231
+ const provider = new FirebaseAuth.OAuthProvider('apple.com');
289
232
  const credential = provider.credential({
290
233
  idToken: identityToken,
291
234
  rawNonce: nonce,
292
235
  });
293
236
 
294
- await signInWithCredential(firebaseAuthInstance, credential);
295
- console.log('Apple Sign-In successful');
237
+ await FirebaseAuth.signInWithCredential(firebaseAuthInstance, credential);
238
+ console.log('Apple Sign-In successful');
296
239
 
297
240
  } catch (err: any) {
298
- console.error('Apple Sign-In Error:', err);
299
-
300
- if (err.code === 'ERR_REQUEST_CANCELED') {
301
- const cancelError: AuthError = {
302
- code: AuthErrorCode.APPLE_SIGN_IN_CANCELLED,
303
- message: 'Apple Sign-In was cancelled',
304
- originalError: err
305
- };
241
+ console.error('Apple Sign-In Error:', err);
242
+
243
+ if (err.code === ProviderErrorCodes.APPLE_CANCELLED) {
244
+ const cancelError = new AppleSignInCancelledException(err);
306
245
  setError(cancelError);
307
246
  setStatus(AuthStatus.UNAUTHENTICATED);
308
247
  return;
309
248
  }
310
249
 
311
- const mappedError = mapFirebaseError(err);
312
- setError(mappedError);
250
+ const mappedException = mapFirebaseError(err);
251
+ setError(mappedException);
313
252
  setStatus(AuthStatus.UNAUTHENTICATED);
314
- throw mappedError;
253
+ throw mappedException;
315
254
  }
316
255
  };
317
256
 
318
- // Sign Out
319
257
  const signOut = async () => {
320
258
  try {
321
259
  if (firebaseAuthInstance) {
@@ -328,9 +266,11 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
328
266
  console.log('Google sign-out skipped or failed:', googleSignOutError);
329
267
  }
330
268
  }
331
- console.log('Sign out successful');
269
+ console.log('Sign out successful');
332
270
  } catch (err) {
333
- console.error('Sign out error:', err);
271
+ console.error('Sign out error:', err);
272
+ const signOutError = mapFirebaseError(err);
273
+ setError(signOutError);
334
274
  setUser(null);
335
275
  setStatus(AuthStatus.UNAUTHENTICATED);
336
276
  }
@@ -341,10 +281,8 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
341
281
  const value = useMemo(() => ({
342
282
  user,
343
283
  status,
344
- // ✅ NEW: Combine internal loading with AuthStatus
345
284
  isLoading: isDataLoading || status === AuthStatus.LOADING,
346
285
  error,
347
- // ✅ NEW: Expose config for UI to read
348
286
  config,
349
287
  signInWithEmail,
350
288
  signUpWithEmail,