Skip to content

Commit

Permalink
Merge pull request #62 from oat-sa/feature/NEX-831_implement-fetch-ba…
Browse files Browse the repository at this point in the history
…sed-request

Feature/nex 831 implement fetch based request
  • Loading branch information
krampstudio authored May 5, 2020
2 parents afd5661 + a6e1cf8 commit bf99f2d
Show file tree
Hide file tree
Showing 9 changed files with 592 additions and 144 deletions.
3 changes: 2 additions & 1 deletion environment/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ define(['/node_modules/@oat-sa/tao-core-libs/dist/pathdefinition.js'], function(

'jquery.mockjax': '/node_modules/jquery-mockjax/dist/jquery.mockjax',
'webcrypto-shim': '/node_modules/webcrypto-shim/webcrypto-shim',
'idb-wrapper': '/node_modules/idb-wrapper/idbstore'
'idb-wrapper': '/node_modules/idb-wrapper/idbstore',
'fetch-mock': '/node_modules/fetch-mock/es5/client-bundle'
},
libPathDefinition
),
Expand Down
89 changes: 88 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oat-sa/tao-core-sdk",
"version": "1.3.0",
"version": "1.4.0",
"displayName": "TAO Core SDK",
"description": "Core libraries of TAO",
"homepage": "https://github.com/oat-sa/tao-core-sdk-fe#readme",
Expand Down Expand Up @@ -50,6 +50,7 @@
"eslint": "^5.16.0",
"eslint-plugin-es": "^1.4.0",
"eslint-plugin-jsdoc": "^4.8.3",
"fetch-mock": "^9.4.0",
"gamp": "0.2.1",
"glob": "^7.1.3",
"handlebars": "1.3.0",
Expand Down
113 changes: 113 additions & 0 deletions src/core/fetchRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2020 (original work) Open Assessment Technologies SA ;
*/

/**
* !!! IE11 requires polyfill https://www.npmjs.com/package/whatwg-fetch
* Creates an HTTP request to the url based on the provided parameters
* Request is based on fetch, so behaviour and parameters are the same, except
* - every response where response code is not 2xx will be rejected and
* - every response will be parsed as json.
* @param {string} url - url that should be requested
* @param {object} options - fetch request options that implements RequestInit (https://fetch.spec.whatwg.org/#requestinit)
* @param {integer} [options.timeout] - (default: 5000) if timeout reached, the request will be rejected
* @param {object} [options.jwtTokenHandler] - core/jwt/jwtTokenHandler instance that should be used during request
* @returns {Promise<Response>} resolves with http Response object
*/
const requestFactory = (url, options) => {
options = Object.assign(
{
timeout: 5000
},
options
);

let flow = Promise.resolve();

if (options.jwtTokenHandler) {
flow = flow
.then(options.jwtTokenHandler.getToken)
.then(token => ({
Authorization: `Bearer ${token}`
}))
.then(headers => {
options.headers = Object.assign({}, options.headers, headers);
});
}

flow = flow.then(() => Promise.race([
fetch(url, options),
new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Timeout')), options.timeout);
})
]));

if (options.jwtTokenHandler) {
flow = flow.then(response => {
if (response.status === 401) {
return options.jwtTokenHandler
.refreshToken()
.then(options.jwtTokenHandler.getToken)
.then(token => {
options.headers.Authorization = `Bearer ${token}`;
return fetch(url, options);
});
}

return Promise.resolve(response);
});
}

/**
* Stores the original response
*/
let originalResponse;
/**
* Stores the response code
*/
let responseCode;

flow = flow.then(response => {
originalResponse = response;
responseCode = response.status;
return response.json().catch(() => ({}));
})
.then(response => {
// successful request
if (responseCode === 200 || response.success === true) {
return response;
}

if (responseCode === 204) {
return null;
}

// create error
let err;
if (response.errorCode) {
err = new Error(`${response.errorCode} : ${response.errorMsg || response.errorMessage || response.error}`);
} else {
err = new Error(`${responseCode} : Request error`);
}
err.response = originalResponse;
throw err;
});

return flow;
};

export default requestFactory;
25 changes: 17 additions & 8 deletions src/core/jwt/jwtTokenHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@

/**
* Give and refresh JWT token
* !!! The module uses native fetch request to refresh token.
* !!! IE11 requires polyfill https://www.npmjs.com/package/whatwg-fetch
* @module core/jwtTokenHandler
* @author Tamas Besenyei <[email protected]>
*/

import jwtTokenStoreFactory from 'core/jwt/jwtTokenStore';
import coreRequest from 'core/request';
import promiseQueue from 'core/promiseQueue';

/**
Expand Down Expand Up @@ -54,14 +55,22 @@ const jwtTokenHandlerFactory = function jwtTokenHandlerFactory({serviceName = 't
if (!refreshToken) {
throw new Error('Refresh token is not available');
} else {
return coreRequest({
url: refreshTokenUrl,
return fetch(refreshTokenUrl, {
method: 'POST',
data: JSON.stringify({ refreshToken }),
dataType: 'json',
contentType: 'application/json',
noToken: true
}).then(({ accessToken }) => tokenStorage.setAccessToken(accessToken).then(() => accessToken));
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ refreshToken })
})
.then(response => {
if (response.status === 200) {
return response.json();
}
const error = new Error('Unsuccessful token refresh');
error.response = response;
return Promise.reject(error);
})
.then(({ accessToken }) => tokenStorage.setAccessToken(accessToken).then(() => accessToken));

}
});
Expand Down
21 changes: 21 additions & 0 deletions test/core/fetchRequest/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Core - Fetch Request</title>
<script type="text/javascript" src="/environment/require.js"></script>
<script type="text/javascript">
require(['/environment/config.js'], function() {
require(['qunitEnv'], function() {
require(['test/core/fetchRequest/test'], function() {
QUnit.start();
});
});
});
</script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>
Loading

0 comments on commit bf99f2d

Please sign in to comment.