Created
February 12, 2020 12:05
-
-
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
This file contains hidden or 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 { 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'); | |
| } |
This file contains hidden or 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
| 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', | |
| }; |
This file contains hidden or 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 { 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; | |
| } |
This file contains hidden or 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
| 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