mirror of
https://github.com/silkimen/cordova-plugin-advanced-http.git
synced 2026-02-11 00:00:06 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d7f02b4b3 | ||
|
|
afc9e3e944 | ||
|
|
9af2d1c21a | ||
|
|
9dce2fb964 | ||
|
|
eb946b49ab | ||
|
|
72ca81b515 | ||
|
|
68633f1bb8 | ||
|
|
ee26b78e0d | ||
|
|
d924f98844 | ||
|
|
8fceb4df97 | ||
|
|
7a09fa9460 | ||
|
|
3e5c941fdd | ||
|
|
0f273f401b | ||
|
|
684874184d | ||
|
|
19e1e7206b | ||
|
|
21bec76c11 | ||
|
|
594d03aa41 | ||
|
|
b3276ad2d4 | ||
|
|
3b4a5b7c26 | ||
|
|
867b8ea202 |
56
.github/workflows/ci.yml
vendored
Normal file
56
.github/workflows/ci.yml
vendored
Normal 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,6 +1,6 @@
|
||||
notifications:
|
||||
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:
|
||||
directories:
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"editor.tabSize": 2
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
# 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
|
||||
|
||||
1
LICENSE
1
LICENSE
@@ -1,5 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Sefa Ilkimen
|
||||
Copyright (c) 2017 Mobisys GmbH
|
||||
Copyright (c) 2014 Wymsee, Inc
|
||||
|
||||
|
||||
35
README.md
35
README.md
@@ -3,7 +3,9 @@ Cordova Advanced HTTP
|
||||
[](https://badge.fury.io/js/cordova-plugin-advanced-http)
|
||||
[](https://www.npmjs.com/package/cordova-plugin-advanced-http)
|
||||
[](https://opensource.org/licenses/mit-license.php)
|
||||
[](https://travis-ci.org/silkimen/cordova-plugin-advanced-http)
|
||||
|
||||
[](https://travis-ci.org/silkimen/cordova-plugin-advanced-http)
|
||||
[](https://github.com/silkimen/cordova-plugin-advanced-http/actions)
|
||||
|
||||
|
||||
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
|
||||
|
||||
- Background threading - all requests are done in a background thread.
|
||||
- Handling of HTTP code 401 - read more at [Issue CB-2415](https://issues.apache.org/jira/browse/CB-2415).
|
||||
- SSL Pinning
|
||||
- SSL / TLS Pinning
|
||||
- CORS restrictions do not apply
|
||||
- Handling of HTTP code 401 - read more at [Issue CB-2415](https://issues.apache.org/jira/browse/CB-2415)
|
||||
|
||||
## 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")
|
||||
* `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")
|
||||
* `multipart`: send FormData objects as multipart content in body (content type "multipart/form-data")
|
||||
|
||||
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
|
||||
Set how long to wait for a request to respond, in seconds.
|
||||
@@ -176,15 +181,6 @@ This function was deprecated in 2.0.9. Use ["setFollowRedirect"](#setFollowRedir
|
||||
### setSSLCertMode (deprecated)
|
||||
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
|
||||
Remove all cookies associated with a given URL.
|
||||
|
||||
@@ -388,6 +384,7 @@ Following features are *not* supported:
|
||||
* Pinning SSL certificate
|
||||
* Disabling SSL certificate check
|
||||
* Disabling transparently following redirects (HTTP codes 3xx)
|
||||
* Circumventing CORS restrictions
|
||||
|
||||
## Libraries
|
||||
|
||||
@@ -399,6 +396,16 @@ This plugin utilizes some awesome open source 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
|
||||
|
||||
We've set up a separate document for our [contribution guidelines](CONTRIBUTING.md).
|
||||
|
||||
633
package-lock.json
generated
633
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"name": "cordova-plugin-advanced-http",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
|
||||
"scripts": {
|
||||
"updatecert": "node ./scripts/update-e2e-server-cert.js && node ./scripts/update-e2e-client-cert.js",
|
||||
"buildbrowser": "./scripts/build-test-app.sh --browser",
|
||||
"testandroid": "npm run updatecert && ./scripts/build-test-app.sh --android --emulator && ./scripts/test-app.sh --android --emulator",
|
||||
"testios": "npm run updatecert && ./scripts/build-test-app.sh --ios --emulator && ./scripts/test-app.sh --ios --emulator",
|
||||
"buildandroid": "./scripts/build-test-app.sh --android --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",
|
||||
"testjs": "mocha ./test/js-specs.js",
|
||||
"test": "npm run testjs && npm run testapp",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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.2.0">
|
||||
<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>
|
||||
<description>
|
||||
Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning
|
||||
@@ -9,6 +9,7 @@
|
||||
</engines>
|
||||
<dependency id="cordova-plugin-file" version=">=2.0.0"/>
|
||||
<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/helpers.js" name="helpers"/>
|
||||
@@ -16,6 +17,7 @@
|
||||
<js-module src="www/local-storage-store.js" name="local-storage-store"/>
|
||||
<js-module src="www/lodash.js" name="lodash"/>
|
||||
<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/umd-tough-cookie.js" name="tough-cookie"/>
|
||||
<js-module src="www/url-util.js" name="url-util"/>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.silkimen.cordovahttp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -19,9 +21,11 @@ import com.silkimen.http.TLSConfiguration;
|
||||
|
||||
import org.apache.cordova.CallbackContext;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
abstract class CordovaHttpBase implements Runnable {
|
||||
@@ -120,7 +124,7 @@ abstract class CordovaHttpBase implements Runnable {
|
||||
request.readTimeout(this.timeout);
|
||||
request.acceptCharset("UTF-8");
|
||||
request.uncompress(true);
|
||||
request.setConnectionFactory(new OkConnectionFactory());
|
||||
HttpRequest.setConnectionFactory(new OkConnectionFactory());
|
||||
|
||||
if (this.tlsConfiguration.getHostnameVerifier() != null) {
|
||||
request.setHostnameVerifier(this.tlsConfiguration.getHostnameVerifier());
|
||||
@@ -141,6 +145,8 @@ abstract class CordovaHttpBase implements Runnable {
|
||||
request.contentType("text/plain", "UTF-8");
|
||||
} else if ("urlencoded".equals(this.serializer)) {
|
||||
// intentionally left blank, because content type is set in HttpRequest.form()
|
||||
} else if ("multipart".equals(this.serializer)) {
|
||||
request.contentType("multipart/form-data");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +161,22 @@ abstract class CordovaHttpBase implements Runnable {
|
||||
request.send(((JSONObject) this.data).getString("text"));
|
||||
} else if ("urlencoded".equals(this.serializer)) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +290,64 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
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 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
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -223,6 +223,42 @@
|
||||
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
|
||||
parameters:(id)parameters
|
||||
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
|
||||
@@ -247,6 +283,71 @@
|
||||
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
|
||||
URLString:(NSString *)URLString
|
||||
parameters:(id)parameters
|
||||
|
||||
@@ -142,6 +142,142 @@
|
||||
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 {
|
||||
NSString *certMode = [command.arguments objectAtIndex:0];
|
||||
|
||||
@@ -164,260 +300,27 @@
|
||||
}
|
||||
|
||||
- (void)get:(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];
|
||||
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 {
|
||||
[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;
|
||||
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
|
||||
|
||||
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;
|
||||
[[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];
|
||||
}
|
||||
[self executeRequestWithoutData: command withMethod:@"GET"];
|
||||
}
|
||||
|
||||
- (void)delete:(CDVInvokedUrlCommand*)command {
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
manager.securityPolicy = securityPolicy;
|
||||
[self executeRequestWithoutData: command withMethod:@"DELETE"];
|
||||
}
|
||||
|
||||
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 {
|
||||
[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)head:(CDVInvokedUrlCommand*)command {
|
||||
[self executeRequestWithoutData: command withMethod:@"HEAD"];
|
||||
}
|
||||
|
||||
- (void)post:(CDVInvokedUrlCommand*)command {
|
||||
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 {
|
||||
[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];
|
||||
}
|
||||
[self executeRequestWithData: command withMethod:@"POST"];
|
||||
}
|
||||
|
||||
- (void)put:(CDVInvokedUrlCommand*)command {
|
||||
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 {
|
||||
[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];
|
||||
}
|
||||
[self executeRequestWithData: command withMethod:@"PUT"];
|
||||
}
|
||||
|
||||
- (void)patch:(CDVInvokedUrlCommand*)command {
|
||||
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 {
|
||||
[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];
|
||||
}
|
||||
[self executeRequestWithData: command withMethod:@"PATCH"];
|
||||
}
|
||||
|
||||
- (void)uploadFiles:(CDVInvokedUrlCommand*)command {
|
||||
@@ -503,7 +406,7 @@
|
||||
[[SDNetworkActivityIndicator sharedActivityIndicator] startActivity];
|
||||
|
||||
@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
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</platform>
|
||||
<engine name="android" spec="7.1.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" />
|
||||
<preference name="AndroidPersistentFileLocation" value="Internal" />
|
||||
</widget>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"cordova": "7.0.1",
|
||||
"cordova-android": "7.1.0",
|
||||
"cordova-browser": "5.0.0",
|
||||
"cordova-ios": "4.4.0"
|
||||
"cordova-ios": "5.0.1"
|
||||
},
|
||||
"cordova": {
|
||||
"platforms": [
|
||||
|
||||
@@ -3,8 +3,25 @@ const app = {
|
||||
|
||||
lastResult: null,
|
||||
|
||||
testsFlaggedToRun: [],
|
||||
|
||||
initialize: function () {
|
||||
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) {
|
||||
@@ -47,9 +64,9 @@ const app = {
|
||||
cb(app.lastResult);
|
||||
},
|
||||
|
||||
runTest: function (index) {
|
||||
runTest: function (tests, index) {
|
||||
const testDefinition = tests[index];
|
||||
const titleText = app.testIndex + ': ' + testDefinition.description;
|
||||
const titleText = index + ': ' + testDefinition.description;
|
||||
const expectedText = 'expected - ' + testDefinition.expected;
|
||||
|
||||
document.getElementById('statusInput').value = 'running';
|
||||
@@ -130,8 +147,8 @@ const app = {
|
||||
onNextBtnClick: function () {
|
||||
app.testIndex += 1;
|
||||
|
||||
if (app.testIndex < tests.length) {
|
||||
app.runTest(app.testIndex);
|
||||
if (app.testIndex < app.testsFlaggedToRun.length) {
|
||||
app.runTest(app.testsFlaggedToRun, app.testIndex);
|
||||
} else {
|
||||
app.onFinishedAllTests();
|
||||
}
|
||||
|
||||
BIN
test/e2e-app-template/www/res/cordova_logo.png
Normal file
BIN
test/e2e-app-template/www/res/cordova_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
@@ -33,6 +33,7 @@ const helpers = {
|
||||
setJsonSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('json')); },
|
||||
setUtf8StringSerializer: function (resolve) { resolve(cordova.plugin.http.setDataSerializer('utf8')); },
|
||||
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)); },
|
||||
enableFollowingRedirect: function(resolve) { resolve(cordova.plugin.http.setFollowRedirect(true)); },
|
||||
getWithXhr: function (done, url, type) {
|
||||
@@ -74,6 +75,13 @@ const helpers = {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -789,8 +797,55 @@ const tests = [
|
||||
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
|
||||
|
||||
// TODO: not ready yet
|
||||
// {
|
||||
// description: 'should authenticate correctly when client cert auth is configured with a PKCS12 container',
|
||||
// expected: 'resolved: {"status": 200, ...',
|
||||
|
||||
73
test/e2e-tooling/caps.js
Normal file
73
test/e2e-tooling/caps.js
Normal 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();
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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.9.1',
|
||||
platformName: 'iOS',
|
||||
platformVersion: '10.3',
|
||||
deviceName: 'iPhone 6',
|
||||
autoWebview: true,
|
||||
app: undefined // will be set later
|
||||
},
|
||||
iosEmulator: {
|
||||
browserName: '',
|
||||
'appium-version': '1.9.1',
|
||||
platformName: 'iOS',
|
||||
platformVersion: '10.3',
|
||||
deviceName: 'iPhone Simulator',
|
||||
autoWebview: true,
|
||||
app: undefined // will be set later
|
||||
},
|
||||
androidEmulator: {
|
||||
browserName: '',
|
||||
'appium-version': '1.9.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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
exports.configure = driver => {
|
||||
module.exports = { setupLogging };
|
||||
|
||||
function setupLogging(driver) {
|
||||
require('colors');
|
||||
|
||||
driver.on('status', info => {
|
||||
console.log(info.cyan);
|
||||
});
|
||||
@@ -10,4 +14,4 @@ exports.configure = driver => {
|
||||
driver.on('http', (meth, path, data) => {
|
||||
console.log(' > ' + meth.magenta, path, (data || '').grey);
|
||||
});
|
||||
};
|
||||
}
|
||||
17
test/e2e-tooling/server.js
Normal file
17
test/e2e-tooling/server.js
Normal 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()];
|
||||
}
|
||||
@@ -1,100 +1,124 @@
|
||||
const wd = require('wd');
|
||||
const chai = require('chai');
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
const apps = require('./helpers/apps');
|
||||
const caps = Object.assign({}, require('./helpers/caps'));
|
||||
const serverConfig = require('./helpers/server');
|
||||
const logging = require('./logging');
|
||||
const capsConfig = require('./caps');
|
||||
const serverConfig = require('./server');
|
||||
const testDefinitions = require('../e2e-specs');
|
||||
const pkgjson = require('../../package.json');
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
|
||||
global.should = chai.should();
|
||||
|
||||
require('colors');
|
||||
|
||||
describe('Advanced HTTP', function() {
|
||||
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 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;
|
||||
|
||||
this.timeout(900000);
|
||||
this.timeout(15000);
|
||||
this.slow(4000);
|
||||
|
||||
const getCaps = appName => {
|
||||
const desiredOs = isAndroid ? 'android' : 'ios';
|
||||
const desiredCaps = caps[desiredOs + (isDevice ? 'Device' : 'Emulator')];
|
||||
const desiredApp = apps[desiredOs + appName];
|
||||
before(async function () {
|
||||
// connecting to saucelabs can take some time
|
||||
this.timeout(300000);
|
||||
|
||||
desiredCaps.name = pkgjson.name + ` (${desiredOs})`;
|
||||
desiredCaps.app = desiredApp;
|
||||
driver = await wd.promiseChainRemote(serverConfig.getServer(environment));
|
||||
|
||||
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
|
||||
.elementById('descriptionLbl')
|
||||
.text()
|
||||
.then(text => parseInt(text.match(/(\d+):/)[1], 10))
|
||||
.should.eventually.become(number, 'Test index is not matching!');
|
||||
const onlyFlaggedTests = [];
|
||||
const enabledTests = [];
|
||||
|
||||
const validateTestTitle = testTitle => driver
|
||||
.elementById('descriptionLbl')
|
||||
.text()
|
||||
.then(text => text.match(/\d+:\ (.*)/)[1])
|
||||
.should.eventually.become(testTitle, 'Test description is not matching!');
|
||||
testDefinitions.tests.forEach(test => {
|
||||
if (test.only) {
|
||||
onlyFlaggedTests.push(test);
|
||||
}
|
||||
|
||||
const waitToBeFinished = timeout => new Promise((resolve, reject) => {
|
||||
const timeoutTimestamp = Date.now() + timeout;
|
||||
const checkIfFinished = () => driver
|
||||
.elementById('statusInput')
|
||||
.getValue()
|
||||
.then(value => {
|
||||
if (value === 'finished') {
|
||||
resolve();
|
||||
} else if (Date.now() > timeoutTimestamp) {
|
||||
reject(new Error('Test function timed out!'));
|
||||
} else {
|
||||
setTimeout(checkIfFinished, 500);
|
||||
}
|
||||
});
|
||||
|
||||
checkIfFinished();
|
||||
if (!test.disabled) {
|
||||
enabledTests.push(test);
|
||||
}
|
||||
});
|
||||
|
||||
const validateResult = testDefinition => driver
|
||||
.safeExecute('app.lastResult')
|
||||
.then(result => testDefinition.validationFunc(driver, result, targetInfo));
|
||||
|
||||
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))
|
||||
});
|
||||
});
|
||||
if (onlyFlaggedTests.length) {
|
||||
onlyFlaggedTests.forEach(defineTestForMocha);
|
||||
} else {
|
||||
enabledTests.forEach(defineTestForMocha);
|
||||
}
|
||||
});
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
271
test/js-specs.js
271
test/js-specs.js
@@ -1,7 +1,14 @@
|
||||
const chai = require('chai');
|
||||
const mock = require('mock-require');
|
||||
const util = require('util');
|
||||
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 () {
|
||||
const messages = require('../www/messages');
|
||||
|
||||
@@ -17,7 +24,7 @@ describe('Advanced HTTP public interface', function () {
|
||||
const errorCodes = require('../www/error-codes');
|
||||
const WebStorageCookieStore = require('../www/local-storage-store')(ToughCookie, lodash);
|
||||
const cookieHandler = require('../www/cookie-handler')(null, ToughCookie, WebStorageCookieStore);
|
||||
const helpers = require('../www/helpers')(jsUtil, cookieHandler, messages, errorCodes);
|
||||
const helpers = require('../www/helpers')(null, jsUtil, cookieHandler, messages, errorCodes);
|
||||
const urlUtil = require('../www/url-util')(jsUtil);
|
||||
|
||||
return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs, errorCodes };
|
||||
@@ -29,7 +36,7 @@ describe('Advanced HTTP public interface', function () {
|
||||
|
||||
beforeEach(() => {
|
||||
// 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());
|
||||
});
|
||||
|
||||
@@ -265,13 +272,13 @@ describe('Common helpers', function () {
|
||||
const init = require('../www/helpers');
|
||||
init.debug = true;
|
||||
|
||||
const helpers = init(null, null, null);
|
||||
const helpers = init(null, null, null, null, null, null);
|
||||
|
||||
it('merges empty header sets correctly', () => {
|
||||
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 });
|
||||
});
|
||||
|
||||
@@ -282,13 +289,13 @@ describe('Common helpers', function () {
|
||||
|
||||
describe('getCookieHeader(url)', function () {
|
||||
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({});
|
||||
});
|
||||
|
||||
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' });
|
||||
});
|
||||
@@ -297,7 +304,7 @@ describe('Common helpers', function () {
|
||||
describe('checkClientAuthOptions()', function () {
|
||||
const jsUtil = require('../www/js-util');
|
||||
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', () => {
|
||||
helpers.checkClientAuthOptions('none').should.eql({
|
||||
@@ -362,7 +369,7 @@ describe('Common helpers', function () {
|
||||
describe('handleMissingOptions()', function () {
|
||||
const jsUtil = require('../www/js-util');
|
||||
const messages = require('../www/messages');
|
||||
const helpers = require('../www/helpers')(jsUtil, null, messages);
|
||||
const helpers = require('../www/helpers')(null, jsUtil, null, messages);
|
||||
const mockGlobals = {
|
||||
headers: {},
|
||||
serializer: 'urlencoded',
|
||||
@@ -394,7 +401,7 @@ describe('Common helpers', function () {
|
||||
};
|
||||
|
||||
it('does not change response data if it is an ArrayBuffer', () => {
|
||||
const helpers = require('../www/helpers')(jsUtil, null, messages, null, errorCodes);
|
||||
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
|
||||
const buffer = new ArrayBuffer(5);
|
||||
const handler = helpers.injectRawResponseHandler(
|
||||
'arraybuffer',
|
||||
@@ -406,7 +413,7 @@ describe('Common helpers', function () {
|
||||
|
||||
it('does not change response data if it is a Blob', () => {
|
||||
const fakeJsUtil = { getTypeOf: () => 'Blob' };
|
||||
const helpers = require('../www/helpers')(fakeJsUtil, null, messages, null, errorCodes);
|
||||
const helpers = require('../www/helpers')(null, fakeJsUtil, null, messages, null, errorCodes);
|
||||
const handler = helpers.injectRawResponseHandler(
|
||||
'blob',
|
||||
response => response.data.should.be.equal('fakeData')
|
||||
@@ -416,7 +423,7 @@ describe('Common helpers', function () {
|
||||
});
|
||||
|
||||
it('does not change response data if response type is "text"', () => {
|
||||
const helpers = require('../www/helpers')(jsUtil, null, messages, null, errorCodes);
|
||||
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
|
||||
const example = 'exampleText';
|
||||
const handler = helpers.injectRawResponseHandler(
|
||||
'text',
|
||||
@@ -428,7 +435,7 @@ describe('Common helpers', function () {
|
||||
|
||||
it('handles response type "json" correctly', () => {
|
||||
const fakeData = { myString: 'bla', myNumber: 10 };
|
||||
const helpers = require('../www/helpers')(jsUtil, null, messages, null, errorCodes);
|
||||
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, errorCodes);
|
||||
const handler = helpers.injectRawResponseHandler(
|
||||
'json',
|
||||
response => response.data.should.be.eql(fakeData)
|
||||
@@ -438,7 +445,7 @@ describe('Common helpers', function () {
|
||||
});
|
||||
|
||||
it('handles response type "arraybuffer" correctly', () => {
|
||||
const helpers = require('../www/helpers')(jsUtil, null, messages, fakeBase64, errorCodes);
|
||||
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
|
||||
const handler = helpers.injectRawResponseHandler(
|
||||
'arraybuffer',
|
||||
response => response.data.should.be.equal('fakeArrayBuffer')
|
||||
@@ -448,7 +455,7 @@ describe('Common helpers', function () {
|
||||
});
|
||||
|
||||
it('handles response type "blob" correctly', () => {
|
||||
const helpers = require('../www/helpers')(jsUtil, null, messages, fakeBase64, errorCodes);
|
||||
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
|
||||
const handler = helpers.injectRawResponseHandler(
|
||||
'blob',
|
||||
(response) => {
|
||||
@@ -462,7 +469,7 @@ describe('Common helpers', function () {
|
||||
});
|
||||
|
||||
it('calls failure callback when post-processing fails', () => {
|
||||
const helpers = require('../www/helpers')(jsUtil, null, messages, fakeBase64, errorCodes);
|
||||
const helpers = require('../www/helpers')(null, jsUtil, null, messages, fakeBase64, errorCodes);
|
||||
const handler = helpers.injectRawResponseHandler(
|
||||
'json',
|
||||
null,
|
||||
@@ -476,10 +483,10 @@ describe('Common helpers', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkUploadFileOptions()', function() {
|
||||
describe('checkUploadFileOptions()', function () {
|
||||
const jsUtil = require('../www/js-util');
|
||||
const messages = require('../www/messages');
|
||||
const helpers = require('../www/helpers')(jsUtil, null, messages, null, null);
|
||||
const helpers = require('../www/helpers')(null, jsUtil, null, messages, null, null);
|
||||
|
||||
it('checks valid file options correctly', () => {
|
||||
const opts = {
|
||||
@@ -508,4 +515,232 @@ describe('Common helpers', function () {
|
||||
(() => 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
test/mocks/Blob.mock.js
Normal file
35
test/mocks/Blob.mock.js
Normal 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
test/mocks/Console.mock.js
Normal file
11
test/mocks/Console.mock.js
Normal 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
test/mocks/File.mock.js
Normal file
17
test/mocks/File.mock.js
Normal 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
test/mocks/FileReader.mock.js
Normal file
39
test/mocks/FileReader.mock.js
Normal 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
test/mocks/FormData.mock.js
Normal file
52
test/mocks/FormData.mock.js
Normal 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();
|
||||
}
|
||||
};
|
||||
@@ -14,8 +14,12 @@ var ToughCookie = require(pluginId + '.tough-cookie');
|
||||
var lodash = require(pluginId + '.lodash');
|
||||
var WebStorageCookieStore = require(pluginId + '.local-storage-store')(ToughCookie, lodash);
|
||||
var cookieHandler = require(pluginId + '.cookie-handler')(window.localStorage, ToughCookie, WebStorageCookieStore);
|
||||
var helpers = require(pluginId + '.helpers')(jsUtil, cookieHandler, messages, base64, errorCodes);
|
||||
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 publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs, errorCodes);
|
||||
var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs, errorCodes, ponyfills);
|
||||
|
||||
dependencyValidator.logWarnings();
|
||||
|
||||
module.exports = publicInterface;
|
||||
|
||||
43
www/dependency-validator.js
Normal file
43
www/dependency-validator.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = function init(jsUtil, cookieHandler, messages, base64, errorCodes) {
|
||||
var validSerializers = ['urlencoded', 'json', 'utf8'];
|
||||
module.exports = function init(global, jsUtil, cookieHandler, messages, base64, errorCodes, dependencyValidator, ponyfills) {
|
||||
var validSerializers = ['urlencoded', 'json', 'utf8', 'multipart'];
|
||||
var validCertModes = ['default', 'nocheck', 'pinned', 'legacy'];
|
||||
var validClientAuthModes = ['none', 'systemstore', 'buffer'];
|
||||
var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download'];
|
||||
@@ -18,7 +18,7 @@ module.exports = function init(jsUtil, cookieHandler, messages, base64, errorCod
|
||||
checkTimeoutValue: checkTimeoutValue,
|
||||
checkUploadFileOptions: checkUploadFileOptions,
|
||||
getMergedHeaders: getMergedHeaders,
|
||||
getProcessedData: getProcessedData,
|
||||
processData: processData,
|
||||
handleMissingCallbacks: handleMissingCallbacks,
|
||||
handleMissingOptions: handleMissingOptions,
|
||||
injectCookieHandler: injectCookieHandler,
|
||||
@@ -270,7 +270,7 @@ module.exports = function init(jsUtil, cookieHandler, messages, base64, errorCod
|
||||
entry.isFile = rawEntry.isFile;
|
||||
entry.name = rawEntry.name;
|
||||
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;
|
||||
|
||||
return entry;
|
||||
@@ -363,24 +363,101 @@ module.exports = function init(jsUtil, cookieHandler, messages, base64, errorCod
|
||||
return ['String'];
|
||||
case 'urlencoded':
|
||||
return ['Object'];
|
||||
default:
|
||||
case 'json':
|
||||
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 allowedDataTypes = getAllowedDataTypes(dataSerializer);
|
||||
var allowedInstanceTypes = getAllowedInstanceTypes(dataSerializer);
|
||||
|
||||
if (allowedDataTypes.indexOf(currentDataType) === -1) {
|
||||
if (allowedInstanceTypes) {
|
||||
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 (!allowedInstanceTypes && allowedDataTypes.indexOf(currentDataType) === -1) {
|
||||
throw new Error(messages.TYPE_MISMATCH_DATA + ' ' + allowedDataTypes.join(', '));
|
||||
}
|
||||
|
||||
if (dataSerializer === 'utf8') {
|
||||
data = { text: 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);
|
||||
}
|
||||
|
||||
return data;
|
||||
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) {
|
||||
|
||||
@@ -2,6 +2,7 @@ module.exports = {
|
||||
ADDING_COOKIES_NOT_SUPPORTED: 'advanced-http: "setHeader" does not support adding cookies, please use "setCookie" function instead',
|
||||
EMPTY_FILE_PATHS: 'advanced-http: "filePaths" option array must not be empty, <filePaths: string[]>',
|
||||
EMPTY_NAMES: 'advanced-http: "names" option array must not be empty, <names: string[]>',
|
||||
INSTANCE_TYPE_MISMATCH_DATA: 'advanced-http: "data" option is configured to support only following instance types:',
|
||||
INVALID_CLIENT_AUTH_ALIAS: 'advanced-http: invalid client certificate alias, needs to be a string or undefined, <alias: string | undefined>',
|
||||
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 dictionary style object',
|
||||
@@ -17,8 +18,13 @@ module.exports = {
|
||||
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 supports only following data types:',
|
||||
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[]>',
|
||||
|
||||
47
www/ponyfills.js
Normal file
47
www/ponyfills.js
Normal 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;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConfigs, errorCodes) {
|
||||
module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConfigs, errorCodes, ponyfills) {
|
||||
var publicInterface = {
|
||||
getBasicAuthHeader: getBasicAuthHeader,
|
||||
useBasicAuth: useBasicAuth,
|
||||
@@ -29,7 +29,8 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
|
||||
head: head,
|
||||
uploadFile: uploadFile,
|
||||
downloadFile: downloadFile,
|
||||
ErrorCode: errorCodes
|
||||
ErrorCode: errorCodes,
|
||||
ponyfills: ponyfills
|
||||
};
|
||||
|
||||
function getBasicAuthHeader(username, password) {
|
||||
@@ -152,8 +153,9 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
|
||||
case 'post':
|
||||
case 'put':
|
||||
case 'patch':
|
||||
var data = helpers.getProcessedData(options.data, options.serializer);
|
||||
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout, options.followRedirect, options.responseType]);
|
||||
return helpers.processData(options.data, options.serializer, function(data) {
|
||||
exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout, options.followRedirect, options.responseType]);
|
||||
});
|
||||
case 'upload':
|
||||
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]);
|
||||
|
||||
Reference in New Issue
Block a user