@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-firebase",
3
- "version": "1.13.20",
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
+ }
@@ -1 +1,7 @@
1
- export * from "./uploader";
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
+ }
@@ -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?.metadata,
72
+ customMetadata: options?.customMetadata,
83
73
  };
84
74
 
85
- const snapshot = await uploadBytes(storageRef, blob, metadata);
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?.metadata,
117
+ customMetadata: options?.customMetadata,
122
118
  };
123
119
 
124
- const snapshot = await uploadBytes(storageRef, blob, metadata);
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
-