Source: ZEventsApiAuth/ZEventsApiAuth.js

import ifPromise from 'onyx-common/ifPromise'
import isPromise from 'onyx-common/isPromise'
import hasKey from 'onyx-common/hasKey'
/*
All API calls are broken into (at least) 2 methods:
- A "public" method the app-land code we call from the store. (ie: login)
- A "private" method by the same name but with a "_" in front that actually handles the raw API call (ie: _login)

Why?
- We can test the API contract with the private method.
- With the public, we can manipulate the raw result we get back to make it easier to work with in our app itself
  - ie: we camelize the field names, filter down the fields we care about, etc.
*/
const ZEventsApiAuth = ({ prototype }) => {
  prototype.normalizeLoginableReturn = function (data) {
    const ret = {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresIn: data.expires_in,
      createdAt: data.created_at,
      isAdmin: data.status === 'admin'
    }
    if (hasKey(data, 'user_id')) ret.id = data.user_id
    return ret
  }

  /**
   * @typedef {object} ManualLoginReturn
   * @property {string} accessToken - access token
   * @property {string} refreshToken - refresh token
   */

  /**
   * @function manualLogin
   * @param {object} payload - The manualLogin payload
   * @param {string} payload.accessToken - current accessToken
   * @param {string} payload.refreshToken - current refreshToken
   * @returns {ManualLoginReturn} - manualLogin return
   * @example
   *
   * manualLogin({
   *   accessToken: 'asdfasdfsdfadsf',
   *   refreshToken: 'asdfasdfasdf'
   * })
   */
  prototype.manualLogin = function (payload) {
    const finalPayload = {
      refreshToken: payload.refreshToken,
      accessToken: payload.accessToken
    }

    return this.authable.onLogin(finalPayload)
  }

  /**
   * @function SSOLogin
   * @param {object} payload - The SSOLogin payload
   * @param {string} payload.accessToken - current accessToken
   * @param {string} payload.refreshToken - current refreshToken
   * @returns {ManualLoginReturn} - SSOLogin return
   * @example
   *
   * SSOLogin({
   *   accessToken: 'asdfasdfsdfadsf',
   *   refreshToken: 'asdfasdfasdf'
   * })
   */
  prototype.SSOLogin = prototype.manualLogin

  prototype._loginWithFirebase = function (sentIn) {
    const payload = {
      url: this.getUrl('/api/v1/authentication/firebase'),
      method: 'POST',
      requestType: 'json',
      data: {
        ...sentIn,
        grant_type: 'refresh_token',
        ...this.getAuthParams()
      }
    }

    return this.fetchWrap(payload)
  }

  /**
   * @typedef {object} FirebaseLoginReturn
   * @property {string} accessToken - access token
   * @property {string} refreshToken - refresh token
   * @property {string} expiresIn - expires in
   * @property {string} createdAt - created at
   * @property {string} isAdmin - whether or not user is admin
   */

  /**
   * @function loginWithFirebase
   * @param {object} payload - The loginWithFirebase payload
   * @param {string} payload.firebaseToken - user email
   * @returns {FirebaseLoginReturn} - login return
   * @example
   *
   * loginWithFirebase({
   *   firebaseToken: 'xxxxxxxxxxxxxxx'
   * })
   */
  prototype.loginWithFirebase = function (payload) {
    // this gives us the ability to pass in the response from _login directly if we want to

    const finalPayload = {
      firebase_token: payload.firebaseToken
    }

    const raw = ifPromise(payload, () => this._loginWithFirebase(finalPayload))

    return raw
      .then(res => this.normalizeLoginableReturn(res.data))
      .then(data => this.authable.onLogin(data))
      .catch(res => this.onError('loginWithFirebase', res))
  }

  prototype._login = function (sentIn) {
    const {
      email,
      password
    } = sentIn

    const payload = {
      url: this.getUrl('/api/v1/oauth/token'),
      method: 'POST',
      requestType: 'json',
      data: {
        grant_type: 'password',
        email,
        password,
        ...this.getAuthParams()
      }
    }

    return this.fetchWrap(payload)
  }

  /**
   * @typedef {object} LoginReturn
   * @property {number} id - user id
   * @property {string} accessToken - access token
   * @property {string} refreshToken - refresh token
   * @property {number} expiresIn - seconds until accessToken is expired
   * @property {number} createdAt - access token create timestamp
   * @property {string} isAdmin - whether or not user is admin
   */

  /**
   * @function login
   * @param {object} payload - The login payload
   * @param {string} payload.email - email address of user
   * @param {string} payload.password - password to test against
   * @returns {LoginReturn} - LoginReturn return
   * @example
   *
   * login({
   *   email: 'b@.com',
   *   password: 'bah'
   * })
   */
  prototype.login = function (payload) {
    // this gives us the ability to pass in the response from _login directly if we want to

    const finalPayload = {
      email: payload.email,
      password: payload.password
    }

    const raw = ifPromise(payload, () => this._login(finalPayload))

    return raw
      .then(res => this.normalizeLoginableReturn(res.data))
      .then(data => this.authable.onLogin(data))
      .catch(res => {
        const mapError = {
          'Invalid Email Adress or password.': 'LOGIN_INVALID'
        }
        return this.onError('login', res, mapError)
      })
  }

  prototype._logout = function (sentIn = {}) {
    const {
      token
    } = sentIn

    const url = this.getUrl('/api/v1/oauth/revoke')
    const payload = {
      url,
      method: 'POST',
      requestType: 'json',
      data: {
        token,
        ...this.getAuthParams()
      }
    }

    return this.fetchWrap(payload)
  }

  /**
   * @function logout
   * @param {object} [payload] - The logout payload
   * @param {string} [payload.accessToken=this.authable.getAccessToken()] - accessToken to process
   * @returns {void}
   * @example
   *
   * logout({
   *   accessToken: 'asdfasdfasdfadsfd'
   * })
   */
  prototype.logout = function (payload = {}) {
    this.authable.onLogout()

    let raw = payload
    if (!isPromise(raw)) {
      const {
        accessToken = this.authable.getAccessToken()
      } = payload

      let accessTokenPromise = accessToken

      if (!isPromise(accessTokenPromise)) {
        accessTokenPromise = Promise.resolve(accessToken)
      }

      raw = accessTokenPromise
        .then(access_token => {
          const finalPayload = {
            access_token
          }
          return this._logout(finalPayload)
        })
    }

    return raw
      .catch(error => this.onError('logout', error))
  }

  prototype._refresh = function (sentIn) {
    const {
      refresh_token
    } = sentIn

    const url = this.getUrl('/api/v1/oauth/token')
    const payload = {
      url,
      method: 'POST',
      requestType: 'json',
      data: {
        grant_type: 'refresh_token',
        refresh_token,
        ...this.getAuthParams()
      }
    }

    return this.fetchWrap(payload)
  }

  /**
   * @typedef {object} RefreshReturn
   * @property {string} accessToken - access token
   * @property {string} refreshToken - refresh token
   * @property {number} expiresIn - seconds until accessToken is expired
   * @property {number} createdAt - access token create timestamp
   * @property {string} isAdmin - whether or not user is admin
   */

  /**
   * @function refresh
   * @param {object} [payload] - The refresh payload
   * @param {string} [payload.refreshToken=this.authable.getRefreshToken()] - refreshToken to process
   * @returns {RefreshReturn} refresh return
   * @example
   *
   * refresh({
   *   refreshToken: '12345'
   * })
   */

  prototype.refresh = function (payload = {}) {
    let raw = payload
    if (!isPromise(raw)) {
      const {
        refreshToken = this.authable.getRefreshToken()
      } = payload

      let refreshTokenPromise = refreshToken

      if (!isPromise(refreshTokenPromise)) {
        refreshTokenPromise = Promise.resolve(refreshToken)
      }

      raw = refreshTokenPromise
        .then(refresh_token => {
          const finalPayload = {
            refresh_token
          }
          return this._refresh(finalPayload)
        })
    }

    return raw
      .then(res => this.normalizeLoginableReturn(res.data))
      .then(data => this.authable.onRefresh(data))
      .catch(error => this.onError('refresh', error))
  }
}

export default ZEventsApiAuth