2017-03-09 15:26:28 +00:00
|
|
|
|
#import "RNFirebaseStorage.h"
|
|
|
|
|
|
2017-05-25 14:14:01 +00:00
|
|
|
|
#if __has_include(<FirebaseStorage/FIRStorage.h>)
|
2017-07-12 14:49:33 +00:00
|
|
|
|
|
2017-05-25 14:14:01 +00:00
|
|
|
|
#import "RNFirebaseEvents.h"
|
2017-10-26 10:55:07 +00:00
|
|
|
|
#import "RNFirebaseUtil.h"
|
2017-08-25 17:05:50 +00:00
|
|
|
|
#import <MobileCoreServices/MobileCoreServices.h>
|
2017-03-09 15:26:28 +00:00
|
|
|
|
#import <Photos/Photos.h>
|
2017-08-17 16:25:51 +00:00
|
|
|
|
#import <Firebase.h>
|
2018-06-08 21:44:48 +00:00
|
|
|
|
#import <React/RCTUtils.h>
|
2017-03-09 15:26:28 +00:00
|
|
|
|
|
|
|
|
|
@implementation RNFirebaseStorage
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_MODULE(RNFirebaseStorage);
|
|
|
|
|
|
|
|
|
|
// Run on a different thread
|
2017-03-22 18:49:36 +00:00
|
|
|
|
- (dispatch_queue_t)methodQueue {
|
2018-05-15 09:02:18 +00:00
|
|
|
|
return dispatch_queue_create("io.invertase.react-native-firebase.storage", DISPATCH_QUEUE_SERIAL);
|
2017-03-22 18:49:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
delete
|
2017-05-31 14:22:15 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#delete
|
|
|
|
|
@param NSString path
|
|
|
|
|
*/
|
2018-01-03 20:00:38 +00:00
|
|
|
|
RCT_EXPORT_METHOD(delete:(NSString *) appDisplayName
|
2017-08-25 15:18:42 +00:00
|
|
|
|
path:(NSString *) path
|
|
|
|
|
resolver:(RCTPromiseResolveBlock) resolve
|
|
|
|
|
rejecter:(RCTPromiseRejectBlock) reject) {
|
2018-01-03 20:00:38 +00:00
|
|
|
|
FIRStorageReference *fileRef = [self getReference:path appDisplayName:appDisplayName];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-07-12 14:49:33 +00:00
|
|
|
|
[fileRef deleteWithCompletion:^(NSError *_Nullable error) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
if (error != nil) {
|
2017-07-12 14:49:33 +00:00
|
|
|
|
[self promiseRejectStorageException:reject error:error];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
} else {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
resolve([NSNull null]);
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
/**
|
|
|
|
|
getDownloadURL
|
2017-05-31 14:22:15 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getDownloadURL
|
|
|
|
|
@param NSString path
|
|
|
|
|
*/
|
2018-01-03 20:00:38 +00:00
|
|
|
|
RCT_EXPORT_METHOD(getDownloadURL:(NSString *) appDisplayName
|
2017-08-25 15:18:42 +00:00
|
|
|
|
path:(NSString *) path
|
|
|
|
|
resolver:(RCTPromiseResolveBlock) resolve
|
|
|
|
|
rejecter:(RCTPromiseRejectBlock) reject) {
|
2018-01-03 20:00:38 +00:00
|
|
|
|
FIRStorageReference *fileRef = [self getReference:path appDisplayName:appDisplayName];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-07-12 14:49:33 +00:00
|
|
|
|
[fileRef downloadURLWithCompletion:^(NSURL *_Nullable URL, NSError *_Nullable error) {
|
2017-03-09 15:26:28 +00:00
|
|
|
|
if (error != nil) {
|
2017-07-12 14:49:33 +00:00
|
|
|
|
[self promiseRejectStorageException:reject error:error];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
} else {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
resolve([URL absoluteString]);
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
/**
|
2017-03-29 16:48:21 +00:00
|
|
|
|
getMetadata
|
2017-05-31 14:22:15 +00:00
|
|
|
|
|
2017-03-29 16:48:21 +00:00
|
|
|
|
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getMetadata
|
2017-03-22 18:49:36 +00:00
|
|
|
|
@param NSString path
|
|
|
|
|
*/
|
2018-01-03 20:00:38 +00:00
|
|
|
|
RCT_EXPORT_METHOD(getMetadata:(NSString *) appDisplayName
|
2017-08-25 15:18:42 +00:00
|
|
|
|
path:(NSString *) path
|
|
|
|
|
resolver:(RCTPromiseResolveBlock) resolve
|
|
|
|
|
rejecter:(RCTPromiseRejectBlock) reject) {
|
2018-01-03 20:00:38 +00:00
|
|
|
|
FIRStorageReference *fileRef = [self getReference:path appDisplayName:appDisplayName];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-07-12 14:49:33 +00:00
|
|
|
|
[fileRef metadataWithCompletion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) {
|
2017-03-09 15:26:28 +00:00
|
|
|
|
if (error != nil) {
|
2017-07-12 14:49:33 +00:00
|
|
|
|
[self promiseRejectStorageException:reject error:error];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
} else {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
resolve([metadata dictionaryRepresentation]);
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
/**
|
|
|
|
|
updateMetadata
|
2017-05-31 14:22:15 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata
|
|
|
|
|
@param NSString path
|
|
|
|
|
@param NSDictionary metadata
|
|
|
|
|
*/
|
2018-01-03 20:00:38 +00:00
|
|
|
|
RCT_EXPORT_METHOD(updateMetadata:(NSString *) appDisplayName
|
2017-08-25 15:18:42 +00:00
|
|
|
|
path:(NSString *) path
|
|
|
|
|
metadata:(NSDictionary *) metadata
|
|
|
|
|
resolver:(RCTPromiseResolveBlock) resolve
|
|
|
|
|
rejecter:(RCTPromiseRejectBlock) reject) {
|
2018-01-03 20:00:38 +00:00
|
|
|
|
FIRStorageReference *fileRef = [self getReference:path appDisplayName:appDisplayName];
|
2017-06-29 16:12:57 +00:00
|
|
|
|
FIRStorageMetadata *firmetadata = [self buildMetadataFromMap:metadata];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-07-12 14:49:33 +00:00
|
|
|
|
[fileRef updateMetadata:firmetadata completion:^(FIRStorageMetadata *_Nullable metadata, NSError *_Nullable error) {
|
2017-03-09 15:26:28 +00:00
|
|
|
|
if (error != nil) {
|
2017-07-12 14:49:33 +00:00
|
|
|
|
[self promiseRejectStorageException:reject error:error];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
} else {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
resolve([metadata dictionaryRepresentation]);
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
/**
|
|
|
|
|
downloadFile
|
2017-05-31 14:22:15 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#downloadFile
|
|
|
|
|
@param NSString path
|
|
|
|
|
@param NSString localPath
|
|
|
|
|
*/
|
2018-01-03 20:00:38 +00:00
|
|
|
|
RCT_EXPORT_METHOD(downloadFile:(NSString *) appDisplayName
|
2017-08-25 15:18:42 +00:00
|
|
|
|
path:(NSString *) path
|
|
|
|
|
localPath:(NSString *) localPath
|
|
|
|
|
resolver:(RCTPromiseResolveBlock) resolve
|
|
|
|
|
rejecter:(RCTPromiseRejectBlock) reject) {
|
2018-01-03 20:00:38 +00:00
|
|
|
|
FIRStorageReference *fileRef = [self getReference:path appDisplayName:appDisplayName];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
NSURL *localFile = [NSURL fileURLWithPath:localPath];
|
2018-05-16 16:22:47 +00:00
|
|
|
|
|
|
|
|
|
__block FIRStorageDownloadTask *downloadTask;
|
2018-06-08 21:44:48 +00:00
|
|
|
|
RCTUnsafeExecuteOnMainQueueSync(^{
|
2018-05-16 16:22:47 +00:00
|
|
|
|
downloadTask = [fileRef writeToFile:localFile];
|
|
|
|
|
});
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// listen for state changes, errors, and completion of the download.
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[downloadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// download resumed, also fires when the upload starts
|
2017-03-09 15:26:28 +00:00
|
|
|
|
NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot];
|
2018-01-03 20:00:38 +00:00
|
|
|
|
[self sendJSEvent:appDisplayName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[downloadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// download paused
|
2017-03-09 15:26:28 +00:00
|
|
|
|
NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot];
|
2018-01-03 20:00:38 +00:00
|
|
|
|
[self sendJSEvent:appDisplayName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[downloadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// download reported progress
|
2017-03-09 15:26:28 +00:00
|
|
|
|
NSDictionary *event = [self getDownloadTaskAsDictionary:snapshot];
|
2018-01-03 20:00:38 +00:00
|
|
|
|
[self sendJSEvent:appDisplayName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[downloadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// download completed successfully
|
2017-03-09 15:26:28 +00:00
|
|
|
|
NSDictionary *resp = [self getDownloadTaskAsDictionary:snapshot];
|
2018-01-03 20:00:38 +00:00
|
|
|
|
[self sendJSEvent:appDisplayName type:STORAGE_EVENT path:path title:STORAGE_DOWNLOAD_SUCCESS props:resp];
|
2017-03-22 18:49:36 +00:00
|
|
|
|
resolve(resp);
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[downloadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// download task failed
|
2017-03-29 16:48:21 +00:00
|
|
|
|
// TODO sendJSError event
|
2017-03-09 15:26:28 +00:00
|
|
|
|
if (snapshot.error != nil) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
[self promiseRejectStorageException:reject error:snapshot.error];
|
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
}
|
2017-03-09 15:26:28 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
/**
|
|
|
|
|
setMaxDownloadRetryTime
|
2017-05-31 14:22:15 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
@url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxDownloadRetryTime
|
|
|
|
|
@param NSNumber milliseconds
|
|
|
|
|
*/
|
2018-01-03 20:00:38 +00:00
|
|
|
|
RCT_EXPORT_METHOD(setMaxDownloadRetryTime:(NSString *) appDisplayName
|
2018-01-07 21:09:40 +00:00
|
|
|
|
milliseconds:(nonnull NSNumber *) milliseconds) {
|
2018-01-03 20:00:38 +00:00
|
|
|
|
FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName];
|
|
|
|
|
[[FIRStorage storageForApp:firApp] setMaxDownloadRetryTime:[milliseconds doubleValue]];
|
2017-03-22 18:49:36 +00:00
|
|
|
|
}
|
2017-03-09 15:26:28 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
/**
|
|
|
|
|
setMaxOperationRetryTime
|
2017-05-31 14:22:15 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
@url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxOperationRetryTime
|
|
|
|
|
@param NSNumber milliseconds
|
|
|
|
|
*/
|
2018-01-03 20:00:38 +00:00
|
|
|
|
RCT_EXPORT_METHOD(setMaxOperationRetryTime:(NSString *) appDisplayName
|
2018-01-07 21:09:40 +00:00
|
|
|
|
milliseconds:(nonnull NSNumber *) milliseconds) {
|
2018-01-03 20:00:38 +00:00
|
|
|
|
FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName];
|
|
|
|
|
[[FIRStorage storageForApp:firApp] setMaxOperationRetryTime:[milliseconds doubleValue]];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
/**
|
|
|
|
|
setMaxUploadRetryTime
|
2017-05-31 14:22:15 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
@url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxUploadRetryTime
|
|
|
|
|
*/
|
2018-01-03 20:00:38 +00:00
|
|
|
|
RCT_EXPORT_METHOD(setMaxUploadRetryTime:(NSString *) appDisplayName
|
2018-01-07 21:09:40 +00:00
|
|
|
|
milliseconds:(nonnull NSNumber *) milliseconds) {
|
2018-01-03 20:00:38 +00:00
|
|
|
|
FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName];
|
|
|
|
|
[[FIRStorage storageForApp:firApp] setMaxUploadRetryTime:[milliseconds doubleValue]];
|
2017-03-22 18:49:36 +00:00
|
|
|
|
}
|
2017-03-09 15:26:28 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
/**
|
|
|
|
|
putFile
|
2017-05-31 14:22:15 +00:00
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
@url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putFile
|
|
|
|
|
@param NSString path
|
|
|
|
|
@param NSString localPath
|
|
|
|
|
@param NSDictionary metadata
|
|
|
|
|
*/
|
2018-01-03 20:00:38 +00:00
|
|
|
|
RCT_EXPORT_METHOD(putFile:(NSString *) appDisplayName
|
2017-08-25 15:18:42 +00:00
|
|
|
|
path:(NSString *) path
|
|
|
|
|
localPath:(NSString *) localPath
|
|
|
|
|
metadata:(NSDictionary *) metadata
|
|
|
|
|
resolver:(RCTPromiseResolveBlock) resolve
|
|
|
|
|
rejecter:(RCTPromiseRejectBlock) reject) {
|
2017-08-25 17:05:50 +00:00
|
|
|
|
FIRStorageMetadata *firmetadata = [self buildMetadataFromMap:metadata];
|
2018-07-07 02:10:35 +00:00
|
|
|
|
|
2018-07-09 16:46:14 +00:00
|
|
|
|
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) {
|
|
|
|
|
if (UTTypeConformsTo((__bridge CFStringRef)dataUTI, kUTTypeJPEG)) {
|
|
|
|
|
firmetadata.contentType = [self utiToMimeType:dataUTI];
|
|
|
|
|
[self uploadData:appDisplayName data:imageData firmetadata:firmetadata path:path resolver:resolve rejecter:reject];
|
|
|
|
|
} else {
|
|
|
|
|
// if the image UTI is not JPEG then convert to JPEG, e.g. HEI
|
|
|
|
|
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
|
|
|
|
|
NSDictionary *imageInfo = (__bridge NSDictionary*)CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
|
|
|
|
|
NSDictionary *imageMetadata = [imageInfo copy];
|
|
|
|
|
NSMutableData *imageDataJPEG = [NSMutableData data];
|
|
|
|
|
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageDataJPEG, kUTTypeJPEG, 1, NULL);
|
|
|
|
|
CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef)imageMetadata);
|
|
|
|
|
CGImageDestinationFinalize(destination);
|
|
|
|
|
// Manually set mimetype to JPEG
|
|
|
|
|
firmetadata.contentType = @"image/jpeg";
|
|
|
|
|
[self uploadData:appDisplayName data:[NSData dataWithData:imageDataJPEG] firmetadata:firmetadata 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<PHAssetResource *> *resources = [PHAssetResource assetResourcesForAsset:asset];
|
|
|
|
|
for (PHAssetResource *resource in resources) {
|
|
|
|
|
exportSession.outputFileType = resource.uniformTypeIdentifier;
|
|
|
|
|
if (exportSession.outputFileType != nil) break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[exportSession exportAsynchronouslyWithCompletionHandler:^{
|
|
|
|
|
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
|
|
|
|
|
firmetadata.contentType = [self utiToMimeType:exportSession.outputFileType];
|
|
|
|
|
[self uploadFile:appDisplayName url:tempUrl firmetadata:firmetadata path:path resolver:resolve rejecter:reject];
|
|
|
|
|
// TODO we're not cleaning up the temporary file at the moment, relying on the OS to do it
|
|
|
|
|
} 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 {
|
|
|
|
|
if (![[NSFileManager defaultManager] fileExistsAtPath:localPath]) {
|
|
|
|
|
reject(@"storage/file-not-found", @"File specified at path does not exist.", nil);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO large files should not go through 'data', should use file directly
|
|
|
|
|
// TODO heic conversion not working here UIImageJPEGRepresentation -> returns nil
|
|
|
|
|
|
|
|
|
|
// BOOL isHeic = [self isHeic:localPath];
|
|
|
|
|
NSData *data = [NSData dataWithContentsOfFile:localPath];
|
|
|
|
|
|
|
|
|
|
if ([firmetadata valueForKey:@"contentType"] == nil) {
|
|
|
|
|
firmetadata.contentType = [self mimeTypeForPath:localPath];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if (isHeic) {
|
|
|
|
|
// UIImage *image = [UIImage imageWithData: data];
|
|
|
|
|
// data = UIImageJPEGRepresentation(image, 1);
|
|
|
|
|
// firmetadata.contentType = @"image/jpeg";
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
[self uploadData:appDisplayName data:data firmetadata:firmetadata path:path resolver:resolve rejecter:reject];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
2018-07-09 16:46:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
-(BOOL) isHeic: (NSString*) path {
|
|
|
|
|
return [[path pathExtension] caseInsensitiveCompare:@"heic"] == NSOrderedSame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString *)utiToMimeType:(NSString *) dataUTI {
|
|
|
|
|
return (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)dataUTI, kUTTagClassMIMEType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSURL *)temporaryFileUrl {
|
|
|
|
|
NSString *filename = [NSString stringWithFormat:@"%@.tmp", [[NSProcessInfo processInfo] globallyUniqueString]];
|
|
|
|
|
return [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:filename];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-07 02:10:35 +00:00
|
|
|
|
- (NSString*) mimeTypeForPath: (NSString *) path {
|
|
|
|
|
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
|
|
|
|
|
CFStringRef mimeType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
|
|
|
|
|
CFRelease(UTI);
|
|
|
|
|
|
|
|
|
|
if (!mimeType) {
|
|
|
|
|
return @"application/octet-stream";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (__bridge_transfer NSString *) mimeType;
|
2017-08-25 17:05:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-03 20:00:38 +00:00
|
|
|
|
- (void)uploadFile:(NSString *)appDisplayName url:(NSURL *)url firmetadata:(FIRStorageMetadata *)firmetadata path:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
|
|
|
|
|
FIRStorageReference *fileRef = [self getReference:path appDisplayName:appDisplayName];
|
2018-05-16 16:22:47 +00:00
|
|
|
|
__block FIRStorageUploadTask *uploadTask;
|
2018-06-08 21:44:48 +00:00
|
|
|
|
RCTUnsafeExecuteOnMainQueueSync(^{
|
2018-05-16 16:22:47 +00:00
|
|
|
|
uploadTask = [fileRef putFile:url metadata:firmetadata];
|
|
|
|
|
});
|
2018-01-03 20:00:38 +00:00
|
|
|
|
[self addUploadObservers:appDisplayName uploadTask:uploadTask path:path resolver:resolve rejecter:reject];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-03 20:00:38 +00:00
|
|
|
|
- (void)uploadData:(NSString *)appDisplayName data:(NSData *)data firmetadata:(FIRStorageMetadata *)firmetadata path:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
|
|
|
|
|
FIRStorageReference *fileRef = [self getReference:path appDisplayName:appDisplayName];
|
2018-05-16 16:22:47 +00:00
|
|
|
|
__block FIRStorageUploadTask *uploadTask;
|
2018-06-08 21:44:48 +00:00
|
|
|
|
RCTUnsafeExecuteOnMainQueueSync(^{
|
2018-05-16 16:22:47 +00:00
|
|
|
|
uploadTask = [fileRef putData:data metadata:firmetadata];
|
|
|
|
|
});
|
2018-01-03 20:00:38 +00:00
|
|
|
|
[self addUploadObservers:appDisplayName uploadTask:uploadTask path:path resolver:resolve rejecter:reject];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-03 20:00:38 +00:00
|
|
|
|
- (void)addUploadObservers:(NSString *)appDisplayName uploadTask:(FIRStorageUploadTask *)uploadTask path:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// listen for state changes, errors, and completion of the upload.
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[uploadTask observeStatus:FIRStorageTaskStatusResume handler:^(FIRStorageTaskSnapshot *snapshot) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// upload resumed, also fires when the upload starts
|
2018-05-16 16:22:47 +00:00
|
|
|
|
[self getUploadTaskAsDictionary:snapshot handler:^(NSDictionary *event) {
|
|
|
|
|
[self sendJSEvent:appDisplayName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
|
|
|
|
|
}];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[uploadTask observeStatus:FIRStorageTaskStatusPause handler:^(FIRStorageTaskSnapshot *snapshot) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// upload paused
|
2018-05-16 16:22:47 +00:00
|
|
|
|
[self getUploadTaskAsDictionary:snapshot handler:^(NSDictionary *event) {
|
|
|
|
|
[self sendJSEvent:appDisplayName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
|
|
|
|
|
}];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}];
|
|
|
|
|
[uploadTask observeStatus:FIRStorageTaskStatusProgress handler:^(FIRStorageTaskSnapshot *snapshot) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// upload reported progress
|
2018-05-16 16:22:47 +00:00
|
|
|
|
[self getUploadTaskAsDictionary:snapshot handler:^(NSDictionary *event) {
|
|
|
|
|
[self sendJSEvent:appDisplayName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
|
|
|
|
|
}];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[uploadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
// upload completed successfully
|
2018-05-16 16:22:47 +00:00
|
|
|
|
[self getUploadTaskAsDictionary:snapshot handler:^(NSDictionary *event) {
|
|
|
|
|
[self sendJSEvent:appDisplayName type:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:event];
|
|
|
|
|
[self sendJSEvent:appDisplayName type:STORAGE_EVENT path:path title:STORAGE_UPLOAD_SUCCESS props:event];
|
|
|
|
|
resolve(event);
|
|
|
|
|
}];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}];
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[uploadTask observeStatus:FIRStorageTaskStatusFailure handler:^(FIRStorageTaskSnapshot *snapshot) {
|
|
|
|
|
if (snapshot.error != nil) {
|
2017-03-22 18:49:36 +00:00
|
|
|
|
[self promiseRejectStorageException:reject error:snapshot.error];
|
|
|
|
|
}
|
|
|
|
|
}];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 15:18:42 +00:00
|
|
|
|
- (FIRStorageReference *)getReference:(NSString *)path
|
2018-01-03 20:00:38 +00:00
|
|
|
|
appDisplayName:(NSString *)appDisplayName {
|
|
|
|
|
FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
if ([path hasPrefix:@"url::"]) {
|
|
|
|
|
NSString *url = [path substringFromIndex:5];
|
2018-01-03 20:00:38 +00:00
|
|
|
|
return [[FIRStorage storageForApp:firApp] referenceForURL:url];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
} else {
|
2018-01-03 20:00:38 +00:00
|
|
|
|
return [[FIRStorage storageForApp:firApp] referenceWithPath:path];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSDictionary *)getDownloadTaskAsDictionary:(FIRStorageTaskSnapshot *)task {
|
2017-07-12 14:49:33 +00:00
|
|
|
|
return @{@"bytesTransferred": @(task.progress.completedUnitCount), @"ref": task.reference.fullPath, @"state": [self getTaskStatus:task.status], @"totalBytes": @(task.progress.totalUnitCount)};
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-16 16:22:47 +00:00
|
|
|
|
- (void)getUploadTaskAsDictionary:(FIRStorageTaskSnapshot *)task
|
|
|
|
|
handler:(void(^)(NSDictionary *))handler {
|
|
|
|
|
[[task reference] downloadURLWithCompletion:^(NSURL * _Nullable URL, NSError * _Nullable error) {
|
|
|
|
|
NSString *downloadUrl = [URL absoluteString];
|
|
|
|
|
NSDictionary *metadata = [task.metadata dictionaryRepresentation];
|
|
|
|
|
NSDictionary *dictionary = @{@"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)};
|
|
|
|
|
handler(dictionary);
|
|
|
|
|
}];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-29 16:12:57 +00:00
|
|
|
|
- (FIRStorageMetadata *)buildMetadataFromMap:(NSDictionary *)metadata {
|
2017-08-25 17:05:50 +00:00
|
|
|
|
FIRStorageMetadata *storageMetadata = [[FIRStorageMetadata alloc] initWithDictionary:metadata];
|
2017-08-14 12:51:17 +00:00
|
|
|
|
storageMetadata.customMetadata = [metadata[@"customMetadata"] mutableCopy];
|
|
|
|
|
return storageMetadata;
|
2017-06-29 16:12:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
- (NSString *)getTaskStatus:(FIRStorageTaskStatus)status {
|
2017-03-09 15:26:28 +00:00
|
|
|
|
if (status == FIRStorageTaskStatusResume || status == FIRStorageTaskStatusProgress) {
|
2017-03-22 19:47:22 +00:00
|
|
|
|
return @"running";
|
2017-03-09 15:26:28 +00:00
|
|
|
|
} else if (status == FIRStorageTaskStatusPause) {
|
2017-03-22 19:47:22 +00:00
|
|
|
|
return @"paused";
|
2017-03-09 15:26:28 +00:00
|
|
|
|
} else if (status == FIRStorageTaskStatusSuccess) {
|
2017-03-22 19:47:22 +00:00
|
|
|
|
return @"success";
|
2017-03-09 15:26:28 +00:00
|
|
|
|
} else if (status == FIRStorageTaskStatusFailure) {
|
2017-03-22 19:47:22 +00:00
|
|
|
|
return @"error";
|
2017-03-09 15:26:28 +00:00
|
|
|
|
} else {
|
2017-03-22 19:47:22 +00:00
|
|
|
|
return @"unknown";
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
- (NSString *)getPathForDirectory:(int)directory {
|
|
|
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES);
|
|
|
|
|
return [paths firstObject];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 18:49:36 +00:00
|
|
|
|
- (NSDictionary *)constantsToExport {
|
2017-07-12 14:49:33 +00:00
|
|
|
|
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};
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSArray<NSString *> *)supportedEvents {
|
|
|
|
|
return @[STORAGE_EVENT, STORAGE_ERROR];
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-03 20:00:38 +00:00
|
|
|
|
- (void)sendJSError:(NSString *)appDisplayName error:(NSError *)error path:(NSString *)path {
|
2017-07-12 14:49:33 +00:00
|
|
|
|
NSDictionary *evt = @{@"path": path, @"message": [error debugDescription]};
|
2018-01-03 20:00:38 +00:00
|
|
|
|
[self sendJSEvent:appDisplayName type:STORAGE_ERROR path:path title:STORAGE_ERROR props:evt];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-03 20:00:38 +00:00
|
|
|
|
- (void)sendJSEvent:(NSString *)appDisplayName type:(NSString *)type path:(NSString *)path title:(NSString *)title props:(NSDictionary *)props {
|
|
|
|
|
[RNFirebaseUtil sendJSEvent:self name:type body:@{@"eventName": title, @"appName": appDisplayName, @"path": path, @"body": props}];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 15:18:42 +00:00
|
|
|
|
/**
|
|
|
|
|
Reject a promise with a storage exception
|
|
|
|
|
|
|
|
|
|
@param reject RCTPromiseRejectBlock
|
|
|
|
|
@param error NSError
|
|
|
|
|
*/
|
|
|
|
|
- (void)promiseRejectStorageException:(RCTPromiseRejectBlock)reject error:(NSError *)error {
|
|
|
|
|
NSString *code = @"storage/unknown";
|
|
|
|
|
NSString *message = [error localizedDescription];
|
|
|
|
|
|
|
|
|
|
NSDictionary *userInfo = [error userInfo];
|
|
|
|
|
NSError *underlyingError = userInfo[NSUnderlyingErrorKey];
|
|
|
|
|
NSString *underlyingErrorDescription = [underlyingError localizedDescription];
|
|
|
|
|
|
|
|
|
|
switch (error.code) {
|
|
|
|
|
case FIRStorageErrorCodeUnknown:
|
|
|
|
|
if ([underlyingErrorDescription isEqualToString:@"The operation couldn’t be completed. Permission denied"]) {
|
|
|
|
|
code = @"storage/invalid-device-file-path";
|
|
|
|
|
message = @"The specified device file path is invalid or is restricted.";
|
|
|
|
|
} else {
|
|
|
|
|
code = @"storage/unknown";
|
|
|
|
|
message = @"An unknown error has occurred.";
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case FIRStorageErrorCodeObjectNotFound:
|
|
|
|
|
code = @"storage/object-not-found";
|
|
|
|
|
message = @"No object exists at the desired reference.";
|
|
|
|
|
break;
|
|
|
|
|
case FIRStorageErrorCodeBucketNotFound:
|
|
|
|
|
code = @"storage/bucket-not-found";
|
|
|
|
|
message = @"No bucket is configured for Firebase Storage.";
|
|
|
|
|
break;
|
|
|
|
|
case FIRStorageErrorCodeProjectNotFound:
|
|
|
|
|
code = @"storage/project-not-found";
|
|
|
|
|
message = @"No project is configured for Firebase Storage.";
|
|
|
|
|
break;
|
|
|
|
|
case FIRStorageErrorCodeQuotaExceeded:
|
|
|
|
|
code = @"storage/quota-exceeded";
|
|
|
|
|
message = @"Quota on your Firebase Storage bucket has been exceeded.";
|
|
|
|
|
break;
|
|
|
|
|
case FIRStorageErrorCodeUnauthenticated:
|
|
|
|
|
code = @"storage/unauthenticated";
|
|
|
|
|
message = @"User is unauthenticated. Authenticate and try again.";
|
|
|
|
|
break;
|
|
|
|
|
case FIRStorageErrorCodeUnauthorized:
|
|
|
|
|
code = @"storage/unauthorized";
|
|
|
|
|
message = @"User is not authorized to perform the desired action.";
|
|
|
|
|
break;
|
|
|
|
|
case FIRStorageErrorCodeRetryLimitExceeded:
|
|
|
|
|
code = @"storage/retry-limit-exceeded";
|
|
|
|
|
message = @"The maximum time limit on an operation (upload, download, delete, etc.) has been exceeded.";
|
|
|
|
|
break;
|
|
|
|
|
case FIRStorageErrorCodeNonMatchingChecksum:
|
|
|
|
|
code = @"storage/non-matching-checksum";
|
|
|
|
|
message = @"File on the client does not match the checksum of the file received by the server.";
|
|
|
|
|
break;
|
|
|
|
|
case FIRStorageErrorCodeDownloadSizeExceeded:
|
|
|
|
|
code = @"storage/download-size-exceeded";
|
|
|
|
|
message = @"Size of the downloaded file exceeds the amount of memory allocated for the download.";
|
|
|
|
|
break;
|
|
|
|
|
case FIRStorageErrorCodeCancelled:
|
|
|
|
|
code = @"storage/cancelled";
|
|
|
|
|
message = @"User cancelled the operation.";
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (userInfo != nil && userInfo[@"data"]) {
|
|
|
|
|
// errors with 'data' are unserializable - it breaks react so we send nil instead
|
|
|
|
|
reject(code, message, nil);
|
|
|
|
|
} else {
|
|
|
|
|
reject(code, message, error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-12 08:18:01 +00:00
|
|
|
|
+ (BOOL)requiresMainQueueSetup
|
|
|
|
|
{
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
2017-03-09 15:26:28 +00:00
|
|
|
|
|
|
|
|
|
@end
|
2017-05-25 14:14:01 +00:00
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
@implementation RNFirebaseStorage
|
|
|
|
|
@end
|
|
|
|
|
#endif
|