@umituz/react-native-auth 1.3.1 → 1.4.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/README.md CHANGED
@@ -15,6 +15,7 @@ npm install @umituz/react-native-auth
15
15
  - `firebase` >= 11.0.0
16
16
  - `react` >= 18.2.0
17
17
  - `react-native` >= 0.74.0
18
+ - `@umituz/react-native-firebase-auth` >= 1.0.0 (for Firebase Auth initialization)
18
19
 
19
20
  ## Features
20
21
 
@@ -46,9 +47,10 @@ Initialize the service early in your app (e.g., in `App.tsx`):
46
47
 
47
48
  ```typescript
48
49
  import { initializeAuthService } from '@umituz/react-native-auth';
49
- import { getFirebaseAuth } from '@umituz/react-native-firebase';
50
+ import { getFirebaseAuth } from '@umituz/react-native-firebase-auth';
50
51
 
51
- // Initialize Firebase first (using @umituz/react-native-firebase)
52
+ // Initialize Firebase App first (using @umituz/react-native-firebase)
53
+ // Then initialize Firebase Auth (using @umituz/react-native-firebase-auth)
52
54
  const auth = getFirebaseAuth();
53
55
 
54
56
  // Initialize auth service
@@ -189,15 +191,16 @@ await authService.setGuestMode();
189
191
  3. **Guest Mode**: Use guest mode for offline-first apps that don't require authentication
190
192
  4. **User Callbacks**: Use `onUserCreated` and `onSignOut` callbacks for app-specific logic
191
193
 
192
- ## Integration with @umituz/react-native-firebase
194
+ ## Integration with Firebase Packages
193
195
 
194
- This package works seamlessly with `@umituz/react-native-firebase`:
196
+ This package works seamlessly with Firebase initialization packages:
195
197
 
196
198
  ```typescript
197
- import { initializeFirebase, getFirebaseAuth } from '@umituz/react-native-firebase';
199
+ import { initializeFirebase } from '@umituz/react-native-firebase';
200
+ import { initializeFirebaseAuth, getFirebaseAuth } from '@umituz/react-native-firebase-auth';
198
201
  import { initializeAuthService } from '@umituz/react-native-auth';
199
202
 
200
- // Initialize Firebase
203
+ // 1. Initialize Firebase App
201
204
  const config = {
202
205
  apiKey: 'your-api-key',
203
206
  authDomain: 'your-project.firebaseapp.com',
@@ -205,11 +208,19 @@ const config = {
205
208
  };
206
209
  initializeFirebase(config);
207
210
 
208
- // Initialize Auth Service
211
+ // 2. Initialize Firebase Auth
212
+ initializeFirebaseAuth();
213
+
214
+ // 3. Initialize Auth Service (business logic)
209
215
  const auth = getFirebaseAuth();
210
- initializeAuthService(auth);
216
+ initializeAuthService(auth, {
217
+ minPasswordLength: 6,
218
+ // ... other config
219
+ });
211
220
  ```
212
221
 
222
+ **Note:** This package is provider-agnostic. While it currently uses Firebase Auth, it can be easily adapted to work with Supabase or other authentication providers in the future.
223
+
213
224
  ## License
214
225
 
215
226
  MIT
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "1.3.1",
4
- "description": "Firebase Authentication wrapper for React Native apps - Secure, type-safe, and production-ready",
3
+ "version": "1.4.0",
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",
7
7
  "scripts": {
@@ -34,6 +34,7 @@
34
34
  "firebase": ">=11.0.0",
35
35
  "react": ">=18.2.0",
36
36
  "react-native": ">=0.74.0",
37
+ "@umituz/react-native-firebase-auth": ">=1.1.0",
37
38
  "@react-navigation/stack": "^6.0.0",
38
39
  "@react-navigation/native": "^6.0.0",
39
40
  "@umituz/react-native-design-system": "*",
@@ -20,9 +20,17 @@ export interface AuthConfig {
20
20
  onUserUpdated?: (user: any) => Promise<void> | void;
21
21
  /** Callback for sign out cleanup */
22
22
  onSignOut?: () => Promise<void> | void;
23
+ /** Callback for analytics logging on sign in */
24
+ onSignIn?: (method: string) => Promise<void> | void;
25
+ /** Callback for analytics logging on guest mode */
26
+ onGuestModeEnabled?: () => Promise<void> | void;
27
+ /** Callback for analytics initialization when user authenticates */
28
+ onAnalyticsInit?: (userId: string) => Promise<void> | void;
29
+ /** Callback for analytics initialization when guest mode enabled */
30
+ onAnalyticsInitGuest?: () => Promise<void> | void;
23
31
  }
24
32
 
25
- export const DEFAULT_AUTH_CONFIG: Required<Omit<AuthConfig, 'onUserCreated' | 'onUserUpdated' | 'onSignOut'>> = {
33
+ export const DEFAULT_AUTH_CONFIG: Required<Omit<AuthConfig, 'onUserCreated' | 'onUserUpdated' | 'onSignOut' | 'onSignIn' | 'onGuestModeEnabled' | 'onAnalyticsInit' | 'onAnalyticsInitGuest'>> = {
26
34
  minPasswordLength: 6,
27
35
  requireUppercase: false,
28
36
  requireLowercase: false,
package/src/index.ts CHANGED
@@ -74,3 +74,9 @@ export type {
74
74
  export { AuthLegalLinks } from './presentation/components/AuthLegalLinks';
75
75
  export type { AuthLegalLinksProps } from './presentation/components/AuthLegalLinks';
76
76
 
77
+ // =============================================================================
78
+ // PRESENTATION LAYER - Utilities
79
+ // =============================================================================
80
+
81
+ export { getAuthErrorLocalizationKey } from './presentation/utils/getAuthErrorMessage';
82
+
@@ -96,9 +96,6 @@ function mapFirebaseAuthError(error: any): Error {
96
96
  if (code === "auth/invalid-email") {
97
97
  return new AuthInvalidEmailError();
98
98
  }
99
- if (code === "auth/operation-not-allowed") {
100
- return new AuthConfigurationError("Email/password authentication is not enabled");
101
- }
102
99
  if (code === "auth/weak-password") {
103
100
  return new AuthWeakPasswordError();
104
101
  }
@@ -117,6 +114,12 @@ function mapFirebaseAuthError(error: any): Error {
117
114
  if (code === "auth/too-many-requests") {
118
115
  return new AuthError("Too many requests. Please try again later.", "AUTH_TOO_MANY_REQUESTS");
119
116
  }
117
+ if (code === "auth/configuration-not-found" || code === "auth/app-not-authorized") {
118
+ return new AuthConfigurationError("Authentication is not properly configured. Please contact support.");
119
+ }
120
+ if (code === "auth/operation-not-allowed") {
121
+ return new AuthConfigurationError("Email/password authentication is not enabled. Please contact support.");
122
+ }
120
123
 
121
124
  return new AuthError(message, code);
122
125
  }
@@ -201,6 +204,7 @@ export class AuthService implements IAuthService {
201
204
  }
202
205
 
203
206
  // Call user created callback if provided
207
+ // User state is managed by Firebase Auth's onAuthStateChanged
204
208
  if (this.config.onUserCreated) {
205
209
  try {
206
210
  await this.config.onUserCreated(userCredential.user);
@@ -242,6 +246,17 @@ export class AuthService implements IAuthService {
242
246
  );
243
247
 
244
248
  this.isGuestMode = false;
249
+
250
+ // Call analytics callback if provided
251
+ // User state is managed by Firebase Auth's onAuthStateChanged
252
+ if (this.config.onSignIn) {
253
+ try {
254
+ await this.config.onSignIn("email");
255
+ } catch (callbackError) {
256
+ // Don't fail signin if analytics callback fails
257
+ }
258
+ }
259
+
245
260
  return userCredential.user;
246
261
  } catch (error: any) {
247
262
  throw mapFirebaseAuthError(error);
@@ -264,6 +279,7 @@ export class AuthService implements IAuthService {
264
279
  this.isGuestMode = false;
265
280
 
266
281
  // Call sign out callback if provided
282
+ // User state is managed by Firebase Auth's onAuthStateChanged
267
283
  if (this.config.onSignOut) {
268
284
  try {
269
285
  await this.config.onSignOut();
@@ -292,6 +308,16 @@ export class AuthService implements IAuthService {
292
308
  }
293
309
 
294
310
  this.isGuestMode = true;
311
+
312
+ // Call analytics callback if provided
313
+ // Guest mode state is managed by useAuth hook
314
+ if (this.config.onGuestModeEnabled) {
315
+ try {
316
+ await this.config.onGuestModeEnabled();
317
+ } catch (callbackError) {
318
+ // Don't fail guest mode if analytics callback fails
319
+ }
320
+ }
295
321
  }
296
322
 
297
323
  /**
@@ -59,3 +59,6 @@ const styles = StyleSheet.create({
59
59
  },
60
60
  });
61
61
 
62
+
63
+
64
+
@@ -57,3 +57,6 @@ const styles = StyleSheet.create({
57
57
  },
58
58
  });
59
59
 
60
+
61
+
62
+
@@ -41,3 +41,6 @@ const styles = StyleSheet.create({
41
41
  },
42
42
  });
43
43
 
44
+
45
+
46
+
@@ -36,3 +36,6 @@ const styles = StyleSheet.create({
36
36
  },
37
37
  });
38
38
 
39
+
40
+
41
+
@@ -21,3 +21,6 @@ export const AuthGradientBackground: React.FC = () => {
21
21
  );
22
22
  };
23
23
 
24
+
25
+
26
+
@@ -66,3 +66,6 @@ const styles = StyleSheet.create({
66
66
  },
67
67
  });
68
68
 
69
+
70
+
71
+
@@ -133,3 +133,6 @@ const styles = StyleSheet.create({
133
133
  },
134
134
  });
135
135
 
136
+
137
+
138
+
@@ -62,3 +62,6 @@ const styles = StyleSheet.create({
62
62
  },
63
63
  });
64
64
 
65
+
66
+
67
+
@@ -11,6 +11,7 @@ import { useAuth } from "../hooks/useAuth";
11
11
  import { AuthErrorDisplay } from "./AuthErrorDisplay";
12
12
  import { AuthDivider } from "./AuthDivider";
13
13
  import { AuthLink } from "./AuthLink";
14
+ import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
14
15
 
15
16
  interface LoginFormProps {
16
17
  onNavigateToRegister: () => void;
@@ -73,7 +74,8 @@ export const LoginForm: React.FC<LoginFormProps> = ({
73
74
  try {
74
75
  await signIn(email.trim(), password);
75
76
  } catch (err: any) {
76
- const errorMessage = err.message || t("auth.errors.unknownError");
77
+ const localizationKey = getAuthErrorLocalizationKey(err);
78
+ const errorMessage = t(localizationKey);
77
79
  setLocalError(errorMessage);
78
80
  }
79
81
  };
@@ -17,6 +17,7 @@ import { useAuth } from "../hooks/useAuth";
17
17
  import { AuthErrorDisplay } from "./AuthErrorDisplay";
18
18
  import { AuthLink } from "./AuthLink";
19
19
  import { AuthLegalLinks } from "./AuthLegalLinks";
20
+ import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
20
21
 
21
22
  interface RegisterFormProps {
22
23
  onNavigateToLogin: () => void;
@@ -121,7 +122,8 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
121
122
  displayName.trim() || undefined,
122
123
  );
123
124
  } catch (err: any) {
124
- const errorMessage = err.message || t("auth.errors.unknownError");
125
+ const localizationKey = getAuthErrorLocalizationKey(err);
126
+ const errorMessage = t(localizationKey);
125
127
  setLocalError(errorMessage);
126
128
  }
127
129
  };
@@ -1,11 +1,15 @@
1
1
  /**
2
2
  * useAuth Hook
3
3
  * React hook for authentication state management
4
+ *
5
+ * Uses Firebase Auth's built-in state management via useFirebaseAuth hook.
6
+ * Adds app-specific state (guest mode, error handling) on top of Firebase Auth.
4
7
  */
5
8
 
6
- import { useEffect, useState, useCallback } from "react";
9
+ import { useEffect, useRef, useCallback, useState } from "react";
7
10
  import type { User } from "firebase/auth";
8
11
  import { getAuthService } from "../../infrastructure/services/AuthService";
12
+ import { useFirebaseAuth } from "@umituz/react-native-firebase-auth";
9
13
 
10
14
  export interface UseAuthResult {
11
15
  /** Current authenticated user */
@@ -26,58 +30,62 @@ export interface UseAuthResult {
26
30
  signOut: () => Promise<void>;
27
31
  /** Continue as guest function */
28
32
  continueAsGuest: () => Promise<void>;
33
+ /** Set error manually (for form validation, etc.) */
34
+ setError: (error: string | null) => void;
29
35
  }
30
36
 
31
37
  /**
32
38
  * Hook for authentication state management
33
39
  *
40
+ * Uses Firebase Auth's built-in state management and adds app-specific features:
41
+ * - Guest mode support
42
+ * - Error handling
43
+ * - Loading states
44
+ *
34
45
  * @example
35
46
  * ```typescript
36
47
  * const { user, isAuthenticated, signIn, signUp, signOut } = useAuth();
37
48
  * ```
38
49
  */
39
50
  export function useAuth(): UseAuthResult {
40
- const [user, setUser] = useState<User | null>(null);
41
- const [loading, setLoading] = useState(true);
51
+ // Use Firebase Auth's built-in state management
52
+ const { user: firebaseUser, loading: firebaseLoading } = useFirebaseAuth();
53
+
54
+ // App-specific state
42
55
  const [isGuest, setIsGuest] = useState(false);
43
56
  const [error, setError] = useState<string | null>(null);
57
+ const [loading, setLoading] = useState(false);
58
+ const prevAuthState = useRef({ isAuthenticated: false, isGuest: false });
59
+
60
+ // Sync Firebase Auth user with guest mode
61
+ const user = isGuest ? null : firebaseUser;
62
+ const isAuthenticated = !!user && !isGuest;
44
63
 
64
+ // Handle analytics initialization (if callbacks are provided in config)
45
65
  useEffect(() => {
46
- const service = getAuthService();
47
- if (!service) {
48
- // Auth service not initialized
49
- setUser(null);
50
- setIsGuest(false);
51
- setLoading(false);
52
- return () => {};
53
- }
66
+ const authChanged =
67
+ prevAuthState.current.isAuthenticated !== isAuthenticated ||
68
+ prevAuthState.current.isGuest !== isGuest;
54
69
 
55
- try {
56
- const unsubscribe = service.onAuthStateChange((currentUser) => {
57
- setUser(currentUser);
58
- setIsGuest(service.getIsGuestMode());
59
- setLoading(false);
60
- });
70
+ // Analytics initialization is handled by AuthService callbacks (onAnalyticsInit, onAnalyticsInitGuest)
71
+ // This effect is kept for backward compatibility but analytics should be configured via AuthConfig
61
72
 
62
- // Set initial state
63
- const currentUser = service.getCurrentUser();
64
- setUser(currentUser);
65
- setIsGuest(service.getIsGuestMode());
66
- setLoading(false);
73
+ // Update previous state
74
+ prevAuthState.current = { isAuthenticated, isGuest };
75
+ }, [isAuthenticated, isGuest]);
67
76
 
68
- return () => {
69
- unsubscribe();
70
- };
71
- } catch (error) {
72
- // Auth service error
73
- setUser(null);
77
+ // Reset guest mode when user signs in
78
+ useEffect(() => {
79
+ if (firebaseUser && isGuest) {
74
80
  setIsGuest(false);
75
- setLoading(false);
76
- return () => {};
77
81
  }
78
- }, []);
82
+ }, [firebaseUser, isGuest]);
79
83
 
80
- const signUp = useCallback(async (email: string, password: string, displayName?: string) => {
84
+ const signUp = useCallback(async (
85
+ email: string,
86
+ password: string,
87
+ displayName?: string
88
+ ) => {
81
89
  const service = getAuthService();
82
90
  if (!service) {
83
91
  const err = "Auth service is not initialized";
@@ -85,13 +93,16 @@ export function useAuth(): UseAuthResult {
85
93
  throw new Error(err);
86
94
  }
87
95
  try {
96
+ setLoading(true);
88
97
  setError(null);
89
98
  await service.signUp({ email, password, displayName });
90
- // State will be updated via onAuthStateChange
99
+ // User state is updated by Firebase Auth's onAuthStateChanged
91
100
  } catch (err: any) {
92
101
  const errorMessage = err.message || "Sign up failed";
93
102
  setError(errorMessage);
94
103
  throw err;
104
+ } finally {
105
+ setLoading(false);
95
106
  }
96
107
  }, []);
97
108
 
@@ -103,13 +114,16 @@ export function useAuth(): UseAuthResult {
103
114
  throw new Error(err);
104
115
  }
105
116
  try {
117
+ setLoading(true);
106
118
  setError(null);
107
119
  await service.signIn({ email, password });
108
- // State will be updated via onAuthStateChange
120
+ // User state is updated by Firebase Auth's onAuthStateChanged
109
121
  } catch (err: any) {
110
- const errorMessage = err.message || "Sign in failed";
122
+ const errorMessage = err.message || "Failed to sign in";
111
123
  setError(errorMessage);
112
124
  throw err;
125
+ } finally {
126
+ setLoading(false);
113
127
  }
114
128
  }, []);
115
129
 
@@ -118,9 +132,13 @@ export function useAuth(): UseAuthResult {
118
132
  if (!service) {
119
133
  return;
120
134
  }
121
- await service.signOut();
122
- setUser(null);
123
- setIsGuest(false);
135
+ try {
136
+ setLoading(true);
137
+ await service.signOut();
138
+ // User state is updated by Firebase Auth's onAuthStateChanged
139
+ } finally {
140
+ setLoading(false);
141
+ }
124
142
  }, []);
125
143
 
126
144
  const continueAsGuest = useCallback(async () => {
@@ -129,21 +147,25 @@ export function useAuth(): UseAuthResult {
129
147
  setIsGuest(true);
130
148
  return;
131
149
  }
132
- await service.setGuestMode();
133
- setUser(null);
134
- setIsGuest(true);
150
+ try {
151
+ setLoading(true);
152
+ await service.setGuestMode();
153
+ setIsGuest(true);
154
+ } finally {
155
+ setLoading(false);
156
+ }
135
157
  }, []);
136
158
 
137
159
  return {
138
160
  user,
139
- loading,
161
+ loading: loading || firebaseLoading,
140
162
  isGuest,
141
- isAuthenticated: !!user && !isGuest,
163
+ isAuthenticated,
142
164
  error,
143
165
  signUp,
144
166
  signIn,
145
167
  signOut,
146
168
  continueAsGuest,
169
+ setError,
147
170
  };
148
171
  }
149
-
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Get localized error message from AuthError
3
+ * Maps error codes to localization keys
4
+ */
5
+
6
+ import type { AuthError } from "../../domain/errors/AuthError";
7
+
8
+ /**
9
+ * Map AuthError code to localization key
10
+ */
11
+ export function getAuthErrorLocalizationKey(error: Error): string {
12
+ // Check if error has code property
13
+ const code = (error as any).code;
14
+
15
+ // Map error codes to localization keys
16
+ const errorCodeMap: Record<string, string> = {
17
+ AUTH_INVALID_EMAIL: "auth.errors.invalidEmail",
18
+ AUTH_WEAK_PASSWORD: "auth.errors.weakPassword",
19
+ AUTH_USER_NOT_FOUND: "auth.errors.userNotFound",
20
+ AUTH_WRONG_PASSWORD: "auth.errors.wrongPassword",
21
+ AUTH_EMAIL_ALREADY_IN_USE: "auth.errors.emailAlreadyInUse",
22
+ AUTH_NETWORK_ERROR: "auth.errors.networkError",
23
+ AUTH_CONFIG_ERROR: "auth.errors.configurationError",
24
+ AUTH_TOO_MANY_REQUESTS: "auth.errors.tooManyRequests",
25
+ AUTH_USER_DISABLED: "auth.errors.userDisabled",
26
+ AUTH_NOT_INITIALIZED: "auth.errors.authNotInitialized",
27
+ };
28
+
29
+ // Check error name for specific error types
30
+ if (error.name === "AuthInvalidEmailError") {
31
+ return "auth.errors.invalidEmail";
32
+ }
33
+ if (error.name === "AuthWeakPasswordError") {
34
+ return "auth.errors.weakPassword";
35
+ }
36
+ if (error.name === "AuthUserNotFoundError") {
37
+ return "auth.errors.userNotFound";
38
+ }
39
+ if (error.name === "AuthWrongPasswordError") {
40
+ return "auth.errors.wrongPassword";
41
+ }
42
+ if (error.name === "AuthEmailAlreadyInUseError") {
43
+ return "auth.errors.emailAlreadyInUse";
44
+ }
45
+ if (error.name === "AuthNetworkError") {
46
+ return "auth.errors.networkError";
47
+ }
48
+ if (error.name === "AuthConfigurationError") {
49
+ return "auth.errors.configurationError";
50
+ }
51
+ if (error.name === "AuthInitializationError") {
52
+ return "auth.errors.authNotInitialized";
53
+ }
54
+
55
+ // Use code if available
56
+ if (code && errorCodeMap[code]) {
57
+ return errorCodeMap[code];
58
+ }
59
+
60
+ // Check error message for specific patterns
61
+ const message = error.message.toLowerCase();
62
+ if (message.includes("too many requests")) {
63
+ return "auth.errors.tooManyRequests";
64
+ }
65
+ if (message.includes("user account has been disabled") || message.includes("user disabled")) {
66
+ return "auth.errors.userDisabled";
67
+ }
68
+ if (message.includes("not properly configured") || message.includes("configuration")) {
69
+ return "auth.errors.configurationError";
70
+ }
71
+ if (message.includes("not enabled") || message.includes("operation not allowed")) {
72
+ return "auth.errors.operationNotAllowed";
73
+ }
74
+
75
+ // Default to unknown error
76
+ return "auth.errors.unknownError";
77
+ }
78
+
79
+
80
+
81
+