├── .github └── ISSUE_TEMPLATE ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── android.js ├── android ├── .gitignore ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── build.gradle ├── gradle.properties ├── 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 │ │ ├── FileProvider.java │ │ └── PathResolver.java │ └── res │ ├── values │ └── strings.xml │ └── xml │ └── provider_paths.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.d.ts ├── index.js ├── index.js.flow ├── ios.js ├── ios ├── 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 ├── RNFetchBlobRequest.h └── RNFetchBlobRequest.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 ├── rn-fetch-blob.podspec ├── 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 | -------------------------------------------------------------------------------- /.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 | yarn.lock 42 | 43 | # BUCK 44 | buck-out/ 45 | \.buckd/ 46 | android/app/libs 47 | android/keystores/debug.keystore 48 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | CONTRIBUTORS.md 2 | CONTRIBUTING.md 3 | CODE_OF_CONDUCT.md 4 | 5 | .github/ 6 | components/ 7 | img/ 8 | scripts/ 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | For developers who interested in making contribution to this project, please see [https://github.com/joltup/rn-fetch-blob-dev](https://github.com/joltup/rn-fetch-blob-dev) for more information. 2 | -------------------------------------------------------------------------------- /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 | function getSDCardDir() { 42 | if(Platform.OS === 'android') 43 | return RNFetchBlob.getSDCardDir() 44 | else 45 | return Promise.reject('RNFetchBlob.android.getSDCardDir only supports Android.') 46 | } 47 | 48 | function getSDCardApplicationDir() { 49 | if(Platform.OS === 'android') 50 | return RNFetchBlob.getSDCardApplicationDir() 51 | else 52 | return Promise.reject('RNFetchBlob.android.getSDCardApplicationDir only supports Android.') 53 | } 54 | 55 | 56 | export default { 57 | actionViewIntent, 58 | getContentIntent, 59 | addCompleteDownload, 60 | getSDCardDir, 61 | getSDCardApplicationDir, 62 | } 63 | -------------------------------------------------------------------------------- /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/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | 19 | 1634215444278 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | def safeExtGet(prop, fallback) { 4 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | google() 10 | } 11 | 12 | buildscript { 13 | repositories { 14 | mavenCentral() 15 | google() 16 | } 17 | dependencies { 18 | classpath 'com.android.tools.build:gradle:4.0.1' 19 | } 20 | } 21 | 22 | android { 23 | compileSdkVersion safeExtGet('compileSdkVersion', 30) 24 | buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3') 25 | defaultConfig { 26 | minSdkVersion safeExtGet('minSdkVersion', 16) 27 | targetSdkVersion safeExtGet('targetSdkVersion', 30) 28 | versionCode 1 29 | versionName "1.0" 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | productFlavors { 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" 43 | implementation "com.squareup.okhttp3:okhttp:${safeExtGet('okhttp', '+')}" 44 | implementation "com.squareup.okhttp3:logging-interceptor:${safeExtGet('okhttp', '+')}" 45 | implementation "com.squareup.okhttp3:okhttp-urlconnection:${safeExtGet('okhttp', '+')}" 46 | // {RNFetchBlob_PRE_0.28_DEPDENDENCY} 47 | } 48 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 07 22:58:34 IST 2020 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-6.1.1-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 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /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.content.pm.PackageManager; 7 | import android.net.Uri; 8 | import android.os.Build; 9 | import androidx.core.content.FileProvider; 10 | import android.util.SparseArray; 11 | 12 | import com.facebook.react.bridge.ActivityEventListener; 13 | import com.facebook.react.bridge.Callback; 14 | import com.facebook.react.bridge.LifecycleEventListener; 15 | import com.facebook.react.bridge.Promise; 16 | import com.facebook.react.bridge.ReactApplicationContext; 17 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 18 | import com.facebook.react.bridge.ReactMethod; 19 | import com.facebook.react.bridge.ReadableArray; 20 | import com.facebook.react.bridge.ReadableMap; 21 | 22 | // Cookies 23 | import com.facebook.react.bridge.WritableMap; 24 | import com.facebook.react.modules.network.ForwardingCookieHandler; 25 | import com.facebook.react.modules.network.CookieJarContainer; 26 | import com.facebook.react.modules.network.OkHttpClientProvider; 27 | 28 | import okhttp3.JavaNetCookieJar; 29 | import okhttp3.OkHttpClient; 30 | 31 | import java.io.File; 32 | import java.util.Map; 33 | import java.util.concurrent.LinkedBlockingQueue; 34 | import java.util.concurrent.ThreadPoolExecutor; 35 | import java.util.concurrent.TimeUnit; 36 | 37 | import static android.app.Activity.RESULT_OK; 38 | import static com.RNFetchBlob.RNFetchBlobConst.GET_CONTENT_INTENT; 39 | 40 | public class RNFetchBlob extends ReactContextBaseJavaModule { 41 | 42 | private final OkHttpClient mClient; 43 | 44 | static ReactApplicationContext RCTContext; 45 | private static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); 46 | private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue); 47 | static LinkedBlockingQueue fsTaskQueue = new LinkedBlockingQueue<>(); 48 | private static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue); 49 | private static boolean ActionViewVisible = false; 50 | private static SparseArray promiseTable = new SparseArray<>(); 51 | 52 | public RNFetchBlob(ReactApplicationContext reactContext) { 53 | 54 | super(reactContext); 55 | 56 | mClient = OkHttpClientProvider.getOkHttpClient(); 57 | ForwardingCookieHandler mCookieHandler = new ForwardingCookieHandler(reactContext); 58 | CookieJarContainer mCookieJarContainer = (CookieJarContainer) mClient.cookieJar(); 59 | mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler)); 60 | 61 | RCTContext = reactContext; 62 | reactContext.addActivityEventListener(new ActivityEventListener() { 63 | @Override 64 | public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { 65 | if(requestCode == GET_CONTENT_INTENT && resultCode == RESULT_OK) { 66 | Uri d = data.getData(); 67 | promiseTable.get(GET_CONTENT_INTENT).resolve(d.toString()); 68 | promiseTable.remove(GET_CONTENT_INTENT); 69 | } 70 | } 71 | 72 | @Override 73 | public void onNewIntent(Intent intent) { 74 | 75 | } 76 | }); 77 | } 78 | 79 | @Override 80 | public String getName() { 81 | return "RNFetchBlob"; 82 | } 83 | 84 | @Override 85 | public Map getConstants() { 86 | return RNFetchBlobFS.getSystemfolders(this.getReactApplicationContext()); 87 | } 88 | 89 | @ReactMethod 90 | public void createFile(final String path, final String content, final String encode, final Promise promise) { 91 | threadPool.execute(new Runnable() { 92 | @Override 93 | public void run() { 94 | RNFetchBlobFS.createFile(path, content, encode, promise); 95 | } 96 | }); 97 | } 98 | 99 | @ReactMethod 100 | public void createFileASCII(final String path, final ReadableArray dataArray, final Promise promise) { 101 | threadPool.execute(new Runnable() { 102 | @Override 103 | public void run() { 104 | RNFetchBlobFS.createFileASCII(path, dataArray, promise); 105 | } 106 | }); 107 | } 108 | 109 | @ReactMethod 110 | public void actionViewIntent(String path, String mime, final Promise promise) { 111 | try { 112 | Uri uriForFile = FileProvider.getUriForFile(this.getReactApplicationContext(), 113 | this.getReactApplicationContext().getPackageName() + ".provider", new File(path)); 114 | 115 | // Create the intent with data and type 116 | Intent intent = new Intent(Intent.ACTION_VIEW) 117 | .setDataAndType(uriForFile, mime); 118 | 119 | // Set flag to give temporary permission to external app to use FileProvider 120 | intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 121 | // All the activity to be opened outside of an activity 122 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 123 | 124 | // Validate that the device can open the file 125 | PackageManager pm = getCurrentActivity().getPackageManager(); 126 | if (intent.resolveActivity(pm) != null) { 127 | this.getReactApplicationContext().startActivity(intent); 128 | } else { 129 | promise.reject("EUNSPECIFIED", "Cannot open the URL."); 130 | } 131 | 132 | ActionViewVisible = true; 133 | 134 | final LifecycleEventListener listener = new LifecycleEventListener() { 135 | @Override 136 | public void onHostResume() { 137 | if(ActionViewVisible) 138 | promise.resolve(null); 139 | RCTContext.removeLifecycleEventListener(this); 140 | } 141 | 142 | @Override 143 | public void onHostPause() { 144 | 145 | } 146 | 147 | @Override 148 | public void onHostDestroy() { 149 | 150 | } 151 | }; 152 | RCTContext.addLifecycleEventListener(listener); 153 | } catch(Exception ex) { 154 | promise.reject("EUNSPECIFIED", ex.getLocalizedMessage()); 155 | } 156 | } 157 | 158 | @ReactMethod 159 | public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) { 160 | RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback); 161 | } 162 | 163 | @ReactMethod 164 | public void unlink(String path, Callback callback) { 165 | RNFetchBlobFS.unlink(path, callback); 166 | } 167 | 168 | @ReactMethod 169 | public void mkdir(String path, Promise promise) { 170 | RNFetchBlobFS.mkdir(path, promise); 171 | } 172 | 173 | @ReactMethod 174 | public void exists(String path, Callback callback) { 175 | RNFetchBlobFS.exists(path, callback); 176 | } 177 | 178 | @ReactMethod 179 | public void cp(final String path, final String dest, final Callback callback) { 180 | threadPool.execute(new Runnable() { 181 | @Override 182 | public void run() { 183 | RNFetchBlobFS.cp(path, dest, callback); 184 | } 185 | }); 186 | } 187 | 188 | @ReactMethod 189 | public void mv(String path, String dest, Callback callback) { 190 | RNFetchBlobFS.mv(path, dest, callback); 191 | } 192 | 193 | @ReactMethod 194 | public void ls(String path, Promise promise) { 195 | RNFetchBlobFS.ls(path, promise); 196 | } 197 | 198 | @ReactMethod 199 | public void writeStream(String path, String encode, boolean append, Callback callback) { 200 | new RNFetchBlobFS(this.getReactApplicationContext()).writeStream(path, encode, append, callback); 201 | } 202 | 203 | @ReactMethod 204 | public void writeChunk(String streamId, String data, Callback callback) { 205 | RNFetchBlobFS.writeChunk(streamId, data, callback); 206 | } 207 | 208 | @ReactMethod 209 | public void closeStream(String streamId, Callback callback) { 210 | RNFetchBlobFS.closeStream(streamId, callback); 211 | } 212 | 213 | @ReactMethod 214 | public void removeSession(ReadableArray paths, Callback callback) { 215 | RNFetchBlobFS.removeSession(paths, callback); 216 | } 217 | 218 | @ReactMethod 219 | public void readFile(final String path, final String encoding, final Promise promise) { 220 | threadPool.execute(new Runnable() { 221 | @Override 222 | public void run() { 223 | RNFetchBlobFS.readFile(path, encoding, promise); 224 | } 225 | }); 226 | } 227 | 228 | @ReactMethod 229 | public void writeFileArray(final String path, final ReadableArray data, final boolean append, final Promise promise) { 230 | threadPool.execute(new Runnable() { 231 | @Override 232 | public void run() { 233 | RNFetchBlobFS.writeFile(path, data, append, promise); 234 | } 235 | }); 236 | } 237 | 238 | @ReactMethod 239 | public void writeFile(final String path, final String encoding, final String data, final boolean append, final Promise promise) { 240 | threadPool.execute(new Runnable() { 241 | @Override 242 | public void run() { 243 | RNFetchBlobFS.writeFile(path, encoding, data, append, promise); 244 | } 245 | }); 246 | } 247 | 248 | @ReactMethod 249 | public void lstat(String path, Callback callback) { 250 | RNFetchBlobFS.lstat(path, callback); 251 | } 252 | 253 | @ReactMethod 254 | public void stat(String path, Callback callback) { 255 | RNFetchBlobFS.stat(path, callback); 256 | } 257 | 258 | @ReactMethod 259 | public void scanFile(final ReadableArray pairs, final Callback callback) { 260 | final ReactApplicationContext ctx = this.getReactApplicationContext(); 261 | threadPool.execute(new Runnable() { 262 | @Override 263 | public void run() { 264 | int size = pairs.size(); 265 | String [] p = new String[size]; 266 | String [] m = new String[size]; 267 | for(int i=0;i 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 | enum ReportType { 9 | Upload, 10 | Download 11 | }; 12 | 13 | private long lastTick = 0; 14 | private int tick = 0; 15 | private int count = -1; 16 | private int interval = -1; 17 | private boolean enable = false; 18 | private ReportType type = ReportType.Download; 19 | 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 | StringBuilder sb = new StringBuilder(); 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 | // TODO: Is discarding errors the intent? (https://www.owasp.org/index.php/Return_Inside_Finally_Block) 41 | return result; 42 | } 43 | 44 | } 45 | 46 | public static void emitWarningEvent(String data) { 47 | WritableMap args = Arguments.createMap(); 48 | args.putString("event", "warn"); 49 | args.putString("detail", data); 50 | 51 | // emit event to js context 52 | RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 53 | .emit(RNFetchBlobConst.EVENT_MESSAGE, args); 54 | } 55 | 56 | public static OkHttpClient.Builder getUnsafeOkHttpClient(OkHttpClient client) { 57 | try { 58 | // Create a trust manager that does not validate certificate chains 59 | final X509TrustManager x509TrustManager = 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 | final TrustManager[] trustAllCerts = new TrustManager[]{ x509TrustManager }; 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, x509TrustManager); 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 androidx.annotation.NonNull; 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 | boolean isEndMarkerReceived; 37 | 38 | public RNFetchBlobFileResp(ReactApplicationContext ctx, String taskId, ResponseBody body, String path, boolean overwrite) throws IOException { 39 | super(); 40 | this.rctContext = ctx; 41 | this.mTaskId = taskId; 42 | this.originalBody = body; 43 | assert path != null; 44 | this.mPath = path; 45 | this.isEndMarkerReceived = false; 46 | if (path != null) { 47 | boolean appendToExistingFile = !overwrite; 48 | path = path.replace("?append=true", ""); 49 | mPath = path; 50 | File f = new File(path); 51 | 52 | File parent = f.getParentFile(); 53 | if(parent != null && !parent.exists() && !parent.mkdirs()){ 54 | throw new IllegalStateException("Couldn't create dir: " + parent); 55 | } 56 | 57 | if(!f.exists()) 58 | f.createNewFile(); 59 | ofStream = new FileOutputStream(new File(path), appendToExistingFile); 60 | } 61 | } 62 | 63 | @Override 64 | public MediaType contentType() { 65 | return originalBody.contentType(); 66 | } 67 | 68 | @Override 69 | public long contentLength() { 70 | return originalBody.contentLength(); 71 | } 72 | 73 | public boolean isDownloadComplete() { 74 | return (bytesDownloaded == contentLength()) // Case of non-chunked downloads 75 | || (contentLength() == -1 && isEndMarkerReceived); // Case of chunked downloads 76 | } 77 | 78 | @Override 79 | public BufferedSource source() { 80 | ProgressReportingSource countable = new ProgressReportingSource(); 81 | return Okio.buffer(countable); 82 | } 83 | 84 | private class ProgressReportingSource implements Source { 85 | @Override 86 | public long read(@NonNull Buffer sink, long byteCount) throws IOException { 87 | try { 88 | byte[] bytes = new byte[(int) byteCount]; 89 | long read = originalBody.byteStream().read(bytes, 0, (int) byteCount); 90 | bytesDownloaded += read > 0 ? read : 0; 91 | if (read > 0) { 92 | ofStream.write(bytes, 0, (int) read); 93 | } else if (contentLength() == -1 && read == -1) { 94 | // End marker has been received for chunked download 95 | isEndMarkerReceived = true; 96 | } 97 | RNFetchBlobProgressConfig reportConfig = RNFetchBlobReq.getReportProgress(mTaskId); 98 | 99 | if (contentLength() != 0) { 100 | 101 | // For non-chunked download, progress is received / total 102 | // For chunked download, progress can be either 0 (started) or 1 (ended) 103 | float progress = (contentLength() != -1) ? bytesDownloaded / contentLength() : ( ( isEndMarkerReceived ) ? 1 : 0 ); 104 | 105 | if (reportConfig != null && reportConfig.shouldReport(progress /* progress */)) { 106 | if (contentLength() != -1) { 107 | // For non-chunked downloads 108 | reportProgress(mTaskId, bytesDownloaded, contentLength()); 109 | } else { 110 | // For chunked downloads 111 | if (!isEndMarkerReceived) { 112 | reportProgress(mTaskId, 0, contentLength()); 113 | } else{ 114 | reportProgress(mTaskId, bytesDownloaded, bytesDownloaded); 115 | } 116 | } 117 | } 118 | 119 | } 120 | 121 | return read; 122 | } catch(Exception ex) { 123 | return -1; 124 | } 125 | } 126 | 127 | private void reportProgress(String taskId, long bytesDownloaded, long contentLength) { 128 | WritableMap args = Arguments.createMap(); 129 | args.putString("taskId", taskId); 130 | args.putString("written", String.valueOf(bytesDownloaded)); 131 | args.putString("total", String.valueOf(contentLength)); 132 | rctContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 133 | .emit(RNFetchBlobConst.EVENT_PROGRESS, args); 134 | } 135 | 136 | @Override 137 | public Timeout timeout() { 138 | return null; 139 | } 140 | 141 | @Override 142 | public void close() throws IOException { 143 | ofStream.close(); 144 | 145 | } 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/Utils/FileProvider.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob.Utils; 2 | 3 | public class FileProvider extends androidx.core.content.FileProvider { 4 | } 5 | -------------------------------------------------------------------------------- /android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java: -------------------------------------------------------------------------------- 1 | package com.RNFetchBlob.Utils; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.provider.DocumentsContract; 9 | import android.provider.MediaStore; 10 | import android.content.ContentUris; 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 | @TargetApi(19) 19 | public static String getRealPathFromURI(final Context context, final Uri uri) { 20 | 21 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 22 | 23 | // DocumentProvider 24 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 25 | // ExternalStorageProvider 26 | if (isExternalStorageDocument(uri)) { 27 | final String docId = DocumentsContract.getDocumentId(uri); 28 | final String[] split = docId.split(":"); 29 | final String type = split[0]; 30 | 31 | if ("primary".equalsIgnoreCase(type)) { 32 | return context.getExternalFilesDir(null) + "/" + split[1]; 33 | } 34 | 35 | // TODO handle non-primary volumes 36 | } 37 | // DownloadsProvider 38 | else if (isDownloadsDocument(uri)) { 39 | try { 40 | final String id = DocumentsContract.getDocumentId(uri); 41 | //Starting with Android O, this "id" is not necessarily a long (row number), 42 | //but might also be a "raw:/some/file/path" URL 43 | if (id != null && id.startsWith("raw:/")) { 44 | Uri rawuri = Uri.parse(id); 45 | String path = rawuri.getPath(); 46 | return path; 47 | } 48 | final Uri contentUri = ContentUris.withAppendedId( 49 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 50 | 51 | return getDataColumn(context, contentUri, null, null); 52 | } 53 | catch (Exception ex) { 54 | //something went wrong, but android should still be able to handle the original uri by returning null here (see readFile(...)) 55 | return null; 56 | } 57 | 58 | } 59 | // MediaProvider 60 | else if (isMediaDocument(uri)) { 61 | final String docId = DocumentsContract.getDocumentId(uri); 62 | final String[] split = docId.split(":"); 63 | final String type = split[0]; 64 | 65 | Uri contentUri = null; 66 | if ("image".equals(type)) { 67 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 68 | } else if ("video".equals(type)) { 69 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 70 | } else if ("audio".equals(type)) { 71 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 72 | } 73 | 74 | final String selection = "_id=?"; 75 | final String[] selectionArgs = new String[] { 76 | split[1] 77 | }; 78 | 79 | return getDataColumn(context, contentUri, selection, selectionArgs); 80 | } 81 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 82 | 83 | // Return the remote address 84 | if (isGooglePhotosUri(uri)) 85 | return uri.getLastPathSegment(); 86 | 87 | return getDataColumn(context, uri, null, null); 88 | } 89 | // Other Providers 90 | else{ 91 | try { 92 | InputStream attachment = context.getContentResolver().openInputStream(uri); 93 | if (attachment != null) { 94 | String filename = getContentName(context.getContentResolver(), uri); 95 | if (filename != null) { 96 | File file = new File(context.getCacheDir(), filename); 97 | FileOutputStream tmp = new FileOutputStream(file); 98 | byte[] buffer = new byte[1024]; 99 | while (attachment.read(buffer) > 0) { 100 | tmp.write(buffer); 101 | } 102 | tmp.close(); 103 | attachment.close(); 104 | return file.getAbsolutePath(); 105 | } 106 | } 107 | } catch (Exception e) { 108 | RNFetchBlobUtils.emitWarningEvent(e.toString()); 109 | return null; 110 | } 111 | } 112 | } 113 | // MediaStore (and general) 114 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 115 | 116 | // Return the remote address 117 | if (isGooglePhotosUri(uri)) 118 | return uri.getLastPathSegment(); 119 | 120 | return getDataColumn(context, uri, null, null); 121 | } 122 | // File 123 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 124 | return uri.getPath(); 125 | } 126 | 127 | return null; 128 | } 129 | 130 | private static String getContentName(ContentResolver resolver, Uri uri) { 131 | Cursor cursor = resolver.query(uri, null, null, null, null); 132 | cursor.moveToFirst(); 133 | int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); 134 | if (nameIndex >= 0) { 135 | String name = cursor.getString(nameIndex); 136 | cursor.close(); 137 | return name; 138 | } 139 | return null; 140 | } 141 | 142 | /** 143 | * Get the value of the data column for this Uri. This is useful for 144 | * MediaStore Uris, and other file-based ContentProviders. 145 | * 146 | * @param context The context. 147 | * @param uri The Uri to query. 148 | * @param selection (Optional) Filter used in the query. 149 | * @param selectionArgs (Optional) Selection arguments used in the query. 150 | * @return The value of the _data column, which is typically a file path. 151 | */ 152 | public static String getDataColumn(Context context, Uri uri, String selection, 153 | String[] selectionArgs) { 154 | 155 | Cursor cursor = null; 156 | String result = null; 157 | final String column = "_data"; 158 | final String[] projection = { 159 | column 160 | }; 161 | 162 | try { 163 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 164 | null); 165 | if (cursor != null && cursor.moveToFirst()) { 166 | final int index = cursor.getColumnIndexOrThrow(column); 167 | result = cursor.getString(index); 168 | } 169 | } 170 | catch (Exception ex) { 171 | ex.printStackTrace(); 172 | return null; 173 | } 174 | finally { 175 | if (cursor != null) 176 | cursor.close(); 177 | } 178 | return result; 179 | } 180 | 181 | 182 | /** 183 | * @param uri The Uri to check. 184 | * @return Whether the Uri authority is ExternalStorageProvider. 185 | */ 186 | public static boolean isExternalStorageDocument(Uri uri) { 187 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 188 | } 189 | 190 | /** 191 | * @param uri The Uri to check. 192 | * @return Whether the Uri authority is DownloadsProvider. 193 | */ 194 | public static boolean isDownloadsDocument(Uri uri) { 195 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 196 | } 197 | 198 | /** 199 | * @param uri The Uri to check. 200 | * @return Whether the Uri authority is MediaProvider. 201 | */ 202 | public static boolean isMediaDocument(Uri uri) { 203 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 204 | } 205 | 206 | /** 207 | * @param uri The Uri to check. 208 | * @return Whether the Uri authority is Google Photos. 209 | */ 210 | public static boolean isGooglePhotosUri(Uri uri) { 211 | return "com.google.android.apps.photos.content".equals(uri.getAuthority()); 212 | } 213 | 214 | } -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | rn-fetch-blob 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /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, code, 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 | const err = new Error(detail) 48 | err.code = code || 'EUNSPECIFIED' 49 | if(this._onError) 50 | this._onError(err) 51 | else 52 | throw err 53 | } 54 | // when stream closed or error, remove event handler 55 | if (event === 'error' || event === 'end') { 56 | subscription.remove() 57 | this.closed = true 58 | } 59 | }) 60 | 61 | } 62 | 63 | open() { 64 | if(!this.closed) 65 | RNFetchBlob.readStream(this.path, this.encoding, this.bufferSize || 10240 , this.tick || -1, this.streamId) 66 | else 67 | throw new Error('Stream closed') 68 | } 69 | 70 | onData(fn:() => void) { 71 | this._onData = fn 72 | } 73 | 74 | onError(fn) { 75 | this._onError = fn 76 | } 77 | 78 | onEnd (fn) { 79 | this._onEnd = fn 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /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 | 13 | let sessions = {} 14 | 15 | export default class RNFetchBlobSession { 16 | 17 | name : string; 18 | 19 | static getSession(name:string):any { 20 | return sessions[name] 21 | } 22 | 23 | static setSession(name:string, val:any) { 24 | sessions[name] = val 25 | } 26 | 27 | static removeSession(name:string) { 28 | delete sessions[name] 29 | } 30 | 31 | constructor(name:string, list:Array) { 32 | this.name = name 33 | if(!sessions[name]) { 34 | if(Array.isArray(list)) 35 | sessions[name] = list 36 | else 37 | sessions[name] = [] 38 | } 39 | } 40 | 41 | add(path:string):RNFetchBlobSession { 42 | sessions[this.name].push(path) 43 | return this 44 | } 45 | 46 | remove(path:string):RNFetchBlobSession { 47 | let list = sessions[this.name] 48 | for(let i of list) { 49 | if(list[i] === path) { 50 | sessions[this.name].splice(i, 1) 51 | break; 52 | } 53 | } 54 | return this 55 | } 56 | 57 | list():Array { 58 | return sessions[this.name] 59 | } 60 | 61 | dispose():Promise { 62 | return new Promise((resolve, reject) => { 63 | RNFetchBlob.removeSession(sessions[this.name], (err) => { 64 | if(err) 65 | reject(new Error(err)) 66 | else { 67 | delete sessions[this.name] 68 | resolve() 69 | } 70 | }) 71 | }) 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /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 | 13 | export default class RNFetchBlobWriteStream { 14 | 15 | id : string; 16 | encoding : string; 17 | append : boolean; 18 | 19 | constructor(streamId:string, encoding:string, append:boolean) { 20 | this.id = streamId 21 | this.encoding = encoding 22 | this.append = append 23 | } 24 | 25 | write(data:string): Promise { 26 | return new Promise((resolve, reject) => { 27 | try { 28 | let method = this.encoding === 'ascii' ? 'writeArrayChunk' : 'writeChunk' 29 | if(this.encoding.toLocaleLowerCase() === 'ascii' && !Array.isArray(data)) { 30 | reject(new Error('ascii input data must be an Array')) 31 | return 32 | } 33 | RNFetchBlob[method](this.id, data, (error) => { 34 | if(error) 35 | reject(new Error(error)) 36 | else 37 | resolve(this) 38 | }) 39 | } catch(err) { 40 | reject(new Error(err)) 41 | } 42 | }) 43 | } 44 | 45 | close() { 46 | return new Promise((resolve, reject) => { 47 | try { 48 | RNFetchBlob.closeStream(this.id, () => { 49 | resolve() 50 | }) 51 | } catch (err) { 52 | reject(new Error(err)) 53 | } 54 | }) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /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 type {RNFetchBlobConfig, RNFetchBlobNative, RNFetchBlobStream} from './types' 6 | 7 | import {NativeModules, Platform} from 'react-native' 8 | import RNFetchBlobSession from './class/RNFetchBlobSession' 9 | import RNFetchBlobWriteStream from './class/RNFetchBlobWriteStream' 10 | import RNFetchBlobReadStream from './class/RNFetchBlobReadStream' 11 | import RNFetchBlobFile from './class/RNFetchBlobFile' 12 | 13 | const RNFetchBlob: RNFetchBlobNative = NativeModules.RNFetchBlob 14 | 15 | const dirs = { 16 | DocumentDir : RNFetchBlob.DocumentDir, 17 | CacheDir : RNFetchBlob.CacheDir, 18 | PictureDir : RNFetchBlob.PictureDir, 19 | MusicDir : RNFetchBlob.MusicDir, 20 | MovieDir : RNFetchBlob.MovieDir, 21 | DownloadDir : RNFetchBlob.DownloadDir, 22 | DCIMDir : RNFetchBlob.DCIMDir, 23 | SDCardDir: RNFetchBlob.SDCardDir, // Depracated 24 | SDCardApplicationDir: RNFetchBlob.SDCardApplicationDir, // Deprecated 25 | MainBundleDir : RNFetchBlob.MainBundleDir, 26 | LibraryDir : RNFetchBlob.LibraryDir 27 | } 28 | 29 | function addCode(code: string, error: Error): Error { 30 | error.code = code 31 | return error 32 | } 33 | 34 | /** 35 | * Get a file cache session 36 | * @param {string} name Stream ID 37 | * @return {RNFetchBlobSession} 38 | */ 39 | function session(name: string): RNFetchBlobSession { 40 | let s = RNFetchBlobSession.getSession(name) 41 | if (s) 42 | return new RNFetchBlobSession(name) 43 | else { 44 | RNFetchBlobSession.setSession(name, []) 45 | return new RNFetchBlobSession(name, []) 46 | } 47 | } 48 | 49 | function asset(path: string): string { 50 | if (Platform.OS === 'ios') { 51 | // path from camera roll 52 | if (/^assets-library\:\/\//.test(path)) 53 | return path 54 | } 55 | return 'bundle-assets://' + path 56 | } 57 | 58 | function createFile(path: string, data: string, encoding: 'base64' | 'ascii' | 'utf8' = 'utf8'): Promise { 59 | if (encoding.toLowerCase() === 'ascii') { 60 | return Array.isArray(data) ? 61 | RNFetchBlob.createFileASCII(path, data) : 62 | Promise.reject(addCode('EINVAL', new TypeError('`data` of ASCII file must be an array with 0..255 numbers'))) 63 | } 64 | else { 65 | return RNFetchBlob.createFile(path, data, encoding) 66 | } 67 | } 68 | 69 | /** 70 | * Create write stream to a file. 71 | * @param {string} path Target path of file stream. 72 | * @param {string} encoding Encoding of input data. 73 | * @param {boolean} [append] A flag represent if data append to existing ones. 74 | * @return {Promise} A promise resolves a `WriteStream` object. 75 | */ 76 | function writeStream( 77 | path: string, 78 | encoding?: 'utf8' | 'ascii' | 'base64' = 'utf8', 79 | append?: boolean = false, 80 | ): Promise { 81 | if (typeof path !== 'string') { 82 | return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 83 | } 84 | return new Promise((resolve, reject) => { 85 | RNFetchBlob.writeStream(path, encoding, append, (errCode, errMsg, streamId: string) => { 86 | if (errMsg) { 87 | const err = new Error(errMsg) 88 | err.code = errCode 89 | reject(err) 90 | } 91 | else 92 | resolve(new RNFetchBlobWriteStream(streamId, encoding)) 93 | }) 94 | }) 95 | } 96 | 97 | /** 98 | * Create file stream from file at `path`. 99 | * @param {string} path The file path. 100 | * @param {string} encoding Data encoding, should be one of `base64`, `utf8`, `ascii` 101 | * @param {boolean} bufferSize Size of stream buffer. 102 | * @param {number} [tick=10] Interval in milliseconds between reading chunks of data 103 | * @return {RNFetchBlobStream} RNFetchBlobStream stream instance. 104 | */ 105 | function readStream( 106 | path: string, 107 | encoding: 'utf8' | 'ascii' | 'base64' = 'utf8', 108 | bufferSize?: number, 109 | tick?: number = 10 110 | ): Promise { 111 | if (typeof path !== 'string') { 112 | return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 113 | } 114 | return Promise.resolve(new RNFetchBlobReadStream(path, encoding, bufferSize, tick)) 115 | } 116 | 117 | /** 118 | * Create a directory. 119 | * @param {string} path Path of directory to be created 120 | * @return {Promise} 121 | */ 122 | function mkdir(path: string): Promise { 123 | if (typeof path !== 'string') { 124 | return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 125 | } 126 | return RNFetchBlob.mkdir(path) 127 | } 128 | 129 | /** 130 | * Returns the path for the app group. 131 | * @param {string} groupName Name of app group 132 | * @return {Promise} 133 | */ 134 | function pathForAppGroup(groupName: string): Promise { 135 | return RNFetchBlob.pathForAppGroup(groupName) 136 | } 137 | 138 | /** 139 | * Returns the path for the app group synchronous. 140 | * @param {string} groupName Name of app group 141 | * @return {string} Path of App Group dir 142 | */ 143 | function syncPathAppGroup(groupName: string): string { 144 | if (Platform.OS === 'ios') { 145 | return RNFetchBlob.syncPathAppGroup(groupName); 146 | } else { 147 | return ''; 148 | } 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 = 'utf8'): Promise { 158 | if (typeof path !== 'string') { 159 | return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 160 | } 161 | return RNFetchBlob.readFile(path, encoding) 162 | } 163 | 164 | /** 165 | * Write data to file. 166 | * @param {string} path Path of the file. 167 | * @param {string | number[]} data Data to write to the file. 168 | * @param {string} encoding Encoding of data (Optional). 169 | * @return {Promise} 170 | */ 171 | function writeFile(path: string, data: string | Array, encoding: ?string = 'utf8'): Promise { 172 | if (typeof path !== 'string') { 173 | return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 174 | } 175 | if (encoding.toLocaleLowerCase() === 'ascii') { 176 | if (!Array.isArray(data)) { 177 | return Promise.reject(addCode('EINVAL', new TypeError('"data" must be an Array when encoding is "ascii"'))) 178 | } 179 | else 180 | return RNFetchBlob.writeFileArray(path, data, false) 181 | } 182 | else { 183 | if (typeof data !== 'string') { 184 | return Promise.reject(addCode('EINVAL', new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`))) 185 | } 186 | else 187 | return RNFetchBlob.writeFile(path, encoding, data, false) 188 | } 189 | } 190 | 191 | function appendFile(path: string, data: string | Array, encoding?: string = 'utf8'): Promise { 192 | if (typeof path !== 'string') { 193 | return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 194 | } 195 | if (encoding.toLocaleLowerCase() === 'ascii') { 196 | if (!Array.isArray(data)) { 197 | return Promise.reject(addCode('EINVAL', new TypeError('`data` of ASCII file must be an array with 0..255 numbers'))) 198 | } 199 | else 200 | return RNFetchBlob.writeFileArray(path, data, true) 201 | } 202 | else { 203 | if (typeof data !== 'string') { 204 | return Promise.reject(addCode('EINVAL'), new TypeError(`"data" must be a String when encoding is "utf8" or "base64", but it is "${typeof data}"`)) 205 | } 206 | else 207 | return RNFetchBlob.writeFile(path, encoding, data, true) 208 | } 209 | } 210 | 211 | /** 212 | * Show statistic data of a path. 213 | * @param {string} path Target path 214 | * @return {RNFetchBlobFile} 215 | */ 216 | function stat(path: string): Promise { 217 | return new Promise((resolve, reject) => { 218 | if (typeof path !== 'string') { 219 | return reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 220 | } 221 | RNFetchBlob.stat(path, (err, stat) => { 222 | if (err) 223 | reject(new Error(err)) 224 | else { 225 | if (stat) { 226 | stat.size = parseInt(stat.size) 227 | stat.lastModified = parseInt(stat.lastModified) 228 | } 229 | resolve(stat) 230 | } 231 | }) 232 | }) 233 | } 234 | 235 | /** 236 | * Android only method, request media scanner to scan the file. 237 | * @param {Array>} pairs Array contains Key value pairs with key `path` and `mime`. 238 | * @return {Promise} 239 | */ 240 | function scanFile(pairs: any): Promise { 241 | return new Promise((resolve, reject) => { 242 | if (pairs === undefined) { 243 | return reject(addCode('EINVAL', new TypeError('Missing argument'))) 244 | } 245 | RNFetchBlob.scanFile(pairs, (err) => { 246 | if (err) 247 | reject(addCode('EUNSPECIFIED', new Error(err))) 248 | else 249 | resolve() 250 | }) 251 | }) 252 | } 253 | 254 | function hash(path: string, algorithm: string): Promise { 255 | if (typeof path !== 'string' || typeof algorithm !== 'string') { 256 | return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "algorithm"'))) 257 | } 258 | return RNFetchBlob.hash(path, algorithm) 259 | } 260 | 261 | function cp(path: string, dest: string): Promise { 262 | return new Promise((resolve, reject) => { 263 | if (typeof path !== 'string' || typeof dest !== 'string') { 264 | return reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "destination"'))) 265 | } 266 | RNFetchBlob.cp(path, dest, (err, res) => { 267 | if (err) 268 | reject(addCode('EUNSPECIFIED', new Error(err))) 269 | else 270 | resolve(res) 271 | }) 272 | }) 273 | } 274 | 275 | function mv(path: string, dest: string): Promise { 276 | return new Promise((resolve, reject) => { 277 | if (typeof path !== 'string' || typeof dest !== 'string') { 278 | return reject(addCode('EINVAL', new TypeError('Missing argument "path" and/or "destination"'))) 279 | } 280 | RNFetchBlob.mv(path, dest, (err, res) => { 281 | if (err) 282 | reject(addCode('EUNSPECIFIED', new Error(err))) 283 | else 284 | resolve(res) 285 | }) 286 | }) 287 | } 288 | 289 | function lstat(path: string): Promise> { 290 | return new Promise((resolve, reject) => { 291 | if (typeof path !== 'string') { 292 | return reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 293 | } 294 | RNFetchBlob.lstat(path, (err, stat) => { 295 | if (err) 296 | reject(addCode('EUNSPECIFIED', new Error(err))) 297 | else 298 | resolve(stat) 299 | }) 300 | }) 301 | } 302 | 303 | function ls(path: string): Promise> { 304 | if (typeof path !== 'string') { 305 | return Promise.reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 306 | } 307 | return RNFetchBlob.ls(path) 308 | } 309 | 310 | /** 311 | * Remove file at path. 312 | * @param {string} path:string Path of target file. 313 | * @return {Promise} 314 | */ 315 | function unlink(path: string): Promise { 316 | return new Promise((resolve, reject) => { 317 | if (typeof path !== 'string') { 318 | return reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 319 | } 320 | RNFetchBlob.unlink(path, (err) => { 321 | if (err) { 322 | reject(addCode('EUNSPECIFIED', new Error(err))) 323 | } 324 | else 325 | resolve() 326 | }) 327 | }) 328 | } 329 | 330 | /** 331 | * Check if file exists and if it is a folder. 332 | * @param {string} path Path to check 333 | * @return {Promise} 334 | */ 335 | function exists(path: string): Promise { 336 | return new Promise((resolve, reject) => { 337 | if (typeof path !== 'string') { 338 | return reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 339 | } 340 | try { 341 | RNFetchBlob.exists(path, (exist) => { 342 | resolve(exist) 343 | }) 344 | }catch (err){ 345 | reject(addCode('EUNSPECIFIED', new Error(err))) 346 | } 347 | }) 348 | 349 | } 350 | 351 | function slice(src: string, dest: string, start: number, end: number): Promise { 352 | if (typeof src !== 'string' || typeof dest !== 'string') { 353 | return reject(addCode('EINVAL', new TypeError('Missing argument "src" and/or "destination"'))) 354 | } 355 | 356 | let p = Promise.resolve() 357 | let size = 0 358 | 359 | function normalize(num, size) { 360 | if (num < 0) 361 | return Math.max(0, size + num) 362 | if (!num && num !== 0) 363 | return size 364 | return num 365 | } 366 | 367 | if (start < 0 || end < 0 || !start || !end) { 368 | p = p.then(() => stat(src)) 369 | .then((stat) => { 370 | size = Math.floor(stat.size) 371 | start = normalize(start || 0, size) 372 | end = normalize(end, size) 373 | }) 374 | } 375 | return p.then(() => RNFetchBlob.slice(src, dest, start, end)) 376 | } 377 | 378 | function isDir(path: string): Promise { 379 | return new Promise((resolve, reject) => { 380 | if (typeof path !== 'string') { 381 | return reject(addCode('EINVAL', new TypeError('Missing argument "path" '))) 382 | } 383 | try { 384 | RNFetchBlob.exists(path, (exist, isDir) => { 385 | resolve(isDir) 386 | }) 387 | }catch (err){ 388 | reject(addCode('EUNSPECIFIED', new Error(err))) 389 | } 390 | }) 391 | 392 | } 393 | 394 | function df(): Promise<{ free: number, total: number }> { 395 | return new Promise((resolve, reject) => { 396 | RNFetchBlob.df((err, stat) => { 397 | if (err) 398 | reject(addCode('EUNSPECIFIED', new Error(err))) 399 | else 400 | resolve(stat) 401 | }) 402 | }) 403 | } 404 | 405 | export default { 406 | RNFetchBlobSession, 407 | unlink, 408 | mkdir, 409 | session, 410 | ls, 411 | readStream, 412 | mv, 413 | cp, 414 | writeStream, 415 | writeFile, 416 | appendFile, 417 | pathForAppGroup, 418 | syncPathAppGroup, 419 | readFile, 420 | hash, 421 | exists, 422 | createFile, 423 | isDir, 424 | stat, 425 | lstat, 426 | scanFile, 427 | dirs, 428 | slice, 429 | asset, 430 | df 431 | } 432 | -------------------------------------------------------------------------------- /img/RNFB-Body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/RNFB-Body.png -------------------------------------------------------------------------------- /img/RNFB-Flow-hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/RNFB-Flow-hd.png -------------------------------------------------------------------------------- /img/RNFB-HTTP-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/RNFB-HTTP-flow.png -------------------------------------------------------------------------------- /img/RNFB-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/RNFB-flow.png -------------------------------------------------------------------------------- /img/action-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/action-menu.png -------------------------------------------------------------------------------- /img/android-notification1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/android-notification1.png -------------------------------------------------------------------------------- /img/android-notification2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/android-notification2.png -------------------------------------------------------------------------------- /img/download-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/download-manager.png -------------------------------------------------------------------------------- /img/ios-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/ios-1.png -------------------------------------------------------------------------------- /img/ios-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/ios-2.png -------------------------------------------------------------------------------- /img/ios-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/ios-3.png -------------------------------------------------------------------------------- /img/ios-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/ios-4.png -------------------------------------------------------------------------------- /img/ios-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/ios-5.png -------------------------------------------------------------------------------- /img/issue_57_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/issue_57_1.png -------------------------------------------------------------------------------- /img/issue_57_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/issue_57_2.png -------------------------------------------------------------------------------- /img/issue_57_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/issue_57_3.png -------------------------------------------------------------------------------- /img/performance_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/performance_1.png -------------------------------------------------------------------------------- /img/performance_encoding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/performance_encoding.png -------------------------------------------------------------------------------- /img/performance_f2f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltup/rn-fetch-blob/cf9e8843599de92031df2660d5a1da18491fa3c0/img/performance_f2f.png -------------------------------------------------------------------------------- /index.js.flow: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | 3 | declare class AndroidApi { 4 | actionViewIntent(path: string, mime?: string): Promise; 5 | addCompleteDownload(options: AndroidDownloadOption): Promise; 6 | getContentIntent(mime: string): Promise; 7 | getSDCardApplicationDir(): Promise; 8 | getSDCardDir(): Promise; 9 | } 10 | 11 | declare class FetchBlobResponse { 12 | data: string; 13 | respInfo: RNFetchBlobResponseInfo; 14 | taskId: string; 15 | type: "utf8" | "base64" | "path"; 16 | array(): Promise; 17 | base64(): string | Promise; 18 | blob(): Promise; 19 | flush(): Promise; 20 | info(): RNFetchBlobResponseInfo; 21 | // $FlowExpectedError Not possible to specify in strict mode. 22 | json(): any; 23 | path(): string; 24 | readFile(encoding: Encoding): ?Promise; 25 | readStream(encoding: Encoding): ?Promise; 26 | session(name: string): ?RNFetchBlobSession; 27 | text(): string | Promise; 28 | } 29 | 30 | declare class FsApi { 31 | RNFetchBlobSession: RNFetchBlobSession; 32 | dirs: Dirs; 33 | appendFile(path: string, data: string | number[], encoding?: Encoding | "uri"): Promise; 34 | asset(path: string): string; 35 | cp(path: string, dest: string): Promise; 36 | createFile(path: string, data: string, encoding: Encoding | "uri"): Promise; 37 | df(): Promise; 38 | exists(path: string): Promise; 39 | hash(path: string, algorithm: HashAlgorithm): Promise; 40 | isDir(path: string): Promise; 41 | ls(path: string): Promise; 42 | lstat(path: string): Promise; 43 | mkdir(path: string): Promise; 44 | mv(path: string, dest: string): Promise; 45 | pathForAppGroup(groupName: string): Promise; 46 | readFile(path: string, encoding: Encoding, bufferSize?: number): Promise; 47 | readStream(path: string, encoding: Encoding, bufferSize?: number, tick?: number): Promise; 48 | scanFile(pairs: {mime: string, path: string}[]): Promise; 49 | session(name: string): RNFetchBlobSession; 50 | slice(src: string, dest: string, start: number, end: number): Promise; 51 | stat(path: string): Promise; 52 | unlink(path: string): Promise; 53 | writeFile(path: string, data: string | number[], encoding?: Encoding | "uri"): Promise; 54 | writeStream(path: string, encoding: Encoding, append?: boolean): Promise; 55 | } 56 | 57 | declare class IosApi { 58 | excludeFromBackupKey(path: string): Promise; 59 | openDocument(path: string, scheme?: string): Promise; 60 | previewDocument(path: string, scheme?: string): Promise; 61 | } 62 | 63 | declare class RNFetchBlobReadStream { 64 | bufferSize?: number; 65 | closed: boolean; 66 | encoding: Encoding; 67 | path: string; 68 | tick: number; 69 | onData(fn: (chunk: string) => void): void; 70 | onEnd(fn: () => void): void; 71 | onError(fn: (err: Error) => void): void; 72 | open(): void; 73 | } 74 | 75 | declare class RNFetchBlobSession { 76 | static getSession(name: string): RNFetchBlobSession; 77 | static removeSession(name: string): void; 78 | static setSession(name: string, val: RNFetchBlobSession): void; 79 | 80 | name: string; 81 | add(path: string): RNFetchBlobSession; 82 | constructor(name: string, list: string[]): RNFetchBlobSession; 83 | dispose(): Promise; 84 | list(): string[]; 85 | remove(path: string): RNFetchBlobSession; 86 | } 87 | 88 | declare class RNFetchBlobWriteStream { 89 | append: boolean; 90 | encoding: string; 91 | id: string; 92 | close(): void; 93 | write(data: string): Promise; 94 | } 95 | 96 | declare class RNFetchBlob { 97 | android: AndroidApi; 98 | base64: {+decode: (input: string) => string, +encode: (input: string) => string}; 99 | fs: FsApi; 100 | ios: IosApi; 101 | config(options: RNFetchBlobConfig): {fetch: (method: Methods, url: string, headers?: {[key: string]: string}, body?: string | FormField[]) => StatefulPromise}; 102 | fetch(method: Methods, url: string, headers?: {[key: string]: string}, body?: string | FormField[]): StatefulPromise; 103 | session(name: string): RNFetchBlobSession; 104 | wrap(path: string): string; 105 | } 106 | 107 | declare class StatefulPromise extends Promise { 108 | cancel(callback?: (error: ?string, taskId: ?string) => void): void; 109 | expire(callback: () => void): StatefulPromise; 110 | progress(config: {count?: number, interval?: number} | ProgressCallback, callback?: ProgressCallback): StatefulPromise; 111 | stateChange(callback: (state: RNFetchBlobResponseInfo) => void): StatefulPromise; 112 | uploadProgress(config: {count?: number, interval?: number} | UploadProgressCallback, callback?: UploadProgressCallback): StatefulPromise; 113 | } 114 | 115 | export type AddAndroidDownloads = { 116 | description?: string, 117 | mediaScannable?: boolean, 118 | mime?: string, 119 | notification?: boolean, 120 | path?: string, 121 | title?: string, 122 | useDownloadManager?: boolean 123 | }; 124 | export type AndroidDownloadOption = { 125 | description?: string, 126 | mime?: string, 127 | path: string, 128 | showNotification?: boolean, 129 | title?: string 130 | }; 131 | export type AndroidFsStat = { 132 | external_free: number, 133 | external_total: number, 134 | internal_free: number, 135 | internal_total: number 136 | }; 137 | export type Dirs = { 138 | CacheDir: string, 139 | DCIMDir: string, 140 | DocumentDir: string, 141 | DownloadDir: string, 142 | LibraryDir: string, 143 | MainBundleDir: string, 144 | MovieDir: string, 145 | MusicDir: string, 146 | PictureDir: string, 147 | SDCardApplicationDir: string, 148 | SDCardDir: string 149 | }; 150 | export type Encoding = "utf8" | "ascii" | "base64"; 151 | export type FormField = {data: string, filename?: string, name: string, type?: string}; 152 | export type HashAlgorithm = "md5" | "sha1" | "sha224" | "sha256" | "sha384" | "sha512"; 153 | export type IosFsStat = {free: number, total: number}; 154 | export type Methods = "POST" | "GET" | "DELETE" | "PUT" | "post" | "get" | "delete" | "put"; 155 | export type ProgressCallback = (received: number, total: number) => void; 156 | export type RNFetchBlobConfig = { 157 | IOSBackgroundTask?: boolean, 158 | addAndroidDownloads?: AddAndroidDownloads, 159 | appendExt?: string, 160 | fileCache?: boolean, 161 | indicator?: boolean, 162 | overwrite?: boolean, 163 | path?: string, 164 | session?: string, 165 | timeout?: number, 166 | trusty?: boolean, 167 | wifiOnly?: boolean, 168 | followRedirect?: boolean 169 | }; 170 | export type RNFetchBlobResponseInfo = { 171 | headers: {[fieldName: string]: string}, 172 | redirects: string[], 173 | respType: "text" | "blob" | "" | "json", 174 | rnfbEncode: "path" | Encoding, 175 | state: string, 176 | status: number, 177 | taskId: string, 178 | timeout: boolean 179 | }; 180 | export type RNFetchBlobStat = { 181 | filename: string, 182 | lastModified: number, 183 | path: string, 184 | size: number, 185 | type: "directory" | "file" | "asset" 186 | }; 187 | export type UploadProgressCallback = (sent: number, total: number) => void; 188 | 189 | declare export default RNFetchBlob; 190 | -------------------------------------------------------------------------------- /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/RNFetchBlob.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8C4801A6200CF71700FED7ED /* RNFetchBlobRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */; }; 11 | A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */; }; 12 | A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */; }; 13 | A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */ = {isa = PBXBuildFile; fileRef = A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */; }; 14 | A15C30141CD25C330074CB35 /* RNFetchBlob.m in Sources */ = {isa = PBXBuildFile; fileRef = A15C30131CD25C330074CB35 /* RNFetchBlob.m */; }; 15 | A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */ = {isa = PBXBuildFile; fileRef = A15C30111CD25C330074CB35 /* RNFetchBlob.h */; }; 16 | A19B48251D98102400E6868A /* RNFetchBlobProgress.m in Sources */ = {isa = PBXBuildFile; fileRef = A19B48241D98102400E6868A /* RNFetchBlobProgress.m */; }; 17 | A1AAE2991D300E4D0051D11C /* RNFetchBlobReqBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXCopyFilesBuildPhase section */ 21 | A15C300C1CD25C330074CB35 /* CopyFiles */ = { 22 | isa = PBXCopyFilesBuildPhase; 23 | buildActionMask = 2147483647; 24 | dstPath = "include/$(PRODUCT_NAME)"; 25 | dstSubfolderSpec = 16; 26 | files = ( 27 | ); 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 8C4801A4200CF71700FED7ED /* RNFetchBlobRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobRequest.h; sourceTree = ""; }; 34 | 8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobRequest.m; sourceTree = ""; }; 35 | A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobFS.m; sourceTree = ""; }; 36 | A158F4281D052E57006FFD38 /* RNFetchBlobFS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobFS.h; sourceTree = ""; }; 37 | A158F4291D0534A9006FFD38 /* RNFetchBlobConst.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobConst.h; sourceTree = ""; }; 38 | A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobConst.m; sourceTree = ""; }; 39 | A158F42E1D0539CE006FFD38 /* RNFetchBlobNetwork.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobNetwork.h; sourceTree = ""; }; 40 | A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobNetwork.m; sourceTree = ""; }; 41 | A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNFetchBlob.a; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | A15C30111CD25C330074CB35 /* RNFetchBlob.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNFetchBlob.h; path = RNFetchBlob/RNFetchBlob.h; sourceTree = ""; }; 43 | A15C30131CD25C330074CB35 /* RNFetchBlob.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RNFetchBlob.m; path = RNFetchBlob/RNFetchBlob.m; sourceTree = ""; }; 44 | A19B48231D98100800E6868A /* RNFetchBlobProgress.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobProgress.h; sourceTree = ""; }; 45 | A19B48241D98102400E6868A /* RNFetchBlobProgress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobProgress.m; sourceTree = ""; }; 46 | A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFetchBlobReqBuilder.h; sourceTree = ""; }; 47 | A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFetchBlobReqBuilder.m; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | A15C300B1CD25C330074CB35 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | 8BD9ABDFAF76406291A798F2 /* Libraries */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | ); 65 | name = Libraries; 66 | sourceTree = ""; 67 | }; 68 | A15C30051CD25C330074CB35 = { 69 | isa = PBXGroup; 70 | children = ( 71 | A19B48241D98102400E6868A /* RNFetchBlobProgress.m */, 72 | A19B48231D98100800E6868A /* RNFetchBlobProgress.h */, 73 | A1AAE2981D300E4D0051D11C /* RNFetchBlobReqBuilder.m */, 74 | A1AAE2971D300E3E0051D11C /* RNFetchBlobReqBuilder.h */, 75 | A158F42E1D0539CE006FFD38 /* RNFetchBlobNetwork.h */, 76 | A158F42F1D0539DB006FFD38 /* RNFetchBlobNetwork.m */, 77 | 8C4801A4200CF71700FED7ED /* RNFetchBlobRequest.h */, 78 | 8C4801A5200CF71700FED7ED /* RNFetchBlobRequest.m */, 79 | A158F42C1D0535BB006FFD38 /* RNFetchBlobConst.m */, 80 | A158F4291D0534A9006FFD38 /* RNFetchBlobConst.h */, 81 | A158F4281D052E57006FFD38 /* RNFetchBlobFS.h */, 82 | A158F4261D052E49006FFD38 /* RNFetchBlobFS.m */, 83 | A15C30111CD25C330074CB35 /* RNFetchBlob.h */, 84 | A15C30131CD25C330074CB35 /* RNFetchBlob.m */, 85 | A15C300F1CD25C330074CB35 /* Products */, 86 | 8BD9ABDFAF76406291A798F2 /* Libraries */, 87 | ); 88 | sourceTree = ""; 89 | }; 90 | A15C300F1CD25C330074CB35 /* Products */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | /* End PBXGroup section */ 99 | 100 | /* Begin PBXNativeTarget section */ 101 | A15C300D1CD25C330074CB35 /* RNFetchBlob */ = { 102 | isa = PBXNativeTarget; 103 | buildConfigurationList = A15C30171CD25C330074CB35 /* Build configuration list for PBXNativeTarget "RNFetchBlob" */; 104 | buildPhases = ( 105 | A15C300A1CD25C330074CB35 /* Sources */, 106 | A15C300B1CD25C330074CB35 /* Frameworks */, 107 | A15C300C1CD25C330074CB35 /* CopyFiles */, 108 | ); 109 | buildRules = ( 110 | ); 111 | dependencies = ( 112 | ); 113 | name = RNFetchBlob; 114 | productName = RNFetchBlob; 115 | productReference = A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */; 116 | productType = "com.apple.product-type.library.static"; 117 | }; 118 | /* End PBXNativeTarget section */ 119 | 120 | /* Begin PBXProject section */ 121 | A15C30061CD25C330074CB35 /* Project object */ = { 122 | isa = PBXProject; 123 | attributes = { 124 | LastUpgradeCheck = 730; 125 | ORGANIZATIONNAME = wkh237.github.io; 126 | TargetAttributes = { 127 | A15C300D1CD25C330074CB35 = { 128 | CreatedOnToolsVersion = 7.3; 129 | }; 130 | }; 131 | }; 132 | buildConfigurationList = A15C30091CD25C330074CB35 /* Build configuration list for PBXProject "RNFetchBlob" */; 133 | compatibilityVersion = "Xcode 3.2"; 134 | developmentRegion = English; 135 | hasScannedForEncodings = 0; 136 | knownRegions = ( 137 | en, 138 | ); 139 | mainGroup = A15C30051CD25C330074CB35; 140 | productRefGroup = A15C300F1CD25C330074CB35 /* Products */; 141 | projectDirPath = ""; 142 | projectRoot = ""; 143 | targets = ( 144 | A15C300D1CD25C330074CB35 /* RNFetchBlob */, 145 | ); 146 | }; 147 | /* End PBXProject section */ 148 | 149 | /* Begin PBXSourcesBuildPhase section */ 150 | A15C300A1CD25C330074CB35 /* Sources */ = { 151 | isa = PBXSourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */, 155 | 8C4801A6200CF71700FED7ED /* RNFetchBlobRequest.m in Sources */, 156 | A158F42D1D0535BB006FFD38 /* RNFetchBlobConst.m in Sources */, 157 | A158F4271D052E49006FFD38 /* RNFetchBlobFS.m in Sources */, 158 | A158F4301D0539DB006FFD38 /* RNFetchBlobNetwork.m in Sources */, 159 | A19B48251D98102400E6868A /* RNFetchBlobProgress.m in Sources */, 160 | A1AAE2991D300E4D0051D11C /* RNFetchBlobReqBuilder.m in Sources */, 161 | A15C30141CD25C330074CB35 /* RNFetchBlob.m in Sources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXSourcesBuildPhase section */ 166 | 167 | /* Begin XCBuildConfiguration section */ 168 | A15C30151CD25C330074CB35 /* Debug */ = { 169 | isa = XCBuildConfiguration; 170 | buildSettings = { 171 | ALWAYS_SEARCH_USER_PATHS = NO; 172 | CLANG_ANALYZER_NONNULL = YES; 173 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 174 | CLANG_CXX_LIBRARY = "libc++"; 175 | CLANG_ENABLE_MODULES = YES; 176 | CLANG_ENABLE_OBJC_ARC = YES; 177 | CLANG_WARN_BOOL_CONVERSION = YES; 178 | CLANG_WARN_CONSTANT_CONVERSION = YES; 179 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 180 | CLANG_WARN_EMPTY_BODY = YES; 181 | CLANG_WARN_ENUM_CONVERSION = YES; 182 | CLANG_WARN_INT_CONVERSION = YES; 183 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 184 | CLANG_WARN_UNREACHABLE_CODE = YES; 185 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 186 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 187 | COPY_PHASE_STRIP = NO; 188 | DEBUG_INFORMATION_FORMAT = dwarf; 189 | ENABLE_STRICT_OBJC_MSGSEND = YES; 190 | ENABLE_TESTABILITY = YES; 191 | GCC_C_LANGUAGE_STANDARD = gnu99; 192 | GCC_DYNAMIC_NO_PIC = NO; 193 | GCC_NO_COMMON_BLOCKS = YES; 194 | GCC_OPTIMIZATION_LEVEL = 0; 195 | GCC_PREPROCESSOR_DEFINITIONS = ( 196 | "DEBUG=1", 197 | "$(inherited)", 198 | ); 199 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 200 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 201 | GCC_WARN_UNDECLARED_SELECTOR = YES; 202 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 203 | GCC_WARN_UNUSED_FUNCTION = YES; 204 | GCC_WARN_UNUSED_VARIABLE = YES; 205 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 206 | MTL_ENABLE_DEBUG_INFO = YES; 207 | ONLY_ACTIVE_ARCH = YES; 208 | SDKROOT = iphoneos; 209 | }; 210 | name = Debug; 211 | }; 212 | A15C30161CD25C330074CB35 /* Release */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | ALWAYS_SEARCH_USER_PATHS = NO; 216 | CLANG_ANALYZER_NONNULL = YES; 217 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 218 | CLANG_CXX_LIBRARY = "libc++"; 219 | CLANG_ENABLE_MODULES = YES; 220 | CLANG_ENABLE_OBJC_ARC = YES; 221 | CLANG_WARN_BOOL_CONVERSION = YES; 222 | CLANG_WARN_CONSTANT_CONVERSION = YES; 223 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 224 | CLANG_WARN_EMPTY_BODY = YES; 225 | CLANG_WARN_ENUM_CONVERSION = YES; 226 | CLANG_WARN_INT_CONVERSION = YES; 227 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 228 | CLANG_WARN_UNREACHABLE_CODE = YES; 229 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 230 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 231 | COPY_PHASE_STRIP = NO; 232 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 233 | ENABLE_NS_ASSERTIONS = NO; 234 | ENABLE_STRICT_OBJC_MSGSEND = YES; 235 | GCC_C_LANGUAGE_STANDARD = gnu99; 236 | GCC_NO_COMMON_BLOCKS = YES; 237 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 238 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 239 | GCC_WARN_UNDECLARED_SELECTOR = YES; 240 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 241 | GCC_WARN_UNUSED_FUNCTION = YES; 242 | GCC_WARN_UNUSED_VARIABLE = YES; 243 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 244 | MTL_ENABLE_DEBUG_INFO = NO; 245 | SDKROOT = iphoneos; 246 | VALIDATE_PRODUCT = YES; 247 | }; 248 | name = Release; 249 | }; 250 | A15C30181CD25C330074CB35 /* Debug */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | GCC_INPUT_FILETYPE = automatic; 255 | HEADER_SEARCH_PATHS = ( 256 | "$(inherited)", 257 | "$(SRCROOT)/Libraries/**", 258 | "$(SRCROOT)/../node_modules/react-native/React/**", 259 | "$(SRCROOT)/../../React/**", 260 | "$(SRCROOT)/../../react-native/React/**", 261 | "$(SRCROOT)/../../node_modules/react-native/React/**", 262 | "$(SRCROOT)/../../node_modules/rn-fetch-blob/ios/RNFetchBlob", 263 | "$(SRCROOT)/../../RNFetchBlobTest/node_modules/react-native/**", 264 | ); 265 | OTHER_LDFLAGS = "-ObjC"; 266 | PRODUCT_NAME = "$(TARGET_NAME)"; 267 | SKIP_INSTALL = YES; 268 | }; 269 | name = Debug; 270 | }; 271 | A15C30191CD25C330074CB35 /* Release */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | GCC_INPUT_FILETYPE = automatic; 276 | HEADER_SEARCH_PATHS = ( 277 | "$(inherited)", 278 | "$(SRCROOT)/Libraries/**", 279 | "$(SRCROOT)/../node_modules/react-native/React/**", 280 | "$(SRCROOT)/../../React/**", 281 | "$(SRCROOT)/../../react-native/React/**", 282 | "$(SRCROOT)/../../node_modules/react-native/React/**", 283 | "$(SRCROOT)/../../node_modules/rn-fetch-blob/ios/RNFetchBlob", 284 | "$(SRCROOT)/../../RNFetchBlobTest/node_modules/react-native/**", 285 | ); 286 | OTHER_LDFLAGS = "-ObjC"; 287 | PRODUCT_NAME = "$(TARGET_NAME)"; 288 | SKIP_INSTALL = YES; 289 | }; 290 | name = Release; 291 | }; 292 | /* End XCBuildConfiguration section */ 293 | 294 | /* Begin XCConfigurationList section */ 295 | A15C30091CD25C330074CB35 /* Build configuration list for PBXProject "RNFetchBlob" */ = { 296 | isa = XCConfigurationList; 297 | buildConfigurations = ( 298 | A15C30151CD25C330074CB35 /* Debug */, 299 | A15C30161CD25C330074CB35 /* Release */, 300 | ); 301 | defaultConfigurationIsVisible = 0; 302 | defaultConfigurationName = Release; 303 | }; 304 | A15C30171CD25C330074CB35 /* Build configuration list for PBXNativeTarget "RNFetchBlob" */ = { 305 | isa = XCConfigurationList; 306 | buildConfigurations = ( 307 | A15C30181CD25C330074CB35 /* Debug */, 308 | A15C30191CD25C330074CB35 /* Release */, 309 | ); 310 | defaultConfigurationIsVisible = 0; 311 | defaultConfigurationName = Release; 312 | }; 313 | /* End XCConfigurationList section */ 314 | }; 315 | rootObject = A15C30061CD25C330074CB35 /* Project object */; 316 | } 317 | -------------------------------------------------------------------------------- /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 | 43 | @end 44 | 45 | #endif /* RNFetchBlob_h */ 46 | -------------------------------------------------------------------------------- /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_WIFI_ONLY; 36 | extern NSString *const CONFIG_INDICATOR; 37 | extern NSString *const CONFIG_KEY; 38 | extern NSString *const CONFIG_EXTRA_BLOB_CTYPE; 39 | 40 | // fs events 41 | extern NSString *const FS_EVENT_DATA; 42 | extern NSString *const FS_EVENT_END; 43 | extern NSString *const FS_EVENT_WARN; 44 | extern NSString *const FS_EVENT_ERROR; 45 | 46 | extern NSString *const KEY_REPORT_PROGRESS; 47 | extern NSString *const KEY_REPORT_UPLOAD_PROGRESS; 48 | 49 | // response type 50 | extern NSString *const RESP_TYPE_BASE64; 51 | extern NSString *const RESP_TYPE_UTF8; 52 | extern NSString *const RESP_TYPE_PATH; 53 | 54 | 55 | 56 | #endif /* RNFetchBlobConst_h */ 57 | -------------------------------------------------------------------------------- /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 | NSString *const FILE_PREFIX = @"RNFetchBlob-file://"; 11 | NSString *const ASSET_PREFIX = @"bundle-assets://"; 12 | NSString *const AL_PREFIX = @"assets-library://"; 13 | 14 | // fetch configs 15 | NSString *const CONFIG_USE_TEMP = @"fileCache"; 16 | NSString *const CONFIG_FILE_PATH = @"path"; 17 | NSString *const CONFIG_FILE_EXT = @"appendExt"; 18 | NSString *const CONFIG_TRUSTY = @"trusty"; 19 | NSString *const CONFIG_WIFI_ONLY = @"wifiOnly"; 20 | NSString *const CONFIG_INDICATOR = @"indicator"; 21 | NSString *const CONFIG_KEY = @"key"; 22 | NSString *const CONFIG_EXTRA_BLOB_CTYPE = @"binaryContentTypes"; 23 | 24 | NSString *const EVENT_STATE_CHANGE = @"RNFetchBlobState"; 25 | NSString *const EVENT_SERVER_PUSH = @"RNFetchBlobServerPush"; 26 | NSString *const EVENT_PROGRESS = @"RNFetchBlobProgress"; 27 | NSString *const EVENT_PROGRESS_UPLOAD = @"RNFetchBlobProgress-upload"; 28 | NSString *const EVENT_EXPIRE = @"RNFetchBlobExpire"; 29 | 30 | NSString *const MSG_EVENT = @"RNFetchBlobMessage"; 31 | NSString *const MSG_EVENT_LOG = @"log"; 32 | NSString *const MSG_EVENT_WARN = @"warn"; 33 | NSString *const MSG_EVENT_ERROR = @"error"; 34 | NSString *const FS_EVENT_DATA = @"data"; 35 | NSString *const FS_EVENT_END = @"end"; 36 | NSString *const FS_EVENT_WARN = @"warn"; 37 | NSString *const FS_EVENT_ERROR = @"error"; 38 | 39 | NSString *const KEY_REPORT_PROGRESS = @"reportProgress"; 40 | NSString *const KEY_REPORT_UPLOAD_PROGRESS = @"reportUploadProgress"; 41 | 42 | // response type 43 | NSString *const RESP_TYPE_BASE64 = @"base64"; 44 | NSString *const RESP_TYPE_UTF8 = @"utf8"; 45 | NSString *const RESP_TYPE_PATH = @"path"; 46 | -------------------------------------------------------------------------------- /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 * _Nullable outStream; 38 | @property (nonatomic) NSInputStream * _Nullable 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 *) getCacheDir; 50 | + (NSString *) getDocumentDir; 51 | + (NSString *) getDownloadDir; 52 | + (NSString *) getLibraryDir; 53 | + (NSString *) getMainBundleDir; 54 | + (NSString *) getMovieDir; 55 | + (NSString *) getMusicDir; 56 | + (NSString *) getPictureDir; 57 | + (NSString *) getTempPath; 58 | + (NSString *) getTempPath:(NSString*)taskId withExtension:(NSString *)ext; 59 | + (NSString *) getPathOfAsset:(NSString *)assetURI; 60 | + (NSString *) getPathForAppGroup:(NSString *)groupName; 61 | + (void) getPathFromUri:(NSString *)uri completionHandler:(void(^)(NSString * path, ALAssetRepresentation *asset)) onComplete; 62 | 63 | // fs methods 64 | + (RNFetchBlobFS *) getFileStreams; 65 | + (BOOL) mkdir:(NSString *) path; 66 | + (void) mkdir:(NSString *) path resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject; 67 | + (void) hash:(NSString *)path 68 | algorithm:(NSString *)algorithm 69 | resolver:(RCTPromiseResolveBlock)resolve 70 | rejecter:(RCTPromiseRejectBlock)reject; 71 | + (NSDictionary *) stat:(NSString *) path error:(NSError **) error; 72 | + (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback; 73 | + (void) writeFileArray:(NSString *)path data:(NSArray *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject; 74 | + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString *)data append:(BOOL)append resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject; 75 | + (void) readFile:(NSString *)path encoding:(NSString *)encoding onComplete:(void (^)(NSData * content, NSString* code, NSString * errMsg))onComplete; 76 | + (void) readAssetFile:(NSData *)assetUrl completionBlock:(void(^)(NSData * content))completionBlock failBlock:(void(^)(NSError * err))failBlock; 77 | + (void) slice:(NSString *)path 78 | dest:(NSString *)dest 79 | start:(nonnull NSNumber *)start 80 | end:(nonnull NSNumber *)end 81 | encode:(NSString *)encode 82 | resolver:(RCTPromiseResolveBlock)resolve 83 | rejecter:(RCTPromiseRejectBlock)reject; 84 | //+ (void) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append; 85 | + (void) writeAssetToPath:(ALAssetRepresentation * )rep dest:(NSString *)dest; 86 | + (void) readStream:(NSString *)uri encoding:(NSString * )encoding bufferSize:(int)bufferSize tick:(int)tick streamId:(NSString *)streamId bridgeRef:(RCTBridge *)bridgeRef; 87 | + (void) df:(RCTResponseSenderBlock)callback; 88 | 89 | // constructor 90 | - (id) init; 91 | - (id)initWithCallback:(RCTResponseSenderBlock)callback; 92 | - (id)initWithBridgeRef:(RCTBridge *)bridgeRef; 93 | 94 | // file stream 95 | - (void) openWithDestination; 96 | - (NSString *)openWithPath:(NSString *)destPath encode:(nullable NSString *)encode appendData:(BOOL)append; 97 | 98 | // file stream write data 99 | - (void)write:(NSData *) chunk; 100 | - (void)writeEncodeChunk:(NSString *) chunk; 101 | 102 | - (void) closeInStream; 103 | - (void) closeOutStream; 104 | 105 | - (void) openFile:( NSString * _Nonnull ) uri; 106 | 107 | @end 108 | 109 | #endif /* RNFetchBlobFS_h */ 110 | -------------------------------------------------------------------------------- /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 | #ifndef RNFetchBlobNetwork_h 10 | #define RNFetchBlobNetwork_h 11 | 12 | #import 13 | #import "RNFetchBlobProgress.h" 14 | #import "RNFetchBlobFS.h" 15 | #import "RNFetchBlobRequest.h" 16 | 17 | #if __has_include() 18 | #import 19 | #else 20 | #import "RCTBridgeModule.h" 21 | #endif 22 | 23 | 24 | @interface RNFetchBlobNetwork : NSObject 25 | 26 | @property(nonnull, nonatomic) NSOperationQueue *taskQueue; 27 | @property(nonnull, nonatomic) NSMapTable * requestsTable; 28 | @property(nonnull, nonatomic) NSMutableDictionary *rebindProgressDict; 29 | @property(nonnull, nonatomic) NSMutableDictionary *rebindUploadProgressDict; 30 | 31 | + (RNFetchBlobNetwork* _Nullable)sharedInstance; 32 | + (NSMutableDictionary * _Nullable ) normalizeHeaders:(NSDictionary * _Nullable)headers; 33 | + (void) emitExpiredTasks; 34 | 35 | - (nullable id) init; 36 | - (void) sendRequest:(NSDictionary * _Nullable )options 37 | contentLength:(long)contentLength 38 | bridge:(RCTBridge * _Nullable)bridgeRef 39 | taskId:(NSString * _Nullable)taskId 40 | withRequest:(NSURLRequest * _Nullable)req 41 | callback:(_Nullable RCTResponseSenderBlock) callback; 42 | - (void) cancelRequest:(NSString * _Nonnull)taskId; 43 | - (void) enableProgressReport:(NSString * _Nonnull) taskId config:(RNFetchBlobProgress * _Nullable)config; 44 | - (void) enableUploadProgress:(NSString * _Nonnull) taskId config:(RNFetchBlobProgress * _Nullable)config; 45 | 46 | 47 | @end 48 | 49 | 50 | #endif /* RNFetchBlobNetwork_h */ 51 | -------------------------------------------------------------------------------- /ios/RNFetchBlobNetwork.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlobNetwork.m 3 | // RNFetchBlob 4 | // 5 | // Created by wkh237 on 2016/6/6. 6 | // Copyright © 2016 wkh237. All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | #import "RNFetchBlobNetwork.h" 12 | 13 | #import "RNFetchBlob.h" 14 | #import "RNFetchBlobConst.h" 15 | #import "RNFetchBlobProgress.h" 16 | 17 | #if __has_include() 18 | #import 19 | #import 20 | #import 21 | #import 22 | #else 23 | #import "RCTRootView.h" 24 | #import "RCTLog.h" 25 | #import "RCTEventDispatcher.h" 26 | #import "RCTBridge.h" 27 | #endif 28 | 29 | //////////////////////////////////////// 30 | // 31 | // HTTP request handler 32 | // 33 | //////////////////////////////////////// 34 | 35 | NSMapTable * expirationTable; 36 | 37 | __attribute__((constructor)) 38 | static void initialize_tables() { 39 | if (expirationTable == nil) { 40 | expirationTable = [[NSMapTable alloc] init]; 41 | } 42 | } 43 | 44 | 45 | @implementation RNFetchBlobNetwork 46 | 47 | 48 | - (id)init { 49 | self = [super init]; 50 | if (self) { 51 | self.requestsTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory]; 52 | 53 | self.taskQueue = [[NSOperationQueue alloc] init]; 54 | self.taskQueue.qualityOfService = NSQualityOfServiceUtility; 55 | self.taskQueue.maxConcurrentOperationCount = 10; 56 | self.rebindProgressDict = [NSMutableDictionary dictionary]; 57 | self.rebindUploadProgressDict = [NSMutableDictionary dictionary]; 58 | } 59 | 60 | return self; 61 | } 62 | 63 | + (RNFetchBlobNetwork* _Nullable)sharedInstance { 64 | static id _sharedInstance = nil; 65 | static dispatch_once_t onceToken; 66 | 67 | dispatch_once(&onceToken, ^{ 68 | _sharedInstance = [[self alloc] init]; 69 | }); 70 | 71 | return _sharedInstance; 72 | } 73 | 74 | - (void) sendRequest:(__weak NSDictionary * _Nullable )options 75 | contentLength:(long) contentLength 76 | bridge:(RCTBridge * _Nullable)bridgeRef 77 | taskId:(NSString * _Nullable)taskId 78 | withRequest:(__weak NSURLRequest * _Nullable)req 79 | callback:(_Nullable RCTResponseSenderBlock) callback 80 | { 81 | RNFetchBlobRequest *request = [[RNFetchBlobRequest alloc] init]; 82 | [request sendRequest:options 83 | contentLength:contentLength 84 | bridge:bridgeRef 85 | taskId:taskId 86 | withRequest:req 87 | taskOperationQueue:self.taskQueue 88 | callback:callback]; 89 | 90 | @synchronized([RNFetchBlobNetwork class]) { 91 | [self.requestsTable setObject:request forKey:taskId]; 92 | [self checkProgressConfig]; 93 | } 94 | } 95 | 96 | - (void) checkProgressConfig { 97 | //reconfig progress 98 | [self.rebindProgressDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, RNFetchBlobProgress * _Nonnull config, BOOL * _Nonnull stop) { 99 | [self enableProgressReport:key config:config]; 100 | }]; 101 | [self.rebindProgressDict removeAllObjects]; 102 | 103 | //reconfig uploadProgress 104 | [self.rebindUploadProgressDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, RNFetchBlobProgress * _Nonnull config, BOOL * _Nonnull stop) { 105 | [self enableUploadProgress:key config:config]; 106 | }]; 107 | [self.rebindUploadProgressDict removeAllObjects]; 108 | } 109 | 110 | - (void) enableProgressReport:(NSString *) taskId config:(RNFetchBlobProgress *)config 111 | { 112 | if (config) { 113 | @synchronized ([RNFetchBlobNetwork class]) { 114 | if (![self.requestsTable objectForKey:taskId]) { 115 | [self.rebindProgressDict setValue:config forKey:taskId]; 116 | } else { 117 | [self.requestsTable objectForKey:taskId].progressConfig = config; 118 | } 119 | } 120 | } 121 | } 122 | 123 | - (void) enableUploadProgress:(NSString *) taskId config:(RNFetchBlobProgress *)config 124 | { 125 | if (config) { 126 | @synchronized ([RNFetchBlobNetwork class]) { 127 | if (![self.requestsTable objectForKey:taskId]) { 128 | [self.rebindUploadProgressDict setValue:config forKey:taskId]; 129 | } else { 130 | [self.requestsTable objectForKey:taskId].uploadProgressConfig = config; 131 | } 132 | } 133 | } 134 | } 135 | 136 | - (void) cancelRequest:(NSString *)taskId 137 | { 138 | NSURLSessionDataTask * task; 139 | 140 | @synchronized ([RNFetchBlobNetwork class]) { 141 | task = [self.requestsTable objectForKey:taskId].task; 142 | } 143 | 144 | if (task && task.state == NSURLSessionTaskStateRunning) { 145 | [task cancel]; 146 | } 147 | } 148 | 149 | // removing case from headers 150 | + (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers 151 | { 152 | NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init]; 153 | for (NSString * key in headers) { 154 | [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]]; 155 | } 156 | 157 | return mheaders; 158 | } 159 | 160 | // #115 Invoke fetch.expire event on those expired requests so that the expired event can be handled 161 | + (void) emitExpiredTasks 162 | { 163 | @synchronized ([RNFetchBlobNetwork class]){ 164 | NSEnumerator * emu = [expirationTable keyEnumerator]; 165 | NSString * key; 166 | 167 | while ((key = [emu nextObject])) 168 | { 169 | RCTBridge * bridge = [RNFetchBlob getRCTBridge]; 170 | id args = @{ @"taskId": key }; 171 | [bridge.eventDispatcher sendDeviceEventWithName:EVENT_EXPIRE body:args]; 172 | 173 | } 174 | 175 | // clear expired task entries 176 | [expirationTable removeAllObjects]; 177 | expirationTable = [[NSMapTable alloc] init]; 178 | } 179 | } 180 | 181 | @end 182 | -------------------------------------------------------------------------------- /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:(NSDictionary *) 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 | 15 | #if __has_include() 16 | #import 17 | #else 18 | #import "RCTLog.h" 19 | #endif 20 | 21 | @interface RNFetchBlobReqBuilder() 22 | { 23 | 24 | } 25 | @end 26 | 27 | @implementation RNFetchBlobReqBuilder 28 | 29 | 30 | // Fetch blob data request 31 | +(void) buildMultipartRequest:(NSDictionary *)options 32 | taskId:(NSString *)taskId 33 | method:(NSString *)method 34 | url:(NSString *)url 35 | headers:(NSDictionary *)headers 36 | form:(NSArray *)form 37 | onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete 38 | { 39 | // NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 40 | NSString * encodedUrl = url; 41 | 42 | // send request 43 | __block NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString: encodedUrl]]; 44 | __block NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]]; 45 | NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970]; 46 | NSNumber * timeStampObj = [NSNumber numberWithDouble: timeStamp]; 47 | 48 | // generate boundary 49 | __block NSString * boundary = [NSString stringWithFormat:@"RNFetchBlob%d", timeStampObj]; 50 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 51 | __block NSMutableData * postData = [[NSMutableData alloc] init]; 52 | // combine multipart/form-data body 53 | [[self class] buildFormBody:form boundary:boundary onComplete:^(NSData *formData, BOOL hasError) { 54 | if(hasError) 55 | { 56 | onComplete(nil, nil); 57 | } 58 | else 59 | { 60 | if(formData != nil) 61 | { 62 | [postData appendData:formData]; 63 | // close form data 64 | [postData appendData: [[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 65 | [request setHTTPBody:postData]; 66 | } 67 | // set content-length 68 | [mheaders setValue:[NSString stringWithFormat:@"%lu",[postData length]] forKey:@"Content-Length"]; 69 | [mheaders setValue:@"100-continue" forKey:@"Expect"]; 70 | // appaned boundary to content-type 71 | [mheaders setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forKey:@"content-type"]; 72 | [request setHTTPMethod: method]; 73 | [request setAllHTTPHeaderFields:mheaders]; 74 | onComplete(request, [formData length]); 75 | } 76 | }]; 77 | 78 | }); 79 | } 80 | 81 | // Fetch blob data request 82 | +(void) buildOctetRequest:(NSDictionary *)options 83 | taskId:(NSString *)taskId 84 | method:(NSString *)method 85 | url:(NSString *)url 86 | headers:(NSDictionary *)headers 87 | body:(NSString *)body 88 | onComplete:(void(^)(__weak NSURLRequest * req, long bodyLength))onComplete 89 | { 90 | // NSString * encodedUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 91 | NSString * encodedUrl = url; 92 | // send request 93 | __block NSMutableURLRequest *request = [[NSMutableURLRequest alloc] 94 | initWithURL:[NSURL URLWithString: encodedUrl]]; 95 | 96 | __block NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[RNFetchBlobNetwork normalizeHeaders:headers]]; 97 | // move heavy task to another thread 98 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 99 | NSMutableData * blobData; 100 | long size = -1; 101 | // if method is POST, PUT or PATCH, convert data string format 102 | if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"] || [[method lowercaseString] isEqualToString:@"patch"]) { 103 | // generate octet-stream body 104 | if(body != nil) { 105 | __block NSString * cType = [[self class] getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders]; 106 | __block NSString * transferEncoding = [[self class] getHeaderIgnoreCases:@"transfer-encoding" fromHeaders:mheaders]; 107 | // when headers does not contain a key named "content-type" (case ignored), use default content type 108 | if(cType == nil) 109 | { 110 | [mheaders setValue:@"application/octet-stream" forKey:@"Content-Type"]; 111 | } 112 | 113 | // when body is a string contains file path prefix, try load file from the path 114 | if([body hasPrefix:FILE_PREFIX]) { 115 | __block NSString * orgPath = [body substringFromIndex:[FILE_PREFIX length]]; 116 | orgPath = [RNFetchBlobFS getPathOfAsset:orgPath]; 117 | if([orgPath hasPrefix:AL_PREFIX]) 118 | { 119 | 120 | [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(id content, NSString* code, NSString * err) { 121 | if(err != nil) 122 | { 123 | onComplete(nil, nil); 124 | } 125 | else 126 | { 127 | [request setHTTPBody:((NSData *)content)]; 128 | [request setHTTPMethod: method]; 129 | [request setAllHTTPHeaderFields:mheaders]; 130 | onComplete(request, [((NSData *)content) length]); 131 | } 132 | }]; 133 | 134 | return; 135 | } 136 | size = [[[NSFileManager defaultManager] attributesOfItemAtPath:orgPath error:nil] fileSize]; 137 | if(transferEncoding != nil && [[transferEncoding lowercaseString] isEqualToString:@"chunked"]) 138 | { 139 | [request setHTTPBodyStream: [NSInputStream inputStreamWithFileAtPath:orgPath ]]; 140 | } 141 | else 142 | { 143 | __block NSData * bodyBytes = [NSData dataWithContentsOfFile:orgPath ]; 144 | [request setHTTPBody:bodyBytes]; 145 | } 146 | } 147 | // otherwise convert it as BASE64 data string 148 | else { 149 | 150 | __block NSString * cType = [[self class]getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders]; 151 | // when content-type is application/octet* decode body string using BASE64 decoder 152 | if([[cType lowercaseString] hasPrefix:@"application/octet"] || [[cType lowercaseString] containsString:@";base64"]) 153 | { 154 | __block NSString * ncType = [[cType stringByReplacingOccurrencesOfString:@";base64" withString:@""]stringByReplacingOccurrencesOfString:@";BASE64" withString:@""]; 155 | if([mheaders valueForKey:@"content-type"] != nil) 156 | [mheaders setValue:ncType forKey:@"content-type"]; 157 | if([mheaders valueForKey:@"Content-Type"] != nil) 158 | [mheaders setValue:ncType forKey:@"Content-Type"]; 159 | blobData = [[NSData alloc] initWithBase64EncodedString:body options:0]; 160 | [request setHTTPBody:blobData]; 161 | size = [blobData length]; 162 | } 163 | // otherwise use the body as-is 164 | else 165 | { 166 | size = [body length]; 167 | [request setHTTPBody: [body dataUsingEncoding:NSUTF8StringEncoding]]; 168 | } 169 | } 170 | } 171 | } 172 | 173 | [request setHTTPMethod: method]; 174 | [request setAllHTTPHeaderFields:mheaders]; 175 | 176 | onComplete(request, size); 177 | }); 178 | } 179 | 180 | +(void) buildFormBody:(NSArray *)form boundary:(NSString *)boundary onComplete:(void(^)(NSData * formData, BOOL hasError))onComplete 181 | { 182 | __block NSMutableData * formData = [[NSMutableData alloc] init]; 183 | if(form == nil) 184 | onComplete(nil, NO); 185 | else 186 | { 187 | __block int i = 0; 188 | __block int count = [form count]; 189 | // a recursive block that builds multipart body asynchornously 190 | void __block (^getFieldData)(id field) = ^(id field) 191 | { 192 | NSString * name = [field valueForKey:@"name"]; 193 | __block NSString * content = [field valueForKey:@"data"]; 194 | NSString * contentType = [field valueForKey:@"type"]; 195 | // skip when the form field `name` or `data` is empty 196 | if(content == nil || name == nil) 197 | { 198 | i++; 199 | getFieldData([form objectAtIndex:i]); 200 | RCTLogWarn(@"RNFetchBlob multipart request builder has found a field without `data` or `name` property, the field will be removed implicitly."); 201 | return; 202 | } 203 | 204 | // field is a text field 205 | if([field valueForKey:@"filename"] == nil || content == nil) { 206 | contentType = contentType == nil ? @"text/plain" : contentType; 207 | [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 208 | [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]]; 209 | [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; 210 | [formData appendData:[[NSString stringWithFormat:@"%@\r\n", content] dataUsingEncoding:NSUTF8StringEncoding]]; 211 | } 212 | // field contains a file 213 | else { 214 | contentType = contentType == nil ? @"application/octet-stream" : contentType; 215 | NSMutableData * blobData; 216 | if(content != nil) 217 | { 218 | // append data from file asynchronously 219 | if([content hasPrefix:FILE_PREFIX]) 220 | { 221 | NSString * orgPath = [content substringFromIndex:[FILE_PREFIX length]]; 222 | orgPath = [RNFetchBlobFS getPathOfAsset:orgPath]; 223 | 224 | [RNFetchBlobFS readFile:orgPath encoding:nil onComplete:^(NSData *content, NSString* code, NSString * err) { 225 | if(err != nil) 226 | { 227 | onComplete(formData, YES); 228 | return; 229 | } 230 | NSString * filename = [field valueForKey:@"filename"]; 231 | [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 232 | [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]]; 233 | [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; 234 | [formData appendData:content]; 235 | [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; 236 | i++; 237 | if(i < count) 238 | { 239 | __block NSDictionary * nextField = [form objectAtIndex:i]; 240 | getFieldData(nextField); 241 | } 242 | else 243 | { 244 | onComplete(formData, NO); 245 | getFieldData = nil; 246 | } 247 | }]; 248 | return ; 249 | } 250 | else 251 | blobData = [[NSData alloc] initWithBase64EncodedString:content options:0]; 252 | } 253 | NSString * filename = [field valueForKey:@"filename"]; 254 | [formData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 255 | [formData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]]; 256 | [formData appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]]; 257 | [formData appendData:blobData]; 258 | [formData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; 259 | blobData = nil; 260 | } 261 | i++; 262 | if(i < count) 263 | { 264 | __block NSDictionary * nextField = [form objectAtIndex:i]; 265 | getFieldData(nextField); 266 | } 267 | else 268 | { 269 | onComplete(formData, NO); 270 | getFieldData = nil; 271 | } 272 | 273 | }; 274 | __block NSDictionary * nextField = [form objectAtIndex:i]; 275 | getFieldData(nextField); 276 | } 277 | } 278 | 279 | +(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSDictionary *) headers { 280 | 281 | NSString * normalCase = [headers valueForKey:field]; 282 | NSString * ignoredCase = [headers valueForKey:[field lowercaseString]]; 283 | if( normalCase != nil) 284 | return normalCase; 285 | else 286 | return ignoredCase; 287 | 288 | } 289 | 290 | 291 | @end 292 | -------------------------------------------------------------------------------- /ios/RNFetchBlobRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNFetchBlobRequest.h 3 | // RNFetchBlob 4 | // 5 | // Created by Artur Chrusciel on 15.01.18. 6 | // Copyright © 2018 wkh237.github.io. All rights reserved. 7 | // 8 | 9 | #ifndef RNFetchBlobRequest_h 10 | #define RNFetchBlobRequest_h 11 | 12 | #import 13 | 14 | #import "RNFetchBlobProgress.h" 15 | 16 | #if __has_include() 17 | #import 18 | #else 19 | #import "RCTBridgeModule.h" 20 | #endif 21 | 22 | @interface RNFetchBlobRequest : NSObject 23 | 24 | @property (nullable, nonatomic) NSString * taskId; 25 | @property (nonatomic) long long expectedBytes; 26 | @property (nonatomic) long long receivedBytes; 27 | @property (nonatomic) BOOL isServerPush; 28 | @property (nullable, nonatomic) NSMutableData * respData; 29 | @property (nullable, strong, nonatomic) RCTResponseSenderBlock callback; 30 | @property (nullable, nonatomic) RCTBridge * bridge; 31 | @property (nullable, nonatomic) NSDictionary * options; 32 | @property (nullable, nonatomic) NSError * error; 33 | @property (nullable, nonatomic) RNFetchBlobProgress *progressConfig; 34 | @property (nullable, nonatomic) RNFetchBlobProgress *uploadProgressConfig; 35 | @property (nullable, nonatomic, weak) NSURLSessionDataTask *task; 36 | 37 | - (void) sendRequest:(NSDictionary * _Nullable )options 38 | contentLength:(long)contentLength 39 | bridge:(RCTBridge * _Nullable)bridgeRef 40 | taskId:(NSString * _Nullable)taskId 41 | withRequest:(NSURLRequest * _Nullable)req 42 | taskOperationQueue:(NSOperationQueue * _Nonnull)operationQueue 43 | callback:(_Nullable RCTResponseSenderBlock) callback; 44 | 45 | @end 46 | 47 | #endif /* RNFetchBlobRequest_h */ 48 | -------------------------------------------------------------------------------- /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( 13 | 'Use JSONStream will automatically replace window.XMLHttpRequest with RNFetchBlob.polyfill.XMLHttpRequest. ' + 14 | 'You are seeing this warning because you did not replace it manually.' 15 | ) 16 | } 17 | 18 | if(typeof arg === 'string') { 19 | if(URIUtil.isFileURI(arg)) { 20 | arg = { 21 | url : 'JSONStream://' + arg, 22 | headers : { noCache : true } 23 | } 24 | } 25 | else 26 | arg = 'JSONStream://' + arg 27 | 28 | } 29 | else if(typeof arg === 'object') { 30 | let headers = arg.headers || {} 31 | if(URIUtil.isFileURI(arg.url)) { 32 | headers.noCache = true 33 | } 34 | arg = Object.assign(arg, { 35 | url : 'JSONStream://' + arg.url, 36 | headers 37 | }) 38 | } 39 | return Oboe(arg) 40 | } 41 | 42 | export default OboeExtended 43 | -------------------------------------------------------------------------------- /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", 32 | "wkh237 " 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /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 {NativeModules} from 'react-native'; 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 RNFetchBlob = NativeModules.RNFetchBlob 12 | const log = new Log('Blob') 13 | const blobCacheDir = fs.dirs.DocumentDir + '/RNFetchBlob-blobs/' 14 | 15 | log.disable() 16 | // log.level(3) 17 | 18 | /** 19 | * A RNFetchBlob style Blob polyfill class, this is a Blob which compatible to 20 | * Response object attain fron RNFetchBlob.fetch. 21 | */ 22 | export default class Blob extends EventTarget { 23 | 24 | cacheName:string; 25 | type:string; 26 | size:number; 27 | isRNFetchBlobPolyfill:boolean = true; 28 | multipartBoundary:string = null; 29 | 30 | _ref:string = null; 31 | _blobCreated:boolean = false; 32 | _onCreated:Array = []; 33 | _closed:boolean = false; 34 | 35 | /** 36 | * Static method that remove all files in Blob cache folder. 37 | * @nonstandard 38 | * @return {Promise} 39 | */ 40 | static clearCache() { 41 | return fs.unlink(blobCacheDir).then(() => fs.mkdir(blobCacheDir)) 42 | } 43 | 44 | static build(data:any, cType:any):Promise { 45 | return new Promise((resolve, reject) => { 46 | new Blob(data, cType).onCreated(resolve) 47 | }) 48 | } 49 | 50 | get blobPath() { 51 | return this._ref 52 | } 53 | 54 | static setLog(level:number) { 55 | if(level === -1) 56 | log.disable() 57 | else 58 | log.level(level) 59 | } 60 | 61 | /** 62 | * RNFetchBlob Blob polyfill, create a Blob directly from file path, BASE64 63 | * encoded data, and string. The conversion is done implicitly according to 64 | * given `mime`. However, the blob creation is asynchronously, to register 65 | * event `onCreated` is need to ensure the Blob is creadted. 66 | * @param {any} data Content of Blob object 67 | * @param {any} mime Content type settings of Blob object, `text/plain` 68 | * by default 69 | * @param {boolean} defer When this argument set to `true`, blob constructor 70 | * will not invoke blob created event automatically. 71 | */ 72 | constructor(data:any, cType:any, defer:boolean) { 73 | super() 74 | cType = cType || {} 75 | this.cacheName = getBlobName() 76 | this.isRNFetchBlobPolyfill = true 77 | this.isDerived = defer 78 | this.type = cType.type || 'text/plain' 79 | log.verbose('Blob constructor called', 'mime', this.type, 'type', typeof data, 'length', data? data.length:0) 80 | this._ref = blobCacheDir + this.cacheName 81 | let p = null 82 | if(!data) 83 | data = '' 84 | if(data.isRNFetchBlobPolyfill) { 85 | log.verbose('create Blob cache file from Blob object') 86 | let size = 0 87 | this._ref = String(data.getRNFetchBlobRef()) 88 | let orgPath = this._ref 89 | 90 | p = fs.exists(orgPath) 91 | .then((exist) => { 92 | if(exist) 93 | return fs.writeFile(orgPath, data, 'uri') 94 | .then((size) => Promise.resolve(size)) 95 | .catch((err) => { 96 | throw `RNFetchBlob Blob file creation error, ${err}` 97 | }) 98 | else 99 | throw `could not create Blob from path ${orgPath}, file not exists` 100 | }) 101 | } 102 | // process FormData 103 | else if(data instanceof FormData) { 104 | log.verbose('create Blob cache file from FormData', data) 105 | let boundary = `RNFetchBlob-${this.cacheName}-${Date.now()}` 106 | this.multipartBoundary = boundary 107 | let parts = data.getParts() 108 | let formArray = [] 109 | if(!parts) { 110 | p = fs.writeFile(this._ref, '', 'utf8') 111 | } 112 | else { 113 | for(let i in parts) { 114 | formArray.push('\r\n--'+boundary+'\r\n') 115 | let part = parts[i] 116 | for(let j in part.headers) { 117 | formArray.push(j + ': ' +part.headers[j] + '\r\n') 118 | } 119 | formArray.push('\r\n') 120 | if(part.isRNFetchBlobPolyfill) 121 | formArray.push(part) 122 | else 123 | formArray.push(part.string) 124 | } 125 | log.verbose('FormData array', formArray) 126 | formArray.push('\r\n--'+boundary+'--\r\n') 127 | p = createMixedBlobData(this._ref, formArray) 128 | } 129 | } 130 | // if the data is a string starts with `RNFetchBlob-file://`, append the 131 | // Blob data from file path 132 | else if(typeof data === 'string' && data.startsWith('RNFetchBlob-file://')) { 133 | log.verbose('create Blob cache file from file path', data) 134 | // set this flag so that we know this blob is a wrapper of an existing file 135 | this._isReference = true 136 | this._ref = String(data).replace('RNFetchBlob-file://', '') 137 | let orgPath = this._ref 138 | if(defer) 139 | return 140 | else { 141 | p = fs.stat(orgPath) 142 | .then((stat) => { 143 | return Promise.resolve(stat.size) 144 | }) 145 | } 146 | } 147 | // content from variable need create file 148 | else if(typeof data === 'string') { 149 | let encoding = 'utf8' 150 | let mime = String(this.type) 151 | // when content type contains application/octet* or *;base64, RNFetchBlob 152 | // fs will treat it as BASE64 encoded string binary data 153 | if(/(application\/octet|\;base64)/i.test(mime)) 154 | encoding = 'base64' 155 | else 156 | data = data.toString() 157 | // create cache file 158 | this.type = String(this.type).replace(/;base64/ig, '') 159 | log.verbose('create Blob cache file from string', 'encode', encoding) 160 | p = fs.writeFile(this._ref, data, encoding) 161 | .then((size) => { 162 | return Promise.resolve(size) 163 | }) 164 | 165 | } 166 | // TODO : ArrayBuffer support 167 | // else if (data instanceof ArrayBuffer ) { 168 | // 169 | // } 170 | // when input is an array of mixed data types, create a file cache 171 | else if(Array.isArray(data)) { 172 | log.verbose('create Blob cache file from mixed array', data) 173 | p = createMixedBlobData(this._ref, data) 174 | } 175 | else { 176 | data = data.toString() 177 | p = fs.writeFile(this._ref, data, 'utf8') 178 | .then((size) => Promise.resolve(size)) 179 | } 180 | p && p.then((size) => { 181 | this.size = size 182 | this._invokeOnCreateEvent() 183 | }) 184 | .catch((err) => { 185 | log.error('RNFetchBlob could not create Blob : '+ this._ref, err) 186 | }) 187 | 188 | } 189 | 190 | /** 191 | * Since Blob content will asynchronously write to a file during creation, 192 | * use this method to register an event handler for Blob initialized event. 193 | * @nonstandard 194 | * @param {(b:Blob) => void} An event handler invoked when Blob created 195 | * @return {Blob} The Blob object instance itself 196 | */ 197 | onCreated(fn:() => void):Blob { 198 | log.verbose('#register blob onCreated', this._blobCreated) 199 | if(!this._blobCreated) 200 | this._onCreated.push(fn) 201 | else { 202 | fn(this) 203 | } 204 | return this 205 | } 206 | 207 | markAsDerived() { 208 | this._isDerived = true 209 | } 210 | 211 | get isDerived() { 212 | return this._isDerived || false 213 | } 214 | 215 | /** 216 | * Get file reference of the Blob object. 217 | * @nonstandard 218 | * @return {string} Blob file reference which can be consumed by RNFetchBlob fs 219 | */ 220 | getRNFetchBlobRef() { 221 | return this._ref 222 | } 223 | 224 | /** 225 | * Create a Blob object which is sliced from current object 226 | * @param {number} start Start byte number 227 | * @param {number} end End byte number 228 | * @param {string} contentType Optional, content type of new Blob object 229 | * @return {Blob} 230 | */ 231 | slice(start:?number, end:?number, contentType:?string=''):Blob { 232 | if(this._closed) 233 | throw 'Blob has been released.' 234 | log.verbose('slice called', start, end, contentType) 235 | 236 | 237 | let resPath = blobCacheDir + getBlobName() 238 | let pass = false 239 | log.debug('fs.slice new blob will at', resPath) 240 | let result = new Blob(RNFetchBlob.wrap(resPath), { type : contentType }, true) 241 | fs.exists(blobCacheDir) 242 | .then((exist) => { 243 | if(exist) 244 | return Promise.resolve() 245 | return fs.mkdir(blobCacheDir) 246 | }) 247 | .then(() => fs.slice(this._ref, resPath, start, end)) 248 | .then((dest) => { 249 | log.debug('fs.slice done', dest) 250 | result._invokeOnCreateEvent() 251 | pass = true 252 | }) 253 | .catch((err) => { 254 | console.warn('Blob.slice failed:', err) 255 | pass = true 256 | }) 257 | log.debug('slice returning new Blob') 258 | 259 | return result 260 | } 261 | 262 | /** 263 | * Read data of the Blob object, this is not standard method. 264 | * @nonstandard 265 | * @param {string} encoding Read data with encoding 266 | * @return {Promise} 267 | */ 268 | readBlob(encoding:string):Promise { 269 | if(this._closed) 270 | throw 'Blob has been released.' 271 | return fs.readFile(this._ref, encoding || 'utf8') 272 | } 273 | 274 | /** 275 | * Release the resource of the Blob object. 276 | * @nonstandard 277 | * @return {Promise} 278 | */ 279 | close() { 280 | if(this._closed) 281 | return Promise.reject('Blob has been released.') 282 | this._closed = true 283 | return fs.unlink(this._ref).catch((err) => { 284 | console.warn(err) 285 | }) 286 | } 287 | 288 | safeClose() { 289 | if(this._closed) 290 | return Promise.reject('Blob has been released.') 291 | this._closed = true 292 | if(!this._isReference) { 293 | return fs.unlink(this._ref).catch((err) => { 294 | console.warn(err) 295 | }) 296 | } 297 | else { 298 | return Promise.resolve() 299 | } 300 | } 301 | 302 | _invokeOnCreateEvent() { 303 | log.verbose('invoke create event', this._onCreated) 304 | this._blobCreated = true 305 | let fns = this._onCreated 306 | for(let i in fns) { 307 | if(typeof fns[i] === 'function') { 308 | fns[i](this) 309 | } 310 | } 311 | delete this._onCreated 312 | } 313 | 314 | } 315 | 316 | /** 317 | * Get a temp filename for Blob object 318 | * @return {string} Temporary filename 319 | */ 320 | function getBlobName() { 321 | return 'blob-' + getUUID() 322 | } 323 | 324 | /** 325 | * Create a file according to given array. The element in array can be a number, 326 | * Blob, String, Array. 327 | * @param {string} ref File path reference 328 | * @param {Array} dataArray An array contains different types of data. 329 | * @return {Promise} 330 | */ 331 | function createMixedBlobData(ref, dataArray) { 332 | // create an empty file for store blob data 333 | let p = fs.writeFile(ref, '') 334 | let args = [] 335 | let size = 0 336 | for(let i in dataArray) { 337 | let part = dataArray[i] 338 | if(!part) 339 | continue 340 | if(part.isRNFetchBlobPolyfill) { 341 | args.push([ref, part._ref, 'uri']) 342 | } 343 | else if(typeof part === 'string') 344 | args.push([ref, part, 'utf8']) 345 | // TODO : ArrayBuffer 346 | // else if (part instanceof ArrayBuffer) { 347 | // 348 | // } 349 | else if (Array.isArray(part)) 350 | args.push([ref, part, 'ascii']) 351 | } 352 | // start write blob data 353 | for(let i in args) { 354 | p = p.then(function(written){ 355 | let arg = this 356 | if(written) 357 | size += written 358 | log.verbose('mixed blob write', args[i], written) 359 | return fs.appendFile(...arg) 360 | }.bind(args[i])) 361 | } 362 | return p.then(() => Promise.resolve(size)) 363 | } 364 | -------------------------------------------------------------------------------- /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 {NativeModules} from 'react-native'; 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 RNFetchBlob = NativeModules.RNFetchBlob 8 | const log = new Log('FetchPolyfill') 9 | 10 | log.disable() 11 | // log.level(3) 12 | 13 | export default class Fetch { 14 | 15 | constructor(config:RNFetchBlobConfig) { 16 | Object.assign(this, new RNFetchBlobFetchPolyfill(config)) 17 | } 18 | 19 | } 20 | 21 | class RNFetchBlobFetchPolyfill { 22 | 23 | constructor(config:RNFetchBlobConfig) { 24 | this.build = () => (url, options = {}) => { 25 | 26 | let body = options.body 27 | let promise = Promise.resolve() 28 | let blobCache = null 29 | 30 | options.headers = options.headers || {} 31 | let ctype = options['Content-Type'] || options['content-type'] 32 | let ctypeH = options.headers['Content-Type'] || options.headers['content-type'] 33 | options.headers['Content-Type'] = ctype || ctypeH 34 | options.headers['content-type'] = ctype || ctypeH 35 | options.method = options.method || 'GET' 36 | if(body) { 37 | // When the request body is an instance of FormData, create a Blob cache 38 | // to upload the body. 39 | if(body instanceof FormData) { 40 | log.verbose('convert FormData to blob body') 41 | promise = Blob.build(body).then((b) => { 42 | blobCache = b 43 | options.headers['Content-Type'] = 'multipart/form-data;boundary=' + b.multipartBoundary 44 | options.headers['content-type'] = 'multipart/form-data;boundary=' + b.multipartBoundary 45 | return Promise.resolve(RNFetchBlob.wrap(b._ref)) 46 | }) 47 | } 48 | // When request body is a Blob, use file URI of the Blob as request body. 49 | else if (body.isRNFetchBlobPolyfill) 50 | promise = Promise.resolve(RNFetchBlob.wrap(body.blobPath)) 51 | else if (typeof body !== 'object' && options.headers['Content-Type'] !== 'application/json') 52 | promise = Promise.resolve(JSON.stringify(body)) 53 | else if (typeof body !== 'string') 54 | promise = Promise.resolve(body.toString()) 55 | // send it as-is, leave the native module decide how to send the body. 56 | else 57 | promise = Promise.resolve(body) 58 | } 59 | // task is a progress reportable and cancellable Promise, however, 60 | // task.then is not, so we have to extend task.then with progress and 61 | // cancel function 62 | let progressHandler, uploadHandler, cancelHandler 63 | let statefulPromise = promise 64 | .then((body) => { 65 | let task = RNFetchBlob.config(config) 66 | .fetch(options.method, url, options.headers, body) 67 | if(progressHandler) 68 | task.progress(progressHandler) 69 | if(uploadHandler) 70 | task.uploadProgress(uploadHandler) 71 | if(cancelHandler) 72 | task.cancel() 73 | return task.then((resp) => { 74 | log.verbose('response', resp) 75 | // release blob cache created when sending request 76 | if(blobCache !== null && blobCache instanceof Blob) 77 | blobCache.close() 78 | return Promise.resolve(new RNFetchBlobFetchResponse(resp)) 79 | }) 80 | }) 81 | 82 | // extend task.then progress with report and cancelling functions 83 | statefulPromise.progress = (fn) => { 84 | progressHandler = fn 85 | } 86 | statefulPromise.uploadProgress = (fn) => { 87 | uploadHandler = fn 88 | } 89 | statefulPromise.cancel = () => { 90 | cancelHandler = true 91 | if(task.cancel) 92 | task.cancel() 93 | } 94 | 95 | return statefulPromise 96 | 97 | } 98 | } 99 | 100 | } 101 | 102 | class RNFetchBlobFetchResponse { 103 | 104 | constructor(resp:FetchBlobResponse) { 105 | let info = resp.info() 106 | this.headers = info.headers 107 | this.ok = info.status >= 200 && info.status <= 299, 108 | this.status = info.status 109 | this.type = 'basic' 110 | this.bodyUsed = false 111 | this.resp = resp 112 | this.rnfbRespInfo = info 113 | this.rnfbResp = resp 114 | } 115 | 116 | rawResp() { 117 | return Promise.resolve(this.rnfbResp) 118 | } 119 | 120 | arrayBuffer(){ 121 | log.verbose('to arrayBuffer', this.rnfbRespInfo) 122 | this.bodyUsed = true 123 | return readArrayBuffer(this.rnfbResp, this.rnfbRespInfo) 124 | } 125 | 126 | text() { 127 | log.verbose('to text', this.rnfbResp, this.rnfbRespInfo) 128 | this.bodyUsed = true 129 | return readText(this.rnfbResp, this.rnfbRespInfo) 130 | } 131 | 132 | json() { 133 | log.verbose('to json', this.rnfbResp, this.rnfbRespInfo) 134 | this.bodyUsed = true 135 | return readJSON(this.rnfbResp, this.rnfbRespInfo) 136 | } 137 | 138 | blob() { 139 | log.verbose('to blob', this.rnfbResp, this.rnfbRespInfo) 140 | this.bodyUsed = true 141 | return readBlob(this.rnfbResp, this.rnfbRespInfo) 142 | } 143 | } 144 | 145 | /** 146 | * Get response data as array. 147 | * @param {FetchBlobResponse} resp Response data object from RNFB fetch call. 148 | * @param {RNFetchBlobResponseInfo} info Response informations. 149 | * @return {Promise} 150 | */ 151 | function readArrayBuffer(resp, info):Promise { 152 | switch (info.rnfbEncode) { 153 | case 'path': 154 | return resp.readFile('ascii') 155 | break 156 | default: 157 | let buffer = [] 158 | let str = resp.text() 159 | for (let i in str) { 160 | buffer[i] = str.charCodeAt(i); 161 | } 162 | return Promise.resolve(buffer) 163 | break 164 | } 165 | } 166 | 167 | /** 168 | * Get response data as string. 169 | * @param {FetchBlobResponse} resp Response data object from RNFB fetch call. 170 | * @param {RNFetchBlobResponseInfo} info Response informations. 171 | * @return {Promise} 172 | */ 173 | function readText(resp, info):Promise { 174 | switch (info.rnfbEncode) { 175 | case 'base64': 176 | return Promise.resolve(resp.text()) 177 | break 178 | case 'path': 179 | return resp.text() 180 | break 181 | default: 182 | return Promise.resolve(resp.text()) 183 | break 184 | } 185 | } 186 | 187 | 188 | /** 189 | * Get response data as RNFetchBlob Blob polyfill object. 190 | * @param {FetchBlobResponse} resp Response data object from RNFB fetch call. 191 | * @param {RNFetchBlobResponseInfo} info Response informations. 192 | * @return {Promise} 193 | */ 194 | function readBlob(resp, info):Promise { 195 | log.verbose('readBlob', resp, info) 196 | return resp.blob() 197 | } 198 | 199 | /** 200 | * Get response data as JSON object. 201 | * @param {FetchBlobResponse} resp Response data object from RNFB fetch call. 202 | * @param {RNFetchBlobResponseInfo} info Response informations. 203 | * @return {Promise} 204 | */ 205 | function readJSON(resp, info):Promise { 206 | log.verbose('readJSON', resp, info) 207 | switch (info.rnfbEncode) { 208 | case 'base64': 209 | return Promise.resolve(resp.json()) 210 | case 'path': 211 | return resp.json() 212 | default: 213 | return Promise.resolve(resp.json()) 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /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 Blob from './Blob.js' 6 | 7 | export default class File extends Blob { 8 | 9 | name : string = ''; 10 | 11 | static build(name:string, data:any, cType:string):Promise { 12 | return new Promise((resolve, reject) => { 13 | if (data === undefined) { 14 | reject(new TypeError('data is undefined')) 15 | } 16 | new File(data, cType).onCreated((f) => { 17 | f.name = name 18 | resolve(f) 19 | }) 20 | }) 21 | } 22 | 23 | constructor(data:any , cType:string) { 24 | super(data, cType) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /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 ProgressEvent from './ProgressEvent.js' 6 | import EventTarget from './EventTarget' 7 | import Blob from './Blob' 8 | import Log from '../utils/log.js' 9 | import fs from '../fs' 10 | 11 | const log = new Log('FileReader') 12 | 13 | log.level(3) 14 | 15 | export default class FileReader extends EventTarget { 16 | 17 | static get EMPTY(){ 18 | return 0 19 | } 20 | static get LOADING(){ 21 | return 1 22 | } 23 | static get DONE(){ 24 | return 2 25 | } 26 | 27 | // properties 28 | _readState:number = 0; 29 | _result:any; 30 | _error:any; 31 | 32 | get isRNFBPolyFill(){ return true } 33 | 34 | // event handlers 35 | onloadstart:(e:Event) => void; 36 | onprogress:(e:Event) => void; 37 | onload:(e:Event) => void; 38 | onabort:(e:Event) => void; 39 | onerror:(e:Event) => void; 40 | onloadend:(e:Event) => void; 41 | 42 | constructor() { 43 | super() 44 | log.verbose('file reader const') 45 | this._result = null 46 | } 47 | 48 | abort() { 49 | log.verbose('abort') 50 | } 51 | 52 | readAsArrayBuffer(b:Blob) { 53 | log.verbose('readAsArrayBuffer', b) 54 | } 55 | 56 | readAsBinaryString(b:Blob) { 57 | log.verbose('readAsBinaryString', b) 58 | } 59 | 60 | readAsText(b:Blob, label:?string) { 61 | log.verbose('readAsText', b, label) 62 | } 63 | 64 | readAsDataURL(b:Blob) { 65 | log.verbose('readAsDataURL', b) 66 | } 67 | 68 | dispatchEvent(event, e) { 69 | log.verbose('dispatch event', event, e) 70 | super.dispatchEvent(event, e) 71 | if(typeof this[`on${event}`] === 'function') { 72 | this[`on${event}`](e) 73 | } 74 | } 75 | 76 | // private methods 77 | 78 | // getters and setters 79 | 80 | get readyState() { 81 | return this._readyState 82 | } 83 | 84 | get result() { 85 | return this._result 86 | } 87 | 88 | 89 | 90 | } 91 | -------------------------------------------------------------------------------- /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 {NativeModules} from 'react-native'; 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 RNFetchBlob = NativeModules.RNFetchBlob 13 | const log = new Log('XMLHttpRequest') 14 | 15 | log.disable() 16 | // log.level(3) 17 | 18 | const UNSENT = 0 19 | const OPENED = 1 20 | const HEADERS_RECEIVED = 2 21 | const LOADING = 3 22 | const DONE = 4 23 | 24 | export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ 25 | 26 | _onreadystatechange : () => void; 27 | 28 | upload : XMLHttpRequestEventTarget = new XMLHttpRequestEventTarget(); 29 | static binaryContentTypes : Array = [ 30 | 'image/', 'video/', 'audio/' 31 | ]; 32 | 33 | // readonly 34 | _readyState : number = UNSENT; 35 | _uriType : 'net' | 'file' = 'net'; 36 | _response : any = ''; 37 | _responseText : any = ''; 38 | _responseHeaders : any = {}; 39 | _responseType : '' | 'arraybuffer' | 'blob' | 'json' | 'text' = ''; 40 | // TODO : not suppoted ATM 41 | _responseURL : null = ''; 42 | _responseXML : null = ''; 43 | _status : number = 0; 44 | _statusText : string = ''; 45 | _timeout : number = 60000; 46 | _sendFlag : boolean = false; 47 | _uploadStarted : boolean = false; 48 | _increment : boolean = false; 49 | 50 | // RNFetchBlob compatible data structure 51 | _config : RNFetchBlobConfig = {}; 52 | _url : any; 53 | _method : string; 54 | _headers: any = { 55 | 'Content-Type' : 'text/plain' 56 | }; 57 | _cleanUp : () => void = null; 58 | _body: any; 59 | 60 | // RNFetchBlob promise object, which has `progress`, `uploadProgress`, and 61 | // `cancel` methods. 62 | _task: any; 63 | 64 | // constants 65 | get UNSENT() { return UNSENT } 66 | get OPENED() { return OPENED } 67 | get HEADERS_RECEIVED() { return HEADERS_RECEIVED } 68 | get LOADING() { return LOADING } 69 | get DONE() { return DONE } 70 | 71 | static get UNSENT() { 72 | return UNSENT 73 | } 74 | 75 | static get OPENED() { 76 | return OPENED 77 | } 78 | 79 | static get HEADERS_RECEIVED() { 80 | return HEADERS_RECEIVED 81 | } 82 | 83 | static get LOADING() { 84 | return LOADING 85 | } 86 | 87 | static get DONE() { 88 | return DONE 89 | } 90 | 91 | static setLog(level:number) { 92 | if(level === -1) 93 | log.disable() 94 | else 95 | log.level(level) 96 | } 97 | 98 | static addBinaryContentType(substr:string) { 99 | for(let i in XMLHttpRequest.binaryContentTypes) { 100 | if(new RegExp(substr,'i').test(XMLHttpRequest.binaryContentTypes[i])) { 101 | return 102 | } 103 | } 104 | XMLHttpRequest.binaryContentTypes.push(substr) 105 | 106 | } 107 | 108 | static removeBinaryContentType(val) { 109 | for(let i in XMLHttpRequest.binaryContentTypes) { 110 | if(new RegExp(substr,'i').test(XMLHttpRequest.binaryContentTypes[i])) { 111 | XMLHttpRequest.binaryContentTypes.splice(i,1) 112 | return 113 | } 114 | } 115 | } 116 | 117 | constructor() { 118 | log.verbose('XMLHttpRequest constructor called') 119 | super() 120 | } 121 | 122 | 123 | /** 124 | * XMLHttpRequest.open, always async, user and password not supported. When 125 | * this method invoked, headers should becomes empty again. 126 | * @param {string} method Request method 127 | * @param {string} url Request URL 128 | * @param {true} async Always async 129 | * @param {any} user NOT SUPPORTED 130 | * @param {any} password NOT SUPPORTED 131 | */ 132 | open(method:string, url:string, async:true, user:any, password:any) { 133 | log.verbose('XMLHttpRequest open ', method, url, async, user, password) 134 | this._method = method 135 | this._url = url 136 | this._headers = {} 137 | this._increment = URIUtil.isJSONStreamURI(this._url) 138 | this._url = this._url.replace(/^JSONStream\:\/\//, '') 139 | this._dispatchReadStateChange(XMLHttpRequest.OPENED) 140 | } 141 | 142 | /** 143 | * Invoke this function to send HTTP request, and set body. 144 | * @param {any} body Body in RNfetchblob flavor 145 | */ 146 | send(body) { 147 | 148 | this._body = body 149 | 150 | if(this._readyState !== XMLHttpRequest.OPENED) 151 | throw 'InvalidStateError : XMLHttpRequest is not opened yet.' 152 | let promise = Promise.resolve() 153 | this._sendFlag = true 154 | log.verbose('XMLHttpRequest send ', body) 155 | let {_method, _url, _headers } = this 156 | log.verbose('sending request with args', _method, _url, _headers, body) 157 | log.verbose(typeof body, body instanceof FormData) 158 | 159 | if(body instanceof Blob) { 160 | log.debug('sending blob body', body._blobCreated) 161 | promise = new Promise((resolve, reject) => { 162 | body.onCreated((blob) => { 163 | // when the blob is derived (not created by RN developer), the blob 164 | // will be released after XMLHttpRequest sent 165 | if(blob.isDerived) { 166 | this._cleanUp = () => { 167 | blob.close() 168 | } 169 | } 170 | log.debug('body created send request') 171 | body = RNFetchBlob.wrap(blob.getRNFetchBlobRef()) 172 | resolve() 173 | }) 174 | }) 175 | } 176 | else if(typeof body === 'object') { 177 | body = JSON.stringify(body) 178 | promise = Promise.resolve() 179 | } 180 | else { 181 | body = body ? body.toString() : body 182 | promise = Promise.resolve() 183 | } 184 | 185 | promise.then(() => { 186 | log.debug('send request invoke', body) 187 | for(let h in _headers) { 188 | _headers[h] = _headers[h].toString() 189 | } 190 | 191 | this._task = RNFetchBlob 192 | .config({ 193 | auto: true, 194 | timeout : this._timeout, 195 | increment : this._increment, 196 | binaryContentTypes : XMLHttpRequest.binaryContentTypes 197 | }) 198 | .fetch(_method, _url, _headers, body) 199 | this._task 200 | .stateChange(this._headerReceived) 201 | .uploadProgress(this._uploadProgressEvent) 202 | .progress(this._progressEvent) 203 | .catch(this._onError) 204 | .then(this._onDone) 205 | 206 | }) 207 | } 208 | 209 | overrideMimeType(mime:string) { 210 | log.verbose('XMLHttpRequest overrideMimeType', mime) 211 | this._headers['Content-Type'] = mime 212 | } 213 | 214 | setRequestHeader(name, value) { 215 | log.verbose('XMLHttpRequest set header', name, value) 216 | if(this._readyState !== OPENED || this._sendFlag) { 217 | throw `InvalidStateError : Calling setRequestHeader in wrong state ${this._readyState}` 218 | } 219 | // UNICODE SHOULD NOT PASS 220 | if(typeof name !== 'string' || /[^\u0000-\u00ff]/.test(name)) { 221 | throw 'TypeError : header field name should be a string' 222 | } 223 | // 224 | let invalidPatterns = [ 225 | /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/, 226 | /tt/ 227 | ] 228 | for(let pattern of invalidPatterns) { 229 | if(pattern.test(name) || typeof name !== 'string') { 230 | throw `SyntaxError : Invalid header field name ${name}` 231 | } 232 | } 233 | this._headers[name] = value 234 | } 235 | 236 | abort() { 237 | log.verbose('XMLHttpRequest abort ') 238 | if(!this._task) 239 | return 240 | this._task.cancel((err) => { 241 | let e = { 242 | timeStamp : Date.now(), 243 | } 244 | if(this.onabort) 245 | this.onabort() 246 | if(err) { 247 | e.detail = err 248 | e.type = 'error' 249 | this.dispatchEvent('error', e) 250 | } 251 | else { 252 | e.type = 'abort' 253 | this.dispatchEvent('abort', e) 254 | } 255 | }) 256 | } 257 | 258 | getResponseHeader(field:string):string | null { 259 | log.verbose('XMLHttpRequest get header', field, this._responseHeaders) 260 | if(!this._responseHeaders) 261 | return null 262 | return (this._responseHeaders[field] || this._responseHeaders[field.toLowerCase()]) || null 263 | 264 | } 265 | 266 | getAllResponseHeaders():string | null { 267 | log.verbose('XMLHttpRequest get all headers', this._responseHeaders) 268 | if(!this._responseHeaders) 269 | return '' 270 | let result = '' 271 | let respHeaders = this.responseHeaders 272 | for(let i in respHeaders) { 273 | result += `${i}: ${respHeaders[i]}${String.fromCharCode(0x0D,0x0A)}` 274 | } 275 | return result.substr(0, result.length-2) 276 | } 277 | 278 | _headerReceived = (e) => { 279 | log.debug('header received ', this._task.taskId, e) 280 | this.responseURL = this._url 281 | if(e.state === "2" && e.taskId === this._task.taskId) { 282 | this._responseHeaders = e.headers 283 | this._statusText = e.status 284 | this._status = Math.floor(e.status) 285 | this._dispatchReadStateChange(XMLHttpRequest.HEADERS_RECEIVED) 286 | } 287 | } 288 | 289 | _uploadProgressEvent = (send:number, total:number) => { 290 | if(!this._uploadStarted) { 291 | this.upload.dispatchEvent('loadstart') 292 | this._uploadStarted = true 293 | } 294 | if(send >= total) 295 | this.upload.dispatchEvent('load') 296 | this.upload.dispatchEvent('progress', new ProgressEvent(true, send, total)) 297 | } 298 | 299 | _progressEvent = (send:number, total:number, chunk:string) => { 300 | log.verbose(this.readyState) 301 | if(this._readyState === XMLHttpRequest.HEADERS_RECEIVED) 302 | this._dispatchReadStateChange(XMLHttpRequest.LOADING) 303 | let lengthComputable = false 304 | if(total && total >= 0) 305 | lengthComputable = true 306 | let e = new ProgressEvent(lengthComputable, send, total) 307 | 308 | if(this._increment) { 309 | this._responseText += chunk 310 | } 311 | this.dispatchEvent('progress', e) 312 | } 313 | 314 | _onError = (err) => { 315 | let statusCode = Math.floor(this.status) 316 | if(statusCode >= 100 && statusCode !== 408) { 317 | return 318 | } 319 | log.debug('XMLHttpRequest error', err) 320 | this._statusText = err 321 | this._status = String(err).match(/\d+/) 322 | this._status = this._status ? Math.floor(this.status) : 404 323 | this._dispatchReadStateChange(XMLHttpRequest.DONE) 324 | if(err && String(err.message).match(/(timed\sout|timedout)/) || this._status == 408) { 325 | this.dispatchEvent('timeout') 326 | } 327 | this.dispatchEvent('loadend') 328 | this.dispatchEvent('error', { 329 | type : 'error', 330 | detail : err 331 | }) 332 | this.clearEventListeners() 333 | } 334 | 335 | _onDone = (resp) => { 336 | log.debug('XMLHttpRequest done', this._url, resp, this) 337 | this._statusText = this._status 338 | let responseDataReady = () => { 339 | log.debug('request done state = 4') 340 | this.dispatchEvent('load') 341 | this.dispatchEvent('loadend') 342 | this._dispatchReadStateChange(XMLHttpRequest.DONE) 343 | this.clearEventListeners() 344 | } 345 | if(resp) { 346 | let info = resp.respInfo || {} 347 | log.debug(this._url, info, info.respType) 348 | switch(this._responseType) { 349 | case 'blob' : 350 | resp.blob().then((b) => { 351 | this._responseText = resp.text() 352 | this._response = b 353 | responseDataReady() 354 | }) 355 | break; 356 | case 'arraybuffer': 357 | // TODO : to array buffer 358 | break 359 | case 'json': 360 | this._response = resp.json() 361 | this._responseText = resp.text() 362 | break 363 | default : 364 | this._responseText = resp.text() 365 | this._response = this.responseText 366 | responseDataReady() 367 | break; 368 | } 369 | } 370 | 371 | } 372 | 373 | _dispatchReadStateChange(state) { 374 | this._readyState = state 375 | if(typeof this._onreadystatechange === 'function') 376 | this._onreadystatechange() 377 | } 378 | 379 | set onreadystatechange(fn:() => void) { 380 | log.verbose('XMLHttpRequest set onreadystatechange', fn) 381 | this._onreadystatechange = fn 382 | } 383 | 384 | get onreadystatechange() { 385 | return this._onreadystatechange 386 | } 387 | 388 | get readyState() { 389 | log.verbose('get readyState', this._readyState) 390 | return this._readyState 391 | } 392 | 393 | get status() { 394 | log.verbose('get status', this._status) 395 | return this._status 396 | } 397 | 398 | get statusText() { 399 | log.verbose('get statusText', this._statusText) 400 | return this._statusText 401 | } 402 | 403 | get response() { 404 | log.verbose('get response', this._response) 405 | return this._response 406 | } 407 | 408 | get responseText() { 409 | log.verbose('get responseText', this._responseText) 410 | return this._responseText 411 | } 412 | 413 | get responseURL() { 414 | log.verbose('get responseURL', this._responseURL) 415 | return this._responseURL 416 | } 417 | 418 | get responseHeaders() { 419 | log.verbose('get responseHeaders', this._responseHeaders) 420 | return this._responseHeaders 421 | } 422 | 423 | set timeout(val) { 424 | this._timeout = val*1000 425 | log.verbose('set timeout', this._timeout) 426 | } 427 | 428 | get timeout() { 429 | log.verbose('get timeout', this._timeout) 430 | return this._timeout 431 | } 432 | 433 | set responseType(val) { 434 | log.verbose('set response type', this._responseType) 435 | this._responseType = val 436 | } 437 | 438 | get responseType() { 439 | log.verbose('get response type', this._responseType) 440 | return this._responseType 441 | } 442 | 443 | static get isRNFBPolyfill() { 444 | return true 445 | } 446 | 447 | } 448 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /rn-fetch-blob.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | package = JSON.parse(File.read('package.json')) 3 | 4 | Pod::Spec.new do |s| 5 | s.name = package['name'] 6 | s.version = package['version'] 7 | s.summary = package['description'] 8 | s.requires_arc = true 9 | s.license = 'MIT' 10 | s.homepage = 'n/a' 11 | s.source = { :git => "https://github.com/joltup/rn-fetch-blob" } 12 | s.author = 'Joltup' 13 | s.source_files = 'ios/**/*.{h,m}' 14 | s.platform = :ios, "8.0" 15 | s.dependency 'React-Core' 16 | end 17 | -------------------------------------------------------------------------------- /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 | followRedirect : bool, 10 | trusty : bool, 11 | wifiOnly : bool 12 | }; 13 | 14 | type RNFetchBlobNative = { 15 | // API for fetch octet-stream data 16 | fetchBlob : ( 17 | options:fetchConfig, 18 | taskId:string, 19 | method:string, 20 | url:string, 21 | headers:any, 22 | body:any, 23 | callback:(err:any, ...data:any) => void 24 | ) => void, 25 | // API for fetch form data 26 | fetchBlobForm : ( 27 | options:fetchConfig, 28 | taskId:string, 29 | method:string, 30 | url:string, 31 | headers:any, 32 | form:Array, 33 | callback:(err:any, ...data:any) => void 34 | ) => void, 35 | // open file stream 36 | readStream : ( 37 | path:string, 38 | encode:'utf8' | 'ascii' | 'base64' 39 | ) => void, 40 | // get system folders 41 | getEnvironmentDirs : (dirs:any) => void, 42 | // unlink file by path 43 | unlink : (path:string, callback: (err:any) => void) => void, 44 | removeSession : (paths:Array, callback: (err:any) => void) => void, 45 | ls : (path:string, callback: (err:any) => void) => void, 46 | }; 47 | 48 | type RNFetchBlobResponseInfo = { 49 | taskId : string, 50 | state : number, 51 | headers : any, 52 | status : number, 53 | respType : 'text' | 'blob' | '' | 'json', 54 | rnfbEncode : 'path' | 'base64' | 'ascii' | 'utf8' 55 | } 56 | 57 | type RNFetchBlobStream = { 58 | onData : () => void, 59 | onError : () => void, 60 | onEnd : () => void, 61 | _onData : () => void, 62 | _onEnd : () => void, 63 | _onError : () => void, 64 | } 65 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------