@umituz/react-native-auth 4.3.69 → 4.3.71
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/index.ts +1 -11
- package/src/infrastructure/services/AnonymousModeService.ts +9 -3
- package/src/infrastructure/services/AuthEventService.ts +3 -1
- package/src/infrastructure/services/AuthService.ts +3 -1
- package/src/infrastructure/utils/authStateHandler.ts +3 -1
- package/src/infrastructure/utils/calculators/index.ts +0 -6
- package/src/infrastructure/utils/calculators/passwordStrengthCalculator.ts +6 -34
- package/src/infrastructure/utils/calculators/userProfileCalculator.ts +0 -29
- package/src/infrastructure/utils/listener/authListenerStateHandler.ts +3 -1
- package/src/infrastructure/utils/listener/listenerState.util.ts +3 -1
- package/src/infrastructure/utils/listener/setupListener.ts +10 -4
- package/src/infrastructure/utils/safeCallback.ts +12 -4
- package/src/infrastructure/utils/validation/types.ts +7 -11
- package/src/init/createAuthInitModule.ts +9 -3
- package/src/presentation/components/AccountActions.tsx +3 -1
- package/src/presentation/components/AuthErrorDisplay.tsx +6 -5
- package/src/presentation/components/PasswordMatchIndicator.tsx +5 -6
- package/src/presentation/components/PasswordStrengthIndicator.tsx +14 -17
- package/src/presentation/hooks/README.md +0 -57
- package/src/presentation/hooks/useAccountManagement.md +0 -1
- package/src/presentation/hooks/useLoginForm.ts +10 -1
- package/src/presentation/hooks/usePasswordPromptNavigation.ts +3 -1
- package/src/presentation/providers/AuthProvider.tsx +3 -1
- package/src/presentation/stores/authModalStore.ts +6 -2
- package/src/presentation/utils/passwordPromptCallback.ts +3 -1
- package/src/presentation/hooks/useProfileEdit.ts +0 -83
- package/src/presentation/hooks/useSocialLogin.md +0 -381
- package/src/presentation/hooks/useSocialLogin.ts +0 -95
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.71",
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ export { initializeAuth, isAuthInitialized, resetAuthInitialization } from './in
|
|
|
32
32
|
export type { InitializeAuthOptions } from './infrastructure/services/initializeAuth';
|
|
33
33
|
export { validateEmail, validatePasswordForLogin, validatePasswordForRegister, validatePasswordConfirmation, validateDisplayName } from './infrastructure/utils/AuthValidation';
|
|
34
34
|
export type { ValidationResult, PasswordStrengthResult, PasswordRequirements } from './infrastructure/utils/AuthValidation';
|
|
35
|
-
export type {
|
|
35
|
+
export type { FormValidationError, FormValidationResult } from './infrastructure/utils/validation/types';
|
|
36
36
|
export { SECURITY_LIMITS, sanitizeEmail, sanitizePassword, sanitizeName } from './infrastructure/utils/validation/sanitization';
|
|
37
37
|
export type { SecurityLimitKey } from './infrastructure/utils/validation/sanitization';
|
|
38
38
|
export { isEmpty, isEmptyEmail, isEmptyPassword, isEmptyName, isNotEmpty, hasContent } from './infrastructure/utils/validation/validationHelpers';
|
|
@@ -59,16 +59,10 @@ export {
|
|
|
59
59
|
getFirstErrorMessage,
|
|
60
60
|
// User Profile Calculator
|
|
61
61
|
calculateUserProfileDisplay,
|
|
62
|
-
calculateDisplayName,
|
|
63
|
-
hasUserAvatar,
|
|
64
|
-
getAvatarUrl,
|
|
65
62
|
// Password Strength Calculator
|
|
66
63
|
calculatePasswordRequirements,
|
|
67
64
|
calculatePasswordsMatch,
|
|
68
|
-
calculateConfirmationError,
|
|
69
|
-
calculatePasswordValidity,
|
|
70
65
|
calculatePasswordValidation,
|
|
71
|
-
hasMinLength,
|
|
72
66
|
calculatePasswordStrength,
|
|
73
67
|
} from './infrastructure/utils/calculators';
|
|
74
68
|
|
|
@@ -89,10 +83,6 @@ export { useUserProfile } from './presentation/hooks/useUserProfile';
|
|
|
89
83
|
export type { UserProfileData, UseUserProfileParams } from './presentation/hooks/useUserProfile';
|
|
90
84
|
export { useAccountManagement } from './presentation/hooks/useAccountManagement';
|
|
91
85
|
export type { UseAccountManagementReturn, UseAccountManagementOptions } from './presentation/hooks/useAccountManagement';
|
|
92
|
-
export { useProfileEdit } from './presentation/hooks/useProfileEdit';
|
|
93
|
-
export type { ProfileEditFormState, UseProfileEditReturn } from './presentation/hooks/useProfileEdit';
|
|
94
|
-
export { useSocialLogin } from './presentation/hooks/useSocialLogin';
|
|
95
|
-
export type { UseSocialLoginConfig, UseSocialLoginResult } from './presentation/hooks/useSocialLogin';
|
|
96
86
|
export { useAppleAuth } from './presentation/hooks/useAppleAuth';
|
|
97
87
|
export type { UseAppleAuthResult } from './presentation/hooks/useAppleAuth';
|
|
98
88
|
export { useAuthBottomSheet } from './presentation/hooks/useAuthBottomSheet';
|
|
@@ -20,7 +20,9 @@ export class AnonymousModeService {
|
|
|
20
20
|
this.isAnonymousMode = value === "true";
|
|
21
21
|
return this.isAnonymousMode;
|
|
22
22
|
} catch (error) {
|
|
23
|
-
|
|
23
|
+
if (__DEV__) {
|
|
24
|
+
console.error('[AnonymousModeService] Failed to load state:', error instanceof Error ? error.message : String(error));
|
|
25
|
+
}
|
|
24
26
|
this.isAnonymousMode = false;
|
|
25
27
|
return false;
|
|
26
28
|
}
|
|
@@ -31,7 +33,9 @@ export class AnonymousModeService {
|
|
|
31
33
|
await storageProvider.set(this.storageKey, value.toString());
|
|
32
34
|
return true;
|
|
33
35
|
} catch (error) {
|
|
34
|
-
|
|
36
|
+
if (__DEV__) {
|
|
37
|
+
console.error('[AnonymousModeService] Failed to save state:', error instanceof Error ? error.message : String(error));
|
|
38
|
+
}
|
|
35
39
|
return false;
|
|
36
40
|
}
|
|
37
41
|
}
|
|
@@ -42,7 +46,9 @@ export class AnonymousModeService {
|
|
|
42
46
|
this.isAnonymousMode = false;
|
|
43
47
|
return true;
|
|
44
48
|
} catch (error) {
|
|
45
|
-
|
|
49
|
+
if (__DEV__) {
|
|
50
|
+
console.error('[AnonymousModeService] Failed to clear state:', error instanceof Error ? error.message : String(error));
|
|
51
|
+
}
|
|
46
52
|
return false;
|
|
47
53
|
}
|
|
48
54
|
}
|
|
@@ -101,7 +101,9 @@ class AuthEventService {
|
|
|
101
101
|
try {
|
|
102
102
|
listener(payload);
|
|
103
103
|
} catch (error) {
|
|
104
|
-
|
|
104
|
+
if (__DEV__) {
|
|
105
|
+
console.error(`[AuthEventService] Listener error for "${event}":`, error);
|
|
106
|
+
}
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
}
|
|
@@ -82,7 +82,9 @@ export class AuthService {
|
|
|
82
82
|
if (this.anonymousModeService.getIsAnonymousMode() && this.storageProvider) {
|
|
83
83
|
const success = await this.anonymousModeService.clear(this.storageProvider);
|
|
84
84
|
if (!success) {
|
|
85
|
-
|
|
85
|
+
if (__DEV__) {
|
|
86
|
+
console.warn('[AuthService] Failed to clear anonymous mode from storage');
|
|
87
|
+
}
|
|
86
88
|
// Force clear in memory to maintain consistency and prevent stale state
|
|
87
89
|
// Storage clear failure shouldn't block auth flow, so we force update memory
|
|
88
90
|
this.anonymousModeService.setAnonymousMode(false);
|
|
@@ -49,7 +49,9 @@ export function createAuthStateHandler(
|
|
|
49
49
|
try {
|
|
50
50
|
await ensureUserDocument(user, extras);
|
|
51
51
|
} catch (error) {
|
|
52
|
-
|
|
52
|
+
if (__DEV__) {
|
|
53
|
+
console.error('[AuthStateHandler] Failed to ensure user document:', error);
|
|
54
|
+
}
|
|
53
55
|
// Continue execution - don't let user document creation failure block auth flow
|
|
54
56
|
}
|
|
55
57
|
|
|
@@ -25,18 +25,12 @@ export {
|
|
|
25
25
|
// User Profile Calculator
|
|
26
26
|
export {
|
|
27
27
|
calculateUserProfileDisplay,
|
|
28
|
-
calculateDisplayName,
|
|
29
|
-
hasUserAvatar,
|
|
30
|
-
getAvatarUrl,
|
|
31
28
|
} from "./userProfileCalculator";
|
|
32
29
|
|
|
33
30
|
// Password Strength Calculator
|
|
34
31
|
export {
|
|
35
32
|
calculatePasswordRequirements,
|
|
36
33
|
calculatePasswordsMatch,
|
|
37
|
-
calculateConfirmationError,
|
|
38
|
-
calculatePasswordValidity,
|
|
39
34
|
calculatePasswordValidation,
|
|
40
|
-
hasMinLength,
|
|
41
35
|
calculatePasswordStrength,
|
|
42
36
|
} from "./passwordStrengthCalculator";
|
|
@@ -50,31 +50,6 @@ export function calculatePasswordsMatch(
|
|
|
50
50
|
return !!(password && confirmPassword && password === confirmPassword);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/**
|
|
54
|
-
* Calculate password confirmation error
|
|
55
|
-
*/
|
|
56
|
-
export function calculateConfirmationError(
|
|
57
|
-
password: string,
|
|
58
|
-
confirmPassword: string
|
|
59
|
-
): string | null {
|
|
60
|
-
if (!confirmPassword) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const result = validatePasswordConfirmation(password, confirmPassword);
|
|
65
|
-
return result.error ?? null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Calculate overall password validity
|
|
70
|
-
*/
|
|
71
|
-
export function calculatePasswordValidity(
|
|
72
|
-
requirements: PasswordRequirements,
|
|
73
|
-
passwordsMatch: boolean
|
|
74
|
-
): boolean {
|
|
75
|
-
return requirements.hasMinLength && passwordsMatch;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
53
|
/**
|
|
79
54
|
* Calculate all password validation state at once
|
|
80
55
|
* More efficient than calling individual functions
|
|
@@ -91,10 +66,14 @@ export function calculatePasswordValidation(
|
|
|
91
66
|
const passwordsMatch = calculatePasswordsMatch(password, confirmPassword);
|
|
92
67
|
|
|
93
68
|
// Calculate confirmation error
|
|
94
|
-
|
|
69
|
+
let confirmationError: string | null = null;
|
|
70
|
+
if (confirmPassword) {
|
|
71
|
+
const result = validatePasswordConfirmation(password, confirmPassword);
|
|
72
|
+
confirmationError = result.error ?? null;
|
|
73
|
+
}
|
|
95
74
|
|
|
96
75
|
// Calculate overall validity
|
|
97
|
-
const isValid =
|
|
76
|
+
const isValid = requirements.hasMinLength && passwordsMatch;
|
|
98
77
|
|
|
99
78
|
return {
|
|
100
79
|
requirements,
|
|
@@ -104,13 +83,6 @@ export function calculatePasswordValidation(
|
|
|
104
83
|
};
|
|
105
84
|
}
|
|
106
85
|
|
|
107
|
-
/**
|
|
108
|
-
* Quick check if password meets minimum length requirement
|
|
109
|
-
*/
|
|
110
|
-
export function hasMinLength(password: string, minLength: number): boolean {
|
|
111
|
-
return password.length >= minLength;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
86
|
/**
|
|
115
87
|
* Calculate password strength score (0-100)
|
|
116
88
|
* Can be extended for more sophisticated strength calculation
|
|
@@ -46,32 +46,3 @@ export function calculateUserProfileDisplay(
|
|
|
46
46
|
accountSettingsRoute: accountRoute,
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Get display name for user
|
|
52
|
-
* Extracts display name calculation for reusability
|
|
53
|
-
*/
|
|
54
|
-
export function calculateDisplayName(
|
|
55
|
-
user: AuthUser,
|
|
56
|
-
anonymousDisplayName: string = "Anonymous User"
|
|
57
|
-
): string {
|
|
58
|
-
if (user.isAnonymous) {
|
|
59
|
-
return anonymousDisplayName;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return user.displayName || user.email || anonymousDisplayName;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Check if user has avatar
|
|
67
|
-
*/
|
|
68
|
-
export function hasUserAvatar(user: AuthUser): boolean {
|
|
69
|
-
return !!user.photoURL;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get avatar URL if available
|
|
74
|
-
*/
|
|
75
|
-
export function getAvatarUrl(user: AuthUser): string | undefined {
|
|
76
|
-
return user.photoURL || undefined;
|
|
77
|
-
}
|
|
@@ -42,7 +42,9 @@ export function handleAuthStateChange(
|
|
|
42
42
|
// Call user callback with proper error handling for async callbacks
|
|
43
43
|
safeCallbackSync(onAuthStateChange, [user], '[AuthListener]');
|
|
44
44
|
} catch (error) {
|
|
45
|
-
|
|
45
|
+
if (__DEV__) {
|
|
46
|
+
console.error("[AuthListener] Error handling auth state change:", error);
|
|
47
|
+
}
|
|
46
48
|
// Ensure we don't leave the app in a bad state
|
|
47
49
|
store.setInitialized(true);
|
|
48
50
|
store.setLoading(false);
|
|
@@ -119,7 +119,9 @@ export function resetListenerState(): void {
|
|
|
119
119
|
try {
|
|
120
120
|
state.unsubscribe();
|
|
121
121
|
} catch (error) {
|
|
122
|
-
|
|
122
|
+
if (__DEV__) {
|
|
123
|
+
console.error('[ListenerState] Error during unsubscribe:', error);
|
|
124
|
+
}
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
state.initialized = false;
|
|
@@ -10,6 +10,8 @@ import { getAuthService } from "../../services/AuthService";
|
|
|
10
10
|
import { completeInitialization, setUnsubscribe } from "./listenerState.util";
|
|
11
11
|
import { handleAuthStateChange } from "./authListenerStateHandler";
|
|
12
12
|
|
|
13
|
+
const AUTH_LISTENER_TIMEOUT_MS = 10000;
|
|
14
|
+
|
|
13
15
|
type StoreActions = AuthActions;
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -32,15 +34,17 @@ export function setupAuthListener(
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
// Safety timeout: if listener doesn't trigger within
|
|
37
|
+
// Safety timeout: if listener doesn't trigger within AUTH_LISTENER_TIMEOUT_MS, mark as initialized
|
|
36
38
|
let hasTriggered = false;
|
|
37
39
|
const timeout = setTimeout(() => {
|
|
38
40
|
if (!hasTriggered) {
|
|
39
|
-
|
|
41
|
+
if (__DEV__) {
|
|
42
|
+
console.warn("[AuthListener] Auth listener timeout - marking as initialized");
|
|
43
|
+
}
|
|
40
44
|
store.setInitialized(true);
|
|
41
45
|
store.setLoading(false);
|
|
42
46
|
}
|
|
43
|
-
},
|
|
47
|
+
}, AUTH_LISTENER_TIMEOUT_MS);
|
|
44
48
|
|
|
45
49
|
try {
|
|
46
50
|
const unsubscribe = onIdTokenChanged(auth, (user) => {
|
|
@@ -55,7 +59,9 @@ export function setupAuthListener(
|
|
|
55
59
|
} catch (error) {
|
|
56
60
|
clearTimeout(timeout);
|
|
57
61
|
// If listener setup fails, ensure we clean up and mark as initialized
|
|
58
|
-
|
|
62
|
+
if (__DEV__) {
|
|
63
|
+
console.error("[AuthListener] Failed to setup auth listener:", error);
|
|
64
|
+
}
|
|
59
65
|
completeInitialization();
|
|
60
66
|
store.setLoading(false);
|
|
61
67
|
store.setInitialized(true);
|
|
@@ -34,12 +34,16 @@ export async function safeCallback<T extends unknown[]>(
|
|
|
34
34
|
// If callback returns a promise, await it and catch rejections
|
|
35
35
|
if (result && typeof result.then === 'function') {
|
|
36
36
|
await result.catch((error) => {
|
|
37
|
-
|
|
37
|
+
if (__DEV__) {
|
|
38
|
+
console.error(`${errorPrefix} User callback promise rejected:`, error);
|
|
39
|
+
}
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
} catch (error) {
|
|
41
43
|
// Catch synchronous errors
|
|
42
|
-
|
|
44
|
+
if (__DEV__) {
|
|
45
|
+
console.error(`${errorPrefix} User callback error:`, error);
|
|
46
|
+
}
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -70,11 +74,15 @@ export function safeCallbackSync<T extends unknown[]>(
|
|
|
70
74
|
// If callback returns a promise, catch rejections but don't await
|
|
71
75
|
if (result && typeof result.then === 'function') {
|
|
72
76
|
result.catch((error) => {
|
|
73
|
-
|
|
77
|
+
if (__DEV__) {
|
|
78
|
+
console.error(`${errorPrefix} User callback promise rejected:`, error);
|
|
79
|
+
}
|
|
74
80
|
});
|
|
75
81
|
}
|
|
76
82
|
} catch (error) {
|
|
77
83
|
// Catch synchronous errors
|
|
78
|
-
|
|
84
|
+
if (__DEV__) {
|
|
85
|
+
console.error(`${errorPrefix} User callback error:`, error);
|
|
86
|
+
}
|
|
79
87
|
}
|
|
80
88
|
}
|
|
@@ -3,19 +3,12 @@
|
|
|
3
3
|
* Shared type definitions for validation results across the application
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Base validation result
|
|
8
|
-
* Common interface for all validation results
|
|
9
|
-
*/
|
|
10
|
-
export interface BaseValidationResult {
|
|
11
|
-
isValid: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
6
|
/**
|
|
15
7
|
* Single field validation result
|
|
16
8
|
* Used for validating individual fields (email, password, etc.)
|
|
17
9
|
*/
|
|
18
|
-
export interface ValidationResult
|
|
10
|
+
export interface ValidationResult {
|
|
11
|
+
isValid: boolean;
|
|
19
12
|
error?: string;
|
|
20
13
|
}
|
|
21
14
|
|
|
@@ -32,7 +25,8 @@ export interface FormValidationError {
|
|
|
32
25
|
* Multi-field form validation result
|
|
33
26
|
* Used for validating entire forms with multiple fields
|
|
34
27
|
*/
|
|
35
|
-
export interface FormValidationResult
|
|
28
|
+
export interface FormValidationResult {
|
|
29
|
+
isValid: boolean;
|
|
36
30
|
errors: FormValidationError[];
|
|
37
31
|
}
|
|
38
32
|
|
|
@@ -48,6 +42,8 @@ export interface PasswordRequirements {
|
|
|
48
42
|
* Password strength validation result
|
|
49
43
|
* Extends ValidationResult with password-specific requirements
|
|
50
44
|
*/
|
|
51
|
-
export interface PasswordStrengthResult
|
|
45
|
+
export interface PasswordStrengthResult {
|
|
46
|
+
isValid: boolean;
|
|
47
|
+
error?: string;
|
|
52
48
|
requirements: PasswordRequirements;
|
|
53
49
|
}
|
|
@@ -92,7 +92,9 @@ export function createAuthInitModule(
|
|
|
92
92
|
try {
|
|
93
93
|
await onRestorePurchases();
|
|
94
94
|
} catch (error) {
|
|
95
|
-
|
|
95
|
+
if (__DEV__) {
|
|
96
|
+
console.error('[AuthInitModule] Purchase restoration failed:', error instanceof Error ? error.message : String(error));
|
|
97
|
+
}
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -101,7 +103,9 @@ export function createAuthInitModule(
|
|
|
101
103
|
try {
|
|
102
104
|
await onUserConverted(anonymousId, authenticatedId);
|
|
103
105
|
} catch (error) {
|
|
104
|
-
|
|
106
|
+
if (__DEV__) {
|
|
107
|
+
console.error('[AuthInitModule] onUserConverted callback failed:', error instanceof Error ? error.message : String(error));
|
|
108
|
+
}
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
},
|
|
@@ -109,7 +113,9 @@ export function createAuthInitModule(
|
|
|
109
113
|
|
|
110
114
|
return true;
|
|
111
115
|
} catch (error) {
|
|
112
|
-
|
|
116
|
+
if (__DEV__) {
|
|
117
|
+
console.error('[AuthInitModule] Auth initialization failed:', error instanceof Error ? error.message : String(error));
|
|
118
|
+
}
|
|
113
119
|
throw error;
|
|
114
120
|
}
|
|
115
121
|
},
|
|
@@ -58,7 +58,9 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
58
58
|
try {
|
|
59
59
|
await onLogout();
|
|
60
60
|
} catch (error) {
|
|
61
|
-
|
|
61
|
+
if (__DEV__) {
|
|
62
|
+
console.error('[AccountActions] Logout failed:', error instanceof Error ? error.message : String(error));
|
|
63
|
+
}
|
|
62
64
|
}
|
|
63
65
|
},
|
|
64
66
|
},
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth Error Display Component
|
|
3
3
|
* Displays authentication errors using design system tokens
|
|
4
|
+
* PERFORMANCE: Memoized to prevent unnecessary re-renders when parent updates
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import React from "react";
|
|
7
|
+
import React, { memo } from "react";
|
|
7
8
|
import { AlertInline, AlertService, AlertMode } from "@umituz/react-native-design-system/molecules";
|
|
8
9
|
|
|
9
10
|
interface AuthErrorDisplayProps {
|
|
10
11
|
error: string | null;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export const AuthErrorDisplay
|
|
14
|
-
error,
|
|
15
|
-
}) => {
|
|
14
|
+
export const AuthErrorDisplay = memo<AuthErrorDisplayProps>(({ error }) => {
|
|
16
15
|
const alert = React.useMemo(() => {
|
|
17
16
|
if (!error) return null;
|
|
18
17
|
return AlertService.createErrorAlert(error, undefined, {
|
|
@@ -25,7 +24,9 @@ export const AuthErrorDisplay: React.FC<AuthErrorDisplayProps> = ({
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
return <AlertInline alert={alert} />;
|
|
28
|
-
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
AuthErrorDisplay.displayName = 'AuthErrorDisplay';
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { memo } from "react";
|
|
2
2
|
import { View, StyleSheet } from "react-native";
|
|
3
3
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
4
4
|
import { AtomicText } from "@umituz/react-native-design-system/atoms";
|
|
@@ -13,10 +13,7 @@ interface PasswordMatchIndicatorProps {
|
|
|
13
13
|
isMatch: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export const PasswordMatchIndicator
|
|
17
|
-
translations,
|
|
18
|
-
isMatch,
|
|
19
|
-
}) => {
|
|
16
|
+
export const PasswordMatchIndicator = memo<PasswordMatchIndicatorProps>(({ translations, isMatch }) => {
|
|
20
17
|
const tokens = useAppDesignTokens();
|
|
21
18
|
const color = isMatch ? tokens.colors.success : tokens.colors.error;
|
|
22
19
|
const text = isMatch ? translations.match : translations.noMatch;
|
|
@@ -27,7 +24,9 @@ export const PasswordMatchIndicator: React.FC<PasswordMatchIndicatorProps> = ({
|
|
|
27
24
|
<AtomicText type="labelSmall" style={{ color }}>{text}</AtomicText>
|
|
28
25
|
</View>
|
|
29
26
|
);
|
|
30
|
-
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
PasswordMatchIndicator.displayName = 'PasswordMatchIndicator';
|
|
31
30
|
|
|
32
31
|
const styles = StyleSheet.create({
|
|
33
32
|
container: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useMemo, memo } from "react";
|
|
2
2
|
import { View, StyleSheet } from "react-native";
|
|
3
3
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
4
4
|
import { AtomicText } from "@umituz/react-native-design-system/atoms";
|
|
@@ -22,12 +22,7 @@ interface RequirementDotProps {
|
|
|
22
22
|
pendingColor: ColorVariant;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const RequirementDot
|
|
26
|
-
label,
|
|
27
|
-
isValid,
|
|
28
|
-
successColor,
|
|
29
|
-
pendingColor,
|
|
30
|
-
}) => {
|
|
25
|
+
const RequirementDot = memo<RequirementDotProps>(({ label, isValid, successColor, pendingColor }) => {
|
|
31
26
|
const tokens = useAppDesignTokens();
|
|
32
27
|
const colorKey = isValid ? successColor : pendingColor;
|
|
33
28
|
const dotColor = (tokens.colors as Record<string, string>)[colorKey] || tokens.colors.textTertiary;
|
|
@@ -40,20 +35,20 @@ const RequirementDot: React.FC<RequirementDotProps> = ({
|
|
|
40
35
|
</AtomicText>
|
|
41
36
|
</View>
|
|
42
37
|
);
|
|
43
|
-
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
RequirementDot.displayName = 'RequirementDot';
|
|
44
41
|
|
|
45
|
-
export const PasswordStrengthIndicator
|
|
46
|
-
translations,
|
|
47
|
-
requirements,
|
|
48
|
-
showLabels = true,
|
|
49
|
-
}) => {
|
|
42
|
+
export const PasswordStrengthIndicator = memo<PasswordStrengthIndicatorProps>(({ translations, requirements, showLabels = true }) => {
|
|
50
43
|
const tokens = useAppDesignTokens();
|
|
51
44
|
const successColor: ColorVariant = "success";
|
|
52
45
|
const pendingColor: ColorVariant = "textTertiary";
|
|
53
46
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
// PERFORMANCE: Memoize items array to prevent recreation on every render
|
|
48
|
+
const items = useMemo(
|
|
49
|
+
() => [{ key: "minLength" as const, label: translations.minLength, isValid: requirements.hasMinLength }],
|
|
50
|
+
[translations.minLength, requirements.hasMinLength]
|
|
51
|
+
);
|
|
57
52
|
|
|
58
53
|
if (!showLabels) {
|
|
59
54
|
return (
|
|
@@ -86,7 +81,9 @@ export const PasswordStrengthIndicator: React.FC<PasswordStrengthIndicatorProps>
|
|
|
86
81
|
))}
|
|
87
82
|
</View>
|
|
88
83
|
);
|
|
89
|
-
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
PasswordStrengthIndicator.displayName = 'PasswordStrengthIndicator';
|
|
90
87
|
|
|
91
88
|
const styles = StyleSheet.create({
|
|
92
89
|
container: {
|
|
@@ -125,32 +125,6 @@ import { useUserProfile } from '@umituz/react-native-auth';
|
|
|
125
125
|
|
|
126
126
|
**Documentation**: `useUserProfile.md`
|
|
127
127
|
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
**Purpose**: Profile update operations and form management
|
|
132
|
-
|
|
133
|
-
**When to Use**:
|
|
134
|
-
- Profile editing screens
|
|
135
|
-
- Settings screens
|
|
136
|
-
- Form state management
|
|
137
|
-
- Profile modifications
|
|
138
|
-
|
|
139
|
-
**Import Path**:
|
|
140
|
-
```typescript
|
|
141
|
-
import {
|
|
142
|
-
useProfileEdit
|
|
143
|
-
} from '@umituz/react-native-auth';
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
**Rules**:
|
|
148
|
-
- MUST validate before update
|
|
149
|
-
- MUST handle loading state
|
|
150
|
-
- MUST show errors to user
|
|
151
|
-
- MUST not allow anonymous updates
|
|
152
|
-
|
|
153
|
-
|
|
154
128
|
---
|
|
155
129
|
|
|
156
130
|
### useAccountManagement
|
|
@@ -180,37 +154,6 @@ import { useAccountManagement } from '@umituz/react-native-auth';
|
|
|
180
154
|
|
|
181
155
|
---
|
|
182
156
|
|
|
183
|
-
### useSocialLogin
|
|
184
|
-
|
|
185
|
-
**Purpose**: Google and Apple authentication
|
|
186
|
-
|
|
187
|
-
**When to Use**:
|
|
188
|
-
- Social authentication needed
|
|
189
|
-
- Want Google sign-in
|
|
190
|
-
- Want Apple sign-in (iOS)
|
|
191
|
-
- Unified social auth interface
|
|
192
|
-
|
|
193
|
-
**Import Path**:
|
|
194
|
-
```typescript
|
|
195
|
-
import {
|
|
196
|
-
useSocialLogin,
|
|
197
|
-
useGoogleAuth,
|
|
198
|
-
useAppleAuth
|
|
199
|
-
} from '@umituz/react-native-auth';
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**File**: `useSocialLogin.ts`
|
|
203
|
-
|
|
204
|
-
**Rules**:
|
|
205
|
-
- MUST configure providers before use
|
|
206
|
-
- MUST check provider availability
|
|
207
|
-
- MUST handle loading states
|
|
208
|
-
- MUST handle platform differences
|
|
209
|
-
|
|
210
|
-
**Documentation**: `useSocialLogin.md`
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
157
|
### useAuthBottomSheet
|
|
215
158
|
|
|
216
159
|
**Purpose**: Auth modal management
|
|
@@ -330,7 +330,6 @@ import { useAccountManagement } from '@umituz/react-native-auth';
|
|
|
330
330
|
|
|
331
331
|
- **`useAuth`** (`src/presentation/hooks/useAuth.ts`) - Authentication state
|
|
332
332
|
- **`useUserProfile`** (`src/presentation/hooks/useUserProfile.ts`) - Profile data
|
|
333
|
-
- **`useProfileEdit`** (`src/presentation/hooks/useProfileEdit.ts`) - Profile editing form
|
|
334
333
|
|
|
335
334
|
## Related Components
|
|
336
335
|
|
|
@@ -98,7 +98,16 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
|
|
|
98
98
|
} catch (err: unknown) {
|
|
99
99
|
setLocalError(handleAuthError(err));
|
|
100
100
|
}
|
|
101
|
-
}, [
|
|
101
|
+
}, [
|
|
102
|
+
fields.email,
|
|
103
|
+
fields.password,
|
|
104
|
+
signIn,
|
|
105
|
+
translations,
|
|
106
|
+
handleAuthError,
|
|
107
|
+
getErrorMessage,
|
|
108
|
+
clearFieldErrorsState,
|
|
109
|
+
setLocalError,
|
|
110
|
+
]);
|
|
102
111
|
|
|
103
112
|
const handleContinueAnonymously = useCallback(async () => {
|
|
104
113
|
try {
|
|
@@ -39,7 +39,9 @@ export const usePasswordPromptNavigation = (
|
|
|
39
39
|
})
|
|
40
40
|
);
|
|
41
41
|
} catch (error) {
|
|
42
|
-
|
|
42
|
+
if (__DEV__) {
|
|
43
|
+
console.error('[usePasswordPromptNavigation] Navigation failed:', error);
|
|
44
|
+
}
|
|
43
45
|
setPasswordPromptCallback(null);
|
|
44
46
|
resolve(null);
|
|
45
47
|
}
|
|
@@ -74,7 +74,9 @@ export function AuthProvider({ children, ErrorFallback = DefaultErrorFallback }:
|
|
|
74
74
|
try {
|
|
75
75
|
unsubscribe();
|
|
76
76
|
} catch (error) {
|
|
77
|
-
|
|
77
|
+
if (__DEV__) {
|
|
78
|
+
console.error('[AuthProvider] Cleanup failed:', error instanceof Error ? error.message : String(error));
|
|
79
|
+
}
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
};
|
|
@@ -78,11 +78,15 @@ export const useAuthModalStore = createStore<AuthModalState, AuthModalActions>({
|
|
|
78
78
|
// If it's a promise, catch rejections
|
|
79
79
|
if (result && typeof result.then === 'function') {
|
|
80
80
|
result.catch((error) => {
|
|
81
|
-
|
|
81
|
+
if (__DEV__) {
|
|
82
|
+
console.error('[AuthModalStore] Pending callback error:', error);
|
|
83
|
+
}
|
|
82
84
|
});
|
|
83
85
|
}
|
|
84
86
|
} catch (error) {
|
|
85
|
-
|
|
87
|
+
if (__DEV__) {
|
|
88
|
+
console.error('[AuthModalStore] Pending callback error:', error);
|
|
89
|
+
}
|
|
86
90
|
}
|
|
87
91
|
set({ pendingCallback: null });
|
|
88
92
|
}
|
|
@@ -3,7 +3,9 @@ let callback: ((value: string | null) => void) | null = null;
|
|
|
3
3
|
export const setPasswordPromptCallback = (cb: ((value: string | null) => void) | null): void => {
|
|
4
4
|
// Warn if overriding an existing callback (indicates potential bug)
|
|
5
5
|
if (callback && cb) {
|
|
6
|
-
|
|
6
|
+
if (__DEV__) {
|
|
7
|
+
console.warn('[passwordPromptCallback] Overriding existing callback - this may indicate a bug');
|
|
8
|
+
}
|
|
7
9
|
}
|
|
8
10
|
callback = cb;
|
|
9
11
|
};
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Profile Edit Hook
|
|
3
|
-
* Simple profile editing with form state management
|
|
4
|
-
* Apps provide image picker and backend update logic
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useState, useCallback } from "react";
|
|
8
|
-
import { validateProfileForm } from "../utils/form/validation/formValidators";
|
|
9
|
-
|
|
10
|
-
export interface ProfileEditFormState {
|
|
11
|
-
displayName: string;
|
|
12
|
-
email: string;
|
|
13
|
-
photoURL: string | null;
|
|
14
|
-
isModified: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface UseProfileEditReturn {
|
|
18
|
-
formState: ProfileEditFormState;
|
|
19
|
-
setDisplayName: (value: string) => void;
|
|
20
|
-
setEmail: (value: string) => void;
|
|
21
|
-
setPhotoURL: (value: string | null) => void;
|
|
22
|
-
resetForm: (initial: Partial<ProfileEditFormState>) => void;
|
|
23
|
-
validateForm: () => { isValid: boolean; errors: string[] };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const useProfileEdit = (
|
|
27
|
-
initialState: Partial<ProfileEditFormState> = {},
|
|
28
|
-
): UseProfileEditReturn => {
|
|
29
|
-
const [formState, setFormState] = useState<ProfileEditFormState>({
|
|
30
|
-
displayName: initialState.displayName || "",
|
|
31
|
-
email: initialState.email || "",
|
|
32
|
-
photoURL: initialState.photoURL || null,
|
|
33
|
-
isModified: false,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const setDisplayName = useCallback((value: string) => {
|
|
37
|
-
setFormState((prev) => ({ ...prev, displayName: value, isModified: true }));
|
|
38
|
-
}, []);
|
|
39
|
-
|
|
40
|
-
const setEmail = useCallback((value: string) => {
|
|
41
|
-
setFormState((prev) => ({ ...prev, email: value, isModified: true }));
|
|
42
|
-
}, []);
|
|
43
|
-
|
|
44
|
-
const setPhotoURL = useCallback((value: string | null) => {
|
|
45
|
-
setFormState((prev) => ({ ...prev, photoURL: value, isModified: true }));
|
|
46
|
-
}, []);
|
|
47
|
-
|
|
48
|
-
const resetForm = useCallback((initial: Partial<ProfileEditFormState>) => {
|
|
49
|
-
setFormState({
|
|
50
|
-
displayName: initial.displayName || "",
|
|
51
|
-
email: initial.email || "",
|
|
52
|
-
photoURL: initial.photoURL || null,
|
|
53
|
-
isModified: false,
|
|
54
|
-
});
|
|
55
|
-
}, []);
|
|
56
|
-
|
|
57
|
-
const validateForm = useCallback((): {
|
|
58
|
-
isValid: boolean;
|
|
59
|
-
errors: string[];
|
|
60
|
-
} => {
|
|
61
|
-
const result = validateProfileForm(
|
|
62
|
-
{
|
|
63
|
-
displayName: formState.displayName,
|
|
64
|
-
email: formState.email,
|
|
65
|
-
},
|
|
66
|
-
key => key // Identity function for translation
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
isValid: result.isValid,
|
|
71
|
-
errors: result.errors.map((e) => e.message),
|
|
72
|
-
};
|
|
73
|
-
}, [formState]);
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
formState,
|
|
77
|
-
setDisplayName,
|
|
78
|
-
setEmail,
|
|
79
|
-
setPhotoURL,
|
|
80
|
-
resetForm,
|
|
81
|
-
validateForm,
|
|
82
|
-
};
|
|
83
|
-
};
|
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
# Social Login Hooks
|
|
2
|
-
|
|
3
|
-
Hooks for Google and Apple social authentication.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## useSocialLogin
|
|
8
|
-
|
|
9
|
-
General social login functionality wrapper.
|
|
10
|
-
|
|
11
|
-
### Strategy
|
|
12
|
-
|
|
13
|
-
**Purpose**: Provides unified interface for Google and Apple social authentication by wrapping `@umituz/react-native-firebase`'s `useSocialAuth`.
|
|
14
|
-
|
|
15
|
-
**When to Use**:
|
|
16
|
-
- Need both Google and Apple auth
|
|
17
|
-
- Want unified social auth interface
|
|
18
|
-
- Prefer single hook for multiple providers
|
|
19
|
-
- Don't need provider-specific features
|
|
20
|
-
|
|
21
|
-
**Import Path**:
|
|
22
|
-
```typescript
|
|
23
|
-
import { useSocialLogin } from '@umituz/react-native-auth';
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
**Hook Location**: `src/presentation/hooks/useSocialLogin.ts`
|
|
27
|
-
|
|
28
|
-
**Dependency**: `@umituz/react-native-firebase`
|
|
29
|
-
|
|
30
|
-
### Rules
|
|
31
|
-
|
|
32
|
-
**MUST**:
|
|
33
|
-
- Configure provider settings before use
|
|
34
|
-
- Check provider availability before showing buttons
|
|
35
|
-
- Handle loading states appropriately
|
|
36
|
-
- Display errors to users
|
|
37
|
-
- Check `googleConfigured` before using Google
|
|
38
|
-
- Check `appleAvailable` before using Apple
|
|
39
|
-
|
|
40
|
-
**MUST NOT**:
|
|
41
|
-
- Show unavailable provider buttons
|
|
42
|
-
- Assume provider is configured
|
|
43
|
-
- Ignore loading states
|
|
44
|
-
- Bypass error handling
|
|
45
|
-
|
|
46
|
-
### Constraints
|
|
47
|
-
|
|
48
|
-
**CONFIGURATION PARAMETERS**:
|
|
49
|
-
- `google?: GoogleAuthConfig` - Google client IDs
|
|
50
|
-
- `apple?: { enabled: boolean }` - Apple enable flag
|
|
51
|
-
|
|
52
|
-
**RETURN VALUES**:
|
|
53
|
-
- `signInWithGoogle: () => Promise<SocialAuthResult>` - Google sign-in
|
|
54
|
-
- `signInWithApple: () => Promise<SocialAuthResult>` - Apple sign-in
|
|
55
|
-
- `googleLoading: boolean` - Google loading state
|
|
56
|
-
- `appleLoading: boolean` - Apple loading state
|
|
57
|
-
- `googleConfigured: boolean` - Google configuration status
|
|
58
|
-
- `appleAvailable: boolean` - Apple availability status
|
|
59
|
-
|
|
60
|
-
**PLATFORM LIMITATIONS**:
|
|
61
|
-
- Google: All platforms
|
|
62
|
-
- Apple: iOS only (returns `appleAvailable: false` on other platforms)
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## useGoogleAuth
|
|
67
|
-
|
|
68
|
-
Google OAuth authentication with expo-auth-session.
|
|
69
|
-
|
|
70
|
-
### Strategy
|
|
71
|
-
|
|
72
|
-
**Purpose**: Complete Google OAuth flow using expo-auth-session for cross-platform support.
|
|
73
|
-
|
|
74
|
-
**When to Use**:
|
|
75
|
-
- Need Google OAuth specifically
|
|
76
|
-
- Want full OAuth flow (not just Firebase)
|
|
77
|
-
- Need web/expo support
|
|
78
|
-
- Require custom Google configuration
|
|
79
|
-
|
|
80
|
-
**Import Path**:
|
|
81
|
-
```typescript
|
|
82
|
-
import { useGoogleAuth } from '@umituz/react-native-auth';
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
**Hook Location**: `src/presentation/hooks/useSocialLogin.ts`
|
|
86
|
-
|
|
87
|
-
**Dependencies**:
|
|
88
|
-
- `expo-auth-session`
|
|
89
|
-
- `expo-web-browser`
|
|
90
|
-
- `@react-native-firebase/auth`
|
|
91
|
-
|
|
92
|
-
### Rules
|
|
93
|
-
|
|
94
|
-
**MUST**:
|
|
95
|
-
- Provide at least one client ID
|
|
96
|
-
- Call `WebBrowser.maybeCompleteAuthSession()` in app root
|
|
97
|
-
- Check `googleConfigured` before showing button
|
|
98
|
-
- Handle loading and error states
|
|
99
|
-
- Support platform-specific client IDs
|
|
100
|
-
|
|
101
|
-
**MUST NOT**:
|
|
102
|
-
- Call without client ID configuration
|
|
103
|
-
- Skip web browser setup
|
|
104
|
-
- Ignore platform differences
|
|
105
|
-
- Show button if not configured
|
|
106
|
-
|
|
107
|
-
### Constraints
|
|
108
|
-
|
|
109
|
-
**CLIENT IDs REQUIRED**:
|
|
110
|
-
- `iosClientId?: string` - iOS client ID (optional)
|
|
111
|
-
- `webClientId?: string` - Web client ID (optional)
|
|
112
|
-
- `androidClientId?: string` - Android client ID (optional)
|
|
113
|
-
- At least one MUST be provided
|
|
114
|
-
|
|
115
|
-
**RETURN VALUES**:
|
|
116
|
-
- `signInWithGoogle: () => Promise<SocialAuthResult>` - Sign-in function
|
|
117
|
-
- `googleLoading: boolean` - Loading state
|
|
118
|
-
- `googleConfigured: boolean` - Configuration status
|
|
119
|
-
|
|
120
|
-
**PLATFORM BEHAVIOR**:
|
|
121
|
-
- iOS: Uses `iosClientId` or falls back to `webClientId`
|
|
122
|
-
- Android: Uses `androidClientId` or falls back to `webClientId`
|
|
123
|
-
- Web: Uses `webClientId`
|
|
124
|
-
- Requires OAuth 2.0 client ID from Google Cloud Console
|
|
125
|
-
|
|
126
|
-
**SETUP REQUIREMENTS**:
|
|
127
|
-
- OAuth 2.0 Client ID from Google Cloud Console
|
|
128
|
-
- Authorized redirect URI (auto-configured by expo)
|
|
129
|
-
- Web browser warm-up (maybeCompleteAuthSession)
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## useAppleAuth
|
|
134
|
-
|
|
135
|
-
Apple Sign-In authentication wrapper.
|
|
136
|
-
|
|
137
|
-
### Strategy
|
|
138
|
-
|
|
139
|
-
**Purpose**: Convenience wrapper for Apple Sign-In functionality on iOS.
|
|
140
|
-
|
|
141
|
-
**When to Use**:
|
|
142
|
-
- Need Apple Sign-In specifically
|
|
143
|
-
- Targeting iOS users
|
|
144
|
-
- Want simple Apple auth integration
|
|
145
|
-
- Don't need custom Apple configuration
|
|
146
|
-
|
|
147
|
-
**Import Path**:
|
|
148
|
-
```typescript
|
|
149
|
-
import { useAppleAuth } from '@umituz/react-native-auth';
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**Hook Location**: `src/presentation/hooks/useSocialLogin.ts`
|
|
153
|
-
|
|
154
|
-
**Dependencies**:
|
|
155
|
-
- `expo-apple-authentication`
|
|
156
|
-
- `@react-native-firebase/auth`
|
|
157
|
-
|
|
158
|
-
### Rules
|
|
159
|
-
|
|
160
|
-
**MUST**:
|
|
161
|
-
- Check `appleAvailable` before showing button
|
|
162
|
-
- Handle loading and error states
|
|
163
|
-
- Only show on iOS platform
|
|
164
|
-
- Support Apple Sign-In requirements
|
|
165
|
-
|
|
166
|
-
**MUST NOT**:
|
|
167
|
-
- Show Apple button on Android/Web
|
|
168
|
-
- Call without availability check
|
|
169
|
-
- Ignore Apple's guidelines
|
|
170
|
-
- Require Apple auth for all users
|
|
171
|
-
|
|
172
|
-
### Constraints
|
|
173
|
-
|
|
174
|
-
**RETURN VALUES**:
|
|
175
|
-
- `signInWithApple: () => Promise<SocialAuthResult>` - Sign-in function
|
|
176
|
-
- `appleLoading: boolean` - Loading state
|
|
177
|
-
- `appleAvailable: boolean` - iOS availability status
|
|
178
|
-
|
|
179
|
-
**PLATFORM SUPPORT**:
|
|
180
|
-
- iOS: ✅ Fully supported (if configured)
|
|
181
|
-
- Android: ❌ Not supported (appleAvailable = false)
|
|
182
|
-
- Web: ❌ Not supported (appleAvailable = false)
|
|
183
|
-
|
|
184
|
-
**SETUP REQUIREMENTS**:
|
|
185
|
-
- Apple Developer account
|
|
186
|
-
- App ID with Sign In with Apple enabled
|
|
187
|
-
- Firebase Auth with Apple enabled
|
|
188
|
-
- Physical device (may not work in simulator)
|
|
189
|
-
|
|
190
|
-
**APPLE GUIDELINES**:
|
|
191
|
-
- Must offer alternative auth methods
|
|
192
|
-
- Cannot require Apple as only option
|
|
193
|
-
- Must follow Apple's UI guidelines
|
|
194
|
-
- Button design per Apple specifications
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## SocialAuthResult
|
|
199
|
-
|
|
200
|
-
Common return type for all social auth functions.
|
|
201
|
-
|
|
202
|
-
### Structure
|
|
203
|
-
|
|
204
|
-
**PROPERTIES**:
|
|
205
|
-
- `success: boolean` - Operation success status
|
|
206
|
-
- `error?: string` - Error message if failed
|
|
207
|
-
- `user?: AuthUser` - User object if successful
|
|
208
|
-
|
|
209
|
-
**Rules**:
|
|
210
|
-
- MUST check `success` before using `user`
|
|
211
|
-
- MUST handle `error` if `success = false`
|
|
212
|
-
- MUST NOT assume `user` exists without checking
|
|
213
|
-
|
|
214
|
-
**Constraints**:
|
|
215
|
-
- Always returns success boolean
|
|
216
|
-
- User object only present on success
|
|
217
|
-
- Error string only present on failure
|
|
218
|
-
- Used by all social auth functions
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## Configuration Strategy
|
|
223
|
-
|
|
224
|
-
### Strategy
|
|
225
|
-
|
|
226
|
-
**Purpose**: Proper setup and configuration for social authentication.
|
|
227
|
-
|
|
228
|
-
**Rules**:
|
|
229
|
-
- MUST configure OAuth providers in Firebase Console
|
|
230
|
-
- MUST set up projects in provider consoles
|
|
231
|
-
- MUST provide correct client IDs
|
|
232
|
-
- MUST test on physical devices
|
|
233
|
-
|
|
234
|
-
**MUST NOT**:
|
|
235
|
-
- Use development client IDs in production
|
|
236
|
-
- Skip provider console setup
|
|
237
|
-
- Assume configuration is correct
|
|
238
|
-
- Test only on simulator
|
|
239
|
-
|
|
240
|
-
### Constraints
|
|
241
|
-
|
|
242
|
-
**FIREBASE SETUP**:
|
|
243
|
-
- Enable Google Sign-In in Firebase Auth
|
|
244
|
-
- Enable Apple Sign-In in Firebase Auth
|
|
245
|
-
- Configure OAuth consent screen
|
|
246
|
-
- Set up authorized domains
|
|
247
|
-
|
|
248
|
-
**GOOGLE CONSOLE SETUP**:
|
|
249
|
-
1. Go to Google Cloud Console
|
|
250
|
-
2. Create OAuth 2.0 Client IDs
|
|
251
|
-
3. Add authorized redirect URIs
|
|
252
|
-
4. Copy client IDs to app config
|
|
253
|
-
|
|
254
|
-
**APPLE SETUP**:
|
|
255
|
-
1. Apple Developer account
|
|
256
|
-
2. Enable Sign In with Apple for App ID
|
|
257
|
-
3. Create provider ID in Firebase
|
|
258
|
-
4. Configure certificates
|
|
259
|
-
|
|
260
|
-
---
|
|
261
|
-
|
|
262
|
-
## Error Handling
|
|
263
|
-
|
|
264
|
-
### Strategy
|
|
265
|
-
|
|
266
|
-
**Purpose**: Graceful handling of social authentication failures.
|
|
267
|
-
|
|
268
|
-
**Rules**:
|
|
269
|
-
- MUST distinguish cancellation from errors
|
|
270
|
-
- MUST show user-friendly error messages
|
|
271
|
-
- MUST allow retry after failures
|
|
272
|
-
- MUST not crash on auth failures
|
|
273
|
-
|
|
274
|
-
**MUST NOT**:
|
|
275
|
-
- Show raw OAuth errors
|
|
276
|
-
- Block retry indefinitely
|
|
277
|
-
- Crash on provider errors
|
|
278
|
-
- Expose sensitive tokens in errors
|
|
279
|
-
|
|
280
|
-
### Constraints
|
|
281
|
-
|
|
282
|
-
**ERROR TYPES**:
|
|
283
|
-
- User cancellation: Silent handling
|
|
284
|
-
- Network errors: Retry prompt
|
|
285
|
-
- Configuration errors: Developer message
|
|
286
|
-
- Provider errors: Generic "try again"
|
|
287
|
-
|
|
288
|
-
**CANCELLATION HANDLING**:
|
|
289
|
-
- Check error message for "cancelled"
|
|
290
|
-
- Don't show error for cancellation
|
|
291
|
-
- Allow retry without blocking
|
|
292
|
-
- Silent return preferred
|
|
293
|
-
|
|
294
|
-
---
|
|
295
|
-
|
|
296
|
-
## Security Requirements
|
|
297
|
-
|
|
298
|
-
### Strategy
|
|
299
|
-
|
|
300
|
-
**Purpose**: Secure social authentication implementation.
|
|
301
|
-
|
|
302
|
-
**Rules**:
|
|
303
|
-
- MUST use HTTPS for all OAuth endpoints
|
|
304
|
-
- MUST store tokens securely
|
|
305
|
-
- MUST validate tokens server-side
|
|
306
|
-
- MUST never log OAuth credentials
|
|
307
|
-
- MUST implement token refresh
|
|
308
|
-
|
|
309
|
-
**MUST NOT**:
|
|
310
|
-
- Store tokens in AsyncStorage
|
|
311
|
-
- Log OAuth responses
|
|
312
|
-
- Skip server-side validation
|
|
313
|
-
- Expose client secrets
|
|
314
|
-
- Use HTTP for OAuth flows
|
|
315
|
-
|
|
316
|
-
### Constraints
|
|
317
|
-
|
|
318
|
-
**TOKEN HANDLING**:
|
|
319
|
-
- Tokens managed by Firebase SDK
|
|
320
|
-
- Secure storage automatic
|
|
321
|
-
- App never accesses refresh tokens
|
|
322
|
-
- ID tokens available for API calls
|
|
323
|
-
- Token refresh handled by Firebase
|
|
324
|
-
|
|
325
|
-
**CLIENT SECRETS**:
|
|
326
|
-
- Never included in app code
|
|
327
|
-
- Public client flows only
|
|
328
|
-
- Server-side validation required
|
|
329
|
-
- Firebase manages credentials
|
|
330
|
-
|
|
331
|
-
---
|
|
332
|
-
|
|
333
|
-
## Platform-Specific Behavior
|
|
334
|
-
|
|
335
|
-
### Strategy
|
|
336
|
-
|
|
337
|
-
**Purpose**: Optimize social auth experience for each platform.
|
|
338
|
-
|
|
339
|
-
**Rules**:
|
|
340
|
-
- MUST respect platform limitations
|
|
341
|
-
- MUST use appropriate client IDs
|
|
342
|
-
- MUST handle platform-specific errors
|
|
343
|
-
- MUST test on target platforms
|
|
344
|
-
|
|
345
|
-
**Constraints**:
|
|
346
|
-
|
|
347
|
-
**iOS**:
|
|
348
|
-
- Apple Sign-In available
|
|
349
|
-
- Google uses app-based OAuth
|
|
350
|
-
- Requires Info.plist configuration
|
|
351
|
-
- Best on physical devices
|
|
352
|
-
|
|
353
|
-
**Android**:
|
|
354
|
-
- Apple Sign-In NOT available
|
|
355
|
-
- Google uses app-based OAuth
|
|
356
|
-
- Requires google-services.json
|
|
357
|
-
- Works on emulator
|
|
358
|
-
|
|
359
|
-
**Web**:
|
|
360
|
-
- Apple Sign-In NOT available
|
|
361
|
-
- Google uses popup OAuth
|
|
362
|
-
- Requires proper callback handling
|
|
363
|
-
- Browser popup blockers
|
|
364
|
-
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
## Related Hooks
|
|
368
|
-
|
|
369
|
-
- **`useAuth`** (`src/presentation/hooks/useAuth.ts`) - Core authentication state
|
|
370
|
-
- **`useAuthBottomSheet`** (`src/presentation/hooks/useAuthBottomSheet.md`) - Auth modal integration
|
|
371
|
-
|
|
372
|
-
## Related Components
|
|
373
|
-
|
|
374
|
-
- **`SocialLoginButtons`** (`src/presentation/components/SocialLoginButtons.md`) - Social auth UI
|
|
375
|
-
|
|
376
|
-
## External Dependencies
|
|
377
|
-
|
|
378
|
-
- **`@umituz/react-native-firebase`** - Firebase social auth wrapper
|
|
379
|
-
- **`expo-auth-session`** - OAuth session management
|
|
380
|
-
- **`expo-web-browser`** - Web browser for OAuth
|
|
381
|
-
- **`expo-apple-authentication`** - Apple Sign-In
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useSocialLogin Hook
|
|
3
|
-
* Provides unified social login functionality using Firebase package
|
|
4
|
-
*
|
|
5
|
-
* This hook wraps @umituz/react-native-firebase's useSocialAuth
|
|
6
|
-
* and provides a simple interface for social authentication.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* ```typescript
|
|
10
|
-
* const { signInWithGoogle, signInWithApple, googleLoading, appleLoading } = useSocialLogin({
|
|
11
|
-
* google: { webClientId: '...', iosClientId: '...' },
|
|
12
|
-
* apple: { enabled: true }
|
|
13
|
-
* });
|
|
14
|
-
* ```
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { useCallback } from "react";
|
|
18
|
-
import { Platform } from "react-native";
|
|
19
|
-
import {
|
|
20
|
-
useSocialAuth,
|
|
21
|
-
type SocialAuthConfig,
|
|
22
|
-
type SocialAuthResult,
|
|
23
|
-
} from "@umituz/react-native-firebase";
|
|
24
|
-
|
|
25
|
-
export interface UseSocialLoginConfig extends SocialAuthConfig {}
|
|
26
|
-
|
|
27
|
-
export interface UseSocialLoginResult {
|
|
28
|
-
/** Sign in with Google (handles OAuth flow internally if configured) */
|
|
29
|
-
signInWithGoogle: () => Promise<SocialAuthResult>;
|
|
30
|
-
/** Sign in with Apple */
|
|
31
|
-
signInWithApple: () => Promise<SocialAuthResult>;
|
|
32
|
-
/** Whether Google sign-in is in progress */
|
|
33
|
-
googleLoading: boolean;
|
|
34
|
-
/** Whether Apple sign-in is in progress */
|
|
35
|
-
appleLoading: boolean;
|
|
36
|
-
/** Whether Google is configured */
|
|
37
|
-
googleConfigured: boolean;
|
|
38
|
-
/** Whether Apple is available */
|
|
39
|
-
appleAvailable: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Hook for social authentication
|
|
44
|
-
* Integrates with @umituz/react-native-firebase for Firebase auth
|
|
45
|
-
*/
|
|
46
|
-
export function useSocialLogin(config?: UseSocialLoginConfig): UseSocialLoginResult {
|
|
47
|
-
const {
|
|
48
|
-
signInWithApple: firebaseSignInWithApple,
|
|
49
|
-
googleLoading,
|
|
50
|
-
appleLoading,
|
|
51
|
-
googleConfigured,
|
|
52
|
-
appleAvailable,
|
|
53
|
-
} = useSocialAuth(config);
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Sign in with Google
|
|
57
|
-
* Note: For full OAuth flow, use useGoogleAuth hook which handles
|
|
58
|
-
* expo-auth-session OAuth flow and Firebase authentication
|
|
59
|
-
*/
|
|
60
|
-
const signInWithGoogle = useCallback((): Promise<SocialAuthResult> => {
|
|
61
|
-
if (!googleConfigured) {
|
|
62
|
-
return Promise.resolve({ success: false, error: "Google Sign-In is not configured" });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return Promise.resolve({
|
|
66
|
-
success: false,
|
|
67
|
-
error: "Use useGoogleAuth hook for Google OAuth flow",
|
|
68
|
-
});
|
|
69
|
-
}, [googleConfigured]);
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Sign in with Apple (full flow handled by Firebase package)
|
|
73
|
-
*/
|
|
74
|
-
const signInWithApple = useCallback(async (): Promise<SocialAuthResult> => {
|
|
75
|
-
if (Platform.OS !== "ios") {
|
|
76
|
-
return { success: false, error: "Apple Sign-In is only available on iOS" };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!appleAvailable) {
|
|
80
|
-
return { success: false, error: "Apple Sign-In is not available" };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return firebaseSignInWithApple();
|
|
84
|
-
}, [appleAvailable, firebaseSignInWithApple]);
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
signInWithGoogle,
|
|
88
|
-
signInWithApple,
|
|
89
|
-
googleLoading,
|
|
90
|
-
appleLoading,
|
|
91
|
-
googleConfigured,
|
|
92
|
-
appleAvailable,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|