@talend/react-cmf
Version:
A framework built on top of best react libraries
397 lines (380 loc) • 13.4 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.HTTPError = exports.HTTP = void 0;
exports.encodePayload = encodePayload;
exports.getDefaultConfig = getDefaultConfig;
exports.handleBody = handleBody;
exports.handleCSRFToken = handleCSRFToken;
exports.handleDefaultHttpConfiguration = void 0;
exports.handleError = handleError;
exports.handleHttpResponse = handleHttpResponse;
exports.httpDelete = httpDelete;
exports.httpFetch = httpFetch;
exports.httpGet = httpGet;
exports.httpHead = httpHead;
exports.httpPatch = httpPatch;
exports.httpPost = httpPost;
exports.httpPut = httpPut;
exports.setDefaultConfig = setDefaultConfig;
exports.setDefaultLanguage = setDefaultLanguage;
exports.wrapFetch = wrapFetch;
var _effects = require("redux-saga/effects");
var _httpInterceptors = _interopRequireDefault(require("../httpInterceptors"));
var _constants = require("../middlewares/http/constants");
var _csrfHandling = require("../middlewares/http/csrfHandling");
var _lodash = require("lodash");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* Storage point for the doc setup using `setDefaultConfig`
*/
const HTTP = exports.HTTP = {
defaultConfig: null
};
/**
* merge the CSRFToken handling rule from the module defaultConfig
* into config argument
* @param {Object} config
* @returns {Function}
*/
function handleCSRFToken(config) {
return (0, _csrfHandling.mergeCSRFToken)({
security: config.security
})(config);
}
class HTTPError extends Error {
constructor({
data,
response
}) {
super(response.statusText);
this.name = `HTTP ${response.status}`;
this.data = data;
this.response = response;
}
}
/**
* handleHttpResponse - handle the http body
*
* @param {Response} response A response object
* @return {Promise} A promise that resolves with the result of parsing the body
*/
exports.HTTPError = HTTPError;
function handleBody(response, {
method
} = {}) {
if (response.status === _constants.HTTP_STATUS.NO_CONTENT || method === _constants.HTTP_METHODS.HEAD) {
return Promise.resolve({
data: '',
response
});
}
let methodBody = 'text';
const headers = (0, _lodash.get)(response, 'headers', new Headers());
const contentType = headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
methodBody = 'json';
}
if (contentType && contentType.includes('application/zip')) {
methodBody = 'blob';
}
return response[methodBody]().then(data => ({
data,
response
}));
}
/**
* handleHttpResponse - handle the http error
*
* @param {Response} response A response object
* @return {Promise} A promise that reject with the result of parsing the body
*/
function handleError(response, request = {}) {
// in case of network issue
if (response instanceof Error) {
return new HTTPError({
response,
data: response
});
}
return handleBody(response, request).then(body => new HTTPError(body));
}
/**
* handleHttpResponse - handle the http response
*
* @param {Response} response A response object
* @return {Promise} A promise that:
* - resolves with the result of parsing the body
* - reject the response
*/
function handleHttpResponse(response, request = {}) {
if (!_constants.testHTTPCode.isSuccess(response.status)) {
return Promise.reject(response);
}
return handleBody(response, request);
}
/**
* encodePayload - encore the payload if necessary
*
* @param {object} headers request headers
* @param {object} payload payload to send with the request
* @return {string|FormData} The encoded payload.
*/
function encodePayload(headers, payload) {
const type = headers['Content-Type'];
if (payload instanceof FormData || typeof payload === 'string') {
return payload;
} else if (type && type.includes('json')) {
return JSON.stringify(payload);
}
return payload;
}
/**
* httpFetch - call the api fetch to request the url
*
* @param {string} url url to request
* @param {object} config option that you want apply to the request
* @param {string} method = HTTP_METHODS.GET method to apply
* @param {object} payload payload to send with the request
* @return {Promise} A Promise that resolves to a Response object.
*/
function httpFetch(url, config, method, payload) {
const defaultHeaders = {
Accept: 'application/json',
'Content-Type': 'application/json'
};
/**
* If the playload is an instance of FormData the body should be set to this object
* and the Content-type header should be stripped since the browser
* have to build a special headers with file boundary in if said FormData is used to upload file
*/
if (payload instanceof FormData) {
delete defaultHeaders['Content-Type'];
}
const params = (0, _lodash.merge)({
credentials: 'include',
headers: defaultHeaders,
method
}, {
...HTTP.defaultConfig,
...config
});
return fetch(url, handleCSRFToken({
...params,
body: encodePayload(params.headers, payload)
})).then(response => handleHttpResponse(response, params)).catch(response => handleError(response, params));
}
/**
* function - wrap the fetch request with the actions errors
*
* @param {string} url url to request
* @param {object} config option that you want apply to the request
* @param {string} method = HTTP_METHODS.GET method to apply
* @param {object} payload payload to send with the request
* @param {object} options options to deal with cmf automatically
* @return {object} the response of the request
*/
function* wrapFetch(url, config, method = _constants.HTTP_METHODS.GET, payload, options) {
const newConfig = yield (0, _effects.call)(_httpInterceptors.default.onRequest, {
url,
method,
payload,
...config
});
const answer = yield (0, _effects.call)(httpFetch, newConfig.url, newConfig, newConfig.method, newConfig.payload);
yield (0, _effects.call)(_httpInterceptors.default.onResponse, answer);
const silent = (0, _lodash.get)(options, 'silent');
if (silent !== true && answer instanceof Error) {
yield (0, _effects.put)({
error: {
// allow RFC-7807 compliance
...(0, _lodash.get)(answer, 'data', {}),
// legacy properties
message: (0, _lodash.get)(answer, 'data.message'),
stack: {
status: (0, _lodash.get)(answer, 'response.status')
}
},
url,
config,
method,
payload,
options,
type: _constants.ACTION_TYPE_HTTP_ERRORS
});
}
return answer;
}
/**
* function - fetch a url with POST method
*
* @param {string} url url to request
* @param {object} payload payload to send with the request
* @param {object} config option that you want apply to the request
* @param {object} options options to deal with cmf automatically
* @example
* import { sagas } from '@talend/react-cmf';
* import { call } from 'redux-saga/effects'
* yield call(sagas.http.post, '/foo', {foo: 42});
*/
function* httpPost(url, payload, config, options) {
return yield* wrapFetch(url, config, _constants.HTTP_METHODS.POST, payload, options);
}
/**
* function - fetch a url with PATCH method
*
* @param {string} url url to request
* @param {object} payload payload to send with the request
* @param {object} config option that you want apply to the request
* @param {object} options options to deal with cmf automatically
* @example
* import { sagas } from '@talend/react-cmf';
* import { call } from 'redux-saga/effects'
* yield call(sagas.http.patch, '/foo', {foo: 42});
*/
function* httpPatch(url, payload, config, options) {
return yield* wrapFetch(url, config, _constants.HTTP_METHODS.PATCH, payload, options);
}
/**
* function - fetch a url with PUT method
*
* @param {string} url url to request
* @param {object} payload payload to send with the request
* @param {object} config option that you want apply to the request
* @param {object} options options to deal with cmf automatically
* @example
* import { sagas } from '@talend/react-cmf';
* import { call } from 'redux-saga/effects'
* yield call(sagas.http.put, '/foo', {foo: 42});
*/
function* httpPut(url, payload, config, options) {
return yield* wrapFetch(url, config, _constants.HTTP_METHODS.PUT, payload, options);
}
/**
* function - fetch a url with DELETE method
*
* @param {string} url url to request
* @param {object} config option that you want apply to the request
* @param {object} options options to deal with cmf automatically
* @example
* import { sagas } from '@talend/react-cmf';
* import { call } from 'redux-saga/effects'
* yield call(sagas.http.delete, '/foo');
*/
function* httpDelete(url, config, options) {
return yield* wrapFetch(url, config, _constants.HTTP_METHODS.DELETE, undefined, options);
}
/**
* function - fetch a url with GET method
*
* @param {string} url url to request
* @param {object} config option that you want apply to the request
* @param {object} options options to deal with cmf automatically
* @example
* import { sagas } from '@talend/react-cmf';
* import { call } from 'redux-saga/effects'
* yield call(sagas.http.get, '/foo');
*/
function* httpGet(url, config, options) {
return yield* wrapFetch(url, config, _constants.HTTP_METHODS.GET, undefined, options);
}
/**
* function - fetch a url with GET method
*
* @param {string} url url to request
* @param {object} config option that you want apply to the request
* @param {object} options options to deal with cmf automatically
* @example
* import { sagas } from '@talend/react-cmf';
* import { call } from 'redux-saga/effects'
* yield call(sagas.http.get, '/foo');
*/
function* httpHead(url, config, options) {
return yield* wrapFetch(url, config, _constants.HTTP_METHODS.HEAD, undefined, options);
}
/**
* setDefaultHeader - define a default config to use with the saga http
* this default config is stored in this module for the whole application
*
* @param {object} config key/value of header to apply
* @example
* import { setDefaultConfig } from '@talend/react-cmf/sagas/http';
* setDefaultConfig({headers: {
* 'Accept-Language': preferredLanguage,
* }});
*/
function setDefaultConfig(config) {
if (HTTP.defaultConfig) {
throw new Error('ERROR: setDefaultConfig should not be called twice, if you wish to change the language use setDefaultLanguage api.');
}
HTTP.defaultConfig = config;
}
/**
* To change only the Accept-Language default headers
* on the global http defaultConfig
* @param {String} language
*/
function setDefaultLanguage(language) {
if ((0, _lodash.get)(HTTP, 'defaultConfig.headers')) {
HTTP.defaultConfig.headers['Accept-Language'] = language;
} else {
// eslint-disable-next-line no-console
throw new Error('ERROR: you should call setDefaultConfig.');
}
}
const handleDefaultHttpConfiguration = exports.handleDefaultHttpConfiguration = (0, _lodash.curry)((defaultHttpConfig, httpConfig) =>
/**
* Wall of explain
* merge mutate your object see https://lodash.com/docs/4.17.10#merge little note at the
* end of the documentation, so why ? don't know but its bad.
*
* so defaultHttpConfig was mutated inside the curried function and applied to
* all other call providing httpConfig, leading to interesting bug like having one time
* httpConfig override merged into defaultHttConfig.
* a test with two sccessive call will detect this issue.
*/
(0, _lodash.merge)({}, defaultHttpConfig, httpConfig));
/**
* getDefaultConfig - return the defaultConfig
*
* @return {object} the defaultConfig used by cmf
*/
function getDefaultConfig() {
return HTTP.defaultConfig;
}
var _default = exports.default = {
delete: httpDelete,
get: httpGet,
head: httpHead,
post: httpPost,
put: httpPut,
patch: httpPatch,
setDefaultConfig,
setDefaultLanguage,
getDefaultConfig,
create(createConfig = {}) {
const configEnhancer = handleDefaultHttpConfiguration(createConfig);
return {
delete: function* configuredDelete(url, config = {}, options = {}) {
return yield (0, _effects.call)(httpDelete, url, configEnhancer(config), options);
},
get: function* configuredGet(url, config = {}, options = {}) {
return yield (0, _effects.call)(httpGet, url, configEnhancer(config), options);
},
post: function* configuredPost(url, payload, config = {}, options = {}) {
return yield (0, _effects.call)(httpPost, url, payload, configEnhancer(config), options);
},
put: function* configuredPut(url, payload, config = {}, options = {}) {
return yield (0, _effects.call)(httpPut, url, payload, configEnhancer(config), options);
},
patch: function* configuredPatch(url, payload, config = {}, options = {}) {
return yield (0, _effects.call)(httpPatch, url, payload, configEnhancer(config), options);
},
head: function* configuredPatch(url, config = {}, options = {}) {
return yield (0, _effects.call)(httpHead, url, configEnhancer(config), options);
}
};
}
};
//# sourceMappingURL=http.js.map
;