Last active
July 18, 2022 11:03
-
-
Save tomazzaman/554f13da450ac8bc8438 to your computer and use it in GitHub Desktop.
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/merge'; | |
import { camelizeKeys, decamelizeKeys } from 'humps'; | |
import config from 'config'; | |
const CALL_API = Symbol.for('Call API'); | |
/** | |
* 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/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 state = store.getState(); | |
return state.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. | |
* | |
* TODO: make sure to parse request body correctly whether it's JSON or FormData | |
* TODO: camelize/decamelize request/response keys | |
*/ | |
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; | |
} | |
const [requestType, successType, failureType] = request.types; | |
// 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 (!err && response.ok) next(actionWith({ type: successType, body: response.body })); | |
else next(actionWith({ type: failureType, body: response.body })); | |
} | |
// Set defaults via destructuring | |
const { method = 'GET', onProgress = () => {} } = request; | |
const body = decamelizeKeys(request.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.files) { | |
request.files.forEach(file => { | |
preparedRequest.attach(file.name, file); | |
}); | |
/* eslint guard-for-in: 0*/ | |
for (const property in body) { | |
preparedRequest.field(property, body[property]); | |
} | |
preparedRequest.on('progress', event => onProgress(event)); | |
} else { | |
preparedRequest.send(body); | |
} | |
preparedRequest.end(completeResponse); | |
}; |
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
const CALL_API = Symbol.for('Call API'); | |
export const USER_LOGIN = 'USER_LOGIN'; | |
export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'; | |
export const USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE'; | |
export function login({ email, password }) { | |
return { | |
[CALL_API]: { | |
types: [USER_LOGIN, USER_LOGIN_SUCCESS, USER_LOGIN_FAILURE], | |
endpoint: '/user/login', | |
method: 'POST', | |
body: { email, password }, | |
}, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment