diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..ff30c44
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "editor.tabSize": 2
+}
\ No newline at end of file
diff --git a/plugin.xml b/plugin.xml
index 45d2d42..418a77e 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -17,6 +17,7 @@
+
diff --git a/test/e2e-specs.js b/test/e2e-specs.js
index fbb8308..83f753d 100644
--- a/test/e2e-specs.js
+++ b/test/e2e-specs.js
@@ -799,12 +799,12 @@ const tests = [
}
},
{
- disabled: true,
description: 'should serialize FormData instance correctly when it contains string value',
expected: 'resolved: {"status": 200, ...',
before: helpers.setMultipartSerializer,
func: function (resolve, reject) {
- var formData = new FormData();
+ var ponyfills = cordova.plugin.http.ponyfills;
+ var formData = new ponyfills.FormData();
formData.append('myString', 'This is a test!');
var url = 'https://httpbin.org/anything';
@@ -818,13 +818,13 @@ const tests = [
}
},
{
- disabled: true,
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 FormData();
+ var formData = new ponyfills.FormData();
formData.append('CordovaLogo', blob);
var url = 'https://httpbin.org/anything';
diff --git a/test/e2e-tooling/caps.js b/test/e2e-tooling/caps.js
index 8cc4a74..dff023e 100644
--- a/test/e2e-tooling/caps.js
+++ b/test/e2e-tooling/caps.js
@@ -22,8 +22,7 @@ const configs = {
},
localAndroidEmulator: {
platformName: 'Android',
- platformVersion: '9',
- automationName: 'XCUITest',
+ platformVersion: '5',
deviceName: 'Android Emulator',
autoWebview: true,
fullReset: true,
diff --git a/test/js-specs.js b/test/js-specs.js
index 51516c9..8a2698c 100644
--- a/test/js-specs.js
+++ b/test/js-specs.js
@@ -529,7 +529,7 @@ describe('Common helpers', function () {
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 helpers = require('../www/helpers')(mockWindow, jsUtil, null, messages, base64, null, dependencyValidator, {});
const testString = 'Test String öäüß 👍😉';
const testStringBase64 = Buffer.from(testString).toString('base64');
@@ -540,11 +540,6 @@ describe('Common helpers', function () {
(() => helpers.processData({}, 'utf8')).should.throw(messages.TYPE_MISMATCH_DATA);
});
- it('throws an error when needed Web API is not available', () => {
- const helpers = require('../www/helpers')({}, jsUtil, null, messages, null, null);
- (() => helpers.processData(null, 'multipart')).should.throw(`${messages.INSTANCE_TYPE_NOT_SUPPORTED} FormData`);
- });
-
it('throws an error when given data does not match allowed instance types', () => {
(() => helpers.processData('myString', 'multipart')).should.throw(messages.INSTANCE_TYPE_MISMATCH_DATA);
});
@@ -640,12 +635,12 @@ describe('Dependency Validator', function () {
});
});
- describe('checkFormDataApi()', function () {
- it('throws an error if FormData.entries() API is not supported', function () {
+ 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.checkFormDataApi()).should.throw(messages.MISSING_FORMDATA_ENTRIES_API);
+ (() => validator.checkFormDataInstance({})).should.throw(messages.MISSING_FORMDATA_ENTRIES_API);
});
});
@@ -658,3 +653,94 @@ describe('Dependency Validator', function () {
});
});
});
+
+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);
+ })
+ });
+ });
+});
diff --git a/test/mocks/File.mock.js b/test/mocks/File.mock.js
index 1c1ccfc..9806262 100644
--- a/test/mocks/File.mock.js
+++ b/test/mocks/File.mock.js
@@ -4,9 +4,14 @@ 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;
+ }
}
diff --git a/www/advanced-http.js b/www/advanced-http.js
index 54f90c0..bd1237a 100644
--- a/www/advanced-http.js
+++ b/www/advanced-http.js
@@ -15,9 +15,10 @@ 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 dependencyValidator = require(pluginId + '.dependency-validator')(window, window.console, messages);
-var helpers = require(pluginId + '.helpers')(window, jsUtil, cookieHandler, messages, base64, errorCodes, dependencyValidator);
+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();
diff --git a/www/dependency-validator.js b/www/dependency-validator.js
index 9858a07..9951034 100644
--- a/www/dependency-validator.js
+++ b/www/dependency-validator.js
@@ -2,7 +2,7 @@ module.exports = function init(global, console, messages) {
var interface = {
checkBlobApi: checkBlobApi,
checkFileReaderApi: checkFileReaderApi,
- checkFormDataApi: checkFormDataApi,
+ checkFormDataInstance: checkFormDataInstance,
checkTextEncoderApi: checkTextEncoderApi,
logWarnings: logWarnings,
};
@@ -29,8 +29,8 @@ module.exports = function init(global, console, messages) {
}
}
- function checkFormDataApi() {
- if (!global.FormData || !global.FormData.prototype || !global.FormData.prototype.entries) {
+ function checkFormDataInstance(instance) {
+ if (!instance || !instance.entries) {
throw new Error(messages.MISSING_FORMDATA_ENTRIES_API);
}
}
diff --git a/www/helpers.js b/www/helpers.js
index 05486d6..4b4ed28 100644
--- a/www/helpers.js
+++ b/www/helpers.js
@@ -1,4 +1,4 @@
-module.exports = function init(global, jsUtil, cookieHandler, messages, base64, errorCodes, dependencyValidator) {
+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'];
@@ -370,24 +370,30 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64,
}
}
- function getAllowedInstanceType(dataSerializer) {
- return dataSerializer === 'multipart' ? 'FormData' : null;
+ function getAllowedInstanceTypes(dataSerializer) {
+ return dataSerializer === 'multipart' ? ['FormData'] : null;
}
function processData(data, dataSerializer, cb) {
var currentDataType = jsUtil.getTypeOf(data);
var allowedDataTypes = getAllowedDataTypes(dataSerializer);
- var allowedInstanceType = getAllowedInstanceType(dataSerializer);
+ var allowedInstanceTypes = getAllowedInstanceTypes(dataSerializer);
- if (allowedInstanceType && !global[allowedInstanceType]) {
- throw new Error(messages.INSTANCE_TYPE_NOT_SUPPORTED + ' ' + allowedInstanceType);
+ 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 (allowedInstanceType && !(data instanceof global[allowedInstanceType])) {
- throw new Error(messages.INSTANCE_TYPE_MISMATCH_DATA + ' ' + allowedInstanceType);
- }
-
- if (!allowedInstanceType && allowedDataTypes.indexOf(currentDataType) === -1) {
+ if (!allowedInstanceTypes && allowedDataTypes.indexOf(currentDataType) === -1) {
throw new Error(messages.TYPE_MISMATCH_DATA + ' ' + allowedDataTypes.join(', '));
}
@@ -404,8 +410,8 @@ module.exports = function init(global, jsUtil, cookieHandler, messages, base64,
function processFormData(data, cb) {
dependencyValidator.checkBlobApi();
dependencyValidator.checkFileReaderApi();
- dependencyValidator.checkFormDataApi();
dependencyValidator.checkTextEncoderApi();
+ dependencyValidator.checkFormDataInstance(data);
var textEncoder = new global.TextEncoder('utf8');
var iterator = data.entries();
diff --git a/www/messages.js b/www/messages.js
index 03e02dc..3a4f6b8 100644
--- a/www/messages.js
+++ b/www/messages.js
@@ -3,7 +3,6 @@ module.exports = {
EMPTY_FILE_PATHS: 'advanced-http: "filePaths" option array must not be empty, ',
EMPTY_NAMES: 'advanced-http: "names" option array must not be empty, ',
INSTANCE_TYPE_MISMATCH_DATA: 'advanced-http: "data" option is configured to support only following instance types:',
- INSTANCE_TYPE_NOT_SUPPORTED: 'advanced-http: this webview does not support following Web API which is needed for this plugin:',
INVALID_CLIENT_AUTH_ALIAS: 'advanced-http: invalid client certificate alias, needs to be a string or 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',
@@ -22,7 +21,7 @@ module.exports = {
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: This webview does not implement FormData API specification correctly, FormData.entries() is missing. If you want to use "multipart/form-data" requests, you need to load a polyfill library before loading this plugin. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.',
+ MISSING_FORMDATA_ENTRIES_API: 'advanced-http: Given instance of FormData does not implement FormData API specification correctly, FormData.entries() is missing. If you want to use "multipart/form-data" requests, you can use an included ponyfill. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.',
MISSING_TEXT_ENCODER_API: 'advanced-http: TextEncoder API is not supported in this webview. If you want to use "multipart/form-data" requests, you need to load a polyfill library before loading this plugin. Check out https://github.com/silkimen/cordova-plugin-advanced-http/wiki/Web-APIs-required-for-Multipart-requests for more info.',
POST_PROCESSING_FAILED: 'advanced-http: an error occured during post processing response:',
TYPE_MISMATCH_DATA: 'advanced-http: "data" option is configured to support only following data types:',
diff --git a/www/ponyfills.js b/www/ponyfills.js
new file mode 100644
index 0000000..67b9bbc
--- /dev/null
+++ b/www/ponyfills.js
@@ -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;
+};
\ No newline at end of file
diff --git a/www/public-interface.js b/www/public-interface.js
index 10d81f6..09fb1a3 100644
--- a/www/public-interface.js
+++ b/www/public-interface.js
@@ -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) {