@umituz/react-native-firebase 1.13.20 → 1.13.22
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 +2 -2
- package/src/storage/deleter.ts +80 -0
- package/src/storage/index.ts +7 -1
- package/src/storage/types.ts +19 -0
- package/src/storage/uploader.ts +19 -17
- package/src/firestore/domain/entities/QuotaMetrics.ts +0 -28
- package/src/firestore/domain/entities/RequestLog.ts +0 -30
- package/src/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +0 -165
- package/src/firestore/infrastructure/services/QuotaMonitorService.ts +0 -108
- package/src/firestore/utils/quota-error-detector.util.ts +0 -100
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.22",
|
|
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",
|
|
@@ -52,4 +52,4 @@
|
|
|
52
52
|
"README.md",
|
|
53
53
|
"LICENSE"
|
|
54
54
|
]
|
|
55
|
-
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Storage Deleter
|
|
3
|
+
* Handles image deletion from Firebase Storage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getStorage, ref, deleteObject } from "firebase/storage";
|
|
7
|
+
import { getFirebaseApp } from "../infrastructure/config/FirebaseClient";
|
|
8
|
+
import type { DeleteResult } from "./types";
|
|
9
|
+
|
|
10
|
+
declare const __DEV__: boolean;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extract storage path from Firebase download URL
|
|
14
|
+
*/
|
|
15
|
+
function extractStoragePath(downloadUrl: string): string | null {
|
|
16
|
+
if (!downloadUrl.startsWith("https://")) {
|
|
17
|
+
return downloadUrl;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const urlObj = new URL(downloadUrl);
|
|
22
|
+
const pathMatch = urlObj.pathname.match(/\/o\/(.+)/);
|
|
23
|
+
|
|
24
|
+
if (!pathMatch) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return decodeURIComponent(pathMatch[1]);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getStorageInstance() {
|
|
35
|
+
const app = getFirebaseApp();
|
|
36
|
+
return app ? getStorage(app) : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Delete image from Firebase Storage
|
|
41
|
+
* Accepts either a download URL or storage path
|
|
42
|
+
*/
|
|
43
|
+
export async function deleteStorageFile(
|
|
44
|
+
downloadUrlOrPath: string
|
|
45
|
+
): Promise<DeleteResult> {
|
|
46
|
+
if (__DEV__) {
|
|
47
|
+
console.log("[StorageDeleter] Deleting", { url: downloadUrlOrPath });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const storagePath = extractStoragePath(downloadUrlOrPath);
|
|
51
|
+
|
|
52
|
+
if (!storagePath) {
|
|
53
|
+
if (__DEV__) {
|
|
54
|
+
console.error("[StorageDeleter] Invalid URL", {
|
|
55
|
+
url: downloadUrlOrPath,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return { success: false, storagePath: downloadUrlOrPath };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const storage = getStorageInstance();
|
|
63
|
+
if (!storage) {
|
|
64
|
+
throw new Error("Firebase Storage not initialized");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const storageRef = ref(storage, storagePath);
|
|
68
|
+
await deleteObject(storageRef);
|
|
69
|
+
|
|
70
|
+
if (__DEV__) {
|
|
71
|
+
console.log("[StorageDeleter] Deleted successfully", {
|
|
72
|
+
storagePath,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { success: true, storagePath };
|
|
77
|
+
} catch {
|
|
78
|
+
return { success: false, storagePath };
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/storage/index.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Storage Module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type { UploadResult, UploadOptions, DeleteResult } from "./types";
|
|
6
|
+
export { uploadBase64Image, uploadFile, getMimeType } from "./uploader";
|
|
7
|
+
export { deleteStorageFile } from "./deleter";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Storage Types
|
|
3
|
+
* Shared types for storage operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface UploadResult {
|
|
7
|
+
downloadUrl: string;
|
|
8
|
+
storagePath: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface UploadOptions {
|
|
12
|
+
mimeType?: string;
|
|
13
|
+
customMetadata?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DeleteResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
storagePath: string;
|
|
19
|
+
}
|
package/src/storage/uploader.ts
CHANGED
|
@@ -11,20 +11,10 @@ import {
|
|
|
11
11
|
type UploadMetadata,
|
|
12
12
|
} from "firebase/storage";
|
|
13
13
|
import { getFirebaseApp } from "../infrastructure/config/FirebaseClient";
|
|
14
|
+
import type { UploadResult, UploadOptions } from "./types";
|
|
14
15
|
|
|
15
16
|
declare const __DEV__: boolean;
|
|
16
17
|
|
|
17
|
-
export interface UploadResult {
|
|
18
|
-
downloadUrl: string;
|
|
19
|
-
storagePath: string;
|
|
20
|
-
metadata?: any;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface UploadOptions {
|
|
24
|
-
mimeType?: string;
|
|
25
|
-
metadata?: any;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
18
|
/**
|
|
29
19
|
* Extract MIME type from base64 data URL or return default
|
|
30
20
|
*/
|
|
@@ -79,16 +69,22 @@ export async function uploadBase64Image(
|
|
|
79
69
|
|
|
80
70
|
const metadata: UploadMetadata = {
|
|
81
71
|
contentType: mimeType,
|
|
82
|
-
customMetadata: options?.
|
|
72
|
+
customMetadata: options?.customMetadata,
|
|
83
73
|
};
|
|
84
74
|
|
|
85
|
-
|
|
75
|
+
await uploadBytes(storageRef, blob, metadata);
|
|
86
76
|
const downloadUrl = await getDownloadURL(storageRef);
|
|
87
77
|
|
|
78
|
+
if (__DEV__) {
|
|
79
|
+
console.log("[StorageUploader] Upload complete", {
|
|
80
|
+
storagePath,
|
|
81
|
+
downloadUrl,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
88
85
|
return {
|
|
89
86
|
downloadUrl,
|
|
90
87
|
storagePath,
|
|
91
|
-
metadata: snapshot.metadata,
|
|
92
88
|
};
|
|
93
89
|
}
|
|
94
90
|
|
|
@@ -118,15 +114,21 @@ export async function uploadFile(
|
|
|
118
114
|
|
|
119
115
|
const metadata: UploadMetadata = {
|
|
120
116
|
contentType: options?.mimeType ?? "image/jpeg",
|
|
121
|
-
customMetadata: options?.
|
|
117
|
+
customMetadata: options?.customMetadata,
|
|
122
118
|
};
|
|
123
119
|
|
|
124
|
-
|
|
120
|
+
await uploadBytes(storageRef, blob, metadata);
|
|
125
121
|
const downloadUrl = await getDownloadURL(storageRef);
|
|
126
122
|
|
|
123
|
+
if (__DEV__) {
|
|
124
|
+
console.log("[StorageUploader] Upload complete", {
|
|
125
|
+
storagePath,
|
|
126
|
+
downloadUrl,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
127
130
|
return {
|
|
128
131
|
downloadUrl,
|
|
129
132
|
storagePath,
|
|
130
|
-
metadata: snapshot.metadata,
|
|
131
133
|
};
|
|
132
134
|
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quota Metrics Entity
|
|
3
|
-
* Domain entity for tracking Firestore quota usage
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface QuotaMetrics {
|
|
7
|
-
readCount: number;
|
|
8
|
-
writeCount: number;
|
|
9
|
-
deleteCount: number;
|
|
10
|
-
timestamp: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface QuotaLimits {
|
|
14
|
-
dailyReadLimit: number;
|
|
15
|
-
dailyWriteLimit: number;
|
|
16
|
-
dailyDeleteLimit: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface QuotaStatus {
|
|
20
|
-
metrics: QuotaMetrics;
|
|
21
|
-
limits: QuotaLimits;
|
|
22
|
-
readPercentage: number;
|
|
23
|
-
writePercentage: number;
|
|
24
|
-
deletePercentage: number;
|
|
25
|
-
isNearLimit: boolean;
|
|
26
|
-
isOverLimit: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Request Log Entity
|
|
3
|
-
* Domain entity for tracking Firestore requests
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type RequestType = 'read' | 'write' | 'delete' | 'listener';
|
|
7
|
-
|
|
8
|
-
export interface RequestLog {
|
|
9
|
-
id: string;
|
|
10
|
-
type: RequestType;
|
|
11
|
-
collection: string;
|
|
12
|
-
documentId?: string;
|
|
13
|
-
timestamp: number;
|
|
14
|
-
duration?: number;
|
|
15
|
-
success: boolean;
|
|
16
|
-
error?: string;
|
|
17
|
-
cached: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface RequestStats {
|
|
21
|
-
totalRequests: number;
|
|
22
|
-
readRequests: number;
|
|
23
|
-
writeRequests: number;
|
|
24
|
-
deleteRequests: number;
|
|
25
|
-
listenerRequests: number;
|
|
26
|
-
cachedRequests: number;
|
|
27
|
-
failedRequests: number;
|
|
28
|
-
averageDuration: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quota Tracking Middleware
|
|
3
|
-
* Tracks Firestore operations for quota monitoring
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { quotaMonitorService } from '../services/QuotaMonitorService';
|
|
7
|
-
import { requestLoggerService } from '../services/RequestLoggerService';
|
|
8
|
-
import type { RequestType } from '../../domain/entities/RequestLog';
|
|
9
|
-
|
|
10
|
-
interface TrackedOperation {
|
|
11
|
-
type: RequestType;
|
|
12
|
-
collection: string;
|
|
13
|
-
documentId?: string;
|
|
14
|
-
count: number;
|
|
15
|
-
cached?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class QuotaTrackingMiddleware {
|
|
19
|
-
/**
|
|
20
|
-
* Track a read operation
|
|
21
|
-
*/
|
|
22
|
-
trackRead(collection: string, count: number = 1, cached: boolean = false): void {
|
|
23
|
-
quotaMonitorService.incrementRead(count);
|
|
24
|
-
requestLoggerService.logRequest({
|
|
25
|
-
type: 'read',
|
|
26
|
-
collection,
|
|
27
|
-
success: true,
|
|
28
|
-
cached,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Track a write operation
|
|
34
|
-
*/
|
|
35
|
-
trackWrite(
|
|
36
|
-
collection: string,
|
|
37
|
-
documentId?: string,
|
|
38
|
-
count: number = 1,
|
|
39
|
-
): void {
|
|
40
|
-
quotaMonitorService.incrementWrite(count);
|
|
41
|
-
requestLoggerService.logRequest({
|
|
42
|
-
type: 'write',
|
|
43
|
-
collection,
|
|
44
|
-
documentId,
|
|
45
|
-
success: true,
|
|
46
|
-
cached: false,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Track a delete operation
|
|
52
|
-
*/
|
|
53
|
-
trackDelete(
|
|
54
|
-
collection: string,
|
|
55
|
-
documentId?: string,
|
|
56
|
-
count: number = 1,
|
|
57
|
-
): void {
|
|
58
|
-
quotaMonitorService.incrementDelete(count);
|
|
59
|
-
requestLoggerService.logRequest({
|
|
60
|
-
type: 'delete',
|
|
61
|
-
collection,
|
|
62
|
-
documentId,
|
|
63
|
-
success: true,
|
|
64
|
-
cached: false,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Track a listener operation
|
|
70
|
-
*/
|
|
71
|
-
trackListener(collection: string, documentId?: string): void {
|
|
72
|
-
requestLoggerService.logRequest({
|
|
73
|
-
type: 'listener',
|
|
74
|
-
collection,
|
|
75
|
-
documentId,
|
|
76
|
-
success: true,
|
|
77
|
-
cached: false,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Track a failed operation
|
|
83
|
-
*/
|
|
84
|
-
trackError(
|
|
85
|
-
type: RequestType,
|
|
86
|
-
collection: string,
|
|
87
|
-
error: string,
|
|
88
|
-
documentId?: string,
|
|
89
|
-
): void {
|
|
90
|
-
requestLoggerService.logRequest({
|
|
91
|
-
type,
|
|
92
|
-
collection,
|
|
93
|
-
documentId,
|
|
94
|
-
success: false,
|
|
95
|
-
error,
|
|
96
|
-
cached: false,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Track operation with timing
|
|
102
|
-
*/
|
|
103
|
-
async trackOperation<T>(
|
|
104
|
-
operation: TrackedOperation,
|
|
105
|
-
operationFn: () => Promise<T>,
|
|
106
|
-
): Promise<T> {
|
|
107
|
-
const startTime = Date.now();
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
const result = await operationFn();
|
|
111
|
-
const duration = Date.now() - startTime;
|
|
112
|
-
|
|
113
|
-
if (operation.type === 'read') {
|
|
114
|
-
quotaMonitorService.incrementRead(operation.count);
|
|
115
|
-
requestLoggerService.logRequest({
|
|
116
|
-
type: 'read',
|
|
117
|
-
collection: operation.collection,
|
|
118
|
-
documentId: operation.documentId,
|
|
119
|
-
success: true,
|
|
120
|
-
cached: operation.cached || false,
|
|
121
|
-
duration,
|
|
122
|
-
});
|
|
123
|
-
} else if (operation.type === 'write') {
|
|
124
|
-
quotaMonitorService.incrementWrite(operation.count);
|
|
125
|
-
requestLoggerService.logRequest({
|
|
126
|
-
type: 'write',
|
|
127
|
-
collection: operation.collection,
|
|
128
|
-
documentId: operation.documentId,
|
|
129
|
-
success: true,
|
|
130
|
-
cached: false,
|
|
131
|
-
duration,
|
|
132
|
-
});
|
|
133
|
-
} else if (operation.type === 'delete') {
|
|
134
|
-
quotaMonitorService.incrementDelete(operation.count);
|
|
135
|
-
requestLoggerService.logRequest({
|
|
136
|
-
type: 'delete',
|
|
137
|
-
collection: operation.collection,
|
|
138
|
-
documentId: operation.documentId,
|
|
139
|
-
success: true,
|
|
140
|
-
cached: false,
|
|
141
|
-
duration,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return result;
|
|
146
|
-
} catch (error) {
|
|
147
|
-
const duration = Date.now() - startTime;
|
|
148
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
149
|
-
requestLoggerService.logRequest({
|
|
150
|
-
type: operation.type,
|
|
151
|
-
collection: operation.collection,
|
|
152
|
-
documentId: operation.documentId,
|
|
153
|
-
success: false,
|
|
154
|
-
error: errorMessage,
|
|
155
|
-
cached: false,
|
|
156
|
-
duration,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
throw error;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export const quotaTrackingMiddleware = new QuotaTrackingMiddleware();
|
|
165
|
-
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quota Monitor Service
|
|
3
|
-
* Infrastructure service for monitoring Firestore quota usage
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { QuotaMetrics, QuotaLimits, QuotaStatus } from '../../domain/entities/QuotaMetrics';
|
|
7
|
-
import { QuotaCalculator } from '../../domain/services/QuotaCalculator';
|
|
8
|
-
|
|
9
|
-
export class QuotaMonitorService {
|
|
10
|
-
private metrics: QuotaMetrics = {
|
|
11
|
-
readCount: 0,
|
|
12
|
-
writeCount: 0,
|
|
13
|
-
deleteCount: 0,
|
|
14
|
-
timestamp: Date.now(),
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
private limits: QuotaLimits = QuotaCalculator.getDefaultLimits();
|
|
18
|
-
private listeners: Set<(status: QuotaStatus) => void> = new Set();
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Set quota limits
|
|
22
|
-
*/
|
|
23
|
-
setLimits(limits: Partial<QuotaLimits>): void {
|
|
24
|
-
this.limits = { ...this.limits, ...limits };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Increment read count
|
|
29
|
-
*/
|
|
30
|
-
incrementRead(count: number = 1): void {
|
|
31
|
-
this.metrics.readCount += count;
|
|
32
|
-
this.notifyListeners();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Increment write count
|
|
37
|
-
*/
|
|
38
|
-
incrementWrite(count: number = 1): void {
|
|
39
|
-
this.metrics.writeCount += count;
|
|
40
|
-
this.notifyListeners();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Increment delete count
|
|
45
|
-
*/
|
|
46
|
-
incrementDelete(count: number = 1): void {
|
|
47
|
-
this.metrics.deleteCount += count;
|
|
48
|
-
this.notifyListeners();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Get current metrics
|
|
53
|
-
*/
|
|
54
|
-
getMetrics(): QuotaMetrics {
|
|
55
|
-
return { ...this.metrics };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Get current status
|
|
60
|
-
*/
|
|
61
|
-
getStatus(): QuotaStatus {
|
|
62
|
-
return QuotaCalculator.calculateStatus(this.metrics, this.limits);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Reset metrics
|
|
67
|
-
*/
|
|
68
|
-
resetMetrics(): void {
|
|
69
|
-
this.metrics = {
|
|
70
|
-
readCount: 0,
|
|
71
|
-
writeCount: 0,
|
|
72
|
-
deleteCount: 0,
|
|
73
|
-
timestamp: Date.now(),
|
|
74
|
-
};
|
|
75
|
-
this.notifyListeners();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Add status change listener
|
|
80
|
-
*/
|
|
81
|
-
addListener(listener: (status: QuotaStatus) => void): () => void {
|
|
82
|
-
this.listeners.add(listener);
|
|
83
|
-
return () => {
|
|
84
|
-
this.listeners.delete(listener);
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Notify all listeners
|
|
90
|
-
*/
|
|
91
|
-
private notifyListeners(): void {
|
|
92
|
-
const status = this.getStatus();
|
|
93
|
-
this.listeners.forEach((listener) => {
|
|
94
|
-
try {
|
|
95
|
-
listener(status);
|
|
96
|
-
} catch (error) {
|
|
97
|
-
/* eslint-disable-next-line no-console */
|
|
98
|
-
if (__DEV__) {
|
|
99
|
-
/* eslint-disable-next-line no-console */
|
|
100
|
-
console.error('[QuotaMonitor] Listener error:', error);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export const quotaMonitorService = new QuotaMonitorService();
|
|
108
|
-
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quota Error Detector Utility
|
|
3
|
-
* Single Responsibility: Detect Firebase quota errors
|
|
4
|
-
*
|
|
5
|
-
* Firebase quota limits:
|
|
6
|
-
* - Free tier: 50K reads/day, 20K writes/day, 20K deletes/day
|
|
7
|
-
* - Blaze plan: Pay as you go, higher limits
|
|
8
|
-
*
|
|
9
|
-
* Quota errors are NOT retryable - quota won't increase by retrying
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Check if error is a Firebase quota error
|
|
14
|
-
* Quota errors indicate daily read/write/delete limits are exceeded
|
|
15
|
-
*
|
|
16
|
-
* @param error - Error object to check
|
|
17
|
-
* @returns true if error is a quota error
|
|
18
|
-
*/
|
|
19
|
-
export function isQuotaError(error: unknown): boolean {
|
|
20
|
-
if (!error || typeof error !== "object") {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const firebaseError = error as { code?: string; message?: string; name?: string };
|
|
25
|
-
|
|
26
|
-
// Check error code
|
|
27
|
-
if (firebaseError.code === "resource-exhausted") {
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Check error message
|
|
32
|
-
const errorMessage = firebaseError.message?.toLowerCase() || "";
|
|
33
|
-
if (
|
|
34
|
-
errorMessage.includes("quota") ||
|
|
35
|
-
errorMessage.includes("quota exceeded") ||
|
|
36
|
-
errorMessage.includes("resource-exhausted") ||
|
|
37
|
-
errorMessage.includes("daily limit")
|
|
38
|
-
) {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Check error name
|
|
43
|
-
const errorName = firebaseError.name?.toLowerCase() || "";
|
|
44
|
-
if (errorName.includes("quota") || errorName.includes("resource-exhausted")) {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Check if error is retryable
|
|
53
|
-
* Quota errors are NOT retryable
|
|
54
|
-
*
|
|
55
|
-
* @param error - Error object to check
|
|
56
|
-
* @returns true if error is retryable
|
|
57
|
-
*/
|
|
58
|
-
export function isRetryableError(error: unknown): boolean {
|
|
59
|
-
// Quota errors are NOT retryable
|
|
60
|
-
if (isQuotaError(error)) {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!error || typeof error !== "object") {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const firebaseError = error as { code?: string; message?: string };
|
|
69
|
-
|
|
70
|
-
// Firestore transaction conflicts are retryable
|
|
71
|
-
if (firebaseError.code === "failed-precondition") {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Network errors are retryable
|
|
76
|
-
if (
|
|
77
|
-
firebaseError.code === "unavailable" ||
|
|
78
|
-
firebaseError.code === "deadline-exceeded"
|
|
79
|
-
) {
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Timeout errors are retryable
|
|
84
|
-
const errorMessage = firebaseError.message?.toLowerCase() || "";
|
|
85
|
-
if (errorMessage.includes("timeout")) {
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Get user-friendly quota error message
|
|
94
|
-
*
|
|
95
|
-
* @returns User-friendly error message
|
|
96
|
-
*/
|
|
97
|
-
export function getQuotaErrorMessage(): string {
|
|
98
|
-
return "Firebase quota exceeded. Please try again later or upgrade your Firebase plan.";
|
|
99
|
-
}
|
|
100
|
-
|