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
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
|
-
|
|
257
|
+
### 📌 Usage Example (React Native)
|
|
239
258
|
|
|
240
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
+
---
|
|
261
351
|
|
|
262
|
-
|
|
352
|
+
## Error Handling
|
|
263
353
|
|
|
264
|
-
|
|
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
|
-
|
|
384
|
+
import { useAuth } from 'rn-swiftauth-sdk';
|
|
267
385
|
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
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
|
-
|
|
288
|
-
console.log(
|
|
449
|
+
console.log('Firebase Error Code:', error.originalError.code);
|
|
450
|
+
console.log('Firebase Error Message:', error.originalError.message);
|
|
289
451
|
|
|
290
|
-
|
|
291
|
-
|
|
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
|
|
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
|
-
//
|
|
48
|
-
const isFormFilled =
|
|
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
|
-
//
|
|
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;
|
|
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(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
|
149
|
-
setError(
|
|
152
|
+
const mappedException = (0, errors_1.mapFirebaseError)(err);
|
|
153
|
+
setError(mappedException);
|
|
150
154
|
setStatus(types_1.AuthStatus.UNAUTHENTICATED);
|
|
151
|
-
throw
|
|
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
|
|
164
|
-
setError(
|
|
167
|
+
const mappedException = (0, errors_1.mapFirebaseError)(err);
|
|
168
|
+
setError(mappedException);
|
|
165
169
|
setStatus(types_1.AuthStatus.UNAUTHENTICATED);
|
|
166
|
-
throw
|
|
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
|
-
|
|
174
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
|
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
|
|
278
|
-
setError(
|
|
269
|
+
const mappedException = (0, errors_1.mapFirebaseError)(err);
|
|
270
|
+
setError(mappedException);
|
|
279
271
|
setStatus(types_1.AuthStatus.UNAUTHENTICATED);
|
|
280
|
-
throw
|
|
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 {
|
|
2
|
-
export declare const mapFirebaseError: (error: any) =>
|
|
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;
|