@umituz/react-native-firebase 2.4.71 → 2.4.73
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 -12
- package/src/domains/account-deletion/infrastructure/services/account-deletion.service.ts +12 -2
- package/src/domains/auth/infrastructure/services/user-document.service.ts +4 -1
- package/src/domains/firestore/index.ts +2 -0
- package/src/domains/firestore/presentation/hooks/useFirestoreMutation.ts +1 -1
- package/src/domains/firestore/presentation/hooks/useFirestoreSnapshot.ts +7 -4
- package/src/domains/firestore/utils/dateUtils.ts +46 -0
- package/src/domains/firestore/utils/operation/operation-executor.util.ts +20 -8
- package/src/domains/firestore/utils/pagination.helper.ts +2 -1
- package/src/domains/firestore/utils/transaction/transaction.util.ts +6 -0
- package/src/init/createFirebaseInitModule.ts +1 -1
- package/src/shared/domain/utils/index.ts +6 -0
- package/src/shared/domain/utils/result/result-helpers.ts +8 -1
- package/src/shared/domain/utils/validators/firebase.validator.ts +2 -2
- package/src/shared/infrastructure/config/services/FirebaseInitializationService.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-firebase",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.73",
|
|
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",
|
|
@@ -128,19 +128,14 @@
|
|
|
128
128
|
"default": "./src/index.ts"
|
|
129
129
|
},
|
|
130
130
|
"./auth": {
|
|
131
|
-
"react-native": "./src/auth/index.ts",
|
|
132
|
-
"types": "./src/auth/index.ts",
|
|
133
|
-
"default": "./src/auth/index.ts"
|
|
131
|
+
"react-native": "./src/domains/auth/index.ts",
|
|
132
|
+
"types": "./src/domains/auth/index.ts",
|
|
133
|
+
"default": "./src/domains/auth/index.ts"
|
|
134
134
|
},
|
|
135
135
|
"./firestore": {
|
|
136
|
-
"react-native": "./src/firestore/index.ts",
|
|
137
|
-
"types": "./src/firestore/index.ts",
|
|
138
|
-
"default": "./src/firestore/index.ts"
|
|
139
|
-
},
|
|
140
|
-
"./storage": {
|
|
141
|
-
"react-native": "./src/storage/index.ts",
|
|
142
|
-
"types": "./src/storage/index.ts",
|
|
143
|
-
"default": "./src/storage/index.ts"
|
|
136
|
+
"react-native": "./src/domains/firestore/index.ts",
|
|
137
|
+
"types": "./src/domains/firestore/index.ts",
|
|
138
|
+
"default": "./src/domains/firestore/index.ts"
|
|
144
139
|
},
|
|
145
140
|
"./init": {
|
|
146
141
|
"react-native": "./src/init/index.ts",
|
|
@@ -77,7 +77,10 @@ export async function deleteCurrentUser(
|
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
await markUserDeleted(user.uid);
|
|
80
|
+
const marked = await markUserDeleted(user.uid);
|
|
81
|
+
if (!marked && __DEV__) {
|
|
82
|
+
console.warn('[AccountDeletion] Failed to mark user document as deleted before account removal');
|
|
83
|
+
}
|
|
81
84
|
await deleteUser(user);
|
|
82
85
|
return successResult();
|
|
83
86
|
} catch (error: unknown) {
|
|
@@ -185,7 +188,10 @@ async function attemptReauth(user: User, options: AccountDeletionOptions, origin
|
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
190
|
|
|
188
|
-
await markUserDeleted(currentUser.uid);
|
|
191
|
+
const marked = await markUserDeleted(currentUser.uid);
|
|
192
|
+
if (!marked && __DEV__) {
|
|
193
|
+
console.warn('[AccountDeletion] Failed to mark user document as deleted before account removal (reauth path)');
|
|
194
|
+
}
|
|
189
195
|
await deleteUser(currentUser);
|
|
190
196
|
return successResult();
|
|
191
197
|
} catch (err: unknown) {
|
|
@@ -218,6 +224,10 @@ export async function deleteUserAccount(user: User | null): Promise<AccountDelet
|
|
|
218
224
|
}
|
|
219
225
|
|
|
220
226
|
try {
|
|
227
|
+
const marked = await markUserDeleted(user.uid);
|
|
228
|
+
if (!marked && __DEV__) {
|
|
229
|
+
console.warn('[AccountDeletion] Failed to mark user document as deleted (deleteUserAccount)');
|
|
230
|
+
}
|
|
221
231
|
await deleteUser(user);
|
|
222
232
|
return successResult();
|
|
223
233
|
} catch (error: unknown) {
|
|
@@ -84,7 +84,10 @@ export async function markUserDeleted(userId: string): Promise<boolean> {
|
|
|
84
84
|
updatedAt: serverTimestamp(),
|
|
85
85
|
}, { merge: true });
|
|
86
86
|
return true;
|
|
87
|
-
} catch {
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
89
|
+
console.error("[UserDocumentService] Failed to mark user deleted:", error);
|
|
90
|
+
}
|
|
88
91
|
return false;
|
|
89
92
|
}
|
|
90
93
|
}
|
|
@@ -40,7 +40,7 @@ export function useFirestoreMutation<TData, TVars, TContext = unknown>(
|
|
|
40
40
|
|
|
41
41
|
return useMutation<TData, Error, TVars, TContext>({
|
|
42
42
|
...rest,
|
|
43
|
-
onSuccess: (data, variables,
|
|
43
|
+
onSuccess: (data, variables, _context) => {
|
|
44
44
|
if (invalidateKeys?.length) {
|
|
45
45
|
invalidateKeys.forEach((key) => {
|
|
46
46
|
queryClient.invalidateQueries({ queryKey: key });
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { useEffect, useRef } from 'react';
|
|
23
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
24
24
|
import {
|
|
25
25
|
useQuery,
|
|
26
26
|
useQueryClient,
|
|
@@ -46,19 +46,22 @@ export function useFirestoreSnapshot<TData>(
|
|
|
46
46
|
const queryClient = useQueryClient();
|
|
47
47
|
const unsubscribeRef = useRef<(() => void) | null>(null);
|
|
48
48
|
|
|
49
|
+
// Stabilize queryKey to prevent unnecessary listener re-subscriptions
|
|
50
|
+
const stableKeyString = JSON.stringify(queryKey);
|
|
51
|
+
const stableQueryKey = useMemo(() => queryKey, [stableKeyString]);
|
|
52
|
+
|
|
49
53
|
useEffect(() => {
|
|
50
54
|
if (!enabled) return;
|
|
51
55
|
|
|
52
56
|
unsubscribeRef.current = subscribe((data) => {
|
|
53
|
-
queryClient.setQueryData(
|
|
57
|
+
queryClient.setQueryData(stableQueryKey, data);
|
|
54
58
|
});
|
|
55
59
|
|
|
56
60
|
return () => {
|
|
57
61
|
unsubscribeRef.current?.();
|
|
58
62
|
unsubscribeRef.current = null;
|
|
59
63
|
};
|
|
60
|
-
|
|
61
|
-
}, [enabled, queryClient, ...queryKey]);
|
|
64
|
+
}, [enabled, queryClient, stableQueryKey, subscribe]);
|
|
62
65
|
|
|
63
66
|
return useQuery<TData, Error>({
|
|
64
67
|
queryKey,
|
|
@@ -51,3 +51,49 @@ export function timestampToDate(timestamp: Timestamp | null | undefined): Date |
|
|
|
51
51
|
export function getCurrentISOString(): string {
|
|
52
52
|
return new Date().toISOString();
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Labels for relative time formatting.
|
|
57
|
+
* Pass localized strings to support i18n.
|
|
58
|
+
*/
|
|
59
|
+
export interface RelativeTimeLabels {
|
|
60
|
+
now: string;
|
|
61
|
+
minutes: string;
|
|
62
|
+
hours: string;
|
|
63
|
+
days: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const DEFAULT_LABELS: RelativeTimeLabels = {
|
|
67
|
+
now: "now",
|
|
68
|
+
minutes: "m",
|
|
69
|
+
hours: "h",
|
|
70
|
+
days: "d",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Format a Date (or Firestore Timestamp) as a short relative time string.
|
|
75
|
+
*
|
|
76
|
+
* Examples: "now", "5m", "2h", "3d", or a localized date for older values.
|
|
77
|
+
*
|
|
78
|
+
* @param date The date to format (typically from `timestampToDate()` or `Timestamp.toDate()`)
|
|
79
|
+
* @param labels Optional localized labels — defaults to English abbreviations
|
|
80
|
+
*/
|
|
81
|
+
export function formatRelativeTime(
|
|
82
|
+
date: Date,
|
|
83
|
+
labels: RelativeTimeLabels = DEFAULT_LABELS,
|
|
84
|
+
): string {
|
|
85
|
+
const now = new Date();
|
|
86
|
+
const diffMs = now.getTime() - date.getTime();
|
|
87
|
+
const diffMins = Math.floor(diffMs / 60_000);
|
|
88
|
+
|
|
89
|
+
if (diffMins < 1) return labels.now;
|
|
90
|
+
if (diffMins < 60) return `${diffMins}${labels.minutes}`;
|
|
91
|
+
|
|
92
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
93
|
+
if (diffHours < 24) return `${diffHours}${labels.hours}`;
|
|
94
|
+
|
|
95
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
96
|
+
if (diffDays < 7) return `${diffDays}${labels.days}`;
|
|
97
|
+
|
|
98
|
+
return date.toLocaleDateString();
|
|
99
|
+
}
|
|
@@ -24,28 +24,40 @@ export async function withFirestore<T>(
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Execute a Firestore operation that returns void
|
|
27
|
-
*
|
|
27
|
+
* Returns Result<void> for consistent error handling
|
|
28
28
|
*/
|
|
29
29
|
export async function withFirestoreVoid(
|
|
30
30
|
operation: (db: Firestore) => Promise<void>,
|
|
31
|
-
): Promise<void
|
|
31
|
+
): Promise<Result<void>> {
|
|
32
32
|
const db = getFirestore();
|
|
33
33
|
if (!db) {
|
|
34
|
-
|
|
34
|
+
return createNoDbErrorResult<void>();
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
await operation(db);
|
|
38
|
+
return { success: true, data: undefined as void };
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const message = error instanceof Error ? error.message : 'Operation failed';
|
|
41
|
+
return { success: false, error: { code: 'firestore/operation-failed', message } };
|
|
35
42
|
}
|
|
36
|
-
return operation(db);
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/**
|
|
40
46
|
* Execute a Firestore operation that returns boolean
|
|
41
|
-
*
|
|
47
|
+
* Returns Result<boolean> for consistent error handling
|
|
42
48
|
*/
|
|
43
49
|
export async function withFirestoreBool(
|
|
44
50
|
operation: (db: Firestore) => Promise<boolean>,
|
|
45
|
-
): Promise<boolean
|
|
51
|
+
): Promise<Result<boolean>> {
|
|
46
52
|
const db = getFirestore();
|
|
47
53
|
if (!db) {
|
|
48
|
-
|
|
54
|
+
return createNoDbErrorResult<boolean>();
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const result = await operation(db);
|
|
58
|
+
return { success: true, data: result };
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const message = error instanceof Error ? error.message : 'Operation failed';
|
|
61
|
+
return { success: false, error: { code: 'firestore/operation-failed', message } };
|
|
49
62
|
}
|
|
50
|
-
return operation(db);
|
|
51
63
|
}
|
|
@@ -55,7 +55,8 @@ export class PaginationHelper<T> {
|
|
|
55
55
|
* @returns Page limit
|
|
56
56
|
*/
|
|
57
57
|
getLimit(params?: PaginationParams, defaultLimit: number = 10): number {
|
|
58
|
-
|
|
58
|
+
const limit = params?.limit ?? defaultLimit;
|
|
59
|
+
return Math.max(1, Math.floor(limit));
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/**
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { getFirestore } from "../../infrastructure/config/FirestoreClient";
|
|
12
12
|
import { hasCodeProperty } from "../../../../shared/domain/utils/type-guards.util";
|
|
13
13
|
import { ERROR_MESSAGES } from "../../../../shared/domain/utils/error-handlers/error-messages";
|
|
14
|
+
import { isQuotaError } from "../../../../shared/domain/utils/error-handlers/error-checkers";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Execute a transaction with automatic DB instance check.
|
|
@@ -28,6 +29,11 @@ export async function runTransaction<T>(
|
|
|
28
29
|
} catch (error) {
|
|
29
30
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
30
31
|
const errorCode = hasCodeProperty(error) ? error.code : 'unknown';
|
|
32
|
+
|
|
33
|
+
if (isQuotaError(error)) {
|
|
34
|
+
throw new Error(`[runTransaction] ${ERROR_MESSAGES.FIRESTORE.QUOTA_EXCEEDED}: ${errorMessage} (Code: ${errorCode})`);
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
throw new Error(`[runTransaction] Transaction failed: ${errorMessage} (Code: ${errorCode})`);
|
|
32
38
|
}
|
|
33
39
|
}
|
|
@@ -39,7 +39,7 @@ export interface FirebaseInitModuleConfig {
|
|
|
39
39
|
export function createFirebaseInitModule(
|
|
40
40
|
config: FirebaseInitModuleConfig = {}
|
|
41
41
|
): InitModule {
|
|
42
|
-
const { authInitializer, critical =
|
|
42
|
+
const { authInitializer, critical = true } = config;
|
|
43
43
|
|
|
44
44
|
return {
|
|
45
45
|
name: 'firebase',
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// Result types and helpers
|
|
7
7
|
export type {
|
|
8
8
|
Result,
|
|
9
|
+
FailureResult,
|
|
9
10
|
} from './result/result-types';
|
|
10
11
|
|
|
11
12
|
export {
|
|
@@ -13,6 +14,11 @@ export {
|
|
|
13
14
|
failureResultFrom,
|
|
14
15
|
} from './result/result-creators';
|
|
15
16
|
|
|
17
|
+
export {
|
|
18
|
+
isSuccess,
|
|
19
|
+
isFailure,
|
|
20
|
+
} from './result/result-helpers';
|
|
21
|
+
|
|
16
22
|
// Async operation execution
|
|
17
23
|
export {
|
|
18
24
|
executeOperation,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Utility functions for working with Result type
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { Result, SuccessResult } from './result-types';
|
|
6
|
+
import type { Result, SuccessResult, FailureResult } from './result-types';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Check if result is successful
|
|
@@ -11,3 +11,10 @@ import type { Result, SuccessResult } from './result-types';
|
|
|
11
11
|
export function isSuccess<T>(result: Result<T>): result is SuccessResult<T> {
|
|
12
12
|
return result.success === true && result.error === undefined;
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if result is a failure
|
|
17
|
+
*/
|
|
18
|
+
export function isFailure<T>(result: Result<T>): result is FailureResult {
|
|
19
|
+
return result.success === false && result.error !== undefined;
|
|
20
|
+
}
|
|
@@ -26,8 +26,8 @@ export function isValidFirebaseAuthDomain(authDomain: string): boolean {
|
|
|
26
26
|
return false;
|
|
27
27
|
}
|
|
28
28
|
return (
|
|
29
|
-
authDomain.
|
|
30
|
-
authDomain.
|
|
29
|
+
authDomain.endsWith('.firebaseapp.com') ||
|
|
30
|
+
authDomain.endsWith('.web.app')
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
|