Skip to content

Instantly share code, notes, and snippets.

@tomazzaman
Last active July 18, 2022 11:03
Show Gist options
  • Save tomazzaman/554f13da450ac8bc8438 to your computer and use it in GitHub Desktop.
Save tomazzaman/554f13da450ac8bc8438 to your computer and use it in GitHub Desktop.
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);
};
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