@umituz/react-native-firebase 1.13.105 → 1.13.107
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/config/FirebaseAuthClient.ts +13 -7
- package/src/auth/infrastructure/config/initializers/FirebaseAuthInitializer.ts +0 -2
- package/src/auth/infrastructure/services/account-deletion.service.ts +23 -12
- package/src/auth/infrastructure/services/anonymous-auth.service.ts +1 -9
- package/src/auth/infrastructure/services/apple-auth.service.ts +5 -17
- package/src/auth/infrastructure/services/auth-guard.service.ts +2 -1
- package/src/auth/infrastructure/services/crypto.util.ts +17 -0
- package/src/auth/infrastructure/services/firestore-utils.service.ts +0 -2
- package/src/auth/infrastructure/services/password.service.ts +4 -3
- package/src/auth/infrastructure/services/reauthentication.service.ts +23 -17
- package/src/auth/infrastructure/stores/auth.store.ts +2 -4
- package/src/auth/presentation/hooks/useAnonymousAuth.ts +0 -2
- package/src/auth/presentation/hooks/useSocialAuth.ts +6 -2
- package/src/auth/presentation/hooks/utils/auth-state-change.handler.ts +0 -2
- package/src/domain/errors/FirebaseError.ts +3 -3
- package/src/firestore/infrastructure/config/FirestoreClient.ts +1 -2
- package/src/firestore/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +1 -5
- package/src/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +1 -1
- package/src/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +0 -2
- package/src/firestore/infrastructure/repositories/BasePaginatedRepository.ts +2 -2
- package/src/firestore/infrastructure/repositories/BaseRepository.ts +3 -3
- package/src/firestore/infrastructure/services/RequestLoggerService.ts +2 -2
- package/src/firestore/utils/dateUtils.ts +2 -2
- package/src/firestore/utils/firestore-helper.ts +0 -2
- package/src/global.d.ts +5 -0
- package/src/infrastructure/config/FirebaseClient.ts +1 -4
- package/src/infrastructure/config/FirebaseConfigLoader.ts +2 -2
- package/src/infrastructure/config/initializers/FirebaseAppInitializer.ts +5 -3
- package/src/infrastructure/config/orchestrators/FirebaseInitializationOrchestrator.ts +0 -2
- package/src/infrastructure/config/services/FirebaseServiceInitializer.ts +0 -2
- package/src/init/createFirebaseInitModule.ts +2 -4
- package/src/storage/deleter.ts +7 -12
- package/src/storage/storage-instance.ts +11 -0
- package/src/storage/uploader.ts +1 -9
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.107",
|
|
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",
|
|
@@ -7,8 +7,6 @@ import { getFirebaseApp } from '../../../infrastructure/config/FirebaseClient';
|
|
|
7
7
|
import { FirebaseAuthInitializer } from './initializers/FirebaseAuthInitializer';
|
|
8
8
|
import type { FirebaseAuthConfig } from '../../domain/value-objects/FirebaseAuthConfig';
|
|
9
9
|
|
|
10
|
-
declare const __DEV__: boolean;
|
|
11
|
-
|
|
12
10
|
class FirebaseAuthClientSingleton {
|
|
13
11
|
private static instance: FirebaseAuthClientSingleton | null = null;
|
|
14
12
|
private auth: Auth | null = null;
|
|
@@ -28,9 +26,10 @@ class FirebaseAuthClientSingleton {
|
|
|
28
26
|
if (!app) return null;
|
|
29
27
|
this.auth = FirebaseAuthInitializer.initialize(app, config);
|
|
30
28
|
return this.auth;
|
|
31
|
-
} catch (error:
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
} catch (error: unknown) {
|
|
30
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
31
|
+
if (__DEV__) console.error('[FirebaseAuth] Init error:', message);
|
|
32
|
+
this.initializationError = message;
|
|
34
33
|
return null;
|
|
35
34
|
}
|
|
36
35
|
}
|
|
@@ -40,14 +39,21 @@ class FirebaseAuthClientSingleton {
|
|
|
40
39
|
return this.auth;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
getInitializationError(): string | null {
|
|
43
|
+
return this.initializationError;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
reset(): void {
|
|
47
|
+
this.auth = null;
|
|
48
|
+
this.initializationError = null;
|
|
49
|
+
}
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
export const firebaseAuthClient = FirebaseAuthClientSingleton.getInstance();
|
|
47
53
|
export const initializeFirebaseAuth = (c?: FirebaseAuthConfig) => firebaseAuthClient.initialize(c);
|
|
48
54
|
export const getFirebaseAuth = () => firebaseAuthClient.getAuth();
|
|
49
55
|
export const isFirebaseAuthInitialized = () => firebaseAuthClient.getAuth() !== null;
|
|
50
|
-
export const getFirebaseAuthInitializationError = () => firebaseAuthClient.
|
|
56
|
+
export const getFirebaseAuthInitializationError = () => firebaseAuthClient.getInitializationError();
|
|
51
57
|
export const resetFirebaseAuthClient = () => firebaseAuthClient.reset();
|
|
52
58
|
|
|
53
59
|
export type { Auth } from 'firebase/auth';
|
|
@@ -17,8 +17,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
|
17
17
|
import type { FirebaseApp } from 'firebase/app';
|
|
18
18
|
import type { FirebaseAuthConfig } from '../../../domain/value-objects/FirebaseAuthConfig';
|
|
19
19
|
|
|
20
|
-
declare const __DEV__: boolean;
|
|
21
|
-
|
|
22
20
|
/**
|
|
23
21
|
* Initializes Firebase Auth
|
|
24
22
|
* Platform-agnostic: Works on all platforms (Web, iOS, Android)
|
|
@@ -14,15 +14,23 @@ import type { AccountDeletionResult, AccountDeletionOptions } from "./reauthenti
|
|
|
14
14
|
|
|
15
15
|
export type { AccountDeletionResult, AccountDeletionOptions } from "./reauthentication.types";
|
|
16
16
|
|
|
17
|
+
function toAuthError(error: unknown): { code: string; message: string } {
|
|
18
|
+
const err = error as { code?: string; message?: string };
|
|
19
|
+
return {
|
|
20
|
+
code: err.code || "auth/failed",
|
|
21
|
+
message: err.message || "Unknown error",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
export async function deleteCurrentUser(
|
|
18
26
|
options: AccountDeletionOptions = { autoReauthenticate: true }
|
|
19
27
|
): Promise<AccountDeletionResult> {
|
|
20
28
|
const auth = getFirebaseAuth();
|
|
21
29
|
const user = auth?.currentUser;
|
|
22
30
|
|
|
23
|
-
if (!auth || !user) return {
|
|
24
|
-
success: false,
|
|
25
|
-
error: { code: "auth/not-ready", message: "Auth not ready", requiresReauth: false }
|
|
31
|
+
if (!auth || !user) return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: { code: "auth/not-ready", message: "Auth not ready", requiresReauth: false }
|
|
26
34
|
};
|
|
27
35
|
if (user.isAnonymous) return {
|
|
28
36
|
success: false,
|
|
@@ -32,21 +40,22 @@ export async function deleteCurrentUser(
|
|
|
32
40
|
try {
|
|
33
41
|
await deleteUser(user);
|
|
34
42
|
return { success: true };
|
|
35
|
-
} catch (error:
|
|
36
|
-
|
|
43
|
+
} catch (error: unknown) {
|
|
44
|
+
const authErr = toAuthError(error);
|
|
45
|
+
if (authErr.code === "auth/requires-recent-login" && (options.autoReauthenticate || options.password || options.googleIdToken)) {
|
|
37
46
|
const reauth = await attemptReauth(user, options);
|
|
38
47
|
if (reauth) return reauth;
|
|
39
48
|
}
|
|
40
49
|
return {
|
|
41
50
|
success: false,
|
|
42
|
-
error: {
|
|
51
|
+
error: { ...authErr, requiresReauth: authErr.code === "auth/requires-recent-login" }
|
|
43
52
|
};
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
async function attemptReauth(user: User, options: AccountDeletionOptions): Promise<AccountDeletionResult | null> {
|
|
48
57
|
const provider = getUserAuthProvider(user);
|
|
49
|
-
let res: { success: boolean
|
|
58
|
+
let res: { success: boolean; error?: { code?: string; message?: string } };
|
|
50
59
|
|
|
51
60
|
if (provider === "apple.com") res = await reauthenticateWithApple(user);
|
|
52
61
|
else if (provider === "google.com") {
|
|
@@ -61,11 +70,12 @@ async function attemptReauth(user: User, options: AccountDeletionOptions): Promi
|
|
|
61
70
|
try {
|
|
62
71
|
await deleteUser(user);
|
|
63
72
|
return { success: true };
|
|
64
|
-
} catch (err:
|
|
65
|
-
|
|
73
|
+
} catch (err: unknown) {
|
|
74
|
+
const authErr = toAuthError(err);
|
|
75
|
+
return { success: false, error: { ...authErr, requiresReauth: false } };
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
|
-
return { success: false, error: { code: res.error?.code || "auth/reauth-failed", message: res.error?.message, requiresReauth: true } };
|
|
78
|
+
return { success: false, error: { code: res.error?.code || "auth/reauth-failed", message: res.error?.message || "Reauth failed", requiresReauth: true } };
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
export async function deleteUserAccount(user: User): Promise<AccountDeletionResult> {
|
|
@@ -73,7 +83,8 @@ export async function deleteUserAccount(user: User): Promise<AccountDeletionResu
|
|
|
73
83
|
try {
|
|
74
84
|
await deleteUser(user);
|
|
75
85
|
return { success: true };
|
|
76
|
-
} catch (error:
|
|
77
|
-
|
|
86
|
+
} catch (error: unknown) {
|
|
87
|
+
const authErr = toAuthError(error);
|
|
88
|
+
return { success: false, error: { ...authErr, requiresReauth: authErr.code === "auth/requires-recent-login" } };
|
|
78
89
|
}
|
|
79
90
|
}
|
|
@@ -22,15 +22,7 @@ export class AnonymousAuthService implements AnonymousAuthServiceInterface {
|
|
|
22
22
|
|
|
23
23
|
const currentUser = auth.currentUser;
|
|
24
24
|
|
|
25
|
-
if (currentUser
|
|
26
|
-
return {
|
|
27
|
-
user: currentUser,
|
|
28
|
-
anonymousUser: toAnonymousUser(currentUser),
|
|
29
|
-
wasAlreadySignedIn: true,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (currentUser && currentUser.isAnonymous) {
|
|
25
|
+
if (currentUser) {
|
|
34
26
|
return {
|
|
35
27
|
user: currentUser,
|
|
36
28
|
anonymousUser: toAnonymousUser(currentUser),
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
type UserCredential,
|
|
11
11
|
} from "firebase/auth";
|
|
12
12
|
import * as AppleAuthentication from "expo-apple-authentication";
|
|
13
|
-
import * as Crypto from "expo-crypto";
|
|
14
13
|
import { Platform } from "react-native";
|
|
14
|
+
import { generateNonce, hashNonce } from "./crypto.util";
|
|
15
15
|
|
|
16
16
|
export interface AppleAuthResult {
|
|
17
17
|
success: boolean;
|
|
@@ -25,7 +25,8 @@ export class AppleAuthService {
|
|
|
25
25
|
if (Platform.OS !== "ios") return false;
|
|
26
26
|
try {
|
|
27
27
|
return await AppleAuthentication.isAvailableAsync();
|
|
28
|
-
} catch {
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (__DEV__) console.warn('[AppleAuth] isAvailable check failed:', error);
|
|
29
30
|
return false;
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -40,11 +41,8 @@ export class AppleAuthService {
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
const nonce = await
|
|
44
|
-
const hashedNonce = await
|
|
45
|
-
Crypto.CryptoDigestAlgorithm.SHA256,
|
|
46
|
-
nonce,
|
|
47
|
-
);
|
|
44
|
+
const nonce = await generateNonce();
|
|
45
|
+
const hashedNonce = await hashNonce(nonce);
|
|
48
46
|
|
|
49
47
|
const appleCredential = await AppleAuthentication.signInAsync({
|
|
50
48
|
requestedScopes: [
|
|
@@ -86,16 +84,6 @@ export class AppleAuthService {
|
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
86
|
|
|
89
|
-
private async generateNonce(length: number = 32): Promise<string> {
|
|
90
|
-
const randomBytes = await Crypto.getRandomBytesAsync(length);
|
|
91
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
92
|
-
let result = "";
|
|
93
|
-
for (let i = 0; i < randomBytes.length; i++) {
|
|
94
|
-
const byte = randomBytes[i];
|
|
95
|
-
if (byte !== undefined) result += chars.charAt(byte % chars.length);
|
|
96
|
-
}
|
|
97
|
-
return result;
|
|
98
|
-
}
|
|
99
87
|
}
|
|
100
88
|
|
|
101
89
|
export const appleAuthService = new AppleAuthService();
|
|
@@ -52,7 +52,8 @@ export class AuthGuardService {
|
|
|
52
52
|
async getAuthenticatedUserId(): Promise<string | null> {
|
|
53
53
|
try {
|
|
54
54
|
return await this.requireAuthenticatedUser();
|
|
55
|
-
} catch {
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (__DEV__) console.warn('[AuthGuard] getAuthenticatedUserId:', error);
|
|
56
57
|
return null;
|
|
57
58
|
}
|
|
58
59
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto Utilities
|
|
3
|
+
* Shared cryptographic helpers for auth services
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as Crypto from "expo-crypto";
|
|
7
|
+
|
|
8
|
+
const NONCE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
9
|
+
|
|
10
|
+
export async function generateNonce(length: number = 32): Promise<string> {
|
|
11
|
+
const bytes = await Crypto.getRandomBytesAsync(length);
|
|
12
|
+
return Array.from(bytes).map(b => NONCE_CHARS.charAt(b % NONCE_CHARS.length)).join("");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function hashNonce(nonce: string): Promise<string> {
|
|
16
|
+
return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, nonce);
|
|
17
|
+
}
|
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
import type { Auth } from "firebase/auth";
|
|
7
7
|
import { checkAuthState, verifyUserId } from "./auth-utils.service";
|
|
8
8
|
|
|
9
|
-
declare const __DEV__: boolean;
|
|
10
|
-
|
|
11
9
|
export interface FirestoreQueryOptions {
|
|
12
10
|
readonly skipForGuest?: boolean;
|
|
13
11
|
readonly skipIfNotAuthenticated?: boolean;
|
|
@@ -24,12 +24,13 @@ export async function updateUserPassword(user: User, newPassword: string): Promi
|
|
|
24
24
|
try {
|
|
25
25
|
await updatePassword(user, newPassword);
|
|
26
26
|
return { success: true };
|
|
27
|
-
} catch (error:
|
|
27
|
+
} catch (error: unknown) {
|
|
28
|
+
const err = error as { code?: string; message?: string };
|
|
28
29
|
return {
|
|
29
30
|
success: false,
|
|
30
31
|
error: {
|
|
31
|
-
code:
|
|
32
|
-
message:
|
|
32
|
+
code: err.code || 'auth/password-update-failed',
|
|
33
|
+
message: err.message || 'Failed to update password',
|
|
33
34
|
},
|
|
34
35
|
};
|
|
35
36
|
}
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
type User,
|
|
12
12
|
} from "firebase/auth";
|
|
13
13
|
import * as AppleAuthentication from "expo-apple-authentication";
|
|
14
|
-
import * as Crypto from "expo-crypto";
|
|
15
14
|
import { Platform } from "react-native";
|
|
15
|
+
import { generateNonce, hashNonce } from "./crypto.util";
|
|
16
16
|
import type {
|
|
17
17
|
ReauthenticationResult,
|
|
18
18
|
AuthProviderType,
|
|
@@ -25,6 +25,14 @@ export type {
|
|
|
25
25
|
ReauthCredentialResult
|
|
26
26
|
} from "./reauthentication.types";
|
|
27
27
|
|
|
28
|
+
function toAuthError(error: unknown): { code: string; message: string } {
|
|
29
|
+
const err = error as { code?: string; message?: string };
|
|
30
|
+
return {
|
|
31
|
+
code: err.code || "auth/failed",
|
|
32
|
+
message: err.message || "Unknown error",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
export function getUserAuthProvider(user: User): AuthProviderType {
|
|
29
37
|
if (user.isAnonymous) return "anonymous";
|
|
30
38
|
const data = user.providerData;
|
|
@@ -39,8 +47,9 @@ export async function reauthenticateWithGoogle(user: User, idToken: string): Pro
|
|
|
39
47
|
try {
|
|
40
48
|
await reauthenticateWithCredential(user, GoogleAuthProvider.credential(idToken));
|
|
41
49
|
return { success: true };
|
|
42
|
-
} catch (error:
|
|
43
|
-
|
|
50
|
+
} catch (error: unknown) {
|
|
51
|
+
const err = toAuthError(error);
|
|
52
|
+
return { success: false, error: err };
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
55
|
|
|
@@ -49,17 +58,12 @@ export async function reauthenticateWithPassword(user: User, pass: string): Prom
|
|
|
49
58
|
try {
|
|
50
59
|
await reauthenticateWithCredential(user, EmailAuthProvider.credential(user.email, pass));
|
|
51
60
|
return { success: true };
|
|
52
|
-
} catch (error:
|
|
53
|
-
|
|
61
|
+
} catch (error: unknown) {
|
|
62
|
+
const err = toAuthError(error);
|
|
63
|
+
return { success: false, error: err };
|
|
54
64
|
}
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
async function generateNonce(len: number = 32): Promise<string> {
|
|
58
|
-
const bytes = await Crypto.getRandomBytesAsync(len);
|
|
59
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
60
|
-
return Array.from(bytes).map(b => chars.charAt(b % chars.length)).join("");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
67
|
export async function getAppleReauthCredential(): Promise<ReauthCredentialResult> {
|
|
64
68
|
if (Platform.OS !== "ios") return { success: false, error: { code: "auth/ios-only", message: "iOS only" } };
|
|
65
69
|
try {
|
|
@@ -67,7 +71,7 @@ export async function getAppleReauthCredential(): Promise<ReauthCredentialResult
|
|
|
67
71
|
return { success: false, error: { code: "auth/unavailable", message: "Unavailable" } };
|
|
68
72
|
|
|
69
73
|
const nonce = await generateNonce();
|
|
70
|
-
const hashed = await
|
|
74
|
+
const hashed = await hashNonce(nonce);
|
|
71
75
|
const apple = await AppleAuthentication.signInAsync({
|
|
72
76
|
requestedScopes: [AppleAuthentication.AppleAuthenticationScope.FULL_NAME, AppleAuthentication.AppleAuthenticationScope.EMAIL],
|
|
73
77
|
nonce: hashed,
|
|
@@ -75,9 +79,10 @@ export async function getAppleReauthCredential(): Promise<ReauthCredentialResult
|
|
|
75
79
|
|
|
76
80
|
if (!apple.identityToken) return { success: false, error: { code: "auth/no-token", message: "No token" } };
|
|
77
81
|
return { success: true, credential: new OAuthProvider("apple.com").credential({ idToken: apple.identityToken, rawNonce: nonce }) };
|
|
78
|
-
} catch (error:
|
|
79
|
-
const
|
|
80
|
-
|
|
82
|
+
} catch (error: unknown) {
|
|
83
|
+
const err = toAuthError(error);
|
|
84
|
+
const code = err.message.includes("ERR_CANCELED") ? "auth/cancelled" : err.code;
|
|
85
|
+
return { success: false, error: { code, message: err.message } };
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
@@ -87,7 +92,8 @@ export async function reauthenticateWithApple(user: User): Promise<Reauthenticat
|
|
|
87
92
|
try {
|
|
88
93
|
await reauthenticateWithCredential(user, res.credential);
|
|
89
94
|
return { success: true };
|
|
90
|
-
} catch (error:
|
|
91
|
-
|
|
95
|
+
} catch (error: unknown) {
|
|
96
|
+
const err = toAuthError(error);
|
|
97
|
+
return { success: false, error: err };
|
|
92
98
|
}
|
|
93
99
|
}
|
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
import { createStore, type SetState, type GetState } from "@umituz/react-native-design-system";
|
|
11
11
|
import { onAuthStateChanged, type User, type Auth } from "firebase/auth";
|
|
12
12
|
|
|
13
|
-
declare const __DEV__: boolean;
|
|
14
|
-
|
|
15
13
|
interface AuthState {
|
|
16
14
|
user: User | null;
|
|
17
15
|
loading: boolean;
|
|
@@ -52,7 +50,7 @@ export const useFirebaseAuthStore = createStore<AuthState, AuthActions>({
|
|
|
52
50
|
|
|
53
51
|
try {
|
|
54
52
|
unsubscribe = onAuthStateChanged(auth, (currentUser: User | null) => {
|
|
55
|
-
if (
|
|
53
|
+
if (__DEV__) {
|
|
56
54
|
console.log(
|
|
57
55
|
"[FirebaseAuthStore] Auth state changed:",
|
|
58
56
|
currentUser?.uid || "null"
|
|
@@ -71,7 +69,7 @@ export const useFirebaseAuthStore = createStore<AuthState, AuthActions>({
|
|
|
71
69
|
// On error, release the mutex so retry is possible
|
|
72
70
|
setupInProgress = false;
|
|
73
71
|
set({ listenerSetup: false });
|
|
74
|
-
if (
|
|
72
|
+
if (__DEV__) {
|
|
75
73
|
console.error("[FirebaseAuthStore] Failed to setup listener:", error);
|
|
76
74
|
}
|
|
77
75
|
}
|
|
@@ -9,8 +9,6 @@ import type { AuthCheckResult } from "../../infrastructure/services/auth-utils.s
|
|
|
9
9
|
import { anonymousAuthService, type AnonymousAuthResult } from "../../infrastructure/services/anonymous-auth.service";
|
|
10
10
|
import { createAuthStateChangeHandler, userToAuthCheckResult } from "./utils/auth-state-change.handler";
|
|
11
11
|
|
|
12
|
-
declare const __DEV__: boolean;
|
|
13
|
-
|
|
14
12
|
export interface UseAnonymousAuthResult extends AuthCheckResult {
|
|
15
13
|
/**
|
|
16
14
|
* Sign in anonymously
|
|
@@ -79,8 +79,10 @@ export function useSocialAuth(config?: SocialAuthConfig): UseSocialAuthResult {
|
|
|
79
79
|
|
|
80
80
|
setGoogleLoading(true);
|
|
81
81
|
try {
|
|
82
|
+
const auth = getFirebaseAuth();
|
|
83
|
+
if (!auth) return { success: false, error: "Firebase Auth not initialized" };
|
|
82
84
|
return await signInWrapper(() =>
|
|
83
|
-
googleAuthService.signInWithIdToken(
|
|
85
|
+
googleAuthService.signInWithIdToken(auth, idToken)
|
|
84
86
|
);
|
|
85
87
|
} catch (error) {
|
|
86
88
|
return {
|
|
@@ -101,8 +103,10 @@ export function useSocialAuth(config?: SocialAuthConfig): UseSocialAuthResult {
|
|
|
101
103
|
|
|
102
104
|
setAppleLoading(true);
|
|
103
105
|
try {
|
|
106
|
+
const auth = getFirebaseAuth();
|
|
107
|
+
if (!auth) return { success: false, error: "Firebase Auth not initialized" };
|
|
104
108
|
return await signInWrapper(() =>
|
|
105
|
-
appleAuthService.signIn(
|
|
109
|
+
appleAuthService.signIn(auth)
|
|
106
110
|
);
|
|
107
111
|
} catch (error) {
|
|
108
112
|
return {
|
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
import type { User } from 'firebase/auth';
|
|
7
7
|
import type { AuthCheckResult } from '../../../infrastructure/services/auth-utils.service';
|
|
8
8
|
|
|
9
|
-
declare const __DEV__: boolean;
|
|
10
|
-
|
|
11
9
|
/**
|
|
12
10
|
* Convert Firebase User to AuthCheckResult
|
|
13
11
|
* @param user - Firebase user or null
|
|
@@ -12,7 +12,7 @@ if (__DEV__) {
|
|
|
12
12
|
* Base Firebase error class
|
|
13
13
|
*/
|
|
14
14
|
export class FirebaseError extends Error {
|
|
15
|
-
constructor(message: string, public code?: string, public cause?:
|
|
15
|
+
constructor(message: string, public code?: string, public cause?: unknown) {
|
|
16
16
|
super(message);
|
|
17
17
|
this.name = 'FirebaseError';
|
|
18
18
|
Object.setPrototypeOf(this, FirebaseError.prototype);
|
|
@@ -23,7 +23,7 @@ export class FirebaseError extends Error {
|
|
|
23
23
|
* Initialization specific error
|
|
24
24
|
*/
|
|
25
25
|
export class FirebaseInitializationError extends FirebaseError {
|
|
26
|
-
constructor(message: string, cause?:
|
|
26
|
+
constructor(message: string, cause?: unknown) {
|
|
27
27
|
super(message, 'INITIALIZATION_ERROR', cause);
|
|
28
28
|
this.name = 'FirebaseInitializationError';
|
|
29
29
|
Object.setPrototypeOf(this, FirebaseInitializationError.prototype);
|
|
@@ -34,7 +34,7 @@ export class FirebaseInitializationError extends FirebaseError {
|
|
|
34
34
|
* Configuration specific error
|
|
35
35
|
*/
|
|
36
36
|
export class FirebaseConfigurationError extends FirebaseError {
|
|
37
|
-
constructor(message: string, cause?:
|
|
37
|
+
constructor(message: string, cause?: unknown) {
|
|
38
38
|
super(message, 'CONFIGURATION_ERROR', cause);
|
|
39
39
|
this.name = 'FirebaseConfigurationError';
|
|
40
40
|
Object.setPrototypeOf(this, FirebaseConfigurationError.prototype);
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
* Use @umituz/react-native-firebase to initialize Firebase App.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] FirestoreClient.ts - Module loading");
|
|
11
|
+
if (__DEV__) console.log("📍 [LIFECYCLE] FirestoreClient.ts - Module loading");
|
|
13
12
|
|
|
14
13
|
import type { Firestore } from 'firebase/firestore';
|
|
15
14
|
import { getFirebaseApp } from '../../../infrastructure/config/FirebaseClient';
|
|
@@ -33,12 +33,8 @@ export class FirebaseFirestoreInitializer {
|
|
|
33
33
|
return initializeFirestore(app, {
|
|
34
34
|
localCache: memoryLocalCache(),
|
|
35
35
|
});
|
|
36
|
-
} catch (error:
|
|
36
|
+
} catch (error: unknown) {
|
|
37
37
|
// If already initialized, get existing instance
|
|
38
|
-
if (error.code === 'failed-precondition') {
|
|
39
|
-
return getFirestore(app);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
38
|
return getFirestore(app);
|
|
43
39
|
}
|
|
44
40
|
}
|
|
@@ -19,7 +19,7 @@ export class QueryDeduplicationMiddleware {
|
|
|
19
19
|
private pendingQueries = new Map<string, PendingQuery>();
|
|
20
20
|
private readonly DEDUPLICATION_WINDOW_MS = 1000; // 1 second
|
|
21
21
|
private readonly CLEANUP_INTERVAL_MS = 5000; // 5 seconds
|
|
22
|
-
private cleanupTimer:
|
|
22
|
+
private cleanupTimer: ReturnType<typeof setInterval> | null = null;
|
|
23
23
|
|
|
24
24
|
constructor() {
|
|
25
25
|
this.startCleanupTimer();
|
|
@@ -42,8 +42,8 @@ export abstract class BasePaginatedRepository extends BaseQueryRepository {
|
|
|
42
42
|
limit(fetchLimit),
|
|
43
43
|
);
|
|
44
44
|
|
|
45
|
-
if (helper.hasCursor(params)) {
|
|
46
|
-
const cursorDoc = await getDoc(doc(db, collectionName, params
|
|
45
|
+
if (helper.hasCursor(params) && params?.cursor) {
|
|
46
|
+
const cursorDoc = await getDoc(doc(db, collectionName, params.cursor));
|
|
47
47
|
if (cursorDoc.exists()) {
|
|
48
48
|
q = query(
|
|
49
49
|
collectionRef,
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
* It provides a consistent interface for Firestore operations.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] BaseRepository.ts - Module loading");
|
|
17
|
+
if (__DEV__) console.log("📍 [LIFECYCLE] BaseRepository.ts - Module loading");
|
|
19
18
|
|
|
20
19
|
import type { Firestore } from "firebase/firestore";
|
|
21
20
|
import { getFirestore } from "../config/FirestoreClient";
|
|
@@ -71,7 +70,8 @@ export class BaseRepository {
|
|
|
71
70
|
try {
|
|
72
71
|
const db = getFirestore();
|
|
73
72
|
return db !== null;
|
|
74
|
-
} catch {
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (__DEV__) console.warn('[BaseRepository] isDbInitialized check failed:', error);
|
|
75
75
|
return false;
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -74,8 +74,8 @@ export class RequestLoggerService {
|
|
|
74
74
|
const failedRequests = this.logs.filter((l) => !l.success).length;
|
|
75
75
|
|
|
76
76
|
const durations = this.logs
|
|
77
|
-
.
|
|
78
|
-
.
|
|
77
|
+
.map((l) => l.duration)
|
|
78
|
+
.filter((d): d is number => d !== undefined);
|
|
79
79
|
const averageDuration =
|
|
80
80
|
durations.length > 0
|
|
81
81
|
? durations.reduce((sum, d) => sum + d, 0) / durations.length
|
|
@@ -10,7 +10,7 @@ export function isoToTimestamp(isoString: string): Timestamp {
|
|
|
10
10
|
/**
|
|
11
11
|
* Convert Firestore Timestamp to ISO string
|
|
12
12
|
*/
|
|
13
|
-
export function timestampToISO(timestamp: Timestamp): string {
|
|
13
|
+
export function timestampToISO(timestamp: Timestamp | null | undefined): string {
|
|
14
14
|
if (!timestamp) return new Date().toISOString();
|
|
15
15
|
return timestamp.toDate().toISOString();
|
|
16
16
|
}
|
|
@@ -18,7 +18,7 @@ export function timestampToISO(timestamp: Timestamp): string {
|
|
|
18
18
|
/**
|
|
19
19
|
* Convert Firestore Timestamp to Date
|
|
20
20
|
*/
|
|
21
|
-
export function timestampToDate(timestamp: Timestamp): Date {
|
|
21
|
+
export function timestampToDate(timestamp: Timestamp | null | undefined): Date {
|
|
22
22
|
if (!timestamp) return new Date();
|
|
23
23
|
return timestamp.toDate();
|
|
24
24
|
}
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* Firestore Helper - Centralized Firestore access utilities
|
|
3
3
|
* Provides common patterns for Firestore operations with error handling
|
|
4
4
|
*/
|
|
5
|
-
declare const __DEV__: boolean;
|
|
6
|
-
|
|
7
5
|
import { getFirestore } from "../infrastructure/config/FirestoreClient";
|
|
8
6
|
import type { Firestore } from "../infrastructure/config/FirestoreClient";
|
|
9
7
|
|
package/src/global.d.ts
ADDED
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
* - Dependency Inversion: Depends on abstractions (interfaces), not concrete implementations
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("📍 [LIFECYCLE] FirebaseClient.ts - Module loading");
|
|
19
|
+
if (__DEV__) console.log("📍 [LIFECYCLE] FirebaseClient.ts - Module loading");
|
|
21
20
|
|
|
22
21
|
import type { FirebaseConfig } from '../../domain/value-objects/FirebaseConfig';
|
|
23
22
|
import type { IFirebaseClient } from '../../application/ports/IFirebaseClient';
|
|
@@ -33,8 +32,6 @@ import { loadFirebaseConfig } from './FirebaseConfigLoader';
|
|
|
33
32
|
|
|
34
33
|
export type { FirebaseApp, AuthInitializer, ServiceInitializationOptions };
|
|
35
34
|
|
|
36
|
-
declare const __DEV__: boolean;
|
|
37
|
-
|
|
38
35
|
/**
|
|
39
36
|
* Service initialization result interface
|
|
40
37
|
*/
|
|
@@ -37,11 +37,11 @@ function getEnvValue(key: ConfigKey): string {
|
|
|
37
37
|
*/
|
|
38
38
|
function loadExpoConfig(): Record<string, string> {
|
|
39
39
|
try {
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
41
40
|
const Constants = require('expo-constants');
|
|
42
41
|
const expoConfig = Constants?.expoConfig || Constants?.default?.expoConfig;
|
|
43
42
|
return expoConfig?.extra || {};
|
|
44
|
-
} catch {
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (__DEV__) console.warn('[FirebaseConfigLoader] expo-constants not available:', error);
|
|
45
45
|
return {};
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -21,7 +21,7 @@ class FirebaseConfigMapper {
|
|
|
21
21
|
/**
|
|
22
22
|
* Map domain config to Firebase SDK config
|
|
23
23
|
*/
|
|
24
|
-
static toFirebaseConfig(config: FirebaseConfig): Record<string,
|
|
24
|
+
static toFirebaseConfig(config: FirebaseConfig): Record<string, string | undefined> {
|
|
25
25
|
return {
|
|
26
26
|
apiKey: config.apiKey,
|
|
27
27
|
authDomain: config.authDomain,
|
|
@@ -44,7 +44,8 @@ class FirebaseAppManager {
|
|
|
44
44
|
try {
|
|
45
45
|
const existingApps = getApps();
|
|
46
46
|
return existingApps.length > 0;
|
|
47
|
-
} catch {
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (__DEV__) console.warn('[FirebaseAppManager] isInitialized check failed:', error);
|
|
48
49
|
return false;
|
|
49
50
|
}
|
|
50
51
|
}
|
|
@@ -56,7 +57,8 @@ class FirebaseAppManager {
|
|
|
56
57
|
try {
|
|
57
58
|
const existingApps = getApps();
|
|
58
59
|
return existingApps.length > 0 ? existingApps[0] ?? null : null;
|
|
59
|
-
} catch {
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (__DEV__) console.warn('[FirebaseAppManager] getExistingApp failed:', error);
|
|
60
62
|
return null;
|
|
61
63
|
}
|
|
62
64
|
}
|
|
@@ -12,8 +12,6 @@ import { FirebaseAppInitializer } from '../initializers/FirebaseAppInitializer';
|
|
|
12
12
|
import { loadFirebaseConfig } from '../FirebaseConfigLoader';
|
|
13
13
|
import type { FirebaseClientState } from '../state/FirebaseClientState';
|
|
14
14
|
|
|
15
|
-
declare const __DEV__: boolean;
|
|
16
|
-
|
|
17
15
|
export class FirebaseInitializationOrchestrator {
|
|
18
16
|
static initialize(
|
|
19
17
|
config: FirebaseConfig,
|
|
@@ -7,8 +7,6 @@ import type { InitModule } from '@umituz/react-native-design-system';
|
|
|
7
7
|
import { initializeAllFirebaseServices } from '../infrastructure/config/FirebaseClient';
|
|
8
8
|
import { initializeFirebaseAuth } from '../auth/infrastructure/config/FirebaseAuthClient';
|
|
9
9
|
|
|
10
|
-
declare const __DEV__: boolean;
|
|
11
|
-
|
|
12
10
|
export interface FirebaseInitModuleConfig {
|
|
13
11
|
/**
|
|
14
12
|
* Custom auth initializer function
|
|
@@ -53,13 +51,13 @@ export function createFirebaseInitModule(
|
|
|
53
51
|
authInitializer: authInitializer ?? (() => initializeFirebaseAuth()),
|
|
54
52
|
});
|
|
55
53
|
|
|
56
|
-
if (
|
|
54
|
+
if (__DEV__) {
|
|
57
55
|
console.log('[createFirebaseInitModule] Firebase initialized');
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
return true;
|
|
61
59
|
} catch (error) {
|
|
62
|
-
if (
|
|
60
|
+
if (__DEV__) {
|
|
63
61
|
console.error('[createFirebaseInitModule] Error:', error);
|
|
64
62
|
}
|
|
65
63
|
// Return false to indicate failure, let the app initializer handle it
|
package/src/storage/deleter.ts
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
* Handles single and batch deletion from Firebase Storage
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { ref, deleteObject } from "firebase/storage";
|
|
7
|
+
import { getStorageInstance } from "./storage-instance";
|
|
8
8
|
import type { DeleteResult } from "./types";
|
|
9
9
|
|
|
10
|
-
declare const __DEV__: boolean;
|
|
11
|
-
|
|
12
10
|
/**
|
|
13
11
|
* Batch delete result interface
|
|
14
12
|
*/
|
|
@@ -34,16 +32,12 @@ function extractStoragePath(downloadUrl: string): string | null {
|
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
return decodeURIComponent(pathMatch[1]);
|
|
37
|
-
} catch {
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (__DEV__) console.warn('[StorageDeleter] Failed to parse URL:', error);
|
|
38
37
|
return null;
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
function getStorageInstance() {
|
|
43
|
-
const app = getFirebaseApp();
|
|
44
|
-
return app ? getStorage(app) : null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
41
|
/**
|
|
48
42
|
* Delete image from Firebase Storage
|
|
49
43
|
* Accepts either a download URL or storage path
|
|
@@ -130,9 +124,10 @@ export async function deleteStorageFiles(
|
|
|
130
124
|
successful.push(result.value.storagePath);
|
|
131
125
|
} else {
|
|
132
126
|
const errorMessage = result.status === "rejected"
|
|
133
|
-
? String(
|
|
127
|
+
? String(result.reason instanceof Error ? result.reason.message : "Unknown error")
|
|
134
128
|
: "Delete operation failed";
|
|
135
|
-
|
|
129
|
+
const path = urlsOrPaths[index] ?? "unknown";
|
|
130
|
+
failed.push({ path, error: errorMessage });
|
|
136
131
|
}
|
|
137
132
|
});
|
|
138
133
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Firebase Storage instance getter
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getStorage } from "firebase/storage";
|
|
6
|
+
import { getFirebaseApp } from "../infrastructure/config/FirebaseClient";
|
|
7
|
+
|
|
8
|
+
export function getStorageInstance() {
|
|
9
|
+
const app = getFirebaseApp();
|
|
10
|
+
return app ? getStorage(app) : null;
|
|
11
|
+
}
|
package/src/storage/uploader.ts
CHANGED
|
@@ -4,17 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
getStorage,
|
|
8
7
|
ref,
|
|
9
8
|
uploadBytes,
|
|
10
9
|
getDownloadURL,
|
|
11
10
|
type UploadMetadata,
|
|
12
11
|
} from "firebase/storage";
|
|
13
|
-
import {
|
|
12
|
+
import { getStorageInstance } from "./storage-instance";
|
|
14
13
|
import type { UploadResult, UploadOptions } from "./types";
|
|
15
14
|
|
|
16
|
-
declare const __DEV__: boolean;
|
|
17
|
-
|
|
18
15
|
/**
|
|
19
16
|
* Extract MIME type from base64 data URL or return default
|
|
20
17
|
*/
|
|
@@ -35,11 +32,6 @@ function ensureDataUrl(base64: string, mimeType: string): string {
|
|
|
35
32
|
return `data:${mimeType};base64,${base64}`;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
function getStorageInstance() {
|
|
39
|
-
const app = getFirebaseApp();
|
|
40
|
-
return app ? getStorage(app) : null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
35
|
/**
|
|
44
36
|
* Upload base64 image to Firebase Storage
|
|
45
37
|
*/
|