Source: UROAuth.js

import { URL } from 'url'
import { OAuth } from 'oauth'

/**
 * Token info
 * @typedef {Object} Token Token info
 * @property {string} token Token public
 * @property {string} secret Token secret
 */

/**
 * Query result
 * @typedef {Object} QueryResult Query result
 * @property {Object<String,*>} context reply context
 * @property {Object<String,*>} [items] reply items
 */

/**
 * This object allows to call API with javascript functions instead of passing call name as string.
 * For example, instead of doing `urOAuth.query('collections.getCollectionPage', { deckOnly: true })`, you can do `urOAuth.proxyClient.collections.getCollectionPage({ deckOnly: true })`
 * The parameters are the same as query parameters following the call name since the function uses proxies and bind.
 * The function still returns a promise. Please note as well that since proxies are used, you need to have an environment
 * which supports it. For node.js, you need node.js 6+. If you use it with a previous version, even with babel, it will likely fail.
 * @typedef {Object} UROAuthProxyClient Object allowing to directly call the API
 * @property {ApiCaller} characters Characters API
 * @property {ApiCaller} collections Collections API
 * @property {ApiCaller} forums Forums API
 * @property {ApiCaller} general General API
 * @property {ApiCaller} guilds Guilds API
 * @property {ApiCaller} market Market API
 * @property {ApiCaller} missions Missions API
 * @property {ApiCaller} players Players API
 */

/**
 * Object from proxyClient which allows direct API calls
 * @typedef {Object<string, ApiCallerFunction>} ApiCaller Object from proxyClient which allows direct API calls
 */

/**
 * @typedef {Function} ApiCallerFunction
 * @param {Object<String,*>} [params={}] Call params
 * @param {APIFilter} [filters={}] All filters you want on your query result
 * @param {Array<String>} [filters.itemsFilter=[]] Filter on items sent by the server
 * @param {Array<String>} [filters.contextFilter=[]] Filter on context sent by the server
 * @returns {Promise<QueryResult>} The query result
 */

/**
 * API Filters
 * @typedef {Object} APIFilter API Filters
 * @property {Array<String>} itemsFilter Filter on items sent by the server
 * @property {Array<String>} contextFilter Filter on context sent by the server
 */

/**
 * The Urban Rivals API
 * @type {UROAuth}
 * @extends {OAuth}
 */
class UROAuth extends OAuth {
  /**
   * query URL
   * @type {String}
   * @readonly
   */
  static API_URL = 'https://www.urban-rivals.com/api'

  /**
   * Authorization URL
   * @type {String}
   * @readonly
   */
  static AUTHORIZE_URL = `${this.API_URL}/auth/authorize.php`

  /**
   *
   * @param {Object} options API options
   * @param {String} options.key Application key
   * @param {String} options.secret Application secret
   * @param {String} [options.algorithm='HMAC-SHA1'] Signing method
   * @param {Function} [options.authorizeCallback=null] Callback called when authorized
   */
  constructor({
    key,
    secret,
    algorithm = 'HMAC-SHA1',
    authorizeCallback = null,
  }) {
    super(
      'https://www.urban-rivals.com/api/auth/request_token.php',
      'https://www.urban-rivals.com/api/auth/access_token.php',
      key,
      secret,
      '1.0',
      authorizeCallback,
      algorithm,
    )

    /**
     * Request token
     * @type {Token}
     */
    this.requestToken = {}

    /**
     * Access token
     * @type {Token}
     */
    this.accessToken = {}
  }

  /**
   * Get request token
   * @return {Promise<Token>}
   */
  getRequestToken() {
    return new Promise((resolve, reject) => {
      this.getOAuthRequestToken((err, token, token_secret) => {
        if (err) {
          reject(err)
        }
        else {

          const tokenObject = {
            token: token,
            secret: token_secret,
          }

          this.requestToken = tokenObject

          resolve(tokenObject)
        }
      })
    })
  }

  /**
   * Get access token from request token
   * @param {Token} [requestToken] Request token
   * @return {Promise<Token>}
   */
  getAccessToken(requestToken = this.requestToken) {
    if (typeof requestToken === 'object' && requestToken.hasOwnProperty('requestToken')) {
      requestToken = requestToken.requestToken
    }
    return new Promise((resolve, reject) => {
      this.getOAuthAccessToken(requestToken.token, requestToken.secret, (err, accessToken, accessSecret, results) => {

        if(err)
          reject(err)
        else {
          this.accessToken = {
            token: accessToken,
            secret: accessSecret,
          }

          resolve(this.token)
        }
      })
    })
  }

  /**
   * Do one or more queries on the UR API
   * @param {Object} queries All queries to do
   * @param {String} queries.call Call name
   * @param {Object} [queries.params={}] params of the query
   * @param {Array} [queries.contextFilter=[]] filter on the received context attributes
   * @param {Array} [queries.itemsFilter=[]] filter on the received items attributes
   * @returns {Promise<Object<String,QueryResult>>} The queries results, indexed by the call name
   */
  multipleQueries(...queries) {
    const queriesToDo = queries.map(({call, params = {}, contextFilter = [], itemsFilter = []}) => ({
      call,
      params,
      contextFilter,
      itemsFilter,
    }))

    const jsonEncodedQuery = JSON.stringify(queriesToDo);
    return new Promise((resolve, reject) => {
      this.post(this.constructor.API_URL, this.accessToken.token, this.accessToken.secret, {
        request: jsonEncodedQuery,
      }, 'application/x-www-form-urlencoded', (err, response, result) => {
        if (err) {
          reject(err)
        } else {
          resolve(JSON.parse(response))
        }
      })
    })
  }

  /**
   * Do one query
   * @param {String} call Call name
   * @param {Object<String,*>} [params={}] Call params
   * @param {APIFilter} [filters={}] All filters you want on your query result
   * @param {Array<String>} [filters.itemsFilter=[]] Filter on items sent by the server
   * @param {Array<String>} [filters.contextFilter=[]] Filter on context sent by the server
   * @returns {Promise<QueryResult>} The query result
   */
  async query(call, params = {}, { itemsFilter = [], contextFilter = [] } = {}) {
    return (await this.multipleQueries({
      call,
      params,
      itemsFilter,
      contextFilter,
    }))[call]
  }


  /**
   * Get the authorization URL for the player
   * @param [callbackUrl] The callback url for the query
   * @returns {string} The authorize URL
   */
  getAuthorizeUrl(callbackUrl = '') {
    const authorizeUrl = new URL(this.constructor.AUTHORIZE_URL)
    authorizeUrl.searchParams.set('oauth_token', this.requestToken.token)
    if (callbackUrl) {
      authorizeUrl.searchParams.set('oauth_callback', callbackUrl)
    }

    return authorizeUrl.toString()
  }

  /**
   * Creates a proxy client to call API with syntaxic sugar
   * @returns {UROAuthProxyClient} The UROAuth proxy client
   */
  get proxyClient() {
    return new Proxy({}, {
      get: (target, callGroup) =>
        new Proxy({}, {
          get: (target, callName) => this.query.bind(this, `${callGroup}.${callName}`),
          set: () => false,
          deleteProperty: () => false,
          ownKeys: () => [],
          has: () => true,
          defineProperty: () => false,
        }),
      set: () => false,
      deleteProperty: () => false,
      ownKeys: () => [],
      has: () => true,
      defineProperty: () => false,
    })
  }
}

export default UROAuth