@umituz/react-native-auth 3.6.86 → 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.
- package/package.json +1 -1
- package/src/infrastructure/services/AnonymousModeService.ts +5 -2
- package/src/infrastructure/services/AuthService.ts +13 -1
- package/src/infrastructure/utils/AuthValidation.ts +8 -4
- package/src/infrastructure/utils/UserMapper.ts +7 -3
- package/src/infrastructure/utils/error/errorCodeMapping.constants.ts +12 -109
- package/src/infrastructure/utils/error/mappings/actionCodeErrorMappings.ts +26 -0
- package/src/infrastructure/utils/error/mappings/authErrorMappings.ts +69 -0
- package/src/infrastructure/utils/error/mappings/configErrorMappings.ts +31 -0
- package/src/infrastructure/utils/error/mappings/errorMapping.types.ts +12 -0
- package/src/infrastructure/utils/error/mappings/networkErrorMappings.ts +22 -0
- package/src/infrastructure/utils/listener/anonymousHandler.ts +31 -0
- package/src/infrastructure/utils/listener/authStateHandler.ts +59 -0
- package/src/infrastructure/utils/listener/cleanupHandlers.ts +46 -0
- package/src/infrastructure/utils/listener/initializationHandlers.ts +26 -0
- package/src/infrastructure/utils/listener/listenerLifecycle.util.ts +19 -161
- package/src/infrastructure/utils/listener/listenerState.util.ts +21 -3
- package/src/infrastructure/utils/listener/setupListener.ts +63 -0
- package/src/presentation/hooks/registerForm/registerFormHandlers.ts +57 -0
- package/src/presentation/hooks/registerForm/registerFormSubmit.ts +64 -0
- package/src/presentation/hooks/registerForm/useRegisterForm.types.ts +39 -0
- package/src/presentation/hooks/useRegisterForm.ts +31 -109
- package/src/presentation/utils/form/formValidation.util.ts +23 -114
- package/src/presentation/utils/form/useFormField.hook.ts +2 -2
- package/src/presentation/utils/form/validation/formValidation.hook.ts +30 -0
- package/src/presentation/utils/form/validation/formValidation.types.ts +31 -0
- package/src/presentation/utils/form/validation/formValidation.utils.ts +17 -0
- 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.
|
|
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",
|
|
@@ -51,8 +51,11 @@ export class AnonymousModeService {
|
|
|
51
51
|
if (provider?.getCurrentUser()) {
|
|
52
52
|
try {
|
|
53
53
|
await provider.signOut();
|
|
54
|
-
} catch {
|
|
55
|
-
//
|
|
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 (
|
|
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
|
-
|
|
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
|
|
43
|
+
if (googleProvider) return "google.com";
|
|
40
44
|
|
|
41
45
|
const appleProvider = validProviders.find((p) => p.providerId === "apple.com");
|
|
42
|
-
if (appleProvider
|
|
46
|
+
if (appleProvider) return "apple.com";
|
|
43
47
|
|
|
44
48
|
const passwordProvider = validProviders.find((p) => p.providerId === "password");
|
|
45
|
-
if (passwordProvider
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
18
|
-
export type ErrorFactory
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
}
|