@umituz/react-native-firebase 1.13.140 → 1.13.141

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/src/auth/infrastructure/config/FirebaseAuthClient.ts +11 -2
  3. package/src/auth/infrastructure/services/account-deletion.service.ts +40 -20
  4. package/src/auth/infrastructure/services/anonymous-auth.service.ts +7 -11
  5. package/src/auth/infrastructure/services/apple-auth.service.ts +44 -18
  6. package/src/auth/infrastructure/services/base/base-auth.service.ts +9 -40
  7. package/src/auth/infrastructure/services/firestore-utils.service.ts +2 -2
  8. package/src/auth/infrastructure/services/google-auth.service.ts +27 -23
  9. package/src/auth/infrastructure/services/password.service.ts +6 -21
  10. package/src/auth/infrastructure/services/reauthentication.service.ts +19 -29
  11. package/src/auth/presentation/hooks/shared/hook-utils.util.ts +222 -0
  12. package/src/auth/presentation/hooks/useSocialAuth.ts +10 -1
  13. package/src/domain/utils/async-executor.util.ts +176 -0
  14. package/src/domain/utils/credential.util.ts +102 -0
  15. package/src/domain/utils/index.ts +101 -0
  16. package/src/domain/utils/result.util.ts +129 -0
  17. package/src/domain/utils/service-config.util.ts +99 -0
  18. package/src/domain/utils/validation.util.ts +78 -0
  19. package/src/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +3 -5
  20. package/src/firestore/infrastructure/repositories/BaseQueryRepository.ts +3 -3
  21. package/src/firestore/utils/dateUtils.ts +21 -5
  22. package/src/firestore/utils/deduplication/pending-query-manager.util.ts +3 -7
  23. package/src/firestore/utils/deduplication/query-key-generator.util.ts +12 -3
  24. package/src/firestore/utils/deduplication/timer-manager.util.ts +8 -2
  25. package/src/infrastructure/config/FirebaseClient.ts +36 -4
  26. package/src/init/createFirebaseInitModule.ts +18 -3
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Shared Hook Utilities
3
+ * Common utilities and patterns for React hooks
4
+ * Reduces code duplication across presentation hooks
5
+ */
6
+
7
+ import { useState, useCallback, useRef, useEffect } from 'react';
8
+ import { onAuthStateChanged, type Auth, type User } from 'firebase/auth';
9
+
10
+ /**
11
+ * Hook state management result
12
+ */
13
+ export interface HookState<T> {
14
+ value: T;
15
+ loading: boolean;
16
+ error: Error | null;
17
+ }
18
+
19
+ /**
20
+ * Hook state actions
21
+ */
22
+ export interface HookStateActions<T> {
23
+ setValue: (value: T) => void;
24
+ setLoading: (loading: boolean) => void;
25
+ setError: (error: Error | null) => void;
26
+ clearError: () => void;
27
+ reset: () => void;
28
+ }
29
+
30
+ /**
31
+ * Create a stateful hook with loading and error handling
32
+ */
33
+ export function useHookState<T>(
34
+ initialValue: T
35
+ ): [HookState<T>, HookStateActions<T>] {
36
+ const [value, setValue] = useState<T>(initialValue);
37
+ const [loading, setLoading] = useState<boolean>(false);
38
+ const [error, setError] = useState<Error | null>(null);
39
+
40
+ const clearError = useCallback(() => {
41
+ setError(null);
42
+ }, []);
43
+
44
+ const reset = useCallback(() => {
45
+ setValue(initialValue);
46
+ setLoading(false);
47
+ setError(null);
48
+ }, [initialValue]);
49
+
50
+ const state: HookState<T> = { value, loading, error };
51
+ const actions: HookStateActions<T> = {
52
+ setValue,
53
+ setLoading,
54
+ setError,
55
+ clearError,
56
+ reset,
57
+ };
58
+
59
+ return [state, actions];
60
+ }
61
+
62
+ /**
63
+ * Hook for managing cleanup callbacks
64
+ */
65
+ export function useCleanup(
66
+ cleanupFn: () => void,
67
+ deps: React.DependencyList = []
68
+ ): void {
69
+ useEffect(() => {
70
+ return () => {
71
+ cleanupFn();
72
+ };
73
+ }, deps);
74
+ }
75
+
76
+ /**
77
+ * Hook for managing async operation state
78
+ */
79
+ export function useAsyncOperation<T = void>() {
80
+ const [loading, setLoading] = useState<boolean>(false);
81
+ const [error, setError] = useState<Error | null>(null);
82
+ const operationRef = useRef<Promise<T> | null>(null);
83
+
84
+ const execute = useCallback(async (operation: () => Promise<T>): Promise<T> => {
85
+ setLoading(true);
86
+ setError(null);
87
+
88
+ try {
89
+ const promise = operation();
90
+ operationRef.current = promise;
91
+ const result = await promise;
92
+ return result;
93
+ } catch (err) {
94
+ const error = err instanceof Error ? err : new Error(String(err));
95
+ setError(error);
96
+ throw error;
97
+ } finally {
98
+ setLoading(false);
99
+ operationRef.current = null;
100
+ }
101
+ }, []);
102
+
103
+ const clearError = useCallback(() => {
104
+ setError(null);
105
+ }, []);
106
+
107
+ return {
108
+ loading,
109
+ error,
110
+ execute,
111
+ clearError,
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Hook for debounced value updates
117
+ */
118
+ export function useDebouncedValue<T>(value: T, delayMs: number): T {
119
+ const [debouncedValue, setDebouncedValue] = useState<T>(value);
120
+
121
+ useEffect(() => {
122
+ const handler = setTimeout(() => {
123
+ setDebouncedValue(value);
124
+ }, delayMs);
125
+
126
+ return () => {
127
+ clearTimeout(handler);
128
+ };
129
+ }, [value, delayMs]);
130
+
131
+ return debouncedValue;
132
+ }
133
+
134
+ /**
135
+ * Hook for tracking mounted state
136
+ */
137
+ export function useIsMounted(): () => boolean {
138
+ const isMountedRef = useRef<boolean>(true);
139
+
140
+ useEffect(() => {
141
+ isMountedRef.current = true;
142
+ return () => {
143
+ isMountedRef.current = false;
144
+ };
145
+ }, []);
146
+
147
+ return useCallback(() => isMountedRef.current, []);
148
+ }
149
+
150
+ /**
151
+ * Hook for safe state updates (only if mounted)
152
+ */
153
+ export function useSafeState<T>(
154
+ initialValue: T
155
+ ): [T, (value: T | ((prev: T) => T)) => void] {
156
+ const isMounted = useIsMounted();
157
+ const [state, setState] = useState<T>(initialValue);
158
+
159
+ const setSafeState = useCallback(
160
+ (value: T | ((prev: T) => T)) => {
161
+ if (isMounted()) {
162
+ setState(value);
163
+ }
164
+ },
165
+ [isMounted]
166
+ );
167
+
168
+ return [state, setSafeState];
169
+ }
170
+
171
+ /**
172
+ * Create an auth state handler
173
+ */
174
+ export function createAuthStateHandler(
175
+ setState: (user: User | null) => void,
176
+ setLoading: (loading: boolean) => void,
177
+ setError: (error: Error | null) => void
178
+ ): (user: User | null) => void {
179
+ return (user: User | null) => {
180
+ try {
181
+ setState(user);
182
+ setError(null);
183
+ } catch (err) {
184
+ const error = err instanceof Error ? err : new Error('Auth state update failed');
185
+ setError(error);
186
+ } finally {
187
+ setLoading(false);
188
+ }
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Hook for managing auth listener lifecycle
194
+ */
195
+ export function useAuthListener(
196
+ auth: Auth | null,
197
+ onAuthStateChange: (user: User | null) => void
198
+ ): void {
199
+ const unsubscribeRef = useRef<(() => void) | null>(null);
200
+
201
+ useEffect(() => {
202
+ // Cleanup previous listener
203
+ if (unsubscribeRef.current) {
204
+ unsubscribeRef.current();
205
+ unsubscribeRef.current = null;
206
+ }
207
+
208
+ if (!auth) {
209
+ onAuthStateChange(null);
210
+ return;
211
+ }
212
+
213
+ unsubscribeRef.current = onAuthStateChanged(auth, onAuthStateChange);
214
+
215
+ return () => {
216
+ if (unsubscribeRef.current) {
217
+ unsubscribeRef.current();
218
+ unsubscribeRef.current = null;
219
+ }
220
+ };
221
+ }, [auth, onAuthStateChange]);
222
+ }
@@ -48,11 +48,20 @@ export function useSocialAuth(config?: SocialAuthConfig): UseSocialAuthResult {
48
48
  }, [googleConfig]);
49
49
 
50
50
  useEffect(() => {
51
+ let cancelled = false;
52
+
51
53
  const checkApple = async () => {
52
54
  const available = await appleAuthService.isAvailable();
53
- setAppleAvailable(available && (config?.apple?.enabled ?? false));
55
+ if (!cancelled) {
56
+ setAppleAvailable(available && (config?.apple?.enabled ?? false));
57
+ }
54
58
  };
59
+
55
60
  checkApple();
61
+
62
+ return () => {
63
+ cancelled = true;
64
+ };
56
65
  }, [config?.apple?.enabled]);
57
66
 
58
67
  const signInWithGoogleToken = useCallback(
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Async Operation Executor Utility
3
+ * Centralized async operation execution with error handling
4
+ * Eliminates code duplication across services
5
+ */
6
+
7
+ import type { Result, FailureResult } from './result.util';
8
+ import { failureResultFromError, successResult } from './result.util';
9
+
10
+ /**
11
+ * Error converter function type
12
+ * Converts unknown errors to ErrorInfo
13
+ */
14
+ export type ErrorConverter = (error: unknown) => { code: string; message: string };
15
+
16
+ /**
17
+ * Default error converter for auth operations
18
+ */
19
+ export function authErrorConverter(error: unknown): { code: string; message: string } {
20
+ if (error instanceof Error) {
21
+ return {
22
+ code: (error as { code?: string }).code ?? 'auth/failed',
23
+ message: error.message,
24
+ };
25
+ }
26
+ return {
27
+ code: 'auth/failed',
28
+ message: typeof error === 'string' ? error : 'Authentication failed',
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Default error converter for operations
34
+ */
35
+ export function defaultErrorConverter(
36
+ error: unknown,
37
+ defaultCode = 'operation/failed'
38
+ ): { code: string; message: string } {
39
+ if (error instanceof Error) {
40
+ return {
41
+ code: (error as { code?: string }).code ?? defaultCode,
42
+ message: error.message,
43
+ };
44
+ }
45
+ return {
46
+ code: defaultCode,
47
+ message: typeof error === 'string' ? error : 'Operation failed',
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Execute async operation with error handling
53
+ * Returns Result type with success/failure
54
+ */
55
+ export async function executeOperation<T>(
56
+ operation: () => Promise<T>,
57
+ errorConverter?: ErrorConverter
58
+ ): Promise<Result<T>> {
59
+ try {
60
+ const data = await operation();
61
+ return successResult(data);
62
+ } catch (error) {
63
+ const converter = errorConverter ?? defaultErrorConverter;
64
+ return { success: false, error: converter(error) };
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Execute async operation with error handling and default code
70
+ */
71
+ export async function executeOperationWithCode<T>(
72
+ operation: () => Promise<T>,
73
+ defaultErrorCode = 'operation/failed'
74
+ ): Promise<Result<T>> {
75
+ return executeOperation(operation, (error) => defaultErrorConverter(error, defaultErrorCode));
76
+ }
77
+
78
+ /**
79
+ * Execute async void operation
80
+ * Useful for operations that don't return data
81
+ */
82
+ export async function executeVoidOperation(
83
+ operation: () => Promise<void>,
84
+ errorConverter?: ErrorConverter
85
+ ): Promise<Result<void>> {
86
+ return executeOperation(operation, errorConverter) as Promise<Result<void>>;
87
+ }
88
+
89
+ /**
90
+ * Execute async operation with auth error handling
91
+ */
92
+ export async function executeAuthOperation<T>(
93
+ operation: () => Promise<T>
94
+ ): Promise<Result<T>> {
95
+ return executeOperation(operation, authErrorConverter);
96
+ }
97
+
98
+ /**
99
+ * Execute multiple operations in parallel
100
+ * Returns success only if all operations succeed
101
+ */
102
+ export async function executeAll<T>(
103
+ ...operations: (() => Promise<Result<T>>)[]
104
+ ): Promise<Result<T[]>> {
105
+ try {
106
+ const results = await Promise.all(operations.map((op) => op()));
107
+ const failures = results.filter((r) => !r.success);
108
+ if (failures.length > 0) {
109
+ return failures[0] as FailureResult;
110
+ }
111
+ const data = results.map((r) => (r as { success: true; data: T }).data);
112
+ return successResult(data);
113
+ } catch (error) {
114
+ return failureResultFromError(error);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Execute operations in sequence, stopping at first failure
120
+ */
121
+ export async function executeSequence<T>(
122
+ ...operations: (() => Promise<Result<T>>)[]
123
+ ): Promise<Result<void>> {
124
+ for (const operation of operations) {
125
+ const result = await operation();
126
+ if (!result.success) {
127
+ return result as Result<void>;
128
+ }
129
+ }
130
+ return successResult();
131
+ }
132
+
133
+ /**
134
+ * Execute operation with retry
135
+ */
136
+ export async function executeWithRetry<T>(
137
+ operation: () => Promise<T>,
138
+ maxRetries = 3,
139
+ delayMs = 1000
140
+ ): Promise<Result<T>> {
141
+ let lastError: unknown;
142
+ for (let i = 0; i <= maxRetries; i++) {
143
+ try {
144
+ const data = await operation();
145
+ return successResult(data);
146
+ } catch (error) {
147
+ lastError = error;
148
+ if (i < maxRetries) {
149
+ await new Promise((resolve) => setTimeout(resolve, delayMs * (i + 1)));
150
+ }
151
+ }
152
+ }
153
+ return failureResultFromError(lastError);
154
+ }
155
+
156
+ /**
157
+ * Execute operation with timeout
158
+ */
159
+ export async function executeWithTimeout<T>(
160
+ operation: () => Promise<T>,
161
+ timeoutMs: number
162
+ ): Promise<Result<T>> {
163
+ return Promise.race([
164
+ executeOperation(operation),
165
+ new Promise<Result<T>>((resolve) =>
166
+ setTimeout(
167
+ () =>
168
+ resolve({
169
+ success: false,
170
+ error: { code: 'timeout', message: `Operation timed out after ${timeoutMs}ms` },
171
+ }),
172
+ timeoutMs
173
+ )
174
+ ),
175
+ ]);
176
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Credential Utility
3
+ * Centralized credential generation for authentication providers
4
+ * Eliminates code duplication across auth services
5
+ */
6
+
7
+ import {
8
+ GoogleAuthProvider,
9
+ OAuthProvider,
10
+ type AuthCredential,
11
+ } from 'firebase/auth';
12
+ import * as AppleAuthentication from 'expo-apple-authentication';
13
+ import { Platform } from 'react-native';
14
+ import { generateNonce, hashNonce } from '../../auth/infrastructure/services/crypto.util';
15
+
16
+ /**
17
+ * Generate Google credential from ID token
18
+ */
19
+ export function generateGoogleCredential(idToken: string): AuthCredential {
20
+ return GoogleAuthProvider.credential(idToken);
21
+ }
22
+
23
+ /**
24
+ * Generate Apple credential from identity token and nonce
25
+ */
26
+ export function generateAppleCredential(
27
+ identityToken: string,
28
+ rawNonce: string
29
+ ): AuthCredential {
30
+ const provider = new OAuthProvider('apple.com');
31
+ return provider.credential({
32
+ idToken: identityToken,
33
+ rawNonce,
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Check if Apple Sign-In is available
39
+ */
40
+ export async function isAppleSignInAvailable(): Promise<boolean> {
41
+ if (Platform.OS !== 'ios') {
42
+ return false;
43
+ }
44
+ try {
45
+ return await AppleAuthentication.isAvailableAsync();
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Perform Apple Sign-In with default scopes
53
+ */
54
+ export async function performAppleSignIn(): Promise<{
55
+ identityToken: string | null;
56
+ nonce: string;
57
+ rawNonce: string;
58
+ }> {
59
+ const rawNonce = await generateNonce();
60
+ const hashedNonce = await hashNonce(rawNonce);
61
+
62
+ const result = await AppleAuthentication.signInAsync({
63
+ requestedScopes: [
64
+ AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
65
+ AppleAuthentication.AppleAuthenticationScope.EMAIL,
66
+ ],
67
+ nonce: hashedNonce,
68
+ });
69
+
70
+ return {
71
+ identityToken: result.identityToken,
72
+ nonce: hashedNonce,
73
+ rawNonce,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Email/Password credential interface
79
+ */
80
+ export interface EmailPasswordCredential {
81
+ email: string;
82
+ password: string;
83
+ }
84
+
85
+ /**
86
+ * Validate email/password credential
87
+ */
88
+ export function isValidEmailPassword(credential: EmailPasswordCredential): boolean {
89
+ return (
90
+ typeof credential.email === 'string' &&
91
+ credential.email.length > 0 &&
92
+ typeof credential.password === 'string' &&
93
+ credential.password.length > 0
94
+ );
95
+ }
96
+
97
+ /**
98
+ * Custom credential generator interface
99
+ */
100
+ export interface CredentialGenerator {
101
+ generate(): AuthCredential | Promise<AuthCredential>;
102
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Domain Utils
3
+ * Centralized utilities for domain operations
4
+ */
5
+
6
+ // Result types
7
+ export {
8
+ successResult,
9
+ failureResult,
10
+ failureResultFrom,
11
+ failureResultFromError,
12
+ isSuccess,
13
+ isFailure,
14
+ getDataOrDefault,
15
+ mapResult,
16
+ chainResults,
17
+ type Result,
18
+ type SuccessResult,
19
+ type FailureResult,
20
+ type ErrorInfo,
21
+ } from './result.util';
22
+
23
+ // Async operation execution
24
+ export {
25
+ executeOperation,
26
+ executeOperationWithCode,
27
+ executeVoidOperation,
28
+ executeAuthOperation,
29
+ executeAll,
30
+ executeSequence,
31
+ executeWithRetry,
32
+ executeWithTimeout,
33
+ authErrorConverter,
34
+ defaultErrorConverter,
35
+ type ErrorConverter,
36
+ } from './async-executor.util';
37
+
38
+ // Service configuration
39
+ export {
40
+ ConfigurableService,
41
+ createConfigurableService,
42
+ type ConfigState,
43
+ type IConfigurableService,
44
+ } from './service-config.util';
45
+
46
+ // Credential utilities
47
+ export {
48
+ generateGoogleCredential,
49
+ generateAppleCredential,
50
+ isAppleSignInAvailable,
51
+ performAppleSignIn,
52
+ isValidEmailPassword,
53
+ type EmailPasswordCredential,
54
+ type CredentialGenerator,
55
+ } from './credential.util';
56
+
57
+ // Error handling
58
+ export {
59
+ toErrorInfo,
60
+ toAuthErrorInfo,
61
+ hasErrorCode,
62
+ isCancelledError,
63
+ isQuotaErrorInfo,
64
+ isNetworkError,
65
+ isAuthError,
66
+ isQuotaError,
67
+ isRetryableError,
68
+ getQuotaErrorMessage,
69
+ getRetryableErrorMessage,
70
+ type ErrorInfo as ErrorHandlerErrorInfo,
71
+ } from './error-handler.util';
72
+
73
+ // Type guards
74
+ export {
75
+ hasCodeProperty,
76
+ hasMessageProperty,
77
+ hasCodeAndMessageProperties,
78
+ } from './type-guards.util';
79
+
80
+ // Validation
81
+ export {
82
+ isValidString,
83
+ isEmptyString,
84
+ isValidFirebaseApiKey,
85
+ isValidFirebaseAuthDomain,
86
+ isValidFirebaseProjectId,
87
+ isValidUrl,
88
+ isValidHttpsUrl,
89
+ isValidEmail,
90
+ isDefined,
91
+ isNonEmptyArray,
92
+ isInRange,
93
+ isPositive,
94
+ isNonNegative,
95
+ } from './validation.util';
96
+
97
+ // ID generation
98
+ export {
99
+ generateUniqueId,
100
+ generateShortId,
101
+ } from './id-generator.util';