UNPKG

marketcloud-node

Version:
304 lines (266 loc) 8.91 kB
module.exports = (function () { 'use strict' var Promise = require('bluebird') var crypto = require('crypto') var request = require('superagent') var path = require('path') var endpoints = require(path.join(__dirname, 'endpoints.js')) var VERSION = require('../package.json').version /* * * [Client The client's instance] * * @param {string} config.public_key The application's public key * @param {string} config.secret_key The application's secret key * * */ function Client (config) { this.token = null this.public_key = config.public_key this.secret_key = config.secret_key this.baseUrl = 'https://api.marketcloud.it' // Marketcloud's api is versioned by URL // for instance, the current api is api.marketcloud.it/v0/<endpoint> this.apiVersion = 'v0' // If this is true, then api responses with status code >= 400 // are rejected as errors. // If set to false all responses from server are resolved // // In both cases, "failures" are rejected. this.rejectApiErrors = config.rejectApiErrors || true // When the client is unable to re-authenticate for a number of times that // exceeds MAX_RETRIES this.throwErrorWhenOutOfRetries = config.throwErrorWhenOutOfRetries || true // Creating resources instances for (var endpointName in endpoints) { var e = new endpoints[endpointName](this) this[e.name] = e } this.RETRIES = 0 this.MAX_RETRIES = 2 // This is the property that will hold a reference to the last // request's configuration. this.LAST_REQUEST = null } /* * * @returns {string} The base url for api calls * e.g. https://api.marketcloud.it/v0 */ Client.prototype.getApiBaseUrl = function () { return this.baseUrl + '/' + this.apiVersion } /* * * [requestFactory: Performs an HTTP request based on the configuration parameter] * * @param {string} config.method The HTTP method 'PUT','POST','GET','PATCH' etc * @param {object} config.data POST data to append to the request * @param {object} config.query Query string object */ Client.prototype.requestFactory = function (config) { var _this = this if (this.token === null) { return this.authenticate() .then(function () { return _this.requestFactory(config) }) } if (config.options && "object" !== typeof config.options){ throw new TypeError('Expected "options" to be an object, got ' + typeof(config.options)); } return new Promise(function (resolve, reject) { // Initialize the superagent Request object var req = request(config.method, _this.getApiBaseUrl() + config.endpoint) // Addding authorization header req.set('Authorization', _this.getAuthorizationHeader()) // Adding useful headers about SDK version, this helps us trace bugs // and eventually help users more efficiently req.set('X-sdk-variant', 'nodejs') req.set('X-sdk-version', VERSION) // Setting request options // These are mostly marketcloud related headers // Like currency and locale if (config.options && config.options.currency){ //req.set('Currency', config.options.currency); if (!config.query) config.query = {}; config.query.currency = config.options.currency; } // Setting the request query object if (config.query) { req.query(config.query || {}) } // Setting the body if (config.data) { req.send(config.data || {}) } // For observability and testability _this.LAST_REQUEST = config // Firing the request req.end(function (err, response) { if (err) { if (err.response) { // This is an auth error. It might be due to the token's expiration // We retry the last request after authenticating. // If it still fails, then we reject. if (err.response.statusCode === 401 || err.response.statusCode === 403) { return _this.authenticate() .then(function () { // Re authenticated after token expiration return _this.requestFactory(config) .then(function (response) { resolve(response) }) .catch(function (error) { reject(error) }) }) .catch(function (response) { // Automatic re-authentication failed. Rejecting. reject(response) }) } // Packaging the error response in an error // This is most likely a response with status >= 400 if (_this.rejectApiErrors) { // If the option is true // then responses with status >= 400 are rejected as errors var _err = new Error() for (var k in err.response.body.errors[0]) { _err[k] = err.response.body.errors[0][k] } reject(_err) } else { // If the simple option is false // then responses with code >= 400 are resolved resolve(err.response.body.errors[0]) } } else { // If the response is not defined, its most likely a netowrking error reject(err) } } else { // Refreshing retries _this.RETRIES = 0 resolve(response.body) } }) }) } /* * @param {String} endpoint The endpoint to append to the base url for this request * @param {Object} query Object to be used as querystring */ Client.prototype._Get = function (endpoint, query, options) { return this.requestFactory({ method: 'GET', endpoint: endpoint, query: query || {}, options : options || {} }) } /* * @param {String} endpoint The endpoint to append to the base url for this request * @param {Object} data Object to be used as request body */ Client.prototype._Post = function (endpoint, data, options) { return this.requestFactory({ method: 'POST', endpoint: endpoint, data: data || {}, options : options || {} }) } /* * @param {String} endpoint The endpoint to append to the base url for this request * @param {Object} data Object to be used as request body * * @return {Promise} */ Client.prototype._Put = function (endpoint, data, options) { return this.requestFactory({ method: 'PUT', endpoint: endpoint, data: data || {}, options : options || {} }) } /* * @param {String} endpoint The endpoint to append to the base url for this request * @param {Object} data Object to be used as request body * * @return {Promise} */ Client.prototype._Patch = function (endpoint, data, options) { return this.requestFactory({ method: 'PATCH', endpoint: endpoint, data: data || {}, options : options || {} }) } /* * @param {String} endpoint The endpoint to append to the base url for this request * * @return {Promise} */ Client.prototype._Delete = function (endpoint, options) { return this.requestFactory({ method: 'DELETE', endpoint: endpoint, options : options || {} }) } /* * * @return {String} Returns a formatted HTTP header from authentication data * */ Client.prototype.getAuthorizationHeader = function () { if (this.token) { return this.public_key + ':' + this.token } else { return this.public_key } } /* * Generates an auth Token from the credentials stored inside the client. * * @return {Promise} */ Client.prototype.authenticate = function () { var now = Date.now() var h = '' + this.secret_key + now var hash = crypto.createHash('sha256') .update(h) .digest('base64') var _this = this var payload = { publicKey: _this.public_key, secretKey: hash, timestamp: now } return new Promise(function (resolve, reject) { request .post(_this.getApiBaseUrl() + '/tokens') .set('Authorization', _this.getAuthorizationHeader()) .send(payload) .end(function (err, response) { if (err) { if (err.response) { // Packaging the error response in an error var _err = new Error() for (var k in err.response.body.errors[0]) { _err[k] = err.response.body.errors[0][k] } reject(_err) } else { reject(err) } } else { _this.token = response.body.token resolve(response.body.data) } }) }) } return Client })()