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
-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
-## 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
 # react-native-fetch-blob
1
 # react-native-fetch-blob
14
 [![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)]()
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
 * [Installation](#user-content-installation)
18
 * [Installation](#user-content-installation)
31
 * [HTTP Data Transfer](#user-content-http-data-transfer)
19
 * [HTTP Data Transfer](#user-content-http-data-transfer)
32
  * [Regular Request](#user-content-regular-request)
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
  * [Upload file](#user-content-upload-example--dropbox-files-upload-api)
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
  * [Upload/Download progress](#user-content-uploaddownload-progress)
24
  * [Upload/Download progress](#user-content-uploaddownload-progress)
37
  * [Cancel HTTP request](#user-content-cancel-request)
25
  * [Cancel HTTP request](#user-content-cancel-request)
38
  * [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support)
26
  * [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support)
611
 - [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs)
599
 - [dirs](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#dirs)
612
 - [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise)
600
 - [createFile](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#createfilepath-data-encodingpromise)
613
 - [writeFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#writefilepathstring-contentstring--array-encodingstring-appendbooleanpromise)
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
 - [readFile (0.6.0)](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#readfilepath-encodingpromise)
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
 - [unlink](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#unlinkpathstringpromise)
608
 - [unlink](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#unlinkpathstringpromise)
619
 - [mkdir](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#mkdirpathstringpromise)
609
 - [mkdir](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#mkdirpathstringpromise)
620
 - [ls](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#lspathstringpromise)
610
 - [ls](https://github.com/wkh237/react-native-fetch-blob/wiki/File-System-Access-API#lspathstringpromise)
657
       console.log('oops', err)
647
       console.log('oops', err)
658
     })
648
     })
659
     ifstream.onEnd(() => {  
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
 When using `writeStream`, the stream object becomes writable, and you can then perform operations like `write` and `close`.
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
 ```js
696
 ```js
668
 RNFetchBlob.fs.writeStream(
697
 RNFetchBlob.fs.writeStream(
669
     PATH_TO_FILE,
698
     PATH_TO_FILE,
672
     // should data append to existing content ?
701
     // should data append to existing content ?
673
     true)
702
     true)
674
 .then((ofstream) => {
703
 .then((ofstream) => {
704
+    // BAD IDEA - Don't do this, those writes are unchecked:
675
     ofstream.write('foo')
705
     ofstream.write('foo')
676
     ofstream.write('bar')
706
     ofstream.write('bar')
677
     ofstream.close()
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
 ### Cache File Management
716
 ### Cache File Management
683
 
717
 
684
 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
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
 ### Development
842
 ### Development
809
 
843
 
810
 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.
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
 
13
 
14
 /**
14
 /**
15
  * Send an intent to open the file.
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
  * @param  {string} mime MIME type string
17
  * @param  {string} mime MIME type string
18
  * @return {Promise}
18
  * @return {Promise}
19
  */
19
  */
38
     return Promise.reject('RNFetchBlob.android.addCompleteDownload only supports Android.')
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
 export default {
56
 export default {
43
   actionViewIntent,
57
   actionViewIntent,
44
   getContentIntent,
58
   getContentIntent,
45
-  addCompleteDownload
59
+  addCompleteDownload,
60
+  getSDCardDir,
61
+  getSDCardApplicationDir,
46
 }
62
 }

+ 1
- 0
android/build.gradle View File

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

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

1
-#Wed May 18 12:33:41 CST 2016
1
+#Fri Jun 01 10:33:07 BRT 2018
2
 distributionBase=GRADLE_USER_HOME
2
 distributionBase=GRADLE_USER_HOME
3
 distributionPath=wrapper/dists
3
 distributionPath=wrapper/dists
4
 zipStoreBase=GRADLE_USER_HOME
4
 zipStoreBase=GRADLE_USER_HOME
5
 zipStorePath=wrapper/dists
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
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
1
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
     package="com.RNFetchBlob">
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
     </application>
35
     </application>
8
 
36
 
9
-</manifest>
37
+</manifest>

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

3
 import android.app.Activity;
3
 import android.app.Activity;
4
 import android.app.DownloadManager;
4
 import android.app.DownloadManager;
5
 import android.content.Intent;
5
 import android.content.Intent;
6
+import android.content.pm.PackageManager;
6
 import android.net.Uri;
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
 import com.facebook.react.bridge.ActivityEventListener;
12
 import com.facebook.react.bridge.ActivityEventListener;
9
 import com.facebook.react.bridge.Callback;
13
 import com.facebook.react.bridge.Callback;
23
 import okhttp3.OkHttpClient;
27
 import okhttp3.OkHttpClient;
24
 import okhttp3.JavaNetCookieJar;
28
 import okhttp3.JavaNetCookieJar;
25
 
29
 
30
+import java.io.File;
26
 import java.util.HashMap;
31
 import java.util.HashMap;
27
 import java.util.Map;
32
 import java.util.Map;
28
 import java.util.concurrent.LinkedBlockingQueue;
33
 import java.util.concurrent.LinkedBlockingQueue;
34
 
39
 
35
 public class RNFetchBlob extends ReactContextBaseJavaModule {
40
 public class RNFetchBlob extends ReactContextBaseJavaModule {
36
 
41
 
37
-    // Cookies
38
-    private final ForwardingCookieHandler mCookieHandler;
39
-    private final CookieJarContainer mCookieJarContainer;
40
     private final OkHttpClient mClient;
42
     private final OkHttpClient mClient;
41
 
43
 
42
     static ReactApplicationContext RCTContext;
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
     static LinkedBlockingQueue<Runnable> fsTaskQueue = new LinkedBlockingQueue<>();
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
     public RNFetchBlob(ReactApplicationContext reactContext) {
52
     public RNFetchBlob(ReactApplicationContext reactContext) {
51
 
53
 
52
         super(reactContext);
54
         super(reactContext);
53
 
55
 
54
         mClient = OkHttpClientProvider.getOkHttpClient();
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
         mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler));
59
         mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler));
58
 
60
 
59
         RCTContext = reactContext;
61
         RCTContext = reactContext;
85
     }
87
     }
86
 
88
 
87
     @ReactMethod
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
         threadPool.execute(new Runnable() {
91
         threadPool.execute(new Runnable() {
90
             @Override
92
             @Override
91
             public void run() {
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
     @ReactMethod
109
     @ReactMethod
99
     public void actionViewIntent(String path, String mime, final Promise promise) {
110
     public void actionViewIntent(String path, String mime, final Promise promise) {
100
         try {
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
             ActionViewVisible = true;
135
             ActionViewVisible = true;
106
 
136
 
107
             final LifecycleEventListener listener = new LifecycleEventListener() {
137
             final LifecycleEventListener listener = new LifecycleEventListener() {
124
             };
154
             };
125
             RCTContext.addLifecycleEventListener(listener);
155
             RCTContext.addLifecycleEventListener(listener);
126
         } catch(Exception ex) {
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
     @ReactMethod
161
     @ReactMethod
143
     public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) {
162
     public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) {
144
         RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
163
         RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
150
     }
169
     }
151
 
170
 
152
     @ReactMethod
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
     @ReactMethod
176
     @ReactMethod
167
                 RNFetchBlobFS.cp(path, dest, callback);
186
                 RNFetchBlobFS.cp(path, dest, callback);
168
             }
187
             }
169
         });
188
         });
170
-
171
     }
189
     }
172
 
190
 
173
     @ReactMethod
191
     @ReactMethod
176
     }
194
     }
177
 
195
 
178
     @ReactMethod
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
     @ReactMethod
201
     @ReactMethod
228
                 RNFetchBlobFS.writeFile(path, encoding, data, append, promise);
246
                 RNFetchBlobFS.writeFile(path, encoding, data, append, promise);
229
             }
247
             }
230
         });
248
         });
231
-
232
     }
249
     }
233
 
250
 
234
     @ReactMethod
251
     @ReactMethod
263
                 new RNFetchBlobFS(ctx).scanFile(p, m, callback);
280
                 new RNFetchBlobFS(ctx).scanFile(p, m, callback);
264
             }
281
             }
265
         });
282
         });
266
-
267
     }
283
     }
268
 
284
 
269
     @ReactMethod
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
      * @param path Stream file path
296
      * @param path Stream file path
272
      * @param encoding Stream encoding, should be one of `base64`, `ascii`, and `utf8`
297
      * @param encoding Stream encoding, should be one of `base64`, `ascii`, and `utf8`
273
      * @param bufferSize Stream buffer size, default to 4096 or 4095(base64).
298
      * @param bufferSize Stream buffer size, default to 4096 or 4095(base64).
274
      */
299
      */
300
+    @ReactMethod
275
     public void readStream(final String path, final String encoding, final int bufferSize, final int tick, final String streamId) {
301
     public void readStream(final String path, final String encoding, final int bufferSize, final int tick, final String streamId) {
276
         final ReactApplicationContext ctx = this.getReactApplicationContext();
302
         final ReactApplicationContext ctx = this.getReactApplicationContext();
277
         fsThreadPool.execute(new Runnable() {
303
         fsThreadPool.execute(new Runnable() {
324
     @ReactMethod
350
     @ReactMethod
325
     public void fetchBlob(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, final Callback callback) {
351
     public void fetchBlob(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, final Callback callback) {
326
         new RNFetchBlobReq(options, taskId, method, url, headers, body, null, mClient, callback).run();
352
         new RNFetchBlobReq(options, taskId, method, url, headers, body, null, mClient, callback).run();
327
-}
353
+    }
328
 
354
 
329
     @ReactMethod
355
     @ReactMethod
330
     public void fetchBlobForm(ReadableMap options, String taskId, String method, String url, ReadableMap headers, ReadableArray body, final Callback callback) {
356
     public void fetchBlobForm(ReadableMap options, String taskId, String method, String url, ReadableMap headers, ReadableArray body, final Callback callback) {
345
 
371
 
346
     @ReactMethod
372
     @ReactMethod
347
     public void addCompleteDownload (ReadableMap config, Promise promise) {
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
         String path = RNFetchBlobFS.normalizePath(config.getString("path"));
375
         String path = RNFetchBlobFS.normalizePath(config.getString("path"));
350
         if(path == null) {
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
             return;
378
             return;
353
         }
379
         }
354
         try {
380
         try {
365
             promise.resolve(null);
391
             promise.resolve(null);
366
         }
392
         }
367
         catch(Exception ex) {
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
 package com.RNFetchBlob;
1
 package com.RNFetchBlob;
2
 
2
 
3
+import android.support.annotation.NonNull;
3
 import android.util.Base64;
4
 import android.util.Base64;
4
 
5
 
5
 import com.facebook.react.bridge.Arguments;
6
 import com.facebook.react.bridge.Arguments;
17
 import java.io.InputStream;
18
 import java.io.InputStream;
18
 import java.util.ArrayList;
19
 import java.util.ArrayList;
19
 
20
 
21
+import android.net.Uri;
20
 import okhttp3.MediaType;
22
 import okhttp3.MediaType;
21
 import okhttp3.RequestBody;
23
 import okhttp3.RequestBody;
22
 import okio.BufferedSink;
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
     int reported = 0;
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
         this.mTaskId = taskId;
40
         this.mTaskId = taskId;
40
     }
41
     }
41
 
42
 
49
         return this;
50
         return this;
50
     }
51
     }
51
 
52
 
52
-    RNFetchBlobBody setRequestType( RNFetchBlobReq.RequestType type) {
53
+    RNFetchBlobBody setRequestType(RNFetchBlobReq.RequestType type) {
53
         this.requestType = type;
54
         this.requestType = type;
54
         return this;
55
         return this;
55
     }
56
     }
68
         try {
69
         try {
69
             switch (requestType) {
70
             switch (requestType) {
70
                 case SingleFile:
71
                 case SingleFile:
71
-                    requestStream = getReuqestStream();
72
+                    requestStream = getRequestStream();
72
                     contentLength = requestStream.available();
73
                     contentLength = requestStream.available();
73
                     break;
74
                     break;
74
                 case AsIs:
75
                 case AsIs:
114
     }
115
     }
115
 
116
 
116
     @Override
117
     @Override
117
-    public void writeTo(BufferedSink sink) {
118
+    public void writeTo(@NonNull BufferedSink sink) {
118
         try {
119
         try {
119
             pipeStreamToSink(requestStream, sink);
120
             pipeStreamToSink(requestStream, sink);
120
         } catch(Exception ex) {
121
         } catch(Exception ex) {
135
         return true;
136
         return true;
136
     }
137
     }
137
 
138
 
138
-    private InputStream getReuqestStream() throws Exception {
139
+    private InputStream getRequestStream() throws Exception {
139
 
140
 
140
         // upload from storage
141
         // upload from storage
141
         if (rawBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
142
         if (rawBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
159
                     throw new Exception("error when getting request stream: " +e.getLocalizedMessage());
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
         // base 64 encoded
171
         // base 64 encoded
164
         else {
172
         else {
186
         ArrayList<FormField> fields = countFormDataLength();
194
         ArrayList<FormField> fields = countFormDataLength();
187
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
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
             String data = field.data;
198
             String data = field.data;
192
             String name = field.name;
199
             String name = field.name;
193
             // skip invalid fields
200
             // skip invalid fields
225
                             RNFetchBlobUtils.emitWarningEvent("Failed to create form data from path :" + orgPath + ", file not exists.");
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
                 // base64 embedded file content
250
                 // base64 embedded file content
230
                 else {
251
                 else {
258
      * @param sink      The request body buffer sink
279
      * @param sink      The request body buffer sink
259
      * @throws IOException
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
         int totalWritten = 0;
284
         int totalWritten = 0;
265
         int read;
285
         int read;
266
         while((read = stream.read(chunk, 0, 10240)) > 0) {
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
         stream.close();
291
         stream.close();
274
     }
292
     }
291
 
309
 
292
     /**
310
     /**
293
      * Compute approximate content length for form data
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
         long total = 0;
315
         long total = 0;
298
         ArrayList<FormField> list = new ArrayList<>();
316
         ArrayList<FormField> list = new ArrayList<>();
299
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
317
         ReactApplicationContext ctx = RNFetchBlob.RCTContext;
300
         for(int i = 0;i < form.size(); i++) {
318
         for(int i = 0;i < form.size(); i++) {
301
             FormField field = new FormField(form.getMap(i));
319
             FormField field = new FormField(form.getMap(i));
302
             list.add(field);
320
             list.add(field);
303
-            String data = field.data;
304
-            if(data == null) {
321
+            if(field.data == null) {
305
                 RNFetchBlobUtils.emitWarningEvent("RNFetchBlob multipart request builder has found a field without `data` property, the field `"+ field.name +"` will be removed implicitly.");
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
             else if (field.filename != null) {
324
             else if (field.filename != null) {
325
+                String data = field.data;
308
                 // upload from storage
326
                 // upload from storage
309
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
327
                 if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
310
                     String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
328
                     String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
324
                         File file = new File(RNFetchBlobFS.normalizePath(orgPath));
342
                         File file = new File(RNFetchBlobFS.normalizePath(orgPath));
325
                         total += file.length();
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
                 // base64 embedded file content
361
                 // base64 embedded file content
329
                 else {
362
                 else {
333
             }
366
             }
334
             // data field
367
             // data field
335
             else {
368
             else {
336
-                total += field.data != null ? field.data.getBytes().length : 0;
369
+                total += field.data.getBytes().length;
337
             }
370
             }
338
         }
371
         }
339
         contentLength = total;
372
         contentLength = total;
346
      */
379
      */
347
     private class FormField {
380
     private class FormField {
348
         public String name;
381
         public String name;
349
-        public String filename;
350
-        public String mime;
382
+        String filename;
383
+        String mime;
351
         public String data;
384
         public String data;
352
 
385
 
353
-        public FormField(ReadableMap rawData) {
386
+        FormField(ReadableMap rawData) {
354
             if(rawData.hasKey("name"))
387
             if(rawData.hasKey("name"))
355
                 name = rawData.getString("name");
388
                 name = rawData.getString("name");
356
             if(rawData.hasKey("filename"))
389
             if(rawData.hasKey("filename"))
368
 
401
 
369
     /**
402
     /**
370
      * Emit progress event
403
      * Emit progress event
371
-     * @param written
404
+     * @param written  Integer
372
      */
405
      */
373
     private void emitUploadProgress(int written) {
406
     private void emitUploadProgress(int written) {
374
         RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId);
407
         RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId);

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

3
 import com.facebook.react.bridge.ReadableArray;
3
 import com.facebook.react.bridge.ReadableArray;
4
 import com.facebook.react.bridge.ReadableMap;
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
     public Boolean fileCache;
8
     public Boolean fileCache;
12
     public String path;
9
     public String path;

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

7
     public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
7
     public static final String EVENT_HTTP_STATE = "RNFetchBlobState";
8
     public static final String EVENT_MESSAGE = "RNFetchBlobMessage";
8
     public static final String EVENT_MESSAGE = "RNFetchBlobMessage";
9
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
9
     public static final String FILE_PREFIX = "RNFetchBlob-file://";
10
+    public static final String CONTENT_PREFIX = "RNFetchBlob-content://";
10
     public static final String FILE_PREFIX_BUNDLE_ASSET = "bundle-assets://";
11
     public static final String FILE_PREFIX_BUNDLE_ASSET = "bundle-assets://";
11
     public static final String FILE_PREFIX_CONTENT = "content://";
12
     public static final String FILE_PREFIX_CONTENT = "content://";
12
     public static final String DATA_ENCODE_URI = "uri";
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
  */
5
  */
6
 public class RNFetchBlobProgressConfig {
6
 public class RNFetchBlobProgressConfig {
7
 
7
 
8
-    public enum ReportType {
8
+    enum ReportType {
9
         Upload,
9
         Upload,
10
         Download
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
     RNFetchBlobProgressConfig(boolean report, int interval, int count, ReportType type) {
20
     RNFetchBlobProgressConfig(boolean report, int interval, int count, ReportType type) {
21
         this.enable = report;
21
         this.enable = report;

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

8
 import android.database.Cursor;
8
 import android.database.Cursor;
9
 import android.net.Uri;
9
 import android.net.Uri;
10
 import android.os.Build;
10
 import android.os.Build;
11
+import android.support.annotation.NonNull;
11
 import android.util.Base64;
12
 import android.util.Base64;
12
 
13
 
13
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
14
 import com.RNFetchBlob.Response.RNFetchBlobDefaultResp;
22
 import com.facebook.react.bridge.WritableArray;
23
 import com.facebook.react.bridge.WritableArray;
23
 import com.facebook.react.bridge.WritableMap;
24
 import com.facebook.react.bridge.WritableMap;
24
 import com.facebook.react.modules.core.DeviceEventManagerModule;
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
 import java.io.File;
32
 import java.io.File;
29
 import java.io.FileOutputStream;
33
 import java.io.FileOutputStream;
37
 import java.nio.charset.CharacterCodingException;
41
 import java.nio.charset.CharacterCodingException;
38
 import java.nio.charset.Charset;
42
 import java.nio.charset.Charset;
39
 import java.nio.charset.CharsetEncoder;
43
 import java.nio.charset.CharsetEncoder;
44
+import java.security.KeyStore;
40
 import java.util.ArrayList;
45
 import java.util.ArrayList;
46
+import java.util.Arrays;
41
 import java.util.List;
47
 import java.util.List;
42
 import java.util.HashMap;
48
 import java.util.HashMap;
43
 
49
 
44
 import java.util.concurrent.TimeUnit;
50
 import java.util.concurrent.TimeUnit;
45
 
51
 
52
+import 	javax.net.ssl.SSLSocketFactory;
53
+
46
 import okhttp3.Call;
54
 import okhttp3.Call;
47
 import okhttp3.ConnectionPool;
55
 import okhttp3.ConnectionPool;
48
 import okhttp3.ConnectionSpec;
56
 import okhttp3.ConnectionSpec;
79
     }
87
     }
80
 
88
 
81
     public static HashMap<String, Call> taskTable = new HashMap<>();
89
     public static HashMap<String, Call> taskTable = new HashMap<>();
90
+    public static HashMap<String, Long> androidDownloadManagerTaskTable = new HashMap<>();
82
     static HashMap<String, RNFetchBlobProgressConfig> progressReport = new HashMap<>();
91
     static HashMap<String, RNFetchBlobProgressConfig> progressReport = new HashMap<>();
83
     static HashMap<String, RNFetchBlobProgressConfig> uploadProgressReport = new HashMap<>();
92
     static HashMap<String, RNFetchBlobProgressConfig> uploadProgressReport = new HashMap<>();
84
     static ConnectionPool pool = new ConnectionPool();
93
     static ConnectionPool pool = new ConnectionPool();
85
 
94
 
86
-    ReactApplicationContext ctx;
87
     RNFetchBlobConfig options;
95
     RNFetchBlobConfig options;
88
     String taskId;
96
     String taskId;
89
     String method;
97
     String method;
135
             call.cancel();
143
             call.cancel();
136
             taskTable.remove(taskId);
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
     @Override
155
     @Override
162
                 }
177
                 }
163
                 // set headers
178
                 // set headers
164
                 ReadableMapKeySetIterator it = headers.keySetIterator();
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
                     req.allowScanningByMediaScanner();
181
                     req.allowScanningByMediaScanner();
167
                 }
182
                 }
168
                 while (it.hasNextKey()) {
183
                 while (it.hasNextKey()) {
172
                 Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
187
                 Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
173
                 DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
188
                 DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
174
                 downloadManagerId = dm.enqueue(req);
189
                 downloadManagerId = dm.enqueue(req);
190
+                androidDownloadManagerTaskTable.put(taskId, Long.valueOf(downloadManagerId));
175
                 appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
191
                 appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
176
                 return;
192
                 return;
177
             }
193
             }
188
                 cacheKey = this.taskId;
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
             if (file.exists()) {
209
             if (file.exists()) {
194
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
210
                 callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
199
         if(this.options.path != null)
215
         if(this.options.path != null)
200
             this.destPath = this.options.path;
216
             this.destPath = this.options.path;
201
         else if(this.options.fileCache)
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
         OkHttpClient.Builder clientBuilder;
221
         OkHttpClient.Builder clientBuilder;
250
                     requestType = RequestType.SingleFile;
266
                     requestType = RequestType.SingleFile;
251
                 }
267
                 }
252
                 if(rawRequestBody != null) {
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
                         requestType = RequestType.SingleFile;
271
                         requestType = RequestType.SingleFile;
255
                     }
272
                     }
256
                     else if (cType.toLowerCase().contains(";base64") || cType.toLowerCase().startsWith("application/octet")) {
273
                     else if (cType.toLowerCase().contains(";base64") || cType.toLowerCase().startsWith("application/octet")) {
321
             // Add request interceptor for upload progress event
338
             // Add request interceptor for upload progress event
322
             clientBuilder.addInterceptor(new Interceptor() {
339
             clientBuilder.addInterceptor(new Interceptor() {
323
                 @Override
340
                 @Override
324
-                public Response intercept(Chain chain) throws IOException {
341
+                public Response intercept(@NonNull Chain chain) throws IOException {
325
                     try {
342
                     try {
326
                         Response originalResponse = chain.proceed(req);
343
                         Response originalResponse = chain.proceed(req);
327
                         ResponseBody extended;
344
                         ResponseBody extended;
383
             call.enqueue(new okhttp3.Callback() {
400
             call.enqueue(new okhttp3.Callback() {
384
 
401
 
385
                 @Override
402
                 @Override
386
-                public void onFailure(Call call, IOException e) {
403
+                public void onFailure(@NonNull Call call, IOException e) {
387
                     cancelTask(taskId);
404
                     cancelTask(taskId);
388
                     if(respInfo == null) {
405
                     if(respInfo == null) {
389
                         respInfo = Arguments.createMap();
406
                         respInfo = Arguments.createMap();
400
                 }
417
                 }
401
 
418
 
402
                 @Override
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
                     ReadableMap notifyConfig = options.addAndroidDownloads;
421
                     ReadableMap notifyConfig = options.addAndroidDownloads;
405
                     // Download manager settings
422
                     // Download manager settings
406
                     if(notifyConfig != null ) {
423
                     if(notifyConfig != null ) {
438
     private void releaseTaskResource() {
455
     private void releaseTaskResource() {
439
         if(taskTable.containsKey(taskId))
456
         if(taskTable.containsKey(taskId))
440
             taskTable.remove(taskId);
457
             taskTable.remove(taskId);
458
+        if(androidDownloadManagerTaskTable.containsKey(taskId))
459
+            androidDownloadManagerTaskTable.remove(taskId);
441
         if(uploadProgressReport.containsKey(taskId))
460
         if(uploadProgressReport.containsKey(taskId))
442
             uploadProgressReport.remove(taskId);
461
             uploadProgressReport.remove(taskId);
443
         if(progressReport.containsKey(taskId))
462
         if(progressReport.containsKey(taskId))
459
                     // For XMLHttpRequest, automatic response data storing strategy, when response
478
                     // For XMLHttpRequest, automatic response data storing strategy, when response
460
                     // data is considered as binary data, write it to file system
479
                     // data is considered as binary data, write it to file system
461
                     if(isBlobResp && options.auto) {
480
                     if(isBlobResp && options.auto) {
462
-                        String dest = RNFetchBlobFS.getTmpPath(ctx, taskId);
481
+                        String dest = RNFetchBlobFS.getTmpPath(taskId);
463
                         InputStream ins = resp.body().byteStream();
482
                         InputStream ins = resp.body().byteStream();
464
                         FileOutputStream os = new FileOutputStream(new File(dest));
483
                         FileOutputStream os = new FileOutputStream(new File(dest));
465
                         int read;
484
                         int read;
466
-                        byte [] buffer = new byte[10240];
485
+                        byte[] buffer = new byte[10240];
467
                         while ((read = ins.read(buffer)) != -1) {
486
                         while ((read = ins.read(buffer)) != -1) {
468
                             os.write(buffer, 0, read);
487
                             os.write(buffer, 0, read);
469
                         }
488
                         }
635
             Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
654
             Context appCtx = RNFetchBlob.RCTContext.getApplicationContext();
636
             long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
655
             long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
637
             if (id == this.downloadManagerId) {
656
             if (id == this.downloadManagerId) {
657
+                releaseTaskResource(); // remove task ID from task map
658
+
638
                 DownloadManager.Query query = new DownloadManager.Query();
659
                 DownloadManager.Query query = new DownloadManager.Query();
639
                 query.setFilterById(downloadManagerId);
660
                 query.setFilterById(downloadManagerId);
640
                 DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
661
                 DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
648
                     // #297 handle failed request
669
                     // #297 handle failed request
649
                     int statusCode = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
670
                     int statusCode = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
650
                     if(statusCode == DownloadManager.STATUS_FAILED) {
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
                         return;
673
                         return;
653
                     }
674
                     }
654
                     String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
675
                     String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
657
                             options.addAndroidDownloads.getString("mime").contains("image")) {
678
                             options.addAndroidDownloads.getString("mime").contains("image")) {
658
                         Uri uri = Uri.parse(contentUri);
679
                         Uri uri = Uri.parse(contentUri);
659
                         Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null);
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
                         if (cursor != null) {
682
                         if (cursor != null) {
663
                             cursor.moveToFirst();
683
                             cursor.moveToFirst();
664
                             filePath = cursor.getString(0);
684
                             filePath = cursor.getString(0);
685
+                            cursor.close();
665
                         }
686
                         }
666
                     }
687
                     }
667
                 }
688
                 }
695
     public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
716
     public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
696
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
717
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
697
             try {
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
                 ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
733
                 ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
701
                         .tlsVersions(TlsVersion.TLS_1_2)
734
                         .tlsVersions(TlsVersion.TLS_1_2)

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

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

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

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

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

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

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

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

+ 2
- 7
class/RNFetchBlobSession.js View File

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

+ 8
- 9
class/RNFetchBlobWriteStream.js View File

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

+ 182
- 157
fs.js View File

2
 // Use of this source code is governed by a MIT-style license that can be
2
 // Use of this source code is governed by a MIT-style license that can be
3
 // found in the LICENSE file.
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
 import RNFetchBlobSession from './class/RNFetchBlobSession'
8
 import RNFetchBlobSession from './class/RNFetchBlobSession'
12
 import RNFetchBlobWriteStream from './class/RNFetchBlobWriteStream'
9
 import RNFetchBlobWriteStream from './class/RNFetchBlobWriteStream'
13
 import RNFetchBlobReadStream from './class/RNFetchBlobReadStream'
10
 import RNFetchBlobReadStream from './class/RNFetchBlobReadStream'
14
 import RNFetchBlobFile from './class/RNFetchBlobFile'
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
 const dirs = {
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
  * @param  {string} name Stream ID
36
  * @param  {string} name Stream ID
40
  * @return {RNFetchBlobSession}
37
  * @return {RNFetchBlobSession}
41
  */
38
  */
42
-function session(name:string):RNFetchBlobSession {
39
+function session(name: string): RNFetchBlobSession {
43
   let s = RNFetchBlobSession.getSession(name)
40
   let s = RNFetchBlobSession.getSession(name)
44
-  if(s)
41
+  if (s)
45
     return new RNFetchBlobSession(name)
42
     return new RNFetchBlobSession(name)
46
   else {
43
   else {
47
     RNFetchBlobSession.setSession(name, [])
44
     RNFetchBlobSession.setSession(name, [])
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
     // path from camera roll
51
     // path from camera roll
55
-    if(/^assets-library\:\/\//.test(path))
52
+    if (/^assets-library\:\/\//.test(path))
56
       return path
53
       return path
57
   }
54
   }
58
   return 'bundle-assets://' + path
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
  * Create write stream to a file.
70
  * Create write stream to a file.
84
  * @param  {string} path Target path of file stream.
71
  * @param  {string} path Target path of file stream.
85
  * @param  {string} encoding Encoding of input data.
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
 function writeStream(
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
   return new Promise((resolve, reject) => {
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
       else
91
       else
103
         resolve(new RNFetchBlobWriteStream(streamId, encoding))
92
         resolve(new RNFetchBlobWriteStream(streamId, encoding))
104
     })
93
     })
110
  * @param  {string} path   The file path.
99
  * @param  {string} path   The file path.
111
  * @param  {string} encoding Data encoding, should be one of `base64`, `utf8`, `ascii`
100
  * @param  {string} encoding Data encoding, should be one of `base64`, `utf8`, `ascii`
112
  * @param  {boolean} bufferSize Size of stream buffer.
101
  * @param  {boolean} bufferSize Size of stream buffer.
102
+ * @param  {number} [tick=10] Interval in milliseconds between reading chunks of data
113
  * @return {RNFetchBlobStream} RNFetchBlobStream stream instance.
103
  * @return {RNFetchBlobStream} RNFetchBlobStream stream instance.
114
  */
104
  */
115
 function readStream(
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
   return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize, tick))
114
   return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize, tick))
122
 }
115
 }
123
 
116
 
126
  * @param  {string} path Path of directory to be created
119
  * @param  {string} path Path of directory to be created
127
  * @return {Promise}
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
  * @param  {string} groupName Name of app group
131
  * @param  {string} groupName Name of app group
145
  * @return {Promise}
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
  * @param  {'base64' | 'utf8' | 'ascii'} encoding Encoding of read stream.
141
  * @param  {'base64' | 'utf8' | 'ascii'} encoding Encoding of read stream.
155
  * @return {Promise<Array<number> | string>}
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
   return RNFetchBlob.readFile(path, encoding)
148
   return RNFetchBlob.readFile(path, encoding)
161
 }
149
 }
162
 
150
 
167
  * @param  {string} encoding Encoding of data (Optional).
155
  * @param  {string} encoding Encoding of data (Optional).
168
  * @return {Promise}
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
     else
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
     else
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
     else
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
     else
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
  * @param  {string} path Target path
200
  * @param  {string} path Target path
207
  * @return {RNFetchBlobFile}
201
  * @return {RNFetchBlobFile}
208
  */
202
  */
209
-function stat(path:string):Promise<RNFetchBlobFile> {
203
+function stat(path: string): Promise<RNFetchBlobFile> {
210
   return new Promise((resolve, reject) => {
204
   return new Promise((resolve, reject) => {
205
+    if (typeof path !== 'string') {
206
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
207
+    }
211
     RNFetchBlob.stat(path, (err, stat) => {
208
     RNFetchBlob.stat(path, (err, stat) => {
212
-      if(err)
209
+      if (err)
213
         reject(new Error(err))
210
         reject(new Error(err))
214
       else {
211
       else {
215
-        if(stat) {
212
+        if (stat) {
216
           stat.size = parseInt(stat.size)
213
           stat.size = parseInt(stat.size)
217
           stat.lastModified = parseInt(stat.lastModified)
214
           stat.lastModified = parseInt(stat.lastModified)
218
         }
215
         }
224
 
221
 
225
 /**
222
 /**
226
  * Android only method, request media scanner to scan the file.
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
  * @return {Promise}
225
  * @return {Promise}
229
  */
226
  */
230
-function scanFile(pairs:any):Promise {
227
+function scanFile(pairs: any): Promise {
231
   return new Promise((resolve, reject) => {
228
   return new Promise((resolve, reject) => {
229
+    if (pairs === undefined) {
230
+      return reject(addCode('EINVAL', new TypeError('Missing argument')))
231
+    }
232
     RNFetchBlob.scanFile(pairs, (err) => {
232
     RNFetchBlob.scanFile(pairs, (err) => {
233
-      if(err)
234
-        reject(new Error(err))
233
+      if (err)
234
+        reject(addCode('EUNSPECIFIED', new Error(err)))
235
       else
235
       else
236
         resolve()
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
   return new Promise((resolve, reject) => {
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
     RNFetchBlob.cp(path, dest, (err, res) => {
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
       else
256
       else
247
         resolve(res)
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
   return new Promise((resolve, reject) => {
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
     RNFetchBlob.mv(path, dest, (err, res) => {
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
       else
270
       else
258
         resolve(res)
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
   return new Promise((resolve, reject) => {
277
   return new Promise((resolve, reject) => {
278
+    if (typeof path !== 'string') {
279
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
280
+    }
265
     RNFetchBlob.lstat(path, (err, stat) => {
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
       else
284
       else
269
         resolve(stat)
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
  * @param  {string}   path:string Path of target file.
299
  * @param  {string}   path:string Path of target file.
288
  * @return {Promise}
300
  * @return {Promise}
289
  */
301
  */
290
-function unlink(path:string):Promise {
302
+function unlink(path: string): Promise {
291
   return new Promise((resolve, reject) => {
303
   return new Promise((resolve, reject) => {
304
+    if (typeof path !== 'string') {
305
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
306
+    }
292
     RNFetchBlob.unlink(path, (err) => {
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
       else
311
       else
297
         resolve()
312
         resolve()
302
 /**
317
 /**
303
  * Check if file exists and if it is a folder.
318
  * Check if file exists and if it is a folder.
304
  * @param  {string} path Path to check
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
   return new Promise((resolve, reject) => {
323
   return new Promise((resolve, reject) => {
324
+    if (typeof path !== 'string') {
325
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
326
+    }
310
     try {
327
     try {
311
       RNFetchBlob.exists(path, (exist) => {
328
       RNFetchBlob.exists(path, (exist) => {
312
         resolve(exist)
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
   let p = Promise.resolve()
343
   let p = Promise.resolve()
323
   let size = 0
344
   let size = 0
345
+
324
   function normalize(num, size) {
346
   function normalize(num, size) {
325
-    if(num < 0)
347
+    if (num < 0)
326
       return Math.max(0, size + num)
348
       return Math.max(0, size + num)
327
-    if(!num && num !== 0)
349
+    if (!num && num !== 0)
328
       return size
350
       return size
329
     return num
351
     return num
330
   }
352
   }
331
-  if(start < 0 || end < 0 || !start || !end) {
353
+
354
+  if (start < 0 || end < 0 || !start || !end) {
332
     p = p.then(() => stat(src))
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
   return p.then(() => RNFetchBlob.slice(src, dest, start, end))
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
   return new Promise((resolve, reject) => {
366
   return new Promise((resolve, reject) => {
367
+    if (typeof path !== 'string') {
368
+      return reject(addCode('EINVAL', new TypeError('Missing argument "path" ')))
369
+    }
346
     try {
370
     try {
347
       RNFetchBlob.exists(path, (exist, isDir) => {
371
       RNFetchBlob.exists(path, (exist, isDir) => {
348
         resolve(isDir)
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
   return new Promise((resolve, reject) => {
382
   return new Promise((resolve, reject) => {
359
     RNFetchBlob.df((err, stat) => {
383
     RNFetchBlob.df((err, stat) => {
360
-      if(err)
361
-        reject(err)
384
+      if (err)
385
+        reject(addCode('EUNSPECIFIED', new Error(err)))
362
       else
386
       else
363
         resolve(stat)
387
         resolve(stat)
364
     })
388
     })
379
   appendFile,
403
   appendFile,
380
   pathForAppGroup,
404
   pathForAppGroup,
381
   readFile,
405
   readFile,
406
+  hash,
382
   exists,
407
   exists,
383
   createFile,
408
   createFile,
384
   isDir,
409
   isDir,

+ 9
- 10
index.js View File

17
   RNFetchBlobResponseInfo
17
   RNFetchBlobResponseInfo
18
 } from './types'
18
 } from './types'
19
 import URIUtil from './utils/uri'
19
 import URIUtil from './utils/uri'
20
-import StatefulPromise from './class/StatefulPromise.js'
20
+//import StatefulPromise from './class/StatefulPromise.js'
21
 import fs from './fs'
21
 import fs from './fs'
22
 import getUUID from './utils/uuid'
22
 import getUUID from './utils/uuid'
23
 import base64 from 'base-64'
23
 import base64 from 'base-64'
79
 }
79
 }
80
 
80
 
81
 function wrap(path:string):string {
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
  * @param  {string} method     Should be one of `get`, `post`, `put`
119
  * @param  {string} method     Should be one of `get`, `post`, `put`
119
  * @param  {string} url        A file URI string
120
  * @param  {string} url        A file URI string
120
  * @param  {string} headers    Arguments of file system API
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
  * @return {Promise}
123
  * @return {Promise}
123
  */
124
  */
124
 function fetchFile(options = {}, method, url, headers = {}, body):Promise {
125
 function fetchFile(options = {}, method, url, headers = {}, body):Promise {
520
     }
521
     }
521
     /**
522
     /**
522
      * Start read stream from cached file
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
      * @return {void}
525
      * @return {void}
526
      */
526
      */
527
-    this.readStream = (encode: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => {
527
+    this.readStream = (encoding: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => {
528
       if(this.type === 'path') {
528
       if(this.type === 'path') {
529
-        return readStream(this.data, encode)
529
+        return readStream(this.data, encoding)
530
       }
530
       }
531
       else {
531
       else {
532
         console.warn('RNFetchblob', 'this response data does not contains any available stream')
532
         console.warn('RNFetchblob', 'this response data does not contains any available stream')
539
      * @param  {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`.
539
      * @param  {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`.
540
      * @return {String}
540
      * @return {String}
541
      */
541
      */
542
-    this.readFile = (encode: 'base64' | 'utf8' | 'ascii') => {
542
+    this.readFile = (encoding: 'base64' | 'utf8' | 'ascii') => {
543
       if(this.type === 'path') {
543
       if(this.type === 'path') {
544
-        encode = encode || 'utf8'
545
-        return readFile(this.data, encode)
544
+        return readFile(this.data, encoding)
546
       }
545
       }
547
       else {
546
       else {
548
         console.warn('RNFetchblob', 'this response does not contains a readable file')
547
         console.warn('RNFetchblob', 'this response does not contains a readable file')

+ 2
- 2
ios.js View File

13
 
13
 
14
 /**
14
 /**
15
  * Open a file using UIDocumentInteractionController
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
  * @param  {string} scheme URI scheme that needs to support, optional
17
  * @param  {string} scheme URI scheme that needs to support, optional
18
  * @return {Promise}
18
  * @return {Promise}
19
  */
19
  */
26
 
26
 
27
 /**
27
 /**
28
  * Preview a file using UIDocumentInteractionController
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
  * @param  {string} scheme URI scheme that needs to support, optional
30
  * @param  {string} scheme URI scheme that needs to support, optional
31
  * @return {Promise}
31
  * @return {Promise}
32
  */
32
  */

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

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

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

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

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

38
 
38
 
39
 + (RCTBridge *)getRCTBridge
39
 + (RCTBridge *)getRCTBridge
40
 {
40
 {
41
-    RCTRootView * rootView = [[UIApplication sharedApplication] keyWindow].rootViewController.view;
41
+    RCTRootView * rootView = (RCTRootView*) [[UIApplication sharedApplication] keyWindow].rootViewController.view;
42
     return rootView.bridge;
42
     return rootView.bridge;
43
 }
43
 }
44
 
44
 
45
++ (BOOL)requiresMainQueueSetup {
46
+    return NO;
47
+}
48
+
45
 RCT_EXPORT_MODULE();
49
 RCT_EXPORT_MODULE();
46
 
50
 
47
 - (id) init {
51
 - (id) init {
96
         // send HTTP request
100
         // send HTTP request
97
         else
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
         // send HTTP request
136
         // send HTTP request
129
         else
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
 #pragma mark - fs.createFile
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
     NSFileManager * fm = [NSFileManager defaultManager];
156
     NSFileManager * fm = [NSFileManager defaultManager];
141
     NSData * fileContent = nil;
157
     NSData * fileContent = nil;
142
 
158
 
154
         fileContent = [[NSData alloc] initWithData:[data dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];
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
 #pragma mark - fs.createFileASCII
185
 #pragma mark - fs.createFileASCII
166
 // method for create file with ASCII content
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
     NSFileManager * fm = [NSFileManager defaultManager];
192
     NSFileManager * fm = [NSFileManager defaultManager];
170
     NSMutableData * fileContent = [NSMutableData alloc];
193
     NSMutableData * fileContent = [NSMutableData alloc];
171
     // prevent stack overflow, alloc on heap
194
     // prevent stack overflow, alloc on heap
174
     for(int i = 0; i < dataArray.count; i++) {
197
     for(int i = 0; i < dataArray.count; i++) {
175
         bytes[i] = [[dataArray objectAtIndex:i] charValue];
198
         bytes[i] = [[dataArray objectAtIndex:i] charValue];
176
     }
199
     }
200
+
177
     [fileContent appendBytes:bytes length:dataArray.count];
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
 #pragma mark - fs.pathForAppGroup
217
 #pragma mark - fs.pathForAppGroup
194
     if(path) {
224
     if(path) {
195
         resolve(path);
225
         resolve(path);
196
     } else {
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
 {
250
 {
221
     RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
251
     RNFetchBlobFS * fileStream = [[RNFetchBlobFS alloc] initWithBridgeRef:self.bridge];
222
     NSFileManager * fm = [NSFileManager defaultManager];
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
     NSString * streamId = [fileStream openWithPath:path encode:encoding appendData:append];
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
 #pragma mark - fs.writeArrayChunk
275
 #pragma mark - fs.writeArrayChunk
291
 }
333
 }
292
 
334
 
293
 #pragma mark - fs.ls
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
     NSFileManager* fm = [NSFileManager defaultManager];
338
     NSFileManager* fm = [NSFileManager defaultManager];
297
     BOOL exist = nil;
339
     BOOL exist = nil;
298
     BOOL isDir = nil;
340
     BOOL isDir = nil;
299
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
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
     NSError * error = nil;
348
     NSError * error = nil;
305
     NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
349
     NSArray * result = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
306
 
350
 
307
     if(error == nil)
351
     if(error == nil)
308
-        callback(@[[NSNull null], result == nil ? [NSNull null] :result ]);
352
+        resolve(result);
309
     else
353
     else
310
-        callback(@[[error localizedDescription], [NSNull null]]);
311
-
354
+        reject(@"EUNSPECIFIED", [error description], nil);
312
 }
355
 }
313
 
356
 
314
 #pragma mark - fs.stat
357
 #pragma mark - fs.stat
326
 
369
 
327
             exist = [fm fileExistsAtPath:path isDirectory:&isDir];
370
             exist = [fm fileExistsAtPath:path isDirectory:&isDir];
328
             if(exist == NO) {
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
                 return ;
373
                 return ;
331
             }
374
             }
332
             result = [RNFetchBlobFS stat:path error:&error];
375
             result = [RNFetchBlobFS stat:path error:&error];
362
 
405
 
363
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
406
     exist = [fm fileExistsAtPath:path isDirectory:&isDir];
364
     if(exist == NO) {
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
         return ;
409
         return ;
367
     }
410
     }
368
     NSError * error = nil;
411
     NSError * error = nil;
389
 #pragma mark - fs.cp
432
 #pragma mark - fs.cp
390
 RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback)
433
 RCT_EXPORT_METHOD(cp:(NSString*)src toPath:(NSString *)dest callback:(RCTResponseSenderBlock) callback)
391
 {
434
 {
392
-
393
 //    path = [RNFetchBlobFS getPathOfAsset:path];
435
 //    path = [RNFetchBlobFS getPathOfAsset:path];
394
     [RNFetchBlobFS getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
436
     [RNFetchBlobFS getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
395
         NSError * error = nil;
437
         NSError * error = nil;
400
         }
442
         }
401
         else
443
         else
402
         {
444
         {
445
+            // If the destination exists there will be an error
403
             BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
446
             BOOL result = [[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:path] toURL:[NSURL fileURLWithPath:dest] error:&error];
404
 
447
 
405
             if(error == nil)
448
             if(error == nil)
408
                 callback(@[[error localizedDescription], @NO]);
451
                 callback(@[[error localizedDescription], @NO]);
409
         }
452
         }
410
     }];
453
     }];
411
-
412
 }
454
 }
413
 
455
 
414
 
456
 
426
 }
468
 }
427
 
469
 
428
 #pragma mark - fs.mkdir
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
 #pragma mark - fs.readFile
476
 #pragma mark - fs.readFile
443
                   resolver:(RCTPromiseResolveBlock)resolve
479
                   resolver:(RCTPromiseResolveBlock)resolve
444
                   rejecter:(RCTPromiseRejectBlock)reject)
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
             return;
486
             return;
452
         }
487
         }
453
-        if(encoding == @"ascii")
454
-        {
488
+        if(encoding == @"ascii") {
455
             resolve((NSMutableArray *)content);
489
             resolve((NSMutableArray *)content);
456
         }
490
         }
457
-        else
458
-        {
491
+        else {
459
             resolve((NSString *)content);
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
 #pragma mark - fs.readStream
506
 #pragma mark - fs.readStream
465
 RCT_EXPORT_METHOD(readStream:(NSString *)path withEncoding:(NSString *)encoding bufferSize:(int)bufferSize tick:(int)tick streamId:(NSString *)streamId)
507
 RCT_EXPORT_METHOD(readStream:(NSString *)path withEncoding:(NSString *)encoding bufferSize:(int)bufferSize tick:(int)tick streamId:(NSString *)streamId)
466
 {
508
 {
488
 
530
 
489
 #pragma mark - net.cancelRequest
531
 #pragma mark - net.cancelRequest
490
 RCT_EXPORT_METHOD(cancelRequest:(NSString *)taskId callback:(RCTResponseSenderBlock)callback) {
532
 RCT_EXPORT_METHOD(cancelRequest:(NSString *)taskId callback:(RCTResponseSenderBlock)callback) {
491
-    [RNFetchBlobNetwork cancelRequest:taskId];
533
+    [[RNFetchBlobNetwork sharedInstance] cancelRequest:taskId];
492
     callback(@[[NSNull null], taskId]);
534
     callback(@[[NSNull null], taskId]);
493
 
535
 
494
 }
536
 }
498
 {
540
 {
499
 
541
 
500
     RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Download interval:interval count:count];
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
 #pragma mark - net.enableUploadProgressReport
546
 #pragma mark - net.enableUploadProgressReport
505
 RCT_EXPORT_METHOD(enableUploadProgressReport:(NSString *)taskId interval:(nonnull NSNumber*)interval count:(nonnull NSNumber*)count)
547
 RCT_EXPORT_METHOD(enableUploadProgressReport:(NSString *)taskId interval:(nonnull NSNumber*)interval count:(nonnull NSNumber*)count)
506
 {
548
 {
507
     RNFetchBlobProgress * cfg = [[RNFetchBlobProgress alloc] initWithType:Upload interval:interval count:count];
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
 #pragma mark - fs.slice
553
 #pragma mark - fs.slice
529
       });
571
       });
530
         resolve(@[[NSNull null]]);
572
         resolve(@[[NSNull null]]);
531
     } else {
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
 
587
 
546
     if(scheme == nil || [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:scheme]]) {
588
     if(scheme == nil || [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:scheme]]) {
547
         dispatch_sync(dispatch_get_main_queue(), ^{
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
     } else {
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
     {
608
     {
564
         resolve(@[[NSNull null]]);
609
         resolve(@[[NSNull null]]);
565
     } else {
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
 //
7
 //
8
 #import "RNFetchBlobConst.h"
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
 // fetch configs
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
 // response type
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
     NSString * streamId;
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
 @property (strong, nonatomic) RCTResponseSenderBlock callback;
39
 @property (strong, nonatomic) RCTResponseSenderBlock callback;
40
 @property (nonatomic) RCTBridge * bridge;
40
 @property (nonatomic) RCTBridge * bridge;
41
 @property (nonatomic) NSString * encoding;
41
 @property (nonatomic) NSString * encoding;
58
 // fs methods
58
 // fs methods
59
 + (RNFetchBlobFS *) getFileStreams;
59
 + (RNFetchBlobFS *) getFileStreams;
60
 + (BOOL) mkdir:(NSString *) path;
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
 + (NSDictionary *) stat:(NSString *) path error:(NSError **) error;
66
 + (NSDictionary *) stat:(NSString *) path error:(NSError **) error;
62
 + (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback;
67
 + (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback;
63
 + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
68
 + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
64
 + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
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
 + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
71
 + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock;
67
 + (void) slice:(NSString *)path
72
 + (void) slice:(NSString *)path
68
          dest:(NSString *)dest
73
          dest:(NSString *)dest

+ 190
- 56
ios/RNFetchBlobFS.m View File

13
 #import "IOS7Polyfill.h"
13
 #import "IOS7Polyfill.h"
14
 @import AssetsLibrary;
14
 @import AssetsLibrary;
15
 
15
 
16
+#import <CommonCrypto/CommonDigest.h>
17
+
16
 #if __has_include(<React/RCTAssert.h>)
18
 #if __has_include(<React/RCTAssert.h>)
17
 #import <React/RCTBridge.h>
19
 #import <React/RCTBridge.h>
18
 #import <React/RCTEventDispatcher.h>
20
 #import <React/RCTEventDispatcher.h>
163
             {
165
             {
164
                 if([[NSFileManager defaultManager] fileExistsAtPath:path] == NO)
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
                     [event sendDeviceEventWithName:streamId body:payload];
170
                     [event sendDeviceEventWithName:streamId body:payload];
169
                     free(buffer);
171
                     free(buffer);
170
                     return ;
172
                     return ;
197
             }
199
             }
198
             else
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
                 [event sendDeviceEventWithName:streamId body:payload];
203
                 [event sendDeviceEventWithName:streamId body:payload];
202
             }
204
             }
203
             // release buffer
205
             // release buffer
207
         }
209
         }
208
         @catch (NSError * err)
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
             [event sendDeviceEventWithName:streamId body:payload];
213
             [event sendDeviceEventWithName:streamId body:payload];
212
         }
214
         }
213
         @finally
215
         @finally
254
                     [asciiArray addObject:[NSNumber numberWithChar:bytePtr[i]]];
256
                     [asciiArray addObject:[NSNumber numberWithChar:bytePtr[i]]];
255
                 }
257
                 }
256
             }
258
             }
257
-            
259
+
258
             NSDictionary * payload = @{ @"event": FS_EVENT_DATA,  @"detail" : asciiArray };
260
             NSDictionary * payload = @{ @"event": FS_EVENT_DATA,  @"detail" : asciiArray };
259
             [event sendDeviceEventWithName:streamId body:payload];
261
             [event sendDeviceEventWithName:streamId body:payload];
260
         }
262
         }
261
-        
263
+
262
     }
264
     }
263
     @catch (NSException * ex)
265
     @catch (NSException * ex)
264
     {
266
     {
330
 
332
 
331
 # pragma mark - write file
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
     @try {
342
     @try {
336
         NSFileManager * fm = [NSFileManager defaultManager];
343
         NSFileManager * fm = [NSFileManager defaultManager];
337
         NSError * err = nil;
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
         // after the folders created, write data into the file
346
         // after the folders created, write data into the file
340
         NSString * folder = [path stringByDeletingLastPathComponent];
347
         NSString * folder = [path stringByDeletingLastPathComponent];
341
         encoding = [encoding lowercaseString];
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
         NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
368
         NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
351
         NSData * content = nil;
369
         NSData * content = nil;
352
         if([encoding RNFBContainsString:@"base64"]) {
370
         if([encoding RNFBContainsString:@"base64"]) {
355
         else if([encoding isEqualToString:@"uri"]) {
373
         else if([encoding isEqualToString:@"uri"]) {
356
             NSNumber* size = [[self class] writeFileFromFile:data toFile:path append:append callback:^(NSString *errMsg, NSNumber *size) {
374
             NSNumber* size = [[self class] writeFileFromFile:data toFile:path append:append callback:^(NSString *errMsg, NSNumber *size) {
357
                 if(errMsg != nil)
375
                 if(errMsg != nil)
358
-                    reject(@"RNFetchBlob writeFile Error", errMsg, nil);
376
+                    reject(@"EUNSPECIFIED", errMsg, nil);
359
                 else
377
                 else
360
                     resolve(size);
378
                     resolve(size);
361
             }];
379
             }];
378
     }
396
     }
379
     @catch (NSException * e)
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
 # pragma mark - write file (array)
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
     @try {
411
     @try {
389
         NSFileManager * fm = [NSFileManager defaultManager];
412
         NSFileManager * fm = [NSFileManager defaultManager];
390
         NSError * err = nil;
413
         NSError * err = nil;
391
         // check if the folder exists, if not exists, create folders recursively
414
         // check if the folder exists, if not exists, create folders recursively
392
         // after the folders created, write data into the file
415
         // after the folders created, write data into the file
393
         NSString * folder = [path stringByDeletingLastPathComponent];
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
             [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err];
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
         NSMutableData * fileContent = [NSMutableData alloc];
433
         NSMutableData * fileContent = [NSMutableData alloc];
398
         // prevent stack overflow, alloc on heap
434
         // prevent stack overflow, alloc on heap
399
         char * bytes = (char*) malloc([data count]);
435
         char * bytes = (char*) malloc([data count]);
401
             bytes[i] = [[data objectAtIndex:i] charValue];
437
             bytes[i] = [[data objectAtIndex:i] charValue];
402
         }
438
         }
403
         [fileContent appendBytes:bytes length:data.count];
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
         // if file exists, write file
446
         // if file exists, write file
408
         else {
447
         else {
422
     }
461
     }
423
     @catch (NSException * e)
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
 
469
 
431
 + (void) readFile:(NSString *)path
470
 + (void) readFile:(NSString *)path
432
          encoding:(NSString *)encoding
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
     [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
474
     [[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
436
         __block NSData * fileContent;
475
         __block NSData * fileContent;
442
             [asset getBytes:buffer fromOffset:0 length:asset.size error:&err];
481
             [asset getBytes:buffer fromOffset:0 length:asset.size error:&err];
443
             if(err != nil)
482
             if(err != nil)
444
             {
483
             {
445
-                onComplete(nil, [err description]);
484
+                onComplete(nil, @"EUNSPECIFIED", [err description]);
446
                 free(buffer);
485
                 free(buffer);
447
                 return;
486
                 return;
448
             }
487
             }
451
         }
490
         }
452
         else
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
                 return;
500
                 return;
457
             }
501
             }
458
             fileContent = [NSData dataWithContentsOfFile:path];
502
             fileContent = [NSData dataWithContentsOfFile:path];
459
-            
503
+
460
         }
504
         }
461
-        
505
+
462
         if(encoding != nil)
506
         if(encoding != nil)
463
         {
507
         {
464
             if([[encoding lowercaseString] isEqualToString:@"utf8"])
508
             if([[encoding lowercaseString] isEqualToString:@"utf8"])
465
             {
509
             {
466
                 NSString * utf8 = [[NSString alloc] initWithData:fileContent encoding:NSUTF8StringEncoding];
510
                 NSString * utf8 = [[NSString alloc] initWithData:fileContent encoding:NSUTF8StringEncoding];
467
                 if(utf8 == nil)
511
                 if(utf8 == nil)
468
-                    onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil);
512
+                    onComplete([[NSString alloc] initWithData:fileContent encoding:NSISOLatin1StringEncoding], nil, nil);
469
                 else
513
                 else
470
-                    onComplete(utf8, nil);
514
+                    onComplete(utf8, nil, nil);
471
             }
515
             }
472
             else if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
516
             else if ([[encoding lowercaseString] isEqualToString:@"base64"]) {
473
-                onComplete([fileContent base64EncodedStringWithOptions:0], nil);
517
+                onComplete([fileContent base64EncodedStringWithOptions:0], nil, nil);
474
             }
518
             }
475
             else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) {
519
             else if ([[encoding lowercaseString] isEqualToString:@"ascii"]) {
476
                 NSMutableArray * resultArray = [NSMutableArray array];
520
                 NSMutableArray * resultArray = [NSMutableArray array];
478
                 for(int i=0;i<[fileContent length];i++) {
522
                 for(int i=0;i<[fileContent length];i++) {
479
                     [resultArray addObject:[NSNumber numberWithChar:bytes[i]]];
523
                     [resultArray addObject:[NSNumber numberWithChar:bytes[i]]];
480
                 }
524
                 }
481
-                onComplete(resultArray, nil);
525
+                onComplete(resultArray, nil, nil);
482
             }
526
             }
483
         }
527
         }
484
         else
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
 # pragma mark - mkdir
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
     NSError * err = nil;
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
         [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&err];
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
 # pragma mark - stat
636
 # pragma mark - stat
571
     NSData * decodedData = nil;
702
     NSData * decodedData = nil;
572
     if([[self.encoding lowercaseString] isEqualToString:@"base64"]) {
703
     if([[self.encoding lowercaseString] isEqualToString:@"base64"]) {
573
         decodedData = [[NSData alloc] initWithBase64EncodedString:chunk options: NSDataBase64DecodingIgnoreUnknownCharacters];
704
         decodedData = [[NSData alloc] initWithBase64EncodedString:chunk options: NSDataBase64DecodingIgnoreUnknownCharacters];
574
-    } 
705
+    }
575
     else if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) {
706
     else if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) {
576
         decodedData = [chunk dataUsingEncoding:NSUTF8StringEncoding];
707
         decodedData = [chunk dataUsingEncoding:NSUTF8StringEncoding];
577
     }
708
     }
632
             NSFileManager * fm = [NSFileManager defaultManager];
763
             NSFileManager * fm = [NSFileManager defaultManager];
633
             NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
764
             NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
634
             [os open];
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
             long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
778
             long size = [fm attributesOfItemAtPath:path error:nil].fileSize;
642
             long max = MIN(size, [end longValue]);
779
             long max = MIN(size, [end longValue]);
643
 
780
 
644
             if(![fm fileExistsAtPath:dest]) {
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
             [handle seekToFileOffset:[start longValue]];
786
             [handle seekToFileOffset:[start longValue]];
648
-            while(read < expected)
649
-            {
650
-
787
+            while(read < expected) {
651
                 NSData * chunk;
788
                 NSData * chunk;
652
                 long chunkSize = 0;
789
                 long chunkSize = 0;
653
                 if([start longValue] + read + 10240 > max)
790
                 if([start longValue] + read + 10240 > max)
683
             long size = asset.size;
820
             long size = asset.size;
684
             long max = MIN(size, [end longValue]);
821
             long max = MIN(size, [end longValue]);
685
 
822
 
686
-            while(read < expected)
687
-            {
688
-
823
+            while(read < expected) {
689
                 uint8_t * chunk[10240];
824
                 uint8_t * chunk[10240];
690
                 long chunkSize = 0;
825
                 long chunkSize = 0;
691
                 if([start longValue] + read + 10240 > max)
826
                 if([start longValue] + read + 10240 > max)
710
             [os close];
845
             [os close];
711
             resolve(dest);
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
     if (dictionary) {
899
     if (dictionary) {
766
         NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize];
900
         NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize];
767
         NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize];
901
         NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize];
768
-        
902
+
769
         callback(@[[NSNull null], @{
903
         callback(@[[NSNull null], @{
770
                   @"free" : freeFileSystemSizeInBytes,
904
                   @"free" : freeFileSystemSizeInBytes,
771
                   @"total" : fileSystemSizeInBytes,
905
                   @"total" : fileSystemSizeInBytes,
793
     return;
927
     return;
794
 }
928
 }
795
 
929
 
796
-@end
930
+@end

+ 16
- 28
ios/RNFetchBlobNetwork.h View File

6
 //  Copyright © 2016 wkh237. All rights reserved.
6
 //  Copyright © 2016 wkh237. All rights reserved.
7
 //
7
 //
8
 
8
 
9
+#ifndef RNFetchBlobNetwork_h
10
+#define RNFetchBlobNetwork_h
11
+
9
 #import <Foundation/Foundation.h>
12
 #import <Foundation/Foundation.h>
10
 #import "RNFetchBlobProgress.h"
13
 #import "RNFetchBlobProgress.h"
11
 #import "RNFetchBlobFS.h"
14
 #import "RNFetchBlobFS.h"
15
+#import "RNFetchBlobRequest.h"
12
 
16
 
13
 #if __has_include(<React/RCTAssert.h>)
17
 #if __has_include(<React/RCTAssert.h>)
14
 #import <React/RCTBridgeModule.h>
18
 #import <React/RCTBridgeModule.h>
16
 #import "RCTBridgeModule.h"
20
 #import "RCTBridgeModule.h"
17
 #endif
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
 @interface RNFetchBlobNetwork : NSObject  <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
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
 + (NSMutableDictionary  * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers;
30
 + (NSMutableDictionary  * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers;
44
-+ (void) cancelRequest:(NSString *)taskId;
45
-+ (void) enableProgressReport:(NSString *) taskId;
46
-+ (void) enableUploadProgress:(NSString *) taskId;
47
 + (void) emitExpiredTasks;
31
 + (void) emitExpiredTasks;
48
 
32
 
49
 - (nullable id) init;
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
 @end
45
 @end

+ 68
- 529
ios/RNFetchBlobNetwork.m View File

8
 
8
 
9
 
9
 
10
 #import <Foundation/Foundation.h>
10
 #import <Foundation/Foundation.h>
11
-#import "RNFetchBlob.h"
12
-#import "RNFetchBlobFS.h"
13
 #import "RNFetchBlobNetwork.h"
11
 #import "RNFetchBlobNetwork.h"
12
+
13
+#import "RNFetchBlob.h"
14
 #import "RNFetchBlobConst.h"
14
 #import "RNFetchBlobConst.h"
15
-#import "RNFetchBlobReqBuilder.h"
16
-#import "IOS7Polyfill.h"
17
-#import <CommonCrypto/CommonDigest.h>
18
 #import "RNFetchBlobProgress.h"
15
 #import "RNFetchBlobProgress.h"
19
 
16
 
20
 #if __has_include(<React/RCTAssert.h>)
17
 #if __has_include(<React/RCTAssert.h>)
35
 //
32
 //
36
 ////////////////////////////////////////
33
 ////////////////////////////////////////
37
 
34
 
38
-NSMapTable * taskTable;
39
 NSMapTable * expirationTable;
35
 NSMapTable * expirationTable;
40
-NSMutableDictionary * progressTable;
41
-NSMutableDictionary * uploadProgressTable;
42
 
36
 
43
 __attribute__((constructor))
37
 __attribute__((constructor))
44
 static void initialize_tables() {
38
 static void initialize_tables() {
45
-    if(expirationTable == nil)
46
-    {
39
+    if (expirationTable == nil) {
47
         expirationTable = [[NSMapTable alloc] init];
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
 @implementation RNFetchBlobNetwork
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
 - (id)init {
48
 - (id)init {
107
     self = [super init];
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
     return self;
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
 - (void) sendRequest:(__weak NSDictionary  * _Nullable )options
72
 - (void) sendRequest:(__weak NSDictionary  * _Nullable )options
159
        contentLength:(long) contentLength
73
        contentLength:(long) contentLength
160
               bridge:(RCTBridge * _Nullable)bridgeRef
74
               bridge:(RCTBridge * _Nullable)bridgeRef
162
          withRequest:(__weak NSURLRequest * _Nullable)req
76
          withRequest:(__weak NSURLRequest * _Nullable)req
163
             callback:(_Nullable RCTResponseSenderBlock) callback
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
         [task cancel];
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
                      body:(NSString *)body
29
                      body:(NSString *)body
30
                onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete;
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
 @end
35
 @end

+ 5
- 5
ios/RNFetchBlobReqBuilder.m View File

118
                     if([orgPath hasPrefix:AL_PREFIX])
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
                             if(err != nil)
122
                             if(err != nil)
123
                             {
123
                             {
124
                                 onComplete(nil, nil);
124
                                 onComplete(nil, nil);
125
                             }
125
                             }
126
                             else
126
                             else
127
                             {
127
                             {
128
-                                [request setHTTPBody:content];
128
+                                [request setHTTPBody:((NSData *)content)];
129
                                 [request setHTTPMethod: method];
129
                                 [request setHTTPMethod: method];
130
                                 [request setAllHTTPHeaderFields:mheaders];
130
                                 [request setAllHTTPHeaderFields:mheaders];
131
-                                onComplete(request, [content length]);
131
+                                onComplete(request, [((NSData *)content) length]);
132
                             }
132
                             }
133
                         }];
133
                         }];
134
                         
134
                         
222
                         NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]];
222
                         NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]];
223
                         orgPath = [RNFetchBlobFS getPathOfAsset:orgPath];
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
                             if(err != nil)
226
                             if(err != nil)
227
                             {
227
                             {
228
                                 onComplete(formData, YES);
228
                                 onComplete(formData, YES);
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
     NSString * normalCase = [headers valueForKey:field];
282
     NSString * normalCase = [headers valueForKey:field];
283
     NSString * ignoredCase = [headers valueForKey:[field lowercaseString]];
283
     NSString * ignoredCase = [headers valueForKey:[field lowercaseString]];

+ 47
- 0
ios/RNFetchBlobRequest.h View File

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

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
 import XMLHttpRequest from './polyfill/XMLHttpRequest'
2
 import XMLHttpRequest from './polyfill/XMLHttpRequest'
3
 import URIUtil from './utils/uri'
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
   window.location = ''
8
   window.location = ''
9
 
9
 
10
   if(!window.XMLHttpRequest.isRNFBPolyfill ) {
10
   if(!window.XMLHttpRequest.isRNFBPolyfill ) {
11
     window.XMLHttpRequest = XMLHttpRequest
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
   if(typeof arg === 'string') {
18
   if(typeof arg === 'string') {

+ 2
- 2
polyfill/Fetch.js View File

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

+ 3
- 1
polyfill/File.js View File

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

+ 2
- 2
polyfill/XMLHttpRequest.js View File

224
       /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/,
224
       /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/,
225
       /tt/
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
         throw `SyntaxError : Invalid header field name ${name}`
229
         throw `SyntaxError : Invalid header field name ${name}`
230
       }
230
       }
231
     }
231
     }