@umituz/react-native-auth 3.6.61 → 3.6.63
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 +5 -5
- package/src/application/ports/IAuthRepository.ts +3 -3
- package/src/index.ts +5 -18
- package/src/infrastructure/providers/FirebaseAuthProvider.ts +6 -2
- package/src/infrastructure/repositories/AuthRepository.ts +3 -3
- package/src/infrastructure/services/AuthEventService.ts +4 -0
- package/src/infrastructure/services/AuthService.ts +14 -36
- package/src/infrastructure/services/initializeAuth.ts +0 -15
- package/src/infrastructure/utils/AuthValidation.ts +5 -2
- package/src/infrastructure/utils/UserMapper.ts +9 -6
- package/src/infrastructure/utils/authStateHandler.ts +0 -7
- package/src/infrastructure/utils/validation/sanitization.ts +2 -89
- package/src/init/createAuthInitModule.ts +0 -15
- package/src/presentation/hooks/mutations/useAuthMutations.ts +3 -3
- package/src/presentation/hooks/useAuthBottomSheet.ts +10 -1
- package/src/presentation/hooks/useGoogleAuth.ts +43 -11
- package/src/presentation/stores/auth.selectors.ts +12 -6
- package/src/presentation/stores/authStore.ts +6 -9
- package/src/presentation/stores/initializeAuthListener.ts +44 -7
- package/src/application/ports/IAuthService.ts +0 -60
- package/src/domain/utils/migration.ts +0 -44
- package/src/infrastructure/adapters/UIProviderAdapter.ts +0 -43
- package/src/infrastructure/services/UserDocument.types.ts +0 -60
- package/src/infrastructure/services/UserDocumentService.ts +0 -86
- package/src/infrastructure/services/app-service-helpers.ts +0 -35
- package/src/infrastructure/types/UI.types.ts +0 -11
- package/src/infrastructure/utils/auth-tracker.util.ts +0 -23
- package/src/infrastructure/utils/userDocumentBuilder.util.ts +0 -114
- package/src/infrastructure/utils/validation/BaseValidators.ts +0 -35
- package/src/infrastructure/utils/validation/CollectionValidators.ts +0 -56
- package/src/infrastructure/utils/validation/DateValidators.ts +0 -71
- package/src/infrastructure/utils/validation/FormValidators.ts +0 -22
- package/src/infrastructure/utils/validation/NumberValidators.ts +0 -50
- package/src/infrastructure/utils/validation/StringValidators.ts +0 -55
- package/src/infrastructure/utils/validation/types.ts +0 -15
- package/src/presentation/screens/change-password/ChangePasswordScreen.tsx +0 -179
- package/src/presentation/screens/change-password/ChangePasswordScreen.types.ts +0 -77
- package/src/presentation/screens/change-password/RequirementItem.tsx +0 -47
- package/src/presentation/screens/change-password/index.ts +0 -6
- package/src/types/translations.types.ts +0 -89
|
@@ -29,21 +29,33 @@ export interface UseGoogleAuthResult {
|
|
|
29
29
|
signInWithGoogle: () => Promise<SocialAuthResult>;
|
|
30
30
|
googleLoading: boolean;
|
|
31
31
|
googleConfigured: boolean;
|
|
32
|
+
googleError: string | null;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const PLACEHOLDER_CLIENT_ID = "000000000000-placeholder.apps.googleusercontent.com";
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Validate Google auth config
|
|
39
|
+
*/
|
|
40
|
+
function validateGoogleConfig(config?: GoogleAuthConfig): boolean {
|
|
41
|
+
if (!config) return false;
|
|
42
|
+
|
|
43
|
+
const hasValidClientId =
|
|
44
|
+
!!(config.iosClientId && config.iosClientId !== PLACEHOLDER_CLIENT_ID) ||
|
|
45
|
+
!!(config.webClientId && config.webClientId !== PLACEHOLDER_CLIENT_ID) ||
|
|
46
|
+
!!(config.androidClientId && config.androidClientId !== PLACEHOLDER_CLIENT_ID);
|
|
47
|
+
|
|
48
|
+
return hasValidClientId;
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
/**
|
|
37
52
|
* Hook for Google authentication with expo-auth-session
|
|
38
53
|
*/
|
|
39
54
|
export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
|
|
40
55
|
const [isLoading, setIsLoading] = useState(false);
|
|
56
|
+
const [googleError, setGoogleError] = useState<string | null>(null);
|
|
41
57
|
|
|
42
|
-
const googleConfigured =
|
|
43
|
-
config?.iosClientId ||
|
|
44
|
-
config?.webClientId ||
|
|
45
|
-
config?.androidClientId
|
|
46
|
-
);
|
|
58
|
+
const googleConfigured = validateGoogleConfig(config);
|
|
47
59
|
|
|
48
60
|
const socialAuthConfig: SocialAuthConfig = {
|
|
49
61
|
google: config,
|
|
@@ -70,8 +82,11 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
|
|
|
70
82
|
const idToken = googleResponse.authentication?.idToken;
|
|
71
83
|
if (idToken) {
|
|
72
84
|
setIsLoading(true);
|
|
85
|
+
setGoogleError(null);
|
|
73
86
|
signInWithGoogleToken(idToken)
|
|
74
87
|
.catch((error) => {
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : "Firebase sign-in failed";
|
|
89
|
+
setGoogleError(errorMessage);
|
|
75
90
|
if (__DEV__) {
|
|
76
91
|
console.error("[useGoogleAuth] Firebase sign-in failed:", error);
|
|
77
92
|
}
|
|
@@ -80,23 +95,33 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
|
|
|
80
95
|
setIsLoading(false);
|
|
81
96
|
});
|
|
82
97
|
}
|
|
98
|
+
} else if (googleResponse?.type === "error") {
|
|
99
|
+
setGoogleError("Google authentication failed");
|
|
100
|
+
setIsLoading(false);
|
|
83
101
|
}
|
|
84
102
|
}, [googleResponse, signInWithGoogleToken]);
|
|
85
103
|
|
|
86
104
|
const signInWithGoogle = useCallback(async (): Promise<SocialAuthResult> => {
|
|
87
105
|
if (!promptGoogleAsync) {
|
|
88
|
-
|
|
106
|
+
const error = "expo-auth-session is not available";
|
|
107
|
+
setGoogleError(error);
|
|
108
|
+
return { success: false, error };
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
if (!googleConfigured) {
|
|
92
|
-
|
|
112
|
+
const error = "Google Sign-In is not configured. Please provide valid client IDs.";
|
|
113
|
+
setGoogleError(error);
|
|
114
|
+
return { success: false, error };
|
|
93
115
|
}
|
|
94
116
|
|
|
95
117
|
if (!request) {
|
|
96
|
-
|
|
118
|
+
const error = "Google Sign-In not ready";
|
|
119
|
+
setGoogleError(error);
|
|
120
|
+
return { success: false, error };
|
|
97
121
|
}
|
|
98
122
|
|
|
99
123
|
setIsLoading(true);
|
|
124
|
+
setGoogleError(null);
|
|
100
125
|
try {
|
|
101
126
|
const result = await promptGoogleAsync();
|
|
102
127
|
|
|
@@ -108,14 +133,20 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
|
|
|
108
133
|
}
|
|
109
134
|
|
|
110
135
|
if (result.type === "cancel") {
|
|
111
|
-
|
|
136
|
+
const error = "Google Sign-In was cancelled";
|
|
137
|
+
setGoogleError(error);
|
|
138
|
+
return { success: false, error };
|
|
112
139
|
}
|
|
113
140
|
|
|
114
|
-
|
|
141
|
+
const error = "Google Sign-In failed";
|
|
142
|
+
setGoogleError(error);
|
|
143
|
+
return { success: false, error };
|
|
115
144
|
} catch (error) {
|
|
145
|
+
const errorMessage = error instanceof Error ? error.message : "Google sign-in failed";
|
|
146
|
+
setGoogleError(errorMessage);
|
|
116
147
|
return {
|
|
117
148
|
success: false,
|
|
118
|
-
error:
|
|
149
|
+
error: errorMessage,
|
|
119
150
|
};
|
|
120
151
|
} finally {
|
|
121
152
|
setIsLoading(false);
|
|
@@ -126,5 +157,6 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
|
|
|
126
157
|
signInWithGoogle,
|
|
127
158
|
googleLoading: isLoading || firebaseLoading,
|
|
128
159
|
googleConfigured,
|
|
160
|
+
googleError,
|
|
129
161
|
};
|
|
130
162
|
}
|
|
@@ -71,34 +71,40 @@ export const selectShowAuthModal = (state: { showAuthModal: (callback?: () => vo
|
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Get current user ID
|
|
74
|
+
* Uses firebaseUser as single source of truth
|
|
74
75
|
*/
|
|
75
76
|
export const selectUserId = (state: AuthStore): string | null => {
|
|
76
|
-
return state.firebaseUser?.uid ??
|
|
77
|
+
return state.firebaseUser?.uid ?? null;
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
|
-
* Check if user is authenticated (not anonymous)
|
|
81
|
+
* Check if user is authenticated (has a valid Firebase user, not anonymous)
|
|
82
|
+
* Uses firebaseUser as single source of truth
|
|
81
83
|
*/
|
|
82
84
|
export const selectIsAuthenticated = (state: AuthStore): boolean => {
|
|
83
|
-
|
|
85
|
+
const hasFirebaseUser = !!state.firebaseUser;
|
|
86
|
+
const isNotAnonymous = !state.firebaseUser?.isAnonymous;
|
|
87
|
+
return hasFirebaseUser && isNotAnonymous;
|
|
84
88
|
};
|
|
85
89
|
|
|
86
90
|
/**
|
|
87
91
|
* Check if user is anonymous
|
|
92
|
+
* Uses firebaseUser as single source of truth
|
|
88
93
|
*/
|
|
89
94
|
export const selectIsAnonymous = (state: AuthStore): boolean => {
|
|
90
|
-
return state.firebaseUser?.isAnonymous ??
|
|
95
|
+
return state.firebaseUser?.isAnonymous ?? false;
|
|
91
96
|
};
|
|
92
97
|
|
|
93
98
|
/**
|
|
94
99
|
* Get current user type
|
|
100
|
+
* Derived from firebaseUser state
|
|
95
101
|
*/
|
|
96
102
|
export const selectUserType = (state: AuthStore): UserType => {
|
|
97
|
-
if (!state.firebaseUser
|
|
103
|
+
if (!state.firebaseUser) {
|
|
98
104
|
return "none";
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
return
|
|
107
|
+
return state.firebaseUser.isAnonymous ? "anonymous" : "authenticated";
|
|
102
108
|
};
|
|
103
109
|
|
|
104
110
|
/**
|
|
@@ -71,17 +71,14 @@ export const useAuthStore = createStore<AuthState, AuthActions>({
|
|
|
71
71
|
},
|
|
72
72
|
|
|
73
73
|
setIsAnonymous: (isAnonymous) => {
|
|
74
|
-
const {
|
|
74
|
+
const { firebaseUser } = get();
|
|
75
75
|
if (__DEV__) {
|
|
76
|
-
console.log("[AuthStore] setIsAnonymous:", { isAnonymous,
|
|
77
|
-
}
|
|
78
|
-
// Update user.isAnonymous flag to match the new state
|
|
79
|
-
// This handles both anonymous → registered and registered → anonymous conversions
|
|
80
|
-
if (user && user.isAnonymous !== isAnonymous) {
|
|
81
|
-
set({ isAnonymous, user: { ...user, isAnonymous } });
|
|
82
|
-
} else {
|
|
83
|
-
set({ isAnonymous });
|
|
76
|
+
console.log("[AuthStore] setIsAnonymous:", { isAnonymous, hasFirebaseUser: !!firebaseUser });
|
|
84
77
|
}
|
|
78
|
+
// Only update the isAnonymous flag
|
|
79
|
+
// The user object will be updated by setFirebaseUser when needed
|
|
80
|
+
// This prevents inconsistencies between firebaseUser and user
|
|
81
|
+
set({ isAnonymous });
|
|
85
82
|
},
|
|
86
83
|
|
|
87
84
|
setError: (error) => {
|
|
@@ -20,6 +20,8 @@ const ANONYMOUS_SIGNIN_TIMEOUT_MS = 10000;
|
|
|
20
20
|
let listenerInitialized = false;
|
|
21
21
|
let listenerRefCount = 0;
|
|
22
22
|
let firebaseUnsubscribe: (() => void) | null = null;
|
|
23
|
+
let anonymousSignInInProgress = false; // Prevent race conditions
|
|
24
|
+
let initializationInProgress = false; // Prevent duplicate initialization
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Initialize Firebase auth listener
|
|
@@ -34,9 +36,20 @@ export function initializeAuthListener(
|
|
|
34
36
|
console.log("[AuthListener] initializeAuthListener called:", {
|
|
35
37
|
autoAnonymousSignIn,
|
|
36
38
|
alreadyInitialized: listenerInitialized,
|
|
39
|
+
initializationInProgress,
|
|
37
40
|
});
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
// Prevent duplicate initialization - return existing unsubscribe if already initializing
|
|
44
|
+
if (initializationInProgress) {
|
|
45
|
+
if (__DEV__) {
|
|
46
|
+
console.warn("[AuthListener] Initialization already in progress, returning existing unsubscribe");
|
|
47
|
+
}
|
|
48
|
+
return () => {
|
|
49
|
+
// No-op - will be handled by the initial initialization
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
// If already initialized, increment ref count and return unsubscribe that decrements
|
|
41
54
|
if (listenerInitialized) {
|
|
42
55
|
listenerRefCount++;
|
|
@@ -58,10 +71,14 @@ export function initializeAuthListener(
|
|
|
58
71
|
firebaseUnsubscribe = null;
|
|
59
72
|
listenerInitialized = false;
|
|
60
73
|
listenerRefCount = 0;
|
|
74
|
+
anonymousSignInInProgress = false;
|
|
61
75
|
}
|
|
62
76
|
};
|
|
63
77
|
}
|
|
64
78
|
|
|
79
|
+
// Mark initialization as in progress
|
|
80
|
+
initializationInProgress = true;
|
|
81
|
+
|
|
65
82
|
const auth = getFirebaseAuth();
|
|
66
83
|
const store = useAuthStore.getState();
|
|
67
84
|
|
|
@@ -69,6 +86,7 @@ export function initializeAuthListener(
|
|
|
69
86
|
if (__DEV__) {
|
|
70
87
|
console.log("[AuthListener] No Firebase auth, marking initialized");
|
|
71
88
|
}
|
|
89
|
+
initializationInProgress = false;
|
|
72
90
|
store.setLoading(false);
|
|
73
91
|
store.setInitialized(true);
|
|
74
92
|
return () => {};
|
|
@@ -103,21 +121,31 @@ export function initializeAuthListener(
|
|
|
103
121
|
}
|
|
104
122
|
|
|
105
123
|
if (!user && autoAnonymousSignIn) {
|
|
124
|
+
// Prevent race condition: only one anonymous sign-in at a time
|
|
125
|
+
if (anonymousSignInInProgress) {
|
|
126
|
+
if (__DEV__) {
|
|
127
|
+
console.log("[AuthListener] Anonymous sign-in already in progress, skipping");
|
|
128
|
+
}
|
|
129
|
+
store.setFirebaseUser(null);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
106
133
|
if (__DEV__) {
|
|
107
134
|
console.log("[AuthListener] No user, auto signing in anonymously...");
|
|
108
135
|
}
|
|
109
136
|
// Set loading state while attempting sign-in
|
|
110
137
|
store.setLoading(true);
|
|
138
|
+
anonymousSignInInProgress = true;
|
|
111
139
|
|
|
112
|
-
// Start anonymous sign-in
|
|
113
|
-
//
|
|
140
|
+
// Start anonymous sign-in without blocking the listener
|
|
141
|
+
// The listener will be triggered again when sign-in completes
|
|
114
142
|
void (async () => {
|
|
115
|
-
// Add timeout protection
|
|
116
|
-
const timeoutPromise = new Promise((_, reject) =>
|
|
117
|
-
setTimeout(() => reject(new Error("Anonymous sign-in timeout")), ANONYMOUS_SIGNIN_TIMEOUT_MS)
|
|
118
|
-
);
|
|
119
|
-
|
|
120
143
|
try {
|
|
144
|
+
// Add timeout protection
|
|
145
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
146
|
+
setTimeout(() => reject(new Error("Anonymous sign-in timeout")), ANONYMOUS_SIGNIN_TIMEOUT_MS)
|
|
147
|
+
);
|
|
148
|
+
|
|
121
149
|
// Race between sign-in and timeout
|
|
122
150
|
await Promise.race([
|
|
123
151
|
(async () => {
|
|
@@ -156,6 +184,8 @@ export function initializeAuthListener(
|
|
|
156
184
|
store.setLoading(false);
|
|
157
185
|
store.setInitialized(true);
|
|
158
186
|
store.setError("Failed to sign in anonymously. Please check your connection.");
|
|
187
|
+
} finally {
|
|
188
|
+
anonymousSignInInProgress = false;
|
|
159
189
|
}
|
|
160
190
|
})();
|
|
161
191
|
|
|
@@ -163,6 +193,7 @@ export function initializeAuthListener(
|
|
|
163
193
|
// The listener will be triggered again when sign-in succeeds
|
|
164
194
|
// For now, set null user and let loading state indicate in-progress
|
|
165
195
|
store.setFirebaseUser(null);
|
|
196
|
+
initializationInProgress = false;
|
|
166
197
|
return;
|
|
167
198
|
}
|
|
168
199
|
|
|
@@ -179,6 +210,8 @@ export function initializeAuthListener(
|
|
|
179
210
|
onAuthStateChange?.(user);
|
|
180
211
|
});
|
|
181
212
|
|
|
213
|
+
initializationInProgress = false;
|
|
214
|
+
|
|
182
215
|
return () => {
|
|
183
216
|
listenerRefCount--;
|
|
184
217
|
if (__DEV__) {
|
|
@@ -193,6 +226,8 @@ export function initializeAuthListener(
|
|
|
193
226
|
firebaseUnsubscribe = null;
|
|
194
227
|
listenerInitialized = false;
|
|
195
228
|
listenerRefCount = 0;
|
|
229
|
+
anonymousSignInInProgress = false;
|
|
230
|
+
initializationInProgress = false;
|
|
196
231
|
}
|
|
197
232
|
};
|
|
198
233
|
}
|
|
@@ -207,6 +242,8 @@ export function resetAuthListener(): void {
|
|
|
207
242
|
}
|
|
208
243
|
listenerInitialized = false;
|
|
209
244
|
listenerRefCount = 0;
|
|
245
|
+
anonymousSignInInProgress = false;
|
|
246
|
+
initializationInProgress = false;
|
|
210
247
|
}
|
|
211
248
|
|
|
212
249
|
/**
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Service Interface
|
|
3
|
-
* Port for authentication operations (provider-agnostic)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
7
|
-
|
|
8
|
-
export interface SignUpParams {
|
|
9
|
-
email: string;
|
|
10
|
-
password: string;
|
|
11
|
-
displayName?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface SignInParams {
|
|
15
|
-
email: string;
|
|
16
|
-
password: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface IAuthService {
|
|
20
|
-
/**
|
|
21
|
-
* Sign up a new user
|
|
22
|
-
*/
|
|
23
|
-
signUp(params: SignUpParams): Promise<AuthUser>;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Sign in an existing user
|
|
27
|
-
*/
|
|
28
|
-
signIn(params: SignInParams): Promise<AuthUser>;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Sign out current user
|
|
32
|
-
*/
|
|
33
|
-
signOut(): Promise<void>;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Set anonymous mode (no authentication)
|
|
37
|
-
*/
|
|
38
|
-
setAnonymousMode(): Promise<void>;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Check if currently in anonymous mode
|
|
42
|
-
*/
|
|
43
|
-
getIsAnonymousMode(): boolean;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get current authenticated user
|
|
47
|
-
*/
|
|
48
|
-
getCurrentUser(): AuthUser | null;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Subscribe to auth state changes
|
|
52
|
-
*/
|
|
53
|
-
onAuthStateChange(callback: (user: AuthUser | null) => void): () => void;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Check if auth is initialized
|
|
57
|
-
*/
|
|
58
|
-
isInitialized(): boolean;
|
|
59
|
-
}
|
|
60
|
-
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Data Migration Utilities
|
|
3
|
-
* Handles migrating data when anonymous user converts to authenticated user
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface MigrationConfig {
|
|
7
|
-
onMigrationStart?: (anonymousId: string, authId: string) => void;
|
|
8
|
-
onMigrationComplete?: (anonymousId: string, authId: string) => void;
|
|
9
|
-
onMigrationError?: (error: Error) => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let migrationConfig: MigrationConfig = {};
|
|
13
|
-
|
|
14
|
-
export const configureMigration = (config: MigrationConfig): void => {
|
|
15
|
-
migrationConfig = config;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export const migrateUserData = (
|
|
19
|
-
anonymousId: string,
|
|
20
|
-
authId: string
|
|
21
|
-
): void => {
|
|
22
|
-
if (__DEV__) {
|
|
23
|
-
console.log(`[Migration] Starting migration from ${anonymousId} to ${authId}`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
migrationConfig.onMigrationStart?.(anonymousId, authId);
|
|
28
|
-
|
|
29
|
-
// App-specific migration logic handled via callbacks
|
|
30
|
-
// This keeps the package generic and flexible
|
|
31
|
-
|
|
32
|
-
migrationConfig.onMigrationComplete?.(anonymousId, authId);
|
|
33
|
-
|
|
34
|
-
if (__DEV__) {
|
|
35
|
-
console.log(`[Migration] Successfully migrated data for ${authId}`);
|
|
36
|
-
}
|
|
37
|
-
} catch (error) {
|
|
38
|
-
if (__DEV__) {
|
|
39
|
-
console.error(`[Migration] Failed to migrate user data:`, error);
|
|
40
|
-
}
|
|
41
|
-
migrationConfig.onMigrationError?.(error as Error);
|
|
42
|
-
// Silent fail - don't block auth flow
|
|
43
|
-
}
|
|
44
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UI Provider Adapter
|
|
3
|
-
* Adapts external UI implementations to our IUIProvider interface
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { DesignTokens } from "@umituz/react-native-design-system";
|
|
7
|
-
import type { IUIProvider } from "../types/UI.types";
|
|
8
|
-
|
|
9
|
-
export class UIProviderAdapter implements IUIProvider {
|
|
10
|
-
private theme: DesignTokens | null = null;
|
|
11
|
-
private localization: Record<string, unknown> | null = null;
|
|
12
|
-
|
|
13
|
-
constructor(theme?: DesignTokens, localization?: Record<string, unknown>) {
|
|
14
|
-
this.theme = theme || null;
|
|
15
|
-
this.localization = localization || null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
getTheme(): DesignTokens | null {
|
|
19
|
-
return this.theme;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
getLocalization(): Record<string, unknown> | null {
|
|
23
|
-
return this.localization;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
updateTheme(theme: DesignTokens): void {
|
|
27
|
-
this.theme = theme;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
updateLocalization(localization: Record<string, unknown>): void {
|
|
31
|
-
this.localization = localization;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Create UI provider from theme and localization implementations
|
|
37
|
-
*/
|
|
38
|
-
export function createUIProvider(
|
|
39
|
-
theme?: DesignTokens,
|
|
40
|
-
localization?: Record<string, unknown>
|
|
41
|
-
): IUIProvider {
|
|
42
|
-
return new UIProviderAdapter(theme, localization);
|
|
43
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Document Types and Configuration
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Minimal user interface for document creation
|
|
7
|
-
* Compatible with both Firebase User and AuthUser
|
|
8
|
-
*/
|
|
9
|
-
export interface UserDocumentUser {
|
|
10
|
-
uid: string;
|
|
11
|
-
displayName?: string | null;
|
|
12
|
-
email?: string | null;
|
|
13
|
-
photoURL?: string | null;
|
|
14
|
-
isAnonymous?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Configuration for user document service
|
|
19
|
-
*/
|
|
20
|
-
export interface UserDocumentConfig {
|
|
21
|
-
/** Firestore collection name (default: "users") */
|
|
22
|
-
collectionName?: string;
|
|
23
|
-
/** Additional fields to store with user document */
|
|
24
|
-
extraFields?: Record<string, unknown>;
|
|
25
|
-
/** Callback to collect device/app info */
|
|
26
|
-
collectExtras?: () => Promise<UserDocumentExtras>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* User document extras from device/app
|
|
31
|
-
*/
|
|
32
|
-
export interface UserDocumentExtras {
|
|
33
|
-
[key: string]: string | number | boolean | null | undefined;
|
|
34
|
-
deviceId?: string;
|
|
35
|
-
persistentDeviceId?: string;
|
|
36
|
-
nativeDeviceId?: string;
|
|
37
|
-
platform?: string;
|
|
38
|
-
deviceModel?: string;
|
|
39
|
-
deviceBrand?: string;
|
|
40
|
-
deviceName?: string;
|
|
41
|
-
deviceType?: number | string;
|
|
42
|
-
deviceYearClass?: number | string;
|
|
43
|
-
isDevice?: boolean;
|
|
44
|
-
osName?: string;
|
|
45
|
-
osVersion?: string;
|
|
46
|
-
osBuildId?: string;
|
|
47
|
-
totalMemory?: number | string;
|
|
48
|
-
appVersion?: string;
|
|
49
|
-
buildNumber?: string;
|
|
50
|
-
locale?: string;
|
|
51
|
-
region?: string;
|
|
52
|
-
timezone?: string;
|
|
53
|
-
screenWidth?: number;
|
|
54
|
-
screenHeight?: number;
|
|
55
|
-
screenScale?: number;
|
|
56
|
-
fontScale?: number;
|
|
57
|
-
isLandscape?: boolean;
|
|
58
|
-
previousAnonymousUserId?: string;
|
|
59
|
-
signUpMethod?: string;
|
|
60
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Document Service
|
|
3
|
-
* Generic service for creating/updating user documents in Firestore
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { doc, getDoc, setDoc, serverTimestamp } from "firebase/firestore";
|
|
7
|
-
import type { User } from "firebase/auth";
|
|
8
|
-
import { getFirestore } from "@umituz/react-native-firebase";
|
|
9
|
-
import type {
|
|
10
|
-
UserDocumentUser,
|
|
11
|
-
UserDocumentConfig,
|
|
12
|
-
UserDocumentExtras,
|
|
13
|
-
} from "./UserDocument.types";
|
|
14
|
-
import {
|
|
15
|
-
getSignUpMethod,
|
|
16
|
-
buildBaseData,
|
|
17
|
-
buildCreateData,
|
|
18
|
-
buildUpdateData,
|
|
19
|
-
} from "../utils/userDocumentBuilder.util";
|
|
20
|
-
|
|
21
|
-
export type {
|
|
22
|
-
UserDocumentUser,
|
|
23
|
-
UserDocumentConfig,
|
|
24
|
-
UserDocumentExtras,
|
|
25
|
-
} from "./UserDocument.types";
|
|
26
|
-
|
|
27
|
-
let userDocumentConfig: UserDocumentConfig = {};
|
|
28
|
-
|
|
29
|
-
export function configureUserDocumentService(config: UserDocumentConfig): void {
|
|
30
|
-
userDocumentConfig = { ...config };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function ensureUserDocument(
|
|
34
|
-
user: UserDocumentUser | User,
|
|
35
|
-
extras?: UserDocumentExtras,
|
|
36
|
-
): Promise<boolean> {
|
|
37
|
-
const db = getFirestore();
|
|
38
|
-
if (!db || !user.uid || user.uid.trim().length === 0) return false;
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
let allExtras = extras || {};
|
|
42
|
-
if (userDocumentConfig.collectExtras) {
|
|
43
|
-
const collectedExtras = await userDocumentConfig.collectExtras();
|
|
44
|
-
allExtras = { ...collectedExtras, ...allExtras };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!allExtras.signUpMethod) allExtras.signUpMethod = getSignUpMethod(user);
|
|
48
|
-
|
|
49
|
-
const collectionName = userDocumentConfig.collectionName || "users";
|
|
50
|
-
const userRef = doc(db, collectionName, user.uid);
|
|
51
|
-
const userDoc = await getDoc(userRef);
|
|
52
|
-
const baseData = buildBaseData(user, allExtras);
|
|
53
|
-
|
|
54
|
-
const docData = !userDoc.exists()
|
|
55
|
-
? buildCreateData(baseData, userDocumentConfig.extraFields, allExtras)
|
|
56
|
-
: buildUpdateData(baseData, allExtras);
|
|
57
|
-
|
|
58
|
-
await setDoc(userRef, docData, { merge: true });
|
|
59
|
-
return true;
|
|
60
|
-
} catch (error) {
|
|
61
|
-
if (__DEV__) {
|
|
62
|
-
console.error("[UserDocumentService] Failed:", error);
|
|
63
|
-
}
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function markUserDeleted(userId: string): Promise<boolean> {
|
|
69
|
-
const db = getFirestore();
|
|
70
|
-
if (!db || !userId) return false;
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const userRef = doc(db, userDocumentConfig.collectionName || "users", userId);
|
|
74
|
-
await setDoc(userRef, {
|
|
75
|
-
isDeleted: true,
|
|
76
|
-
deletedAt: serverTimestamp(),
|
|
77
|
-
updatedAt: serverTimestamp(),
|
|
78
|
-
}, { merge: true });
|
|
79
|
-
return true;
|
|
80
|
-
} catch (error) {
|
|
81
|
-
if (__DEV__) {
|
|
82
|
-
console.error("[UserDocumentService] markUserDeleted failed:", error);
|
|
83
|
-
}
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* App Service Helpers
|
|
3
|
-
* Creates ready-to-use auth service for configureAppServices
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useAuthStore } from "../../presentation/stores/authStore";
|
|
7
|
-
import { selectIsAuthenticated, selectUserId } from "../../presentation/stores/auth.selectors";
|
|
8
|
-
import { useAuthModalStore } from "../../presentation/stores/authModalStore";
|
|
9
|
-
|
|
10
|
-
export interface IAppAuthServiceHelper {
|
|
11
|
-
getUserId: () => string | null;
|
|
12
|
-
isAuthenticated: () => boolean;
|
|
13
|
-
requireAuth: () => string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Creates an auth service implementation for configureAppServices
|
|
18
|
-
*/
|
|
19
|
-
export function createAuthService(): IAppAuthServiceHelper {
|
|
20
|
-
return {
|
|
21
|
-
getUserId: () => selectUserId(useAuthStore.getState()),
|
|
22
|
-
isAuthenticated: () => selectIsAuthenticated(useAuthStore.getState()),
|
|
23
|
-
requireAuth: () => {
|
|
24
|
-
if (!selectIsAuthenticated(useAuthStore.getState())) {
|
|
25
|
-
useAuthModalStore.getState().showAuthModal();
|
|
26
|
-
throw new Error("Auth required");
|
|
27
|
-
}
|
|
28
|
-
const userId = selectUserId(useAuthStore.getState());
|
|
29
|
-
if (!userId) {
|
|
30
|
-
throw new Error("User ID missing");
|
|
31
|
-
}
|
|
32
|
-
return userId;
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UI Provider Interface
|
|
3
|
-
* Generic interface for theme and localization providers
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { DesignTokens } from "@umituz/react-native-design-system";
|
|
7
|
-
|
|
8
|
-
export interface IUIProvider {
|
|
9
|
-
getTheme(): DesignTokens | null;
|
|
10
|
-
getLocalization(): Record<string, unknown> | null;
|
|
11
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple tracker for auth operations
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export const authTracker = {
|
|
6
|
-
logOperationStarted: (operation: string, data?: Record<string, unknown>) => {
|
|
7
|
-
if (__DEV__) {
|
|
8
|
-
console.log(`[Auth] ${operation} started`, data);
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
|
|
12
|
-
logOperationSuccess: (operation: string, data?: Record<string, unknown>) => {
|
|
13
|
-
if (__DEV__) {
|
|
14
|
-
console.log(`[Auth] ${operation} successful`, data);
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
logOperationError: (operation: string, error: unknown, metadata?: Record<string, unknown>) => {
|
|
19
|
-
if (__DEV__) {
|
|
20
|
-
console.error(`[Auth] ${operation} failed`, { error, ...metadata });
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
};
|