shernshiou/node-uber

View on GitHub
lib/Uber.js

Summary

Maintainability
D
2 days
Test Coverage
var request = require('request');
var qs = require('querystring');
var OAuth = require('oauth');
var util = require('util');
var Promise = require('bluebird');
var NodeGeocoder = require('node-geocoder');
var ProxyAgent = require('proxy-agent');

var resources = {
  Estimates: require('./resources/riders/Estimates'),
  Products: require('./resources/riders/Products'),
  User: require('./resources/riders/User'),
  Requests: require('./resources/riders/Requests'),
  Places: require('./resources/riders/Places'),
  Payment: require('./resources/riders/Payment'),
  PartnerPayments: require('./resources/drivers/PartnerPayments'),
  PartnerProfile: require('./resources/drivers/PartnerProfile'),
  PartnerTrips: require('./resources/drivers/PartnerTrips')
};

function Uber(options) {
  this.sandbox = options.sandbox
    ? options.sandbox
    : false;
  this.defaults = {
    client_id: options.client_id,
    client_secret: options.client_secret,
    server_token: options.server_token,
    redirect_uri: options.redirect_uri,
    name: options.name,
    base_url: this.sandbox
      ? 'https://sandbox-api.uber.com/'
      : 'https://api.uber.com/',
    base_version: 'v1.2',
    language: options.language
      ? options.language
      : 'en_US',
    authorize_url: 'https://login.uber.com/oauth/v2/authorize',
    access_token_url: 'https://login.uber.com/oauth/v2/token',
    revoke_token_url: 'https://login.uber.com/oauth/v2/revoke',
    proxy: options.proxy
      ? options.proxy
      : ''
  };

  this.oauth2 = new OAuth.OAuth2(this.defaults.client_id, this.defaults.client_secret, '', this.defaults.authorize_url, this.defaults.access_token_url);

  this.geocoder = NodeGeocoder({
    provider: "google",
    apiKey: options.google_maps_api_key
  });

  if (this.defaults.proxy) {
    this.oauth2.setAgent(new ProxyAgent(this.defaults.proxy));
  }
  this.oauth2.useAuthorizationHeaderforGET(true);

  this.resources = resources;
  this.access_token = options.access_token;
  this.refresh_token = options.refresh_token;
  this.tokenExpiration = '';
  this.authorizedScopes = '';

  this._initResources();
}

module.exports = Uber;

Uber.prototype._initResources = function _initResources() {
  for (var name in this.resources) {
    if ({}.hasOwnProperty.call(this.resources, name)) {
      this[name.toLowerCase()] = Promise.promisifyAll(new resources[name](this));
    }
  }
};

Uber.prototype.getCoordinatesForAddress = function getCoordinatesForAddress(address, callback) {
  this.geocoder.geocode(address, function (err, data) {
    if (err || data.length === 0) {
      return callback((err
        ? err
        : new Error('No coordinates found for: "' + address + '"')), {
          lat: '',
          lng: ''
        });
    }

    const result = { lat: data[0].latitude, lng: data[0].longitude };
    callback(null, result);
  });
};

Uber.prototype.replaceAddressWithCoordinates = function
  replaceAddressWithCoordinates(params, addressField, latField, lngField, callback) {
  if (this.hasOwnNestedProperty(params, addressField) && !this.getNestedProperty(params, latField) && !this.getNestedProperty(params, lngField)) {
    // get coordinates from address
    this.getCoordinatesForAddress(this.getNestedProperty(params, addressField), function (err, data) {
      if (err) {
        return callback(err);
      }

      console.log(data)
      this.removeNestedProperty(params, addressField);
      this.setNestedProperty(params, latField, data.lat);
      this.setNestedProperty(params, lngField, data.lng);

      return callback(null, params);
    }.bind(this));
  } else {
    return callback(null, params);
  }
};

Uber.prototype.getAuthorizeUrl = function getAuthorizeUrl(scope) {
  if (!Array.isArray(scope)) {
    return new Error('Scope is not an array');
  }
  if (scope.length === 0) {
    return new Error('Scope is empty');
  }

  return this.oauth2.getAuthorizeUrl({ response_type: 'code', redirect_uri: this.defaults.redirect_uri, scope: scope.join(' ') });
};

Uber.prototype.authorization = function authorization(options, callback) {
  var self = this;
  var grantType = '';
  var code = '';
  var nD = null;

  if (options.hasOwnProperty('authorization_code')) {
    grantType = 'authorization_code';
    code = options.authorization_code;
  } else if (options.hasOwnProperty('refresh_token')) {
    grantType = 'refresh_token';
    code = options.refresh_token;
  } else {
    return callback(new Error('No authorization_code or refresh_token'));
  }

  this.oauth2.getOAuthAccessToken(code, {
    client_id: this.defaults.client_id,
    client_secret: this.defaults.client_secret,
    redirect_uri: this.defaults.redirect_uri,
    grant_type: grantType
  }, function (err, access_token, refresh_token, results) {
    if (err) {
      return callback(err);
    } else {
      self.access_token = access_token;
      self.refresh_token = refresh_token;
      // store auth scopes
      self.authorizedScopes = results.scope;
      // store expiration date
      nD = new Date();
      // expires value indicates seconds
      nD.setSeconds(nD.getSeconds() + results.expires_in);
      self.tokenExpiration = nD;
      return callback(null, [self.access_token, self.refresh_token, self.authorizedScopes, self.tokenExpiration]);
    }
  });

  return self;
};

Uber.prototype.authorizationAsync = Promise.promisify(Uber.prototype.authorization);

Uber.prototype.delete = function (options, callback) {
  return this.modifierMethodHelper(options, callback, 'delete');
};

Uber.prototype.patch = function patch(options, callback) {
  return this.modifierMethodHelper(options, callback, 'patch');
};

Uber.prototype.post = function post(options, callback) {
  return this.modifierMethodHelper(options, callback, 'post');
};

Uber.prototype.put = function put(options, callback) {
  return this.modifierMethodHelper(options, callback, 'put');
};

Uber.prototype.modifierMethodExecute = function modifierMethodExecute(method, params, callback) {
  if (this.defaults.proxy) {
    params.agent = new ProxyAgent(this.defaults.proxy);
  }

  switch (method) {
    case 'delete':
      request.delete(params, callback);
      break;
    case 'post':
      request.post(params, callback);
      break;
    case 'put':
      request.put(params, callback);
      break;
    case 'patch':
      request.patch(params, callback);
      break;
  }
};

Uber.prototype.modifierMethodHelper = function modifierMethodHelper(options, callback, method) {
  var access_type;
  var self = this;
  var localCallback = function localCallback(err, data, res) {
    // shared callback between put, post, patch, and delete requests
    if (err || data.statusCode >= 400) {
      return callback(((err)
        ? err
        : 'HTTP Response with error code: ' + data.statusCode), data);
    } else {
      return callback(null, res);
    }
  };
  var refreshCallback = function refreshCallback() {
    if (!self.checkScopes(options)) {
      return callback(new Error('Required scope not found'));
    }

    // remove scope parameter from options
    delete options.scope;

    var params = {
      url: self.getRequestURL(options.version, options.url),
      json: true,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': access_type,
        'Accept-Language': self.defaults.language
      },
      body: ((options.params)
        ? options.params
        : '')
    };

    self.modifierMethodExecute(method, params, localCallback);

    return self;
  };
  if (options && options.server_token) {
    access_type = 'Token ' + this.defaults.server_token;
    refreshCallback();
  } else {
    if (!this.access_token) {
      return callback(new Error('Invalid access token'));
    } else {
      // defaults to OAuth with access_token
      // auto renew if required
      if (this.isTokenStale()) {
        this.authorization({
          refresh_token: this.refresh_token
        }, function (err, res) {
          if (err) {
            return callback(err);
          } else {
            access_type = 'Bearer ' + res[0];
            refreshCallback();
          }
        });
      } else {
        access_type = 'Bearer ' + this.access_token;
        refreshCallback();
      }
    }
  }
};

Uber.prototype.createAccessHeader = function createAccessHeader(server_token) {
  var access_type;

  if (server_token) {
    access_type = 'Token ' + this.defaults.server_token;
  } else {
    if (this.access_token) {
      access_type = 'Bearer ' + this.access_token;
    }
  }

  return access_type;
};

Uber.prototype.get = function get(options, callback) {
  var access_type = this.createAccessHeader(options.server_token);
  if (!access_type) {
    return callback(new Error('Invalid access token'), 'A valid access token is required for this request');
  }
  var url = this.getRequestURL(options.version, options.url);

  if (!this.checkScopes(options)) {
    return callback(new Error('Required scope not found'));
  }

  // remove scope parameter from options
  delete options.scope;

  // add all further option params
  if (options.params) {
    url += '?' + qs.stringify(options.params);
  }

  var reqOptions = {
    url: url,
    json: true,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': access_type,
      'Accept-Language': this.defaults.language
    }
  };
  if (this.defaults.proxy) {
    reqOptions.agent = new ProxyAgent(this.defaults.proxy);
  }

  request.get(reqOptions, function (err, data, res) {
    if (err || data.statusCode >= 400) {
      return callback((err
        ? err
        : data), res);
    } else {
      return callback(null, res);
    }
  });

  return this;
};

Uber.prototype.checkScopes = function checkScopes(options) {
  if (!options || !options.scope) {
    // checking scopes is not relevant
    return true;
  }

  // check if options.scope is array
  if (options.scope && Array === options.scope.constructor) {
    var regExp;
    for (var i = 0; i < options.scope.length; i++) {
      // regEx is required to avoid mismatch between
      // request and request_receipt
      regExp = new RegExp('\\b' + options.scope[i] + '\\b', 'gi');
      if (regExp.test(this.authorizedScopes)) {
        return true;
      }
    }
    return false;
  } else {
    if (this.authorizedScopes.indexOf(options.scope) > -1) {
      return true;
    } else {
      return false;
    }
  }
};

Uber.prototype.revokeToken = function revokeToken(token, callback) {
  if (typeof token !== "string" || token.length === 0) {
    return callback(new Error('Passed token is not acceptable'));
  }

  request.post(this.defaults.revoke_token_url, {
    form: {
      client_id: this.defaults.client_id,
      client_secret: this.defaults.client_secret,
      token: token
    }
  }, function (err, res, body) {
    if (err) {
      return callback(err);
    }

    return callback(null, res);
  });
};

Uber.prototype.revokeTokenAsync = Promise.promisify(Uber.prototype.revokeToken);

Uber.prototype.clearTokens = function clearTokens() {
  this.access_token = null;
  this.refresh_token = null;
  this.tokenExpiration = null;
  this.authorizedScopes = null;
};

Uber.prototype.setTokens = function setTokens(access_token, refresh_token, tokenExpiration, authorizedScopes) {
  this.access_token = access_token;
  this.refresh_token = refresh_token;
  this.tokenExpiration = tokenExpiration;
  this.authorizedScopes = authorizedScopes;
}

Uber.prototype.isNumeric = function isNumeric(input) {
  return (!input || isNaN(input))
    ? false
    : true;
};

Uber.prototype.hasOwnNestedProperty = function hasOwnNestedProperty(obj, key) {
  return key.split('.').every(function (x) {
    if (typeof obj !== 'object' || obj === null || !(x in obj))
      return false;
    obj = obj[x];
    return true;
  });
};

Uber.prototype.getNestedProperty = function getNestedProperty(obj, key) {
  return key.split(".").reduce(function (o, x) {
    return (typeof o === 'undefined' || o === null)
      ? o
      : o[x];
  }, obj);
};

Uber.prototype.removeNestedProperty = function removeNestedProperty(obj, key) {
  var props = key.split('.');
  var propName = props[props.length - 1];
  for (var p in obj) {
    if (obj.hasOwnProperty(p)) {
      if (p === propName) {
        delete obj[p];
      } else if (typeof obj[p] === 'object') {
        this.removeNestedProperty(obj[p], propName);
      }
    }
  }
  return obj;
};

Uber.prototype.setNestedProperty = function setNestedProperty(obj, key, value) {
  var schema = obj;
  var pList = key.split('.');
  var len = pList.length;
  for (var i = 0; i < len - 1; i++) {
    var elem = pList[i];
    schema = schema[elem];
  }

  schema[pList[len - 1]] = value;
};

Uber.prototype.getRequestURL = function getRequestURL(version, url) {
  return this.defaults.base_url + (version
    ? version
    : this.defaults.base_version) + '/' + url;
};

Uber.prototype.isTokenStale = function isTokenStale() {
  var nD = new Date();
  var threshold = 500;
  nD.setSeconds(nD.getSeconds() + threshold);
  return nD > this.tokenExpiration;
};

Uber.prototype.isSurge = function isSurge(err) {
  return err.hasOwnProperty('surge_confirmation');
};