@react-native-firebase/storage 20.1.0 → 20.2.1

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/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [20.2.1](https://github.com/invertase/react-native-firebase/compare/v20.2.0...v20.2.1) (2024-07-17)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **storage:** ensure emulator is used for different storage buckets ([#7892](https://github.com/invertase/react-native-firebase/issues/7892)) ([3fa3f11](https://github.com/invertase/react-native-firebase/commit/3fa3f110b357ef0dbe2cc0fc12e982edc913b588))
11
+
12
+ ## [20.2.0](https://github.com/invertase/react-native-firebase/compare/v20.1.0...v20.2.0) (2024-07-15)
13
+
14
+ ### Features
15
+
16
+ - **other:** Add Storage support ([#7888](https://github.com/invertase/react-native-firebase/issues/7888)) ([9b8dda7](https://github.com/invertase/react-native-firebase/commit/9b8dda704a01243039624bfcc7614021e6c3a527))
17
+
6
18
  ## [20.1.0](https://github.com/invertase/react-native-firebase/compare/v20.0.0...v20.1.0) (2024-06-04)
7
19
 
8
20
  **Note:** Version bump only for package @react-native-firebase/storage
@@ -281,12 +281,15 @@ public class ReactNativeFirebaseStorageModule extends ReactNativeFirebaseModule
281
281
  * @link https://firebase.google.com/docs/reference/js/firebase.storage.Storage#useEmulator
282
282
  */
283
283
  @ReactMethod
284
- public void useEmulator(String appName, String host, int port, Promise promise) {
284
+ public void useEmulator(String appName, String host, int port, String bucketUrl, Promise promise) {
285
285
  FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
286
- FirebaseStorage firebaseStorage = FirebaseStorage.getInstance(firebaseApp);
287
- if (emulatorConfigs.get(appName) == null) {
286
+
287
+ FirebaseStorage firebaseStorage = FirebaseStorage.getInstance(firebaseApp, bucketUrl);
288
+ String emulatorKey = appName + ":" + bucketUrl;
289
+
290
+ if (emulatorConfigs.get(emulatorKey) == null) {
288
291
  firebaseStorage.useEmulator(host, port);
289
- emulatorConfigs.put(appName, "true");
292
+ emulatorConfigs.put(emulatorKey, "true");
290
293
  }
291
294
  promise.resolve(null);
292
295
  }
@@ -511,12 +511,15 @@ RCT_EXPORT_METHOD(putString
511
511
  RCT_EXPORT_METHOD(useEmulator
512
512
  : (FIRApp *)firebaseApp
513
513
  : (nonnull NSString *)host
514
- : (NSInteger)port) {
514
+ : (NSInteger)port
515
+ : (NSString *)bucketUrl) {
515
516
  emulatorHost = host;
516
517
  emulatorPort = port;
517
- if (!emulatorConfigs[firebaseApp.name]) {
518
- [[FIRStorage storageForApp:firebaseApp] useEmulatorWithHost:host port:port];
519
- emulatorConfigs[firebaseApp.name] = @YES;
518
+ NSString *key = [self createEmulatorKey:bucketUrl appName:firebaseApp.name];
519
+
520
+ if (!emulatorConfigs[key]) {
521
+ [[FIRStorage storageForApp:firebaseApp URL:bucketUrl] useEmulatorWithHost:host port:port];
522
+ emulatorConfigs[key] = @YES;
520
523
  }
521
524
  }
522
525
 
@@ -557,6 +560,10 @@ RCT_EXPORT_METHOD(setTaskStatus
557
560
  #pragma mark -
558
561
  #pragma mark Firebase Storage Internals
559
562
 
563
+ - (NSString *)createEmulatorKey:(NSString *)bucketUrl appName:(NSString *)appName {
564
+ return [NSString stringWithFormat:@"%@-%@", appName, bucketUrl];
565
+ }
566
+
560
567
  - (void)addUploadTaskObservers:(FIRStorageUploadTask *)uploadTask
561
568
  appDisplayName:(NSString *)appDisplayName
562
569
  taskId:(NSNumber *)taskId
@@ -675,11 +682,11 @@ RCT_EXPORT_METHOD(setTaskStatus
675
682
  storage = [FIRStorage storageForApp:firebaseApp URL:bucket];
676
683
 
677
684
  NSLog(@"Setting emulator - host %@ port %ld", emulatorHost, (long)emulatorPort);
678
- if (![emulatorHost isEqual:[NSNull null]] && emulatorHost != nil &&
679
- !emulatorConfigs[firebaseApp.name]) {
685
+ NSString *key = [self createEmulatorKey:bucket appName:firebaseApp.name];
686
+ if (![emulatorHost isEqual:[NSNull null]] && emulatorHost != nil && !emulatorConfigs[key]) {
680
687
  @try {
681
688
  [storage useEmulatorWithHost:emulatorHost port:emulatorPort];
682
- emulatorConfigs[firebaseApp.name] = @YES;
689
+ emulatorConfigs[key] = @YES;
683
690
  } @catch (NSException *e) {
684
691
  NSLog(@"WARNING: Unable to set the Firebase Storage emulator settings. These must be set "
685
692
  @"before any usages of Firebase Storage. If you see this log after a hot "
package/lib/index.js CHANGED
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import { isAndroid, isNumber, isString } from '@react-native-firebase/app/lib/common';
19
+ import { setReactNativeModule } from '@react-native-firebase/app/lib/internal/nativeModule';
19
20
  import {
20
21
  createModuleNamespace,
21
22
  FirebaseModule,
@@ -25,6 +26,7 @@ import StorageReference from './StorageReference';
25
26
  import StorageStatics from './StorageStatics';
26
27
  import { getGsUrlParts, getHttpUrlParts, handleStorageEvent } from './utils';
27
28
  import version from './version';
29
+ import fallBackModule from './web/RNFBStorageModule';
28
30
 
29
31
  export {
30
32
  getStorage,
@@ -201,7 +203,7 @@ class FirebaseStorageModule extends FirebaseModule {
201
203
  }
202
204
  this.emulatorHost = host;
203
205
  this.emulatorPort = port;
204
- this.native.useEmulator(_host, port);
206
+ this.native.useEmulator(_host, port, this._customUrlOrRegion);
205
207
  return [_host, port]; // undocumented return, just used to unit test android host remapping
206
208
  }
207
209
  }
@@ -230,3 +232,5 @@ export default createModuleNamespace({
230
232
  // storage().X(...);
231
233
  // firebase.storage().X(...);
232
234
  export const firebase = getFirebaseRoot();
235
+
236
+ setReactNativeModule(nativeModuleName, fallBackModule);
package/lib/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- module.exports = '20.1.0';
2
+ module.exports = '20.2.1';
@@ -0,0 +1,2 @@
1
+ // No-op for android.
2
+ export default {};
@@ -0,0 +1,2 @@
1
+ // No-op for ios.
2
+ export default {};
@@ -0,0 +1,462 @@
1
+ import {
2
+ getApps,
3
+ connectStorageEmulator,
4
+ getApp,
5
+ getStorage,
6
+ deleteObject,
7
+ getDownloadURL,
8
+ getMetadata,
9
+ list,
10
+ listAll,
11
+ updateMetadata,
12
+ uploadBytesResumable,
13
+ ref as firebaseStorageRef,
14
+ } from '@react-native-firebase/app/lib/internal/web/firebaseStorage';
15
+ import { guard, getWebError, emitEvent } from '@react-native-firebase/app/lib/internal/web/utils';
16
+ import { Base64 } from '@react-native-firebase/app/lib/common';
17
+
18
+ function rejectWithCodeAndMessage(code, message) {
19
+ return Promise.reject(
20
+ getWebError({
21
+ code,
22
+ message,
23
+ }),
24
+ );
25
+ }
26
+
27
+ function metadataToObject(metadata) {
28
+ const out = {
29
+ bucket: metadata.bucket,
30
+ generation: metadata.generation,
31
+ metageneration: metadata.metageneration,
32
+ fullPath: metadata.fullPath,
33
+ name: metadata.name,
34
+ size: metadata.size,
35
+ timeCreated: metadata.timeCreated,
36
+ updated: metadata.updated,
37
+ md5Hash: metadata.md5Hash,
38
+ };
39
+
40
+ if ('cacheControl' in metadata) {
41
+ out.cacheControl = metadata.cacheControl;
42
+ }
43
+
44
+ if ('contentLanguage' in metadata) {
45
+ out.contentLanguage = metadata.contentLanguage;
46
+ }
47
+
48
+ if ('contentDisposition' in metadata) {
49
+ out.contentDisposition = metadata.contentDisposition;
50
+ }
51
+
52
+ if ('contentEncoding' in metadata) {
53
+ out.contentEncoding = metadata.contentEncoding;
54
+ }
55
+
56
+ if ('contentType' in metadata) {
57
+ out.contentType = metadata.contentType;
58
+ }
59
+
60
+ if ('customMetadata' in metadata) {
61
+ out.customMetadata = metadata.customMetadata;
62
+ // To match Android/iOS
63
+ out.metadata = metadata.customMetadata;
64
+ }
65
+
66
+ return out;
67
+ }
68
+
69
+ function uploadTaskErrorToObject(error, snapshot) {
70
+ return {
71
+ ...uploadTaskSnapshotToObject(snapshot),
72
+ state: 'error',
73
+ error: getWebError(error),
74
+ };
75
+ }
76
+
77
+ function uploadTaskSnapshotToObject(snapshot) {
78
+ return {
79
+ totalBytes: snapshot ? snapshot.totalBytes : 0,
80
+ bytesTransferred: snapshot ? snapshot.bytesTransferred : 0,
81
+ state: snapshot ? taskStateToString(snapshot.state) : 'unknown',
82
+ metadata: snapshot ? metadataToObject(snapshot.metadata) : {},
83
+ };
84
+ }
85
+
86
+ function taskStateToString(state) {
87
+ const override = {
88
+ canceled: 'cancelled',
89
+ };
90
+
91
+ if (state in override) {
92
+ return override[state];
93
+ }
94
+
95
+ return state;
96
+ }
97
+
98
+ function makeSettableMetadata(metadata) {
99
+ return {
100
+ cacheControl: metadata.cacheControl,
101
+ contentDisposition: metadata.contentDisposition,
102
+ contentEncoding: metadata.contentEncoding,
103
+ contentType: metadata.contentType,
104
+ contentLanguage: metadata.contentLanguage,
105
+ customMetadata: metadata.customMetadata,
106
+ };
107
+ }
108
+
109
+ function listResultToObject(result) {
110
+ return {
111
+ nextPageToken: result.nextPageToken,
112
+ items: result.items.map(ref => ref.fullPath),
113
+ prefixes: result.prefixes.map(ref => ref.fullPath),
114
+ };
115
+ }
116
+
117
+ const emulatorForApp = {};
118
+ const appInstances = {};
119
+ const storageInstances = {};
120
+ const tasks = {};
121
+
122
+ function getBucketFromUrl(url) {
123
+ const pathWithBucketName = url.substring(5);
124
+ const bucket = url.substring(0, pathWithBucketName.indexOf('/') + 5);
125
+ return bucket;
126
+ }
127
+
128
+ function getCachedAppInstance(appName) {
129
+ return (appInstances[appName] ??= getApp(appName));
130
+ }
131
+
132
+ // Returns a cached Storage instance.
133
+ function getCachedStorageInstance(appName, url) {
134
+ let instance;
135
+ if (!url) {
136
+ instance = getCachedStorageInstance(
137
+ appName,
138
+ getCachedAppInstance(appName).options.storageBucket,
139
+ );
140
+ } else {
141
+ const bucket = getBucketFromUrl(url);
142
+ instance = storageInstances[`${appName}|${bucket}`] ??= getStorage(
143
+ getCachedAppInstance(appName),
144
+ bucket,
145
+ );
146
+ }
147
+ if (emulatorForApp[appName]) {
148
+ connectStorageEmulator(instance, emulatorForApp[appName].host, emulatorForApp[appName].port);
149
+ }
150
+ return instance;
151
+ }
152
+
153
+ // Returns a Storage Reference.
154
+ function getReferenceFromUrl(appName, url) {
155
+ const path = url.substring(url.indexOf('/') + 1);
156
+ const instance = getCachedStorageInstance(appName, path);
157
+ return firebaseStorageRef(instance, url);
158
+ }
159
+
160
+ const CONSTANTS = {};
161
+ const defaultAppInstance = getApps()[0];
162
+
163
+ if (defaultAppInstance) {
164
+ CONSTANTS.maxDownloadRetryTime = 0;
165
+ CONSTANTS.maxOperationRetryTime = 0;
166
+ CONSTANTS.maxUploadRetryTime = 0;
167
+ }
168
+
169
+ export default {
170
+ ...CONSTANTS,
171
+
172
+ /**
173
+ * Delete an object at the path.
174
+ * @param {string} appName - The app name.
175
+ * @param {string} url - The path to the object.
176
+ * @return {Promise<void>}
177
+ */
178
+ delete(appName, url) {
179
+ return guard(async () => {
180
+ const ref = getReferenceFromUrl(appName, url);
181
+ await deleteObject(ref);
182
+ });
183
+ },
184
+
185
+ /**
186
+ * Get the download URL for an object.
187
+ * @param {string} appName - The app name.
188
+ * @param {string} url - The path to the object.
189
+ * @return {Promise<string>} The download URL.
190
+ */
191
+ getDownloadURL(appName, url) {
192
+ return guard(async () => {
193
+ const ref = getReferenceFromUrl(appName, url);
194
+ const downloadURL = await getDownloadURL(ref);
195
+ return downloadURL;
196
+ });
197
+ },
198
+
199
+ /**
200
+ * Get the metadata for an object.
201
+ * @param {string} appName - The app name.
202
+ * @param {string} url - The path to the object.
203
+ * @return {Promise<Object>} The metadata.
204
+ */
205
+ getMetadata(appName, url) {
206
+ return guard(async () => {
207
+ const ref = getReferenceFromUrl(appName, url);
208
+ const metadata = await getMetadata(ref);
209
+ return metadataToObject(metadata);
210
+ });
211
+ },
212
+
213
+ /**
214
+ * List objects at the path.
215
+ * @param {string} appName - The app name.
216
+ * @param {string} url - The path to the object.
217
+ * @param {Object} listOptions - The list options.
218
+ * @return {Promise<Object>} The list result.
219
+ */
220
+ list(appName, url, listOptions) {
221
+ return guard(async () => {
222
+ const ref = getReferenceFromUrl(appName, url);
223
+ const listResult = await list(ref, listOptions);
224
+ return listResultToObject(listResult);
225
+ });
226
+ },
227
+
228
+ /**
229
+ * List all objects at the path.
230
+ * @param {string} appName - The app name.
231
+ * @param {string} url - The path to the object.
232
+ * @return {Promise<Object>} The list result.
233
+ */
234
+ listAll(appName, url) {
235
+ return guard(async () => {
236
+ const ref = getReferenceFromUrl(appName, url);
237
+ const listResult = await listAll(ref);
238
+ return listResultToObject(listResult);
239
+ });
240
+ },
241
+
242
+ /**
243
+ * Update the metadata for an object.
244
+ * @param {string} appName - The app name.
245
+ * @param {string} url - The path to the object.
246
+ * @param {Object} metadata - The metadata (SettableMetadata).
247
+ */
248
+ updateMetadata(appName, url, metadata) {
249
+ return guard(async () => {
250
+ const ref = getReferenceFromUrl(appName, url);
251
+ const updated = await updateMetadata(ref, makeSettableMetadata(metadata));
252
+ return metadataToObject(updated);
253
+ });
254
+ },
255
+
256
+ setMaxDownloadRetryTime() {
257
+ if (__DEV__) {
258
+ // eslint-disable-next-line no-console
259
+ console.warn(
260
+ 'The Firebase Storage `setMaxDownloadRetryTime` method is not available in the this environment.',
261
+ );
262
+ return;
263
+ }
264
+ },
265
+
266
+ /**
267
+ * Set the maximum operation retry time.
268
+ * @param {string} appName - The app name.
269
+ * @param {number} milliseconds - The maximum operation retry time.
270
+ * @return {Promise<void>}
271
+ */
272
+ setMaxOperationRetryTime(appName, milliseconds) {
273
+ return guard(async () => {
274
+ const storage = getCachedStorageInstance(appName);
275
+ storage.maxOperationRetryTime = milliseconds;
276
+ });
277
+ },
278
+
279
+ /**
280
+ * Set the maximum upload retry time.
281
+ * @param {string} appName - The app name.
282
+ * @param {number} milliseconds - The maximum upload retry time.
283
+ * @return {Promise<void>}
284
+ */
285
+ setMaxUploadRetryTime(appName, milliseconds) {
286
+ return guard(async () => {
287
+ const storage = getCachedStorageInstance(appName);
288
+ storage.maxUploadRetryTime = milliseconds;
289
+ });
290
+ },
291
+
292
+ /**
293
+ * Use the Firebase Storage emulator.
294
+ * @param {string} appName - The app name.
295
+ * @param {string} host - The emulator host.
296
+ * @param {number} port - The emulator port.
297
+ * @return {Promise<void>}
298
+ */
299
+ useEmulator(appName, host, port) {
300
+ return guard(async () => {
301
+ const instance = getCachedStorageInstance(appName);
302
+ connectStorageEmulator(instance, host, port);
303
+ emulatorForApp[appName] = { host, port };
304
+ });
305
+ },
306
+
307
+ writeToFile() {
308
+ return rejectWithCodeAndMessage(
309
+ 'unsupported',
310
+ 'This operation is not supported in this environment.',
311
+ );
312
+ },
313
+
314
+ /**
315
+ * Put a string to the path.
316
+ * @param {string} appName - The app name.
317
+ * @param {string} url - The path to the object.
318
+ * @param {string} string - The string to put.
319
+ * @param {string} format - The format of the string.
320
+ * @param {Object} metadata - The metadata (SettableMetadata).
321
+ * @param {string} taskId - The task ID.
322
+ * @return {Promise<Object>} The upload snapshot.
323
+ */
324
+ putString(appName, url, string, format, metadata = {}, taskId) {
325
+ return guard(async () => {
326
+ const ref = getReferenceFromUrl(appName, url);
327
+
328
+ let base64String = null;
329
+
330
+ switch (format) {
331
+ case 'base64':
332
+ base64String = Base64.atob(string);
333
+ break;
334
+ case 'base64url':
335
+ base64String = Base64.atob(string.replace(/_/g, '/').replace(/-/g, '+'));
336
+ break;
337
+ }
338
+
339
+ const byteArray = new Uint8Array(base64String ? base64String.length : 0);
340
+
341
+ if (base64String) {
342
+ for (let i = 0; i < base64String.length; i++) {
343
+ byteArray[i] = base64String.charCodeAt(i);
344
+ }
345
+ }
346
+
347
+ // Start a resumable upload task.
348
+ const task = uploadBytesResumable(ref, byteArray, {
349
+ ...makeSettableMetadata(metadata),
350
+ md5Hash: metadata.md5Hash,
351
+ });
352
+
353
+ // Store the task in the tasks map.
354
+ tasks[taskId] = task;
355
+
356
+ const snapshot = await new Promise((resolve, reject) => {
357
+ task.on(
358
+ 'state_changed',
359
+ snapshot => {
360
+ const event = {
361
+ body: uploadTaskSnapshotToObject(snapshot),
362
+ appName,
363
+ taskId,
364
+ eventName: 'state_changed',
365
+ };
366
+ emitEvent('storage_event', event);
367
+ },
368
+ error => {
369
+ const errorSnapshot = uploadTaskErrorToObject(error, task.snapshot);
370
+ const event = {
371
+ body: {
372
+ ...errorSnapshot,
373
+ state: 'error',
374
+ },
375
+ appName,
376
+ taskId,
377
+ eventName: 'state_changed',
378
+ };
379
+ emitEvent('storage_event', event);
380
+ emitEvent('storage_event', {
381
+ ...event,
382
+ eventName: 'upload_failure',
383
+ });
384
+ delete tasks[taskId];
385
+ reject(error);
386
+ },
387
+ () => {
388
+ delete tasks[taskId];
389
+ const event = {
390
+ body: {
391
+ ...uploadTaskSnapshotToObject(snapshot),
392
+ state: 'success',
393
+ },
394
+ appName,
395
+ taskId,
396
+ eventName: 'state_changed',
397
+ };
398
+ emitEvent('storage_event', event);
399
+ emitEvent('storage_event', {
400
+ ...event,
401
+ eventName: 'upload_success',
402
+ });
403
+ resolve(task.snapshot);
404
+ },
405
+ );
406
+ });
407
+
408
+ return uploadTaskSnapshotToObject(snapshot);
409
+ });
410
+ },
411
+
412
+ putFile() {
413
+ return rejectWithCodeAndMessage(
414
+ 'unsupported',
415
+ 'This operation is not supported in this environment.',
416
+ );
417
+ },
418
+
419
+ /**
420
+ * Set the status of a task.
421
+ * @param {string} appName - The app name.
422
+ * @param {string} taskId - The task ID.
423
+ * @param {number} status - The status.
424
+ * @return {Promise<boolean>} Whether the status was set.
425
+ */
426
+ setTaskStatus(appName, taskId, status) {
427
+ // TODO this function implementation cannot
428
+ // be tested right now since we're unable
429
+ // to create a big enough upload to be able to
430
+ // pause/resume/cancel it in time.
431
+ return guard(async () => {
432
+ const task = tasks[taskId];
433
+
434
+ // If the task doesn't exist, return false.
435
+ if (!task) {
436
+ return false;
437
+ }
438
+
439
+ let result = false;
440
+
441
+ switch (status) {
442
+ case 0:
443
+ result = await task.pause();
444
+ break;
445
+ case 1:
446
+ result = await task.resume();
447
+ break;
448
+ case 2:
449
+ result = await task.cancel();
450
+ break;
451
+ }
452
+
453
+ emitEvent('storage_event', {
454
+ data: buildUploadSnapshotMap(task.snapshot),
455
+ appName,
456
+ taskId,
457
+ });
458
+
459
+ return result;
460
+ });
461
+ },
462
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-firebase/storage",
3
- "version": "20.1.0",
3
+ "version": "20.2.1",
4
4
  "author": "Invertase <oss@invertase.io> (http://invertase.io)",
5
5
  "description": "React Native Firebase - React Native Firebase provides native integration with Cloud Storage, providing support to upload and download files directly from your device and from your Firebase Cloud Storage bucket.",
6
6
  "main": "lib/index.js",
@@ -29,10 +29,10 @@
29
29
  "download"
30
30
  ],
31
31
  "peerDependencies": {
32
- "@react-native-firebase/app": "20.1.0"
32
+ "@react-native-firebase/app": "20.2.1"
33
33
  },
34
34
  "publishConfig": {
35
35
  "access": "public"
36
36
  },
37
- "gitHead": "99819b9cef23ac46080cbffe50cc85083305d04c"
37
+ "gitHead": "2c787c2dbefbefcc637018e1e5d74a73b39600ab"
38
38
  }