Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is the project still maintained? #127

Open
thesubr00t opened this issue Sep 7, 2017 · 10 comments
Open

Is the project still maintained? #127

thesubr00t opened this issue Sep 7, 2017 · 10 comments

Comments

@thesubr00t
Copy link

Hi,
Given the number of pull requests and issues unanswered, I wonder if the project is still maintained.
Any information?

@oprearocks
Copy link

@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 ?

@wesley-harding
Copy link

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.

@lynndylanhurley
Copy link
Owner

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.

@MarkNBroadhead
Copy link

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

@PawelStepak
Copy link

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 ?

@SpLouk
Copy link

SpLouk commented May 6, 2018

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

@AlexMcConnell
Copy link

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

@betoharres
Copy link

betoharres commented Nov 12, 2018

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

@exocode
Copy link

exocode commented Feb 14, 2020

@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?

@betoharres
Copy link

betoharres commented Feb 14, 2020

@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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants