From 3bbe72daac06b4af75230918bbc78e91a1354f8b Mon Sep 17 00:00:00 2001 From: Cristiano Coelho Date: Thu, 28 Nov 2019 17:59:17 -0300 Subject: [PATCH 1/7] wifi only option, and missing typings. --- README.md | 46 +++++++++++---- .../com/RNFetchBlob/RNFetchBlobConfig.java | 2 + .../java/com/RNFetchBlob/RNFetchBlobReq.java | 59 +++++++++++++++---- index.d.ts | 10 ++++ index.js | 6 ++ index.js.flow | 4 +- ios/RNFetchBlobConst.h | 1 + ios/RNFetchBlobConst.m | 1 + ios/RNFetchBlobRequest.m | 4 ++ types.js | 5 +- 10 files changed, 112 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index f2d547667..b5ca84c45 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ rn-fetch-blob version 0.10.16 is only compatible with react native 0.60 and up. ## About -This project was started in the cause of solving issue [facebook/react-native#854](https://github.com/facebook/react-native/issues/854), React Native's lacks of `Blob` implementation which results into problems when transferring binary data. +This project was started in the cause of solving issue [facebook/react-native#854](https://github.com/facebook/react-native/issues/854), React Native's lacks of `Blob` implementation which results into problems when transferring binary data. It is committed to making file access and transfer easier and more efficient for React Native developers. We've implemented highly customizable filesystem and network module which plays well together. For example, developers can upload and download data directly from/to storage, which is more efficient, especially for large files. The file system supports file stream, so you don't have to worry about OOM problem when accessing large files. @@ -116,8 +116,8 @@ If you're going to access external storage (say, SD card storage) for `Android 5 -+ -+ ++ ++ + ... @@ -129,10 +129,18 @@ Also, if you're going to use `Android Download Manager` you have to add this to -+ ++ ``` +If you are going to use the `wifiOnly` flag, you need to add this to `AndroidManifest.xml` + +```diff ++ + ... + +``` + **Grant Access Permission for Android 6.0** Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. So adding permissions in `AndroidManifest.xml` won't work for Android 6.0+ devices. To grant permissions in runtime, you might use [PermissionAndroid API](https://facebook.github.io/react-native/docs/permissionsandroid.html). @@ -168,7 +176,7 @@ To sum up: - To send a form data, the `Content-Type` header does not matter. When the body is an `Array` we will set proper content type for you. - To send binary data, you have two choices, use BASE64 encoded string or path points to a file contains the body. - - If the `Content-Type` containing substring`;BASE64` or `application/octet` the given body will be considered as a BASE64 encoded data which will be decoded to binary data as the request body. + - If the `Content-Type` containing substring`;BASE64` or `application/octet` the given body will be considered as a BASE64 encoded data which will be decoded to binary data as the request body. - Otherwise, if a string starts with `RNFetchBlob-file://` (which can simply be done by `RNFetchBlob.wrap(PATH_TO_THE_FILE)`), it will try to find the data from the URI string after `RNFetchBlob-file://` and use it as the request body. - To send the body as-is, simply use a `Content-Type` header not containing `;BASE64` or `application/octet`. @@ -189,7 +197,7 @@ RNFetchBlob.fetch('GET', 'http://www.example.com/images/img1.png', { }) .then((res) => { let status = res.info().status; - + if(status == 200) { // the conversion is done in native code let base64Str = res.base64() @@ -290,7 +298,7 @@ RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', { 'Content-Type' : 'application/octet-stream', // here's the body you're going to send, should be a BASE64 encoded string // (you can use "base64"(refer to the library 'mathiasbynens/base64') APIs to make one). - // The data will be converted to "byte array"(say, blob) before request sent. + // The data will be converted to "byte array"(say, blob) before request sent. }, base64ImageString) .then((res) => { console.log(res.text()) @@ -648,7 +656,7 @@ RNFetchBlob.fs.readStream( ifstream.onError((err) => { console.log('oops', err) }) - ifstream.onEnd(() => { + ifstream.onEnd(() => { { // set session of a response res.session('foo') - }) + }) RNFetchblob.config({ // you can also set session beforehand @@ -759,7 +767,7 @@ You can also group requests by using `session` API and use `dispose` to remove t .fetch('GET', 'http://example.com/download/file') .then((res) => { // ... - }) + }) // or put an existing file path to the session RNFetchBlob.session('foo').add('some-file-path') @@ -794,6 +802,22 @@ RNFetchBlob.config({ }) ``` +### WiFi only requests + +If you wish to only route requests through the Wifi interface, set the below configuration. +Note: On Android, the `ACCESS_NETWORK_STATE` permission must be set, and this flag will only work +on API version 21 (Lollipop, Android 5.0) or above. APIs below 21 will ignore this flag. + +```js +RNFetchBlob.config({ + wifiOnly : true +}) +.fetch('GET', 'https://mysite.com') +.then((resp) => { + // ... +}) +``` + ## Web API Polyfills After `0.8.0` we've made some [Web API polyfills](https://github.com/joltup/rn-fetch-blob/wiki/Web-API-Polyfills-(experimental)) that makes some browser-based library available in RN. diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java index a5c68b689..8ac9e7a85 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java @@ -10,6 +10,7 @@ class RNFetchBlobConfig { public String appendExt; public ReadableMap addAndroidDownloads; public Boolean trusty; + public Boolean wifiOnly = false; public String key; public String mime; public Boolean auto; @@ -26,6 +27,7 @@ class RNFetchBlobConfig { this.path = options.hasKey("path") ? options.getString("path") : null; this.appendExt = options.hasKey("appendExt") ? options.getString("appendExt") : ""; this.trusty = options.hasKey("trusty") ? options.getBoolean("trusty") : false; + this.wifiOnly = options.hasKey("wifiOnly") ? options.getBoolean("wifiOnly") : false; if(options.hasKey("addAndroidDownloads")) { this.addAndroidDownloads = options.getMap("addAndroidDownloads"); } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index b73907950..197477a0f 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -9,6 +9,9 @@ import android.net.Uri; import android.os.Build; import androidx.annotation.NonNull; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.ConnectivityManager; import android.util.Base64; import com.RNFetchBlob.Response.RNFetchBlobDefaultResp; @@ -36,6 +39,7 @@ import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.URL; +import java.net.Proxy; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; @@ -231,6 +235,45 @@ else if(this.options.fileCache) clientBuilder = client.newBuilder(); } + // wifi only, need ACCESS_NETWORK_STATE permission + // and API level >= 21 + if(this.options.wifiOnly){ + + boolean found = false; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ConnectivityManager connectivityManager = (ConnectivityManager) RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.CONNECTIVITY_SERVICE); + Network[] networks = connectivityManager.getAllNetworks(); + + for (Network network : networks) { + //NetworkInfo netInfo = connectivityManager.getNetworkInfo(network); + NetworkCapabilities caps = connectivityManager.getNetworkCapabilities(network); + if(caps == null){ + continue; + } + + // netinfo is deprecated + //if (netInfo.getType() == ConnectivityManager.TYPE_WIFI && netInfo.getState() == NetworkInfo.State.CONNECTED) { + if(caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)){ + clientBuilder.proxy(Proxy.NO_PROXY); + clientBuilder.socketFactory(network.getSocketFactory()); + found = true; + break; + + } + } + + if(!found){ + callback.invoke("No available WiFi connections.", null, null); + releaseTaskResource(); + return; + } + } + else{ + RNFetchBlobUtils.emitWarningEvent("RNFetchBlob: wifiOnly was set, but SDK < 21. wifiOnly was ignored."); + } + } + final Request.Builder builder = new Request.Builder(); try { builder.url(new URL(url)); @@ -530,26 +573,16 @@ private void done(Response resp) { } break; case FileStorage: - ResponseBody responseBody = resp.body(); - try { // In order to write response data to `destPath` we have to invoke this method. // It uses customized response body which is able to report download progress // and write response data to destination path. - responseBody.bytes(); + resp.body().bytes(); } catch (Exception ignored) { // ignored.printStackTrace(); } - - RNFetchBlobFileResp rnFetchBlobFileResp = (RNFetchBlobFileResp) responseBody; - - if(rnFetchBlobFileResp != null && rnFetchBlobFileResp.isDownloadComplete() == false){ - callback.invoke("RNFetchBlob failed. Download interrupted.", null); - } - else { - this.destPath = this.destPath.replace("?append=true", ""); - callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath); - } + this.destPath = this.destPath.replace("?append=true", ""); + callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath); break; default: try { diff --git a/index.d.ts b/index.d.ts index 540a99c81..972f8e609 100644 --- a/index.d.ts +++ b/index.d.ts @@ -546,6 +546,16 @@ export interface RNFetchBlobConfig { */ trusty?: boolean; + /** + * Set this property to true will only do requests through the WiFi interface, and fail otherwise. + */ + wifiOnly?: boolean; + + /** + * Set this property so redirects are not automatically followed. + */ + followRedirect?: boolean; + /** * Set this property to true will makes response data of the fetch stored in a temp file, by default the temp * file will stored in App's own root folder with file name template RNFetchBlob_tmp${timestamp}. diff --git a/index.js b/index.js index 1be30833f..2a36ba75b 100644 --- a/index.js +++ b/index.js @@ -105,6 +105,12 @@ function wrap(path:string):string { * If it doesn't exist, the file is downloaded as usual * @property {number} timeout * Request timeout in millionseconds, by default it's 30000ms. + * @property {boolean} followRedirect + * Follow redirects automatically, default true + * @property {boolean} trusty + * Trust all certificates + * @property {boolean} wifiOnly + * Only do requests through WiFi. Android SDK 21 or above only. * * @return {function} This method returns a `fetch` method instance. */ diff --git a/index.js.flow b/index.js.flow index ecfd50a2d..03666e569 100644 --- a/index.js.flow +++ b/index.js.flow @@ -163,7 +163,9 @@ export type RNFetchBlobConfig = { path?: string, session?: string, timeout?: number, - trusty?: boolean + trusty?: boolean, + wifiOnly?: boolean, + followRedirect?: boolean }; export type RNFetchBlobResponseInfo = { headers: {[fieldName: string]: string}, diff --git a/ios/RNFetchBlobConst.h b/ios/RNFetchBlobConst.h index 6347b7a45..7d09c3a74 100644 --- a/ios/RNFetchBlobConst.h +++ b/ios/RNFetchBlobConst.h @@ -32,6 +32,7 @@ extern NSString *const CONFIG_USE_TEMP; extern NSString *const CONFIG_FILE_PATH; extern NSString *const CONFIG_FILE_EXT; extern NSString *const CONFIG_TRUSTY; +extern NSString *const CONFIG_WIFI_ONLY; extern NSString *const CONFIG_INDICATOR; extern NSString *const CONFIG_KEY; extern NSString *const CONFIG_EXTRA_BLOB_CTYPE; diff --git a/ios/RNFetchBlobConst.m b/ios/RNFetchBlobConst.m index bc9b793a5..1376d692e 100644 --- a/ios/RNFetchBlobConst.m +++ b/ios/RNFetchBlobConst.m @@ -16,6 +16,7 @@ NSString *const CONFIG_FILE_PATH = @"path"; NSString *const CONFIG_FILE_EXT = @"appendExt"; NSString *const CONFIG_TRUSTY = @"trusty"; +NSString *const CONFIG_WIFI_ONLY = @"wifiOnly"; NSString *const CONFIG_INDICATOR = @"indicator"; NSString *const CONFIG_KEY = @"key"; NSString *const CONFIG_EXTRA_BLOB_CTYPE = @"binaryContentTypes"; diff --git a/ios/RNFetchBlobRequest.m b/ios/RNFetchBlobRequest.m index a56cc92d0..7bb0ddde5 100644 --- a/ios/RNFetchBlobRequest.m +++ b/ios/RNFetchBlobRequest.m @@ -124,6 +124,10 @@ - (void) sendRequest:(__weak NSDictionary * _Nullable )options defaultConfigObject.timeoutIntervalForRequest = timeout/1000; } + if([options valueForKey:CONFIG_WIFI_ONLY] != nil && ![options[CONFIG_WIFI_ONLY] boolValue]){ + [defaultConfigObject setAllowsCellularAccess:NO]; + } + defaultConfigObject.HTTPMaximumConnectionsPerHost = 10; session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:operationQueue]; diff --git a/types.js b/types.js index f2f03ccd1..da256af61 100644 --- a/types.js +++ b/types.js @@ -5,7 +5,10 @@ type RNFetchBlobConfig = { appendExt : string, session : string, addAndroidDownloads : any, - indicator : bool + indicator : bool, + followRedirect : bool, + trusty : bool, + wifiOnly : bool }; type RNFetchBlobNative = { From 52aa5ecd7a4d9534faef75aaeff267584f56c5c3 Mon Sep 17 00:00:00 2001 From: Cristiano Coelho Date: Tue, 10 Dec 2019 23:20:47 -0300 Subject: [PATCH 2/7] do not use P2P Wifi connections. Fixes issue on samsung --- android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index 197477a0f..6a9ebf599 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -252,8 +252,11 @@ else if(this.options.fileCache) continue; } - // netinfo is deprecated - //if (netInfo.getType() == ConnectivityManager.TYPE_WIFI && netInfo.getState() == NetworkInfo.State.CONNECTED) { + // Don't use P2P Wi-Fi on recent samsung devices + if(caps.hasTransport(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)){ + continue; + } + if(caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)){ clientBuilder.proxy(Proxy.NO_PROXY); clientBuilder.socketFactory(network.getSocketFactory()); From 99a81e5ec35bc392879a5babaed26289f1a9065a Mon Sep 17 00:00:00 2001 From: Cristiano Coelho Date: Wed, 11 Dec 2019 01:38:43 -0300 Subject: [PATCH 3/7] take 2 at samsung issues: ensure network is connected --- .../src/main/java/com/RNFetchBlob/RNFetchBlobReq.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index 6a9ebf599..bad184c62 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -10,6 +10,7 @@ import android.os.Build; import androidx.annotation.NonNull; import android.net.Network; +import android.net.NetworkInfo; import android.net.NetworkCapabilities; import android.net.ConnectivityManager; import android.util.Base64; @@ -246,14 +247,15 @@ else if(this.options.fileCache) Network[] networks = connectivityManager.getAllNetworks(); for (Network network : networks) { - //NetworkInfo netInfo = connectivityManager.getNetworkInfo(network); + + NetworkInfo netInfo = connectivityManager.getNetworkInfo(network); NetworkCapabilities caps = connectivityManager.getNetworkCapabilities(network); - if(caps == null){ + + if(caps == null || netInfo == null){ continue; } - // Don't use P2P Wi-Fi on recent samsung devices - if(caps.hasTransport(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)){ + if(!netInfo.isConnected()){ continue; } From 3e3d36ea1ca3318938ccb30f5e5f308d5f409f6d Mon Sep 17 00:00:00 2001 From: Cristiano Coelho Date: Thu, 12 Dec 2019 10:56:37 -0300 Subject: [PATCH 4/7] comment warning, too noisy on timeouts. --- android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index bad184c62..d7de9627c 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -426,7 +426,7 @@ public Response intercept(@NonNull Chain chain) throws IOException { } catch (SocketTimeoutException e ){ timeout = true; - RNFetchBlobUtils.emitWarningEvent("RNFetchBlob error when sending request : " + e.getLocalizedMessage()); + //RNFetchBlobUtils.emitWarningEvent("RNFetchBlob error when sending request : " + e.getLocalizedMessage()); } catch(Exception ex) { } @@ -721,7 +721,7 @@ public void onReceive(Context context, Intent intent) { } String filePath = null; - try { + try { // the file exists in media content database if (c.moveToFirst()) { // #297 handle failed request From 8ee728e6c14dbc6a7b109e8afedc50711b497dd1 Mon Sep 17 00:00:00 2001 From: Cristiano Coelho Date: Thu, 12 Dec 2019 11:26:32 -0300 Subject: [PATCH 5/7] undo these changes that were carried over from outdated master --- .../java/com/RNFetchBlob/RNFetchBlobReq.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index d7de9627c..e6611c3a4 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -578,16 +578,27 @@ private void done(Response resp) { } break; case FileStorage: + ResponseBody responseBody = resp.body(); + try { // In order to write response data to `destPath` we have to invoke this method. // It uses customized response body which is able to report download progress // and write response data to destination path. - resp.body().bytes(); + responseBody.bytes(); } catch (Exception ignored) { // ignored.printStackTrace(); } - this.destPath = this.destPath.replace("?append=true", ""); - callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath); + + RNFetchBlobFileResp rnFetchBlobFileResp = (RNFetchBlobFileResp) responseBody; + + if(rnFetchBlobFileResp != null && !rnFetchBlobFileResp.isDownloadComplete()){ + callback.invoke("RNFetchBlob failed. Download interrupted.", null); + } + else { + this.destPath = this.destPath.replace("?append=true", ""); + callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath); + } + break; default: try { From 92a46a2ec1fb4f1264bd029f72582f6a3a5f35e9 Mon Sep 17 00:00:00 2001 From: Cristiano Coelho Date: Thu, 12 Dec 2019 12:00:05 -0300 Subject: [PATCH 6/7] Use a friendlier error for download interrupts. Interrupts are normal, and this error ends up directly on the UI --- android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index e6611c3a4..1fe0145fd 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -592,7 +592,7 @@ private void done(Response resp) { RNFetchBlobFileResp rnFetchBlobFileResp = (RNFetchBlobFileResp) responseBody; if(rnFetchBlobFileResp != null && !rnFetchBlobFileResp.isDownloadComplete()){ - callback.invoke("RNFetchBlob failed. Download interrupted.", null); + callback.invoke("Download interrupted.", null); } else { this.destPath = this.destPath.replace("?append=true", ""); From f2ac3e0b3bef57f6163e3efbb91a25d7423864d0 Mon Sep 17 00:00:00 2001 From: Cristiano Coelho Date: Thu, 12 Dec 2019 14:59:52 -0300 Subject: [PATCH 7/7] make request timeout consistent with iOS, do not mess with ios indicator if not requested to --- .../java/com/RNFetchBlob/RNFetchBlobReq.java | 2 +- ios/RNFetchBlobRequest.m | 127 +++++++++--------- 2 files changed, 66 insertions(+), 63 deletions(-) diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index 1fe0145fd..a431c697c 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -462,7 +462,7 @@ public void onFailure(@NonNull Call call, IOException e) { // check if this error caused by socket timeout if(e.getClass().equals(SocketTimeoutException.class)) { respInfo.putBoolean("timeout", true); - callback.invoke("request timed out.", null, null); + callback.invoke("The request timed out.", null, null); } else callback.invoke(e.getLocalizedMessage(), null, null); diff --git a/ios/RNFetchBlobRequest.m b/ios/RNFetchBlobRequest.m index 7bb0ddde5..cdbe6b1e5 100644 --- a/ios/RNFetchBlobRequest.m +++ b/ios/RNFetchBlobRequest.m @@ -56,7 +56,7 @@ - (NSString *)md5:(NSString *)input { const char* str = [input UTF8String]; unsigned char result[CC_MD5_DIGEST_LENGTH]; CC_MD5(str, (CC_LONG)strlen(str), result); - + NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2]; for (int i = 0; i 0) { defaultConfigObject.timeoutIntervalForRequest = timeout/1000; } - + if([options valueForKey:CONFIG_WIFI_ONLY] != nil && ![options[CONFIG_WIFI_ONLY] boolValue]){ [defaultConfigObject setAllowsCellularAccess:NO]; } - + defaultConfigObject.HTTPMaximumConnectionsPerHost = 10; session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:operationQueue]; - + if (path || [self.options valueForKey:CONFIG_USE_TEMP]) { respFile = YES; - + NSString* cacheKey = taskId; if (key) { cacheKey = [self md5:key]; - + if (!cacheKey) { cacheKey = taskId; } - + destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]]; - + if ([[NSFileManager defaultManager] fileExistsAtPath:destPath]) { callback(@[[NSNull null], RESP_TYPE_PATH, destPath]); - + return; } } - + if (path) { destPath = path; } else { @@ -160,10 +160,10 @@ - (void) sendRequest:(__weak NSDictionary * _Nullable )options respData = [[NSMutableData alloc] init]; respFile = NO; } - + self.task = [session dataTaskWithRequest:req]; [self.task resume]; - + // network status indicator if ([[options objectForKey:CONFIG_INDICATOR] boolValue]) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -187,17 +187,17 @@ - (void) sendRequest:(__weak NSDictionary * _Nullable )options - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { expectedBytes = [response expectedContentLength]; - + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode]; NSString * respType = @""; respStatus = statusCode; - + if ([response respondsToSelector:@selector(allHeaderFields)]) { NSDictionary *headers = [httpResponse allHeaderFields]; NSString * respCType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"Content-Type" fromHeaders:headers] lowercaseString]; - + if (self.isServerPush) { if (partBuffer) { [self.bridge.eventDispatcher @@ -208,7 +208,7 @@ - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dat } ]; } - + partBuffer = [[NSMutableData alloc] init]; completionHandler(NSURLSessionResponseAllow); @@ -216,11 +216,11 @@ - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dat } else { self.isServerPush = [[respCType lowercaseString] RNFBContainsString:@"multipart/x-mixed-replace;"]; } - + if(respCType) { NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE]; - + if ([respCType RNFBContainsString:@"text/"]) { respType = @"text"; } else if ([respCType RNFBContainsString:@"application/json"]) { @@ -236,7 +236,7 @@ - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dat } } else { respType = @"blob"; - + // for XMLHttpRequest, switch response data handling strategy automatically if ([options valueForKey:@"auto"]) { respFile = YES; @@ -246,7 +246,7 @@ - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dat } else { respType = @"text"; } - + #pragma mark - handling cookies // # 153 get cookies if (response.URL) { @@ -256,7 +256,7 @@ - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dat [cookieStore setCookies:cookies forURL:response.URL mainDocumentURL:nil]; } } - + [self.bridge.eventDispatcher sendDeviceEventWithName: EVENT_STATE_CHANGE body:@{ @@ -272,33 +272,33 @@ - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dat } else { NSLog(@"oops"); } - + if (respFile) { @try{ NSFileManager * fm = [NSFileManager defaultManager]; NSString * folder = [destPath stringByDeletingLastPathComponent]; - + if (![fm fileExistsAtPath:folder]) { [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:nil]; } - + // if not set overwrite in options, defaults to TRUE BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue]; BOOL appendToExistingFile = [destPath RNFBContainsString:@"?append=true"]; - + appendToExistingFile = !overwrite; - + // For solving #141 append response data if the file already exists // base on PR#139 @kejinliang if (appendToExistingFile) { destPath = [destPath stringByReplacingOccurrencesOfString:@"?append=true" withString:@""]; } - + if (![fm fileExistsAtPath:destPath]) { [fm createFileAtPath:destPath contents:[[NSData alloc] init] attributes:nil]; } - + writeStream = [[NSOutputStream alloc] initToFileAtPath:destPath append:appendToExistingFile]; [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [writeStream open]; @@ -308,7 +308,7 @@ - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dat NSLog(@"write file error"); } } - + completionHandler(NSURLSessionResponseAllow); } @@ -320,30 +320,30 @@ - (void) URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dat if (self.isServerPush) { [partBuffer appendData:data]; - + return ; } - + NSNumber * received = [NSNumber numberWithLong:[data length]]; receivedBytes += [received longValue]; NSString * chunkString = @""; - + if (isIncrement) { chunkString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; } - + if (respFile) { [writeStream write:[data bytes] maxLength:[data length]]; } else { [respData appendData:data]; } - + if (expectedBytes == 0) { return; } - + NSNumber * now =[NSNumber numberWithFloat:((float)receivedBytes/(float)expectedBytes)]; - + if ([self.progressConfig shouldReport:now]) { [self.bridge.eventDispatcher sendDeviceEventWithName:EVENT_PROGRESS @@ -367,16 +367,19 @@ - (void) URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { - + self.error = error; NSString * errMsg; NSString * respStr; NSString * rnfbRespType; - - dispatch_async(dispatch_get_main_queue(), ^{ - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; - }); - + + // only run this if we were requested to change it + if ([[options objectForKey:CONFIG_INDICATOR] boolValue]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; + }); + } + if (error) { if (error.domain == NSURLErrorDomain && error.code == NSURLErrorCancelled) { errMsg = @"task cancelled"; @@ -384,7 +387,7 @@ - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCom errMsg = [error localizedDescription]; } } - + if (respFile) { [writeStream close]; rnfbRespType = RESP_TYPE_PATH; @@ -395,7 +398,7 @@ - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCom // if it turns out not to be `nil` that means the response data contains valid UTF8 string, // in order to properly encode the UTF8 string, use URL encoding before BASE64 encoding. NSString * utf8 = [[NSString alloc] initWithData:respData encoding:NSUTF8StringEncoding]; - + if (responseFormat == BASE64) { rnfbRespType = RESP_TYPE_BASE64; respStr = [respData base64EncodedStringWithOptions:0]; @@ -412,18 +415,18 @@ - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCom } } } - - + + callback(@[ errMsg ?: [NSNull null], rnfbRespType ?: @"", respStr ?: [NSNull null] ]); - + respData = nil; receivedBytes = 0; [session finishTasksAndInvalidate]; - + } // upload progress handler @@ -432,7 +435,7 @@ - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSen if (totalBytesExpectedToWrite == 0) { return; } - + NSNumber * now = [NSNumber numberWithFloat:((float)totalBytesWritten/(float)totalBytesExpectedToWrite)]; if ([self.uploadProgressConfig shouldReport:now]) { @@ -465,12 +468,12 @@ - (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)sessio - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler { - + if (followRedirect) { if (request.URL) { [redirects addObject:[request.URL absoluteString]]; } - + completionHandler(request); } else { completionHandler(nil);