ソースを参照

Merge pull request #8 from flatfox-ag/exception_fixes

Synchronized tables, separated requests and network logic and other...
Travis Nuttall 6 年 前
コミット
1f9a1761ae
No account linked to committer's email address

+ 1
- 1
README.md ファイルの表示

@@ -236,7 +236,7 @@ RNFetchBlob
236 236
     console.log('The file saved to ', res.path())
237 237
     // Beware that when using a file path as Image source on Android,
238 238
     // you must prepend "file://"" before the file path
239
-    imageView = <Image source={{ uri : Platform.OS === 'android' ? 'file://' + res.path()  : '' + res.path() }}/>
239
+    imageView = <Image source={{ uri : Platform.OS === 'android' ? 'file://' + res.path() : '' + res.path() }}/>
240 240
   })
241 241
 ```
242 242
 

+ 17
- 1
android.js ファイルの表示

@@ -38,9 +38,25 @@ function addCompleteDownload(config) {
38 38
     return Promise.reject('RNFetchBlob.android.addCompleteDownload only supports Android.')
39 39
 }
40 40
 
41
+function getSDCardDir() {
42
+  if(Platform.OS === 'android')
43
+    return RNFetchBlob.getSDCardDir()
44
+  else
45
+    return Promise.reject('RNFetchBlob.android.getSDCardDir only supports Android.')
46
+}
47
+
48
+function getSDCardApplicationDir() {
49
+  if(Platform.OS === 'android')
50
+    return RNFetchBlob.getSDCardApplicationDir()
51
+  else
52
+    return Promise.reject('RNFetchBlob.android.getSDCardApplicationDir only supports Android.')
53
+}
54
+
41 55
 
42 56
 export default {
43 57
   actionViewIntent,
44 58
   getContentIntent,
45
-  addCompleteDownload
59
+  addCompleteDownload,
60
+  getSDCardDir,
61
+  getSDCardApplicationDir,
46 62
 }

+ 10
- 5
android/src/main/java/com/RNFetchBlob/RNFetchBlob.java ファイルの表示

@@ -100,7 +100,6 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
100 100
                 RNFetchBlobFS.createFileASCII(path, dataArray, promise);
101 101
             }
102 102
         });
103
-
104 103
     }
105 104
 
106 105
     @ReactMethod
@@ -164,7 +163,6 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
164 163
                 RNFetchBlobFS.cp(path, dest, callback);
165 164
             }
166 165
         });
167
-
168 166
     }
169 167
 
170 168
     @ReactMethod
@@ -225,7 +223,6 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
225 223
                 RNFetchBlobFS.writeFile(path, encoding, data, append, promise);
226 224
             }
227 225
         });
228
-
229 226
     }
230 227
 
231 228
     @ReactMethod
@@ -260,7 +257,6 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
260 257
                 new RNFetchBlobFS(ctx).scanFile(p, m, callback);
261 258
             }
262 259
         });
263
-
264 260
     }
265 261
 
266 262
     @ReactMethod
@@ -331,7 +327,7 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
331 327
     @ReactMethod
332 328
     public void fetchBlob(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, final Callback callback) {
333 329
         new RNFetchBlobReq(options, taskId, method, url, headers, body, null, mClient, callback).run();
334
-}
330
+    }
335 331
 
336 332
     @ReactMethod
337 333
     public void fetchBlobForm(ReadableMap options, String taskId, String method, String url, ReadableMap headers, ReadableArray body, final Callback callback) {
@@ -377,4 +373,13 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
377 373
 
378 374
     }
379 375
 
376
+    @ReactMethod
377
+    public void getSDCardDir(Promise promise) {
378
+        RNFetchBlobFS.getSDCardDir(promise);
379
+    }
380
+
381
+    @ReactMethod
382
+    public void getSDCardApplicationDir(Promise promise) {
383
+        RNFetchBlobFS.getSDCardApplicationDir(this.getReactApplicationContext(), promise);
384
+    }
380 385
 }

+ 27
- 1
android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java ファイルの表示

@@ -246,12 +246,38 @@ class RNFetchBlobFS {
246 246
         state = Environment.getExternalStorageState();
247 247
         if (state.equals(Environment.MEDIA_MOUNTED)) {
248 248
             res.put("SDCardDir", Environment.getExternalStorageDirectory().getAbsolutePath());
249
-            res.put("SDCardApplicationDir", ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath());
249
+            try {
250
+                res.put("SDCardApplicationDir", ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath());
251
+            } catch(Exception e) {
252
+                res.put("SDCardApplicationDir", "");
253
+            }
250 254
         }
251 255
         res.put("MainBundleDir", ctx.getApplicationInfo().dataDir);
252 256
         return res;
253 257
     }
254 258
 
259
+    static public void getSDCardDir(Promise promise) {
260
+        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
261
+            promise.resolve(Environment.getExternalStorageDirectory().getAbsolutePath());
262
+        } else {
263
+            promise.reject("RNFetchBlob.getSDCardDir", "External storage not mounted");
264
+        }
265
+
266
+    }
267
+
268
+    static public void getSDCardApplicationDir(ReactApplicationContext ctx, Promise promise) {
269
+        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
270
+            try {
271
+                final String path = ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath();
272
+                promise.resolve(path);
273
+            } catch (Exception e) {
274
+                promise.reject("RNFetchBlob.getSDCardApplicationDir", e.getLocalizedMessage());
275
+            }
276
+        } else {
277
+            promise.reject("RNFetchBlob.getSDCardApplicationDir", "External storage not mounted");
278
+        }
279
+    }
280
+
255 281
     /**
256 282
      * Static method that returns a temp file path
257 283
      * @param taskId    An unique string for identify

+ 20
- 11
fs.js ファイルの表示

@@ -13,17 +13,26 @@ import RNFetchBlobFile from './class/RNFetchBlobFile'
13 13
 const RNFetchBlob: RNFetchBlobNative = NativeModules.RNFetchBlob
14 14
 
15 15
 const dirs = {
16
-  DocumentDir: RNFetchBlob.DocumentDir,
17
-  CacheDir: RNFetchBlob.CacheDir,
18
-  PictureDir: RNFetchBlob.PictureDir,
19
-  MusicDir: RNFetchBlob.MusicDir,
20
-  MovieDir: RNFetchBlob.MovieDir,
21
-  DownloadDir: RNFetchBlob.DownloadDir,
22
-  DCIMDir: RNFetchBlob.DCIMDir,
23
-  SDCardDir: RNFetchBlob.SDCardDir,
24
-  SDCardApplicationDir: RNFetchBlob.SDCardApplicationDir,
25
-  MainBundleDir: RNFetchBlob.MainBundleDir,
26
-  LibraryDir: RNFetchBlob.LibraryDir
16
+  DocumentDir :  RNFetchBlob.DocumentDir,
17
+  CacheDir : RNFetchBlob.CacheDir,
18
+  PictureDir : RNFetchBlob.PictureDir,
19
+  MusicDir : RNFetchBlob.MusicDir,
20
+  MovieDir : RNFetchBlob.MovieDir,
21
+  DownloadDir : RNFetchBlob.DownloadDir,
22
+  DCIMDir : RNFetchBlob.DCIMDir,
23
+  get SDCardDir() {
24
+    console.warn('SDCardDir as a constant is deprecated and will be removed in feature release. ' +
25
+                 'Use RNFetchBlob.android.getSDCardDir():Promise instead.');
26
+    return RNFetchBlob.SDCardDir;
27
+  },
28
+  get SDCardApplicationDir() {
29
+    console.warn('SDCardApplicationDir as a constant is deprecated and will be removed in feature release. ' +
30
+                 'Use RNFetchBlob.android.getSDCardApplicationDir():Promise instead. ' +
31
+                 'This variable can be empty on error in native code.');
32
+    return RNFetchBlob.SDCardApplicationDir;
33
+  },
34
+  MainBundleDir : RNFetchBlob.MainBundleDir,
35
+  LibraryDir : RNFetchBlob.LibraryDir
27 36
 }
28 37
 
29 38
 function addCode(code: string, error: Error): Error {

+ 7
- 1
ios/RNFetchBlob.xcodeproj/project.pbxproj ファイルの表示

@@ -7,6 +7,7 @@
7 7
 	objects = {
8 8
 
9 9
 /* Begin PBXBuildFile section */
10
+		8C4801A6200CF71700FED7ED /* RNFetchBlobRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */; };
10 11
 		A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */; };
11 12
 		A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */; };
12 13
 		A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */; };
@@ -29,6 +30,8 @@
29 30
 /* End PBXCopyFilesBuildPhase section */
30 31
 
31 32
 /* Begin PBXFileReference section */
33
+		8C4801A4200CF71700FED7ED /* RNFetchBlobRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobRequest.h; sourceTree = "<group>"; };
34
+		8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobRequest.m; sourceTree = "<group>"; };
32 35
 		A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobFS.m; sourceTree = "<group>"; };
33 36
 		A158F4281D052E57006FFD38 /* RNFetchBlobFS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobFS.h; sourceTree = "<group>"; };
34 37
 		A158F4291D0534A9006FFD38 /* RNFetchBlobConst.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobConst.h; sourceTree = "<group>"; };
@@ -71,8 +74,10 @@
71 74
 				A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */,
72 75
 				A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */,
73 76
 				A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */,
74
-				A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */,
75 77
 				A158F42E1D0539CE006FFD38 /* RNFetchBlobNetwork.h */,
78
+				A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */,
79
+				8C4801A4200CF71700FED7ED /* RNFetchBlobRequest.h */,
80
+				8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */,
76 81
 				A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */,
77 82
 				A158F4291D0534A9006FFD38 /* RNFetchBlobConst.h */,
78 83
 				A158F4281D052E57006FFD38 /* RNFetchBlobFS.h */,
@@ -149,6 +154,7 @@
149 154
 			buildActionMask = 2147483647;
150 155
 			files = (
151 156
 				A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */,
157
+				8C4801A6200CF71700FED7ED /* RNFetchBlobRequest.m in Sources */,
152 158
 				A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */,
153 159
 				A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */,
154 160
 				A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */,

+ 0
- 1
ios/RNFetchBlob/RNFetchBlob.h ファイルの表示

@@ -39,7 +39,6 @@
39 39
 @property (retain) UIDocumentInteractionController * documentController;
40 40
 
41 41
 + (RCTBridge *)getRCTBridge;
42
-+ (void) checkExpiredSessions;
43 42
 
44 43
 @end
45 44
 

+ 16
- 8
ios/RNFetchBlob/RNFetchBlob.m ファイルの表示

@@ -38,7 +38,7 @@ dispatch_queue_t fsQueue;
38 38
 
39 39
 + (RCTBridge *)getRCTBridge
40 40
 {
41
-    RCTRootView * rootView = [[UIApplication sharedApplication] keyWindow].rootViewController.view;
41
+    RCTRootView * rootView = (RCTRootView*) [[UIApplication sharedApplication] keyWindow].rootViewController.view;
42 42
     return rootView.bridge;
43 43
 }
44 44
 
@@ -101,8 +101,12 @@ RCT_EXPORT_METHOD(fetchBlobForm:(NSDictionary *)options
101 101
         // send HTTP request
102 102
         else
103 103
         {
104
-            RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init];
105
-            [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback];
104
+            [[RNFetchBlobNetwork sharedInstance] sendRequest:options
105
+                                               contentLength:bodyLength
106
+                                                      bridge:self.bridge
107
+                                                      taskId:taskId
108
+                                                 withRequest:req
109
+                                                    callback:callback];
106 110
         }
107 111
     }];
108 112
 
@@ -133,8 +137,12 @@ RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options
133 137
         // send HTTP request
134 138
         else
135 139
         {
136
-            __block RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init];
137
-            [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback];
140
+            [[RNFetchBlobNetwork sharedInstance] sendRequest:options
141
+                                               contentLength:bodyLength
142
+                                                      bridge:self.bridge
143
+                                                      taskId:taskId
144
+                                                 withRequest:req
145
+                                                    callback:callback];
138 146
         }
139 147
     }];
140 148
 }
@@ -523,7 +531,7 @@ RCT_EXPORT_METHOD(getEnvironmentDirs:(RCTResponseSenderBlock) callback)
523 531
 
524 532
 #pragma mark - net.cancelRequest
525 533
 RCT_EXPORT_METHOD(cancelRequest:(NSString *)taskId callback:(RCTResponseSenderBlock)callback) {
526
-    [RNFetchBlobNetwork cancelRequest:taskId];
534
+    [[RNFetchBlobNetwork sharedInstance] cancelRequest:taskId];
527 535
     callback(@[[NSNull null], taskId]);
528 536
 
529 537
 }
@@ -533,14 +541,14 @@ RCT_EXPORT_METHOD(enableProgressReport:(NSString *)taskId interval:(nonnull NSNu
533 541
 {
534 542
 
535 543
     RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Download interval:interval count:count];
536
-    [RNFetchBlobNetwork enableProgressReport:taskId config:cfg];
544
+    [[RNFetchBlobNetwork sharedInstance] enableProgressReport:taskId config:cfg];
537 545
 }
538 546
 
539 547
 #pragma mark - net.enableUploadProgressReport
540 548
 RCT_EXPORT_METHOD(enableUploadProgressReport:(NSString *)taskId interval:(nonnull NSNumber*)interval count:(nonnull NSNumber*)count)
541 549
 {
542 550
     RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Upload interval:interval count:count];
543
-    [RNFetchBlobNetwork enableUploadProgress:taskId config:cfg];
551
+    [[RNFetchBlobNetwork sharedInstance] enableUploadProgress:taskId config:cfg];
544 552
 }
545 553
 
546 554
 #pragma mark - fs.slice

+ 28
- 28
ios/RNFetchBlobConst.m ファイルの表示

@@ -7,38 +7,38 @@
7 7
 //
8 8
 #import "RNFetchBlobConst.h"
9 9
 
10
-extern NSString *const FILE_PREFIX = @"RNFetchBlob-file://";
11
-extern NSString *const ASSET_PREFIX = @"bundle-assets://";
12
-extern NSString *const AL_PREFIX = @"assets-library://";
10
+NSString *const FILE_PREFIX = @"RNFetchBlob-file://";
11
+NSString *const ASSET_PREFIX = @"bundle-assets://";
12
+NSString *const AL_PREFIX = @"assets-library://";
13 13
 
14 14
 // fetch configs
15
-extern NSString *const CONFIG_USE_TEMP = @"fileCache";
16
-extern NSString *const CONFIG_FILE_PATH = @"path";
17
-extern NSString *const CONFIG_FILE_EXT = @"appendExt";
18
-extern NSString *const CONFIG_TRUSTY = @"trusty";
19
-extern NSString *const CONFIG_INDICATOR = @"indicator";
20
-extern NSString *const CONFIG_KEY = @"key";
21
-extern NSString *const CONFIG_EXTRA_BLOB_CTYPE = @"binaryContentTypes";
15
+NSString *const CONFIG_USE_TEMP = @"fileCache";
16
+NSString *const CONFIG_FILE_PATH = @"path";
17
+NSString *const CONFIG_FILE_EXT = @"appendExt";
18
+NSString *const CONFIG_TRUSTY = @"trusty";
19
+NSString *const CONFIG_INDICATOR = @"indicator";
20
+NSString *const CONFIG_KEY = @"key";
21
+NSString *const CONFIG_EXTRA_BLOB_CTYPE = @"binaryContentTypes";
22 22
 
23
-extern NSString *const EVENT_STATE_CHANGE = @"RNFetchBlobState";
24
-extern NSString *const EVENT_SERVER_PUSH = @"RNFetchBlobServerPush";
25
-extern NSString *const EVENT_PROGRESS = @"RNFetchBlobProgress";
26
-extern NSString *const EVENT_PROGRESS_UPLOAD = @"RNFetchBlobProgress-upload";
27
-extern NSString *const EVENT_EXPIRE = @"RNFetchBlobExpire";
23
+NSString *const EVENT_STATE_CHANGE = @"RNFetchBlobState";
24
+NSString *const EVENT_SERVER_PUSH = @"RNFetchBlobServerPush";
25
+NSString *const EVENT_PROGRESS = @"RNFetchBlobProgress";
26
+NSString *const EVENT_PROGRESS_UPLOAD = @"RNFetchBlobProgress-upload";
27
+NSString *const EVENT_EXPIRE = @"RNFetchBlobExpire";
28 28
 
29
-extern NSString *const MSG_EVENT = @"RNFetchBlobMessage";
30
-extern NSString *const MSG_EVENT_LOG = @"log";
31
-extern NSString *const MSG_EVENT_WARN = @"warn";
32
-extern NSString *const MSG_EVENT_ERROR = @"error";
33
-extern NSString *const FS_EVENT_DATA = @"data";
34
-extern NSString *const FS_EVENT_END = @"end";
35
-extern NSString *const FS_EVENT_WARN = @"warn";
36
-extern NSString *const FS_EVENT_ERROR = @"error";
29
+NSString *const MSG_EVENT = @"RNFetchBlobMessage";
30
+NSString *const MSG_EVENT_LOG = @"log";
31
+NSString *const MSG_EVENT_WARN = @"warn";
32
+NSString *const MSG_EVENT_ERROR = @"error";
33
+NSString *const FS_EVENT_DATA = @"data";
34
+NSString *const FS_EVENT_END = @"end";
35
+NSString *const FS_EVENT_WARN = @"warn";
36
+NSString *const FS_EVENT_ERROR = @"error";
37 37
 
38
-extern NSString *const KEY_REPORT_PROGRESS = @"reportProgress";
39
-extern NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress";
38
+NSString *const KEY_REPORT_PROGRESS = @"reportProgress";
39
+NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress";
40 40
 
41 41
 // response type
42
-extern NSString *const RESP_TYPE_BASE64 = @"base64";
43
-extern NSString *const RESP_TYPE_UTF8 = @"utf8";
44
-extern NSString *const RESP_TYPE_PATH = @"path";
42
+NSString *const RESP_TYPE_BASE64 = @"base64";
43
+NSString *const RESP_TYPE_UTF8 = @"utf8";
44
+NSString *const RESP_TYPE_PATH = @"path";

+ 2
- 2
ios/RNFetchBlobFS.h ファイルの表示

@@ -34,8 +34,8 @@
34 34
     NSString * streamId;
35 35
 }
36 36
 
37
-@property (nonatomic) NSOutputStream * outStream;
38
-@property (nonatomic) NSInputStream * inStream;
37
+@property (nonatomic) NSOutputStream * _Nullable outStream;
38
+@property (nonatomic) NSInputStream * _Nullable inStream;
39 39
 @property (strong, nonatomic) RCTResponseSenderBlock callback;
40 40
 @property (nonatomic) RCTBridge * bridge;
41 41
 @property (nonatomic) NSString * encoding;

+ 16
- 28
ios/RNFetchBlobNetwork.h ファイルの表示

@@ -6,9 +6,13 @@
6 6
 //  Copyright © 2016 wkh237. All rights reserved.
7 7
 //
8 8
 
9
+#ifndef RNFetchBlobNetwork_h
10
+#define RNFetchBlobNetwork_h
11
+
9 12
 #import <Foundation/Foundation.h>
10 13
 #import "RNFetchBlobProgress.h"
11 14
 #import "RNFetchBlobFS.h"
15
+#import "RNFetchBlobRequest.h"
12 16
 
13 17
 #if __has_include(<React/RCTAssert.h>)
14 18
 #import <React/RCTBridgeModule.h>
@@ -16,42 +20,26 @@
16 20
 #import "RCTBridgeModule.h"
17 21
 #endif
18 22
 
19
-#ifndef RNFetchBlobNetwork_h
20
-#define RNFetchBlobNetwork_h
21
-
22
-
23
-
24
-typedef void(^CompletionHander)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error);
25
-typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse * _Nullable response, NSError * _Nullable error);
26 23
 
27 24
 @interface RNFetchBlobNetwork : NSObject  <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
28 25
 
29
-@property (nullable, nonatomic) NSString * taskId;
30
-@property (nonatomic) int expectedBytes;
31
-@property (nonatomic) int receivedBytes;
32
-@property (nonatomic) BOOL isServerPush;
33
-@property (nullable, nonatomic) NSMutableData * respData;
34
-@property (strong, nonatomic) RCTResponseSenderBlock callback;
35
-@property (nullable, nonatomic) RCTBridge * bridge;
36
-@property (nullable, nonatomic) NSDictionary * options;
37
-@property (nullable, nonatomic) RNFetchBlobFS * fileStream;
38
-@property (strong, nonatomic) CompletionHander fileTaskCompletionHandler;
39
-@property (strong, nonatomic) DataTaskCompletionHander dataTaskCompletionHandler;
40
-@property (nullable, nonatomic) NSError * error;
41
-
26
+@property(nonnull, nonatomic) NSOperationQueue *taskQueue;
27
+@property(nonnull, nonatomic) NSMapTable<NSString*, RNFetchBlobRequest*> * requestsTable;
42 28
 
29
++ (RNFetchBlobNetwork* _Nullable)sharedInstance;
43 30
 + (NSMutableDictionary  * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers;
44
-+ (void) cancelRequest:(NSString *)taskId;
45
-+ (void) enableProgressReport:(NSString *) taskId;
46
-+ (void) enableUploadProgress:(NSString *) taskId;
47 31
 + (void) emitExpiredTasks;
48 32
 
49 33
 - (nullable id) init;
50
-- (void) sendRequest;
51
-- (void) sendRequest:(NSDictionary  * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback;
52
-+ (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config;
53
-+ (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config;
54
-
34
+- (void) sendRequest:(NSDictionary  * _Nullable )options
35
+       contentLength:(long)contentLength
36
+              bridge:(RCTBridge * _Nullable)bridgeRef
37
+              taskId:(NSString * _Nullable)taskId
38
+         withRequest:(NSURLRequest * _Nullable)req
39
+            callback:(_Nullable RCTResponseSenderBlock) callback;
40
+- (void) cancelRequest:(NSString * _Nonnull)taskId;
41
+- (void) enableProgressReport:(NSString * _Nonnull) taskId config:(RNFetchBlobProgress * _Nullable)config;
42
+- (void) enableUploadProgress:(NSString * _Nonnull) taskId config:(RNFetchBlobProgress * _Nullable)config;
55 43
 
56 44
 
57 45
 @end

+ 67
- 550
ios/RNFetchBlobNetwork.m ファイルの表示

@@ -8,13 +8,10 @@
8 8
 
9 9
 
10 10
 #import <Foundation/Foundation.h>
11
-#import "RNFetchBlob.h"
12
-#import "RNFetchBlobFS.h"
13 11
 #import "RNFetchBlobNetwork.h"
12
+
13
+#import "RNFetchBlob.h"
14 14
 #import "RNFetchBlobConst.h"
15
-#import "RNFetchBlobReqBuilder.h"
16
-#import "IOS7Polyfill.h"
17
-#import <CommonCrypto/CommonDigest.h>
18 15
 #import "RNFetchBlobProgress.h"
19 16
 
20 17
 #if __has_include(<React/RCTAssert.h>)
@@ -35,130 +32,43 @@
35 32
 //
36 33
 ////////////////////////////////////////
37 34
 
38
-NSMapTable * taskTable;
39 35
 NSMapTable * expirationTable;
40
-NSMutableDictionary * progressTable;
41
-NSMutableDictionary * uploadProgressTable;
42 36
 
43 37
 __attribute__((constructor))
44 38
 static void initialize_tables() {
45
-    if(expirationTable == nil)
46
-    {
39
+    if (expirationTable == nil) {
47 40
         expirationTable = [[NSMapTable alloc] init];
48 41
     }
49
-    if(taskTable == nil)
50
-    {
51
-        taskTable = [[NSMapTable alloc] init];
52
-    }
53
-    if(progressTable == nil)
54
-    {
55
-        progressTable = [[NSMutableDictionary alloc] init];
56
-    }
57
-    if(uploadProgressTable == nil)
58
-    {
59
-        uploadProgressTable = [[NSMutableDictionary alloc] init];
60
-    }
61
-}
62
-
63
-
64
-typedef NS_ENUM(NSUInteger, ResponseFormat) {
65
-    UTF8,
66
-    BASE64,
67
-    AUTO
68
-};
69
-
70
-
71
-@interface RNFetchBlobNetwork ()
72
-{
73
-    BOOL * respFile;
74
-    BOOL isNewPart;
75
-    BOOL * isIncrement;
76
-    NSMutableData * partBuffer;
77
-    NSString * destPath;
78
-    NSOutputStream * writeStream;
79
-    long bodyLength;
80
-    NSMutableDictionary * respInfo;
81
-    NSInteger respStatus;
82
-    NSMutableArray * redirects;
83
-    ResponseFormat responseFormat;
84
-    BOOL * followRedirect;
85
-    BOOL backgroundTask;
86 42
 }
87 43
 
88
-@end
89 44
 
90 45
 @implementation RNFetchBlobNetwork
91 46
 
92
-NSOperationQueue *taskQueue;
93
-@synthesize taskId;
94
-@synthesize expectedBytes;
95
-@synthesize receivedBytes;
96
-@synthesize respData;
97
-@synthesize callback;
98
-@synthesize bridge;
99
-@synthesize options;
100
-@synthesize fileTaskCompletionHandler;
101
-@synthesize dataTaskCompletionHandler;
102
-@synthesize error;
103 47
 
104
-
105
-// constructor
106 48
 - (id)init {
107 49
     self = [super init];
108
-    if(taskQueue == nil) {
109
-        @synchronized ([RNFetchBlobNetwork class]) {
110
-            if (taskQueue == nil) {
111
-                taskQueue = [[NSOperationQueue alloc] init];
112
-                taskQueue.maxConcurrentOperationCount = 10;
113
-            }
114
-        }
50
+    if (self) {
51
+        self.requestsTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
52
+        
53
+        self.taskQueue = [[NSOperationQueue alloc] init];
54
+        self.taskQueue.qualityOfService = NSQualityOfServiceUtility;
55
+        self.taskQueue.maxConcurrentOperationCount = 10;
115 56
     }
57
+    
116 58
     return self;
117 59
 }
118 60
 
119
-+ (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config
120
-{
121
-    if(progressTable == nil)
122
-    {
123
-        progressTable = [[NSMutableDictionary alloc] init];
124
-    }
125
-    [progressTable setValue:config forKey:taskId];
126
-}
127
-
128
-+ (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config
129
-{
130
-    if(uploadProgressTable == nil)
131
-    {
132
-        uploadProgressTable = [[NSMutableDictionary alloc] init];
133
-    }
134
-    [uploadProgressTable setValue:config forKey:taskId];
135
-}
136
-
137
-// removing case from headers
138
-+ (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers
139
-{
140
-
141
-    NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init];
142
-    for(NSString * key in headers) {
143
-        [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]];
144
-    }
145
-
146
-    return mheaders;
147
-}
148
-
149
-- (NSString *)md5:(NSString *)input {
150
-    const char* str = [input UTF8String];
151
-    unsigned char result[CC_MD5_DIGEST_LENGTH];
152
-    CC_MD5(str, (CC_LONG)strlen(str), result);
61
++ (RNFetchBlobNetwork* _Nullable)sharedInstance {
62
+    static id _sharedInstance = nil;
63
+    static dispatch_once_t onceToken;
153 64
 
154
-    NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2];
155
-    for(int i = 0; i<CC_MD5_DIGEST_LENGTH; i++) {
156
-        [ret appendFormat:@"%02x",result[i]];
157
-    }
158
-    return ret;
65
+    dispatch_once(&onceToken, ^{
66
+        _sharedInstance = [[self alloc] init];
67
+    });
68
+    
69
+    return _sharedInstance;
159 70
 }
160 71
 
161
-// send HTTP request
162 72
 - (void) sendRequest:(__weak NSDictionary  * _Nullable )options
163 73
        contentLength:(long) contentLength
164 74
               bridge:(RCTBridge * _Nullable)bridgeRef
@@ -166,473 +76,80 @@ NSOperationQueue *taskQueue;
166 76
          withRequest:(__weak NSURLRequest * _Nullable)req
167 77
             callback:(_Nullable RCTResponseSenderBlock) callback
168 78
 {
169
-    self.taskId = taskId;
170
-    self.respData = [[NSMutableData alloc] initWithLength:0];
171
-    self.callback = callback;
172
-    self.bridge = bridgeRef;
173
-    self.expectedBytes = 0;
174
-    self.receivedBytes = 0;
175
-    self.options = options;
176
-    
177
-    backgroundTask = [options valueForKey:@"IOSBackgroundTask"] == nil ? NO : [[options valueForKey:@"IOSBackgroundTask"] boolValue];
178
-    followRedirect = [options valueForKey:@"followRedirect"] == nil ? YES : [[options valueForKey:@"followRedirect"] boolValue];
179
-    isIncrement = [options valueForKey:@"increment"] == nil ? NO : [[options valueForKey:@"increment"] boolValue];
180
-    redirects = [[NSMutableArray alloc] init];
181
-    if(req.URL != nil)
182
-        [redirects addObject:req.URL.absoluteString];
183
-
184
-    // set response format
185
-    NSString * rnfbResp = [req.allHTTPHeaderFields valueForKey:@"RNFB-Response"];
186
-    if([[rnfbResp lowercaseString] isEqualToString:@"base64"])
187
-        responseFormat = BASE64;
188
-    else if([[rnfbResp lowercaseString] isEqualToString:@"utf8"])
189
-        responseFormat = UTF8;
190
-    else
191
-        responseFormat = AUTO;
192
-
193
-    NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
194
-    NSString * ext = [self.options valueForKey:CONFIG_FILE_EXT];
195
-	NSString * key = [self.options valueForKey:CONFIG_KEY];
196
-    __block NSURLSession * session;
197
-
198
-    bodyLength = contentLength;
199
-
200
-    // the session trust any SSL certification
201
-    NSURLSessionConfiguration *defaultConfigObject;
202
-
203
-    defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
204
-
205
-    if(backgroundTask)
206
-    {
207
-        defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId];
208
-    }
209
-
210
-    // set request timeout
211
-    float timeout = [options valueForKey:@"timeout"] == nil ? -1 : [[options valueForKey:@"timeout"] floatValue];
212
-    if(timeout > 0)
213
-    {
214
-        defaultConfigObject.timeoutIntervalForRequest = timeout/1000;
215
-    }
216
-    defaultConfigObject.HTTPMaximumConnectionsPerHost = 10;
217
-    session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:taskQueue];
218
-    if(path != nil || [self.options valueForKey:CONFIG_USE_TEMP]!= nil)
219
-    {
220
-        respFile = YES;
221
-
222
-		NSString* cacheKey = taskId;
223
-		if (key != nil) {
224
-            cacheKey = [self md5:key];
225
-			if (cacheKey == nil) {
226
-				cacheKey = taskId;
227
-			}
228
-
229
-			destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
230
-            if ([[NSFileManager defaultManager] fileExistsAtPath:destPath]) {
231
-				callback(@[[NSNull null], RESP_TYPE_PATH, destPath]);
232
-                return;
233
-            }
234
-		}
235
-
236
-        if(path != nil)
237
-            destPath = path;
238
-        else
239
-            destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
240
-    }
241
-    else
242
-    {
243
-        respData = [[NSMutableData alloc] init];
244
-        respFile = NO;
245
-    }
246
-
247
-    __block NSURLSessionDataTask * task = [session dataTaskWithRequest:req];
79
+    RNFetchBlobRequest *request = [[RNFetchBlobRequest alloc] init];
80
+    [request sendRequest:options
81
+           contentLength:contentLength
82
+                  bridge:bridgeRef
83
+                  taskId:taskId
84
+             withRequest:req
85
+      taskOperationQueue:self.taskQueue
86
+                callback:callback];
248 87
     
249
-    [taskTable setObject:@{ @"session" : task, @"isCancelled" : @NO } forKey:taskId];
250
-    [task resume];
251
-
252
-    // network status indicator
253
-    if ([[options objectForKey:CONFIG_INDICATOR] boolValue] == YES) {
254
-        dispatch_async(dispatch_get_main_queue(), ^{
255
-            [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
256
-        });
257
-    }
258
-    __block UIApplication * app = [UIApplication sharedApplication];
259
-
260
-}
261
-
262
-// #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled
263
-+ (void) emitExpiredTasks
264
-{
265
-    NSEnumerator * emu =  [expirationTable keyEnumerator];
266
-    NSString * key;
267
-
268
-    while((key = [emu nextObject]))
269
-    {
270
-        RCTBridge * bridge = [RNFetchBlob getRCTBridge];
271
-        NSData * args = @{ @"taskId": key };
272
-        [bridge.eventDispatcher sendDeviceEventWithName:EVENT_EXPIRE body:args];
273
-
88
+    @synchronized([RNFetchBlobNetwork class]) {
89
+        [self.requestsTable setObject:request forKey:taskId];
274 90
     }
275
-
276
-    // clear expired task entries
277
-    [expirationTable removeAllObjects];
278
-    expirationTable = [[NSMapTable alloc] init];
279
-
280 91
 }
281 92
 
282
-////////////////////////////////////////
283
-//
284
-//  NSURLSession delegates
285
-//
286
-////////////////////////////////////////
287
-
288
-
289
-#pragma mark NSURLSession delegate methods
290
-
291
-
292
-#pragma mark - Received Response
293
-// set expected content length on response received
294
-- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
93
+- (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config
295 94
 {
296
-    expectedBytes = [response expectedContentLength];
297
-
298
-    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
299
-    NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
300
-    NSString * respType = @"";
301
-    respStatus = statusCode;
302
-    if ([response respondsToSelector:@selector(allHeaderFields)])
303
-    {
304
-        NSDictionary *headers = [httpResponse allHeaderFields];
305
-        NSString * respCType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"Content-Type" fromHeaders:headers] lowercaseString];
306
-        if(self.isServerPush == NO)
307
-        {
308
-            self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"];
309
-        }
310
-        if(self.isServerPush)
311
-        {
312
-            if(partBuffer != nil)
313
-            {
314
-                [self.bridge.eventDispatcher
315
-                 sendDeviceEventWithName:EVENT_SERVER_PUSH
316
-                 body:@{
317
-                        @"taskId": taskId,
318
-                        @"chunk": [partBuffer base64EncodedStringWithOptions:0],
319
-                        }
320
-                 ];
321
-            }
322
-            partBuffer = [[NSMutableData alloc] init];
323
-            completionHandler(NSURLSessionResponseAllow);
324
-            return;
325
-        }
326
-        if(respCType != nil)
327
-        {
328
-            NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE];
329
-            if([respCType RNFBContainsString:@"text/"])
330
-            {
331
-                respType = @"text";
332
-            }
333
-            else if([respCType RNFBContainsString:@"application/json"])
334
-            {
335
-                respType = @"json";
336
-            }
337
-            // If extra blob content type is not empty, check if response type matches
338
-            else if( extraBlobCTypes !=  nil) {
339
-                for(NSString * substr in extraBlobCTypes)
340
-                {
341
-                    if([respCType RNFBContainsString:[substr lowercaseString]])
342
-                    {
343
-                        respType = @"blob";
344
-                        respFile = YES;
345
-                        destPath = [RNFetchBlobFS getTempPath:taskId withExtension:nil];
346
-                        break;
347
-                    }
348
-                }
349
-            }
350
-            else
351
-            {
352
-                respType = @"blob";
353
-                // for XMLHttpRequest, switch response data handling strategy automatically
354
-                if([options valueForKey:@"auto"] == YES) {
355
-                    respFile = YES;
356
-                    destPath = [RNFetchBlobFS getTempPath:taskId withExtension:@""];
357
-                }
358
-            }
359
-        }
360
-        else
361
-            respType = @"text";
362
-        respInfo = @{
363
-                     @"taskId": taskId,
364
-                     @"state": @"2",
365
-                     @"headers": headers,
366
-                     @"redirects": redirects,
367
-                     @"respType" : respType,
368
-                     @"timeout" : @NO,
369
-                     @"status": [NSNumber numberWithInteger:statusCode]
370
-                    };
371
-
372
-#pragma mark - handling cookies
373
-        // # 153 get cookies
374
-        if(response.URL != nil)
375
-        {
376
-            NSHTTPCookieStorage * cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage];
377
-            NSArray<NSHTTPCookie *> * cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: headers forURL:response.URL];
378
-            if(cookies != nil && [cookies count] > 0) {
379
-                [cookieStore setCookies:cookies forURL:response.URL mainDocumentURL:nil];
380
-            }
381
-        }
382
-
383
-        [self.bridge.eventDispatcher
384
-         sendDeviceEventWithName: EVENT_STATE_CHANGE
385
-         body:respInfo
386
-        ];
387
-        headers = nil;
388
-        respInfo = nil;
389
-
390
-    }
391
-    else
392
-        NSLog(@"oops");
393
-
394
-    if(respFile == YES)
395
-    {
396
-        @try{
397
-            NSFileManager * fm = [NSFileManager defaultManager];
398
-            NSString * folder = [destPath stringByDeletingLastPathComponent];
399
-            if(![fm fileExistsAtPath:folder])
400
-            {
401
-                [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil];
402
-            }
403
-            BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue];
404
-            BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"];
405
-
406
-            appendToExistingFile = !overwrite;
407
-
408
-            // For solving #141 append response data if the file already exists
409
-            // base on PR#139 @kejinliang
410
-            if(appendToExistingFile)
411
-            {
412
-                destPath = [destPath stringByReplacingOccurrencesOfString:@"?append=true" withString:@""];
413
-            }
414
-            if (![fm fileExistsAtPath:destPath])
415
-            {
416
-                [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil];
417
-            }
418
-            writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:appendToExistingFile];
419
-            [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
420
-            [writeStream open];
421
-        }
422
-        @catch(NSException * ex)
423
-        {
424
-            NSLog(@"write file error");
95
+    if (config) {
96
+        @synchronized ([RNFetchBlobNetwork class]) {
97
+            [self.requestsTable objectForKey:taskId].progressConfig = config;
425 98
         }
426 99
     }
427
-
428
-    completionHandler(NSURLSessionResponseAllow);
429
-}
430
-
431
-
432
-// download progress handler
433
-- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
434
-{
435
-    // For #143 handling multipart/x-mixed-replace response
436
-    if(self.isServerPush)
437
-    {
438
-        [partBuffer appendData:data];
439
-        return ;
440
-    }
441
-
442
-    NSNumber * received = [NSNumber numberWithLong:[data length]];
443
-    receivedBytes += [received longValue];
444
-    NSString * chunkString = @"";
445
-
446
-    if(isIncrement == YES)
447
-    {
448
-        chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
449
-    }
450
-
451
-    if(respFile == NO)
452
-    {
453
-        [respData appendData:data];
454
-    }
455
-    else
456
-    {
457
-        [writeStream write:[data bytes] maxLength:[data length]];
458
-    }
459
-    RNFetchBlobProgress * pconfig = [progressTable valueForKey:taskId];
460
-    if(expectedBytes == 0)
461
-        return;
462
-    NSNumber * now =[NSNumber numberWithFloat:((float)receivedBytes/(float)expectedBytes)];
463
-    if(pconfig != nil && [pconfig shouldReport:now])
464
-    {
465
-        [self.bridge.eventDispatcher
466
-         sendDeviceEventWithName:EVENT_PROGRESS
467
-         body:@{
468
-                @"taskId": taskId,
469
-                @"written": [NSString stringWithFormat:@"%d", receivedBytes],
470
-                @"total": [NSString stringWithFormat:@"%d", expectedBytes],
471
-                @"chunk": chunkString
472
-            }
473
-         ];
474
-    }
475
-    received = nil;
476
-
477
-}
478
-
479
-- (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
480
-{
481
-    if([session isEqual:session])
482
-        session = nil;
483 100
 }
484 101
 
485
-
486
-- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
102
+- (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config
487 103
 {
488
-
489
-    self.error = error;
490
-    NSString * errMsg = [NSNull null];
491
-    NSString * respStr = [NSNull null];
492
-    NSString * rnfbRespType = @"";
493
-
494
-    dispatch_async(dispatch_get_main_queue(), ^{
495
-        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
496
-    });
497
-
498
-    if(respInfo == nil)
499
-    {
500
-        respInfo = [NSNull null];
501
-    }
502
-
503
-    if(error != nil)
504
-    {
505
-        errMsg = [error localizedDescription];
506
-    }
507
-    NSDictionary * taskSession = [taskTable objectForKey:taskId];
508
-    BOOL isCancelled = [[taskSession valueForKey:@"isCancelled"] boolValue];
509
-    if(isCancelled) {
510
-        errMsg = @"task cancelled";
511
-    }
512
-
513
-    if(respFile == YES)
514
-    {
515
-        [writeStream close];
516
-        rnfbRespType = RESP_TYPE_PATH;
517
-        respStr = destPath;
518
-    }
519
-    // base64 response
520
-    else {
521
-        // #73 fix unicode data encoding issue :
522
-        // when response type is BASE64, we should first try to encode the response data to UTF8 format
523
-        // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
524
-        // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
525
-        NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding];
526
-
527
-        if(responseFormat == BASE64)
528
-        {
529
-            rnfbRespType = RESP_TYPE_BASE64;
530
-            respStr = [respData base64EncodedStringWithOptions:0];
531
-        }
532
-        else if (responseFormat == UTF8)
533
-        {
534
-            rnfbRespType = RESP_TYPE_UTF8;
535
-            respStr = utf8;
536
-        }
537
-        else
538
-        {
539
-            if(utf8 != nil)
540
-            {
541
-                rnfbRespType = RESP_TYPE_UTF8;
542
-                respStr = utf8;
543
-            }
544
-            else
545
-            {
546
-                rnfbRespType = RESP_TYPE_BASE64;
547
-                respStr = [respData base64EncodedStringWithOptions:0];
548
-            }
104
+    if (config) {
105
+        @synchronized ([RNFetchBlobNetwork class]) {
106
+            [self.requestsTable objectForKey:taskId].uploadProgressConfig = config;
549 107
         }
550 108
     }
551
-
552
-
553
-    callback(@[ errMsg, rnfbRespType, respStr]);
554
-
555
-    @synchronized(taskTable, uploadProgressTable, progressTable)
556
-    {
557
-        if([taskTable objectForKey:taskId] == nil)
558
-            NSLog(@"object released by ARC.");
559
-        else
560
-            [taskTable removeObjectForKey:taskId];
561
-        [uploadProgressTable removeObjectForKey:taskId];
562
-        [progressTable removeObjectForKey:taskId];
563
-    }
564
-
565
-    respData = nil;
566
-    receivedBytes = 0;
567
-    [session finishTasksAndInvalidate];
568
-
569
-}
570
-
571
-// upload progress handler
572
-- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesWritten totalBytesExpectedToSend:(int64_t)totalBytesExpectedToWrite
573
-{
574
-    RNFetchBlobProgress * pconfig = [uploadProgressTable valueForKey:taskId];
575
-    if(totalBytesExpectedToWrite == 0)
576
-        return;
577
-    NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)];
578
-    if(pconfig != nil && [pconfig shouldReport:now]) {
579
-        [self.bridge.eventDispatcher
580
-         sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD
581
-         body:@{
582
-                @"taskId": taskId,
583
-                @"written": [NSString stringWithFormat:@"%d", totalBytesWritten],
584
-                @"total": [NSString stringWithFormat:@"%d", totalBytesExpectedToWrite]
585
-                }
586
-         ];
587
-    }
588 109
 }
589 110
 
590
-+ (void) cancelRequest:(NSString *)taskId
111
+- (void) cancelRequest:(NSString *)taskId
591 112
 {
592
-    NSDictionary * task = [taskTable objectForKey:taskId];
113
+    NSURLSessionDataTask * task;
593 114
     
594
-    if(task != nil) {
595
-        NSURLSessionDataTask * session = [task objectForKey:@"session"];
596
-        if(session.state == NSURLSessionTaskStateRunning) {
597
-            [task setValue:@NO forKey:@"isCancelled"];
598
-            [session cancel];
599
-        }
115
+    @synchronized ([RNFetchBlobNetwork class]) {
116
+        task = [self.requestsTable objectForKey:taskId].task;
600 117
     }
601 118
     
602
-}
603
-
604
-
605
-- (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler
606
-{
607
-    BOOL trusty = [options valueForKey:CONFIG_TRUSTY];
608
-    if(!trusty)
609
-    {
610
-        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
611
-    }
612
-    else
613
-    {
614
-        completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
119
+    if (task && task.state == NSURLSessionTaskStateRunning) {
120
+        [task cancel];
615 121
     }
616 122
 }
617 123
 
618
-
619
-- (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
124
+// removing case from headers
125
++ (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers
620 126
 {
621
-    NSLog(@"sess done in background");
127
+    NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init];
128
+    for (NSString * key in headers) {
129
+        [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]];
130
+    }
131
+    
132
+    return mheaders;
622 133
 }
623 134
 
624
-- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
135
+// #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled
136
++ (void) emitExpiredTasks
625 137
 {
626
-
627
-    if(followRedirect)
628
-    {
629
-        if(request.URL != nil)
630
-            [redirects addObject:[request.URL absoluteString]];
631
-        completionHandler(request);
632
-    }
633
-    else
634
-    {
635
-        completionHandler(nil);
138
+    @synchronized ([RNFetchBlobNetwork class]){
139
+        NSEnumerator * emu =  [expirationTable keyEnumerator];
140
+        NSString * key;
141
+        
142
+        while ((key = [emu nextObject]))
143
+        {
144
+            RCTBridge * bridge = [RNFetchBlob getRCTBridge];
145
+            id args = @{ @"taskId": key };
146
+            [bridge.eventDispatcher sendDeviceEventWithName:EVENT_EXPIRE body:args];
147
+            
148
+        }
149
+        
150
+        // clear expired task entries
151
+        [expirationTable removeAllObjects];
152
+        expirationTable = [[NSMapTable alloc] init];
636 153
     }
637 154
 }
638 155
 

+ 1
- 1
ios/RNFetchBlobReqBuilder.h ファイルの表示

@@ -29,7 +29,7 @@
29 29
                      body:(NSString *)body
30 30
                onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete;
31 31
 
32
-+(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableArray *) headers;
32
++(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSDictionary *) headers;
33 33
 
34 34
 
35 35
 @end

+ 1
- 1
ios/RNFetchBlobReqBuilder.m ファイルの表示

@@ -277,7 +277,7 @@
277 277
     }
278 278
 }
279 279
 
280
-+(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableDictionary *) headers {
280
++(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSDictionary *) headers {
281 281
 
282 282
     NSString * normalCase = [headers valueForKey:field];
283 283
     NSString * ignoredCase = [headers valueForKey:[field lowercaseString]];

+ 47
- 0
ios/RNFetchBlobRequest.h ファイルの表示

@@ -0,0 +1,47 @@
1
+//
2
+//  RNFetchBlobRequest.h
3
+//  RNFetchBlob
4
+//
5
+//  Created by Artur Chrusciel on 15.01.18.
6
+//  Copyright © 2018 wkh237.github.io. All rights reserved.
7
+//
8
+
9
+#ifndef RNFetchBlobRequest_h
10
+#define RNFetchBlobRequest_h
11
+
12
+#import <Foundation/Foundation.h>
13
+
14
+#import "RNFetchBlobProgress.h"
15
+
16
+#if __has_include(<React/RCTAssert.h>)
17
+#import <React/RCTBridgeModule.h>
18
+#else
19
+#import "RCTBridgeModule.h"
20
+#endif
21
+
22
+@interface RNFetchBlobRequest : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
23
+
24
+@property (nullable, nonatomic) NSString * taskId;
25
+@property (nonatomic) long long expectedBytes;
26
+@property (nonatomic) long long receivedBytes;
27
+@property (nonatomic) BOOL isServerPush;
28
+@property (nullable, nonatomic) NSMutableData * respData;
29
+@property (nullable, strong, nonatomic) RCTResponseSenderBlock callback;
30
+@property (nullable, nonatomic) RCTBridge * bridge;
31
+@property (nullable, nonatomic) NSDictionary * options;
32
+@property (nullable, nonatomic) NSError * error;
33
+@property (nullable, nonatomic) RNFetchBlobProgress *progressConfig;
34
+@property (nullable, nonatomic) RNFetchBlobProgress *uploadProgressConfig;
35
+@property (nullable, nonatomic, weak) NSURLSessionDataTask *task;
36
+
37
+- (void) sendRequest:(NSDictionary  * _Nullable )options
38
+       contentLength:(long)contentLength
39
+              bridge:(RCTBridge * _Nullable)bridgeRef
40
+              taskId:(NSString * _Nullable)taskId
41
+         withRequest:(NSURLRequest * _Nullable)req
42
+  taskOperationQueue:(NSOperationQueue * _Nonnull)operationQueue
43
+            callback:(_Nullable RCTResponseSenderBlock) callback;
44
+
45
+@end
46
+
47
+#endif /* RNFetchBlobRequest_h */

+ 477
- 0
ios/RNFetchBlobRequest.m ファイルの表示

@@ -0,0 +1,477 @@
1
+//
2
+//  RNFetchBlobRequest.m
3
+//  RNFetchBlob
4
+//
5
+//  Created by Artur Chrusciel on 15.01.18.
6
+//  Copyright © 2018 wkh237.github.io. All rights reserved.
7
+//
8
+
9
+#import "RNFetchBlobRequest.h"
10
+
11
+#import "RNFetchBlobFS.h"
12
+#import "RNFetchBlobConst.h"
13
+#import "RNFetchBlobReqBuilder.h"
14
+
15
+#import "IOS7Polyfill.h"
16
+#import <CommonCrypto/CommonDigest.h>
17
+
18
+
19
+typedef NS_ENUM(NSUInteger, ResponseFormat) {
20
+    UTF8,
21
+    BASE64,
22
+    AUTO
23
+};
24
+
25
+@interface RNFetchBlobRequest ()
26
+{
27
+    BOOL respFile;
28
+    BOOL isNewPart;
29
+    BOOL isIncrement;
30
+    NSMutableData * partBuffer;
31
+    NSString * destPath;
32
+    NSOutputStream * writeStream;
33
+    long bodyLength;
34
+    NSInteger respStatus;
35
+    NSMutableArray * redirects;
36
+    ResponseFormat responseFormat;
37
+    BOOL followRedirect;
38
+    BOOL backgroundTask;
39
+}
40
+
41
+@end
42
+
43
+@implementation RNFetchBlobRequest
44
+
45
+@synthesize taskId;
46
+@synthesize expectedBytes;
47
+@synthesize receivedBytes;
48
+@synthesize respData;
49
+@synthesize callback;
50
+@synthesize bridge;
51
+@synthesize options;
52
+@synthesize error;
53
+
54
+
55
+- (NSString *)md5:(NSString *)input {
56
+    const char* str = [input UTF8String];
57
+    unsigned char result[CC_MD5_DIGEST_LENGTH];
58
+    CC_MD5(str, (CC_LONG)strlen(str), result);
59
+    
60
+    NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2];
61
+    for (int i = 0; i<CC_MD5_DIGEST_LENGTH; i++) {
62
+        [ret appendFormat:@"%02x",result[i]];
63
+    }
64
+    return ret;
65
+}
66
+
67
+// send HTTP request
68
+- (void) sendRequest:(__weak NSDictionary  * _Nullable )options
69
+       contentLength:(long) contentLength
70
+              bridge:(RCTBridge * _Nullable)bridgeRef
71
+              taskId:(NSString * _Nullable)taskId
72
+         withRequest:(__weak NSURLRequest * _Nullable)req
73
+  taskOperationQueue:(NSOperationQueue * _Nonnull)operationQueue
74
+            callback:(_Nullable RCTResponseSenderBlock) callback
75
+{
76
+    self.taskId = taskId;
77
+    self.respData = [[NSMutableData alloc] initWithLength:0];
78
+    self.callback = callback;
79
+    self.bridge = bridgeRef;
80
+    self.expectedBytes = 0;
81
+    self.receivedBytes = 0;
82
+    self.options = options;
83
+    
84
+    backgroundTask = [[options valueForKey:@"IOSBackgroundTask"] boolValue];
85
+    // when followRedirect not set in options, defaults to TRUE
86
+    followRedirect = [options valueForKey:@"followRedirect"] == nil ? YES : [[options valueForKey:@"followRedirect"] boolValue];
87
+    isIncrement = [[options valueForKey:@"increment"] boolValue];
88
+    redirects = [[NSMutableArray alloc] init];
89
+    
90
+    if (req.URL) {
91
+        [redirects addObject:req.URL.absoluteString];
92
+    }
93
+    
94
+    // set response format
95
+    NSString * rnfbResp = [req.allHTTPHeaderFields valueForKey:@"RNFB-Response"];
96
+    
97
+    if ([[rnfbResp lowercaseString] isEqualToString:@"base64"]) {
98
+        responseFormat = BASE64;
99
+    } else if ([[rnfbResp lowercaseString] isEqualToString:@"utf8"]) {
100
+        responseFormat = UTF8;
101
+    } else {
102
+        responseFormat = AUTO;
103
+    }
104
+    
105
+    NSString * path = [self.options valueForKey:CONFIG_FILE_PATH];
106
+    NSString * key = [self.options valueForKey:CONFIG_KEY];
107
+    NSURLSession * session;
108
+    
109
+    bodyLength = contentLength;
110
+    
111
+    // the session trust any SSL certification
112
+    NSURLSessionConfiguration *defaultConfigObject;
113
+    
114
+    defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
115
+    
116
+    if (backgroundTask) {
117
+        defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId];
118
+    }
119
+    
120
+    // request timeout, -1 if not set in options
121
+    float timeout = [options valueForKey:@"timeout"] == nil ? -1 : [[options valueForKey:@"timeout"] floatValue];
122
+    
123
+    if (timeout > 0) {
124
+        defaultConfigObject.timeoutIntervalForRequest = timeout/1000;
125
+    }
126
+    
127
+    defaultConfigObject.HTTPMaximumConnectionsPerHost = 10;
128
+    session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:operationQueue];
129
+    
130
+    if (path || [self.options valueForKey:CONFIG_USE_TEMP]) {
131
+        respFile = YES;
132
+        
133
+        NSString* cacheKey = taskId;
134
+        if (key) {
135
+            cacheKey = [self md5:key];
136
+            
137
+            if (!cacheKey) {
138
+                cacheKey = taskId;
139
+            }
140
+            
141
+            destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
142
+            
143
+            if ([[NSFileManager defaultManager] fileExistsAtPath:destPath]) {
144
+                callback(@[[NSNull null], RESP_TYPE_PATH, destPath]);
145
+                
146
+                return;
147
+            }
148
+        }
149
+        
150
+        if (path) {
151
+            destPath = path;
152
+        } else {
153
+            destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]];
154
+        }
155
+    } else {
156
+        respData = [[NSMutableData alloc] init];
157
+        respFile = NO;
158
+    }
159
+    
160
+    self.task = [session dataTaskWithRequest:req];
161
+    [self.task resume];
162
+    
163
+    // network status indicator
164
+    if ([[options objectForKey:CONFIG_INDICATOR] boolValue]) {
165
+        dispatch_async(dispatch_get_main_queue(), ^{
166
+            [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
167
+        });
168
+    }
169
+}
170
+
171
+////////////////////////////////////////
172
+//
173
+//  NSURLSession delegates
174
+//
175
+////////////////////////////////////////
176
+
177
+
178
+#pragma mark NSURLSession delegate methods
179
+
180
+
181
+#pragma mark - Received Response
182
+// set expected content length on response received
183
+- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
184
+{
185
+    expectedBytes = [response expectedContentLength];
186
+    
187
+    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
188
+    NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
189
+    NSString * respType = @"";
190
+    respStatus = statusCode;
191
+    
192
+    if ([response respondsToSelector:@selector(allHeaderFields)])
193
+    {
194
+        NSDictionary *headers = [httpResponse allHeaderFields];
195
+        NSString * respCType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"Content-Type" fromHeaders:headers] lowercaseString];
196
+        
197
+        if (self.isServerPush) {
198
+            if (partBuffer) {
199
+                [self.bridge.eventDispatcher
200
+                 sendDeviceEventWithName:EVENT_SERVER_PUSH
201
+                 body:@{
202
+                        @"taskId": taskId,
203
+                        @"chunk": [partBuffer base64EncodedStringWithOptions:0],
204
+                        }
205
+                 ];
206
+            }
207
+            
208
+            partBuffer = [[NSMutableData alloc] init];
209
+            completionHandler(NSURLSessionResponseAllow);
210
+
211
+            return;
212
+        } else {
213
+            self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"];
214
+        }
215
+        
216
+        if(respCType)
217
+        {
218
+            NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE];
219
+            
220
+            if ([respCType RNFBContainsString:@"text/"]) {
221
+                respType = @"text";
222
+            } else if ([respCType RNFBContainsString:@"application/json"]) {
223
+                respType = @"json";
224
+            } else if(extraBlobCTypes) { // If extra blob content type is not empty, check if response type matches
225
+                for (NSString * substr in extraBlobCTypes) {
226
+                    if ([respCType RNFBContainsString:[substr lowercaseString]]) {
227
+                        respType = @"blob";
228
+                        respFile = YES;
229
+                        destPath = [RNFetchBlobFS getTempPath:taskId withExtension:nil];
230
+                        break;
231
+                    }
232
+                }
233
+            } else {
234
+                respType = @"blob";
235
+                
236
+                // for XMLHttpRequest, switch response data handling strategy automatically
237
+                if ([options valueForKey:@"auto"]) {
238
+                    respFile = YES;
239
+                    destPath = [RNFetchBlobFS getTempPath:taskId withExtension:@""];
240
+                }
241
+            }
242
+        } else {
243
+            respType = @"text";
244
+        }
245
+        
246
+#pragma mark - handling cookies
247
+        // # 153 get cookies
248
+        if (response.URL) {
249
+            NSHTTPCookieStorage * cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage];
250
+            NSArray<NSHTTPCookie *> * cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: headers forURL:response.URL];
251
+            if (cookies.count) {
252
+                [cookieStore setCookies:cookies forURL:response.URL mainDocumentURL:nil];
253
+            }
254
+        }
255
+        
256
+        [self.bridge.eventDispatcher
257
+         sendDeviceEventWithName: EVENT_STATE_CHANGE
258
+         body:@{
259
+                @"taskId": taskId,
260
+                @"state": @"2",
261
+                @"headers": headers,
262
+                @"redirects": redirects,
263
+                @"respType" : respType,
264
+                @"timeout" : @NO,
265
+                @"status": [NSNumber numberWithInteger:statusCode]
266
+                }
267
+         ];
268
+    } else {
269
+        NSLog(@"oops");
270
+    }
271
+    
272
+    if (respFile)
273
+    {
274
+        @try{
275
+            NSFileManager * fm = [NSFileManager defaultManager];
276
+            NSString * folder = [destPath stringByDeletingLastPathComponent];
277
+            
278
+            if (![fm fileExistsAtPath:folder]) {
279
+                [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil];
280
+            }
281
+            
282
+            // if not set overwrite in options, defaults to TRUE
283
+            BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue];
284
+            BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"];
285
+            
286
+            appendToExistingFile = !overwrite;
287
+            
288
+            // For solving #141 append response data if the file already exists
289
+            // base on PR#139 @kejinliang
290
+            if (appendToExistingFile) {
291
+                destPath = [destPath stringByReplacingOccurrencesOfString:@"?append=true" withString:@""];
292
+            }
293
+            
294
+            if (![fm fileExistsAtPath:destPath]) {
295
+                [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil];
296
+            }
297
+            
298
+            writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:appendToExistingFile];
299
+            [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
300
+            [writeStream open];
301
+        }
302
+        @catch(NSException * ex)
303
+        {
304
+            NSLog(@"write file error");
305
+        }
306
+    }
307
+    
308
+    completionHandler(NSURLSessionResponseAllow);
309
+}
310
+
311
+
312
+// download progress handler
313
+- (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
314
+{
315
+    // For #143 handling multipart/x-mixed-replace response
316
+    if (self.isServerPush)
317
+    {
318
+        [partBuffer appendData:data];
319
+        
320
+        return ;
321
+    }
322
+    
323
+    NSNumber * received = [NSNumber numberWithLong:[data length]];
324
+    receivedBytes += [received longValue];
325
+    NSString * chunkString = @"";
326
+    
327
+    if (isIncrement) {
328
+        chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
329
+    }
330
+    
331
+    if (respFile) {
332
+        [writeStream write:[data bytes] maxLength:[data length]];
333
+    } else {
334
+        [respData appendData:data];
335
+    }
336
+    
337
+    if (expectedBytes == 0) {
338
+        return;
339
+    }
340
+    
341
+    NSNumber * now =[NSNumber numberWithFloat:((float)receivedBytes/(float)expectedBytes)];
342
+    
343
+    if ([self.progressConfig shouldReport:now]) {
344
+        [self.bridge.eventDispatcher
345
+         sendDeviceEventWithName:EVENT_PROGRESS
346
+         body:@{
347
+                @"taskId": taskId,
348
+                @"written": [NSString stringWithFormat:@"%ld", (long) receivedBytes],
349
+                @"total": [NSString stringWithFormat:@"%ld", (long) expectedBytes],
350
+                @"chunk": chunkString
351
+                }
352
+         ];
353
+    }
354
+}
355
+
356
+- (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error
357
+{
358
+    if ([session isEqual:session]) {
359
+        session = nil;
360
+    }
361
+}
362
+
363
+
364
+- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
365
+{
366
+    
367
+    self.error = error;
368
+    NSString * errMsg;
369
+    NSString * respStr;
370
+    NSString * rnfbRespType;
371
+    
372
+    dispatch_async(dispatch_get_main_queue(), ^{
373
+        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
374
+    });
375
+    
376
+    if (error) {
377
+        if (error.domain == NSURLErrorDomain && error.code == NSURLErrorCancelled) {
378
+            errMsg = @"task cancelled";
379
+        } else {
380
+            errMsg = [error localizedDescription];
381
+        }
382
+    }
383
+    
384
+    if (respFile) {
385
+        [writeStream close];
386
+        rnfbRespType = RESP_TYPE_PATH;
387
+        respStr = destPath;
388
+    } else { // base64 response
389
+        // #73 fix unicode data encoding issue :
390
+        // when response type is BASE64, we should first try to encode the response data to UTF8 format
391
+        // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
392
+        // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
393
+        NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding];
394
+        
395
+        if (responseFormat == BASE64) {
396
+            rnfbRespType = RESP_TYPE_BASE64;
397
+            respStr = [respData base64EncodedStringWithOptions:0];
398
+        } else if (responseFormat == UTF8) {
399
+            rnfbRespType = RESP_TYPE_UTF8;
400
+            respStr = utf8;
401
+        } else {
402
+            if (utf8) {
403
+                rnfbRespType = RESP_TYPE_UTF8;
404
+                respStr = utf8;
405
+            } else {
406
+                rnfbRespType = RESP_TYPE_BASE64;
407
+                respStr = [respData base64EncodedStringWithOptions:0];
408
+            }
409
+        }
410
+    }
411
+    
412
+    
413
+    callback(@[
414
+               errMsg ?: [NSNull null],
415
+               rnfbRespType ?: @"",
416
+               respStr ?: [NSNull null]
417
+               ]);
418
+    
419
+    respData = nil;
420
+    receivedBytes = 0;
421
+    [session finishTasksAndInvalidate];
422
+    
423
+}
424
+
425
+// upload progress handler
426
+- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesWritten totalBytesExpectedToSend:(int64_t)totalBytesExpectedToWrite
427
+{
428
+    if (totalBytesExpectedToWrite == 0) {
429
+        return;
430
+    }
431
+    
432
+    NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)];
433
+
434
+    if ([self.uploadProgressConfig shouldReport:now]) {
435
+        [self.bridge.eventDispatcher
436
+         sendDeviceEventWithName:EVENT_PROGRESS_UPLOAD
437
+         body:@{
438
+                @"taskId": taskId,
439
+                @"written": [NSString stringWithFormat:@"%ld", (long) totalBytesWritten],
440
+                @"total": [NSString stringWithFormat:@"%ld", (long) totalBytesExpectedToWrite]
441
+                }
442
+         ];
443
+    }
444
+}
445
+
446
+
447
+- (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler
448
+{
449
+    if ([[options valueForKey:CONFIG_TRUSTY] boolValue]) {
450
+        completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
451
+    } else {
452
+        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
453
+    }
454
+}
455
+
456
+
457
+- (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
458
+{
459
+    NSLog(@"sess done in background");
460
+}
461
+
462
+- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
463
+{
464
+    
465
+    if (followRedirect) {
466
+        if (request.URL) {
467
+            [redirects addObject:[request.URL absoluteString]];
468
+        }
469
+        
470
+        completionHandler(request);
471
+    } else {
472
+        completionHandler(nil);
473
+    }
474
+}
475
+
476
+
477
+@end