diff --git a/ios/RNFirebase/storage/RNFirebaseStorage.m b/ios/RNFirebase/storage/RNFirebaseStorage.m index 95852902..e281691f 100644 --- a/ios/RNFirebase/storage/RNFirebaseStorage.m +++ b/ios/RNFirebase/storage/RNFirebaseStorage.m @@ -15,6 +15,372 @@ RCT_EXPORT_MODULE(RNFirebaseStorage); return dispatch_queue_create("com.invertase.firebase.storage", DISPATCH_QUEUE_SERIAL); } +/** + delete + + @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#delete + @param NSString path + */ +RCT_EXPORT_METHOD(delete:(NSString *) appName + path:(NSString *) path + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRStorageReference *fileRef = [self getReference:path appName:appName]; + + [fileRef deleteWithCompletion:^(NSError *_Nullable error) { + if (error != nil) { + [self promiseRejectStorageException:reject error:error]; + } else { + resolve([NSNull null]); + } + }]; +} + +/** + getDownloadURL + + @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getDownloadURL + @param NSString path + */ +RCT_EXPORT_METHOD(getDownloadURL:(NSString *) appName + path:(NSString *) path + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRStorageReference *fileRef = [self getReference:path appName:appName]; + + [fileRef downloadURLWithCompletion:^(NSURL *_Nullable URL, NSError *_Nullable error) { + if (error != nil) { + [self promiseRejectStorageException:reject error:error]; + } else { + resolve([URL absoluteString]); + } + }]; +} + +/** + getMetadata + + @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getMetadata + @param NSString path + */ +RCT_EXPORT_METHOD(getMetadata:(NSString *) appName + path:(NSString *) path + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRStorageReference *fileRef = [self getReference:path appName:appName]; + + [fileRef metadataWithCompletion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) { + if (error != nil) { + [self promiseRejectStorageException:reject error:error]; + } else { + resolve([metadata dictionaryRepresentation]); + } + }]; +} + +/** + updateMetadata + + @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata + @param NSString path + @param NSDictionary metadata + */ +RCT_EXPORT_METHOD(updateMetadata:(NSString *) appName + path:(NSString *) path + metadata:(NSDictionary *) metadata + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRStorageReference *fileRef = [self getReference:path appName:appName]; + FIRStorageMetadata *firmetadata = [self buildMetadataFromMap:metadata]; + + [fileRef updateMetadata:firmetadata completion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) { + if (error != nil) { + [self promiseRejectStorageException:reject error:error]; + } else { + resolve([metadata dictionaryRepresentation]); + } + }]; +} + +/** + downloadFile + + @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#downloadFile + @param NSString path + @param NSString localPath + */ +RCT_EXPORT_METHOD(downloadFile:(NSString *) appName + path:(NSString *) path + localPath:(NSString *) localPath + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRStorageReference *fileRef = [self getReference:path appName:appName]; + NSURL *localFile = [NSURL fileURLWithPath:localPath]; + FIRStorageDownloadTask *downloadTask = [fileRef writeToFile:localFile]; + + // listen for state changes, errors, and completion of the download. + [downloadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) { + // download resumed, also fires when the upload starts + NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot]; + [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; + }]; + + [downloadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) { + // download paused + NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot]; + [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; + }]; + + [downloadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) { + // download reported progress + NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot]; + [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; + }]; + + [downloadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) { + // download completed successfully + NSDictionary *resp = [self getDownloadTaskAsDictionary:snapshot]; + [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_DOWNLOAD_SUCCESS props:resp]; + resolve(resp); + }]; + + [downloadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) { + // download task failed + // TODO sendJSError event + if (snapshot.error != nil) { + [self promiseRejectStorageException:reject error:snapshot.error]; + } + }]; +} + +/** + setMaxDownloadRetryTime + + @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxDownloadRetryTime + @param NSNumber milliseconds + */ +RCT_EXPORT_METHOD(setMaxDownloadRetryTime:(NSString *) appName + milliseconds:(NSNumber *) milliseconds) { + [[FIRStorage storageForApp:[FIRApp appNamed:appName]] setMaxDownloadRetryTime:[milliseconds doubleValue]]; +} + +/** + setMaxOperationRetryTime + + @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxOperationRetryTime + @param NSNumber milliseconds + */ +RCT_EXPORT_METHOD(setMaxOperationRetryTime:(NSString *) appName + milliseconds:(NSNumber *) milliseconds) { + [[FIRStorage storageForApp:[FIRApp appNamed:appName]] setMaxOperationRetryTime:[milliseconds doubleValue]]; +} + +/** + setMaxUploadRetryTime + + @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxUploadRetryTime + */ +RCT_EXPORT_METHOD(setMaxUploadRetryTime:(NSString *) appName + milliseconds:(NSNumber *) milliseconds) { + [[FIRStorage storageForApp:[FIRApp appNamed:appName]] setMaxUploadRetryTime:[milliseconds doubleValue]]; +} + +/** + putFile + + @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putFile + @param NSString path + @param NSString localPath + @param NSDictionary metadata + */ +RCT_EXPORT_METHOD(putFile:(NSString *) appName + path:(NSString *) path + localPath:(NSString *) localPath + metadata:(NSDictionary *) metadata + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + if ([localPath hasPrefix:@"assets-library://"] || [localPath hasPrefix:@"ph://"]) { + PHFetchResult *assets; + + if ([localPath hasPrefix:@"assets-library://"]) { + NSURL *localFile = [[NSURL alloc] initWithString:localPath]; + assets = [PHAsset fetchAssetsWithALAssetURLs:@[localFile] options:nil]; + } else { + NSString *assetId = [localPath substringFromIndex:@"ph://".length]; + assets = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetId] options:nil]; + } + + PHAsset *asset = [assets firstObject]; + + // this is based on http://stackoverflow.com/questions/35241449 + if (asset.mediaType == PHAssetMediaTypeImage) { + // images + PHImageRequestOptions *options = [PHImageRequestOptions new]; + options.networkAccessAllowed = true; + [[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { + if (info[PHImageErrorKey] == nil) { + [self uploadData:appName data:imageData metadata:metadata path:path resolver:resolve rejecter:reject]; + } else { + reject(@"storage/request-image-data-failed", @"Could not obtain image data for the specified file.", nil); + } + }]; + } else if (asset.mediaType == PHAssetMediaTypeVideo) { + // video + PHVideoRequestOptions *options = [PHVideoRequestOptions new]; + options.networkAccessAllowed = true; + [[PHImageManager defaultManager] requestExportSessionForVideo:asset options:options exportPreset:AVAssetExportPresetHighestQuality resultHandler:^(AVAssetExportSession *_Nullable exportSession, NSDictionary *_Nullable info) { + if (info[PHImageErrorKey] == nil) { + NSURL *tempUrl = [self temporaryFileUrl]; + exportSession.outputURL = tempUrl; + + NSArray *resources = [PHAssetResource assetResourcesForAsset:asset]; + for (PHAssetResource *resource in resources) { + exportSession.outputFileType = resource.uniformTypeIdentifier; + if (exportSession.outputFileType != nil) break; + } + + [exportSession exportAsynchronouslyWithCompletionHandler:^{ + if (exportSession.status == AVAssetExportSessionStatusCompleted) { + [self uploadFile:appName url:tempUrl metadata:metadata path:path resolver:resolve rejecter:reject]; + // we're not cleaning up the temporary file at the moment, just relying on the OS to do that in it's own time - todo? + } else { + reject(@"storage/temporary-file-failure", @"Unable to create temporary file for upload.", nil); + } + }]; + } else { + reject(@"storage/export-session-failure", @"Unable to create export session for asset.", nil); + } + }]; + } + } else { + NSData *data = [[NSFileManager defaultManager] contentsAtPath:localPath]; + [self uploadData:appName data:data metadata:metadata path:path resolver:resolve rejecter:reject]; + } + +} + +- (NSURL *)temporaryFileUrl { + NSString *filename = [NSString stringWithFormat:@"%@.tmp", [[NSProcessInfo processInfo] globallyUniqueString]]; + return [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:filename]; +} + +- (void)uploadFile:(NSString *)appName url:(NSURL *)url metadata:(NSDictionary *)metadata path:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject { + FIRStorageReference *fileRef = [self getReference:path appName:appName]; + FIRStorageMetadata *firmetadata = [self buildMetadataFromMap:metadata]; + FIRStorageUploadTask *uploadTask = [fileRef putFile:url metadata:firmetadata]; + [self addUploadObservers:appName uploadTask:uploadTask path:path resolver:resolve rejecter:reject]; +} + +- (void)uploadData:(NSString *)appName data:(NSData *)data metadata:(NSDictionary *)metadata path:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject { + FIRStorageReference *fileRef = [self getReference:path appName:appName]; + FIRStorageMetadata *firmetadata = [self buildMetadataFromMap:metadata]; + FIRStorageUploadTask *uploadTask = [fileRef putData:data metadata:firmetadata]; + [self addUploadObservers:appName uploadTask:uploadTask path:path resolver:resolve rejecter:reject]; +} + +- (void)addUploadObservers:(NSString *)appName uploadTask:(FIRStorageUploadTask *)uploadTask path:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject { + // listen for state changes, errors, and completion of the upload. + [uploadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) { + // upload resumed, also fires when the upload starts + NSDictionary *event = [self getUploadTaskAsDictionary:snapshot]; + [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; + }]; + + [uploadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) { + // upload paused + NSDictionary *event = [self getUploadTaskAsDictionary:snapshot]; + [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; + }]; + [uploadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) { + // upload reported progress + NSDictionary *event = [self getUploadTaskAsDictionary:snapshot]; + [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; + }]; + + [uploadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) { + // upload completed successfully + NSDictionary *resp = [self getUploadTaskAsDictionary:snapshot]; + [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:resp]; + [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_UPLOAD_SUCCESS props:resp]; + resolve(resp); + }]; + + [uploadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) { + if (snapshot.error != nil) { + [self promiseRejectStorageException:reject error:snapshot.error]; + } + }]; +} + +- (FIRStorageReference *)getReference:(NSString *)path + appName:(NSString *)appName { + if ([path hasPrefix:@"url::"]) { + NSString *url = [path substringFromIndex:5]; + return [[FIRStorage storageForApp:[FIRApp appNamed:appName]] referenceForURL:url]; + } else { + return [[FIRStorage storageForApp:[FIRApp appNamed:appName]] referenceWithPath:path]; + } +} + +- (NSDictionary *)getDownloadTaskAsDictionary:(FIRStorageTaskSnapshot *)task { + return @{@"bytesTransferred": @(task.progress.completedUnitCount), @"ref": task.reference.fullPath, @"state": [self getTaskStatus:task.status], @"totalBytes": @(task.progress.totalUnitCount)}; +} + +- (NSDictionary *)getUploadTaskAsDictionary:(FIRStorageTaskSnapshot *)task { + NSString *downloadUrl = [task.metadata.downloadURL absoluteString]; + FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:[task.metadata dictionaryRepresentation]]; + return @{@"bytesTransferred": @(task.progress.completedUnitCount), @"downloadUrl": downloadUrl != nil ? downloadUrl : [NSNull null], @"metadata": metadata != nil ? metadata : [NSNull null], @"ref": task.reference.fullPath, @"state": [self getTaskStatus:task.status], @"totalBytes": @(task.progress.totalUnitCount)}; +} + +- (FIRStorageMetadata *)buildMetadataFromMap:(NSDictionary *)metadata { + NSMutableDictionary *metaCopy = [metadata mutableCopy]; + [metaCopy removeObjectForKey:@"customMetadata"]; + FIRStorageMetadata *storageMetadata = [[FIRStorageMetadata alloc] initWithDictionary:metaCopy]; + storageMetadata.customMetadata = [metadata[@"customMetadata"] mutableCopy]; + return storageMetadata; +} + +- (NSString *)getTaskStatus:(FIRStorageTaskStatus)status { + if (status == FIRStorageTaskStatusResume || status == FIRStorageTaskStatusProgress) { + return @"running"; + } else if (status == FIRStorageTaskStatusPause) { + return @"paused"; + } else if (status == FIRStorageTaskStatusSuccess) { + return @"success"; + } else if (status == FIRStorageTaskStatusFailure) { + return @"error"; + } else { + return @"unknown"; + } +} + +- (NSString *)getPathForDirectory:(int)directory { + NSArray *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES); + return [paths firstObject]; +} + +- (NSDictionary *)constantsToExport { + return @{@"MAIN_BUNDLE_PATH": [[NSBundle mainBundle] bundlePath], @"CACHES_DIRECTORY_PATH": [self getPathForDirectory:NSCachesDirectory], @"DOCUMENT_DIRECTORY_PATH": [self getPathForDirectory:NSDocumentDirectory], @"EXTERNAL_DIRECTORY_PATH": [NSNull null], @"EXTERNAL_STORAGE_DIRECTORY_PATH": [NSNull null], @"TEMP_DIRECTORY_PATH": NSTemporaryDirectory(), @"LIBRARY_DIRECTORY_PATH": [self getPathForDirectory:NSLibraryDirectory], @"FILETYPE_REGULAR": NSFileTypeRegular, @"FILETYPE_DIRECTORY": NSFileTypeDirectory}; +} + +- (NSArray *)supportedEvents { + return @[STORAGE_EVENT, STORAGE_ERROR]; +} + +- (void)sendJSError:(NSString *)appName error:(NSError *)error path:(NSString *)path { + NSDictionary *evt = @{@"path": path, @"message": [error debugDescription]}; + [self sendJSEvent:appName type:STORAGE_ERROR path:path title:STORAGE_ERROR props:evt]; +} + +- (void)sendJSEvent:(NSString *)appName type:(NSString *)type path:(NSString *)path title:(NSString *)title props:(NSDictionary *)props { + @try { + [self sendEventWithName:type body:@{@"eventName": title, @"appName": appName, @"path": path, @"body": props}]; + } @catch (NSException *err) { + NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]); + NSLog(@"Tried to send: %@ with %@", title, props); + } +} + /** Reject a promise with a storage exception @@ -92,392 +458,6 @@ RCT_EXPORT_MODULE(RNFirebaseStorage); } -/** - delete - - @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#delete - @param NSString path - */ -RCT_EXPORT_METHOD(delete: - (NSString *) path - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { - FIRStorageReference *fileRef = [self getReference:path]; - - [fileRef deleteWithCompletion:^(NSError *_Nullable error) { - if (error != nil) { - [self promiseRejectStorageException:reject error:error]; - } else { - resolve([NSNull null]); - } - }]; -} - -/** - getDownloadURL - - @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getDownloadURL - @param NSString path - */ -RCT_EXPORT_METHOD(getDownloadURL: - (NSString *) path - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { - FIRStorageReference *fileRef = [self getReference:path]; - - [fileRef downloadURLWithCompletion:^(NSURL *_Nullable URL, NSError *_Nullable error) { - if (error != nil) { - [self promiseRejectStorageException:reject error:error]; - } else { - resolve([URL absoluteString]); - } - }]; -} - -/** - getMetadata - - @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getMetadata - @param NSString path - */ -RCT_EXPORT_METHOD(getMetadata: - (NSString *) path - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { - FIRStorageReference *fileRef = [self getReference:path]; - - [fileRef metadataWithCompletion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) { - if (error != nil) { - [self promiseRejectStorageException:reject error:error]; - } else { - resolve([metadata dictionaryRepresentation]); - } - }]; -} - -/** - updateMetadata - - @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata - @param NSString path - @param NSDictionary metadata - */ -RCT_EXPORT_METHOD(updateMetadata: - (NSString *) path - metadata: - (NSDictionary *) metadata - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { - FIRStorageReference *fileRef = [self getReference:path]; - FIRStorageMetadata *firmetadata = [self buildMetadataFromMap:metadata]; - - [fileRef updateMetadata:firmetadata completion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) { - if (error != nil) { - [self promiseRejectStorageException:reject error:error]; - } else { - resolve([metadata dictionaryRepresentation]); - } - }]; -} - -/** - downloadFile - - @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#downloadFile - @param NSString path - @param NSString localPath - */ -RCT_EXPORT_METHOD(downloadFile: - (NSString *) appName - path: - (NSString *) path - localPath: - (NSString *) localPath - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { - FIRStorageReference *fileRef = [self getReference:path]; - NSURL *localFile = [NSURL fileURLWithPath:localPath]; - FIRStorageDownloadTask *downloadTask = [fileRef writeToFile:localFile]; - - // listen for state changes, errors, and completion of the download. - [downloadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) { - // download resumed, also fires when the upload starts - NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot]; - [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; - }]; - - [downloadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) { - // download paused - NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot]; - [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; - }]; - - [downloadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) { - // download reported progress - NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot]; - [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; - }]; - - [downloadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) { - // download completed successfully - NSDictionary *resp = [self getDownloadTaskAsDictionary:snapshot]; - [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_DOWNLOAD_SUCCESS props:resp]; - resolve(resp); - }]; - - [downloadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) { - // download task failed - // TODO sendJSError event - if (snapshot.error != nil) { - [self promiseRejectStorageException:reject error:snapshot.error]; - } - }]; -} - -/** - setMaxDownloadRetryTime - - @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxDownloadRetryTime - @param NSNumber milliseconds - */ -RCT_EXPORT_METHOD(setMaxDownloadRetryTime: - (NSNumber *) milliseconds) { - [[FIRStorage storage] setMaxDownloadRetryTime:[milliseconds doubleValue]]; -} - -/** - setMaxOperationRetryTime - - @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxOperationRetryTime - @param NSNumber milliseconds - */ -RCT_EXPORT_METHOD(setMaxOperationRetryTime: - (NSNumber *) milliseconds) { - [[FIRStorage storage] setMaxOperationRetryTime:[milliseconds doubleValue]]; -} - -/** - setMaxUploadRetryTime - - @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxUploadRetryTime - */ -RCT_EXPORT_METHOD(setMaxUploadRetryTime: - (NSNumber *) milliseconds) { - [[FIRStorage storage] setMaxUploadRetryTime:[milliseconds doubleValue]]; -} - -/** - putFile - - @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putFile - @param NSString path - @param NSString localPath - @param NSDictionary metadata - */ -RCT_EXPORT_METHOD(putFile: - (NSString *) appName - path: - (NSString *) path - localPath: - (NSString *) localPath - metadata: - (NSDictionary *) metadata - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { - if ([localPath hasPrefix:@"assets-library://"] || [localPath hasPrefix:@"ph://"]) { - PHFetchResult *assets; - - if ([localPath hasPrefix:@"assets-library://"]) { - NSURL *localFile = [[NSURL alloc] initWithString:localPath]; - assets = [PHAsset fetchAssetsWithALAssetURLs:@[localFile] options:nil]; - } else { - NSString *assetId = [localPath substringFromIndex:@"ph://".length]; - assets = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetId] options:nil]; - } - - PHAsset *asset = [assets firstObject]; - - // this is based on http://stackoverflow.com/questions/35241449 - if (asset.mediaType == PHAssetMediaTypeImage) { - // images - PHImageRequestOptions *options = [PHImageRequestOptions new]; - options.networkAccessAllowed = true; - [[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { - if (info[PHImageErrorKey] == nil) { - [self uploadData:appName data:imageData metadata:metadata path:path resolver:resolve rejecter:reject]; - } else { - reject(@"storage/request-image-data-failed", @"Could not obtain image data for the specified file.", nil); - } - }]; - } else if (asset.mediaType == PHAssetMediaTypeVideo) { - // video - PHVideoRequestOptions *options = [PHVideoRequestOptions new]; - options.networkAccessAllowed = true; - [[PHImageManager defaultManager] requestExportSessionForVideo:asset options:options exportPreset:AVAssetExportPresetHighestQuality resultHandler:^(AVAssetExportSession *_Nullable exportSession, NSDictionary *_Nullable info) { - if (info[PHImageErrorKey] == nil) { - NSURL *tempUrl = [self temporaryFileUrl]; - exportSession.outputURL = tempUrl; - - NSArray *resources = [PHAssetResource assetResourcesForAsset:asset]; - for (PHAssetResource *resource in resources) { - exportSession.outputFileType = resource.uniformTypeIdentifier; - if (exportSession.outputFileType != nil) break; - } - - [exportSession exportAsynchronouslyWithCompletionHandler:^{ - if (exportSession.status == AVAssetExportSessionStatusCompleted) { - [self uploadFile:appName url:tempUrl metadata:metadata path:path resolver:resolve rejecter:reject]; - // we're not cleaning up the temporary file at the moment, just relying on the OS to do that in it's own time - todo? - } else { - reject(@"storage/temporary-file-failure", @"Unable to create temporary file for upload.", nil); - } - }]; - } else { - reject(@"storage/export-session-failure", @"Unable to create export session for asset.", nil); - } - }]; - } - } else { - NSData *data = [[NSFileManager defaultManager] contentsAtPath:localPath]; - [self uploadData:appName data:data metadata:metadata path:path resolver:resolve rejecter:reject]; - } - -} - -- (NSURL *)temporaryFileUrl { - NSString *filename = [NSString stringWithFormat:@"%@.tmp", [[NSProcessInfo processInfo] globallyUniqueString]]; - return [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:filename]; -} - -- (void)uploadFile:(NSString *)appName url:(NSURL *)url metadata:(NSDictionary *)metadata path:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject { - FIRStorageReference *fileRef = [self getReference:path]; - FIRStorageMetadata *firmetadata = [self buildMetadataFromMap:metadata]; - FIRStorageUploadTask *uploadTask = [fileRef putFile:url metadata:firmetadata]; - [self addUploadObservers:appName uploadTask:uploadTask path:path resolver:resolve rejecter:reject]; -} - -- (void)uploadData:(NSString *)appName data:(NSData *)data metadata:(NSDictionary *)metadata path:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject { - FIRStorageReference *fileRef = [self getReference:path]; - FIRStorageMetadata *firmetadata = [self buildMetadataFromMap:metadata]; - FIRStorageUploadTask *uploadTask = [fileRef putData:data metadata:firmetadata]; - [self addUploadObservers:appName uploadTask:uploadTask path:path resolver:resolve rejecter:reject]; -} - -- (void)addUploadObservers:(NSString *)appName uploadTask:(FIRStorageUploadTask *)uploadTask path:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject { - // listen for state changes, errors, and completion of the upload. - [uploadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) { - // upload resumed, also fires when the upload starts - NSDictionary *event = [self getUploadTaskAsDictionary:snapshot]; - [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; - }]; - - [uploadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) { - // upload paused - NSDictionary *event = [self getUploadTaskAsDictionary:snapshot]; - [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; - }]; - [uploadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) { - // upload reported progress - NSDictionary *event = [self getUploadTaskAsDictionary:snapshot]; - [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event]; - }]; - - [uploadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) { - // upload completed successfully - NSDictionary *resp = [self getUploadTaskAsDictionary:snapshot]; - [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:resp]; - [self sendJSEvent:appName type:STORAGE_EVENT path:path title:STORAGE_UPLOAD_SUCCESS props:resp]; - resolve(resp); - }]; - - [uploadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) { - if (snapshot.error != nil) { - [self promiseRejectStorageException:reject error:snapshot.error]; - } - }]; -} - -- (FIRStorageReference *)getReference:(NSString *)path { - if ([path hasPrefix:@"url::"]) { - NSString *url = [path substringFromIndex:5]; - return [[FIRStorage storage] referenceForURL:url]; - } else { - return [[FIRStorage storage] referenceWithPath:path]; - } -} - -- (NSDictionary *)getDownloadTaskAsDictionary:(FIRStorageTaskSnapshot *)task { - return @{@"bytesTransferred": @(task.progress.completedUnitCount), @"ref": task.reference.fullPath, @"state": [self getTaskStatus:task.status], @"totalBytes": @(task.progress.totalUnitCount)}; -} - -- (NSDictionary *)getUploadTaskAsDictionary:(FIRStorageTaskSnapshot *)task { - NSString *downloadUrl = [task.metadata.downloadURL absoluteString]; - FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] initWithDictionary:[task.metadata dictionaryRepresentation]]; - return @{@"bytesTransferred": @(task.progress.completedUnitCount), @"downloadUrl": downloadUrl != nil ? downloadUrl : [NSNull null], @"metadata": metadata != nil ? metadata : [NSNull null], @"ref": task.reference.fullPath, @"state": [self getTaskStatus:task.status], @"totalBytes": @(task.progress.totalUnitCount)}; -} - -- (FIRStorageMetadata *)buildMetadataFromMap:(NSDictionary *)metadata { - NSMutableDictionary *metaCopy = [metadata mutableCopy]; - [metaCopy removeObjectForKey:@"customMetadata"]; - FIRStorageMetadata *storageMetadata = [[FIRStorageMetadata alloc] initWithDictionary:metaCopy]; - storageMetadata.customMetadata = [metadata[@"customMetadata"] mutableCopy]; - return storageMetadata; -} - -- (NSString *)getTaskStatus:(FIRStorageTaskStatus)status { - if (status == FIRStorageTaskStatusResume || status == FIRStorageTaskStatusProgress) { - return @"running"; - } else if (status == FIRStorageTaskStatusPause) { - return @"paused"; - } else if (status == FIRStorageTaskStatusSuccess) { - return @"success"; - } else if (status == FIRStorageTaskStatusFailure) { - return @"error"; - } else { - return @"unknown"; - } -} - -- (NSString *)getPathForDirectory:(int)directory { - NSArray *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES); - return [paths firstObject]; -} - -- (NSDictionary *)constantsToExport { - return @{@"MAIN_BUNDLE_PATH": [[NSBundle mainBundle] bundlePath], @"CACHES_DIRECTORY_PATH": [self getPathForDirectory:NSCachesDirectory], @"DOCUMENT_DIRECTORY_PATH": [self getPathForDirectory:NSDocumentDirectory], @"EXTERNAL_DIRECTORY_PATH": [NSNull null], @"EXTERNAL_STORAGE_DIRECTORY_PATH": [NSNull null], @"TEMP_DIRECTORY_PATH": NSTemporaryDirectory(), @"LIBRARY_DIRECTORY_PATH": [self getPathForDirectory:NSLibraryDirectory], @"FILETYPE_REGULAR": NSFileTypeRegular, @"FILETYPE_DIRECTORY": NSFileTypeDirectory}; -} - -- (NSArray *)supportedEvents { - return @[STORAGE_EVENT, STORAGE_ERROR]; -} - -- (void)sendJSError:(NSString *)appName error:(NSError *)error path:(NSString *)path { - NSDictionary *evt = @{@"path": path, @"message": [error debugDescription]}; - [self sendJSEvent:appName type:STORAGE_ERROR path:path title:STORAGE_ERROR props:evt]; -} - -- (void)sendJSEvent:(NSString *)appName type:(NSString *)type path:(NSString *)path title:(NSString *)title props:(NSDictionary *)props { - @try { - [self sendEventWithName:type body:@{@"eventName": title, @"appName": appName, @"path": path, @"body": props}]; - } @catch (NSException *err) { - NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]); - NSLog(@"Tried to send: %@ with %@", title, props); - } -} - - @end #else