@umituz/react-native-auth 1.8.1 → 1.10.0
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/package.json +1 -1
- package/src/application/ports/IAuthService.ts +5 -0
- package/src/infrastructure/providers/FirebaseAuthProvider.ts +5 -15
- package/src/infrastructure/utils/UserMapper.ts +27 -0
- package/src/presentation/hooks/useAuthState.ts +16 -31
- package/src/presentation/hooks/useLoginForm.ts +3 -12
- package/src/presentation/utils/getAuthErrorMessage.ts +2 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design supports Firebase Auth and can be adapted for Supabase or other providers.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
} from "../../application/ports/IAuthProvider";
|
|
20
20
|
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
21
21
|
import { mapFirebaseAuthError } from "../utils/AuthErrorMapper";
|
|
22
|
+
import { mapToAuthUser } from "../utils/UserMapper";
|
|
22
23
|
|
|
23
24
|
export class FirebaseAuthProvider implements IAuthProvider {
|
|
24
25
|
private auth: Auth | null = null;
|
|
@@ -54,7 +55,7 @@ export class FirebaseAuthProvider implements IAuthProvider {
|
|
|
54
55
|
credentials.email.trim(),
|
|
55
56
|
credentials.password
|
|
56
57
|
);
|
|
57
|
-
return
|
|
58
|
+
return mapToAuthUser(userCredential.user) as AuthUser;
|
|
58
59
|
} catch (error: unknown) {
|
|
59
60
|
throw mapFirebaseAuthError(error);
|
|
60
61
|
}
|
|
@@ -82,7 +83,7 @@ export class FirebaseAuthProvider implements IAuthProvider {
|
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
return
|
|
86
|
+
return mapToAuthUser(userCredential.user) as AuthUser;
|
|
86
87
|
} catch (error: unknown) {
|
|
87
88
|
throw mapFirebaseAuthError(error);
|
|
88
89
|
}
|
|
@@ -104,7 +105,7 @@ export class FirebaseAuthProvider implements IAuthProvider {
|
|
|
104
105
|
if (!this.auth?.currentUser) {
|
|
105
106
|
return null;
|
|
106
107
|
}
|
|
107
|
-
return
|
|
108
|
+
return mapToAuthUser(this.auth.currentUser);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
onAuthStateChange(callback: (user: AuthUser | null) => void): () => void {
|
|
@@ -114,18 +115,7 @@ export class FirebaseAuthProvider implements IAuthProvider {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
return onAuthStateChanged(this.auth, (user) => {
|
|
117
|
-
callback(
|
|
118
|
+
callback(mapToAuthUser(user));
|
|
118
119
|
});
|
|
119
120
|
}
|
|
120
|
-
|
|
121
|
-
private mapFirebaseUser(user: User): AuthUser {
|
|
122
|
-
return {
|
|
123
|
-
uid: user.uid,
|
|
124
|
-
email: user.email,
|
|
125
|
-
displayName: user.displayName,
|
|
126
|
-
isAnonymous: user.isAnonymous,
|
|
127
|
-
emailVerified: user.emailVerified,
|
|
128
|
-
photoURL: user.photoURL,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
121
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Mapper
|
|
3
|
+
* Single Source of Truth for user object transformations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
7
|
+
|
|
8
|
+
interface FirebaseUserLike {
|
|
9
|
+
uid: string;
|
|
10
|
+
email: string | null;
|
|
11
|
+
displayName: string | null;
|
|
12
|
+
isAnonymous: boolean;
|
|
13
|
+
emailVerified: boolean;
|
|
14
|
+
photoURL: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function mapToAuthUser(user: FirebaseUserLike | null): AuthUser | null {
|
|
18
|
+
if (!user) return null;
|
|
19
|
+
return {
|
|
20
|
+
uid: user.uid,
|
|
21
|
+
email: user.email,
|
|
22
|
+
displayName: user.displayName,
|
|
23
|
+
isAnonymous: user.isAnonymous,
|
|
24
|
+
emailVerified: user.emailVerified,
|
|
25
|
+
photoURL: user.photoURL,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -3,26 +3,13 @@
|
|
|
3
3
|
* Single Responsibility: Manage authentication state
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useState, useEffect } from "react";
|
|
6
|
+
import { useState, useEffect, useRef } from "react";
|
|
7
7
|
import { DeviceEventEmitter } from "react-native";
|
|
8
8
|
import { getAuthService } from "../../infrastructure/services/AuthService";
|
|
9
9
|
import { useFirebaseAuth } from "@umituz/react-native-firebase-auth";
|
|
10
|
+
import { mapToAuthUser } from "../../infrastructure/utils/UserMapper";
|
|
10
11
|
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
11
12
|
|
|
12
|
-
type FirebaseUser = ReturnType<typeof useFirebaseAuth>["user"];
|
|
13
|
-
|
|
14
|
-
function mapToAuthUser(user: FirebaseUser): AuthUser | null {
|
|
15
|
-
if (!user) return null;
|
|
16
|
-
return {
|
|
17
|
-
uid: user.uid,
|
|
18
|
-
email: user.email,
|
|
19
|
-
displayName: user.displayName,
|
|
20
|
-
isAnonymous: user.isAnonymous,
|
|
21
|
-
emailVerified: user.emailVerified,
|
|
22
|
-
photoURL: user.photoURL,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
13
|
export interface UseAuthStateResult {
|
|
27
14
|
user: AuthUser | null;
|
|
28
15
|
isAuthenticated: boolean;
|
|
@@ -46,9 +33,17 @@ export function useAuthState(): UseAuthStateResult {
|
|
|
46
33
|
const [error, setError] = useState<string | null>(null);
|
|
47
34
|
const [loading, setLoading] = useState(false);
|
|
48
35
|
|
|
36
|
+
// Ref to track latest isGuest value for event handlers
|
|
37
|
+
const isGuestRef = useRef(isGuest);
|
|
38
|
+
|
|
49
39
|
const user = isGuest ? null : mapToAuthUser(firebaseUser);
|
|
50
40
|
const isAuthenticated = !!user && !isGuest;
|
|
51
41
|
|
|
42
|
+
// Keep ref in sync with state
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
isGuestRef.current = isGuest;
|
|
45
|
+
}, [isGuest]);
|
|
46
|
+
|
|
52
47
|
// Reset guest mode when user signs in
|
|
53
48
|
useEffect(() => {
|
|
54
49
|
if (firebaseUser && isGuest) {
|
|
@@ -56,38 +51,28 @@ export function useAuthState(): UseAuthStateResult {
|
|
|
56
51
|
}
|
|
57
52
|
}, [firebaseUser, isGuest]);
|
|
58
53
|
|
|
59
|
-
// Sync isGuest state with service on mount
|
|
54
|
+
// Sync isGuest state with service on mount only
|
|
60
55
|
useEffect(() => {
|
|
61
56
|
const service = getAuthService();
|
|
62
57
|
if (service) {
|
|
63
58
|
const serviceIsGuest = service.getIsGuestMode();
|
|
64
|
-
if (serviceIsGuest !==
|
|
59
|
+
if (serviceIsGuest !== isGuestRef.current) {
|
|
65
60
|
setIsGuest(serviceIsGuest);
|
|
66
61
|
}
|
|
67
62
|
}
|
|
68
63
|
}, []);
|
|
69
64
|
|
|
70
|
-
// Listen for
|
|
65
|
+
// Listen for auth events - subscribe once on mount
|
|
71
66
|
useEffect(() => {
|
|
72
67
|
const guestSubscription = DeviceEventEmitter.addListener(
|
|
73
68
|
"guest-mode-enabled",
|
|
74
|
-
() =>
|
|
75
|
-
/* eslint-disable-next-line no-console */
|
|
76
|
-
if (__DEV__) {
|
|
77
|
-
console.log("[useAuthState] Guest mode enabled event received");
|
|
78
|
-
}
|
|
79
|
-
setIsGuest(true);
|
|
80
|
-
}
|
|
69
|
+
() => setIsGuest(true)
|
|
81
70
|
);
|
|
82
71
|
|
|
83
72
|
const authSubscription = DeviceEventEmitter.addListener(
|
|
84
73
|
"user-authenticated",
|
|
85
74
|
() => {
|
|
86
|
-
|
|
87
|
-
if (__DEV__) {
|
|
88
|
-
console.log("[useAuthState] User authenticated event received");
|
|
89
|
-
}
|
|
90
|
-
if (isGuest) {
|
|
75
|
+
if (isGuestRef.current) {
|
|
91
76
|
setIsGuest(false);
|
|
92
77
|
}
|
|
93
78
|
}
|
|
@@ -97,7 +82,7 @@ export function useAuthState(): UseAuthStateResult {
|
|
|
97
82
|
guestSubscription.remove();
|
|
98
83
|
authSubscription.remove();
|
|
99
84
|
};
|
|
100
|
-
}, [
|
|
85
|
+
}, []);
|
|
101
86
|
|
|
102
87
|
return {
|
|
103
88
|
user,
|
|
@@ -7,14 +7,7 @@ import { useState, useCallback } from "react";
|
|
|
7
7
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
8
|
import { useAuth } from "./useAuth";
|
|
9
9
|
import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Validate email format
|
|
13
|
-
*/
|
|
14
|
-
function validateEmail(emailValue: string): boolean {
|
|
15
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
16
|
-
return emailRegex.test(emailValue);
|
|
17
|
-
}
|
|
10
|
+
import { validateEmail } from "../../infrastructure/utils/AuthValidation";
|
|
18
11
|
|
|
19
12
|
export interface UseLoginFormResult {
|
|
20
13
|
email: string;
|
|
@@ -66,10 +59,8 @@ export function useLoginForm(): UseLoginFormResult {
|
|
|
66
59
|
|
|
67
60
|
let hasError = false;
|
|
68
61
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
hasError = true;
|
|
72
|
-
} else if (!validateEmail(email.trim())) {
|
|
62
|
+
const emailResult = validateEmail(email.trim());
|
|
63
|
+
if (!emailResult.isValid) {
|
|
73
64
|
setEmailError(t("auth.errors.invalidEmail"));
|
|
74
65
|
hasError = true;
|
|
75
66
|
}
|
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
* Maps error codes to localization keys
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { AuthError } from "../../domain/errors/AuthError";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Map AuthError code to localization key
|
|
10
10
|
*/
|
|
11
11
|
export function getAuthErrorLocalizationKey(error: Error): string {
|
|
12
|
-
|
|
13
|
-
const code = (error as any).code;
|
|
12
|
+
const code = error instanceof AuthError ? error.code : undefined;
|
|
14
13
|
|
|
15
14
|
// Map error codes to localization keys
|
|
16
15
|
const errorCodeMap: Record<string, string> = {
|
|
@@ -75,14 +74,3 @@ export function getAuthErrorLocalizationKey(error: Error): string {
|
|
|
75
74
|
// Default to unknown error
|
|
76
75
|
return "auth.errors.unknownError";
|
|
77
76
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|