Browse Source

Merge branch '0.10.9'

Travis Nuttall 6 years ago
parent
commit
822f568908
39 changed files with 1869 additions and 1235 deletions
  1. 0
    5
      .github/PULL_REQUEST_TEMPLATE
  2. 54
    20
      README.md
  3. 18
    2
      android.js
  4. 1
    0
      android/build.gradle
  5. 2
    2
      android/gradle/wrapper/gradle-wrapper.properties
  6. 31
    3
      android/src/main/AndroidManifest.xml
  7. 75
    40
      android/src/main/java/com/RNFetchBlob/RNFetchBlob.java
  8. 68
    35
      android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java
  9. 1
    4
      android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java
  10. 1
    0
      android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java
  11. 369
    186
      android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
  12. 7
    7
      android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java
  13. 49
    16
      android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
  14. 2
    1
      android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java
  15. 4
    3
      android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java
  16. 2
    0
      android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java
  17. 9
    0
      android/src/main/res/xml/provider_paths.xml
  18. 5
    3
      class/RNFetchBlobReadStream.js
  19. 2
    7
      class/RNFetchBlobSession.js
  20. 8
    9
      class/RNFetchBlobWriteStream.js
  21. 182
    157
      fs.js
  22. 9
    10
      index.js
  23. 2
    2
      ios.js
  24. 7
    1
      ios/RNFetchBlob.xcodeproj/project.pbxproj
  25. 0
    1
      ios/RNFetchBlob/RNFetchBlob.h
  26. 109
    64
      ios/RNFetchBlob/RNFetchBlob.m
  27. 28
    28
      ios/RNFetchBlobConst.m
  28. 8
    3
      ios/RNFetchBlobFS.h
  29. 190
    56
      ios/RNFetchBlobFS.m
  30. 16
    28
      ios/RNFetchBlobNetwork.h
  31. 68
    529
      ios/RNFetchBlobNetwork.m
  32. 1
    1
      ios/RNFetchBlobReqBuilder.h
  33. 5
    5
      ios/RNFetchBlobReqBuilder.m
  34. 47
    0
      ios/RNFetchBlobRequest.h
  35. 477
    0
      ios/RNFetchBlobRequest.m
  36. 5
    2
      json-stream.js
  37. 2
    2
      polyfill/Fetch.js
  38. 3
    1
      polyfill/File.js
  39. 2
    2
      polyfill/XMLHttpRequest.js

+ 0
- 5
.github/PULL_REQUEST_TEMPLATE View File

@@ -1,5 +0,0 @@
1
-Thank you for making a pull request ! Just a gentle reminder :)
2
-
3
-1. If the PR is offering a feature please make the request to our "Feature Branch" 0.11.0
4
-2. Bug fix request to "Bug Fix Branch" 0.10.9
5
-3. Correct README.md can directly to master

+ 54
- 20
README.md View File

@@ -1,15 +1,3 @@
1
-## New Maintainers
2
-
3
-We make quite a bit of use of react-native-fetch-blob at Jolt and would like to maintain the project.  Feel free to open issues, PRs, etc. here as you would on the original repository.  We will be investigating a new npm namespace under which to publish future versions of this library.
4
-
5
-<br>
6
-
7
-## About Pull Requests
8
-
9
-Bugfixes should be applied to the `0.10.9` branch and new features should be applied to the `0.11.0`. Documentation/README updates can be applied directly to `master`.
10
-
11
-<br>
12
-
13 1
 # react-native-fetch-blob
14 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)]()
15 3
 
@@ -30,9 +18,9 @@ A project committed to making file access and data transfer easier and more effi
30 18
 * [Installation](#user-content-installation)
31 19
 * [HTTP Data Transfer](#user-content-http-data-transfer)
32 20
  * [Regular Request](#user-content-regular-request)
33
- * [Download file](#download-example-fetch-files-that-need-authorization-token)
21
+ * [Download file](#user-content-download-example--fetch-files-that-needs-authorization-token)
34 22
  * [Upload file](#user-content-upload-example--dropbox-files-upload-api)
35
- * [Multipart/form upload](#multipartform-data-example-post-form-data-with-file-and-data)
23
+ * [Multipart/form upload](#user-content-multipartform-data-example--post-form-data-with-file-and-data)
36 24
  * [Upload/Download progress](#user-content-uploaddownload-progress)
37 25
  * [Cancel HTTP request](#user-content-cancel-request)
38 26
  * [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support)
@@ -611,10 +599,12 @@ File Access APIs
611 599
 - [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs)
612 600
 - [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise)
613 601
 - [writeFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writefilepathstring-contentstring--array-encodingstring-appendbooleanpromise)
614
-- [appendFile (0.6.0) ](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#appendfilepathstring-contentstring--array-encodingstringpromise)
602
+- [appendFile (0.6.0) ](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#appendfilepathstring-contentstring--arraynumber-encodingstring-promisenumber)
615 603
 - [readFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readfilepath-encodingpromise)
616
-- [readStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readstreampath-encoding-buffersizepromise)
617
-- [writeStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writestreampathstring-encodingstring-appendbooleanpromise)
604
+- [readStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readstreampath-encoding-buffersize-interval-promisernfbreadstream)
605
+- [hash (0.10.9)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithm-promise)
606
+- [writeStream](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writestreampathstring-encodingstringpromise)
607
+- [hash](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#hashpath-algorithmpromise)
618 608
 - [unlink](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#unlinkpathstringpromise)
619 609
 - [mkdir](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#mkdirpathstringpromise)
620 610
 - [ls](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#lspathstringpromise)
@@ -657,13 +647,52 @@ RNFetchBlob.fs.readStream(
657 647
       console.log('oops', err)
658 648
     })
659 649
     ifstream.onEnd(() => {  
660
-      <Image source={{ uri : 'data:image/png,base64' + data }}/>
650
+      <Image source={{ uri : 'data:image/png,base64' + data }}
661 651
     })
662 652
 })
663 653
 ```
664 654
 
665 655
 When using `writeStream`, the stream object becomes writable, and you can then perform operations like `write` and `close`.
666 656
 
657
+Since version 0.10.9 `write()` resolves with the `RNFetchBlob` instance so you can promise-chain write calls:
658
+
659
+```js
660
+RNFetchBlob.fs.writeStream(
661
+    PATH_TO_FILE,
662
+    // encoding, should be one of `base64`, `utf8`, `ascii`
663
+    'utf8',
664
+    // should data append to existing content ?
665
+    true
666
+)
667
+.then(ofstream => ofstream.write('foo'))
668
+.then(ofstream => ofstream.write('bar'))
669
+.then(ofstream => ofstream.write('foobar'))
670
+.then(ofstream => ofstream.close())
671
+.catch(console.error)
672
+```
673
+
674
+or 
675
+
676
+```js
677
+RNFetchBlob.fs.writeStream(
678
+    PATH_TO_FILE,
679
+    // encoding, should be one of `base64`, `utf8`, `ascii`
680
+    'utf8',
681
+    // should data append to existing content ?
682
+    true
683
+)
684
+.then(stream => Promise.all([
685
+    stream.write('foo'),
686
+    stream.write('bar'),
687
+    stream.write('foobar')
688
+]))
689
+// Use array destructuring to get the stream object from the first item of the array we get from Promise.all()
690
+.then(([stream]) => stream.close())
691
+.catch(console.error)
692
+```
693
+
694
+You should **NOT** do something like this:
695
+
667 696
 ```js
668 697
 RNFetchBlob.fs.writeStream(
669 698
     PATH_TO_FILE,
@@ -672,13 +701,18 @@ RNFetchBlob.fs.writeStream(
672 701
     // should data append to existing content ?
673 702
     true)
674 703
 .then((ofstream) => {
704
+    // BAD IDEA - Don't do this, those writes are unchecked:
675 705
     ofstream.write('foo')
676 706
     ofstream.write('bar')
677 707
     ofstream.close()
678 708
 })
679
-
709
+.catch(console.error)  // Cannot catch any write() errors!
680 710
 ```
681 711
 
712
+The problem with the above code is that the promises from the `ofstream.write()` calls are detached and "Lost".
713
+That means the entire promise chain A) resolves without waiting for the writes to finish and B) any errors caused by them are lost.
714
+That code may _seem_ to work if there are no errors, but those writes are of the type "fire and forget": You start them and then turn away and never know if they really succeeded.
715
+
682 716
 ### Cache File Management
683 717
 
684 718
 When using `fileCache` or `path` options along with `fetch` API, response data will automatically store into the file system. The files will **NOT** removed unless you `unlink` it. There're several ways to remove the files
@@ -808,4 +842,4 @@ See [release notes](https://github.com/wkh237/react-native-fetch-blob/releases)
808 842
 ### Development
809 843
 
810 844
 If you're interested in hacking this module, check our [development guide](https://github.com/wkh237/react-native-fetch-blob/wiki/Home), there might be some helpful information.
811
-Please feel free to make a PR or file an issue.
845
+Please feel free to make a PR or file an issue.

+ 18
- 2
android.js View File

@@ -13,7 +13,7 @@ const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
13 13
 
14 14
 /**
15 15
  * Send an intent to open the file.
16
- * @param  {string]} path Path of the file to be open.
16
+ * @param  {string} path Path of the file to be open.
17 17
  * @param  {string} mime MIME type string
18 18
  * @return {Promise}
19 19
  */
@@ -38,9 +38,25 @@ function addCompleteDownload(config) {
38 38
     return Promise.reject('RNFetchBlob.android.addCompleteDownload only supports Android.')
39 39
 }
40 40
 
41
+function getSDCardDir() {
42
+  if(Platform.OS === 'android')
43
+    return RNFetchBlob.getSDCardDir()
44
+  else
45
+    return Promise.reject('RNFetchBlob.android.getSDCardDir only supports Android.')
46
+}
47
+
48
+function getSDCardApplicationDir() {
49
+  if(Platform.OS === 'android')
50
+    return RNFetchBlob.getSDCardApplicationDir()
51
+  else
52
+    return Promise.reject('RNFetchBlob.android.getSDCardApplicationDir only supports Android.')
53
+}
54
+
41 55
 
42 56
 export default {
43 57
   actionViewIntent,
44 58
   getContentIntent,
45
-  addCompleteDownload
59
+  addCompleteDownload,
60
+  getSDCardDir,
61
+  getSDCardApplicationDir,
46 62
 }

+ 1
- 0
android/build.gradle View File

@@ -34,5 +34,6 @@ android {
34 34
 
35 35
 dependencies {
36 36
     compile 'com.facebook.react:react-native:+'
37
+    //compile 'com.squareup.okhttp3:okhttp:+'
37 38
     //{RNFetchBlob_PRE_0.28_DEPDENDENCY}
38 39
 }

+ 2
- 2
android/gradle/wrapper/gradle-wrapper.properties View File

@@ -1,6 +1,6 @@
1
-#Wed May 18 12:33:41 CST 2016
1
+#Fri Jun 01 10:33:07 BRT 2018
2 2
 distributionBase=GRADLE_USER_HOME
3 3
 distributionPath=wrapper/dists
4 4
 zipStoreBase=GRADLE_USER_HOME
5 5
 zipStorePath=wrapper/dists
6
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
6
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

+ 31
- 3
android/src/main/AndroidManifest.xml View File

@@ -1,9 +1,37 @@
1 1
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2 2
     package="com.RNFetchBlob">
3 3
 
4
-    <application
5
-        android:label="@string/app_name">
4
+    <!-- Required to access Google Play Licensing -->
5
+    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />
6 6
 
7
+    <!-- Required to download files from Google Play -->
8
+    <uses-permission android:name="android.permission.INTERNET" />
9
+
10
+    <!-- Required to keep CPU alive while downloading files
11
+        (NOT to keep screen awake) -->
12
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
13
+
14
+    <!-- Required to poll the state of the network connection
15
+        and respond to changes -->
16
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
17
+
18
+    <!-- Required to check whether Wi-Fi is enabled -->
19
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
20
+
21
+    <!-- Required to read and write the expansion files on shared storage -->
22
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
23
+
24
+    <application android:label="@string/app_name">
25
+
26
+        <provider
27
+            android:name="android.support.v4.content.FileProvider"
28
+            android:authorities="${applicationId}.provider"
29
+            android:exported="false"
30
+            android:grantUriPermissions="true">
31
+            <meta-data
32
+                android:name="android.support.FILE_PROVIDER_PATHS"
33
+                android:resource="@xml/provider_paths" />
34
+        </provider>
7 35
     </application>
8 36
 
9
-</manifest>
37
+</manifest>

+ 75
- 40
android/src/main/java/com/RNFetchBlob/RNFetchBlob.java View File

@@ -3,7 +3,11 @@ package com.RNFetchBlob;
3 3
 import android.app.Activity;
4 4
 import android.app.DownloadManager;
5 5
 import android.content.Intent;
6
+import android.content.pm.PackageManager;
6 7
 import android.net.Uri;
8
+import android.os.Build;
9
+import android.support.v4.content.FileProvider;
10
+import android.util.SparseArray;
7 11
 
8 12
 import com.facebook.react.bridge.ActivityEventListener;
9 13
 import com.facebook.react.bridge.Callback;
@@ -23,6 +27,7 @@ import com.facebook.react.modules.network.OkHttpClientProvider;
23 27
 import okhttp3.OkHttpClient;
24 28
 import okhttp3.JavaNetCookieJar;
25 29
 
30
+import java.io.File;
26 31
 import java.util.HashMap;
27 32
 import java.util.Map;
28 33
 import java.util.concurrent.LinkedBlockingQueue;
@@ -34,26 +39,23 @@ import static com.RNFetchBlob.RNFetchBlobConst.GET_CONTENT_INTENT;
34 39
 
35 40
 public class RNFetchBlob extends ReactContextBaseJavaModule {
36 41
 
37
-    // Cookies
38
-    private final ForwardingCookieHandler mCookieHandler;
39
-    private final CookieJarContainer mCookieJarContainer;
40 42
     private final OkHttpClient mClient;
41 43
 
42 44
     static ReactApplicationContext RCTContext;
43
-    static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
44
-    static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
45
+    private static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
46
+    private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
45 47
     static LinkedBlockingQueue<Runnable> fsTaskQueue = new LinkedBlockingQueue<>();
46
-    static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
47
-    static public boolean ActionViewVisible = false;
48
-    static HashMap<Integer, Promise> promiseTable = new HashMap<>();
48
+    private static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
49
+    private static boolean ActionViewVisible = false;
50
+    private static SparseArray<Promise> promiseTable = new SparseArray<>();
49 51
 
50 52
     public RNFetchBlob(ReactApplicationContext reactContext) {
51 53
 
52 54
         super(reactContext);
53 55
 
54 56
         mClient = OkHttpClientProvider.getOkHttpClient();
55
-        mCookieHandler = new ForwardingCookieHandler(reactContext);
56
-        mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
57
+        ForwardingCookieHandler mCookieHandler = new ForwardingCookieHandler(reactContext);
58
+        CookieJarContainer mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
57 59
         mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler));
58 60
 
59 61
         RCTContext = reactContext;
@@ -85,23 +87,51 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
85 87
     }
86 88
 
87 89
     @ReactMethod
88
-    public void createFile(final String path, final String content, final String encode, final Callback callback) {
90
+    public void createFile(final String path, final String content, final String encode, final Promise promise) {
89 91
         threadPool.execute(new Runnable() {
90 92
             @Override
91 93
             public void run() {
92
-                RNFetchBlobFS.createFile(path, content, encode, callback);
94
+                RNFetchBlobFS.createFile(path, content, encode, promise);
93 95
             }
94 96
         });
97
+    }
95 98
 
99
+    @ReactMethod
100
+    public void createFileASCII(final String path, final ReadableArray dataArray, final Promise promise) {
101
+        threadPool.execute(new Runnable() {
102
+            @Override
103
+            public void run() {
104
+                RNFetchBlobFS.createFileASCII(path, dataArray, promise);
105
+            }
106
+        });
96 107
     }
97 108
 
98 109
     @ReactMethod
99 110
     public void actionViewIntent(String path, String mime, final Promise promise) {
100 111
         try {
101
-            Intent intent= new Intent(Intent.ACTION_VIEW)
102
-                    .setDataAndType(Uri.parse("file://" + path), mime);
103
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
104
-            this.getReactApplicationContext().startActivity(intent);
112
+            Uri uriForFile = FileProvider.getUriForFile(getCurrentActivity(),
113
+                    this.getReactApplicationContext().getPackageName() + ".provider", new File(path));
114
+
115
+            if (Build.VERSION.SDK_INT >= 24) {
116
+                // Create the intent with data and type
117
+                Intent intent = new Intent(Intent.ACTION_VIEW)
118
+                        .setDataAndType(uriForFile, mime);
119
+
120
+                // Set flag to give temporary permission to external app to use FileProvider
121
+                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
122
+
123
+                // Validate that the device can open the file
124
+                PackageManager pm = getCurrentActivity().getPackageManager();
125
+                if (intent.resolveActivity(pm) != null) {
126
+                    this.getReactApplicationContext().startActivity(intent);
127
+                }
128
+
129
+            } else {
130
+                Intent intent = new Intent(Intent.ACTION_VIEW)
131
+                        .setDataAndType(Uri.parse("file://" + path), mime).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
132
+
133
+                this.getReactApplicationContext().startActivity(intent);
134
+            }
105 135
             ActionViewVisible = true;
106 136
 
107 137
             final LifecycleEventListener listener = new LifecycleEventListener() {
@@ -124,21 +154,10 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
124 154
             };
125 155
             RCTContext.addLifecycleEventListener(listener);
126 156
         } catch(Exception ex) {
127
-            promise.reject(ex.getLocalizedMessage());
157
+            promise.reject("EUNSPECIFIED", ex.getLocalizedMessage());
128 158
         }
129 159
     }
130 160
 
131
-    @ReactMethod
132
-    public void createFileASCII(final String path, final ReadableArray dataArray, final Callback callback) {
133
-        threadPool.execute(new Runnable() {
134
-            @Override
135
-            public void run() {
136
-                RNFetchBlobFS.createFileASCII(path, dataArray, callback);
137
-            }
138
-        });
139
-
140
-    }
141
-
142 161
     @ReactMethod
143 162
     public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) {
144 163
         RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
@@ -150,8 +169,8 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
150 169
     }
151 170
 
152 171
     @ReactMethod
153
-    public void mkdir(String path, Callback callback) {
154
-        RNFetchBlobFS.mkdir(path, callback);
172
+    public void mkdir(String path, Promise promise) {
173
+        RNFetchBlobFS.mkdir(path, promise);
155 174
     }
156 175
 
157 176
     @ReactMethod
@@ -167,7 +186,6 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
167 186
                 RNFetchBlobFS.cp(path, dest, callback);
168 187
             }
169 188
         });
170
-
171 189
     }
172 190
 
173 191
     @ReactMethod
@@ -176,8 +194,8 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
176 194
     }
177 195
 
178 196
     @ReactMethod
179
-    public void ls(String path, Callback callback) {
180
-        RNFetchBlobFS.ls(path, callback);
197
+    public void ls(String path, Promise promise) {
198
+        RNFetchBlobFS.ls(path, promise);
181 199
     }
182 200
 
183 201
     @ReactMethod
@@ -228,7 +246,6 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
228 246
                 RNFetchBlobFS.writeFile(path, encoding, data, append, promise);
229 247
             }
230 248
         });
231
-
232 249
     }
233 250
 
234 251
     @ReactMethod
@@ -263,15 +280,24 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
263 280
                 new RNFetchBlobFS(ctx).scanFile(p, m, callback);
264 281
             }
265 282
         });
266
-
267 283
     }
268 284
 
269 285
     @ReactMethod
286
+    public void hash(final String path, final String algorithm, final Promise promise) {
287
+        threadPool.execute(new Runnable() {
288
+            @Override
289
+            public void run() {
290
+                RNFetchBlobFS.hash(path, algorithm, promise);
291
+            }
292
+        });
293
+    }
294
+
270 295
     /**
271 296
      * @param path Stream file path
272 297
      * @param encoding Stream encoding, should be one of `base64`, `ascii`, and `utf8`
273 298
      * @param bufferSize Stream buffer size, default to 4096 or 4095(base64).
274 299
      */
300
+    @ReactMethod
275 301
     public void readStream(final String path, final String encoding, final int bufferSize, final int tick, final String streamId) {
276 302
         final ReactApplicationContext ctx = this.getReactApplicationContext();
277 303
         fsThreadPool.execute(new Runnable() {
@@ -324,7 +350,7 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
324 350
     @ReactMethod
325 351
     public void fetchBlob(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, final Callback callback) {
326 352
         new RNFetchBlobReq(options, taskId, method, url, headers, body, null, mClient, callback).run();
327
-}
353
+    }
328 354
 
329 355
     @ReactMethod
330 356
     public void fetchBlobForm(ReadableMap options, String taskId, String method, String url, ReadableMap headers, ReadableArray body, final Callback callback) {
@@ -345,10 +371,10 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
345 371
 
346 372
     @ReactMethod
347 373
     public void addCompleteDownload (ReadableMap config, Promise promise) {
348
-        DownloadManager dm = (DownloadManager) RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE);
374
+        DownloadManager dm = (DownloadManager) RCTContext.getSystemService(RCTContext.DOWNLOAD_SERVICE);
349 375
         String path = RNFetchBlobFS.normalizePath(config.getString("path"));
350 376
         if(path == null) {
351
-            promise.reject("RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"), "RNFetchblob.addCompleteDownload can not resolve URI:" + path);
377
+            promise.reject("EINVAL", "RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"));
352 378
             return;
353 379
         }
354 380
         try {
@@ -365,9 +391,18 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
365 391
             promise.resolve(null);
366 392
         }
367 393
         catch(Exception ex) {
368
-            promise.reject("RNFetchblob.addCompleteDownload failed", ex.getStackTrace().toString());
394
+            promise.reject("EUNSPECIFIED", ex.getLocalizedMessage());
369 395
         }
370 396
 
371 397
     }
372 398
 
373
-}
399
+    @ReactMethod
400
+    public void getSDCardDir(Promise promise) {
401
+        RNFetchBlobFS.getSDCardDir(promise);
402
+    }
403
+
404
+    @ReactMethod
405
+    public void getSDCardApplicationDir(Promise promise) {
406
+        RNFetchBlobFS.getSDCardApplicationDir(this.getReactApplicationContext(), promise);
407
+    }
408
+}

+ 68
- 35
android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java View File

@@ -1,5 +1,6 @@
1 1
 package com.RNFetchBlob;
2 2
 
3
+import android.support.annotation.NonNull;
3 4
 import android.util.Base64;
4 5
 
5 6
 import com.facebook.react.bridge.Arguments;
@@ -17,25 +18,25 @@ import java.io.IOException;
17 18
 import java.io.InputStream;
18 19
 import java.util.ArrayList;
19 20
 
21
+import android.net.Uri;
20 22
 import okhttp3.MediaType;
21 23
 import okhttp3.RequestBody;
22 24
 import okio.BufferedSink;
23 25
 
24
-public class RNFetchBlobBody extends RequestBody{
26
+class RNFetchBlobBody extends RequestBody{
25 27
 
26
-    InputStream requestStream;
27
-    long contentLength = 0;
28
-    ReadableArray form;
29
-    String mTaskId;
30
-    String rawBody;
31
-    RNFetchBlobReq.RequestType requestType;
32
-    MediaType mime;
33
-    File bodyCache;
28
+    private InputStream requestStream;
29
+    private long contentLength = 0;
30
+    private ReadableArray form;
31
+    private String mTaskId;
32
+    private String rawBody;
33
+    private RNFetchBlobReq.RequestType requestType;
34
+    private MediaType mime;
35
+    private File bodyCache;
34 36
     int reported = 0;
35
-    Boolean chunkedEncoding = false;
37
+    private Boolean chunkedEncoding = false;
36 38
 
37
-
38
-    public RNFetchBlobBody(String taskId) {
39
+    RNFetchBlobBody(String taskId) {
39 40
         this.mTaskId = taskId;
40 41
     }
41 42
 
@@ -49,7 +50,7 @@ public class RNFetchBlobBody extends RequestBody{
49 50
         return this;
50 51
     }
51 52
 
52
-    RNFetchBlobBody setRequestType( RNFetchBlobReq.RequestType type) {
53
+    RNFetchBlobBody setRequestType(RNFetchBlobReq.RequestType type) {
53 54
         this.requestType = type;
54 55
         return this;
55 56
     }
@@ -68,7 +69,7 @@ public class RNFetchBlobBody extends RequestBody{
68 69
         try {
69 70
             switch (requestType) {
70 71
                 case SingleFile:
71
-                    requestStream = getReuqestStream();
72
+                    requestStream = getRequestStream();
72 73
                     contentLength = requestStream.available();
73 74
                     break;
74 75
                 case AsIs:
@@ -114,7 +115,7 @@ public class RNFetchBlobBody extends RequestBody{
114 115
     }
115 116
 
116 117
     @Override
117
-    public void writeTo(BufferedSink sink) {
118
+    public void writeTo(@NonNull BufferedSink sink) {
118 119
         try {
119 120
             pipeStreamToSink(requestStream, sink);
120 121
         } catch(Exception ex) {
@@ -135,7 +136,7 @@ public class RNFetchBlobBody extends RequestBody{
135 136
         return true;
136 137
     }
137 138
 
138
-    private InputStream getReuqestStream() throws Exception {
139
+    private InputStream getRequestStream() throws Exception {
139 140
 
140 141
         // upload from storage
141 142
         if (rawBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
@@ -159,6 +160,13 @@ public class RNFetchBlobBody extends RequestBody{
159 160
                     throw new Exception("error when getting request stream: " +e.getLocalizedMessage());
160 161
                 }
161 162
             }
163
+        } else if (rawBody.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
164
+            String contentURI = rawBody.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
165
+            try {
166
+                return RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(contentURI));
167
+            } catch (Exception e) {
168
+                throw new Exception("error when getting request stream for content URI: " + contentURI, e);
169
+            }
162 170
         }
163 171
         // base 64 encoded
164 172
         else {
@@ -186,8 +194,7 @@ public class RNFetchBlobBody extends RequestBody{
186 194
         ArrayList<FormField> fields = countFormDataLength();
187 195
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
188 196
 
189
-        for(int i = 0;i < fields.size(); i++) {
190
-            FormField field = fields.get(i);
197
+        for(FormField field : fields) {
191 198
             String data = field.data;
192 199
             String name = field.name;
193 200
             // skip invalid fields
@@ -225,6 +232,20 @@ public class RNFetchBlobBody extends RequestBody{
225 232
                             RNFetchBlobUtils.emitWarningEvent("Failed to create form data from path :" + orgPath + ", file not exists.");
226 233
                         }
227 234
                     }
235
+                } else if (data.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
236
+                    String contentURI = data.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
237
+                    InputStream is = null;
238
+                    try {
239
+                        is = ctx.getContentResolver().openInputStream(Uri.parse(contentURI));
240
+                        pipeStreamToFileStream(is, os);
241
+                    } catch(Exception e) {
242
+                        RNFetchBlobUtils.emitWarningEvent(
243
+                                "Failed to create form data from content URI:" + contentURI + ", " + e.getLocalizedMessage());
244
+                    } finally {
245
+                        if (is != null) {
246
+                            is.close();
247
+                        }
248
+                    }
228 249
                 }
229 250
                 // base64 embedded file content
230 251
                 else {
@@ -258,17 +279,14 @@ public class RNFetchBlobBody extends RequestBody{
258 279
      * @param sink      The request body buffer sink
259 280
      * @throws IOException
260 281
      */
261
-    private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws Exception {
262
-
263
-        byte [] chunk = new byte[10240];
282
+    private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
283
+        byte[] chunk = new byte[10240];
264 284
         int totalWritten = 0;
265 285
         int read;
266 286
         while((read = stream.read(chunk, 0, 10240)) > 0) {
267
-            if(read > 0) {
268
-                sink.write(chunk, 0, read);
269
-                totalWritten += read;
270
-                emitUploadProgress(totalWritten);
271
-            }
287
+            sink.write(chunk, 0, read);
288
+            totalWritten += read;
289
+            emitUploadProgress(totalWritten);
272 290
         }
273 291
         stream.close();
274 292
     }
@@ -291,20 +309,20 @@ public class RNFetchBlobBody extends RequestBody{
291 309
 
292 310
     /**
293 311
      * Compute approximate content length for form data
294
-     * @return
312
+     * @return ArrayList<FormField>
295 313
      */
296
-    private ArrayList<FormField> countFormDataLength() {
314
+    private ArrayList<FormField> countFormDataLength() throws IOException {
297 315
         long total = 0;
298 316
         ArrayList<FormField> list = new ArrayList<>();
299 317
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
300 318
         for(int i = 0;i < form.size(); i++) {
301 319
             FormField field = new FormField(form.getMap(i));
302 320
             list.add(field);
303
-            String data = field.data;
304
-            if(data == null) {
321
+            if(field.data == null) {
305 322
                 RNFetchBlobUtils.emitWarningEvent("RNFetchBlob multipart request builder has found a field without `data` property, the field `"+ field.name +"` will be removed implicitly.");
306 323
             }
307 324
             else if (field.filename != null) {
325
+                String data = field.data;
308 326
                 // upload from storage
309 327
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
310 328
                     String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
@@ -324,6 +342,21 @@ public class RNFetchBlobBody extends RequestBody{
324 342
                         File file = new File(RNFetchBlobFS.normalizePath(orgPath));
325 343
                         total += file.length();
326 344
                     }
345
+                } else if (data.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
346
+                    String contentURI = data.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
347
+                    InputStream is = null;
348
+                    try {
349
+                        is = ctx.getContentResolver().openInputStream(Uri.parse(contentURI));
350
+                        long length = is.available();
351
+                        total += length;
352
+                    } catch(Exception e) {
353
+                        RNFetchBlobUtils.emitWarningEvent(
354
+                                "Failed to estimate form data length from content URI:" + contentURI + ", " + e.getLocalizedMessage());
355
+                    } finally {
356
+                        if (is != null) {
357
+                            is.close();
358
+                        }
359
+                    }
327 360
                 }
328 361
                 // base64 embedded file content
329 362
                 else {
@@ -333,7 +366,7 @@ public class RNFetchBlobBody extends RequestBody{
333 366
             }
334 367
             // data field
335 368
             else {
336
-                total += field.data != null ? field.data.getBytes().length : 0;
369
+                total += field.data.getBytes().length;
337 370
             }
338 371
         }
339 372
         contentLength = total;
@@ -346,11 +379,11 @@ public class RNFetchBlobBody extends RequestBody{
346 379
      */
347 380
     private class FormField {
348 381
         public String name;
349
-        public String filename;
350
-        public String mime;
382
+        String filename;
383
+        String mime;
351 384
         public String data;
352 385
 
353
-        public FormField(ReadableMap rawData) {
386
+        FormField(ReadableMap rawData) {
354 387
             if(rawData.hasKey("name"))
355 388
                 name = rawData.getString("name");
356 389
             if(rawData.hasKey("filename"))
@@ -368,7 +401,7 @@ public class RNFetchBlobBody extends RequestBody{
368 401
 
369 402
     /**
370 403
      * Emit progress event
371
-     * @param written
404
+     * @param written  Integer
372 405
      */
373 406
     private void emitUploadProgress(int written) {
374 407
         RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId);

+ 1
- 4
android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java View File

@@ -3,10 +3,7 @@ package com.RNFetchBlob;
3 3
 import com.facebook.react.bridge.ReadableArray;
4 4
 import com.facebook.react.bridge.ReadableMap;
5 5
 
6
-import java.util.HashMap;
7
-
8
-
9
-public class RNFetchBlobConfig {
6
+class RNFetchBlobConfig {
10 7
 
11 8
     public Boolean fileCache;
12 9
     public String path;

+ 1
- 0
android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java View File

@@ -7,6 +7,7 @@ public class RNFetchBlobConst {
7 7
     public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
8 8
     public static final String EVENT_MESSAGE = "RNFetchBlobMessage";
9 9
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
10
+    public static final String CONTENT_PREFIX = "RNFetchBlob-content://";
10 11
     public static final String FILE_PREFIX_BUNDLE_ASSET = "bundle-assets://";
11 12
     public static final String FILE_PREFIX_CONTENT = "content://";
12 13
     public static final String DATA_ENCODE_URI = "uri";

+ 369
- 186
android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
File diff suppressed because it is too large
View File


+ 7
- 7
android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java View File

@@ -5,17 +5,17 @@ package com.RNFetchBlob;
5 5
  */
6 6
 public class RNFetchBlobProgressConfig {
7 7
 
8
-    public enum ReportType {
8
+    enum ReportType {
9 9
         Upload,
10 10
         Download
11 11
     };
12 12
 
13
-    long lastTick = 0;
14
-    int tick = 0;
15
-    int count = -1;
16
-    public int interval = -1;
17
-    public boolean enable = false;
18
-    public ReportType type = ReportType.Download;
13
+    private long lastTick = 0;
14
+    private int tick = 0;
15
+    private int count = -1;
16
+    private int interval = -1;
17
+    private boolean enable = false;
18
+    private ReportType type = ReportType.Download;
19 19
 
20 20
     RNFetchBlobProgressConfig(boolean report, int interval, int count, ReportType type) {
21 21
         this.enable = report;

+ 49
- 16
android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java View File

@@ -8,6 +8,7 @@ import android.content.IntentFilter;
8 8
 import android.database.Cursor;
9 9
 import android.net.Uri;
10 10
 import android.os.Build;
11
+import android.support.annotation.NonNull;
11 12
 import android.util.Base64;
12 13
 
13 14
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
@@ -22,8 +23,11 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator;
22 23
 import com.facebook.react.bridge.WritableArray;
23 24
 import com.facebook.react.bridge.WritableMap;
24 25
 import com.facebook.react.modules.core.DeviceEventManagerModule;
25
-import com.facebook.react.modules.network.OkHttpClientProvider;
26
-import com.facebook.react.modules.network.TLSSocketFactory;
26
+
27
+import javax.net.ssl.TrustManagerFactory;
28
+import javax.net.ssl.TrustManager;
29
+import javax.net.ssl.X509TrustManager;
30
+import javax.net.ssl.SSLContext;
27 31
 
28 32
 import java.io.File;
29 33
 import java.io.FileOutputStream;
@@ -37,12 +41,16 @@ import java.nio.ByteBuffer;
37 41
 import java.nio.charset.CharacterCodingException;
38 42
 import java.nio.charset.Charset;
39 43
 import java.nio.charset.CharsetEncoder;
44
+import java.security.KeyStore;
40 45
 import java.util.ArrayList;
46
+import java.util.Arrays;
41 47
 import java.util.List;
42 48
 import java.util.HashMap;
43 49
 
44 50
 import java.util.concurrent.TimeUnit;
45 51
 
52
+import 	javax.net.ssl.SSLSocketFactory;
53
+
46 54
 import okhttp3.Call;
47 55
 import okhttp3.ConnectionPool;
48 56
 import okhttp3.ConnectionSpec;
@@ -79,11 +87,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
79 87
     }
80 88
 
81 89
     public static HashMap<String, Call> taskTable = new HashMap<>();
90
+    public static HashMap<String, Long> androidDownloadManagerTaskTable = new HashMap<>();
82 91
     static HashMap<String, RNFetchBlobProgressConfig> progressReport = new HashMap<>();
83 92
     static HashMap<String, RNFetchBlobProgressConfig> uploadProgressReport = new HashMap<>();
84 93
     static ConnectionPool pool = new ConnectionPool();
85 94
 
86
-    ReactApplicationContext ctx;
87 95
     RNFetchBlobConfig options;
88 96
     String taskId;
89 97
     String method;
@@ -135,6 +143,13 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
135 143
             call.cancel();
136 144
             taskTable.remove(taskId);
137 145
         }
146
+
147
+        if (androidDownloadManagerTaskTable.containsKey(taskId)) {
148
+            long downloadManagerIdForTaskId = androidDownloadManagerTaskTable.get(taskId).longValue();
149
+            Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
150
+            DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
151
+            dm.remove(downloadManagerIdForTaskId);
152
+        }
138 153
     }
139 154
 
140 155
     @Override
@@ -162,7 +177,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
162 177
                 }
163 178
                 // set headers
164 179
                 ReadableMapKeySetIterator it = headers.keySetIterator();
165
-                if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable") == true ) {
180
+                if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable")) {
166 181
                     req.allowScanningByMediaScanner();
167 182
                 }
168 183
                 while (it.hasNextKey()) {
@@ -172,6 +187,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
172 187
                 Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
173 188
                 DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
174 189
                 downloadManagerId = dm.enqueue(req);
190
+                androidDownloadManagerTaskTable.put(taskId, Long.valueOf(downloadManagerId));
175 191
                 appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
176 192
                 return;
177 193
             }
@@ -188,7 +204,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
188 204
                 cacheKey = this.taskId;
189 205
             }
190 206
 
191
-            File file = new File(RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext);
207
+            File file = new File(RNFetchBlobFS.getTmpPath(cacheKey) + ext);
192 208
 
193 209
             if (file.exists()) {
194 210
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
@@ -199,7 +215,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
199 215
         if(this.options.path != null)
200 216
             this.destPath = this.options.path;
201 217
         else if(this.options.fileCache)
202
-            this.destPath = RNFetchBlobFS.getTmpPath(RNFetchBlob.RCTContext, cacheKey) + ext;
218
+            this.destPath = RNFetchBlobFS.getTmpPath(cacheKey) + ext;
203 219
 
204 220
 
205 221
         OkHttpClient.Builder clientBuilder;
@@ -250,7 +266,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
250 266
                     requestType = RequestType.SingleFile;
251 267
                 }
252 268
                 if(rawRequestBody != null) {
253
-                    if(rawRequestBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
269
+                    if (rawRequestBody.startsWith(RNFetchBlobConst.FILE_PREFIX)
270
+                            || rawRequestBody.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
254 271
                         requestType = RequestType.SingleFile;
255 272
                     }
256 273
                     else if (cType.toLowerCase().contains(";base64") || cType.toLowerCase().startsWith("application/octet")) {
@@ -321,7 +338,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
321 338
             // Add request interceptor for upload progress event
322 339
             clientBuilder.addInterceptor(new Interceptor() {
323 340
                 @Override
324
-                public Response intercept(Chain chain) throws IOException {
341
+                public Response intercept(@NonNull Chain chain) throws IOException {
325 342
                     try {
326 343
                         Response originalResponse = chain.proceed(req);
327 344
                         ResponseBody extended;
@@ -383,7 +400,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
383 400
             call.enqueue(new okhttp3.Callback() {
384 401
 
385 402
                 @Override
386
-                public void onFailure(Call call, IOException e) {
403
+                public void onFailure(@NonNull Call call, IOException e) {
387 404
                     cancelTask(taskId);
388 405
                     if(respInfo == null) {
389 406
                         respInfo = Arguments.createMap();
@@ -400,7 +417,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
400 417
                 }
401 418
 
402 419
                 @Override
403
-                public void onResponse(Call call, Response response) throws IOException {
420
+                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
404 421
                     ReadableMap notifyConfig = options.addAndroidDownloads;
405 422
                     // Download manager settings
406 423
                     if(notifyConfig != null ) {
@@ -438,6 +455,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
438 455
     private void releaseTaskResource() {
439 456
         if(taskTable.containsKey(taskId))
440 457
             taskTable.remove(taskId);
458
+        if(androidDownloadManagerTaskTable.containsKey(taskId))
459
+            androidDownloadManagerTaskTable.remove(taskId);
441 460
         if(uploadProgressReport.containsKey(taskId))
442 461
             uploadProgressReport.remove(taskId);
443 462
         if(progressReport.containsKey(taskId))
@@ -459,11 +478,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
459 478
                     // For XMLHttpRequest, automatic response data storing strategy, when response
460 479
                     // data is considered as binary data, write it to file system
461 480
                     if(isBlobResp && options.auto) {
462
-                        String dest = RNFetchBlobFS.getTmpPath(ctx, taskId);
481
+                        String dest = RNFetchBlobFS.getTmpPath(taskId);
463 482
                         InputStream ins = resp.body().byteStream();
464 483
                         FileOutputStream os = new FileOutputStream(new File(dest));
465 484
                         int read;
466
-                        byte [] buffer = new byte[10240];
485
+                        byte[] buffer = new byte[10240];
467 486
                         while ((read = ins.read(buffer)) != -1) {
468 487
                             os.write(buffer, 0, read);
469 488
                         }
@@ -635,6 +654,8 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
635 654
             Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
636 655
             long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
637 656
             if (id == this.downloadManagerId) {
657
+                releaseTaskResource(); // remove task ID from task map
658
+
638 659
                 DownloadManager.Query query = new DownloadManager.Query();
639 660
                 query.setFilterById(downloadManagerId);
640 661
                 DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
@@ -648,7 +669,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
648 669
                     // #297 handle failed request
649 670
                     int statusCode = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
650 671
                     if(statusCode == DownloadManager.STATUS_FAILED) {
651
-                        this.callback.invoke("Download manager failed to download from  " + this.url + ". Statu Code = " + statusCode, null, null);
672
+                        this.callback.invoke("Download manager failed to download from  " + this.url + ". Status Code = " + statusCode, null, null);
652 673
                         return;
653 674
                     }
654 675
                     String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
@@ -657,11 +678,11 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
657 678
                             options.addAndroidDownloads.getString("mime").contains("image")) {
658 679
                         Uri uri = Uri.parse(contentUri);
659 680
                         Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null);
660
-
661
-                            // use default destination of DownloadManager
681
+                        // use default destination of DownloadManager
662 682
                         if (cursor != null) {
663 683
                             cursor.moveToFirst();
664 684
                             filePath = cursor.getString(0);
685
+                            cursor.close();
665 686
                         }
666 687
                     }
667 688
                 }
@@ -695,7 +716,19 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
695 716
     public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
696 717
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
697 718
             try {
698
-                client.sslSocketFactory(new TLSSocketFactory());
719
+                // Code from https://stackoverflow.com/a/40874952/544779
720
+                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
721
+                trustManagerFactory.init((KeyStore) null);
722
+                TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
723
+                if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
724
+                    throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
725
+                }
726
+                X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
727
+                SSLContext sslContext = SSLContext.getInstance("SSL");
728
+                sslContext.init(null, new TrustManager[] { trustManager }, null);
729
+                SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
730
+
731
+                client.sslSocketFactory(sslSocketFactory, trustManager);
699 732
 
700 733
                 ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
701 734
                         .tlsVersions(TlsVersion.TLS_1_2)

+ 2
- 1
android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java View File

@@ -27,7 +27,7 @@ public class RNFetchBlobUtils {
27 27
             md.update(input.getBytes());
28 28
             byte[] digest = md.digest();
29 29
 
30
-            StringBuffer sb = new StringBuffer();
30
+            StringBuilder sb = new StringBuilder();
31 31
 
32 32
             for (byte b : digest) {
33 33
                 sb.append(String.format("%02x", b & 0xff));
@@ -37,6 +37,7 @@ public class RNFetchBlobUtils {
37 37
         } catch (Exception ex) {
38 38
             ex.printStackTrace();
39 39
         } finally {
40
+            // TODO: Is discarding errors the intent? (https://www.owasp.org/index.php/Return_Inside_Finally_Block)
40 41
             return result;
41 42
         }
42 43
 

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

@@ -1,5 +1,6 @@
1 1
 package com.RNFetchBlob.Response;
2 2
 
3
+import android.support.annotation.NonNull;
3 4
 import android.util.Log;
4 5
 
5 6
 import com.RNFetchBlob.RNFetchBlobConst;
@@ -48,11 +49,11 @@ public class RNFetchBlobFileResp extends ResponseBody {
48 49
             File f = new File(path);
49 50
 
50 51
             File parent = f.getParentFile();
51
-            if(!parent.exists() && !parent.mkdirs()){
52
+            if(parent != null && !parent.exists() && !parent.mkdirs()){
52 53
                 throw new IllegalStateException("Couldn't create dir: " + parent);
53 54
             }
54 55
 
55
-            if(f.exists() == false)
56
+            if(!f.exists())
56 57
                 f.createNewFile();
57 58
             ofStream = new FileOutputStream(new File(path), appendToExistingFile);
58 59
         }
@@ -76,7 +77,7 @@ public class RNFetchBlobFileResp extends ResponseBody {
76 77
 
77 78
     private class ProgressReportingSource implements Source {
78 79
         @Override
79
-        public long read(Buffer sink, long byteCount) throws IOException {
80
+        public long read(@NonNull Buffer sink, long byteCount) throws IOException {
80 81
             try {
81 82
                 byte[] bytes = new byte[(int) byteCount];
82 83
                 long read = originalBody.byteStream().read(bytes, 0, (int) byteCount);

+ 2
- 0
android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java View File

@@ -1,5 +1,6 @@
1 1
 package com.RNFetchBlob.Utils;
2 2
 
3
+import android.annotation.TargetApi;
3 4
 import android.content.Context;
4 5
 import android.database.Cursor;
5 6
 import android.net.Uri;
@@ -15,6 +16,7 @@ import java.io.InputStream;
15 16
 import java.io.FileOutputStream;
16 17
 
17 18
 public class PathResolver {
19
+    @TargetApi(19)
18 20
     public static String getRealPathFromURI(final Context context, final Uri uri) {
19 21
 
20 22
         final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

+ 9
- 0
android/src/main/res/xml/provider_paths.xml View File

@@ -0,0 +1,9 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
3
+    <external-path
4
+        name="external_files"
5
+        path="." />
6
+    <files-path
7
+        name="files-path"
8
+        path="." /> <!-- Used to access into application data files -->
9
+</paths>

+ 5
- 3
class/RNFetchBlobReadStream.js View File

@@ -35,7 +35,7 @@ export default class RNFetchBlobReadStream {
35 35
 
36 36
     // register for file stream event
37 37
     let subscription = emitter.addListener(this.streamId, (e) => {
38
-      let {event, detail} = e
38
+      let {event, code, detail} = e
39 39
       if(this._onData && event === 'data') {
40 40
         this._onData(detail)
41 41
         return
@@ -44,10 +44,12 @@ export default class RNFetchBlobReadStream {
44 44
         this._onEnd(detail)
45 45
       }
46 46
       else {
47
+        const err = new Error(detail)
48
+        err.code = code || 'EUNSPECIFIED'
47 49
         if(this._onError)
48
-          this._onError(detail)
50
+          this._onError(err)
49 51
         else
50
-          throw new Error(detail)
52
+          throw err
51 53
       }
52 54
       // when stream closed or error, remove event handler
53 55
       if (event === 'error' || event === 'end') {

+ 2
- 7
class/RNFetchBlobSession.js View File

@@ -9,16 +9,11 @@ import {
9 9
 } from 'react-native'
10 10
 
11 11
 const RNFetchBlob = NativeModules.RNFetchBlob
12
-const emitter = DeviceEventEmitter
13 12
 
14 13
 let sessions = {}
15 14
 
16 15
 export default class RNFetchBlobSession {
17 16
 
18
-  add : (path:string) => RNFetchBlobSession;
19
-  remove : (path:string) => RNFetchBlobSession;
20
-  dispose : () => Promise;
21
-  list : () => Array<string>;
22 17
   name : string;
23 18
 
24 19
   static getSession(name:string):any {
@@ -50,7 +45,7 @@ export default class RNFetchBlobSession {
50 45
 
51 46
   remove(path:string):RNFetchBlobSession {
52 47
     let list = sessions[this.name]
53
-    for(let i in list) {
48
+    for(let i of list) {
54 49
       if(list[i] === path) {
55 50
         sessions[this.name].splice(i, 1)
56 51
         break;
@@ -67,7 +62,7 @@ export default class RNFetchBlobSession {
67 62
     return new Promise((resolve, reject) => {
68 63
       RNFetchBlob.removeSession(sessions[this.name], (err) => {
69 64
         if(err)
70
-          reject(err)
65
+          reject(new Error(err))
71 66
         else {
72 67
           delete sessions[this.name]
73 68
           resolve()

+ 8
- 9
class/RNFetchBlobWriteStream.js View File

@@ -9,36 +9,35 @@ import {
9 9
 } from 'react-native'
10 10
 
11 11
 const RNFetchBlob = NativeModules.RNFetchBlob
12
-const emitter = DeviceEventEmitter
13 12
 
14 13
 export default class RNFetchBlobWriteStream {
15 14
 
16 15
   id : string;
17 16
   encoding : string;
18
-  append : bool;
17
+  append : boolean;
19 18
 
20
-  constructor(streamId:string, encoding:string, append:string) {
19
+  constructor(streamId:string, encoding:string, append:boolean) {
21 20
     this.id = streamId
22 21
     this.encoding = encoding
23 22
     this.append = append
24 23
   }
25 24
 
26
-  write(data:string) {
25
+  write(data:string): Promise<RNFetchBlobWriteStream> {
27 26
     return new Promise((resolve, reject) => {
28 27
       try {
29 28
         let method = this.encoding === 'ascii' ? 'writeArrayChunk' : 'writeChunk'
30 29
         if(this.encoding.toLocaleLowerCase() === 'ascii' && !Array.isArray(data)) {
31
-            reject('ascii input data must be an Array')
30
+            reject(new Error('ascii input data must be an Array'))
32 31
             return
33 32
         }
34 33
         RNFetchBlob[method](this.id, data, (error) => {
35 34
           if(error)
36
-            reject(error)
35
+            reject(new Error(error))
37 36
           else
38
-            resolve()
37
+            resolve(this)
39 38
         })
40 39
       } catch(err) {
41
-        reject(err)
40
+        reject(new Error(err))
42 41
       }
43 42
     })
44 43
   }
@@ -50,7 +49,7 @@ export default class RNFetchBlobWriteStream {
50 49
           resolve()
51 50
         })
52 51
       } catch (err) {
53
-        reject(err)
52
+        reject(new Error(err))
54 53
       }
55 54
     })
56 55
   }

+ 182
- 157
fs.js View File

@@ -2,36 +2,33 @@
2 2
 // Use of this source code is governed by a MIT-style license that can be
3 3
 // found in the LICENSE file.
4 4
 
5
-import {
6
-  NativeModules,
7
-  DeviceEventEmitter,
8
-  Platform,
9
-  NativeAppEventEmitter,
10
-} from 'react-native'
5
+// import type {RNFetchBlobConfig, RNFetchBlobNative, RNFetchBlobStream} from './types'
6
+
7
+import {NativeModules, Platform} from 'react-native'
11 8
 import RNFetchBlobSession from './class/RNFetchBlobSession'
12 9
 import RNFetchBlobWriteStream from './class/RNFetchBlobWriteStream'
13 10
 import RNFetchBlobReadStream from './class/RNFetchBlobReadStream'
14 11
 import RNFetchBlobFile from './class/RNFetchBlobFile'
15
-import type {
16
-  RNFetchBlobNative,
17
-  RNFetchBlobConfig,
18
-  RNFetchBlobStream
19
-} from './types'
20
-
21
-const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
22
-const emitter = DeviceEventEmitter
12
+
13
+const RNFetchBlob: RNFetchBlobNative = NativeModules.RNFetchBlob
14
+
23 15
 const dirs = {
24
-    DocumentDir :  RNFetchBlob.DocumentDir,
25
-    CacheDir : RNFetchBlob.CacheDir,
26
-    PictureDir : RNFetchBlob.PictureDir,
27
-    MusicDir : RNFetchBlob.MusicDir,
28
-    MovieDir : RNFetchBlob.MovieDir,
29
-    DownloadDir : RNFetchBlob.DownloadDir,
30
-    DCIMDir : RNFetchBlob.DCIMDir,
31
-    SDCardDir : RNFetchBlob.SDCardDir,
32
-    SDCardApplicationDir : RNFetchBlob.SDCardApplicationDir,
33
-    MainBundleDir : RNFetchBlob.MainBundleDir,
34
-    LibraryDir : RNFetchBlob.LibraryDir
16
+  DocumentDir :  RNFetchBlob.DocumentDir,
17
+  CacheDir : RNFetchBlob.CacheDir,
18
+  PictureDir : RNFetchBlob.PictureDir,
19
+  MusicDir : RNFetchBlob.MusicDir,
20
+  MovieDir : RNFetchBlob.MovieDir,
21
+  DownloadDir : RNFetchBlob.DownloadDir,
22
+  DCIMDir : RNFetchBlob.DCIMDir,
23
+  SDCardDir: RNFetchBlob.SDCardDir, // Depracated
24
+  SDCardApplicationDir: RNFetchBlob.SDCardApplicationDir, // Deprecated
25
+  MainBundleDir : RNFetchBlob.MainBundleDir,
26
+  LibraryDir : RNFetchBlob.LibraryDir
27
+}
28
+
29
+function addCode(code: string, error: Error): Error {
30
+  error.code = code
31
+  return error
35 32
 }
36 33
 
37 34
 /**
@@ -39,9 +36,9 @@ const dirs = {
39 36
  * @param  {string} name Stream ID
40 37
  * @return {RNFetchBlobSession}
41 38
  */
42
-function session(name:string):RNFetchBlobSession {
39
+function session(name: string): RNFetchBlobSession {
43 40
   let s = RNFetchBlobSession.getSession(name)
44
-  if(s)
41
+  if (s)
45 42
     return new RNFetchBlobSession(name)
46 43
   else {
47 44
     RNFetchBlobSession.setSession(name, [])
@@ -49,56 +46,48 @@ function session(name:string):RNFetchBlobSession {
49 46
   }
50 47
 }
51 48
 
52
-function asset(path:string):string {
53
-  if(Platform.OS === 'ios') {
49
+function asset(path: string): string {
50
+  if (Platform.OS === 'ios') {
54 51
     // path from camera roll
55
-    if(/^assets-library\:\/\//.test(path))
52
+    if (/^assets-library\:\/\//.test(path))
56 53
       return path
57 54
   }
58 55
   return 'bundle-assets://' + path
59 56
 }
60 57
 
61
-function createFile(path:string, data:string, encoding: 'base64' | 'ascii' | 'utf8'):Promise {
62
-  encoding = encoding || 'utf8'
63
-  return new Promise((resolve, reject) => {
64
-    let handler = (err) => {
65
-      if(err)
66
-        reject(new Error(err))
67
-      else
68
-        resolve()
69
-    }
70
-    if(encoding.toLowerCase() === 'ascii') {
71
-      if(Array.isArray(data))
72
-        RNFetchBlob.createFileASCII(path, data, handler)
73
-      else
74
-        reject(new Error('`data` of ASCII file must be an array contains numbers'))
75
-    }
76
-    else {
77
-      RNFetchBlob.createFile(path, data, encoding, handler)
78
-    }
79
-  })
58
+function createFile(path: string, data: string, encoding: 'base64' | 'ascii' | 'utf8' = 'utf8'): Promise<string> {
59
+  if (encoding.toLowerCase() === 'ascii') {
60
+    return Array.isArray(data) ?
61
+      RNFetchBlob.createFileASCII(path, data) :
62
+      Promise.reject(addCode('EINVAL', new TypeError('`data` of ASCII file must be an array with 0..255 numbers')))
63
+  }
64
+  else {
65
+    return RNFetchBlob.createFile(path, data, encoding)
66
+  }
80 67
 }
81 68
 
82 69
 /**
83 70
  * Create write stream to a file.
84 71
  * @param  {string} path Target path of file stream.
85 72
  * @param  {string} encoding Encoding of input data.
86
- * @param  {bool} append  A flag represent if data append to existing ones.
87
- * @return {Promise<WriteStream>} A promise resolves a `WriteStream` object.
73
+ * @param  {boolean} [append]  A flag represent if data append to existing ones.
74
+ * @return {Promise<RNFetchBlobWriteStream>} A promise resolves a `WriteStream` object.
88 75
  */
89 76
 function writeStream(
90
-  path : string,
91
-  encoding : 'utf8' | 'ascii' | 'base64',
92
-  append? : ?bool,
93
-):Promise<RNFetchBlobWriteStream> {
94
-  if(!path)
95
-    throw Error('RNFetchBlob could not open file stream with empty `path`')
96
-  encoding = encoding || 'utf8'
97
-  append = append || false
77
+  path: string,
78
+  encoding?: 'utf8' | 'ascii' | 'base64' = 'utf8',
79
+  append?: boolean = false,
80
+): Promise<RNFetchBlobWriteStream> {
81
+  if (typeof path !== 'string') {
82
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
83
+  }
98 84
   return new Promise((resolve, reject) => {
99
-    RNFetchBlob.writeStream(path, encoding || 'base64', append || false, (err, streamId:string) => {
100
-      if(err)
101
-        reject(new Error(err))
85
+    RNFetchBlob.writeStream(path, encoding, append, (errCode, errMsg, streamId: string) => {
86
+      if (errMsg) {
87
+        const err = new Error(errMsg)
88
+        err.code = errCode
89
+        reject(err)
90
+      }
102 91
       else
103 92
         resolve(new RNFetchBlobWriteStream(streamId, encoding))
104 93
     })
@@ -110,14 +99,18 @@ function writeStream(
110 99
  * @param  {string} path   The file path.
111 100
  * @param  {string} encoding Data encoding, should be one of `base64`, `utf8`, `ascii`
112 101
  * @param  {boolean} bufferSize Size of stream buffer.
102
+ * @param  {number} [tick=10] Interval in milliseconds between reading chunks of data
113 103
  * @return {RNFetchBlobStream} RNFetchBlobStream stream instance.
114 104
  */
115 105
 function readStream(
116
-  path : string,
117
-  encoding : 'utf8' | 'ascii' | 'base64',
118
-  bufferSize? : ?number,
119
-  tick : ?number = 10
120
-):Promise<RNFetchBlobReadStream> {
106
+  path: string,
107
+  encoding: 'utf8' | 'ascii' | 'base64' = 'utf8',
108
+  bufferSize?: number,
109
+  tick?: number = 10
110
+): Promise<RNFetchBlobReadStream> {
111
+  if (typeof path !== 'string') {
112
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
113
+  }
121 114
   return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize, tick))
122 115
 }
123 116
 
@@ -126,17 +119,11 @@ function readStream(
126 119
  * @param  {string} path Path of directory to be created
127 120
  * @return {Promise}
128 121
  */
129
-function mkdir(path:string):Promise {
130
-
131
-  return new Promise((resolve, reject) => {
132
-    RNFetchBlob.mkdir(path, (err, res) => {
133
-      if(err)
134
-        reject(new Error(err))
135
-      else
136
-        resolve()
137
-    })
138
-  })
139
-
122
+function mkdir(path: string): Promise {
123
+  if (typeof path !== 'string') {
124
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
125
+  }
126
+  return RNFetchBlob.mkdir(path)
140 127
 }
141 128
 
142 129
 /**
@@ -144,8 +131,8 @@ function mkdir(path:string):Promise {
144 131
  * @param  {string} groupName Name of app group
145 132
  * @return {Promise}
146 133
  */
147
-function pathForAppGroup(groupName:string):Promise {
148
-  return RNFetchBlob.pathForAppGroup(groupName);
134
+function pathForAppGroup(groupName: string): Promise {
135
+  return RNFetchBlob.pathForAppGroup(groupName)
149 136
 }
150 137
 
151 138
 /**
@@ -154,9 +141,10 @@ function pathForAppGroup(groupName:string):Promise {
154 141
  * @param  {'base64' | 'utf8' | 'ascii'} encoding Encoding of read stream.
155 142
  * @return {Promise<Array<number> | string>}
156 143
  */
157
-function readFile(path:string, encoding:string, bufferSize:?number):Promise<any> {
158
-  if(typeof path !== 'string')
159
-    return Promise.reject(new Error('Invalid argument "path" '))
144
+function readFile(path: string, encoding: string = 'utf8'): Promise<any> {
145
+  if (typeof path !== 'string') {
146
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
147
+  }
160 148
   return RNFetchBlob.readFile(path, encoding)
161 149
 }
162 150
 
@@ -167,37 +155,43 @@ function readFile(path:string, encoding:string, bufferSize:?number):Promise<any>
167 155
  * @param  {string} encoding Encoding of data (Optional).
168 156
  * @return {Promise}
169 157
  */
170
-function writeFile(path:string, data:string | Array<number>, encoding:?string):Promise {
171
-  encoding = encoding || 'utf8'
172
-  if(typeof path !== 'string')
173
-    return Promise.reject('Invalid argument "path" ')
174
-  if(encoding.toLocaleLowerCase() === 'ascii') {
175
-    if(!Array.isArray(data))
176
-      return Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`))
158
+function writeFile(path: string, data: string | Array<number>, encoding: ?string = 'utf8'): Promise {
159
+  if (typeof path !== 'string') {
160
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
161
+  }
162
+  if (encoding.toLocaleLowerCase() === 'ascii') {
163
+    if (!Array.isArray(data)) {
164
+      return Promise.reject(addCode('EINVAL', new TypeError('"data" must be an Array when encoding is "ascii"')))
165
+    }
177 166
     else
178
-      return RNFetchBlob.writeFileArray(path, data, false);
179
-  } else {
180
-    if(typeof data !== 'string')
181
-      return Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`))
167
+      return RNFetchBlob.writeFileArray(path, data, false)
168
+  }
169
+  else {
170
+    if (typeof data !== 'string') {
171
+      return Promise.reject(addCode('EINVAL', new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`)))
172
+    }
182 173
     else
183
-      return RNFetchBlob.writeFile(path, encoding, data, false);
174
+      return RNFetchBlob.writeFile(path, encoding, data, false)
184 175
   }
185 176
 }
186 177
 
187
-function appendFile(path:string, data:string | Array<number>, encoding:?string):Promise {
188
-  encoding = encoding || 'utf8'
189
-  if(typeof path !== 'string')
190
-    return Promise.reject('Invalid argument "path" ')
191
-  if(encoding.toLocaleLowerCase() === 'ascii') {
192
-    if(!Array.isArray(data))
193
-      return Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`))
178
+function appendFile(path: string, data: string | Array<number>, encoding?: string = 'utf8'): Promise<number> {
179
+  if (typeof path !== 'string') {
180
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
181
+  }
182
+  if (encoding.toLocaleLowerCase() === 'ascii') {
183
+    if (!Array.isArray(data)) {
184
+      return Promise.reject(addCode('EINVAL', new TypeError('`data` of ASCII file must be an array with 0..255 numbers')))
185
+    }
194 186
     else
195
-      return RNFetchBlob.writeFileArray(path, data, true);
196
-  } else {
197
-    if(typeof data !== 'string')
198
-      return Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`))
187
+      return RNFetchBlob.writeFileArray(path, data, true)
188
+  }
189
+  else {
190
+    if (typeof data !== 'string') {
191
+      return Promise.reject(addCode('EINVAL'), new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`))
192
+    }
199 193
     else
200
-      return RNFetchBlob.writeFile(path, encoding, data, true);
194
+      return RNFetchBlob.writeFile(path, encoding, data, true)
201 195
   }
202 196
 }
203 197
 
@@ -206,13 +200,16 @@ function appendFile(path:string, data:string | Array<number>, encoding:?string):
206 200
  * @param  {string} path Target path
207 201
  * @return {RNFetchBlobFile}
208 202
  */
209
-function stat(path:string):Promise<RNFetchBlobFile> {
203
+function stat(path: string): Promise<RNFetchBlobFile> {
210 204
   return new Promise((resolve, reject) => {
205
+    if (typeof path !== 'string') {
206
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
207
+    }
211 208
     RNFetchBlob.stat(path, (err, stat) => {
212
-      if(err)
209
+      if (err)
213 210
         reject(new Error(err))
214 211
       else {
215
-        if(stat) {
212
+        if (stat) {
216 213
           stat.size = parseInt(stat.size)
217 214
           stat.lastModified = parseInt(stat.lastModified)
218 215
         }
@@ -224,62 +221,77 @@ function stat(path:string):Promise<RNFetchBlobFile> {
224 221
 
225 222
 /**
226 223
  * Android only method, request media scanner to scan the file.
227
- * @param  {Array<Object<string, string>>} Array contains Key value pairs with key `path` and `mime`.
224
+ * @param  {Array<Object<string, string>>} pairs Array contains Key value pairs with key `path` and `mime`.
228 225
  * @return {Promise}
229 226
  */
230
-function scanFile(pairs:any):Promise {
227
+function scanFile(pairs: any): Promise {
231 228
   return new Promise((resolve, reject) => {
229
+    if (pairs === undefined) {
230
+      return reject(addCode('EINVAL', new TypeError('Missing argument')))
231
+    }
232 232
     RNFetchBlob.scanFile(pairs, (err) => {
233
-      if(err)
234
-        reject(new Error(err))
233
+      if (err)
234
+        reject(addCode('EUNSPECIFIED', new Error(err)))
235 235
       else
236 236
         resolve()
237 237
     })
238 238
   })
239 239
 }
240 240
 
241
-function cp(path:string, dest:string):Promise<boolean> {
241
+function hash(path: string, algorithm: string): Promise<string> {
242
+  if (typeof path !== 'string' || typeof algorithm !== 'string') {
243
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "algorithm"')))
244
+  }
245
+  return RNFetchBlob.hash(path, algorithm)
246
+}
247
+
248
+function cp(path: string, dest: string): Promise<boolean> {
242 249
   return new Promise((resolve, reject) => {
250
+    if (typeof path !== 'string' || typeof dest !== 'string') {
251
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "destination"')))
252
+    }
243 253
     RNFetchBlob.cp(path, dest, (err, res) => {
244
-      if(err)
245
-        reject(new Error(err))
254
+      if (err)
255
+        reject(addCode('EUNSPECIFIED', new Error(err)))
246 256
       else
247 257
         resolve(res)
248 258
     })
249 259
   })
250 260
 }
251 261
 
252
-function mv(path:string, dest:string):Promise<boolean> {
262
+function mv(path: string, dest: string): Promise<boolean> {
253 263
   return new Promise((resolve, reject) => {
264
+    if (typeof path !== 'string' || typeof dest !== 'string') {
265
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "destination"')))
266
+    }
254 267
     RNFetchBlob.mv(path, dest, (err, res) => {
255
-      if(err)
256
-        reject(new Error(err))
268
+      if (err)
269
+        reject(addCode('EUNSPECIFIED', new Error(err)))
257 270
       else
258 271
         resolve(res)
259 272
     })
260 273
   })
261 274
 }
262 275
 
263
-function lstat(path:string):Promise<Array<RNFetchBlobFile>> {
276
+function lstat(path: string): Promise<Array<RNFetchBlobFile>> {
264 277
   return new Promise((resolve, reject) => {
278
+    if (typeof path !== 'string') {
279
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
280
+    }
265 281
     RNFetchBlob.lstat(path, (err, stat) => {
266
-      if(err)
267
-        reject(new Error(err))
282
+      if (err)
283
+        reject(addCode('EUNSPECIFIED', new Error(err)))
268 284
       else
269 285
         resolve(stat)
270 286
     })
271 287
   })
272 288
 }
273 289
 
274
-function ls(path:string):Promise<Array<String>> {
275
-  return new Promise((resolve, reject) => {
276
-    RNFetchBlob.ls(path, (err, res) => {
277
-      if(err)
278
-        reject(new Error(err))
279
-      else
280
-        resolve(res)
281
-    })
282
-  })
290
+function ls(path: string): Promise<Array<String>> {
291
+  if (typeof path !== 'string') {
292
+    return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
293
+  }
294
+  return RNFetchBlob.ls(path)
283 295
 }
284 296
 
285 297
 /**
@@ -287,11 +299,14 @@ function ls(path:string):Promise<Array<String>> {
287 299
  * @param  {string}   path:string Path of target file.
288 300
  * @return {Promise}
289 301
  */
290
-function unlink(path:string):Promise {
302
+function unlink(path: string): Promise {
291 303
   return new Promise((resolve, reject) => {
304
+    if (typeof path !== 'string') {
305
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
306
+    }
292 307
     RNFetchBlob.unlink(path, (err) => {
293
-      if(err) {
294
-        reject(new Error(err))
308
+      if (err) {
309
+        reject(addCode('EUNSPECIFIED', new Error(err)))
295 310
       }
296 311
       else
297 312
         resolve()
@@ -302,63 +317,72 @@ function unlink(path:string):Promise {
302 317
 /**
303 318
  * Check if file exists and if it is a folder.
304 319
  * @param  {string} path Path to check
305
- * @return {Promise<bool, bool>}
320
+ * @return {Promise<boolean>}
306 321
  */
307
-function exists(path:string):Promise<bool, bool> {
308
-
322
+function exists(path: string): Promise<boolean> {
309 323
   return new Promise((resolve, reject) => {
324
+    if (typeof path !== 'string') {
325
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
326
+    }
310 327
     try {
311 328
       RNFetchBlob.exists(path, (exist) => {
312 329
         resolve(exist)
313 330
       })
314
-    } catch(err) {
315
-      reject(new Error(err))
331
+    }catch (err){
332
+      reject(addCode('EUNSPECIFIED', new Error(err)))
316 333
     }
317 334
   })
318 335
 
319 336
 }
320 337
 
321
-function slice(src:string, dest:string, start:number, end:number):Promise {
338
+function slice(src: string, dest: string, start: number, end: number): Promise {
339
+  if (typeof src !== 'string' || typeof dest !== 'string') {
340
+    return reject(addCode('EINVAL', new TypeError('Missing argument "src" and/or "destination"')))
341
+  }
342
+
322 343
   let p = Promise.resolve()
323 344
   let size = 0
345
+
324 346
   function normalize(num, size) {
325
-    if(num < 0)
347
+    if (num < 0)
326 348
       return Math.max(0, size + num)
327
-    if(!num && num !== 0)
349
+    if (!num && num !== 0)
328 350
       return size
329 351
     return num
330 352
   }
331
-  if(start < 0 || end < 0 || !start || !end) {
353
+
354
+  if (start < 0 || end < 0 || !start || !end) {
332 355
     p = p.then(() => stat(src))
333
-         .then((stat) => {
334
-           size = Math.floor(stat.size)
335
-           start = normalize(start || 0, size)
336
-           end = normalize(end, size)
337
-           return Promise.resolve()
338
-         })
356
+      .then((stat) => {
357
+        size = Math.floor(stat.size)
358
+        start = normalize(start || 0, size)
359
+        end = normalize(end, size)
360
+      })
339 361
   }
340 362
   return p.then(() => RNFetchBlob.slice(src, dest, start, end))
341 363
 }
342 364
 
343
-function isDir(path:string):Promise<bool, bool> {
344
-
365
+function isDir(path: string): Promise<bool> {
345 366
   return new Promise((resolve, reject) => {
367
+    if (typeof path !== 'string') {
368
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
369
+    }
346 370
     try {
347 371
       RNFetchBlob.exists(path, (exist, isDir) => {
348 372
         resolve(isDir)
349 373
       })
350
-    } catch(err) {
351
-      reject(new Error(err))
374
+    }catch (err){
375
+      reject(addCode('EUNSPECIFIED', new Error(err)))
352 376
     }
353 377
   })
354 378
 
355 379
 }
356 380
 
357
-function df():Promise<{ free : number, total : number }> {
381
+function df(): Promise<{ free: number, total: number }> {
358 382
   return new Promise((resolve, reject) => {
359 383
     RNFetchBlob.df((err, stat) => {
360
-      if(err)
361
-        reject(err)
384
+      if (err)
385
+        reject(addCode('EUNSPECIFIED', new Error(err)))
362 386
       else
363 387
         resolve(stat)
364 388
     })
@@ -379,6 +403,7 @@ export default {
379 403
   appendFile,
380 404
   pathForAppGroup,
381 405
   readFile,
406
+  hash,
382 407
   exists,
383 408
   createFile,
384 409
   isDir,

+ 9
- 10
index.js View File

@@ -17,7 +17,7 @@ import type {
17 17
   RNFetchBlobResponseInfo
18 18
 } from './types'
19 19
 import URIUtil from './utils/uri'
20
-import StatefulPromise from './class/StatefulPromise.js'
20
+//import StatefulPromise from './class/StatefulPromise.js'
21 21
 import fs from './fs'
22 22
 import getUUID from './utils/uuid'
23 23
 import base64 from 'base-64'
@@ -79,7 +79,8 @@ if(!RNFetchBlob || !RNFetchBlob.fetchBlobForm || !RNFetchBlob.fetchBlob) {
79 79
 }
80 80
 
81 81
 function wrap(path:string):string {
82
-  return 'RNFetchBlob-file://' + path
82
+  const prefix = path.startsWith('content://') ? 'RNFetchBlob-content://' : 'RNFetchBlob-file://'
83
+  return prefix + path
83 84
 }
84 85
 
85 86
 /**
@@ -118,7 +119,7 @@ function config (options:RNFetchBlobConfig) {
118 119
  * @param  {string} method     Should be one of `get`, `post`, `put`
119 120
  * @param  {string} url        A file URI string
120 121
  * @param  {string} headers    Arguments of file system API
121
- * @param  {any} body       Data to put or post to file systen.
122
+ * @param  {any}    body       Data to put or post to file systen.
122 123
  * @return {Promise}
123 124
  */
124 125
 function fetchFile(options = {}, method, url, headers = {}, body):Promise {
@@ -520,13 +521,12 @@ class FetchBlobResponse {
520 521
     }
521 522
     /**
522 523
      * Start read stream from cached file
523
-     * @param  {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`.
524
-     * @param  {Function} fn On data event handler
524
+     * @param  {String} encoding Encode type, should be one of `base64`, `ascii`, `utf8`.
525 525
      * @return {void}
526 526
      */
527
-    this.readStream = (encode: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => {
527
+    this.readStream = (encoding: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => {
528 528
       if(this.type === 'path') {
529
-        return readStream(this.data, encode)
529
+        return readStream(this.data, encoding)
530 530
       }
531 531
       else {
532 532
         console.warn('RNFetchblob', 'this response data does not contains any available stream')
@@ -539,10 +539,9 @@ class FetchBlobResponse {
539 539
      * @param  {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`.
540 540
      * @return {String}
541 541
      */
542
-    this.readFile = (encode: 'base64' | 'utf8' | 'ascii') => {
542
+    this.readFile = (encoding: 'base64' | 'utf8' | 'ascii') => {
543 543
       if(this.type === 'path') {
544
-        encode = encode || 'utf8'
545
-        return readFile(this.data, encode)
544
+        return readFile(this.data, encoding)
546 545
       }
547 546
       else {
548 547
         console.warn('RNFetchblob', 'this response does not contains a readable file')

+ 2
- 2
ios.js View File

@@ -13,7 +13,7 @@ const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
13 13
 
14 14
 /**
15 15
  * Open a file using UIDocumentInteractionController
16
- * @param  {string]} path Path of the file to be open.
16
+ * @param  {string} path Path of the file to be open.
17 17
  * @param  {string} scheme URI scheme that needs to support, optional
18 18
  * @return {Promise}
19 19
  */
@@ -26,7 +26,7 @@ function previewDocument(path:string, scheme:string) {
26 26
 
27 27
 /**
28 28
  * Preview a file using UIDocumentInteractionController
29
- * @param  {string]} path Path of the file to be open.
29
+ * @param  {string} path Path of the file to be open.
30 30
  * @param  {string} scheme URI scheme that needs to support, optional
31 31
  * @return {Promise}
32 32
  */

+ 7
- 1
ios/RNFetchBlob.xcodeproj/project.pbxproj View File

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

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

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

+ 109
- 64
ios/RNFetchBlob/RNFetchBlob.m View File

@@ -38,10 +38,14 @@ dispatch_queue_t fsQueue;
38 38
 
39 39
 + (RCTBridge *)getRCTBridge
40 40
 {
41
-    RCTRootView * rootView = [[UIApplication sharedApplication] keyWindow].rootViewController.view;
41
+    RCTRootView * rootView = (RCTRootView*) [[UIApplication sharedApplication] keyWindow].rootViewController.view;
42 42
     return rootView.bridge;
43 43
 }
44 44
 
45
++ (BOOL)requiresMainQueueSetup {
46
+    return NO;
47
+}
48
+
45 49
 RCT_EXPORT_MODULE();
46 50
 
47 51
 - (id) init {
@@ -96,8 +100,12 @@ RCT_EXPORT_METHOD(fetchBlobForm:(NSDictionary *)options
96 100
         // send HTTP request
97 101
         else
98 102
         {
99
-            RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init];
100
-            [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback];
103
+            [[RNFetchBlobNetwork sharedInstance] sendRequest:options
104
+                                               contentLength:bodyLength
105
+                                                      bridge:self.bridge
106
+                                                      taskId:taskId
107
+                                                 withRequest:req
108
+                                                    callback:callback];
101 109
         }
102 110
     }];
103 111
 
@@ -128,15 +136,23 @@ RCT_EXPORT_METHOD(fetchBlob:(NSDictionary *)options
128 136
         // send HTTP request
129 137
         else
130 138
         {
131
-            __block RNFetchBlobNetwork * utils = [[RNFetchBlobNetwork alloc] init];
132
-            [utils sendRequest:options contentLength:bodyLength bridge:self.bridge taskId:taskId withRequest:req callback:callback];
139
+            [[RNFetchBlobNetwork sharedInstance] sendRequest:options
140
+                                               contentLength:bodyLength
141
+                                                      bridge:self.bridge
142
+                                                      taskId:taskId
143
+                                                 withRequest:req
144
+                                                    callback:callback];
133 145
         }
134 146
     }];
135 147
 }
136 148
 
137 149
 #pragma mark - fs.createFile
138
-RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NSString *)encoding callback:(RCTResponseSenderBlock)callback) {
139
-
150
+RCT_EXPORT_METHOD(createFile:(NSString *)path
151
+                  data:(NSString *)data
152
+                  encoding:(NSString *)encoding
153
+                  resolver:(RCTPromiseResolveBlock)resolve
154
+                  rejecter:(RCTPromiseRejectBlock)reject)
155
+{
140 156
     NSFileManager * fm = [NSFileManager defaultManager];
141 157
     NSData * fileContent = nil;
142 158
 
@@ -154,18 +170,25 @@ RCT_EXPORT_METHOD(createFile:(NSString *)path data:(NSString *)data encoding:(NS
154 170
         fileContent = [[NSData alloc] initWithData:[data dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];
155 171
     }
156 172
 
157
-    BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
158
-    if(success == YES)
159
-        callback(@[[NSNull null]]);
160
-    else
161
-        callback(@[[NSString stringWithFormat:@"failed to create new file at path %@ please ensure the folder exists"]]);
162
-
173
+    if ([fm fileExistsAtPath:path]) {
174
+        reject(@"EEXIST", [NSString stringWithFormat:@"File '%@' already exists", path], nil);
175
+    }
176
+    else {
177
+        BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
178
+        if(success == YES)
179
+            resolve(@[[NSNull null]]);
180
+        else
181
+            reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Failed to create new file at path '%@', please ensure the folder exists", path], nil);
182
+    }
163 183
 }
164 184
 
165 185
 #pragma mark - fs.createFileASCII
166 186
 // method for create file with ASCII content
167
-RCT_EXPORT_METHOD(createFileASCII:(NSString *)path data:(NSArray *)dataArray callback:(RCTResponseSenderBlock)callback) {
168
-
187
+RCT_EXPORT_METHOD(createFileASCII:(NSString *)path
188
+                  data:(NSArray *)dataArray
189
+                  resolver:(RCTPromiseResolveBlock)resolve
190
+                  rejecter:(RCTPromiseRejectBlock)reject)
191
+{
169 192
     NSFileManager * fm = [NSFileManager defaultManager];
170 193
     NSMutableData * fileContent = [NSMutableData alloc];
171 194
     // prevent stack overflow, alloc on heap
@@ -174,14 +197,21 @@ RCT_EXPORT_METHOD(createFileASCII:(NSString *)path data:(NSArray *)dataArray cal
174 197
     for(int i = 0; i < dataArray.count; i++) {
175 198
         bytes[i] = [[dataArray objectAtIndex:i] charValue];
176 199
     }
200
+
177 201
     [fileContent appendBytes:bytes length:dataArray.count];
178
-    BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
179
-    free(bytes);
180
-    if(success == YES)
181
-        callback(@[[NSNull null]]);
182
-    else
183
-        callback(@[[NSString stringWithFormat:@"failed to create new file at path %@ please ensure the folder exists"]]);
184 202
 
203
+    if ([fm fileExistsAtPath:path]) {
204
+        reject(@"EEXIST", [NSString stringWithFormat:@"File '%@' already exists", path], nil);
205
+    }
206
+    else {
207
+        BOOL success = [fm createFileAtPath:path contents:fileContent attributes:NULL];
208
+        if(success == YES)
209
+            resolve(@[[NSNull null]]);
210
+        else
211
+            reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"failed to create new file at path '%@', please ensure the folder exists", path], nil);
212
+    }
213
+
214
+    free(bytes);
185 215
 }
186 216
 
187 217
 #pragma mark - fs.pathForAppGroup
@@ -194,7 +224,7 @@ RCT_EXPORT_METHOD(pathForAppGroup:(NSString *)groupName
194 224
     if(path) {
195 225
         resolve(path);
196 226
     } else {
197
-        reject(@"RNFetchBlob file not found", @"could not find path for app group", nil);
227
+        reject(@"EUNSPECIFIED", @"could not find path for app group", nil);
198 228
     }
199 229
 }
200 230
 
@@ -220,14 +250,26 @@ RCT_EXPORT_METHOD(writeStream:(NSString *)path withEncoding:(NSString *)encoding
220 250
 {
221 251
     RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
222 252
     NSFileManager * fm = [NSFileManager defaultManager];
223
-    BOOL isDir = nil;
224
-    BOOL exist = [fm fileExistsAtPath:path isDirectory:&isDir];
225
-    if( exist == NO || isDir == YES) {
226
-        callback(@[[NSString stringWithFormat:@"target path `%@` may not exists or it's a folder", path]]);
227
-        return;
253
+    NSString * folder = [path stringByDeletingLastPathComponent];
254
+    NSError* err = nil;
255
+    BOOL isDir = NO;
256
+    BOOL exists = [fm fileExistsAtPath:path isDirectory: &isDir];
257
+
258
+    if(!exists) {
259
+        [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
260
+        if(err != nil) {
261
+            callback(@[@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]]]);
262
+        }
263
+        if(![fm createFileAtPath:path contents:nil attributes:nil]) {
264
+            callback(@[@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path]]);
265
+        }
266
+    }
267
+    else if(isDir) {
268
+        callback(@[@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path]]);
228 269
     }
270
+
229 271
     NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append];
230
-    callback(@[[NSNull null], streamId]);
272
+    callback(@[[NSNull null], [NSNull null], streamId]);
231 273
 }
232 274
 
233 275
 #pragma mark - fs.writeArrayChunk
@@ -291,24 +333,25 @@ RCT_EXPORT_METHOD(removeSession:(NSArray *)paths callback:(RCTResponseSenderBloc
291 333
 }
292 334
 
293 335
 #pragma mark - fs.ls
294
-RCT_EXPORT_METHOD(ls:(NSString *)path callback:(RCTResponseSenderBlock) callback)
336
+RCT_EXPORT_METHOD(ls:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
295 337
 {
296 338
     NSFileManager* fm = [NSFileManager defaultManager];
297 339
     BOOL exist = nil;
298 340
     BOOL isDir = nil;
299 341
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
300
-    if(exist == NO || isDir == NO) {
301
-        callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not a folder", path]]);
302
-        return ;
342
+    if(exist == NO) {
343
+        return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path], nil);
344
+    }
345
+    if(isDir == NO) {
346
+        return reject(@"ENOTDIR", [NSString stringWithFormat:@"Not a directory '%@'", path], nil);
303 347
     }
304 348
     NSError * error = nil;
305 349
     NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
306 350
 
307 351
     if(error == nil)
308
-        callback(@[[NSNull null], result == nil ? [NSNull null] :result ]);
352
+        resolve(result);
309 353
     else
310
-        callback(@[[error localizedDescription], [NSNull null]]);
311
-
354
+        reject(@"EUNSPECIFIED", [error description], nil);
312 355
 }
313 356
 
314 357
 #pragma mark - fs.stat
@@ -326,7 +369,7 @@ RCT_EXPORT_METHOD(stat:(NSString *)target callback:(RCTResponseSenderBlock) call
326 369
 
327 370
             exist = [fm fileExistsAtPath:path isDirectory:&isDir];
328 371
             if(exist == NO) {
329
-                callback(@[[NSString stringWithFormat:@"failed to stat path `%@` for it is not exist or it is not exist", path]]);
372
+                callback(@[[NSString stringWithFormat:@"failed to stat path `%@` because it does not exist or it is not a folder", path]]);
330 373
                 return ;
331 374
             }
332 375
             result = [RNFetchBlobFS stat:path error:&error];
@@ -362,7 +405,7 @@ RCT_EXPORT_METHOD(lstat:(NSString *)path callback:(RCTResponseSenderBlock) callb
362 405
 
363 406
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
364 407
     if(exist == NO) {
365
-        callback(@[[NSString stringWithFormat:@"failed to list path `%@` for it is not exist or it is not exist", path]]);
408
+        callback(@[[NSString stringWithFormat:@"failed to lstat path `%@` because it does not exist or it is not a folder", path]]);
366 409
         return ;
367 410
     }
368 411
     NSError * error = nil;
@@ -389,7 +432,6 @@ RCT_EXPORT_METHOD(lstat:(NSString *)path callback:(RCTResponseSenderBlock) callb
389 432
 #pragma mark - fs.cp
390 433
 RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback)
391 434
 {
392
-
393 435
 //    path = [RNFetchBlobFS getPathOfAsset:path];
394 436
     [RNFetchBlobFS getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
395 437
         NSError * error = nil;
@@ -400,6 +442,7 @@ RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTRespons
400 442
         }
401 443
         else
402 444
         {
445
+            // If the destination exists there will be an error
403 446
             BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
404 447
 
405 448
             if(error == nil)
@@ -408,7 +451,6 @@ RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTRespons
408 451
                 callback(@[[error localizedDescription], @NO]);
409 452
         }
410 453
     }];
411
-
412 454
 }
413 455
 
414 456
 
@@ -426,15 +468,9 @@ RCT_EXPORT_METHOD(mv:(NSString *)path toPath:(NSString *)dest callback:(RCTRespo
426 468
 }
427 469
 
428 470
 #pragma mark - fs.mkdir
429
-RCT_EXPORT_METHOD(mkdir:(NSString *)path callback:(RCTResponseSenderBlock) callback)
471
+RCT_EXPORT_METHOD(mkdir:(NSString *)path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
430 472
 {
431
-    if([[NSFileManager defaultManager] fileExistsAtPath:path]) {
432
-        callback(@[@"mkdir failed, folder already exists"]);
433
-        return;
434
-    }
435
-    else
436
-        [RNFetchBlobFS mkdir:path];
437
-    callback(@[[NSNull null]]);
473
+    [RNFetchBlobFS mkdir:path resolver:resolve rejecter:reject];
438 474
 }
439 475
 
440 476
 #pragma mark - fs.readFile
@@ -443,24 +479,30 @@ RCT_EXPORT_METHOD(readFile:(NSString *)path
443 479
                   resolver:(RCTPromiseResolveBlock)resolve
444 480
                   rejecter:(RCTPromiseRejectBlock)reject)
445 481
 {
446
-
447
-    [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(id content, NSString * err) {
448
-        if(err != nil)
449
-        {
450
-            reject(@"RNFetchBlob failed to read file", err, nil);
482
+    
483
+    [RNFetchBlobFS readFile:path encoding:encoding onComplete:^(NSData * content, NSString * code, NSString * err) {
484
+        if(err != nil) {
485
+            reject(code, err, nil);
451 486
             return;
452 487
         }
453
-        if(encoding == @"ascii")
454
-        {
488
+        if(encoding == @"ascii") {
455 489
             resolve((NSMutableArray *)content);
456 490
         }
457
-        else
458
-        {
491
+        else {
459 492
             resolve((NSString *)content);
460 493
         }
461 494
     }];
462 495
 }
463 496
 
497
+#pragma mark - fs.hash
498
+RCT_EXPORT_METHOD(hash:(NSString *)path
499
+                  algorithm:(NSString *)algorithm
500
+                  resolver:(RCTPromiseResolveBlock)resolve
501
+                  rejecter:(RCTPromiseRejectBlock)reject)
502
+{
503
+    [RNFetchBlobFS hash:path algorithm:[NSString stringWithString:algorithm] resolver:resolve rejecter:reject];
504
+}
505
+
464 506
 #pragma mark - fs.readStream
465 507
 RCT_EXPORT_METHOD(readStream:(NSString *)path withEncoding:(NSString *)encoding bufferSize:(int)bufferSize tick:(int)tick streamId:(NSString *)streamId)
466 508
 {
@@ -488,7 +530,7 @@ RCT_EXPORT_METHOD(getEnvironmentDirs:(RCTResponseSenderBlock) callback)
488 530
 
489 531
 #pragma mark - net.cancelRequest
490 532
 RCT_EXPORT_METHOD(cancelRequest:(NSString *)taskId callback:(RCTResponseSenderBlock)callback) {
491
-    [RNFetchBlobNetwork cancelRequest:taskId];
533
+    [[RNFetchBlobNetwork sharedInstance] cancelRequest:taskId];
492 534
     callback(@[[NSNull null], taskId]);
493 535
 
494 536
 }
@@ -498,14 +540,14 @@ RCT_EXPORT_METHOD(enableProgressReport:(NSString *)taskId interval:(nonnull NSNu
498 540
 {
499 541
 
500 542
     RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Download interval:interval count:count];
501
-    [RNFetchBlobNetwork enableProgressReport:taskId config:cfg];
543
+    [[RNFetchBlobNetwork sharedInstance] enableProgressReport:taskId config:cfg];
502 544
 }
503 545
 
504 546
 #pragma mark - net.enableUploadProgressReport
505 547
 RCT_EXPORT_METHOD(enableUploadProgressReport:(NSString *)taskId interval:(nonnull NSNumber*)interval count:(nonnull NSNumber*)count)
506 548
 {
507 549
     RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Upload interval:interval count:count];
508
-    [RNFetchBlobNetwork enableUploadProgress:taskId config:cfg];
550
+    [[RNFetchBlobNetwork sharedInstance] enableUploadProgress:taskId config:cfg];
509 551
 }
510 552
 
511 553
 #pragma mark - fs.slice
@@ -529,7 +571,7 @@ RCT_EXPORT_METHOD(previewDocument:(NSString*)uri scheme:(NSString *)scheme resol
529 571
       });
530 572
         resolve(@[[NSNull null]]);
531 573
     } else {
532
-        reject(@"RNFetchBlob could not open document", @"scheme is not supported", nil);
574
+        reject(@"EINVAL", @"scheme is not supported", nil);
533 575
     }
534 576
 }
535 577
 
@@ -545,11 +587,14 @@ RCT_EXPORT_METHOD(openDocument:(NSString*)uri scheme:(NSString *)scheme resolver
545 587
 
546 588
     if(scheme == nil || [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:scheme]]) {
547 589
         dispatch_sync(dispatch_get_main_queue(), ^{
548
-            [documentController presentPreviewAnimated:YES];
590
+            if([documentController presentPreviewAnimated:YES]) {
591
+                resolve(@[[NSNull null]]);
592
+            } else {
593
+                reject(@"EINVAL", @"document is not supported", nil);
594
+            }
549 595
         });
550
-        resolve(@[[NSNull null]]);
551 596
     } else {
552
-        reject(@"RNFetchBlob could not open document", @"scheme is not supported", nil);
597
+        reject(@"EINVAL", @"scheme is not supported", nil);
553 598
     }
554 599
 }
555 600
 
@@ -563,7 +608,7 @@ RCT_EXPORT_METHOD(excludeFromBackupKey:(NSString *)url resolver:(RCTPromiseResol
563 608
     {
564 609
         resolve(@[[NSNull null]]);
565 610
     } else {
566
-        reject(@"RNFetchBlob could not open document", [error description], nil);
611
+        reject(@"EUNSPECIFIED", [error description], nil);
567 612
     }
568 613
 
569 614
 }

+ 28
- 28
ios/RNFetchBlobConst.m View File

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

+ 8
- 3
ios/RNFetchBlobFS.h View File

@@ -34,8 +34,8 @@
34 34
     NSString * streamId;
35 35
 }
36 36
 
37
-@property (nonatomic) NSOutputStream * outStream;
38
-@property (nonatomic) NSInputStream * inStream;
37
+@property (nonatomic) NSOutputStream * _Nullable outStream;
38
+@property (nonatomic) NSInputStream * _Nullable inStream;
39 39
 @property (strong, nonatomic) RCTResponseSenderBlock callback;
40 40
 @property (nonatomic) RCTBridge * bridge;
41 41
 @property (nonatomic) NSString * encoding;
@@ -58,11 +58,16 @@
58 58
 // fs methods
59 59
 + (RNFetchBlobFS *) getFileStreams;
60 60
 + (BOOL) mkdir:(NSString *) path;
61
++ (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
62
++ (void) hash:(NSString *)path
63
+    algorithm:(NSString *)algorithm
64
+     resolver:(RCTPromiseResolveBlock)resolve
65
+     rejecter:(RCTPromiseRejectBlock)reject;
61 66
 + (NSDictionary *) stat:(NSString *) path error:(NSError **) error;
62 67
 + (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback;
63 68
 + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
64 69
 + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
65
-+ (void) readFile:(NSString *)path encoding:(NSString *)encoding onComplete:(void (^)(NSData * content, NSString * errMsg))onComplete;
70
++ (void) readFile:(NSString *)path encoding:(NSString *)encoding onComplete:(void (^)(NSData * content, NSString* code, NSString * errMsg))onComplete;
66 71
 + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
67 72
 + (void) slice:(NSString *)path
68 73
          dest:(NSString *)dest

+ 190
- 56
ios/RNFetchBlobFS.m View File

@@ -13,6 +13,8 @@
13 13
 #import "IOS7Polyfill.h"
14 14
 @import AssetsLibrary;
15 15
 
16
+#import <CommonCrypto/CommonDigest.h>
17
+
16 18
 #if __has_include(<React/RCTAssert.h>)
17 19
 #import <React/RCTBridge.h>
18 20
 #import <React/RCTEventDispatcher.h>
@@ -163,8 +165,8 @@ NSMutableDictionary *fileStreams = nil;
163 165
             {
164 166
                 if([[NSFileManager defaultManager] fileExistsAtPath:path] == NO)
165 167
                 {
166
-                    NSString * message = [NSString stringWithFormat:@"File not exists at path %@", path];
167
-                    NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": message };
168
+                    NSString * message = [NSString stringWithFormat:@"File does not exist at path %@", path];
169
+                    NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"ENOENT", @"detail": message };
168 170
                     [event sendDeviceEventWithName:streamId body:payload];
169 171
                     free(buffer);
170 172
                     return ;
@@ -197,7 +199,7 @@ NSMutableDictionary *fileStreams = nil;
197 199
             }
198 200
             else
199 201
             {
200
-                NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": @"RNFetchBlob.readStream unable to resolve URI" };
202
+                NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"EINVAL", @"detail": @"Unable to resolve URI" };
201 203
                 [event sendDeviceEventWithName:streamId body:payload];
202 204
             }
203 205
             // release buffer
@@ -207,7 +209,7 @@ NSMutableDictionary *fileStreams = nil;
207 209
         }
208 210
         @catch (NSError * err)
209 211
         {
210
-            NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"detail": [NSString stringWithFormat:@"RNFetchBlob.readStream error %@", [err description]] };
212
+            NSDictionary * payload = @{ @"event": FS_EVENT_ERROR, @"code": @"EUNSPECIFIED", @"detail": [err description] };
211 213
             [event sendDeviceEventWithName:streamId body:payload];
212 214
         }
213 215
         @finally
@@ -254,11 +256,11 @@ NSMutableDictionary *fileStreams = nil;
254 256
                     [asciiArray addObject:[NSNumber numberWithChar:bytePtr[i]]];
255 257
                 }
256 258
             }
257
-            
259
+
258 260
             NSDictionary * payload = @{ @"event": FS_EVENT_DATA,  @"detail" : asciiArray };
259 261
             [event sendDeviceEventWithName:streamId body:payload];
260 262
         }
261
-        
263
+
262 264
     }
263 265
     @catch (NSException * ex)
264 266
     {
@@ -330,23 +332,39 @@ NSMutableDictionary *fileStreams = nil;
330 332
 
331 333
 # pragma mark - write file
332 334
 
333
-+ (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
335
++ (void) writeFile:(NSString *)path
336
+                    encoding:(NSString *)encoding
337
+                    data:(NSString *)data
338
+                    append:(BOOL)append
339
+                    resolver:(RCTPromiseResolveBlock)resolve
340
+                    rejecter:(RCTPromiseRejectBlock)reject
334 341
 {
335 342
     @try {
336 343
         NSFileManager * fm = [NSFileManager defaultManager];
337 344
         NSError * err = nil;
338
-        // check if the folder exists, if not exists, create folders recursively
345
+        // check if the folder exists, if it does not exist create folders recursively
339 346
         // after the folders created, write data into the file
340 347
         NSString * folder = [path stringByDeletingLastPathComponent];
341 348
         encoding = [encoding lowercaseString];
342
-        if(![fm fileExistsAtPath:folder]) {
343
-            [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
344
-            [fm createFileAtPath:path contents:nil attributes:nil];
349
+
350
+        BOOL isDir = NO;
351
+        BOOL exists = NO;
352
+        exists = [fm fileExistsAtPath:path isDirectory: &isDir];
353
+
354
+        if (isDir) {
355
+            return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
345 356
         }
346
-        if(err != nil) {
347
-            reject(@"RNFetchBlob writeFile Error", @"could not create file at path", nil);
348
-            return;
357
+
358
+        if(!exists) {
359
+            [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
360
+            if(err != nil) {
361
+                return reject(@"ENOTDIR", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil);
362
+            }
363
+            if(![fm createFileAtPath:path contents:nil attributes:nil]) {
364
+                return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil);
365
+            }
349 366
         }
367
+
350 368
         NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
351 369
         NSData * content = nil;
352 370
         if([encoding RNFBContainsString:@"base64"]) {
@@ -355,7 +373,7 @@ NSMutableDictionary *fileStreams = nil;
355 373
         else if([encoding isEqualToString:@"uri"]) {
356 374
             NSNumber* size = [[self class] writeFileFromFile:data toFile:path append:append callback:^(NSString *errMsg, NSNumber *size) {
357 375
                 if(errMsg != nil)
358
-                    reject(@"RNFetchBlob writeFile Error", errMsg, nil);
376
+                    reject(@"EUNSPECIFIED", errMsg, nil);
359 377
                 else
360 378
                     resolve(size);
361 379
             }];
@@ -378,22 +396,40 @@ NSMutableDictionary *fileStreams = nil;
378 396
     }
379 397
     @catch (NSException * e)
380 398
     {
381
-        reject(@"RNFetchBlob writeFile Error", @"Error", [e description]);
399
+        reject(@"EUNSPECIFIED", [e description], nil);
382 400
     }
383 401
 }
384 402
 
385 403
 # pragma mark - write file (array)
386 404
 
387
-+ (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
405
++ (void) writeFileArray:(NSString *)path
406
+                         data:(NSArray *)data
407
+                         append:(BOOL)append
408
+                         resolver:(RCTPromiseResolveBlock)resolve
409
+                         rejecter:(RCTPromiseRejectBlock)reject
410
+{
388 411
     @try {
389 412
         NSFileManager * fm = [NSFileManager defaultManager];
390 413
         NSError * err = nil;
391 414
         // check if the folder exists, if not exists, create folders recursively
392 415
         // after the folders created, write data into the file
393 416
         NSString * folder = [path stringByDeletingLastPathComponent];
394
-        if(![fm fileExistsAtPath:folder]) {
417
+
418
+        BOOL isDir = NO;
419
+        BOOL exists = NO;
420
+        exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir];
421
+
422
+        if (isDir) {
423
+            return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
424
+        }
425
+
426
+        if(!exists) {
395 427
             [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
428
+            if(err != nil) {
429
+                return reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Failed to create parent directory of '%@'; error: %@", path, [err description]], nil);
430
+            }
396 431
         }
432
+
397 433
         NSMutableData * fileContent = [NSMutableData alloc];
398 434
         // prevent stack overflow, alloc on heap
399 435
         char * bytes = (char*) malloc([data count]);
@@ -401,8 +437,11 @@ NSMutableDictionary *fileStreams = nil;
401 437
             bytes[i] = [[data objectAtIndex:i] charValue];
402 438
         }
403 439
         [fileContent appendBytes:bytes length:data.count];
404
-        if(![fm fileExistsAtPath:path]) {
405
-            [fm createFileAtPath:path contents:fileContent attributes:NULL];
440
+
441
+        if(!exists) {
442
+            if(![fm createFileAtPath:path contents:fileContent attributes:NULL]) {
443
+                return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil);
444
+            }
406 445
         }
407 446
         // if file exists, write file
408 447
         else {
@@ -422,7 +461,7 @@ NSMutableDictionary *fileStreams = nil;
422 461
     }
423 462
     @catch (NSException * e)
424 463
     {
425
-        reject(@"RNFetchBlob writeFile Error", @"Error", [e description]);
464
+        reject(@"EUNSPECIFIED", [e description], nil);
426 465
     }
427 466
 }
428 467
 
@@ -430,7 +469,7 @@ NSMutableDictionary *fileStreams = nil;
430 469
 
431 470
 + (void) readFile:(NSString *)path
432 471
          encoding:(NSString *)encoding
433
-       onComplete:(void (^)(id content, NSString * errMsg))onComplete
472
+       onComplete:(void (^)(NSData * content, NSString * codeStr, NSString * errMsg))onComplete
434 473
 {
435 474
     [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
436 475
         __block NSData * fileContent;
@@ -442,7 +481,7 @@ NSMutableDictionary *fileStreams = nil;
442 481
             [asset getBytes:buffer fromOffset:0 length:asset.size error:&err];
443 482
             if(err != nil)
444 483
             {
445
-                onComplete(nil, [err description]);
484
+                onComplete(nil, @"EUNSPECIFIED", [err description]);
446 485
                 free(buffer);
447 486
                 return;
448 487
             }
@@ -451,26 +490,31 @@ NSMutableDictionary *fileStreams = nil;
451 490
         }
452 491
         else
453 492
         {
454
-            if(![[NSFileManager defaultManager] fileExistsAtPath:path]) {
455
-                onComplete(nil, @"file not exists");
493
+            BOOL isDir = NO;
494
+            if(![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir]) {
495
+                if (isDir) {
496
+                    onComplete(nil, @"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path]);
497
+                } else {
498
+                    onComplete(nil, @"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path]);
499
+                }
456 500
                 return;
457 501
             }
458 502
             fileContent = [NSData dataWithContentsOfFile:path];
459
-            
503
+
460 504
         }
461
-        
505
+
462 506
         if(encoding != nil)
463 507
         {
464 508
             if([[encoding lowercaseString] isEqualToString:@"utf8"])
465 509
             {
466 510
                 NSString * utf8 = [[NSString alloc] initWithData:fileContent encoding:NSUTF8StringEncoding];
467 511
                 if(utf8 == nil)
468
-                    onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil);
512
+                    onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil, nil);
469 513
                 else
470
-                    onComplete(utf8, nil);
514
+                    onComplete(utf8, nil, nil);
471 515
             }
472 516
             else if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
473
-                onComplete([fileContent base64EncodedStringWithOptions:0], nil);
517
+                onComplete([fileContent base64EncodedStringWithOptions:0], nil, nil);
474 518
             }
475 519
             else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) {
476 520
                 NSMutableArray * resultArray = [NSMutableArray array];
@@ -478,28 +522,115 @@ NSMutableDictionary *fileStreams = nil;
478 522
                 for(int i=0;i<[fileContent length];i++) {
479 523
                     [resultArray addObject:[NSNumber numberWithChar:bytes[i]]];
480 524
                 }
481
-                onComplete(resultArray, nil);
525
+                onComplete(resultArray, nil, nil);
482 526
             }
483 527
         }
484 528
         else
485 529
         {
486
-            onComplete(fileContent, nil);
530
+            onComplete(fileContent, nil, nil);
487 531
         }
488
-        
532
+
489 533
     }];
490 534
 }
491 535
 
536
+# pragma mark - hash
537
+
538
++ (void) hash:(NSString *)path
539
+                  algorithm:(NSString *)algorithm
540
+                  resolver:(RCTPromiseResolveBlock)resolve
541
+                  rejecter:(RCTPromiseRejectBlock)reject
542
+{
543
+    BOOL isDir = NO;
544
+    BOOL exists = NO;
545
+    exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory: &isDir];
546
+
547
+    if (isDir) {
548
+        return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
549
+    }
550
+    if (!exists) {
551
+        return reject(@"ENOENT", [NSString stringWithFormat:@"No such file '%@'", path], nil);
552
+    }
553
+
554
+    NSError *error = nil;
555
+
556
+    NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error];
557
+
558
+    if (error) {
559
+        reject(@"EUNKNOWN", [error description], nil);
560
+        return;
561
+    }
562
+
563
+    if ([attributes objectForKey:NSFileType] == NSFileTypeDirectory) {
564
+        reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
565
+        return;
566
+    }
567
+
568
+    NSData *content = [[NSFileManager defaultManager] contentsAtPath:path];
569
+
570
+    NSArray *keys = [NSArray arrayWithObjects:@"md5", @"sha1", @"sha224", @"sha256", @"sha384", @"sha512", nil];
571
+
572
+    NSArray *digestLengths = [NSArray arrayWithObjects:
573
+        @CC_MD5_DIGEST_LENGTH,
574
+        @CC_SHA1_DIGEST_LENGTH,
575
+        @CC_SHA224_DIGEST_LENGTH,
576
+        @CC_SHA256_DIGEST_LENGTH,
577
+        @CC_SHA384_DIGEST_LENGTH,
578
+        @CC_SHA512_DIGEST_LENGTH,
579
+        nil];
580
+
581
+    NSDictionary *keysToDigestLengths = [NSDictionary dictionaryWithObjects:digestLengths forKeys:keys];
582
+
583
+    int digestLength = [[keysToDigestLengths objectForKey:algorithm] intValue];
584
+
585
+    if (!digestLength) {
586
+      return reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil);
587
+    }
588
+
589
+    unsigned char buffer[digestLength];
590
+
591
+    if ([algorithm isEqualToString:@"md5"]) {
592
+        CC_MD5(content.bytes, (CC_LONG)content.length, buffer);
593
+    } else if ([algorithm isEqualToString:@"sha1"]) {
594
+        CC_SHA1(content.bytes, (CC_LONG)content.length, buffer);
595
+    } else if ([algorithm isEqualToString:@"sha224"]) {
596
+        CC_SHA224(content.bytes, (CC_LONG)content.length, buffer);
597
+    } else if ([algorithm isEqualToString:@"sha256"]) {
598
+        CC_SHA256(content.bytes, (CC_LONG)content.length, buffer);
599
+    } else if ([algorithm isEqualToString:@"sha384"]) {
600
+        CC_SHA384(content.bytes, (CC_LONG)content.length, buffer);
601
+    } else if ([algorithm isEqualToString:@"sha512"]) {
602
+        CC_SHA512(content.bytes, (CC_LONG)content.length, buffer);
603
+    } else {
604
+        reject(@"EINVAL", [NSString stringWithFormat:@"Invalid algorithm '%@', must be one of md5, sha1, sha224, sha256, sha384, sha512", algorithm], nil);
605
+        return;
606
+    }
607
+
608
+    NSMutableString *output = [NSMutableString stringWithCapacity:digestLength * 2];
609
+    for(int i = 0; i < digestLength; i++)
610
+        [output appendFormat:@"%02x",buffer[i]];
611
+
612
+    resolve(output);
613
+}
492 614
 
493 615
 # pragma mark - mkdir
494 616
 
495
-+ (BOOL) mkdir:(NSString *) path {
496
-    BOOL isDir;
617
++ (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
618
+{
619
+    BOOL isDir = NO;
497 620
     NSError * err = nil;
498
-    // if temp folder not exists, create one
499
-    if(![[NSFileManager defaultManager] fileExistsAtPath: path isDirectory:&isDir]) {
621
+    if([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
622
+        reject(@"EEXIST", [NSString stringWithFormat:@"%@ '%@' already exists", isDir ? @"Directory" : @"File", path], nil);
623
+        return;
624
+    }
625
+    else {
500 626
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
501 627
     }
502
-    return err == nil;
628
+    if(err == nil) {
629
+        resolve(@YES);
630
+    }
631
+    else {
632
+        reject(@"EUNSPECIFIED", [NSString stringWithFormat:@"Error creating folder '%@', error: %@", path, [err description]], nil);
633
+    }
503 634
 }
504 635
 
505 636
 # pragma mark - stat
@@ -571,7 +702,7 @@ NSMutableDictionary *fileStreams = nil;
571 702
     NSData * decodedData = nil;
572 703
     if([[self.encoding lowercaseString] isEqualToString:@"base64"]) {
573 704
         decodedData = [[NSData alloc] initWithBase64EncodedString:chunk options: NSDataBase64DecodingIgnoreUnknownCharacters];
574
-    } 
705
+    }
575 706
     else if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) {
576 707
         decodedData = [chunk dataUsingEncoding:NSUTF8StringEncoding];
577 708
     }
@@ -632,22 +763,28 @@ NSMutableDictionary *fileStreams = nil;
632 763
             NSFileManager * fm = [NSFileManager defaultManager];
633 764
             NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
634 765
             [os open];
635
-            // abort for the source file not exists
636
-            if([fm fileExistsAtPath:path] == NO)
637
-            {
638
-                reject(@"RNFetchBlob slice failed : the file does not exists", path, nil);
639
-                return;
766
+
767
+            BOOL isDir = NO;
768
+            BOOL exists = NO;
769
+            exists = [fm fileExistsAtPath:path isDirectory: &isDir];
770
+
771
+            if (isDir) {
772
+                return reject(@"EISDIR", [NSString stringWithFormat:@"Expecting a file but '%@' is a directory", path], nil);
773
+            }
774
+            if(!exists) {
775
+                return reject(@"ENOENT", [NSString stringWithFormat: @"No such file '%@'", path ], nil);
640 776
             }
777
+
641 778
             long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
642 779
             long max = MIN(size, [end longValue]);
643 780
 
644 781
             if(![fm fileExistsAtPath:dest]) {
645
-                [fm createFileAtPath:dest contents:@"" attributes:nil];
782
+                if(![fm createFileAtPath:dest contents:@"" attributes:nil]) {
783
+                    return reject(@"ENOENT", [NSString stringWithFormat:@"File '%@' does not exist and could not be created", path], nil);
784
+                }
646 785
             }
647 786
             [handle seekToFileOffset:[start longValue]];
648
-            while(read < expected)
649
-            {
650
-
787
+            while(read < expected) {
651 788
                 NSData * chunk;
652 789
                 long chunkSize = 0;
653 790
                 if([start longValue] + read + 10240 > max)
@@ -683,9 +820,7 @@ NSMutableDictionary *fileStreams = nil;
683 820
             long size = asset.size;
684 821
             long max = MIN(size, [end longValue]);
685 822
 
686
-            while(read < expected)
687
-            {
688
-
823
+            while(read < expected) {
689 824
                 uint8_t * chunk[10240];
690 825
                 long chunkSize = 0;
691 826
                 if([start longValue] + read + 10240 > max)
@@ -710,9 +845,8 @@ NSMutableDictionary *fileStreams = nil;
710 845
             [os close];
711 846
             resolve(dest);
712 847
         }
713
-        else
714
-        {
715
-            reject(@"slice error",  [NSString stringWithFormat: @"could not resolve URI %@", path ], nil);
848
+        else {
849
+            reject(@"EINVAL", [NSString stringWithFormat: @"Could not resolve URI %@", path ], nil);
716 850
         }
717 851
 
718 852
     }];
@@ -765,7 +899,7 @@ NSMutableDictionary *fileStreams = nil;
765 899
     if (dictionary) {
766 900
         NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize];
767 901
         NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize];
768
-        
902
+
769 903
         callback(@[[NSNull null], @{
770 904
                   @"free" : freeFileSystemSizeInBytes,
771 905
                   @"total" : fileSystemSizeInBytes,
@@ -793,4 +927,4 @@ NSMutableDictionary *fileStreams = nil;
793 927
     return;
794 928
 }
795 929
 
796
-@end
930
+@end

+ 16
- 28
ios/RNFetchBlobNetwork.h View File

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

+ 68
- 529
ios/RNFetchBlobNetwork.m View File

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

+ 1
- 1
ios/RNFetchBlobReqBuilder.h View File

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

+ 5
- 5
ios/RNFetchBlobReqBuilder.m View File

@@ -118,17 +118,17 @@
118 118
                     if([orgPath hasPrefix:AL_PREFIX])
119 119
                     {
120 120
                         
121
-                        [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString * err) {
121
+                        [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(id content, NSString* code, NSString * err) {
122 122
                             if(err != nil)
123 123
                             {
124 124
                                 onComplete(nil, nil);
125 125
                             }
126 126
                             else
127 127
                             {
128
-                                [request setHTTPBody:content];
128
+                                [request setHTTPBody:((NSData *)content)];
129 129
                                 [request setHTTPMethod: method];
130 130
                                 [request setAllHTTPHeaderFields:mheaders];
131
-                                onComplete(request, [content length]);
131
+                                onComplete(request, [((NSData *)content) length]);
132 132
                             }
133 133
                         }];
134 134
                         
@@ -222,7 +222,7 @@
222 222
                         NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]];
223 223
                         orgPath = [RNFetchBlobFS getPathOfAsset:orgPath];
224 224
 
225
-                        [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString * err) {
225
+                        [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString* code, NSString * err) {
226 226
                             if(err != nil)
227 227
                             {
228 228
                                 onComplete(formData, YES);
@@ -277,7 +277,7 @@
277 277
     }
278 278
 }
279 279
 
280
-+(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableDictionary *) headers {
280
++(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSDictionary *) headers {
281 281
 
282 282
     NSString * normalCase = [headers valueForKey:field];
283 283
     NSString * ignoredCase = [headers valueForKey:[field lowercaseString]];

+ 47
- 0
ios/RNFetchBlobRequest.h View File

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

+ 477
- 0
ios/RNFetchBlobRequest.m View File

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

+ 5
- 2
json-stream.js View File

@@ -2,14 +2,17 @@ import Oboe from './lib/oboe-browser.min.js'
2 2
 import XMLHttpRequest from './polyfill/XMLHttpRequest'
3 3
 import URIUtil from './utils/uri'
4 4
 
5
-const OboeExtended = (arg: string | object) => {
5
+const OboeExtended = (arg: string | Object) => {
6 6
 
7 7
 
8 8
   window.location = ''
9 9
 
10 10
   if(!window.XMLHttpRequest.isRNFBPolyfill ) {
11 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.')
12
+    console.warn(
13
+        'Use JSONStream will automatically replace window.XMLHttpRequest with RNFetchBlob.polyfill.XMLHttpRequest. ' +
14
+        'You are seeing this warning because you did not replace it manually.'
15
+    )
13 16
   }
14 17
 
15 18
   if(typeof arg === 'string') {

+ 2
- 2
polyfill/Fetch.js View File

@@ -73,7 +73,7 @@ class RNFetchBlobFetchPolyfill {
73 73
               // release blob cache created when sending request
74 74
               if(blobCache !== null && blobCache instanceof Blob)
75 75
                 blobCache.close()
76
-              return Promise.resolve(new RNFetchBlobFetchRepsonse(resp))
76
+              return Promise.resolve(new RNFetchBlobFetchResponse(resp))
77 77
             })
78 78
           })
79 79
 
@@ -97,7 +97,7 @@ class RNFetchBlobFetchPolyfill {
97 97
 
98 98
 }
99 99
 
100
-class RNFetchBlobFetchRepsonse {
100
+class RNFetchBlobFetchResponse {
101 101
 
102 102
   constructor(resp:FetchBlobResponse) {
103 103
     let info = resp.info()

+ 3
- 1
polyfill/File.js View File

@@ -2,7 +2,6 @@
2 2
 // Use of this source code is governed by a MIT-style license that can be
3 3
 // found in the LICENSE file.
4 4
 
5
-import fs from '../fs.js'
6 5
 import Blob from './Blob.js'
7 6
 
8 7
 export default class File extends Blob {
@@ -11,6 +10,9 @@ export default class File extends Blob {
11 10
 
12 11
   static build(name:string, data:any, cType:string):Promise<File> {
13 12
     return new Promise((resolve, reject) => {
13
+      if (data === undefined) {
14
+        reject(new TypeError('data is undefined'))
15
+      }
14 16
       new File(data, cType).onCreated((f) => {
15 17
         f.name = name
16 18
         resolve(f)

+ 2
- 2
polyfill/XMLHttpRequest.js View File

@@ -224,8 +224,8 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
224 224
       /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/,
225 225
       /tt/
226 226
     ]
227
-    for(let i in invalidPatterns) {
228
-      if(invalidPatterns[i].test(name) || typeof name !== 'string') {
227
+    for(let pattern of invalidPatterns) {
228
+      if(pattern.test(name) || typeof name !== 'string') {
229 229
         throw `SyntaxError : Invalid header field name ${name}`
230 230
       }
231 231
     }