Browse Source

Merge branch '0.10.0'

Ben Hsieh 7 years ago
parent
commit
5da01132cc
48 changed files with 52288 additions and 190 deletions
  1. 5
    9
      CONTRIBUTORS.md
  2. 8
    7
      README.md
  3. 22
    4
      package.json
  4. 1
    0
      scripts/README.md
  5. 21
    0
      scripts/contributors.sh
  6. 1
    0
      scripts/test.sh
  7. 25
    4
      src/README.md
  8. 52
    3
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java
  9. 9
    0
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java
  10. 30
    6
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
  11. 33
    17
      src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
  12. 11
    1
      src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobDefaultResp.java
  13. 2
    2
      src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java
  14. 53
    0
      src/android/src/main/java/com/RNFetchBlob/Utils/RNFBCookieJar.java
  15. 15
    2
      src/fs.js
  16. 124
    5
      src/index.js
  17. 44
    0
      src/ios.js
  18. 2
    2
      src/ios/RNFetchBlob.xcodeproj/project.pbxproj
  19. 4
    1
      src/ios/RNFetchBlob/RNFetchBlob.h
  20. 66
    3
      src/ios/RNFetchBlob/RNFetchBlob.m
  21. 1
    0
      src/ios/RNFetchBlobConst.h
  22. 1
    2
      src/ios/RNFetchBlobConst.m
  23. 4
    0
      src/ios/RNFetchBlobFS.h
  24. 37
    5
      src/ios/RNFetchBlobFS.m
  25. 12
    7
      src/ios/RNFetchBlobNetwork.h
  26. 165
    61
      src/ios/RNFetchBlobNetwork.m
  27. 2
    2
      src/ios/RNFetchBlobProgress.m
  28. 39
    0
      src/json-stream.js
  29. 2703
    0
      src/lib/oboe-browser.js
  30. 1
    0
      src/lib/oboe-browser.min.js
  31. 26
    0
      src/net.js
  32. 21
    4
      src/package.json
  33. 1
    1
      src/polyfill/Fetch.js
  34. 17
    3
      src/polyfill/XMLHttpRequest.js
  35. 2
    2
      src/react-native-fetch-blob.podspec
  36. 28
    0
      src/utils/uri.js
  37. 3
    0
      test-server/public/json-dummy-1.json
  38. 48331
    15
      test-server/public/json-dummy.json
  39. 36
    12
      test-server/server.js
  40. 1
    0
      test-server/test.jpeg
  41. 4
    0
      test/nedb.js
  42. 214
    0
      test/test-0.10.0.js
  43. 3
    1
      test/test-0.6.2.js
  44. 8
    2
      test/test-0.9.4.js
  45. 3
    3
      test/test-0.9.5.js
  46. 51
    0
      test/test-background.js
  47. 6
    4
      test/test-init.js
  48. 40
    0
      test/test-readable.js

+ 5
- 9
CONTRIBUTORS.md View File

@@ -1,18 +1,14 @@
1
-
1
+Andreas Amsenius <andreas@amsenius.se>
2
+Corentin Smith <corentin.smith@gmail.com>
2 3
 Dmitry Petukhov <dmitryvpetukhov@gmail.com>
3
-
4 4
 Erik Smartt <code@eriksmartt.com>
5
-
6 5
 Evgeniy Baraniuk <ev.baraniuk@gmail.com>
7
-
8 6
 Juan B. Rodriguez <jbrodriguez@gmail.com>
9
-
10 7
 Kaishley <kklingachetti@msn.com>
11
-
8
+Mike Monteith <mike@mikemonteith.com>
12 9
 Nguyen Cao Nhat Linh <nhatlinh95@gmail.com>
13
-
10
+Tim Suchanek <tim.suchanek@gmail.com>
14 11
 follower <github@rancidbacon.com>
15
-
16 12
 francisco-sanchez-molina <psm1984@gmail.com>
17
-
13
+kejinliang <kejinliang@users.noreply.github.com>
18 14
 smartt <github@eriksmartt.com>

+ 8
- 7
README.md View File

@@ -14,6 +14,7 @@ A project committed to make file acess and data transfer easier, efficient for R
14 14
 - Native-to-native file manipulation API, reduce JS bridging performance loss
15 15
 - File stream support for dealing with large file
16 16
 - Blob, File, XMLHttpRequest polyfills that make browser-based library available in RN (experimental)
17
+- JSON stream supported base on [Oboe.js@jimhigson](https://github.com/jimhigson/oboe.js/)
17 18
 
18 19
 ## TOC
19 20
 * [About](#user-content-about)
@@ -81,13 +82,13 @@ Optionally, use the following command to add Android permissions to `AndroidMani
81 82
 RNFB_ANDROID_PERMISSIONS=true react-native link
82 83
 ```
83 84
 
84
-pre 0.29 projects 
85
+pre 0.29 projects
85 86
 
86 87
 ```sh
87 88
 RNFB_ANDROID_PERMISSIONS=true rnpm link
88 89
 ```
89 90
 
90
-The link script might not take effect if you have non-default project structure, please visit [the wiki](https://github.com/wkh237/react-native-fetch-blob/wiki/Manually-Link-Package) to manually link the package.
91
+The link script might not take effect if you have non-default project structure, please visit [the wiki](https://github.com/wkh237/react-native-fetch-blob/wiki/Manually-Link-Package) to manually link the pacakge.
91 92
 
92 93
 **Grant Permission to External storage for Android 5.0 or lower**
93 94
 
@@ -156,10 +157,10 @@ To sum up :
156 157
 - To send a form data, the `Content-Type` header does not matters. When the body is an `Array` we will set proper content type for you.
157 158
 - To send binary data, you have two choices, use BASE64 encoded string or path points to a file contains the body.
158 159
  - If the `Content-Type` containing substring`;BASE64` or `application/octet` the given body will be considered as a BASE64 encoded data which will be decoded to binary data as the request body.   
159
- - Otherwise, if a string starts with `RNFetchBlob-file://` (which can simply done by `RNFetchBlob.wrap(PATH_TO_THE_FILE)`), it will try to find the data from the URI string after `RNFetchBlob-file://` and use it as request body. 
160
+ - Otherwise, if a string starts with `RNFetchBlob-file://` (which can simply done by `RNFetchBlob.wrap(PATH_TO_THE_FILE)`), it will try to find the data from the URI string after `RNFetchBlob-file://` and use it as request body.
160 161
 - To send the body as-is, simply use a `Content-Type` header not containing `;BASE64` or `application/octet`.
161 162
 
162
-> It is Worth to mentioning that the HTTP request uses cache by default, if you're going to disable it simply add a Cache Control header `'Cache-Control' : 'no-store'` 
163
+> It is Worth to mentioning that the HTTP request uses cache by default, if you're going to disable it simply add a Cache Control header `'Cache-Control' : 'no-store'`
163 164
 
164 165
 > After 0.9.4, we disabled `Chunked` transfer encoding by default, if you're going to use it, you should explicitly set header `Transfer-Encoding` to `Chunked`.
165 166
 
@@ -382,7 +383,7 @@ What if you want to append a file to form data ? Just like [upload a file from s
382 383
 
383 384
 ### Upload/Download progress
384 385
 
385
-In `version >= 0.4.2` it is possible to know the upload/download progress. After `0.7.0` IOS and Android upload progress are also supported. 
386
+In `version >= 0.4.2` it is possible to know the upload/download progress. After `0.7.0` IOS and Android upload progress are also supported.
386 387
 
387 388
 ```js
388 389
   RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
@@ -739,7 +740,7 @@ Here's a [sample app](https://github.com/wkh237/rn-firebase-storage-upload-sampl
739 740
 
740 741
 **Read Stream and Progress Event Overhead**
741 742
 
742
-When reading data via `fs.readStream` the process seems blocking JS thread when file is large, it's because the default buffer size is quite small (4kb) which result in large amount of events triggered in JS thread, try to increase the buffer size (for example 100kb = 102400) and set a larger interval (which is introduced in 0.9.4 default value is 10ms) to limit the frequency. 
743
+When reading data via `fs.readStream` the process seems blocking JS thread when file is large, it's because the default buffer size is quite small (4kb) which result in large amount of events triggered in JS thread, try to increase the buffer size (for example 100kb = 102400) and set a larger interval (which is introduced in 0.9.4 default value is 10ms) to limit the frequency.
743 744
 
744 745
 **Reduce RCT Bridge and BASE64 Overhead**
745 746
 
@@ -761,7 +762,7 @@ If you're going to concatenate files, you don't have to read the data to JS cont
761 762
 
762 763
 ## Caveats
763 764
 
764
-* This library does not urlencode unicode characters in URL automatically, see [#146](https://github.com/wkh237/react-native-fetch-blob/issues/146). 
765
+* This library does not urlencode unicode characters in URL automatically, see [#146](https://github.com/wkh237/react-native-fetch-blob/issues/146).
765 766
 * When a `Blob` is created from existing file, the file **WILL BE REMOVE** if you `close` the blob.
766 767
 * If you replaced `window.XMLHttpRequest` for some reason (e.g. make Firebase SDK work), it will also effect how official `fetch` works (basically it should work just fine).
767 768
 * When file stream and upload/download progress event slow down your app, consider upgrade to `0.9.6+`, use [additional arguments](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API#fetchprogressconfig-eventlistenerpromisernfetchblobresponse) to limit its frequency.

+ 22
- 4
package.json View File

@@ -1,10 +1,11 @@
1 1
 {
2 2
   "name": "react-native-fetch-blob-dev-env",
3
-  "description" : "RNFB development environment, not dist package",
4
-  "version": "0.9.6",
3
+  "description": "RNFB development environment, not dist package",
4
+  "version": "0.10.0-dev",
5 5
   "private": true,
6 6
   "scripts": {
7 7
     "start": "node node_modules/react-native/local-cli/cli.js start",
8
+    "update-info": "sh scripts/contributors.sh",
8 9
     "test": "sh test.sh"
9 10
   },
10 11
   "devDependencies": {
@@ -13,5 +14,22 @@
13 14
     "chokidar": "^1.5.1",
14 15
     "express": "^4.13.4",
15 16
     "multer": "^1.1.0"
16
-  }
17
-}
17
+  },
18
+  "contributors": [
19
+    "Andreas Amsenius <andreas@amsenius.se>",
20
+    "Corentin Smith <corentin.smith@gmail.com>",
21
+    "Dmitry Petukhov <dmitryvpetukhov@gmail.com>",
22
+    "Erik Smartt <code@eriksmartt.com>",
23
+    "Evgeniy Baraniuk <ev.baraniuk@gmail.com>",
24
+    "Juan B. Rodriguez <jbrodriguez@gmail.com>",
25
+    "Kaishley <kklingachetti@msn.com>",
26
+    "Mike Monteith <mike@mikemonteith.com>",
27
+    "Nguyen Cao Nhat Linh <nhatlinh95@gmail.com>",
28
+    "Tim Suchanek <tim.suchanek@gmail.com>",
29
+    "follower <github@rancidbacon.com>",
30
+    "francisco-sanchez-molina <psm1984@gmail.com>",
31
+    "kejinliang <kejinliang@users.noreply.github.com>",
32
+    "smartt <github@eriksmartt.com>",
33
+    ""
34
+  ]
35
+}

+ 1
- 0
scripts/README.md View File

@@ -0,0 +1 @@
1
+Always execute these scripts from root folder, not here.

+ 21
- 0
scripts/contributors.sh View File

@@ -0,0 +1,21 @@
1
+
2
+git log --pretty="%an <%ae>" | sort |uniq > CONTRIBUTORS.md
3
+sed -i.bak '/xeiyan@gmail.com/d' ./CONTRIBUTORS.md
4
+rm CONTRIBUTORS.md.bak
5
+
6
+echo "list contributors .."
7
+
8
+cat CONTRIBUTORS.md
9
+
10
+echo "update package.json .."
11
+
12
+node -e "var fs = require('fs');\
13
+        var json = JSON.parse(fs.readFileSync('./package.json'));\
14
+        var contributors = String(fs.readFileSync('./CONTRIBUTORS.md')).split(/[\r\n]/);\
15
+        json.contributors = contributors;\
16
+        var distJSON = JSON.parse(fs.readFileSync('./src/package.json'));\
17
+        distJSON.contributors = contributors;\
18
+        fs.writeFileSync('./src/package.json', JSON.stringify(distJSON, null, 2));\
19
+        fs.writeFileSync('./package.json', JSON.stringify(json, null, 2));"
20
+
21
+echo "done"

test.sh → scripts/test.sh View File

@@ -39,6 +39,7 @@ node -e "var fs=require('fs'); var pkg = JSON.parse(fs.readFileSync('./RNFetchBl
39 39
 cd "${TEST_APP_PATH}"
40 40
 # npm install --save "${CWD}/src"
41 41
 npm install --save react-native-fetch-blob
42
+# libs that requires web API polyfills
42 43
 npm install --save firebase
43 44
 react-native link
44 45
 

+ 25
- 4
src/README.md View File

@@ -1,11 +1,8 @@
1 1
 # react-native-fetch-blob
2 2
 [![release](https://img.shields.io/github/release/wkh237/react-native-fetch-blob.svg?style=flat-square)](https://github.com/wkh237/react-native-fetch-blob/releases) [![npm](https://img.shields.io/npm/v/react-native-fetch-blob.svg?style=flat-square)](https://www.npmjs.com/package/react-native-fetch-blob) ![](https://img.shields.io/badge/PR-Welcome-brightgreen.svg?style=flat-square) [![](https://img.shields.io/badge/Wiki-Public-brightgreen.svg?style=flat-square)](https://github.com/wkh237/react-native-fetch-blob/wiki) [![npm](https://img.shields.io/npm/l/react-native-fetch-blob.svg?maxAge=2592000&style=flat-square)]()
3 3
 
4
-
5 4
 A project committed to make file acess and data transfer easier, efficient for React Native developers.
6 5
 
7
-# [Please visit our Github Page for latest document](https://github.com/wkh237/react-native-fetch-blob)
8
-
9 6
 ## Features
10 7
 - Transfer data directly from/to storage without BASE64 bridging
11 8
 - File API supports normal files, Asset files, and CameraRoll files
@@ -85,7 +82,7 @@ pre 0.29 projects
85 82
 RNFB_ANDROID_PERMISSIONS=true rnpm link
86 83
 ```
87 84
 
88
-The link script might not take effect if you have non-default project structure, please visit [the wiki](https://github.com/wkh237/react-native-fetch-blob/wiki/Manually-Link-Package/_edit) to manually link the pacakge.
85
+The link script might not take effect if you have non-default project structure, please visit [the wiki](https://github.com/wkh237/react-native-fetch-blob/wiki/Manually-Link-Package) to manually link the pacakge.
89 86
 
90 87
 **Grant Permission to External storage for Android 5.0 or lower**
91 88
 
@@ -403,6 +400,30 @@ In `version >= 0.4.2` it is possible to know the upload/download progress. After
403 400
     })
404 401
 ```
405 402
 
403
+In `0.9.6`, you can specify an optional first argument which contains `count` and `interval` to limit progress event frequency (this will be done in native context in order to reduce RCT bridge overhead). Notice that `count` argument will not work if the server does not provide response content length.
404
+
405
+
406
+```js
407
+  RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
408
+      ... some headers,
409
+      'Content-Type' : 'octet-stream'
410
+    }, base64DataString)
411
+    // listen to upload progress event, emit every 250ms
412
+    .uploadProgress({ interval : 250 },(written, total) => {
413
+        console.log('uploaded', written / total)
414
+    })
415
+    // listen to download progress event, every 10%
416
+    .progress({ count : 10 }, (received, total) => {
417
+        console.log('progress', received / total)
418
+    })
419
+    .then((resp) => {
420
+      // ...
421
+    })
422
+    .catch((err) => {
423
+      // ...
424
+    })
425
+```
426
+
406 427
 ### Cancel Request
407 428
 
408 429
 After `0.7.0` it is possible to cancel a HTTP request. When the request cancel, it will definately throws an promise rejection, be sure to catch it.

+ 52
- 3
src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java View File

@@ -3,13 +3,16 @@ package com.RNFetchBlob;
3 3
 import android.content.Intent;
4 4
 import android.net.Uri;
5 5
 
6
+import com.RNFetchBlob.Utils.RNFBCookieJar;
6 7
 import com.facebook.react.bridge.Callback;
8
+import com.facebook.react.bridge.LifecycleEventListener;
7 9
 import com.facebook.react.bridge.Promise;
8 10
 import com.facebook.react.bridge.ReactApplicationContext;
9 11
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
10 12
 import com.facebook.react.bridge.ReactMethod;
11 13
 import com.facebook.react.bridge.ReadableArray;
12 14
 import com.facebook.react.bridge.ReadableMap;
15
+import com.facebook.react.bridge.WritableArray;
13 16
 
14 17
 import java.util.Map;
15 18
 import java.util.concurrent.LinkedBlockingQueue;
@@ -23,10 +26,12 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
23 26
     static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
24 27
     static LinkedBlockingQueue<Runnable> fsTaskQueue = new LinkedBlockingQueue<>();
25 28
     static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
29
+    static public boolean ActionViewVisible = false;
26 30
 
27 31
     public RNFetchBlob(ReactApplicationContext reactContext) {
28 32
 
29 33
         super(reactContext);
34
+
30 35
         RCTContext = reactContext;
31 36
     }
32 37
 
@@ -52,14 +57,33 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
52 57
     }
53 58
 
54 59
     @ReactMethod
55
-    public void actionViewIntent(String path, String mime, Promise promise) {
60
+    public void actionViewIntent(String path, String mime, final Promise promise) {
56 61
         try {
57 62
             Intent intent= new Intent(Intent.ACTION_VIEW)
58 63
                     .setDataAndType(Uri.parse("file://" + path), mime);
59 64
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
60
-
61 65
             this.getReactApplicationContext().startActivity(intent);
62
-            promise.resolve(null);
66
+            ActionViewVisible = true;
67
+
68
+            final LifecycleEventListener listener = new LifecycleEventListener() {
69
+                @Override
70
+                public void onHostResume() {
71
+                    if(ActionViewVisible)
72
+                        promise.resolve(null);
73
+                    RCTContext.removeLifecycleEventListener(this);
74
+                }
75
+
76
+                @Override
77
+                public void onHostPause() {
78
+
79
+                }
80
+
81
+                @Override
82
+                public void onHostDestroy() {
83
+
84
+                }
85
+            };
86
+            RCTContext.addLifecycleEventListener(listener);
63 87
         } catch(Exception ex) {
64 88
             promise.reject(ex.getLocalizedMessage());
65 89
         }
@@ -203,6 +227,20 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
203 227
 
204 228
     }
205 229
 
230
+    @ReactMethod
231
+    /**
232
+     * Get cookies belongs specific host.
233
+     * @param host String host name.
234
+     */
235
+    public void getCookies(String host, Promise promise) {
236
+        try {
237
+            WritableArray cookies = RNFBCookieJar.getCookies(host);
238
+            promise.resolve(cookies);
239
+        } catch(Exception err) {
240
+            promise.reject("RNFetchBlob.getCookies", err.getMessage());
241
+        }
242
+    }
243
+
206 244
     @ReactMethod
207 245
     /**
208 246
      * @param path Stream file path
@@ -241,6 +279,17 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
241 279
         RNFetchBlobReq.progressReport.put(taskId, config);
242 280
     }
243 281
 
282
+    @ReactMethod
283
+    public void df(final Callback callback) {
284
+        fsThreadPool.execute(new Runnable() {
285
+            @Override
286
+            public void run() {
287
+                RNFetchBlobFS.df(callback);
288
+            }
289
+        });
290
+    }
291
+
292
+
244 293
     @ReactMethod
245 294
     public void enableUploadProgressReport(String taskId, int interval, int count) {
246 295
         RNFetchBlobProgressConfig config = new RNFetchBlobProgressConfig(true, interval, count, RNFetchBlobProgressConfig.ReportType.Upload);

+ 9
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java View File

@@ -16,7 +16,9 @@ public class RNFetchBlobConfig {
16 16
     public String key;
17 17
     public String mime;
18 18
     public Boolean auto;
19
+    public Boolean overwrite = true;
19 20
     public long timeout = 60000;
21
+    public Boolean increment = false;
20 22
     public ReadableArray binaryContentTypes = null;
21 23
 
22 24
     RNFetchBlobConfig(ReadableMap options) {
@@ -31,8 +33,15 @@ public class RNFetchBlobConfig {
31 33
         }
32 34
         if(options.hasKey("binaryContentTypes"))
33 35
             this.binaryContentTypes = options.getArray("binaryContentTypes");
36
+        if(this.path != null && path.toLowerCase().contains("?append=true")) {
37
+            this.overwrite = false;
38
+        }
39
+
40
+        if(options.hasKey("overwrite"))
41
+            this.overwrite = options.getBoolean("overwrite");
34 42
         this.key = options.hasKey("key") ? options.getString("key") : null;
35 43
         this.mime = options.hasKey("contentType") ? options.getString("contentType") : null;
44
+        this.increment = options.hasKey("increment") ? options.getBoolean("increment") : false;
36 45
         this.auto = options.hasKey("auto") ? options.getBoolean("auto") : false;
37 46
         if(options.hasKey("timeout")) {
38 47
             this.timeout = options.getInt("timeout");

+ 30
- 6
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java View File

@@ -1,10 +1,14 @@
1 1
 package com.RNFetchBlob;
2 2
 
3
+import android.content.pm.PackageInfo;
4
+import android.content.pm.PackageManager;
3 5
 import android.content.res.AssetFileDescriptor;
4 6
 import android.media.MediaScannerConnection;
5 7
 import android.net.Uri;
6 8
 import android.os.AsyncTask;
9
+import android.os.Build;
7 10
 import android.os.Environment;
11
+import android.os.StatFs;
8 12
 import android.os.SystemClock;
9 13
 import android.util.Base64;
10 14
 
@@ -184,6 +188,7 @@ public class RNFetchBlobFS {
184 188
      */
185 189
     static public Map<String, Object> getSystemfolders(ReactApplicationContext ctx) {
186 190
         Map<String, Object> res = new HashMap<>();
191
+
187 192
         res.put("DocumentDir", ctx.getFilesDir().getAbsolutePath());
188 193
         res.put("CacheDir", ctx.getCacheDir().getAbsolutePath());
189 194
         res.put("DCIMDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath());
@@ -192,7 +197,12 @@ public class RNFetchBlobFS {
192 197
         res.put("DownloadDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath());
193 198
         res.put("MovieDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath());
194 199
         res.put("RingtoneDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES).getAbsolutePath());
195
-        res.put("SDCard", Environment.getExternalStorageDirectory().getAbsolutePath());
200
+        String state;
201
+        state = Environment.getExternalStorageState();
202
+        if (state.equals(Environment.MEDIA_MOUNTED)) {
203
+            res.put("SDCard", Environment.getExternalStorageDirectory().getAbsolutePath());
204
+        }
205
+        res.put("MainBundleDir", ctx.getApplicationInfo().dataDir);
196 206
         return res;
197 207
     }
198 208
 
@@ -687,7 +697,7 @@ public class RNFetchBlobFS {
687 697
             }
688 698
             else {
689 699
                 if (!created) {
690
-                    callback.invoke("create file error: failed to create file at path `" + path + "` for its parent path may not exists");
700
+                    callback.invoke("create file error: failed to create file at path `" + path + "` for its parent path may not exists, or the file already exists. If you intended to overwrite the existing file use fs.writeFile instead.");
691 701
                     return;
692 702
                 }
693 703
                 OutputStream ostream = new FileOutputStream(dest);
@@ -730,6 +740,20 @@ public class RNFetchBlobFS {
730 740
         }
731 741
     }
732 742
 
743
+    static void df(Callback callback) {
744
+        StatFs stat = new StatFs(Environment.getDataDirectory().getPath());
745
+        WritableMap args = Arguments.createMap();
746
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
747
+            args.putString("internal_free", String.valueOf(stat.getFreeBytes()));
748
+            args.putString("internal_total", String.valueOf(stat.getTotalBytes()));
749
+            StatFs statEx = new StatFs(Environment.getExternalStorageDirectory().getPath());
750
+            args.putString("external_free", String.valueOf(statEx.getFreeBytes()));
751
+            args.putString("external_total", String.valueOf(statEx.getTotalBytes()));
752
+
753
+        }
754
+        callback.invoke(null ,args);
755
+    }
756
+
733 757
     /**
734 758
      * Remove files in session.
735 759
      * @param paths An array of file paths.
@@ -782,14 +806,14 @@ public class RNFetchBlobFS {
782 806
      * @param event Event name, `data`, `end`, `error`, etc.
783 807
      * @param data  Event data
784 808
      */
785
-    void emitStreamEvent(String streamName, String event, String data) {
809
+    private void emitStreamEvent(String streamName, String event, String data) {
786 810
         WritableMap eventData = Arguments.createMap();
787 811
         eventData.putString("event", event);
788 812
         eventData.putString("detail", data);
789 813
         this.emitter.emit(streamName, eventData);
790 814
     }
791 815
 
792
-    void emitStreamEvent(String streamName, String event, WritableArray  data) {
816
+    private void emitStreamEvent(String streamName, String event, WritableArray data) {
793 817
         WritableMap eventData = Arguments.createMap();
794 818
         eventData.putString("event", event);
795 819
         eventData.putArray("detail", data);
@@ -838,13 +862,13 @@ public class RNFetchBlobFS {
838 862
 
839 863
     }
840 864
 
841
-    public static boolean isAsset(String path) {
865
+    static boolean isAsset(String path) {
842 866
         if(path != null)
843 867
             return path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET);
844 868
         return false;
845 869
     }
846 870
 
847
-    public static String normalizePath(String path) {
871
+    static String normalizePath(String path) {
848 872
         if(path == null)
849 873
             return null;
850 874
         Uri uri = Uri.parse(path);

+ 33
- 17
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java View File

@@ -11,6 +11,7 @@ import android.util.Base64;
11 11
 
12 12
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
13 13
 import com.RNFetchBlob.Response.RNFetchBlobFileResp;
14
+import com.RNFetchBlob.Utils.RNFBCookieJar;
14 15
 import com.facebook.react.bridge.Arguments;
15 16
 import com.facebook.react.bridge.Callback;
16 17
 import com.facebook.react.bridge.ReactApplicationContext;
@@ -25,6 +26,9 @@ import java.io.File;
25 26
 import java.io.FileOutputStream;
26 27
 import java.io.IOException;
27 28
 import java.io.InputStream;
29
+import java.net.CookieHandler;
30
+import java.net.CookieManager;
31
+import java.net.CookiePolicy;
28 32
 import java.net.MalformedURLException;
29 33
 import java.net.SocketException;
30 34
 import java.net.SocketTimeoutException;
@@ -39,6 +43,7 @@ import java.util.concurrent.TimeUnit;
39 43
 
40 44
 import okhttp3.Call;
41 45
 import okhttp3.ConnectionPool;
46
+import okhttp3.CookieJar;
42 47
 import okhttp3.Headers;
43 48
 import okhttp3.Interceptor;
44 49
 import okhttp3.MediaType;
@@ -162,7 +167,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
162 167
 
163 168
         // find cached result if `key` property exists
164 169
         String cacheKey = this.taskId;
165
-        String ext = this.options.appendExt.isEmpty() ? "." + this.options.appendExt : "";
170
+        String ext = this.options.appendExt.isEmpty() ? "" : "." + this.options.appendExt;
166 171
 
167 172
         if (this.options.key != null) {
168 173
             cacheKey = RNFetchBlobUtils.getMD5(this.options.key);
@@ -183,6 +188,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
183 188
         else if(this.options.fileCache)
184 189
             this.destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext;
185 190
 
191
+
186 192
         OkHttpClient.Builder clientBuilder;
187 193
 
188 194
         try {
@@ -220,7 +226,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
220 226
                 }
221 227
             }
222 228
 
223
-            if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put")) {
229
+            if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch")) {
224 230
                 String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase();
225 231
 
226 232
                 if(rawRequestBodyArray != null) {
@@ -281,7 +287,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
281 287
                     break;
282 288
 
283 289
                 case WithoutBody:
284
-                    if(method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("PUT"))
290
+                    if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch"))
285 291
                     {
286 292
                         builder.method(method, RequestBody.create(null, new byte[0]));
287 293
                     }
@@ -290,7 +296,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
290 296
                     break;
291 297
             }
292 298
 
299
+            // #156 fix cookie issue
300
+
301
+
293 302
             final Request req = builder.build();
303
+            clientBuilder.cookieJar(new RNFBCookieJar());
294 304
             clientBuilder.addNetworkInterceptor(new Interceptor() {
295 305
                 @Override
296 306
                 public Response intercept(Chain chain) throws IOException {
@@ -310,20 +320,23 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
310 320
                                 extended = new RNFetchBlobDefaultResp(
311 321
                                         RNFetchBlob.RCTContext,
312 322
                                         taskId,
313
-                                        originalResponse.body());
323
+                                        originalResponse.body(),
324
+                                        options.increment);
314 325
                                 break;
315 326
                             case FileStorage:
316 327
                                 extended = new RNFetchBlobFileResp(
317 328
                                         RNFetchBlob.RCTContext,
318 329
                                         taskId,
319 330
                                         originalResponse.body(),
320
-                                        destPath);
331
+                                        destPath,
332
+                                        options.overwrite);
321 333
                                 break;
322 334
                             default:
323 335
                                 extended = new RNFetchBlobDefaultResp(
324 336
                                         RNFetchBlob.RCTContext,
325 337
                                         taskId,
326
-                                        originalResponse.body());
338
+                                        originalResponse.body(),
339
+                                        options.increment);
327 340
                                 break;
328 341
                         }
329 342
                         return originalResponse.newBuilder().body(extended).build();
@@ -333,8 +346,9 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
333 346
                     }
334 347
                     catch (SocketTimeoutException e ){
335 348
                         timeout = true;
349
+                        RNFetchBlobUtils.emitWarningEvent("RNFetchBlob error when sending request : " + e.getLocalizedMessage());
336 350
                     } catch(Exception ex) {
337
-                        RNFetchBlobUtils.emitWarningEvent("RNFetchBlob error when sending request : " + ex.getLocalizedMessage());
351
+
338 352
                     }
339 353
                     return chain.proceed(chain.request());
340 354
                 }
@@ -486,7 +500,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
486 500
                     // and write response data to destination path.
487 501
                     resp.body().bytes();
488 502
                 } catch (Exception ignored) {
489
-                    ignored.printStackTrace();
503
+//                    ignored.printStackTrace();
490 504
                 }
491 505
                 this.destPath = this.destPath.replace("?append=true", "");
492 506
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath);
@@ -613,6 +627,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
613 627
                 DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
614 628
                 dm.query(query);
615 629
                 Cursor c = dm.query(query);
630
+                String error = null;
616 631
                 String filePath = null;
617 632
                 // the file exists in media content database
618 633
                 if (c.moveToFirst()) {
@@ -623,11 +638,6 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
623 638
                     if (cursor != null) {
624 639
                         cursor.moveToFirst();
625 640
                         filePath = cursor.getString(0);
626
-                        cursor.close();
627
-                        if(filePath != null) {
628
-                            this.callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, filePath);
629
-                            return;
630
-                        }
631 641
                     }
632 642
                 }
633 643
                 // When the file is not found in media content database, check if custom path exists
@@ -637,14 +647,20 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
637 647
                         boolean exists = new File(customDest).exists();
638 648
                         if(!exists)
639 649
                             throw new Exception("Download manager download failed, the file does not downloaded to destination.");
640
-                        callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, customDest);
650
+                        else
651
+                            this.callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, customDest);
641 652
 
642 653
                     } catch(Exception ex) {
643
-                        this.callback.invoke(ex.getLocalizedMessage(), null, null);
654
+                        error = ex.getLocalizedMessage();
644 655
                     }
645 656
                 }
646
-                else
647
-                    this.callback.invoke("Download manager could not resolve downloaded file path.", RNFetchBlobConst.RNFB_RESPONSE_PATH, null);
657
+                else {
658
+                    if(filePath == null)
659
+                        this.callback.invoke("Download manager could not resolve downloaded file path.", RNFetchBlobConst.RNFB_RESPONSE_PATH, null);
660
+                    else
661
+                        this.callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, filePath);
662
+                }
663
+
648 664
             }
649 665
         }
650 666
     }

+ 11
- 1
src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobDefaultResp.java View File

@@ -9,6 +9,7 @@ import com.facebook.react.bridge.WritableMap;
9 9
 import com.facebook.react.modules.core.DeviceEventManagerModule;
10 10
 
11 11
 import java.io.IOException;
12
+import java.nio.charset.Charset;
12 13
 
13 14
 import okhttp3.MediaType;
14 15
 import okhttp3.ResponseBody;
@@ -26,11 +27,13 @@ public class RNFetchBlobDefaultResp extends ResponseBody {
26 27
     String mTaskId;
27 28
     ReactApplicationContext rctContext;
28 29
     ResponseBody originalBody;
30
+    boolean isIncrement = false;
29 31
 
30
-    public RNFetchBlobDefaultResp(ReactApplicationContext ctx, String taskId, ResponseBody body) {
32
+    public RNFetchBlobDefaultResp(ReactApplicationContext ctx, String taskId, ResponseBody body, boolean isIncrement) {
31 33
         this.rctContext = ctx;
32 34
         this.mTaskId = taskId;
33 35
         this.originalBody = body;
36
+        this.isIncrement = isIncrement;
34 37
     }
35 38
 
36 39
     @Override
@@ -69,6 +72,13 @@ public class RNFetchBlobDefaultResp extends ResponseBody {
69 72
                 args.putString("taskId", mTaskId);
70 73
                 args.putString("written", String.valueOf(bytesRead));
71 74
                 args.putString("total", String.valueOf(contentLength()));
75
+                if(isIncrement) {
76
+                    args.putString("chunk", sink.readString(Charset.defaultCharset()));
77
+                }
78
+                else {
79
+                    args.putString("chunk", "");
80
+                }
81
+
72 82
                 rctContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
73 83
                         .emit(RNFetchBlobConst.EVENT_PROGRESS, args);
74 84
             }

+ 2
- 2
src/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java View File

@@ -34,7 +34,7 @@ public class RNFetchBlobFileResp extends ResponseBody {
34 34
     ReactApplicationContext rctContext;
35 35
     FileOutputStream ofStream;
36 36
 
37
-    public RNFetchBlobFileResp(ReactApplicationContext ctx, String taskId, ResponseBody body, String path) throws IOException {
37
+    public RNFetchBlobFileResp(ReactApplicationContext ctx, String taskId, ResponseBody body, String path, boolean overwrite) throws IOException {
38 38
         super();
39 39
         this.rctContext = ctx;
40 40
         this.mTaskId = taskId;
@@ -42,7 +42,7 @@ public class RNFetchBlobFileResp extends ResponseBody {
42 42
         assert path != null;
43 43
         this.mPath = path;
44 44
         if (path != null) {
45
-            boolean appendToExistingFile = path.contains("?append=true");
45
+            boolean appendToExistingFile = !overwrite;
46 46
             path = path.replace("?append=true", "");
47 47
             mPath = path;
48 48
             File f = new File(path);

+ 53
- 0
src/android/src/main/java/com/RNFetchBlob/Utils/RNFBCookieJar.java View File

@@ -0,0 +1,53 @@
1
+package com.RNFetchBlob.Utils;
2
+
3
+import com.facebook.react.bridge.Arguments;
4
+import com.facebook.react.bridge.ReadableMap;
5
+import com.facebook.react.bridge.WritableArray;
6
+import com.facebook.react.bridge.WritableMap;
7
+
8
+import java.util.ArrayList;
9
+import java.util.HashMap;
10
+import java.util.List;
11
+
12
+import okhttp3.Cookie;
13
+import okhttp3.CookieJar;
14
+import okhttp3.HttpUrl;
15
+
16
+/**
17
+ * Created by wkh237on 2016/10/14.
18
+ */
19
+
20
+
21
+
22
+public class RNFBCookieJar implements CookieJar {
23
+
24
+    static final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
25
+    private List<Cookie> cookies;
26
+
27
+    @Override
28
+    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
29
+        cookieStore.put(url.host(), cookies);
30
+    }
31
+
32
+    @Override
33
+    public List<Cookie> loadForRequest(HttpUrl url) {
34
+        List<Cookie> cookies = cookieStore.get(url.host());
35
+        return cookies != null ? cookies : new ArrayList<Cookie>();
36
+    }
37
+
38
+    public static WritableArray getCookies(String host) {
39
+        HttpUrl url = HttpUrl.parse(host);
40
+        List<Cookie> cookies = null;
41
+        if(url != null) {
42
+            cookies = cookieStore.get(url.host());
43
+        }
44
+        WritableArray cookieList = Arguments.createArray();
45
+        if(cookies != null) {
46
+            for(Cookie c : cookies){
47
+                cookieList.pushString(c.toString());
48
+            }
49
+            return cookieList;
50
+        }
51
+        return null;
52
+    }
53
+}

+ 15
- 2
src/fs.js View File

@@ -29,7 +29,8 @@ const dirs = {
29 29
     MovieDir : RNFetchBlob.MovieDir,
30 30
     DownloadDir : RNFetchBlob.DownloadDir,
31 31
     DCIMDir : RNFetchBlob.DCIMDir,
32
-    SDCardDir : RNFetchBlob.SDCardDir
32
+    SDCardDir : RNFetchBlob.SDCardDir,
33
+    MainBundleDir : RNFetchBlob.MainBundleDir
33 34
 }
34 35
 
35 36
 /**
@@ -338,6 +339,17 @@ function isDir(path:string):Promise<bool, bool> {
338 339
 
339 340
 }
340 341
 
342
+function df():Promise<{ free : number, total : number }> {
343
+  return new Promise((resolve, reject) => {
344
+    RNFetchBlob.df((err, stat) => {
345
+      if(err)
346
+        reject(err)
347
+      else
348
+        resolve(stat)
349
+    })
350
+  })
351
+}
352
+
341 353
 export default {
342 354
   RNFetchBlobSession,
343 355
   unlink,
@@ -359,5 +371,6 @@ export default {
359 371
   scanFile,
360 372
   dirs,
361 373
   slice,
362
-  asset
374
+  asset,
375
+  df
363 376
 }

+ 124
- 5
src/index.js View File

@@ -9,6 +9,7 @@ import {
9 9
   NativeAppEventEmitter,
10 10
   Platform,
11 11
   AsyncStorage,
12
+  AppState,
12 13
 } from 'react-native'
13 14
 import type {
14 15
   RNFetchBlobNative,
@@ -16,12 +17,16 @@ import type {
16 17
   RNFetchBlobStream,
17 18
   RNFetchBlobResponseInfo
18 19
 } from './types'
20
+import URIUtil from './utils/uri'
19 21
 import StatefulPromise from './class/StatefulPromise.js'
20 22
 import fs from './fs'
21 23
 import getUUID from './utils/uuid'
22 24
 import base64 from 'base-64'
23 25
 import polyfill from './polyfill'
24 26
 import android from './android'
27
+import ios from './ios'
28
+import net from './net'
29
+import JSONStream from './json-stream'
25 30
 const {
26 31
   RNFetchBlobSession,
27 32
   readStream,
@@ -38,13 +43,23 @@ const {
38 43
   cp
39 44
 } = fs
40 45
 
41
-
42 46
 const Blob = polyfill.Blob
43 47
 const emitter = DeviceEventEmitter
44
-const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
48
+const RNFetchBlob = NativeModules.RNFetchBlob
49
+
50
+// when app resumes, check if there's any expired network task and trigger
51
+// their .expire event
52
+if(Platform.OS === 'ios') {
53
+  AppState.addEventListener('change', (e) => {
54
+    console.log('app state changed', e)
55
+    if(e === 'active')
56
+      RNFetchBlob.emitExpiredEvent(()=>{})
57
+  })
58
+}
45 59
 
46 60
 // register message channel event handler.
47 61
 emitter.addListener("RNFetchBlobMessage", (e) => {
62
+
48 63
   if(e.event === 'warn') {
49 64
     console.warn(e.detail)
50 65
   }
@@ -99,6 +114,89 @@ function config (options:RNFetchBlobConfig) {
99 114
   return { fetch : fetch.bind(options) }
100 115
 }
101 116
 
117
+/**
118
+ * Fetch from file system, use the same interface as RNFB.fetch
119
+ * @param  {RNFetchBlobConfig} [options={}] Fetch configurations
120
+ * @param  {string} method     Should be one of `get`, `post`, `put`
121
+ * @param  {string} url        A file URI string
122
+ * @param  {string} headers    Arguments of file system API
123
+ * @param  {any} body       Data to put or post to file systen.
124
+ * @return {Promise}
125
+ */
126
+function fetchFile(options = {}, method, url, headers = {}, body):Promise {
127
+
128
+  if(!URIUtil.isFileURI(url)) {
129
+    throw `could not fetch file from an invalid URI : ${url}`
130
+  }
131
+
132
+  url = URIUtil.unwrapFileURI(url)
133
+
134
+  let promise = null
135
+  let cursor = 0
136
+  let total = -1
137
+  let cacheData = ''
138
+  let info = null
139
+  let _progress, _uploadProgress, _stateChange
140
+
141
+  switch(method.toLowerCase()) {
142
+
143
+    case 'post':
144
+    break
145
+
146
+    case 'put':
147
+    break
148
+
149
+    // read data from file system
150
+    default:
151
+      promise = fs.stat(url)
152
+      .then((stat) => {
153
+        total = stat.size
154
+        return fs.readStream(url,
155
+          headers.encoding || 'utf8',
156
+          Math.floor(headers.bufferSize) || 409600,
157
+          Math.floor(headers.interval) || 100
158
+        )
159
+      })
160
+      .then((stream) => new Promise((resolve, reject) => {
161
+        stream.open()
162
+        info = {
163
+          state : "2",
164
+          headers : { 'source' : 'system-fs' },
165
+          status : 200,
166
+          respType : 'text',
167
+          rnfbEncode : headers.encoding || 'utf8'
168
+        }
169
+        _stateChange(info)
170
+        stream.onData((chunk) => {
171
+          _progress && _progress(cursor, total, chunk)
172
+          if(headers.noCache)
173
+            return
174
+          cacheData += chunk
175
+        })
176
+        stream.onError((err) => { reject(err) })
177
+        stream.onEnd(() => {
178
+          resolve(new FetchBlobResponse(null, info, cacheData))
179
+        })
180
+      }))
181
+    break
182
+  }
183
+
184
+  promise.progress = (fn) => {
185
+    _progress = fn
186
+    return promise
187
+  }
188
+  promise.stateChange = (fn) => {
189
+    _stateChange = fn
190
+    return promise
191
+  }
192
+  promise.uploadProgress = (fn) => {
193
+    _uploadProgress = fn
194
+    return promise
195
+  }
196
+
197
+  return promise
198
+}
199
+
102 200
 /**
103 201
  * Create a HTTP request by settings, the `this` context is a `RNFetchBlobConfig` object.
104 202
  * @param  {string} method HTTP method, should be `GET`, `POST`, `PUT`, `DELETE`
@@ -118,15 +216,21 @@ function fetch(...args:any):Promise {
118 216
   let options = this || {}
119 217
   let subscription, subscriptionUpload, stateEvent, partEvent
120 218
   let respInfo = {}
219
+  let [method, url, headers, body] = [...args]
220
+
221
+  // fetch from file system
222
+  if(URIUtil.isFileURI(url)) {
223
+    return fetchFile(options, method, url, headers, body)
224
+  }
121 225
 
226
+  // from remote HTTP(S)
122 227
   let promise = new Promise((resolve, reject) => {
123
-    let [method, url, headers, body] = [...args]
124 228
     let nativeMethodName = Array.isArray(body) ? 'fetchBlobForm' : 'fetchBlob'
125 229
 
126 230
     // on progress event listener
127 231
     subscription = emitter.addListener('RNFetchBlobProgress', (e) => {
128 232
       if(e.taskId === taskId && promise.onProgress) {
129
-        promise.onProgress(e.written, e.total)
233
+        promise.onProgress(e.written, e.total, e.chunk)
130 234
       }
131 235
     })
132 236
 
@@ -143,6 +247,13 @@ function fetch(...args:any):Promise {
143 247
       }
144 248
     })
145 249
 
250
+    subscription = emitter.addListener('RNFetchBlobExpire', (e) => {
251
+      console.log(e , 'EXPIRED!!')
252
+      if(e.taskId === taskId && promise.onExpire) {
253
+        promise.onExpire(e)
254
+      }
255
+    })
256
+
146 257
     partEvent = emitter.addListener('RNFetchBlobServerPush', (e) => {
147 258
       if(e.taskId === taskId && promise.onPartData) {
148 259
         promise.onPartData(e.chunk)
@@ -180,6 +291,7 @@ function fetch(...args:any):Promise {
180 291
       delete promise['stateChange']
181 292
       delete promise['part']
182 293
       delete promise['cancel']
294
+      // delete promise['expire']
183 295
       promise.cancel = () => {}
184 296
 
185 297
       if(err)
@@ -245,6 +357,10 @@ function fetch(...args:any):Promise {
245 357
     promise.onStateChange = fn
246 358
     return promise
247 359
   }
360
+  promise.expire = (fn) => {
361
+    promise.onExpire = fn
362
+    return promise
363
+  }
248 364
   promise.cancel = (fn) => {
249 365
     fn = fn || function(){}
250 366
     subscription.remove()
@@ -438,9 +554,12 @@ export default {
438 554
   fetch,
439 555
   base64,
440 556
   android,
557
+  ios,
441 558
   config,
442 559
   session,
443 560
   fs,
444 561
   wrap,
445
-  polyfill
562
+  net,
563
+  polyfill,
564
+  JSONStream
446 565
 }

+ 44
- 0
src/ios.js View File

@@ -0,0 +1,44 @@
1
+// Copyright 2016 wkh237@github. All rights reserved.
2
+// Use of this source code is governed by a MIT-style license that can be
3
+// found in the LICENSE file.
4
+// @flow
5
+
6
+import {
7
+  NativeModules,
8
+  DeviceEventEmitter,
9
+  Platform,
10
+  NativeAppEventEmitter,
11
+} from 'react-native'
12
+
13
+const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
14
+
15
+/**
16
+ * Open a file using UIDocumentInteractionController
17
+ * @param  {string]} path Path of the file to be open.
18
+ * @param  {string} scheme URI scheme that needs to support, optional
19
+ * @return {Promise}
20
+ */
21
+function previewDocument(path:string, scheme:string) {
22
+  if(Platform.OS === 'ios')
23
+    return RNFetchBlob.previewDocument('file://' + path, scheme)
24
+  else
25
+    return Promise.reject('RNFetchBlob.openDocument only supports IOS.')
26
+}
27
+
28
+/**
29
+ * Preview a file using UIDocumentInteractionController
30
+ * @param  {string]} path Path of the file to be open.
31
+ * @param  {string} scheme URI scheme that needs to support, optional
32
+ * @return {Promise}
33
+ */
34
+function openDocument(path:string, scheme:string) {
35
+  if(Platform.OS === 'ios')
36
+    return RNFetchBlob.openDocument('file://' + path, scheme)
37
+  else
38
+    return Promise.reject('RNFetchBlob.previewDocument only supports IOS.')
39
+}
40
+
41
+export default {
42
+  openDocument,
43
+  previewDocument
44
+}

+ 2
- 2
src/ios/RNFetchBlob.xcodeproj/project.pbxproj View File

@@ -198,7 +198,7 @@
198 198
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
199 199
 				GCC_WARN_UNUSED_FUNCTION = YES;
200 200
 				GCC_WARN_UNUSED_VARIABLE = YES;
201
-				IPHONEOS_DEPLOYMENT_TARGET = 7.0;
201
+				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
202 202
 				MTL_ENABLE_DEBUG_INFO = YES;
203 203
 				ONLY_ACTIVE_ARCH = YES;
204 204
 				SDKROOT = iphoneos;
@@ -236,7 +236,7 @@
236 236
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
237 237
 				GCC_WARN_UNUSED_FUNCTION = YES;
238 238
 				GCC_WARN_UNUSED_VARIABLE = YES;
239
-				IPHONEOS_DEPLOYMENT_TARGET = 7.0;
239
+				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
240 240
 				MTL_ENABLE_DEBUG_INFO = NO;
241 241
 				SDKROOT = iphoneos;
242 242
 				VALIDATE_PRODUCT = YES;

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

@@ -7,17 +7,20 @@
7 7
 #ifndef RNFetchBlob_h
8 8
 #define RNFetchBlob_h
9 9
 #import "RCTBridgeModule.h"
10
+#import <UIKit/UIKit.h>
10 11
 
11 12
 
12
-@interface RNFetchBlob : NSObject <RCTBridgeModule> {
13
+@interface RNFetchBlob : NSObject <RCTBridgeModule, UIDocumentInteractionControllerDelegate> {
13 14
 
14 15
     NSString * filePathPrefix;
15 16
 
16 17
 }
17 18
 
18 19
 @property (nonatomic) NSString * filePathPrefix;
20
+@property (retain) UIDocumentInteractionController * documentController;
19 21
 
20 22
 + (RCTBridge *)getRCTBridge;
23
++ (void) checkExpiredSessions;
21 24
 
22 25
 @end
23 26
 

+ 66
- 3
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"
@@ -15,7 +16,7 @@
15 16
 #import "RNFetchBlobProgress.h"
16 17
 
17 18
 
18
-RCTBridge * bridgeRef;
19
+__strong RCTBridge * bridgeRef;
19 20
 dispatch_queue_t commonTaskQueue;
20 21
 dispatch_queue_t fsQueue;
21 22
 
@@ -30,6 +31,7 @@ dispatch_queue_t fsQueue;
30 31
 @implementation RNFetchBlob
31 32
 
32 33
 @synthesize filePathPrefix;
34
+@synthesize documentController;
33 35
 @synthesize bridge = _bridge;
34 36
 
35 37
 - (dispatch_queue_t) methodQueue {
@@ -40,7 +42,8 @@ dispatch_queue_t fsQueue;
40 42
 
41 43
 + (RCTBridge *)getRCTBridge
42 44
 {
43
-    return bridgeRef;
45
+    RCTRootView * rootView = [[UIApplication sharedApplication] keyWindow].rootViewController.view;
46
+    return rootView.bridge;
44 47
 }
45 48
 
46 49
 RCT_EXPORT_MODULE();
@@ -58,12 +61,14 @@ RCT_EXPORT_MODULE();
58 61
         [[NSFileManager defaultManager] createDirectoryAtPath:[RNFetchBlobFS getTempPath] withIntermediateDirectories:YES attributes:nil error:NULL];
59 62
     }
60 63
     bridgeRef = _bridge;
64
+    [RNFetchBlobNetwork emitExpiredTasks];
61 65
     return self;
62 66
 }
63 67
 
64 68
 - (NSDictionary *)constantsToExport
65 69
 {
66 70
     return @{
71
+             @"MainBundleDir" : [RNFetchBlobFS getMainBundleDir],
67 72
              @"DocumentDir": [RNFetchBlobFS getDocumentDir],
68 73
              @"CacheDir" : [RNFetchBlobFS getCacheDir]
69 74
              };
@@ -87,6 +92,7 @@ RCT_EXPORT_METHOD(fetchBlobForm:(NSDictionary *)options
87 92
 
88 93
 }
89 94
 
95
+
90 96
 // Fetch blob data request
91 97
 RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options
92 98
                   taskId:(NSString *)taskId
@@ -432,7 +438,64 @@ RCT_EXPORT_METHOD(slice:(NSString *)src dest:(NSString *)dest start:(nonnull NSN
432 438
     [RNFetchBlobFS slice:src dest:dest start:start end:end encode:@"" resolver:resolve rejecter:reject];
433 439
 })
434 440
 
435
-#pragma mark RNFetchBlob private methods
441
+RCT_EXPORT_METHOD(previewDocument:(NSString*)uri scheme:(NSString *)scheme resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
442
+{
443
+    
444
+    NSURL * url = [[NSURL alloc] initWithString:uri];
445
+    documentController = [UIDocumentInteractionController interactionControllerWithURL:url];
446
+    UIViewController *rootCtrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
447
+    documentController.delegate = self;
448
+    if(scheme == nil || [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:scheme]]) {
449
+        dispatch_sync(dispatch_get_main_queue(), ^{
450
+            [documentController  presentOptionsMenuFromRect:rootCtrl.view.bounds inView:rootCtrl.view animated:YES];
451
+        });
452
+        resolve(@[[NSNull null]]);
453
+    } else {
454
+        reject(@"RNFetchBlob could not open document", @"scheme is not supported", nil);
455
+    }
456
+})
457
+
458
+# pragma mark - open file with UIDocumentInteractionController and delegate
459
+
460
+RCT_EXPORT_METHOD(openDocument:(NSString*)uri scheme:(NSString *)scheme resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
461
+{
462
+    
463
+    NSURL * url = [[NSURL alloc] initWithString:uri];
464
+    documentController = [UIDocumentInteractionController interactionControllerWithURL:url];
465
+    documentController.delegate = self;
466
+    
467
+    if(scheme == nil || [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:scheme]]) {
468
+        dispatch_sync(dispatch_get_main_queue(), ^{
469
+            [documentController presentPreviewAnimated:YES];
470
+        });
471
+        resolve(@[[NSNull null]]);
472
+    } else {
473
+        reject(@"RNFetchBlob could not open document", @"scheme is not supported", nil);
474
+    }
475
+})
476
+
477
+RCT_EXPORT_METHOD(df:(RCTResponseSenderBlock)callback
478
+{
479
+    [RNFetchBlobFS df:callback];
480
+})
481
+
482
+- (UIViewController *) documentInteractionControllerViewControllerForPreview: (UIDocumentInteractionController *) controller {
483
+    UIWindow *window = [UIApplication sharedApplication].keyWindow;
484
+    return window.rootViewController;
485
+}
486
+
487
+# pragma mark - getCookies
488
+RCT_EXPORT_METHOD(getCookies:(NSString *)url resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
489
+{
490
+    resolve([RNFetchBlobNetwork getCookies:url]);
491
+})
492
+
493
+# pragma mark - check expired network events
494
+
495
+RCT_EXPORT_METHOD(emitExpiredEvent:(RCTResponseSenderBlock)callback
496
+{
497
+    [RNFetchBlobNetwork emitExpiredTasks];
498
+})
436 499
 
437 500
 
438 501
 @end

+ 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_SERVER_PUSH;
22 23
 extern NSString *const EVENT_PROGRESS_UPLOAD;

+ 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";
@@ -26,6 +24,7 @@ extern NSString *const EVENT_STATE_CHANGE = @"RNFetchBlobState";
26 24
 extern NSString *const EVENT_SERVER_PUSH = @"RNFetchBlobServerPush";
27 25
 extern NSString *const EVENT_PROGRESS = @"RNFetchBlobProgress";
28 26
 extern NSString *const EVENT_PROGRESS_UPLOAD = @"RNFetchBlobProgress-upload";
27
+extern NSString *const EVENT_EXPIRE = @"RNFetchBlobExpire";
29 28
 
30 29
 extern NSString *const MSG_EVENT = @"RNFetchBlobMessage";
31 30
 extern NSString *const MSG_EVENT_LOG = @"log";

+ 4
- 0
src/ios/RNFetchBlobFS.h View File

@@ -39,6 +39,7 @@
39 39
 @property (nonatomic) BOOL appendData;
40 40
 
41 41
 // get dirs
42
++ (NSString *) getMainBundleDir;
42 43
 + (NSString *) getTempPath;
43 44
 + (NSString *) getCacheDir;
44 45
 + (NSString *) getDocumentDir;
@@ -65,6 +66,7 @@
65 66
 //+ (void) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append;
66 67
 + (void) writeAssetToPath:(ALAssetRepresentation * )rep dest:(NSString *)dest;
67 68
 + (void) readStream:(NSString *)uri encoding:(NSString * )encoding bufferSize:(int)bufferSize tick:(int)tick streamId:(NSString *)streamId bridgeRef:(RCTBridge *)bridgeRef;
69
++ (void) df:(RCTResponseSenderBlock)callback;
68 70
 
69 71
 // constructor
70 72
 - (id) init;
@@ -82,6 +84,8 @@
82 84
 - (void) closeInStream;
83 85
 - (void) closeOutStream;
84 86
 
87
+- (void) openFile:( NSString * _Nonnull ) uri;
88
+
85 89
 @end
86 90
 
87 91
 #endif /* RNFetchBlobFS_h */

+ 37
- 5
src/ios/RNFetchBlobFS.m View File

@@ -24,7 +24,10 @@ NSMutableDictionary *fileStreams = nil;
24 24
 //  File system access methods
25 25
 //
26 26
 ////////////////////////////////////////
27
-
27
+@interface RNFetchBlobFS() {
28
+    UIDocumentInteractionController * docCtrl;
29
+}
30
+@end
28 31
 @implementation RNFetchBlobFS
29 32
 
30 33
 
@@ -82,6 +85,10 @@ NSMutableDictionary *fileStreams = nil;
82 85
 
83 86
 #pragma mark - system directories
84 87
 
88
++ (NSString *) getMainBundleDir {
89
+    return [[NSBundle mainBundle] bundlePath];
90
+}
91
+
85 92
 + (NSString *) getCacheDir {
86 93
     return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
87 94
 }
@@ -104,7 +111,7 @@ NSMutableDictionary *fileStreams = nil;
104 111
 
105 112
 + (NSString *) getTempPath {
106 113
     
107
-    return [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingString:@"/RNFetchBlob_tmp"];
114
+    return NSTemporaryDirectory();
108 115
 }
109 116
 
110 117
 + (NSString *) getTempPath:(NSString*)taskId withExtension:(NSString *)ext {
@@ -409,9 +416,9 @@ NSMutableDictionary *fileStreams = nil;
409 416
             }
410 417
             else
411 418
             {
412
-                BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path];
413
-                if(!exists) {
414
-                    reject(@"RNFetchBlobFS readFile error", @"file not exists", [[NSError alloc]init]);
419
+                if(![[NSFileManager defaultManager] fileExistsAtPath:path]) {
420
+                    
421
+                    reject(@"RNFetchBlobFS readFile error", @"file not exists", nil);
415 422
                     return;
416 423
                 }
417 424
                 fileContent = [NSData dataWithContentsOfFile:path];
@@ -715,6 +722,31 @@ NSMutableDictionary *fileStreams = nil;
715 722
     }
716 723
 }
717 724
 
725
+#pragma mark - get disk space
726
+
727
++(void) df:(RCTResponseSenderBlock)callback
728
+{
729
+    uint64_t totalSpace = 0;
730
+    uint64_t totalFreeSpace = 0;
731
+    NSError *error = nil;
732
+    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
733
+    NSDictionary *dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: &error];
734
+    
735
+    if (dictionary) {
736
+        NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize];
737
+        NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize];
738
+        totalSpace = [fileSystemSizeInBytes unsignedLongLongValue];
739
+        totalFreeSpace = [freeFileSystemSizeInBytes unsignedLongLongValue];
740
+        callback(@[[NSNull null], @{
741
+                  @"free" : [NSString stringWithFormat:@"%d", totalFreeSpace],
742
+                  @"total" : [NSString stringWithFormat:@"%d", totalSpace]
743
+                }]);
744
+    } else {
745
+        callback(@[@"failed to get storage usage."]);
746
+    }
747
+    
748
+}
749
+
718 750
 + (void) writeAssetToPath:(ALAssetRepresentation * )rep dest:(NSString *)dest
719 751
 {
720 752
     int read = 0;

+ 12
- 7
src/ios/RNFetchBlobNetwork.h View File

@@ -6,8 +6,8 @@
6 6
 //  Copyright © 2016 wkh237. All rights reserved.
7 7
 //
8 8
 
9
-#ifndef RNFetchBlobResp_h
10
-#define RNFetchBlobResp_h
9
+#ifndef RNFetchBlobNetwork_h
10
+#define RNFetchBlobNetwork_h
11 11
 
12 12
 #import <Foundation/Foundation.h>
13 13
 #import "RCTBridgeModule.h"
@@ -33,17 +33,22 @@ typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse
33 33
 @property (nullable, nonatomic) NSError * error;
34 34
 
35 35
 
36
-- (nullable id) init;
37
-- (void) sendRequest;
38
-
39 36
 + (NSMutableDictionary  * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers;
40 37
 + (void) cancelRequest:(NSString *)taskId;
38
++ (void) enableProgressReport:(NSString *) taskId;
39
++ (void) enableUploadProgress:(NSString *) taskId;
40
++ (void) emitExpiredTasks;
41
+
42
+- (nullable id) init;
43
+- (void) sendRequest;
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;
41 45
 + (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config;
42 46
 + (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config;
43
-- (void) sendRequest:(NSDictionary  * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback;
47
++ (NSArray *) getCookies:(NSString *) url;
48
+
44 49
 
45 50
 
46 51
 @end
47 52
 
48 53
 
49
-#endif /* RNFetchBlobResp_h */
54
+#endif /* RNFetchBlobNetwork_h */

+ 165
- 61
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"
@@ -25,6 +27,8 @@
25 27
 ////////////////////////////////////////
26 28
 
27 29
 NSMapTable * taskTable;
30
+NSMapTable * expirationTable;
31
+NSMapTable * cookiesTable;
28 32
 NSMutableDictionary * progressTable;
29 33
 NSMutableDictionary * uploadProgressTable;
30 34
 
@@ -39,6 +43,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
39 43
 {
40 44
     BOOL * respFile;
41 45
     BOOL isNewPart;
46
+    BOOL * isIncrement;
42 47
     NSMutableData * partBuffer;
43 48
     NSString * destPath;
44 49
     NSOutputStream * writeStream;
@@ -73,7 +78,12 @@ NSOperationQueue *taskQueue;
73 78
         taskQueue = [[NSOperationQueue alloc] init];
74 79
         taskQueue.maxConcurrentOperationCount = 10;
75 80
     }
76
-    if(taskTable == nil) {
81
+    if(expirationTable == nil)
82
+    {
83
+        expirationTable = [[NSMapTable alloc] init];
84
+    }
85
+    if(taskTable == nil)
86
+    {
77 87
         taskTable = [[NSMapTable alloc] init];
78 88
     }
79 89
     if(progressTable == nil)
@@ -84,9 +94,55 @@ NSOperationQueue *taskQueue;
84 94
     {
85 95
         uploadProgressTable = [[NSMutableDictionary alloc] init];
86 96
     }
97
+    if(cookiesTable == nil)
98
+    {
99
+        cookiesTable = [[NSMapTable alloc] init];
100
+    }
87 101
     return self;
88 102
 }
89 103
 
104
++ (NSArray *) getCookies:(NSString *) url
105
+{
106
+    NSString * hostname = [[NSURL URLWithString:url] host];
107
+    NSMutableArray * cookies = [NSMutableArray new];
108
+    NSArray * list = [cookiesTable objectForKey:hostname];
109
+    for(NSHTTPCookie * cookie in list)
110
+    {
111
+        NSMutableString * cookieStr = [[NSMutableString alloc] init];
112
+        [cookieStr appendString:cookie.name];
113
+        [cookieStr appendString:@"="];
114
+        [cookieStr appendString:cookie.value];
115
+        
116
+        if(cookie.expiresDate == nil) {
117
+            [cookieStr appendString:@"; max-age=0"];
118
+        }
119
+        else {
120
+            [cookieStr appendString:@"; expires="];
121
+            NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
122
+            [dateFormatter setDateFormat:@"EEE, dd MM yyyy HH:mm:ss ZZZ"];
123
+            NSString *strDate = [dateFormatter stringFromDate:cookie.expiresDate];
124
+            [cookieStr appendString:strDate];
125
+        }
126
+        
127
+        
128
+        [cookieStr appendString:@"; domain="];
129
+        [cookieStr appendString:hostname];
130
+        [cookieStr appendString:@"; path="];
131
+        [cookieStr appendString:cookie.path];
132
+        
133
+        
134
+        if (cookie.isSecure) {
135
+            [cookieStr appendString:@"; secure"];
136
+        }
137
+        
138
+        if (cookie.isHTTPOnly) {
139
+            [cookieStr appendString:@"; httponly"];
140
+        }
141
+        [cookies addObject:cookieStr];
142
+    }
143
+    return cookies;
144
+}
145
+
90 146
 + (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config
91 147
 {
92 148
     [progressTable setValue:config forKey:taskId];
@@ -136,9 +192,11 @@ NSOperationQueue *taskQueue;
136 192
     self.expectedBytes = 0;
137 193
     self.receivedBytes = 0;
138 194
     self.options = options;
195
+    isIncrement = [options valueForKey:@"increment"] == nil ? NO : [[options valueForKey:@"increment"] boolValue];
139 196
     redirects = [[NSMutableArray alloc] init];
140
-    [redirects addObject:req.URL.absoluteString];
141
-    
197
+    if(req.URL != nil)
198
+        [redirects addObject:req.URL.absoluteString];
199
+
142 200
     // set response format
143 201
     NSString * rnfbResp = [req.allHTTPHeaderFields valueForKey:@"RNFB-Response"];
144 202
     if([[rnfbResp lowercaseString] isEqualToString:@"base64"])
@@ -156,7 +214,9 @@ NSOperationQueue *taskQueue;
156 214
     bodyLength = contentLength;
157 215
 
158 216
     // the session trust any SSL certification
159
-    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
217
+//    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
218
+    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId];
219
+
160 220
     // set request timeout
161 221
     float timeout = [options valueForKey:@"timeout"] == nil ? -1 : [[options valueForKey:@"timeout"] floatValue];
162 222
     if(timeout > 0)
@@ -193,13 +253,44 @@ NSOperationQueue *taskQueue;
193 253
         respData = [[NSMutableData alloc] init];
194 254
         respFile = NO;
195 255
     }
196
-    NSURLSessionDataTask * task = [session dataTaskWithRequest:req];
256
+
257
+    __block NSURLSessionDataTask * task = [session dataTaskWithRequest:req];
197 258
     [taskTable setObject:task forKey:taskId];
198 259
     [task resume];
199 260
 
200 261
     // network status indicator
201 262
     if([[options objectForKey:CONFIG_INDICATOR] boolValue] == YES)
202 263
         [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
264
+    __block UIApplication * app = [UIApplication sharedApplication];
265
+
266
+    // #115 handling task expired when application entering backgound for a long time
267
+    UIBackgroundTaskIdentifier tid = [app beginBackgroundTaskWithName:taskId expirationHandler:^{
268
+        NSLog([NSString stringWithFormat:@"session %@ expired", taskId ]);
269
+        [expirationTable setObject:task forKey:taskId];
270
+        [app endBackgroundTask:tid];
271
+    }];
272
+
273
+
274
+}
275
+
276
+// #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled
277
++ (void) emitExpiredTasks
278
+{
279
+    NSEnumerator * emu =  [expirationTable keyEnumerator];
280
+    NSString * key;
281
+
282
+    while((key = [emu nextObject]))
283
+    {
284
+        RCTBridge * bridge = [RNFetchBlob getRCTBridge];
285
+        NSData * args = @{ @"taskId": key };
286
+        [bridge.eventDispatcher sendDeviceEventWithName:EVENT_EXPIRE body:args];
287
+
288
+    }
289
+    
290
+    // clear expired task entries
291
+    [expirationTable removeAllObjects];
292
+    expirationTable = [[NSMapTable alloc] init];
293
+
203 294
 }
204 295
 
205 296
 ////////////////////////////////////////
@@ -211,6 +302,8 @@ NSOperationQueue *taskQueue;
211 302
 
212 303
 #pragma mark NSURLSession delegate methods
213 304
 
305
+
306
+#pragma mark - Received Response
214 307
 // set expected content length on response received
215 308
 - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
216 309
 {
@@ -287,15 +380,26 @@ NSOperationQueue *taskQueue;
287 380
                      @"redirects": redirects,
288 381
                      @"respType" : respType,
289 382
                      @"timeout" : @NO,
290
-                     @"status": [NSString stringWithFormat:@"%d", statusCode ]
383
+                     @"status": [NSNumber numberWithInteger:statusCode]
291 384
                     };
292 385
 
386
+#pragma mark - handling cookies
387
+        // # 153 get cookies
388
+        if(response.URL != nil)
389
+        {
390
+            NSArray<NSHTTPCookie *> * cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: headers forURL:response.URL];
391
+            if(cookies != nil && [cookies count] > 0) {
392
+                [cookiesTable setObject:cookies forKey:response.URL.host];
393
+            }
394
+        }
395
+        
293 396
         [self.bridge.eventDispatcher
294 397
          sendDeviceEventWithName: EVENT_STATE_CHANGE
295 398
          body:respInfo
296 399
         ];
297 400
         headers = nil;
298 401
         respInfo = nil;
402
+
299 403
     }
300 404
     else
301 405
         NSLog(@"oops");
@@ -309,7 +413,11 @@ NSOperationQueue *taskQueue;
309 413
             {
310 414
                 [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil];
311 415
             }
416
+            BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue];
312 417
             BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"];
418
+            
419
+            appendToExistingFile = !overwrite;
420
+
313 421
             // For solving #141 append response data if the file already exists
314 422
             // base on PR#139 @kejinliang
315 423
             if(appendToExistingFile)
@@ -320,7 +428,7 @@ NSOperationQueue *taskQueue;
320 428
             {
321 429
                 [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil];
322 430
             }
323
-            writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:YES];
431
+            writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:appendToExistingFile];
324 432
             [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
325 433
             [writeStream open];
326 434
         }
@@ -343,9 +451,16 @@ NSOperationQueue *taskQueue;
343 451
         [partBuffer appendData:data];
344 452
         return ;
345 453
     }
346
-    
454
+
347 455
     NSNumber * received = [NSNumber numberWithLong:[data length]];
348 456
     receivedBytes += [received longValue];
457
+    NSString * chunkString = @"";
458
+
459
+    if(isIncrement == YES)
460
+    {
461
+        chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
462
+    }
463
+
349 464
     if(respFile == NO)
350 465
     {
351 466
         [respData appendData:data];
@@ -365,7 +480,8 @@ NSOperationQueue *taskQueue;
365 480
          body:@{
366 481
                 @"taskId": taskId,
367 482
                 @"written": [NSString stringWithFormat:@"%d", receivedBytes],
368
-                @"total": [NSString stringWithFormat:@"%d", expectedBytes]
483
+                @"total": [NSString stringWithFormat:@"%d", expectedBytes],
484
+                @"chunk": chunkString
369 485
             }
370 486
          ];
371 487
     }
@@ -399,47 +515,46 @@ NSOperationQueue *taskQueue;
399 515
     {
400 516
         errMsg = [error localizedDescription];
401 517
     }
402
-    else
518
+
519
+    if(respFile == YES)
403 520
     {
404
-        if(respFile == YES)
521
+        [writeStream close];
522
+        rnfbRespType = RESP_TYPE_PATH;
523
+        respStr = destPath;
524
+    }
525
+    // base64 response
526
+    else {
527
+        // #73 fix unicode data encoding issue :
528
+        // when response type is BASE64, we should first try to encode the response data to UTF8 format
529
+        // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
530
+        // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
531
+        NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding];
532
+        
533
+        if(responseFormat == BASE64)
405 534
         {
406
-            [writeStream close];
407
-            rnfbRespType = RESP_TYPE_PATH;
408
-            respStr = destPath;
535
+            rnfbRespType = RESP_TYPE_BASE64;
536
+            respStr = [respData base64EncodedStringWithOptions:0];
409 537
         }
410
-        // base64 response
411
-        else {
412
-            // #73 fix unicode data encoding issue :
413
-            // when response type is BASE64, we should first try to encode the response data to UTF8 format
414
-            // if it turns out not to be `nil` that means the response data contains valid UTF8 string,
415
-            // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding.
416
-            NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding];
417
-            
418
-            if(responseFormat == BASE64)
419
-            {
420
-                rnfbRespType = RESP_TYPE_BASE64;
421
-                respStr = [respData base64EncodedStringWithOptions:0];
422
-            }
423
-            else if (responseFormat == UTF8)
538
+        else if (responseFormat == UTF8)
539
+        {
540
+            rnfbRespType = RESP_TYPE_UTF8;
541
+            respStr = utf8;
542
+        }
543
+        else
544
+        {
545
+            if(utf8 != nil)
424 546
             {
425 547
                 rnfbRespType = RESP_TYPE_UTF8;
426 548
                 respStr = utf8;
427 549
             }
428 550
             else
429 551
             {
430
-                if(utf8 != nil)
431
-                {
432
-                    rnfbRespType = RESP_TYPE_UTF8;
433
-                    respStr = utf8;
434
-                }
435
-                else
436
-                {
437
-                    rnfbRespType = RESP_TYPE_BASE64;
438
-                    respStr = [respData base64EncodedStringWithOptions:0];
439
-                }
552
+                rnfbRespType = RESP_TYPE_BASE64;
553
+                respStr = [respData base64EncodedStringWithOptions:0];
440 554
             }
441 555
         }
442
-    }
556
+        }
557
+
443 558
 
444 559
     callback(@[ errMsg, rnfbRespType, respStr]);
445 560
 
@@ -488,38 +603,27 @@ NSOperationQueue *taskQueue;
488 603
 
489 604
 - (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler
490 605
 {
491
-    if([options valueForKey:CONFIG_TRUSTY] != nil)
606
+    BOOL trusty = [options valueForKey:CONFIG_TRUSTY];
607
+    if(!trusty)
492 608
     {
493 609
         completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
494 610
     }
495 611
     else
496 612
     {
497
-        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
498
-        __block NSURLCredential *credential = nil;
499
-        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
500
-        {
501
-            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
502
-            if (credential) {
503
-                disposition = NSURLSessionAuthChallengeUseCredential;
504
-            } else {
505
-                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
506
-            }
507
-        }
508
-        else
509
-        {
510
-            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
511
-            RCTLogWarn(@"counld not create connection with an unstrusted SSL certification, if you're going to create connection anyway, add `trusty:true` to RNFetchBlob.config");
512
-            [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
513
-        }
514
-        if (completionHandler) {
515
-            completionHandler(disposition, credential);
516
-        }
613
+        completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
517 614
     }
518 615
 }
519 616
 
617
+
618
+- (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
619
+{
620
+    NSLog(@"sess done in background");
621
+}
622
+
520 623
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
521 624
 {
522
-    [redirects addObject:[request.URL absoluteString]];
625
+    if(request.URL != nil)
626
+        [redirects addObject:[request.URL absoluteString]];
523 627
     completionHandler(request);
524 628
 }
525 629
 

+ 2
- 2
src/ios/RNFetchBlobProgress.m View File

@@ -32,7 +32,7 @@
32 32
 
33 33
 -(BOOL)shouldReport:(NSNumber *)nextProgress
34 34
 {
35
-    BOOL result = YES;
35
+    BOOL * result = YES;
36 36
     float countF = [self.count floatValue];
37 37
     if(countF > 0 && [nextProgress floatValue] > 0)
38 38
     {
@@ -43,7 +43,7 @@
43 43
     // NSTimeInterval is defined as double
44 44
     NSNumber *timeStampObj = [NSNumber numberWithDouble: timeStamp];
45 45
     float delta = [timeStampObj doubleValue] - lastTick;
46
-    BOOL shouldReport = delta > [self.interval doubleValue] && self.enable && result;
46
+    BOOL * shouldReport = delta > [self.interval doubleValue] && self.enable && result;
47 47
     if(shouldReport)
48 48
     {
49 49
         tick++;

+ 39
- 0
src/json-stream.js View File

@@ -0,0 +1,39 @@
1
+import Oboe from './lib/oboe-browser.min.js'
2
+import XMLHttpRequest from './polyfill/XMLHttpRequest'
3
+import URIUtil from './utils/uri'
4
+
5
+const OboeExtended = (arg: string | object) => {
6
+
7
+
8
+  window.location = ''
9
+
10
+  if(!window.XMLHttpRequest.isRNFBPolyfill ) {
11
+    window.XMLHttpRequest = XMLHttpRequest
12
+    console.warn('Use JSONStream will automatically replace window.XMLHttpRequest with RNFetchBlob.polyfill.XMLHttpRequest. You are seeing this warning because you did not replace it maually.')
13
+  }
14
+
15
+  if(typeof arg === 'string') {
16
+    if(URIUtil.isFileURI(arg)) {
17
+      arg = {
18
+        url : 'JSONStream://' + arg,
19
+        headers : { noCache : true }
20
+      }
21
+    }
22
+    else
23
+      arg = 'JSONStream://' + arg
24
+
25
+  }
26
+  else if(typeof arg === 'object') {
27
+    let headers = arg.headers || {}
28
+    if(URIUtil.isFileURI(arg.url)) {
29
+      headers.noCache = true
30
+    }
31
+    arg = Object.assign(arg, {
32
+      url : 'JSONStream://' + arg.url,
33
+      headers
34
+    })
35
+  }
36
+  return Oboe(arg)
37
+}
38
+
39
+export default OboeExtended

+ 2703
- 0
src/lib/oboe-browser.js
File diff suppressed because it is too large
View File


+ 1
- 0
src/lib/oboe-browser.min.js
File diff suppressed because it is too large
View File


+ 26
- 0
src/net.js View File

@@ -0,0 +1,26 @@
1
+// Copyright 2016 wkh237@github. All rights reserved.
2
+// Use of this source code is governed by a MIT-style license that can be
3
+// found in the LICENSE file.
4
+// @flow
5
+
6
+import {
7
+  NativeModules,
8
+  DeviceEventEmitter,
9
+  Platform,
10
+  NativeAppEventEmitter,
11
+} from 'react-native'
12
+
13
+const RNFetchBlob = NativeModules.RNFetchBlob
14
+
15
+/**
16
+ * Get cookie according to the given url.
17
+ * @param  {string} url HTTP URL string.
18
+ * @return {Promise<Array<String>>}     Cookies of a specific domain.
19
+ */
20
+function getCookies(url:string):Promise<Array<String>> {
21
+  return RNFetchBlob.getCookies(url)
22
+}
23
+
24
+export default {
25
+  getCookies
26
+}

+ 21
- 4
src/package.json View File

@@ -1,6 +1,6 @@
1 1
 {
2 2
   "name": "react-native-fetch-blob",
3
-  "version": "0.9.6",
3
+  "version": "0.10.0",
4 4
   "description": "A module provides upload, download, and files access API. Supports file stream read/write for process large files.",
5 5
   "main": "index.js",
6 6
   "scripts": {
@@ -29,6 +29,23 @@
29 29
   "repository": {
30 30
     "url": "https://github.com/wkh237/react-native-fetch-blob.git"
31 31
   },
32
-  "author": "wkh237",
33
-  "license": "MIT"
34
-}
32
+  "author": "wkh237 <xeiyan@gmail.com>",
33
+  "license": "MIT",
34
+  "contributors": [
35
+    "Andreas Amsenius <andreas@amsenius.se>",
36
+    "Corentin Smith <corentin.smith@gmail.com>",
37
+    "Dmitry Petukhov <dmitryvpetukhov@gmail.com>",
38
+    "Erik Smartt <code@eriksmartt.com>",
39
+    "Evgeniy Baraniuk <ev.baraniuk@gmail.com>",
40
+    "Juan B. Rodriguez <jbrodriguez@gmail.com>",
41
+    "Kaishley <kklingachetti@msn.com>",
42
+    "Mike Monteith <mike@mikemonteith.com>",
43
+    "Nguyen Cao Nhat Linh <nhatlinh95@gmail.com>",
44
+    "Tim Suchanek <tim.suchanek@gmail.com>",
45
+    "follower <github@rancidbacon.com>",
46
+    "francisco-sanchez-molina <psm1984@gmail.com>",
47
+    "kejinliang <kejinliang@users.noreply.github.com>",
48
+    "smartt <github@eriksmartt.com>",
49
+    ""
50
+  ]
51
+}

+ 1
- 1
src/polyfill/Fetch.js View File

@@ -47,7 +47,7 @@ class RNFetchBlobFetchPolyfill {
47 47
         // When request body is a Blob, use file URI of the Blob as request body.
48 48
         else if (body.isRNFetchBlobPolyfill)
49 49
           promise = Promise.resolve(RNFetchBlob.wrap(body.blobPath))
50
-        else if (typeof body !== 'object')
50
+        else if (typeof body !== 'object' && options.headers['Content-Type'] !== 'application/json')
51 51
           promise = Promise.resolve(JSON.stringify(body))
52 52
         else if (typeof body !== 'string')
53 53
           promise = Promise.resolve(body.toString())

+ 17
- 3
src/polyfill/XMLHttpRequest.js View File

@@ -7,6 +7,7 @@ import XMLHttpRequestEventTarget from './XMLHttpRequestEventTarget.js'
7 7
 import Log from '../utils/log.js'
8 8
 import Blob from './Blob.js'
9 9
 import ProgressEvent from './ProgressEvent.js'
10
+import URIUtil from '../utils/uri'
10 11
 
11 12
 const log = new Log('XMLHttpRequest')
12 13
 
@@ -30,8 +31,9 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
30 31
 
31 32
   // readonly
32 33
   _readyState : number = UNSENT;
34
+  _uriType : 'net' | 'file' = 'net';
33 35
   _response : any = '';
34
-  _responseText : any = null;
36
+  _responseText : any = '';
35 37
   _responseHeaders : any = {};
36 38
   _responseType : '' | 'arraybuffer' | 'blob'  | 'json' | 'text' = '';
37 39
   // TODO : not suppoted ATM
@@ -42,6 +44,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
42 44
   _timeout : number = 60000;
43 45
   _sendFlag : boolean = false;
44 46
   _uploadStarted : boolean = false;
47
+  _increment : boolean = false;
45 48
 
46 49
   // RNFetchBlob compatible data structure
47 50
   _config : RNFetchBlobConfig = {};
@@ -129,6 +132,8 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
129 132
     this._method = method
130 133
     this._url = url
131 134
     this._headers = {}
135
+    this._increment = URIUtil.isJSONStreamURI(this._url)
136
+    this._url = this._url.replace(/^JSONStream\:\/\//, '')
132 137
     this._dispatchReadStateChange(XMLHttpRequest.OPENED)
133 138
   }
134 139
 
@@ -137,7 +142,9 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
137 142
    * @param  {any} body Body in RNfetchblob flavor
138 143
    */
139 144
   send(body) {
145
+
140 146
     this._body = body
147
+
141 148
     if(this._readyState !== XMLHttpRequest.OPENED)
142 149
       throw 'InvalidStateError : XMLHttpRequest is not opened yet.'
143 150
     let promise = Promise.resolve()
@@ -171,10 +178,12 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
171 178
       for(let h in _headers) {
172 179
         _headers[h] = _headers[h].toString()
173 180
       }
181
+
174 182
       this._task = RNFetchBlob
175 183
                     .config({
176 184
                       auto: true,
177 185
                       timeout : this._timeout,
186
+                      increment : this._increment,
178 187
                       binaryContentTypes : XMLHttpRequest.binaryContentTypes
179 188
                     })
180 189
                     .fetch(_method, _url, _headers, body)
@@ -184,6 +193,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
184 193
           .progress(this._progressEvent.bind(this))
185 194
           .catch(this._onError.bind(this))
186 195
           .then(this._onDone.bind(this))
196
+
187 197
     })
188 198
   }
189 199
 
@@ -277,7 +287,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
277 287
     this.upload.dispatchEvent('progress', new ProgressEvent(true, send, total))
278 288
   }
279 289
 
280
-  _progressEvent(send:number, total:number) {
290
+  _progressEvent(send:number, total:number, chunk:string) {
281 291
     log.verbose(this.readyState)
282 292
     if(this._readyState === XMLHttpRequest.HEADERS_RECEIVED)
283 293
       this._dispatchReadStateChange(XMLHttpRequest.LOADING)
@@ -285,6 +295,10 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
285 295
     if(total && total >= 0)
286 296
         lengthComputable = true
287 297
     let e = new ProgressEvent(lengthComputable, send, total)
298
+
299
+    if(this._increment) {
300
+      this._responseText += chunk
301
+    }
288 302
     this.dispatchEvent('progress', e)
289 303
   }
290 304
 
@@ -417,7 +431,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
417 431
     return this._responseType
418 432
   }
419 433
 
420
-  get isRNFBPolyfill() {
434
+  static get isRNFBPolyfill() {
421 435
     return true
422 436
   }
423 437
 

+ 2
- 2
src/react-native-fetch-blob.podspec View File

@@ -1,12 +1,12 @@
1 1
 Pod::Spec.new do |s|
2 2
   s.name             = "react-native-fetch-blob"
3
-  s.version          = "0.9.6"
3
+  s.version          = "0.10.0"
4 4
   s.summary          = "A project committed to make file acess and data transfer easier, effiecient for React Native developers."
5 5
   s.requires_arc = true
6 6
   s.license      = 'MIT'
7 7
   s.homepage     = 'n/a'
8 8
   s.authors      = { "wkh237" => "xeiyan@gmail.com" }
9
-  s.source       = { :git => "https://github.com/wkh237/react-native-fetch-blob", :tag => 'v0.9.6'}
9
+  s.source       = { :git => "https://github.com/wkh237/react-native-fetch-blob", :tag => 'v0.10.0'}
10 10
   s.source_files = 'ios/**/*.{h,m}'
11 11
   s.platform     = :ios, "7.0"
12 12
   s.dependency 'React/Core'

+ 28
- 0
src/utils/uri.js View File

@@ -0,0 +1,28 @@
1
+export default {
2
+
3
+  isFileURI : (uri:string):boolean => {
4
+    if(typeof uri !== 'string')
5
+      return false
6
+    return /^RNFetchBlob-file\:\/\//.test(uri)
7
+  },
8
+
9
+  isJSONStreamURI : (uri:string):boolean => {
10
+    if(typeof uri !== 'string')
11
+      return false
12
+    return /^JSONStream\:\/\//.test(uri)
13
+  },
14
+
15
+  removeURIScheme : (uri:string, iterations:number):string => {
16
+    iterations = iterations || 1
17
+    let result = uri
18
+    for(let i=0;i<iterations;i++) {
19
+      result = String(result).replace(/^[^\:]+\:\/\//, '')
20
+    }
21
+    return String(result)
22
+  },
23
+
24
+  unwrapFileURI : (uri:string):string => {
25
+    return String(uri).replace(/^RNFetchBlob-file\:\/\//, '')
26
+  }
27
+
28
+}

+ 3
- 0
test-server/public/json-dummy-1.json View File

@@ -0,0 +1,3 @@
1
+{
2
+  "name" : "fetchblob-dev"
3
+}

+ 48331
- 15
test-server/public/json-dummy.json
File diff suppressed because it is too large
View File


+ 36
- 12
test-server/server.js View File

@@ -66,6 +66,23 @@ app.use(function(req, res, next) {
66 66
   next();
67 67
 })
68 68
 
69
+
70
+app.get('/10s-download', (req,res) => {
71
+  var count = 0
72
+  var data = ''
73
+  for(var i =0;i<1024000;i++)
74
+    data += '1'
75
+  res.set('Contet-Length', 1024000*10)
76
+  var it = setInterval(() => {
77
+    res.write(data)
78
+    count++
79
+    if(count == 10) {
80
+      clearInterval(it)
81
+      res.end()
82
+    }
83
+  }, 1000)
84
+})
85
+
69 86
 app.get('/video/:count', (req, res) => {
70 87
   var count = 0
71 88
   res.set('Content-Type', 'multipart/x-mixed-replace; boundary="---osclivepreview---"')
@@ -191,24 +208,20 @@ app.all('/timeout408/:time', (req, res) => {
191 208
   }, 5000)
192 209
 })
193 210
 
194
-app.get('/10s-download', (req,res) => {
195
-  var count = 0
196
-  var data = ''
197
-  for(var i =0;i<1024000;i++)
198
-    data += '1'
199
-  res.set('Contet-Length', 1024000*10)
211
+app.all('/long/:ticks', (req, res) => {
212
+  var count = 0;
200 213
   var it = setInterval(() => {
201
-    res.write(data)
202
-    count++
203
-    if(count == 10) {
214
+    console.log('write data', count)
215
+    res.write('a')
216
+    if(++count > req.params.ticks){
204 217
       clearInterval(it)
205 218
       res.end()
206 219
     }
207
-  }, 1000)
220
+  }, 1000);
221
+
208 222
 })
209 223
 
210
-app.all('/long', (req, res) => {
211
-  var count = 0;
224
+app.all('/long/', (req, res) => {  var count = 0;
212 225
   var it = setInterval(() => {
213 226
     console.log('write data', count)
214 227
     res.write('a')
@@ -220,6 +233,17 @@ app.all('/long', (req, res) => {
220 233
 
221 234
 })
222 235
 
236
+app.all('/cookie/:data', (req, res) => {
237
+  res.cookie('cookieName', req.params.data);
238
+  res.end()
239
+})
240
+
241
+app.all('/err-body', (req, res) => {
242
+  res.status(400)
243
+  res.write(JSON.stringify({ data : Date.now() }))
244
+  res.end()
245
+})
246
+
223 247
 app.all('/timeout', (req, res) => {
224 248
 })
225 249
 

+ 1
- 0
test-server/test.jpeg View File

@@ -0,0 +1 @@
1
+undefined

+ 4
- 0
test/nedb.js
File diff suppressed because it is too large
View File


+ 214
- 0
test/test-0.10.0.js View File

@@ -0,0 +1,214 @@
1
+import RNTest from './react-native-testkit/'
2
+import React from 'react'
3
+import RNFetchBlob from 'react-native-fetch-blob'
4
+import {
5
+  StyleSheet,
6
+  Text,
7
+  View,
8
+  ScrollView,
9
+  Linking,
10
+  Platform,
11
+  Dimensions,
12
+  BackAndroid,
13
+  AsyncStorage,
14
+  Image,
15
+} from 'react-native';
16
+
17
+window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest
18
+window.Blob = RNFetchBlob.polyfill.Blob
19
+
20
+const JSONStream = RNFetchBlob.JSONStream
21
+const fs = RNFetchBlob.fs
22
+const { Assert, Comparer, Info, prop } = RNTest
23
+const describe = RNTest.config({
24
+  group : '0.10.0',
25
+  run : true,
26
+  expand : false,
27
+  timeout : 20000,
28
+})
29
+const { TEST_SERVER_URL, TEST_SERVER_URL_SSL, FILENAME, DROPBOX_TOKEN, styles } = prop()
30
+const dirs = RNFetchBlob.fs.dirs
31
+let prefix = ((Platform.OS === 'android') ? 'file://' : '')
32
+let begin = Date.now()
33
+
34
+describe('json stream via HTTP', (report, done) => {
35
+
36
+  let count = 0
37
+  JSONStream(`${TEST_SERVER_URL}/public/json-dummy.json`).node('name', (name) => {
38
+    count++
39
+    if(Date.now() - begin < 100)
40
+    return
41
+    begin = Date.now()
42
+    report(<Info key="report" uid="100">
43
+      <Text>{count} records</Text>
44
+    </Info>)
45
+    done()
46
+  })
47
+
48
+})
49
+
50
+describe('json stream via fs', (report, done) => {
51
+
52
+  let fetch2 = new RNFetchBlob.polyfill.Fetch({
53
+    auto : true
54
+  })
55
+  let res = null
56
+  let count = 0
57
+
58
+  RNFetchBlob.config({
59
+    fileCache : true
60
+  })
61
+  .fetch('GET',`${TEST_SERVER_URL}/public/json-dummy.json`)
62
+  .then((resp) => {
63
+    res = resp
64
+    JSONStream({
65
+      url : RNFetchBlob.wrap(res.path()),
66
+      headers : { bufferSize : 10240 }
67
+    }).node('name', (name) => {
68
+      count++
69
+      if(Date.now() - begin < 100)
70
+      return
71
+      begin = Date.now()
72
+      report(<Info key="report" uid="100">
73
+        <Text>{count} records</Text>
74
+      </Info>)
75
+      done()
76
+    })
77
+  })
78
+})
79
+
80
+
81
+describe('cookie test', (report, done) => {
82
+  let time = Date.now()
83
+  RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/cookie/${time}`)
84
+  .then((res) => RNFetchBlob.net.getCookies(`${TEST_SERVER_URL}`))
85
+  .then((cookies) => {
86
+    let result = /cookieName\=[^;]+/.exec(cookies[0])
87
+    console.log(result, 'cookieName=' + time)
88
+    report(<Assert key="cookie should not be empty"
89
+      expect={'cookieName=' + time}
90
+      actual={result[0]}/>)
91
+    done()
92
+  })
93
+
94
+})
95
+
96
+describe('SSL test #159', (report, done) => {
97
+  RNFetchBlob.config({
98
+    trusty : true
99
+    })
100
+    .fetch('GET', `${TEST_SERVER_URL_SSL}/public/github.png`, {
101
+      'Cache-Control' : 'no-store'
102
+    })
103
+    .then(res => {
104
+      report(<Assert
105
+        key="trusty request should pass"
106
+        expect={true}
107
+        actual={true}/>)
108
+      return RNFetchBlob.fetch('GET',`${TEST_SERVER_URL_SSL}/public/github.png`)
109
+    })
110
+    .catch(e => {
111
+      report(<Assert
112
+        key="non-trusty request should not pass"
113
+        expect={true}
114
+        actual={true}/>)
115
+      done()
116
+    })
117
+})
118
+
119
+describe('#171 appendExt verify', (report, done) => {
120
+
121
+  RNFetchBlob.config({
122
+    fileCache : true,
123
+    appendExt : 'png'
124
+  })
125
+  .fetch('GET', `${TEST_SERVER_URL}/public/github.png`, {
126
+    'Cache-Control' : 'no-store'
127
+  })
128
+  .then(res => {
129
+    console.log(res.path())
130
+    report(<Assert
131
+      key="extension appended to tmp path"
132
+      actual={/.png$/.test(res.path())}
133
+      expect={true}/>)
134
+    return fs.stat(res.path())
135
+  })
136
+  .then(stat => {
137
+    report(<Assert
138
+      key="verify the file existence"
139
+      expect="23975"
140
+      actual={stat.size} />)
141
+    done()
142
+  })
143
+
144
+})
145
+
146
+describe('#173 issue with append option', (report, done) => {
147
+  let dest = dirs.DocumentDir + '/tmp' + Date.now()
148
+  RNFetchBlob.config({
149
+    path : dest,
150
+    overwrite : true
151
+  })
152
+  .fetch('GET', `${TEST_SERVER_URL}/public/github.png`)
153
+  .then((res) => fs.stat(res.path()))
154
+  .then((stat) => {
155
+    report(<Assert
156
+      key="file size check #1"
157
+      expect="23975"
158
+      actual={stat.size}/>)
159
+    return RNFetchBlob.config({
160
+      path : dest,
161
+      overwrite : false
162
+    })
163
+    .fetch('GET', `${TEST_SERVER_URL}/public/github.png`)
164
+  })
165
+  .then((res) => fs.stat(res.path()))
166
+  .then((stat) => {
167
+    report(<Assert
168
+      key="file size check #2"
169
+      expect="47950"
170
+      actual={stat.size}/>)
171
+    return RNFetchBlob.config({
172
+      path : dest,
173
+      overwrite : true
174
+    })
175
+    .fetch('GET', `${TEST_SERVER_URL}/public/github.png`)
176
+  })
177
+  .then((res) => fs.stat(res.path()))
178
+  .then((stat) => {
179
+    report(<Assert
180
+      key="file size check #3"
181
+      expect="23975"
182
+      actual={stat.size}/>)
183
+    return RNFetchBlob.config({
184
+      path : dest,
185
+    })
186
+    .fetch('GET', `${TEST_SERVER_URL}/public/github.png`)
187
+  })
188
+  .then((res) => fs.stat(res.path()))
189
+  .then((stat) => {
190
+    report(<Assert
191
+      key="it should successfully overwrite existing file without config"
192
+      expect="23975"
193
+      actual={stat.size}/>)
194
+    done()
195
+  })
196
+
197
+})
198
+
199
+describe('#171 verification ', (report, done) => {
200
+
201
+  RNFetchBlob
202
+    .config({
203
+      session: 'SESSION_NAME',
204
+      fileCache: true,
205
+      appendExt: 'mp4'
206
+    })
207
+    .fetch('GET', `${TEST_SERVER_URL}/public/cat-fu.mp4`)
208
+    .then(res => {
209
+      console.log(res.path())
210
+    })
211
+
212
+
213
+
214
+})

+ 3
- 1
test/test-0.6.2.js View File

@@ -62,6 +62,7 @@ describe('upload asset from camera roll', (report, done) => {
62 62
 
63 63
 describe('Upload multipart data with file from CameraRoll', (report, done) => {
64 64
     let filename = 'test-from-storage-img-'+Date.now()+'.png'
65
+    console.log(photo)
65 66
     RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/upload-form`, {
66 67
         'Content-Type' : 'multipart/form-data',
67 68
       }, [
@@ -165,10 +166,11 @@ describe('upload file from assets',(report, done) => {
165 166
   let assetName = fs.asset('test-asset1.json')
166 167
   RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
167 168
     Authorization : `Bearer ${DROPBOX_TOKEN}`,
168
-    'Dropbox-API-Arg': `{\"path\": \"/rn-upload/file-from-asset-${Platform.OS}.json\",\"mode\": \"add\",\"autorename\": false,\"mute\": false}`,
169
+    'Dropbox-API-Arg': `{\"path\": \"/rn-upload/file-from-asset-${Platform.OS}.json\",\"mode\": \"overwrite\",\"autorename\": false,\"mute\": false}`,
169 170
     'Content-Type' : 'application/octet-stream',
170 171
   }, RNFetchBlob.wrap(assetName))
171 172
   .then((resp) => {
173
+    console.log(resp)
172 174
     resp = resp.json()
173 175
     report(
174 176
       <Assert key="file name check"

+ 8
- 2
test/test-0.9.4.js View File

@@ -14,6 +14,10 @@ import {
14 14
 
15 15
 window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest
16 16
 window.Blob = RNFetchBlob.polyfill.Blob
17
+window.fetch = new RNFetchBlob.polyfill.Fetch({
18
+  auto : true,
19
+  binaryContentTypes : ['image/', 'video/', 'audio/']
20
+}).build()
17 21
 
18 22
 const fs = RNFetchBlob.fs
19 23
 const { Assert, Comparer, Info, prop } = RNTest
@@ -56,7 +60,7 @@ describe('issue #106', (report, done) => {
56 60
       return res.json()
57 61
     })
58 62
     .then((data) => {
59
-      // console.log(data)
63
+      console.log(data)
60 64
       report(<Assert key="fetch request success" expect={20000} actual={data.total}/>)
61 65
       done()
62 66
     })
@@ -64,7 +68,9 @@ describe('issue #106', (report, done) => {
64 68
 })
65 69
 
66 70
 describe('issue #111 get redirect destination', (report, done) => {
67
-  RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/redirect`)
71
+  RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/redirect`, {
72
+    'Cache-Control' : 'no-store'
73
+  })
68 74
   .then((res) => {
69 75
     console.log(res.info())
70 76
     report(

+ 3
- 3
test/test-0.9.5.js View File

@@ -30,7 +30,7 @@ let prefix = ((Platform.OS === 'android') ? 'file://' : '')
30 30
 
31 31
 describe('issue #122 force response data format', (report, done) => {
32 32
 
33
-  RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/public/json-dummy.json`, {
33
+  RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/public/json-dummy-1.json`, {
34 34
     'RNFB-Response' : 'base64'
35 35
   })
36 36
   .then((res) => {
@@ -38,7 +38,7 @@ describe('issue #122 force response data format', (report, done) => {
38 38
     report(
39 39
       <Assert key="test data verify" expect="fetchblob-dev" actual={JSON.parse(r).name}/>,
40 40
       <Assert key="should successfully decode the data" expect={true} actual={true}/>)
41
-    return RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/public/json-dummy.json`)
41
+    return RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/public/json-dummy-1.json`)
42 42
   })
43 43
   .then((res) => {
44 44
     report(
@@ -79,7 +79,7 @@ describe('#129 memory leaking when enable uploadProgress', (report, done) => {
79 79
 
80 80
 })
81 81
 
82
-false && describe('#131 status code != 200 should not throw an error', (report, done) => {
82
+describe('#131 status code != 200 should not throw an error', (report, done) => {
83 83
 
84 84
   let count = 0
85 85
   let codes = [404, 500, 501, 403]

+ 51
- 0
test/test-background.js View File

@@ -0,0 +1,51 @@
1
+import RNTest from './react-native-testkit/'
2
+import React from 'react'
3
+import RNFetchBlob from 'react-native-fetch-blob'
4
+import {
5
+  StyleSheet,
6
+  Text,
7
+  View,
8
+  ScrollView,
9
+  Linking,
10
+  Platform,
11
+  Dimensions,
12
+  AsyncStorage,
13
+  Image,
14
+} from 'react-native';
15
+const JSONStream = RNFetchBlob.JSONStream
16
+const fs = RNFetchBlob.fs
17
+const { Assert, Comparer, Info, prop } = RNTest
18
+const describe = RNTest.config({
19
+  group : 'background',
20
+  run : true,
21
+  expand : true,
22
+  timeout : 20000,
23
+})
24
+const { TEST_SERVER_URL, TEST_SERVER_URL_SSL, FILENAME, DROPBOX_TOKEN, styles } = prop()
25
+const dirs = RNFetchBlob.fs.dirs
26
+let prefix = ((Platform.OS === 'android') ? 'file://' : '')
27
+let begin = Date.now()
28
+
29
+describe('background http response', (report, done) => {
30
+  let count = 0
31
+
32
+  let task = RNFetchBlob.config({
33
+    timeout : -1
34
+  }).fetch('GET', `${TEST_SERVER_URL}/long/3600`, {
35
+    'Cache-Control' : 'no-store'
36
+  })
37
+
38
+  task.expire(() => {
39
+    done()
40
+  })
41
+
42
+  task.catch((err) => {
43
+    console.log(err)
44
+  })
45
+
46
+  task.then((res) => {
47
+    console.log('resp response received', res.data.length)
48
+    // done()
49
+  })
50
+
51
+})

+ 6
- 4
test/test-init.js View File

@@ -17,11 +17,11 @@ const { Assert, Comparer, Info, prop } = RNTest
17 17
 
18 18
 // test environment variables
19 19
 
20
-prop('FILENAME', `${Platform.OS}-0.8.0-${Date.now()}.png`)
20
+prop('FILENAME', `${Platform.OS}-0.10.0-${Date.now()}.png`)
21 21
 prop('TEST_SERVER_URL', 'http://localhost:8123')
22 22
 prop('TEST_SERVER_URL_SSL', 'https://localhost:8124')
23
-// prop('TEST_SERVER_URL', 'http://192.168.17.194:8123')
24
-// prop('TEST_SERVER_URL_SSL', 'https://192.168.17.194:8124')
23
+// prop('TEST_SERVER_URL', 'http://192.168.0.12:8123')
24
+// prop('TEST_SERVER_URL_SSL', 'https://192.168.0.12:8124')
25 25
 prop('DROPBOX_TOKEN', 'fsXcpmKPrHgAAAAAAAAAoXZhcXYWdgLpQMan6Tb_bzJ237DXhgQSev12hA-gUXt4')
26 26
 prop('styles', {
27 27
   image : {
@@ -73,7 +73,9 @@ describe('GET image from server', (report, done) => {
73 73
 // require('./test-0.9.4')
74 74
 // require('./test-0.9.5')
75 75
 // require('./test-0.9.6')
76
-require('./test-stream')
76
+require('./test-0.10.0')
77
+// require('./test-background.js')
78
+// require('./test-stream')
77 79
 // require('./test-fetch')
78 80
 // require('./test-fs')
79 81
 // require('./test-xmlhttp')

+ 40
- 0
test/test-readable.js View File

@@ -0,0 +1,40 @@
1
+import RNTest from './react-native-testkit/'
2
+import React from 'react'
3
+import RNFetchBlob from 'react-native-fetch-blob'
4
+import Timer from 'react-timer-mixin'
5
+
6
+import {
7
+  StyleSheet,
8
+  Text,
9
+  View,
10
+  ScrollView,
11
+  CameraRoll,
12
+  Platform,
13
+  Dimensions,
14
+  Image,
15
+} from 'react-native';
16
+
17
+import EventEmitter from 'EventEmitter'
18
+
19
+const fs = RNFetchBlob.fs
20
+
21
+const Readable = RNFetchBlob.polyfill.Readable
22
+const { Assert, Comparer, Info, prop } = RNTest
23
+const describe = RNTest.config({
24
+  group : 'Blob',
25
+  run : true,
26
+  expand : false,
27
+  timeout : 20000,
28
+})
29
+const { TEST_SERVER_URL, TEST_SERVER_URL_SSL, DROPBOX_TOKEN, styles } = prop()
30
+const  dirs = RNFetchBlob.fs.dirs
31
+
32
+let prefix = ((Platform.OS === 'android') ? 'file://' : '')
33
+let file = RNTest.prop('image')
34
+
35
+describe('first test', (report, done) => {
36
+  let e = new EventEmitter()
37
+  console.log(e)
38
+  e.addListener('aaa', (data) => { console.log(data) })
39
+  e.emit('aaa', 123)
40
+})