@umituz/react-native-firebase 1.13.137 → 1.13.139
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/auth/infrastructure/services/account-deletion.service.ts +52 -16
- package/src/auth/infrastructure/services/anonymous-auth.service.ts +0 -1
- package/src/auth/infrastructure/services/apple-auth.service.ts +2 -5
- package/src/auth/infrastructure/services/auth-guard.service.ts +1 -3
- package/src/auth/infrastructure/services/auth-utils.service.ts +14 -9
- package/src/auth/infrastructure/services/base/base-auth.service.ts +0 -9
- package/src/auth/infrastructure/services/firestore-utils.service.ts +1 -4
- package/src/auth/infrastructure/services/google-auth.service.ts +0 -2
- package/src/auth/infrastructure/services/password.service.ts +4 -3
- package/src/auth/infrastructure/services/reauthentication.service.ts +46 -8
- package/src/auth/infrastructure/stores/auth.store.ts +0 -3
- package/src/auth/presentation/hooks/utils/auth-state-change.handler.ts +8 -29
- package/src/domain/guards/firebase-error.guard.ts +1 -1
- package/src/domain/utils/error-handler.util.ts +5 -26
- package/src/domain/utils/type-guards.util.ts +66 -0
- package/src/firestore/infrastructure/repositories/BasePaginatedRepository.ts +6 -5
- package/src/firestore/infrastructure/repositories/BaseQueryRepository.ts +4 -4
- package/src/firestore/infrastructure/repositories/BaseRepository.ts +63 -108
- package/src/firestore/utils/transaction/transaction.util.ts +3 -8
- package/src/infrastructure/config/FirebaseClient.ts +32 -38
- package/src/infrastructure/config/FirebaseConfigLoader.ts +11 -13
- package/src/infrastructure/config/base/ServiceClientSingleton.ts +0 -13
- package/src/infrastructure/config/initializers/FirebaseAppInitializer.ts +3 -5
- package/src/infrastructure/config/orchestrators/FirebaseInitializationOrchestrator.ts +36 -44
- package/src/infrastructure/config/services/FirebaseServiceInitializer.ts +33 -39
- package/src/init/createFirebaseInitModule.ts +2 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-firebase",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.139",
|
|
4
4
|
"description": "Unified Firebase package for React Native apps - Auth and Firestore services using Firebase JS SDK (no native modules).",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Account Deletion Service
|
|
3
|
+
* Handles Firebase account deletion with reauthentication support
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import { deleteUser, type User } from "firebase/auth";
|
|
@@ -21,14 +22,19 @@ export async function deleteCurrentUser(
|
|
|
21
22
|
const auth = getFirebaseAuth();
|
|
22
23
|
const user = auth?.currentUser;
|
|
23
24
|
|
|
24
|
-
if (!auth || !user)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
if (!auth || !user) {
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
error: { code: "auth/not-ready", message: "Auth not ready", requiresReauth: false }
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (user.isAnonymous) {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
error: { code: "auth/anonymous", message: "Cannot delete anonymous", requiresReauth: false }
|
|
36
|
+
};
|
|
37
|
+
}
|
|
32
38
|
|
|
33
39
|
try {
|
|
34
40
|
await deleteUser(user);
|
|
@@ -50,14 +56,27 @@ async function attemptReauth(user: User, options: AccountDeletionOptions): Promi
|
|
|
50
56
|
const provider = getUserAuthProvider(user);
|
|
51
57
|
let res: { success: boolean; error?: { code?: string; message?: string } };
|
|
52
58
|
|
|
53
|
-
if (provider === "apple.com")
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
if (provider === "apple.com") {
|
|
60
|
+
res = await reauthenticateWithApple(user);
|
|
61
|
+
} else if (provider === "google.com") {
|
|
62
|
+
if (!options.googleIdToken) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: { code: "auth/google-reauth", message: "Google reauth required", requiresReauth: true }
|
|
66
|
+
};
|
|
67
|
+
}
|
|
56
68
|
res = await reauthenticateWithGoogle(user, options.googleIdToken);
|
|
57
69
|
} else if (provider === "password") {
|
|
58
|
-
if (!options.password)
|
|
70
|
+
if (!options.password) {
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
error: { code: "auth/password-reauth", message: "Password required", requiresReauth: true }
|
|
74
|
+
};
|
|
75
|
+
}
|
|
59
76
|
res = await reauthenticateWithPassword(user, options.password);
|
|
60
|
-
} else
|
|
77
|
+
} else {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
61
80
|
|
|
62
81
|
if (res.success) {
|
|
63
82
|
try {
|
|
@@ -68,16 +87,33 @@ async function attemptReauth(user: User, options: AccountDeletionOptions): Promi
|
|
|
68
87
|
return { success: false, error: { ...authErr, requiresReauth: false } };
|
|
69
88
|
}
|
|
70
89
|
}
|
|
71
|
-
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: {
|
|
94
|
+
code: res.error?.code || "auth/reauth-failed",
|
|
95
|
+
message: res.error?.message || "Reauth failed",
|
|
96
|
+
requiresReauth: true
|
|
97
|
+
}
|
|
98
|
+
};
|
|
72
99
|
}
|
|
73
100
|
|
|
74
101
|
export async function deleteUserAccount(user: User | null): Promise<AccountDeletionResult> {
|
|
75
|
-
if (!user || user.isAnonymous)
|
|
102
|
+
if (!user || user.isAnonymous) {
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
error: { code: "auth/invalid", message: "Invalid user", requiresReauth: false }
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
76
109
|
try {
|
|
77
110
|
await deleteUser(user);
|
|
78
111
|
return { success: true };
|
|
79
112
|
} catch (error: unknown) {
|
|
80
113
|
const authErr = toAuthErrorInfo(error);
|
|
81
|
-
return {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: { ...authErr, requiresReauth: authErr.code === "auth/requires-recent-login" }
|
|
117
|
+
};
|
|
82
118
|
}
|
|
83
119
|
}
|
|
@@ -15,7 +15,6 @@ import type { AppleAuthResult } from "./apple-auth.types";
|
|
|
15
15
|
import {
|
|
16
16
|
createSuccessResult,
|
|
17
17
|
createFailureResult,
|
|
18
|
-
logAuthError,
|
|
19
18
|
isCancellationError,
|
|
20
19
|
} from "./base/base-auth.service";
|
|
21
20
|
|
|
@@ -24,8 +23,7 @@ export class AppleAuthService {
|
|
|
24
23
|
if (Platform.OS !== "ios") return false;
|
|
25
24
|
try {
|
|
26
25
|
return await AppleAuthentication.isAvailableAsync();
|
|
27
|
-
} catch
|
|
28
|
-
if (__DEV__) console.warn('[AppleAuth] isAvailable check failed:', error);
|
|
26
|
+
} catch {
|
|
29
27
|
return false;
|
|
30
28
|
}
|
|
31
29
|
}
|
|
@@ -37,7 +35,7 @@ export class AppleAuthService {
|
|
|
37
35
|
return {
|
|
38
36
|
success: false,
|
|
39
37
|
error: "Apple Sign-In is not available on this device",
|
|
40
|
-
code: "unavailable"
|
|
38
|
+
code: "unavailable"
|
|
41
39
|
};
|
|
42
40
|
}
|
|
43
41
|
|
|
@@ -77,7 +75,6 @@ export class AppleAuthService {
|
|
|
77
75
|
};
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
logAuthError('Apple Sign-In', error);
|
|
81
78
|
return createFailureResult(error);
|
|
82
79
|
}
|
|
83
80
|
}
|
|
@@ -52,8 +52,7 @@ export class AuthGuardService {
|
|
|
52
52
|
async getAuthenticatedUserId(): Promise<string | null> {
|
|
53
53
|
try {
|
|
54
54
|
return await this.requireAuthenticatedUser();
|
|
55
|
-
} catch
|
|
56
|
-
if (__DEV__) console.warn('[AuthGuard] getAuthenticatedUserId:', error);
|
|
55
|
+
} catch {
|
|
57
56
|
return null;
|
|
58
57
|
}
|
|
59
58
|
}
|
|
@@ -95,4 +94,3 @@ export class AuthGuardService {
|
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
export const authGuardService = new AuthGuardService();
|
|
98
|
-
|
|
@@ -15,12 +15,11 @@ export interface AuthCheckResult {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* Create auth check result from user
|
|
19
|
+
* Centralized utility for creating AuthCheckResult objects
|
|
19
20
|
*/
|
|
20
|
-
export function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (!currentUser) {
|
|
21
|
+
export function createAuthCheckResult(user: User | null): AuthCheckResult {
|
|
22
|
+
if (!user) {
|
|
24
23
|
return {
|
|
25
24
|
isAuthenticated: false,
|
|
26
25
|
isAnonymous: false,
|
|
@@ -31,12 +30,19 @@ export function checkAuthState(auth: Auth): AuthCheckResult {
|
|
|
31
30
|
|
|
32
31
|
return {
|
|
33
32
|
isAuthenticated: true,
|
|
34
|
-
isAnonymous:
|
|
35
|
-
currentUser,
|
|
36
|
-
userId:
|
|
33
|
+
isAnonymous: user.isAnonymous,
|
|
34
|
+
currentUser: user,
|
|
35
|
+
userId: user.uid,
|
|
37
36
|
};
|
|
38
37
|
}
|
|
39
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Check current authentication state
|
|
41
|
+
*/
|
|
42
|
+
export function checkAuthState(auth: Auth): AuthCheckResult {
|
|
43
|
+
return createAuthCheckResult(auth.currentUser);
|
|
44
|
+
}
|
|
45
|
+
|
|
40
46
|
/**
|
|
41
47
|
* Check if user is authenticated
|
|
42
48
|
*/
|
|
@@ -124,4 +130,3 @@ export function isValidUser(user: unknown): user is User {
|
|
|
124
130
|
isValidString(user.uid)
|
|
125
131
|
);
|
|
126
132
|
}
|
|
127
|
-
|
|
@@ -93,12 +93,3 @@ export function createSuccessResult(userCredential: UserCredential): AuthSuccess
|
|
|
93
93
|
isNewUser: checkIsNewUser(userCredential),
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Log auth error in development mode
|
|
99
|
-
*/
|
|
100
|
-
export function logAuthError(serviceName: string, error: unknown): void {
|
|
101
|
-
if (__DEV__) {
|
|
102
|
-
console.error(`[Firebase Auth] ${serviceName} failed:`, error);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
@@ -85,10 +85,7 @@ export function shouldSkipFirestoreQuery(
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
return createResult(false, authState);
|
|
88
|
-
} catch
|
|
89
|
-
if (__DEV__) {
|
|
90
|
-
console.error("[FirestoreUtils] Error checking query", error);
|
|
91
|
-
}
|
|
88
|
+
} catch {
|
|
92
89
|
// Return a default result on error
|
|
93
90
|
return createResult(true, {
|
|
94
91
|
isAuthenticated: false,
|
|
@@ -12,7 +12,6 @@ import type { GoogleAuthConfig, GoogleAuthResult } from "./google-auth.types";
|
|
|
12
12
|
import {
|
|
13
13
|
createSuccessResult,
|
|
14
14
|
createFailureResult,
|
|
15
|
-
logAuthError,
|
|
16
15
|
} from "./base/base-auth.service";
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -48,7 +47,6 @@ export class GoogleAuthService {
|
|
|
48
47
|
const userCredential = await signInWithCredential(auth, credential);
|
|
49
48
|
return createSuccessResult(userCredential);
|
|
50
49
|
} catch (error) {
|
|
51
|
-
logAuthError('Google Sign-In', error);
|
|
52
50
|
return createFailureResult(error);
|
|
53
51
|
}
|
|
54
52
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { updatePassword, type User } from 'firebase/auth';
|
|
7
|
+
import { toAuthErrorInfo } from '../../../domain/utils/error-handler.util';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Result of a password update operation
|
|
@@ -25,12 +26,12 @@ export async function updateUserPassword(user: User, newPassword: string): Promi
|
|
|
25
26
|
await updatePassword(user, newPassword);
|
|
26
27
|
return { success: true };
|
|
27
28
|
} catch (error: unknown) {
|
|
28
|
-
const
|
|
29
|
+
const errorInfo = toAuthErrorInfo(error);
|
|
29
30
|
return {
|
|
30
31
|
success: false,
|
|
31
32
|
error: {
|
|
32
|
-
code:
|
|
33
|
-
message:
|
|
33
|
+
code: errorInfo.code,
|
|
34
|
+
message: errorInfo.message,
|
|
34
35
|
},
|
|
35
36
|
};
|
|
36
37
|
}
|
|
@@ -47,7 +47,13 @@ export async function reauthenticateWithGoogle(user: User, idToken: string): Pro
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export async function reauthenticateWithPassword(user: User, pass: string): Promise<ReauthenticationResult> {
|
|
50
|
-
if (!user.email)
|
|
50
|
+
if (!user.email) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: { code: "auth/no-email", message: "User has no email" }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
try {
|
|
52
58
|
await reauthenticateWithCredential(user, EmailAuthProvider.credential(user.email, pass));
|
|
53
59
|
return { success: true };
|
|
@@ -58,30 +64,62 @@ export async function reauthenticateWithPassword(user: User, pass: string): Prom
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
export async function getAppleReauthCredential(): Promise<ReauthCredentialResult> {
|
|
61
|
-
if (Platform.OS !== "ios")
|
|
67
|
+
if (Platform.OS !== "ios") {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: { code: "auth/ios-only", message: "iOS only" }
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
62
74
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
const isAvailable = await AppleAuthentication.isAvailableAsync();
|
|
76
|
+
if (!isAvailable) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: { code: "auth/unavailable", message: "Unavailable" }
|
|
80
|
+
};
|
|
81
|
+
}
|
|
65
82
|
|
|
66
83
|
const nonce = await generateNonce();
|
|
67
84
|
const hashed = await hashNonce(nonce);
|
|
68
85
|
const apple = await AppleAuthentication.signInAsync({
|
|
69
|
-
requestedScopes: [
|
|
86
|
+
requestedScopes: [
|
|
87
|
+
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
|
88
|
+
AppleAuthentication.AppleAuthenticationScope.EMAIL
|
|
89
|
+
],
|
|
70
90
|
nonce: hashed,
|
|
71
91
|
});
|
|
72
92
|
|
|
73
|
-
if (!apple.identityToken)
|
|
74
|
-
|
|
93
|
+
if (!apple.identityToken) {
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
error: { code: "auth/no-token", message: "No token" }
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const credential = new OAuthProvider("apple.com").credential({
|
|
101
|
+
idToken: apple.identityToken,
|
|
102
|
+
rawNonce: nonce
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
success: true,
|
|
107
|
+
credential
|
|
108
|
+
};
|
|
75
109
|
} catch (error: unknown) {
|
|
76
110
|
const err = toAuthErrorInfo(error);
|
|
77
111
|
const code = isCancelledError(err) ? "auth/cancelled" : err.code;
|
|
78
|
-
return {
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
error: { code, message: err.message }
|
|
115
|
+
};
|
|
79
116
|
}
|
|
80
117
|
}
|
|
81
118
|
|
|
82
119
|
export async function reauthenticateWithApple(user: User): Promise<ReauthenticationResult> {
|
|
83
120
|
const res = await getAppleReauthCredential();
|
|
84
121
|
if (!res.success || !res.credential) return { success: false, error: res.error };
|
|
122
|
+
|
|
85
123
|
try {
|
|
86
124
|
await reauthenticateWithCredential(user, res.credential);
|
|
87
125
|
return { success: true };
|
|
@@ -65,9 +65,6 @@ export const useFirebaseAuthStore = createStore<AuthState, AuthActions>({
|
|
|
65
65
|
// On error, release the mutex so retry is possible
|
|
66
66
|
setupInProgress = false;
|
|
67
67
|
set({ listenerSetup: false, loading: false });
|
|
68
|
-
if (__DEV__) {
|
|
69
|
-
console.error('[Auth Store] Failed to setup auth listener:', error);
|
|
70
|
-
}
|
|
71
68
|
throw error; // Re-throw to allow caller to handle
|
|
72
69
|
}
|
|
73
70
|
},
|
|
@@ -5,29 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { User } from 'firebase/auth';
|
|
7
7
|
import type { AuthCheckResult } from '../../../infrastructure/services/auth-utils.service';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Convert Firebase User to AuthCheckResult
|
|
11
|
-
* @param user - Firebase user or null
|
|
12
|
-
* @returns AuthCheckResult
|
|
13
|
-
*/
|
|
14
|
-
export function userToAuthCheckResult(user: User | null): AuthCheckResult {
|
|
15
|
-
if (!user) {
|
|
16
|
-
return {
|
|
17
|
-
isAuthenticated: false,
|
|
18
|
-
isAnonymous: false,
|
|
19
|
-
currentUser: null,
|
|
20
|
-
userId: null,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
isAuthenticated: true,
|
|
26
|
-
isAnonymous: user.isAnonymous === true,
|
|
27
|
-
currentUser: user,
|
|
28
|
-
userId: user.uid,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
8
|
+
import { createAuthCheckResult } from '../../../infrastructure/services/auth-utils.service';
|
|
31
9
|
|
|
32
10
|
/**
|
|
33
11
|
* Create auth state change handler callback
|
|
@@ -53,20 +31,21 @@ export function createAuthStateChangeHandler(
|
|
|
53
31
|
|
|
54
32
|
return (user: User | null) => {
|
|
55
33
|
try {
|
|
56
|
-
const authState =
|
|
34
|
+
const authState = createAuthCheckResult(user);
|
|
57
35
|
setAuthState(authState);
|
|
58
36
|
setError(null);
|
|
59
37
|
} catch (err) {
|
|
60
38
|
const authError =
|
|
61
39
|
err instanceof Error ? err : new Error('Auth state check failed');
|
|
62
40
|
setError(authError);
|
|
63
|
-
|
|
64
|
-
if (__DEV__) {
|
|
65
|
-
|
|
66
|
-
console.error('[AuthStateHandler] Auth state change error', authError);
|
|
67
|
-
}
|
|
68
41
|
} finally {
|
|
69
42
|
setLoading(false);
|
|
70
43
|
}
|
|
71
44
|
};
|
|
72
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Convert Firebase User to AuthCheckResult
|
|
49
|
+
* Re-exports createAuthCheckResult for convenience
|
|
50
|
+
*/
|
|
51
|
+
export const userToAuthCheckResult = createAuthCheckResult;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { FirestoreError } from 'firebase/firestore';
|
|
10
10
|
import type { AuthError } from 'firebase/auth';
|
|
11
|
-
import { hasCodeProperty } from '../utils/
|
|
11
|
+
import { hasCodeProperty } from '../utils/type-guards.util';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Firebase error base interface
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Centralized error handling utilities for Firebase operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { hasCodeProperty, hasMessageProperty, hasCodeAndMessageProperties } from './type-guards.util';
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Standard error structure with code and message
|
|
8
10
|
*/
|
|
@@ -36,32 +38,6 @@ const QUOTA_ERROR_MESSAGES = [
|
|
|
36
38
|
*/
|
|
37
39
|
const RETRYABLE_ERROR_CODES = ['unavailable', 'deadline-exceeded', 'aborted'];
|
|
38
40
|
|
|
39
|
-
/**
|
|
40
|
-
* Type guard for error with code property
|
|
41
|
-
* Uses proper type predicate instead of 'as' assertion
|
|
42
|
-
*/
|
|
43
|
-
export function hasCodeProperty(error: unknown): error is { code: string } {
|
|
44
|
-
return (
|
|
45
|
-
typeof error === 'object' &&
|
|
46
|
-
error !== null &&
|
|
47
|
-
'code' in error &&
|
|
48
|
-
typeof (error as { code: unknown }).code === 'string'
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Type guard for error with message property
|
|
54
|
-
* Uses proper type predicate instead of 'as' assertion
|
|
55
|
-
*/
|
|
56
|
-
export function hasMessageProperty(error: unknown): error is { message: string } {
|
|
57
|
-
return (
|
|
58
|
-
typeof error === 'object' &&
|
|
59
|
-
error !== null &&
|
|
60
|
-
'message' in error &&
|
|
61
|
-
typeof (error as { message: unknown }).message === 'string'
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
41
|
/**
|
|
66
42
|
* Convert unknown error to standard error info
|
|
67
43
|
* Handles Error objects, strings, and unknown types
|
|
@@ -197,3 +173,6 @@ export function getQuotaErrorMessage(): string {
|
|
|
197
173
|
export function getRetryableErrorMessage(): string {
|
|
198
174
|
return 'Temporary error occurred. Please try again.';
|
|
199
175
|
}
|
|
176
|
+
|
|
177
|
+
// Re-export type guards for convenience
|
|
178
|
+
export { hasCodeProperty, hasMessageProperty, hasCodeAndMessageProperties };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Guard Utilities
|
|
3
|
+
*
|
|
4
|
+
* Common type guards for Firebase and JavaScript objects.
|
|
5
|
+
* Provides type-safe checking without using 'as' assertions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Type guard for objects with a 'code' property of type string
|
|
10
|
+
* Commonly used for Firebase errors and other error objects
|
|
11
|
+
*/
|
|
12
|
+
export function hasCodeProperty(error: unknown): error is { code: string } {
|
|
13
|
+
return (
|
|
14
|
+
typeof error === 'object' &&
|
|
15
|
+
error !== null &&
|
|
16
|
+
'code' in error &&
|
|
17
|
+
typeof (error as { code: unknown }).code === 'string'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Type guard for objects with a 'message' property of type string
|
|
23
|
+
* Commonly used for Error objects
|
|
24
|
+
*/
|
|
25
|
+
export function hasMessageProperty(error: unknown): error is { message: string } {
|
|
26
|
+
return (
|
|
27
|
+
typeof error === 'object' &&
|
|
28
|
+
error !== null &&
|
|
29
|
+
'message' in error &&
|
|
30
|
+
typeof (error as { message: unknown }).message === 'string'
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Type guard for objects with both 'code' and 'message' properties
|
|
36
|
+
* Commonly used for Firebase error objects
|
|
37
|
+
*/
|
|
38
|
+
export function hasCodeAndMessageProperties(error: unknown): error is { code: string; message: string } {
|
|
39
|
+
return hasCodeProperty(error) && hasMessageProperty(error);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Type guard for objects with a 'name' property of type string
|
|
44
|
+
* Commonly used for Error objects
|
|
45
|
+
*/
|
|
46
|
+
export function hasNameProperty(error: unknown): error is { name: string } {
|
|
47
|
+
return (
|
|
48
|
+
typeof error === 'object' &&
|
|
49
|
+
error !== null &&
|
|
50
|
+
'name' in error &&
|
|
51
|
+
typeof (error as { name: unknown }).name === 'string'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Type guard for objects with a 'stack' property of type string
|
|
57
|
+
* Commonly used for Error objects
|
|
58
|
+
*/
|
|
59
|
+
export function hasStackProperty(error: unknown): error is { stack: string } {
|
|
60
|
+
return (
|
|
61
|
+
typeof error === 'object' &&
|
|
62
|
+
error !== null &&
|
|
63
|
+
'stack' in error &&
|
|
64
|
+
typeof (error as { stack: unknown }).stack === 'string'
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -30,7 +30,11 @@ export abstract class BasePaginatedRepository extends BaseQueryRepository {
|
|
|
30
30
|
orderByField: string = "createdAt",
|
|
31
31
|
orderDirection: "asc" | "desc" = "desc",
|
|
32
32
|
): Promise<QueryDocumentSnapshot<DocumentData>[]> {
|
|
33
|
-
const db = this.
|
|
33
|
+
const db = this.getDb();
|
|
34
|
+
if (!db) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
const helper = new PaginationHelper();
|
|
35
39
|
const pageLimit = helper.getLimit(params);
|
|
36
40
|
const fetchLimit = helper.getFetchLimit(pageLimit);
|
|
@@ -49,9 +53,6 @@ export abstract class BasePaginatedRepository extends BaseQueryRepository {
|
|
|
49
53
|
|
|
50
54
|
if (!cursorDoc.exists()) {
|
|
51
55
|
// Cursor document doesn't exist - return empty result
|
|
52
|
-
if (__DEV__) {
|
|
53
|
-
console.warn(`[BasePaginatedRepository] Cursor document not found: ${params.cursor}`);
|
|
54
|
-
}
|
|
55
56
|
return [];
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -114,4 +115,4 @@ export abstract class BasePaginatedRepository extends BaseQueryRepository {
|
|
|
114
115
|
const pageLimit = helper.getLimit(params);
|
|
115
116
|
return helper.buildResult(items, pageLimit, getCursor);
|
|
116
117
|
}
|
|
117
|
-
}
|
|
118
|
+
}
|
|
@@ -43,13 +43,13 @@ export abstract class BaseQueryRepository extends BaseRepository {
|
|
|
43
43
|
return queryDeduplicationMiddleware.deduplicate(queryKey, async () => {
|
|
44
44
|
// Execute the query function
|
|
45
45
|
const result = await queryFn();
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
// Track the operation after successful execution
|
|
48
48
|
// We calculate count based on result if possible, otherwise default to 1 (for list/count queries)
|
|
49
49
|
const count = Array.isArray(result) ? result.length : 1;
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
this.trackRead(collection, count, cached);
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
return result;
|
|
54
54
|
});
|
|
55
55
|
}
|
|
@@ -82,4 +82,4 @@ export abstract class BaseQueryRepository extends BaseRepository {
|
|
|
82
82
|
): void {
|
|
83
83
|
quotaTrackingMiddleware.trackDelete(collection, count);
|
|
84
84
|
}
|
|
85
|
-
}
|
|
85
|
+
}
|
|
@@ -1,152 +1,107 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Base Repository - Core Operations
|
|
2
|
+
* Base Repository - Core Firestore Operations
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Architecture:
|
|
8
|
-
* - DRY: Centralized database access (getDb)
|
|
9
|
-
* - SOLID: Single Responsibility - Database access only
|
|
10
|
-
* - KISS: Simple base class with protected db property
|
|
11
|
-
* - App-agnostic: Works with any app, no app-specific code
|
|
12
|
-
*
|
|
13
|
-
* This class is designed to be used across hundreds of apps.
|
|
14
|
-
* It provides a consistent interface for Firestore operations.
|
|
4
|
+
* Provides base functionality for all Firestore repositories.
|
|
5
|
+
* Handles initialization checks, error handling, and quota tracking.
|
|
15
6
|
*/
|
|
16
7
|
|
|
17
|
-
import type { Firestore } from
|
|
18
|
-
import { getFirestore } from
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
8
|
+
import type { Firestore } from 'firebase/firestore';
|
|
9
|
+
import { getFirestore } from 'firebase/firestore';
|
|
10
|
+
import { isQuotaError as checkQuotaError } from '../../utils/quota-error-detector.util';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Repository destruction state
|
|
14
|
+
*/
|
|
15
|
+
export enum RepositoryState {
|
|
16
|
+
ACTIVE = 'active',
|
|
17
|
+
DESTROYED = 'destroyed',
|
|
18
|
+
}
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Base repository for Firestore operations
|
|
22
|
+
* Provides common functionality for all repositories
|
|
23
|
+
*/
|
|
24
|
+
export abstract class BaseRepository {
|
|
25
|
+
protected state: RepositoryState = RepositoryState.ACTIVE;
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
|
-
* Get Firestore
|
|
30
|
-
*
|
|
31
|
-
* Use getDbOrThrow() if you need to throw an error instead
|
|
32
|
-
*
|
|
33
|
-
* @returns Firestore instance or null if not initialized
|
|
28
|
+
* Get the Firestore instance
|
|
29
|
+
* @throws Error if repository is destroyed
|
|
34
30
|
*/
|
|
35
31
|
protected getDb(): Firestore | null {
|
|
36
|
-
if (this.
|
|
37
|
-
if (__DEV__) {
|
|
38
|
-
console.warn('[BaseRepository] Attempted to use destroyed repository');
|
|
39
|
-
}
|
|
32
|
+
if (this.state === RepositoryState.DESTROYED) {
|
|
40
33
|
return null;
|
|
41
34
|
}
|
|
42
35
|
return getFirestore();
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
/**
|
|
46
|
-
* Get Firestore database instance or throw error
|
|
47
|
-
* Throws error if Firestore is not initialized
|
|
48
|
-
* Use this method when Firestore is required for the operation
|
|
49
|
-
*
|
|
50
|
-
* @returns Firestore instance
|
|
51
|
-
* @throws Error if Firestore is not initialized
|
|
52
|
-
*/
|
|
53
|
-
protected getDbOrThrow(): Firestore {
|
|
54
|
-
if (this.isDestroyed) {
|
|
55
|
-
throw new Error("Repository has been destroyed");
|
|
56
|
-
}
|
|
57
|
-
const db = getFirestore();
|
|
58
|
-
if (!db) {
|
|
59
|
-
throw new Error("Firestore is not initialized. Please initialize Firebase App first.");
|
|
60
|
-
}
|
|
61
|
-
return db;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
38
|
/**
|
|
65
39
|
* Check if Firestore is initialized
|
|
66
|
-
* Useful for conditional operations
|
|
67
|
-
*
|
|
68
|
-
* @returns true if Firestore is initialized, false otherwise
|
|
69
40
|
*/
|
|
70
41
|
protected isDbInitialized(): boolean {
|
|
71
42
|
try {
|
|
72
|
-
const db =
|
|
43
|
+
const db = this.getDb();
|
|
73
44
|
return db !== null;
|
|
74
|
-
} catch
|
|
75
|
-
if (__DEV__) console.warn('[BaseRepository] isDbInitialized check failed:', error);
|
|
45
|
+
} catch {
|
|
76
46
|
return false;
|
|
77
47
|
}
|
|
78
48
|
}
|
|
79
49
|
|
|
80
50
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* @
|
|
85
|
-
* @
|
|
86
|
-
*/
|
|
87
|
-
protected isQuotaError(error: unknown): boolean {
|
|
88
|
-
return checkQuotaError(error);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Handle quota error
|
|
93
|
-
* Throws FirebaseFirestoreQuotaError with user-friendly message
|
|
94
|
-
*
|
|
95
|
-
* @param error - Original error
|
|
96
|
-
* @throws FirebaseFirestoreQuotaError
|
|
51
|
+
* Execute operation with error handling
|
|
52
|
+
* @param collection - Collection name for tracking
|
|
53
|
+
* @param operation - Operation to execute
|
|
54
|
+
* @returns Operation result
|
|
55
|
+
* @throws Error if operation fails
|
|
97
56
|
*/
|
|
98
|
-
protected
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Wrap Firestore operation with quota error handling
|
|
105
|
-
* Automatically detects and handles quota errors
|
|
106
|
-
*
|
|
107
|
-
* @param operation - Firestore operation to execute
|
|
108
|
-
* @returns Result of the operation
|
|
109
|
-
* @throws FirebaseFirestoreQuotaError if quota error occurs
|
|
110
|
-
*/
|
|
111
|
-
protected async executeWithQuotaHandling<T>(
|
|
112
|
-
operation: () => Promise<T>,
|
|
57
|
+
protected async executeOperation<T>(
|
|
58
|
+
collection: string,
|
|
59
|
+
operation: () => Promise<T>
|
|
113
60
|
): Promise<T> {
|
|
61
|
+
if (this.state === RepositoryState.DESTROYED) {
|
|
62
|
+
throw new Error('Repository has been destroyed');
|
|
63
|
+
}
|
|
64
|
+
|
|
114
65
|
try {
|
|
115
66
|
return await operation();
|
|
116
67
|
} catch (error) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
123
|
-
console.error('[BaseRepository] Operation failed:', errorMessage);
|
|
68
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
69
|
+
|
|
70
|
+
// Check if this is a quota error
|
|
71
|
+
if (checkQuotaError(error)) {
|
|
72
|
+
throw new Error(`Firestore quota exceeded: ${errorMessage}`);
|
|
124
73
|
}
|
|
74
|
+
|
|
125
75
|
throw error;
|
|
126
76
|
}
|
|
127
77
|
}
|
|
128
78
|
|
|
129
79
|
/**
|
|
130
|
-
*
|
|
131
|
-
* Child classes can override onDestroy() to add custom cleanup logic
|
|
80
|
+
* Track read operation
|
|
132
81
|
*/
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
82
|
+
protected trackRead(_collection: string, _count: number = 1): void {
|
|
83
|
+
// Quota tracking delegated to middleware
|
|
84
|
+
}
|
|
137
85
|
|
|
138
|
-
|
|
139
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Track write operation
|
|
88
|
+
*/
|
|
89
|
+
protected trackWrite(_collection: string, _count: number = 1): void {
|
|
90
|
+
// Quota tracking delegated to middleware
|
|
91
|
+
}
|
|
140
92
|
|
|
141
|
-
|
|
142
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Track delete operation
|
|
95
|
+
*/
|
|
96
|
+
protected trackDelete(_collection: string, _count: number = 1): void {
|
|
97
|
+
// Quota tracking delegated to middleware
|
|
143
98
|
}
|
|
144
99
|
|
|
145
100
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
101
|
+
* Destroy the repository
|
|
102
|
+
* Prevents further operations
|
|
148
103
|
*/
|
|
149
|
-
|
|
150
|
-
|
|
104
|
+
destroy(): void {
|
|
105
|
+
this.state = RepositoryState.DESTROYED;
|
|
151
106
|
}
|
|
152
|
-
}
|
|
107
|
+
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type Transaction,
|
|
10
10
|
} from "firebase/firestore";
|
|
11
11
|
import { getFirestore } from "../../infrastructure/config/FirestoreClient";
|
|
12
|
-
import
|
|
12
|
+
import { hasCodeProperty } from "../../../domain/utils/type-guards.util";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Execute a transaction with automatic DB instance check.
|
|
@@ -23,15 +23,10 @@ export async function runTransaction<T>(
|
|
|
23
23
|
throw new Error("[runTransaction] Firestore database is not initialized. Please ensure Firebase is properly initialized before running transactions.");
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
|
-
return await fbRunTransaction(db
|
|
26
|
+
return await fbRunTransaction(db, updateFunction);
|
|
27
27
|
} catch (error) {
|
|
28
28
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
29
|
-
const errorCode = error
|
|
30
|
-
|
|
31
|
-
if (__DEV__) {
|
|
32
|
-
console.error(`[runTransaction] Transaction failed (Code: ${errorCode}):`, errorMessage);
|
|
33
|
-
}
|
|
34
|
-
|
|
29
|
+
const errorCode = hasCodeProperty(error) ? error.code : 'unknown';
|
|
35
30
|
throw new Error(`[runTransaction] Transaction failed: ${errorMessage} (Code: ${errorCode})`);
|
|
36
31
|
}
|
|
37
32
|
}
|
|
@@ -6,14 +6,6 @@
|
|
|
6
6
|
*
|
|
7
7
|
* IMPORTANT: This package does NOT read from .env files.
|
|
8
8
|
* Configuration must be provided by the application.
|
|
9
|
-
*
|
|
10
|
-
* NOTE: Auth initialization is handled by the main app via callback.
|
|
11
|
-
* This removes the need for dynamic require() which causes issues in production.
|
|
12
|
-
*
|
|
13
|
-
* SOLID Principles:
|
|
14
|
-
* - Single Responsibility: Only orchestrates initialization, delegates to specialized classes
|
|
15
|
-
* - Open/Closed: Extensible through configuration, closed for modification
|
|
16
|
-
* - Dependency Inversion: Depends on abstractions (interfaces), not concrete implementations
|
|
17
9
|
*/
|
|
18
10
|
|
|
19
11
|
import type { FirebaseConfig } from '../../domain/value-objects/FirebaseConfig';
|
|
@@ -21,22 +13,29 @@ import type { IFirebaseClient } from '../../application/ports/IFirebaseClient';
|
|
|
21
13
|
import type { FirebaseApp } from './initializers/FirebaseAppInitializer';
|
|
22
14
|
import { FirebaseClientState } from './state/FirebaseClientState';
|
|
23
15
|
import { FirebaseInitializationOrchestrator } from './orchestrators/FirebaseInitializationOrchestrator';
|
|
24
|
-
import {
|
|
25
|
-
FirebaseServiceInitializer,
|
|
26
|
-
type AuthInitializer,
|
|
27
|
-
type ServiceInitializationOptions,
|
|
28
|
-
} from './services/FirebaseServiceInitializer';
|
|
29
16
|
import { loadFirebaseConfig } from './FirebaseConfigLoader';
|
|
30
17
|
|
|
31
|
-
export type { FirebaseApp
|
|
18
|
+
export type { FirebaseApp };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Auth initializer callback type
|
|
22
|
+
*/
|
|
23
|
+
export type AuthInitializer = () => Promise<void> | ((() => (any | null) | null));
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Service initialization options
|
|
27
|
+
*/
|
|
28
|
+
export interface ServiceInitializationOptions {
|
|
29
|
+
authInitializer?: AuthInitializer;
|
|
30
|
+
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
34
33
|
* Service initialization result interface
|
|
35
34
|
*/
|
|
36
35
|
export interface ServiceInitializationResult {
|
|
37
36
|
app: FirebaseApp | null;
|
|
38
|
-
auth:
|
|
39
|
-
authError?: string;
|
|
37
|
+
auth: boolean | null;
|
|
38
|
+
authError?: string;
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
/**
|
|
@@ -59,11 +58,11 @@ class FirebaseClientSingleton implements IFirebaseClient {
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
initialize(config: FirebaseConfig): FirebaseApp | null {
|
|
62
|
-
return FirebaseInitializationOrchestrator.initialize(config
|
|
61
|
+
return FirebaseInitializationOrchestrator.initialize(config);
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
getApp(): FirebaseApp | null {
|
|
66
|
-
return FirebaseInitializationOrchestrator.autoInitialize(
|
|
65
|
+
return FirebaseInitializationOrchestrator.autoInitialize();
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
isInitialized(): boolean {
|
|
@@ -109,24 +108,7 @@ export function autoInitializeFirebase(): FirebaseApp | null {
|
|
|
109
108
|
|
|
110
109
|
/**
|
|
111
110
|
* Initialize all Firebase services (App and Auth)
|
|
112
|
-
* This is the main entry point for applications
|
|
113
|
-
*
|
|
114
|
-
* IMPORTANT: Auth initialization is handled via callback to avoid require() issues.
|
|
115
|
-
* The main app should pass the authInitializer callback from react-native-firebase-auth.
|
|
116
|
-
*
|
|
117
|
-
* @param config - Optional Firebase configuration
|
|
118
|
-
* @param options - Optional service initialization options including authInitializer
|
|
119
|
-
* @returns Object with initialization results for each service
|
|
120
|
-
*
|
|
121
|
-
* @example
|
|
122
|
-
* ```typescript
|
|
123
|
-
* import { initializeAllFirebaseServices } from '@umituz/react-native-firebase';
|
|
124
|
-
* import { initializeFirebaseAuth } from '@umituz/react-native-firebase-auth';
|
|
125
|
-
*
|
|
126
|
-
* const result = await initializeAllFirebaseServices(undefined, {
|
|
127
|
-
* authInitializer: () => initializeFirebaseAuth(),
|
|
128
|
-
* });
|
|
129
|
-
* ```
|
|
111
|
+
* This is the main entry point for applications
|
|
130
112
|
*/
|
|
131
113
|
export async function initializeAllFirebaseServices(
|
|
132
114
|
config?: FirebaseConfig,
|
|
@@ -141,11 +123,23 @@ export async function initializeAllFirebaseServices(
|
|
|
141
123
|
};
|
|
142
124
|
}
|
|
143
125
|
|
|
144
|
-
|
|
126
|
+
// Initialize Auth if provided
|
|
127
|
+
let authSuccess = null;
|
|
128
|
+
let authError: string | undefined;
|
|
129
|
+
|
|
130
|
+
if (options?.authInitializer) {
|
|
131
|
+
try {
|
|
132
|
+
await options.authInitializer();
|
|
133
|
+
authSuccess = true;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
authError = error instanceof Error ? error.message : 'Auth initialization failed';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
145
138
|
|
|
146
139
|
return {
|
|
147
140
|
app,
|
|
148
|
-
auth,
|
|
141
|
+
auth: authSuccess,
|
|
142
|
+
authError,
|
|
149
143
|
};
|
|
150
144
|
}
|
|
151
145
|
|
|
@@ -46,8 +46,7 @@ function loadExpoConfig(): Record<string, string> {
|
|
|
46
46
|
const Constants = require('expo-constants');
|
|
47
47
|
const expoConfig = Constants?.expoConfig || Constants?.default?.expoConfig;
|
|
48
48
|
return expoConfig?.extra || {};
|
|
49
|
-
} catch
|
|
50
|
-
if (__DEV__) console.warn('[FirebaseConfigLoader] expo-constants not available:', error);
|
|
49
|
+
} catch {
|
|
51
50
|
return {};
|
|
52
51
|
}
|
|
53
52
|
}
|
|
@@ -87,27 +86,26 @@ export function loadFirebaseConfig(): FirebaseConfig | null {
|
|
|
87
86
|
const projectId = config.projectId?.trim();
|
|
88
87
|
|
|
89
88
|
if (!isValidString(apiKey) || !isValidString(authDomain) || !isValidString(projectId)) {
|
|
90
|
-
if (__DEV__) {
|
|
91
|
-
console.error('[FirebaseConfigLoader] Missing required configuration fields');
|
|
92
|
-
}
|
|
93
89
|
return null;
|
|
94
90
|
}
|
|
95
91
|
|
|
96
92
|
// Validate API key format
|
|
97
93
|
if (!isValidFirebaseApiKey(apiKey)) {
|
|
98
|
-
if (__DEV__) {
|
|
99
|
-
console.error('[FirebaseConfigLoader] Invalid API key format');
|
|
100
|
-
}
|
|
101
94
|
return null;
|
|
102
95
|
}
|
|
103
96
|
|
|
104
97
|
// Validate authDomain format (should be like "projectId.firebaseapp.com")
|
|
105
98
|
if (!isValidFirebaseAuthDomain(authDomain)) {
|
|
106
|
-
|
|
107
|
-
console.warn('[FirebaseConfigLoader] Unusual authDomain format, expected "projectId.firebaseapp.com" or similar');
|
|
108
|
-
}
|
|
99
|
+
// Invalid format but not a critical error - continue
|
|
109
100
|
}
|
|
110
101
|
|
|
111
|
-
|
|
102
|
+
// Build type-safe FirebaseConfig object
|
|
103
|
+
return {
|
|
104
|
+
apiKey,
|
|
105
|
+
authDomain,
|
|
106
|
+
projectId,
|
|
107
|
+
storageBucket: config.storageBucket || undefined,
|
|
108
|
+
messagingSenderId: config.messagingSenderId || undefined,
|
|
109
|
+
appId: config.appId || undefined,
|
|
110
|
+
};
|
|
112
111
|
}
|
|
113
|
-
|
|
@@ -67,11 +67,6 @@ export class ServiceClientSingleton<TInstance, TConfig = unknown> {
|
|
|
67
67
|
} catch (error) {
|
|
68
68
|
const errorMessage = error instanceof Error ? error.message : `Failed to initialize ${this.options.serviceName}`;
|
|
69
69
|
this.state.initializationError = errorMessage;
|
|
70
|
-
|
|
71
|
-
if (__DEV__) {
|
|
72
|
-
console.error(`[${this.options.serviceName}] Initialization failed:`, errorMessage);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
70
|
return null;
|
|
76
71
|
}
|
|
77
72
|
}
|
|
@@ -99,10 +94,6 @@ export class ServiceClientSingleton<TInstance, TConfig = unknown> {
|
|
|
99
94
|
} catch (error) {
|
|
100
95
|
const errorMessage = error instanceof Error ? error.message : `Failed to initialize ${this.options.serviceName}`;
|
|
101
96
|
this.state.initializationError = errorMessage;
|
|
102
|
-
|
|
103
|
-
if (__DEV__) {
|
|
104
|
-
console.error(`[${this.options.serviceName}] Auto-initialization failed:`, errorMessage);
|
|
105
|
-
}
|
|
106
97
|
}
|
|
107
98
|
}
|
|
108
99
|
|
|
@@ -130,10 +121,6 @@ export class ServiceClientSingleton<TInstance, TConfig = unknown> {
|
|
|
130
121
|
this.state.instance = null;
|
|
131
122
|
this.state.initializationError = null;
|
|
132
123
|
this.state.isInitialized = false;
|
|
133
|
-
|
|
134
|
-
if (__DEV__) {
|
|
135
|
-
console.log(`[${this.options.serviceName}] Service reset`);
|
|
136
|
-
}
|
|
137
124
|
}
|
|
138
125
|
|
|
139
126
|
/**
|
|
@@ -44,8 +44,7 @@ class FirebaseAppManager {
|
|
|
44
44
|
try {
|
|
45
45
|
const existingApps = getApps();
|
|
46
46
|
return existingApps.length > 0;
|
|
47
|
-
} catch
|
|
48
|
-
if (__DEV__) console.warn('[FirebaseAppManager] isInitialized check failed:', error);
|
|
47
|
+
} catch {
|
|
49
48
|
return false;
|
|
50
49
|
}
|
|
51
50
|
}
|
|
@@ -57,8 +56,7 @@ class FirebaseAppManager {
|
|
|
57
56
|
try {
|
|
58
57
|
const existingApps = getApps();
|
|
59
58
|
return existingApps.length > 0 ? existingApps[0] ?? null : null;
|
|
60
|
-
} catch
|
|
61
|
-
if (__DEV__) console.warn('[FirebaseAppManager] getExistingApp failed:', error);
|
|
59
|
+
} catch {
|
|
62
60
|
return null;
|
|
63
61
|
}
|
|
64
62
|
}
|
|
@@ -98,4 +96,4 @@ export class FirebaseAppInitializer {
|
|
|
98
96
|
// Create new app
|
|
99
97
|
return FirebaseAppManager.createApp(config);
|
|
100
98
|
}
|
|
101
|
-
}
|
|
99
|
+
}
|
|
@@ -1,61 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Firebase Initialization Orchestrator
|
|
3
|
-
* Handles the initialization logic for Firebase App
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* Orchestrates the initialization of Firebase services
|
|
6
5
|
*/
|
|
7
6
|
|
|
7
|
+
import type { FirebaseApp } from 'firebase/app';
|
|
8
|
+
import { getApps } from 'firebase/app';
|
|
9
|
+
import { initializeApp } from 'firebase/app';
|
|
8
10
|
import type { FirebaseConfig } from '../../../domain/value-objects/FirebaseConfig';
|
|
9
|
-
import
|
|
10
|
-
import { FirebaseConfigValidator } from '../validators/FirebaseConfigValidator';
|
|
11
|
-
import { FirebaseAppInitializer } from '../initializers/FirebaseAppInitializer';
|
|
12
|
-
import { loadFirebaseConfig } from '../FirebaseConfigLoader';
|
|
13
|
-
import type { FirebaseClientState } from '../state/FirebaseClientState';
|
|
11
|
+
import { FirebaseInitializationError } from '../../../domain/errors/FirebaseError';
|
|
14
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Orchestrates Firebase initialization
|
|
15
|
+
*/
|
|
15
16
|
export class FirebaseInitializationOrchestrator {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
): FirebaseApp
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return null;
|
|
17
|
+
/**
|
|
18
|
+
* Initialize Firebase app
|
|
19
|
+
*/
|
|
20
|
+
static initialize(config: FirebaseConfig): FirebaseApp {
|
|
21
|
+
// Check for existing app
|
|
22
|
+
const existingApps = getApps();
|
|
23
|
+
const existingApp = existingApps.length > 0 ? existingApps[0] : undefined;
|
|
24
|
+
if (existingApp) {
|
|
25
|
+
return existingApp;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Initialize new app
|
|
28
29
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
return initializeApp({
|
|
31
|
+
apiKey: config.apiKey,
|
|
32
|
+
authDomain: config.authDomain,
|
|
33
|
+
projectId: config.projectId,
|
|
34
|
+
storageBucket: config.storageBucket,
|
|
35
|
+
messagingSenderId: config.messagingSenderId,
|
|
36
|
+
appId: config.appId,
|
|
37
|
+
});
|
|
34
38
|
} catch (error) {
|
|
35
|
-
|
|
36
|
-
error instanceof Error
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
state.setInitializationError(errorMessage);
|
|
40
|
-
|
|
41
|
-
if (__DEV__) {
|
|
42
|
-
console.error('[Firebase] Initialization failed:', errorMessage);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return null;
|
|
39
|
+
throw new FirebaseInitializationError(
|
|
40
|
+
`Failed to initialize Firebase: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
41
|
+
error
|
|
42
|
+
);
|
|
46
43
|
}
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (autoConfig) {
|
|
56
|
-
return this.initialize(autoConfig, state);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return null;
|
|
46
|
+
/**
|
|
47
|
+
* Get existing app instance
|
|
48
|
+
*/
|
|
49
|
+
static autoInitialize(): FirebaseApp | null {
|
|
50
|
+
const existingApps = getApps();
|
|
51
|
+
return existingApps.length > 0 ? (existingApps[0] ?? null) : null;
|
|
60
52
|
}
|
|
61
53
|
}
|
|
@@ -1,49 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Firebase Service Initializer
|
|
3
|
-
* Handles initialization of Firebase Auth service
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
* This
|
|
7
|
-
*
|
|
8
|
-
* Single Responsibility: Only initializes Firebase Auth service
|
|
4
|
+
* Orchestrates initialization of all Firebase services
|
|
5
|
+
* NOTE: This file is deprecated - use FirebaseClient.ts instead
|
|
6
|
+
* Kept for backwards compatibility
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export interface ServiceInitializationOptions {
|
|
14
|
-
authInitializer?: AuthInitializer;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ServiceInitializationResult {
|
|
18
|
-
auth: unknown;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ServiceInitializationResult {
|
|
22
|
-
auth: unknown;
|
|
23
|
-
authError?: string;
|
|
24
|
-
}
|
|
9
|
+
import type { FirebaseApp } from 'firebase/app';
|
|
25
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Initialize Firebase services
|
|
13
|
+
* @deprecated Use initializeAllFirebaseServices from FirebaseClient instead
|
|
14
|
+
*/
|
|
26
15
|
export class FirebaseServiceInitializer {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (__DEV__) {
|
|
42
|
-
console.error('[FirebaseServiceInitializer] Auth initialization failed:', errorMessage);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
16
|
+
/**
|
|
17
|
+
* Initialize all Firebase services
|
|
18
|
+
* @deprecated
|
|
19
|
+
*/
|
|
20
|
+
static async initializeAll(
|
|
21
|
+
_app: FirebaseApp,
|
|
22
|
+
_options?: unknown
|
|
23
|
+
): Promise<{ app: FirebaseApp | null; auth: boolean | null }> {
|
|
24
|
+
// This is now handled by FirebaseClient
|
|
25
|
+
return {
|
|
26
|
+
app: null,
|
|
27
|
+
auth: null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
46
30
|
|
|
47
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Initialize services (legacy compatibility)
|
|
33
|
+
* @deprecated
|
|
34
|
+
*/
|
|
35
|
+
static async initializeServices(
|
|
36
|
+
_options?: unknown
|
|
37
|
+
): Promise<{ app: FirebaseApp | null; auth: boolean | null }> {
|
|
38
|
+
return {
|
|
39
|
+
app: null,
|
|
40
|
+
auth: null,
|
|
41
|
+
};
|
|
48
42
|
}
|
|
49
43
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import type { InitModule } from '@umituz/react-native-design-system';
|
|
7
7
|
import { initializeAllFirebaseServices } from '../infrastructure/config/FirebaseClient';
|
|
8
|
-
import { initializeFirebaseAuth } from '../auth/infrastructure/config/FirebaseAuthClient';
|
|
9
8
|
|
|
10
9
|
export interface FirebaseInitModuleConfig {
|
|
11
10
|
/**
|
|
@@ -48,17 +47,10 @@ export function createFirebaseInitModule(
|
|
|
48
47
|
init: async () => {
|
|
49
48
|
try {
|
|
50
49
|
await initializeAllFirebaseServices(undefined, {
|
|
51
|
-
authInitializer: authInitializer ?? (() =>
|
|
50
|
+
authInitializer: authInitializer ?? (() => Promise.resolve()),
|
|
52
51
|
});
|
|
53
|
-
|
|
54
|
-
if (__DEV__) {
|
|
55
|
-
}
|
|
56
|
-
|
|
57
52
|
return true;
|
|
58
|
-
} catch
|
|
59
|
-
if (__DEV__) {
|
|
60
|
-
console.error('[createFirebaseInitModule] Error:', error);
|
|
61
|
-
}
|
|
53
|
+
} catch {
|
|
62
54
|
// Return false to indicate failure, let the app initializer handle it
|
|
63
55
|
return false;
|
|
64
56
|
}
|