From 34a559bc6ed5349aaa1b7ec40eaa80108a8a4068 Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Thu, 26 Sep 2019 04:31:08 +0200 Subject: [PATCH] feature #253: add support for response type "json" --- CHANGELOG.md | 1 + README.md | 3 +- test/js-specs.js | 104 ++++++++++++++++++++++++++++++++++++++-- www/advanced-http.js | 5 +- www/error-codes.js | 9 ++++ www/helpers.js | 47 +++++++++++------- www/messages.js | 1 + www/public-interface.js | 15 ++---- 8 files changed, 150 insertions(+), 35 deletions(-) create mode 100644 www/error-codes.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 65b0520..f643d0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 2.2.0 - Feature #239: add enumeration style object for error codes +- Feature #253: add support for response type "json" ## 2.1.1 diff --git a/README.md b/README.md index fec18ef..1e26607 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,8 @@ The options object contains following keys: * `params`: query params to be appended to the URL (only applicable on `get`, `head`, `delete`, `upload` or `download` methods) * `serializer`: data serializer to be used (only applicable on `post`, `put` or `patch` methods), defaults to global serializer value, see [setDataSerializer](#setDataSerializer) for supported values * `responseType`: expected response type, defaults to `text`, needs to be one of the following values: - * `text`: data is returned as decoded string, use this for all kinds of string responses (e.g. JSON, XML, HTML, plain text, etc.) + * `text`: data is returned as decoded string, use this for all kinds of string responses (e.g. XML, HTML, plain text, etc.) + * `json` data is treated as JSON and returned as parsed object * `arraybuffer`: data is returned as [ArrayBuffer instance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) * `blob`: data is returned as [Blob instance](https://developer.mozilla.org/en-US/docs/Web/API/Blob) * `timeout`: timeout value for the request in seconds, defaults to global timeout value diff --git a/test/js-specs.js b/test/js-specs.js index e860eb5..f2678c1 100644 --- a/test/js-specs.js +++ b/test/js-specs.js @@ -4,6 +4,7 @@ const should = chai.should(); describe('Advanced HTTP public interface', function () { const messages = require('../www/messages'); + let http = {}; const noop = () => { /* intentionally doing nothing */ }; @@ -13,16 +14,17 @@ describe('Advanced HTTP public interface', function () { const jsUtil = require('../www/js-util'); const ToughCookie = require('../www/umd-tough-cookie'); const lodash = require('../www/lodash'); + 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); + const helpers = require('../www/helpers')(jsUtil, cookieHandler, messages, errorCodes); const urlUtil = require('../www/url-util')(jsUtil); - return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs }; + return { exec: noop, cookieHandler, urlUtil: urlUtil, helpers, globalConfigs, errorCodes }; }; const loadHttp = (deps) => { - http = require('../www/public-interface')(deps.exec, deps.cookieHandler, deps.urlUtil, deps.helpers, deps.globalConfigs); + http = require('../www/public-interface')(deps.exec, deps.cookieHandler, deps.urlUtil, deps.helpers, deps.globalConfigs, deps.errorCodes); }; beforeEach(() => { @@ -377,4 +379,100 @@ describe('Common helpers', function () { .should.throw(messages.INVALID_FOLLOW_REDIRECT_VALUE); }); }); + + describe('injectRawResponseHandler()', function () { + const jsUtil = require('../www/js-util'); + const messages = require('../www/messages'); + const errorCodes = require('../www/error-codes'); + + const fakeBase64 = { toArrayBuffer: () => 'fakeArrayBuffer' }; + + global.Blob = function (array, meta) { + this.isFakeBlob = true; + this.array = array; + this.meta = meta; + }; + + it('does not change response data if it is an ArrayBuffer', () => { + const helpers = require('../www/helpers')(jsUtil, null, messages, null, errorCodes); + const buffer = new ArrayBuffer(5); + const handler = helpers.injectRawResponseHandler( + 'arraybuffer', + response => response.data.should.be.equal(buffer) + ); + + handler({ data: buffer }); + }); + + it('does not change response data if it is a Blob', () => { + const fakeJsUtil = { getTypeOf: () => 'Blob' }; + const helpers = require('../www/helpers')(fakeJsUtil, null, messages, null, errorCodes); + const handler = helpers.injectRawResponseHandler( + 'blob', + response => response.data.should.be.equal('fakeData') + ); + + handler({ data: 'fakeData' }); + }); + + it('does not change response data if response type is "text"', () => { + const helpers = require('../www/helpers')(jsUtil, null, messages, null, errorCodes); + const example = 'exampleText'; + const handler = helpers.injectRawResponseHandler( + 'text', + response => response.data.should.be.equal(example) + ); + + handler({ data: example }); + }); + + it('handles response type "json" correctly', () => { + const fakeData = { myString: 'bla', myNumber: 10 }; + const helpers = require('../www/helpers')(jsUtil, null, messages, null, errorCodes); + const handler = helpers.injectRawResponseHandler( + 'json', + response => response.data.should.be.eql(fakeData) + ); + + handler({ data: JSON.stringify(fakeData) }); + }); + + it('handles response type "arraybuffer" correctly', () => { + const helpers = require('../www/helpers')(jsUtil, null, messages, fakeBase64, errorCodes); + const handler = helpers.injectRawResponseHandler( + 'arraybuffer', + response => response.data.should.be.equal('fakeArrayBuffer') + ); + + handler({ data: 'myString' }); + }); + + it('handles response type "blob" correctly', () => { + const helpers = require('../www/helpers')(jsUtil, null, messages, fakeBase64, errorCodes); + const handler = helpers.injectRawResponseHandler( + 'blob', + (response) => { + response.data.isFakeBlob.should.be.equal(true); + response.data.array.should.be.eql(['fakeArrayBuffer']); + response.data.meta.type.should.be.equal('fakeType'); + } + ); + + handler({ data: 'myString', headers: { 'content-type': 'fakeType'} }); + }); + + it('calls failure callback when post-processing fails', () => { + const helpers = require('../www/helpers')(jsUtil, null, messages, fakeBase64, errorCodes); + const handler = helpers.injectRawResponseHandler( + 'json', + null, + (response) => { + response.status.should.be.equal(errorCodes.POST_PROCESSING_FAILED); + response.error.should.include('Unexpected token N in JSON at position 0'); + } + ); + + handler({ data: 'NotValidJson' }); + }); + }); }) diff --git a/www/advanced-http.js b/www/advanced-http.js index a2a8700..ee5faac 100644 --- a/www/advanced-http.js +++ b/www/advanced-http.js @@ -7,14 +7,15 @@ var pluginId = module.id.slice(0, module.id.lastIndexOf('.')); var exec = require('cordova/exec'); var base64 = require('cordova/base64'); var messages = require(pluginId + '.messages'); +var errorCodes = require(pluginId + '.error-codes'); var globalConfigs = require(pluginId + '.global-configs'); var jsUtil = require(pluginId + '.js-util'); 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); +var helpers = require(pluginId + '.helpers')(jsUtil, cookieHandler, messages, base64, errorCodes); var urlUtil = require(pluginId + '.url-util')(jsUtil); -var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs); +var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs, errorCodes); module.exports = publicInterface; diff --git a/www/error-codes.js b/www/error-codes.js new file mode 100644 index 0000000..a0bfd6c --- /dev/null +++ b/www/error-codes.js @@ -0,0 +1,9 @@ +module.exports = { + GENERIC: -1, + SSL_EXCEPTION: -2, + SERVER_NOT_FOUND: -3, + TIMEOUT: -4, + UNSUPPORTED_URL: -5, + NOT_CONNECTED: -6, + POST_PROCESSING_FAILED: -7, +}; diff --git a/www/helpers.js b/www/helpers.js index 2598b99..e7402b4 100644 --- a/www/helpers.js +++ b/www/helpers.js @@ -1,9 +1,9 @@ -module.exports = function init(jsUtil, cookieHandler, messages, base64) { +module.exports = function init(jsUtil, cookieHandler, messages, base64, errorCodes) { var validSerializers = ['urlencoded', 'json', 'utf8']; var validCertModes = ['default', 'nocheck', 'pinned', 'legacy']; var validClientAuthModes = ['none', 'systemstore', 'buffer']; var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download']; - var validResponseTypes = ['text','arraybuffer', 'blob']; + var validResponseTypes = ['text', 'json', 'arraybuffer', 'blob']; var interface = { b64EncodeUnicode: b64EncodeUnicode, @@ -234,30 +234,43 @@ module.exports = function init(jsUtil, cookieHandler, messages, base64) { } } - function injectRawResponseHandler(responseType, cb) { + function injectRawResponseHandler(responseType, success, failure) { return function (response) { var dataType = jsUtil.getTypeOf(response.data); // don't need post-processing if it's already binary type (on browser platform) if (dataType === 'ArrayBuffer' || dataType === 'Blob') { - return cb(response); + return success(response); } - // arraybuffer - if (responseType === validResponseTypes[1]) { - var buffer = base64.toArrayBuffer(response.data); - response.data = buffer; - } + try { + // json + if (responseType === validResponseTypes[1]) { + response.data = JSON.parse(response.data); + } - // blob - if (responseType === validResponseTypes[2]) { - var buffer = base64.toArrayBuffer(response.data); - var type = response.headers['content-type'] || ''; - var blob = new Blob([ buffer ], { type: type }); - response.data = blob; - } + // arraybuffer + else if (responseType === validResponseTypes[2]) { + response.data = base64.toArrayBuffer(response.data); + } - cb(response); + // blob + else if (responseType === validResponseTypes[3]) { + var buffer = base64.toArrayBuffer(response.data); + var type = response.headers['content-type'] || ''; + var blob = new Blob([ buffer ], { type: type }); + response.data = blob; + } + + success(response); + } catch (error) { + failure({ + status: errorCodes.POST_PROCESSING_FAILED, + error: messages.POST_PROCESSING_FAILED + ' ' + error.message, + url: response.url, + headers: response.headers + }); + } } } diff --git a/www/messages.js b/www/messages.js index dd14eaf..37f15c0 100644 --- a/www/messages.js +++ b/www/messages.js @@ -16,4 +16,5 @@ module.exports = { INVALID_TIMEOUT_VALUE: 'advanced-http: invalid timeout value, needs to be a positive numeric value', MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function', MANDATORY_SUCCESS: 'advanced-http: missing mandatory "onSuccess" callback function', + POST_PROCESSING_FAILED: 'advanced-http: an error occured during post processing response:', }; diff --git a/www/public-interface.js b/www/public-interface.js index 694900a..80117d1 100644 --- a/www/public-interface.js +++ b/www/public-interface.js @@ -1,13 +1,4 @@ -module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConfigs) { - var ErrorCode = { - Generic: -1, - SslException: -2, - ServerNotFound: -3, - Timeout: -4, - UnsupportedUrl: -5, - NotConnected: -6, - }; - +module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConfigs, errorCodes) { var publicInterface = { getBasicAuthHeader: getBasicAuthHeader, useBasicAuth: useBasicAuth, @@ -38,7 +29,7 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf head: head, uploadFile: uploadFile, downloadFile: downloadFile, - ErrorCode: ErrorCode + ErrorCode: errorCodes }; function getBasicAuthHeader(username, password) { @@ -155,7 +146,7 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf var headers = helpers.getMergedHeaders(url, options.headers, globalConfigs.headers); var onFail = helpers.injectCookieHandler(url, failure); - var onSuccess = helpers.injectCookieHandler(url, helpers.injectRawResponseHandler(options.responseType, success)); + var onSuccess = helpers.injectCookieHandler(url, helpers.injectRawResponseHandler(options.responseType, success, failure)); switch (options.method) { case 'post':