diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index 65d12bb4c..25e78c4cb 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -4,3 +4,4 @@ You may want to take a look on that page or find issues tagged "trouble shooting * please provide the version of installed library and RN project. * a sample code snippet/repository is very helpful to spotting the problem. * issues which have been tagged as 'needs feedback', will be closed after 2 weeks if receive no feedbacks. +* issues lack of detailed information will be closed without any feedback diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 6f184b4a1..b03e3e8e3 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -1,5 +1,5 @@ Thank you for making a pull request ! Just a gentle reminder :) 1. If the PR is offering a feature please make the request to our "Feature Branch" 0.11.0 -2. Bug fix request to "Bug Fix Branch" 0.10.6 +2. Bug fix request to "Bug Fix Branch" 0.10.9 3. Correct README.md can directly to master diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..fb71468eb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +For developers who interested in making contribution to this project, please see [https://github.com/wkh237/react-native-fetch-blob-dev](https://github.com/wkh237/react-native-fetch-blob-dev) for more information. + +Please read the following rules before opening a PR : + +1. If the PR is offering a feature please make the PR to our "Feature Branch" 0.11.0 +2. Bug fix request to "Bug Fix Branch" 0.10.6 +3. Correct README.md can directly to master diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 63c11724f..5fa3a88c4 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,7 +1,12 @@ 960px +Amerrnath Andreas Amsenius +Andrew Jack Arthur Ouaki +Ben +Ben Hsieh Binur Konarbai +Bronco Chris Sloey Corentin Smith Dmitry Petukhov @@ -9,16 +14,29 @@ Dombi Soma Kristóf Erik Smartt Evgeniy Baraniuk Frank van der Hoek +Guy Blank +Jacob Lauritzen +Jeremi Stadler +Jon San Miguel Juan B. Rodriguez Kaishley Martin Giachetti +Max Gurela Mike Monteith Naoki AINOYA Nguyen Cao Nhat Linh +Nick Pomfret +Oliver Petter Hesselberg +Reza Ghorbani +Simón Gómez +Steve Liles Tim Suchanek +Yonsh Lin +atlanteh follower francisco-sanchez-molina +gferreyra91 hhravn kejinliang pedramsaleh diff --git a/README.md b/README.md index b315848b9..ab235d606 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ +# New Maintainers and Repository Location + +This repository no longer is the main location of "react-native-fetch-blob". + +The owners of this fork have agreed to maintain this package: + +https://github.com/joltup/rn-fetch-blob + +That means issues and PRs should be posted there. + +--- + # react-native-fetch-blob [![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)]() @@ -26,13 +38,13 @@ A project committed to making file access and data transfer easier and more effi * [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support) * [Self-Signed SSL Server](#user-content-self-signed-ssl-server) * [Transfer Encoding](#user-content-transfer-encoding) - * [RNFetchBlob as Fetch](#user-content-rnfetchblob-as-fetch) + * [Drop-in Fetch Replacement](#user-content-drop-in-fetch-replacement) * [File System](#user-content-file-system) * [File access](#user-content-file-access) * [File stream](#user-content-file-stream) * [Manage cached files](#user-content-cache-file-management) * [Web API Polyfills](#user-content-web-api-polyfills) -* [Performance Tips](#user-content-performance-tipsd) +* [Performance Tips](#user-content-performance-tips) * [API References](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API) * [Caveats](#user-content-caveats) * [Development](#user-content-development) @@ -452,11 +464,11 @@ task.cancel((err) => { ... }) ``` -### RNFetchBlob as Fetch +### Drop-in Fetch Replacement 0.9.0 -If you have existing code that uses `whatwg-fetch`(the official **fetch**), you don't have to change them after 0.9.0, just use fetch replacement. The difference between Official fetch and fetch replacement is, official fetch uses [whatwg-fetch](https://github.com/github/fetch) js library which wraps XMLHttpRequest polyfill under the hood it's a great library for web developers, however that does not play very well with RN. Our implementation is simply a wrapper of RNFetchBlob.fetch and fs APIs, so you can access all the features we provide. +If you have existing code that uses `whatwg-fetch`(the official **fetch**), it's not necessary to replace them with `RNFetchblob.fetch`, you can simply use our **Fetch Replacement**. The difference between Official them is official fetch uses [whatwg-fetch](https://github.com/github/fetch) which wraps XMLHttpRequest polyfill under the hood. It's a great library for web developers, but does not play very well with RN. Our implementation is simply a wrapper of our `fetch` and `fs` APIs, so you can access all the features we provided. [See document and examples](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API#fetch-replacement) @@ -613,6 +625,8 @@ In `v0.5.0` we've added `writeStream` and `readStream`, which allows your app r When calling `readStream` method, you have to `open` the stream, and start to read data. When the file is large, consider using an appropriate `bufferSize` and `interval` to reduce the native event dispatching overhead (see [Performance Tips](#user-content-performance-tips)) +> The file stream event has a default throttle(10ms) and buffer size which preventing it cause too much overhead to main thread, yo can also [tweak these values](#user-content-performance-tips). + ```js let data = '' RNFetchBlob.fs.readStream( diff --git a/android.js b/android.js index e39d52d32..0b5dbd84b 100644 --- a/android.js +++ b/android.js @@ -21,18 +21,26 @@ function actionViewIntent(path:string, mime:string = 'text/plain') { if(Platform.OS === 'android') return RNFetchBlob.actionViewIntent(path, mime) else - return Promise.reject('RNFetchBlob.actionViewIntent only supports Android.') + return Promise.reject('RNFetchBlob.android.actionViewIntent only supports Android.') } function getContentIntent(mime:string) { if(Platform.OS === 'android') return RNFetchBlob.getContentIntent(mime) else - return Promise.reject('RNFetchBlob.getContentIntent only supports Android.') + return Promise.reject('RNFetchBlob.android.getContentIntent only supports Android.') +} + +function addCompleteDownload(config) { + if(Platform.OS === 'android') + return RNFetchBlob.addCompleteDownload(config) + else + return Promise.reject('RNFetchBlob.android.addCompleteDownload only supports Android.') } export default { actionViewIntent, - getContentIntent + getContentIntent, + addCompleteDownload } diff --git a/android/build.gradle b/android/build.gradle index 6a477b8b2..919e64ac8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,10 +6,10 @@ repositories { buildscript { repositories { - mavenCentral() + jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0' + classpath 'com.android.tools.build:gradle:2.2.3' } } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java index 88f42ca74..19e1be435 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java @@ -1,11 +1,10 @@ package com.RNFetchBlob; import android.app.Activity; +import android.app.DownloadManager; import android.content.Intent; import android.net.Uri; -import android.util.Log; -import com.RNFetchBlob.Utils.RNFBCookieJar; import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.LifecycleEventListener; @@ -15,12 +14,17 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableArray; + +// Cookies import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.network.ForwardingCookieHandler; +import com.facebook.react.modules.network.CookieJarContainer; +import com.facebook.react.modules.network.OkHttpClientProvider; +import okhttp3.OkHttpClient; +import okhttp3.JavaNetCookieJar; import java.util.HashMap; import java.util.Map; -import java.util.UUID; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -30,6 +34,11 @@ public class RNFetchBlob extends ReactContextBaseJavaModule { + // Cookies + private final ForwardingCookieHandler mCookieHandler; + private final CookieJarContainer mCookieJarContainer; + private final OkHttpClient mClient; + static ReactApplicationContext RCTContext; static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue); @@ -42,6 +51,11 @@ public RNFetchBlob(ReactApplicationContext reactContext) { super(reactContext); + mClient = OkHttpClientProvider.getOkHttpClient(); + mCookieHandler = new ForwardingCookieHandler(reactContext); + mCookieJarContainer = (CookieJarContainer) mClient.cookieJar(); + mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler)); + RCTContext = reactContext; reactContext.addActivityEventListener(new ActivityEventListener() { @Override @@ -252,35 +266,6 @@ public void run() { } - @ReactMethod - /** - * Get cookies belongs specific host. - * @param host String domain name. - */ - public void getCookies(String domain, Promise promise) { - try { - WritableMap cookies = RNFBCookieJar.getCookies(domain); - promise.resolve(cookies); - } catch(Exception err) { - promise.reject("RNFetchBlob.getCookies", err.getMessage()); - } - } - - @ReactMethod - /** - * Remove cookies for specific domain - * @param domain String of the domain - * @param promise JSC promise injected by RN - */ - public void removeCookies(String domain, Promise promise) { - try { - RNFBCookieJar.removeCookies(domain); - promise.resolve(null); - } catch(Exception err) { - promise.reject("RNFetchBlob.removeCookies", err.getMessage()); - } - } - @ReactMethod /** * @param path Stream file path @@ -338,12 +323,12 @@ public void enableUploadProgressReport(String taskId, int interval, int count) { @ReactMethod public void fetchBlob(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, final Callback callback) { - new RNFetchBlobReq(options, taskId, method, url, headers, body, null, callback).run(); - } + new RNFetchBlobReq(options, taskId, method, url, headers, body, null, mClient, callback).run(); +} @ReactMethod public void fetchBlobForm(ReadableMap options, String taskId, String method, String url, ReadableMap headers, ReadableArray body, final Callback callback) { - new RNFetchBlobReq(options, taskId, method, url, headers, null, body, callback).run(); + new RNFetchBlobReq(options, taskId, method, url, headers, null, body, mClient, callback).run(); } @ReactMethod @@ -358,4 +343,31 @@ public void getContentIntent(String mime, Promise promise) { } + @ReactMethod + public void addCompleteDownload (ReadableMap config, Promise promise) { + DownloadManager dm = (DownloadManager) RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE); + String path = RNFetchBlobFS.normalizePath(config.getString("path")); + if(path == null) { + promise.reject("RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"), "RNFetchblob.addCompleteDownload can not resolve URI:" + path); + return; + } + try { + WritableMap stat = RNFetchBlobFS.statFile(path); + dm.addCompletedDownload( + config.hasKey("title") ? config.getString("title") : "", + config.hasKey("description") ? config.getString("description") : "", + true, + config.hasKey("mime") ? config.getString("mime") : null, + path, + Long.valueOf(stat.getString("size")), + config.hasKey("showNotification") && config.getBoolean("showNotification") + ); + promise.resolve(null); + } + catch(Exception ex) { + promise.reject("RNFetchblob.addCompleteDownload failed", ex.getStackTrace().toString()); + } + + } + } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java index 8517283a8..7a7910546 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java @@ -138,11 +138,13 @@ static public void writeFile(String path, ReadableArray data, final boolean appe * @param promise */ static public void readFile(String path, String encoding, final Promise promise ) { - path = normalizePath(path); + String resolved = normalizePath(path); + if(resolved != null) + path = resolved; try { byte[] bytes; - if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { + if(resolved != null && resolved.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { String assetName = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""); long length = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength(); bytes = new byte[(int) length]; @@ -150,6 +152,14 @@ static public void readFile(String path, String encoding, final Promise promise in.read(bytes, 0, (int) length); in.close(); } + // issue 287 + else if(resolved == null) { + InputStream in = RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(path)); + int length = (int) in.available(); + bytes = new byte[length]; + in.read(bytes); + in.close(); + } else { File f = new File(path); int length = (int) f.length(); @@ -203,6 +213,7 @@ static public Map getSystemfolders(ReactApplicationContext ctx) state = Environment.getExternalStorageState(); if (state.equals(Environment.MEDIA_MOUNTED)) { res.put("SDCardDir", Environment.getExternalStorageDirectory().getAbsolutePath()); + res.put("SDCardApplicationDir", ctx.getExternalFilesDir(null).getParentFile().getAbsolutePath()); } res.put("MainBundleDir", ctx.getApplicationInfo().dataDir); return res; @@ -225,7 +236,9 @@ static public String getTmpPath(ReactApplicationContext ctx, String taskId) { * @param bufferSize Buffer size of read stream, default to 4096 (4095 when encode is `base64`) */ public void readStream(String path, String encoding, int bufferSize, int tick, final String streamId) { - path = normalizePath(path); + String resolved = normalizePath(path); + if(resolved != null) + path = resolved; try { int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096; @@ -233,9 +246,14 @@ public void readStream(String path, String encoding, int bufferSize, int tick, f chunkSize = bufferSize; InputStream fs; - if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { - fs = RNFetchBlob.RCTContext.getAssets() - .open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "")); + + if(resolved != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { + fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "")); + + } + // fix issue 287 + else if(resolved == null) { + fs = RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(path)); } else { fs = new FileInputStream(new File(path)); @@ -249,10 +267,7 @@ public void readStream(String path, String encoding, int bufferSize, int tick, f CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); while ((cursor = fs.read(buffer)) != -1) { encoder.encode(ByteBuffer.wrap(buffer).asCharBuffer()); - String chunk = new String(buffer); - if(cursor != bufferSize) { - chunk = chunk.substring(0, cursor); - } + String chunk = new String(buffer, 0, cursor); emitStreamEvent(streamId, "data", chunk); if(tick > 0) SystemClock.sleep(tick); @@ -881,13 +896,21 @@ static boolean isAsset(String path) { return false; } + /** + * Normalize the path, remove URI scheme (xxx://) so that we can handle it. + * @param path URI string. + * @return Normalized string + */ static String normalizePath(String path) { if(path == null) return null; - Uri uri = Uri.parse(path); - if(uri.getScheme() == null) { + if(!path.matches("\\w+\\:.*")) return path; + if(path.startsWith("file://")) { + return path.replace("file://", ""); } + + Uri uri = Uri.parse(path); if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) { return path; } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java index 74e0224a7..48aac7ac3 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java @@ -20,7 +20,6 @@ public List createNativeModules(ReactApplicationContext reactConte return modules; } - @Override public List> createJSModules() { return Collections.emptyList(); } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java index 8af663ecf..8a81a832e 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java @@ -7,11 +7,12 @@ import android.content.IntentFilter; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.util.Base64; import com.RNFetchBlob.Response.RNFetchBlobDefaultResp; import com.RNFetchBlob.Response.RNFetchBlobFileResp; -import com.RNFetchBlob.Utils.RNFBCookieJar; +import com.facebook.common.logging.FLog; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; @@ -21,14 +22,13 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.modules.network.OkHttpClientProvider; +import com.facebook.react.modules.network.TLSSocketFactory; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.CookieHandler; -import java.net.CookieManager; -import java.net.CookiePolicy; import java.net.MalformedURLException; import java.net.SocketException; import java.net.SocketTimeoutException; @@ -38,12 +38,14 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.ArrayList; +import java.util.List; import java.util.HashMap; + import java.util.concurrent.TimeUnit; import okhttp3.Call; import okhttp3.ConnectionPool; -import okhttp3.CookieJar; +import okhttp3.ConnectionSpec; import okhttp3.Headers; import okhttp3.Interceptor; import okhttp3.MediaType; @@ -52,6 +54,8 @@ import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import okhttp3.TlsVersion; + public class RNFetchBlobReq extends BroadcastReceiver implements Runnable { @@ -98,8 +102,9 @@ enum ResponseFormat { WritableMap respInfo; boolean timeout = false; ArrayList redirects = new ArrayList<>(); + OkHttpClient client; - public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, final Callback callback) { + public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, OkHttpClient client, final Callback callback) { this.method = method.toUpperCase(); this.options = new RNFetchBlobConfig(options); this.taskId = taskId; @@ -108,6 +113,7 @@ public RNFetchBlobReq(ReadableMap options, String taskId, String method, String this.callback = callback; this.rawRequestBody = body; this.rawRequestBodyArray = arrayBody; + this.client = client; if(this.options.fileCache || this.options.path != null) responseType = ResponseType.FileStorage; @@ -150,8 +156,15 @@ public void run() { if(options.addAndroidDownloads.hasKey("path")) { req.setDestinationUri(Uri.parse("file://" + options.addAndroidDownloads.getString("path"))); } + // #391 Add MIME type to the request + if(options.addAndroidDownloads.hasKey("mime")) { + req.setMimeType(options.addAndroidDownloads.getString("mime")); + } // set headers ReadableMapKeySetIterator it = headers.keySetIterator(); + if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable") == true ) { + req.allowScanningByMediaScanner(); + } while (it.hasNextKey()) { String key = it.nextKey(); req.addRequestHeader(key, headers.getString(key)); @@ -194,9 +207,9 @@ else if(this.options.fileCache) try { // use trusty SSL socket if (this.options.trusty) { - clientBuilder = RNFetchBlobUtils.getUnsafeOkHttpClient(); + clientBuilder = RNFetchBlobUtils.getUnsafeOkHttpClient(client); } else { - clientBuilder = new OkHttpClient.Builder(); + clientBuilder = client.newBuilder(); } final Request.Builder builder = new Request.Builder(); @@ -297,10 +310,7 @@ else if(cType.isEmpty()) { } // #156 fix cookie issue - - final Request req = builder.build(); - clientBuilder.cookieJar(new RNFBCookieJar()); clientBuilder.addNetworkInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { @@ -364,9 +374,10 @@ public Response intercept(Chain chain) throws IOException { clientBuilder.retryOnConnectionFailure(false); clientBuilder.followRedirects(options.followRedirect); clientBuilder.followSslRedirects(options.followRedirect); + clientBuilder.retryOnConnectionFailure(true); + OkHttpClient client = enableTls12OnPreLollipop(clientBuilder).build(); - OkHttpClient client = clientBuilder.retryOnConnectionFailure(true).build(); Call call = client.newCall(req); taskTable.put(taskId, call); call.enqueue(new okhttp3.Callback() { @@ -641,16 +652,20 @@ public void onReceive(Context context, Intent intent) { return; } String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); - if (contentUri != null) { + if ( contentUri != null && + options.addAndroidDownloads.hasKey("mime") && + options.addAndroidDownloads.getString("mime").contains("image")) { Uri uri = Uri.parse(contentUri); Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null); - // use default destination of DownloadManager + + // use default destination of DownloadManager if (cursor != null) { cursor.moveToFirst(); filePath = cursor.getString(0); } } } + // When the file is not found in media content database, check if custom path exists if (options.addAndroidDownloads.hasKey("path")) { try { @@ -677,5 +692,28 @@ public void onReceive(Context context, Intent intent) { } } + public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { + try { + client.sslSocketFactory(new TLSSocketFactory()); + + ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .tlsVersions(TlsVersion.TLS_1_2) + .build(); + + List< ConnectionSpec > specs = new ArrayList < > (); + specs.add(cs); + specs.add(ConnectionSpec.COMPATIBLE_TLS); + specs.add(ConnectionSpec.CLEARTEXT); + + client.connectionSpecs(specs); + } catch (Exception exc) { + FLog.e("OkHttpClientProvider", "Error while enabling TLS 1.2", exc); + } + } + + return client; + } + } diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java index 479bd4a50..6e976d775 100644 --- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java +++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java @@ -52,7 +52,7 @@ public static void emitWarningEvent(String data) { .emit(RNFetchBlobConst.EVENT_MESSAGE, args); } - public static OkHttpClient.Builder getUnsafeOkHttpClient() { + public static OkHttpClient.Builder getUnsafeOkHttpClient(OkHttpClient client) { try { // Create a trust manager that does not validate certificate chains final TrustManager[] trustAllCerts = new TrustManager[]{ @@ -78,7 +78,7 @@ public java.security.cert.X509Certificate[] getAcceptedIssuers() { // Create an ssl socket factory with our all-trusting manager final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); - OkHttpClient.Builder builder = new OkHttpClient.Builder(); + OkHttpClient.Builder builder = client.newBuilder(); builder.sslSocketFactory(sslSocketFactory); builder.hostnameVerifier(new HostnameVerifier() { @Override diff --git a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java index bc9b3779c..88fc7c69a 100644 --- a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java +++ b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java @@ -81,7 +81,6 @@ public long read(Buffer sink, long byteCount) throws IOException { byte[] bytes = new byte[(int) byteCount]; long read = originalBody.byteStream().read(bytes, 0, (int) byteCount); bytesDownloaded += read > 0 ? read : 0; - Log.i("bytes downloaded", String.valueOf(byteCount) + "/" + String.valueOf(read) + "=" + String.valueOf(bytesDownloaded)); if (read > 0) { ofStream.write(bytes, 0, (int) read); } diff --git a/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java b/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java index e5742b81e..fef3ada97 100644 --- a/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java +++ b/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java @@ -8,6 +8,11 @@ import android.provider.MediaStore; import android.content.ContentUris; import android.os.Environment; +import android.content.ContentResolver; +import com.RNFetchBlob.RNFetchBlobUtils; +import java.io.File; +import java.io.InputStream; +import java.io.FileOutputStream; public class PathResolver { public static String getRealPathFromURI(final Context context, final Uri uri) { @@ -59,6 +64,37 @@ else if (isMediaDocument(uri)) { return getDataColumn(context, contentUri, selection, selectionArgs); } + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + + return getDataColumn(context, uri, null, null); + } + // Other Providers + else{ + try { + InputStream attachment = context.getContentResolver().openInputStream(uri); + if (attachment != null) { + String filename = getContentName(context.getContentResolver(), uri); + if (filename != null) { + File file = new File(context.getCacheDir(), filename); + FileOutputStream tmp = new FileOutputStream(file); + byte[] buffer = new byte[1024]; + while (attachment.read(buffer) > 0) { + tmp.write(buffer); + } + tmp.close(); + attachment.close(); + return file.getAbsolutePath(); + } + } + } catch (Exception e) { + RNFetchBlobUtils.emitWarningEvent(e.toString()); + return null; + } + } } // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { @@ -77,6 +113,18 @@ else if ("file".equalsIgnoreCase(uri.getScheme())) { return null; } + private static String getContentName(ContentResolver resolver, Uri uri) { + Cursor cursor = resolver.query(uri, null, null, null, null); + cursor.moveToFirst(); + int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); + if (nameIndex >= 0) { + String name = cursor.getString(nameIndex); + cursor.close(); + return name; + } + return null; + } + /** * Get the value of the data column for this Uri. This is useful for * MediaStore Uris, and other file-based ContentProviders. @@ -91,6 +139,7 @@ public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; + String result = null; final String column = "_data"; final String[] projection = { column @@ -101,13 +150,18 @@ public static String getDataColumn(Context context, Uri uri, String selection, null); if (cursor != null && cursor.moveToFirst()) { final int index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(index); + result = cursor.getString(index); } - } finally { + } + catch (Exception ex) { + ex.printStackTrace(); + return null; + } + finally { if (cursor != null) cursor.close(); } - return null; + return result; } diff --git a/android/src/main/java/com/RNFetchBlob/Utils/RNFBCookieJar.java b/android/src/main/java/com/RNFetchBlob/Utils/RNFBCookieJar.java deleted file mode 100644 index 0075cfa90..000000000 --- a/android/src/main/java/com/RNFetchBlob/Utils/RNFBCookieJar.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.RNFetchBlob.Utils; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Set; - -import okhttp3.Cookie; -import okhttp3.CookieJar; -import okhttp3.HttpUrl; - -/** - * Created by wkh237on 2016/10/14. - */ - - - -public class RNFBCookieJar implements CookieJar { - - static final HashMap> cookieStore = new HashMap<>(); - private List cookies; - - @Override - public void saveFromResponse(HttpUrl url, List cookies) { - cookieStore.put(url.host(), cookies); - } - - @Override - public List loadForRequest(HttpUrl url) { - List cookies = cookieStore.get(url.host()); - return cookies != null ? cookies : new ArrayList(); - } - - public static void removeCookies(String domain) { - if(domain != null && domain.length() > 0) { - if(cookieStore.containsKey(domain)) - cookieStore.remove(domain); - } - else - cookieStore.clear(); - } - - public static WritableMap getCookies(String host) { - Set domains = cookieStore.keySet(); - WritableMap cookieMap = Arguments.createMap(); - if(host.length() > 0 && cookieStore.containsKey(host)) { - domains.clear(); - domains.add(host); - } - // no domain specified, return all cookies - for(String key : domains) { - WritableArray cookiesInDomain = Arguments.createArray(); - for(Cookie c: cookieStore.get(key)){ - cookiesInDomain.pushString(c.toString()); - } - cookieMap.putArray(key, cookiesInDomain); - } - - return cookieMap; - } -} diff --git a/fs.js b/fs.js index ee211dd41..83e6bdceb 100644 --- a/fs.js +++ b/fs.js @@ -29,7 +29,9 @@ const dirs = { DownloadDir : RNFetchBlob.DownloadDir, DCIMDir : RNFetchBlob.DCIMDir, SDCardDir : RNFetchBlob.SDCardDir, - MainBundleDir : RNFetchBlob.MainBundleDir + SDCardApplicationDir : RNFetchBlob.SDCardApplicationDir, + MainBundleDir : RNFetchBlob.MainBundleDir, + LibraryDir : RNFetchBlob.LibraryDir } /** @@ -171,12 +173,12 @@ function writeFile(path:string, data:string | Array, encoding:?string):P return Promise.reject('Invalid argument "path" ') if(encoding.toLocaleLowerCase() === 'ascii') { if(!Array.isArray(data)) - Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)) + return Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)) else return RNFetchBlob.writeFileArray(path, data, false); } else { if(typeof data !== 'string') - Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)) + return Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)) else return RNFetchBlob.writeFile(path, encoding, data, false); } @@ -188,12 +190,12 @@ function appendFile(path:string, data:string | Array, encoding:?string): return Promise.reject('Invalid argument "path" ') if(encoding.toLocaleLowerCase() === 'ascii') { if(!Array.isArray(data)) - Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)) + return Promise.reject(new Error(`Expected "data" is an Array when encoding is "ascii", however got ${typeof data}`)) else return RNFetchBlob.writeFileArray(path, data, true); } else { if(typeof data !== 'string') - Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)) + return Promise.reject(new Error(`Expected "data" is a String when encoding is "utf8" or "base64", however got ${typeof data}`)) else return RNFetchBlob.writeFile(path, encoding, data, true); } diff --git a/index.js b/index.js index 0f68fa2e1..c8ed1a9f6 100644 --- a/index.js +++ b/index.js @@ -25,7 +25,6 @@ import polyfill from './polyfill' import _ from 'lodash' import android from './android' import ios from './ios' -import net from './net' import JSONStream from './json-stream' const { RNFetchBlobSession, @@ -51,7 +50,6 @@ const RNFetchBlob = NativeModules.RNFetchBlob // their .expire event if(Platform.OS === 'ios') { AppState.addEventListener('change', (e) => { - console.log('app state changed', e) if(e === 'active') RNFetchBlob.emitExpiredEvent(()=>{}) }) @@ -564,7 +562,6 @@ export default { session, fs, wrap, - net, polyfill, JSONStream } diff --git a/ios.js b/ios.js index 566b424e2..340ef04cf 100644 --- a/ios.js +++ b/ios.js @@ -43,7 +43,7 @@ function openDocument(path:string, scheme:string) { * @param {string} url URL of the resource, only file URL is supported * @return {Promise} */ -function excludeFromBackupKey(url:string) { +function excludeFromBackupKey(path:string) { return RNFetchBlob.excludeFromBackupKey('file://' + path); } diff --git a/ios/RNFetchBlob/RNFetchBlob.m b/ios/RNFetchBlob/RNFetchBlob.m index 4ba969d7b..246d6707c 100644 --- a/ios/RNFetchBlob/RNFetchBlob.m +++ b/ios/RNFetchBlob/RNFetchBlob.m @@ -580,25 +580,6 @@ - (UIViewController *) documentInteractionControllerViewControllerForPreview: (U return window.rootViewController; } -# pragma mark - getCookies - -RCT_EXPORT_METHOD(getCookies:(NSString *)url resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) -{ - resolve([RNFetchBlobNetwork getCookies:url]); -} - -# pragma mark - removeCookie - -RCT_EXPORT_METHOD(removeCookies:(NSString *)domain resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) -{ - NSError * err = nil; - [RNFetchBlobNetwork removeCookies:domain error:&err]; - if(err) - reject(@"RNFetchBlob failed to remove cookie", @"RNFetchBlob failed to remove cookie", nil); - else - resolve(@[[NSNull null]]); -} - # pragma mark - check expired network events RCT_EXPORT_METHOD(emitExpiredEvent:(RCTResponseSenderBlock)callback) diff --git a/ios/RNFetchBlobFS.m b/ios/RNFetchBlobFS.m index 9d4e00b0d..5e102d184 100644 --- a/ios/RNFetchBlobFS.m +++ b/ios/RNFetchBlobFS.m @@ -568,11 +568,11 @@ - (NSString *)openWithPath:(NSString *)destPath encode:(nullable NSString *)enco // Write file chunk into an opened stream - (void)writeEncodeChunk:(NSString *) chunk { - NSMutableData * decodedData = [NSData alloc]; + NSData * decodedData = nil; if([[self.encoding lowercaseString] isEqualToString:@"base64"]) { - decodedData = [[NSData alloc] initWithBase64EncodedData:chunk options:0]; - } - if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) { + decodedData = [[NSData alloc] initWithBase64EncodedString:chunk options: NSDataBase64DecodingIgnoreUnknownCharacters]; + } + else if([[self.encoding lowercaseString] isEqualToString:@"utf8"]) { decodedData = [chunk dataUsingEncoding:NSUTF8StringEncoding]; } else if([[self.encoding lowercaseString] isEqualToString:@"ascii"]) { @@ -793,4 +793,4 @@ + (void) writeAssetToPath:(ALAssetRepresentation * )rep dest:(NSString *)dest return; } -@end +@end \ No newline at end of file diff --git a/ios/RNFetchBlobNetwork.h b/ios/RNFetchBlobNetwork.h index f07f63f1d..d3b4654a5 100644 --- a/ios/RNFetchBlobNetwork.h +++ b/ios/RNFetchBlobNetwork.h @@ -49,10 +49,8 @@ typedef void(^DataTaskCompletionHander) (NSData * _Nullable resp, NSURLResponse - (nullable id) init; - (void) sendRequest; - (void) sendRequest:(NSDictionary * _Nullable )options contentLength:(long)contentLength bridge:(RCTBridge * _Nullable)bridgeRef taskId:(NSString * _Nullable)taskId withRequest:(NSURLRequest * _Nullable)req callback:(_Nullable RCTResponseSenderBlock) callback; -+ (void) removeCookies:(NSString *) domain error:(NSError **)error; + (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config; + (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config; -+ (NSDictionary *) getCookies:(NSString *) url; diff --git a/ios/RNFetchBlobNetwork.m b/ios/RNFetchBlobNetwork.m index 6adbfccfa..7be57fc59 100644 --- a/ios/RNFetchBlobNetwork.m +++ b/ios/RNFetchBlobNetwork.m @@ -82,6 +82,7 @@ @interface RNFetchBlobNetwork () NSMutableArray * redirects; ResponseFormat responseFormat; BOOL * followRedirect; + BOOL backgroundTask; } @end @@ -168,6 +169,8 @@ - (void) sendRequest:(__weak NSDictionary * _Nullable )options self.expectedBytes = 0; self.receivedBytes = 0; self.options = options; + + backgroundTask = [options valueForKey:@"IOSBackgroundTask"] == nil ? NO : [[options valueForKey:@"IOSBackgroundTask"] boolValue]; followRedirect = [options valueForKey:@"followRedirect"] == nil ? YES : [[options valueForKey:@"followRedirect"] boolValue]; isIncrement = [options valueForKey:@"increment"] == nil ? NO : [[options valueForKey:@"increment"] boolValue]; redirects = [[NSMutableArray alloc] init]; @@ -192,13 +195,12 @@ - (void) sendRequest:(__weak NSDictionary * _Nullable )options // the session trust any SSL certification NSURLSessionConfiguration *defaultConfigObject; - if(!followRedirect) - { - defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration]; - } - else + + defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration]; + + if(backgroundTask) { - NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId]; + defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:taskId]; } // set request timeout @@ -247,14 +249,6 @@ - (void) sendRequest:(__weak NSDictionary * _Nullable )options [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; __block UIApplication * app = [UIApplication sharedApplication]; - // #115 handling task expired when application entering backgound for a long time - UIBackgroundTaskIdentifier tid = [app beginBackgroundTaskWithName:taskId expirationHandler:^{ - NSLog([NSString stringWithFormat:@"session %@ expired", taskId ]); - [expirationTable setObject:task forKey:taskId]; - // comment out this one as it might cause app crash #271 -// [app endBackgroundTask:tid]; - }]; - } // #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled @@ -578,89 +572,6 @@ - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSen } } -# pragma mark - cookies handling API - -+ (NSDictionary *) getCookies:(NSString *) domain -{ - NSMutableDictionary * result = [NSMutableDictionary new]; - NSHTTPCookieStorage * cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage]; - for(NSHTTPCookie * cookie in [cookieStore cookies]) - { - NSString * cDomain = [cookie domain]; - if([result objectForKey:cDomain] == nil) - { - [result setObject:[NSMutableArray new] forKey:cDomain]; - } - if([cDomain isEqualToString:domain] || [domain length] == 0) - { - NSMutableString * cookieStr = [[NSMutableString alloc] init]; - cookieStr = [[self class] getCookieString:cookie]; - NSMutableArray * ary = [result objectForKey:cDomain]; - [ary addObject:cookieStr]; - [result setObject:ary forKey:cDomain]; - } - } - return result; -} - -// remove cookies for given domain, if domain is empty remove all cookies in shared cookie storage. -+ (void) removeCookies:(NSString *) domain error:(NSError **)error -{ - @try - { - NSHTTPCookieStorage * cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage]; - for(NSHTTPCookie * cookie in [cookies cookies]) - { - BOOL shouldRemove = domain == nil || [domain length] < 1 || [[cookie domain] isEqualToString:domain]; - if(shouldRemove) - { - [cookies deleteCookie:cookie]; - } - } - } - @catch(NSError * err) - { - *error = err; - } -} - -// convert NSHTTPCookie to string -+ (NSString *) getCookieString:(NSHTTPCookie *) cookie -{ - NSMutableString * cookieStr = [[NSMutableString alloc] init]; - [cookieStr appendString:cookie.name]; - [cookieStr appendString:@"="]; - [cookieStr appendString:cookie.value]; - - if(cookie.expiresDate == nil) { - [cookieStr appendString:@"; max-age=0"]; - } - else { - [cookieStr appendString:@"; expires="]; - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setDateFormat:@"EEE, dd MM yyyy HH:mm:ss ZZZ"]; - NSString *strDate = [dateFormatter stringFromDate:cookie.expiresDate]; - [cookieStr appendString:strDate]; - } - - - [cookieStr appendString:@"; domain="]; - [cookieStr appendString: [cookie domain]]; - [cookieStr appendString:@"; path="]; - [cookieStr appendString:cookie.path]; - - - if (cookie.isSecure) { - [cookieStr appendString:@"; secure"]; - } - - if (cookie.isHTTPOnly) { - [cookieStr appendString:@"; httponly"]; - } - return cookieStr; - -} - + (void) cancelRequest:(NSString *)taskId { NSURLSessionDataTask * task = [taskTable objectForKey:taskId]; diff --git a/net.js b/net.js deleted file mode 100644 index d48388de5..000000000 --- a/net.js +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2016 wkh237@github. All rights reserved. -// Use of this source code is governed by a MIT-style license that can be -// found in the LICENSE file. - -import { - NativeModules, - DeviceEventEmitter, - Platform, - NativeAppEventEmitter, -} from 'react-native' - -const RNFetchBlob = NativeModules.RNFetchBlob - -/** - * Get cookie according to the given url. - * @param {string} domain Domain of the cookies to be removed, remove all - * @return {Promise>} Cookies of a specific domain. - */ -function getCookies(domain:string):Promise> { - return RNFetchBlob.getCookies(domain || '') -} - -/** - * Remove cookies for a specific domain - * @param {?string} domain Domain of the cookies to be removed, remove all - * cookies when this is null. - * @return {Promise} - */ -function removeCookies(domain:?string):Promise { - return RNFetchBlob.removeCookies(domain || '') -} - -export default { - getCookies, - removeCookies -} diff --git a/package.json b/package.json index c842ce5c2..a93dba81d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-fetch-blob", - "version": "0.10.5", + "version": "0.10.8", "description": "A module provides upload, download, and files access API. Supports file stream read/write for process large files.", "main": "index.js", "scripts": { @@ -8,7 +8,7 @@ }, "dependencies": { "base-64": "0.1.0", - "glob": "^7.0.6" + "glob": "7.0.6" }, "keywords": [ "react-native", @@ -32,6 +32,7 @@ "author": "wkh237 ", "license": "MIT", "contributors": [ + "Ben ", "" ] } diff --git a/polyfill/Blob.js b/polyfill/Blob.js index 384ae8fd9..53662a798 100644 --- a/polyfill/Blob.js +++ b/polyfill/Blob.js @@ -130,6 +130,8 @@ export default class Blob extends EventTarget { // Blob data from file path else if(typeof data === 'string' && data.startsWith('RNFetchBlob-file://')) { log.verbose('create Blob cache file from file path', data) + // set this flag so that we know this blob is a wrapper of an existing file + this._isReference = true this._ref = String(data).replace('RNFetchBlob-file://', '') let orgPath = this._ref if(defer) @@ -282,6 +284,20 @@ export default class Blob extends EventTarget { }) } + safeClose() { + if(this._closed) + return Promise.reject('Blob has been released.') + this._closed = true + if(!this._isReference) { + return fs.unlink(this._ref).catch((err) => { + console.warn(err) + }) + } + else { + return Promise.resolve() + } + } + _invokeOnCreateEvent() { log.verbose('invoke create event', this._onCreated) this._blobCreated = true diff --git a/polyfill/Fetch.js b/polyfill/Fetch.js index 7907f8f17..7be52e084 100644 --- a/polyfill/Fetch.js +++ b/polyfill/Fetch.js @@ -57,24 +57,38 @@ class RNFetchBlobFetchPolyfill { // task is a progress reportable and cancellable Promise, however, // task.then is not, so we have to extend task.then with progress and // cancel function - let task = promise + let progressHandler, uploadHandler, cancelHandler + let statefulPromise = promise .then((body) => { - return RNFetchBlob.config(config) - .fetch(options.method, url, options.headers, body) + let task = RNFetchBlob.config(config) + .fetch(options.method, url, options.headers, body) + if(progressHandler) + task.progress(progressHandler) + if(uploadHandler) + task.uploadProgress(uploadHandler) + if(cancelHandler) + task.cancel() + return task.then((resp) => { + log.verbose('response', resp) + // release blob cache created when sending request + if(blobCache !== null && blobCache instanceof Blob) + blobCache.close() + return Promise.resolve(new RNFetchBlobFetchRepsonse(resp)) + }) }) - let statefulPromise = task.then((resp) => { - log.verbose('response', resp) - // release blob cache created when sending request - if(blobCache !== null && blobCache instanceof Blob) - blobCache.close() - return Promise.resolve(new RNFetchBlobFetchRepsonse(resp)) - }) - // extend task.then progress with report and cancelling functions - statefulPromise.cancel = task.cancel - statefulPromise.progress = task.progress - statefulPromise.uploadProgress = task.uploadProgress + statefulPromise.progress = (fn) => { + progressHandler = fn + } + statefulPromise.uploadProgress = (fn) => { + uploadHandler = fn + } + statefulPromise.cancel = () => { + cancelHandler = true + if(task.cancel) + task.cancel() + } return statefulPromise diff --git a/polyfill/FileReader.js b/polyfill/FileReader.js index 3a4e29618..b72df17f7 100644 --- a/polyfill/FileReader.js +++ b/polyfill/FileReader.js @@ -47,15 +47,15 @@ export default class FileReader extends EventTarget { } abort() { - log.verbose('abort', b, label) + log.verbose('abort') } readAsArrayBuffer(b:Blob) { - log.verbose('readAsArrayBuffer', b, label) + log.verbose('readAsArrayBuffer', b) } readAsBinaryString(b:Blob) { - log.verbose('readAsBinaryString', b, label) + log.verbose('readAsBinaryString', b) } readAsText(b:Blob, label:?string) { @@ -63,7 +63,7 @@ export default class FileReader extends EventTarget { } readAsDataURL(b:Blob) { - log.verbose('readAsDataURL', b, label) + log.verbose('readAsDataURL', b) } dispatchEvent(event, e) { @@ -78,7 +78,7 @@ export default class FileReader extends EventTarget { // getters and setters - get readState() { + get readyState() { return this._readyState } diff --git a/polyfill/XMLHttpRequest.js b/polyfill/XMLHttpRequest.js index 661545906..42c987704 100644 --- a/polyfill/XMLHttpRequest.js +++ b/polyfill/XMLHttpRequest.js @@ -196,11 +196,11 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ }) .fetch(_method, _url, _headers, body) this._task - .stateChange(this._headerReceived.bind(this)) - .uploadProgress(this._uploadProgressEvent.bind(this)) - .progress(this._progressEvent.bind(this)) - .catch(this._onError.bind(this)) - .then(this._onDone.bind(this)) + .stateChange(this._headerReceived) + .uploadProgress(this._uploadProgressEvent) + .progress(this._progressEvent) + .catch(this._onError) + .then(this._onDone) }) } @@ -274,10 +274,10 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ return result.substr(0, result.length-2) } - _headerReceived(e) { + _headerReceived = (e) => { log.debug('header received ', this._task.taskId, e) this.responseURL = this._url - if(e.state === "2") { + if(e.state === "2" && e.taskId === this._task.taskId) { this._responseHeaders = e.headers this._statusText = e.status this._status = Math.floor(e.status) @@ -285,7 +285,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ } } - _uploadProgressEvent(send:number, total:number) { + _uploadProgressEvent = (send:number, total:number) => { if(!this._uploadStarted) { this.upload.dispatchEvent('loadstart') this._uploadStarted = true @@ -295,7 +295,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ this.upload.dispatchEvent('progress', new ProgressEvent(true, send, total)) } - _progressEvent(send:number, total:number, chunk:string) { + _progressEvent = (send:number, total:number, chunk:string) => { log.verbose(this.readyState) if(this._readyState === XMLHttpRequest.HEADERS_RECEIVED) this._dispatchReadStateChange(XMLHttpRequest.LOADING) @@ -310,7 +310,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ this.dispatchEvent('progress', e) } - _onError(err) { + _onError = (err) => { let statusCode = Math.floor(this.status) if(statusCode >= 100 && statusCode !== 408) { return @@ -331,7 +331,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ this.clearEventListeners() } - _onDone(resp) { + _onDone = (resp) => { log.debug('XMLHttpRequest done', this._url, resp, this) this._statusText = this._status let responseDataReady = () => { diff --git a/react-native-fetch-blob.podspec b/react-native-fetch-blob.podspec index 2518b32d5..f702d287b 100644 --- a/react-native-fetch-blob.podspec +++ b/react-native-fetch-blob.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |s| s.name = "react-native-fetch-blob" - s.version = "0.10.3-beta.1" + s.version = "0.10.6" s.summary = "A project committed to make file acess and data transfer easier, effiecient for React Native developers." s.requires_arc = true s.license = 'MIT' s.homepage = 'n/a' s.authors = { "wkh237" => "xeiyan@gmail.com" } - s.source = { :git => "https://github.com/wkh237/react-native-fetch-blob", :tag => 'v0.10.3-beta.1'} + s.source = { :git => "https://github.com/wkh237/react-native-fetch-blob", :tag => 'v0.10.6'} s.source_files = 'ios/**/*.{h,m}' s.platform = :ios, "7.0" s.dependency 'React/Core' diff --git a/utils/uuid.js b/utils/uuid.js index e7d0e300f..f147e1f3e 100644 --- a/utils/uuid.js +++ b/utils/uuid.js @@ -1,6 +1,4 @@ export default function getUUID() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - let r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); - return v.toString(16); - }); + return Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15); }