Bläddra i källkod

Separate requests from network logic

Artur Chrusciel 6 år sedan
förälder
incheckning
8dd310d9f6

+ 7
- 1
ios/RNFetchBlob.xcodeproj/project.pbxproj Visa fil

@@ -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 */,

+ 12
- 4
ios/RNFetchBlob/RNFetchBlob.m Visa fil

@@ -96,8 +96,12 @@ RCT_EXPORT_METHOD(fetchBlobForm:(NSDictionary *)options
96 96
         // send HTTP request
97 97
         else
98 98
         {
99
-            RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init];
100
-            [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback];
99
+            [RNFetchBlobNetwork sendRequest:options
100
+                              contentLength:bodyLength
101
+                                     bridge:self.bridge
102
+                                     taskId:taskId
103
+                                withRequest:req
104
+                                   callback:callback];
101 105
         }
102 106
     }];
103 107
 
@@ -128,8 +132,12 @@ RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options
128 132
         // send HTTP request
129 133
         else
130 134
         {
131
-            RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init];
132
-            [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback];
135
+            [RNFetchBlobNetwork sendRequest:options
136
+                              contentLength:bodyLength
137
+                                     bridge:self.bridge
138
+                                     taskId:taskId
139
+                                withRequest:req
140
+                                   callback:callback];
133 141
         }
134 142
     }];
135 143
 }

+ 2
- 12
ios/RNFetchBlobNetwork.h Visa fil

@@ -22,18 +22,8 @@
22 22
 
23 23
 @interface RNFetchBlobNetwork : NSObject  <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
24 24
 
25
-@property (nullable, nonatomic) NSString * taskId;
26
-@property (nonatomic) long long expectedBytes;
27
-@property (nonatomic) long long receivedBytes;
28
-@property (nonatomic) BOOL isServerPush;
29
-@property (nullable, nonatomic) NSMutableData * respData;
30
-@property (nullable, strong, nonatomic) RCTResponseSenderBlock callback;
31
-@property (nullable, nonatomic) RCTBridge * bridge;
32
-@property (nullable, nonatomic) NSDictionary * options;
33
-@property (nullable, nonatomic) RNFetchBlobFS * fileStream;
34
-@property (nullable, nonatomic) NSError * error;
35
-
36 25
 
26
++ (_Nullable instancetype)sharedInstance;
37 27
 + (NSMutableDictionary  * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers;
38 28
 + (void) cancelRequest:(NSString * _Nonnull)taskId;
39 29
 + (void) emitExpiredTasks;
@@ -41,7 +31,7 @@
41 31
 + (void) enableUploadProgress:(NSString * _Nonnull) taskId config:(RNFetchBlobProgress * _Nullable)config;
42 32
 
43 33
 - (nullable id) init;
44
-- (void) sendRequest:(NSDictionary  * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback;
34
++ (void) sendRequest:(NSDictionary  * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback;
45 35
 
46 36
 @end
47 37
 

+ 53
- 527
ios/RNFetchBlobNetwork.m Visa fil

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

+ 47
- 0
ios/RNFetchBlobRequest.h Visa fil

@@ -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
+#import <Foundation/Foundation.h>
10
+
11
+#import "RNFetchBlobProgress.h"
12
+
13
+#if __has_include(<React/RCTAssert.h>)
14
+#import <React/RCTBridgeModule.h>
15
+#else
16
+#import "RCTBridgeModule.h"
17
+#endif
18
+
19
+#ifndef RNFetchBlobRequest_h
20
+#define RNFetchBlobRequest_h
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 */

+ 495
- 0
ios/RNFetchBlobRequest.m Visa fil

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