瀏覽代碼

Add tests #8 and fix issues #9 #10

Ben Hsieh 8 年之前
當前提交
dc7b7302e4

+ 45
- 0
.gitignore 查看文件

@@ -0,0 +1,45 @@
1
+testconfig
2
+RNFetchBlobTest/
3
+test/test-server/public/*
4
+!test/test-server/public/github.png
5
+
6
+# OSX
7
+#
8
+.DS_Store
9
+
10
+# Xcode
11
+#
12
+build/
13
+*.pbxuser
14
+!default.pbxuser
15
+*.mode1v3
16
+!default.mode1v3
17
+*.mode2v3
18
+!default.mode2v3
19
+*.perspectivev3
20
+!default.perspectivev3
21
+xcuserdata
22
+*.xccheckout
23
+*.moved-aside
24
+DerivedData
25
+*.hmap
26
+*.ipa
27
+*.xcuserstate
28
+project.xcworkspace
29
+
30
+# Android/IJ
31
+#
32
+.idea
33
+.gradle
34
+local.properties
35
+
36
+# node.js
37
+#
38
+node_modules/
39
+npm-debug.log
40
+
41
+# BUCK
42
+buck-out/
43
+\.buckd/
44
+android/app/libs
45
+android/keystores/debug.keystore

+ 158
- 0
README.md 查看文件

@@ -0,0 +1,158 @@
1
+# react-native-fetch-blob [![npm version](https://badge.fury.io/js/react-native-fetch-blob.svg)](https://badge.fury.io/js/react-native-fetch-blob)
2
+
3
+A react-native module for fetch file/image with custom headers, supports blob response data.
4
+
5
+If you're dealing with image or file server that requires an `Authorization` token in the header, or you're having problem with `fetch` API when receiving blob data, you might try this module (this is also the reason why I made this).
6
+
7
+See [[fetch] Does fetch with blob() marshal data across the bridge?](https://github.com/facebook/react-native/issues/854).
8
+
9
+This module enables you upload/download binary data in js, see [Examples](#user-content-usage) bellow.
10
+
11
+The source code is very simple, just an implementation of native HTTP request, supports both Android (uses awesome native library  [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client])) and IOS.
12
+
13
+## Usage
14
+
15
+* [Installation](#user-content-installation)
16
+* [Examples](#user-content-usage)
17
+ * [Download file](#user-content-download-example--fetch-files-that-needs-authorization-token)
18
+ * [Upload file](#user-content-upload-example--dropbox-files-upload-api)
19
+ * [Multipart/form upload](#user-content-multipartform-data-example--post-form-data-with-file-and-data)
20
+* [API](#user-content-api)
21
+
22
+## Installation
23
+
24
+Install package from npm
25
+
26
+```sh
27
+npm install --save react-native-fetch-blob
28
+```
29
+
30
+Link package using [rnpm](https://github.com/rnpm/rnpm)
31
+
32
+```sh
33
+rnpm link
34
+```
35
+
36
+## Usage
37
+
38
+```js
39
+import RNFetchBlob from 'react-native-fetch-blob'
40
+```
41
+#### Download example : Fetch files that needs authorization token
42
+
43
+```js
44
+
45
+// send http request in a new thread (using native code)
46
+RNFetchBlob.fetch('GET', 'http://www.example.com/images/img1.png', {
47
+    Authorization : 'Bearer access-token...',
48
+    // more headers  ..
49
+  })
50
+  // when response status code is 200
51
+  .then((res) => {
52
+    // the conversion is done in native code
53
+    let base64Str = res.base64()
54
+    // the following conversions are done in js, it's SYNC
55
+    let text = res.text()
56
+    let json = res.json()
57
+
58
+  })
59
+  // Status code is not 200
60
+  .catch((errorMessage, statusCode) => {
61
+    // error handling
62
+  })
63
+```
64
+
65
+####  Upload example : Dropbox [files-upload](https://www.dropbox.com/developers/documentation/http/documentation#files-upload) API
66
+
67
+`react-native-fetch-blob` will convert the base64 string in `body` to binary format using native API, this process will be  done in a new thread, so it's async.
68
+
69
+```js
70
+
71
+RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
72
+    Authorization : "Bearer access-token...",
73
+    'Dropbox-API-Arg': JSON.stringify({
74
+      path : '/img-from-react-native.png',
75
+      mode : 'add',
76
+      autorename : true,
77
+      mute : false
78
+    }),
79
+    'Content-Type' : 'application/octet-stream',
80
+  }, base64ImageString)
81
+  .then((res) => {
82
+    console.log(res.text())
83
+  })
84
+  .catch((err) => {
85
+    // error handling ..
86
+  })
87
+```
88
+
89
+#### Multipart/form-data example : Post form data with file and data
90
+
91
+In `version >= 0.3.0` you can also post files with form data,  just put an array in `body`, with object elements with property `name`, `data`, and `filename`(optional).
92
+
93
+Elements have property `filename` will be transformed into binary format, otherwise it turns into utf8 string.
94
+
95
+```js
96
+
97
+  RNFetchBlob.fetch('POST', 'http://www.example.com/upload-form', {
98
+    Authorization : "Bearer access-token",
99
+    otherHeader : "foo",
100
+    'Content-Type' : 'multipart/form-data',
101
+  }, [
102
+    // element with property `filename` will be transformed into `file` in form data
103
+    { name : 'avatar', filename : 'avatar.png', data: binaryDataInBase64},
104
+    // elements without property `filename` will be sent as plain text
105
+    { name : 'name', data : 'user'},
106
+    { name : 'info', data : JSON.stringify({
107
+      mail : 'example@example.com',
108
+      tel : '12345678'
109
+    })},
110
+  ]).then((resp) => {
111
+    // ...
112
+  }).catch((err) => {
113
+    // ...
114
+  })
115
+```
116
+
117
+## API
118
+
119
+#### `fetch(method, url, headers, body):Promise<FetchBlobResponse> `
120
+
121
+Send a HTTP request uses given headers and body, and return a Promise.
122
+
123
+#### method:`string` Required
124
+HTTP request method, can be one of `get`, `post`, `delete`, and `put`, case-insensitive.
125
+#### url:`string` Required
126
+HTTP request destination url.
127
+#### headers:`object` (Optional)
128
+Headers of HTTP request, value of headers should be `stringified`, if you're uploading binary files, content-type should be `application/octet-stream` or `multipart/form-data`(see examples above).
129
+#### body:`string | Array<Object>` (Optional)
130
+Body of the HTTP request, body can either be a BASE64 string, or an array contains object elements, each element have 2  required property `name`, and `data`, and 1 optional property `filename`, once `filename` is set, content in `data` property will be consider as BASE64 string that will be converted into byte array later.
131
+
132
+When body is a base64 string , this string will be converted into byte array in native code, and the request body will be sent as `application/octet-stream`.
133
+
134
+#### `base64`
135
+
136
+A helper object simply uses [base-64](https://github.com/mathiasbynens/base64) for decode and encode BASE64 data.
137
+
138
+```js
139
+RNFetchBlob.base64.encode(data)
140
+RNFetchBlob.base64.decode(data)
141
+```
142
+
143
+### FetchBlobResponse
144
+
145
+When `fetch` success, it resolve a `FetchBlobResponse` object as first argument. `FetchBlobResponse` object has the following methods (these method are synchronous, so you might take quite a performance impact if the file is big)
146
+
147
+#### base64():string
148
+  returns base64 string of response data (done in native context)
149
+#### json():object
150
+  returns json parsed object (done in js context)
151
+#### text():string
152
+  returns decoded base64 string (done in js context)
153
+
154
+
155
+### TODO
156
+
157
+* Save file to storage
158
+* Custom MIME type in form data

+ 17
- 0
package.json 查看文件

@@ -0,0 +1,17 @@
1
+{
2
+  "name": "fetchblob",
3
+  "version": "0.4.0",
4
+  "private": true,
5
+  "scripts": {
6
+    "start": "node node_modules/react-native/local-cli/cli.js start",
7
+    "test": "sh test.sh"
8
+  },
9
+  "devDependencies": {
10
+    "body-parser": "^1.15.0",
11
+    "express": "^4.13.4",
12
+    "multer": "^1.1.0"
13
+  },
14
+  "dependencies": {
15
+    "react-native-fetch-blob": "file:src"
16
+  }
17
+}

+ 55
- 0
src/.gitignore 查看文件

@@ -0,0 +1,55 @@
1
+node_modules/
2
+
3
+# Xcode
4
+#
5
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
6
+
7
+## Build generated
8
+build/
9
+DerivedData/
10
+
11
+## Various settings
12
+*.pbxuser
13
+!default.pbxuser
14
+*.mode1v3
15
+!default.mode1v3
16
+*.mode2v3
17
+!default.mode2v3
18
+*.perspectivev3
19
+!default.perspectivev3
20
+xcuserdata/
21
+
22
+## Other
23
+*.moved-aside
24
+*.xcuserstate
25
+
26
+## Obj-C/Swift specific
27
+*.hmap
28
+*.ipa
29
+*.dSYM.zip
30
+*.dSYM
31
+
32
+# CocoaPods
33
+#
34
+# We recommend against adding the Pods directory to your .gitignore. However
35
+# you should judge for yourself, the pros and cons are mentioned at:
36
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
37
+#
38
+# Pods/
39
+
40
+# Carthage
41
+#
42
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
43
+# Carthage/Checkouts
44
+
45
+Carthage/Build
46
+
47
+# fastlane
48
+#
49
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
50
+# screenshots whenever they are needed.
51
+# For more information about the recommended setup visit:
52
+# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
53
+
54
+fastlane/report.xml
55
+fastlane/screenshots

+ 158
- 0
src/README.md 查看文件

@@ -0,0 +1,158 @@
1
+# react-native-fetch-blob [![npm version](https://badge.fury.io/js/react-native-fetch-blob.svg)](https://badge.fury.io/js/react-native-fetch-blob)
2
+
3
+A react-native module for fetch file/image with custom headers, supports blob response data.
4
+
5
+If you're dealing with image or file server that requires an `Authorization` token in the header, or you're having problem with `fetch` API when receiving blob data, you might try this module (this is also the reason why I made this).
6
+
7
+See [[fetch] Does fetch with blob() marshal data across the bridge?](https://github.com/facebook/react-native/issues/854).
8
+
9
+This module enables you upload/download binary data in js, see [Examples](#user-content-usage) bellow.
10
+
11
+The source code is very simple, just an implementation of native HTTP request, supports both Android (uses awesome native library  [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client])) and IOS.
12
+
13
+## Usage
14
+
15
+* [Installation](#user-content-installation)
16
+* [Examples](#user-content-usage)
17
+ * [Download file](#user-content-download-example--fetch-files-that-needs-authorization-token)
18
+ * [Upload file](#user-content-upload-example--dropbox-files-upload-api)
19
+ * [Multipart/form upload](#user-content-multipartform-data-example--post-form-data-with-file-and-data)
20
+* [API](#user-content-api)
21
+
22
+## Installation
23
+
24
+Install package from npm
25
+
26
+```sh
27
+npm install --save react-native-fetch-blob
28
+```
29
+
30
+Link package using [rnpm](https://github.com/rnpm/rnpm)
31
+
32
+```sh
33
+rnpm link
34
+```
35
+
36
+## Usage
37
+
38
+```js
39
+import RNFetchBlob from 'react-native-fetch-blob'
40
+```
41
+#### Download example : Fetch files that needs authorization token
42
+
43
+```js
44
+
45
+// send http request in a new thread (using native code)
46
+RNFetchBlob.fetch('GET', 'http://www.example.com/images/img1.png', {
47
+    Authorization : 'Bearer access-token...',
48
+    // more headers  ..
49
+  })
50
+  // when response status code is 200
51
+  .then((res) => {
52
+    // the conversion is done in native code
53
+    let base64Str = res.base64()
54
+    // the following conversions are done in js, it's SYNC
55
+    let text = res.text()
56
+    let json = res.json()
57
+
58
+  })
59
+  // Status code is not 200
60
+  .catch((errorMessage, statusCode) => {
61
+    // error handling
62
+  })
63
+```
64
+
65
+####  Upload example : Dropbox [files-upload](https://www.dropbox.com/developers/documentation/http/documentation#files-upload) API
66
+
67
+`react-native-fetch-blob` will convert the base64 string in `body` to binary format using native API, this process will be  done in a new thread, so it's async.
68
+
69
+```js
70
+
71
+RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
72
+    Authorization : "Bearer access-token...",
73
+    'Dropbox-API-Arg': JSON.stringify({
74
+      path : '/img-from-react-native.png',
75
+      mode : 'add',
76
+      autorename : true,
77
+      mute : false
78
+    }),
79
+    'Content-Type' : 'application/octet-stream',
80
+  }, base64ImageString)
81
+  .then((res) => {
82
+    console.log(res.text())
83
+  })
84
+  .catch((err) => {
85
+    // error handling ..
86
+  })
87
+```
88
+
89
+#### Multipart/form-data example : Post form data with file and data
90
+
91
+In `version >= 0.3.0` you can also post files with form data,  just put an array in `body`, with object elements with property `name`, `data`, and `filename`(optional).
92
+
93
+Elements have property `filename` will be transformed into binary format, otherwise it turns into utf8 string.
94
+
95
+```js
96
+
97
+  RNFetchBlob.fetch('POST', 'http://www.example.com/upload-form', {
98
+    Authorization : "Bearer access-token",
99
+    otherHeader : "foo",
100
+    'Content-Type' : 'multipart/form-data',
101
+  }, [
102
+    // element with property `filename` will be transformed into `file` in form data
103
+    { name : 'avatar', filename : 'avatar.png', data: binaryDataInBase64},
104
+    // elements without property `filename` will be sent as plain text
105
+    { name : 'name', data : 'user'},
106
+    { name : 'info', data : JSON.stringify({
107
+      mail : 'example@example.com',
108
+      tel : '12345678'
109
+    })},
110
+  ]).then((resp) => {
111
+    // ...
112
+  }).catch((err) => {
113
+    // ...
114
+  })
115
+```
116
+
117
+## API
118
+
119
+#### `fetch(method, url, headers, body):Promise<FetchBlobResponse> `
120
+
121
+Send a HTTP request uses given headers and body, and return a Promise.
122
+
123
+#### method:`string` Required
124
+HTTP request method, can be one of `get`, `post`, `delete`, and `put`, case-insensitive.
125
+#### url:`string` Required
126
+HTTP request destination url.
127
+#### headers:`object` (Optional)
128
+Headers of HTTP request, value of headers should be `stringified`, if you're uploading binary files, content-type should be `application/octet-stream` or `multipart/form-data`(see examples above).
129
+#### body:`string | Array<Object>` (Optional)
130
+Body of the HTTP request, body can either be a BASE64 string, or an array contains object elements, each element have 2  required property `name`, and `data`, and 1 optional property `filename`, once `filename` is set, content in `data` property will be consider as BASE64 string that will be converted into byte array later.
131
+
132
+When body is a base64 string , this string will be converted into byte array in native code, and the request body will be sent as `application/octet-stream`.
133
+
134
+#### `base64`
135
+
136
+A helper object simply uses [base-64](https://github.com/mathiasbynens/base64) for decode and encode BASE64 data.
137
+
138
+```js
139
+RNFetchBlob.base64.encode(data)
140
+RNFetchBlob.base64.decode(data)
141
+```
142
+
143
+### FetchBlobResponse
144
+
145
+When `fetch` success, it resolve a `FetchBlobResponse` object as first argument. `FetchBlobResponse` object has the following methods (these method are synchronous, so you might take quite a performance impact if the file is big)
146
+
147
+#### base64():string
148
+  returns base64 string of response data (done in native context)
149
+#### json():object
150
+  returns json parsed object (done in js context)
151
+#### text():string
152
+  returns decoded base64 string (done in js context)
153
+
154
+
155
+### TODO
156
+
157
+* Save file to storage
158
+* Custom MIME type in form data

+ 55
- 0
src/android/.gitignore 查看文件

@@ -0,0 +1,55 @@
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

+ 38
- 0
src/android/build.gradle 查看文件

@@ -0,0 +1,38 @@
1
+apply plugin: 'com.android.library'
2
+
3
+repositories {
4
+    mavenCentral()
5
+}
6
+
7
+buildscript {
8
+    repositories {
9
+        mavenCentral()
10
+    }
11
+    dependencies {
12
+        classpath 'com.android.tools.build:gradle:2.0.0'
13
+    }
14
+}
15
+
16
+android {
17
+    compileSdkVersion 23
18
+    buildToolsVersion "23.0.3"
19
+    defaultConfig {
20
+        minSdkVersion 16
21
+        targetSdkVersion 23
22
+        versionCode 1
23
+        versionName "1.0"
24
+    }
25
+    buildTypes {
26
+        release {
27
+            minifyEnabled false
28
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
29
+        }
30
+    }
31
+    productFlavors {
32
+    }
33
+}
34
+
35
+dependencies {
36
+    compile 'com.facebook.react:react-native:+'
37
+    compile 'com.loopj.android:android-async-http:1.4.9'
38
+}

二進制
src/android/gradle/wrapper/gradle-wrapper.jar 查看文件


+ 6
- 0
src/android/gradle/wrapper/gradle-wrapper.properties 查看文件

@@ -0,0 +1,6 @@
1
+#Wed May 18 12:33:41 CST 2016
2
+distributionBase=GRADLE_USER_HOME
3
+distributionPath=wrapper/dists
4
+zipStoreBase=GRADLE_USER_HOME
5
+zipStorePath=wrapper/dists
6
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

+ 160
- 0
src/android/gradlew 查看文件

@@ -0,0 +1,160 @@
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 "$@"

+ 90
- 0
src/android/gradlew.bat 查看文件

@@ -0,0 +1,90 @@
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

+ 17
- 0
src/android/proguard-rules.pro 查看文件

@@ -0,0 +1,17 @@
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
+#}

+ 11
- 0
src/android/src/main/AndroidManifest.xml 查看文件

@@ -0,0 +1,11 @@
1
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+    package="com.RNFetchBlob">
3
+
4
+    <application
5
+        android:allowBackup="true"
6
+        android:label="@string/app_name"
7
+        android:supportsRtl="true">
8
+
9
+    </application>
10
+
11
+</manifest>

+ 183
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java 查看文件

@@ -0,0 +1,183 @@
1
+package com.RNFetchBlob;
2
+
3
+import android.net.Uri;
4
+import android.text.style.AlignmentSpan;
5
+
6
+import com.facebook.react.bridge.Callback;
7
+import com.facebook.react.bridge.ReactApplicationContext;
8
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
9
+import com.facebook.react.bridge.ReactMethod;
10
+import com.facebook.react.bridge.ReadableArray;
11
+import com.facebook.react.bridge.ReadableMap;
12
+import com.facebook.react.bridge.ReadableMapKeySetIterator;
13
+import com.loopj.android.http.AsyncHttpClient;
14
+import com.loopj.android.http.AsyncHttpResponseHandler;
15
+import com.loopj.android.http.Base64;
16
+import com.loopj.android.http.BinaryHttpResponseHandler;
17
+import com.loopj.android.http.RequestParams;
18
+
19
+import java.io.ByteArrayOutputStream;
20
+import java.nio.charset.Charset;
21
+import java.nio.charset.StandardCharsets;
22
+
23
+import cz.msebera.android.httpclient.Header;
24
+import cz.msebera.android.httpclient.entity.BufferedHttpEntity;
25
+import cz.msebera.android.httpclient.entity.ByteArrayEntity;
26
+import cz.msebera.android.httpclient.entity.ContentType;
27
+import cz.msebera.android.httpclient.entity.StringEntity;
28
+import cz.msebera.android.httpclient.message.BasicHeader;
29
+import cz.msebera.android.httpclient.protocol.HTTP;
30
+
31
+public class RNFetchBlob extends ReactContextBaseJavaModule {
32
+
33
+
34
+    public RNFetchBlob(ReactApplicationContext reactContext) {
35
+        super(reactContext);
36
+    }
37
+
38
+    @Override
39
+    public String getName() {
40
+        return "RNFetchBlob";
41
+    }
42
+
43
+    @ReactMethod
44
+    public void fetchBlob(String method, String url, ReadableMap headers, String body, final Callback callback) {
45
+
46
+        try {
47
+            Uri uri = Uri.parse(url);
48
+            AsyncHttpClient req = new AsyncHttpClient();
49
+
50
+            // set params
51
+            RequestParams params = new RequestParams();
52
+            ByteArrayEntity entity = null;
53
+
54
+            // set params
55
+            for (String paramName : uri.getQueryParameterNames()) {
56
+                params.put(paramName, uri.getQueryParameter(paramName));
57
+            }
58
+
59
+            // set headers
60
+            ReadableMapKeySetIterator it = headers.keySetIterator();
61
+            while (it.hasNextKey()) {
62
+                String key = it.nextKey();
63
+                req.addHeader(key, headers.getString(key));
64
+            }
65
+
66
+            // set body for POST and PUT
67
+            if(body != null && method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put")) {
68
+                byte [] blob = Base64.decode(body, 0);
69
+                entity = new ByteArrayEntity(blob);
70
+                entity.setContentType(headers.getString("Content-Type"));
71
+            }
72
+
73
+            // create handler
74
+            AsyncHttpResponseHandler handler = new RNFetchBlobHandler(callback);
75
+
76
+            // send request
77
+            switch(method.toLowerCase()) {
78
+                case "get" :
79
+                    req.get(url, params, handler);
80
+                    break;
81
+                case "post" :
82
+                    req.post(this.getReactApplicationContext(), url, entity, "octet-stream", handler);
83
+                    break;
84
+                case "put" :
85
+                    req.put(this.getReactApplicationContext(), url, entity, "octet-stream",handler);
86
+                    break;
87
+                case "delete" :
88
+                    req.delete(url, params, handler);
89
+                    break;
90
+            }
91
+        } catch(Exception error) {
92
+            callback.invoke( "RNFetchBlob serialize request data failed: " + error.getMessage() + error.getCause());
93
+        }
94
+
95
+    }
96
+
97
+    @ReactMethod
98
+    public void fetchBlobForm(String method, String url, ReadableMap headers, ReadableArray body, final Callback callback) {
99
+
100
+        try {
101
+            Uri uri = Uri.parse(url);
102
+            AsyncHttpClient req = new AsyncHttpClient();
103
+
104
+            // set params
105
+            RequestParams params = new RequestParams();
106
+            ByteArrayEntity entity = null;
107
+
108
+            // set params
109
+            for (String paramName : uri.getQueryParameterNames()) {
110
+                params.put(paramName, uri.getQueryParameter(paramName));
111
+            }
112
+
113
+            // set headers
114
+            if(headers != null) {
115
+                ReadableMapKeySetIterator it = headers.keySetIterator();
116
+                while (it.hasNextKey()) {
117
+                    String key = it.nextKey();
118
+                    req.addHeader(key, headers.getString(key));
119
+                }
120
+            }
121
+
122
+            // set body for POST and PUT
123
+            if(body != null && method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put")) {
124
+
125
+                Long tsLong = System.currentTimeMillis()/1000;
126
+                String ts = tsLong.toString();
127
+                ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
128
+                String boundary = "RNFetchBlob".concat(ts);
129
+                for(int i =0; i<body.size() ; i++) {
130
+                    ReadableMap map = body.getMap(i);
131
+                    String name = map.getString("name");
132
+                    // file field
133
+                    if(map.hasKey("filename")) {
134
+                        String filename = map.getString("filename");
135
+                        byte [] file = Base64.decode(map.getString("data"), 0);
136
+                        outputStream.write(String.format("--%s\r\n", boundary).getBytes("UTF-8"));
137
+                        outputStream.write(("Content-Disposition: form-data; name=\""+name+"\"; filename=\""+filename+"\"\r\n").getBytes("UTF-8"));
138
+                        outputStream.write(String.format("Content-Type: application/octet-stream\r\n\r\n").getBytes());
139
+                        outputStream.write(file);
140
+                        outputStream.write("\r\n".getBytes());
141
+                    }
142
+                    // data field
143
+                    else {
144
+                        String data = map.getString("data");
145
+                        outputStream.write(String.format("--%s\r\n", boundary).getBytes("UTF-8"));
146
+                        outputStream.write(String.format("Content-Disposition: form-data; name=\""+name+"\"; \r\n").getBytes("UTF-8"));
147
+                        outputStream.write(String.format("Content-Type: text/plain\r\n\r\n").getBytes());
148
+                        outputStream.write((data+"\r\n").getBytes());
149
+                    }
150
+                }
151
+                outputStream.write(String.format("--%s--\r\n", boundary).getBytes());
152
+                byte bodyBytes[] = outputStream.toByteArray( );
153
+                entity = new ByteArrayEntity(bodyBytes);
154
+                entity.setContentType(headers.getString("Content-Type") + "; charset=utf8; boundary=" + boundary);
155
+                req.addHeader("Content-Type", headers.getString("Content-Type") + "; charset=utf8; boundary=" + boundary);
156
+            }
157
+
158
+            // create handler
159
+            AsyncHttpResponseHandler handler = new RNFetchBlobHandler(callback);
160
+
161
+            // send request
162
+            switch(method.toLowerCase()) {
163
+                case "get" :
164
+                    req.get(url, params, handler);
165
+                    break;
166
+                case "post" :
167
+                    req.post(this.getReactApplicationContext(), url, entity, "multipart/form-data; charset=utf8", handler);
168
+                    break;
169
+                case "put" :
170
+                    req.put(this.getReactApplicationContext(), url, entity, "multipart/form-data",handler);
171
+                    break;
172
+                case "delete" :
173
+                    req.delete(url, params, handler);
174
+                    break;
175
+            }
176
+        } catch(Exception error) {
177
+            callback.invoke( "RNFetchBlob serialize request data failed: " + error.getMessage() + error.getCause());
178
+        }
179
+
180
+    }
181
+
182
+}
183
+

+ 32
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobHandler.java 查看文件

@@ -0,0 +1,32 @@
1
+package com.RNFetchBlob;
2
+
3
+import com.facebook.react.bridge.Callback;
4
+import com.loopj.android.http.AsyncHttpResponseHandler;
5
+import com.loopj.android.http.Base64;
6
+
7
+import cz.msebera.android.httpclient.Header;
8
+
9
+public class RNFetchBlobHandler extends AsyncHttpResponseHandler {
10
+
11
+    Callback onResponse;
12
+
13
+    RNFetchBlobHandler(Callback onResponse) {
14
+        this.onResponse = onResponse;
15
+    }
16
+
17
+    @Override
18
+    public void onSuccess(int statusCode, Header[] headers, byte[] binaryData) {
19
+        String value = Base64.encodeToString(binaryData, Base64.NO_WRAP);
20
+        this.onResponse.invoke(null, value);
21
+    }
22
+
23
+    @Override
24
+    public void onProgress(long bytesWritten, long totalSize) {
25
+        super.onProgress(bytesWritten, totalSize);
26
+    }
27
+
28
+    @Override
29
+    public void onFailure(final int statusCode, final Header[] headers, byte[] binaryData, final Throwable error) {
30
+        this.onResponse.invoke(statusCode, error.getMessage()+ ", "+ error.getCause());
31
+    }
32
+}

+ 43
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobPackage.java 查看文件

@@ -0,0 +1,43 @@
1
+package com.RNFetchBlob;
2
+
3
+import com.facebook.react.ReactPackage;
4
+import com.facebook.react.bridge.Callback;
5
+import com.facebook.react.bridge.JavaScriptModule;
6
+import com.facebook.react.bridge.NativeModule;
7
+import com.facebook.react.bridge.ReactApplicationContext;
8
+import com.facebook.react.bridge.ReactContext;
9
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
10
+import com.facebook.react.bridge.ReactMethod;
11
+import com.facebook.react.bridge.ReadableArray;
12
+import com.facebook.react.bridge.ReadableMap;
13
+import com.facebook.react.bridge.ReadableMapKeySetIterator;
14
+import com.facebook.react.bridge.ReadableType;
15
+import com.facebook.react.uimanager.ViewManager;
16
+
17
+import java.util.ArrayList;
18
+import java.util.Collections;
19
+import java.util.List;
20
+
21
+/**
22
+ * Created by xeiyan on 2016/4/29.
23
+ */
24
+public class RNFetchBlobPackage implements ReactPackage {
25
+
26
+    @Override
27
+    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
28
+        List<NativeModule> modules = new ArrayList<>();
29
+        modules.add(new RNFetchBlob(reactContext));
30
+        return modules;
31
+    }
32
+
33
+    @Override
34
+    public List<Class<? extends JavaScriptModule>> createJSModules() {
35
+        return Collections.emptyList();
36
+    }
37
+
38
+    @Override
39
+    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
40
+        return Collections.emptyList();
41
+    }
42
+
43
+}

+ 3
- 0
src/android/src/main/res/values/strings.xml 查看文件

@@ -0,0 +1,3 @@
1
+<resources>
2
+    <string name="app_name">react-native-fetch-blob</string>
3
+</resources>

+ 86
- 0
src/index.js 查看文件

@@ -0,0 +1,86 @@
1
+/**
2
+ * @author wkh237
3
+ * @version 0.3.3
4
+ */
5
+
6
+import { NativeModules } from 'react-native'
7
+import base64 from 'base-64'
8
+
9
+const RNFetchBlob = NativeModules.RNFetchBlob
10
+
11
+// Show warning if native module not detected
12
+if(RNFetchBlob === void 0) {
13
+  console.warn(
14
+    'react-native-fetch-blob could not find native module.',
15
+    'please make sure you have linked native modules using `rnpm link`,',
16
+    'and restart RN packager or manually compile IOS/Android project.'
17
+  )
18
+}
19
+
20
+// Promise wrapper function
21
+const fetch = (...args) => {
22
+
23
+  let promise = new Promise((resolve, reject) => {
24
+
25
+    let [method, url, headers, body] = [...args]
26
+    let nativeMethodName = Array.isArray(body) ? 'fetchBlobForm' : 'fetchBlob'
27
+
28
+    RNFetchBlob[nativeMethodName](method, url, headers, body, (err, ...data) => {
29
+      if(err)
30
+        reject(new Error(err, ...data))
31
+      else
32
+        resolve(new FetchBlobResponse(...data))
33
+    })
34
+
35
+  })
36
+
37
+  return promise
38
+
39
+}
40
+
41
+/**
42
+ * RNFetchBlob response object class.
43
+ */
44
+class FetchBlobResponse {
45
+
46
+  constructor(data) {
47
+    this.data = data
48
+    /**
49
+     * Convert result to javascript Blob object.
50
+     * @param  {string} contentType MIME type of the blob object.
51
+     * @param  {number} sliceSize   Slice size.
52
+     * @return {blob}             Return Blob object.
53
+     */
54
+    this.blob = (contentType, sliceSize) => {
55
+      console.warn('FetchBlobResponse.blob() is deprecated and has no funtionality.')
56
+      return null
57
+    }
58
+    /**
59
+     * Convert result to text.
60
+     * @return {string} Decoded base64 string.
61
+     */
62
+    this.text = () => {
63
+      return base64.decode(this.data)
64
+    }
65
+    /**
66
+     * Convert result to JSON object.
67
+     * @return {object} Parsed javascript object.
68
+     */
69
+    this.json = () => {
70
+      return JSON.parse(base64.decode(this.data))
71
+    }
72
+    /**
73
+     * Return BASE64 string directly.
74
+     * @return {string} BASE64 string of response body.
75
+     */
76
+    this.base64 = () => {
77
+      return this.data
78
+    }
79
+
80
+  }
81
+
82
+}
83
+
84
+export default {
85
+  fetch, FetchBlobResponse, base64
86
+}

+ 278
- 0
src/ios/RNFetchBlob.xcodeproj/project.pbxproj 查看文件

@@ -0,0 +1,278 @@
1
+// !$*UTF8*$!
2
+{
3
+	archiveVersion = 1;
4
+	classes = {
5
+	};
6
+	objectVersion = 46;
7
+	objects = {
8
+/* Begin PBXBuildFile section */
9
+		A15C30141CD25C330074CB35 /* RNFetchBlob.m in Sources */ = {isa = PBXBuildFile; fileRef = A15C30131CD25C330074CB35 /* RNFetchBlob.m */; };
10
+		A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */ = {isa = PBXBuildFile; fileRef = A15C30111CD25C330074CB35 /* RNFetchBlob.h */; };
11
+/* End PBXBuildFile section */
12
+
13
+/* Begin PBXCopyFilesBuildPhase section */
14
+		A15C300C1CD25C330074CB35 /* CopyFiles */ = {
15
+			isa = PBXCopyFilesBuildPhase;
16
+			buildActionMask = 2147483647;
17
+			dstPath = "include/$(PRODUCT_NAME)";
18
+			dstSubfolderSpec = 16;
19
+			files = (
20
+			);
21
+			runOnlyForDeploymentPostprocessing = 0;
22
+		};
23
+/* End PBXCopyFilesBuildPhase section */
24
+
25
+/* Begin PBXFileReference section */
26
+		A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNFetchBlob.a; sourceTree = BUILT_PRODUCTS_DIR; };
27
+		A15C30111CD25C330074CB35 /* RNFetchBlob.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNFetchBlob.h; path = RNFetchBlob/RNFetchBlob.h; sourceTree = "<group>"; };
28
+		A15C30131CD25C330074CB35 /* RNFetchBlob.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RNFetchBlob.m; path = RNFetchBlob/RNFetchBlob.m; sourceTree = "<group>"; };
29
+		ADC1D945EE804D3DA47CF622 /* RNFetchBlob.xcodeproj */ = {isa = PBXFileReference; name = "RNFetchBlob.xcodeproj"; path = "../../node_modules/react-native-fetch-blob/ios/RNFetchBlob.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
30
+/* End PBXFileReference section */
31
+
32
+/* Begin PBXFrameworksBuildPhase section */
33
+		A15C300B1CD25C330074CB35 /* Frameworks */ = {
34
+			isa = PBXFrameworksBuildPhase;
35
+			buildActionMask = 2147483647;
36
+			files = (
37
+			);
38
+			runOnlyForDeploymentPostprocessing = 0;
39
+		};
40
+/* End PBXFrameworksBuildPhase section */
41
+
42
+/* Begin PBXGroup section */
43
+		A15C30051CD25C330074CB35 = {
44
+			isa = PBXGroup;
45
+			children = (
46
+				A15C30111CD25C330074CB35 /* RNFetchBlob.h */,
47
+				A15C30131CD25C330074CB35 /* RNFetchBlob.m */,
48
+				A15C300F1CD25C330074CB35 /* Products */,
49
+				8BD9ABDFAF76406291A798F2 /* Libraries */,
50
+			);
51
+			sourceTree = "<group>";
52
+		};
53
+		A15C300F1CD25C330074CB35 /* Products */ = {
54
+			isa = PBXGroup;
55
+			children = (
56
+				A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */,
57
+			);
58
+			name = Products;
59
+			sourceTree = "<group>";
60
+		};
61
+		8BD9ABDFAF76406291A798F2 /* Libraries */ = {
62
+			isa = PBXGroup;
63
+			children = (
64
+				ADC1D945EE804D3DA47CF622 /* RNFetchBlob.xcodeproj */,
65
+			);
66
+			name = Libraries;
67
+			path = "";
68
+			sourceTree = "<group>";
69
+		};
70
+/* End PBXGroup section */
71
+
72
+/* Begin PBXNativeTarget section */
73
+		A15C300D1CD25C330074CB35 /* RNFetchBlob */ = {
74
+			isa = PBXNativeTarget;
75
+			buildConfigurationList = A15C30171CD25C330074CB35 /* Build configuration list for PBXNativeTarget "RNFetchBlob" */;
76
+			buildPhases = (
77
+				A15C300A1CD25C330074CB35 /* Sources */,
78
+				A15C300B1CD25C330074CB35 /* Frameworks */,
79
+				A15C300C1CD25C330074CB35 /* CopyFiles */,
80
+			);
81
+			buildRules = (
82
+			);
83
+			dependencies = (
84
+			);
85
+			name = RNFetchBlob;
86
+			productName = RNFetchBlob;
87
+			productReference = A15C300E1CD25C330074CB35 /* libRNFetchBlob.a */;
88
+			productType = "com.apple.product-type.library.static";
89
+		};
90
+/* End PBXNativeTarget section */
91
+
92
+/* Begin PBXProject section */
93
+		A15C30061CD25C330074CB35 /* Project object */ = {
94
+			isa = PBXProject;
95
+			attributes = {
96
+				LastUpgradeCheck = 730;
97
+				ORGANIZATIONNAME = suzuri04x2;
98
+				TargetAttributes = {
99
+					A15C300D1CD25C330074CB35 = {
100
+						CreatedOnToolsVersion = 7.3;
101
+					};
102
+				};
103
+			};
104
+			buildConfigurationList = A15C30091CD25C330074CB35 /* Build configuration list for PBXProject "RNFetchBlob" */;
105
+			compatibilityVersion = "Xcode 3.2";
106
+			developmentRegion = English;
107
+			hasScannedForEncodings = 0;
108
+			knownRegions = (
109
+				en,
110
+			);
111
+			mainGroup = A15C30051CD25C330074CB35;
112
+			productRefGroup = A15C300F1CD25C330074CB35 /* Products */;
113
+			projectDirPath = "";
114
+			projectRoot = "";
115
+			targets = (
116
+				A15C300D1CD25C330074CB35 /* RNFetchBlob */,
117
+			);
118
+		};
119
+/* End PBXProject section */
120
+
121
+/* Begin PBXSourcesBuildPhase section */
122
+		A15C300A1CD25C330074CB35 /* Sources */ = {
123
+			isa = PBXSourcesBuildPhase;
124
+			buildActionMask = 2147483647;
125
+			files = (
126
+				A166D1AA1CE0647A00273590 /* RNFetchBlob.h in Sources */,
127
+				A15C30141CD25C330074CB35 /* RNFetchBlob.m in Sources */,
128
+			);
129
+			runOnlyForDeploymentPostprocessing = 0;
130
+		};
131
+/* End PBXSourcesBuildPhase section */
132
+
133
+/* Begin XCBuildConfiguration section */
134
+		A15C30151CD25C330074CB35 /* Debug */ = {
135
+			isa = XCBuildConfiguration;
136
+			buildSettings = {
137
+				ALWAYS_SEARCH_USER_PATHS = NO;
138
+				CLANG_ANALYZER_NONNULL = YES;
139
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
140
+				CLANG_CXX_LIBRARY = "libc++";
141
+				CLANG_ENABLE_MODULES = YES;
142
+				CLANG_ENABLE_OBJC_ARC = YES;
143
+				CLANG_WARN_BOOL_CONVERSION = YES;
144
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
145
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
146
+				CLANG_WARN_EMPTY_BODY = YES;
147
+				CLANG_WARN_ENUM_CONVERSION = YES;
148
+				CLANG_WARN_INT_CONVERSION = YES;
149
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
150
+				CLANG_WARN_UNREACHABLE_CODE = YES;
151
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
152
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
153
+				COPY_PHASE_STRIP = NO;
154
+				DEBUG_INFORMATION_FORMAT = dwarf;
155
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
156
+				ENABLE_TESTABILITY = YES;
157
+				GCC_C_LANGUAGE_STANDARD = gnu99;
158
+				GCC_DYNAMIC_NO_PIC = NO;
159
+				GCC_NO_COMMON_BLOCKS = YES;
160
+				GCC_OPTIMIZATION_LEVEL = 0;
161
+				GCC_PREPROCESSOR_DEFINITIONS = (
162
+					"DEBUG=1",
163
+					"$(inherited)",
164
+				);
165
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
166
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
167
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
168
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
169
+				GCC_WARN_UNUSED_FUNCTION = YES;
170
+				GCC_WARN_UNUSED_VARIABLE = YES;
171
+				IPHONEOS_DEPLOYMENT_TARGET = 9.3;
172
+				MTL_ENABLE_DEBUG_INFO = YES;
173
+				ONLY_ACTIVE_ARCH = YES;
174
+				SDKROOT = iphoneos;
175
+			};
176
+			name = Debug;
177
+		};
178
+		A15C30161CD25C330074CB35 /* Release */ = {
179
+			isa = XCBuildConfiguration;
180
+			buildSettings = {
181
+				ALWAYS_SEARCH_USER_PATHS = NO;
182
+				CLANG_ANALYZER_NONNULL = YES;
183
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
184
+				CLANG_CXX_LIBRARY = "libc++";
185
+				CLANG_ENABLE_MODULES = YES;
186
+				CLANG_ENABLE_OBJC_ARC = YES;
187
+				CLANG_WARN_BOOL_CONVERSION = YES;
188
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
189
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
190
+				CLANG_WARN_EMPTY_BODY = YES;
191
+				CLANG_WARN_ENUM_CONVERSION = YES;
192
+				CLANG_WARN_INT_CONVERSION = YES;
193
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
194
+				CLANG_WARN_UNREACHABLE_CODE = YES;
195
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
196
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
197
+				COPY_PHASE_STRIP = NO;
198
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
199
+				ENABLE_NS_ASSERTIONS = NO;
200
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
201
+				GCC_C_LANGUAGE_STANDARD = gnu99;
202
+				GCC_NO_COMMON_BLOCKS = YES;
203
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
204
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
205
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
206
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
207
+				GCC_WARN_UNUSED_FUNCTION = YES;
208
+				GCC_WARN_UNUSED_VARIABLE = YES;
209
+				IPHONEOS_DEPLOYMENT_TARGET = 9.3;
210
+				MTL_ENABLE_DEBUG_INFO = NO;
211
+				SDKROOT = iphoneos;
212
+				VALIDATE_PRODUCT = YES;
213
+			};
214
+			name = Release;
215
+		};
216
+		A15C30181CD25C330074CB35 /* Debug */ = {
217
+			isa = XCBuildConfiguration;
218
+			buildSettings = {
219
+				ALWAYS_SEARCH_USER_PATHS = NO;
220
+				HEADER_SEARCH_PATHS = (
221
+					"$(inherited)",
222
+					"$(SRCROOT)/Libraries/**",
223
+					"$(SRCROOT)/../node_modules/react-native/React/**",
224
+					"$(SRCROOT)/../../React/**",
225
+					"$(SRCROOT)/../../react-native/React/**",
226
+					"$(SRCROOT)/../../node_modules/react-native/React/**",
227
+					"$(SRCROOT)/../../node_modules/react-native-fetch-blob/ios/RNFetchBlob",
228
+				);
229
+				OTHER_LDFLAGS = "-ObjC";
230
+				PRODUCT_NAME = "$(TARGET_NAME)";
231
+				SKIP_INSTALL = YES;
232
+			};
233
+			name = Debug;
234
+		};
235
+		A15C30191CD25C330074CB35 /* Release */ = {
236
+			isa = XCBuildConfiguration;
237
+			buildSettings = {
238
+				ALWAYS_SEARCH_USER_PATHS = NO;
239
+				HEADER_SEARCH_PATHS = (
240
+					"$(inherited)",
241
+					"$(SRCROOT)/Libraries/**",
242
+					"$(SRCROOT)/../node_modules/react-native/React/**",
243
+					"$(SRCROOT)/../../React/**",
244
+					"$(SRCROOT)/../../react-native/React/**",
245
+					"$(SRCROOT)/../../node_modules/react-native/React/**",
246
+					"$(SRCROOT)/../../node_modules/react-native-fetch-blob/ios/RNFetchBlob",
247
+				);
248
+				OTHER_LDFLAGS = "-ObjC";
249
+				PRODUCT_NAME = "$(TARGET_NAME)";
250
+				SKIP_INSTALL = YES;
251
+			};
252
+			name = Release;
253
+		};
254
+/* End XCBuildConfiguration section */
255
+
256
+/* Begin XCConfigurationList section */
257
+		A15C30091CD25C330074CB35 /* Build configuration list for PBXProject "RNFetchBlob" */ = {
258
+			isa = XCConfigurationList;
259
+			buildConfigurations = (
260
+				A15C30151CD25C330074CB35 /* Debug */,
261
+				A15C30161CD25C330074CB35 /* Release */,
262
+			);
263
+			defaultConfigurationIsVisible = 0;
264
+			defaultConfigurationName = Release;
265
+		};
266
+		A15C30171CD25C330074CB35 /* Build configuration list for PBXNativeTarget "RNFetchBlob" */ = {
267
+			isa = XCConfigurationList;
268
+			buildConfigurations = (
269
+				A15C30181CD25C330074CB35 /* Debug */,
270
+				A15C30191CD25C330074CB35 /* Release */,
271
+			);
272
+			defaultConfigurationIsVisible = 0;
273
+			defaultConfigurationName = Release;
274
+		};
275
+/* End XCConfigurationList section */
276
+	};
277
+	rootObject = A15C30061CD25C330074CB35 /* Project object */;
278
+}

+ 25
- 0
src/ios/RNFetchBlob/RNFetchBlob.h 查看文件

@@ -0,0 +1,25 @@
1
+//
2
+//  RNFetchBlob.h
3
+//
4
+//  Created by suzuri04x2 on 2016/4/28.
5
+//  Copyright © 2016年 Facebook. All rights reserved.
6
+//
7
+
8
+#ifndef RNFetchBlob_h
9
+#define RNFetchBlob_h
10
+
11
+#import "RCTBridgeModule.h"
12
+
13
+@interface RNFetchBlob : NSObject <RCTBridgeModule> 
14
+
15
+@end
16
+
17
+@interface FetchBlobUtils : NSObject
18
+
19
++ (void) onBlobResponse;
20
++ (NSMutableDictionary *) normalizeHeaders;
21
+
22
+@end
23
+
24
+
25
+#endif /* RNFetchBlob_h */

+ 168
- 0
src/ios/RNFetchBlob/RNFetchBlob.m 查看文件

@@ -0,0 +1,168 @@
1
+//
2
+//  RNFetchBlob.m
3
+//
4
+//  Created by suzuri04x2 on 2016/4/28.
5
+//  Copyright © 2016年 Facebook. All rights reserved.
6
+//
7
+
8
+#import "RNFetchBlob.h"
9
+#import "RCTConvert.h"
10
+#import "RCTLog.h"
11
+#import <Foundation/Foundation.h>
12
+
13
+
14
+////////////////////////////////////////
15
+//
16
+//  Util functions
17
+//
18
+////////////////////////////////////////
19
+
20
+@implementation FetchBlobUtils
21
+
22
+// callback class method to handle request
23
++ (void) onBlobResponse:(NSURLResponse * _Nullable)response withData:(NSData * _Nullable)data withError:(NSError * _Nullable)connectionError withCallback:(RCTResponseSenderBlock)callback{
24
+    
25
+    NSHTTPURLResponse* resp = (NSHTTPURLResponse *) response;
26
+    NSString* status = [NSString stringWithFormat:@"%d", resp.statusCode];
27
+    
28
+    if(connectionError)
29
+    {
30
+        callback(@[[connectionError localizedDescription], [NSNull null]]);
31
+    }
32
+    else if(![status isEqualToString:@"200"]) {
33
+        callback(@[status, [NSNull null]]);
34
+    }
35
+    else {
36
+        callback(@[[NSNull null], [data base64EncodedStringWithOptions:0]]);
37
+    }
38
+    
39
+}
40
+
41
+// removing case of headers
42
++ (NSMutableDictionary *) normalizeHeaders:(NSDictionary *)headers {
43
+    
44
+    NSMutableDictionary * mheaders = [[NSMutableDictionary alloc]init];
45
+    for(NSString * key in headers) {
46
+        [mheaders setValue:[headers valueForKey:key] forKey:[key lowercaseString]];
47
+    }
48
+    
49
+    return mheaders;
50
+}
51
+
52
+@end
53
+
54
+
55
+////////////////////////////////////////
56
+//
57
+//  Exported native methods
58
+//
59
+////////////////////////////////////////
60
+
61
+@implementation RNFetchBlob
62
+
63
+RCT_EXPORT_MODULE();
64
+
65
+// Fetch blob data request
66
+RCT_EXPORT_METHOD(fetchBlobForm:(NSString *)method url:(NSString *)url headers:(NSDictionary *)headers form:(NSArray *)form callback:(RCTResponseSenderBlock)callback)
67
+{
68
+    
69
+    // send request
70
+    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
71
+                                    initWithURL:[NSURL
72
+                                                 URLWithString: url]];
73
+    NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[ FetchBlobUtils normalizeHeaders:headers]];
74
+    
75
+    
76
+    NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
77
+    NSNumber * timeStampObj = [NSNumber numberWithDouble: timeStamp];
78
+    
79
+    // generate boundary
80
+    NSString * boundary = [NSString stringWithFormat:@"RNFetchBlob%d", timeStampObj];
81
+    
82
+    // if method is POST or PUT, convert data string format
83
+    if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) {
84
+        NSMutableData * postData = [[NSMutableData alloc] init];
85
+        
86
+        // combine multipart/form-data body
87
+        for(id field in form) {
88
+            NSString * name = [field valueForKey:@"name"];
89
+            NSString * content = [field valueForKey:@"data"];
90
+            // field is a text field
91
+            if([field valueForKey:@"filename"] == nil) {
92
+                [postData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
93
+                [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", name] dataUsingEncoding:NSUTF8StringEncoding]];
94
+                [postData appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
95
+                [postData appendData:[[NSString stringWithFormat:@"%@\r\n", content] dataUsingEncoding:NSUTF8StringEncoding]];
96
+            }
97
+            // field contains a file
98
+            else {
99
+                NSData* blobData = [[NSData alloc] initWithBase64EncodedString:content options:0];
100
+                NSString * filename = [field valueForKey:@"filename"];
101
+                [postData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
102
+                [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename] dataUsingEncoding:NSUTF8StringEncoding]];
103
+                [postData appendData:[[NSString stringWithFormat:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
104
+                [postData appendData:blobData];
105
+                [postData appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
106
+            }
107
+            
108
+        }
109
+        // close form data
110
+        [postData appendData: [[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
111
+        [request setHTTPBody:postData];
112
+        // set content-length
113
+        [mheaders setValue:[NSString stringWithFormat:@"%d",[postData length]] forKey:@"Content-Length"];
114
+        [mheaders setValue:[NSString stringWithFormat:@"100-continue",[postData length]] forKey:@"Expect"];
115
+        // appaned boundary to content-type
116
+        [mheaders setValue:[NSString stringWithFormat:@"multipart/form-data; charset=utf-8; boundary=%@", boundary] forKey:@"content-type"];
117
+        
118
+    }
119
+    
120
+    [request setHTTPMethod: method];
121
+    [request setAllHTTPHeaderFields:mheaders];
122
+    
123
+    // create thread for http request
124
+    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
125
+    [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
126
+        
127
+        [FetchBlobUtils onBlobResponse:response withData:data withError: connectionError withCallback: callback];
128
+        
129
+    }];
130
+    
131
+}
132
+
133
+// Fetch blob data request
134
+RCT_EXPORT_METHOD(fetchBlob:(NSString *)method url:(NSString *)url headers:(NSDictionary *)headers body:(NSString *)body callback:(RCTResponseSenderBlock)callback)
135
+{
136
+    // send request
137
+    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
138
+                                    initWithURL:[NSURL
139
+                                                 URLWithString: url]];
140
+    
141
+    NSMutableDictionary *mheaders = [[NSMutableDictionary alloc] initWithDictionary:[FetchBlobUtils normalizeHeaders:headers]];
142
+
143
+    // if method is POST or PUT, convert data string format
144
+    if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) {
145
+        
146
+        // generate octet-stream body
147
+        NSData* blobData = [[NSData alloc] initWithBase64EncodedString:body options:0];
148
+        NSMutableData* postBody = [[NSMutableData alloc] init];
149
+        [postBody appendData:[NSData dataWithData:blobData]];
150
+        [request setHTTPBody:postBody];
151
+        [mheaders setValue:@"application/octet-stream" forKey:@"content-type"];
152
+        
153
+    }
154
+    
155
+    [request setHTTPMethod: method];
156
+    [request setAllHTTPHeaderFields:mheaders];
157
+    
158
+    // create thread for http request
159
+    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
160
+    [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
161
+        
162
+        [FetchBlobUtils onBlobResponse:response withData:data withError: connectionError withCallback: callback];
163
+        
164
+    }];
165
+    
166
+}
167
+@end
168
+

+ 23
- 0
src/package.json 查看文件

@@ -0,0 +1,23 @@
1
+{
2
+  "name": "react-native-fetch-blob",
3
+  "version": "0.4.1",
4
+  "description": "A react-native plugin for fetch blob data via HTTP.",
5
+  "main": "index.js",
6
+  "scripts": {
7
+    "test": "echo \"Error: no test specified\" && exit 1"
8
+  },
9
+  "dependencies": {
10
+    "base-64": "0.1.0"
11
+  },
12
+  "keywords": [
13
+    "react-native",
14
+    "fetch",
15
+    "blob",
16
+    "image header"
17
+  ],
18
+  "repository": {
19
+    "url": "https://github.com/wkh237/react-native-fetch-blob.git"
20
+  },
21
+  "author": "wkh237",
22
+  "license": "MIT"
23
+}

+ 49
- 0
test.sh 查看文件

@@ -0,0 +1,49 @@
1
+TEST_APP_NAME='RNFetchBlobTest'
2
+TEST_APP_PATH="$(pwd)/${TEST_APP_NAME}"
3
+TARGET='android'
4
+CWD=$(pwd)
5
+
6
+if [ "$#" -eq 1 ]; then
7
+  if [ "$1" == 'android' ]; then
8
+    TARGET="$1"
9
+    echo 'start android test'
10
+  elif [ "$1" == 'ios' ]; then
11
+    TARGET="$1"
12
+    echo 'start ios test'
13
+  else
14
+    exit "unreconized target platform $1"
15
+  fi
16
+elif [ "$#" -gt 2 ] && [ "$1" == 'path' ]; then
17
+  TEST_APP_PATH="$2"
18
+  TARGET="$3"
19
+  echo "use exist test app path=$2 target=$3"
20
+else
21
+  exit "unreconized arguments."
22
+fi
23
+
24
+
25
+# Create new rn project
26
+if [ "$#" -eq 1 ]; then
27
+  echo 'create new app for test ..'
28
+  rm -rf "${TEST_APP_NAME}"
29
+  react-native init "${TEST_APP_NAME}"
30
+fi
31
+cp -R test/ "${TEST_APP_PATH}/"
32
+
33
+# install module
34
+cd "${TEST_APP_PATH}"
35
+npm install --save "${CWD}/src"
36
+rnpm link
37
+
38
+# start RN
39
+cd "${TEST_APP_PATH}"
40
+if [ "$#" == 4 ]; then
41
+  sed -i.bak "s/RNFetchBlobTest/$4/" ./index.test.js
42
+fi
43
+react-native "run-${TARGET}"
44
+
45
+# start test server
46
+cd "${CWD}/test/test-server"
47
+# kill existing server
48
+kill "$(lsof | grep :8123 | awk '{ printf $2 }')"
49
+node server

+ 38
- 0
test/in-app-test-runner/app-test.js 查看文件

@@ -0,0 +1,38 @@
1
+export default class RNAppTest {
2
+
3
+  constructor() {
4
+    this.tests = []
5
+    this.context = null
6
+  }
7
+
8
+  describe (desc, fn) {
9
+
10
+    this.tests.push({
11
+      status : 'running',
12
+      result : null,
13
+      asserts : [],
14
+      desc, fn,
15
+    })
16
+
17
+  }
18
+
19
+  run (context) {
20
+    this.context = context
21
+    let promise = Promise.resolve()
22
+    for(let i in this.tests) {
23
+      promise = promise.then(function(update, data) {
24
+        return this.fn(update, data)
25
+      }.bind(
26
+        this.tests[i],
27
+        this.update.bind(this, i)
28
+      ))
29
+    }
30
+    return promise
31
+  }
32
+
33
+  update(i, data) {
34
+    Object.assign(this.tests[i], data)
35
+    this.context.forceUpdate()
36
+  }
37
+
38
+}

+ 54
- 0
test/in-app-test-runner/assert.js 查看文件

@@ -0,0 +1,54 @@
1
+// @flow
2
+import React, {Component} from 'react';
3
+import {
4
+  AppRegistry,
5
+  StyleSheet,
6
+  Text,
7
+  View,
8
+  Platform,
9
+  ScrollView,
10
+  Image,
11
+} from 'react-native';
12
+
13
+export default class Assert extends Component {
14
+
15
+  props : {
16
+    expect : any,
17
+    actual : any
18
+  };
19
+
20
+  constructor(props) {
21
+    super(props)
22
+  }
23
+
24
+  render() {
25
+    return (
26
+      <View style={{
27
+        padding : 16,
28
+        borderLeftWidth : 5,
29
+        marginTop : 4,
30
+        backgroundColor : 'white',
31
+        borderColor : this.getAssertion() ? '#00a825' : '#ff0d0d'
32
+      }}>
33
+        <Text style={{
34
+          color : this.getAssertion() ? '#00a825' : '#ff0d0d',
35
+          margin : 8,
36
+          marginLeft : 0
37
+        }}>{ this.getAssertion() ? '✅' : '×' } Assertion description</Text>
38
+        <Text style={{flex : 1, flexDirection : 'row'}}>
39
+          <Text style={{ color : '#AAA'}}>expect </Text>
40
+          <Text style={{flex : 1}}>{this.props.expect}</Text>
41
+        </Text>
42
+        <Text style={{flex : 1, flexDirection : 'row'}}>
43
+          <Text style={{color : '#AAA'}}>actual  </Text>
44
+          <Text style={{flex : 1}}>{this.props.actual}</Text>
45
+        </Text>
46
+      </View>
47
+    )
48
+  }
49
+
50
+  getAssertion() {
51
+    return this.props.expect === this.props.actual
52
+  }
53
+
54
+}

+ 0
- 0
test/in-app-test-runner/detail.js 查看文件


+ 11
- 0
test/in-app-test-runner/index.js 查看文件

@@ -0,0 +1,11 @@
1
+import TestContext from './app-test'
2
+import Reporter from './reporter'
3
+import Assert from './assert'
4
+import Info from './info'
5
+
6
+export default {
7
+  TestContext,
8
+  Reporter,
9
+  Info,
10
+  Assert
11
+}

+ 33
- 0
test/in-app-test-runner/info.js 查看文件

@@ -0,0 +1,33 @@
1
+// @flow
2
+import React, {Component} from 'react';
3
+import {
4
+  AppRegistry,
5
+  StyleSheet,
6
+  Text,
7
+  View,
8
+} from 'react-native';
9
+
10
+export default class Info extends Component {
11
+
12
+  props : {
13
+    description : string,
14
+  }
15
+
16
+  render() {
17
+    return (
18
+      <View style={{
19
+        borderTopWidth :1 ,
20
+        alignItems : 'center',
21
+        borderTopColor : '#DDD'
22
+      }}>
23
+        <View style={{ alignSelf : 'stretch'}}>
24
+          <Text style={{color : '#777', alignSelf : 'stretch', textAlign : 'center', margin : 8}}>
25
+            {this.props.description}
26
+          </Text>
27
+        </View>
28
+        <View style={{alignSelf : 'stretch'}}>{this.props.children}</View>
29
+      </View>
30
+    )
31
+  }
32
+
33
+}

+ 90
- 0
test/in-app-test-runner/reporter.js 查看文件

@@ -0,0 +1,90 @@
1
+import React, {Component} from 'react';
2
+import {
3
+  AppRegistry,
4
+  StyleSheet,
5
+  Text,
6
+  View,
7
+  Platform,
8
+  ScrollView,
9
+  Image,
10
+} from 'react-native';
11
+
12
+import Assert from './assert.js'
13
+
14
+export default class Reporter extends Component {
15
+
16
+  render() {
17
+    return (
18
+      <ScrollView key="rn-test-scroller" style={styles.container}>
19
+        {this.renderTests()}
20
+      </ScrollView>)
21
+  }
22
+
23
+  renderTests() {
24
+    return this.props.context.tests.map((t, i) => {
25
+
26
+      let pass = true
27
+      let foundAssertions = false
28
+
29
+      Array.isArray(t.result) && t.result.forEach((r) => {
30
+        if(r.type.name === 'Assert') {
31
+          foundAssertions = true
32
+          pass = pass && (r.props.actual === r.props.expect)
33
+        }
34
+      })
35
+
36
+      t.status = foundAssertions ? (pass ? 'pass' : 'fail') : t.status
37
+
38
+      return (<View key={'rn-test-' + t.desc} style={{
39
+        borderBottomWidth : 1.5,
40
+        borderColor : '#DDD',
41
+      }}>
42
+        <View key={t.desc} style={{
43
+          alignItems : 'center',
44
+          flexDirection : 'row'
45
+        }}>
46
+          <Text style={[styles.badge, {flex : 1, borderWidth : 0}]}>{t.desc}</Text>
47
+          <Text style={[styles.badge, this.getBadge(t.status)]}>{t.status}</Text>
48
+        </View>
49
+        <View key={t.desc + '-result'} style={{backgroundColor : '#F4F4F4'}}>
50
+          {t.result}
51
+        </View>
52
+      </View>)
53
+    })
54
+  }
55
+
56
+  getBadge(status) {
57
+    if(status === 'running')
58
+      return styles.badgeRunning
59
+    else if(status === 'pass')
60
+      return styles.badgePass
61
+    else
62
+      return styles.badgeFail
63
+  }
64
+
65
+}
66
+
67
+const styles = StyleSheet.create({
68
+  container: {
69
+    flex: 1,
70
+    marginTop : 40,
71
+  },
72
+  badge : {
73
+    margin : 16,
74
+    padding : 4,
75
+    borderRadius : 4,
76
+    borderWidth : 2,
77
+  },
78
+  badgePass: {
79
+    borderColor : '#00a825',
80
+    color : '#00a825'
81
+  },
82
+  badgeRunning: {
83
+    borderColor : '#e3c423',
84
+    color : '#e3c423'
85
+  },
86
+  badgeFail: {
87
+    borderColor : '#ff0d0d',
88
+    color : '#ff0d0d'
89
+  }
90
+});

+ 1
- 0
test/index.android.js 查看文件

@@ -0,0 +1 @@
1
+require('./index.test.js')

+ 1
- 0
test/index.ios.js 查看文件

@@ -0,0 +1 @@
1
+require('./index.test.js')

+ 42
- 0
test/index.test.js 查看文件

@@ -0,0 +1,42 @@
1
+/**
2
+ * Sample React Native App
3
+ * https://github.com/facebook/react-native
4
+ * @flow
5
+ */
6
+import React, {Component} from 'react';
7
+import {
8
+  AppRegistry,
9
+  StyleSheet,
10
+  Text,
11
+  View,
12
+  Platform,
13
+  ScrollView,
14
+  Image,
15
+} from 'react-native';
16
+
17
+import RNTest from './in-app-test-runner'
18
+import TestContext from './tests'
19
+
20
+
21
+class fetchblob extends Component {
22
+
23
+  constructor(props) {
24
+    super(props)
25
+  }
26
+
27
+  componentDidMount() {
28
+    TestContext.run(this)
29
+  }
30
+
31
+  render() {
32
+
33
+    return (
34
+      <RNTest.Reporter key="test-container" context={TestContext}/>
35
+    )
36
+  }
37
+
38
+}
39
+
40
+
41
+
42
+AppRegistry.registerComponent('RNFetchBlobTest', () => fetchblob);

+ 1
- 0
test/test-server/.gitignore 查看文件

@@ -0,0 +1 @@
1
+uploads/

二進制
test/test-server/public/github.png 查看文件


+ 48
- 0
test/test-server/server.js 查看文件

@@ -0,0 +1,48 @@
1
+var express = require('express');
2
+var bodyParser = require('body-parser');
3
+var multer = require('multer');
4
+var upload = multer({dest : 'uploads/'});
5
+var app = express();
6
+var fs = require('fs');
7
+
8
+app.listen(8123, function(err){
9
+
10
+  if(!err)
11
+    console.log('test server running at port ',8123)
12
+
13
+})
14
+
15
+// app.use(bodyParser.raw())
16
+
17
+app.use(upload.any())
18
+
19
+app.use('/public', express.static('./public'))
20
+
21
+app.post('/upload', function(req, res){
22
+
23
+  console.log(req.headers)
24
+  console.log(req.body)
25
+  fs.writeFile('./uploads/file'+Date.now()+'.png', req.body,function(err){
26
+    if(!err)
27
+      res.status(200).send({ message : 'ok'})
28
+    else
29
+      res.status(500).send({ message : err})
30
+  })
31
+
32
+})
33
+
34
+app.post('/upload-form', function(req, res) {
35
+  console.log(req.headers)
36
+  console.log(req.body)
37
+  console.log(req.files)
38
+  if(Array.isArray(req.files)) {
39
+    req.files.forEach((f) => {
40
+      console.log(process.cwd() + f.path, '=>', process.cwd() + '/public/' + f.originalname)
41
+      fs.renameSync('./' + f.path, './public/'+ f.originalname)
42
+    })
43
+  }
44
+  res.status(200).send({
45
+    fields : req.body,
46
+    files : req.files
47
+  })
48
+})

+ 116
- 0
test/tests.js 查看文件

@@ -0,0 +1,116 @@
1
+import RNTest from './in-app-test-runner'
2
+import React from 'react'
3
+import RNFetchBlob from 'react-native-fetch-blob'
4
+
5
+import {
6
+  AppRegistry,
7
+  StyleSheet,
8
+  Text,
9
+  View,
10
+  Platform,
11
+  ScrollView,
12
+  Dimensions,
13
+  Image,
14
+} from 'react-native';
15
+
16
+const FILENAME = `${Platform.OS}-0.4.0-${Date.now()}.png`
17
+// paste your test config here
18
+const TEST_SERVER_URL = 'http://your-local-ip:8123'
19
+const DROPBOX_TOKEN = 'drop-box-api-token'
20
+
21
+const ctx = new RNTest.TestContext()
22
+const Assert = RNTest.Assert
23
+const Info = RNTest.Info
24
+
25
+let image = null
26
+
27
+ctx.describe('GET image from server', async function(report) {
28
+  let resp = await RNFetchBlob
29
+    .fetch('GET', `${TEST_SERVER_URL}/public/github.png`, {
30
+      Authorization : 'Bearer abde123eqweje'
31
+    })
32
+
33
+  image = resp.base64()
34
+  report({
35
+    status : 'pass',
36
+    result : [
37
+      <Info key="11" description="Response image">
38
+        <Image key="1"
39
+          style={{width:Dimensions.get('window').width*0.9, height : Dimensions.get('window').width*0.9,margin :16}}
40
+          source={{uri : `data:image/png;base64, ${image}`}}/>
41
+      </Info>
42
+    ]
43
+  })
44
+  return image
45
+
46
+})
47
+
48
+ctx.describe('Upload octet-stream image to Dropbox', async function(report) {
49
+
50
+  let resp = await RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
51
+    Authorization : `Bearer ${DROPBOX_TOKEN}`,
52
+    'Dropbox-API-Arg': '{\"path\": \"/rn-upload/'+FILENAME+'\",\"mode\": \"add\",\"autorename\": true,\"mute\": false}',
53
+    'Content-Type' : 'application/octet-stream',
54
+  }, image)
55
+  resp = resp.json()
56
+  report({
57
+    status : 'pass',
58
+    result : [
59
+      <Assert key="1" expect={FILENAME} actual={resp.name}/>
60
+    ],
61
+  })
62
+
63
+})
64
+
65
+ctx.describe('Upload multipart/form-data', async function(report, data) {
66
+
67
+  let resp = await RNFetchBlob.fetch('POST', `${TEST_SERVER_URL}/upload-form`, {
68
+      Authorization : "Bearer fsXcpmKPrHgAAAAAAAAAEGxFXwhejXM_E8fznZoXPhHbhbNhA-Lytbe6etp1Jznz",
69
+      'Content-Type' : 'multipart/form-data',
70
+    }, [
71
+      { name : 'test-img', filename : 'test-img.png', data: image},
72
+      { name : 'test-text', filename : 'test-text.txt', data: RNFetchBlob.base64.encode('hello.txt')},
73
+      { name : 'field1', data : 'hello !!'},
74
+      { name : 'field2', data : 'hello2 !!'}
75
+    ])
76
+
77
+  resp = resp.json()
78
+
79
+  report({
80
+    status : 'pass',
81
+    result : [
82
+      <Assert key="1" expect="hello !!" actual={resp.fields.field1}/>,
83
+      <Assert key="2" expect="hello2 !!" actual={resp.fields.field2}/>,
84
+    ],
85
+  })
86
+
87
+})
88
+
89
+ctx.describe('Compare uploaded multipart image', async function(report) {
90
+  // try {
91
+    let resp = await RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/public/test-img.png`, {})
92
+    let resp2 = await RNFetchBlob.fetch('GET', `${TEST_SERVER_URL}/public/test-text.txt`, {})
93
+    console.log(resp)
94
+    console.log(resp2)
95
+    report({
96
+      status : 'pass',
97
+      result : [
98
+        <Assert key="1" expect={image.length} actual={resp.base64().length}/>,
99
+        <Assert key="2" expect={'hello.txt'} actual={resp2.text()}/>
100
+      ]
101
+    })
102
+  // } catch(err) {
103
+  //
104
+  //   report({
105
+  //     status : 'fail',
106
+  //     result :[
107
+  //       <Info key="a" description="Detail">
108
+  //         <Text>{JSON.stringify(err)}</Text>
109
+  //       </Info>]
110
+  //   })
111
+  // }
112
+
113
+})
114
+
115
+
116
+export default ctx