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 CHANGED
@@ -231,69 +231,269 @@ const {
231
231
  buttonText: { color: '#000000', fontWeight: 'bold' }
232
232
  }}
233
233
  />
234
+
235
+
236
+
237
+ ---
238
+
239
+ ## 🔐 Password Management
240
+
241
+ The **SwiftAuth SDK** includes built-in support for **password recovery flows** using **Firebase’s email-based password reset mechanism**.
242
+
243
+ ---
244
+
245
+ ### ✉️ Sending a Reset Email
246
+
247
+ The `sendPasswordReset(email)` function sends a password reset link to the user’s email address.
248
+
249
+ #### Method Signature
250
+
251
+ ```ts
252
+ sendPasswordReset: (email: string) => Promise<void>
234
253
  ```
235
254
 
236
255
  ---
237
256
 
238
- ## Error Handling
257
+ ### 📌 Usage Example (React Native)
239
258
 
240
- SwiftAuth maps Firebase errors to user-friendly messages:
259
+ ```ts
260
+ import React, { useState } from 'react';
261
+ import { View, TextInput, Button, Alert, Text } from 'react-native';
262
+ import { useAuth } from 'rn-swiftauth-sdk';
241
263
 
242
- | Error Code | User Message |
243
- |------------|--------------|
244
- | `auth/invalid-credentials` | "Invalid email or password." |
245
- | `auth/user-not-found` | "Invalid email or password." |
246
- | `auth/email-already-in-use` | "This email is already registered." |
247
- | `auth/weak-password` | "Password is too weak." |
248
- | `auth/network-request-failed` | "Network error. Please check your connection." |
264
+ export const ForgotPasswordScreen = () => {
265
+ const [email, setEmail] = useState('');
266
+ const { sendPasswordReset, isLoading, error } = useAuth();
249
267
 
250
- ### Basic Usage
268
+ const handleReset = async () => {
269
+ if (!email) {
270
+ Alert.alert("Error", "Please enter your email address.");
271
+ return;
272
+ }
273
+
274
+ try {
275
+ await sendPasswordReset(email);
276
+ Alert.alert("Success", "Password reset link sent! Check your email.");
277
+ } catch (err) {
278
+ // Errors are automatically handled by the global `error` state,
279
+ // but can also be caught locally for custom logic.
280
+ console.error("Reset failed:", err);
281
+ }
282
+ };
283
+
284
+ return (
285
+ <View>
286
+ <TextInput
287
+ placeholder="Enter your email"
288
+ value={email}
289
+ onChangeText={setEmail}
290
+ autoCapitalize="none"
291
+ keyboardType="email-address"
292
+ />
293
+
294
+ <Button
295
+ title={isLoading ? "Sending..." : "Reset Password"}
296
+ onPress={handleReset}
297
+ disabled={isLoading}
298
+ />
299
+
300
+ {error && <Text style={{ color: 'red' }}>{error.message}</Text>}
301
+ </View>
302
+ );
303
+ };
304
+ ```
305
+
306
+ ---
307
+
308
+ ### ⚠️ Error Handling
309
+
310
+ The error handling system includes **specific exceptions** for password reset flows.
311
+
312
+ | Exception Class | Error Code | User Message |
313
+ | ----------------------- | --------------------- | ------------------------------------------------------------------- |
314
+ | `UserNotFoundException` | `auth/user-not-found` | "No account found with this email. Please check the email address." |
315
+ | `InvalidEmailException` | `auth/invalid-email` | "The email address is badly formatted." |
316
+
317
+ Errors are exposed through the global `error` state returned by `useAuth()` and can also be handled locally using `try/catch`.
318
+
319
+ ---
320
+
321
+ ### 🎨 Integration with Custom UI
322
+
323
+ You can integrate password recovery into **custom login forms** by toggling a “Forgot Password” mode using the headless hook.
324
+
325
+ ```ts
326
+ const { sendPasswordReset } = useAuth();
327
+
328
+ const onForgotPasswordPress = async (email: string) => {
329
+ await sendPasswordReset(email);
330
+ // Show success feedback to the user
331
+ };
332
+ ```
333
+
334
+ This allows you to fully control your UI while leveraging SwiftAuth’s secure password reset flow.
335
+
336
+ ---
337
+
338
+ If you want, I can also:
339
+
340
+ * Add **UX flow diagrams**
341
+ * Add **Firebase setup prerequisites**
342
+ * Document **rate limits & edge cases**
343
+ * Align tone with the rest of your SDK README
344
+
345
+ Just tell me 👍
251
346
 
252
- ```typescript
253
- const { error } = useAuth();
254
347
 
255
- if (error) {
256
- return <Text style={{ color: 'red' }}>{error.message}</Text>;
257
- }
258
348
  ```
259
349
 
260
- ### Advanced: Raw Firebase Errors
350
+ ---
261
351
 
262
- For custom UI implementations, access raw Firebase errors:
352
+ ## Error Handling
263
353
 
264
- **Method 1: Try/Catch Block**
354
+ SwiftAuth provides a comprehensive error handling system with custom exceptions that map Firebase errors to user-friendly messages.
355
+
356
+ ### Custom Exception Classes
357
+
358
+ All errors extend the base `AuthException` class and include:
359
+ - `code`: Machine-readable error code
360
+ - `message`: User-friendly error message
361
+ - `timestamp`: When the error occurred
362
+ - `originalError`: The underlying Firebase error (optional)
363
+ - `toJSON()`: Serialize for logging/debugging
364
+
365
+ ### Supported Exceptions
366
+
367
+ | Exception Class | Error Code | User Message |
368
+ |-----------------|------------|--------------|
369
+ | `InvalidCredentialsException` | `auth/invalid-credentials` | "Invalid email or password. Please check your credentials and try again." |
370
+ | `UserNotFoundException` | `auth/user-not-found` | "No account found with this email. Please sign up first." |
371
+ | `EmailAlreadyInUseException` | `auth/email-already-in-use` | "This email is already registered. Please sign in or use a different email." |
372
+ | `WeakPasswordException` | `auth/weak-password` | "Password is too weak. Please use at least 6 characters with a mix of letters and numbers." |
373
+ | `TokenExpiredException` | `auth/token-expired` | "Your session has expired. Please sign in again." |
374
+ | `NetworkException` | `auth/network-error` | "Network error. Please check your internet connection and try again." |
375
+ | `GoogleSignInCancelledException` | `auth/google-sign-in-cancelled` | "Google Sign-In was cancelled." |
376
+ | `AppleSignInCancelledException` | `auth/apple-sign-in-cancelled` | "Apple Sign-In was cancelled." |
377
+ | `AppleSignInNotSupportedException` | `auth/apple-sign-in-not-supported` | "Apple Sign-In is only available on iOS 13+ devices." |
378
+ | `GooglePlayServicesUnavailableException` | `auth/google-play-services-unavailable` | "Google Play Services are not available. Please update Google Play Services." |
379
+ | `ConfigurationException` | `auth/configuration-error` | Custom message based on configuration issue |
380
+ | `UnknownAuthException` | `auth/unknown` | "An unexpected error occurred." |
381
+
382
+ ### Basic Error Display
265
383
  ```typescript
266
- const { signInWithEmail } = useAuth();
384
+ import { useAuth } from 'rn-swiftauth-sdk';
267
385
 
268
- const handleLogin = async () => {
269
- try {
270
- await signInWithEmail({ email, password });
271
- } catch (rawError: any) {
272
- if (rawError.code === 'auth/requires-recent-login') {
273
- showReauthModal();
274
- } else if (rawError.code === 'auth/quota-exceeded') {
275
- Alert.alert("System Overload", "Please try again later.");
386
+ const LoginScreen = () => {
387
+ const { error, clearError } = useAuth();
388
+
389
+ return (
390
+ <View>
391
+ {error && (
392
+ <View style={styles.errorContainer}>
393
+ <Text style={styles.errorText}>{error.message}</Text>
394
+ <Button title="Dismiss" onPress={clearError} />
395
+ </View>
396
+ )}
397
+ </View>
398
+ );
399
+ };
400
+ ```
401
+
402
+ ### Handling Specific Exception Types
403
+ ```typescript
404
+ import {
405
+ useAuth,
406
+ InvalidCredentialsException,
407
+ EmailAlreadyInUseException,
408
+ NetworkException
409
+ } from 'rn-swiftauth-sdk';
410
+
411
+ const SignUpScreen = () => {
412
+ const { signUpWithEmail } = useAuth();
413
+
414
+ const handleSignUp = async () => {
415
+ try {
416
+ await signUpWithEmail({ email, password });
417
+ } catch (error) {
418
+ if (error instanceof EmailAlreadyInUseException) {
419
+ Alert.alert(
420
+ "Account Exists",
421
+ "Would you like to sign in instead?",
422
+ [
423
+ { text: "Cancel", style: "cancel" },
424
+ { text: "Sign In", onPress: () => navigation.navigate('SignIn') }
425
+ ]
426
+ );
427
+ } else if (error instanceof NetworkException) {
428
+ Alert.alert("Connection Issue", "Please check your internet and try again.");
429
+ } else if (error instanceof InvalidCredentialsException) {
430
+ Alert.alert("Invalid Input", "Please check your email and password.");
431
+ } else {
432
+ Alert.alert("Error", error.message);
433
+ }
276
434
  }
277
- }
435
+ };
436
+
437
+ return <Button title="Sign Up" onPress={handleSignUp} />;
278
438
  };
279
439
  ```
280
440
 
281
- **Method 2: Global State**
441
+ ### Accessing Original Firebase Errors
442
+
443
+ For advanced use cases, access the raw Firebase error:
282
444
  ```typescript
283
445
  const { error } = useAuth();
284
446
 
285
447
  useEffect(() => {
286
448
  if (error?.originalError) {
287
- const rawCode = (error.originalError as any).code;
288
- console.log("Raw Firebase Code:", rawCode);
449
+ console.log('Firebase Error Code:', error.originalError.code);
450
+ console.log('Firebase Error Message:', error.originalError.message);
289
451
 
290
- if (rawCode === 'auth/invalid-email') {
291
- setLocalizedMessage(t('errors.bad_email'));
452
+ // Custom handling for specific Firebase codes
453
+ if (error.originalError.code === 'auth/requires-recent-login') {
454
+ showReauthenticationPrompt();
292
455
  }
293
456
  }
294
457
  }, [error]);
295
458
  ```
296
459
 
460
+ ### Error Logging for Debugging
461
+ ```typescript
462
+ const { error } = useAuth();
463
+
464
+ useEffect(() => {
465
+ if (error) {
466
+ // Log full error details (includes timestamp, code, original error)
467
+ console.log('Auth Error:', JSON.stringify(error.toJSON(), null, 2));
468
+
469
+ // Send to error tracking service (e.g., Sentry)
470
+ logErrorToService(error.toJSON());
471
+ }
472
+ }, [error]);
473
+ ```
474
+
475
+ ### Global Error Boundary
476
+ ```typescript
477
+ import { useAuth } from 'rn-swiftauth-sdk';
478
+
479
+ const ErrorBoundary = ({ children }) => {
480
+ const { error, clearError } = useAuth();
481
+
482
+ if (error) {
483
+ return (
484
+ <View style={styles.errorScreen}>
485
+ <Text style={styles.errorTitle}>Oops!</Text>
486
+ <Text style={styles.errorMessage}>{error.message}</Text>
487
+ <Text style={styles.errorCode}>Error Code: {error.code}</Text>
488
+ <Button title="Try Again" onPress={clearError} />
489
+ </View>
490
+ );
491
+ }
492
+
493
+ return <>{children}</>;
494
+ };
495
+ ```
496
+
297
497
  ---
298
498
 
299
499
  ## Session Management
@@ -413,7 +613,7 @@ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
413
613
 
414
614
  ## License
415
615
 
416
- MIT License - see [LICENSE](LICENSE) file for details.
616
+ MIT License - see [LICENSE](LICENSE) file for detailss.
417
617
 
418
618
  ---
419
619
 
@@ -421,4 +621,4 @@ MIT License - see [LICENSE](LICENSE) file for details.
421
621
 
422
622
  - **Issues:** [GitHub Issues](https://github.com/allcodez/Auth-SDK_Stage8/issues)
423
623
  - **NPM Package:** [rn-swiftauth-sdk](https://www.npmjs.com/package/rn-swiftauth-sdk)
424
- - **Documentation:** [Full Docs](https://github.com/allcodez/Auth-SDK_Stage8)
624
+ - **Documentation:** [Full Docs](https://github.com/allcodez/Auth-SDK_Stage8)
@@ -40,16 +40,35 @@ const useAuth_1 = require("../hooks/useAuth");
40
40
  const PasswordInput_1 = require("./PasswordInput");
41
41
  const validation_1 = require("../utils/validation");
42
42
  const LoginForm = ({ styles: userStyles }) => {
43
- const { signInWithEmail, signInWithGoogle, signInWithApple, isLoading, error, config } = (0, useAuth_1.useAuth)();
43
+ const { signInWithEmail, signInWithGoogle, signInWithApple, sendPasswordReset, isLoading, error, config, clearError } = (0, useAuth_1.useAuth)();
44
44
  const [email, setEmail] = (0, react_1.useState)('');
45
45
  const [password, setPassword] = (0, react_1.useState)('');
46
+ const [isResetMode, setIsResetMode] = (0, react_1.useState)(false); // ✅ 2. Mode Toggle State
46
47
  const [validationErrors, setValidationErrors] = (0, react_1.useState)({});
47
- //Check if form is filled to enable button
48
- const isFormFilled = email.length > 0 && password.length > 0;
48
+ // 3. Dynamic check: If resetting, we only need Email. If logging in, we need both.
49
+ const isFormFilled = isResetMode
50
+ ? email.length > 0
51
+ : (email.length > 0 && password.length > 0);
52
+ // ✅ 4. New Handler for Password Reset
53
+ const handleResetPassword = async () => {
54
+ setValidationErrors({});
55
+ const emailErr = (0, validation_1.validateEmail)(email);
56
+ if (emailErr) {
57
+ setValidationErrors({ email: emailErr });
58
+ return;
59
+ }
60
+ try {
61
+ await sendPasswordReset(email);
62
+ react_native_1.Alert.alert("Check your email", "If an account exists with this email, a password reset link has been sent.");
63
+ setIsResetMode(false); // Go back to login screen
64
+ }
65
+ catch (e) {
66
+ console.log('Reset failed:', e);
67
+ }
68
+ };
49
69
  const handleLogin = async () => {
50
- // 1. Reset previous errors
51
70
  setValidationErrors({});
52
- // 2. Validate Inputs
71
+ // Validate Inputs
53
72
  const emailErr = (0, validation_1.validateEmail)(email);
54
73
  const passErr = (0, validation_1.validatePasswordLogin)(password);
55
74
  if (emailErr || passErr) {
@@ -57,16 +76,12 @@ const LoginForm = ({ styles: userStyles }) => {
57
76
  email: emailErr || undefined,
58
77
  password: passErr || undefined
59
78
  });
60
- return; //Stop if invalid
79
+ return;
61
80
  }
62
- // 3. Attempt Login
63
81
  try {
64
- //UPDATED: Clean Object Syntax
65
82
  await signInWithEmail({ email, password });
66
83
  }
67
84
  catch (e) {
68
- // Auth errors handled by global state
69
- // DX: Log it for the developer (Optional but helpful for debugging)
70
85
  console.log('Login failed:', e);
71
86
  }
72
87
  };
@@ -86,8 +101,15 @@ const LoginForm = ({ styles: userStyles }) => {
86
101
  console.error('Apple Sign-In Error:', e);
87
102
  }
88
103
  };
104
+ const toggleResetMode = () => {
105
+ setIsResetMode(!isResetMode);
106
+ clearError();
107
+ setValidationErrors({});
108
+ setPassword('');
109
+ };
89
110
  return (react_1.default.createElement(react_native_1.View, { style: [defaultStyles.container, userStyles?.container] },
90
111
  error && (react_1.default.createElement(react_native_1.Text, { style: [defaultStyles.errorText, userStyles?.errorText] }, error.message)),
112
+ isResetMode && (react_1.default.createElement(react_native_1.Text, { style: defaultStyles.modeTitle }, "Reset Password")),
91
113
  react_1.default.createElement(react_native_1.TextInput, { style: [
92
114
  defaultStyles.input,
93
115
  userStyles?.input,
@@ -98,34 +120,30 @@ const LoginForm = ({ styles: userStyles }) => {
98
120
  setValidationErrors({ ...validationErrors, email: undefined });
99
121
  }, autoCapitalize: "none", keyboardType: "email-address", placeholderTextColor: "#999", editable: !isLoading }),
100
122
  validationErrors.email && (react_1.default.createElement(react_native_1.Text, { style: defaultStyles.validationText }, validationErrors.email)),
101
- react_1.default.createElement(PasswordInput_1.PasswordInput, { styles: userStyles, placeholder: "Password", value: password, onChangeText: (text) => {
102
- setPassword(text);
103
- if (validationErrors.password)
104
- setValidationErrors({ ...validationErrors, password: undefined });
105
- }, editable: !isLoading }),
106
- validationErrors.password && (react_1.default.createElement(react_native_1.Text, { style: defaultStyles.validationText }, validationErrors.password)),
123
+ !isResetMode && (react_1.default.createElement(react_1.default.Fragment, null,
124
+ react_1.default.createElement(PasswordInput_1.PasswordInput, { styles: userStyles, placeholder: "Password", value: password, onChangeText: (text) => {
125
+ setPassword(text);
126
+ if (validationErrors.password)
127
+ setValidationErrors({ ...validationErrors, password: undefined });
128
+ }, editable: !isLoading }),
129
+ validationErrors.password && (react_1.default.createElement(react_native_1.Text, { style: defaultStyles.validationText }, validationErrors.password)),
130
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: defaultStyles.forgotPasswordContainer, onPress: toggleResetMode, disabled: isLoading },
131
+ react_1.default.createElement(react_native_1.Text, { style: [defaultStyles.forgotPasswordText, userStyles?.linkText] }, "Forgot Password?")))),
107
132
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
108
133
  defaultStyles.button,
109
- // Disable style if loading OR form is incomplete
110
134
  (isLoading || !isFormFilled) && defaultStyles.buttonDisabled,
111
135
  userStyles?.button
112
- ], onPress: handleLogin,
113
- // Disable interaction if loading OR form is incomplete
114
- disabled: isLoading || !isFormFilled }, isLoading ? (react_1.default.createElement(react_native_1.ActivityIndicator, { color: userStyles?.loadingIndicatorColor || "#fff" })) : (react_1.default.createElement(react_native_1.Text, { style: [defaultStyles.buttonText, userStyles?.buttonText] }, "Sign In"))),
115
- (config.enableGoogle || config.enableApple) && !isLoading && (react_1.default.createElement(react_1.default.Fragment, null,
136
+ ], onPress: isResetMode ? handleResetPassword : handleLogin, disabled: isLoading || !isFormFilled }, isLoading ? (react_1.default.createElement(react_native_1.ActivityIndicator, { color: userStyles?.loadingIndicatorColor || "#fff" })) : (react_1.default.createElement(react_native_1.Text, { style: [defaultStyles.buttonText, userStyles?.buttonText] }, isResetMode ? "Send Reset Link" : "Sign In"))),
137
+ isResetMode && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: defaultStyles.cancelButton, onPress: toggleResetMode, disabled: isLoading },
138
+ react_1.default.createElement(react_native_1.Text, { style: defaultStyles.cancelButtonText }, "Back to Sign In"))),
139
+ !isResetMode && (config.enableGoogle || config.enableApple) && !isLoading && (react_1.default.createElement(react_1.default.Fragment, null,
116
140
  react_1.default.createElement(react_native_1.View, { style: defaultStyles.dividerContainer },
117
141
  react_1.default.createElement(react_native_1.View, { style: defaultStyles.divider }),
118
142
  react_1.default.createElement(react_native_1.Text, { style: defaultStyles.dividerText }, "OR"),
119
143
  react_1.default.createElement(react_native_1.View, { style: defaultStyles.divider })),
120
- config.enableGoogle && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
121
- defaultStyles.oauthButton,
122
- defaultStyles.googleButton,
123
- ], onPress: handleGoogleSignIn },
144
+ config.enableGoogle && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [defaultStyles.oauthButton, defaultStyles.googleButton], onPress: handleGoogleSignIn },
124
145
  react_1.default.createElement(react_native_1.Text, { style: defaultStyles.googleButtonText }, "Continue with Google"))),
125
- config.enableApple && react_native_1.Platform.OS === 'ios' && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
126
- defaultStyles.oauthButton,
127
- defaultStyles.appleButton,
128
- ], onPress: handleAppleSignIn },
146
+ config.enableApple && react_native_1.Platform.OS === 'ios' && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [defaultStyles.oauthButton, defaultStyles.appleButton], onPress: handleAppleSignIn },
129
147
  react_1.default.createElement(react_native_1.Text, { style: defaultStyles.appleButtonText }, "Continue with Apple")))))));
130
148
  };
131
149
  exports.LoginForm = LoginForm;
@@ -185,4 +203,31 @@ const defaultStyles = react_native_1.StyleSheet.create({
185
203
  fontSize: 16,
186
204
  fontWeight: '600',
187
205
  },
206
+ forgotPasswordContainer: {
207
+ alignSelf: 'flex-end',
208
+ marginBottom: 12,
209
+ padding: 4,
210
+ },
211
+ forgotPasswordText: {
212
+ color: '#007AFF',
213
+ fontSize: 14,
214
+ fontWeight: '500',
215
+ },
216
+ modeTitle: {
217
+ fontSize: 18,
218
+ fontWeight: 'bold',
219
+ marginBottom: 16,
220
+ color: '#333',
221
+ textAlign: 'center',
222
+ },
223
+ cancelButton: {
224
+ marginTop: 16,
225
+ alignItems: 'center',
226
+ padding: 8,
227
+ },
228
+ cancelButtonText: {
229
+ color: '#666',
230
+ fontSize: 14,
231
+ fontWeight: '500',
232
+ },
188
233
  });
@@ -41,6 +41,7 @@ const react_1 = __importStar(require("react"));
41
41
  const react_native_1 = require("react-native");
42
42
  const app_1 = require("firebase/app");
43
43
  const FirebaseAuth = __importStar(require("firebase/auth"));
44
+ const auth_1 = require("firebase/auth");
44
45
  const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
45
46
  const google_signin_1 = require("@react-native-google-signin/google-signin");
46
47
  const AppleAuthentication = __importStar(require("expo-apple-authentication"));
@@ -113,9 +114,11 @@ const AuthProvider = ({ config, children }) => {
113
114
  if (tokenError.code === types_1.ProviderErrorCodes.USER_TOKEN_EXPIRED ||
114
115
  tokenError.code === types_1.ProviderErrorCodes.NULL_USER) {
115
116
  setStatus(types_1.AuthStatus.TOKEN_EXPIRED);
117
+ setError((0, errors_1.mapFirebaseError)(tokenError));
116
118
  }
117
119
  else {
118
120
  setStatus(types_1.AuthStatus.UNAUTHENTICATED);
121
+ setError((0, errors_1.mapFirebaseError)(tokenError));
119
122
  }
120
123
  setUser(null);
121
124
  }
@@ -128,6 +131,7 @@ const AuthProvider = ({ config, children }) => {
128
131
  catch (err) {
129
132
  console.error("Auth State Error:", err);
130
133
  setStatus(types_1.AuthStatus.UNAUTHENTICATED);
134
+ setError((0, errors_1.mapFirebaseError)(err));
131
135
  }
132
136
  finally {
133
137
  setIsDataLoading(false);
@@ -145,10 +149,10 @@ const AuthProvider = ({ config, children }) => {
145
149
  await FirebaseAuth.signInWithEmailAndPassword(firebaseAuthInstance, email, password);
146
150
  }
147
151
  catch (err) {
148
- const mapped = (0, errors_1.mapFirebaseError)(err);
149
- setError({ ...mapped, originalError: err });
152
+ const mappedException = (0, errors_1.mapFirebaseError)(err);
153
+ setError(mappedException);
150
154
  setStatus(types_1.AuthStatus.UNAUTHENTICATED);
151
- throw err;
155
+ throw mappedException;
152
156
  }
153
157
  };
154
158
  const signUpWithEmail = async ({ email, password }) => {
@@ -160,18 +164,36 @@ const AuthProvider = ({ config, children }) => {
160
164
  await FirebaseAuth.createUserWithEmailAndPassword(firebaseAuthInstance, email, password);
161
165
  }
162
166
  catch (err) {
163
- const mapped = (0, errors_1.mapFirebaseError)(err);
164
- setError({ ...mapped, originalError: err });
167
+ const mappedException = (0, errors_1.mapFirebaseError)(err);
168
+ setError(mappedException);
165
169
  setStatus(types_1.AuthStatus.UNAUTHENTICATED);
166
- throw err;
170
+ throw mappedException;
171
+ }
172
+ };
173
+ const sendPasswordReset = async (email) => {
174
+ if (!firebaseAuthInstance)
175
+ return;
176
+ try {
177
+ setError(null);
178
+ setStatus(types_1.AuthStatus.LOADING);
179
+ await (0, auth_1.sendPasswordResetEmail)(firebaseAuthInstance, email);
180
+ setStatus(types_1.AuthStatus.UNAUTHENTICATED);
181
+ console.log(`Password reset email sent to ${email}`);
182
+ }
183
+ catch (err) {
184
+ const mappedException = (0, errors_1.mapFirebaseError)(err);
185
+ setError(mappedException);
186
+ setStatus(types_1.AuthStatus.UNAUTHENTICATED);
187
+ throw mappedException;
167
188
  }
168
189
  };
169
190
  const signInWithGoogle = async () => {
170
191
  if (!firebaseAuthInstance)
171
192
  throw new Error('Firebase not initialized');
172
193
  if (!config.enableGoogle || !config.googleWebClientId) {
173
- setError({ code: types_1.AuthErrorCode.CONFIG_ERROR, message: 'Google Auth not configured. Missing googleWebClientId.' });
174
- return;
194
+ const configError = new errors_1.ConfigurationException('Google Auth not configured. Missing googleWebClientId.');
195
+ setError(configError);
196
+ throw configError;
175
197
  }
176
198
  try {
177
199
  setError(null);
@@ -187,55 +209,29 @@ const AuthProvider = ({ config, children }) => {
187
209
  }
188
210
  catch (err) {
189
211
  console.error('Google Sign-In Error:', err);
190
- let mappedError;
191
212
  if (err.code === types_1.ProviderErrorCodes.GOOGLE_CANCELLED) {
192
- mappedError = {
193
- code: types_1.AuthErrorCode.GOOGLE_SIGN_IN_CANCELLED,
194
- message: 'Google Sign-In was cancelled',
195
- originalError: err
196
- };
213
+ const cancelError = new errors_1.GoogleSignInCancelledException(err);
214
+ setError(cancelError);
197
215
  setStatus(types_1.AuthStatus.UNAUTHENTICATED);
198
216
  return;
199
217
  }
200
- else if (err.code === types_1.ProviderErrorCodes.GOOGLE_IN_PROGRESS) {
201
- mappedError = {
202
- code: types_1.AuthErrorCode.GOOGLE_SIGN_IN_IN_PROGRESS,
203
- message: 'Google Sign-In is already in progress',
204
- originalError: err
205
- };
206
- }
207
- else if (err.code === types_1.ProviderErrorCodes.GOOGLE_PLAY_UNAVAILABLE) {
208
- mappedError = {
209
- code: types_1.AuthErrorCode.GOOGLE_PLAY_SERVICES_NOT_AVAILABLE,
210
- message: 'Google Play Services are not available. Please update Google Play Services.',
211
- originalError: err
212
- };
213
- }
214
- else {
215
- mappedError = (0, errors_1.mapFirebaseError)(err);
216
- }
217
- setError({ ...mappedError, originalError: err });
218
+ const mappedException = (0, errors_1.mapFirebaseError)(err);
219
+ setError(mappedException);
218
220
  setStatus(types_1.AuthStatus.UNAUTHENTICATED);
219
- throw mappedError;
221
+ throw mappedException;
220
222
  }
221
223
  };
222
224
  const signInWithApple = async () => {
223
225
  if (!firebaseAuthInstance)
224
226
  throw new Error('Firebase not initialized');
225
227
  if (react_native_1.Platform.OS !== 'ios') {
226
- const platformError = {
227
- code: types_1.AuthErrorCode.APPLE_SIGN_IN_NOT_SUPPORTED,
228
- message: 'Apple Sign-In is only available on iOS devices',
229
- };
228
+ const platformError = new errors_1.AppleSignInNotSupportedException();
230
229
  setError(platformError);
231
230
  throw platformError;
232
231
  }
233
232
  const isAvailable = await AppleAuthentication.isAvailableAsync();
234
233
  if (!isAvailable) {
235
- const availabilityError = {
236
- code: types_1.AuthErrorCode.APPLE_SIGN_IN_NOT_SUPPORTED,
237
- message: 'Apple Sign-In is not available on this device (requires iOS 13+)',
238
- };
234
+ const availabilityError = new errors_1.AppleSignInNotSupportedException();
239
235
  setError(availabilityError);
240
236
  throw availabilityError;
241
237
  }
@@ -265,19 +261,15 @@ const AuthProvider = ({ config, children }) => {
265
261
  catch (err) {
266
262
  console.error('Apple Sign-In Error:', err);
267
263
  if (err.code === types_1.ProviderErrorCodes.APPLE_CANCELLED) {
268
- const cancelError = {
269
- code: types_1.AuthErrorCode.APPLE_SIGN_IN_CANCELLED,
270
- message: 'Apple Sign-In was cancelled',
271
- originalError: err
272
- };
264
+ const cancelError = new errors_1.AppleSignInCancelledException(err);
273
265
  setError(cancelError);
274
266
  setStatus(types_1.AuthStatus.UNAUTHENTICATED);
275
267
  return;
276
268
  }
277
- const mappedError = (0, errors_1.mapFirebaseError)(err);
278
- setError({ ...mappedError, originalError: err });
269
+ const mappedException = (0, errors_1.mapFirebaseError)(err);
270
+ setError(mappedException);
279
271
  setStatus(types_1.AuthStatus.UNAUTHENTICATED);
280
- throw mappedError;
272
+ throw mappedException;
281
273
  }
282
274
  };
283
275
  const signOut = async () => {
@@ -297,6 +289,8 @@ const AuthProvider = ({ config, children }) => {
297
289
  }
298
290
  catch (err) {
299
291
  console.error('Sign out error:', err);
292
+ const signOutError = (0, errors_1.mapFirebaseError)(err);
293
+ setError(signOutError);
300
294
  setUser(null);
301
295
  setStatus(types_1.AuthStatus.UNAUTHENTICATED);
302
296
  }
@@ -310,6 +304,7 @@ const AuthProvider = ({ config, children }) => {
310
304
  config,
311
305
  signInWithEmail,
312
306
  signUpWithEmail,
307
+ sendPasswordReset,
313
308
  signInWithGoogle,
314
309
  signInWithApple,
315
310
  signOut,
@@ -1,2 +1,10 @@
1
- import { AuthError } from '../types';
2
- export declare const mapFirebaseError: (error: any) => AuthError;
1
+ import { AuthException } from './exceptions';
2
+ export declare const mapFirebaseError: (error: any) => AuthException;
3
+ /**
4
+ * Helper function to check if an error is a specific exception type
5
+ */
6
+ export declare const isAuthException: (error: any, exceptionType: new (...args: any[]) => AuthException) => boolean;
7
+ /**
8
+ * Helper to extract user-friendly message from any error
9
+ */
10
+ export declare const getErrorMessage: (error: any) => string;