diff --git a/README.md b/README.md
index 8831db5cf..5f73c6d81 100644
--- a/README.md
+++ b/README.md
@@ -236,7 +236,7 @@ RNFetchBlob
console.log('The file saved to ', res.path())
// Beware that when using a file path as Image source on Android,
// you must prepend "file://"" before the file path
- imageView =
+ imageView =
})
```
diff --git a/android.js b/android.js
index 80c4e4a96..3e4faa472 100644
--- a/android.js
+++ b/android.js
@@ -38,9 +38,25 @@ function addCompleteDownload(config) {
return Promise.reject('RNFetchBlob.android.addCompleteDownload only supports Android.')
}
+function getSDCardDir() {
+ if(Platform.OS === 'android')
+ return RNFetchBlob.getSDCardDir()
+ else
+ return Promise.reject('RNFetchBlob.android.getSDCardDir only supports Android.')
+}
+
+function getSDCardApplicationDir() {
+ if(Platform.OS === 'android')
+ return RNFetchBlob.getSDCardApplicationDir()
+ else
+ return Promise.reject('RNFetchBlob.android.getSDCardApplicationDir only supports Android.')
+}
+
export default {
actionViewIntent,
getContentIntent,
- addCompleteDownload
+ addCompleteDownload,
+ getSDCardDir,
+ getSDCardApplicationDir,
}
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java
index c4980d445..64b0b4833 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java
@@ -100,7 +100,6 @@ public void run() {
RNFetchBlobFS.createFileASCII(path, dataArray, promise);
}
});
-
}
@ReactMethod
@@ -164,7 +163,6 @@ public void run() {
RNFetchBlobFS.cp(path, dest, callback);
}
});
-
}
@ReactMethod
@@ -225,7 +223,6 @@ public void run() {
RNFetchBlobFS.writeFile(path, encoding, data, append, promise);
}
});
-
}
@ReactMethod
@@ -260,7 +257,6 @@ public void run() {
new RNFetchBlobFS(ctx).scanFile(p, m, callback);
}
});
-
}
@ReactMethod
@@ -331,7 +327,7 @@ public void enableUploadProgressReport(String taskId, int interval, int count) {
@ReactMethod
public void fetchBlob(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, final Callback callback) {
new RNFetchBlobReq(options, taskId, method, url, headers, body, null, mClient, callback).run();
-}
+ }
@ReactMethod
public void fetchBlobForm(ReadableMap options, String taskId, String method, String url, ReadableMap headers, ReadableArray body, final Callback callback) {
@@ -377,4 +373,13 @@ public void addCompleteDownload (ReadableMap config, Promise promise) {
}
+ @ReactMethod
+ public void getSDCardDir(Promise promise) {
+ RNFetchBlobFS.getSDCardDir(promise);
+ }
+
+ @ReactMethod
+ public void getSDCardApplicationDir(Promise promise) {
+ RNFetchBlobFS.getSDCardApplicationDir(this.getReactApplicationContext(), promise);
+ }
}
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
index dde39bc4c..39972d066 100644
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
@@ -246,12 +246,38 @@ static Map getSystemfolders(ReactApplicationContext ctx) {
state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
res.put("SDCardDir", Environment.getExternalStorageDirectory().getAbsolutePath());
- res.put("SDCardApplicationDir", ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath());
+ try {
+ res.put("SDCardApplicationDir", ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath());
+ } catch(Exception e) {
+ res.put("SDCardApplicationDir", "");
+ }
}
res.put("MainBundleDir", ctx.getApplicationInfo().dataDir);
return res;
}
+ static public void getSDCardDir(Promise promise) {
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ promise.resolve(Environment.getExternalStorageDirectory().getAbsolutePath());
+ } else {
+ promise.reject("RNFetchBlob.getSDCardDir", "External storage not mounted");
+ }
+
+ }
+
+ static public void getSDCardApplicationDir(ReactApplicationContext ctx, Promise promise) {
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ try {
+ final String path = ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath();
+ promise.resolve(path);
+ } catch (Exception e) {
+ promise.reject("RNFetchBlob.getSDCardApplicationDir", e.getLocalizedMessage());
+ }
+ } else {
+ promise.reject("RNFetchBlob.getSDCardApplicationDir", "External storage not mounted");
+ }
+ }
+
/**
* Static method that returns a temp file path
* @param taskId An unique string for identify
diff --git a/fs.js b/fs.js
index a286e937a..2b4b3ba78 100644
--- a/fs.js
+++ b/fs.js
@@ -13,17 +13,26 @@ import RNFetchBlobFile from './class/RNFetchBlobFile'
const RNFetchBlob: RNFetchBlobNative = NativeModules.RNFetchBlob
const dirs = {
- DocumentDir: RNFetchBlob.DocumentDir,
- CacheDir: RNFetchBlob.CacheDir,
- PictureDir: RNFetchBlob.PictureDir,
- MusicDir: RNFetchBlob.MusicDir,
- MovieDir: RNFetchBlob.MovieDir,
- DownloadDir: RNFetchBlob.DownloadDir,
- DCIMDir: RNFetchBlob.DCIMDir,
- SDCardDir: RNFetchBlob.SDCardDir,
- SDCardApplicationDir: RNFetchBlob.SDCardApplicationDir,
- MainBundleDir: RNFetchBlob.MainBundleDir,
- LibraryDir: RNFetchBlob.LibraryDir
+ DocumentDir : RNFetchBlob.DocumentDir,
+ CacheDir : RNFetchBlob.CacheDir,
+ PictureDir : RNFetchBlob.PictureDir,
+ MusicDir : RNFetchBlob.MusicDir,
+ MovieDir : RNFetchBlob.MovieDir,
+ DownloadDir : RNFetchBlob.DownloadDir,
+ DCIMDir : RNFetchBlob.DCIMDir,
+ get SDCardDir() {
+ console.warn('SDCardDir as a constant is deprecated and will be removed in feature release. ' +
+ 'Use RNFetchBlob.android.getSDCardDir():Promise instead.');
+ return RNFetchBlob.SDCardDir;
+ },
+ get SDCardApplicationDir() {
+ console.warn('SDCardApplicationDir as a constant is deprecated and will be removed in feature release. ' +
+ 'Use RNFetchBlob.android.getSDCardApplicationDir():Promise instead. ' +
+ 'This variable can be empty on error in native code.');
+ return RNFetchBlob.SDCardApplicationDir;
+ },
+ MainBundleDir : RNFetchBlob.MainBundleDir,
+ LibraryDir : RNFetchBlob.LibraryDir
}
function addCode(code: string, error: Error): Error {
diff --git a/ios/RNFetchBlob.xcodeproj/project.pbxproj b/ios/RNFetchBlob.xcodeproj/project.pbxproj
index acd524413..070fcabb1 100644
--- a/ios/RNFetchBlob.xcodeproj/project.pbxproj
+++ b/ios/RNFetchBlob.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 8C4801A6200CF71700FED7ED /* RNFetchBlobRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */; };
A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */; };
A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */; };
A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */; };
@@ -29,6 +30,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 8C4801A4200CF71700FED7ED /* RNFetchBlobRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobRequest.h; sourceTree = ""; };
+ 8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobRequest.m; sourceTree = ""; };
A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobFS.m; sourceTree = ""; };
A158F4281D052E57006FFD38 /* RNFetchBlobFS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobFS.h; sourceTree = ""; };
A158F4291D0534A9006FFD38 /* RNFetchBlobConst.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobConst.h; sourceTree = ""; };
@@ -71,8 +74,10 @@
A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */,
A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */,
A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */,
- A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */,
A158F42E1D0539CE006FFD38 /* RNFetchBlobNetwork.h */,
+ A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */,
+ 8C4801A4200CF71700FED7ED /* RNFetchBlobRequest.h */,
+ 8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */,
A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */,
A158F4291D0534A9006FFD38 /* RNFetchBlobConst.h */,
A158F4281D052E57006FFD38 /* RNFetchBlobFS.h */,
@@ -149,6 +154,7 @@
buildActionMask = 2147483647;
files = (
A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */,
+ 8C4801A6200CF71700FED7ED /* RNFetchBlobRequest.m in Sources */,
A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */,
A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */,
A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */,
diff --git a/ios/RNFetchBlob/RNFetchBlob.h b/ios/RNFetchBlob/RNFetchBlob.h
index e6385114f..669a093b5 100644
--- a/ios/RNFetchBlob/RNFetchBlob.h
+++ b/ios/RNFetchBlob/RNFetchBlob.h
@@ -39,7 +39,6 @@
@property (retain) UIDocumentInteractionController * documentController;
+ (RCTBridge *)getRCTBridge;
-+ (void) checkExpiredSessions;
@end
diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m
index 47bda76c5..d2c25a1f1 100644
--- a/ios/RNFetchBlob/RNFetchBlob.m
+++ b/ios/RNFetchBlob/RNFetchBlob.m
@@ -38,7 +38,7 @@ - (dispatch_queue_t) methodQueue {
+ (RCTBridge *)getRCTBridge
{
- RCTRootView * rootView = [[UIApplication sharedApplication] keyWindow].rootViewController.view;
+ RCTRootView * rootView = (RCTRootView*) [[UIApplication sharedApplication] keyWindow].rootViewController.view;
return rootView.bridge;
}
@@ -96,8 +96,12 @@ - (NSDictionary *)constantsToExport
// send HTTP request
else
{
- RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init];
- [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback];
+ [[RNFetchBlobNetwork sharedInstance] sendRequest:options
+ contentLength:bodyLength
+ bridge:self.bridge
+ taskId:taskId
+ withRequest:req
+ callback:callback];
}
}];
@@ -128,8 +132,12 @@ - (NSDictionary *)constantsToExport
// send HTTP request
else
{
- __block RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init];
- [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback];
+ [[RNFetchBlobNetwork sharedInstance] sendRequest:options
+ contentLength:bodyLength
+ bridge:self.bridge
+ taskId:taskId
+ withRequest:req
+ callback:callback];
}
}];
}
@@ -518,7 +526,7 @@ - (NSDictionary *)constantsToExport
#pragma mark - net.cancelRequest
RCT_EXPORT_METHOD(cancelRequest:(NSString *)taskId callback:(RCTResponseSenderBlock)callback) {
- [RNFetchBlobNetwork cancelRequest:taskId];
+ [[RNFetchBlobNetwork sharedInstance] cancelRequest:taskId];
callback(@[[NSNull null], taskId]);
}
@@ -528,14 +536,14 @@ - (NSDictionary *)constantsToExport
{
RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Download interval:interval count:count];
- [RNFetchBlobNetwork enableProgressReport:taskId config:cfg];
+ [[RNFetchBlobNetwork sharedInstance] enableProgressReport:taskId config:cfg];
}
#pragma mark - net.enableUploadProgressReport
RCT_EXPORT_METHOD(enableUploadProgressReport:(NSString *)taskId interval:(nonnull NSNumber*)interval count:(nonnull NSNumber*)count)
{
RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Upload interval:interval count:count];
- [RNFetchBlobNetwork enableUploadProgress:taskId config:cfg];
+ [[RNFetchBlobNetwork sharedInstance] enableUploadProgress:taskId config:cfg];
}
#pragma mark - fs.slice
diff --git a/ios/RNFetchBlobConst.m b/ios/RNFetchBlobConst.m
index 6f7fef4b2..bc9b793a5 100644
--- a/ios/RNFetchBlobConst.m
+++ b/ios/RNFetchBlobConst.m
@@ -7,38 +7,38 @@
//
#import "RNFetchBlobConst.h"
-extern NSString *const FILE_PREFIX = @"RNFetchBlob-file://";
-extern NSString *const ASSET_PREFIX = @"bundle-assets://";
-extern NSString *const AL_PREFIX = @"assets-library://";
+NSString *const FILE_PREFIX = @"RNFetchBlob-file://";
+NSString *const ASSET_PREFIX = @"bundle-assets://";
+NSString *const AL_PREFIX = @"assets-library://";
// fetch configs
-extern NSString *const CONFIG_USE_TEMP = @"fileCache";
-extern NSString *const CONFIG_FILE_PATH = @"path";
-extern NSString *const CONFIG_FILE_EXT = @"appendExt";
-extern NSString *const CONFIG_TRUSTY = @"trusty";
-extern NSString *const CONFIG_INDICATOR = @"indicator";
-extern NSString *const CONFIG_KEY = @"key";
-extern NSString *const CONFIG_EXTRA_BLOB_CTYPE = @"binaryContentTypes";
+NSString *const CONFIG_USE_TEMP = @"fileCache";
+NSString *const CONFIG_FILE_PATH = @"path";
+NSString *const CONFIG_FILE_EXT = @"appendExt";
+NSString *const CONFIG_TRUSTY = @"trusty";
+NSString *const CONFIG_INDICATOR = @"indicator";
+NSString *const CONFIG_KEY = @"key";
+NSString *const CONFIG_EXTRA_BLOB_CTYPE = @"binaryContentTypes";
-extern NSString *const EVENT_STATE_CHANGE = @"RNFetchBlobState";
-extern NSString *const EVENT_SERVER_PUSH = @"RNFetchBlobServerPush";
-extern NSString *const EVENT_PROGRESS = @"RNFetchBlobProgress";
-extern NSString *const EVENT_PROGRESS_UPLOAD = @"RNFetchBlobProgress-upload";
-extern NSString *const EVENT_EXPIRE = @"RNFetchBlobExpire";
+NSString *const EVENT_STATE_CHANGE = @"RNFetchBlobState";
+NSString *const EVENT_SERVER_PUSH = @"RNFetchBlobServerPush";
+NSString *const EVENT_PROGRESS = @"RNFetchBlobProgress";
+NSString *const EVENT_PROGRESS_UPLOAD = @"RNFetchBlobProgress-upload";
+NSString *const EVENT_EXPIRE = @"RNFetchBlobExpire";
-extern NSString *const MSG_EVENT = @"RNFetchBlobMessage";
-extern NSString *const MSG_EVENT_LOG = @"log";
-extern NSString *const MSG_EVENT_WARN = @"warn";
-extern NSString *const MSG_EVENT_ERROR = @"error";
-extern NSString *const FS_EVENT_DATA = @"data";
-extern NSString *const FS_EVENT_END = @"end";
-extern NSString *const FS_EVENT_WARN = @"warn";
-extern NSString *const FS_EVENT_ERROR = @"error";
+NSString *const MSG_EVENT = @"RNFetchBlobMessage";
+NSString *const MSG_EVENT_LOG = @"log";
+NSString *const MSG_EVENT_WARN = @"warn";
+NSString *const MSG_EVENT_ERROR = @"error";
+NSString *const FS_EVENT_DATA = @"data";
+NSString *const FS_EVENT_END = @"end";
+NSString *const FS_EVENT_WARN = @"warn";
+NSString *const FS_EVENT_ERROR = @"error";
-extern NSString *const KEY_REPORT_PROGRESS = @"reportProgress";
-extern NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress";
+NSString *const KEY_REPORT_PROGRESS = @"reportProgress";
+NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress";
// response type
-extern NSString *const RESP_TYPE_BASE64 = @"base64";
-extern NSString *const RESP_TYPE_UTF8 = @"utf8";
-extern NSString *const RESP_TYPE_PATH = @"path";
+NSString *const RESP_TYPE_BASE64 = @"base64";
+NSString *const RESP_TYPE_UTF8 = @"utf8";
+NSString *const RESP_TYPE_PATH = @"path";
diff --git a/ios/RNFetchBlobFS.h b/ios/RNFetchBlobFS.h
index 7cf0e4faa..222b2f8e6 100644
--- a/ios/RNFetchBlobFS.h
+++ b/ios/RNFetchBlobFS.h
@@ -34,8 +34,8 @@
NSString * streamId;
}
-@property (nonatomic) NSOutputStream * outStream;
-@property (nonatomic) NSInputStream * inStream;
+@property (nonatomic) NSOutputStream * _Nullable outStream;
+@property (nonatomic) NSInputStream * _Nullable inStream;
@property (strong, nonatomic) RCTResponseSenderBlock callback;
@property (nonatomic) RCTBridge * bridge;
@property (nonatomic) NSString * encoding;
diff --git a/ios/RNFetchBlobNetwork.h b/ios/RNFetchBlobNetwork.h
index d3b4654a5..1512712af 100644
--- a/ios/RNFetchBlobNetwork.h
+++ b/ios/RNFetchBlobNetwork.h
@@ -6,9 +6,13 @@
// Copyright © 2016 wkh237. All rights reserved.
//
+#ifndef RNFetchBlobNetwork_h
+#define RNFetchBlobNetwork_h
+
#import
#import "RNFetchBlobProgress.h"
#import "RNFetchBlobFS.h"
+#import "RNFetchBlobRequest.h"
#if __has_include()
#import
@@ -16,42 +20,26 @@
#import "RCTBridgeModule.h"
#endif
-#ifndef RNFetchBlobNetwork_h
-#define RNFetchBlobNetwork_h
-
-
-
-typedef void(^CompletionHander)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error);
-typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse * _Nullable response, NSError * _Nullable error);
@interface RNFetchBlobNetwork : NSObject
-@property (nullable, nonatomic) NSString * taskId;
-@property (nonatomic) int expectedBytes;
-@property (nonatomic) int receivedBytes;
-@property (nonatomic) BOOL isServerPush;
-@property (nullable, nonatomic) NSMutableData * respData;
-@property (strong, nonatomic) RCTResponseSenderBlock callback;
-@property (nullable, nonatomic) RCTBridge * bridge;
-@property (nullable, nonatomic) NSDictionary * options;
-@property (nullable, nonatomic) RNFetchBlobFS * fileStream;
-@property (strong, nonatomic) CompletionHander fileTaskCompletionHandler;
-@property (strong, nonatomic) DataTaskCompletionHander dataTaskCompletionHandler;
-@property (nullable, nonatomic) NSError * error;
-
+@property(nonnull, nonatomic) NSOperationQueue *taskQueue;
+@property(nonnull, nonatomic) NSMapTable * requestsTable;
++ (RNFetchBlobNetwork* _Nullable)sharedInstance;
+ (NSMutableDictionary * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers;
-+ (void) cancelRequest:(NSString *)taskId;
-+ (void) enableProgressReport:(NSString *) taskId;
-+ (void) enableUploadProgress:(NSString *) taskId;
+ (void) emitExpiredTasks;
- (nullable id) init;
-- (void) sendRequest;
-- (void) sendRequest:(NSDictionary * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback;
-+ (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config;
-+ (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config;
-
+- (void) sendRequest:(NSDictionary * _Nullable )options
+ contentLength:(long)contentLength
+ bridge:(RCTBridge * _Nullable)bridgeRef
+ taskId:(NSString * _Nullable)taskId
+ withRequest:(NSURLRequest * _Nullable)req
+ callback:(_Nullable RCTResponseSenderBlock) callback;
+- (void) cancelRequest:(NSString * _Nonnull)taskId;
+- (void) enableProgressReport:(NSString * _Nonnull) taskId config:(RNFetchBlobProgress * _Nullable)config;
+- (void) enableUploadProgress:(NSString * _Nonnull) taskId config:(RNFetchBlobProgress * _Nullable)config;
@end
diff --git a/ios/RNFetchBlobNetwork.m b/ios/RNFetchBlobNetwork.m
index 34ebabb95..4531bf35e 100644
--- a/ios/RNFetchBlobNetwork.m
+++ b/ios/RNFetchBlobNetwork.m
@@ -8,13 +8,10 @@
#import
-#import "RNFetchBlob.h"
-#import "RNFetchBlobFS.h"
#import "RNFetchBlobNetwork.h"
+
+#import "RNFetchBlob.h"
#import "RNFetchBlobConst.h"
-#import "RNFetchBlobReqBuilder.h"
-#import "IOS7Polyfill.h"
-#import
#import "RNFetchBlobProgress.h"
#if __has_include()
@@ -35,130 +32,43 @@
//
////////////////////////////////////////
-NSMapTable * taskTable;
NSMapTable * expirationTable;
-NSMutableDictionary * progressTable;
-NSMutableDictionary * uploadProgressTable;
__attribute__((constructor))
static void initialize_tables() {
- if(expirationTable == nil)
- {
+ if (expirationTable == nil) {
expirationTable = [[NSMapTable alloc] init];
}
- if(taskTable == nil)
- {
- taskTable = [[NSMapTable alloc] init];
- }
- if(progressTable == nil)
- {
- progressTable = [[NSMutableDictionary alloc] init];
- }
- if(uploadProgressTable == nil)
- {
- uploadProgressTable = [[NSMutableDictionary alloc] init];
- }
-}
-
-
-typedef NS_ENUM(NSUInteger, ResponseFormat) {
- UTF8,
- BASE64,
- AUTO
-};
-
-
-@interface RNFetchBlobNetwork ()
-{
- BOOL * respFile;
- BOOL isNewPart;
- BOOL * isIncrement;
- NSMutableData * partBuffer;
- NSString * destPath;
- NSOutputStream * writeStream;
- long bodyLength;
- NSMutableDictionary * respInfo;
- NSInteger respStatus;
- NSMutableArray * redirects;
- ResponseFormat responseFormat;
- BOOL * followRedirect;
- BOOL backgroundTask;
}
-@end
@implementation RNFetchBlobNetwork
-NSOperationQueue *taskQueue;
-@synthesize taskId;
-@synthesize expectedBytes;
-@synthesize receivedBytes;
-@synthesize respData;
-@synthesize callback;
-@synthesize bridge;
-@synthesize options;
-@synthesize fileTaskCompletionHandler;
-@synthesize dataTaskCompletionHandler;
-@synthesize error;
-
-// constructor
- (id)init {
self = [super init];
- if(taskQueue == nil) {
- @synchronized ([RNFetchBlobNetwork class]) {
- if (taskQueue == nil) {
- taskQueue = [[NSOperationQueue alloc] init];
- taskQueue.maxConcurrentOperationCount = 10;
- }
- }
+ if (self) {
+ self.requestsTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
+
+ self.taskQueue = [[NSOperationQueue alloc] init];
+ self.taskQueue.qualityOfService = NSQualityOfServiceUtility;
+ self.taskQueue.maxConcurrentOperationCount = 10;
}
+
return self;
}
-+ (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config
-{
- if(progressTable == nil)
- {
- progressTable = [[NSMutableDictionary alloc] init];
- }
- [progressTable setValue:config forKey:taskId];
-}
-
-+ (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config
-{
- if(uploadProgressTable == nil)
- {
- uploadProgressTable = [[NSMutableDictionary alloc] init];
- }
- [uploadProgressTable setValue:config forKey:taskId];
-}
-
-// removing case from headers
-+ (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers
-{
-
- NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init];
- for(NSString * key in headers) {
- [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]];
- }
-
- return mheaders;
-}
-
-- (NSString *)md5:(NSString *)input {
- const char* str = [input UTF8String];
- unsigned char result[CC_MD5_DIGEST_LENGTH];
- CC_MD5(str, (CC_LONG)strlen(str), result);
++ (RNFetchBlobNetwork* _Nullable)sharedInstance {
+ static id _sharedInstance = nil;
+ static dispatch_once_t onceToken;
- NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2];
- for(int i = 0; i 0)
- {
- defaultConfigObject.timeoutIntervalForRequest = timeout/1000;
- }
- defaultConfigObject.HTTPMaximumConnectionsPerHost = 10;
- session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:taskQueue];
- if(path != nil || [self.options valueForKey:CONFIG_USE_TEMP]!= nil)
- {
- respFile = YES;
-
- NSString* cacheKey = taskId;
- if (key != nil) {
- cacheKey = [self md5:key];
- if (cacheKey == nil) {
- cacheKey = taskId;
- }
-
- destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
- if ([[NSFileManager defaultManager] fileExistsAtPath:destPath]) {
- callback(@[[NSNull null], RESP_TYPE_PATH, destPath]);
- return;
- }
- }
-
- if(path != nil)
- destPath = path;
- else
- destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
- }
- else
- {
- respData = [[NSMutableData alloc] init];
- respFile = NO;
- }
-
- __block NSURLSessionDataTask * task = [session dataTaskWithRequest:req];
+ RNFetchBlobRequest *request = [[RNFetchBlobRequest alloc] init];
+ [request sendRequest:options
+ contentLength:contentLength
+ bridge:bridgeRef
+ taskId:taskId
+ withRequest:req
+ taskOperationQueue:self.taskQueue
+ callback:callback];
- [taskTable setObject:@{ @"session" : task, @"isCancelled" : @NO } forKey:taskId];
- [task resume];
-
- // network status indicator
- if ([[options objectForKey:CONFIG_INDICATOR] boolValue] == YES) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
- });
- }
- __block UIApplication * app = [UIApplication sharedApplication];
-
-}
-
-// #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled
-+ (void) emitExpiredTasks
-{
- NSEnumerator * emu = [expirationTable keyEnumerator];
- NSString * key;
-
- while((key = [emu nextObject]))
- {
- RCTBridge * bridge = [RNFetchBlob getRCTBridge];
- NSData * args = @{ @"taskId": key };
- [bridge.eventDispatcher sendDeviceEventWithName:EVENT_EXPIRE body:args];
-
+ @synchronized([RNFetchBlobNetwork class]) {
+ [self.requestsTable setObject:request forKey:taskId];
}
-
- // clear expired task entries
- [expirationTable removeAllObjects];
- expirationTable = [[NSMapTable alloc] init];
-
}
-////////////////////////////////////////
-//
-// NSURLSession delegates
-//
-////////////////////////////////////////
-
-
-#pragma mark NSURLSession delegate methods
-
-
-#pragma mark - Received Response
-// set expected content length on response received
-- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
+- (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config
{
- expectedBytes = [response expectedContentLength];
-
- NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
- NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
- NSString * respType = @"";
- respStatus = statusCode;
- if ([response respondsToSelector:@selector(allHeaderFields)])
- {
- NSDictionary *headers = [httpResponse allHeaderFields];
- NSString * respCType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"Content-Type" fromHeaders:headers] lowercaseString];
- if(self.isServerPush == NO)
- {
- self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"];
- }
- if(self.isServerPush)
- {
- if(partBuffer != nil)
- {
- [self.bridge.eventDispatcher
- sendDeviceEventWithName:EVENT_SERVER_PUSH
- body:@{
- @"taskId": taskId,
- @"chunk": [partBuffer base64EncodedStringWithOptions:0],
- }
- ];
- }
- partBuffer = [[NSMutableData alloc] init];
- completionHandler(NSURLSessionResponseAllow);
- return;
- }
- if(respCType != nil)
- {
- NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE];
- if([respCType RNFBContainsString:@"text/"])
- {
- respType = @"text";
- }
- else if([respCType RNFBContainsString:@"application/json"])
- {
- respType = @"json";
- }
- // If extra blob content type is not empty, check if response type matches
- else if( extraBlobCTypes != nil) {
- for(NSString * substr in extraBlobCTypes)
- {
- if([respCType RNFBContainsString:[substr lowercaseString]])
- {
- respType = @"blob";
- respFile = YES;
- destPath = [RNFetchBlobFS getTempPath:taskId withExtension:nil];
- break;
- }
- }
- }
- else
- {
- respType = @"blob";
- // for XMLHttpRequest, switch response data handling strategy automatically
- if([options valueForKey:@"auto"] == YES) {
- respFile = YES;
- destPath = [RNFetchBlobFS getTempPath:taskId withExtension:@""];
- }
- }
- }
- else
- respType = @"text";
- respInfo = @{
- @"taskId": taskId,
- @"state": @"2",
- @"headers": headers,
- @"redirects": redirects,
- @"respType" : respType,
- @"timeout" : @NO,
- @"status": [NSNumber numberWithInteger:statusCode]
- };
-
-#pragma mark - handling cookies
- // # 153 get cookies
- if(response.URL != nil)
- {
- NSHTTPCookieStorage * cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage];
- NSArray * cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: headers forURL:response.URL];
- if(cookies != nil && [cookies count] > 0) {
- [cookieStore setCookies:cookies forURL:response.URL mainDocumentURL:nil];
- }
- }
-
- [self.bridge.eventDispatcher
- sendDeviceEventWithName: EVENT_STATE_CHANGE
- body:respInfo
- ];
- headers = nil;
- respInfo = nil;
-
- }
- else
- NSLog(@"oops");
-
- if(respFile == YES)
- {
- @try{
- NSFileManager * fm = [NSFileManager defaultManager];
- NSString * folder = [destPath stringByDeletingLastPathComponent];
- if(![fm fileExistsAtPath:folder])
- {
- [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil];
- }
- BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue];
- BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"];
-
- appendToExistingFile = !overwrite;
-
- // For solving #141 append response data if the file already exists
- // base on PR#139 @kejinliang
- if(appendToExistingFile)
- {
- destPath = [destPath stringByReplacingOccurrencesOfString:@"?append=true" withString:@""];
- }
- if (![fm fileExistsAtPath:destPath])
- {
- [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil];
- }
- writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:appendToExistingFile];
- [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
- [writeStream open];
- }
- @catch(NSException * ex)
- {
- NSLog(@"write file error");
+ if (config) {
+ @synchronized ([RNFetchBlobNetwork class]) {
+ [self.requestsTable objectForKey:taskId].progressConfig = config;
}
}
-
- completionHandler(NSURLSessionResponseAllow);
-}
-
-
-// download progress handler
-- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
-{
- // For #143 handling multipart/x-mixed-replace response
- if(self.isServerPush)
- {
- [partBuffer appendData:data];
- return ;
- }
-
- NSNumber * received = [NSNumber numberWithLong:[data length]];
- receivedBytes += [received longValue];
- NSString * chunkString = @"";
-
- if(isIncrement == YES)
- {
- chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
- }
-
- if(respFile == NO)
- {
- [respData appendData:data];
- }
- else
- {
- [writeStream write:[data bytes] maxLength:[data length]];
- }
- RNFetchBlobProgress * pconfig = [progressTable valueForKey:taskId];
- if(expectedBytes == 0)
- return;
- NSNumber * now =[NSNumber numberWithFloat:((float)receivedBytes/(float)expectedBytes)];
- if(pconfig != nil && [pconfig shouldReport:now])
- {
- [self.bridge.eventDispatcher
- sendDeviceEventWithName:EVENT_PROGRESS
- body:@{
- @"taskId": taskId,
- @"written": [NSString stringWithFormat:@"%d", receivedBytes],
- @"total": [NSString stringWithFormat:@"%d", expectedBytes],
- @"chunk": chunkString
- }
- ];
- }
- received = nil;
-
-}
-
-- (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
-{
- if([session isEqual:session])
- session = nil;
}
-
-- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
+- (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config
{
-
- self.error = error;
- NSString * errMsg = [NSNull null];
- NSString * respStr = [NSNull null];
- NSString * rnfbRespType = @"";
-
- dispatch_async(dispatch_get_main_queue(), ^{
- [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
- });
-
- if(respInfo == nil)
- {
- respInfo = [NSNull null];
- }
-
- if(error != nil)
- {
- errMsg = [error localizedDescription];
- }
- NSDictionary * taskSession = [taskTable objectForKey:taskId];
- BOOL isCancelled = [[taskSession valueForKey:@"isCancelled"] boolValue];
- if(isCancelled) {
- errMsg = @"task cancelled";
- }
-
- if(respFile == YES)
- {
- [writeStream close];
- rnfbRespType = RESP_TYPE_PATH;
- respStr = destPath;
- }
- // base64 response
- else {
- // #73 fix unicode data encoding issue :
- // when response type is BASE64, we should first try to encode the response data to UTF8 format
- // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
- // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
- NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding];
-
- if(responseFormat == BASE64)
- {
- rnfbRespType = RESP_TYPE_BASE64;
- respStr = [respData base64EncodedStringWithOptions:0];
- }
- else if (responseFormat == UTF8)
- {
- rnfbRespType = RESP_TYPE_UTF8;
- respStr = utf8;
- }
- else
- {
- if(utf8 != nil)
- {
- rnfbRespType = RESP_TYPE_UTF8;
- respStr = utf8;
- }
- else
- {
- rnfbRespType = RESP_TYPE_BASE64;
- respStr = [respData base64EncodedStringWithOptions:0];
- }
+ if (config) {
+ @synchronized ([RNFetchBlobNetwork class]) {
+ [self.requestsTable objectForKey:taskId].uploadProgressConfig = config;
}
}
-
-
- callback(@[ errMsg, rnfbRespType, respStr]);
-
- @synchronized(taskTable, uploadProgressTable, progressTable)
- {
- if([taskTable objectForKey:taskId] == nil)
- NSLog(@"object released by ARC.");
- else
- [taskTable removeObjectForKey:taskId];
- [uploadProgressTable removeObjectForKey:taskId];
- [progressTable removeObjectForKey:taskId];
- }
-
- respData = nil;
- receivedBytes = 0;
- [session finishTasksAndInvalidate];
-
-}
-
-// upload progress handler
-- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesWritten totalBytesExpectedToSend:(int64_t)totalBytesExpectedToWrite
-{
- RNFetchBlobProgress * pconfig = [uploadProgressTable valueForKey:taskId];
- if(totalBytesExpectedToWrite == 0)
- return;
- NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)];
- if(pconfig != nil && [pconfig shouldReport:now]) {
- [self.bridge.eventDispatcher
- sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD
- body:@{
- @"taskId": taskId,
- @"written": [NSString stringWithFormat:@"%d", totalBytesWritten],
- @"total": [NSString stringWithFormat:@"%d", totalBytesExpectedToWrite]
- }
- ];
- }
}
-+ (void) cancelRequest:(NSString *)taskId
+- (void) cancelRequest:(NSString *)taskId
{
- NSDictionary * task = [taskTable objectForKey:taskId];
+ NSURLSessionDataTask * task;
- if(task != nil) {
- NSURLSessionDataTask * session = [task objectForKey:@"session"];
- if(session.state == NSURLSessionTaskStateRunning) {
- [task setValue:@NO forKey:@"isCancelled"];
- [session cancel];
- }
+ @synchronized ([RNFetchBlobNetwork class]) {
+ task = [self.requestsTable objectForKey:taskId].task;
}
-}
-
-
-- (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler
-{
- BOOL trusty = [options valueForKey:CONFIG_TRUSTY];
- if(!trusty)
- {
- completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
- }
- else
- {
- completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+ if (task && task.state == NSURLSessionTaskStateRunning) {
+ [task cancel];
}
}
-
-- (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
+// removing case from headers
++ (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers
{
- NSLog(@"sess done in background");
+ NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init];
+ for (NSString * key in headers) {
+ [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]];
+ }
+
+ return mheaders;
}
-- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
+// #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled
++ (void) emitExpiredTasks
{
-
- if(followRedirect)
- {
- if(request.URL != nil)
- [redirects addObject:[request.URL absoluteString]];
- completionHandler(request);
- }
- else
- {
- completionHandler(nil);
+ @synchronized ([RNFetchBlobNetwork class]){
+ NSEnumerator * emu = [expirationTable keyEnumerator];
+ NSString * key;
+
+ while ((key = [emu nextObject]))
+ {
+ RCTBridge * bridge = [RNFetchBlob getRCTBridge];
+ id args = @{ @"taskId": key };
+ [bridge.eventDispatcher sendDeviceEventWithName:EVENT_EXPIRE body:args];
+
+ }
+
+ // clear expired task entries
+ [expirationTable removeAllObjects];
+ expirationTable = [[NSMapTable alloc] init];
}
}
diff --git a/ios/RNFetchBlobReqBuilder.h b/ios/RNFetchBlobReqBuilder.h
index e7abeb9c7..1edc3ff50 100644
--- a/ios/RNFetchBlobReqBuilder.h
+++ b/ios/RNFetchBlobReqBuilder.h
@@ -29,7 +29,7 @@
body:(NSString *)body
onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete;
-+(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableArray *) headers;
++(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSDictionary *) headers;
@end
diff --git a/ios/RNFetchBlobReqBuilder.m b/ios/RNFetchBlobReqBuilder.m
index b6048f553..16154436e 100644
--- a/ios/RNFetchBlobReqBuilder.m
+++ b/ios/RNFetchBlobReqBuilder.m
@@ -277,7 +277,7 @@ void __block (^getFieldData)(id field) = ^(id field)
}
}
-+(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableDictionary *) headers {
++(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSDictionary *) headers {
NSString * normalCase = [headers valueForKey:field];
NSString * ignoredCase = [headers valueForKey:[field lowercaseString]];
diff --git a/ios/RNFetchBlobRequest.h b/ios/RNFetchBlobRequest.h
new file mode 100644
index 000000000..b550ac22e
--- /dev/null
+++ b/ios/RNFetchBlobRequest.h
@@ -0,0 +1,47 @@
+//
+// RNFetchBlobRequest.h
+// RNFetchBlob
+//
+// Created by Artur Chrusciel on 15.01.18.
+// Copyright © 2018 wkh237.github.io. All rights reserved.
+//
+
+#ifndef RNFetchBlobRequest_h
+#define RNFetchBlobRequest_h
+
+#import
+
+#import "RNFetchBlobProgress.h"
+
+#if __has_include()
+#import
+#else
+#import "RCTBridgeModule.h"
+#endif
+
+@interface RNFetchBlobRequest : NSObject
+
+@property (nullable, nonatomic) NSString * taskId;
+@property (nonatomic) long long expectedBytes;
+@property (nonatomic) long long receivedBytes;
+@property (nonatomic) BOOL isServerPush;
+@property (nullable, nonatomic) NSMutableData * respData;
+@property (nullable, strong, nonatomic) RCTResponseSenderBlock callback;
+@property (nullable, nonatomic) RCTBridge * bridge;
+@property (nullable, nonatomic) NSDictionary * options;
+@property (nullable, nonatomic) NSError * error;
+@property (nullable, nonatomic) RNFetchBlobProgress *progressConfig;
+@property (nullable, nonatomic) RNFetchBlobProgress *uploadProgressConfig;
+@property (nullable, nonatomic, weak) NSURLSessionDataTask *task;
+
+- (void) sendRequest:(NSDictionary * _Nullable )options
+ contentLength:(long)contentLength
+ bridge:(RCTBridge * _Nullable)bridgeRef
+ taskId:(NSString * _Nullable)taskId
+ withRequest:(NSURLRequest * _Nullable)req
+ taskOperationQueue:(NSOperationQueue * _Nonnull)operationQueue
+ callback:(_Nullable RCTResponseSenderBlock) callback;
+
+@end
+
+#endif /* RNFetchBlobRequest_h */
diff --git a/ios/RNFetchBlobRequest.m b/ios/RNFetchBlobRequest.m
new file mode 100644
index 000000000..c77668ce8
--- /dev/null
+++ b/ios/RNFetchBlobRequest.m
@@ -0,0 +1,477 @@
+//
+// RNFetchBlobRequest.m
+// RNFetchBlob
+//
+// Created by Artur Chrusciel on 15.01.18.
+// Copyright © 2018 wkh237.github.io. All rights reserved.
+//
+
+#import "RNFetchBlobRequest.h"
+
+#import "RNFetchBlobFS.h"
+#import "RNFetchBlobConst.h"
+#import "RNFetchBlobReqBuilder.h"
+
+#import "IOS7Polyfill.h"
+#import
+
+
+typedef NS_ENUM(NSUInteger, ResponseFormat) {
+ UTF8,
+ BASE64,
+ AUTO
+};
+
+@interface RNFetchBlobRequest ()
+{
+ BOOL respFile;
+ BOOL isNewPart;
+ BOOL isIncrement;
+ NSMutableData * partBuffer;
+ NSString * destPath;
+ NSOutputStream * writeStream;
+ long bodyLength;
+ NSInteger respStatus;
+ NSMutableArray * redirects;
+ ResponseFormat responseFormat;
+ BOOL followRedirect;
+ BOOL backgroundTask;
+}
+
+@end
+
+@implementation RNFetchBlobRequest
+
+@synthesize taskId;
+@synthesize expectedBytes;
+@synthesize receivedBytes;
+@synthesize respData;
+@synthesize callback;
+@synthesize bridge;
+@synthesize options;
+@synthesize error;
+
+
+- (NSString *)md5:(NSString *)input {
+ const char* str = [input UTF8String];
+ unsigned char result[CC_MD5_DIGEST_LENGTH];
+ CC_MD5(str, (CC_LONG)strlen(str), result);
+
+ NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2];
+ for (int i = 0; i 0) {
+ defaultConfigObject.timeoutIntervalForRequest = timeout/1000;
+ }
+
+ defaultConfigObject.HTTPMaximumConnectionsPerHost = 10;
+ session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:operationQueue];
+
+ if (path || [self.options valueForKey:CONFIG_USE_TEMP]) {
+ respFile = YES;
+
+ NSString* cacheKey = taskId;
+ if (key) {
+ cacheKey = [self md5:key];
+
+ if (!cacheKey) {
+ cacheKey = taskId;
+ }
+
+ destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
+
+ if ([[NSFileManager defaultManager] fileExistsAtPath:destPath]) {
+ callback(@[[NSNull null], RESP_TYPE_PATH, destPath]);
+
+ return;
+ }
+ }
+
+ if (path) {
+ destPath = path;
+ } else {
+ destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
+ }
+ } else {
+ respData = [[NSMutableData alloc] init];
+ respFile = NO;
+ }
+
+ self.task = [session dataTaskWithRequest:req];
+ [self.task resume];
+
+ // network status indicator
+ if ([[options objectForKey:CONFIG_INDICATOR] boolValue]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
+ });
+ }
+}
+
+////////////////////////////////////////
+//
+// NSURLSession delegates
+//
+////////////////////////////////////////
+
+
+#pragma mark NSURLSession delegate methods
+
+
+#pragma mark - Received Response
+// set expected content length on response received
+- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
+{
+ expectedBytes = [response expectedContentLength];
+
+ NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
+ NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
+ NSString * respType = @"";
+ respStatus = statusCode;
+
+ if ([response respondsToSelector:@selector(allHeaderFields)])
+ {
+ NSDictionary *headers = [httpResponse allHeaderFields];
+ NSString * respCType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"Content-Type" fromHeaders:headers] lowercaseString];
+
+ if (self.isServerPush) {
+ if (partBuffer) {
+ [self.bridge.eventDispatcher
+ sendDeviceEventWithName:EVENT_SERVER_PUSH
+ body:@{
+ @"taskId": taskId,
+ @"chunk": [partBuffer base64EncodedStringWithOptions:0],
+ }
+ ];
+ }
+
+ partBuffer = [[NSMutableData alloc] init];
+ completionHandler(NSURLSessionResponseAllow);
+
+ return;
+ } else {
+ self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"];
+ }
+
+ if(respCType)
+ {
+ NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE];
+
+ if ([respCType RNFBContainsString:@"text/"]) {
+ respType = @"text";
+ } else if ([respCType RNFBContainsString:@"application/json"]) {
+ respType = @"json";
+ } else if(extraBlobCTypes) { // If extra blob content type is not empty, check if response type matches
+ for (NSString * substr in extraBlobCTypes) {
+ if ([respCType RNFBContainsString:[substr lowercaseString]]) {
+ respType = @"blob";
+ respFile = YES;
+ destPath = [RNFetchBlobFS getTempPath:taskId withExtension:nil];
+ break;
+ }
+ }
+ } else {
+ respType = @"blob";
+
+ // for XMLHttpRequest, switch response data handling strategy automatically
+ if ([options valueForKey:@"auto"]) {
+ respFile = YES;
+ destPath = [RNFetchBlobFS getTempPath:taskId withExtension:@""];
+ }
+ }
+ } else {
+ respType = @"text";
+ }
+
+#pragma mark - handling cookies
+ // # 153 get cookies
+ if (response.URL) {
+ NSHTTPCookieStorage * cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ NSArray * cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: headers forURL:response.URL];
+ if (cookies.count) {
+ [cookieStore setCookies:cookies forURL:response.URL mainDocumentURL:nil];
+ }
+ }
+
+ [self.bridge.eventDispatcher
+ sendDeviceEventWithName: EVENT_STATE_CHANGE
+ body:@{
+ @"taskId": taskId,
+ @"state": @"2",
+ @"headers": headers,
+ @"redirects": redirects,
+ @"respType" : respType,
+ @"timeout" : @NO,
+ @"status": [NSNumber numberWithInteger:statusCode]
+ }
+ ];
+ } else {
+ NSLog(@"oops");
+ }
+
+ if (respFile)
+ {
+ @try{
+ NSFileManager * fm = [NSFileManager defaultManager];
+ NSString * folder = [destPath stringByDeletingLastPathComponent];
+
+ if (![fm fileExistsAtPath:folder]) {
+ [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil];
+ }
+
+ // if not set overwrite in options, defaults to TRUE
+ BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue];
+ BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"];
+
+ appendToExistingFile = !overwrite;
+
+ // For solving #141 append response data if the file already exists
+ // base on PR#139 @kejinliang
+ if (appendToExistingFile) {
+ destPath = [destPath stringByReplacingOccurrencesOfString:@"?append=true" withString:@""];
+ }
+
+ if (![fm fileExistsAtPath:destPath]) {
+ [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil];
+ }
+
+ writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:appendToExistingFile];
+ [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+ [writeStream open];
+ }
+ @catch(NSException * ex)
+ {
+ NSLog(@"write file error");
+ }
+ }
+
+ completionHandler(NSURLSessionResponseAllow);
+}
+
+
+// download progress handler
+- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
+{
+ // For #143 handling multipart/x-mixed-replace response
+ if (self.isServerPush)
+ {
+ [partBuffer appendData:data];
+
+ return ;
+ }
+
+ NSNumber * received = [NSNumber numberWithLong:[data length]];
+ receivedBytes += [received longValue];
+ NSString * chunkString = @"";
+
+ if (isIncrement) {
+ chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ }
+
+ if (respFile) {
+ [writeStream write:[data bytes] maxLength:[data length]];
+ } else {
+ [respData appendData:data];
+ }
+
+ if (expectedBytes == 0) {
+ return;
+ }
+
+ NSNumber * now =[NSNumber numberWithFloat:((float)receivedBytes/(float)expectedBytes)];
+
+ if ([self.progressConfig shouldReport:now]) {
+ [self.bridge.eventDispatcher
+ sendDeviceEventWithName:EVENT_PROGRESS
+ body:@{
+ @"taskId": taskId,
+ @"written": [NSString stringWithFormat:@"%ld", (long) receivedBytes],
+ @"total": [NSString stringWithFormat:@"%ld", (long) expectedBytes],
+ @"chunk": chunkString
+ }
+ ];
+ }
+}
+
+- (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
+{
+ if ([session isEqual:session]) {
+ session = nil;
+ }
+}
+
+
+- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
+{
+
+ self.error = error;
+ NSString * errMsg;
+ NSString * respStr;
+ NSString * rnfbRespType;
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
+ });
+
+ if (error) {
+ if (error.domain == NSURLErrorDomain && error.code == NSURLErrorCancelled) {
+ errMsg = @"task cancelled";
+ } else {
+ errMsg = [error localizedDescription];
+ }
+ }
+
+ if (respFile) {
+ [writeStream close];
+ rnfbRespType = RESP_TYPE_PATH;
+ respStr = destPath;
+ } else { // base64 response
+ // #73 fix unicode data encoding issue :
+ // when response type is BASE64, we should first try to encode the response data to UTF8 format
+ // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
+ // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
+ NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding];
+
+ if (responseFormat == BASE64) {
+ rnfbRespType = RESP_TYPE_BASE64;
+ respStr = [respData base64EncodedStringWithOptions:0];
+ } else if (responseFormat == UTF8) {
+ rnfbRespType = RESP_TYPE_UTF8;
+ respStr = utf8;
+ } else {
+ if (utf8) {
+ rnfbRespType = RESP_TYPE_UTF8;
+ respStr = utf8;
+ } else {
+ rnfbRespType = RESP_TYPE_BASE64;
+ respStr = [respData base64EncodedStringWithOptions:0];
+ }
+ }
+ }
+
+
+ callback(@[
+ errMsg ?: [NSNull null],
+ rnfbRespType ?: @"",
+ respStr ?: [NSNull null]
+ ]);
+
+ respData = nil;
+ receivedBytes = 0;
+ [session finishTasksAndInvalidate];
+
+}
+
+// upload progress handler
+- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesWritten totalBytesExpectedToSend:(int64_t)totalBytesExpectedToWrite
+{
+ if (totalBytesExpectedToWrite == 0) {
+ return;
+ }
+
+ NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)];
+
+ if ([self.uploadProgressConfig shouldReport:now]) {
+ [self.bridge.eventDispatcher
+ sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD
+ body:@{
+ @"taskId": taskId,
+ @"written": [NSString stringWithFormat:@"%ld", (long) totalBytesWritten],
+ @"total": [NSString stringWithFormat:@"%ld", (long) totalBytesExpectedToWrite]
+ }
+ ];
+ }
+}
+
+
+- (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler
+{
+ if ([[options valueForKey:CONFIG_TRUSTY] boolValue]) {
+ completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+ } else {
+ completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+ }
+}
+
+
+- (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
+{
+ NSLog(@"sess done in background");
+}
+
+- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
+{
+
+ if (followRedirect) {
+ if (request.URL) {
+ [redirects addObject:[request.URL absoluteString]];
+ }
+
+ completionHandler(request);
+ } else {
+ completionHandler(nil);
+ }
+}
+
+
+@end