@react-native-firebase/storage 14.11.1 → 15.0.0

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,22 @@
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
+ # [15.0.0](https://github.com/invertase/react-native-firebase/compare/v14.11.1...v15.0.0) (2022-06-20)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **storage, ios:** correct storage metadata update / delete ([2dcb079](https://github.com/invertase/react-native-firebase/commit/2dcb0790c1812a33100cceea9dcb407d6a64cb87))
11
+ - **storage, ios:** surface underlying reason for unknown errors if possible ([6cd53ea](https://github.com/invertase/react-native-firebase/commit/6cd53eaca16ef52c52a28a7b209a7c8313fef08b))
12
+
13
+ - fix(storage, android)!: android now updates customMetadata as a group ([d602436](https://github.com/invertase/react-native-firebase/commit/d602436795bfb78f24bc69c42880133505738c00))
14
+
15
+ ### BREAKING CHANGES
16
+
17
+ - android works like web+iOS now: customMetadata if passed in will be
18
+ updated as a single atomic unit, all keys at once. Any key you want to keep in customMetadata
19
+ must be passed in during update; any missing keys will be removed. Set customMetadata to null
20
+ in order to remove customMetadata entirely, omit it during update to leave it unchanged.
21
+
6
22
  ## [14.11.1](https://github.com/invertase/react-native-firebase/compare/v14.11.0...v14.11.1) (2022-06-17)
7
23
 
8
24
  **Note:** Version bump only for package @react-native-firebase/storage
@@ -36,9 +36,9 @@ import com.google.firebase.storage.StorageReference;
36
36
  import com.google.firebase.storage.StorageTask;
37
37
  import io.invertase.firebase.app.ReactNativeFirebaseApp;
38
38
  import io.invertase.firebase.common.SharedUtils;
39
+ import java.util.HashMap;
39
40
  import java.util.Locale;
40
41
  import java.util.Map;
41
- import java.util.Objects;
42
42
  import javax.annotation.Nullable;
43
43
 
44
44
  class ReactNativeFirebaseStorageCommon {
@@ -93,15 +93,40 @@ class ReactNativeFirebaseStorageCommon {
93
93
  }
94
94
 
95
95
  /** Converts a RN ReadableMap into a StorageMetadata instance */
96
- static StorageMetadata buildMetadataFromMap(ReadableMap metadataMap, @Nullable Uri file) {
96
+ static StorageMetadata buildMetadataFromMap(
97
+ ReadableMap metadataMap, @Nullable Uri file, @Nullable StorageMetadata existingMetadata) {
97
98
  StorageMetadata.Builder metadataBuilder = new StorageMetadata.Builder();
98
99
 
99
100
  if (metadataMap != null) {
100
- if (metadataMap.hasKey(KEY_CUSTOM_META)) {
101
- ReadableMap customerMetaMap = Objects.requireNonNull(metadataMap.getMap(KEY_CUSTOM_META));
102
- Map<String, Object> customMeta = customerMetaMap.toHashMap();
103
- for (Map.Entry<String, Object> entry : customMeta.entrySet()) {
101
+ if (metadataMap.hasKey(KEY_CUSTOM_META) || (existingMetadata != null)) {
102
+
103
+ Map<String, Object> customMetadata = new HashMap<>();
104
+ ReadableMap freshCustomMetadata = metadataMap.getMap(KEY_CUSTOM_META);
105
+ Map<String, Object> existingCustomMetadata = new HashMap<>();
106
+
107
+ // Our baseline will be any existing custom metadata if it exists
108
+ if (existingMetadata != null) {
109
+ for (String existingKey : existingMetadata.getCustomMetadataKeys()) {
110
+ customMetadata.put(existingKey, existingMetadata.getCustomMetadata(existingKey));
111
+ existingCustomMetadata.put(
112
+ existingKey, existingMetadata.getCustomMetadata(existingKey));
113
+ }
114
+ }
115
+
116
+ // Clobber with any fresh custom metadata if it exists
117
+ if (freshCustomMetadata != null) {
118
+ customMetadata.putAll(freshCustomMetadata.toHashMap());
119
+ }
120
+
121
+ for (Map.Entry<String, Object> entry : customMetadata.entrySet()) {
104
122
  metadataBuilder.setCustomMetadata(entry.getKey(), (String) entry.getValue());
123
+
124
+ // API contract updates custom metadata as a group but android SDK has key granularity
125
+ // So if freshCustomMetadata exists, for any key that in our merged map but not in
126
+ // freshCustomMetadata, set to null to clear
127
+ if (freshCustomMetadata == null || !freshCustomMetadata.hasKey(entry.getKey())) {
128
+ metadataBuilder.setCustomMetadata(entry.getKey(), null);
129
+ }
105
130
  }
106
131
  }
107
132
 
@@ -167,20 +192,24 @@ class ReactNativeFirebaseStorageCommon {
167
192
  if (storageMetadata.getCacheControl() != null
168
193
  && storageMetadata.getCacheControl().length() > 0) {
169
194
  metadata.putString(KEY_CACHE_CONTROL, storageMetadata.getCacheControl());
170
- } else {
171
- metadata.putNull(KEY_CACHE_CONTROL);
172
195
  }
173
196
 
174
197
  if (storageMetadata.getContentLanguage() != null
175
198
  && storageMetadata.getContentLanguage().length() > 0) {
176
199
  metadata.putString(KEY_CONTENT_LANG, storageMetadata.getContentLanguage());
177
- } else {
178
- metadata.putNull(KEY_CONTENT_LANG);
179
200
  }
180
201
 
181
- metadata.putString(KEY_CONTENT_DISPOSITION, storageMetadata.getContentDisposition());
182
- metadata.putString(KEY_CONTENT_ENCODING, storageMetadata.getContentEncoding());
183
- metadata.putString(KEY_CONTENT_TYPE, storageMetadata.getContentType());
202
+ if (storageMetadata.getContentDisposition() != null
203
+ && storageMetadata.getContentDisposition().length() > 0) {
204
+ metadata.putString(KEY_CONTENT_DISPOSITION, storageMetadata.getContentDisposition());
205
+ }
206
+ if (storageMetadata.getContentEncoding() != null
207
+ && storageMetadata.getContentEncoding().length() > 0) {
208
+ metadata.putString(KEY_CONTENT_ENCODING, storageMetadata.getContentEncoding());
209
+ }
210
+ if (storageMetadata.getContentType() != null && storageMetadata.getContentType().length() > 0) {
211
+ metadata.putString(KEY_CONTENT_TYPE, storageMetadata.getContentType());
212
+ }
184
213
 
185
214
  if (storageMetadata.getCustomMetadataKeys().size() > 0) {
186
215
  WritableMap customMetadata = Arguments.createMap();
@@ -188,8 +217,6 @@ class ReactNativeFirebaseStorageCommon {
188
217
  customMetadata.putString(key, storageMetadata.getCustomMetadata(key));
189
218
  }
190
219
  metadata.putMap(KEY_CUSTOM_META, customMetadata);
191
- } else {
192
- metadata.putNull(KEY_CUSTOM_META);
193
220
  }
194
221
 
195
222
  return metadata;
@@ -169,6 +169,24 @@ public class ReactNativeFirebaseStorageModule extends ReactNativeFirebaseModule
169
169
  }
170
170
  }
171
171
 
172
+ // Useful for development / debugging
173
+ private void dumpMetadata(StorageMetadata metadata) {
174
+ System.err.println("STORAGE dumping metadata contents");
175
+ System.err.println("STORAGE - cacheControl " + metadata.getCacheControl());
176
+ System.err.println("STORAGE - contentDisposition " + metadata.getContentDisposition());
177
+ System.err.println("STORAGE - contentEncoding " + metadata.getContentEncoding());
178
+ System.err.println("STORAGE - contentLanguage " + metadata.getContentLanguage());
179
+ System.err.println("STORAGE - contentType " + metadata.getContentType());
180
+ for (String customKey : metadata.getCustomMetadataKeys()) {
181
+ System.err.println(
182
+ "STORAGE - customMetadata: '"
183
+ + customKey
184
+ + "' / '"
185
+ + metadata.getCustomMetadata(customKey)
186
+ + "'");
187
+ }
188
+ }
189
+
172
190
  /**
173
191
  * @link https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata
174
192
  */
@@ -177,17 +195,35 @@ public class ReactNativeFirebaseStorageModule extends ReactNativeFirebaseModule
177
195
  String appName, String url, ReadableMap metadataMap, final Promise promise) {
178
196
  try {
179
197
  StorageReference reference = getReferenceFromUrl(url, appName);
180
- StorageMetadata metadata = buildMetadataFromMap(metadataMap, null);
181
198
 
182
199
  reference
183
- .updateMetadata(metadata)
200
+ .getMetadata()
184
201
  .addOnCompleteListener(
185
202
  getExecutor(),
186
- task -> {
187
- if (task.isSuccessful()) {
188
- promise.resolve(getMetadataAsMap(task.getResult()));
203
+ getTask -> {
204
+ if (getTask.isSuccessful()) {
205
+
206
+ // dumpMetadata(getTask.getResult());
207
+ StorageMetadata metadata =
208
+ buildMetadataFromMap(metadataMap, null, getTask.getResult());
209
+ // dumpMetadata(metadata);
210
+
211
+ reference
212
+ .updateMetadata(metadata)
213
+ .addOnCompleteListener(
214
+ getExecutor(),
215
+ updateTask -> {
216
+ if (updateTask.isSuccessful()) {
217
+ // dumpMetadata(updateTask.getResult());
218
+ promise.resolve(getMetadataAsMap(updateTask.getResult()));
219
+ } else {
220
+ updateTask.getException().printStackTrace();
221
+ promiseRejectStorageException(promise, updateTask.getException());
222
+ }
223
+ });
224
+
189
225
  } else {
190
- promiseRejectStorageException(promise, task.getException());
226
+ promiseRejectStorageException(promise, getTask.getException());
191
227
  }
192
228
  });
193
229
  } catch (Exception e) {
@@ -186,7 +186,7 @@ class ReactNativeFirebaseStorageUploadTask extends ReactNativeFirebaseStorageTas
186
186
 
187
187
  /** Put String or Data from JavaScript */
188
188
  void begin(ExecutorService executor, String string, String format, ReadableMap metadataMap) {
189
- StorageMetadata metadata = buildMetadataFromMap(metadataMap, null);
189
+ StorageMetadata metadata = buildMetadataFromMap(metadataMap, null, null);
190
190
  uploadTask = storageReference.putBytes(uploadStringToByteArray(string, format), metadata);
191
191
  setStorageTask(uploadTask);
192
192
  addEventListeners(executor);
@@ -195,7 +195,7 @@ class ReactNativeFirebaseStorageUploadTask extends ReactNativeFirebaseStorageTas
195
195
  /** Put File from JavaScript */
196
196
  void begin(ExecutorService executor, String localFilePath, ReadableMap metadataMap) {
197
197
  Uri fileUri = SharedUtils.getUri(localFilePath);
198
- StorageMetadata metadata = buildMetadataFromMap(metadataMap, fileUri);
198
+ StorageMetadata metadata = buildMetadataFromMap(metadataMap, fileUri, null);
199
199
  uploadTask = storageReference.putFile(fileUri, metadata);
200
200
  setStorageTask(uploadTask);
201
201
  addEventListeners(executor);
@@ -56,7 +56,8 @@
56
56
 
57
57
  + (NSString *)getTaskStatus:(FIRStorageTaskStatus)status;
58
58
 
59
- + (FIRStorageMetadata *)buildMetadataFromMap:(NSDictionary *)metadata;
59
+ + (FIRStorageMetadata *)buildMetadataFromMap:(NSDictionary *)metadata
60
+ existingMetadata:(FIRStorageMetadata *)existingMetadata;
60
61
 
61
62
  + (NSArray *)getErrorCodeMessage:(NSError *)error;
62
63
 
@@ -392,9 +392,64 @@
392
392
  }
393
393
  }
394
394
 
395
- + (FIRStorageMetadata *)buildMetadataFromMap:(NSDictionary *)metadata {
396
- FIRStorageMetadata *storageMetadata = [[FIRStorageMetadata alloc] initWithDictionary:metadata];
397
- storageMetadata.customMetadata = [metadata[@"customMetadata"] mutableCopy];
395
+ + (FIRStorageMetadata *)buildMetadataFromMap:(NSDictionary *)metadata
396
+ existingMetadata:(nullable FIRStorageMetadata *)existingMetadata {
397
+ // If an existing metadata was passed in, modify it with our map, otherwise init a fresh copy
398
+ FIRStorageMetadata *storageMetadata = existingMetadata;
399
+ if (storageMetadata == nil) {
400
+ storageMetadata = [[FIRStorageMetadata alloc] init];
401
+ }
402
+
403
+ if (metadata[@"cacheControl"] == [NSNull null]) {
404
+ storageMetadata.cacheControl = nil;
405
+ } else {
406
+ storageMetadata.cacheControl = metadata[@"cacheControl"];
407
+ }
408
+
409
+ if (metadata[@"contentLanguage"] == [NSNull null]) {
410
+ storageMetadata.contentLanguage = nil;
411
+ } else {
412
+ storageMetadata.contentLanguage = metadata[@"contentLanguage"];
413
+ }
414
+
415
+ if (metadata[@"contentEncoding"] == [NSNull null]) {
416
+ storageMetadata.contentEncoding = nil;
417
+ } else {
418
+ storageMetadata.contentEncoding = metadata[@"contentEncoding"];
419
+ }
420
+
421
+ if (metadata[@"contentDisposition"] == [NSNull null]) {
422
+ storageMetadata.contentDisposition = nil;
423
+ } else {
424
+ storageMetadata.contentDisposition = metadata[@"contentDisposition"];
425
+ }
426
+
427
+ if (metadata[@"contentType"] == [NSNull null]) {
428
+ storageMetadata.contentType = nil;
429
+ } else {
430
+ storageMetadata.contentType = metadata[@"contentType"];
431
+ }
432
+
433
+ if (metadata[@"customMetadata"] == [NSNull null]) {
434
+ storageMetadata.customMetadata = @{};
435
+ } else {
436
+ NSMutableDictionary *customMetadata = [metadata[@"customMetadata"] mutableCopy];
437
+ for (NSString *key in customMetadata.allKeys) {
438
+ if (customMetadata[key] == [NSNull null] || customMetadata[key] == nil) {
439
+ [customMetadata removeObjectForKey:key];
440
+ }
441
+ }
442
+ storageMetadata.customMetadata = customMetadata;
443
+ }
444
+
445
+ // md5hash may be settable but is not update-able, so just test for existence and carry it in
446
+ // FIXME this will need a fix related to
447
+ // https://github.com/firebase/firebase-ios-sdk/issues/9849#issuecomment-1159292592 if
448
+ // (metadata[@"md5hash"]) {
449
+ // NSLog(@"STORAGE md5hash was set");
450
+ // storageMetadata.md5Hash = metadata[@"md5hash"];
451
+ // }
452
+
398
453
  return storageMetadata;
399
454
  }
400
455
 
@@ -417,7 +472,13 @@
417
472
  code = @"invalid-device-file-path";
418
473
  message = @"The specified device file path is invalid or is restricted.";
419
474
  } else {
420
- message = @"An unknown error has occurred.";
475
+ if (userInfo[@"ResponseBody"]) {
476
+ message =
477
+ [NSString stringWithFormat:@"An unknown error has occurred. (underlying reason '%@')",
478
+ userInfo[@"ResponseBody"]];
479
+ } else {
480
+ message = @"An unknown error has occurred.";
481
+ }
421
482
  }
422
483
  break;
423
484
  case FIRStorageErrorCodeObjectNotFound:
@@ -143,17 +143,26 @@ RCT_EXPORT_METHOD(updateMetadata
143
143
  : (RCTPromiseResolveBlock)resolve
144
144
  : (RCTPromiseRejectBlock)reject) {
145
145
  FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp];
146
- FIRStorageMetadata *storageMetadata = [RNFBStorageCommon buildMetadataFromMap:metadata];
147
146
 
148
- [storageReference
149
- updateMetadata:storageMetadata
150
- completion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) {
151
- if (error != nil) {
152
- [self promiseRejectStorageException:reject error:error];
153
- } else {
154
- resolve([RNFBStorageCommon metadataToDict:metadata]);
155
- }
156
- }];
147
+ [storageReference metadataWithCompletion:^(FIRStorageMetadata *_Nullable fetchedMetadata,
148
+ NSError *_Nullable error) {
149
+ if (error != nil) {
150
+ [self promiseRejectStorageException:reject error:error];
151
+ } else {
152
+ FIRStorageMetadata *storageMetadata =
153
+ [RNFBStorageCommon buildMetadataFromMap:metadata existingMetadata:fetchedMetadata];
154
+
155
+ [storageReference updateMetadata:storageMetadata
156
+ completion:^(FIRStorageMetadata *_Nullable updatedMetadata,
157
+ NSError *_Nullable error) {
158
+ if (error != nil) {
159
+ [self promiseRejectStorageException:reject error:error];
160
+ } else {
161
+ resolve([RNFBStorageCommon metadataToDict:updatedMetadata]);
162
+ }
163
+ }];
164
+ }
165
+ }];
157
166
  }
158
167
 
159
168
  /**
@@ -391,7 +400,8 @@ RCT_EXPORT_METHOD(putFile
391
400
  : (nonnull NSNumber *)taskId
392
401
  : (RCTPromiseResolveBlock)resolve
393
402
  : (RCTPromiseRejectBlock)reject) {
394
- FIRStorageMetadata *storageMetadata = [RNFBStorageCommon buildMetadataFromMap:metadata];
403
+ FIRStorageMetadata *storageMetadata = [RNFBStorageCommon buildMetadataFromMap:metadata
404
+ existingMetadata:nil];
395
405
  FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp];
396
406
 
397
407
  [RNFBStorageCommon
@@ -462,7 +472,8 @@ RCT_EXPORT_METHOD(putString
462
472
  : (nonnull NSNumber *)taskId
463
473
  : (RCTPromiseResolveBlock)resolve
464
474
  : (RCTPromiseRejectBlock)reject) {
465
- FIRStorageMetadata *storageMetadata = [RNFBStorageCommon buildMetadataFromMap:metadata];
475
+ FIRStorageMetadata *storageMetadata = [RNFBStorageCommon buildMetadataFromMap:metadata
476
+ existingMetadata:nil];
466
477
  FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp];
467
478
 
468
479
  __block FIRStorageUploadTask *uploadTask;
package/lib/index.d.ts CHANGED
@@ -323,7 +323,7 @@ export namespace FirebaseStorageTypes {
323
323
  /**
324
324
  * Additional user-defined custom metadata for this storage object.
325
325
  *
326
- * String values only are supported for custom metadata property values.
326
+ * All values must be strings. Set to null to delete all. Any keys ommitted during update will be removed.
327
327
  *
328
328
  * #### Example
329
329
  *
package/lib/utils.js CHANGED
@@ -88,9 +88,9 @@ export function validateMetadata(metadata, update = true) {
88
88
  `firebase.storage.SettableMetadata invalid property '${key}' should be a string or null value.`,
89
89
  );
90
90
  }
91
- } else if (!isObject(value)) {
91
+ } else if (!isObject(value) && !isNull(value)) {
92
92
  throw new Error(
93
- 'firebase.storage.SettableMetadata.customMetadata must be an object of keys and string values.',
93
+ 'firebase.storage.SettableMetadata.customMetadata must be an object of keys and string values or null value.',
94
94
  );
95
95
  }
96
96
  }
package/lib/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- module.exports = '14.11.1';
2
+ module.exports = '15.0.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-firebase/storage",
3
- "version": "14.11.1",
3
+ "version": "15.0.0",
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": "14.11.1"
32
+ "@react-native-firebase/app": "15.0.0"
33
33
  },
34
34
  "publishConfig": {
35
35
  "access": "public"
36
36
  },
37
- "gitHead": "b1a430bbf36a41076b33550fec113fa0e44583b4"
37
+ "gitHead": "7bb975c8c099083911a81e28050a2a5b50af2ee6"
38
38
  }