@umituz/react-native-auth 3.6.85 → 3.6.87

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.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/src/infrastructure/repositories/UserDocumentRepository.ts +85 -0
  3. package/src/infrastructure/services/AnonymousModeService.ts +5 -2
  4. package/src/infrastructure/services/AuthService.ts +13 -1
  5. package/src/infrastructure/utils/AuthValidation.ts +8 -4
  6. package/src/infrastructure/utils/UserMapper.ts +7 -3
  7. package/src/infrastructure/utils/error/errorCodeMapping.constants.ts +12 -109
  8. package/src/infrastructure/utils/error/mappings/actionCodeErrorMappings.ts +26 -0
  9. package/src/infrastructure/utils/error/mappings/authErrorMappings.ts +69 -0
  10. package/src/infrastructure/utils/error/mappings/configErrorMappings.ts +31 -0
  11. package/src/infrastructure/utils/error/mappings/errorMapping.types.ts +12 -0
  12. package/src/infrastructure/utils/error/mappings/networkErrorMappings.ts +22 -0
  13. package/src/infrastructure/utils/listener/anonymousHandler.ts +31 -0
  14. package/src/infrastructure/utils/listener/authStateHandler.ts +59 -0
  15. package/src/infrastructure/utils/listener/cleanupHandlers.ts +46 -0
  16. package/src/infrastructure/utils/listener/initializationHandlers.ts +26 -0
  17. package/src/infrastructure/utils/listener/listenerLifecycle.util.ts +19 -152
  18. package/src/infrastructure/utils/listener/listenerState.util.ts +21 -3
  19. package/src/infrastructure/utils/listener/setupListener.ts +63 -0
  20. package/src/presentation/hooks/registerForm/registerFormHandlers.ts +57 -0
  21. package/src/presentation/hooks/registerForm/registerFormSubmit.ts +64 -0
  22. package/src/presentation/hooks/registerForm/useRegisterForm.types.ts +39 -0
  23. package/src/presentation/hooks/useRegisterForm.ts +31 -109
  24. package/src/presentation/utils/form/formValidation.util.ts +23 -114
  25. package/src/presentation/utils/form/useFormField.hook.ts +2 -2
  26. package/src/presentation/utils/form/validation/formValidation.hook.ts +30 -0
  27. package/src/presentation/utils/form/validation/formValidation.types.ts +31 -0
  28. package/src/presentation/utils/form/validation/formValidation.utils.ts +17 -0
  29. package/src/presentation/utils/form/validation/formValidators.ts +86 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "3.6.85",
3
+ "version": "3.6.87",
4
4
  "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,85 @@
1
+ /**
2
+ * User Document Repository
3
+ * Manages Firestore user documents using Firebase Auth UID
4
+ */
5
+
6
+ import { doc, setDoc, getDoc, serverTimestamp, getFirestore } from "firebase/firestore";
7
+ import type { Firestore } from "firebase/firestore";
8
+ import type { User } from "firebase/auth";
9
+ import type { UserProfile } from "../../domain/entities/UserProfile";
10
+
11
+ const USERS_COLLECTION = "users";
12
+
13
+ /**
14
+ * Create or update user document in Firestore
15
+ * Uses Firebase Auth UID as document ID (best practice)
16
+ */
17
+ export async function createOrUpdateUserDocument(
18
+ firestore: Firestore,
19
+ user: User
20
+ ): Promise<void> {
21
+ try {
22
+ const userRef = doc(firestore, USERS_COLLECTION, user.uid);
23
+ const userDoc = await getDoc(userRef);
24
+
25
+ const userData: Partial<UserProfile> = {
26
+ uid: user.uid,
27
+ email: user.email,
28
+ displayName: user.displayName,
29
+ photoURL: user.photoURL,
30
+ isAnonymous: user.isAnonymous,
31
+ lastLoginAt: new Date(),
32
+ };
33
+
34
+ if (!userDoc.exists()) {
35
+ // New user - create document with createdAt
36
+ await setDoc(userRef, {
37
+ ...userData,
38
+ createdAt: serverTimestamp(),
39
+ lastLoginAt: serverTimestamp(),
40
+ });
41
+ } else {
42
+ // Existing user - update lastLoginAt only
43
+ await setDoc(
44
+ userRef,
45
+ {
46
+ lastLoginAt: serverTimestamp(),
47
+ // Update these in case they changed (e.g., anonymous → registered)
48
+ email: userData.email,
49
+ displayName: userData.displayName,
50
+ photoURL: userData.photoURL,
51
+ isAnonymous: userData.isAnonymous,
52
+ },
53
+ { merge: true }
54
+ );
55
+ }
56
+ } catch (error) {
57
+ console.error("[UserDocumentRepository] Failed to create/update user document:", error);
58
+ // Don't throw - allow auth to continue even if Firestore fails
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Check if user document exists
64
+ */
65
+ export async function userDocumentExists(
66
+ firestore: Firestore,
67
+ uid: string
68
+ ): Promise<boolean> {
69
+ try {
70
+ const userRef = doc(firestore, USERS_COLLECTION, uid);
71
+ const userDoc = await getDoc(userRef);
72
+ return userDoc.exists();
73
+ } catch (error) {
74
+ console.error("[UserDocumentRepository] Failed to check user document:", error);
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Get Firestore instance
81
+ * Exported for testing and external use
82
+ */
83
+ export function getFirestoreInstance(): Firestore {
84
+ return getFirestore();
85
+ }
@@ -51,8 +51,11 @@ export class AnonymousModeService {
51
51
  if (provider?.getCurrentUser()) {
52
52
  try {
53
53
  await provider.signOut();
54
- } catch {
55
- // Silently fail - sign out errors are handled elsewhere
54
+ } catch (error) {
55
+ // Log error but don't throw - allow anonymous mode to be enabled
56
+ // even if sign-out fails (user might be in inconsistent state but
57
+ // subsequent auth operations will resolve it)
58
+ console.warn("[AnonymousModeService] Sign-out failed during anonymous mode enable:", error);
56
59
  }
57
60
  }
58
61
 
@@ -33,12 +33,24 @@ export class AuthService {
33
33
  return this.repository;
34
34
  }
35
35
 
36
+ private isFirebaseAuth(obj: IAuthProvider | Auth): obj is Auth {
37
+ return (
38
+ typeof obj === "object" &&
39
+ obj !== null &&
40
+ "currentUser" in obj &&
41
+ "onAuthStateChanged" in obj &&
42
+ "signInWithEmailAndPassword" in obj &&
43
+ "createUserWithEmailAndPassword" in obj &&
44
+ typeof obj.onAuthStateChanged === "function"
45
+ );
46
+ }
47
+
36
48
  async initialize(providerOrAuth: IAuthProvider | Auth): Promise<void> {
37
49
  if (this.initialized) return;
38
50
 
39
51
  let provider: IAuthProvider;
40
52
 
41
- if ("currentUser" in providerOrAuth) {
53
+ if (this.isFirebaseAuth(providerOrAuth)) {
42
54
  const firebaseProvider = new FirebaseAuthProvider(providerOrAuth);
43
55
  await firebaseProvider.initialize();
44
56
  provider = firebaseProvider;
@@ -16,7 +16,11 @@ export interface ValidationConfig {
16
16
  }
17
17
 
18
18
  export const DEFAULT_VAL_CONFIG: ValidationConfig = {
19
- emailRegex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
19
+ // More robust email validation:
20
+ // - Local part: alphanumeric, dots (not consecutive), hyphens, underscores, plus
21
+ // - Domain: alphanumeric and hyphens
22
+ // - TLD: at least 2 characters
23
+ emailRegex: /^[a-zA-Z0-9]([a-zA-Z0-9._+-]*[a-zA-Z0-9])?@[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/,
20
24
  displayNameMinLength: 2,
21
25
  };
22
26
 
@@ -30,7 +34,7 @@ export function validateEmail(
30
34
  }
31
35
 
32
36
  export function validatePasswordForLogin(password: string): ValidationResult {
33
- if (!password || password.length === 0) return { isValid: false, error: "auth.validation.passwordRequired" };
37
+ if (!password || password.trim().length === 0) return { isValid: false, error: "auth.validation.passwordRequired" };
34
38
  return { isValid: true };
35
39
  }
36
40
 
@@ -38,7 +42,7 @@ export function validatePasswordForRegister(
38
42
  password: string,
39
43
  config: PasswordConfig,
40
44
  ): PasswordStrengthResult {
41
- if (!password) {
45
+ if (!password || password.trim().length === 0) {
42
46
  return { isValid: false, error: "auth.validation.passwordRequired", requirements: { hasMinLength: false } };
43
47
  }
44
48
 
@@ -52,7 +56,7 @@ export function validatePasswordForRegister(
52
56
  }
53
57
 
54
58
  export function validatePasswordConfirmation(password: string, confirm: string): ValidationResult {
55
- if (!confirm) return { isValid: false, error: "auth.validation.confirmPasswordRequired" };
59
+ if (!confirm || confirm.trim().length === 0) return { isValid: false, error: "auth.validation.confirmPasswordRequired" };
56
60
  if (password !== confirm) return { isValid: false, error: "auth.validation.passwordsDoNotMatch" };
57
61
  return { isValid: true };
58
62
  }
@@ -35,14 +35,18 @@ export function extractProvider(user: FirebaseUserLike): AuthProviderType {
35
35
  // Filter out null providers and find the first valid one
36
36
  const validProviders = providerData.filter((p): p is ProviderData => p != null);
37
37
 
38
+ if (validProviders.length === 0) {
39
+ return "unknown";
40
+ }
41
+
38
42
  const googleProvider = validProviders.find((p) => p.providerId === "google.com");
39
- if (googleProvider && googleProvider.providerId) return "google.com";
43
+ if (googleProvider) return "google.com";
40
44
 
41
45
  const appleProvider = validProviders.find((p) => p.providerId === "apple.com");
42
- if (appleProvider && appleProvider.providerId) return "apple.com";
46
+ if (appleProvider) return "apple.com";
43
47
 
44
48
  const passwordProvider = validProviders.find((p) => p.providerId === "password");
45
- if (passwordProvider && passwordProvider.providerId) return "password";
49
+ if (passwordProvider) return "password";
46
50
 
47
51
  return "unknown";
48
52
  }
@@ -3,118 +3,21 @@
3
3
  * Centralized configuration for mapping Firebase error codes to domain errors
4
4
  */
5
5
 
6
- import {
7
- AuthError,
8
- AuthConfigurationError,
9
- AuthEmailAlreadyInUseError,
10
- AuthInvalidEmailError,
11
- AuthWeakPasswordError,
12
- AuthUserNotFoundError,
13
- AuthWrongPasswordError,
14
- AuthNetworkError,
15
- } from "../../../domain/errors/AuthError";
6
+ import type { ErrorMapping } from "./mappings/errorMapping.types";
7
+ import { AUTH_ERROR_MAPPINGS } from "./mappings/authErrorMappings";
8
+ import { CONFIG_ERROR_MAPPINGS } from "./mappings/configErrorMappings";
9
+ import { NETWORK_ERROR_MAPPINGS } from "./mappings/networkErrorMappings";
10
+ import { ACTION_CODE_ERROR_MAPPINGS } from "./mappings/actionCodeErrorMappings";
16
11
 
17
- export type ErrorConstructor = new (message?: string) => Error;
18
- export type ErrorFactory = () => Error;
19
-
20
- export interface ErrorMapping {
21
- type: "class" | "factory";
22
- create: ErrorConstructor | ErrorFactory;
23
- }
12
+ // Re-export types for backward compatibility
13
+ export type { ErrorConstructor, ErrorFactory, ErrorMapping } from "./mappings/errorMapping.types";
24
14
 
25
15
  /**
26
- * Firebase error code to domain error mapping
16
+ * Combined Firebase error code to domain error mapping
27
17
  */
28
18
  export const ERROR_CODE_MAP: Record<string, ErrorMapping> = {
29
- "auth/email-already-in-use": {
30
- type: "class",
31
- create: AuthEmailAlreadyInUseError,
32
- },
33
- "auth/invalid-email": {
34
- type: "class",
35
- create: AuthInvalidEmailError,
36
- },
37
- "auth/weak-password": {
38
- type: "class",
39
- create: AuthWeakPasswordError,
40
- },
41
- "auth/user-disabled": {
42
- type: "factory",
43
- create: () => new AuthError(
44
- "Your account has been disabled. Please contact support.",
45
- "AUTH_USER_DISABLED"
46
- ),
47
- },
48
- "auth/user-not-found": {
49
- type: "class",
50
- create: AuthUserNotFoundError,
51
- },
52
- "auth/wrong-password": {
53
- type: "class",
54
- create: AuthWrongPasswordError,
55
- },
56
- "auth/invalid-credential": {
57
- type: "factory",
58
- create: () => new AuthError(
59
- "Invalid email or password. Please check your credentials.",
60
- "AUTH_INVALID_CREDENTIAL"
61
- ),
62
- },
63
- "auth/invalid-login-credentials": {
64
- type: "factory",
65
- create: () => new AuthError(
66
- "Invalid email or password. Please check your credentials.",
67
- "AUTH_INVALID_CREDENTIAL"
68
- ),
69
- },
70
- "auth/network-request-failed": {
71
- type: "class",
72
- create: AuthNetworkError,
73
- },
74
- "auth/too-many-requests": {
75
- type: "factory",
76
- create: () => new AuthError(
77
- "Too many failed attempts. Please wait a few minutes and try again.",
78
- "AUTH_TOO_MANY_REQUESTS"
79
- ),
80
- },
81
- "auth/configuration-not-found": {
82
- type: "factory",
83
- create: () => new AuthConfigurationError(
84
- "Authentication is not properly configured. Please contact support."
85
- ),
86
- },
87
- "auth/app-not-authorized": {
88
- type: "factory",
89
- create: () => new AuthConfigurationError(
90
- "Authentication is not properly configured. Please contact support."
91
- ),
92
- },
93
- "auth/operation-not-allowed": {
94
- type: "factory",
95
- create: () => new AuthConfigurationError(
96
- "Email/password authentication is not enabled. Please contact support."
97
- ),
98
- },
99
- "auth/requires-recent-login": {
100
- type: "factory",
101
- create: () => new AuthError(
102
- "Please sign in again to complete this action.",
103
- "AUTH_REQUIRES_RECENT_LOGIN"
104
- ),
105
- },
106
- "auth/expired-action-code": {
107
- type: "factory",
108
- create: () => new AuthError(
109
- "This link has expired. Please request a new one.",
110
- "AUTH_EXPIRED_ACTION_CODE"
111
- ),
112
- },
113
- "auth/invalid-action-code": {
114
- type: "factory",
115
- create: () => new AuthError(
116
- "This link is invalid. Please request a new one.",
117
- "AUTH_INVALID_ACTION_CODE"
118
- ),
119
- },
19
+ ...AUTH_ERROR_MAPPINGS,
20
+ ...CONFIG_ERROR_MAPPINGS,
21
+ ...NETWORK_ERROR_MAPPINGS,
22
+ ...ACTION_CODE_ERROR_MAPPINGS,
120
23
  };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Action Code Error Mappings
3
+ * Email verification, password reset link errors
4
+ */
5
+
6
+ import { AuthError } from "../../../../domain/errors/AuthError";
7
+ import type { ErrorMapping } from "./errorMapping.types";
8
+
9
+ export const ACTION_CODE_ERROR_MAPPINGS: Record<string, ErrorMapping> = {
10
+ "auth/expired-action-code": {
11
+ type: "factory",
12
+ create: () =>
13
+ new AuthError(
14
+ "This link has expired. Please request a new one.",
15
+ "AUTH_EXPIRED_ACTION_CODE"
16
+ ),
17
+ },
18
+ "auth/invalid-action-code": {
19
+ type: "factory",
20
+ create: () =>
21
+ new AuthError(
22
+ "This link is invalid. Please request a new one.",
23
+ "AUTH_INVALID_ACTION_CODE"
24
+ ),
25
+ },
26
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Authentication Error Mappings
3
+ * Email, password, and credential related errors
4
+ */
5
+
6
+ import {
7
+ AuthError,
8
+ AuthEmailAlreadyInUseError,
9
+ AuthInvalidEmailError,
10
+ AuthWeakPasswordError,
11
+ AuthUserNotFoundError,
12
+ AuthWrongPasswordError,
13
+ } from "../../../../domain/errors/AuthError";
14
+ import type { ErrorMapping } from "./errorMapping.types";
15
+
16
+ export const AUTH_ERROR_MAPPINGS: Record<string, ErrorMapping> = {
17
+ "auth/email-already-in-use": {
18
+ type: "class",
19
+ create: AuthEmailAlreadyInUseError,
20
+ },
21
+ "auth/invalid-email": {
22
+ type: "class",
23
+ create: AuthInvalidEmailError,
24
+ },
25
+ "auth/weak-password": {
26
+ type: "class",
27
+ create: AuthWeakPasswordError,
28
+ },
29
+ "auth/user-disabled": {
30
+ type: "factory",
31
+ create: () =>
32
+ new AuthError(
33
+ "Your account has been disabled. Please contact support.",
34
+ "AUTH_USER_DISABLED"
35
+ ),
36
+ },
37
+ "auth/user-not-found": {
38
+ type: "class",
39
+ create: AuthUserNotFoundError,
40
+ },
41
+ "auth/wrong-password": {
42
+ type: "class",
43
+ create: AuthWrongPasswordError,
44
+ },
45
+ "auth/invalid-credential": {
46
+ type: "factory",
47
+ create: () =>
48
+ new AuthError(
49
+ "Invalid email or password. Please check your credentials.",
50
+ "AUTH_INVALID_CREDENTIAL"
51
+ ),
52
+ },
53
+ "auth/invalid-login-credentials": {
54
+ type: "factory",
55
+ create: () =>
56
+ new AuthError(
57
+ "Invalid email or password. Please check your credentials.",
58
+ "AUTH_INVALID_CREDENTIAL"
59
+ ),
60
+ },
61
+ "auth/requires-recent-login": {
62
+ type: "factory",
63
+ create: () =>
64
+ new AuthError(
65
+ "Please sign in again to complete this action.",
66
+ "AUTH_REQUIRES_RECENT_LOGIN"
67
+ ),
68
+ },
69
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Configuration Error Mappings
3
+ * Firebase configuration and setup errors
4
+ */
5
+
6
+ import { AuthConfigurationError } from "../../../../domain/errors/AuthError";
7
+ import type { ErrorMapping } from "./errorMapping.types";
8
+
9
+ export const CONFIG_ERROR_MAPPINGS: Record<string, ErrorMapping> = {
10
+ "auth/configuration-not-found": {
11
+ type: "factory",
12
+ create: () =>
13
+ new AuthConfigurationError(
14
+ "Authentication is not properly configured. Please contact support."
15
+ ),
16
+ },
17
+ "auth/app-not-authorized": {
18
+ type: "factory",
19
+ create: () =>
20
+ new AuthConfigurationError(
21
+ "Authentication is not properly configured. Please contact support."
22
+ ),
23
+ },
24
+ "auth/operation-not-allowed": {
25
+ type: "factory",
26
+ create: () =>
27
+ new AuthConfigurationError(
28
+ "Email/password authentication is not enabled. Please contact support."
29
+ ),
30
+ },
31
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Error Mapping Types
3
+ * Shared types for error code mappings
4
+ */
5
+
6
+ export type ErrorConstructor = new (message?: string) => Error;
7
+ export type ErrorFactory = () => Error;
8
+
9
+ export interface ErrorMapping {
10
+ type: "class" | "factory";
11
+ create: ErrorConstructor | ErrorFactory;
12
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Network Error Mappings
3
+ * Network and rate limit errors
4
+ */
5
+
6
+ import { AuthError, AuthNetworkError } from "../../../../domain/errors/AuthError";
7
+ import type { ErrorMapping } from "./errorMapping.types";
8
+
9
+ export const NETWORK_ERROR_MAPPINGS: Record<string, ErrorMapping> = {
10
+ "auth/network-request-failed": {
11
+ type: "class",
12
+ create: AuthNetworkError,
13
+ },
14
+ "auth/too-many-requests": {
15
+ type: "factory",
16
+ create: () =>
17
+ new AuthError(
18
+ "Too many failed attempts. Please wait a few minutes and try again.",
19
+ "AUTH_TOO_MANY_REQUESTS"
20
+ ),
21
+ },
22
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Anonymous Mode Handler
3
+ * Handles anonymous sign-in flow
4
+ */
5
+
6
+ import type { Auth } from "firebase/auth";
7
+ import type { AuthActions } from "../../../types/auth-store.types";
8
+ import { createAnonymousSignInHandler } from "./anonymousSignInHandler";
9
+ import {
10
+ startAnonymousSignIn,
11
+ completeAnonymousSignIn,
12
+ } from "./listenerState.util";
13
+
14
+ type Store = AuthActions & { isAnonymous: boolean };
15
+
16
+ /**
17
+ * Handle anonymous mode sign-in
18
+ */
19
+ export async function handleAnonymousMode(store: Store, auth: Auth): Promise<void> {
20
+ if (!startAnonymousSignIn()) {
21
+ return; // Already signing in
22
+ }
23
+
24
+ const handleAnonymousSignIn = createAnonymousSignInHandler(auth, store);
25
+
26
+ try {
27
+ await handleAnonymousSignIn();
28
+ } finally {
29
+ completeAnonymousSignIn();
30
+ }
31
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Auth State Change Handler
3
+ * Processes Firebase auth state changes and updates store
4
+ */
5
+
6
+ import type { Auth, User } from "firebase/auth";
7
+ import type { AuthActions } from "../../../types/auth-store.types";
8
+ import { completeInitialization } from "./listenerState.util";
9
+ import { handleAnonymousMode } from "./anonymousHandler";
10
+ import {
11
+ createOrUpdateUserDocument,
12
+ getFirestoreInstance,
13
+ } from "../../repositories/UserDocumentRepository";
14
+
15
+ type Store = AuthActions & { isAnonymous: boolean };
16
+
17
+ /**
18
+ * Handle auth state change from Firebase
19
+ */
20
+ export function handleAuthStateChange(
21
+ user: User | null,
22
+ store: Store,
23
+ auth: Auth,
24
+ autoAnonymousSignIn: boolean,
25
+ onAuthStateChange?: (user: User | null) => void
26
+ ): void {
27
+ try {
28
+ if (!user && autoAnonymousSignIn) {
29
+ // Start anonymous sign-in without blocking
30
+ void handleAnonymousMode(store, auth);
31
+ store.setFirebaseUser(null);
32
+ completeInitialization();
33
+ return;
34
+ }
35
+
36
+ store.setFirebaseUser(user);
37
+ store.setInitialized(true);
38
+
39
+ // Create or update Firestore user document (best practice)
40
+ if (user) {
41
+ createOrUpdateUserDocument(getFirestoreInstance(), user).catch((error) => {
42
+ console.error("[AuthListener] Failed to create/update user document:", error);
43
+ // Don't throw - Firestore failures shouldn't block auth state updates
44
+ });
45
+ }
46
+
47
+ // Handle conversion from anonymous
48
+ if (user && !user.isAnonymous && store.isAnonymous) {
49
+ store.setIsAnonymous(false);
50
+ }
51
+
52
+ onAuthStateChange?.(user);
53
+ } catch (error) {
54
+ console.error("[AuthListener] Error handling auth state change:", error);
55
+ // Ensure we don't leave the app in a bad state
56
+ store.setInitialized(true);
57
+ store.setLoading(false);
58
+ }
59
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Cleanup Handlers
3
+ * Manages unsubscribe and cleanup logic for auth listener
4
+ */
5
+
6
+ import {
7
+ isListenerInitialized,
8
+ incrementRefCount,
9
+ decrementRefCount,
10
+ resetListenerState,
11
+ } from "./listenerState.util";
12
+
13
+ /**
14
+ * Create unsubscribe function that decrements ref count
15
+ */
16
+ export function createUnsubscribeHandler(): () => void {
17
+ return () => {
18
+ const { shouldCleanup } = decrementRefCount();
19
+
20
+ if (shouldCleanup) {
21
+ resetListenerState();
22
+ }
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Return existing unsubscribe if already initialized
28
+ * Returns null if initialization is in progress
29
+ */
30
+ export function handleExistingInitialization(): (() => void) | null {
31
+ if (!isListenerInitialized()) {
32
+ return null;
33
+ }
34
+
35
+ incrementRefCount();
36
+ return createUnsubscribeHandler();
37
+ }
38
+
39
+ /**
40
+ * Return no-op if initialization is in progress
41
+ */
42
+ export function handleInitializationInProgress(): () => void {
43
+ return () => {
44
+ // No-op - handled by initial initialization
45
+ };
46
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Initialization Handlers
3
+ * Helper functions for listener initialization
4
+ */
5
+
6
+ import type { AuthActions } from "../../../types/auth-store.types";
7
+ import { completeInitialization } from "./listenerState.util";
8
+
9
+ type Store = AuthActions & { isAnonymous: boolean };
10
+
11
+ /**
12
+ * Handle case where Firebase auth is not available
13
+ */
14
+ export function handleNoFirebaseAuth(store: Store): () => void {
15
+ completeInitialization();
16
+ store.setLoading(false);
17
+ store.setInitialized(true);
18
+ return () => {};
19
+ }
20
+
21
+ /**
22
+ * Complete listener setup and mark as initialized
23
+ */
24
+ export function completeListenerSetup(): void {
25
+ completeInitialization();
26
+ }