UNPKG

@talend/react-cmf

Version:

A framework built on top of best react libraries

247 lines (233 loc) 6.83 kB
import { HTTP_METHODS, HTTP_STATUS, testHTTPCode } from './constants'; import { mergeCSRFToken } from './csrfHandling'; import http from '../../actions/http'; import interceptors from '../../httpInterceptors'; /** * @typedef {Object} Action * @property {string} type - Action type */ /** * @typedef {Object} Response * @property {string} status * @property {string} statusText * @property {string} ok * @property {string} redirected * @property {string} type * @property {string} url */ /** * @typedef {Object} Stack * @property {Object} response * @property {Object} message */ /** * @typedef {Object} HttpError * @property {string} name * @property {string} message * @property {string} number * @property {Stack} stack */ /** * @typedef {Object.<string, string>} Headers */ /** * @callback onError * @param {HTTPConfig} action * @param {HttpError} error * @return {Action} */ /** * @callback onResponse * @param {HTTPConfig} action * @param {Object} response * @return {Action} */ /** * @typedef {Object} HTTPConfig * @property {string} body * @property {string} credentials * @property {Headers} headers * @property {string} method - See ./constants.js for a list of suitable method * @property {onError | string} onError * @property {onResponse | string} onResponse * @property {string} onSend - a redux action type */ /** * @typedef {Object} Security * @property {String} CSRFTokenCookieKey - on which value the token should be found in the cookie * @property {String} CSRFTokenHeaderKey - on which header key the token should be sent */ /** * @typedef {Object} Config * @property {Security} security */ import { flow, has, get } from "lodash"; export const DEFAULT_HTTP_HEADERS = { Accept: 'application/json', 'Content-Type': 'application/json' }; /** * check if the provided redux action contain element relative to a * fetch side effect. * If the Action contain nested keys 'cmf.http' it is a fetch descriptor * thus return True * @param {Action} action * @returns {bool} */ export function isHTTPRequest(action) { return action.type in HTTP_METHODS || has(action, 'cmf.http'); } /** * @param {Object} action redux action, sjhould cotains *method* attribute * @return {String} in known HTTP methods */ export function getMethod(action) { return HTTP_METHODS[action.type]; } export function mergeConfiguredHeader(config) { // still need to keep the previous header added by action return options => { const headerMergedConfig = { ...options, headers: { ...DEFAULT_HTTP_HEADERS, ...config.headers, ...options.headers } }; if (headerMergedConfig.body instanceof FormData) { delete headerMergedConfig.headers['Content-Type']; } return headerMergedConfig; }; } export function mergeOptions(action) { const options = { method: getMethod(action), credentials: 'same-origin', ...action }; if (typeof options.body === 'object' && !(options.body instanceof FormData)) { options.body = JSON.stringify(options.body); } delete options.type; return options; } export function HTTPError(response) { let headers = get(response, 'headers/values'); if (headers) { headers = [...headers()]; } this.name = `HTTP ${response.status}`; this.message = response.statusText; this.stack = { response, headers, status: response.status, statusText: response.statusText, ok: response.ok, redirected: response.redirected, type: response.type, // basic, cors url: response.url }; } HTTPError.prototype = Object.create(Error.prototype); HTTPError.prototype.constructor = HTTPError; export function status(response) { if (testHTTPCode.isSuccess(response.status)) { return Promise.resolve(response); } return Promise.reject(new HTTPError(response)); } export function handleResponse(response) { if (response.status === HTTP_STATUS.NO_CONTENT) { return Promise.resolve({}); } if (response.json) { return response.json().then(json => ({ data: json, headers: response.headers })); } return Promise.reject(new HTTPError(response)); } /** * Factory to create error handler. * The provided function will dispatch action with the following types * @param {function} dispatch * @param {Object} httpAction */ function getOnError(dispatch, httpAction) { return function onHTTPError(error) { const errorObject = { name: error.name, message: error.description || error.message, number: error.number, stack: error.stack }; const clone = get(error, 'stack.response.clone'); if (!clone) { dispatch(http.onJSError(errorObject, httpAction)); } else { // clone the response object else the next call to text or json // triggers an exception Already use error.stack.response.clone().text().then(response => { errorObject.stack.response = response; try { errorObject.stack.messageObject = JSON.parse(response); } catch (e) { /* If response is not in json format, it's ok, we have it in errorObject.stack.response */ } finally { if (httpAction.onError) { dispatch(http.onActionError(httpAction, errorObject)); } if (typeof httpAction.onError !== 'function') { dispatch(http.onError(errorObject)); } } }); } }; } /** * @param {Config} middlewareDefaultConfig */ export const httpMiddleware = (middlewareDefaultConfig = {}) => ({ dispatch }) => next => action => { if (!isHTTPRequest(action)) { return next(action); } const httpAction = get(action, 'cmf.http', action); const config = flow([mergeOptions, mergeConfiguredHeader(middlewareDefaultConfig), mergeCSRFToken(middlewareDefaultConfig)])(action); return interceptors.onRequest({ url: httpAction.url, ...config }).then(newConfig => { dispatch(http.onRequest(newConfig.url, newConfig)); if (httpAction.onSend) { dispatch({ type: httpAction.onSend, httpAction }); } const onHTTPError = getOnError(dispatch, httpAction); return fetch(newConfig.url, newConfig).then(status).then(handleResponse).then(interceptors.onResponse).then(response => { const newAction = { ...action }; dispatch(http.onResponse(response.data)); if (newAction.transform) { newAction.response = newAction.transform(response.data); } else { newAction.response = response.data; } if (newAction.onResponse) { dispatch(http.onActionResponse(newAction, newAction.response, response.headers)); } return next(newAction); }).catch(onHTTPError); }); }; //# sourceMappingURL=middleware.js.map