Giter VIP home page Giter VIP logo

Comments (10)

lynndylanhurley avatar lynndylanhurley commented on September 15, 2024 11

I keep making these client libraries and the javascript ecosystem keeps changing out from under me. I've been using an API client in all of my active projects that works a little better with all the latest stuff. I'm going to integrate it into this project, and hopefully it will make this project a little easier to maintain. But I don't have an ETA. It will probably be at least a few weeks, maybe more. I apologize to anyone that this causes problems for.

from redux-auth.

wesley-harding avatar wesley-harding commented on September 15, 2024 5

This repo does not seem to be actively maintained at the moment. We had to fork the project in order to address several issues we came across during integration. We've have been making fixes as needed over at https://github.com/carolhealth/redux-auth.

Fair warning: we may not continue to support our fork. We're identifying significant roadblocks integrating this package. Most of the changes and fixes are driven by our business needs and we do not necessarily have the resources act as the primary maintainer.

from redux-auth.

MarkNBroadhead avatar MarkNBroadhead commented on September 15, 2024 5

@lynndylanhurley would you mind mentioning which API client you recommend?

from redux-auth.

betoharres avatar betoharres commented on September 15, 2024 2

To anyone that need frontend functions to work with devise_token_auth I have this gist that I wrote last year to send and receive tokens from devise token auth ruby gem:

auth.js

import { API_PROTOCOL, API_DOMAIN, headers } from '../config/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')
}

constants.js

export const API_PROTOCOL = process.env.REACT_APP_PROTOCOL
export const API_DOMAIN = process.env.REACT_APP_DOMAIN
export const headers = {
  'Accept': 'application/vnd.mycompany+json;version=1',
  'Content-Type': 'application/json',
}

Then, whenever you need to call the api, here's what you need:
(btw, I'm using immutable on this project, just use plain objects)

parse.js

import { API_PROTOCOL, API_DOMAIN, headers } from '../config/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
}

example os usage(redux-thunk):

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
    }
  }
}

have fun

from redux-auth.

betoharres avatar betoharres commented on September 15, 2024 2

@exocode I forgot to put the name of the file in the snippets. I just updated it with the name now.

parseResponse function exists mostly because you need to call .json() from the fetch response, and also because I have to transform all the object keys into camelCase because of ruby - the response were getting something like this:

{
user_name: "blablabla",
}

and I needed to transform this into camelCase like this:

{
userName: "blabalba"
}

because that's how it JS is used to work with(also some lints will throw errors by using sneak_case variables).

from redux-auth.

oprearocks avatar oprearocks commented on September 15, 2024 1

@thesubr00t Given that the community of people who use this library is still active I'd say it is. Why are you asking ? Do you need help with any specific issue ?

from redux-auth.

PawelStepak avatar PawelStepak commented on September 15, 2024 1

I'm wondering if the project is safe to use as it is not maintained for over 2 years ? Will it work with latest React and Redux libraries ?

from redux-auth.

SpLouk avatar SpLouk commented on September 15, 2024

It won't. I'm using react 15.4.0 to make it work.

from redux-auth.

AlexMcConnell avatar AlexMcConnell commented on September 15, 2024

Any update on the future of this? Also, why isn't this app mentioned in the devise_token_auth list of apps?

from redux-auth.

exocode avatar exocode commented on September 15, 2024

@betoharres thank you for that snippet! I have trouble to find out what's behind the "./parse" file? I am relatively new to React, so can you help me with that, please?

from redux-auth.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.