Browse Source

Add IOS background HTTP expiration handling #115

Ben Hsieh 7 years ago
parent
commit
e1cb0e22f3

+ 18
- 1
src/index.js View File

@@ -48,6 +48,7 @@ const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
48 48
 
49 49
 // register message channel event handler.
50 50
 emitter.addListener("RNFetchBlobMessage", (e) => {
51
+
51 52
   if(e.event === 'warn') {
52 53
     console.warn(e.detail)
53 54
   }
@@ -140,7 +141,11 @@ function fetchFile(options = {}, method, url, headers = {}, body):Promise {
140 141
       promise = fs.stat(url)
141 142
       .then((stat) => {
142 143
         total = stat.size
143
-        return fs.readStream(url, headers.encoding || 'utf8', Math.floor(headers.bufferSize) || 4096)
144
+        return fs.readStream(url,
145
+          headers.encoding || 'utf8',
146
+          Math.floor(headers.bufferSize) || 409600,
147
+          Math.floor(headers.interval) || 100
148
+        )
144 149
       })
145 150
       .then((stream) => new Promise((resolve, reject) => {
146 151
         stream.open()
@@ -232,6 +237,13 @@ function fetch(...args:any):Promise {
232 237
       }
233 238
     })
234 239
 
240
+    subscription = emitter.addListener('RNFetchBlobExpire', (e) => {
241
+      console.log(e , 'EXPIRED!!')
242
+      if(e.taskId === taskId && promise.onExpire) {
243
+        promise.onExpire(e)
244
+      }
245
+    })
246
+
235 247
     // When the request body comes from Blob polyfill, we should use special its ref
236 248
     // as the request body
237 249
     if( body instanceof Blob && body.isRNFetchBlobPolyfill) {
@@ -261,6 +273,7 @@ function fetch(...args:any):Promise {
261 273
       delete promise['uploadProgress']
262 274
       delete promise['stateChange']
263 275
       delete promise['cancel']
276
+      // delete promise['expire']
264 277
       promise.cancel = () => {}
265 278
 
266 279
       if(err)
@@ -296,6 +309,10 @@ function fetch(...args:any):Promise {
296 309
     promise.onStateChange = fn
297 310
     return promise
298 311
   }
312
+  promise.expire = (fn) => {
313
+    promise.onExpire = fn
314
+    return promise
315
+  }
299 316
   promise.cancel = (fn) => {
300 317
     fn = fn || function(){}
301 318
     subscription.remove()

+ 1
- 0
src/ios/RNFetchBlob/RNFetchBlob.h View File

@@ -17,6 +17,7 @@
17 17
 }
18 18
 
19 19
 @property (nonatomic) NSString * filePathPrefix;
20
+@property (nonatomic) UIDocumentInteractionController * documentController;
20 21
 
21 22
 + (RCTBridge *)getRCTBridge;
22 23
 

+ 5
- 2
src/ios/RNFetchBlob/RNFetchBlob.m View File

@@ -6,6 +6,7 @@
6 6
 
7 7
 #import "RNFetchBlob.h"
8 8
 #import "RCTLog.h"
9
+#import "RCTRootView.h"
9 10
 #import "RCTBridge.h"
10 11
 #import "RCTEventDispatcher.h"
11 12
 #import "RNFetchBlobFS.h"
@@ -14,7 +15,7 @@
14 15
 #import "RNFetchBlobReqBuilder.h"
15 16
 
16 17
 
17
-RCTBridge * bridgeRef;
18
+__strong RCTBridge * bridgeRef;
18 19
 dispatch_queue_t commonTaskQueue;
19 20
 dispatch_queue_t fsQueue;
20 21
 
@@ -40,7 +41,8 @@ dispatch_queue_t fsQueue;
40 41
 
41 42
 + (RCTBridge *)getRCTBridge
42 43
 {
43
-    return bridgeRef;
44
+    RCTRootView * rootView = [[UIApplication sharedApplication] keyWindow].rootViewController.view;
45
+    return rootView.bridge;
44 46
 }
45 47
 
46 48
 RCT_EXPORT_MODULE();
@@ -58,6 +60,7 @@ RCT_EXPORT_MODULE();
58 60
         [[NSFileManager defaultManager] createDirectoryAtPath:[RNFetchBlobFS getTempPath] withIntermediateDirectories:YES attributes:nil error:NULL];
59 61
     }
60 62
     bridgeRef = _bridge;
63
+    [RNFetchBlobNetwork getExpiredTasks];
61 64
     return self;
62 65
 }
63 66
 

+ 1
- 0
src/ios/RNFetchBlobConst.h View File

@@ -17,6 +17,7 @@ extern NSString *const MSG_EVENT_LOG;
17 17
 extern NSString *const MSG_EVENT_WARN;
18 18
 extern NSString *const MSG_EVENT_ERROR;
19 19
 
20
+extern NSString *const EVENT_EXPIRE;
20 21
 extern NSString *const EVENT_PROGRESS;
21 22
 extern NSString *const EVENT_PROGRESS_UPLOAD;
22 23
 extern NSString *const EVENT_STATE_CHANGE;

+ 1
- 2
src/ios/RNFetchBlobConst.m View File

@@ -11,8 +11,6 @@ extern NSString *const FILE_PREFIX = @"RNFetchBlob-file://";
11 11
 extern NSString *const ASSET_PREFIX = @"bundle-assets://";
12 12
 extern NSString *const AL_PREFIX = @"assets-library://";
13 13
 
14
-
15
-
16 14
 // fetch configs
17 15
 extern NSString *const CONFIG_USE_TEMP = @"fileCache";
18 16
 extern NSString *const CONFIG_FILE_PATH = @"path";
@@ -25,6 +23,7 @@ extern NSString *const CONFIG_EXTRA_BLOB_CTYPE = @"binaryContentTypes";
25 23
 extern NSString *const EVENT_STATE_CHANGE = @"RNFetchBlobState";
26 24
 extern NSString *const EVENT_PROGRESS = @"RNFetchBlobProgress";
27 25
 extern NSString *const EVENT_PROGRESS_UPLOAD = @"RNFetchBlobProgress-upload";
26
+extern NSString *const EVENT_EXPIRE = @"RNFetchBlobExpire";
28 27
 
29 28
 extern NSString *const MSG_EVENT = @"RNFetchBlobMessage";
30 29
 extern NSString *const MSG_EVENT_LOG = @"log";

+ 5
- 3
src/ios/RNFetchBlobNetwork.h View File

@@ -31,16 +31,18 @@ typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse
31 31
 @property (nullable, nonatomic) NSError * error;
32 32
 
33 33
 
34
-- (nullable id) init;
35
-- (void) sendRequest;
36
-
37 34
 + (NSMutableDictionary  * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers;
38 35
 + (void) cancelRequest:(NSString *)taskId;
39 36
 + (void) enableProgressReport:(NSString *) taskId;
40 37
 + (void) enableUploadProgress:(NSString *) taskId;
38
++ (void) getExpiredTasks;
39
+
40
+- (nullable id) init;
41
+- (void) sendRequest;
41 42
 - (void) sendRequest:(NSDictionary  * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback;
42 43
 
43 44
 
45
+
44 46
 @end
45 47
 
46 48
 

+ 47
- 4
src/ios/RNFetchBlobNetwork.m View File

@@ -9,8 +9,10 @@
9 9
 #import "RCTLog.h"
10 10
 #import <Foundation/Foundation.h>
11 11
 #import "RCTBridge.h"
12
+#import "RNFetchBlob.h"
12 13
 #import "RCTEventDispatcher.h"
13 14
 #import "RNFetchBlobFS.h"
15
+#import "RCTRootView.h"
14 16
 #import "RNFetchBlobNetwork.h"
15 17
 #import "RNFetchBlobConst.h"
16 18
 #import "RNFetchBlobReqBuilder.h"
@@ -24,6 +26,7 @@
24 26
 ////////////////////////////////////////
25 27
 
26 28
 NSMapTable * taskTable;
29
+NSMapTable * expirationTable;
27 30
 NSMutableDictionary * progressTable;
28 31
 NSMutableDictionary * uploadProgressTable;
29 32
 
@@ -37,6 +40,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
37 40
 @interface RNFetchBlobNetwork ()
38 41
 {
39 42
     BOOL * respFile;
43
+    BOOL * isIncrement;
40 44
     NSString * destPath;
41 45
     NSOutputStream * writeStream;
42 46
     long bodyLength;
@@ -70,7 +74,12 @@ NSOperationQueue *taskQueue;
70 74
         taskQueue = [[NSOperationQueue alloc] init];
71 75
         taskQueue.maxConcurrentOperationCount = 10;
72 76
     }
73
-    if(taskTable == nil) {
77
+    if(expirationTable == nil)
78
+    {
79
+        expirationTable = [[NSMapTable alloc] init];
80
+    }
81
+    if(taskTable == nil)
82
+    {
74 83
         taskTable = [[NSMapTable alloc] init];
75 84
     }
76 85
     if(progressTable == nil)
@@ -133,6 +142,7 @@ NSOperationQueue *taskQueue;
133 142
     self.expectedBytes = 0;
134 143
     self.receivedBytes = 0;
135 144
     self.options = options;
145
+    isIncrement = [options valueForKey:@"increment"] == nil ? NO : [[options valueForKey:@"increment"] boolValue];
136 146
     redirects = [[NSMutableArray alloc] init];
137 147
     [redirects addObject:req.URL.absoluteString];
138 148
     
@@ -153,7 +163,9 @@ NSOperationQueue *taskQueue;
153 163
     bodyLength = contentLength;
154 164
 
155 165
     // the session trust any SSL certification
156
-    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
166
+//    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
167
+    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId];
168
+    
157 169
     // set request timeout
158 170
     float timeout = [options valueForKey:@"timeout"] == nil ? -1 : [[options valueForKey:@"timeout"] floatValue];
159 171
     if(timeout > 0)
@@ -190,13 +202,39 @@ NSOperationQueue *taskQueue;
190 202
         respData = [[NSMutableData alloc] init];
191 203
         respFile = NO;
192 204
     }
193
-    NSURLSessionDataTask * task = [session dataTaskWithRequest:req];
205
+    
206
+    __block NSURLSessionDataTask * task = [session dataTaskWithRequest:req];
194 207
     [taskTable setObject:task forKey:taskId];
195 208
     [task resume];
196 209
 
197 210
     // network status indicator
198 211
     if([[options objectForKey:CONFIG_INDICATOR] boolValue] == YES)
199 212
         [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
213
+    __block UIApplication * app = [UIApplication sharedApplication];
214
+    
215
+    // #115 handling task expired when application entering backgound for a long time
216
+    [app beginBackgroundTaskWithName:taskId expirationHandler:^{
217
+        NSLog([NSString stringWithFormat:@"session %@ expired event emit", taskId ]);
218
+        [expirationTable setObject:task forKey:taskId];
219
+        [app endBackgroundTask:task];
220
+        
221
+    }];
222
+    
223
+    
224
+}
225
+
226
+// #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled
227
++ (void) getExpiredTasks
228
+{
229
+    NSEnumerator * emu =  [expirationTable keyEnumerator];
230
+    NSString * key;
231
+
232
+    while((key = [emu nextObject]))
233
+    {
234
+        RCTBridge * bridge = [RNFetchBlob getRCTBridge];
235
+        NSData * args = @{ @"taskId": key };
236
+        [bridge.eventDispatcher sendDeviceEventWithName:EVENT_EXPIRE body:args];
237
+    }
200 238
 }
201 239
 
202 240
 ////////////////////////////////////////
@@ -306,7 +344,7 @@ NSOperationQueue *taskQueue;
306 344
     receivedBytes += [received longValue];
307 345
     NSString * chunkString = @"";
308 346
 
309
-    if(isInrement == YES)
347
+    if(isIncrement == YES)
310 348
     {
311 349
         chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
312 350
     }
@@ -480,6 +518,11 @@ NSOperationQueue *taskQueue;
480 518
     }
481 519
 }
482 520
 
521
+- (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
522
+{
523
+    NSLog(@"sess done in background");
524
+}
525
+
483 526
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
484 527
 {
485 528
     [redirects addObject:[request.URL absoluteString]];

+ 21
- 18
test/test-init.js View File

@@ -18,8 +18,8 @@ const { Assert, Comparer, Info, prop } = RNTest
18 18
 // test environment variables
19 19
 
20 20
 prop('FILENAME', `${Platform.OS}-0.8.0-${Date.now()}.png`)
21
-prop('TEST_SERVER_URL', 'http://192.168.16.70:8123')
22
-prop('TEST_SERVER_URL_SSL', 'https://192.168.16.70:8124')
21
+prop('TEST_SERVER_URL', 'http://localhost:8123')
22
+prop('TEST_SERVER_URL_SSL', 'https://localhost:8124')
23 23
 prop('DROPBOX_TOKEN', 'fsXcpmKPrHgAAAAAAAAAoXZhcXYWdgLpQMan6Tb_bzJ237DXhgQSev12hA-gUXt4')
24 24
 prop('styles', {
25 25
   image : {
@@ -59,21 +59,24 @@ describe('GET image from server', (report, done) => {
59 59
 })
60 60
 
61 61
 
62
-require('./test-0.1.x-0.4.x')
63
-require('./test-0.5.1')
64
-require('./test-0.5.2')
65
-require('./test-0.6.0')
66
-require('./test-0.6.2')
67
-require('./test-0.7.0')
68
-require('./test-0.8.0')
69
-require('./test-0.9.0')
70
-require('./test-0.9.2')
71
-require('./test-0.9.4')
72
-require('./test-fetch')
73
-require('./test-fs')
74
-require('./test-xmlhttp')
75
-require('./test-blob')
76
-require('./test-firebase')
77
-require('./test-android')
62
+// require('./test-0.1.x-0.4.x')
63
+// require('./test-0.5.1')
64
+// require('./test-0.5.2')
65
+// require('./test-0.6.0')
66
+// require('./test-0.6.2')
67
+// require('./test-0.7.0')
68
+// require('./test-0.8.0')
69
+// require('./test-0.9.0')
70
+// require('./test-0.9.2')
71
+// require('./test-0.9.4')
72
+// require('./test-0.9.5')
73
+// require('./test-0.10.0')
74
+// require('./test-fetch')
75
+// require('./test-fs')
76
+// require('./test-xmlhttp')
77
+// require('./test-blob')
78
+// require('./test-firebase')
79
+require('./test-background')
80
+// require('./test-android')
78 81
 // require('./test-stress')
79 82
 // require('./benchmark')