Skip to content

Instantly share code, notes, and snippets.

@exocode
Created February 12, 2020 12:05
Show Gist options
  • Select an option

  • Save exocode/73f0187c2bc7afcd94b4c866254966dd to your computer and use it in GitHub Desktop.

Select an option

Save exocode/73f0187c2bc7afcd94b4c866254966dd to your computer and use it in GitHub Desktop.
If you have a Rails API using devise_token_auth and wanna connect your React app then use this snippet snippet for React apps. Someone posted a snippet, which I adapted: https://github.com/lynndylanhurley/redux-auth/issues/127#issuecomment-437891442
import { API_PROTOCOL, API_DOMAIN, headers } from './constants';
import { parseResponse, paramsToObject } from './parse';
const endpoints = {
signOutPath: '/auth/sign_out',
emailSignInPath: '/auth/sign_in',
emailRegistrationPath: '/auth',
accountUpdatePath: '/auth',
accountDeletePath: '/auth/cancel',
passwordResetPath: '/auth/password',
passwordUpdatePath: '/auth/password',
tokenValidationPath: '/auth/validate_token',
validateUserEmail: '/user_info/email_exists',
};
export function getCredentials(headers) {
const credentialHeaders = {
'access-token': headers.get('access-token'),
client: headers.get('client'),
uid: headers.get('uid'),
expiry: headers.get('expiry'),
};
// if a request to the API happens too close to another, the API will not send the
// new credential headers, but the last one still validates to a new request.
return Object.values(credentialHeaders).includes(null)
? null
: credentialHeaders;
}
export async function login(email, password) {
const response = await fetch(
`${API_PROTOCOL}://${API_DOMAIN}${endpoints.emailSignInPath}`,
{
method: 'POST',
headers,
body: JSON.stringify({ email, password }),
}
);
const parsedResponse = await parseResponse(response);
if (response.ok) {
const credentials = getCredentials(response.headers);
if (!credentials) {
throw new Error('Missing credentials at login response.');
}
writeCredentials(credentials);
const user = parsedResponse.data;
return user;
} else {
return Promise.reject(parsedResponse.errors);
}
}
export async function register(email, password, password_confirmation) {
const response = await fetch(
`${API_PROTOCOL}://${API_DOMAIN}${endpoints.emailRegistrationPath}`,
{
method: 'POST',
headers,
body: JSON.stringify({
email,
password,
password_confirmation,
confirm_success_url: `${API_PROTOCOL}://${window.location.hostname}`,
}),
}
);
const unconfirmedNewUser = await parseResponse(response);
return unconfirmedNewUser.data;
}
export async function validateCredentials() {
const credentials = readCredentials();
if (!credentials) {
return Promise.reject('No credentials saved locally');
}
const response = await fetch(
`${API_PROTOCOL}://${API_DOMAIN}${endpoints.tokenValidationPath}`,
{
method: 'GET',
headers: { ...headers, ...credentials },
}
);
if (response.ok) {
const newCredentials = getCredentials(response.headers);
// too many reloads makes the API return empty headers,
// but current credentials are still valid(sometimes, urgh).
if (newCredentials) {
writeCredentials(newCredentials);
}
// get user data from response
const { data } = await parseResponse(response);
return data;
} else {
// deleteCredentials()
return Promise.reject('Invalid local token');
}
}
export function saveQueryCredentials(query) {
query = paramsToObject(query);
try {
const client = query.client_id;
const { uid, expiry, token } = query;
const credentials = { 'access-token': token, client, uid, expiry };
writeCredentials(credentials);
return true;
} catch (e) {
console.error('Error saving credentials: ', e);
return false;
}
}
export async function logout() {
try {
const credentials = readCredentials();
await fetch(`${API_PROTOCOL}://${API_DOMAIN}${endpoints.signOutPath}`, {
method: 'DELETE',
headers: { ...headers, ...credentials },
});
deleteCredentials();
} catch (e) {
return Promise.reject('Error requesting logout: ', e);
}
}
export async function validateUserEmail(email) {
const response = await fetch(
`${API_PROTOCOL}://${API_DOMAIN}${endpoints.validateUserEmail}`,
{
method: 'POST',
headers,
body: JSON.stringify({ user_info: { email } }),
}
);
return await parseResponse(response);
}
export function readCredentials() {
return JSON.parse(localStorage.getItem('default'));
}
export function writeCredentials(credentials) {
localStorage.setItem('default', JSON.stringify(credentials));
}
export function deleteCredentials() {
localStorage.removeItem('default');
}
export const API_PROTOCOL = process.env.REACT_APP_PROTOCOL;
export const API_DOMAIN = process.env.REACT_APP_DOMAIN;
export const headers = {
Accept: 'application/json;version=1',
'Content-Type': 'application/json',
};
import { API_PROTOCOL, API_DOMAIN, headers } from './constants';
import { readCredentials, writeCredentials, getCredentials } from './auth';
import { Map, fromJS } from 'immutable';
export async function parseResponse(response, cb = () => {}) {
const json = await response.json();
if (json && response.status >= 200 && response.status < 300) {
return parseBodyToCamelCase(json);
} else {
cb();
return Promise.reject(json);
}
}
export async function callAPI(
endpoint = '/',
subdomain,
method = 'GET',
body,
customHeaders
) {
if (method.match(/POST|PUT|PATCH/) && typeof body === 'undefined') {
throw new Error(`missing body in ${method} method`);
}
const url = `${API_PROTOCOL}://${API_DOMAIN}${endpoint}`;
const credentials = readCredentials();
if (credentials) {
let subdomainHeader = {};
if (subdomain) {
subdomainHeader = { Subdomain: subdomain };
}
const request = {
...{ method },
headers: {
...headers,
...customHeaders,
...credentials,
...subdomainHeader,
},
};
if (body && typeof body !== 'undefined') {
request.body = JSON.stringify(parseToSneakCase(body));
}
const response = await fetch(url, request);
const newCredentials = getCredentials(response.headers);
if (newCredentials) {
writeCredentials(newCredentials);
}
if (
customHeaders &&
customHeaders['Content-Type'].match(/application\/pdf/)
) {
return await response.text();
} else {
return response.status === 204 ? null : await parseResponse(response);
}
} else {
return Promise.reject('Cannot make API call. Missing credentials.');
}
}
export function parseToSneakCase(immutableObj) {
immutableObj =
immutableObj instanceof Object ? fromJS(immutableObj) : immutableObj;
const parsedObj = {};
immutableObj.map((value, key) => {
// recursive call ( won't catch Object values )
value = Map.isMap(value) ? parseToSneakCase(value) : value;
const snakeKey = toSnakeCase(key);
return (parsedObj[snakeKey] = value);
});
return fromJS(parsedObj);
}
// In order to speak JS and Ruby lang, we keep switching from sneak to camel case
export function parseBodyToCamelCase(obj) {
if (obj instanceof Array) {
const objList = [];
obj.forEach(objectItem => objList.push(parseToCamelCase(objectItem)));
return objList;
} else {
return parseToCamelCase(obj);
}
}
export function parseToCamelCase(obj) {
const parsedObj = {};
Object.keys(obj).forEach(key => {
// recursive call
obj[key] =
obj[key] instanceof Object ? parseToCamelCase(obj[key]) : obj[key];
const camelKey = toCamelCase(key);
parsedObj[camelKey] = obj[key];
});
return parsedObj;
}
// fooBar => foo_bar
export function toSnakeCase(string) {
return string.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
}
// foo_bar => fooBar
export function toCamelCase(string) {
return string.replace(
/_[a-z]/g,
match => `${match.substring(1).toUpperCase()}`
);
}
export function paramsToObject(params) {
params = params.substring(1);
try {
params = JSON.parse(
'{"' +
decodeURIComponent(params)
.replace(/"/g, '\\"')
.replace(/&/g, '","')
.replace(/=/g, '":"') +
'"}'
);
} catch (e) {
return null;
}
return params;
}
export function handleCreateRole (newRole) {
return async function (dispatch, getState) {
const currentSubdomain = getState().user.get('currentSubdomain')
dispatch(creatingRole())
try {
const createdRole = await callAPI('/roles', currentSubdomain, 'POST', newRole)
dispatch(creatingRoleSuccess(createdRole))
return createdRole
} catch (e) {
dispatch(creatingRoleFailure(e))
return null
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment