/** * Copyright (c) 2016-present Invertase Limited & Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this library except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #import #import #import "RNFBStorageModule.h" #import "RNFBRCTEventEmitter.h" #import "RNFBStorageCommon.h" #import "RNFBSharedUtils.h" static NSString *const RNFB_STORAGE_EVENT = @"storage_event"; static NSString *const RNFB_STORAGE_STATE_CHANGED = @"state_changed"; static NSString *const RNFB_STORAGE_UPLOAD_SUCCESS = @"upload_success"; static NSString *const RNFB_STORAGE_UPLOAD_FAILURE = @"upload_failure"; static NSString *const RNFB_STORAGE_DOWNLOAD_SUCCESS = @"download_success"; static NSString *const RNFB_STORAGE_DOWNLOAD_FAILURE = @"download_failure"; static NSMutableDictionary *PENDING_TASKS; @implementation RNFBStorageModule #pragma mark - #pragma mark Module Setup RCT_EXPORT_MODULE(); + (BOOL)requiresMainQueueSetup { return YES; } - (id)init { self = [super init]; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ PENDING_TASKS = [[NSMutableDictionary alloc] init]; }); return self; } - (void)dealloc { for (NSString *key in PENDING_TASKS) { [PENDING_TASKS removeObjectForKey:key]; } } - (void)invalidate { for (NSString *key in PENDING_TASKS) { [PENDING_TASKS removeObjectForKey:key]; } } #pragma mark - #pragma mark Firebase Storage Methods /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#delete */ RCT_EXPORT_METHOD(delete: (FIRApp *) firebaseApp : (NSString *) url : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp]; [storageReference deleteWithCompletion:^(NSError *_Nullable error) { if (error != nil) { [self promiseRejectStorageException:reject error:error]; } else { resolve([NSNull null]); } }]; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getDownloadURL */ RCT_EXPORT_METHOD(getDownloadURL: (FIRApp *) firebaseApp : (NSString *) url : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp]; [storageReference downloadURLWithCompletion:^(NSURL *_Nullable URL, NSError *_Nullable error) { if (error != nil) { [self promiseRejectStorageException:reject error:error]; } else { resolve([URL absoluteString]); } }]; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getMetadata */ RCT_EXPORT_METHOD(getMetadata: (FIRApp *) firebaseApp : (NSString *) url : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp]; [storageReference metadataWithCompletion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) { if (error != nil) { [self promiseRejectStorageException:reject error:error]; } else { resolve([RNFBStorageCommon metadataToDict:metadata]); } }]; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata */ RCT_EXPORT_METHOD(updateMetadata: (FIRApp *) firebaseApp : (NSString *) url : (NSDictionary *) metadata : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp]; FIRStorageMetadata *storageMetadata = [RNFBStorageCommon buildMetadataFromMap:metadata]; [storageReference updateMetadata:storageMetadata completion:^( FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error ) { if (error != nil) { [self promiseRejectStorageException:reject error:error]; } else { resolve([RNFBStorageCommon metadataToDict:metadata]); } }]; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#list */ RCT_EXPORT_METHOD(list: (FIRApp *) firebaseApp : (NSString *) url : (NSDictionary *) listOptions : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp]; long *maxResults = [listOptions[@"maxResults"] pointerValue]; id completionBlock = ^(FIRStorageListResult *result, NSError *error) { if (error != nil) { [self promiseRejectStorageException:reject error:error]; } else { NSDictionary * dick = [RNFBStorageCommon listResultToDict:result]; // so we can see the result resolve(dick); } }; if (listOptions[@"pageToken"]) { NSString *pageToken = listOptions[@"pageToken"]; [storageReference listWithMaxResults:(int64_t) maxResults pageToken:pageToken completion:completionBlock]; } else { [storageReference listWithMaxResults:(int64_t) maxResults completion:completionBlock]; } } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#listAll */ RCT_EXPORT_METHOD(listAll: (FIRApp *) firebaseApp : (NSString *) url : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp]; id completionBlock = ^(FIRStorageListResult *result, NSError *error) { if (error != nil) { [self promiseRejectStorageException:reject error:error]; } else { resolve([RNFBStorageCommon listResultToDict:result]); } }; [storageReference listAllWithCompletion:completionBlock]; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxDownloadRetryTime */ RCT_EXPORT_METHOD(setMaxDownloadRetryTime: (FIRApp *) firebaseApp : (nonnull NSNumber *) milliseconds : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { [[FIRStorage storageForApp:firebaseApp] setMaxDownloadRetryTime:[milliseconds doubleValue] / 1000]; resolve([NSNull null]); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxOperationRetryTime */ RCT_EXPORT_METHOD(setMaxOperationRetryTime: (FIRApp *) firebaseApp : (nonnull NSNumber *) milliseconds : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { [[FIRStorage storageForApp:firebaseApp] setMaxOperationRetryTime:[milliseconds doubleValue] / 1000]; resolve([NSNull null]); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxUploadRetryTime */ RCT_EXPORT_METHOD(setMaxUploadRetryTime: (FIRApp *) firebaseApp : (nonnull NSNumber *) milliseconds : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { [[FIRStorage storageForApp:firebaseApp] setMaxUploadRetryTime:[milliseconds doubleValue] / 1000]; resolve([NSNull null]); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#downloadFile */ RCT_EXPORT_METHOD(writeToFile: (FIRApp *) firebaseApp : (NSString *) url : (NSString *) localFilePath : (nonnull NSNumber *) taskId : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp]; NSURL *localFile = [NSURL fileURLWithPath:localFilePath]; __block FIRStorageDownloadTask *downloadTask; RCTUnsafeExecuteOnMainQueueSync(^{ downloadTask = [storageReference writeToFile:localFile]; }); PENDING_TASKS[taskId] = downloadTask; // download started or resumed [downloadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) { NSDictionary *eventBody = [RNFBStorageCommon getDownloadTaskAsDictionary:snapshot]; NSDictionary *event = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:firebaseApp.name taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:event]; }]; // download paused [downloadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) { NSDictionary *eventBody = [RNFBStorageCommon getDownloadTaskAsDictionary:snapshot]; NSDictionary *event = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:firebaseApp.name taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:event]; }]; // download reported progress [downloadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) { NSDictionary *eventBody = [RNFBStorageCommon getDownloadTaskAsDictionary:snapshot]; NSDictionary *event = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:firebaseApp.name taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:event]; }]; // download completed successfully [downloadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) { [PENDING_TASKS removeObjectForKey:taskId]; // state_changed NSDictionary *stateChangedEventBody = [RNFBStorageCommon getDownloadTaskAsDictionary:snapshot]; NSDictionary *stateChangedEvent = [RNFBStorageCommon getStorageEventDictionary:stateChangedEventBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:firebaseApp.name taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:stateChangedEvent]; // download_success NSDictionary *eventBody = [RNFBStorageCommon getDownloadTaskAsDictionary:snapshot]; NSDictionary *event = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_DOWNLOAD_SUCCESS appName:firebaseApp.name taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:event]; resolve(eventBody); }]; // download task failed [downloadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) { [PENDING_TASKS removeObjectForKey:taskId]; // state_changed NSDictionary *stateChangedEventBody = [RNFBStorageCommon getDownloadTaskAsDictionary:snapshot]; NSDictionary *stateChangedEvent = [RNFBStorageCommon getStorageEventDictionary:stateChangedEventBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:firebaseApp.name taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:stateChangedEvent]; // download_failed NSMutableDictionary *taskSnapshotDict = [RNFBStorageCommon getDownloadTaskAsDictionary:snapshot]; NSDictionary *eventBody = [RNFBStorageCommon buildErrorSnapshotDict:snapshot.error taskSnapshotDict:taskSnapshotDict]; NSDictionary *event = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_DOWNLOAD_FAILURE appName:firebaseApp.name taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:event]; [self promiseRejectStorageException:reject error:snapshot.error]; }]; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putFile */ RCT_EXPORT_METHOD(putFile: (FIRApp *) firebaseApp : (NSString *) url : (NSString *) localFilePath : (NSDictionary *) metadata : (nonnull NSNumber *) taskId : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { FIRStorageMetadata *storageMetadata = [RNFBStorageCommon buildMetadataFromMap:metadata]; FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp]; [RNFBStorageCommon NSURLForLocalFilePath:localFilePath completion:^( NSArray *errorCodeMessageArray, NSURL *temporaryFileUrl, NSString *contentType ) { if (errorCodeMessageArray != nil) { // state_changed NSMutableDictionary *taskSnapshotDict = [RNFBStorageCommon getUploadTaskAsDictionary:nil]; NSDictionary *eventBody = [RNFBStorageCommon buildErrorSnapshotDictFromCodeAndMessage:errorCodeMessageArray taskSnapshotDict:taskSnapshotDict]; NSDictionary *stateChangedEvent = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:[[[storageReference storage] app] name] taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:stateChangedEvent]; // upload_failed NSDictionary *event = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_UPLOAD_FAILURE appName:[[[storageReference storage] app] name] taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:event]; [RNFBSharedUtils rejectPromiseWithUserInfo:reject userInfo:(NSMutableDictionary *) @{ @"code": errorCodeMessageArray[0], @"message": errorCodeMessageArray[1], }]; return; } storageMetadata.contentType = contentType; if ([storageMetadata valueForKey:@"contentType"] == nil) { storageMetadata.contentType = [RNFBStorageCommon mimeTypeForPath:localFilePath]; } __block FIRStorageUploadTask *uploadTask; RCTUnsafeExecuteOnMainQueueSync(^{ uploadTask = [storageReference putFile:temporaryFileUrl metadata:storageMetadata]; }); [self addUploadTaskObservers:uploadTask appDisplayName:[[[storageReference storage] app] name] taskId:taskId resolver:resolve rejecter:reject]; }]; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putFile */ RCT_EXPORT_METHOD(putString: (FIRApp *) firebaseApp : (NSString *) url : (NSString *) string : (NSString *) format : (NSDictionary *) metadata : (nonnull NSNumber *) taskId : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { FIRStorageMetadata *storageMetadata = [RNFBStorageCommon buildMetadataFromMap:metadata]; FIRStorageReference *storageReference = [self getReferenceFromUrl:url app:firebaseApp]; __block FIRStorageUploadTask *uploadTask; RCTUnsafeExecuteOnMainQueueSync(^{ uploadTask = [storageReference putData:[RNFBStorageCommon NSDataFromUploadString:string format:format] metadata:storageMetadata]; }); [self addUploadTaskObservers:uploadTask appDisplayName:[[[storageReference storage] app] name] taskId:taskId resolver:resolve rejecter:reject]; } /** * @url N/A - RNFB Specific */ RCT_EXPORT_METHOD(setTaskStatus: (FIRApp *) firebaseApp : (nonnull NSNumber *) taskId : (nonnull NSNumber *) status : (RCTPromiseResolveBlock) resolve : (RCTPromiseRejectBlock) reject ) { id task = PENDING_TASKS[taskId]; if (task == nil) { resolve(@(NO)); return; } switch ([status integerValue]) { case 0: [task pause]; break; case 1: [task resume]; break; case 2: [task cancel]; break; default: break; } } #pragma mark - #pragma mark Firebase Storage Internals - (void)addUploadTaskObservers:(FIRStorageUploadTask *)uploadTask appDisplayName:(NSString *)appDisplayName taskId:(NSNumber *)taskId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject { PENDING_TASKS[taskId] = uploadTask; // upload started or resumed [uploadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) { NSDictionary *eventBody = [RNFBStorageCommon getUploadTaskAsDictionary:snapshot]; NSDictionary *event = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:appDisplayName taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:event]; }]; // upload paused [uploadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) { NSDictionary *eventBody = [RNFBStorageCommon getUploadTaskAsDictionary:snapshot]; NSDictionary *event = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:appDisplayName taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:event]; }]; // upload reported progress [uploadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) { NSDictionary *eventBody = [RNFBStorageCommon getUploadTaskAsDictionary:snapshot]; NSDictionary *event = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:appDisplayName taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:event]; }]; // upload completed successfully [uploadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) { [PENDING_TASKS removeObjectForKey:taskId]; NSDictionary *eventBody = [RNFBStorageCommon getUploadTaskAsDictionary:snapshot]; // state_changed NSDictionary *stateChangeEvent = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:appDisplayName taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:stateChangeEvent]; // upload_success NSDictionary *uploadSuccessEvent = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_UPLOAD_SUCCESS appName:appDisplayName taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:uploadSuccessEvent]; resolve(eventBody); }]; [uploadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) { [PENDING_TASKS removeObjectForKey:taskId]; // state_changed NSMutableDictionary *taskSnapshotDict = [RNFBStorageCommon getUploadTaskAsDictionary:snapshot]; NSDictionary *stateChangedEvtBody = [RNFBStorageCommon buildErrorSnapshotDict:snapshot.error taskSnapshotDict:taskSnapshotDict]; NSDictionary *stateChangedEvent = [RNFBStorageCommon getStorageEventDictionary:stateChangedEvtBody internalEventName:RNFB_STORAGE_STATE_CHANGED appName:appDisplayName taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:stateChangedEvent]; // upload_failed NSDictionary *eventBody = [RNFBStorageCommon buildErrorSnapshotDict:snapshot.error taskSnapshotDict:taskSnapshotDict]; NSDictionary *event = [RNFBStorageCommon getStorageEventDictionary:eventBody internalEventName:RNFB_STORAGE_UPLOAD_FAILURE appName:appDisplayName taskId:taskId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_STORAGE_EVENT body:event]; [self promiseRejectStorageException:reject error:snapshot.error]; }]; } - (FIRStorageReference *)getReferenceFromUrl:(NSString *)url app:(FIRApp *)firebaseApp { NSString *pathWithBucketName = [url substringWithRange:NSMakeRange(5, [url length] - 5)]; NSString *bucket = [url substringWithRange:NSMakeRange(0, [pathWithBucketName rangeOfString:@"/"].location + 5)]; return [[FIRStorage storageForApp:firebaseApp URL:bucket] referenceForURL:url]; } - (void)promiseRejectStorageException:(RCTPromiseRejectBlock)reject error:(NSError *)error { NSArray *codeAndMessage = [RNFBStorageCommon getErrorCodeMessage:error]; [RNFBSharedUtils rejectPromiseWithUserInfo:reject userInfo:(NSMutableDictionary *) @{ @"code": (NSString *) codeAndMessage[0], @"message": (NSString *) codeAndMessage[1], }]; } - (NSDictionary *)constantsToExport { NSMutableDictionary *constants = [@{} mutableCopy]; if ([[[FIRApp allApps] allKeys] count] > 0) { FIRStorage *storageInstance = [FIRStorage storage]; constants[@"maxDownloadRetryTime"] = @((NSInteger) [storageInstance maxDownloadRetryTime] * 1000); constants[@"maxOperationRetryTime"] = @((NSInteger) [storageInstance maxOperationRetryTime] * 1000); constants[@"maxUploadRetryTime"] = @((NSInteger) [storageInstance maxUploadRetryTime] * 1000); } return constants; } @end