Compare commits

..

55 Commits

Author SHA1 Message Date
Sefa Ilkimen 7d7f02b4b3 release v2.3.0 2019-12-02 02:20:52 +01:00
Sefa Ilkimen afc9e3e944 Update readme 2019-12-01 07:30:45 +01:00
Sefa Ilkimen 9af2d1c21a Update license and readme file 2019-12-01 06:47:29 +01:00
Sefa Ilkimen 9dce2fb964 Update badges in readme 2019-12-01 06:28:21 +01:00
Sefa Ilkimen eb946b49ab Add GitHub Actions CI workflow 2019-12-01 06:08:16 +01:00
Sefa Ilkimen 72ca81b515 Update changelog 2019-11-30 17:36:49 +01:00
Sefa Ilkimen 68633f1bb8 chore: update changelog and readme 2019-11-18 04:22:37 +01:00
Sefa Ilkimen ee26b78e0d chore: update mocha timeout values 2019-11-18 03:09:32 +01:00
Sefa Ilkimen d924f98844 chore: update slack notifications for travis builds 2019-11-18 02:59:22 +01:00
Sefa Ilkimen 8fceb4df97 chore: configure mocha threshold values 2019-11-18 02:28:52 +01:00
Sefa Ilkimen 7a09fa9460 feat: add ponyfills to support multipart requests on android webview versions < 50 and iOS versions < 13.2 2019-11-18 02:01:02 +01:00
Sefa Ilkimen 3e5c941fdd refactor: iOS implementation 2019-11-17 22:46:51 +01:00
Sefa Ilkimen 0f273f401b feat(ios): implement multipart requests
- expose new AFHTTPSessionManager method "uploadTaskWithHTTPMethod"
2019-11-17 21:29:38 +01:00
Sefa Ilkimen 684874184d - refactor: iOS implementation
- chore: add flags to run only one spec or disable spec in e2e tests
2019-11-17 19:33:19 +01:00
Sefa Ilkimen 19e1e7206b implement e2e tests for #101 2019-11-14 04:45:20 +01:00
Sefa Ilkimen 21bec76c11 refactor e2e test scripts 2019-11-14 02:06:33 +01:00
Sefa Ilkimen 594d03aa41 WIP: major progress for #101
- feat(www): implement preprocessor for FormData instances
- feat(www): implement API checks for multipart requests
- feat(android): implement multipart requests
- chore(specs): implement www specs for new prprocessor
2019-11-11 04:49:35 +01:00
Sefa Ilkimen b3276ad2d4 update cordova-ios in test app template 2019-11-10 17:05:52 +01:00
Sefa Ilkimen 3b4a5b7c26 add missing module reference for "dependency-validator.js" 2019-11-10 06:55:38 +01:00
Sefa Ilkimen 867b8ea202 - WIP: implement data pre-processor for #101
- implement checks for #101
- add some specs
2019-11-10 06:40:31 +01:00
Sefa Ilkimen cc3e70771c release v2.2.0 2019-09-29 23:39:40 +02:00
Sefa Ilkimen 9e892119cc feat(#127): adding multiple file upload
- implement multi uploading files for ios
- update readme
2019-09-29 21:43:58 +02:00
Sefa Ilkimen f93f69e0aa feat(#127): adding multiple file upload
- remove www interface function "uploadFiles" as it confuses more than it helps
- implement multi uploading files for android
- add e2e spec
2019-09-29 20:37:36 +02:00
Sefa Ilkimen 4ace394464 feat(#127): adding multiple file upload
- fix missing option params
2019-09-27 13:25:14 +02:00
Sefa Ilkimen ae3e821639 feat(#127): adding multiple file upload
- prepare www interface
- implement options validation
- implement specs for js interface
- improve error messages
2019-09-26 16:37:19 +02:00
Sefa Ilkimen e17768041c fix: missing file reference in plugin.xml 2019-09-26 04:44:40 +02:00
Sefa Ilkimen 34a559bc6e feature #253: add support for response type "json" 2019-09-26 04:31:08 +02:00
Sefa Ilkimen 2ce1fc407d feature #239: add enumeration style object for error codes 2019-09-26 03:00:30 +02:00
Sefa Ilkimen 4b73c3762c Merge pull request #243 from joellimberg/patch-1
Readme typo fix: ”plugin.” → “plugin.http.”
2019-07-30 23:46:05 +02:00
Joel Limberg 5a9c176bca Readme typo fix: ”plugin.” → “plugin.http.” 2019-07-26 22:07:46 +03:00
Sefa Ilkimen 40aa944d0f fix #231: remove broken link 2019-07-23 23:02:34 +02:00
Sefa Ilkimen c7feb7aca3 improve readme 2019-07-23 22:39:56 +02:00
Sefa Ilkimen 680105175b Update issue templates 2019-06-25 02:26:16 +02:00
Sefa Ilkimen 07811d0380 Update issue templates 2019-06-25 02:25:20 +02:00
Sefa Ilkimen 026e8589c2 Update issue templates 2019-06-24 17:49:08 +02:00
Sefa Ilkimen e1592001af Update issue templates 2019-06-24 17:36:30 +02:00
Sefa Ilkimen f057b126ba release v2.1.1 2019-06-14 17:01:30 +02:00
Sefa Ilkimen 2b567cdf32 Fix #224: responseType "arraybuffer" and "blob" not working on browser platform 2019-06-14 16:43:36 +02:00
Sefa Ilkimen d99c7bb154 release v2.1.0 2019-06-14 02:57:16 +02:00
Sefa Ilkimen 336a56be1c update changelog 2019-06-14 02:56:59 +02:00
Sefa Ilkimen 8b962cc350 Merge pull request #205 from BeMyEye/master
add OKHTTP_VERSION plugin variable
2019-06-14 02:37:52 +02:00
Sefa Ilkimen 13bf4666b0 feature #171: support for responseType "blob" 2019-06-14 02:29:45 +02:00
Sefa Ilkimen 1fc3d6230c - update readme
- implement response type support for browser platform
2019-06-14 00:52:27 +02:00
Sefa Ilkimen 85346e0381 Merge branch 'binary_response'
# Conflicts:
#	test/e2e-specs.js
2019-06-13 16:44:35 +02:00
Sefa Ilkimen c9f8c8b66a release v2.0.11 2019-06-13 16:12:50 +02:00
Sefa Ilkimen 9b26a0f031 fix #221: headers are not set on Android when request fails due to non-success status code 2019-06-13 16:00:26 +02:00
Sefa Ilkimen 298c031433 release v2.0.10 2019-06-13 00:20:01 +02:00
Sefa Ilkimen eab6acf85c - update changelog
- increment version
2019-06-13 00:12:09 +02:00
Sefa Ilkimen 8cf0d21a7a add response type "arraybuffer" support for iOS 2019-06-12 23:43:40 +02:00
Sefa Ilkimen f91727e14a add response type "arraybuffer" support for android 2019-06-12 23:01:15 +02:00
Sefa Ilkimen 7b485507dc - use appium 1.9.1 for saucelabs
- update readme
2019-06-03 13:49:51 +02:00
Sefa Ilkimen 03b0abb74e using updated appium version for saucelabs 2019-06-03 13:32:45 +02:00
Sefa Ilkimen c3d60c37bf fix #218: headers are used as params on browser platform 2019-06-03 11:59:35 +02:00
cvaliere 6e68bf4dfe Merge pull request #1 from BeMyEye/use-preference-OKHTTP_VERSION
use OKHTTP_VERSION for okhttp-urlconnection
2019-04-11 17:35:14 +02:00
Kevin Simon 87f0f3600c use OKHTTP_VERSION for okhttp-urlconnection 2019-04-11 17:24:27 +02:00
58 changed files with 2532 additions and 969 deletions
+33
View File
@@ -0,0 +1,33 @@
---
name: "\U0001F41BBug report"
about: Create a report to help us improve
title: "[Bug] [platform] your issue title"
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is and what you expected to happen.
**System info**
- affected HTTP plugin version: [e.g. 2.1.1]
- affected platform(s) and version(s): [e.g. iOS 12.2]
- affected device(s): [e.g. iPhone 8]
- cordova version: [e.g. 6.5.0]
- cordova platform version(s): [e.g. android 7.0.0, browser 5.0.3]
**Are you using ionic-native-wrapper?**
- ionic-native-wrapper version: [e.g. 5.8.0]
- did you check [ionic-native issue tracker](https://github.com/ionic-team/ionic-native/issues) for your problem?
**Minimum viable code to reproduce**
If applicable, add formatted sample coding to help explain your problem.
e.g.:
```js
cordova.plugin.http.setDataSerializer('urlencoded');
```
**Screenshots**
If applicable, add screenshots to help explain your problem.
@@ -0,0 +1,21 @@
---
name: "\U0001F680Feature request"
about: Suggest an idea for this project
title: "[Feature]"
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. e.g. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen. If applicable, add formatted sample coding to help explain your idea.
```js
// do some fancy stuff here
```
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
@@ -0,0 +1,18 @@
---
name: "\U0001F914Support question"
about: Ask the community
title: ''
labels: question
assignees: ''
---
**You've got questions?**
We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks! 😁
* README.md: https://github.com/silkimen/cordova-plugin-advanced-http/blob/master/README.md
* StackOverflow: https://stackoverflow.com/questions/tagged/cordova-plugin-advanced-http using the tag `cordova-plugin-advanced-http`
* Wiki: https://github.com/silkimen/cordova-plugin-advanced-http/wiki
And don't forget: If you get help, help others. Good karma rulez!
+56
View File
@@ -0,0 +1,56 @@
name: Cordova HTTP Plugin CI
on: [push]
env:
nodejs: '10.x'
jobs:
test-www-interface:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install Node.js ${{ env.nodejs }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.nodejs }}
- name: Install node modules
run: npm ci
- name: Run WWW interface tests
run: npm run testjs
build-ios:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: Install Node.js ${{ env.nodejs }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.nodejs }}
- name: Install node modules
run: npm ci
- name: Update test cert for httpbin.org
run: npm run updatecert
- name: Build test app
run: scripts/build-test-app.sh --ios --emulator
build-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install Node.js ${{ env.nodejs }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.nodejs }}
- name: Install node modules
run: npm ci
- name: Install JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Update test cert for httpbin.org
run: npm run updatecert
- name: Add workaround for mipsel reference
run: sudo mkdir -p $ANDROID_HOME/ndk-bundle/toolchains/mips64el-linux-android-4.9/prebuilt/linux-x86_64
- name: Build test app
run: scripts/build-test-app.sh --android --emulator
+1 -1
View File
@@ -1,6 +1,6 @@
notifications: notifications:
slack: slack:
secure: lXE+2AgsxZU5G5dI91LkMAIgo8MAWfdM7DB5UOtn5LpuNln+2FmJo1gOI7tkdmLOqpXTGYnpI2VyQN3H4nOF21YhuouzD1Sh8n2wtQg1iTm353kuQpqiVhSBX8ZJ7Be1e1G8OsnxoYOxbs4Zo9qI40EruwkvqLCBHWM5MRGyd4M7EFWwb9Z29VZN0y1Nt5g/c3bT76kdKmF+JCLur2OeEKxAity7sIKgZekSqeIMwEVLSxXnda6Dbjc/cg0MJ0iDArkD7iu6fz/Fcrrxgm/pUxjcgvqze7Gy5i31mjEfspnrglWV1cshMd48BTDKCJ2AMmxH8O3GPSWE2txjIvGRWUve7iViNylvmQCVz3Eyf99+4EuuVGa+5PSodQ/CqODx/65EwtcN3PE1tNz2puKOK8nrOJcFkcbG8KTHKUlQtHCkjitbykUnj/hvhLK5/oWlQYVOLWWrHwdGUh8FI8aFPVGjRjWbHbhdayjEIqxwr1ns+6mYrP1EFNXbaeZxnLNC59XpJl1ifuezqYAk7YEiU5j4rtC7YKgyQ3ueb7anOHTJoTMyDn8mpZXgwuyhoBaeEYytQVgRyMtL6Y5cP98Jn2kv0+vdne3rkk9/JEBTo32HOjvoij6rsqEvXC0LhUDJSNadOVdHht0jjoN6zBH37HIE5/3zysLlPcAcHAS83ow= secure: twDT06GAiu0jsKizow7TcghZj70KbuuTrlo02QGmbSxBk2rJfsXSrHAsA3+s/9Q4mudENk6na7fs1aPCxz+u2etUGp+2PaJKVKR5n3jrNNt3SnYeWsBgVo7o7H1aLXatX3a+TdPXh1F5gQ4Ycr93nTYbW/077jsOholwbOHDZE3VcU9dzNPwFaEvhrDbr/ei3tef0ZiM1qxIad74TgwWMKClwai3I7HCVkZOPsyV+ve6cdIJ8Dt47JzFUHSW3SZuoe5Kywxvp0VvMo/QAJw95y3edNafx4EXHwbaN71rpGWSJXIKSZzcSQalZJ9DxGYspIBkWvGsNuQRzG9CzIoNQK10iERlIVC5vKDfKX22gayOQPSDkswJzIduylBUC8zdTPCndXyNEM/Lrj6hg+ksFWN58vYNPgfUeiga7X+LV5HytftsMFW+xx2kbnGeU8doGeX8Q8G7h9OIkHCTTG7R0ldYMIqTm8YJGPkRIv4OReC5ZOhiZD+wSg4KQ0wmMeRi+hyn+I5UPnKEOHAIN8FmLNCZFbgr1wuPFp9xnJIOcumQnQVZ2t6vk6IjIbwhYPWCnf7Sr4BvJxE8eyiLrEaXK0FiPb3My9wK9tLFjj1zdD7e4+SLq+WFMeCxp2eXOGF0Bu+2VK2tGjgWhaudaIpjbRQAAQ5nPa43h16NruEvNWI=
cache: cache:
directories: directories:
+3
View File
@@ -0,0 +1,3 @@
{
"editor.tabSize": 2
}
+31
View File
@@ -1,5 +1,36 @@
# Changelog # Changelog
## 2.3.0
- Feature #101: Support "multipart/form-data" requests (thanks SDA SE Open Industry Solutions)
#### Important information
This feature depends on several Web APIs. See https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.
## 2.2.0
- Feature #239: add enumeration style object for error codes
- Feature #253: add support for response type "json"
- Feature #127: add multiple file upload (thanks SDA SE Open Industry Solutions and nilswitschel)
## 2.1.1
- Fixed #224: response type "arraybuffer" and "blob" not working on browser platform
## 2.1.0
- Feature #216: Support for response type `arraybuffer`
- Feature #171: Support for response type `blob`
- Feature #205: Add preference for configuring OKHTTP version (thanks RougeCiel)
## 2.0.11
- Fixed #221: headers not set on Android when request fails due to non-success status code
## 2.0.10
- Fixed #218: headers are used as params on browser platform
## 2.0.9 ## 2.0.9
- Fixed #204: broken support for cordova-android < 7.0 - Fixed #204: broken support for cordova-android < 7.0
+1
View File
@@ -1,5 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2019 Sefa Ilkimen
Copyright (c) 2017 Mobisys GmbH Copyright (c) 2017 Mobisys GmbH
Copyright (c) 2014 Wymsee, Inc Copyright (c) 2014 Wymsee, Inc
+59 -23
View File
@@ -3,7 +3,9 @@ Cordova Advanced HTTP
[![npm version](https://badge.fury.io/js/cordova-plugin-advanced-http.svg)](https://badge.fury.io/js/cordova-plugin-advanced-http) [![npm version](https://badge.fury.io/js/cordova-plugin-advanced-http.svg)](https://badge.fury.io/js/cordova-plugin-advanced-http)
[![downloads/month](https://img.shields.io/npm/dm/cordova-plugin-advanced-http.svg)](https://www.npmjs.com/package/cordova-plugin-advanced-http) [![downloads/month](https://img.shields.io/npm/dm/cordova-plugin-advanced-http.svg)](https://www.npmjs.com/package/cordova-plugin-advanced-http)
[![MIT Licence](https://badges.frapsoft.com/os/mit/mit.png)](https://opensource.org/licenses/mit-license.php) [![MIT Licence](https://badges.frapsoft.com/os/mit/mit.png)](https://opensource.org/licenses/mit-license.php)
[![Build Status](https://travis-ci.org/silkimen/cordova-plugin-advanced-http.svg?branch=master)](https://travis-ci.org/silkimen/cordova-plugin-advanced-http)
[![Travis Build Status](https://img.shields.io/travis/silkimen/cordova-plugin-advanced-http/master?label=Travis%20CI)](https://travis-ci.org/silkimen/cordova-plugin-advanced-http)
[![GitHub Build Status](https://img.shields.io/github/workflow/status/silkimen/cordova-plugin-advanced-http/Cordova%20HTTP%20Plugin%20CI/master?label=GitHub%20Actions)](https://github.com/silkimen/cordova-plugin-advanced-http/actions)
Cordova / Phonegap plugin for communicating with HTTP servers. Supports iOS, Android and [Browser](#browserSupport). Cordova / Phonegap plugin for communicating with HTTP servers. Supports iOS, Android and [Browser](#browserSupport).
@@ -12,9 +14,9 @@ This is a fork of [Wymsee's Cordova-HTTP plugin](https://github.com/wymsee/cordo
## Advantages over Javascript requests ## Advantages over Javascript requests
- Background threading - all requests are done in a background thread. - SSL / TLS Pinning
- Handling of HTTP code 401 - read more at [Issue CB-2415](https://issues.apache.org/jira/browse/CB-2415). - CORS restrictions do not apply
- SSL Pinning - read more at [LumberBlog](http://blog.lumberlabs.com/2012/04/why-app-developers-should-care-about.html). - Handling of HTTP code 401 - read more at [Issue CB-2415](https://issues.apache.org/jira/browse/CB-2415)
## Updates ## Updates
@@ -92,10 +94,13 @@ You can choose one of these:
* `urlencoded`: send data as url encoded content in body (content type "application/x-www-form-urlencoded") * `urlencoded`: send data as url encoded content in body (content type "application/x-www-form-urlencoded")
* `json`: send data as JSON encoded content in body (content type "application/json") * `json`: send data as JSON encoded content in body (content type "application/json")
* `utf8`: send data as plain UTF8 encoded string in body (content type "plain/text") * `utf8`: send data as plain UTF8 encoded string in body (content type "plain/text")
* `multipart`: send FormData objects as multipart content in body (content type "multipart/form-data")
You can also override the default content type headers by specifying your own headers (see [setHeader](#setHeader)). This defaults to `urlencoded`. You can also override the default content type headers by specifying your own headers (see [setHeader](#setHeader)).
__Caution__: `urlencoded` does not support serializing deep structures whereas `json` does. :warning: `urlencoded` does not support serializing deep structures whereas `json` does.
:warning: `multipart` depends on several Web API standards which need to be supported in your web view. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.
### setRequestTimeout ### setRequestTimeout
Set how long to wait for a request to respond, in seconds. Set how long to wait for a request to respond, in seconds.
@@ -108,7 +113,7 @@ cordova.plugin.http.setRequestTimeout(5.0);
Configure if it should follow redirects automatically. This defaults to true. Configure if it should follow redirects automatically. This defaults to true.
```js ```js
cordova.plugin.setFollowRedirect(true); cordova.plugin.http.setFollowRedirect(true);
``` ```
### getCookieString ### getCookieString
@@ -176,15 +181,6 @@ This function was deprecated in 2.0.9. Use ["setFollowRedirect"](#setFollowRedir
### setSSLCertMode (deprecated) ### setSSLCertMode (deprecated)
This function was deprecated in 2.0.8. Use ["setServerTrustMode"](#setServerTrustMode) instead. This function was deprecated in 2.0.8. Use ["setServerTrustMode"](#setServerTrustMode) instead.
### enableSSLPinning (obsolete)
This function was removed in 2.0.0. Use ["setServerTrustMode"](#setServerTrustMode) to enable SSL pinning (mode "pinned").
### acceptAllCerts (obsolete)
This function was removed in 2.0.0. Use ["setServerTrustMode"](#setServerTrustMode) to disable checking certs (mode "nocheck").
### validateDomainName (obsolete)
This function was removed in v1.6.2. Domain name validation is disabled automatically when you set server trust mode to "nocheck".
### removeCookies ### removeCookies
Remove all cookies associated with a given URL. Remove all cookies associated with a given URL.
@@ -192,8 +188,10 @@ Remove all cookies associated with a given URL.
cordova.plugin.http.removeCookies(url, callback); cordova.plugin.http.removeCookies(url, callback);
``` ```
### sendRequest ### sendRequest<a name="sendRequest"></a>
Execute a HTTP request. Takes a URL and an options object. This is the internally used implementation of the following shorthand functions ([post](#post), [get](#get), [put](#put), [patch](#patch), [delete](#delete), [head](#head), [uploadFile](#uploadFile) and [downloadFile](#downloadFile)). You can use this function, if you want to override global settings for each single request. Execute a HTTP request. Takes a URL and an options object. This is the internally used implementation of the following shorthand functions ([post](#post), [get](#get), [put](#put), [patch](#patch), [delete](#delete), [head](#head), [uploadFile](#uploadFile) and [downloadFile](#downloadFile)). You can use this function, if you want to override global settings for each single request. Check the documentation of the respective shorthand function for details on what is returned on success and failure.
:warning: You need to encode the base URL yourself if it contains special characters like whitespaces. You can use `encodeURI()` for this purpose.
The options object contains following keys: The options object contains following keys:
@@ -202,11 +200,16 @@ The options object contains following keys:
* `data`: payload to be send to the server (only applicable on `post`, `put` or `patch` methods) * `data`: payload to be send to the server (only applicable on `post`, `put` or `patch` methods)
* `params`: query params to be appended to the URL (only applicable on `get`, `head`, `delete`, `upload` or `download` methods) * `params`: query params to be appended to the URL (only applicable on `get`, `head`, `delete`, `upload` or `download` methods)
* `serializer`: data serializer to be used (only applicable on `post`, `put` or `patch` methods), defaults to global serializer value, see [setDataSerializer](#setDataSerializer) for supported values * `serializer`: data serializer to be used (only applicable on `post`, `put` or `patch` methods), defaults to global serializer value, see [setDataSerializer](#setDataSerializer) for supported values
* `responseType`: expected response type, defaults to `text`, needs to be one of the following values:
* `text`: data is returned as decoded string, use this for all kinds of string responses (e.g. XML, HTML, plain text, etc.)
* `json` data is treated as JSON and returned as parsed object
* `arraybuffer`: data is returned as [ArrayBuffer instance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)
* `blob`: data is returned as [Blob instance](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
* `timeout`: timeout value for the request in seconds, defaults to global timeout value * `timeout`: timeout value for the request in seconds, defaults to global timeout value
* `followRedirect`: enable or disable automatically following redirects * `followRedirect`: enable or disable automatically following redirects
* `headers`: headers object (key value pair), will be merged with global values * `headers`: headers object (key value pair), will be merged with global values
* `filePath`: filePath to be used during upload and download see [uploadFile](#uploadFile) and [downloadFile](#downloadFile) for detailed information * `filePath`: file path(s) to be used during upload and download see [uploadFile](#uploadFile) and [downloadFile](#downloadFile) for detailed information
* `name`: name to be used during upload see [uploadFile](#uploadFile) for detailed information * `name`: name(s) to be used during upload see [uploadFile](#uploadFile) for detailed information
Here's a quick example: Here's a quick example:
@@ -232,6 +235,18 @@ cordova.plugin.http.sendRequest('https://google.com/', options, function(respons
### post<a name="post"></a> ### post<a name="post"></a>
Execute a POST request. Takes a URL, data, and headers. Execute a POST request. Takes a URL, data, and headers.
```js
cordova.plugin.http.post('https://google.com/', {
test: 'testString'
}, {
Authorization: 'OAuth2: token'
}, function(response) {
console.log(response.status);
}, function(response) {
console.error(response.error);
});
```
#### success #### success
The success function receives a response object with 4 properties: status, data, url, and headers. **status** is the HTTP response code as numeric value. **data** is the response from the server as a string. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase. The success function receives a response object with 4 properties: status, data, url, and headers. **status** is the HTTP response code as numeric value. **data** is the response from the server as a string. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
@@ -274,7 +289,7 @@ cordova.plugin.http.post('https://google.com/', {
``` ```
#### failure #### failure
The error function receives a response object with 4 properties: status, error, url, and headers (url and headers being optional). **status** is the HTTP response code as numeric value. **error** is the error response from the server as a string. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase. The error function receives a response object with 4 properties: status, error, url, and headers (url and headers being optional). **status** is a HTTP response code or an internal error code. Positive values are HTTP status codes whereas negative values do represent internal error codes. **error** is the error response from the server as a string or an internal error message. **url** is the final URL obtained after any redirects as a string. **headers** is an object with the headers. The keys of the returned object are the header names and the values are the respective header values. All header names are lowercase.
Here's a quick example: Here's a quick example:
@@ -289,6 +304,8 @@ Here's a quick example:
} }
``` ```
:warning: An enumeration style object is exposed as `cordova.plugin.http.ErrorCode`. You can use it to check against internal error codes.
### get<a name="get"></a> ### get<a name="get"></a>
Execute a GET request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure. Execute a GET request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
@@ -316,13 +333,21 @@ Execute a DELETE request. Takes a URL, parameters, and headers. See the [post]
Execute a HEAD request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure. Execute a HEAD request. Takes a URL, parameters, and headers. See the [post](#post) documentation for details on what is returned on success and failure.
### uploadFile<a name="uploadFile"></a> ### uploadFile<a name="uploadFile"></a>
Uploads a file saved on the device. Takes a URL, parameters, headers, filePath, and the name of the parameter to pass the file along as. See the [post](#post) documentation for details on what is returned on success and failure. Uploads one or more file(s) saved on the device. Takes a URL, parameters, headers, filePath(s), and the name(s) of the parameter to pass the file along as. See the [post](#post) documentation for details on what is returned on success and failure.
```js ```js
// e.g. for single file
const filePath = 'file:///somepicture.jpg';
const name = 'picture';
// e.g. for multiple files
const filePath = ['file:///somepicture.jpg', 'file:///somedocument.doc'];
const name = ['picture', 'document'];
cordova.plugin.http.uploadFile("https://google.com/", { cordova.plugin.http.uploadFile("https://google.com/", {
id: '12', id: '12',
message: 'test' message: 'test'
}, { Authorization: 'OAuth2: token' }, 'file:///somepicture.jpg', 'picture', function(response) { }, { Authorization: 'OAuth2: token' }, filePath, name, function(response) {
console.log(response.status); console.log(response.status);
}, function(response) { }, function(response) {
console.error(response.error); console.error(response.error);
@@ -359,6 +384,7 @@ Following features are *not* supported:
* Pinning SSL certificate * Pinning SSL certificate
* Disabling SSL certificate check * Disabling SSL certificate check
* Disabling transparently following redirects (HTTP codes 3xx) * Disabling transparently following redirects (HTTP codes 3xx)
* Circumventing CORS restrictions
## Libraries ## Libraries
@@ -370,6 +396,16 @@ This plugin utilizes some awesome open source libraries:
We made a few modifications to the networking libraries. We made a few modifications to the networking libraries.
## CI Builds & E2E Testing
This plugin uses amazing cloud services to maintain quality. CI Builds and E2E testing are powered by:
* [GitHub Actions](https://github.com/features/actions)
* [Travis CI](https://travis-ci.org/)
* [BrowserStack](https://www.browserstack.com/)
* [Sauce Labs](https://saucelabs.com/)
* [httpbin.org](https://httpbin.org/)
## Contribute & Develop ## Contribute & Develop
We've set up a separate document for our [contribution guidelines](CONTRIBUTING.md). We've set up a separate document for our [contribution guidelines](CONTRIBUTING.md).
+277 -356
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -1,12 +1,14 @@
{ {
"name": "cordova-plugin-advanced-http", "name": "cordova-plugin-advanced-http",
"version": "2.0.9", "version": "2.3.0",
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning", "description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
"scripts": { "scripts": {
"updatecert": "node ./scripts/update-e2e-server-cert.js && node ./scripts/update-e2e-client-cert.js", "updatecert": "node ./scripts/update-e2e-server-cert.js && node ./scripts/update-e2e-client-cert.js",
"buildbrowser": "./scripts/build-test-app.sh --browser", "buildbrowser": "./scripts/build-test-app.sh --browser",
"testandroid": "npm run updatecert && ./scripts/build-test-app.sh --android --emulator && ./scripts/test-app.sh --android --emulator", "buildandroid": "./scripts/build-test-app.sh --android --emulator",
"testios": "npm run updatecert && ./scripts/build-test-app.sh --ios --emulator && ./scripts/test-app.sh --ios --emulator", "buildios": "./scripts/build-test-app.sh --ios --emulator",
"testandroid": "npm run updatecert && npm run buildandroid && ./scripts/test-app.sh --android --emulator",
"testios": "npm run updatecert && npm run buildios && ./scripts/test-app.sh --ios --emulator",
"testapp": "npm run testandroid && npm run testios", "testapp": "npm run testandroid && npm run testios",
"testjs": "mocha ./test/js-specs.js", "testjs": "mocha ./test/js-specs.js",
"test": "npm run testjs && npm run testapp", "test": "npm run testjs && npm run testapp",
+8 -2
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="2.0.9"> <plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-advanced-http" version="2.3.0">
<name>Advanced HTTP plugin</name> <name>Advanced HTTP plugin</name>
<description> <description>
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
@@ -9,12 +9,15 @@
</engines> </engines>
<dependency id="cordova-plugin-file" version=">=2.0.0"/> <dependency id="cordova-plugin-file" version=">=2.0.0"/>
<js-module src="www/cookie-handler.js" name="cookie-handler"/> <js-module src="www/cookie-handler.js" name="cookie-handler"/>
<js-module src="www/dependency-validator.js" name="dependency-validator"/>
<js-module src="www/error-codes.js" name="error-codes"/>
<js-module src="www/global-configs.js" name="global-configs"/> <js-module src="www/global-configs.js" name="global-configs"/>
<js-module src="www/helpers.js" name="helpers"/> <js-module src="www/helpers.js" name="helpers"/>
<js-module src="www/js-util.js" name="js-util"/> <js-module src="www/js-util.js" name="js-util"/>
<js-module src="www/local-storage-store.js" name="local-storage-store"/> <js-module src="www/local-storage-store.js" name="local-storage-store"/>
<js-module src="www/lodash.js" name="lodash"/> <js-module src="www/lodash.js" name="lodash"/>
<js-module src="www/messages.js" name="messages"/> <js-module src="www/messages.js" name="messages"/>
<js-module src="www/ponyfills.js" name="ponyfills"/>
<js-module src="www/public-interface.js" name="public-interface"/> <js-module src="www/public-interface.js" name="public-interface"/>
<js-module src="www/umd-tough-cookie.js" name="tough-cookie"/> <js-module src="www/umd-tough-cookie.js" name="tough-cookie"/>
<js-module src="www/url-util.js" name="url-util"/> <js-module src="www/url-util.js" name="url-util"/>
@@ -28,6 +31,7 @@
</feature> </feature>
</config-file> </config-file>
<header-file src="src/ios/CordovaHttpPlugin.h"/> <header-file src="src/ios/CordovaHttpPlugin.h"/>
<header-file src="src/ios/BinaryResponseSerializer.h"/>
<header-file src="src/ios/TextResponseSerializer.h"/> <header-file src="src/ios/TextResponseSerializer.h"/>
<header-file src="src/ios/TextRequestSerializer.h"/> <header-file src="src/ios/TextRequestSerializer.h"/>
<header-file src="src/ios/AFNetworking/AFHTTPSessionManager.h"/> <header-file src="src/ios/AFNetworking/AFHTTPSessionManager.h"/>
@@ -39,6 +43,7 @@
<header-file src="src/ios/AFNetworking/AFURLSessionManager.h"/> <header-file src="src/ios/AFNetworking/AFURLSessionManager.h"/>
<header-file src="src/ios/SDNetworkActivityIndicator/SDNetworkActivityIndicator.h"/> <header-file src="src/ios/SDNetworkActivityIndicator/SDNetworkActivityIndicator.h"/>
<source-file src="src/ios/CordovaHttpPlugin.m"/> <source-file src="src/ios/CordovaHttpPlugin.m"/>
<source-file src="src/ios/BinaryResponseSerializer.m"/>
<source-file src="src/ios/TextResponseSerializer.m"/> <source-file src="src/ios/TextResponseSerializer.m"/>
<source-file src="src/ios/TextRequestSerializer.m"/> <source-file src="src/ios/TextRequestSerializer.m"/>
<source-file src="src/ios/AFNetworking/AFHTTPSessionManager.m"/> <source-file src="src/ios/AFNetworking/AFHTTPSessionManager.m"/>
@@ -75,7 +80,8 @@
<source-file src="src/android/com/silkimen/http/OkConnectionFactory.java" target-dir="src/com/silkimen/http"/> <source-file src="src/android/com/silkimen/http/OkConnectionFactory.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/TLSConfiguration.java" target-dir="src/com/silkimen/http"/> <source-file src="src/android/com/silkimen/http/TLSConfiguration.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/TLSSocketFactory.java" target-dir="src/com/silkimen/http"/> <source-file src="src/android/com/silkimen/http/TLSSocketFactory.java" target-dir="src/com/silkimen/http"/>
<framework src="com.squareup.okhttp3:okhttp-urlconnection:3.10.0"/> <preference name="OKHTTP_VERSION" default="3.10.0"/>
<framework src="com.squareup.okhttp3:okhttp-urlconnection:$OKHTTP_VERSION"/>
</platform> </platform>
<platform name="browser"> <platform name="browser">
<config-file target="config.xml" parent="/*"> <config-file target="config.xml" parent="/*">
@@ -1,7 +1,9 @@
package com.silkimen.cordovahttp; package com.silkimen.cordovahttp;
import java.io.IOException; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@@ -19,9 +21,11 @@ import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext; import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import android.util.Base64;
import android.util.Log; import android.util.Log;
abstract class CordovaHttpBase implements Runnable { abstract class CordovaHttpBase implements Runnable {
@@ -30,6 +34,7 @@ abstract class CordovaHttpBase implements Runnable {
protected String method; protected String method;
protected String url; protected String url;
protected String serializer = "none"; protected String serializer = "none";
protected String responseType;
protected Object data; protected Object data;
protected JSONObject headers; protected JSONObject headers;
protected int timeout; protected int timeout;
@@ -38,7 +43,8 @@ abstract class CordovaHttpBase implements Runnable {
protected CallbackContext callbackContext; protected CallbackContext callbackContext;
public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout, public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout,
boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) { boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
this.method = method; this.method = method;
this.url = url; this.url = url;
@@ -47,18 +53,20 @@ abstract class CordovaHttpBase implements Runnable {
this.headers = headers; this.headers = headers;
this.timeout = timeout; this.timeout = timeout;
this.followRedirects = followRedirects; this.followRedirects = followRedirects;
this.responseType = responseType;
this.tlsConfiguration = tlsConfiguration; this.tlsConfiguration = tlsConfiguration;
this.callbackContext = callbackContext; this.callbackContext = callbackContext;
} }
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout, boolean followRedirects, public CordovaHttpBase(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) { String responseType, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
this.method = method; this.method = method;
this.url = url; this.url = url;
this.headers = headers; this.headers = headers;
this.timeout = timeout; this.timeout = timeout;
this.followRedirects = followRedirects; this.followRedirects = followRedirects;
this.responseType = responseType;
this.tlsConfiguration = tlsConfiguration; this.tlsConfiguration = tlsConfiguration;
this.callbackContext = callbackContext; this.callbackContext = callbackContext;
} }
@@ -116,7 +124,7 @@ abstract class CordovaHttpBase implements Runnable {
request.readTimeout(this.timeout); request.readTimeout(this.timeout);
request.acceptCharset("UTF-8"); request.acceptCharset("UTF-8");
request.uncompress(true); request.uncompress(true);
request.setConnectionFactory(new OkConnectionFactory()); HttpRequest.setConnectionFactory(new OkConnectionFactory());
if (this.tlsConfiguration.getHostnameVerifier() != null) { if (this.tlsConfiguration.getHostnameVerifier() != null) {
request.setHostnameVerifier(this.tlsConfiguration.getHostnameVerifier()); request.setHostnameVerifier(this.tlsConfiguration.getHostnameVerifier());
@@ -137,6 +145,8 @@ abstract class CordovaHttpBase implements Runnable {
request.contentType("text/plain", "UTF-8"); request.contentType("text/plain", "UTF-8");
} else if ("urlencoded".equals(this.serializer)) { } else if ("urlencoded".equals(this.serializer)) {
// intentionally left blank, because content type is set in HttpRequest.form() // intentionally left blank, because content type is set in HttpRequest.form()
} else if ("multipart".equals(this.serializer)) {
request.contentType("multipart/form-data");
} }
} }
@@ -151,6 +161,22 @@ abstract class CordovaHttpBase implements Runnable {
request.send(((JSONObject) this.data).getString("text")); request.send(((JSONObject) this.data).getString("text"));
} else if ("urlencoded".equals(this.serializer)) { } else if ("urlencoded".equals(this.serializer)) {
request.form(JsonUtils.getObjectMap((JSONObject) this.data)); request.form(JsonUtils.getObjectMap((JSONObject) this.data));
} else if ("multipart".equals(this.serializer)) {
JSONArray buffers = ((JSONObject) this.data).getJSONArray("buffers");
JSONArray names = ((JSONObject) this.data).getJSONArray("names");
JSONArray fileNames = ((JSONObject) this.data).getJSONArray("fileNames");
JSONArray types = ((JSONObject) this.data).getJSONArray("types");
for (int i = 0; i < buffers.length(); ++i) {
byte[] bytes = Base64.decode(buffers.getString(i), Base64.DEFAULT);
String name = names.getString(i);
if (fileNames.isNull(i)) {
request.part(name, new String(bytes, "UTF-8"));
} else {
request.part(name, fileNames.getString(i), types.getString(i), new ByteArrayInputStream(bytes));
}
}
} }
} }
@@ -158,17 +184,19 @@ abstract class CordovaHttpBase implements Runnable {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
request.receive(outputStream); request.receive(outputStream);
ByteBuffer rawOutput = ByteBuffer.wrap(outputStream.toByteArray());
String decodedBody = HttpBodyDecoder.decodeBody(rawOutput, request.charset());
response.setStatus(request.code()); response.setStatus(request.code());
response.setUrl(request.url().toString()); response.setUrl(request.url().toString());
response.setHeaders(request.headers()); response.setHeaders(request.headers());
if (request.code() >= 200 && request.code() < 300) { if (request.code() >= 200 && request.code() < 300) {
response.setBody(decodedBody); if ("text".equals(this.responseType)) {
String decoded = HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset());
response.setBody(decoded);
} else {
response.setData(outputStream.toByteArray());
}
} else { } else {
response.setErrorMessage(decodedBody); response.setErrorMessage(HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset()));
} }
} }
} }
@@ -19,7 +19,7 @@ class CordovaHttpDownload extends CordovaHttpBase {
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout, boolean followRedirects, public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) { TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
super("GET", url, headers, timeout, followRedirects, tlsConfiguration, callbackContext); super("GET", url, headers, timeout, followRedirects, "text", tlsConfiguration, callbackContext);
this.filePath = filePath; this.filePath = filePath;
} }
@@ -10,14 +10,16 @@ import org.json.JSONObject;
class CordovaHttpOperation extends CordovaHttpBase { class CordovaHttpOperation extends CordovaHttpBase {
public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers, public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers,
int timeout, boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) { int timeout, boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
super(method, url, serializer, data, headers, timeout, followRedirects, tlsConfiguration, callbackContext); super(method, url, serializer, data, headers, timeout, followRedirects, responseType, tlsConfiguration,
callbackContext);
} }
public CordovaHttpOperation(String method, String url, JSONObject headers, int timeout, boolean followRedirects, public CordovaHttpOperation(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) { String responseType, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
super(method, url, headers, timeout, followRedirects, tlsConfiguration, callbackContext); super(method, url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
} }
} }
@@ -63,8 +63,8 @@ public class CordovaHttpPlugin extends CordovaPlugin {
return this.executeHttpRequestWithData(action, args, callbackContext); return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("patch".equals(action)) { } else if ("patch".equals(action)) {
return this.executeHttpRequestWithData(action, args, callbackContext); return this.executeHttpRequestWithData(action, args, callbackContext);
} else if ("uploadFile".equals(action)) { } else if ("uploadFiles".equals(action)) {
return this.uploadFile(args, callbackContext); return this.uploadFiles(args, callbackContext);
} else if ("downloadFile".equals(action)) { } else if ("downloadFile".equals(action)) {
return this.downloadFile(args, callbackContext); return this.downloadFile(args, callbackContext);
} else if ("setServerTrustMode".equals(action)) { } else if ("setServerTrustMode".equals(action)) {
@@ -83,9 +83,10 @@ public class CordovaHttpPlugin extends CordovaPlugin {
JSONObject headers = args.getJSONObject(1); JSONObject headers = args.getJSONObject(1);
int timeout = args.getInt(2) * 1000; int timeout = args.getInt(2) * 1000;
boolean followRedirect = args.getBoolean(3); boolean followRedirect = args.getBoolean(3);
String responseType = args.getString(4);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout, followRedirect, CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout, followRedirect,
this.tlsConfiguration, callbackContext); responseType, this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(request); cordova.getThreadPool().execute(request);
@@ -101,25 +102,27 @@ public class CordovaHttpPlugin extends CordovaPlugin {
JSONObject headers = args.getJSONObject(3); JSONObject headers = args.getJSONObject(3);
int timeout = args.getInt(4) * 1000; int timeout = args.getInt(4) * 1000;
boolean followRedirect = args.getBoolean(5); boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers, CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
timeout, followRedirect, this.tlsConfiguration, callbackContext); timeout, followRedirect, responseType, this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(request); cordova.getThreadPool().execute(request);
return true; return true;
} }
private boolean uploadFile(final JSONArray args, final CallbackContext callbackContext) throws JSONException { private boolean uploadFiles(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String url = args.getString(0); String url = args.getString(0);
JSONObject headers = args.getJSONObject(1); JSONObject headers = args.getJSONObject(1);
String filePath = args.getString(2); JSONArray filePaths = args.getJSONArray(2);
String uploadName = args.getString(3); JSONArray uploadNames = args.getJSONArray(3);
int timeout = args.getInt(4) * 1000; int timeout = args.getInt(4) * 1000;
boolean followRedirect = args.getBoolean(5); boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePath, uploadName, timeout, followRedirect, CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePaths, uploadNames, timeout, followRedirect,
this.tlsConfiguration, callbackContext); responseType, this.tlsConfiguration, this.cordova.getActivity().getApplicationContext(), callbackContext);
cordova.getThreadPool().execute(upload); cordova.getThreadPool().execute(upload);
@@ -1,5 +1,7 @@
package com.silkimen.cordovahttp; package com.silkimen.cordovahttp;
import java.nio.ByteBuffer;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -9,15 +11,18 @@ import org.json.JSONObject;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.Base64;
class CordovaHttpResponse { class CordovaHttpResponse {
private int status; private int status;
private String url; private String url;
private Map<String, List<String>> headers; private Map<String, List<String>> headers;
private String body; private String body;
private byte[] rawData;
private JSONObject fileEntry; private JSONObject fileEntry;
private boolean hasFailed; private boolean hasFailed;
private boolean isFileOperation; private boolean isFileOperation;
private boolean isRawResponse;
private String error; private String error;
public void setStatus(int status) { public void setStatus(int status) {
@@ -36,6 +41,11 @@ class CordovaHttpResponse {
this.body = body; this.body = body;
} }
public void setData(byte[] rawData) {
this.isRawResponse = true;
this.rawData = rawData;
}
public void setFileEntry(JSONObject entry) { public void setFileEntry(JSONObject entry) {
this.isFileOperation = true; this.isFileOperation = true;
this.fileEntry = entry; this.fileEntry = entry;
@@ -56,13 +66,17 @@ class CordovaHttpResponse {
json.put("status", this.status); json.put("status", this.status);
json.put("url", this.url); json.put("url", this.url);
if (this.headers != null && !this.headers.isEmpty()) {
json.put("headers", new JSONObject(getFilteredHeaders()));
}
if (this.hasFailed) { if (this.hasFailed) {
json.put("error", this.error); json.put("error", this.error);
} else if (this.isFileOperation) { } else if (this.isFileOperation) {
json.put("headers", new JSONObject(getFilteredHeaders()));
json.put("file", this.fileEntry); json.put("file", this.fileEntry);
} else if (this.isRawResponse) {
json.put("data", Base64.encodeToString(this.rawData, Base64.DEFAULT));
} else { } else {
json.put("headers", new JSONObject(getFilteredHeaders()));
json.put("data", this.body); json.put("data", this.body);
} }
@@ -72,10 +86,6 @@ class CordovaHttpResponse {
private Map<String, String> getFilteredHeaders() throws JSONException { private Map<String, String> getFilteredHeaders() throws JSONException {
Map<String, String> filteredHeaders = new HashMap<String, String>(); Map<String, String> filteredHeaders = new HashMap<String, String>();
if (this.headers == null || this.headers.isEmpty()) {
return filteredHeaders;
}
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) { for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
List<String> value = entry.getValue(); List<String> value = entry.getValue();
@@ -1,43 +1,92 @@
package com.silkimen.cordovahttp; package com.silkimen.cordovahttp;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import com.silkimen.http.HttpRequest; import com.silkimen.http.HttpRequest;
import com.silkimen.http.TLSConfiguration;
import java.io.File; import java.io.File;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext; import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
class CordovaHttpUpload extends CordovaHttpBase { class CordovaHttpUpload extends CordovaHttpBase {
private String filePath; private JSONArray filePaths;
private String uploadName; private JSONArray uploadNames;
private Context applicationContext;
public CordovaHttpUpload(String url, JSONObject headers, String filePath, String uploadName, int timeout, public CordovaHttpUpload(String url, JSONObject headers, JSONArray filePaths, JSONArray uploadNames, int timeout,
boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) { boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
Context applicationContext, CallbackContext callbackContext) {
super("POST", url, headers, timeout, followRedirects, tlsConfiguration, callbackContext); super("POST", url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
this.filePath = filePath; this.filePaths = filePaths;
this.uploadName = uploadName; this.uploadNames = uploadNames;
this.applicationContext = applicationContext;
} }
@Override @Override
protected void sendBody(HttpRequest request) throws Exception { protected void sendBody(HttpRequest request) throws Exception {
int filenameIndex = this.filePath.lastIndexOf('/'); for (int i = 0; i < this.filePaths.length(); ++i) {
String filename = this.filePath.substring(filenameIndex + 1); String uploadName = this.uploadNames.getString(i);
String filePath = this.filePaths.getString(i);
int extIndex = this.filePath.lastIndexOf('.'); Uri fileUri = Uri.parse(filePath);
String ext = this.filePath.substring(extIndex + 1);
// File Scheme
if (ContentResolver.SCHEME_FILE.equals(fileUri.getScheme())) {
File file = new File(new URI(filePath));
String fileName = file.getName().trim();
String mimeType = this.getMimeTypeFromFileName(fileName);
request.part(uploadName, fileName, mimeType, file);
}
// Content Scheme
if (ContentResolver.SCHEME_CONTENT.equals(fileUri.getScheme())) {
InputStream inputStream = this.applicationContext.getContentResolver().openInputStream(fileUri);
String fileName = this.getFileNameFromContentScheme(fileUri, this.applicationContext).trim();
String mimeType = this.getMimeTypeFromFileName(fileName);
request.part(uploadName, fileName, mimeType, inputStream);
}
}
}
private String getFileNameFromContentScheme(Uri contentSchemeUri, Context applicationContext) {
Cursor returnCursor = applicationContext.getContentResolver().query(contentSchemeUri, null, null, null, null);
if (returnCursor == null || !returnCursor.moveToFirst()) {
return null;
}
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
String fileName = returnCursor.getString(nameIndex);
returnCursor.close();
return fileName;
}
private String getMimeTypeFromFileName(String fileName) {
if (fileName == null || !fileName.contains(".")) {
return null;
}
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
String mimeType = mimeTypeMap.getMimeTypeFromExtension(ext); int extIndex = fileName.lastIndexOf('.') + 1;
String extension = fileName.substring(extIndex).toLowerCase();
request.part(this.uploadName, filename, mimeType, new File(new URI(this.filePath))); return mimeTypeMap.getMimeTypeFromExtension(extension);
} }
} }
@@ -10,21 +10,28 @@ import java.nio.charset.MalformedInputException;
public class HttpBodyDecoder { public class HttpBodyDecoder {
private static final String[] ACCEPTED_CHARSETS = new String[] { "UTF-8", "ISO-8859-1" }; private static final String[] ACCEPTED_CHARSETS = new String[] { "UTF-8", "ISO-8859-1" };
public static String decodeBody(ByteBuffer rawOutput, String charsetName) public static String decodeBody(byte[] body, String charsetName)
throws CharacterCodingException, MalformedInputException {
return decodeBody(ByteBuffer.wrap(body), charsetName);
}
public static String decodeBody(ByteBuffer body, String charsetName)
throws CharacterCodingException, MalformedInputException { throws CharacterCodingException, MalformedInputException {
if (charsetName == null) { if (charsetName == null) {
return tryDecodeByteBuffer(rawOutput); return tryDecodeByteBuffer(body);
} else {
return decodeByteBuffer(body, charsetName);
} }
return decodeByteBuffer(rawOutput, charsetName);
} }
private static String tryDecodeByteBuffer(ByteBuffer rawOutput) throws CharacterCodingException, MalformedInputException { private static String tryDecodeByteBuffer(ByteBuffer buffer)
throws CharacterCodingException, MalformedInputException {
for (int i = 0; i < ACCEPTED_CHARSETS.length - 1; i++) { for (int i = 0; i < ACCEPTED_CHARSETS.length - 1; i++) {
try { try {
return decodeByteBuffer(rawOutput, ACCEPTED_CHARSETS[i]); return decodeByteBuffer(buffer, ACCEPTED_CHARSETS[i]);
} catch (MalformedInputException e) { } catch (MalformedInputException e) {
continue; continue;
} catch (CharacterCodingException e) { } catch (CharacterCodingException e) {
@@ -32,13 +39,13 @@ public class HttpBodyDecoder {
} }
} }
return decodeBody(rawOutput, ACCEPTED_CHARSETS[ACCEPTED_CHARSETS.length - 1]); return decodeBody(buffer, ACCEPTED_CHARSETS[ACCEPTED_CHARSETS.length - 1]);
} }
private static String decodeByteBuffer(ByteBuffer rawOutput, String charsetName) private static String decodeByteBuffer(ByteBuffer buffer, String charsetName)
throws CharacterCodingException, MalformedInputException { throws CharacterCodingException, MalformedInputException {
return createCharsetDecoder(charsetName).decode(rawOutput).toString(); return createCharsetDecoder(charsetName).decode(buffer).toString();
} }
private static CharsetDecoder createCharsetDecoder(String charsetName) { private static CharsetDecoder createCharsetDecoder(String charsetName) {
+44 -20
View File
@@ -52,11 +52,19 @@ function deserializeResponseHeaders(headers) {
return headerMap; return headerMap;
} }
function getResponseData(xhr) {
if (xhr.responseType !== 'text' || jsUtil.getTypeOf(xhr.responseText) !== 'String') {
return xhr.response;
}
return xhr.responseText;
}
function createXhrSuccessObject(xhr) { function createXhrSuccessObject(xhr) {
return { return {
url: xhr.responseURL, url: xhr.responseURL,
status: xhr.status, status: xhr.status,
data: jsUtil.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response, data: getResponseData(xhr),
headers: deserializeResponseHeaders(xhr.getAllResponseHeaders()) headers: deserializeResponseHeaders(xhr.getAllResponseHeaders())
}; };
} }
@@ -65,7 +73,7 @@ function createXhrFailureObject(xhr) {
var obj = {}; var obj = {};
obj.headers = xhr.getAllResponseHeaders(); obj.headers = xhr.getAllResponseHeaders();
obj.error = jsUtil.getTypeOf(xhr.responseText) === 'String' ? xhr.responseText : xhr.response; obj.error = getResponseData(xhr);
obj.error = obj.error || 'advanced-http: please check browser console for error messages'; obj.error = obj.error || 'advanced-http: please check browser console for error messages';
if (xhr.responseURL) obj.url = xhr.responseURL; if (xhr.responseURL) obj.url = xhr.responseURL;
@@ -101,12 +109,23 @@ function setHeaders(xhr, headers) {
} }
function sendRequest(method, withData, opts, success, failure) { function sendRequest(method, withData, opts, success, failure) {
var data = withData ? opts[1] : null; var data, serializer, headers, timeout, followRedirect, responseType;
var params = withData ? null : serializeParams(opts[1]); var url = opts[0];
var serializer = withData ? opts[2] : null;
var headers = withData ? opts[3] : opts[2]; if (withData) {
var timeout = withData ? opts[4] : opts[3]; data = opts[1];
var url = params ? opts[0] + '?' + params : opts[0]; serializer = opts[2];
headers = opts[3];
timeout = opts[4];
followRedirect = opts[5];
responseType = opts[6];
} else {
headers = opts[1];
timeout = opts[2];
followRedirect = opts[3];
responseType = opts[4];
}
var processedData = null; var processedData = null;
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
@@ -117,6 +136,10 @@ function sendRequest(method, withData, opts, success, failure) {
return failure('advanced-http: custom cookies not supported on browser platform'); return failure('advanced-http: custom cookies not supported on browser platform');
} }
if (!followRedirect) {
return failure('advanced-http: disabling follow redirect not supported on browser platform');
}
switch (serializer) { switch (serializer) {
case 'json': case 'json':
setDefaultContentType(headers, 'application/json; charset=utf8'); setDefaultContentType(headers, 'application/json; charset=utf8');
@@ -140,6 +163,7 @@ function sendRequest(method, withData, opts, success, failure) {
} }
xhr.timeout = timeout * 1000; xhr.timeout = timeout * 1000;
xhr.responseType = responseType;
setHeaders(xhr, headers); setHeaders(xhr, headers);
xhr.onerror = xhr.ontimeout = function () { xhr.onerror = xhr.ontimeout = function () {
@@ -160,27 +184,30 @@ function sendRequest(method, withData, opts, success, failure) {
} }
var browserInterface = { var browserInterface = {
post: function (success, failure, opts) {
return sendRequest('post', true, opts, success, failure);
},
get: function (success, failure, opts) { get: function (success, failure, opts) {
return sendRequest('get', false, opts, success, failure); return sendRequest('get', false, opts, success, failure);
}, },
head: function (success, failure, opts) {
return sendRequest('head', false, opts, success, failure);
},
delete: function (success, failure, opts) {
return sendRequest('delete', false, opts, success, failure);
},
post: function (success, failure, opts) {
return sendRequest('post', true, opts, success, failure);
},
put: function (success, failure, opts) { put: function (success, failure, opts) {
return sendRequest('put', true, opts, success, failure); return sendRequest('put', true, opts, success, failure);
}, },
patch: function (success, failure, opts) { patch: function (success, failure, opts) {
return sendRequest('patch', true, opts, success, failure); return sendRequest('patch', true, opts, success, failure);
}, },
delete: function (success, failure, opts) {
return sendRequest('delete', false, opts, success, failure);
},
head: function (success, failure, opts) {
return sendRequest('head', false, opts, success, failure);
},
uploadFile: function (success, failure, opts) { uploadFile: function (success, failure, opts) {
return failure('advanced-http: function "uploadFile" not supported on browser platform'); return failure('advanced-http: function "uploadFile" not supported on browser platform');
}, },
uploadFiles: function (success, failure, opts) {
return failure('advanced-http: function "uploadFiles" not supported on browser platform');
},
downloadFile: function (success, failure, opts) { downloadFile: function (success, failure, opts) {
return failure('advanced-http: function "downloadFile" not supported on browser platform'); return failure('advanced-http: function "downloadFile" not supported on browser platform');
}, },
@@ -189,9 +216,6 @@ var browserInterface = {
}, },
setClientAuthMode: function (success, failure, opts) { setClientAuthMode: function (success, failure, opts) {
return failure('advanced-http: function "setClientAuthMode" not supported on browser platform'); return failure('advanced-http: function "setClientAuthMode" not supported on browser platform');
},
disableRedirect: function (success, failure, opts) {
return failure('advanced-http: function "disableRedirect" not supported on browser platform');
} }
}; };
@@ -290,6 +290,64 @@ NS_ASSUME_NONNULL_BEGIN
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a multipart request using given HTTP method.
@param HTTPMethod The HTTP method used to create the request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol.
@param uploadProgress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
*/
- (nullable NSURLSessionDataTask *)uploadTaskWithHTTPMethod:(NSString *)HTTPMethod
URLString:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with given HTTP method.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param uploadProgress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
*/
- (nullable NSURLSessionDataTask *)uploadTaskWithHTTPMethod:(NSString *)HTTPMethod
URLString:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a given HTTP method.
@param HTTPMethod The HTTP method used to create the request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param downloadProgress A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
*/
- (nullable NSURLSessionDataTask *)downloadTaskWithHTTPMethod:(NSString *)HTTPMethod
URLString:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
+101
View File
@@ -223,6 +223,42 @@
return dataTask; return dataTask;
} }
- (NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
__block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(task, error);
}
} else {
if (success) {
success(task, responseObject);
}
}
}];
[task resume];
return task;
}
- (NSURLSessionDataTask *)PATCH:(NSString *)URLString - (NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(id)parameters parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
@@ -247,6 +283,71 @@
return dataTask; return dataTask;
} }
- (NSURLSessionDataTask *)uploadTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
__block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(task, error);
}
} else {
if (success) {
success(task, responseObject);
}
}
}];
[task resume];
return task;
}
- (NSURLSessionDataTask *)uploadTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:method URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
[dataTask resume];
return dataTask;
}
- (NSURLSessionDataTask *)downloadTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
progress:(nullable void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *task = [self dataTaskWithHTTPMethod:method URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:downloadProgress success:success failure:failure];
[task resume];
return task;
}
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString URLString:(NSString *)URLString
parameters:(id)parameters parameters:(id)parameters
@@ -308,4 +308,11 @@ FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseErrorK
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey; FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey;
/**
`AFNetworkingOperationFailingURLResponseBodyErrorKey`
The corresponding value is an `NSString` containing the decoded error message.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseBodyErrorKey;
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
@@ -34,6 +34,7 @@
NSString * const AFURLResponseSerializationErrorDomain = @"com.alamofire.error.serialization.response"; NSString * const AFURLResponseSerializationErrorDomain = @"com.alamofire.error.serialization.response";
NSString * const AFNetworkingOperationFailingURLResponseErrorKey = @"com.alamofire.serialization.response.error.response"; NSString * const AFNetworkingOperationFailingURLResponseErrorKey = @"com.alamofire.serialization.response.error.response";
NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey = @"com.alamofire.serialization.response.error.data"; NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey = @"com.alamofire.serialization.response.error.data";
NSString * const AFNetworkingOperationFailingURLResponseBodyErrorKey = @"com.alamofire.serialization.response.error.body";
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) { static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) { if (!error) {
@@ -525,7 +526,7 @@ static NSLock* imageLock = nil;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
imageLock = [[NSLock alloc] init]; imageLock = [[NSLock alloc] init];
}); });
[imageLock lock]; [imageLock lock];
image = [UIImage imageWithData:data]; image = [UIImage imageWithData:data];
[imageLock unlock]; [imageLock unlock];
@@ -539,7 +540,7 @@ static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
if (image.images) { if (image.images) {
return image; return image;
} }
return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation]; return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
} }
+8
View File
@@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>
#import "AFURLResponseSerialization.h"
@interface BinaryResponseSerializer : AFHTTPResponseSerializer
+ (instancetype)serializer;
@end
+126
View File
@@ -0,0 +1,126 @@
#import "BinaryResponseSerializer.h"
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
return underlyingError;
}
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES;
} else if (error.userInfo[NSUnderlyingErrorKey]) {
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
}
return NO;
}
@implementation BinaryResponseSerializer
+ (instancetype)serializer {
BinaryResponseSerializer *serializer = [[self alloc] init];
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = nil;
return self;
}
- (NSString*)decodeResponseData:(NSData*)rawResponseData withEncoding:(CFStringEncoding)cfEncoding {
NSStringEncoding nsEncoding;
NSString* decoded = nil;
if (cfEncoding != kCFStringEncodingInvalidId) {
nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
NSStringEncoding supportedEncodings[6] = {
NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding,
NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding
};
for (int i = 0; i < sizeof(supportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) {
if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == supportedEncodings[i]) {
decoded = [[NSString alloc] initWithData:rawResponseData encoding:supportedEncodings[i]];
}
}
return decoded;
}
- (CFStringEncoding) getEncoding:(NSURLResponse *)response {
CFStringEncoding encoding = kCFStringEncodingInvalidId;
if (response.textEncodingName) {
encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
}
return encoding;
}
#pragma mark -
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey: [response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
// trying to decode error message in body
mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] = [self decodeResponseData:data withEncoding:[self getEncoding:response]];
}
if (error) {
*error = [NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo];
}
return NO;
}
}
return YES;
}
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
return [data base64EncodedStringWithOptions:0];
}
@end
+1 -1
View File
@@ -10,7 +10,7 @@
- (void)put:(CDVInvokedUrlCommand*)command; - (void)put:(CDVInvokedUrlCommand*)command;
- (void)patch:(CDVInvokedUrlCommand*)command; - (void)patch:(CDVInvokedUrlCommand*)command;
- (void)delete:(CDVInvokedUrlCommand*)command; - (void)delete:(CDVInvokedUrlCommand*)command;
- (void)uploadFile:(CDVInvokedUrlCommand*)command; - (void)uploadFiles:(CDVInvokedUrlCommand*)command;
- (void)downloadFile:(CDVInvokedUrlCommand*)command; - (void)downloadFile:(CDVInvokedUrlCommand*)command;
@end @end
+169 -247
View File
@@ -1,5 +1,6 @@
#import "CordovaHttpPlugin.h" #import "CordovaHttpPlugin.h"
#import "CDVFile.h" #import "CDVFile.h"
#import "BinaryResponseSerializer.h"
#import "TextResponseSerializer.h" #import "TextResponseSerializer.h"
#import "TextRequestSerializer.h" #import "TextRequestSerializer.h"
#import "AFHTTPSessionManager.h" #import "AFHTTPSessionManager.h"
@@ -57,6 +58,15 @@
[manager.requestSerializer setTimeoutInterval:timeout]; [manager.requestSerializer setTimeoutInterval:timeout];
} }
- (void)setResponseSerializer:(NSString*)responseType forManager:(AFHTTPSessionManager*)manager {
if ([responseType isEqualToString: @"text"]) {
manager.responseSerializer = [TextResponseSerializer serializer];
} else {
manager.responseSerializer = [BinaryResponseSerializer serializer];
}
}
- (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data { - (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data {
if (response != nil) { if (response != nil) {
[dictionary setValue:response.URL.absoluteString forKey:@"url"]; [dictionary setValue:response.URL.absoluteString forKey:@"url"];
@@ -74,8 +84,8 @@
[dictionary setValue:response.URL.absoluteString forKey:@"url"]; [dictionary setValue:response.URL.absoluteString forKey:@"url"];
[dictionary setObject:[NSNumber numberWithInt:(int)response.statusCode] forKey:@"status"]; [dictionary setObject:[NSNumber numberWithInt:(int)response.statusCode] forKey:@"status"];
[dictionary setObject:[self copyHeaderFields:response.allHeaderFields] forKey:@"headers"]; [dictionary setObject:[self copyHeaderFields:response.allHeaderFields] forKey:@"headers"];
if (error.userInfo[AFNetworkingOperationFailingURLResponseBodyKey]) { if (error.userInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey]) {
[dictionary setObject:error.userInfo[AFNetworkingOperationFailingURLResponseBodyKey] forKey:@"error"]; [dictionary setObject:error.userInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] forKey:@"error"];
} }
} else { } else {
[dictionary setObject:[self getStatusCode:error] forKey:@"status"]; [dictionary setObject:[self getStatusCode:error] forKey:@"status"];
@@ -132,6 +142,142 @@
return headerFieldsCopy; return headerFieldsCopy;
} }
- (void)executeRequestWithoutData:(CDVInvokedUrlCommand*)command withMethod:(NSString*) method {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:3] boolValue];
NSString *responseType = [command.arguments objectAtIndex:4];
[self setRequestSerializer: @"default" forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
void (^onSuccess)(NSURLSessionTask *, id) = ^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
// no 'body' for HEAD request, omitting 'data'
if ([method isEqualToString:@"HEAD"]) {
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil];
} else {
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
}
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
};
void (^onFailure)(NSURLSessionTask *, NSError *) = ^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
};
[manager downloadTaskWithHTTPMethod:method URLString:url parameters:nil progress:nil success:onSuccess failure:onFailure];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
- (void)executeRequestWithData:(CDVInvokedUrlCommand*)command withMethod:(NSString*)method {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *data = [command.arguments objectAtIndex:1];
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
NSString *responseType = [command.arguments objectAtIndex:6];
[self setRequestSerializer: serializerName forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
void (^constructBody)(id<AFMultipartFormData>) = ^(id<AFMultipartFormData> formData) {
NSArray *buffers = [data mutableArrayValueForKey:@"buffers"];
NSArray *fileNames = [data mutableArrayValueForKey:@"fileNames"];
NSArray *names = [data mutableArrayValueForKey:@"names"];
NSArray *types = [data mutableArrayValueForKey:@"types"];
NSError *error;
for (int i = 0; i < [buffers count]; ++i) {
NSData *decodedBuffer = [[NSData alloc] initWithBase64EncodedString:[buffers objectAtIndex:i] options:0];
NSString *fileName = [fileNames objectAtIndex:i];
NSString *partName = [names objectAtIndex:i];
NSString *partType = [types objectAtIndex:i];
if (![fileName isEqual:[NSNull null]]) {
[formData appendPartWithFileData:decodedBuffer name:partName fileName:fileName mimeType:partType];
} else {
[formData appendPartWithFormData:decodedBuffer name:[names objectAtIndex:i]];
}
}
if (error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:[NSNumber numberWithInt:400] forKey:@"status"];
[dictionary setObject:@"Could not add part to multipart request body." forKey:@"error"];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
return;
}
};
void (^onSuccess)(NSURLSessionTask *, id) = ^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
};
void (^onFailure)(NSURLSessionTask *, NSError *) = ^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
};
if ([serializerName isEqualToString:@"multipart"]) {
[manager uploadTaskWithHTTPMethod:method URLString:url parameters:nil constructingBodyWithBlock:constructBody progress:nil success:onSuccess failure:onFailure];
} else {
[manager uploadTaskWithHTTPMethod:method URLString:url parameters:data progress:nil success:onSuccess failure:onFailure];
}
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
- (void)setServerTrustMode:(CDVInvokedUrlCommand*)command { - (void)setServerTrustMode:(CDVInvokedUrlCommand*)command {
NSString *certMode = [command.arguments objectAtIndex:0]; NSString *certMode = [command.arguments objectAtIndex:0];
@@ -154,282 +300,58 @@
} }
- (void)get:(CDVInvokedUrlCommand*)command { - (void)get:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [self executeRequestWithoutData: command withMethod:@"GET"];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:3] boolValue];
[self setRequestSerializer: @"default" forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager GET:url parameters:nil success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
}
- (void)head:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:3] boolValue];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager HEAD:url parameters:nil success:^(NSURLSessionTask *task) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
// no 'body' for HEAD request, omitting 'data'
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
} }
- (void)delete:(CDVInvokedUrlCommand*)command { - (void)delete:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [self executeRequestWithoutData: command withMethod:@"DELETE"];
manager.securityPolicy = securityPolicy; }
NSString *url = [command.arguments objectAtIndex:0]; - (void)head:(CDVInvokedUrlCommand*)command {
NSDictionary *headers = [command.arguments objectAtIndex:1]; [self executeRequestWithoutData: command withMethod:@"HEAD"];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:2] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:3] boolValue];
[self setRequestSerializer: @"default" forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager DELETE:url parameters:nil success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
} }
- (void)post:(CDVInvokedUrlCommand*)command { - (void)post:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [self executeRequestWithData: command withMethod:@"POST"];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *data = [command.arguments objectAtIndex:1];
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
[self setRequestSerializer: serializerName forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager POST:url parameters:data success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
} }
- (void)put:(CDVInvokedUrlCommand*)command { - (void)put:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [self executeRequestWithData: command withMethod:@"PUT"];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *data = [command.arguments objectAtIndex:1];
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
[self setRequestSerializer: serializerName forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager PUT:url parameters:data success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
} }
- (void)patch:(CDVInvokedUrlCommand*)command { - (void)patch:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [self executeRequestWithData: command withMethod:@"PATCH"];
manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *data = [command.arguments objectAtIndex:1];
NSString *serializerName = [command.arguments objectAtIndex:2];
NSDictionary *headers = [command.arguments objectAtIndex:3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
[self setRequestSerializer: serializerName forManager: manager];
[self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try {
[manager PATCH:url parameters:data success:^(NSURLSessionTask *task, id responseObject) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
[weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
}];
}
@catch (NSException *exception) {
[[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
[self handleException:exception withCommand:command];
}
} }
- (void)uploadFile:(CDVInvokedUrlCommand*)command { - (void)uploadFiles:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy; manager.securityPolicy = securityPolicy;
NSString *url = [command.arguments objectAtIndex:0]; NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1]; NSDictionary *headers = [command.arguments objectAtIndex:1];
NSString *filePath = [command.arguments objectAtIndex: 2]; NSArray *filePaths = [command.arguments objectAtIndex: 2];
NSString *name = [command.arguments objectAtIndex: 3]; NSArray *names = [command.arguments objectAtIndex: 3];
NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue]; NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue];
bool followRedirect = [[command.arguments objectAtIndex:5] boolValue]; bool followRedirect = [[command.arguments objectAtIndex:5] boolValue];
NSString *responseType = [command.arguments objectAtIndex:6];
NSURL *fileURL = [NSURL URLWithString: filePath];
[self setRequestHeaders: headers forManager: manager]; [self setRequestHeaders: headers forManager: manager];
[self setTimeout:timeoutInSeconds forManager:manager]; [self setTimeout:timeoutInSeconds forManager:manager];
[self setRedirect:followRedirect forManager:manager]; [self setRedirect:followRedirect forManager:manager];
[self setResponseSerializer:responseType forManager:manager];
CordovaHttpPlugin* __weak weakSelf = self; CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [TextResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity]; [[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try { @try {
[manager POST:url parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { [manager POST:url parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSError *error; NSError *error;
[formData appendPartWithFileURL:fileURL name:name error:&error]; for (int i = 0; i < [filePaths count]; i++) {
NSString *filePath = (NSString *) [filePaths objectAtIndex:i];
NSString *uploadName = (NSString *) [names objectAtIndex:i];
NSURL *fileURL = [NSURL URLWithString: filePath];
[formData appendPartWithFileURL:fileURL name:uploadName error:&error];
}
if (error) { if (error) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"]; [dictionary setObject:[NSNumber numberWithInt:500] forKey:@"status"];
@@ -464,6 +386,7 @@
- (void)downloadFile:(CDVInvokedUrlCommand*)command { - (void)downloadFile:(CDVInvokedUrlCommand*)command {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = securityPolicy; manager.securityPolicy = securityPolicy;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSString *url = [command.arguments objectAtIndex:0]; NSString *url = [command.arguments objectAtIndex:0];
NSDictionary *headers = [command.arguments objectAtIndex:1]; NSDictionary *headers = [command.arguments objectAtIndex:1];
@@ -480,11 +403,10 @@
} }
CordovaHttpPlugin* __weak weakSelf = self; CordovaHttpPlugin* __weak weakSelf = self;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity]; [[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
@try { @try {
[manager GET:url parameters:nil success:^(NSURLSessionTask *task, id responseObject) { [manager GET:url parameters:nil progress: nil success:^(NSURLSessionTask *task, id responseObject) {
/* /*
* *
* Licensed to the Apache Software Foundation (ASF) under one * Licensed to the Apache Software Foundation (ASF) under one
-2
View File
@@ -5,6 +5,4 @@
+ (instancetype)serializer; + (instancetype)serializer;
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseBodyKey;
@end @end
+10 -8
View File
@@ -1,8 +1,5 @@
#import "TextResponseSerializer.h" #import "TextResponseSerializer.h"
NSString * const AFNetworkingOperationFailingURLResponseBodyKey = @"com.alamofire.serialization.response.error.body";
NSStringEncoding const SupportedEncodings[6] = { NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding, NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding };
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) { static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) { if (!error) {
return underlyingError; return underlyingError;
@@ -55,9 +52,14 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co
nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); nsEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
} }
for (int i = 0; i < sizeof(SupportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) { NSStringEncoding supportedEncodings[6] = {
if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == SupportedEncodings[i]) { NSUTF8StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin1StringEncoding,
decoded = [[NSString alloc] initWithData:rawResponseData encoding:SupportedEncodings[i]]; NSISOLatin2StringEncoding, NSASCIIStringEncoding, NSUnicodeStringEncoding
};
for (int i = 0; i < sizeof(supportedEncodings) / sizeof(NSStringEncoding) && !decoded; ++i) {
if (cfEncoding == kCFStringEncodingInvalidId || nsEncoding == supportedEncodings[i]) {
decoded = [[NSString alloc] initWithData:rawResponseData encoding:supportedEncodings[i]];
} }
} }
@@ -94,7 +96,7 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co
NSURLErrorFailingURLErrorKey:[response URL], NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response, AFNetworkingOperationFailingURLResponseErrorKey: response,
AFNetworkingOperationFailingURLResponseDataErrorKey: data, AFNetworkingOperationFailingURLResponseDataErrorKey: data,
AFNetworkingOperationFailingURLResponseBodyKey: @"Could not decode response data due to invalid or unknown charset encoding", AFNetworkingOperationFailingURLResponseBodyErrorKey: @"Could not decode response data due to invalid or unknown charset encoding",
} mutableCopy]; } mutableCopy];
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError); validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
@@ -108,7 +110,7 @@ static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger co
if (data) { if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data; mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyKey] = *decoded; mutableUserInfo[AFNetworkingOperationFailingURLResponseBodyErrorKey] = *decoded;
} }
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError); validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
+1 -1
View File
@@ -24,7 +24,7 @@
</platform> </platform>
<engine name="android" spec="7.1.0" /> <engine name="android" spec="7.1.0" />
<engine name="browser" spec="5.0.0" /> <engine name="browser" spec="5.0.0" />
<engine name="ios" spec="4.4.0" /> <engine name="ios" spec="5.0.1" />
<plugin name="cordova-plugin-file" spec="6.0.1" /> <plugin name="cordova-plugin-file" spec="6.0.1" />
<preference name="AndroidPersistentFileLocation" value="Internal" /> <preference name="AndroidPersistentFileLocation" value="Internal" />
</widget> </widget>
+1 -1
View File
@@ -14,7 +14,7 @@
"cordova": "7.0.1", "cordova": "7.0.1",
"cordova-android": "7.1.0", "cordova-android": "7.1.0",
"cordova-browser": "5.0.0", "cordova-browser": "5.0.0",
"cordova-ios": "4.4.0" "cordova-ios": "5.0.1"
}, },
"cordova": { "cordova": {
"platforms": [ "platforms": [
+1 -1
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com https://self-signed.badssl.com http://httpbin.org http://www.columbia.edu 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;"> <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com https://self-signed.badssl.com http://httpbin.org http://www.columbia.edu 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content: blob:;">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no"> <meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width"> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
+21 -4
View File
@@ -3,8 +3,25 @@ const app = {
lastResult: null, lastResult: null,
testsFlaggedToRun: [],
initialize: function () { initialize: function () {
document.getElementById('nextBtn').addEventListener('click', app.onNextBtnClick); document.getElementById('nextBtn').addEventListener('click', app.onNextBtnClick);
var onlyFlaggedTests = [];
var enabledTests = [];
tests.forEach(function (test) {
if (test.only) {
onlyFlaggedTests.push(test);
}
if (!test.disabled) {
enabledTests.push(test);
}
});
app.testsFlaggedToRun = onlyFlaggedTests.length ? onlyFlaggedTests : enabledTests;
}, },
printResult: function (prefix, content) { printResult: function (prefix, content) {
@@ -47,9 +64,9 @@ const app = {
cb(app.lastResult); cb(app.lastResult);
}, },
runTest: function (index) { runTest: function (tests, index) {
const testDefinition = tests[index]; const testDefinition = tests[index];
const titleText = app.testIndex + ': ' + testDefinition.description; const titleText = index + ': ' + testDefinition.description;
const expectedText = 'expected - ' + testDefinition.expected; const expectedText = 'expected - ' + testDefinition.expected;
document.getElementById('statusInput').value = 'running'; document.getElementById('statusInput').value = 'running';
@@ -130,8 +147,8 @@ const app = {
onNextBtnClick: function () { onNextBtnClick: function () {
app.testIndex += 1; app.testIndex += 1;
if (app.testIndex < tests.length) { if (app.testIndex < app.testsFlaggedToRun.length) {
app.runTest(app.testIndex); app.runTest(app.testsFlaggedToRun, app.testIndex);
} else { } else {
app.onFinishedAllTests(); app.onFinishedAllTests();
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

+224 -3
View File
@@ -3,6 +3,11 @@ const hooks = {
cordova.plugin.http.clearCookies(); cordova.plugin.http.clearCookies();
helpers.enableFollowingRedirect(function() { helpers.enableFollowingRedirect(function() {
// server trust mode is not supported on brpwser platform
if (cordova.platformId === 'browser') {
return resolve();
}
helpers.setDefaultServerTrustMode(function () { helpers.setDefaultServerTrustMode(function () {
// @TODO: not ready yet // @TODO: not ready yet
// helpers.setNoneClientAuthMode(resolve, reject); // helpers.setNoneClientAuthMode(resolve, reject);
@@ -28,6 +33,7 @@ const helpers = {
setJsonSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('json')); }, setJsonSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('json')); },
setUtf8StringSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('utf8')); }, setUtf8StringSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('utf8')); },
setUrlEncodedSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('urlencoded')); }, setUrlEncodedSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('urlencoded')); },
setMultipartSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('multipart')); },
disableFollowingRedirect: function (resolve) { resolve(cordova.plugin.http.setFollowRedirect(false)); }, disableFollowingRedirect: function (resolve) { resolve(cordova.plugin.http.setFollowRedirect(false)); },
enableFollowingRedirect: function(resolve) { resolve(cordova.plugin.http.setFollowRedirect(true)); }, enableFollowingRedirect: function(resolve) { resolve(cordova.plugin.http.setFollowRedirect(true)); },
getWithXhr: function (done, url, type) { getWithXhr: function (done, url, type) {
@@ -57,6 +63,25 @@ const helpers = {
}, done); }, done);
}, done); }, done);
}, done); }, done);
},
// adopted from: https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
hashArrayBuffer: function (buffer) {
var hash = 0;
var byteArray = new Uint8Array(buffer);
for (var i = 0; i < byteArray.length; i++) {
hash = ((hash << 5) - hash) + byteArray[i];
hash |= 0; // Convert to 32bit integer
}
return hash;
},
checkResult: function (result, expected) {
if (result.type === 'throwed' && expected !== 'throwed') {
throw new Error('Expected function not to throw: ' + result.message);
}
result.type.should.be.equal(expected);
} }
}; };
@@ -332,6 +357,43 @@ const tests = [
.should.be.equal(fileContent); .should.be.equal(fileContent);
} }
}, },
{
description: 'should upload multiple files from given paths in local filesystem to given URL #127',
expected: 'resolved: {"status": 200, "data": "files": {"test-file.txt": "I am a dummy file. I am used ...',
func: function (resolve, reject) {
var fileName = 'test-file.txt';
var fileName2 = 'test-file2.txt';
var fileContent = 'I am a dummy file. I am used for testing purposes!';
var fileContent2 = 'I am the second dummy file. I am used for testing purposes!';
var sourcePath = cordova.file.cacheDirectory + fileName;
var sourcePath2 = cordova.file.cacheDirectory + fileName2;
var targetUrl = 'http://httpbin.org/post';
helpers.writeToFile(function () {
helpers.writeToFile(function() {
cordova.plugin.http.uploadFile(targetUrl, {}, {}, [sourcePath, sourcePath2], [fileName, fileName2], resolve, reject);
}, fileName2, fileContent2);
}, fileName, fileContent);
},
validationFunc: function (driver, result) {
var fileName = 'test-file.txt';
var fileName2 = 'test-file2.txt';
var fileContent = 'I am a dummy file. I am used for testing purposes!';
var fileContent2 = 'I am the second dummy file. I am used for testing purposes!';
result.type.should.be.equal('resolved');
result.data.data.should.be.a('string');
var parsed = JSON.parse(result.data.data);
parsed.files[fileName].should.be.equal(fileContent);
parsed.files[fileName2].should.be.equal(fileContent2);
}
},
{ {
description: 'should encode HTTP array params correctly (GET) #45', description: 'should encode HTTP array params correctly (GET) #45',
expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"http://httpbin.org/get?myArray[]=val1&myArray[]=val2&myArray[]=val3\\"}\" ...', expected: 'resolved: {"status": 200, "data": "{\\"url\\":\\"http://httpbin.org/get?myArray[]=val1&myArray[]=val2&myArray[]=val3\\"}\" ...',
@@ -356,7 +418,7 @@ const tests = [
}, },
validationFunc: function (driver, result) { validationFunc: function (driver, result) {
result.type.should.be.equal('throwed'); result.type.should.be.equal('throwed');
result.message.should.be.equal('advanced-http: header values must be strings'); result.message.should.be.equal(require('../www/messages').TYPE_MISMATCH_HEADERS);
} }
}, },
{ {
@@ -367,7 +429,7 @@ const tests = [
}, },
validationFunc: function (driver, result) { validationFunc: function (driver, result) {
result.type.should.be.equal('throwed'); result.type.should.be.equal('throwed');
result.message.should.be.equal('advanced-http: header values must be strings'); result.message.should.be.equal(require('../www/messages').INVALID_HEADER_VALUE);
} }
}, },
{ {
@@ -624,7 +686,166 @@ const tests = [
result.data.content.should.be.equal("<?xml version='1.0' encoding='us-ascii'?>\n\n<!-- A SAMPLE set of slides -->\n\n<slideshow \n title=\"Sample Slide Show\"\n date=\"Date of publication\"\n author=\"Yours Truly\"\n >\n\n <!-- TITLE SLIDE -->\n <slide type=\"all\">\n <title>Wake up to WonderWidgets!</title>\n </slide>\n\n <!-- OVERVIEW -->\n <slide type=\"all\">\n <title>Overview</title>\n <item>Why <em>WonderWidgets</em> are great</item>\n <item/>\n <item>Who <em>buys</em> WonderWidgets</item>\n </slide>\n\n</slideshow>"); result.data.content.should.be.equal("<?xml version='1.0' encoding='us-ascii'?>\n\n<!-- A SAMPLE set of slides -->\n\n<slideshow \n title=\"Sample Slide Show\"\n date=\"Date of publication\"\n author=\"Yours Truly\"\n >\n\n <!-- TITLE SLIDE -->\n <slide type=\"all\">\n <title>Wake up to WonderWidgets!</title>\n </slide>\n\n <!-- OVERVIEW -->\n <slide type=\"all\">\n <title>Overview</title>\n <item>Why <em>WonderWidgets</em> are great</item>\n <item/>\n <item>Who <em>buys</em> WonderWidgets</item>\n </slide>\n\n</slideshow>");
} }
}, },
// @TODO: not ready yet {
description: 'should return header object when request failed due to non-success response from server #221',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://httpbin.org/status/418', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.headers.should.be.an('object');
}
},
{
description: 'should return status code when request failed due to non-success response from server',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://httpbin.org/status/418', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.status.should.be.equal(418);
}
},
{
description: 'should return url string when request failed due to non-success response from server',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://httpbin.org/status/418', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.url.should.be.equal('https://httpbin.org/status/418');
}
},
{
description: 'shouldn\'t return header object when request failed before receiving response from server',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://not_existing_url', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
should.equal(result.data.headers, undefined);
}
},
{
description: 'should return status code when request failed before receiving response from server',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://not_existing_url', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.status.should.be.a('number');
}
},
{
description: 'shouldn\'t return url string when request failed before receiving response from server',
expected: 'rejected:',
func: function (resolve, reject) { cordova.plugin.http.get('https://not_existing_url', {}, {}, resolve, reject); },
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
should.equal(result.data.url, undefined);
}
},
{
description: 'should fetch binary correctly when response type is "arraybuffer"',
expected: 'resolved: {"isArrayBuffer:true,"hash":-1032603775,"byteLength":35588}',
func: function (resolve, reject) {
var url = 'https://httpbin.org/image/jpeg';
var options = { method: 'get', responseType: 'arraybuffer' };
var success = function (response) {
resolve({
isArrayBuffer: response.data.constructor === ArrayBuffer,
hash: helpers.hashArrayBuffer(response.data),
byteLength: response.data.byteLength
});
};
cordova.plugin.http.sendRequest(url, options, success, reject);
},
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.isArrayBuffer.should.be.equal(true);
result.data.hash.should.be.equal(-1032603775);
result.data.byteLength.should.be.equal(35588);
}
},
{
description: 'should fetch binary correctly when response type is "blob"',
expected: 'resolved: {"isBlob":true,byteLength":35588}',
func: function (resolve, reject) {
var url = 'https://httpbin.org/image/jpeg';
var options = { method: 'get', responseType: 'blob' };
var success = function (response) {
resolve({
isBlob: response.data.constructor === Blob,
type: response.data.type,
byteLength: response.data.size
});
};
cordova.plugin.http.sendRequest(url, options, success, reject);
},
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.isBlob.should.be.equal(true);
result.data.type.should.be.equal('image/jpeg');
result.data.byteLength.should.be.equal(35588);
}
},
{
description: 'should decode error body even if response type is "arraybuffer"',
expected: 'rejected: {"status": 418, ...',
func: function (resolve, reject) {
var url = 'https://httpbin.org/status/418';
var options = { method: 'get', responseType: 'arraybuffer' };
cordova.plugin.http.sendRequest(url, options, resolve, reject);
},
validationFunc: function (driver, result) {
result.type.should.be.equal('rejected');
result.data.status.should.be.equal(418);
result.data.error.should.be.equal("\n -=[ teapot ]=-\n\n _...._\n .' _ _ `.\n | .\"` ^ `\". _,\n \\_;`\"---\"`|//\n | ;/\n \\_ _/\n `\"\"\"`\n");
}
},
{
description: 'should serialize FormData instance correctly when it contains string value',
expected: 'resolved: {"status": 200, ...',
before: helpers.setMultipartSerializer,
func: function (resolve, reject) {
var ponyfills = cordova.plugin.http.ponyfills;
var formData = new ponyfills.FormData();
formData.append('myString', 'This is a test!');
var url = 'https://httpbin.org/anything';
var options = { method: 'post', data: formData };
cordova.plugin.http.sendRequest(url, options, resolve, reject);
},
validationFunc: function (driver, result) {
helpers.checkResult(result, 'resolved');
result.data.status.should.be.equal(200);
JSON.parse(result.data.data).form.should.be.eql({ myString: 'This is a test!' });
}
},
{
description: 'should serialize FormData instance correctly when it contains blob value',
expected: 'resolved: {"status": 200, ...',
before: helpers.setMultipartSerializer,
func: function (resolve, reject) {
var ponyfills = cordova.plugin.http.ponyfills;
helpers.getWithXhr(function(blob) {
var formData = new ponyfills.FormData();
formData.append('CordovaLogo', blob);
var url = 'https://httpbin.org/anything';
var options = { method: 'post', data: formData };
cordova.plugin.http.sendRequest(url, options, resolve, reject);
}, './res/cordova_logo.png', 'blob');
},
validationFunc: function (driver, result) {
helpers.checkResult(result, 'resolved');
result.data.status.should.be.equal(200);
// httpbin.org encodes posted binaries in base64 and echoes them back
// therefore we need to check for base64 string with mime type prefix
const fs = require('fs');
const rawLogo = fs.readFileSync('./test/e2e-app-template/www/res/cordova_logo.png');
const b64Logo = rawLogo.toString('base64');
JSON.parse(result.data.data).files.CordovaLogo.should.be.equal('data:image/png;base64,' + b64Logo);
}
}
// TODO: not ready yet
// { // {
// description: 'should authenticate correctly when client cert auth is configured with a PKCS12 container', // description: 'should authenticate correctly when client cert auth is configured with a PKCS12 container',
// expected: 'resolved: {"status": 200, ...', // expected: 'resolved: {"status": 200, ...',
+73
View File
@@ -0,0 +1,73 @@
module.exports = { getCaps };
const path = require('path');
const configs = {
// testing on local machine
localIosDevice: {
platformName: 'iOS',
platformVersion: '10.3',
automationName: 'XCUITest',
deviceName: 'iPhone 8',
autoWebview: true,
app: path.resolve('temp/platforms/ios/build/emulator/HttpDemo.app')
},
localIosEmulator: {
platformName: 'iOS',
platformVersion: '13.2',
automationName: 'XCUITest',
deviceName: 'iPhone 8',
autoWebview: true,
app: path.resolve('temp/platforms/ios/build/emulator/HttpDemo.app')
},
localAndroidEmulator: {
platformName: 'Android',
platformVersion: '5',
deviceName: 'Android Emulator',
autoWebview: true,
fullReset: true,
app: path.resolve('temp/platforms/android/app/build/outputs/apk/debug/app-debug.apk')
},
// testing on SauceLabs
saucelabsIosDevice: {
browserName: '',
'appium-version': '1.9.1',
platformName: 'iOS',
platformVersion: '10.3',
deviceName: 'iPhone 6',
autoWebview: true,
app: 'sauce-storage:HttpDemo.app.zip'
},
saucelabsIosEmulator: {
browserName: '',
'appium-version': '1.9.1',
platformName: 'iOS',
platformVersion: '10.3',
deviceName: 'iPhone Simulator',
autoWebview: true,
app: 'sauce-storage:HttpDemo.app.zip'
},
saucelabsAndroidEmulator: {
browserName: '',
'appium-version': '1.9.1',
platformName: 'Android',
platformVersion: '5.1',
deviceName: 'Android Emulator',
autoWebview: true,
app: 'sauce-storage:HttpDemo.apk'
}
};
function getCaps(environment, os, runtime) {
const key = environment.toLowerCase() + capitalize(os) + capitalize(runtime);
const caps = configs[key];
caps.name = `cordova-plugin-advanced-http (${os})`;
return caps;
};
function capitalize(text) {
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
}
-10
View File
@@ -1,10 +0,0 @@
const path = require('path');
if (process.env.SAUCE_USERNAME) {
exports.iosTestApp = 'sauce-storage:HttpDemo.app.zip';
exports.androidTestApp = 'sauce-storage:HttpDemo.apk';
} else {
// these paths are relative to working directory
exports.iosTestApp = path.resolve('temp/platforms/ios/build/emulator/HttpDemo.app');
exports.androidTestApp = path.resolve('temp/platforms/android/app/build/outputs/apk/debug/app-debug.apk');
}
-60
View File
@@ -1,60 +0,0 @@
const local = {
iosDevice: {
platformName: 'iOS',
platformVersion: '10.3',
deviceName: 'iPhone 6',
autoWebview: true,
app: undefined // will be set later
},
iosEmulator: {
platformName: 'iOS',
platformVersion: '11.0',
deviceName: 'iPhone Simulator',
autoWebview: true,
app: undefined // will be set later
},
androidEmulator: {
platformName: 'Android',
platformVersion: '5.1',
deviceName: 'Android Emulator',
autoWebview: true,
fullReset: true,
app: undefined // will be set later
}
};
const sauce = {
iosDevice: {
browserName: '',
'appium-version': '1.7.1',
platformName: 'iOS',
platformVersion: '10.3',
deviceName: 'iPhone 6',
autoWebview: true,
app: undefined // will be set later
},
iosEmulator: {
browserName: '',
'appium-version': '1.7.1',
platformName: 'iOS',
platformVersion: '10.3',
deviceName: 'iPhone Simulator',
autoWebview: true,
app: undefined // will be set later
},
androidEmulator: {
browserName: '',
'appium-version': '1.7.1',
platformName: 'Android',
platformVersion: '5.1',
deviceName: 'Android Emulator',
autoWebview: true,
app: undefined // will be set later
}
};
if (process.env.SAUCE_USERNAME) {
module.exports = sauce;
} else {
module.exports = local;
}
-16
View File
@@ -1,16 +0,0 @@
const local = {
host: 'localhost',
port: 4723
};
const sauce = {
host: 'ondemand.saucelabs.com',
port: 80,
auth: process.env.SAUCE_USERNAME + ":" + process.env.SAUCE_ACCESS_KEY
};
if (process.env.SAUCE_USERNAME) {
module.exports = sauce;
} else {
module.exports = local;
}
-12
View File
@@ -1,12 +0,0 @@
const wd = require("wd");
require('colors');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const should = chai.should();
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
exports.should = should;
@@ -1,4 +1,8 @@
exports.configure = driver => { module.exports = { setupLogging };
function setupLogging(driver) {
require('colors');
driver.on('status', info => { driver.on('status', info => {
console.log(info.cyan); console.log(info.cyan);
}); });
@@ -10,4 +14,4 @@ exports.configure = driver => {
driver.on('http', (meth, path, data) => { driver.on('http', (meth, path, data) => {
console.log(' > ' + meth.magenta, path, (data || '').grey); console.log(' > ' + meth.magenta, path, (data || '').grey);
}); });
}; }
+17
View File
@@ -0,0 +1,17 @@
module.exports = { getServer };
const configs = {
local: {
host: 'localhost',
port: 4723
},
saucelabs: {
host: 'ondemand.saucelabs.com',
port: 80,
auth: process.env.SAUCE_USERNAME + ":" + process.env.SAUCE_ACCESS_KEY
}
}
function getServer(environment) {
return configs[environment.toLowerCase()];
}
+106 -76
View File
@@ -1,94 +1,124 @@
require('./helpers/setup');
const wd = require('wd'); const wd = require('wd');
const apps = require('./helpers/apps'); const chai = require('chai');
const caps = Object.assign({}, require('./helpers/caps')); const chaiAsPromised = require('chai-as-promised');
const serverConfig = require('./helpers/server'); const logging = require('./logging');
const capsConfig = require('./caps');
const serverConfig = require('./server');
const testDefinitions = require('../e2e-specs'); const testDefinitions = require('../e2e-specs');
const pkgjson = require('../../package.json');
describe('Advanced HTTP', function() { chai.use(chaiAsPromised);
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
global.should = chai.should();
describe('Advanced HTTP e2e test suite', function () {
const isSauceLabs = !!process.env.SAUCE_USERNAME;
const isVerbose = process.argv.includes('--verbose');
const isDevice = process.argv.includes('--device'); const isDevice = process.argv.includes('--device');
const isAndroid = process.argv.includes('--android'); const isAndroid = process.argv.includes('--android');
const targetInfo = { isDevice, isAndroid };
let driver = null; const targetInfo = { isSauceLabs, isDevice, isAndroid };
const environment = isSauceLabs ? 'saucelabs' : 'local';
let driver;
let allPassed = true; let allPassed = true;
this.timeout(900000); this.timeout(15000);
this.slow(4000);
const getCaps = appName => { before(async function () {
const desiredOs = isAndroid ? 'android' : 'ios'; // connecting to saucelabs can take some time
const desiredCaps = caps[desiredOs + (isDevice ? 'Device' : 'Emulator')]; this.timeout(300000);
const desiredApp = apps[desiredOs + appName];
desiredCaps.name = pkgjson.name + ` (${desiredOs})`; driver = await wd.promiseChainRemote(serverConfig.getServer(environment));
desiredCaps.app = desiredApp;
return desiredCaps; if (isVerbose) {
logging.setupLogging(driver);
}
await driver.init(
capsConfig.getCaps(
environment,
isAndroid ? 'android' : 'ios',
isDevice ? 'device' : 'emulator'
)
);
});
after(async function () {
await driver.quit().finally(
() => isSauceLabs && driver.sauceJobStatus(allPassed)
);
});
const defineTestForMocha = (test, index) => {
it(index + ': ' + test.description, async () => {
await clickNext(driver);
await validateTestIndex(driver, index);
await validateTestTitle(driver, test.description);
await waitToBeFinished(driver, test.timeout || 10000);
await validateResult(driver, test.validationFunc, targetInfo);
});
}; };
const validateTestIndex = number => driver const onlyFlaggedTests = [];
.elementById('descriptionLbl') const enabledTests = [];
.text()
.then(text => parseInt(text.match(/(\d+):/)[1], 10))
.should.eventually.become(number, 'Test index is not matching!');
const validateTestTitle = testTitle => driver testDefinitions.tests.forEach(test => {
.elementById('descriptionLbl') if (test.only) {
.text() onlyFlaggedTests.push(test);
.then(text => text.match(/\d+:\ (.*)/)[1]) }
.should.eventually.become(testTitle, 'Test description is not matching!');
const waitToBeFinished = timeout => new Promise((resolve, reject) => { if (!test.disabled) {
const timeoutTimestamp = Date.now() + timeout; enabledTests.push(test);
const checkIfFinished = () => driver }
.elementById('statusInput')
.getValue()
.then(value => {
if (value === 'finished') {
resolve();
} else if (Date.now() > timeoutTimestamp) {
reject('Test function timed out!');
} else {
setTimeout(checkIfFinished, 500);
}
});
checkIfFinished();
}); });
const validateResult = testDefinition => driver if (onlyFlaggedTests.length) {
.safeExecute('app.lastResult') onlyFlaggedTests.forEach(defineTestForMocha);
.then(result => testDefinition.validationFunc(driver, result, targetInfo)); } else {
enabledTests.forEach(defineTestForMocha);
const clickNext = () => driver }
.elementById('nextBtn')
.click()
.sleep(1000);
before(() => {
driver = wd.promiseChainRemote(serverConfig);
require('./helpers/logging').configure(driver);
return driver.init(getCaps('TestApp'));
});
after(() => driver
.quit()
.finally(function () {
if (process.env.SAUCE_USERNAME) {
return driver.sauceJobStatus(allPassed);
}
}));
testDefinitions.tests.forEach((definition, index) => {
it(index + ': ' + definition.description, function() {
return clickNext()
.then(() => validateTestIndex(index))
.then(() => validateTestTitle(definition.description))
.then(() => waitToBeFinished(definition.timeout || 10000))
.then(() => validateResult(definition))
});
});
}); });
async function clickNext(driver) {
await driver.elementById('nextBtn').click().sleep(1000);
}
async function validateTestIndex(driver, testIndex) {
const description = await driver.elementById('descriptionLbl').text();
const index = parseInt(description.match(/(\d+):/)[1], 10);
index.should.be.equal(testIndex, 'Test index is not matching!');
}
async function validateTestTitle(driver, testTitle) {
const description = await driver.elementById('descriptionLbl').text();
const title = description.match(/\d+:\ (.*)/)[1];
title.should.be.equal(testTitle, 'Test description is not matching!');
}
async function waitToBeFinished(driver, timeout) {
const timeoutTimestamp = Date.now() + timeout;
while (true) {
if (await driver.elementById('statusInput').getValue() === 'finished') {
return true;
}
if (Date.now() > timeoutTimestamp) {
throw new Error('Test function timed out!');
}
await sleep(500);
}
}
async function validateResult(driver, validationFunc, targetInfo) {
const result = await driver.safeExecute('app.lastResult');
validationFunc(driver, result, targetInfo);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
+384 -14
View File
@@ -1,9 +1,17 @@
const chai = require('chai'); const chai = require('chai');
const mock = require('mock-require'); const mock = require('mock-require');
const util = require('util');
const should = chai.should(); const should = chai.should();
const BlobMock = require('./mocks/Blob.mock');
const ConsoleMock = require('./mocks/Console.mock');
const FileMock = require('./mocks/File.mock');
const FileReaderMock = require('./mocks/FileReader.mock');
const FormDataMock = require('./mocks/FormData.mock');
describe('Advanced HTTP public interface', function () { describe('Advanced HTTP public interface', function () {
const messages = require('../www/messages'); const messages = require('../www/messages');
let http = {}; let http = {};
const noop = () => { /* intentionally doing nothing */ }; const noop = () => { /* intentionally doing nothing */ };
@@ -13,21 +21,22 @@ describe('Advanced HTTP public interface', function () {
const jsUtil = require('../www/js-util'); const jsUtil = require('../www/js-util');
const ToughCookie = require('../www/umd-tough-cookie'); const ToughCookie = require('../www/umd-tough-cookie');
const lodash = require('../www/lodash'); const lodash = require('../www/lodash');
const errorCodes = require('../www/error-codes');
const WebStorageCookieStore = require('../www/local-storage-store')(ToughCookie, lodash); const WebStorageCookieStore = require('../www/local-storage-store')(ToughCookie, lodash);
const cookieHandler = require('../www/cookie-handler')(null, ToughCookie, WebStorageCookieStore); const cookieHandler = require('../www/cookie-handler')(null, ToughCookie, WebStorageCookieStore);
const helpers = require('../www/helpers')(jsUtil, cookieHandler, messages); const helpers = require('../www/helpers')(null, jsUtil, cookieHandler, messages, errorCodes);
const urlUtil = require('../www/url-util')(jsUtil); const urlUtil = require('../www/url-util')(jsUtil);
return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs }; return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs, errorCodes };
}; };
const loadHttp = (deps) => { const loadHttp = (deps) => {
http = require('../www/public-interface')(deps.exec, deps.cookieHandler, deps.urlUtil, deps.helpers, deps.globalConfigs); http = require('../www/public-interface')(deps.exec, deps.cookieHandler, deps.urlUtil, deps.helpers, deps.globalConfigs, deps.errorCodes);
}; };
beforeEach(() => { beforeEach(() => {
// mocked btoa function (base 64 encoding strings) // mocked btoa function (base 64 encoding strings)
global.btoa = decoded => new Buffer(decoded).toString('base64'); global.btoa = decoded => Buffer.from(decoded).toString('base64');
loadHttp(getDependenciesBlueprint()); loadHttp(getDependenciesBlueprint());
}); });
@@ -127,7 +136,7 @@ describe('Advanced HTTP public interface', function () {
}); });
it('throws an Error when you try to add a cookie by using "setHeader" #46', () => { it('throws an Error when you try to add a cookie by using "setHeader" #46', () => {
(function () { http.setHeader('*', 'cookie', 'value'); }).should.throw(); (() => { http.setHeader('*', 'cookie', 'value'); }).should.throw(messages.ADDING_COOKIES_NOT_SUPPORTED);
}); });
it('configures global timeout value correctly with given valid value', () => { it('configures global timeout value correctly with given valid value', () => {
@@ -136,7 +145,7 @@ describe('Advanced HTTP public interface', function () {
}); });
it('throws an Error when you try to configure global timeout with a string', () => { it('throws an Error when you try to configure global timeout with a string', () => {
(function () { http.setRequestTimeout('myString'); }).should.throw(messages.INVALID_TIMEOUT_VALUE); (() => { http.setRequestTimeout('myString'); }).should.throw(messages.INVALID_TIMEOUT_VALUE);
}); });
it('sets global option for following redirects correctly', () => { it('sets global option for following redirects correctly', () => {
@@ -145,7 +154,11 @@ describe('Advanced HTTP public interface', function () {
}); });
it('throws an Error when you try to configure global option for following redirects with a string', () => { it('throws an Error when you try to configure global option for following redirects with a string', () => {
(function () { http.setFollowRedirect('myString'); }).should.throw(messages.INVALID_FOLLOW_REDIRECT_VALUE); (() => { http.setFollowRedirect('myString'); }).should.throw(messages.INVALID_FOLLOW_REDIRECT_VALUE);
});
it('exposes an enumeration style object with mappings for the error codes', () => {
Object.keys(http.ErrorCode).forEach(key => http.ErrorCode[key].should.be.a('number'));
}); });
}); });
@@ -259,13 +272,13 @@ describe('Common helpers', function () {
const init = require('../www/helpers'); const init = require('../www/helpers');
init.debug = true; init.debug = true;
const helpers = init(null, null, null); const helpers = init(null, null, null, null, null, null);
it('merges empty header sets correctly', () => { it('merges empty header sets correctly', () => {
helpers.mergeHeaders({}, {}).should.eql({}); helpers.mergeHeaders({}, {}).should.eql({});
}); });
it('merges ssimple header sets without collision correctly', () => { it('merges simple header sets without collision correctly', () => {
helpers.mergeHeaders({ a: 1 }, { b: 2 }).should.eql({ a: 1, b: 2 }); helpers.mergeHeaders({ a: 1 }, { b: 2 }).should.eql({ a: 1, b: 2 });
}); });
@@ -276,13 +289,13 @@ describe('Common helpers', function () {
describe('getCookieHeader(url)', function () { describe('getCookieHeader(url)', function () {
it('resolves cookie header correctly when no cookie is set #198', () => { it('resolves cookie header correctly when no cookie is set #198', () => {
const helpers = require('../www/helpers')(null, { getCookieString: () => '' }, null); const helpers = require('../www/helpers')(null, null, { getCookieString: () => '' }, null);
helpers.getCookieHeader('http://ilkimen.net').should.eql({}); helpers.getCookieHeader('http://ilkimen.net').should.eql({});
}); });
it('resolves cookie header correctly when a cookie is set', () => { it('resolves cookie header correctly when a cookie is set', () => {
const helpers = require('../www/helpers')(null, { getCookieString: () => 'cookie=value' }, null); const helpers = require('../www/helpers')(null, null, { getCookieString: () => 'cookie=value' }, null);
helpers.getCookieHeader('http://ilkimen.net').should.eql({ Cookie: 'cookie=value' }); helpers.getCookieHeader('http://ilkimen.net').should.eql({ Cookie: 'cookie=value' });
}); });
@@ -291,7 +304,7 @@ describe('Common helpers', function () {
describe('checkClientAuthOptions()', function () { describe('checkClientAuthOptions()', function () {
const jsUtil = require('../www/js-util'); const jsUtil = require('../www/js-util');
const messages = require('../www/messages'); const messages = require('../www/messages');
const helpers = require('../www/helpers')(jsUtil, null, messages); const helpers = require('../www/helpers')(null, jsUtil, null, messages);
it('returns options object with empty values when mode is "none" and no options are given', () => { it('returns options object with empty values when mode is "none" and no options are given', () => {
helpers.checkClientAuthOptions('none').should.eql({ helpers.checkClientAuthOptions('none').should.eql({
@@ -356,7 +369,7 @@ describe('Common helpers', function () {
describe('handleMissingOptions()', function () { describe('handleMissingOptions()', function () {
const jsUtil = require('../www/js-util'); const jsUtil = require('../www/js-util');
const messages = require('../www/messages'); const messages = require('../www/messages');
const helpers = require('../www/helpers')(jsUtil, null, messages); const helpers = require('../www/helpers')(null, jsUtil, null, messages);
const mockGlobals = { const mockGlobals = {
headers: {}, headers: {},
serializer: 'urlencoded', serializer: 'urlencoded',
@@ -373,4 +386,361 @@ describe('Common helpers', function () {
.should.throw(messages.INVALID_FOLLOW_REDIRECT_VALUE); .should.throw(messages.INVALID_FOLLOW_REDIRECT_VALUE);
}); });
}); });
})
describe('injectRawResponseHandler()', function () {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const errorCodes = require('../www/error-codes');
const fakeBase64 = { toArrayBuffer: () => 'fakeArrayBuffer' };
global.Blob = function (array, meta) {
this.isFakeBlob = true;
this.array = array;
this.meta = meta;
};
it('does not change response data if it is an ArrayBuffer', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
const buffer = new ArrayBuffer(5);
const handler = helpers.injectRawResponseHandler(
'arraybuffer',
response => response.data.should.be.equal(buffer)
);
handler({ data: buffer });
});
it('does not change response data if it is a Blob', () => {
const fakeJsUtil = { getTypeOf: () => 'Blob' };
const helpers = require('../www/helpers')(null, fakeJsUtil, null, messages, null, errorCodes);
const handler = helpers.injectRawResponseHandler(
'blob',
response => response.data.should.be.equal('fakeData')
);
handler({ data: 'fakeData' });
});
it('does not change response data if response type is "text"', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
const example = 'exampleText';
const handler = helpers.injectRawResponseHandler(
'text',
response => response.data.should.be.equal(example)
);
handler({ data: example });
});
it('handles response type "json" correctly', () => {
const fakeData = { myString: 'bla', myNumber: 10 };
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
const handler = helpers.injectRawResponseHandler(
'json',
response => response.data.should.be.eql(fakeData)
);
handler({ data: JSON.stringify(fakeData) });
});
it('handles response type "arraybuffer" correctly', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'arraybuffer',
response => response.data.should.be.equal('fakeArrayBuffer')
);
handler({ data: 'myString' });
});
it('handles response type "blob" correctly', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'blob',
(response) => {
response.data.isFakeBlob.should.be.equal(true);
response.data.array.should.be.eql(['fakeArrayBuffer']);
response.data.meta.type.should.be.equal('fakeType');
}
);
handler({ data: 'myString', headers: { 'content-type': 'fakeType'} });
});
it('calls failure callback when post-processing fails', () => {
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
const handler = helpers.injectRawResponseHandler(
'json',
null,
(response) => {
response.status.should.be.equal(errorCodes.POST_PROCESSING_FAILED);
response.error.should.include('Unexpected token N in JSON at position 0');
}
);
handler({ data: 'NotValidJson' });
});
});
describe('checkUploadFileOptions()', function () {
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, null);
it('checks valid file options correctly', () => {
const opts = {
filePaths: ['file://path/to/file.png'],
names: ['ScreenCapture']
};
// string values
helpers.checkUploadFileOptions(opts.filePaths[0], opts.names[0]).should.be.eql(opts);
// string array values
helpers.checkUploadFileOptions(opts.filePaths, opts.names).should.be.eql(opts);
});
it('throws an error when file options are missing', () => {
(() => helpers.checkUploadFileOptions(undefined, ['ScreenCapture'])).should.throw(messages.FILE_PATHS_TYPE_MISMATCH);
(() => helpers.checkUploadFileOptions(['file://path/to/file.png'], undefined)).should.throw(messages.NAMES_TYPE_MISMATCH);
});
it('throws an error when file options contains empty arrays', () => {
(() => helpers.checkUploadFileOptions([], ['ScreenCapture'])).should.throw(messages.EMPTY_FILE_PATHS);
(() => helpers.checkUploadFileOptions(['file://path/to/file.png'], [])).should.throw(messages.EMPTY_NAMES);
});
it('throws an error when file options contains invalid values', () => {
(() => helpers.checkUploadFileOptions([1], ['ScreenCapture'])).should.throw(messages.FILE_PATHS_TYPE_MISMATCH);
(() => helpers.checkUploadFileOptions(['file://path/to/file.png'], [1])).should.throw(messages.NAMES_TYPE_MISMATCH);
});
});
describe('processData()', function () {
const mockWindow = {
Blob: BlobMock,
File: FileMock,
FileReader: FileReaderMock,
FormData: FormDataMock,
TextEncoder: util.TextEncoder,
}
const base64 = { fromArrayBuffer: ab => Buffer.from(ab).toString('base64') };
const jsUtil = require('../www/js-util');
const messages = require('../www/messages');
const dependencyValidator = require('../www/dependency-validator')(mockWindow, null, messages);
const helpers = require('../www/helpers')(mockWindow, jsUtil, null, messages, base64, null, dependencyValidator, {});
const testString = 'Test String öäüß 👍😉';
const testStringBase64 = Buffer.from(testString).toString('base64');
it('throws an error when given data does not match allowed data types', () => {
(() => helpers.processData('myString', 'urlencoded')).should.throw(messages.TYPE_MISMATCH_DATA);
(() => helpers.processData('myString', 'json')).should.throw(messages.TYPE_MISMATCH_DATA);
(() => helpers.processData({}, 'utf8')).should.throw(messages.TYPE_MISMATCH_DATA);
});
it('throws an error when given data does not match allowed instance types', () => {
(() => helpers.processData('myString', 'multipart')).should.throw(messages.INSTANCE_TYPE_MISMATCH_DATA);
});
it('processes data correctly when serializer "utf8" is configured', (cb) => {
helpers.processData('myString', 'utf8', (data) => {
data.should.be.eql({text: 'myString'});
cb();
})
});
it('processes data correctly when serializer "multipart" is configured and form data contains string value', (cb) => {
const formData = new FormDataMock();
formData.append('myString', testString);
helpers.processData(formData, 'multipart', (data) => {
data.buffers.length.should.be.equal(1);
data.names.length.should.be.equal(1);
data.fileNames.length.should.be.equal(1);
data.types.length.should.be.equal(1);
data.buffers[0].should.be.eql(testStringBase64);
data.names[0].should.be.equal('myString');
should.equal(data.fileNames[0], null);
data.types[0].should.be.equal('text/plain');
cb();
});
});
it('processes data correctly when serializer "multipart" is configured and form data contains file value', (cb) => {
const formData = new FormDataMock();
formData.append('myFile', new BlobMock([testString], { type: 'application/octet-stream' }));
helpers.processData(formData, 'multipart', (data) => {
data.buffers.length.should.be.equal(1);
data.names.length.should.be.equal(1);
data.fileNames.length.should.be.equal(1);
data.types.length.should.be.equal(1);
data.buffers[0].should.be.eql(testStringBase64);
data.names[0].should.be.equal('myFile');
data.fileNames[0].should.be.equal('blob');
data.types[0].should.be.equal('application/octet-stream');
cb();
});
});
});
});
describe('Dependency Validator', function () {
const messages = require('../www/messages');
describe('logWarnings()', function () {
it('logs a warning message if FormData API is not supported', function () {
const console = new ConsoleMock();
require('../www/dependency-validator')({}, console, messages).logWarnings();
console.messageList.length.should.be.equal(1);
console.messageList[0].type.should.be.equal('warn');
console.messageList[0].message.should.be.eql([messages.MISSING_FORMDATA_API]);
});
it('logs a warning message if FormData.entries() API is not supported', function () {
const console = new ConsoleMock();
require('../www/dependency-validator')({ FormData: {} }, console, messages).logWarnings();
console.messageList.length.should.be.equal(1);
console.messageList[0].type.should.be.equal('warn');
console.messageList[0].message.should.be.eql([messages.MISSING_FORMDATA_ENTRIES_API]);
});
});
describe('checkBlobApi()', function () {
it('throws an error if Blob API is not supported', function () {
const console = new ConsoleMock();
const validator = require('../www/dependency-validator')({}, console, messages);
(() => validator.checkBlobApi()).should.throw(messages.MISSING_BLOB_API);
});
});
describe('checkFileReaderApi()', function () {
it('throws an error if FileReader API is not supported', function () {
const console = new ConsoleMock();
const validator = require('../www/dependency-validator')({}, console, messages);
(() => validator.checkFileReaderApi()).should.throw(messages.MISSING_FILE_READER_API);
});
});
describe('checkFormDataInstance()', function () {
it('throws an error if FormData.entries() is not supported on given instance', function () {
const console = new ConsoleMock();
const validator = require('../www/dependency-validator')({ FormData: {}}, console, messages);
(() => validator.checkFormDataInstance({})).should.throw(messages.MISSING_FORMDATA_ENTRIES_API);
});
});
describe('checkTextEncoderApi()', function () {
it('throws an error if TextEncoder API is not supported', function () {
const console = new ConsoleMock();
const validator = require('../www/dependency-validator')({}, console, messages);
(() => validator.checkTextEncoderApi()).should.throw(messages.MISSING_TEXT_ENCODER_API);
});
});
});
describe('Ponyfills', function () {
const mockWindow = {
Blob: BlobMock,
File: FileMock,
};
const init = require('../www/ponyfills');
init.debug = true;
const ponyfills = init(mockWindow);
describe('Iterator', function () {
it('exposes interface correctly', () => {
const iterator = new ponyfills.Iterator([]);
iterator.next.should.be.a('function');
});
describe('next()', function () {
it('returns iteration object correctly when list is empty', () => {
const iterator = new ponyfills.Iterator([]);
iterator.next().should.be.eql({ done: true, value: undefined });
});
it('returns iteration object correctly when end posititon of list is not reached yet', () => {
const iterator = new ponyfills.Iterator([['first', 'this is the first item']]);
iterator.next().should.be.eql({ done: false, value: ['first', 'this is the first item'] });
});
it('returns iteration object correctly when end posititon of list is already reached', () => {
const iterator = new ponyfills.Iterator([['first', 'this is the first item']]);
iterator.next();
iterator.next().should.be.eql({ done: true, value: undefined });
});
});
});
describe('FormData', function () {
it('exposes interface correctly', () => {
const formData = new ponyfills.FormData();
formData.append.should.be.a('function');
formData.entries.should.be.a('function');
});
describe('append()', function () {
it('appends string value correctly', () => {
const formData = new ponyfills.FormData();
formData.append('test', 'myTestString');
formData.__items[0].should.be.eql(['test', 'myTestString']);
});
it('appends numeric value correctly', () => {
const formData = new ponyfills.FormData();
formData.append('test', 10);
formData.__items[0].should.be.eql(['test', '10']);
formData.__items[0][1].should.be.a('string');
});
it('appends Blob value correctly', () => {
const formData = new ponyfills.FormData();
const blob = new BlobMock(['another test'], { type: 'text/plain' });
formData.append('myBlob', blob, 'myFileName.txt');
formData.__items[0].should.be.eql(['myBlob', blob]);
formData.__items[0][1].name.should.be.equal('myFileName.txt');
formData.__items[0][1].lastModifiedDate.should.be.a('Date');
});
it('appends File value correctly', () => {
const formData = new ponyfills.FormData();
const blob = new BlobMock(['another test'], { type: 'text/plain' });
const file = new FileMock(blob, 'myFileName.txt');
formData.append('myFile', file, 'myOverriddenFileName.txt');
formData.__items[0].should.be.eql(['myFile', file]);
formData.__items[0][1].name.should.be.equal('myFileName.txt');
formData.__items[0][1].lastModifiedDate.should.be.eql(file.lastModifiedDate);
});
});
describe('entries()', function () {
it('returns an iterator correctly', () => {
const formData = new ponyfills.FormData();
formData.entries().should.be.an.instanceof(ponyfills.Iterator);
})
});
});
});
+35
View File
@@ -0,0 +1,35 @@
module.exports = class BlobMock {
constructor(blobParts, options) {
if (blobParts instanceof BlobMock) {
this._buffer = blobParts._buffer;
} else {
this._buffer = new Uint8Array(Buffer.concat(blobParts.map(part => Buffer.from(part, 'utf8')))).buffer;
}
this._type = options.type || '';
}
get size() {
return this._buffer.length;
}
get type() {
return this._type;
}
arrayBuffer() {
throw new Error('Not implemented in BlobMock.');
}
slice() {
throw new Error('Not implemented in BlobMock.');
}
stream() {
throw new Error('Not implemented in BlobMock.');
}
text() {
throw new Error('Not implemented in BlobMock.');
}
}
+11
View File
@@ -0,0 +1,11 @@
module.exports = class ConsoleMock {
constructor() {
this.messageList = [];
}
debug(...message) { this.messageList.push({ type: 'debug', message }); }
error(...message) { this.messageList.push({ type: 'error', message }); }
log(...message) { this.messageList.push({ type: 'log', message }); }
info(...message) { this.messageList.push({ type: 'info', message }); }
warn(...message) { this.messageList.push({ type: 'warn', message }); }
}
+17
View File
@@ -0,0 +1,17 @@
const BlobMock = require('./Blob.mock');
module.exports = class FileMock extends BlobMock {
constructor(blob, fileName) {
super(blob, { type: blob.type });
this._fileName = fileName || '';
this.__lastModifiedDate = new Date();
}
get name() {
return this._fileName;
}
get lastModifiedDate() {
return this.__lastModifiedDate;
}
}
+39
View File
@@ -0,0 +1,39 @@
module.exports = class FileReaderMock {
constructor() {
this.EMPTY = 0;
this.LOADING = 1;
this.DONE = 2;
this.error = null;
this.onabort = () => {};
this.onerror = () => {};
this.onload = () => {};
this.onloadend = () => {};
this.onloadstart = () => {};
this.onprogress = () => {};
this.readyState = this.EMPTY;
this.result = null;
}
readAsArrayBuffer(file) {
this.readyState = this.LOADING;
this.onloadstart();
this.onprogress();
this.result = file._buffer;
this.readyState = this.DONE;
this.onloadend();
this.onload();
}
readAsBinaryString() {
throw new Error('Not implemented in FileReaderMock.');
}
readAsDataUrl() {
throw new Error('Not implemented in FileReaderMock.');
}
readAsText() {
throw new Error('Not implemented in FileReaderMock.');
}
}
+52
View File
@@ -0,0 +1,52 @@
const BlobMock = require('./Blob.mock');
const FileMock = require('./File.mock');
module.exports = class FormDataMock {
constructor() {
this.map = new Map();
}
append(name, value, filename) {
if (value instanceof BlobMock) {
this.map.set(name, new FileMock(value, filename))
} else {
this.map.set(name, value);
}
}
delete() {
throw new Error('Not implemented in FormDataMock.');
}
entries() {
return this.map.entries();
}
forEach(cb) {
return this.map.forEach(cb);
}
get(key) {
return this.map.get(key);
}
getAll() {
throw new Error('Not implemented in FormDataMock.');
}
has(key) {
return this.map.has(key);
}
keys() {
return this.map.keys();
}
set(key, value) {
return this.map.set(key, value);
}
values() {
return this.map.values();
}
};
+8 -2
View File
@@ -5,15 +5,21 @@
var pluginId = module.id.slice(0, module.id.lastIndexOf('.')); var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
var exec = require('cordova/exec'); var exec = require('cordova/exec');
var base64 = require('cordova/base64');
var messages = require(pluginId + '.messages'); var messages = require(pluginId + '.messages');
var errorCodes = require(pluginId + '.error-codes');
var globalConfigs = require(pluginId + '.global-configs'); var globalConfigs = require(pluginId + '.global-configs');
var jsUtil = require(pluginId + '.js-util'); var jsUtil = require(pluginId + '.js-util');
var ToughCookie = require(pluginId + '.tough-cookie'); var ToughCookie = require(pluginId + '.tough-cookie');
var lodash = require(pluginId + '.lodash'); var lodash = require(pluginId + '.lodash');
var WebStorageCookieStore = require(pluginId + '.local-storage-store')(ToughCookie, lodash); var WebStorageCookieStore = require(pluginId + '.local-storage-store')(ToughCookie, lodash);
var cookieHandler = require(pluginId + '.cookie-handler')(window.localStorage, ToughCookie, WebStorageCookieStore); var cookieHandler = require(pluginId + '.cookie-handler')(window.localStorage, ToughCookie, WebStorageCookieStore);
var helpers = require(pluginId + '.helpers')(jsUtil, cookieHandler, messages); var dependencyValidator = require(pluginId + '.dependency-validator')(window, window.console, messages);
var ponyfills = require(pluginId + '.ponyfills')(window);
var helpers = require(pluginId + '.helpers')(window, jsUtil, cookieHandler, messages, base64, errorCodes, dependencyValidator, ponyfills);
var urlUtil = require(pluginId + '.url-util')(jsUtil); var urlUtil = require(pluginId + '.url-util')(jsUtil);
var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs); var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs, errorCodes, ponyfills);
dependencyValidator.logWarnings();
module.exports = publicInterface; module.exports = publicInterface;
+43
View File
@@ -0,0 +1,43 @@
module.exports = function init(global, console, messages) {
var interface = {
checkBlobApi: checkBlobApi,
checkFileReaderApi: checkFileReaderApi,
checkFormDataInstance: checkFormDataInstance,
checkTextEncoderApi: checkTextEncoderApi,
logWarnings: logWarnings,
};
return interface;
function logWarnings() {
if (!global.FormData) {
console.warn(messages.MISSING_FORMDATA_API);
} else if (!global.FormData.prototype || !global.FormData.prototype.entries) {
console.warn(messages.MISSING_FORMDATA_ENTRIES_API);
}
}
function checkBlobApi() {
if (!global.Blob || !global.Blob.prototype) {
throw new Error(messages.MISSING_BLOB_API);
}
}
function checkFileReaderApi() {
if (!global.FileReader || !global.FileReader.prototype) {
throw new Error(messages.MISSING_FILE_READER_API);
}
}
function checkFormDataInstance(instance) {
if (!instance || !instance.entries) {
throw new Error(messages.MISSING_FORMDATA_ENTRIES_API);
}
}
function checkTextEncoderApi() {
if (!global.TextEncoder || !global.TextEncoder.prototype) {
throw new Error(messages.MISSING_TEXT_ENCODER_API);
}
}
};
+9
View File
@@ -0,0 +1,9 @@
module.exports = {
GENERIC: -1,
SSL_EXCEPTION: -2,
SERVER_NOT_FOUND: -3,
TIMEOUT: -4,
UNSUPPORTED_URL: -5,
NOT_CONNECTED: -6,
POST_PROCESSING_FAILED: -7,
};
+200 -26
View File
@@ -1,25 +1,29 @@
module.exports = function init(jsUtil, cookieHandler, messages) { module.exports = function init(global, jsUtil, cookieHandler, messages, base64, errorCodes, dependencyValidator, ponyfills) {
var validSerializers = ['urlencoded', 'json', 'utf8']; var validSerializers = ['urlencoded', 'json', 'utf8', 'multipart'];
var validCertModes = ['default', 'nocheck', 'pinned', 'legacy']; var validCertModes = ['default', 'nocheck', 'pinned', 'legacy'];
var validClientAuthModes = ['none', 'systemstore', 'buffer']; var validClientAuthModes = ['none', 'systemstore', 'buffer'];
var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download']; var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download'];
var validResponseTypes = ['text', 'json', 'arraybuffer', 'blob'];
var interface = { var interface = {
b64EncodeUnicode: b64EncodeUnicode, b64EncodeUnicode: b64EncodeUnicode,
checkSerializer: checkSerializer,
checkSSLCertMode: checkSSLCertMode,
checkClientAuthMode: checkClientAuthMode, checkClientAuthMode: checkClientAuthMode,
checkClientAuthOptions: checkClientAuthOptions, checkClientAuthOptions: checkClientAuthOptions,
checkDownloadFilePath: checkDownloadFilePath,
checkFollowRedirectValue: checkFollowRedirectValue,
checkForBlacklistedHeaderKey: checkForBlacklistedHeaderKey, checkForBlacklistedHeaderKey: checkForBlacklistedHeaderKey,
checkForInvalidHeaderValue: checkForInvalidHeaderValue, checkForInvalidHeaderValue: checkForInvalidHeaderValue,
checkSerializer: checkSerializer,
checkSSLCertMode: checkSSLCertMode,
checkTimeoutValue: checkTimeoutValue, checkTimeoutValue: checkTimeoutValue,
checkFollowRedirectValue: checkFollowRedirectValue, checkUploadFileOptions: checkUploadFileOptions,
getMergedHeaders: getMergedHeaders,
processData: processData,
handleMissingCallbacks: handleMissingCallbacks,
handleMissingOptions: handleMissingOptions,
injectCookieHandler: injectCookieHandler, injectCookieHandler: injectCookieHandler,
injectFileEntryHandler: injectFileEntryHandler, injectFileEntryHandler: injectFileEntryHandler,
getMergedHeaders: getMergedHeaders, injectRawResponseHandler: injectRawResponseHandler,
getProcessedData: getProcessedData,
handleMissingCallbacks: handleMissingCallbacks,
handleMissingOptions: handleMissingOptions
}; };
// expose all functions for testing purposes // expose all functions for testing purposes
@@ -28,6 +32,7 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
interface.checkForValidStringValue = checkForValidStringValue; interface.checkForValidStringValue = checkForValidStringValue;
interface.checkKeyValuePairObject = checkKeyValuePairObject; interface.checkKeyValuePairObject = checkKeyValuePairObject;
interface.checkHttpMethod = checkHttpMethod; interface.checkHttpMethod = checkHttpMethod;
interface.checkResponseType = checkResponseType;
interface.checkHeadersObject = checkHeadersObject; interface.checkHeadersObject = checkHeadersObject;
interface.checkParamsObject = checkParamsObject; interface.checkParamsObject = checkParamsObject;
interface.resolveCookieString = resolveCookieString; interface.resolveCookieString = resolveCookieString;
@@ -91,10 +96,28 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
return obj; return obj;
} }
function checkArray(array, allowedDataTypes, onInvalidValueMessage) {
if (jsUtil.getTypeOf(array) !== 'Array') {
throw new Error(onInvalidValueMessage);
}
for (var i = 0; i < array.length; ++i) {
if (allowedDataTypes.indexOf(jsUtil.getTypeOf(array[i])) === -1) {
throw new Error(onInvalidValueMessage);
}
}
return array;
}
function checkHttpMethod(method) { function checkHttpMethod(method) {
return checkForValidStringValue(validHttpMethods, method, messages.INVALID_HTTP_METHOD); return checkForValidStringValue(validHttpMethods, method, messages.INVALID_HTTP_METHOD);
} }
function checkResponseType(type) {
return checkForValidStringValue(validResponseTypes, type, messages.INVALID_RESPONSE_TYPE);
}
function checkSerializer(serializer) { function checkSerializer(serializer) {
return checkForValidStringValue(validSerializers, serializer, messages.INVALID_DATA_SERIALIZER); return checkForValidStringValue(validSerializers, serializer, messages.INVALID_DATA_SERIALIZER);
} }
@@ -165,7 +188,7 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
function checkForInvalidHeaderValue(value) { function checkForInvalidHeaderValue(value) {
if (jsUtil.getTypeOf(value) !== 'String') { if (jsUtil.getTypeOf(value) !== 'String') {
throw new Error(messages.INVALID_HEADERS_VALUE); throw new Error(messages.INVALID_HEADER_VALUE);
} }
return value; return value;
@@ -188,11 +211,44 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
} }
function checkHeadersObject(headers) { function checkHeadersObject(headers) {
return checkKeyValuePairObject(headers, ['String'], messages.INVALID_HEADERS_VALUE); return checkKeyValuePairObject(headers, ['String'], messages.TYPE_MISMATCH_HEADERS);
} }
function checkParamsObject(params) { function checkParamsObject(params) {
return checkKeyValuePairObject(params, ['String', 'Array'], messages.INVALID_PARAMS_VALUE); return checkKeyValuePairObject(params, ['String', 'Array'], messages.TYPE_MISMATCH_PARAMS);
}
function checkDownloadFilePath(filePath) {
if (!filePath || jsUtil.getTypeOf(filePath) !== 'String') {
throw new Error(messages.INVALID_DOWNLOAD_FILE_PATH);
}
return filePath;
}
function checkUploadFileOptions(filePaths, names) {
if (jsUtil.getTypeOf(filePaths) === 'String') {
filePaths = [filePaths];
}
if (jsUtil.getTypeOf(names) === 'String') {
names = [names];
}
var opts = {
filePaths: checkArray(filePaths, ['String'], messages.TYPE_MISMATCH_FILE_PATHS),
names: checkArray(names, ['String'], messages.TYPE_MISMATCH_NAMES)
};
if (!opts.filePaths.length) {
throw new Error(messages.EMPTY_FILE_PATHS);
}
if (!opts.names.length) {
throw new Error(messages.EMPTY_NAMES);
}
return opts;
} }
function resolveCookieString(headers) { function resolveCookieString(headers) {
@@ -214,7 +270,7 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
entry.isFile = rawEntry.isFile; entry.isFile = rawEntry.isFile;
entry.name = rawEntry.name; entry.name = rawEntry.name;
entry.fullPath = rawEntry.fullPath; entry.fullPath = rawEntry.fullPath;
entry.filesystem = new FileSystem(rawEntry.filesystemName || (rawEntry.filesystem == window.PERSISTENT ? 'persistent' : 'temporary')); entry.filesystem = new FileSystem(rawEntry.filesystemName || (rawEntry.filesystem == global.PERSISTENT ? 'persistent' : 'temporary'));
entry.nativeURL = rawEntry.nativeURL; entry.nativeURL = rawEntry.nativeURL;
return entry; return entry;
@@ -227,6 +283,46 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
} }
} }
function injectRawResponseHandler(responseType, success, failure) {
return function (response) {
var dataType = jsUtil.getTypeOf(response.data);
// don't need post-processing if it's already binary type (on browser platform)
if (dataType === 'ArrayBuffer' || dataType === 'Blob') {
return success(response);
}
try {
// json
if (responseType === validResponseTypes[1]) {
response.data = JSON.parse(response.data);
}
// arraybuffer
else if (responseType === validResponseTypes[2]) {
response.data = base64.toArrayBuffer(response.data);
}
// blob
else if (responseType === validResponseTypes[3]) {
var buffer = base64.toArrayBuffer(response.data);
var type = response.headers['content-type'] || '';
var blob = new Blob([ buffer ], { type: type });
response.data = blob;
}
success(response);
} catch (error) {
failure({
status: errorCodes.POST_PROCESSING_FAILED,
error: messages.POST_PROCESSING_FAILED + ' ' + error.message,
url: response.url,
headers: response.headers
});
}
}
}
function injectFileEntryHandler(cb) { function injectFileEntryHandler(cb) {
return function (response) { return function (response) {
cb(createFileEntry(response.file)); cb(createFileEntry(response.file));
@@ -267,24 +363,101 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
return ['String']; return ['String'];
case 'urlencoded': case 'urlencoded':
return ['Object']; return ['Object'];
default: case 'json':
return ['Array', 'Object']; return ['Array', 'Object'];
default:
return [];
} }
} }
function getProcessedData(data, dataSerializer) { function getAllowedInstanceTypes(dataSerializer) {
return dataSerializer === 'multipart' ? ['FormData'] : null;
}
function processData(data, dataSerializer, cb) {
var currentDataType = jsUtil.getTypeOf(data); var currentDataType = jsUtil.getTypeOf(data);
var allowedDataTypes = getAllowedDataTypes(dataSerializer); var allowedDataTypes = getAllowedDataTypes(dataSerializer);
var allowedInstanceTypes = getAllowedInstanceTypes(dataSerializer);
if (allowedDataTypes.indexOf(currentDataType) === -1) { if (allowedInstanceTypes) {
throw new Error(messages.DATA_TYPE_MISMATCH + ' ' + allowedDataTypes.join(', ')); var isCorrectInstanceType = false;
allowedInstanceTypes.forEach(function(type) {
if ((global[type] && data instanceof global[type]) || (ponyfills[type] && data instanceof ponyfills[type])) {
isCorrectInstanceType = true;
}
});
if (!isCorrectInstanceType) {
throw new Error(messages.INSTANCE_TYPE_MISMATCH_DATA + ' ' + allowedInstanceTypes.join(', '));
}
} }
if (dataSerializer === 'utf8') { if (!allowedInstanceTypes && allowedDataTypes.indexOf(currentDataType) === -1) {
data = { text: data }; throw new Error(messages.TYPE_MISMATCH_DATA + ' ' + allowedDataTypes.join(', '));
} }
return data; switch (dataSerializer) {
case 'utf8':
return cb({ text: data });
case 'multipart':
return processFormData(data, cb);
default:
return cb(data);
}
}
function processFormData(data, cb) {
dependencyValidator.checkBlobApi();
dependencyValidator.checkFileReaderApi();
dependencyValidator.checkTextEncoderApi();
dependencyValidator.checkFormDataInstance(data);
var textEncoder = new global.TextEncoder('utf8');
var iterator = data.entries();
var result = {
buffers: [],
names: [],
fileNames: [],
types: []
};
processFormDataIterator(iterator, textEncoder, result, cb);
}
function processFormDataIterator(iterator, textEncoder, result, onFinished) {
var entry = iterator.next();
if (entry.done) {
return onFinished(result);
}
if (entry.value[1] instanceof global.Blob || entry.value[1] instanceof global.File) {
var reader = new global.FileReader();
reader.onload = function() {
result.buffers.push(base64.fromArrayBuffer(reader.result));
result.names.push(entry.value[0]);
result.fileNames.push(entry.value[1].name || 'blob');
result.types.push(entry.value[1].type || '');
processFormDataIterator(iterator, textEncoder, result, onFinished);
};
return reader.readAsArrayBuffer(entry.value[1]);
}
if (jsUtil.getTypeOf(entry.value[1]) === 'String') {
result.buffers.push(base64.fromArrayBuffer(textEncoder.encode(entry.value[1]).buffer));
result.names.push(entry.value[0]);
result.fileNames.push(null);
result.types.push('text/plain');
return processFormDataIterator(iterator, textEncoder, result, onFinished)
}
// skip items which are not supported
processFormDataIterator(iterator, textEncoder, result, onFinished);
} }
function handleMissingCallbacks(successFn, failFn) { function handleMissingCallbacks(successFn, failFn) {
@@ -301,15 +474,16 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
options = options || {}; options = options || {};
return { return {
method: checkHttpMethod(options.method || validHttpMethods[0]), data: jsUtil.getTypeOf(options.data) === 'Undefined' ? null : options.data,
serializer: checkSerializer(options.serializer || globals.serializer), filePath: options.filePath,
timeout: checkTimeoutValue(options.timeout || globals.timeout),
followRedirect: checkFollowRedirectValue(options.followRedirect || globals.followRedirect), followRedirect: checkFollowRedirectValue(options.followRedirect || globals.followRedirect),
headers: checkHeadersObject(options.headers || {}), headers: checkHeadersObject(options.headers || {}),
method: checkHttpMethod(options.method || validHttpMethods[0]),
name: options.name,
params: checkParamsObject(options.params || {}), params: checkParamsObject(options.params || {}),
data: jsUtil.getTypeOf(options.data) === 'Undefined' ? null : options.data, responseType: checkResponseType(options.responseType || validResponseTypes[0]),
filePath: options.filePath || '', serializer: checkSerializer(options.serializer || globals.serializer),
name: options.name || '' timeout: checkTimeoutValue(options.timeout || globals.timeout),
}; };
} }
}; };
+2
View File
@@ -4,6 +4,8 @@ module.exports = {
switch (Object.prototype.toString.call(object)) { switch (Object.prototype.toString.call(object)) {
case '[object Array]': case '[object Array]':
return 'Array'; return 'Array';
case '[object Blob]':
return 'Blob';
case '[object ArrayBuffer]': case '[object ArrayBuffer]':
return 'ArrayBuffer'; return 'ArrayBuffer';
case '[object Boolean]': case '[object Boolean]':
+28 -14
View File
@@ -1,18 +1,32 @@
module.exports = { module.exports = {
ADDING_COOKIES_NOT_SUPPORTED: 'advanced-http: "setHeader" does not support adding cookies, please use "setCookie" function instead', ADDING_COOKIES_NOT_SUPPORTED: 'advanced-http: "setHeader" does not support adding cookies, please use "setCookie" function instead',
DATA_TYPE_MISMATCH: 'advanced-http: "data" argument supports only following data types:', EMPTY_FILE_PATHS: 'advanced-http: "filePaths" option array must not be empty, <filePaths: string[]>',
MANDATORY_SUCCESS: 'advanced-http: missing mandatory "onSuccess" callback function', EMPTY_NAMES: 'advanced-http: "names" option array must not be empty, <names: string[]>',
MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function', INSTANCE_TYPE_MISMATCH_DATA: 'advanced-http: "data" option is configured to support only following instance types:',
INVALID_HTTP_METHOD: 'advanced-http: invalid HTTP method, supported methods are:', INVALID_CLIENT_AUTH_ALIAS: 'advanced-http: invalid client certificate alias, needs to be a string or undefined, <alias: string | undefined>',
INVALID_DATA_SERIALIZER: 'advanced-http: invalid serializer, supported serializers are:',
INVALID_SSL_CERT_MODE: 'advanced-http: invalid SSL cert mode, supported modes are:',
INVALID_CLIENT_AUTH_MODE: 'advanced-http: invalid client certificate authentication mode, supported modes are:', INVALID_CLIENT_AUTH_MODE: 'advanced-http: invalid client certificate authentication mode, supported modes are:',
INVALID_CLIENT_AUTH_OPTIONS: 'advanced-http: invalid client certificate authentication options, needs to be an object', INVALID_CLIENT_AUTH_OPTIONS: 'advanced-http: invalid client certificate authentication options, needs to be an dictionary style object',
INVALID_CLIENT_AUTH_ALIAS: 'advanced-http: invalid client certificate alias, needs to be a string or undefined', INVALID_CLIENT_AUTH_PKCS_PASSWORD: 'advanced-http: invalid PKCS12 container password, needs to be a string, <pkcsPassword: string>',
INVALID_CLIENT_AUTH_RAW_PKCS: 'advanced-http: invalid PKCS12 container, needs to be an array buffer', INVALID_CLIENT_AUTH_RAW_PKCS: 'advanced-http: invalid PKCS12 container, needs to be an array buffer, <rawPkcs: ArrayBuffer>',
INVALID_CLIENT_AUTH_PKCS_PASSWORD: 'advanced-http: invalid PKCS12 container password, needs to be a string', INVALID_DATA_SERIALIZER: 'advanced-http: invalid serializer, supported serializers are:',
INVALID_HEADERS_VALUE: 'advanced-http: header values must be strings', INVALID_DOWNLOAD_FILE_PATH: 'advanced-http: invalid "filePath" value, needs to be a string, <filePath: string>',
INVALID_TIMEOUT_VALUE: 'advanced-http: invalid timeout value, needs to be a positive numeric value', INVALID_FOLLOW_REDIRECT_VALUE: 'advanced-http: invalid follow redirect value, needs to be a boolean value, <followRedirect: boolean>',
INVALID_FOLLOW_REDIRECT_VALUE: 'advanced-http: invalid follow redirect value, needs to be a boolean value', INVALID_HEADER_VALUE: 'advanced-http: invalid header value, needs to be a string, <header: string>',
INVALID_PARAMS_VALUE: 'advanced-http: invalid params object, needs to be an object with strings' INVALID_HTTP_METHOD: 'advanced-http: invalid HTTP method, supported methods are:',
INVALID_RESPONSE_TYPE: 'advanced-http: invalid response type, supported types are:',
INVALID_SSL_CERT_MODE: 'advanced-http: invalid SSL cert mode, supported modes are:',
INVALID_TIMEOUT_VALUE: 'advanced-http: invalid timeout value, needs to be a positive numeric value, <timeout: number>',
MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function',
MANDATORY_SUCCESS: 'advanced-http: missing mandatory "onSuccess" callback function',
MISSING_BLOB_API: 'advanced-http: Blob API is not supported in this webview. If you want to use "multipart/form-data" requests, you need to load a polyfill library before loading this plugin. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.',
MISSING_FILE_READER_API: 'advanced-http: FileReader API is not supported in this webview. If you want to use "multipart/form-data" requests, you need to load a polyfill library before loading this plugin. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.',
MISSING_FORMDATA_API: 'advanced-http: FormData API is not supported in this webview. If you want to use "multipart/form-data" requests, you need to load a polyfill library before loading this plugin. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.',
MISSING_FORMDATA_ENTRIES_API: 'advanced-http: Given instance of FormData does not implement FormData API specification correctly, FormData.entries() is missing. If you want to use "multipart/form-data" requests, you can use an included ponyfill. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.',
MISSING_TEXT_ENCODER_API: 'advanced-http: TextEncoder API is not supported in this webview. If you want to use "multipart/form-data" requests, you need to load a polyfill library before loading this plugin. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.',
POST_PROCESSING_FAILED: 'advanced-http: an error occured during post processing response:',
TYPE_MISMATCH_DATA: 'advanced-http: "data" option is configured to support only following data types:',
TYPE_MISMATCH_FILE_PATHS: 'advanced-http: "filePaths" option needs to be an string array, <filePaths: string[]>',
TYPE_MISMATCH_HEADERS: 'advanced-http: "headers" option needs to be an dictionary style object with string values, <headers: {[key: string]: string}>',
TYPE_MISMATCH_NAMES: 'advanced-http: "names" option needs to be an string array, <names: string[]>',
TYPE_MISMATCH_PARAMS: 'advanced-http: "params" option needs to be an dictionary style object, <params: {[key: string]: string | string[]}>',
}; };
+47
View File
@@ -0,0 +1,47 @@
module.exports = function init(global) {
var interface = { FormData: FormData };
// expose all constructor functions for testing purposes
if (init.debug) {
interface.Iterator = Iterator;
}
function FormData() {
this.__items = [];
}
FormData.prototype.append = function(name, value, filename) {
if (global.File && value instanceof global.File) {
// nothing to do
} else if (global.Blob && value instanceof global.Blob) {
// mimic File instance by adding missing properties
value.lastModifiedDate = new Date();
value.name = filename || '';
} else {
value = value.toString ? value.toString() : value;
}
this.__items.push([ name, value ]);
};
FormData.prototype.entries = function() {
return new Iterator(this.__items);
};
function Iterator(items) {
this.__items = items;
this.__position = -1;
}
Iterator.prototype.next = function() {
this.__position += 1;
if (this.__position < this.__items.length) {
return { done: false, value: this.__items[this.__position] };
}
return { done: true, value: undefined };
}
return interface;
};
+15 -9
View File
@@ -1,5 +1,5 @@
module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConfigs) { module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConfigs, errorCodes, ponyfills) {
const publicInterface = { var publicInterface = {
getBasicAuthHeader: getBasicAuthHeader, getBasicAuthHeader: getBasicAuthHeader,
useBasicAuth: useBasicAuth, useBasicAuth: useBasicAuth,
getHeaders: getHeaders, getHeaders: getHeaders,
@@ -28,7 +28,9 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
delete: del, delete: del,
head: head, head: head,
uploadFile: uploadFile, uploadFile: uploadFile,
downloadFile: downloadFile downloadFile: downloadFile,
ErrorCode: errorCodes,
ponyfills: ponyfills
}; };
function getBasicAuthHeader(username, password) { function getBasicAuthHeader(username, password) {
@@ -143,22 +145,26 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
url = urlUtil.appendQueryParamsString(url, urlUtil.serializeQueryParams(options.params, true)); url = urlUtil.appendQueryParamsString(url, urlUtil.serializeQueryParams(options.params, true));
var headers = helpers.getMergedHeaders(url, options.headers, globalConfigs.headers); var headers = helpers.getMergedHeaders(url, options.headers, globalConfigs.headers);
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure); var onFail = helpers.injectCookieHandler(url, failure);
var onSuccess = helpers.injectCookieHandler(url, helpers.injectRawResponseHandler(options.responseType, success, failure));
switch (options.method) { switch (options.method) {
case 'post': case 'post':
case 'put': case 'put':
case 'patch': case 'patch':
var data = helpers.getProcessedData(options.data, options.serializer); return helpers.processData(options.data, options.serializer, function(data) {
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout, options.followRedirect]); exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout, options.followRedirect, options.responseType]);
});
case 'upload': case 'upload':
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFile', [url, headers, options.filePath, options.name, options.timeout, options.followRedirect]); var fileOptions = helpers.checkUploadFileOptions(options.filePath, options.name);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFiles', [url, headers, fileOptions.filePaths, fileOptions.names, options.timeout, options.followRedirect, options.responseType]);
case 'download': case 'download':
var filePath = helpers.checkDownloadFilePath(options.filePath);
var onDownloadSuccess = helpers.injectCookieHandler(url, helpers.injectFileEntryHandler(success)); var onDownloadSuccess = helpers.injectCookieHandler(url, helpers.injectFileEntryHandler(success));
return exec(onDownloadSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [url, headers, options.filePath, options.timeout, options.followRedirect]); return exec(onDownloadSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [url, headers, filePath, options.timeout, options.followRedirect]);
default: default:
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, headers, options.timeout, options.followRedirect]); return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, headers, options.timeout, options.followRedirect, options.responseType]);
} }
} }