Created
September 26, 2016 15:10
-
-
Save tomazzaman/1041f3625f5c4344a8d9f027f05e5fdf to your computer and use it in GitHub Desktop.
API middleware for redux
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import superagent from 'superagent'; | |
import { merge } from 'lodash'; | |
import { camelizeKeys, decamelizeKeys } from 'humps'; | |
import config from 'config'; | |
const CALL_API = Symbol.for('Call API'); | |
export const GENERIC_ERROR = 'GENERIC_ERROR'; | |
const genericErrors = { | |
400: 'You\'ve attempted to make a request the server didn\'t expect', | |
401: 'You\'re not authenticated within the system, please log in.', | |
403: 'You\'re not authorized to perform this action', | |
404: 'The resource you\'re trying to fetch or modify does not exist', | |
500: 'An error occured on the server (not your fault). Admin has been contacted.', | |
}; | |
/** | |
* Prepare headers for each request and include the token for authentication. | |
* @param {Boolean} token Authentication token | |
* @return {Object} Request headers | |
*/ | |
function setRequestHeaders(token) { | |
const headers = { | |
Accept: 'application/vnd.referoo.v2+json', | |
}; | |
if (token) headers.Authorization = `Bearer ${token}`; | |
return headers; | |
} | |
/** | |
* Check the state whether we have an auth token present | |
* @param {Object} Redux store | |
* @return {String} Authentication Token | |
*/ | |
function getToken(store) { | |
const user = store.getState().user.toJS(); | |
return user.authToken || null; | |
} | |
/** | |
* Api middleware to make async requests to the server(s). This is the only place | |
* in the app where XHR should be made. It automatically "catches" every action | |
* with CALL_API symbol and extracts it's data to build a proper request. | |
* If a callback is provided in the action, then it may update the calling | |
* component with request (upload) progress. | |
*/ | |
export default store => next => action => { | |
const request = action[CALL_API]; | |
// Ignore the action (pass it on) if it's not meant to make an API request | |
if (typeof request === 'undefined') return next(action); | |
/** | |
* Create a new action (because original should be immutable) and dispatch it | |
* into reducers. It's unnecessary to send request info, so we remove it. | |
* @param {Object} data Incoming JSON payload (from the API) | |
* @return {Object} Data for reducers | |
*/ | |
function actionWith(newAction) { | |
const finalAction = merge({}, action, camelizeKeys(newAction)); | |
delete finalAction[CALL_API]; | |
return finalAction; | |
} | |
// Set defaults via destructuring | |
const [requestType, successType, failureType] = request.types; | |
const { method = 'GET', onProgress = () => {}, callback = () => {} } = request; | |
const body = decamelizeKeys(request.body); | |
// Dispatch a "loading" action (useful for showing spinners) | |
next(actionWith({ type: requestType })); | |
/** | |
* Completes the response from superagent. | |
* Note if you're unable to parse the results (this method with respond with | |
* something like "Parser is unable to parse response (...)") this means the | |
* returned JSON object isn't valid. | |
* @param {String} err Any possible errors, null if none | |
* @param {Object} response The response object, generated by underlying XHR. | |
*/ | |
function completeResponse(err, response) { | |
if (response.ok) { | |
next(actionWith({ type: successType, body: response.body })); | |
callback(); | |
} | |
if (err && genericErrors[err.status]) { | |
next(actionWith({ type: GENERIC_ERROR, message: genericErrors[err.status] })); | |
} | |
if (!response.ok) { | |
next(actionWith({ type: failureType, body: response.body })); | |
} | |
} | |
const preparedRequest = superagent(method, config.apiUrl + request.endpoint) | |
.set(setRequestHeaders(getToken(store))); | |
/** | |
* If the request has 'files' key on it, the request becomes multipart instead | |
* of JSON, which means we have to switch from regular .send() to a series | |
* of .field(name, value) calls (hence the loop). Superagent creates FormData | |
* behind the scenes, so we don't have to. | |
*/ | |
if (request.file) { | |
preparedRequest.on('progress', event => onProgress(event)); | |
const fileAttachKey = 'file'; | |
preparedRequest.attach(fileAttachKey, request.file); | |
// FIXME: make sure to remove the eslint-disable lines and fix the error! | |
/* eslint-disable */ | |
for (const property in body) { | |
if (body.hasOwnProperty(property)) { | |
preparedRequest.field(property, body[property]); | |
} | |
} | |
/* eslint-enable */ | |
} else { | |
preparedRequest.send(body); | |
} | |
preparedRequest.end(completeResponse); | |
return null; // TODO: check whether we can return null or if it even matters. | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello...how I can use it? use applyMiddleware from redux package?
After this how I can use it in my actions?