@umituz/react-native-firebase 3.0.3 → 3.0.6
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 +7 -1
- package/src/domains/account-deletion/index.ts +15 -10
- package/src/domains/account-deletion/infrastructure/services/account-deletion.service.ts +235 -26
- package/src/domains/account-deletion/infrastructure/services/reauthentication.service.ts +160 -0
- package/src/domains/auth/domain/value-objects/FirebaseAuthConfig.ts +1 -1
- package/src/domains/auth/index.ts +156 -6
- package/src/domains/auth/infrastructure/config/FirebaseAuthClient.ts +60 -48
- package/src/domains/auth/infrastructure/config/initializers/FirebaseAuthInitializer.ts +41 -5
- package/src/domains/auth/infrastructure/stores/auth.store.ts +4 -1
- package/src/domains/auth/presentation/hooks/useAnonymousAuth.ts +3 -1
- package/src/domains/auth/presentation/hooks/useGoogleOAuth.ts +115 -20
- package/src/domains/auth/presentation/hooks/utils/auth-state-change.handler.ts +5 -11
- package/src/domains/firestore/domain/constants/QuotaLimits.ts +101 -0
- package/src/domains/firestore/domain/entities/QuotaMetrics.ts +26 -0
- package/src/domains/firestore/domain/entities/RequestLog.ts +28 -0
- package/src/domains/firestore/domain/services/QuotaCalculator.ts +71 -0
- package/src/domains/firestore/index.ts +85 -31
- package/src/domains/firestore/infrastructure/config/FirestoreClient.ts +82 -45
- package/src/domains/firestore/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +249 -4
- package/src/domains/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +306 -0
- package/src/domains/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +92 -0
- package/src/domains/firestore/infrastructure/repositories/BasePaginatedRepository.ts +9 -1
- package/src/domains/firestore/infrastructure/repositories/BaseQueryRepository.ts +34 -8
- package/src/domains/firestore/infrastructure/repositories/BaseRepository.ts +48 -9
- package/src/domains/firestore/infrastructure/services/RequestLoggerService.ts +168 -0
- package/src/domains/firestore/presentation/hooks/index.ts +10 -0
- package/src/domains/firestore/presentation/hooks/useFirestoreMutation.ts +1 -1
- package/src/domains/firestore/presentation/hooks/useFirestoreQuery.ts +1 -1
- package/src/domains/firestore/presentation/hooks/useFirestoreSnapshot.ts +2 -1
- package/src/domains/firestore/presentation/hooks/useSmartFirestoreSnapshot.ts +362 -0
- package/src/domains/firestore/presentation/query-keys/createFirestoreKeys.ts +32 -0
- package/src/domains/firestore/presentation/query-keys/index.ts +1 -0
- package/src/domains/firestore/utils/deduplication/pending-query-manager.util.ts +126 -0
- package/src/domains/firestore/utils/deduplication/query-key-generator.util.ts +41 -0
- package/src/domains/firestore/utils/deduplication/timer-manager.util.ts +83 -0
- package/src/domains/firestore/utils/pagination.helper.ts +5 -2
- package/src/domains/firestore/utils/transaction/transaction.util.ts +8 -2
- package/src/index.ts +324 -32
- package/src/shared/domain/utils/calculation.util.ts +305 -17
- package/src/shared/domain/utils/error-handlers/error-messages.ts +0 -15
- package/src/shared/domain/utils/index.ts +5 -0
- package/src/shared/infrastructure/config/base/ClientStateManager.ts +82 -0
- package/src/shared/infrastructure/config/base/ServiceClientSingleton.ts +136 -20
- package/src/shared/infrastructure/config/clients/FirebaseClientSingleton.ts +1 -1
- package/src/shared/infrastructure/config/initializers/FirebaseAppInitializer.ts +9 -0
- package/src/shared/infrastructure/config/services/FirebaseInitializationService.ts +1 -1
- package/src/shared/infrastructure/config/state/FirebaseClientState.ts +14 -36
- package/src/application/auth/index.ts +0 -10
- package/src/application/auth/use-cases/index.ts +0 -6
- package/src/domains/account-deletion/domain/index.ts +0 -8
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionExecutor.ts +0 -79
- package/src/domains/account-deletion/infrastructure/services/AccountDeletionTypes.ts +0 -32
- package/src/domains/auth/domain.ts +0 -16
- package/src/domains/auth/infrastructure/config/index.ts +0 -2
- package/src/domains/auth/infrastructure/config/initializers/index.ts +0 -1
- package/src/domains/auth/infrastructure/services/index.ts +0 -16
- package/src/domains/auth/infrastructure/services/utils/index.ts +0 -1
- package/src/domains/auth/infrastructure/stores/index.ts +0 -1
- package/src/domains/auth/infrastructure/utils/index.ts +0 -1
- package/src/domains/auth/infrastructure.ts +0 -11
- package/src/domains/auth/presentation/hooks/useAppleAuth.ts +0 -82
- package/src/domains/auth/presentation.ts +0 -31
- package/src/domains/firestore/domain/entities/Collection.ts +0 -122
- package/src/domains/firestore/domain/entities/CollectionFactory.ts +0 -55
- package/src/domains/firestore/domain/entities/CollectionHelpers.ts +0 -143
- package/src/domains/firestore/domain/entities/CollectionUtils.ts +0 -72
- package/src/domains/firestore/domain/entities/CollectionValidation.ts +0 -138
- package/src/domains/firestore/domain/index.ts +0 -61
- package/src/domains/firestore/domain/value-objects/QueryOptions.ts +0 -143
- package/src/domains/firestore/domain/value-objects/QueryOptionsFactory.ts +0 -95
- package/src/domains/firestore/domain/value-objects/QueryOptionsHelpers.ts +0 -110
- package/src/domains/firestore/domain/value-objects/WhereClause.ts +0 -114
- package/src/domains/firestore/domain/value-objects/WhereClauseFactory.ts +0 -101
- package/src/domains/firestore/domain/value-objects/WhereClauseHelpers.ts +0 -123
- package/src/domains/firestore/domain/value-objects/WhereClauseValidation.ts +0 -83
- package/src/shared/infrastructure/base/ErrorHandler.ts +0 -81
- package/src/shared/infrastructure/base/ServiceBase.ts +0 -62
- package/src/shared/infrastructure/base/TypedGuard.ts +0 -131
- package/src/shared/infrastructure/base/index.ts +0 -34
- package/src/shared/types/firebase.types.ts +0 -274
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Auth } from 'firebase/auth';
|
|
8
|
-
import { getAuth as getFirebaseAuthFromFirebase } from 'firebase/auth';
|
|
9
8
|
import { getFirebaseApp } from '../../../../shared/infrastructure/config/services/FirebaseInitializationService';
|
|
9
|
+
import { FirebaseAuthInitializer } from './initializers/FirebaseAuthInitializer';
|
|
10
10
|
import type { FirebaseAuthConfig } from '../../domain/value-objects/FirebaseAuthConfig';
|
|
11
11
|
import { ServiceClientSingleton } from '../../../../shared/infrastructure/config/base/ServiceClientSingleton';
|
|
12
12
|
|
|
@@ -15,64 +15,76 @@ import { ServiceClientSingleton } from '../../../../shared/infrastructure/config
|
|
|
15
15
|
*/
|
|
16
16
|
class FirebaseAuthClientSingleton extends ServiceClientSingleton<Auth, FirebaseAuthConfig> {
|
|
17
17
|
private constructor() {
|
|
18
|
-
super(
|
|
18
|
+
super({
|
|
19
|
+
serviceName: 'FirebaseAuth',
|
|
20
|
+
initializer: (config?: FirebaseAuthConfig) => {
|
|
21
|
+
const app = getFirebaseApp();
|
|
22
|
+
if (!app) {
|
|
23
|
+
this.setError('Firebase App is not initialized');
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const auth = FirebaseAuthInitializer.initialize(app, config);
|
|
27
|
+
if (!auth) {
|
|
28
|
+
this.setError('Auth initialization returned null');
|
|
29
|
+
}
|
|
30
|
+
return auth;
|
|
31
|
+
},
|
|
32
|
+
});
|
|
19
33
|
}
|
|
20
34
|
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const app = getFirebaseApp();
|
|
24
|
-
if (!app) {
|
|
25
|
-
this.setError('Firebase App is not initialized');
|
|
26
|
-
throw new Error('Firebase App is not initialized');
|
|
27
|
-
}
|
|
35
|
+
private static instance: FirebaseAuthClientSingleton | null = null;
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} catch (error) {
|
|
33
|
-
const errorMessage = error instanceof Error ? error.message : 'Auth initialization failed';
|
|
34
|
-
this.setError(errorMessage);
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
+
static getInstance(): FirebaseAuthClientSingleton {
|
|
38
|
+
if (!this.instance) this.instance = new FirebaseAuthClientSingleton();
|
|
39
|
+
return this.instance;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return
|
|
42
|
+
/**
|
|
43
|
+
* Initialize Auth with optional configuration
|
|
44
|
+
*/
|
|
45
|
+
override initialize(config?: FirebaseAuthConfig): Auth | null {
|
|
46
|
+
return super.initialize(config);
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Get Auth instance
|
|
51
|
+
*/
|
|
52
|
+
getAuth(): Auth | null {
|
|
53
|
+
// Attempt initialization if not already initialized
|
|
54
|
+
if (!this.isInitialized() && !this.getInitializationError()) {
|
|
55
|
+
try {
|
|
56
|
+
const app = getFirebaseApp();
|
|
57
|
+
if (app) {
|
|
58
|
+
this.initialize();
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// Silently handle auto-initialization errors
|
|
62
|
+
// The error will be stored in state for later retrieval
|
|
63
|
+
const errorMessage = error instanceof Error ? error.message : 'Auto-initialization failed';
|
|
64
|
+
this.setError(errorMessage);
|
|
65
|
+
}
|
|
51
66
|
}
|
|
52
|
-
|
|
67
|
+
// Enable auto-initialization flag when getting instance
|
|
68
|
+
return this.getInstance(true);
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const isFirebaseAuthInitialized = (): boolean => {
|
|
67
|
-
return firebaseAuthClientSingleton.isInitialized();
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export const getFirebaseAuthInitializationError = (): Error | null => {
|
|
71
|
-
return firebaseAuthClientSingleton.getInitializationError();
|
|
72
|
-
};
|
|
72
|
+
function getFirebaseAuthClientSafe(): FirebaseAuthClientSingleton | null {
|
|
73
|
+
try {
|
|
74
|
+
return FirebaseAuthClientSingleton.getInstance();
|
|
75
|
+
} catch {
|
|
76
|
+
if (__DEV__) {
|
|
77
|
+
console.warn('[Firebase] Could not create FirebaseAuth client singleton.');
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
73
82
|
|
|
74
|
-
export const
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
export const firebaseAuthClient = getFirebaseAuthClientSafe();
|
|
84
|
+
export const initializeFirebaseAuth = (c?: FirebaseAuthConfig) => firebaseAuthClient?.initialize(c) ?? null;
|
|
85
|
+
export const getFirebaseAuth = () => firebaseAuthClient?.getAuth() ?? null;
|
|
86
|
+
export const isFirebaseAuthInitialized = () => firebaseAuthClient?.isInitialized() ?? false;
|
|
87
|
+
export const getFirebaseAuthInitializationError = () => firebaseAuthClient?.getInitializationError() ?? null;
|
|
88
|
+
export const resetFirebaseAuthClient = () => firebaseAuthClient?.reset();
|
|
77
89
|
|
|
78
90
|
export type { Auth } from 'firebase/auth';
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Auth Initializer
|
|
3
|
+
*
|
|
4
|
+
* Single Responsibility: Initialize Firebase Auth instance
|
|
5
|
+
* Platform-agnostic: Works on all platforms (Web, iOS, Android)
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import {
|
|
2
9
|
initializeAuth,
|
|
3
10
|
getAuth,
|
|
4
11
|
// @ts-expect-error: getReactNativePersistence exists in the React Native bundle but missing from type definitions
|
|
12
|
+
// See: https://github.com/firebase/firebase-js-sdk/issues/9316
|
|
5
13
|
getReactNativePersistence,
|
|
6
14
|
} from 'firebase/auth';
|
|
7
15
|
import type { Auth } from 'firebase/auth';
|
|
@@ -9,8 +17,40 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
|
9
17
|
import type { FirebaseApp } from 'firebase/app';
|
|
10
18
|
import type { FirebaseAuthConfig } from '../../../domain/value-objects/FirebaseAuthConfig';
|
|
11
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Initializes Firebase Auth
|
|
22
|
+
* Platform-agnostic: Works on all platforms (Web, iOS, Android)
|
|
23
|
+
*/
|
|
12
24
|
export class FirebaseAuthInitializer {
|
|
25
|
+
/**
|
|
26
|
+
* Initialize Firebase Auth with persistence support
|
|
27
|
+
*/
|
|
13
28
|
static initialize(app: FirebaseApp, config?: FirebaseAuthConfig): Auth | null {
|
|
29
|
+
try {
|
|
30
|
+
const auth = this.initializeWithPersistence(app, config);
|
|
31
|
+
return auth;
|
|
32
|
+
} catch (error: unknown) {
|
|
33
|
+
return this.handleInitializationError(error, app);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private static handleInitializationError(error: unknown, app: FirebaseApp): Auth | null {
|
|
38
|
+
// Any initialization error: try to get existing auth instance
|
|
39
|
+
return this.getExistingAuth(app);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private static getExistingAuth(app: FirebaseApp): Auth | null {
|
|
43
|
+
try {
|
|
44
|
+
return getAuth(app);
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private static initializeWithPersistence(
|
|
51
|
+
app: FirebaseApp,
|
|
52
|
+
config?: FirebaseAuthConfig
|
|
53
|
+
): Auth | null {
|
|
14
54
|
try {
|
|
15
55
|
const storage = config?.authStorage || {
|
|
16
56
|
getItem: (key: string) => AsyncStorage.getItem(key),
|
|
@@ -22,11 +62,7 @@ export class FirebaseAuthInitializer {
|
|
|
22
62
|
persistence: getReactNativePersistence(storage),
|
|
23
63
|
});
|
|
24
64
|
} catch {
|
|
25
|
-
|
|
26
|
-
return getAuth(app);
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
65
|
+
return this.getExistingAuth(app);
|
|
30
66
|
}
|
|
31
67
|
}
|
|
32
68
|
}
|
|
@@ -82,7 +82,10 @@ export const useFirebaseAuthStore = createStore<AuthState, AuthActions>({
|
|
|
82
82
|
unsubscribe = null;
|
|
83
83
|
}
|
|
84
84
|
setupInProgress = false;
|
|
85
|
-
|
|
85
|
+
// Only decrement if we successfully incremented (wasn't already set up)
|
|
86
|
+
if (state.listenerSetup === false && !unsubscribe) {
|
|
87
|
+
activeComponentCount--;
|
|
88
|
+
}
|
|
86
89
|
set({ listenerSetup: false, loading: false });
|
|
87
90
|
throw error; // Re-throw to allow caller to handle
|
|
88
91
|
}
|
|
@@ -87,9 +87,11 @@ export function useAnonymousAuth(auth: Auth | null): UseAnonymousAuthResult {
|
|
|
87
87
|
|
|
88
88
|
try {
|
|
89
89
|
// Listen to auth state changes
|
|
90
|
-
|
|
90
|
+
// FIX: Capture return value for proper cleanup
|
|
91
|
+
const unsubscribe = onAuthStateChanged(auth, (user) => {
|
|
91
92
|
handleAuthStateChange(user);
|
|
92
93
|
});
|
|
94
|
+
unsubscribeRef.current = unsubscribe;
|
|
93
95
|
} catch (err) {
|
|
94
96
|
const authError = err instanceof Error ? err : new Error('Auth listener setup failed');
|
|
95
97
|
setError(authError);
|
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useGoogleOAuth Hook
|
|
3
3
|
* Handles Google OAuth flow using expo-auth-session and Firebase auth
|
|
4
|
-
*
|
|
5
|
-
* Max lines: 150 (enforced for maintainability)
|
|
4
|
+
* This hook is optional and requires expo-auth-session to be installed
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
import { useState, useCallback } from
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
7
|
+
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
|
8
|
+
import { googleOAuthService } from "../../infrastructure/services/google-oauth.service";
|
|
9
|
+
import { getFirebaseAuth } from "../../infrastructure/config/FirebaseAuthClient";
|
|
10
|
+
import type { GoogleOAuthConfig } from "../../infrastructure/services/google-oauth.service";
|
|
11
|
+
// Conditional import for expo-auth-session
|
|
12
|
+
interface AuthSessionResponse {
|
|
13
|
+
type: string;
|
|
14
|
+
authentication?: { idToken?: string } | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ExpoAuthSessionModule {
|
|
18
|
+
useAuthRequest: (config: {
|
|
19
|
+
iosClientId: string;
|
|
20
|
+
webClientId: string;
|
|
21
|
+
androidClientId: string;
|
|
22
|
+
}) => [unknown, AuthSessionResponse | null, (() => Promise<AuthSessionResponse>) | null];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let ExpoAuthSession: ExpoAuthSessionModule | null = null;
|
|
26
|
+
let isExpoAuthAvailable = false;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
30
|
+
ExpoAuthSession = require("expo-auth-session/providers/google") as ExpoAuthSessionModule;
|
|
31
|
+
isExpoAuthAvailable = true;
|
|
32
|
+
} catch {
|
|
33
|
+
// expo-auth-session not available - hook will return unavailable state
|
|
34
|
+
}
|
|
12
35
|
|
|
13
36
|
export interface UseGoogleOAuthResult {
|
|
14
37
|
signInWithGoogle: () => Promise<SocialAuthResult>;
|
|
@@ -26,41 +49,113 @@ interface SocialAuthResult {
|
|
|
26
49
|
|
|
27
50
|
/**
|
|
28
51
|
* Hook for Google OAuth authentication
|
|
52
|
+
* Requires expo-auth-session and expo-web-browser to be installed
|
|
29
53
|
*/
|
|
30
54
|
export function useGoogleOAuth(config?: GoogleOAuthConfig): UseGoogleOAuthResult {
|
|
31
55
|
const [isLoading, setIsLoading] = useState(false);
|
|
32
56
|
const [googleError, setGoogleError] = useState<string | null>(null);
|
|
33
57
|
|
|
34
|
-
|
|
58
|
+
// Memoize service checks to avoid repeated calls on every render
|
|
59
|
+
const googleAvailable = useMemo(() => googleOAuthService.isAvailable(), []);
|
|
60
|
+
const googleConfigured = useMemo(() => googleOAuthService.isConfigured(config), [config]);
|
|
35
61
|
|
|
36
|
-
|
|
37
|
-
const
|
|
62
|
+
// Keep config in a ref so the response useEffect doesn't re-run when config object reference changes
|
|
63
|
+
const configRef = useRef(config);
|
|
64
|
+
configRef.current = config;
|
|
65
|
+
|
|
66
|
+
// Call the Hook directly (only valid in React component)
|
|
67
|
+
// If expo-auth-session is not available, these will be null
|
|
68
|
+
// Empty strings are passed when config is missing to preserve hook call order (Rules of Hooks)
|
|
69
|
+
const [request, response, promptAsync] = isExpoAuthAvailable && ExpoAuthSession
|
|
70
|
+
? ExpoAuthSession.useAuthRequest({
|
|
71
|
+
iosClientId: config?.iosClientId ?? "",
|
|
72
|
+
webClientId: config?.webClientId ?? "",
|
|
73
|
+
androidClientId: config?.androidClientId ?? "",
|
|
74
|
+
})
|
|
75
|
+
: [null, null, null];
|
|
76
|
+
|
|
77
|
+
// Handle OAuth response
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!googleAvailable || !response) return;
|
|
80
|
+
|
|
81
|
+
const handleResponse = async () => {
|
|
82
|
+
if (response.type === "success" && response.authentication?.idToken) {
|
|
83
|
+
setIsLoading(true);
|
|
84
|
+
setGoogleError(null);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const auth = getFirebaseAuth();
|
|
88
|
+
if (!auth) {
|
|
89
|
+
setGoogleError("Firebase Auth not initialized");
|
|
90
|
+
setIsLoading(false);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await googleOAuthService.signInWithOAuth(
|
|
95
|
+
auth,
|
|
96
|
+
configRef.current,
|
|
97
|
+
async () => response
|
|
98
|
+
);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
setGoogleError(
|
|
101
|
+
error instanceof Error ? error.message : "Firebase sign-in failed"
|
|
102
|
+
);
|
|
103
|
+
} finally {
|
|
104
|
+
setIsLoading(false);
|
|
105
|
+
}
|
|
106
|
+
} else if (response.type === "error") {
|
|
107
|
+
setGoogleError("Google authentication failed");
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Call async function and catch errors separately
|
|
112
|
+
handleResponse().catch((err) => {
|
|
113
|
+
// Errors are already handled in handleResponse
|
|
114
|
+
if (__DEV__) {
|
|
115
|
+
console.error('[useGoogleOAuth] Unexpected error in handleResponse:', err);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}, [response, googleAvailable]); // config read via ref to prevent re-running on reference changes
|
|
38
119
|
|
|
39
120
|
const signInWithGoogle = useCallback(async (): Promise<SocialAuthResult> => {
|
|
121
|
+
if (!googleAvailable) {
|
|
122
|
+
const error = "expo-auth-session is not available. Please install expo-auth-session and expo-web-browser.";
|
|
123
|
+
setGoogleError(error);
|
|
124
|
+
return { success: false, error };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!googleConfigured) {
|
|
128
|
+
const error = "Google Sign-In is not configured. Please provide valid client IDs.";
|
|
129
|
+
setGoogleError(error);
|
|
130
|
+
return { success: false, error };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!request || !promptAsync) {
|
|
134
|
+
const error = "Google Sign-In not ready";
|
|
135
|
+
setGoogleError(error);
|
|
136
|
+
return { success: false, error };
|
|
137
|
+
}
|
|
138
|
+
|
|
40
139
|
setIsLoading(true);
|
|
41
140
|
setGoogleError(null);
|
|
42
141
|
|
|
43
142
|
try {
|
|
44
143
|
const auth = getFirebaseAuth();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return { success: false, error: result.error };
|
|
144
|
+
if (!auth) {
|
|
145
|
+
const error = "Firebase Auth not initialized";
|
|
146
|
+
setGoogleError(error);
|
|
147
|
+
return { success: false, error };
|
|
50
148
|
}
|
|
51
149
|
|
|
52
|
-
return
|
|
53
|
-
success: true,
|
|
54
|
-
isNewUser: result.isNewUser,
|
|
55
|
-
};
|
|
150
|
+
return await googleOAuthService.signInWithOAuth(auth, config, promptAsync);
|
|
56
151
|
} catch (error) {
|
|
57
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
152
|
+
const errorMessage = error instanceof Error ? error.message : "Google sign-in failed";
|
|
58
153
|
setGoogleError(errorMessage);
|
|
59
154
|
return { success: false, error: errorMessage };
|
|
60
155
|
} finally {
|
|
61
156
|
setIsLoading(false);
|
|
62
157
|
}
|
|
63
|
-
}, [
|
|
158
|
+
}, [googleAvailable, googleConfigured, request, promptAsync]); // config read via ref to prevent re-creation on reference changes
|
|
64
159
|
|
|
65
160
|
return {
|
|
66
161
|
signInWithGoogle,
|
|
@@ -30,17 +30,11 @@ export function createAuthStateChangeHandler(
|
|
|
30
30
|
const { setAuthState, setLoading, setError } = params;
|
|
31
31
|
|
|
32
32
|
return (user: User | null) => {
|
|
33
|
-
try
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const authError =
|
|
39
|
-
err instanceof Error ? err : new Error('Auth state check failed');
|
|
40
|
-
setError(authError);
|
|
41
|
-
} finally {
|
|
42
|
-
setLoading(false);
|
|
43
|
-
}
|
|
33
|
+
// FIX: Removed unnecessary try-catch - createAuthCheckResult cannot throw
|
|
34
|
+
const authState = createAuthCheckResult(user);
|
|
35
|
+
setAuthState(authState);
|
|
36
|
+
setError(null);
|
|
37
|
+
setLoading(false);
|
|
44
38
|
};
|
|
45
39
|
}
|
|
46
40
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota Limit Constants
|
|
3
|
+
* Domain layer - Default Firestore quota limits
|
|
4
|
+
*
|
|
5
|
+
* General-purpose constants for any app using Firestore
|
|
6
|
+
* Based on Firestore free tier and pricing documentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { calculatePercentage, calculateRemaining } from '../../../../shared/domain/utils/calculation.util';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Firestore free tier daily limits
|
|
13
|
+
* https://firebase.google.com/docs/firestore/quotas
|
|
14
|
+
*/
|
|
15
|
+
export const FREE_TIER_LIMITS = {
|
|
16
|
+
/**
|
|
17
|
+
* Daily read operations (documents)
|
|
18
|
+
* Free tier: 50,000 reads/day
|
|
19
|
+
*/
|
|
20
|
+
DAILY_READS: 50_000,
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Daily write operations (documents)
|
|
24
|
+
* Free tier: 20,000 writes/day
|
|
25
|
+
*/
|
|
26
|
+
DAILY_WRITES: 20_000,
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Daily delete operations (documents)
|
|
30
|
+
* Free tier: 20,000 deletes/day
|
|
31
|
+
*/
|
|
32
|
+
DAILY_DELETES: 20_000,
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Stored data (GB)
|
|
36
|
+
* Free tier: 1 GB
|
|
37
|
+
*/
|
|
38
|
+
STORAGE_GB: 1,
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Quota warning thresholds (percentage of limit)
|
|
43
|
+
* Apps can use these to show warnings before hitting limits
|
|
44
|
+
*/
|
|
45
|
+
export const QUOTA_THRESHOLDS = {
|
|
46
|
+
/**
|
|
47
|
+
* Warning threshold (80% of limit)
|
|
48
|
+
* Show warning to user
|
|
49
|
+
*/
|
|
50
|
+
WARNING: 0.8,
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Critical threshold (95% of limit)
|
|
54
|
+
* Show critical alert, consider disabling features
|
|
55
|
+
*/
|
|
56
|
+
CRITICAL: 0.95,
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Emergency threshold (98% of limit)
|
|
60
|
+
* Disable non-essential features
|
|
61
|
+
*/
|
|
62
|
+
EMERGENCY: 0.98,
|
|
63
|
+
} as const;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Calculate quota usage percentage
|
|
67
|
+
* Optimized: Uses centralized calculation utility
|
|
68
|
+
* @param current - Current usage count
|
|
69
|
+
* @param limit - Total limit
|
|
70
|
+
* @returns Percentage (0-1)
|
|
71
|
+
*/
|
|
72
|
+
export function calculateQuotaUsage(current: number, limit: number): number {
|
|
73
|
+
return calculatePercentage(current, limit);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if quota threshold is reached
|
|
78
|
+
* @param current - Current usage count
|
|
79
|
+
* @param limit - Total limit
|
|
80
|
+
* @param threshold - Threshold to check (0-1)
|
|
81
|
+
* @returns true if threshold is reached
|
|
82
|
+
*/
|
|
83
|
+
export function isQuotaThresholdReached(
|
|
84
|
+
current: number,
|
|
85
|
+
limit: number,
|
|
86
|
+
threshold: number,
|
|
87
|
+
): boolean {
|
|
88
|
+
const usage = calculateQuotaUsage(current, limit);
|
|
89
|
+
return usage >= threshold;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get remaining quota
|
|
94
|
+
* Optimized: Uses centralized calculation utility
|
|
95
|
+
* @param current - Current usage count
|
|
96
|
+
* @param limit - Total limit
|
|
97
|
+
* @returns Remaining quota count
|
|
98
|
+
*/
|
|
99
|
+
export function getRemainingQuota(current: number, limit: number): number {
|
|
100
|
+
return calculateRemaining(current, limit);
|
|
101
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota Metrics Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface QuotaMetrics {
|
|
6
|
+
readCount: number;
|
|
7
|
+
writeCount: number;
|
|
8
|
+
deleteCount: number;
|
|
9
|
+
lastResetDate: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface QuotaLimits {
|
|
13
|
+
dailyReadLimit: number;
|
|
14
|
+
dailyWriteLimit: number;
|
|
15
|
+
dailyDeleteLimit: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface QuotaStatus {
|
|
19
|
+
metrics: QuotaMetrics;
|
|
20
|
+
limits: QuotaLimits;
|
|
21
|
+
readPercentage: number;
|
|
22
|
+
writePercentage: number;
|
|
23
|
+
deletePercentage: number;
|
|
24
|
+
isNearLimit: boolean;
|
|
25
|
+
isOverLimit: boolean;
|
|
26
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Log Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type RequestType = 'read' | 'write' | 'delete' | 'listener';
|
|
6
|
+
|
|
7
|
+
export interface RequestLog {
|
|
8
|
+
id: string;
|
|
9
|
+
type: RequestType;
|
|
10
|
+
collection: string;
|
|
11
|
+
documentId?: string;
|
|
12
|
+
cached: boolean;
|
|
13
|
+
success: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
duration?: number;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RequestStats {
|
|
20
|
+
totalRequests: number;
|
|
21
|
+
readRequests: number;
|
|
22
|
+
writeRequests: number;
|
|
23
|
+
deleteRequests: number;
|
|
24
|
+
listenerRequests: number;
|
|
25
|
+
cachedRequests: number;
|
|
26
|
+
failedRequests: number;
|
|
27
|
+
averageDuration: number;
|
|
28
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota Calculator Service
|
|
3
|
+
* Domain service for calculating quota usage and status
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { QuotaMetrics, QuotaLimits, QuotaStatus } from '../entities/QuotaMetrics';
|
|
7
|
+
import { FREE_TIER_LIMITS, QUOTA_THRESHOLDS, calculateQuotaUsage } from '../constants/QuotaLimits';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default quota limits (Firebase Spark Plan)
|
|
11
|
+
* Can be overridden via configuration
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_QUOTA_LIMITS: QuotaLimits = {
|
|
14
|
+
dailyReadLimit: FREE_TIER_LIMITS.DAILY_READS,
|
|
15
|
+
dailyWriteLimit: FREE_TIER_LIMITS.DAILY_WRITES,
|
|
16
|
+
dailyDeleteLimit: FREE_TIER_LIMITS.DAILY_DELETES,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class QuotaCalculator {
|
|
20
|
+
/**
|
|
21
|
+
* Calculate quota status from metrics and limits
|
|
22
|
+
*/
|
|
23
|
+
static calculateStatus(
|
|
24
|
+
metrics: QuotaMetrics,
|
|
25
|
+
limits: QuotaLimits = DEFAULT_QUOTA_LIMITS,
|
|
26
|
+
): QuotaStatus {
|
|
27
|
+
const readPercentage = calculateQuotaUsage(metrics.readCount, limits.dailyReadLimit) * 100;
|
|
28
|
+
const writePercentage = calculateQuotaUsage(metrics.writeCount, limits.dailyWriteLimit) * 100;
|
|
29
|
+
const deletePercentage = calculateQuotaUsage(metrics.deleteCount, limits.dailyDeleteLimit) * 100;
|
|
30
|
+
|
|
31
|
+
const warningThreshold = QUOTA_THRESHOLDS.WARNING * 100;
|
|
32
|
+
const isNearLimit =
|
|
33
|
+
readPercentage >= warningThreshold ||
|
|
34
|
+
writePercentage >= warningThreshold ||
|
|
35
|
+
deletePercentage >= warningThreshold;
|
|
36
|
+
|
|
37
|
+
const isOverLimit =
|
|
38
|
+
readPercentage >= 100 ||
|
|
39
|
+
writePercentage >= 100 ||
|
|
40
|
+
deletePercentage >= 100;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
metrics,
|
|
44
|
+
limits,
|
|
45
|
+
readPercentage,
|
|
46
|
+
writePercentage,
|
|
47
|
+
deletePercentage,
|
|
48
|
+
isNearLimit,
|
|
49
|
+
isOverLimit,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get default quota limits
|
|
55
|
+
*/
|
|
56
|
+
static getDefaultLimits(): QuotaLimits {
|
|
57
|
+
return { ...DEFAULT_QUOTA_LIMITS };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if metrics are within limits
|
|
62
|
+
*/
|
|
63
|
+
static isWithinLimits(
|
|
64
|
+
metrics: QuotaMetrics,
|
|
65
|
+
limits: QuotaLimits = DEFAULT_QUOTA_LIMITS,
|
|
66
|
+
): boolean {
|
|
67
|
+
const status = this.calculateStatus(metrics, limits);
|
|
68
|
+
return !status.isOverLimit;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|