├── .github ├── ISSUE_TEMPLATE └── PULL_REQUEST_TEMPLATE ├── .gitignore ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── android.js ├── android ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── RNFetchBlob │ │ ├── RNFetchBlob.java │ │ ├── RNFetchBlobBody.java │ │ ├── RNFetchBlobConfig.java │ │ ├── RNFetchBlobConst.java │ │ ├── RNFetchBlobFS.java │ │ ├── RNFetchBlobPackage.java │ │ ├── RNFetchBlobProgressConfig.java │ │ ├── RNFetchBlobReq.java │ │ ├── RNFetchBlobUtils.java │ │ ├── Response │ │ ├── RNFetchBlobDefaultResp.java │ │ └── RNFetchBlobFileResp.java │ │ └── Utils │ │ └── PathResolver.java │ └── res │ └── values │ └── strings.xml ├── class ├── RNFetchBlobFile.js ├── RNFetchBlobReadStream.js ├── RNFetchBlobSession.js ├── RNFetchBlobWriteStream.js └── StatefulPromise.js ├── components ├── Fetch.onPress.js └── Fetch.when.js ├── fs.js ├── img ├── RNFB-Body.png ├── RNFB-Flow-hd.png ├── RNFB-HTTP-flow.png ├── RNFB-flow.png ├── action-menu.png ├── android-notification1.png ├── android-notification2.png ├── download-manager.png ├── ios-1.png ├── ios-2.png ├── ios-3.png ├── ios-4.png ├── ios-5.png ├── issue_57_1.png ├── issue_57_2.png ├── issue_57_3.png ├── performance_1.png ├── performance_encoding.png └── performance_f2f.png ├── index.js ├── ios.js ├── ios ├── IOS7Polyfill.h ├── RNFetchBlob.xcodeproj │ └── project.pbxproj ├── RNFetchBlob │ ├── RNFetchBlob.h │ └── RNFetchBlob.m ├── RNFetchBlobConst.h ├── RNFetchBlobConst.m ├── RNFetchBlobFS.h ├── RNFetchBlobFS.m ├── RNFetchBlobNetwork.h ├── RNFetchBlobNetwork.m ├── RNFetchBlobProgress.h ├── RNFetchBlobProgress.m ├── RNFetchBlobReqBuilder.h └── RNFetchBlobReqBuilder.m ├── json-stream.js ├── lib ├── oboe-browser.js └── oboe-browser.min.js ├── package.json ├── polyfill ├── Blob.js ├── Event.js ├── EventTarget.js ├── Fetch.js ├── File.js ├── FileReader.js ├── ProgressEvent.js ├── XMLHttpRequest.js ├── XMLHttpRequestEventTarget.js └── index.js ├── react-native-fetch-blob.podspec ├── scripts └── prelink.js ├── types.js └── utils ├── log.js ├── unicode.js ├── uri.js └── uuid.js /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | Hi ! Thank you for reporting an issue, but we would like to remind you, we have a trouble shooting page in our wiki. 2 | You may want to take a look on that page or find issues tagged "trouble shooting" :p 3 | 4 | * please provide the version of installed library and RN project. 5 | * a sample code snippet/repository is very helpful to spotting the problem. 6 | * issues which have been tagged as 'needs feedback', will be closed after 2 weeks if receive no feedbacks. 7 | * issues lack of detailed information will be closed without any feedback 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 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 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .imdone 2 | testconfig 3 | RNFetchBlobTest/ 4 | test/test-server/public/* 5 | !test/test-server/public/github.png 6 | 7 | # OSX 8 | # 9 | .DS_Store 10 | 11 | # Xcode 12 | # 13 | build/ 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | *.xccheckout 24 | *.moved-aside 25 | DerivedData 26 | *.hmap 27 | *.ipa 28 | *.xcuserstate 29 | project.xcworkspace 30 | 31 | # Android/IJ 32 | # 33 | .idea 34 | .gradle 35 | local.properties 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | android/app/libs 46 | android/keystores/debug.keystore 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 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. 2 | 3 | Please read the following rules before opening a PR : 4 | 5 | 1. If the PR is offering a feature please make the PR to our "Feature Branch" 0.11.0 6 | 2. Bug fix request to "Bug Fix Branch" 0.10.6 7 | 3. Correct README.md can directly to master 8 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | 960px 2 | Amerrnath 3 | Andreas Amsenius 4 | Andrew Jack 5 | Arthur Ouaki 6 | Ben 7 | Ben Hsieh 8 | Binur Konarbai 9 | Bronco 10 | Chris Sloey 11 | Corentin Smith 12 | Dmitry Petukhov 13 | Dombi Soma Kristóf 14 | Erik Smartt 15 | Evgeniy Baraniuk 16 | Frank van der Hoek 17 | Guy Blank 18 | Jacob Lauritzen 19 | Jeremi Stadler 20 | Jon San Miguel 21 | Juan B. Rodriguez 22 | Kaishley 23 | Martin Giachetti 24 | Max Gurela 25 | Mike Monteith 26 | Naoki AINOYA 27 | Nguyen Cao Nhat Linh 28 | Nick Pomfret 29 | Oliver 30 | Petter Hesselberg 31 | Reza Ghorbani 32 | Simón Gómez 33 | Steve Liles 34 | Tim Suchanek 35 | Yonsh Lin 36 | atlanteh 37 | follower 38 | francisco-sanchez-molina 39 | gferreyra91 40 | hhravn 41 | kejinliang 42 | pedramsaleh 43 | smartt 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 xeiyan@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /android.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import { 6 | NativeModules, 7 | DeviceEventEmitter, 8 | Platform, 9 | NativeAppEventEmitter, 10 | } from 'react-native' 11 | 12 | const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob 13 | 14 | /** 15 | * Send an intent to open the file. 16 | * @param {string]} path Path of the file to be open. 17 | * @param {string} mime MIME type string 18 | * @return {Promise} 19 | */ 20 | function actionViewIntent(path:string, mime:string = 'text/plain') { 21 | if(Platform.OS === 'android') 22 | return RNFetchBlob.actionViewIntent(path, mime) 23 | else 24 | return Promise.reject('RNFetchBlob.android.actionViewIntent only supports Android.') 25 | } 26 | 27 | function getContentIntent(mime:string) { 28 | if(Platform.OS === 'android') 29 | return RNFetchBlob.getContentIntent(mime) 30 | else 31 | return Promise.reject('RNFetchBlob.android.getContentIntent only supports Android.') 32 | } 33 | 34 | function addCompleteDownload(config) { 35 | if(Platform.OS === 'android') 36 | return RNFetchBlob.addCompleteDownload(config) 37 | else 38 | return Promise.reject('RNFetchBlob.android.addCompleteDownload only supports Android.') 39 | } 40 | 41 | 42 | export default { 43 | actionViewIntent, 44 | getContentIntent, 45 | addCompleteDownload 46 | } 47 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | 38 | # Keystore files 39 | *.jks 40 | 41 | # Eclipse Metadata 42 | .metadata/ 43 | # 44 | # Mac OS X clutter 45 | *.DS_Store 46 | # 47 | # Windows clutter 48 | Thumbs.db 49 | # 50 | # # Intellij IDEA (see https://intellij-support.jetbrains.com/entries/23393067) 51 | *.iws 52 | .idea/libraries 53 | .idea/tasks.xml 54 | .idea/vcs.xml 55 | .idea/workspace.xml 56 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | repositories { 4 | mavenCentral() 5 | } 6 | 7 | buildscript { 8 | repositories { 9 | jcenter() 10 | } 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:2.2.3' 13 | } 14 | } 15 | 16 | android { 17 | compileSdkVersion 23 18 | buildToolsVersion "23.0.1" 19 | defaultConfig { 20 | minSdkVersion 16 21 | targetSdkVersion 23 22 | versionCode 1 23 | versionName "1.0" 24 | } 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | productFlavors { 32 | } 33 | } 34 | 35 | dependencies { 36 | compile 'com.facebook.react:react-native:+' 37 | //{RNFetchBlob_PRE_0.28_DEPDENDENCY} 38 | } 39 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 18 12:33:41 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/xeiyan/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/RNFetchBlob.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob; 2 | 3 | import android.app.Activity; 4 | import android.app.DownloadManager; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | 8 | import com.facebook.react.bridge.ActivityEventListener; 9 | import com.facebook.react.bridge.Callback; 10 | import com.facebook.react.bridge.LifecycleEventListener; 11 | import com.facebook.react.bridge.Promise; 12 | import com.facebook.react.bridge.ReactApplicationContext; 13 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 14 | import com.facebook.react.bridge.ReactMethod; 15 | import com.facebook.react.bridge.ReadableArray; 16 | import com.facebook.react.bridge.ReadableMap; 17 | 18 | // Cookies 19 | import com.facebook.react.bridge.WritableMap; 20 | import com.facebook.react.modules.network.ForwardingCookieHandler; 21 | import com.facebook.react.modules.network.CookieJarContainer; 22 | import com.facebook.react.modules.network.OkHttpClientProvider; 23 | import okhttp3.OkHttpClient; 24 | import okhttp3.JavaNetCookieJar; 25 | 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | import java.util.concurrent.LinkedBlockingQueue; 29 | import java.util.concurrent.ThreadPoolExecutor; 30 | import java.util.concurrent.TimeUnit; 31 | 32 | import static android.app.Activity.RESULT_OK; 33 | import static com.RNFetchBlob.RNFetchBlobConst.GET_CONTENT_INTENT; 34 | 35 | public class RNFetchBlob extends ReactContextBaseJavaModule { 36 | 37 | // Cookies 38 | private final ForwardingCookieHandler mCookieHandler; 39 | private final CookieJarContainer mCookieJarContainer; 40 | private final OkHttpClient mClient; 41 | 42 | static ReactApplicationContext RCTContext; 43 | static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); 44 | static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue); 45 | static LinkedBlockingQueue fsTaskQueue = new LinkedBlockingQueue<>(); 46 | static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue); 47 | static public boolean ActionViewVisible = false; 48 | static HashMap promiseTable = new HashMap<>(); 49 | 50 | public RNFetchBlob(ReactApplicationContext reactContext) { 51 | 52 | super(reactContext); 53 | 54 | mClient = OkHttpClientProvider.getOkHttpClient(); 55 | mCookieHandler = new ForwardingCookieHandler(reactContext); 56 | mCookieJarContainer = (CookieJarContainer) mClient.cookieJar(); 57 | mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler)); 58 | 59 | RCTContext = reactContext; 60 | reactContext.addActivityEventListener(new ActivityEventListener() { 61 | @Override 62 | public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { 63 | if(requestCode == GET_CONTENT_INTENT && resultCode == RESULT_OK) { 64 | Uri d = data.getData(); 65 | promiseTable.get(GET_CONTENT_INTENT).resolve(d.toString()); 66 | promiseTable.remove(GET_CONTENT_INTENT); 67 | } 68 | } 69 | 70 | @Override 71 | public void onNewIntent(Intent intent) { 72 | 73 | } 74 | }); 75 | } 76 | 77 | @Override 78 | public String getName() { 79 | return "RNFetchBlob"; 80 | } 81 | 82 | @Override 83 | public Map getConstants() { 84 | return RNFetchBlobFS.getSystemfolders(this.getReactApplicationContext()); 85 | } 86 | 87 | @ReactMethod 88 | public void createFile(final String path, final String content, final String encode, final Callback callback) { 89 | threadPool.execute(new Runnable() { 90 | @Override 91 | public void run() { 92 | RNFetchBlobFS.createFile(path, content, encode, callback); 93 | } 94 | }); 95 | 96 | } 97 | 98 | @ReactMethod 99 | public void actionViewIntent(String path, String mime, final Promise promise) { 100 | 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); 105 | ActionViewVisible = true; 106 | 107 | final LifecycleEventListener listener = new LifecycleEventListener() { 108 | @Override 109 | public void onHostResume() { 110 | if(ActionViewVisible) 111 | promise.resolve(null); 112 | RCTContext.removeLifecycleEventListener(this); 113 | } 114 | 115 | @Override 116 | public void onHostPause() { 117 | 118 | } 119 | 120 | @Override 121 | public void onHostDestroy() { 122 | 123 | } 124 | }; 125 | RCTContext.addLifecycleEventListener(listener); 126 | } catch(Exception ex) { 127 | promise.reject(ex.getLocalizedMessage()); 128 | } 129 | } 130 | 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 143 | public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) { 144 | RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback); 145 | } 146 | 147 | @ReactMethod 148 | public void unlink(String path, Callback callback) { 149 | RNFetchBlobFS.unlink(path, callback); 150 | } 151 | 152 | @ReactMethod 153 | public void mkdir(String path, Callback callback) { 154 | RNFetchBlobFS.mkdir(path, callback); 155 | } 156 | 157 | @ReactMethod 158 | public void exists(String path, Callback callback) { 159 | RNFetchBlobFS.exists(path, callback); 160 | } 161 | 162 | @ReactMethod 163 | public void cp(final String path, final String dest, final Callback callback) { 164 | threadPool.execute(new Runnable() { 165 | @Override 166 | public void run() { 167 | RNFetchBlobFS.cp(path, dest, callback); 168 | } 169 | }); 170 | 171 | } 172 | 173 | @ReactMethod 174 | public void mv(String path, String dest, Callback callback) { 175 | RNFetchBlobFS.mv(path, dest, callback); 176 | } 177 | 178 | @ReactMethod 179 | public void ls(String path, Callback callback) { 180 | RNFetchBlobFS.ls(path, callback); 181 | } 182 | 183 | @ReactMethod 184 | public void writeStream(String path, String encode, boolean append, Callback callback) { 185 | new RNFetchBlobFS(this.getReactApplicationContext()).writeStream(path, encode, append, callback); 186 | } 187 | 188 | @ReactMethod 189 | public void writeChunk(String streamId, String data, Callback callback) { 190 | RNFetchBlobFS.writeChunk(streamId, data, callback); 191 | } 192 | 193 | @ReactMethod 194 | public void closeStream(String streamId, Callback callback) { 195 | RNFetchBlobFS.closeStream(streamId, callback); 196 | } 197 | 198 | @ReactMethod 199 | public void removeSession(ReadableArray paths, Callback callback) { 200 | RNFetchBlobFS.removeSession(paths, callback); 201 | } 202 | 203 | @ReactMethod 204 | public void readFile(final String path, final String encoding, final Promise promise) { 205 | threadPool.execute(new Runnable() { 206 | @Override 207 | public void run() { 208 | RNFetchBlobFS.readFile(path, encoding, promise); 209 | } 210 | }); 211 | } 212 | 213 | @ReactMethod 214 | public void writeFileArray(final String path, final ReadableArray data, final boolean append, final Promise promise) { 215 | threadPool.execute(new Runnable() { 216 | @Override 217 | public void run() { 218 | RNFetchBlobFS.writeFile(path, data, append, promise); 219 | } 220 | }); 221 | } 222 | 223 | @ReactMethod 224 | public void writeFile(final String path, final String encoding, final String data, final boolean append, final Promise promise) { 225 | threadPool.execute(new Runnable() { 226 | @Override 227 | public void run() { 228 | RNFetchBlobFS.writeFile(path, encoding, data, append, promise); 229 | } 230 | }); 231 | 232 | } 233 | 234 | @ReactMethod 235 | public void lstat(String path, Callback callback) { 236 | RNFetchBlobFS.lstat(path, callback); 237 | } 238 | 239 | @ReactMethod 240 | public void stat(String path, Callback callback) { 241 | RNFetchBlobFS.stat(path, callback); 242 | } 243 | 244 | @ReactMethod 245 | public void scanFile(final ReadableArray pairs, final Callback callback) { 246 | final ReactApplicationContext ctx = this.getReactApplicationContext(); 247 | threadPool.execute(new Runnable() { 248 | @Override 249 | public void run() { 250 | int size = pairs.size(); 251 | String [] p = new String[size]; 252 | String [] m = new String[size]; 253 | for(int i=0;i fields = countFormDataLength(); 187 | ReactApplicationContext ctx = RNFetchBlob.RCTContext; 188 | 189 | for(int i = 0;i < fields.size(); i++) { 190 | FormField field = fields.get(i); 191 | String data = field.data; 192 | String name = field.name; 193 | // skip invalid fields 194 | if(name == null || data == null) 195 | continue; 196 | // form begin 197 | String header = "--" + boundary + "\r\n"; 198 | if (field.filename != null) { 199 | header += "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + field.filename + "\"\r\n"; 200 | header += "Content-Type: " + field.mime + "\r\n\r\n"; 201 | os.write(header.getBytes()); 202 | // file field header end 203 | // upload from storage 204 | if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) { 205 | String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length()); 206 | orgPath = RNFetchBlobFS.normalizePath(orgPath); 207 | // path starts with content:// 208 | if (RNFetchBlobFS.isAsset(orgPath)) { 209 | try { 210 | String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""); 211 | InputStream in = ctx.getAssets().open(assetName); 212 | pipeStreamToFileStream(in, os); 213 | } catch (IOException e) { 214 | RNFetchBlobUtils.emitWarningEvent("Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() ); 215 | } 216 | } 217 | // data from normal files 218 | else { 219 | File file = new File(RNFetchBlobFS.normalizePath(orgPath)); 220 | if(file.exists()) { 221 | FileInputStream fs = new FileInputStream(file); 222 | pipeStreamToFileStream(fs, os); 223 | } 224 | else { 225 | RNFetchBlobUtils.emitWarningEvent("Failed to create form data from path :" + orgPath + ", file not exists."); 226 | } 227 | } 228 | } 229 | // base64 embedded file content 230 | else { 231 | byte[] b = Base64.decode(data, 0); 232 | os.write(b); 233 | } 234 | 235 | } 236 | // data field 237 | else { 238 | header += "Content-Disposition: form-data; name=\"" + name + "\"\r\n"; 239 | header += "Content-Type: " + field.mime + "\r\n\r\n"; 240 | os.write(header.getBytes()); 241 | byte[] fieldData = field.data.getBytes(); 242 | os.write(fieldData); 243 | } 244 | // form end 245 | os.write("\r\n".getBytes()); 246 | } 247 | // close the form 248 | byte[] end = ("--" + boundary + "--\r\n").getBytes(); 249 | os.write(end); 250 | os.flush(); 251 | os.close(); 252 | return outputFile; 253 | } 254 | 255 | /** 256 | * Pipe input stream to request body output stream 257 | * @param stream The input stream 258 | * @param sink The request body buffer sink 259 | * @throws IOException 260 | */ 261 | private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws Exception { 262 | 263 | byte [] chunk = new byte[10240]; 264 | int totalWritten = 0; 265 | int read; 266 | 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 | } 272 | } 273 | stream.close(); 274 | } 275 | 276 | /** 277 | * Pipe input stream to a file 278 | * @param is The input stream 279 | * @param os The output stream to a file 280 | * @throws IOException 281 | */ 282 | private void pipeStreamToFileStream(InputStream is, FileOutputStream os) throws IOException { 283 | 284 | byte[] buf = new byte[10240]; 285 | int len; 286 | while ((len = is.read(buf)) > 0) { 287 | os.write(buf, 0, len); 288 | } 289 | is.close(); 290 | } 291 | 292 | /** 293 | * Compute approximate content length for form data 294 | * @return 295 | */ 296 | private ArrayList countFormDataLength() { 297 | long total = 0; 298 | ArrayList list = new ArrayList<>(); 299 | ReactApplicationContext ctx = RNFetchBlob.RCTContext; 300 | for(int i = 0;i < form.size(); i++) { 301 | FormField field = new FormField(form.getMap(i)); 302 | list.add(field); 303 | String data = field.data; 304 | if(data == null) { 305 | RNFetchBlobUtils.emitWarningEvent("RNFetchBlob multipart request builder has found a field without `data` property, the field `"+ field.name +"` will be removed implicitly."); 306 | } 307 | else if (field.filename != null) { 308 | // upload from storage 309 | if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) { 310 | String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length()); 311 | orgPath = RNFetchBlobFS.normalizePath(orgPath); 312 | // path starts with asset:// 313 | if (RNFetchBlobFS.isAsset(orgPath)) { 314 | try { 315 | String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""); 316 | long length = ctx.getAssets().open(assetName).available(); 317 | total += length; 318 | } catch (IOException e) { 319 | RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage()); 320 | } 321 | } 322 | // general files 323 | else { 324 | File file = new File(RNFetchBlobFS.normalizePath(orgPath)); 325 | total += file.length(); 326 | } 327 | } 328 | // base64 embedded file content 329 | else { 330 | byte[] bytes = Base64.decode(data, 0); 331 | total += bytes.length; 332 | } 333 | } 334 | // data field 335 | else { 336 | total += field.data != null ? field.data.getBytes().length : 0; 337 | } 338 | } 339 | contentLength = total; 340 | return list; 341 | } 342 | 343 | /** 344 | * Since ReadableMap could only be access once, we have to store the field into a map for 345 | * repeatedly access. 346 | */ 347 | private class FormField { 348 | public String name; 349 | public String filename; 350 | public String mime; 351 | public String data; 352 | 353 | public FormField(ReadableMap rawData) { 354 | if(rawData.hasKey("name")) 355 | name = rawData.getString("name"); 356 | if(rawData.hasKey("filename")) 357 | filename = rawData.getString("filename"); 358 | if(rawData.hasKey("type")) 359 | mime = rawData.getString("type"); 360 | else { 361 | mime = filename == null ? "text/plain" : "application/octet-stream"; 362 | } 363 | if(rawData.hasKey("data")) { 364 | data = rawData.getString("data"); 365 | } 366 | } 367 | } 368 | 369 | /** 370 | * Emit progress event 371 | * @param written 372 | */ 373 | private void emitUploadProgress(int written) { 374 | RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId); 375 | if(config != null && contentLength != 0 && config.shouldReport((float)written/contentLength)) { 376 | WritableMap args = Arguments.createMap(); 377 | args.putString("taskId", mTaskId); 378 | args.putString("written", String.valueOf(written)); 379 | args.putString("total", String.valueOf(contentLength)); 380 | 381 | // emit event to js context 382 | RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 383 | .emit(RNFetchBlobConst.EVENT_UPLOAD_PROGRESS, args); 384 | } 385 | } 386 | 387 | } 388 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob; 2 | 3 | import com.facebook.react.bridge.ReadableArray; 4 | import com.facebook.react.bridge.ReadableMap; 5 | 6 | import java.util.HashMap; 7 | 8 | 9 | public class RNFetchBlobConfig { 10 | 11 | public Boolean fileCache; 12 | public String path; 13 | public String appendExt; 14 | public ReadableMap addAndroidDownloads; 15 | public Boolean trusty; 16 | public String key; 17 | public String mime; 18 | public Boolean auto; 19 | public Boolean overwrite = true; 20 | public long timeout = 60000; 21 | public Boolean increment = false; 22 | public Boolean followRedirect = true; 23 | public ReadableArray binaryContentTypes = null; 24 | 25 | RNFetchBlobConfig(ReadableMap options) { 26 | if(options == null) 27 | return; 28 | this.fileCache = options.hasKey("fileCache") ? options.getBoolean("fileCache") : false; 29 | this.path = options.hasKey("path") ? options.getString("path") : null; 30 | this.appendExt = options.hasKey("appendExt") ? options.getString("appendExt") : ""; 31 | this.trusty = options.hasKey("trusty") ? options.getBoolean("trusty") : false; 32 | if(options.hasKey("addAndroidDownloads")) { 33 | this.addAndroidDownloads = options.getMap("addAndroidDownloads"); 34 | } 35 | if(options.hasKey("binaryContentTypes")) 36 | this.binaryContentTypes = options.getArray("binaryContentTypes"); 37 | if(this.path != null && path.toLowerCase().contains("?append=true")) { 38 | this.overwrite = false; 39 | } 40 | if(options.hasKey("overwrite")) 41 | this.overwrite = options.getBoolean("overwrite"); 42 | if(options.hasKey("followRedirect")) { 43 | this.followRedirect = options.getBoolean("followRedirect"); 44 | } 45 | this.key = options.hasKey("key") ? options.getString("key") : null; 46 | this.mime = options.hasKey("contentType") ? options.getString("contentType") : null; 47 | this.increment = options.hasKey("increment") ? options.getBoolean("increment") : false; 48 | this.auto = options.hasKey("auto") ? options.getBoolean("auto") : false; 49 | if(options.hasKey("timeout")) { 50 | this.timeout = options.getInt("timeout"); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/RNFetchBlobConst.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob; 2 | 3 | 4 | public class RNFetchBlobConst { 5 | public static final String EVENT_UPLOAD_PROGRESS = "RNFetchBlobProgress-upload"; 6 | public static final String EVENT_PROGRESS = "RNFetchBlobProgress"; 7 | public static final String EVENT_HTTP_STATE = "RNFetchBlobState"; 8 | public static final String EVENT_MESSAGE = "RNFetchBlobMessage"; 9 | public static final String FILE_PREFIX = "RNFetchBlob-file://"; 10 | public static final String FILE_PREFIX_BUNDLE_ASSET = "bundle-assets://"; 11 | public static final String FILE_PREFIX_CONTENT = "content://"; 12 | public static final String DATA_ENCODE_URI = "uri"; 13 | public static final String RNFB_RESPONSE_BASE64 = "base64"; 14 | public static final String RNFB_RESPONSE_UTF8 = "utf8"; 15 | public static final String RNFB_RESPONSE_PATH = "path"; 16 | public static final Integer GET_CONTENT_INTENT = 99900; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | 14 | public class RNFetchBlobPackage implements ReactPackage { 15 | 16 | @Override 17 | public List createNativeModules(ReactApplicationContext reactContext) { 18 | List modules = new ArrayList<>(); 19 | modules.add(new RNFetchBlob(reactContext)); 20 | return modules; 21 | } 22 | 23 | public List> createJSModules() { 24 | return Collections.emptyList(); 25 | } 26 | 27 | @Override 28 | public List createViewManagers(ReactApplicationContext reactContext) { 29 | return Collections.emptyList(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/RNFetchBlobProgressConfig.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob; 2 | 3 | /** 4 | * Created by wkh237 on 2016/9/24. 5 | */ 6 | public class RNFetchBlobProgressConfig { 7 | 8 | public enum ReportType { 9 | Upload, 10 | Download 11 | }; 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; 19 | 20 | RNFetchBlobProgressConfig(boolean report, int interval, int count, ReportType type) { 21 | this.enable = report; 22 | this.interval = interval; 23 | this.type = type; 24 | this.count = count; 25 | } 26 | 27 | public boolean shouldReport(float progress) { 28 | boolean checkCount = true; 29 | if(count > 0 && progress > 0) 30 | checkCount = Math.floor(progress*count)> tick; 31 | boolean result = (System.currentTimeMillis() - lastTick> interval) && enable && checkCount; 32 | if(result) { 33 | tick++; 34 | lastTick = System.currentTimeMillis(); 35 | } 36 | return result; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/RNFetchBlobUtils.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.WritableMap; 5 | import com.facebook.react.modules.core.DeviceEventManagerModule; 6 | 7 | import java.security.MessageDigest; 8 | import java.security.cert.CertificateException; 9 | 10 | import javax.net.ssl.HostnameVerifier; 11 | import javax.net.ssl.SSLContext; 12 | import javax.net.ssl.SSLSession; 13 | import javax.net.ssl.SSLSocketFactory; 14 | import javax.net.ssl.TrustManager; 15 | import javax.net.ssl.X509TrustManager; 16 | 17 | import okhttp3.OkHttpClient; 18 | 19 | 20 | public class RNFetchBlobUtils { 21 | 22 | public static String getMD5(String input) { 23 | String result = null; 24 | 25 | try { 26 | MessageDigest md = MessageDigest.getInstance("MD5"); 27 | md.update(input.getBytes()); 28 | byte[] digest = md.digest(); 29 | 30 | StringBuffer sb = new StringBuffer(); 31 | 32 | for (byte b : digest) { 33 | sb.append(String.format("%02x", b & 0xff)); 34 | } 35 | 36 | result = sb.toString(); 37 | } catch (Exception ex) { 38 | ex.printStackTrace(); 39 | } finally { 40 | return result; 41 | } 42 | 43 | } 44 | 45 | public static void emitWarningEvent(String data) { 46 | WritableMap args = Arguments.createMap(); 47 | args.putString("event", "warn"); 48 | args.putString("detail", data); 49 | 50 | // emit event to js context 51 | RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 52 | .emit(RNFetchBlobConst.EVENT_MESSAGE, args); 53 | } 54 | 55 | public static OkHttpClient.Builder getUnsafeOkHttpClient(OkHttpClient client) { 56 | try { 57 | // Create a trust manager that does not validate certificate chains 58 | final TrustManager[] trustAllCerts = new TrustManager[]{ 59 | new X509TrustManager() { 60 | @Override 61 | public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { 62 | } 63 | 64 | @Override 65 | public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { 66 | } 67 | 68 | @Override 69 | public java.security.cert.X509Certificate[] getAcceptedIssuers() { 70 | return new java.security.cert.X509Certificate[]{}; 71 | } 72 | } 73 | }; 74 | 75 | // Install the all-trusting trust manager 76 | final SSLContext sslContext = SSLContext.getInstance("SSL"); 77 | sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); 78 | // Create an ssl socket factory with our all-trusting manager 79 | final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); 80 | 81 | OkHttpClient.Builder builder = client.newBuilder(); 82 | builder.sslSocketFactory(sslSocketFactory); 83 | builder.hostnameVerifier(new HostnameVerifier() { 84 | @Override 85 | public boolean verify(String hostname, SSLSession session) { 86 | return true; 87 | } 88 | }); 89 | 90 | return builder; 91 | } catch (Exception e) { 92 | throw new RuntimeException(e); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobDefaultResp.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob.Response; 2 | 3 | import com.RNFetchBlob.RNFetchBlobConst; 4 | import com.RNFetchBlob.RNFetchBlobProgressConfig; 5 | import com.RNFetchBlob.RNFetchBlobReq; 6 | import com.facebook.react.bridge.Arguments; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.WritableMap; 9 | import com.facebook.react.modules.core.DeviceEventManagerModule; 10 | 11 | import java.io.IOException; 12 | import java.nio.charset.Charset; 13 | 14 | import okhttp3.MediaType; 15 | import okhttp3.ResponseBody; 16 | import okio.Buffer; 17 | import okio.BufferedSource; 18 | import okio.Okio; 19 | import okio.Source; 20 | import okio.Timeout; 21 | 22 | /** 23 | * Created by wkh237 on 2016/7/11. 24 | */ 25 | public class RNFetchBlobDefaultResp extends ResponseBody { 26 | 27 | String mTaskId; 28 | ReactApplicationContext rctContext; 29 | ResponseBody originalBody; 30 | boolean isIncrement = false; 31 | 32 | public RNFetchBlobDefaultResp(ReactApplicationContext ctx, String taskId, ResponseBody body, boolean isIncrement) { 33 | this.rctContext = ctx; 34 | this.mTaskId = taskId; 35 | this.originalBody = body; 36 | this.isIncrement = isIncrement; 37 | } 38 | 39 | @Override 40 | public MediaType contentType() { 41 | return originalBody.contentType(); 42 | } 43 | 44 | @Override 45 | public long contentLength() { 46 | return originalBody.contentLength(); 47 | } 48 | 49 | @Override 50 | public BufferedSource source() { 51 | return Okio.buffer(new ProgressReportingSource(originalBody.source())); 52 | } 53 | 54 | private class ProgressReportingSource implements Source { 55 | 56 | BufferedSource mOriginalSource; 57 | long bytesRead = 0; 58 | 59 | ProgressReportingSource(BufferedSource originalSource) { 60 | mOriginalSource = originalSource; 61 | } 62 | 63 | @Override 64 | public long read(Buffer sink, long byteCount) throws IOException { 65 | 66 | long read = mOriginalSource.read(sink, byteCount); 67 | bytesRead += read > 0 ? read : 0; 68 | RNFetchBlobProgressConfig reportConfig = RNFetchBlobReq.getReportProgress(mTaskId); 69 | long cLen = contentLength(); 70 | if(reportConfig != null && cLen != 0 && reportConfig.shouldReport(bytesRead/contentLength())) { 71 | WritableMap args = Arguments.createMap(); 72 | args.putString("taskId", mTaskId); 73 | args.putString("written", String.valueOf(bytesRead)); 74 | args.putString("total", String.valueOf(contentLength())); 75 | if(isIncrement) { 76 | args.putString("chunk", sink.readString(Charset.defaultCharset())); 77 | } 78 | else { 79 | args.putString("chunk", ""); 80 | } 81 | 82 | rctContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 83 | .emit(RNFetchBlobConst.EVENT_PROGRESS, args); 84 | } 85 | return read; 86 | } 87 | 88 | @Override 89 | public Timeout timeout() { 90 | return null; 91 | } 92 | 93 | @Override 94 | public void close() throws IOException { 95 | 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob.Response; 2 | 3 | import android.util.Log; 4 | 5 | import com.RNFetchBlob.RNFetchBlobConst; 6 | import com.RNFetchBlob.RNFetchBlobProgressConfig; 7 | import com.RNFetchBlob.RNFetchBlobReq; 8 | import com.facebook.react.bridge.Arguments; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.bridge.WritableMap; 11 | import com.facebook.react.modules.core.DeviceEventManagerModule; 12 | 13 | import java.io.File; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | 17 | import okhttp3.MediaType; 18 | import okhttp3.ResponseBody; 19 | import okio.Buffer; 20 | import okio.BufferedSource; 21 | import okio.Okio; 22 | import okio.Source; 23 | import okio.Timeout; 24 | 25 | /** 26 | * Created by wkh237 on 2016/7/11. 27 | */ 28 | public class RNFetchBlobFileResp extends ResponseBody { 29 | 30 | String mTaskId; 31 | ResponseBody originalBody; 32 | String mPath; 33 | long bytesDownloaded = 0; 34 | ReactApplicationContext rctContext; 35 | FileOutputStream ofStream; 36 | 37 | public RNFetchBlobFileResp(ReactApplicationContext ctx, String taskId, ResponseBody body, String path, boolean overwrite) throws IOException { 38 | super(); 39 | this.rctContext = ctx; 40 | this.mTaskId = taskId; 41 | this.originalBody = body; 42 | assert path != null; 43 | this.mPath = path; 44 | if (path != null) { 45 | boolean appendToExistingFile = !overwrite; 46 | path = path.replace("?append=true", ""); 47 | mPath = path; 48 | File f = new File(path); 49 | 50 | File parent = f.getParentFile(); 51 | if(!parent.exists() && !parent.mkdirs()){ 52 | throw new IllegalStateException("Couldn't create dir: " + parent); 53 | } 54 | 55 | if(f.exists() == false) 56 | f.createNewFile(); 57 | ofStream = new FileOutputStream(new File(path), appendToExistingFile); 58 | } 59 | } 60 | 61 | @Override 62 | public MediaType contentType() { 63 | return originalBody.contentType(); 64 | } 65 | 66 | @Override 67 | public long contentLength() { 68 | return originalBody.contentLength(); 69 | } 70 | 71 | @Override 72 | public BufferedSource source() { 73 | ProgressReportingSource countable = new ProgressReportingSource(); 74 | return Okio.buffer(countable); 75 | } 76 | 77 | private class ProgressReportingSource implements Source { 78 | @Override 79 | public long read(Buffer sink, long byteCount) throws IOException { 80 | try { 81 | byte[] bytes = new byte[(int) byteCount]; 82 | long read = originalBody.byteStream().read(bytes, 0, (int) byteCount); 83 | bytesDownloaded += read > 0 ? read : 0; 84 | if (read > 0) { 85 | ofStream.write(bytes, 0, (int) read); 86 | } 87 | RNFetchBlobProgressConfig reportConfig = RNFetchBlobReq.getReportProgress(mTaskId); 88 | if (reportConfig != null && contentLength() != 0 &&reportConfig.shouldReport(bytesDownloaded / contentLength())) { 89 | WritableMap args = Arguments.createMap(); 90 | args.putString("taskId", mTaskId); 91 | args.putString("written", String.valueOf(bytesDownloaded)); 92 | args.putString("total", String.valueOf(contentLength())); 93 | rctContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 94 | .emit(RNFetchBlobConst.EVENT_PROGRESS, args); 95 | } 96 | return read; 97 | } catch(Exception ex) { 98 | return -1; 99 | } 100 | } 101 | 102 | @Override 103 | public Timeout timeout() { 104 | return null; 105 | } 106 | 107 | @Override 108 | public void close() throws IOException { 109 | ofStream.close(); 110 | 111 | } 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob.Utils; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.net.Uri; 6 | import android.os.Build; 7 | import android.provider.DocumentsContract; 8 | import android.provider.MediaStore; 9 | import android.content.ContentUris; 10 | import android.os.Environment; 11 | import android.content.ContentResolver; 12 | import com.RNFetchBlob.RNFetchBlobUtils; 13 | import java.io.File; 14 | import java.io.InputStream; 15 | import java.io.FileOutputStream; 16 | 17 | public class PathResolver { 18 | public static String getRealPathFromURI(final Context context, final Uri uri) { 19 | 20 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 21 | 22 | // DocumentProvider 23 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 24 | // ExternalStorageProvider 25 | if (isExternalStorageDocument(uri)) { 26 | final String docId = DocumentsContract.getDocumentId(uri); 27 | final String[] split = docId.split(":"); 28 | final String type = split[0]; 29 | 30 | if ("primary".equalsIgnoreCase(type)) { 31 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 32 | } 33 | 34 | // TODO handle non-primary volumes 35 | } 36 | // DownloadsProvider 37 | else if (isDownloadsDocument(uri)) { 38 | 39 | final String id = DocumentsContract.getDocumentId(uri); 40 | final Uri contentUri = ContentUris.withAppendedId( 41 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 42 | 43 | return getDataColumn(context, contentUri, null, null); 44 | } 45 | // MediaProvider 46 | else if (isMediaDocument(uri)) { 47 | final String docId = DocumentsContract.getDocumentId(uri); 48 | final String[] split = docId.split(":"); 49 | final String type = split[0]; 50 | 51 | Uri contentUri = null; 52 | if ("image".equals(type)) { 53 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 54 | } else if ("video".equals(type)) { 55 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 56 | } else if ("audio".equals(type)) { 57 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 58 | } 59 | 60 | final String selection = "_id=?"; 61 | final String[] selectionArgs = new String[] { 62 | split[1] 63 | }; 64 | 65 | return getDataColumn(context, contentUri, selection, selectionArgs); 66 | } 67 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 68 | 69 | // Return the remote address 70 | if (isGooglePhotosUri(uri)) 71 | return uri.getLastPathSegment(); 72 | 73 | return getDataColumn(context, uri, null, null); 74 | } 75 | // Other Providers 76 | else{ 77 | try { 78 | InputStream attachment = context.getContentResolver().openInputStream(uri); 79 | if (attachment != null) { 80 | String filename = getContentName(context.getContentResolver(), uri); 81 | if (filename != null) { 82 | File file = new File(context.getCacheDir(), filename); 83 | FileOutputStream tmp = new FileOutputStream(file); 84 | byte[] buffer = new byte[1024]; 85 | while (attachment.read(buffer) > 0) { 86 | tmp.write(buffer); 87 | } 88 | tmp.close(); 89 | attachment.close(); 90 | return file.getAbsolutePath(); 91 | } 92 | } 93 | } catch (Exception e) { 94 | RNFetchBlobUtils.emitWarningEvent(e.toString()); 95 | return null; 96 | } 97 | } 98 | } 99 | // MediaStore (and general) 100 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 101 | 102 | // Return the remote address 103 | if (isGooglePhotosUri(uri)) 104 | return uri.getLastPathSegment(); 105 | 106 | return getDataColumn(context, uri, null, null); 107 | } 108 | // File 109 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 110 | return uri.getPath(); 111 | } 112 | 113 | return null; 114 | } 115 | 116 | private static String getContentName(ContentResolver resolver, Uri uri) { 117 | Cursor cursor = resolver.query(uri, null, null, null, null); 118 | cursor.moveToFirst(); 119 | int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); 120 | if (nameIndex >= 0) { 121 | String name = cursor.getString(nameIndex); 122 | cursor.close(); 123 | return name; 124 | } 125 | return null; 126 | } 127 | 128 | /** 129 | * Get the value of the data column for this Uri. This is useful for 130 | * MediaStore Uris, and other file-based ContentProviders. 131 | * 132 | * @param context The context. 133 | * @param uri The Uri to query. 134 | * @param selection (Optional) Filter used in the query. 135 | * @param selectionArgs (Optional) Selection arguments used in the query. 136 | * @return The value of the _data column, which is typically a file path. 137 | */ 138 | public static String getDataColumn(Context context, Uri uri, String selection, 139 | String[] selectionArgs) { 140 | 141 | Cursor cursor = null; 142 | String result = null; 143 | final String column = "_data"; 144 | final String[] projection = { 145 | column 146 | }; 147 | 148 | try { 149 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 150 | null); 151 | if (cursor != null && cursor.moveToFirst()) { 152 | final int index = cursor.getColumnIndexOrThrow(column); 153 | result = cursor.getString(index); 154 | } 155 | } 156 | catch (Exception ex) { 157 | ex.printStackTrace(); 158 | return null; 159 | } 160 | finally { 161 | if (cursor != null) 162 | cursor.close(); 163 | } 164 | return result; 165 | } 166 | 167 | 168 | /** 169 | * @param uri The Uri to check. 170 | * @return Whether the Uri authority is ExternalStorageProvider. 171 | */ 172 | public static boolean isExternalStorageDocument(Uri uri) { 173 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 174 | } 175 | 176 | /** 177 | * @param uri The Uri to check. 178 | * @return Whether the Uri authority is DownloadsProvider. 179 | */ 180 | public static boolean isDownloadsDocument(Uri uri) { 181 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 182 | } 183 | 184 | /** 185 | * @param uri The Uri to check. 186 | * @return Whether the Uri authority is MediaProvider. 187 | */ 188 | public static boolean isMediaDocument(Uri uri) { 189 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 190 | } 191 | 192 | /** 193 | * @param uri The Uri to check. 194 | * @return Whether the Uri authority is Google Photos. 195 | */ 196 | public static boolean isGooglePhotosUri(Uri uri) { 197 | return "com.google.android.apps.photos.content".equals(uri.getAuthority()); 198 | } 199 | 200 | } -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | react-native-fetch-blob 3 | 4 | -------------------------------------------------------------------------------- /class/RNFetchBlobFile.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | 6 | import { 7 | NativeModules, 8 | DeviceEventEmitter, 9 | NativeAppEventEmitter, 10 | } from 'react-native' 11 | 12 | const RNFetchBlob = NativeModules.RNFetchBlob 13 | const emitter = DeviceEventEmitter 14 | 15 | export default class RNFetchBlobFile { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /class/RNFetchBlobReadStream.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import { 6 | NativeModules, 7 | DeviceEventEmitter, 8 | NativeAppEventEmitter, 9 | } from 'react-native' 10 | import UUID from '../utils/uuid' 11 | 12 | const RNFetchBlob = NativeModules.RNFetchBlob 13 | const emitter = DeviceEventEmitter 14 | 15 | export default class RNFetchBlobReadStream { 16 | 17 | path : string; 18 | encoding : 'utf8' | 'ascii' | 'base64'; 19 | bufferSize : ?number; 20 | closed : boolean; 21 | tick : number = 10; 22 | 23 | constructor(path:string, encoding:string, bufferSize?:?number, tick:number) { 24 | if(!path) 25 | throw Error('RNFetchBlob could not open file stream with empty `path`') 26 | this.encoding = encoding || 'utf8' 27 | this.bufferSize = bufferSize 28 | this.path = path 29 | this.closed = false 30 | this.tick = tick 31 | this._onData = () => {} 32 | this._onEnd = () => {} 33 | this._onError = () => {} 34 | this.streamId = 'RNFBRS'+ UUID() 35 | 36 | // register for file stream event 37 | let subscription = emitter.addListener(this.streamId, (e) => { 38 | let {event, detail} = e 39 | if(this._onData && event === 'data') { 40 | this._onData(detail) 41 | return 42 | } 43 | else if (this._onEnd && event === 'end') { 44 | this._onEnd(detail) 45 | } 46 | else { 47 | if(this._onError) 48 | this._onError(detail) 49 | else 50 | throw new Error(detail) 51 | } 52 | // when stream closed or error, remove event handler 53 | if (event === 'error' || event === 'end') { 54 | subscription.remove() 55 | this.closed = true 56 | } 57 | }) 58 | 59 | } 60 | 61 | open() { 62 | if(!this.closed) 63 | RNFetchBlob.readStream(this.path, this.encoding, this.bufferSize || 10240 , this.tick || -1, this.streamId) 64 | else 65 | throw new Error('Stream closed') 66 | } 67 | 68 | onData(fn:() => void) { 69 | this._onData = fn 70 | } 71 | 72 | onError(fn) { 73 | this._onError = fn 74 | } 75 | 76 | onEnd (fn) { 77 | this._onEnd = fn 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /class/RNFetchBlobSession.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import { 6 | NativeModules, 7 | DeviceEventEmitter, 8 | NativeAppEventEmitter, 9 | } from 'react-native' 10 | 11 | const RNFetchBlob = NativeModules.RNFetchBlob 12 | const emitter = DeviceEventEmitter 13 | 14 | let sessions = {} 15 | 16 | export default class RNFetchBlobSession { 17 | 18 | add : (path:string) => RNFetchBlobSession; 19 | remove : (path:string) => RNFetchBlobSession; 20 | dispose : () => Promise; 21 | list : () => Array; 22 | name : string; 23 | 24 | static getSession(name:string):any { 25 | return sessions[name] 26 | } 27 | 28 | static setSession(name:string, val:any) { 29 | sessions[name] = val 30 | } 31 | 32 | static removeSession(name:string) { 33 | delete sessions[name] 34 | } 35 | 36 | constructor(name:string, list:Array) { 37 | this.name = name 38 | if(!sessions[name]) { 39 | if(Array.isArray(list)) 40 | sessions[name] = list 41 | else 42 | sessions[name] = [] 43 | } 44 | } 45 | 46 | add(path:string):RNFetchBlobSession { 47 | sessions[this.name].push(path) 48 | return this 49 | } 50 | 51 | remove(path:string):RNFetchBlobSession { 52 | let list = sessions[this.name] 53 | for(let i in list) { 54 | if(list[i] === path) { 55 | sessions[this.name].splice(i, 1) 56 | break; 57 | } 58 | } 59 | return this 60 | } 61 | 62 | list():Array { 63 | return sessions[this.name] 64 | } 65 | 66 | dispose():Promise { 67 | return new Promise((resolve, reject) => { 68 | RNFetchBlob.removeSession(sessions[this.name], (err) => { 69 | if(err) 70 | reject(err) 71 | else { 72 | delete sessions[this.name] 73 | resolve() 74 | } 75 | }) 76 | }) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /class/RNFetchBlobWriteStream.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import { 6 | NativeModules, 7 | DeviceEventEmitter, 8 | NativeAppEventEmitter, 9 | } from 'react-native' 10 | 11 | const RNFetchBlob = NativeModules.RNFetchBlob 12 | const emitter = DeviceEventEmitter 13 | 14 | export default class RNFetchBlobWriteStream { 15 | 16 | id : string; 17 | encoding : string; 18 | append : bool; 19 | 20 | constructor(streamId:string, encoding:string, append:string) { 21 | this.id = streamId 22 | this.encoding = encoding 23 | this.append = append 24 | } 25 | 26 | write(data:string) { 27 | return new Promise((resolve, reject) => { 28 | try { 29 | let method = this.encoding === 'ascii' ? 'writeArrayChunk' : 'writeChunk' 30 | if(this.encoding.toLocaleLowerCase() === 'ascii' && !Array.isArray(data)) { 31 | reject('ascii input data must be an Array') 32 | return 33 | } 34 | RNFetchBlob[method](this.id, data, (error) => { 35 | if(error) 36 | reject(error) 37 | else 38 | resolve() 39 | }) 40 | } catch(err) { 41 | reject(err) 42 | } 43 | }) 44 | } 45 | 46 | close() { 47 | return new Promise((resolve, reject) => { 48 | try { 49 | RNFetchBlob.closeStream(this.id, () => { 50 | resolve() 51 | }) 52 | } catch (err) { 53 | reject(err) 54 | } 55 | }) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /class/StatefulPromise.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | export default class StatefulPromise extends Promise { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /components/Fetch.onPress.js: -------------------------------------------------------------------------------- 1 | // press button to login 2 | {/* 7 | 8 | Click to Login 9 | 10 | */} 11 | -------------------------------------------------------------------------------- /components/Fetch.when.js: -------------------------------------------------------------------------------- 1 | // placeholder 2 | {/* { this.setState({avatar : data.path()}) }} 5 | onError={this.showError}> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | */} 16 | -------------------------------------------------------------------------------- /fs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import { 6 | NativeModules, 7 | DeviceEventEmitter, 8 | Platform, 9 | NativeAppEventEmitter, 10 | } from 'react-native' 11 | import RNFetchBlobSession from './class/RNFetchBlobSession' 12 | import RNFetchBlobWriteStream from './class/RNFetchBlobWriteStream' 13 | import RNFetchBlobReadStream from './class/RNFetchBlobReadStream' 14 | 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 23 | 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 35 | } 36 | 37 | /** 38 | * Get a file cache session 39 | * @param {string} name Stream ID 40 | * @return {RNFetchBlobSession} 41 | */ 42 | function session(name:string):RNFetchBlobSession { 43 | let s = RNFetchBlobSession.getSession(name) 44 | if(s) 45 | return new RNFetchBlobSession(name) 46 | else { 47 | RNFetchBlobSession.setSession(name, []) 48 | return new RNFetchBlobSession(name, []) 49 | } 50 | } 51 | 52 | function asset(path:string):string { 53 | if(Platform.OS === 'ios') { 54 | // path from camera roll 55 | if(/^assets-library\:\/\//.test(path)) 56 | return path 57 | } 58 | return 'bundle-assets://' + path 59 | } 60 | 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 | }) 80 | } 81 | 82 | /** 83 | * Create write stream to a file. 84 | * @param {string} path Target path of file stream. 85 | * @param {string} encoding Encoding of input data. 86 | * @param {bool} append A flag represent if data append to existing ones. 87 | * @return {Promise} A promise resolves a `WriteStream` object. 88 | */ 89 | function writeStream( 90 | path : string, 91 | encoding : 'utf8' | 'ascii' | 'base64', 92 | append? : ?bool, 93 | ):Promise { 94 | if(!path) 95 | throw Error('RNFetchBlob could not open file stream with empty `path`') 96 | encoding = encoding || 'utf8' 97 | append = append || false 98 | return new Promise((resolve, reject) => { 99 | RNFetchBlob.writeStream(path, encoding || 'base64', append || false, (err, streamId:string) => { 100 | if(err) 101 | reject(new Error(err)) 102 | else 103 | resolve(new RNFetchBlobWriteStream(streamId, encoding)) 104 | }) 105 | }) 106 | } 107 | 108 | /** 109 | * Create file stream from file at `path`. 110 | * @param {string} path The file path. 111 | * @param {string} encoding Data encoding, should be one of `base64`, `utf8`, `ascii` 112 | * @param {boolean} bufferSize Size of stream buffer. 113 | * @return {RNFetchBlobStream} RNFetchBlobStream stream instance. 114 | */ 115 | function readStream( 116 | path : string, 117 | encoding : 'utf8' | 'ascii' | 'base64', 118 | bufferSize? : ?number, 119 | tick : ?number = 10 120 | ):Promise { 121 | return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize, tick)) 122 | } 123 | 124 | /** 125 | * Create a directory. 126 | * @param {string} path Path of directory to be created 127 | * @return {Promise} 128 | */ 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 | 140 | } 141 | 142 | /** 143 | * Returns the path for the app group. 144 | * @param {string} groupName Name of app group 145 | * @return {Promise} 146 | */ 147 | function pathForAppGroup(groupName:string):Promise { 148 | return RNFetchBlob.pathForAppGroup(groupName); 149 | } 150 | 151 | /** 152 | * Wrapper method of readStream. 153 | * @param {string} path Path of the file. 154 | * @param {'base64' | 'utf8' | 'ascii'} encoding Encoding of read stream. 155 | * @return {Promise | string>} 156 | */ 157 | function readFile(path:string, encoding:string, bufferSize:?number):Promise { 158 | if(typeof path !== 'string') 159 | return Promise.reject(new Error('Invalid argument "path" ')) 160 | return RNFetchBlob.readFile(path, encoding) 161 | } 162 | 163 | /** 164 | * Write data to file. 165 | * @param {string} path Path of the file. 166 | * @param {string | number[]} data Data to write to the file. 167 | * @param {string} encoding Encoding of data (Optional). 168 | * @return {Promise} 169 | */ 170 | function writeFile(path:string, data:string | Array, 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}`)) 177 | 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}`)) 182 | else 183 | return RNFetchBlob.writeFile(path, encoding, data, false); 184 | } 185 | } 186 | 187 | function appendFile(path:string, data:string | Array, 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}`)) 194 | 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}`)) 199 | else 200 | return RNFetchBlob.writeFile(path, encoding, data, true); 201 | } 202 | } 203 | 204 | /** 205 | * Show statistic data of a path. 206 | * @param {string} path Target path 207 | * @return {RNFetchBlobFile} 208 | */ 209 | function stat(path:string):Promise { 210 | return new Promise((resolve, reject) => { 211 | RNFetchBlob.stat(path, (err, stat) => { 212 | if(err) 213 | reject(new Error(err)) 214 | else { 215 | if(stat) { 216 | stat.size = parseInt(stat.size) 217 | stat.lastModified = parseInt(stat.lastModified) 218 | } 219 | resolve(stat) 220 | } 221 | }) 222 | }) 223 | } 224 | 225 | /** 226 | * Android only method, request media scanner to scan the file. 227 | * @param {Array>} Array contains Key value pairs with key `path` and `mime`. 228 | * @return {Promise} 229 | */ 230 | function scanFile(pairs:any):Promise { 231 | return new Promise((resolve, reject) => { 232 | RNFetchBlob.scanFile(pairs, (err) => { 233 | if(err) 234 | reject(new Error(err)) 235 | else 236 | resolve() 237 | }) 238 | }) 239 | } 240 | 241 | function cp(path:string, dest:string):Promise { 242 | return new Promise((resolve, reject) => { 243 | RNFetchBlob.cp(path, dest, (err, res) => { 244 | if(err) 245 | reject(new Error(err)) 246 | else 247 | resolve(res) 248 | }) 249 | }) 250 | } 251 | 252 | function mv(path:string, dest:string):Promise { 253 | return new Promise((resolve, reject) => { 254 | RNFetchBlob.mv(path, dest, (err, res) => { 255 | if(err) 256 | reject(new Error(err)) 257 | else 258 | resolve(res) 259 | }) 260 | }) 261 | } 262 | 263 | function lstat(path:string):Promise> { 264 | return new Promise((resolve, reject) => { 265 | RNFetchBlob.lstat(path, (err, stat) => { 266 | if(err) 267 | reject(new Error(err)) 268 | else 269 | resolve(stat) 270 | }) 271 | }) 272 | } 273 | 274 | function ls(path:string):Promise> { 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 | }) 283 | } 284 | 285 | /** 286 | * Remove file at path. 287 | * @param {string} path:string Path of target file. 288 | * @return {Promise} 289 | */ 290 | function unlink(path:string):Promise { 291 | return new Promise((resolve, reject) => { 292 | RNFetchBlob.unlink(path, (err) => { 293 | if(err) { 294 | reject(new Error(err)) 295 | } 296 | else 297 | resolve() 298 | }) 299 | }) 300 | } 301 | 302 | /** 303 | * Check if file exists and if it is a folder. 304 | * @param {string} path Path to check 305 | * @return {Promise} 306 | */ 307 | function exists(path:string):Promise { 308 | 309 | return new Promise((resolve, reject) => { 310 | try { 311 | RNFetchBlob.exists(path, (exist) => { 312 | resolve(exist) 313 | }) 314 | } catch(err) { 315 | reject(new Error(err)) 316 | } 317 | }) 318 | 319 | } 320 | 321 | function slice(src:string, dest:string, start:number, end:number):Promise { 322 | let p = Promise.resolve() 323 | let size = 0 324 | function normalize(num, size) { 325 | if(num < 0) 326 | return Math.max(0, size + num) 327 | if(!num && num !== 0) 328 | return size 329 | return num 330 | } 331 | if(start < 0 || end < 0 || !start || !end) { 332 | 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 | }) 339 | } 340 | return p.then(() => RNFetchBlob.slice(src, dest, start, end)) 341 | } 342 | 343 | function isDir(path:string):Promise { 344 | 345 | return new Promise((resolve, reject) => { 346 | try { 347 | RNFetchBlob.exists(path, (exist, isDir) => { 348 | resolve(isDir) 349 | }) 350 | } catch(err) { 351 | reject(new Error(err)) 352 | } 353 | }) 354 | 355 | } 356 | 357 | function df():Promise<{ free : number, total : number }> { 358 | return new Promise((resolve, reject) => { 359 | RNFetchBlob.df((err, stat) => { 360 | if(err) 361 | reject(err) 362 | else 363 | resolve(stat) 364 | }) 365 | }) 366 | } 367 | 368 | export default { 369 | RNFetchBlobSession, 370 | unlink, 371 | mkdir, 372 | session, 373 | ls, 374 | readStream, 375 | mv, 376 | cp, 377 | writeStream, 378 | writeFile, 379 | appendFile, 380 | pathForAppGroup, 381 | readFile, 382 | exists, 383 | createFile, 384 | isDir, 385 | stat, 386 | lstat, 387 | scanFile, 388 | dirs, 389 | slice, 390 | asset, 391 | df 392 | } 393 | -------------------------------------------------------------------------------- /img/RNFB-Body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/RNFB-Body.png -------------------------------------------------------------------------------- /img/RNFB-Flow-hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/RNFB-Flow-hd.png -------------------------------------------------------------------------------- /img/RNFB-HTTP-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/RNFB-HTTP-flow.png -------------------------------------------------------------------------------- /img/RNFB-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/RNFB-flow.png -------------------------------------------------------------------------------- /img/action-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/action-menu.png -------------------------------------------------------------------------------- /img/android-notification1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/android-notification1.png -------------------------------------------------------------------------------- /img/android-notification2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/android-notification2.png -------------------------------------------------------------------------------- /img/download-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/download-manager.png -------------------------------------------------------------------------------- /img/ios-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/ios-1.png -------------------------------------------------------------------------------- /img/ios-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/ios-2.png -------------------------------------------------------------------------------- /img/ios-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/ios-3.png -------------------------------------------------------------------------------- /img/ios-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/ios-4.png -------------------------------------------------------------------------------- /img/ios-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/ios-5.png -------------------------------------------------------------------------------- /img/issue_57_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/issue_57_1.png -------------------------------------------------------------------------------- /img/issue_57_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/issue_57_2.png -------------------------------------------------------------------------------- /img/issue_57_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/issue_57_3.png -------------------------------------------------------------------------------- /img/performance_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/performance_1.png -------------------------------------------------------------------------------- /img/performance_encoding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/performance_encoding.png -------------------------------------------------------------------------------- /img/performance_f2f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkh237/react-native-fetch-blob/57375d8bbed7c247c331d20b99ff39526a206edd/img/performance_f2f.png -------------------------------------------------------------------------------- /ios.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import { 6 | NativeModules, 7 | DeviceEventEmitter, 8 | Platform, 9 | NativeAppEventEmitter, 10 | } from 'react-native' 11 | 12 | const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob 13 | 14 | /** 15 | * Open a file using UIDocumentInteractionController 16 | * @param {string]} path Path of the file to be open. 17 | * @param {string} scheme URI scheme that needs to support, optional 18 | * @return {Promise} 19 | */ 20 | function previewDocument(path:string, scheme:string) { 21 | if(Platform.OS === 'ios') 22 | return RNFetchBlob.previewDocument('file://' + path, scheme) 23 | else 24 | return Promise.reject('RNFetchBlob.openDocument only supports IOS.') 25 | } 26 | 27 | /** 28 | * Preview a file using UIDocumentInteractionController 29 | * @param {string]} path Path of the file to be open. 30 | * @param {string} scheme URI scheme that needs to support, optional 31 | * @return {Promise} 32 | */ 33 | function openDocument(path:string, scheme:string) { 34 | if(Platform.OS === 'ios') 35 | return RNFetchBlob.openDocument('file://' + path, scheme) 36 | else 37 | return Promise.reject('RNFetchBlob.previewDocument only supports IOS.') 38 | } 39 | 40 | /** 41 | * Set excludeFromBackupKey to a URL to prevent the resource to be backuped to 42 | * iCloud. 43 | * @param {string} url URL of the resource, only file URL is supported 44 | * @return {Promise} 45 | */ 46 | function excludeFromBackupKey(path:string) { 47 | return RNFetchBlob.excludeFromBackupKey('file://' + path); 48 | } 49 | 50 | export default { 51 | openDocument, 52 | previewDocument, 53 | excludeFromBackupKey 54 | } 55 | -------------------------------------------------------------------------------- /ios/IOS7Polyfill.h: -------------------------------------------------------------------------------- 1 | // 2 | // IOS7Polyfill.h 3 | // RNFetchBlob 4 | // 5 | // Created by Ben Hsieh on 2016/9/6. 6 | // Copyright © 2016年 wkh237.github.io. All rights reserved. 7 | // 8 | 9 | #ifndef IOS7Polyfill_h 10 | #define IOS7Polyfill_h 11 | 12 | @interface NSString (Contains) 13 | 14 | - (BOOL)RNFBContainsString:(NSString*)other; 15 | 16 | @end 17 | 18 | @implementation NSString (Contains) 19 | 20 | - (BOOL)RNFBContainsString:(NSString*)other { 21 | NSRange range = [self rangeOfString:other]; 22 | return range.length != 0; 23 | } 24 | 25 | 26 | @end 27 | #endif /* IOS7Polyfill_h */ 28 | -------------------------------------------------------------------------------- /ios/RNFetchBlob.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */; }; 11 | A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */; }; 12 | A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */; }; 13 | A15C30141CD25C330074CB35 /* RNFetchBlob.m in Sources */ = {isa = PBXBuildFile; fileRef = A15C30131CD25C330074CB35 /* RNFetchBlob.m */; }; 14 | A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */ = {isa = PBXBuildFile; fileRef = A15C30111CD25C330074CB35 /* RNFetchBlob.h */; }; 15 | A19B48251D98102400E6868A /* RNFetchBlobProgress.m in Sources */ = {isa = PBXBuildFile; fileRef = A19B48241D98102400E6868A /* RNFetchBlobProgress.m */; }; 16 | A1AAE2991D300E4D0051D11C /* RNFetchBlobReqBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | A15C300C1CD25C330074CB35 /* CopyFiles */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = "include/$(PRODUCT_NAME)"; 24 | dstSubfolderSpec = 16; 25 | files = ( 26 | ); 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobFS.m; sourceTree = ""; }; 33 | A158F4281D052E57006FFD38 /* RNFetchBlobFS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobFS.h; sourceTree = ""; }; 34 | A158F4291D0534A9006FFD38 /* RNFetchBlobConst.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobConst.h; sourceTree = ""; }; 35 | A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobConst.m; sourceTree = ""; }; 36 | A158F42E1D0539CE006FFD38 /* RNFetchBlobNetwork.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobNetwork.h; sourceTree = ""; }; 37 | A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobNetwork.m; sourceTree = ""; }; 38 | A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNFetchBlob.a; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | A15C30111CD25C330074CB35 /* RNFetchBlob.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNFetchBlob.h; path = RNFetchBlob/RNFetchBlob.h; sourceTree = ""; }; 40 | A15C30131CD25C330074CB35 /* RNFetchBlob.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RNFetchBlob.m; path = RNFetchBlob/RNFetchBlob.m; sourceTree = ""; }; 41 | A19B48231D98100800E6868A /* RNFetchBlobProgress.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobProgress.h; sourceTree = ""; }; 42 | A19B48241D98102400E6868A /* RNFetchBlobProgress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobProgress.m; sourceTree = ""; }; 43 | A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobReqBuilder.h; sourceTree = ""; }; 44 | A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobReqBuilder.m; sourceTree = ""; }; 45 | A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IOS7Polyfill.h; sourceTree = ""; }; 46 | /* End PBXFileReference section */ 47 | 48 | /* Begin PBXFrameworksBuildPhase section */ 49 | A15C300B1CD25C330074CB35 /* Frameworks */ = { 50 | isa = PBXFrameworksBuildPhase; 51 | buildActionMask = 2147483647; 52 | files = ( 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | 8BD9ABDFAF76406291A798F2 /* Libraries */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | ); 63 | name = Libraries; 64 | sourceTree = ""; 65 | }; 66 | A15C30051CD25C330074CB35 = { 67 | isa = PBXGroup; 68 | children = ( 69 | A19B48241D98102400E6868A /* RNFetchBlobProgress.m */, 70 | A19B48231D98100800E6868A /* RNFetchBlobProgress.h */, 71 | A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */, 72 | A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */, 73 | A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */, 74 | A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */, 75 | A158F42E1D0539CE006FFD38 /* RNFetchBlobNetwork.h */, 76 | A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */, 77 | A158F4291D0534A9006FFD38 /* RNFetchBlobConst.h */, 78 | A158F4281D052E57006FFD38 /* RNFetchBlobFS.h */, 79 | A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */, 80 | A15C30111CD25C330074CB35 /* RNFetchBlob.h */, 81 | A15C30131CD25C330074CB35 /* RNFetchBlob.m */, 82 | A15C300F1CD25C330074CB35 /* Products */, 83 | 8BD9ABDFAF76406291A798F2 /* Libraries */, 84 | ); 85 | sourceTree = ""; 86 | }; 87 | A15C300F1CD25C330074CB35 /* Products */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */, 91 | ); 92 | name = Products; 93 | sourceTree = ""; 94 | }; 95 | /* End PBXGroup section */ 96 | 97 | /* Begin PBXNativeTarget section */ 98 | A15C300D1CD25C330074CB35 /* RNFetchBlob */ = { 99 | isa = PBXNativeTarget; 100 | buildConfigurationList = A15C30171CD25C330074CB35 /* Build configuration list for PBXNativeTarget "RNFetchBlob" */; 101 | buildPhases = ( 102 | A15C300A1CD25C330074CB35 /* Sources */, 103 | A15C300B1CD25C330074CB35 /* Frameworks */, 104 | A15C300C1CD25C330074CB35 /* CopyFiles */, 105 | ); 106 | buildRules = ( 107 | ); 108 | dependencies = ( 109 | ); 110 | name = RNFetchBlob; 111 | productName = RNFetchBlob; 112 | productReference = A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */; 113 | productType = "com.apple.product-type.library.static"; 114 | }; 115 | /* End PBXNativeTarget section */ 116 | 117 | /* Begin PBXProject section */ 118 | A15C30061CD25C330074CB35 /* Project object */ = { 119 | isa = PBXProject; 120 | attributes = { 121 | LastUpgradeCheck = 730; 122 | ORGANIZATIONNAME = wkh237.github.io; 123 | TargetAttributes = { 124 | A15C300D1CD25C330074CB35 = { 125 | CreatedOnToolsVersion = 7.3; 126 | }; 127 | }; 128 | }; 129 | buildConfigurationList = A15C30091CD25C330074CB35 /* Build configuration list for PBXProject "RNFetchBlob" */; 130 | compatibilityVersion = "Xcode 3.2"; 131 | developmentRegion = English; 132 | hasScannedForEncodings = 0; 133 | knownRegions = ( 134 | en, 135 | ); 136 | mainGroup = A15C30051CD25C330074CB35; 137 | productRefGroup = A15C300F1CD25C330074CB35 /* Products */; 138 | projectDirPath = ""; 139 | projectRoot = ""; 140 | targets = ( 141 | A15C300D1CD25C330074CB35 /* RNFetchBlob */, 142 | ); 143 | }; 144 | /* End PBXProject section */ 145 | 146 | /* Begin PBXSourcesBuildPhase section */ 147 | A15C300A1CD25C330074CB35 /* Sources */ = { 148 | isa = PBXSourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */, 152 | A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */, 153 | A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */, 154 | A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */, 155 | A19B48251D98102400E6868A /* RNFetchBlobProgress.m in Sources */, 156 | A1AAE2991D300E4D0051D11C /* RNFetchBlobReqBuilder.m in Sources */, 157 | A15C30141CD25C330074CB35 /* RNFetchBlob.m in Sources */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXSourcesBuildPhase section */ 162 | 163 | /* Begin XCBuildConfiguration section */ 164 | A15C30151CD25C330074CB35 /* Debug */ = { 165 | isa = XCBuildConfiguration; 166 | buildSettings = { 167 | ALWAYS_SEARCH_USER_PATHS = NO; 168 | CLANG_ANALYZER_NONNULL = YES; 169 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 170 | CLANG_CXX_LIBRARY = "libc++"; 171 | CLANG_ENABLE_MODULES = YES; 172 | CLANG_ENABLE_OBJC_ARC = YES; 173 | CLANG_WARN_BOOL_CONVERSION = YES; 174 | CLANG_WARN_CONSTANT_CONVERSION = YES; 175 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 176 | CLANG_WARN_EMPTY_BODY = YES; 177 | CLANG_WARN_ENUM_CONVERSION = YES; 178 | CLANG_WARN_INT_CONVERSION = YES; 179 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 180 | CLANG_WARN_UNREACHABLE_CODE = YES; 181 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 182 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 183 | COPY_PHASE_STRIP = NO; 184 | DEBUG_INFORMATION_FORMAT = dwarf; 185 | ENABLE_STRICT_OBJC_MSGSEND = YES; 186 | ENABLE_TESTABILITY = YES; 187 | GCC_C_LANGUAGE_STANDARD = gnu99; 188 | GCC_DYNAMIC_NO_PIC = NO; 189 | GCC_NO_COMMON_BLOCKS = YES; 190 | GCC_OPTIMIZATION_LEVEL = 0; 191 | GCC_PREPROCESSOR_DEFINITIONS = ( 192 | "DEBUG=1", 193 | "$(inherited)", 194 | ); 195 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 196 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 197 | GCC_WARN_UNDECLARED_SELECTOR = YES; 198 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 199 | GCC_WARN_UNUSED_FUNCTION = YES; 200 | GCC_WARN_UNUSED_VARIABLE = YES; 201 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 202 | MTL_ENABLE_DEBUG_INFO = YES; 203 | ONLY_ACTIVE_ARCH = YES; 204 | SDKROOT = iphoneos; 205 | }; 206 | name = Debug; 207 | }; 208 | A15C30161CD25C330074CB35 /* Release */ = { 209 | isa = XCBuildConfiguration; 210 | buildSettings = { 211 | ALWAYS_SEARCH_USER_PATHS = NO; 212 | CLANG_ANALYZER_NONNULL = YES; 213 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 214 | CLANG_CXX_LIBRARY = "libc++"; 215 | CLANG_ENABLE_MODULES = YES; 216 | CLANG_ENABLE_OBJC_ARC = YES; 217 | CLANG_WARN_BOOL_CONVERSION = YES; 218 | CLANG_WARN_CONSTANT_CONVERSION = YES; 219 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 220 | CLANG_WARN_EMPTY_BODY = YES; 221 | CLANG_WARN_ENUM_CONVERSION = YES; 222 | CLANG_WARN_INT_CONVERSION = YES; 223 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 224 | CLANG_WARN_UNREACHABLE_CODE = YES; 225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 226 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 227 | COPY_PHASE_STRIP = NO; 228 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 229 | ENABLE_NS_ASSERTIONS = NO; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | GCC_C_LANGUAGE_STANDARD = gnu99; 232 | GCC_NO_COMMON_BLOCKS = YES; 233 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 234 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 235 | GCC_WARN_UNDECLARED_SELECTOR = YES; 236 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 237 | GCC_WARN_UNUSED_FUNCTION = YES; 238 | GCC_WARN_UNUSED_VARIABLE = YES; 239 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 240 | MTL_ENABLE_DEBUG_INFO = NO; 241 | SDKROOT = iphoneos; 242 | VALIDATE_PRODUCT = YES; 243 | }; 244 | name = Release; 245 | }; 246 | A15C30181CD25C330074CB35 /* Debug */ = { 247 | isa = XCBuildConfiguration; 248 | buildSettings = { 249 | ALWAYS_SEARCH_USER_PATHS = NO; 250 | GCC_INPUT_FILETYPE = automatic; 251 | HEADER_SEARCH_PATHS = ( 252 | "$(inherited)", 253 | "$(SRCROOT)/Libraries/**", 254 | "$(SRCROOT)/../node_modules/react-native/React/**", 255 | "$(SRCROOT)/../../React/**", 256 | "$(SRCROOT)/../../react-native/React/**", 257 | "$(SRCROOT)/../../node_modules/react-native/React/**", 258 | "$(SRCROOT)/../../node_modules/react-native-fetch-blob/ios/RNFetchBlob", 259 | "$(SRCROOT)/../../RNFetchBlobTest/node_modules/react-native/**", 260 | ); 261 | OTHER_LDFLAGS = "-ObjC"; 262 | PRODUCT_NAME = "$(TARGET_NAME)"; 263 | SKIP_INSTALL = YES; 264 | }; 265 | name = Debug; 266 | }; 267 | A15C30191CD25C330074CB35 /* Release */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | GCC_INPUT_FILETYPE = automatic; 272 | HEADER_SEARCH_PATHS = ( 273 | "$(inherited)", 274 | "$(SRCROOT)/Libraries/**", 275 | "$(SRCROOT)/../node_modules/react-native/React/**", 276 | "$(SRCROOT)/../../React/**", 277 | "$(SRCROOT)/../../react-native/React/**", 278 | "$(SRCROOT)/../../node_modules/react-native/React/**", 279 | "$(SRCROOT)/../../node_modules/react-native-fetch-blob/ios/RNFetchBlob", 280 | "$(SRCROOT)/../../RNFetchBlobTest/node_modules/react-native/**", 281 | ); 282 | OTHER_LDFLAGS = "-ObjC"; 283 | PRODUCT_NAME = "$(TARGET_NAME)"; 284 | SKIP_INSTALL = YES; 285 | }; 286 | name = Release; 287 | }; 288 | /* End XCBuildConfiguration section */ 289 | 290 | /* Begin XCConfigurationList section */ 291 | A15C30091CD25C330074CB35 /* Build configuration list for PBXProject "RNFetchBlob" */ = { 292 | isa = XCConfigurationList; 293 | buildConfigurations = ( 294 | A15C30151CD25C330074CB35 /* Debug */, 295 | A15C30161CD25C330074CB35 /* Release */, 296 | ); 297 | defaultConfigurationIsVisible = 0; 298 | defaultConfigurationName = Release; 299 | }; 300 | A15C30171CD25C330074CB35 /* Build configuration list for PBXNativeTarget "RNFetchBlob" */ = { 301 | isa = XCConfigurationList; 302 | buildConfigurations = ( 303 | A15C30181CD25C330074CB35 /* Debug */, 304 | A15C30191CD25C330074CB35 /* Release */, 305 | ); 306 | defaultConfigurationIsVisible = 0; 307 | defaultConfigurationName = Release; 308 | }; 309 | /* End XCConfigurationList section */ 310 | }; 311 | rootObject = A15C30061CD25C330074CB35 /* Project object */; 312 | } 313 | -------------------------------------------------------------------------------- /ios/RNFetchBlob/RNFetchBlob.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlob.h 3 | // 4 | // Created by wkh237 on 2016/4/28. 5 | // 6 | 7 | //XXX: DO NO REMOVE THIS LINE IF YOU'RE USING IT ON RN > 0.40 PROJECT 8 | 9 | 10 | 11 | #ifndef RNFetchBlob_h 12 | #define RNFetchBlob_h 13 | 14 | 15 | #if __has_include() 16 | #import 17 | #import 18 | #import 19 | #import 20 | #import 21 | #else 22 | #import "RCTBridgeModule.h" 23 | #import "RCTLog.h" 24 | #import "RCTRootView.h" 25 | #import "RCTBridge.h" 26 | #import "RCTEventDispatcher.h" 27 | #endif 28 | 29 | #import 30 | 31 | 32 | @interface RNFetchBlob : NSObject { 33 | 34 | NSString * filePathPrefix; 35 | 36 | } 37 | 38 | @property (nonatomic) NSString * filePathPrefix; 39 | @property (retain) UIDocumentInteractionController * documentController; 40 | 41 | + (RCTBridge *)getRCTBridge; 42 | + (void) checkExpiredSessions; 43 | 44 | @end 45 | 46 | #endif /* RNFetchBlob_h */ 47 | -------------------------------------------------------------------------------- /ios/RNFetchBlobConst.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlobConst.h 3 | // RNFetchBlob 4 | // 5 | // Created by Ben Hsieh on 2016/6/6. 6 | // Copyright © 2016年 suzuri04x2. All rights reserved. 7 | // 8 | 9 | #ifndef RNFetchBlobConst_h 10 | #define RNFetchBlobConst_h 11 | 12 | #import 13 | 14 | // lib event 15 | extern NSString *const MSG_EVENT; 16 | extern NSString *const MSG_EVENT_LOG; 17 | extern NSString *const MSG_EVENT_WARN; 18 | extern NSString *const MSG_EVENT_ERROR; 19 | 20 | extern NSString *const EVENT_EXPIRE; 21 | extern NSString *const EVENT_PROGRESS; 22 | extern NSString *const EVENT_SERVER_PUSH; 23 | extern NSString *const EVENT_PROGRESS_UPLOAD; 24 | extern NSString *const EVENT_STATE_CHANGE; 25 | 26 | extern NSString *const FILE_PREFIX; 27 | extern NSString *const ASSET_PREFIX; 28 | extern NSString *const AL_PREFIX; 29 | 30 | // config 31 | extern NSString *const CONFIG_USE_TEMP; 32 | extern NSString *const CONFIG_FILE_PATH; 33 | extern NSString *const CONFIG_FILE_EXT; 34 | extern NSString *const CONFIG_TRUSTY; 35 | extern NSString *const CONFIG_INDICATOR; 36 | extern NSString *const CONFIG_KEY; 37 | extern NSString *const CONFIG_EXTRA_BLOB_CTYPE; 38 | 39 | // fs events 40 | extern NSString *const FS_EVENT_DATA; 41 | extern NSString *const FS_EVENT_END; 42 | extern NSString *const FS_EVENT_WARN; 43 | extern NSString *const FS_EVENT_ERROR; 44 | 45 | extern NSString *const KEY_REPORT_PROGRESS; 46 | extern NSString *const KEY_REPORT_UPLOAD_PROGRESS; 47 | 48 | // response type 49 | extern NSString *const RESP_TYPE_BASE64; 50 | extern NSString *const RESP_TYPE_UTF8; 51 | extern NSString *const RESP_TYPE_PATH; 52 | 53 | 54 | 55 | #endif /* RNFetchBlobConst_h */ 56 | -------------------------------------------------------------------------------- /ios/RNFetchBlobConst.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlobConst.m 3 | // RNFetchBlob 4 | // 5 | // Created by Ben Hsieh on 2016/6/6. 6 | // Copyright © 2016 wkh237. All rights reserved. 7 | // 8 | #import "RNFetchBlobConst.h" 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://"; 13 | 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"; 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"; 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"; 37 | 38 | extern NSString *const KEY_REPORT_PROGRESS = @"reportProgress"; 39 | extern NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress"; 40 | 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"; 45 | -------------------------------------------------------------------------------- /ios/RNFetchBlobFS.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlobFS.h 3 | // RNFetchBlob 4 | // 5 | // Created by Ben Hsieh on 2016/6/6. 6 | // Copyright © 2016年 suzuri04x2. All rights reserved. 7 | // 8 | 9 | #ifndef RNFetchBlobFS_h 10 | #define RNFetchBlobFS_h 11 | 12 | #import "RNFetchBlob.h" 13 | #import 14 | 15 | #if __has_include() 16 | #import 17 | #else 18 | #import "RCTBridgeModule.h" 19 | #endif 20 | 21 | @import AssetsLibrary; 22 | 23 | @interface RNFetchBlobFS : NSObject { 24 | NSOutputStream * outStream; 25 | NSInputStream * inStream; 26 | RCTResponseSenderBlock callback; 27 | RCTBridge * bridge; 28 | Boolean isOpen; 29 | NSString * encoding; 30 | int bufferSize; 31 | BOOL appendData; 32 | NSString * taskId; 33 | NSString * path; 34 | NSString * streamId; 35 | } 36 | 37 | @property (nonatomic) NSOutputStream * outStream; 38 | @property (nonatomic) NSInputStream * inStream; 39 | @property (strong, nonatomic) RCTResponseSenderBlock callback; 40 | @property (nonatomic) RCTBridge * bridge; 41 | @property (nonatomic) NSString * encoding; 42 | @property (nonatomic) NSString * taskId; 43 | @property (nonatomic) NSString * path; 44 | @property (nonatomic) int bufferSize; 45 | @property (nonatomic) NSString * streamId; 46 | @property (nonatomic) BOOL appendData; 47 | 48 | // get dirs 49 | + (NSString *) getMainBundleDir; 50 | + (NSString *) getTempPath; 51 | + (NSString *) getCacheDir; 52 | + (NSString *) getDocumentDir; 53 | + (NSString *) getTempPath:(NSString*)taskId withExtension:(NSString *)ext; 54 | + (NSString *) getPathOfAsset:(NSString *)assetURI; 55 | + (NSString *) getPathForAppGroup:(NSString *)groupName; 56 | + (void) getPathFromUri:(NSString *)uri completionHandler:(void(^)(NSString * path, ALAssetRepresentation *asset)) onComplete; 57 | 58 | // fs methods 59 | + (RNFetchBlobFS *) getFileStreams; 60 | + (BOOL) mkdir:(NSString *) path; 61 | + (NSDictionary *) stat:(NSString *) path error:(NSError **) error; 62 | + (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback; 63 | + (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; 65 | + (void) readFile:(NSString *)path encoding:(NSString *)encoding onComplete:(void (^)(NSData * content, NSString * errMsg))onComplete; 66 | + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock; 67 | + (void) slice:(NSString *)path 68 | dest:(NSString *)dest 69 | start:(nonnull NSNumber *)start 70 | end:(nonnull NSNumber *)end 71 | encode:(NSString *)encode 72 | resolver:(RCTPromiseResolveBlock)resolve 73 | rejecter:(RCTPromiseRejectBlock)reject; 74 | //+ (void) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append; 75 | + (void) writeAssetToPath:(ALAssetRepresentation * )rep dest:(NSString *)dest; 76 | + (void) readStream:(NSString *)uri encoding:(NSString * )encoding bufferSize:(int)bufferSize tick:(int)tick streamId:(NSString *)streamId bridgeRef:(RCTBridge *)bridgeRef; 77 | + (void) df:(RCTResponseSenderBlock)callback; 78 | 79 | // constructor 80 | - (id) init; 81 | - (id)initWithCallback:(RCTResponseSenderBlock)callback; 82 | - (id)initWithBridgeRef:(RCTBridge *)bridgeRef; 83 | 84 | // file stream 85 | - (void) openWithDestination; 86 | - (NSString *)openWithPath:(NSString *)destPath encode:(nullable NSString *)encode appendData:(BOOL)append; 87 | 88 | // file stream write data 89 | - (void)write:(NSData *) chunk; 90 | - (void)writeEncodeChunk:(NSString *) chunk; 91 | 92 | - (void) closeInStream; 93 | - (void) closeOutStream; 94 | 95 | - (void) openFile:( NSString * _Nonnull ) uri; 96 | 97 | @end 98 | 99 | #endif /* RNFetchBlobFS_h */ 100 | -------------------------------------------------------------------------------- /ios/RNFetchBlobNetwork.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlobNetwork.h 3 | // RNFetchBlob 4 | // 5 | // Created by wkh237 on 2016/6/6. 6 | // Copyright © 2016 wkh237. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RNFetchBlobProgress.h" 11 | #import "RNFetchBlobFS.h" 12 | 13 | #if __has_include() 14 | #import 15 | #else 16 | #import "RCTBridgeModule.h" 17 | #endif 18 | 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 | 27 | @interface RNFetchBlobNetwork : NSObject 28 | 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 | 42 | 43 | + (NSMutableDictionary * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers; 44 | + (void) cancelRequest:(NSString *)taskId; 45 | + (void) enableProgressReport:(NSString *) taskId; 46 | + (void) enableUploadProgress:(NSString *) taskId; 47 | + (void) emitExpiredTasks; 48 | 49 | - (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 | 55 | 56 | 57 | @end 58 | 59 | 60 | #endif /* RNFetchBlobNetwork_h */ 61 | -------------------------------------------------------------------------------- /ios/RNFetchBlobProgress.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlobProgress.h 3 | // RNFetchBlob 4 | // 5 | // Created by Ben Hsieh on 2016/9/25. 6 | // Copyright © 2016年 wkh237.github.io. All rights reserved. 7 | // 8 | 9 | #ifndef RNFetchBlobProgress_h 10 | #define RNFetchBlobProgress_h 11 | 12 | #import 13 | 14 | typedef NS_ENUM(NSUInteger, ProgressType) { 15 | Download, 16 | Upload, 17 | }; 18 | 19 | @interface RNFetchBlobProgress : NSObject 20 | { 21 | NSNumber * count; 22 | NSNumber * interval; 23 | ProgressType type; 24 | BOOL enable; 25 | 26 | } 27 | 28 | @property (nonatomic) NSNumber * count; 29 | @property (nonatomic) NSNumber * interval; 30 | @property (nonatomic) NSInteger type; 31 | @property (nonatomic) BOOL enable; 32 | 33 | -(id)initWithType:(ProgressType)type interval:(NSNumber*)interval count:(NSNumber *)count; 34 | -(BOOL)shouldReport:(NSNumber *) nextProgress; 35 | 36 | 37 | @end 38 | 39 | #endif /* RNFetchBlobProgress_h */ 40 | -------------------------------------------------------------------------------- /ios/RNFetchBlobProgress.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlobProgress.m 3 | // RNFetchBlob 4 | // 5 | // Created by Ben Hsieh on 2016/9/25. 6 | // Copyright © 2016年 wkh237.github.io. All rights reserved. 7 | // 8 | 9 | #import "RNFetchBlobProgress.h" 10 | 11 | @interface RNFetchBlobProgress () 12 | { 13 | float progress; 14 | int tick; 15 | double lastTick; 16 | } 17 | @end 18 | 19 | @implementation RNFetchBlobProgress 20 | 21 | -(id)initWithType:(ProgressType)type interval:(NSNumber *)interval count:(NSNumber *)count 22 | { 23 | self = [super init]; 24 | self.count = count; 25 | self.interval = [NSNumber numberWithFloat:[interval floatValue] /1000]; 26 | self.type = type; 27 | self.enable = YES; 28 | lastTick = 0; 29 | tick = 1; 30 | return self; 31 | } 32 | 33 | -(BOOL)shouldReport:(NSNumber *)nextProgress 34 | { 35 | BOOL * result = YES; 36 | float countF = [self.count floatValue]; 37 | if(countF > 0 && [nextProgress floatValue] > 0) 38 | { 39 | result = (int)(floorf([nextProgress floatValue]*countF)) >= tick; 40 | } 41 | 42 | NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970]; 43 | // NSTimeInterval is defined as double 44 | NSNumber *timeStampObj = [NSNumber numberWithDouble: timeStamp]; 45 | float delta = [timeStampObj doubleValue] - lastTick; 46 | BOOL * shouldReport = delta > [self.interval doubleValue] && self.enable && result; 47 | if(shouldReport) 48 | { 49 | tick++; 50 | lastTick = [timeStampObj doubleValue]; 51 | } 52 | return shouldReport; 53 | 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /ios/RNFetchBlobReqBuilder.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlobReqBuilder.h 3 | // RNFetchBlob 4 | // 5 | // Created by Ben Hsieh on 2016/7/9. 6 | // Copyright © 2016年 wkh237. All rights reserved. 7 | // 8 | 9 | #ifndef RNFetchBlobReqBuilder_h 10 | #define RNFetchBlobReqBuilder_h 11 | 12 | #import 13 | 14 | @interface RNFetchBlobReqBuilder : NSObject; 15 | 16 | +(void) buildMultipartRequest:(NSDictionary *)options 17 | taskId:(NSString *)taskId 18 | method:(NSString *)method 19 | url:(NSString *)url 20 | headers:(NSDictionary *)headers 21 | form:(NSArray *)form 22 | onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete; 23 | 24 | +(void) buildOctetRequest:(NSDictionary *)options 25 | taskId:(NSString *)taskId 26 | method:(NSString *)method 27 | url:(NSString *)url 28 | headers:(NSDictionary *)headers 29 | body:(NSString *)body 30 | onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete; 31 | 32 | +(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableArray *) headers; 33 | 34 | 35 | @end 36 | 37 | #endif /* RNFetchBlobReqBuilder_h */ 38 | -------------------------------------------------------------------------------- /ios/RNFetchBlobReqBuilder.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlobReqBuilder.m 3 | // RNFetchBlob 4 | // 5 | // Created by Ben Hsieh on 2016/7/9. 6 | // Copyright © 2016年 wkh237. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RNFetchBlobReqBuilder.h" 11 | #import "RNFetchBlobNetwork.h" 12 | #import "RNFetchBlobConst.h" 13 | #import "RNFetchBlobFS.h" 14 | #import "IOS7Polyfill.h" 15 | 16 | #if __has_include() 17 | #import 18 | #else 19 | #import "RCTLog.h" 20 | #endif 21 | 22 | @interface RNFetchBlobReqBuilder() 23 | { 24 | 25 | } 26 | @end 27 | 28 | @implementation RNFetchBlobReqBuilder 29 | 30 | 31 | // Fetch blob data request 32 | +(void) buildMultipartRequest:(NSDictionary *)options 33 | taskId:(NSString *)taskId 34 | method:(NSString *)method 35 | url:(NSString *)url 36 | headers:(NSDictionary *)headers 37 | form:(NSArray *)form 38 | onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete 39 | { 40 | // NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 41 | NSString * encodedUrl = url; 42 | 43 | // send request 44 | __block NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString: encodedUrl]]; 45 | __block NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]]; 46 | NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970]; 47 | NSNumber * timeStampObj = [NSNumber numberWithDouble: timeStamp]; 48 | 49 | // generate boundary 50 | __block NSString * boundary = [NSString stringWithFormat:@"RNFetchBlob%d", timeStampObj]; 51 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 52 | __block NSMutableData * postData = [[NSMutableData alloc] init]; 53 | // combine multipart/form-data body 54 | [[self class] buildFormBody:form boundary:boundary onComplete:^(NSData *formData, BOOL hasError) { 55 | if(hasError) 56 | { 57 | onComplete(nil, nil); 58 | } 59 | else 60 | { 61 | if(formData != nil) 62 | { 63 | [postData appendData:formData]; 64 | // close form data 65 | [postData appendData: [[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 66 | [request setHTTPBody:postData]; 67 | } 68 | // set content-length 69 | [mheaders setValue:[NSString stringWithFormat:@"%lu",[postData length]] forKey:@"Content-Length"]; 70 | [mheaders setValue:@"100-continue" forKey:@"Expect"]; 71 | // appaned boundary to content-type 72 | [mheaders setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forKey:@"content-type"]; 73 | [request setHTTPMethod: method]; 74 | [request setAllHTTPHeaderFields:mheaders]; 75 | onComplete(request, [formData length]); 76 | } 77 | }]; 78 | 79 | }); 80 | } 81 | 82 | // Fetch blob data request 83 | +(void) buildOctetRequest:(NSDictionary *)options 84 | taskId:(NSString *)taskId 85 | method:(NSString *)method 86 | url:(NSString *)url 87 | headers:(NSDictionary *)headers 88 | body:(NSString *)body 89 | onComplete:(void(^)(__weak NSURLRequest * req, long bodyLength))onComplete 90 | { 91 | // NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 92 | NSString * encodedUrl = url; 93 | // send request 94 | __block NSMutableURLRequest *request = [[NSMutableURLRequest alloc] 95 | initWithURL:[NSURL URLWithString: encodedUrl]]; 96 | 97 | __block NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]]; 98 | // move heavy task to another thread 99 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 100 | NSMutableData * blobData; 101 | long size = -1; 102 | // if method is POST, PUT or PATCH, convert data string format 103 | if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"] || [[method lowercaseString] isEqualToString:@"patch"]) { 104 | // generate octet-stream body 105 | if(body != nil) { 106 | __block NSString * cType = [[self class] getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders]; 107 | __block NSString * transferEncoding = [[self class] getHeaderIgnoreCases:@"transfer-encoding" fromHeaders:mheaders]; 108 | // when headers does not contain a key named "content-type" (case ignored), use default content type 109 | if(cType == nil) 110 | { 111 | [mheaders setValue:@"application/octet-stream" forKey:@"Content-Type"]; 112 | } 113 | 114 | // when body is a string contains file path prefix, try load file from the path 115 | if([body hasPrefix:FILE_PREFIX]) { 116 | __block NSString * orgPath = [body substringFromIndex:[FILE_PREFIX length]]; 117 | orgPath = [RNFetchBlobFS getPathOfAsset:orgPath]; 118 | if([orgPath hasPrefix:AL_PREFIX]) 119 | { 120 | 121 | [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString * err) { 122 | if(err != nil) 123 | { 124 | onComplete(nil, nil); 125 | } 126 | else 127 | { 128 | [request setHTTPBody:content]; 129 | [request setHTTPMethod: method]; 130 | [request setAllHTTPHeaderFields:mheaders]; 131 | onComplete(request, [content length]); 132 | } 133 | }]; 134 | 135 | return; 136 | } 137 | size = [[[NSFileManager defaultManager] attributesOfItemAtPath:orgPath error:nil] fileSize]; 138 | if(transferEncoding != nil && [[transferEncoding lowercaseString] isEqualToString:@"chunked"]) 139 | { 140 | [request setHTTPBodyStream: [NSInputStream inputStreamWithFileAtPath:orgPath ]]; 141 | } 142 | else 143 | { 144 | __block NSData * bodyBytes = [NSData dataWithContentsOfFile:orgPath ]; 145 | [request setHTTPBody:bodyBytes]; 146 | } 147 | } 148 | // otherwise convert it as BASE64 data string 149 | else { 150 | 151 | __block NSString * cType = [[self class]getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders]; 152 | // when content-type is application/octet* decode body string using BASE64 decoder 153 | if([[cType lowercaseString] hasPrefix:@"application/octet"] || [[cType lowercaseString] RNFBContainsString:@";base64"]) 154 | { 155 | __block NSString * ncType = [[cType stringByReplacingOccurrencesOfString:@";base64" withString:@""]stringByReplacingOccurrencesOfString:@";BASE64" withString:@""]; 156 | if([mheaders valueForKey:@"content-type"] != nil) 157 | [mheaders setValue:ncType forKey:@"content-type"]; 158 | if([mheaders valueForKey:@"Content-Type"] != nil) 159 | [mheaders setValue:ncType forKey:@"Content-Type"]; 160 | blobData = [[NSData alloc] initWithBase64EncodedString:body options:0]; 161 | [request setHTTPBody:blobData]; 162 | size = [blobData length]; 163 | } 164 | // otherwise use the body as-is 165 | else 166 | { 167 | size = [body length]; 168 | [request setHTTPBody: [body dataUsingEncoding:NSUTF8StringEncoding]]; 169 | } 170 | } 171 | } 172 | } 173 | 174 | [request setHTTPMethod: method]; 175 | [request setAllHTTPHeaderFields:mheaders]; 176 | 177 | onComplete(request, size); 178 | }); 179 | } 180 | 181 | +(void) buildFormBody:(NSArray *)form boundary:(NSString *)boundary onComplete:(void(^)(NSData * formData, BOOL hasError))onComplete 182 | { 183 | __block NSMutableData * formData = [[NSMutableData alloc] init]; 184 | if(form == nil) 185 | onComplete(nil, NO); 186 | else 187 | { 188 | __block int i = 0; 189 | __block int count = [form count]; 190 | // a recursive block that builds multipart body asynchornously 191 | void __block (^getFieldData)(id field) = ^(id field) 192 | { 193 | NSString * name = [field valueForKey:@"name"]; 194 | __block NSString * content = [field valueForKey:@"data"]; 195 | NSString * contentType = [field valueForKey:@"type"]; 196 | // skip when the form field `name` or `data` is empty 197 | if(content == nil || name == nil) 198 | { 199 | i++; 200 | getFieldData([form objectAtIndex:i]); 201 | RCTLogWarn(@"RNFetchBlob multipart request builder has found a field without `data` or `name` property, the field will be removed implicitly."); 202 | return; 203 | } 204 | 205 | // field is a text field 206 | if([field valueForKey:@"filename"] == nil || content == nil) { 207 | contentType = contentType == nil ? @"text/plain" : contentType; 208 | [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 209 | [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]]; 210 | [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; 211 | [formData appendData:[[NSString stringWithFormat:@"%@\r\n", content] dataUsingEncoding:NSUTF8StringEncoding]]; 212 | } 213 | // field contains a file 214 | else { 215 | contentType = contentType == nil ? @"application/octet-stream" : contentType; 216 | NSMutableData * blobData; 217 | if(content != nil) 218 | { 219 | // append data from file asynchronously 220 | if([content hasPrefix:FILE_PREFIX]) 221 | { 222 | NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]]; 223 | orgPath = [RNFetchBlobFS getPathOfAsset:orgPath]; 224 | 225 | [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString * err) { 226 | if(err != nil) 227 | { 228 | onComplete(formData, YES); 229 | return; 230 | } 231 | NSString * filename = [field valueForKey:@"filename"]; 232 | [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 233 | [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]]; 234 | [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; 235 | [formData appendData:content]; 236 | [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; 237 | i++; 238 | if(i < count) 239 | { 240 | __block NSDictionary * nextField = [form objectAtIndex:i]; 241 | getFieldData(nextField); 242 | } 243 | else 244 | { 245 | onComplete(formData, NO); 246 | getFieldData = nil; 247 | } 248 | }]; 249 | return ; 250 | } 251 | else 252 | blobData = [[NSData alloc] initWithBase64EncodedString:content options:0]; 253 | } 254 | NSString * filename = [field valueForKey:@"filename"]; 255 | [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 256 | [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]]; 257 | [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; 258 | [formData appendData:blobData]; 259 | [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; 260 | blobData = nil; 261 | } 262 | i++; 263 | if(i < count) 264 | { 265 | __block NSDictionary * nextField = [form objectAtIndex:i]; 266 | getFieldData(nextField); 267 | } 268 | else 269 | { 270 | onComplete(formData, NO); 271 | getFieldData = nil; 272 | } 273 | 274 | }; 275 | __block NSDictionary * nextField = [form objectAtIndex:i]; 276 | getFieldData(nextField); 277 | } 278 | } 279 | 280 | +(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableDictionary *) headers { 281 | 282 | NSString * normalCase = [headers valueForKey:field]; 283 | NSString * ignoredCase = [headers valueForKey:[field lowercaseString]]; 284 | if( normalCase != nil) 285 | return normalCase; 286 | else 287 | return ignoredCase; 288 | 289 | } 290 | 291 | 292 | @end 293 | -------------------------------------------------------------------------------- /json-stream.js: -------------------------------------------------------------------------------- 1 | import Oboe from './lib/oboe-browser.min.js' 2 | import XMLHttpRequest from './polyfill/XMLHttpRequest' 3 | import URIUtil from './utils/uri' 4 | 5 | const OboeExtended = (arg: string | object) => { 6 | 7 | 8 | window.location = '' 9 | 10 | if(!window.XMLHttpRequest.isRNFBPolyfill ) { 11 | window.XMLHttpRequest = XMLHttpRequest 12 | console.warn('Use JSONStream will automatically replace window.XMLHttpRequest with RNFetchBlob.polyfill.XMLHttpRequest. You are seeing this warning because you did not replace it maually.') 13 | } 14 | 15 | if(typeof arg === 'string') { 16 | if(URIUtil.isFileURI(arg)) { 17 | arg = { 18 | url : 'JSONStream://' + arg, 19 | headers : { noCache : true } 20 | } 21 | } 22 | else 23 | arg = 'JSONStream://' + arg 24 | 25 | } 26 | else if(typeof arg === 'object') { 27 | let headers = arg.headers || {} 28 | if(URIUtil.isFileURI(arg.url)) { 29 | headers.noCache = true 30 | } 31 | arg = Object.assign(arg, { 32 | url : 'JSONStream://' + arg.url, 33 | headers 34 | }) 35 | } 36 | return Oboe(arg) 37 | } 38 | 39 | export default OboeExtended 40 | -------------------------------------------------------------------------------- /lib/oboe-browser.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b,c,d,e,f){function g(a,b){return function(){return a.call(this,b.apply(this,arguments))}}function h(a){return function(b){return b[a]}}function i(a,b){return b.apply(f,a)}function j(a){var b=a.length-1,d=c.prototype.slice;if(0==b)return function(){return a.call(this,d.call(arguments))};if(1==b)return function(){return a.call(this,arguments[0],d.call(arguments,1))};var e=c(a.length);return function(){for(var c=0;b>c;c++)e[c]=arguments[c];return e[b]=d.call(arguments,b),a.apply(this,e)}}function k(a){return function(b,c){return a(c,b)}}function l(a,b){return function(c){return a(c)&&b(c)}}function m(){}function n(){return!0}function o(a){return function(){return a}}function p(a,b){return b&&b.constructor===a}function q(a){return a!==f}function r(a,c){return c instanceof b&&y(function(a){return a in c},a)}function s(a,b){return[a,b]}function t(a){return A(a.reduce(k(s),X))}function u(a){return w(function(a,b){return a.unshift(b),a},[],a)}function v(a,b){return b?s(a(Y(b)),v(a,Z(b))):X}function w(a,b,c){return c?a(w(a,b,Z(c)),Y(c)):b}function x(a,b,c){function d(a,c){return a?b(Y(a))?(c(Y(a)),Z(a)):s(Y(a),d(Z(a),c)):X}return d(a,c||m)}function y(a,b){return!b||a(Y(b))&&y(a,Z(b))}function z(a,b){a&&(Y(a).apply(null,b),z(Z(a),b))}function A(a){function b(a,c){return a?b(Z(a),s(Y(a),c)):c}return b(a,X)}function B(a,b){return b&&(a(Y(b))?Y(b):B(a,Z(b)))}function C(a){"use strict";function b(){var a=0;P.length>p&&(c("Max buffer length exceeded: textNode"),a=Math.max(a,P.length)),Q.length>p&&(c("Max buffer length exceeded: numberNode"),a=Math.max(a,Q.length)),O=p-a+Y}function c(a){P&&(m(P),n(),P=""),i=d(a+"\nLn: "+$+"\nCol: "+Z+"\nChr: "+j),o(N(f,f,i))}function e(){return T==s?(m({}),n(),void(S=!0)):((T!==t||0!==X)&&c("Unexpected end"),P&&(m(P),n(),P=""),void(S=!0))}function g(a){return"\r"==a||"\n"==a||" "==a||" "==a}function h(a){if(!i){if(S)return c("Cannot write after close");var d=0;for(j=a[0];j&&(k=j,j=a[d++]);)switch(Y++,"\n"==j?($++,Z=0):Z++,T){case s:if("{"===j)T=u;else if("["===j)T=w;else if(!g(j))return c("Non-whitespace before {[.");continue;case z:case u:if(g(j))continue;if(T===z)U.push(A);else{if("}"===j){m({}),n(),T=U.pop()||t;continue}U.push(v)}if('"'!==j)return c('Malformed object key should start with " ');T=y;continue;case A:case v:if(g(j))continue;if(":"===j)T===v?(U.push(v),P&&(m({}),l(P),P=""),X++):P&&(l(P),P=""),T=t;else if("}"===j)P&&(m(P),n(),P=""),n(),X--,T=U.pop()||t;else{if(","!==j)return c("Bad object");T===v&&U.push(v),P&&(m(P),n(),P=""),T=z}continue;case w:case t:if(g(j))continue;if(T===w){if(m([]),X++,T=t,"]"===j){n(),X--,T=U.pop()||t;continue}U.push(x)}if('"'===j)T=y;else if("{"===j)T=u;else if("["===j)T=w;else if("t"===j)T=B;else if("f"===j)T=E;else if("n"===j)T=I;else if("-"===j)Q+=j;else if("0"===j)Q+=j,T=M;else{if(-1==="123456789".indexOf(j))return c("Bad value");Q+=j,T=M}continue;case x:if(","===j)U.push(x),P&&(m(P),n(),P=""),T=t;else{if("]"!==j){if(g(j))continue;return c("Bad array")}P&&(m(P),n(),P=""),n(),X--,T=U.pop()||t}continue;case y:var e=d-1;a:for(;;){for(;W>0;)if(V+=j,j=a.charAt(d++),4===W?(P+=String.fromCharCode(parseInt(V,16)),W=0,e=d-1):W++,!j)break a;if('"'===j&&!R){T=U.pop()||t,P+=a.substring(e,d-1),P||(m(""),n());break}if("\\"===j&&!R&&(R=!0,P+=a.substring(e,d-1),j=a.charAt(d++),!j))break;if(R){if(R=!1,"n"===j?P+="\n":"r"===j?P+="\r":"t"===j?P+=" ":"f"===j?P+="\f":"b"===j?P+="\b":"u"===j?(W=1,V=""):P+=j,j=a.charAt(d++),e=d-1,j)continue;break}q.lastIndex=d;var f=q.exec(a);if(!f){d=a.length+1,P+=a.substring(e,d-1);break}if(d=f.index+1,j=a.charAt(f.index),!j){P+=a.substring(e,d-1);break}}continue;case B:if(!j)continue;if("r"!==j)return c("Invalid true started with t"+j);T=C;continue;case C:if(!j)continue;if("u"!==j)return c("Invalid true started with tr"+j);T=D;continue;case D:if(!j)continue;if("e"!==j)return c("Invalid true started with tru"+j);m(!0),n(),T=U.pop()||t;continue;case E:if(!j)continue;if("a"!==j)return c("Invalid false started with f"+j);T=F;continue;case F:if(!j)continue;if("l"!==j)return c("Invalid false started with fa"+j);T=G;continue;case G:if(!j)continue;if("s"!==j)return c("Invalid false started with fal"+j);T=H;continue;case H:if(!j)continue;if("e"!==j)return c("Invalid false started with fals"+j);m(!1),n(),T=U.pop()||t;continue;case I:if(!j)continue;if("u"!==j)return c("Invalid null started with n"+j);T=J;continue;case J:if(!j)continue;if("l"!==j)return c("Invalid null started with nu"+j);T=K;continue;case K:if(!j)continue;if("l"!==j)return c("Invalid null started with nul"+j);m(null),n(),T=U.pop()||t;continue;case L:if("."!==j)return c("Leading zero not followed by .");Q+=j,T=M;continue;case M:if(-1!=="0123456789".indexOf(j))Q+=j;else if("."===j){if(-1!==Q.indexOf("."))return c("Invalid number has two dots");Q+=j}else if("e"===j||"E"===j){if(-1!==Q.indexOf("e")||-1!==Q.indexOf("E"))return c("Invalid number has two exponential");Q+=j}else if("+"===j||"-"===j){if("e"!==k&&"E"!==k)return c("Invalid symbol in number");Q+=j}else Q&&(m(parseFloat(Q)),n(),Q=""),d--,T=U.pop()||t;continue;default:return c("Unknown state: "+T)}Y>=O&&b()}}var i,j,k,l=a(qb).emit,m=a(rb).emit,n=a(sb).emit,o=a(jb).emit,p=65536,q=/[\\"\n]/g,r=0,s=r++,t=r++,u=r++,v=r++,w=r++,x=r++,y=r++,z=r++,A=r++,B=r++,C=r++,D=r++,E=r++,F=r++,G=r++,H=r++,I=r++,J=r++,K=r++,L=r++,M=r,O=p,P="",Q="",R=!1,S=!1,T=s,U=[],V=null,W=0,X=0,Y=0,Z=0,$=1;a(nb).on(h),a(ob).on(e)}function D(a,b){"use strict";function c(a){return function(b){d=a(d,b)}}var d,e={};for(var f in b)a(f).on(c(b[f]),e);a(hb).on(function(a){var b,c=Y(d),e=ab(c),f=Z(d);f&&(b=bb(Y(f)),b[e]=a)}),a(ib).on(function(){var a,b=Y(d),c=ab(b),e=Z(d);e&&(a=bb(Y(e)),delete a[c])}),a(pb).on(function(){for(var c in b)a(c).un(e)})}function E(a){var b={};return a&&a.split("\r\n").forEach(function(a){var c=a.indexOf(": ");b[a.substring(0,c)]=a.substring(c+2)}),b}function F(a,b){function c(a){return{"http:":80,"https:":443}[a]}function d(b){return b.port||c(b.protocol||a.protocol)}return!!(b.protocol&&b.protocol!=a.protocol||b.host&&b.host!=a.host||b.host&&d(b)!=d(a))}function G(a){var b=/(\w+:)?(?:\/\/)([\w.-]+)?(?::(\d+))?\/?/,c=b.exec(a)||[];return{protocol:c[1]||"",host:c[2]||"",port:c[3]||""}}function H(){return new XMLHttpRequest}function I(b,c,d,e,g,h,i){"use strict";function j(){var a=c.responseText,b=a.substr(m);b&&k(b),m=V(a)}var k=b(nb).emit,l=b(jb).emit,m=0,n=!0;b(pb).on(function(){c.onreadystatechange=null,c.abort()}),"onprogress"in c&&(c.onprogress=j),c.onreadystatechange=function(){function a(){try{n&&b(mb).emit(c.status,E(c.getAllResponseHeaders())),n=!1}catch(a){}}switch(c.readyState){case 2:case 3:return a();case 4:a();var d=2==String(c.status)[0];d?(j(),b(ob).emit()):l(N(c.status,c.responseText))}};try{c.open(d,e,!0);for(var o in h)c.setRequestHeader(o,h[o]);F(a.location,G(e))||c.setRequestHeader("X-Requested-With","XMLHttpRequest"),c.withCredentials=i,c.send(g)}catch(p){a.setTimeout(T(l,N(f,f,p)),0)}}function J(a,b){return{key:a,node:b}}function K(a){function b(a,b){var d=bb(Y(a));return p(c,d)?f(a,V(d),b):a}function d(a,c){if(!a)return j(c),f(a,cb,c);var d=b(a,c),g=Z(d),h=ab(Y(d));return e(g,h,c),s(J(h,c),g)}function e(a,b,c){bb(Y(a))[b]=c}function f(a,b,c){a&&e(a,b,c);var d=s(J(b,c),a);return h(d),d}function g(a){return i(a),Z(a)||k(bb(Y(a)))}var h=a(fb).emit,i=a(gb).emit,j=a(lb).emit,k=a(kb).emit,l={};return l[rb]=d,l[sb]=g,l[qb]=f,l}function L(a,b,c){function d(a){return function(b){return b.id==a}}var e,f;return{on:function(c,d){var g={listener:c,id:d||c};return b&&b.emit(a,c,g.id),e=s(g,e),f=s(c,f),this},emit:function(){z(f,arguments)},un:function(b){var g;e=x(e,d(b),function(a){g=a}),g&&(f=x(f,function(a){return a==g.listener}),c&&c.emit(a,g.listener,g.id))},listeners:function(){return f},hasListener:function(a){var b=a?d(a):n;return q(B(b,e))}}}function M(){function a(a){return c[a]=L(a,d,e)}function b(b){return c[b]||a(b)}var c={},d=a("newListener"),e=a("removeListener");return["emit","on","un"].forEach(function(a){b[a]=j(function(c,d){i(d,b(c)[a])})}),b}function N(a,b,c){try{var d=e.parse(b)}catch(f){}return{statusCode:a,body:b,jsonBody:d,thrown:c}}function O(a,b){function c(a,b,c){var d=A(c);a(b,u(Z(v(ab,d))),u(v(bb,d)))}function d(b,d,e){var f=a(b).emit;d.on(function(a){var b=e(a);b!==!1&&c(f,bb(b),a)},b),a("removeListener").on(function(c){c==b&&(a(c).listeners()||d.un(b))})}var e={node:a(gb),path:a(fb)};a("newListener").on(function(a){var c=/(node|path):(.*)/.exec(a);if(c){var f=e[c[1]];f.hasListener(a)||d(a,f,b(c[2]))}})}function P(a,b){function c(b,c){return a(b).on(e(c),c),p}function d(a,b,c){c=c||b;var d=e(b);return a.on(function(){var b=!1;p.forget=function(){b=!0},i(arguments,d),delete p.forget,b&&a.un(c)},c),p}function e(b){return function(){try{return b.apply(p,arguments)}catch(c){a(jb).emit(N(f,f,c))}}}function g(b,c){return a(b+":"+c)}function h(a){return function(){var b=a.apply(this,arguments);q(b)&&(b==S.drop?t():u(b))}}function k(a,b,c){var e;e="node"==a?h(c):c,d(g(a,b),e,c)}function l(a,b){for(var c in b)k(a,c,b[c])}function n(a,b,c){return W(b)?k(a,b,c):l(a,b),p}var p,r=/^(node|path):./,s=a(kb),t=a(ib).emit,u=a(hb).emit,v=j(function(b,c){if(p[b])i(c,p[b]);else{var e=a(b),f=c[0];r.test(b)?d(e,f):e.on(f)}return p}),w=function(b,c,d){if("done"==b)s.un(c);else if("node"==b||"path"==b)a.un(b+":"+c,d);else{var e=c;a(b).un(e)}return p};return a(lb).on(function(a){p.root=o(a)}),a(mb).on(function(a,b){p.header=function(a){return a?b[a]:b}}),p={on:v,addListener:v,removeListener:w,emit:a.emit,node:T(n,"node"),path:T(n,"path"),done:T(d,s),start:T(c,mb),fail:a(jb).on,abort:a(pb).emit,header:m,root:m,source:b}}function Q(a,b,c,d,e){var f=M();return b&&I(f,H(),a,b,c,d,e),C(f),D(f,K(f)),O(f,db),P(f,b)}function R(a,b,c,d,f,g,h){function i(a,b){return b===!1&&(a+=-1==a.indexOf("?")?"?":"&",a+="_="+(new Date).getTime()),a}return f=f?e.parse(e.stringify(f)):{},d?W(d)||(d=e.stringify(d),f["Content-Type"]=f["Content-Type"]||"application/json"):d=null,a(c||"GET",i(b,h),d,f,g||!1)}function S(a){var b=$("resume","pause","pipe"),c=T(r,b);return a?c(a)||W(a)?R(Q,a):R(Q,a.url,a.method,a.body,a.headers,a.withCredentials,a.cached):Q()}var T=j(function(a,b){var c=b.length;return j(function(d){for(var e=0;e", 33 | "license": "MIT", 34 | "contributors": [ 35 | "Ben ", 36 | "" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /polyfill/Blob.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import RNFetchBlob from '../index.js' 6 | import fs from '../fs.js' 7 | import getUUID from '../utils/uuid' 8 | import Log from '../utils/log.js' 9 | import EventTarget from './EventTarget' 10 | 11 | const log = new Log('Blob') 12 | const blobCacheDir = fs.dirs.DocumentDir + '/RNFetchBlob-blobs/' 13 | 14 | log.disable() 15 | // log.level(3) 16 | 17 | /** 18 | * A RNFetchBlob style Blob polyfill class, this is a Blob which compatible to 19 | * Response object attain fron RNFetchBlob.fetch. 20 | */ 21 | export default class Blob extends EventTarget { 22 | 23 | cacheName:string; 24 | type:string; 25 | size:number; 26 | isRNFetchBlobPolyfill:boolean = true; 27 | multipartBoundary:string = null; 28 | 29 | _ref:string = null; 30 | _blobCreated:boolean = false; 31 | _onCreated:Array = []; 32 | _closed:boolean = false; 33 | 34 | /** 35 | * Static method that remove all files in Blob cache folder. 36 | * @nonstandard 37 | * @return {Promise} 38 | */ 39 | static clearCache() { 40 | return fs.unlink(blobCacheDir).then(() => fs.mkdir(blobCacheDir)) 41 | } 42 | 43 | static build(data:any, cType:any):Promise { 44 | return new Promise((resolve, reject) => { 45 | new Blob(data, cType).onCreated(resolve) 46 | }) 47 | } 48 | 49 | get blobPath() { 50 | return this._ref 51 | } 52 | 53 | static setLog(level:number) { 54 | if(level === -1) 55 | log.disable() 56 | else 57 | log.level(level) 58 | } 59 | 60 | /** 61 | * RNFetchBlob Blob polyfill, create a Blob directly from file path, BASE64 62 | * encoded data, and string. The conversion is done implicitly according to 63 | * given `mime`. However, the blob creation is asynchronously, to register 64 | * event `onCreated` is need to ensure the Blob is creadted. 65 | * @param {any} data Content of Blob object 66 | * @param {any} mime Content type settings of Blob object, `text/plain` 67 | * by default 68 | * @param {boolean} defer When this argument set to `true`, blob constructor 69 | * will not invoke blob created event automatically. 70 | */ 71 | constructor(data:any, cType:any, defer:boolean) { 72 | super() 73 | cType = cType || {} 74 | this.cacheName = getBlobName() 75 | this.isRNFetchBlobPolyfill = true 76 | this.isDerived = defer 77 | this.type = cType.type || 'text/plain' 78 | log.verbose('Blob constructor called', 'mime', this.type, 'type', typeof data, 'length', data? data.length:0) 79 | this._ref = blobCacheDir + this.cacheName 80 | let p = null 81 | if(!data) 82 | data = '' 83 | if(data.isRNFetchBlobPolyfill) { 84 | log.verbose('create Blob cache file from Blob object') 85 | let size = 0 86 | this._ref = String(data.getRNFetchBlobRef()) 87 | let orgPath = this._ref 88 | 89 | p = fs.exists(orgPath) 90 | .then((exist) => { 91 | if(exist) 92 | return fs.writeFile(orgPath, data, 'uri') 93 | .then((size) => Promise.resolve(size)) 94 | .catch((err) => { 95 | throw `RNFetchBlob Blob file creation error, ${err}` 96 | }) 97 | else 98 | throw `could not create Blob from path ${orgPath}, file not exists` 99 | }) 100 | } 101 | // process FormData 102 | else if(data instanceof FormData) { 103 | log.verbose('create Blob cache file from FormData', data) 104 | let boundary = `RNFetchBlob-${this.cacheName}-${Date.now()}` 105 | this.multipartBoundary = boundary 106 | let parts = data.getParts() 107 | let formArray = [] 108 | if(!parts) { 109 | p = fs.writeFile(this._ref, '', 'utf8') 110 | } 111 | else { 112 | for(let i in parts) { 113 | formArray.push('\r\n--'+boundary+'\r\n') 114 | let part = parts[i] 115 | for(let j in part.headers) { 116 | formArray.push(j + ': ' +part.headers[j] + '\r\n') 117 | } 118 | formArray.push('\r\n') 119 | if(part.isRNFetchBlobPolyfill) 120 | formArray.push(part) 121 | else 122 | formArray.push(part.string) 123 | } 124 | log.verbose('FormData array', formArray) 125 | formArray.push('\r\n--'+boundary+'--\r\n') 126 | p = createMixedBlobData(this._ref, formArray) 127 | } 128 | } 129 | // if the data is a string starts with `RNFetchBlob-file://`, append the 130 | // Blob data from file path 131 | else if(typeof data === 'string' && data.startsWith('RNFetchBlob-file://')) { 132 | log.verbose('create Blob cache file from file path', data) 133 | // set this flag so that we know this blob is a wrapper of an existing file 134 | this._isReference = true 135 | this._ref = String(data).replace('RNFetchBlob-file://', '') 136 | let orgPath = this._ref 137 | if(defer) 138 | return 139 | else { 140 | p = fs.stat(orgPath) 141 | .then((stat) => { 142 | return Promise.resolve(stat.size) 143 | }) 144 | } 145 | } 146 | // content from variable need create file 147 | else if(typeof data === 'string') { 148 | let encoding = 'utf8' 149 | let mime = String(this.type) 150 | // when content type contains application/octet* or *;base64, RNFetchBlob 151 | // fs will treat it as BASE64 encoded string binary data 152 | if(/(application\/octet|\;base64)/i.test(mime)) 153 | encoding = 'base64' 154 | else 155 | data = data.toString() 156 | // create cache file 157 | this.type = String(this.type).replace(/;base64/ig, '') 158 | log.verbose('create Blob cache file from string', 'encode', encoding) 159 | p = fs.writeFile(this._ref, data, encoding) 160 | .then((size) => { 161 | return Promise.resolve(size) 162 | }) 163 | 164 | } 165 | // TODO : ArrayBuffer support 166 | // else if (data instanceof ArrayBuffer ) { 167 | // 168 | // } 169 | // when input is an array of mixed data types, create a file cache 170 | else if(Array.isArray(data)) { 171 | log.verbose('create Blob cache file from mixed array', data) 172 | p = createMixedBlobData(this._ref, data) 173 | } 174 | else { 175 | data = data.toString() 176 | p = fs.writeFile(this._ref, data, 'utf8') 177 | .then((size) => Promise.resolve(size)) 178 | } 179 | p && p.then((size) => { 180 | this.size = size 181 | this._invokeOnCreateEvent() 182 | }) 183 | .catch((err) => { 184 | log.error('RNFetchBlob could not create Blob : '+ this._ref, err) 185 | }) 186 | 187 | } 188 | 189 | /** 190 | * Since Blob content will asynchronously write to a file during creation, 191 | * use this method to register an event handler for Blob initialized event. 192 | * @nonstandard 193 | * @param {(b:Blob) => void} An event handler invoked when Blob created 194 | * @return {Blob} The Blob object instance itself 195 | */ 196 | onCreated(fn:() => void):Blob { 197 | log.verbose('#register blob onCreated', this._blobCreated) 198 | if(!this._blobCreated) 199 | this._onCreated.push(fn) 200 | else { 201 | fn(this) 202 | } 203 | return this 204 | } 205 | 206 | markAsDerived() { 207 | this._isDerived = true 208 | } 209 | 210 | get isDerived() { 211 | return this._isDerived || false 212 | } 213 | 214 | /** 215 | * Get file reference of the Blob object. 216 | * @nonstandard 217 | * @return {string} Blob file reference which can be consumed by RNFetchBlob fs 218 | */ 219 | getRNFetchBlobRef() { 220 | return this._ref 221 | } 222 | 223 | /** 224 | * Create a Blob object which is sliced from current object 225 | * @param {number} start Start byte number 226 | * @param {number} end End byte number 227 | * @param {string} contentType Optional, content type of new Blob object 228 | * @return {Blob} 229 | */ 230 | slice(start:?number, end:?number, contentType:?string=''):Blob { 231 | if(this._closed) 232 | throw 'Blob has been released.' 233 | log.verbose('slice called', start, end, contentType) 234 | 235 | 236 | let resPath = blobCacheDir + getBlobName() 237 | let pass = false 238 | log.debug('fs.slice new blob will at', resPath) 239 | let result = new Blob(RNFetchBlob.wrap(resPath), { type : contentType }, true) 240 | fs.exists(blobCacheDir) 241 | .then((exist) => { 242 | if(exist) 243 | return Promise.resolve() 244 | return fs.mkdir(blobCacheDir) 245 | }) 246 | .then(() => fs.slice(this._ref, resPath, start, end)) 247 | .then((dest) => { 248 | log.debug('fs.slice done', dest) 249 | result._invokeOnCreateEvent() 250 | pass = true 251 | }) 252 | .catch((err) => { 253 | console.warn('Blob.slice failed:', err) 254 | pass = true 255 | }) 256 | log.debug('slice returning new Blob') 257 | 258 | return result 259 | } 260 | 261 | /** 262 | * Read data of the Blob object, this is not standard method. 263 | * @nonstandard 264 | * @param {string} encoding Read data with encoding 265 | * @return {Promise} 266 | */ 267 | readBlob(encoding:string):Promise { 268 | if(this._closed) 269 | throw 'Blob has been released.' 270 | return fs.readFile(this._ref, encoding || 'utf8') 271 | } 272 | 273 | /** 274 | * Release the resource of the Blob object. 275 | * @nonstandard 276 | * @return {Promise} 277 | */ 278 | close() { 279 | if(this._closed) 280 | return Promise.reject('Blob has been released.') 281 | this._closed = true 282 | return fs.unlink(this._ref).catch((err) => { 283 | console.warn(err) 284 | }) 285 | } 286 | 287 | safeClose() { 288 | if(this._closed) 289 | return Promise.reject('Blob has been released.') 290 | this._closed = true 291 | if(!this._isReference) { 292 | return fs.unlink(this._ref).catch((err) => { 293 | console.warn(err) 294 | }) 295 | } 296 | else { 297 | return Promise.resolve() 298 | } 299 | } 300 | 301 | _invokeOnCreateEvent() { 302 | log.verbose('invoke create event', this._onCreated) 303 | this._blobCreated = true 304 | let fns = this._onCreated 305 | for(let i in fns) { 306 | if(typeof fns[i] === 'function') { 307 | fns[i](this) 308 | } 309 | } 310 | delete this._onCreated 311 | } 312 | 313 | } 314 | 315 | /** 316 | * Get a temp filename for Blob object 317 | * @return {string} Temporary filename 318 | */ 319 | function getBlobName() { 320 | return 'blob-' + getUUID() 321 | } 322 | 323 | /** 324 | * Create a file according to given array. The element in array can be a number, 325 | * Blob, String, Array. 326 | * @param {string} ref File path reference 327 | * @param {Array} dataArray An array contains different types of data. 328 | * @return {Promise} 329 | */ 330 | function createMixedBlobData(ref, dataArray) { 331 | // create an empty file for store blob data 332 | let p = fs.writeFile(ref, '') 333 | let args = [] 334 | let size = 0 335 | for(let i in dataArray) { 336 | let part = dataArray[i] 337 | if(!part) 338 | continue 339 | if(part.isRNFetchBlobPolyfill) { 340 | args.push([ref, part._ref, 'uri']) 341 | } 342 | else if(typeof part === 'string') 343 | args.push([ref, part, 'utf8']) 344 | // TODO : ArrayBuffer 345 | // else if (part instanceof ArrayBuffer) { 346 | // 347 | // } 348 | else if (Array.isArray(part)) 349 | args.push([ref, part, 'ascii']) 350 | } 351 | // start write blob data 352 | for(let i in args) { 353 | p = p.then(function(written){ 354 | let arg = this 355 | if(written) 356 | size += written 357 | log.verbose('mixed blob write', args[i], written) 358 | return fs.appendFile(...arg) 359 | }.bind(args[i])) 360 | } 361 | return p.then(() => Promise.resolve(size)) 362 | } 363 | -------------------------------------------------------------------------------- /polyfill/Event.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | export default class Event { 6 | 7 | constructor() { 8 | 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /polyfill/EventTarget.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import Log from '../utils/log.js' 6 | 7 | const log = new Log('EventTarget') 8 | 9 | log.disable() 10 | 11 | export default class EventTarget { 12 | 13 | listeners : any; 14 | 15 | constructor() { 16 | log.info('constructor called') 17 | this.listeners = {} 18 | } 19 | 20 | /** 21 | * Add an event listener to given event type 22 | * @param {string} type Event type string 23 | * @param {(Event) => void} cb Event handler function 24 | */ 25 | addEventListener(type:string, cb : () => void) { 26 | log.info('add event listener', type, cb) 27 | if(!(type in this.listeners)) { 28 | this.listeners[type] = [] 29 | } 30 | this.listeners[type].push(cb) 31 | } 32 | 33 | /** 34 | * Remove an event listener 35 | * @param {string} type Type of the event listener 36 | * @param {()=>void} cb Event listener function. 37 | * @return {[type]} [description] 38 | */ 39 | removeEventListener(type:string, cb:() => void) { 40 | log.info('remove event listener', type, cb) 41 | if(!(type in this.listeners)) 42 | return 43 | let handlers = this.listeners[type] 44 | for(let i in handlers) { 45 | if(cb === handlers[i]) { 46 | handlers.splice(i,1) 47 | return this.removeEventListener(type, cb) 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * Dispatch an event 54 | * @param {string} type Event type. 55 | * @param {Event} event Event data payload. 56 | */ 57 | dispatchEvent(type:string,event:Event) { 58 | log.info('dispatch event', event) 59 | if(!(type in this.listeners)) 60 | return 61 | let handlers = this.listeners[type] 62 | for(let i in handlers) { 63 | handlers[i].call(this, event) 64 | } 65 | 66 | } 67 | 68 | /** 69 | * Remove all registered listeners from this object. 70 | * @nonstandard 71 | * @return {[type]} [description] 72 | */ 73 | clearEventListeners() { 74 | for(let i in this.listeners) { 75 | delete this.listeners[i] 76 | } 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /polyfill/Fetch.js: -------------------------------------------------------------------------------- 1 | import RNFetchBlob from '../index.js' 2 | import Log from '../utils/log.js' 3 | import fs from '../fs' 4 | import unicode from '../utils/unicode' 5 | import Blob from './Blob' 6 | 7 | const log = new Log('FetchPolyfill') 8 | 9 | log.disable() 10 | // log.level(3) 11 | 12 | export default class Fetch { 13 | 14 | constructor(config:RNFetchBlobConfig) { 15 | Object.assign(this, new RNFetchBlobFetchPolyfill(config)) 16 | } 17 | 18 | } 19 | 20 | class RNFetchBlobFetchPolyfill { 21 | 22 | constructor(config:RNFetchBlobConfig) { 23 | this.build = () => (url, options = {}) => { 24 | 25 | let body = options.body 26 | let promise = Promise.resolve() 27 | let blobCache = null 28 | 29 | options.headers = options.headers || {} 30 | let ctype = options['Content-Type'] || options['content-type'] 31 | let ctypeH = options.headers['Content-Type'] || options.headers['content-type'] 32 | options.headers['Content-Type'] = ctype || ctypeH 33 | options.headers['content-type'] = ctype || ctypeH 34 | options.method = options.method || 'GET' 35 | if(body) { 36 | // When the request body is an instance of FormData, create a Blob cache 37 | // to upload the body. 38 | if(body instanceof FormData) { 39 | log.verbose('convert FormData to blob body') 40 | promise = Blob.build(body).then((b) => { 41 | blobCache = b 42 | options.headers['Content-Type'] = 'multipart/form-data;boundary=' + b.multipartBoundary 43 | return Promise.resolve(RNFetchBlob.wrap(b._ref)) 44 | }) 45 | } 46 | // When request body is a Blob, use file URI of the Blob as request body. 47 | else if (body.isRNFetchBlobPolyfill) 48 | promise = Promise.resolve(RNFetchBlob.wrap(body.blobPath)) 49 | else if (typeof body !== 'object' && options.headers['Content-Type'] !== 'application/json') 50 | promise = Promise.resolve(JSON.stringify(body)) 51 | else if (typeof body !== 'string') 52 | promise = Promise.resolve(body.toString()) 53 | // send it as-is, leave the native module decide how to send the body. 54 | else 55 | promise = Promise.resolve(body) 56 | } 57 | // task is a progress reportable and cancellable Promise, however, 58 | // task.then is not, so we have to extend task.then with progress and 59 | // cancel function 60 | let progressHandler, uploadHandler, cancelHandler 61 | let statefulPromise = promise 62 | .then((body) => { 63 | let task = RNFetchBlob.config(config) 64 | .fetch(options.method, url, options.headers, body) 65 | if(progressHandler) 66 | task.progress(progressHandler) 67 | if(uploadHandler) 68 | task.uploadProgress(uploadHandler) 69 | if(cancelHandler) 70 | task.cancel() 71 | return task.then((resp) => { 72 | log.verbose('response', resp) 73 | // release blob cache created when sending request 74 | if(blobCache !== null && blobCache instanceof Blob) 75 | blobCache.close() 76 | return Promise.resolve(new RNFetchBlobFetchRepsonse(resp)) 77 | }) 78 | }) 79 | 80 | // extend task.then progress with report and cancelling functions 81 | statefulPromise.progress = (fn) => { 82 | progressHandler = fn 83 | } 84 | statefulPromise.uploadProgress = (fn) => { 85 | uploadHandler = fn 86 | } 87 | statefulPromise.cancel = () => { 88 | cancelHandler = true 89 | if(task.cancel) 90 | task.cancel() 91 | } 92 | 93 | return statefulPromise 94 | 95 | } 96 | } 97 | 98 | } 99 | 100 | class RNFetchBlobFetchRepsonse { 101 | 102 | constructor(resp:FetchBlobResponse) { 103 | let info = resp.info() 104 | this.headers = info.headers 105 | this.ok = info.status >= 200 && info.status <= 299, 106 | this.status = info.status 107 | this.type = 'basic' 108 | this.bodyUsed = false 109 | this.resp = resp 110 | this.rnfbRespInfo = info 111 | this.rnfbResp = resp 112 | } 113 | 114 | rawResp() { 115 | return Promise.resolve(this.rnfbResp) 116 | } 117 | 118 | arrayBuffer(){ 119 | log.verbose('to arrayBuffer', this.rnfbRespInfo) 120 | this.bodyUsed = true 121 | return readArrayBuffer(this.rnfbResp, this.rnfbRespInfo) 122 | } 123 | 124 | text() { 125 | log.verbose('to text', this.rnfbResp, this.rnfbRespInfo) 126 | this.bodyUsed = true 127 | return readText(this.rnfbResp, this.rnfbRespInfo) 128 | } 129 | 130 | json() { 131 | log.verbose('to json', this.rnfbResp, this.rnfbRespInfo) 132 | this.bodyUsed = true 133 | return readJSON(this.rnfbResp, this.rnfbRespInfo) 134 | } 135 | 136 | blob() { 137 | log.verbose('to blob', this.rnfbResp, this.rnfbRespInfo) 138 | this.bodyUsed = true 139 | return readBlob(this.rnfbResp, this.rnfbRespInfo) 140 | } 141 | } 142 | 143 | /** 144 | * Get response data as array. 145 | * @param {FetchBlobResponse} resp Response data object from RNFB fetch call. 146 | * @param {RNFetchBlobResponseInfo} info Response informations. 147 | * @return {Promise} 148 | */ 149 | function readArrayBuffer(resp, info):Promise { 150 | switch (info.rnfbEncode) { 151 | case 'path': 152 | return resp.readFile('ascii') 153 | break 154 | default: 155 | let buffer = [] 156 | let str = resp.text() 157 | for (let i in str) { 158 | buffer[i] = str.charCodeAt(i); 159 | } 160 | return Promise.resolve(buffer) 161 | break 162 | } 163 | } 164 | 165 | /** 166 | * Get response data as string. 167 | * @param {FetchBlobResponse} resp Response data object from RNFB fetch call. 168 | * @param {RNFetchBlobResponseInfo} info Response informations. 169 | * @return {Promise} 170 | */ 171 | function readText(resp, info):Promise { 172 | switch (info.rnfbEncode) { 173 | case 'base64': 174 | return Promise.resolve(resp.text()) 175 | break 176 | case 'path': 177 | return resp.text() 178 | break 179 | default: 180 | return Promise.resolve(resp.text()) 181 | break 182 | } 183 | } 184 | 185 | 186 | /** 187 | * Get response data as RNFetchBlob Blob polyfill object. 188 | * @param {FetchBlobResponse} resp Response data object from RNFB fetch call. 189 | * @param {RNFetchBlobResponseInfo} info Response informations. 190 | * @return {Promise} 191 | */ 192 | function readBlob(resp, info):Promise { 193 | log.verbose('readBlob', resp, info) 194 | return resp.blob() 195 | } 196 | 197 | /** 198 | * Get response data as JSON object. 199 | * @param {FetchBlobResponse} resp Response data object from RNFB fetch call. 200 | * @param {RNFetchBlobResponseInfo} info Response informations. 201 | * @return {Promise} 202 | */ 203 | function readJSON(resp, info):Promise { 204 | log.verbose('readJSON', resp, info) 205 | switch (info.rnfbEncode) { 206 | case 'base64': 207 | return Promise.resolve(resp.json()) 208 | case 'path': 209 | return resp.json() 210 | default: 211 | return Promise.resolve(resp.json()) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /polyfill/File.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import fs from '../fs.js' 6 | import Blob from './Blob.js' 7 | 8 | export default class File extends Blob { 9 | 10 | name : string = ''; 11 | 12 | static build(name:string, data:any, cType:string):Promise { 13 | return new Promise((resolve, reject) => { 14 | new File(data, cType).onCreated((f) => { 15 | f.name = name 16 | resolve(f) 17 | }) 18 | }) 19 | } 20 | 21 | constructor(data:any , cType:string) { 22 | super(data, cType) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /polyfill/FileReader.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import RNFetchBlob from '../index.js' 6 | import ProgressEvent from './ProgressEvent.js' 7 | import EventTarget from './EventTarget' 8 | import Blob from './Blob' 9 | import Log from '../utils/log.js' 10 | import fs from '../fs' 11 | 12 | const log = new Log('FileReader') 13 | 14 | log.level(3) 15 | 16 | export default class FileReader extends EventTarget { 17 | 18 | static get EMPTY(){ 19 | return 0 20 | } 21 | static get LOADING(){ 22 | return 1 23 | } 24 | static get DONE(){ 25 | return 2 26 | } 27 | 28 | // properties 29 | _readState:number = 0; 30 | _result:any; 31 | _error:any; 32 | 33 | get isRNFBPolyFill(){ return true } 34 | 35 | // event handlers 36 | onloadstart:(e:Event) => void; 37 | onprogress:(e:Event) => void; 38 | onload:(e:Event) => void; 39 | onabort:(e:Event) => void; 40 | onerror:(e:Event) => void; 41 | onloadend:(e:Event) => void; 42 | 43 | constructor() { 44 | super() 45 | log.verbose('file reader const') 46 | this._result = null 47 | } 48 | 49 | abort() { 50 | log.verbose('abort') 51 | } 52 | 53 | readAsArrayBuffer(b:Blob) { 54 | log.verbose('readAsArrayBuffer', b) 55 | } 56 | 57 | readAsBinaryString(b:Blob) { 58 | log.verbose('readAsBinaryString', b) 59 | } 60 | 61 | readAsText(b:Blob, label:?string) { 62 | log.verbose('readAsText', b, label) 63 | } 64 | 65 | readAsDataURL(b:Blob) { 66 | log.verbose('readAsDataURL', b) 67 | } 68 | 69 | dispatchEvent(event, e) { 70 | log.verbose('dispatch event', event, e) 71 | super.dispatchEvent(event, e) 72 | if(typeof this[`on${event}`] === 'function') { 73 | this[`on${event}`](e) 74 | } 75 | } 76 | 77 | // private methods 78 | 79 | // getters and setters 80 | 81 | get readyState() { 82 | return this._readyState 83 | } 84 | 85 | get result() { 86 | return this._result 87 | } 88 | 89 | 90 | 91 | } 92 | -------------------------------------------------------------------------------- /polyfill/ProgressEvent.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import Event from './Event' 6 | 7 | export default class ProgressEvent extends Event { 8 | 9 | _lengthComputable : boolean = false; 10 | _loaded : number = -1; 11 | _total : numver = -1; 12 | 13 | constructor(lengthComputable, loaded, total) { 14 | super() 15 | this._lengthComputable = lengthComputable; 16 | this._loaded = loaded 17 | this._total = total 18 | } 19 | 20 | get lengthComputable() { 21 | return this._lengthComputable 22 | } 23 | 24 | get loaded() { 25 | return this._loaded 26 | } 27 | 28 | get total() { 29 | return this._total 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /polyfill/XMLHttpRequest.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import RNFetchBlob from '../index.js' 6 | import XMLHttpRequestEventTarget from './XMLHttpRequestEventTarget.js' 7 | import Log from '../utils/log.js' 8 | import Blob from './Blob.js' 9 | import ProgressEvent from './ProgressEvent.js' 10 | import URIUtil from '../utils/uri' 11 | 12 | const log = new Log('XMLHttpRequest') 13 | 14 | log.disable() 15 | // log.level(3) 16 | 17 | const UNSENT = 0 18 | const OPENED = 1 19 | const HEADERS_RECEIVED = 2 20 | const LOADING = 3 21 | const DONE = 4 22 | 23 | export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ 24 | 25 | _onreadystatechange : () => void; 26 | 27 | upload : XMLHttpRequestEventTarget = new XMLHttpRequestEventTarget(); 28 | static binaryContentTypes : Array = [ 29 | 'image/', 'video/', 'audio/' 30 | ]; 31 | 32 | // readonly 33 | _readyState : number = UNSENT; 34 | _uriType : 'net' | 'file' = 'net'; 35 | _response : any = ''; 36 | _responseText : any = ''; 37 | _responseHeaders : any = {}; 38 | _responseType : '' | 'arraybuffer' | 'blob' | 'json' | 'text' = ''; 39 | // TODO : not suppoted ATM 40 | _responseURL : null = ''; 41 | _responseXML : null = ''; 42 | _status : number = 0; 43 | _statusText : string = ''; 44 | _timeout : number = 60000; 45 | _sendFlag : boolean = false; 46 | _uploadStarted : boolean = false; 47 | _increment : boolean = false; 48 | 49 | // RNFetchBlob compatible data structure 50 | _config : RNFetchBlobConfig = {}; 51 | _url : any; 52 | _method : string; 53 | _headers: any = { 54 | 'Content-Type' : 'text/plain' 55 | }; 56 | _cleanUp : () => void = null; 57 | _body: any; 58 | 59 | // RNFetchBlob promise object, which has `progress`, `uploadProgress`, and 60 | // `cancel` methods. 61 | _task: any; 62 | 63 | // constants 64 | get UNSENT() { return UNSENT } 65 | get OPENED() { return OPENED } 66 | get HEADERS_RECEIVED() { return HEADERS_RECEIVED } 67 | get LOADING() { return LOADING } 68 | get DONE() { return DONE } 69 | 70 | static get UNSENT() { 71 | return UNSENT 72 | } 73 | 74 | static get OPENED() { 75 | return OPENED 76 | } 77 | 78 | static get HEADERS_RECEIVED() { 79 | return HEADERS_RECEIVED 80 | } 81 | 82 | static get LOADING() { 83 | return LOADING 84 | } 85 | 86 | static get DONE() { 87 | return DONE 88 | } 89 | 90 | static setLog(level:number) { 91 | if(level === -1) 92 | log.disable() 93 | else 94 | log.level(level) 95 | } 96 | 97 | static addBinaryContentType(substr:string) { 98 | for(let i in XMLHttpRequest.binaryContentTypes) { 99 | if(new RegExp(substr,'i').test(XMLHttpRequest.binaryContentTypes[i])) { 100 | return 101 | } 102 | } 103 | XMLHttpRequest.binaryContentTypes.push(substr) 104 | 105 | } 106 | 107 | static removeBinaryContentType(val) { 108 | for(let i in XMLHttpRequest.binaryContentTypes) { 109 | if(new RegExp(substr,'i').test(XMLHttpRequest.binaryContentTypes[i])) { 110 | XMLHttpRequest.binaryContentTypes.splice(i,1) 111 | return 112 | } 113 | } 114 | } 115 | 116 | constructor() { 117 | log.verbose('XMLHttpRequest constructor called') 118 | super() 119 | } 120 | 121 | 122 | /** 123 | * XMLHttpRequest.open, always async, user and password not supported. When 124 | * this method invoked, headers should becomes empty again. 125 | * @param {string} method Request method 126 | * @param {string} url Request URL 127 | * @param {true} async Always async 128 | * @param {any} user NOT SUPPORTED 129 | * @param {any} password NOT SUPPORTED 130 | */ 131 | open(method:string, url:string, async:true, user:any, password:any) { 132 | log.verbose('XMLHttpRequest open ', method, url, async, user, password) 133 | this._method = method 134 | this._url = url 135 | this._headers = {} 136 | this._increment = URIUtil.isJSONStreamURI(this._url) 137 | this._url = this._url.replace(/^JSONStream\:\/\//, '') 138 | this._dispatchReadStateChange(XMLHttpRequest.OPENED) 139 | } 140 | 141 | /** 142 | * Invoke this function to send HTTP request, and set body. 143 | * @param {any} body Body in RNfetchblob flavor 144 | */ 145 | send(body) { 146 | 147 | this._body = body 148 | 149 | if(this._readyState !== XMLHttpRequest.OPENED) 150 | throw 'InvalidStateError : XMLHttpRequest is not opened yet.' 151 | let promise = Promise.resolve() 152 | this._sendFlag = true 153 | log.verbose('XMLHttpRequest send ', body) 154 | let {_method, _url, _headers } = this 155 | log.verbose('sending request with args', _method, _url, _headers, body) 156 | log.verbose(typeof body, body instanceof FormData) 157 | 158 | if(body instanceof Blob) { 159 | log.debug('sending blob body', body._blobCreated) 160 | promise = new Promise((resolve, reject) => { 161 | body.onCreated((blob) => { 162 | // when the blob is derived (not created by RN developer), the blob 163 | // will be released after XMLHttpRequest sent 164 | if(blob.isDerived) { 165 | this._cleanUp = () => { 166 | blob.close() 167 | } 168 | } 169 | log.debug('body created send request') 170 | body = RNFetchBlob.wrap(blob.getRNFetchBlobRef()) 171 | resolve() 172 | }) 173 | }) 174 | } 175 | else if(typeof body === 'object') { 176 | body = JSON.stringify(body) 177 | promise = Promise.resolve() 178 | } 179 | else { 180 | body = body ? body.toString() : body 181 | promise = Promise.resolve() 182 | } 183 | 184 | promise.then(() => { 185 | log.debug('send request invoke', body) 186 | for(let h in _headers) { 187 | _headers[h] = _headers[h].toString() 188 | } 189 | 190 | this._task = RNFetchBlob 191 | .config({ 192 | auto: true, 193 | timeout : this._timeout, 194 | increment : this._increment, 195 | binaryContentTypes : XMLHttpRequest.binaryContentTypes 196 | }) 197 | .fetch(_method, _url, _headers, body) 198 | this._task 199 | .stateChange(this._headerReceived) 200 | .uploadProgress(this._uploadProgressEvent) 201 | .progress(this._progressEvent) 202 | .catch(this._onError) 203 | .then(this._onDone) 204 | 205 | }) 206 | } 207 | 208 | overrideMimeType(mime:string) { 209 | log.verbose('XMLHttpRequest overrideMimeType', mime) 210 | this._headers['Content-Type'] = mime 211 | } 212 | 213 | setRequestHeader(name, value) { 214 | log.verbose('XMLHttpRequest set header', name, value) 215 | if(this._readyState !== OPENED || this._sendFlag) { 216 | throw `InvalidStateError : Calling setRequestHeader in wrong state ${this._readyState}` 217 | } 218 | // UNICODE SHOULD NOT PASS 219 | if(typeof name !== 'string' || /[^\u0000-\u00ff]/.test(name)) { 220 | throw 'TypeError : header field name should be a string' 221 | } 222 | // 223 | let invalidPatterns = [ 224 | /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/, 225 | /tt/ 226 | ] 227 | for(let i in invalidPatterns) { 228 | if(invalidPatterns[i].test(name) || typeof name !== 'string') { 229 | throw `SyntaxError : Invalid header field name ${name}` 230 | } 231 | } 232 | this._headers[name] = value 233 | } 234 | 235 | abort() { 236 | log.verbose('XMLHttpRequest abort ') 237 | if(!this._task) 238 | return 239 | this._task.cancel((err) => { 240 | let e = { 241 | timeStamp : Date.now(), 242 | } 243 | if(this.onabort) 244 | this.onabort() 245 | if(err) { 246 | e.detail = err 247 | e.type = 'error' 248 | this.dispatchEvent('error', e) 249 | } 250 | else { 251 | e.type = 'abort' 252 | this.dispatchEvent('abort', e) 253 | } 254 | }) 255 | } 256 | 257 | getResponseHeader(field:string):string | null { 258 | log.verbose('XMLHttpRequest get header', field, this._responseHeaders) 259 | if(!this._responseHeaders) 260 | return null 261 | return (this._responseHeaders[field] || this._responseHeaders[field.toLowerCase()]) || null 262 | 263 | } 264 | 265 | getAllResponseHeaders():string | null { 266 | log.verbose('XMLHttpRequest get all headers', this._responseHeaders) 267 | if(!this._responseHeaders) 268 | return '' 269 | let result = '' 270 | let respHeaders = this.responseHeaders 271 | for(let i in respHeaders) { 272 | result += `${i}: ${respHeaders[i]}${String.fromCharCode(0x0D,0x0A)}` 273 | } 274 | return result.substr(0, result.length-2) 275 | } 276 | 277 | _headerReceived = (e) => { 278 | log.debug('header received ', this._task.taskId, e) 279 | this.responseURL = this._url 280 | if(e.state === "2" && e.taskId === this._task.taskId) { 281 | this._responseHeaders = e.headers 282 | this._statusText = e.status 283 | this._status = Math.floor(e.status) 284 | this._dispatchReadStateChange(XMLHttpRequest.HEADERS_RECEIVED) 285 | } 286 | } 287 | 288 | _uploadProgressEvent = (send:number, total:number) => { 289 | if(!this._uploadStarted) { 290 | this.upload.dispatchEvent('loadstart') 291 | this._uploadStarted = true 292 | } 293 | if(send >= total) 294 | this.upload.dispatchEvent('load') 295 | this.upload.dispatchEvent('progress', new ProgressEvent(true, send, total)) 296 | } 297 | 298 | _progressEvent = (send:number, total:number, chunk:string) => { 299 | log.verbose(this.readyState) 300 | if(this._readyState === XMLHttpRequest.HEADERS_RECEIVED) 301 | this._dispatchReadStateChange(XMLHttpRequest.LOADING) 302 | let lengthComputable = false 303 | if(total && total >= 0) 304 | lengthComputable = true 305 | let e = new ProgressEvent(lengthComputable, send, total) 306 | 307 | if(this._increment) { 308 | this._responseText += chunk 309 | } 310 | this.dispatchEvent('progress', e) 311 | } 312 | 313 | _onError = (err) => { 314 | let statusCode = Math.floor(this.status) 315 | if(statusCode >= 100 && statusCode !== 408) { 316 | return 317 | } 318 | log.debug('XMLHttpRequest error', err) 319 | this._statusText = err 320 | this._status = String(err).match(/\d+/) 321 | this._status = this._status ? Math.floor(this.status) : 404 322 | this._dispatchReadStateChange(XMLHttpRequest.DONE) 323 | if(err && String(err.message).match(/(timed\sout|timedout)/) || this._status == 408) { 324 | this.dispatchEvent('timeout') 325 | } 326 | this.dispatchEvent('loadend') 327 | this.dispatchEvent('error', { 328 | type : 'error', 329 | detail : err 330 | }) 331 | this.clearEventListeners() 332 | } 333 | 334 | _onDone = (resp) => { 335 | log.debug('XMLHttpRequest done', this._url, resp, this) 336 | this._statusText = this._status 337 | let responseDataReady = () => { 338 | log.debug('request done state = 4') 339 | this.dispatchEvent('load') 340 | this.dispatchEvent('loadend') 341 | this._dispatchReadStateChange(XMLHttpRequest.DONE) 342 | this.clearEventListeners() 343 | } 344 | if(resp) { 345 | let info = resp.respInfo || {} 346 | log.debug(this._url, info, info.respType) 347 | switch(this._responseType) { 348 | case 'blob' : 349 | resp.blob().then((b) => { 350 | this._responseText = resp.text() 351 | this._response = b 352 | responseDataReady() 353 | }) 354 | break; 355 | case 'arraybuffer': 356 | // TODO : to array buffer 357 | break 358 | case 'json': 359 | this._response = resp.json() 360 | this._responseText = resp.text() 361 | break 362 | default : 363 | this._responseText = resp.text() 364 | this._response = this.responseText 365 | responseDataReady() 366 | break; 367 | } 368 | } 369 | 370 | } 371 | 372 | _dispatchReadStateChange(state) { 373 | this._readyState = state 374 | if(typeof this._onreadystatechange === 'function') 375 | this._onreadystatechange() 376 | } 377 | 378 | set onreadystatechange(fn:() => void) { 379 | log.verbose('XMLHttpRequest set onreadystatechange', fn) 380 | this._onreadystatechange = fn 381 | } 382 | 383 | get onreadystatechange() { 384 | return this._onreadystatechange 385 | } 386 | 387 | get readyState() { 388 | log.verbose('get readyState', this._readyState) 389 | return this._readyState 390 | } 391 | 392 | get status() { 393 | log.verbose('get status', this._status) 394 | return this._status 395 | } 396 | 397 | get statusText() { 398 | log.verbose('get statusText', this._statusText) 399 | return this._statusText 400 | } 401 | 402 | get response() { 403 | log.verbose('get response', this._response) 404 | return this._response 405 | } 406 | 407 | get responseText() { 408 | log.verbose('get responseText', this._responseText) 409 | return this._responseText 410 | } 411 | 412 | get responseURL() { 413 | log.verbose('get responseURL', this._responseURL) 414 | return this._responseURL 415 | } 416 | 417 | get responseHeaders() { 418 | log.verbose('get responseHeaders', this._responseHeaders) 419 | return this._responseHeaders 420 | } 421 | 422 | set timeout(val) { 423 | this._timeout = val*1000 424 | log.verbose('set timeout', this._timeout) 425 | } 426 | 427 | get timeout() { 428 | log.verbose('get timeout', this._timeout) 429 | return this._timeout 430 | } 431 | 432 | set responseType(val) { 433 | log.verbose('set response type', this._responseType) 434 | this._responseType = val 435 | } 436 | 437 | get responseType() { 438 | log.verbose('get response type', this._responseType) 439 | return this._responseType 440 | } 441 | 442 | static get isRNFBPolyfill() { 443 | return true 444 | } 445 | 446 | } 447 | -------------------------------------------------------------------------------- /polyfill/XMLHttpRequestEventTarget.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 wkh237@github. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import EventTarget from './EventTarget.js' 6 | import Log from '../utils/log.js' 7 | 8 | const log = new Log('XMLHttpRequestEventTarget') 9 | 10 | log.disable() 11 | // log.level(3) 12 | 13 | export default class XMLHttpRequestEventTarget extends EventTarget { 14 | 15 | _onabort : (e:Event) => void = () => {}; 16 | _onerror : (e:Event) => void = () => {}; 17 | _onload : (e:Event) => void = () => {}; 18 | _onloadstart : (e:Event) => void = () => {}; 19 | _onprogress : (e:Event) => void = () => {}; 20 | _ontimeout : (e:Event) => void = () => {}; 21 | _onloadend : (e:Event) => void = () => {}; 22 | 23 | constructor() { 24 | super() 25 | log.info('constructor called') 26 | } 27 | 28 | dispatchEvent(event:string, e:Event) { 29 | log.debug('dispatch event', event, e) 30 | super.dispatchEvent(event, e) 31 | switch(event) { 32 | case 'abort' : 33 | this._onabort(e) 34 | break; 35 | case 'error' : 36 | this._onerror(e) 37 | break; 38 | case 'load' : 39 | this._onload(e) 40 | break; 41 | case 'loadstart' : 42 | this._onloadstart(e) 43 | break; 44 | case 'loadend' : 45 | this._onloadend(e) 46 | break; 47 | case 'progress' : 48 | this._onprogress(e) 49 | break; 50 | case 'timeout' : 51 | this._ontimeout(e) 52 | break; 53 | } 54 | } 55 | 56 | set onabort(fn:(e:Event) => void) { 57 | log.info('set onabort') 58 | this._onabort = fn 59 | } 60 | 61 | get onabort() { 62 | return this._onabort 63 | } 64 | set onerror(fn:(e:Event) => void) { 65 | log.info('set onerror') 66 | this._onerror = fn 67 | } 68 | 69 | get onerror() { 70 | return this._onerror 71 | } 72 | 73 | set onload(fn:(e:Event) => void) { 74 | log.info('set onload', fn) 75 | this._onload = fn 76 | } 77 | 78 | get onload() { 79 | return this._onload 80 | } 81 | 82 | set onloadstart(fn:(e:Event) => void) { 83 | log.info('set onloadstart') 84 | this._onloadstart = fn 85 | } 86 | 87 | get onloadstart() { 88 | return this._onloadstart 89 | } 90 | 91 | set onprogress(fn:(e:Event) => void) { 92 | log.info('set onprogress') 93 | this._onprogress = fn 94 | } 95 | 96 | get onprogress() { 97 | return this._onprogress 98 | } 99 | 100 | set ontimeout(fn:(e:Event) => void) { 101 | log.info('set ontimeout') 102 | this._ontimeout = fn 103 | } 104 | 105 | get ontimeout() { 106 | return this._ontimeout 107 | } 108 | 109 | set onloadend(fn:(e:Event) => void) { 110 | log.info('set onloadend') 111 | this._onloadend = fn 112 | } 113 | 114 | get onloadend() { 115 | return this._onloadend 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /polyfill/index.js: -------------------------------------------------------------------------------- 1 | import Blob from './Blob.js' 2 | import File from './File.js' 3 | import XMLHttpRequest from './XMLHttpRequest.js' 4 | import ProgressEvent from './ProgressEvent' 5 | import Event from './Event' 6 | import FileReader from './FileReader' 7 | import Fetch from './Fetch' 8 | 9 | export default { 10 | Blob, File, XMLHttpRequest, ProgressEvent, Event, FileReader, Fetch 11 | } 12 | -------------------------------------------------------------------------------- /react-native-fetch-blob.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "react-native-fetch-blob" 3 | s.version = "0.10.6" 4 | s.summary = "A project committed to make file acess and data transfer easier, effiecient for React Native developers." 5 | s.requires_arc = true 6 | s.license = 'MIT' 7 | s.homepage = 'n/a' 8 | s.authors = { "wkh237" => "xeiyan@gmail.com" } 9 | s.source = { :git => "https://github.com/wkh237/react-native-fetch-blob", :tag => 'v0.10.6'} 10 | s.source_files = 'ios/**/*.{h,m}' 11 | s.platform = :ios, "7.0" 12 | s.dependency 'React/Core' 13 | end 14 | -------------------------------------------------------------------------------- /scripts/prelink.js: -------------------------------------------------------------------------------- 1 | try { 2 | var fs = require('fs'); 3 | var glob = require('glob'); 4 | var addAndroidPermissions = process.env.RNFB_ANDROID_PERMISSIONS == 'true'; 5 | var MANIFEST_PATH = glob.sync(process.cwd() + '/android/app/src/main/**/AndroidManifest.xml')[0]; 6 | var PACKAGE_JSON = process.cwd() + '/package.json'; 7 | var package = JSON.parse(fs.readFileSync(PACKAGE_JSON)); 8 | var APP_NAME = package.name; 9 | var PACKAGE_GRADLE = process.cwd() + '/node_modules/react-native-fetch-blob/android/build.gradle' 10 | var VERSION = checkVersion(); 11 | 12 | console.log('RNFetchBlob detected app version => ' + VERSION); 13 | 14 | if(VERSION < 0.28) { 15 | console.log('You project version is '+ VERSION + ' which may not compatible to react-native-fetch-blob 7.0+, please consider upgrade your application template to react-native 0.27+.') 16 | // add OkHttp3 dependency fo pre 0.28 project 17 | var main = fs.readFileSync(PACKAGE_GRADLE); 18 | console.log('adding OkHttp3 dependency to pre 0.28 project .. ') 19 | main = String(main).replace('//{RNFetchBlob_PRE_0.28_DEPDENDENCY}', "compile 'com.squareup.okhttp3:okhttp:3.4.1'"); 20 | fs.writeFileSync(PACKAGE_GRADLE, main); 21 | console.log('adding OkHttp3 dependency to pre 0.28 project .. ok') 22 | } 23 | 24 | console.log('Add Android permissions => ' + (addAndroidPermissions == "true")) 25 | 26 | if(addAndroidPermissions) { 27 | 28 | // set file access permission for Android < 6.0 29 | fs.readFile(MANIFEST_PATH, function(err, data) { 30 | 31 | if(err) 32 | console.log('failed to locate AndroidManifest.xml file, you may have to add file access permission manually.'); 33 | else { 34 | 35 | console.log('RNFetchBlob patching AndroidManifest.xml .. '); 36 | // append fs permission 37 | data = String(data).replace( 38 | '', 39 | '\n ' 40 | ) 41 | // append DOWNLOAD_COMPLETE intent permission 42 | data = String(data).replace( 43 | '', 44 | '\n ' 45 | ) 46 | fs.writeFileSync(MANIFEST_PATH, data); 47 | console.log('RNFetchBlob patching AndroidManifest.xml .. ok'); 48 | 49 | } 50 | 51 | }) 52 | } 53 | else { 54 | console.log( 55 | '\033[95mreact-native-fetch-blob \033[97mwill not automatically add Android permissions after \033[92m0.9.4 '+ 56 | '\033[97mplease run the following command if you want to add default permissions :\n\n' + 57 | '\033[96m\tRNFB_ANDROID_PERMISSIONS=true react-native link \n') 58 | } 59 | 60 | function checkVersion() { 61 | console.log('RNFetchBlob checking app version ..'); 62 | return parseFloat(/\d\.\d+(?=\.)/.exec(package.dependencies['react-native'])); 63 | } 64 | 65 | } catch(err) { 66 | console.log( 67 | '\033[95mreact-native-fetch-blob\033[97m link \033[91mFAILED \033[97m\nCould not automatically link package :'+ 68 | err.stack + 69 | 'please follow the instructions to manually link the library : ' + 70 | '\033[4mhttps://github.com/wkh237/react-native-fetch-blob/wiki/Manually-Link-Package\n') 71 | } 72 | -------------------------------------------------------------------------------- /types.js: -------------------------------------------------------------------------------- 1 | 2 | type RNFetchBlobConfig = { 3 | fileCache : bool, 4 | path : string, 5 | appendExt : string, 6 | session : string, 7 | addAndroidDownloads : any, 8 | indicator : bool 9 | }; 10 | 11 | type RNFetchBlobNative = { 12 | // API for fetch octet-stream data 13 | fetchBlob : ( 14 | options:fetchConfig, 15 | taskId:string, 16 | method:string, 17 | url:string, 18 | headers:any, 19 | body:any, 20 | callback:(err:any, ...data:any) => void 21 | ) => void, 22 | // API for fetch form data 23 | fetchBlobForm : ( 24 | options:fetchConfig, 25 | taskId:string, 26 | method:string, 27 | url:string, 28 | headers:any, 29 | form:Array, 30 | callback:(err:any, ...data:any) => void 31 | ) => void, 32 | // open file stream 33 | readStream : ( 34 | path:string, 35 | encode:'utf8' | 'ascii' | 'base64' 36 | ) => void, 37 | // get system folders 38 | getEnvironmentDirs : (dirs:any) => void, 39 | // unlink file by path 40 | unlink : (path:string, callback: (err:any) => void) => void, 41 | removeSession : (paths:Array, callback: (err:any) => void) => void, 42 | ls : (path:string, callback: (err:any) => void) => void, 43 | }; 44 | 45 | type RNFetchBlobResponseInfo = { 46 | taskId : string, 47 | state : number, 48 | headers : any, 49 | status : number, 50 | respType : 'text' | 'blob' | '' | 'json', 51 | rnfbEncode : 'path' | 'base64' | 'ascii' | 'utf8' 52 | } 53 | 54 | type RNFetchBlobStream = { 55 | onData : () => void, 56 | onError : () => void, 57 | onEnd : () => void, 58 | _onData : () => void, 59 | _onEnd : () => void, 60 | _onError : () => void, 61 | } 62 | -------------------------------------------------------------------------------- /utils/log.js: -------------------------------------------------------------------------------- 1 | export default class Log { 2 | 3 | _name:string; 4 | _isEnable:boolean = true 5 | _level:number = 0 6 | 7 | constructor(name:string) { 8 | this._name = name 9 | } 10 | 11 | level(val:number) { 12 | this._isEnable = true 13 | this._level = val 14 | } 15 | 16 | enable() { 17 | this._isEnable = true 18 | } 19 | 20 | disable() { 21 | this._isEnable = false 22 | } 23 | 24 | verbose(...args) { 25 | this._isEnable && this._level > 2 && console.log(this._name, 'verbose:', ...args) 26 | } 27 | 28 | debug(...args) { 29 | this._isEnable && this._level > 1 && console.log(this._name, 'debug:', ...args) 30 | } 31 | 32 | info(...args) { 33 | this._isEnable && this._level > 0 && console.log(this._name, 'info:', ...args) 34 | } 35 | 36 | error(...args) { 37 | this._isEnable && this._level > -1 && console.warn(this._name, 'error:', ...args) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /utils/unicode.js: -------------------------------------------------------------------------------- 1 | export default function(x) { 2 | var r = /\\u([\d\w]{4})/gi 3 | x = x.replace(r, function (match, grp) { 4 | return String.fromCharCode(parseInt(grp, 16)) 5 | }) 6 | return unescape(x) 7 | } 8 | -------------------------------------------------------------------------------- /utils/uri.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | isFileURI : (uri:string):boolean => { 4 | if(typeof uri !== 'string') 5 | return false 6 | return /^RNFetchBlob-file\:\/\//.test(uri) 7 | }, 8 | 9 | isJSONStreamURI : (uri:string):boolean => { 10 | if(typeof uri !== 'string') 11 | return false 12 | return /^JSONStream\:\/\//.test(uri) 13 | }, 14 | 15 | removeURIScheme : (uri:string, iterations:number):string => { 16 | iterations = iterations || 1 17 | let result = uri 18 | for(let i=0;i { 25 | return String(uri).replace(/^RNFetchBlob-file\:\/\//, '') 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /utils/uuid.js: -------------------------------------------------------------------------------- 1 | export default function getUUID() { 2 | return Math.random().toString(36).substring(2, 15) + 3 | Math.random().toString(36).substring(2, 15); 4 | } 5 | --------------------------------------------------------------------------------