diff --git a/package.json b/package.json index e897c69..e9bcfa4 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,9 @@ "mocha": "^5.2.0" }, "dependencies": { - "axios": "^0.18.0", + "axios": "^0.19.0", "debug": "^4.1.0", - "merge-options": "^1.0.1" + "merge-options": "^1.0.1", + "promise-retry": "^1.1.1" } } diff --git a/src/api.js b/src/api.js index 685c726..0681c6e 100644 --- a/src/api.js +++ b/src/api.js @@ -8,12 +8,14 @@ class Api { * @param {*} endpoints * @param {string} token * @param {string} key + * @param {object} opts */ - constructor(namespace, endpoints, token, key) { + constructor(namespace, endpoints, token, key, opts) { this.namespace = namespace; this._endpoints = endpoints; this.token = token; this.key = key; + this.opts = opts; debug(`endpoints:${this.namespace}`, this.endpoints); } @@ -32,7 +34,8 @@ class Api { `${this.namespace}:${endpoint}`, this._endpoints[endpoint], this.token, - this.key + this.key, + this.opts ); } diff --git a/src/endpoint.js b/src/endpoint.js index 102614b..eee4fc7 100644 --- a/src/endpoint.js +++ b/src/endpoint.js @@ -1,4 +1,5 @@ const axios = require('axios'); +const promiseRetry = require('promise-retry'); const debug = require('./utils/debug')(__filename); const Response = require('./response'); @@ -8,14 +9,16 @@ class Endpoint { * @param {*} definition * @param {string} token * @param {string} key + * @param {object} opts */ - constructor(name, definition, token, key) { + constructor(name, definition, token, key, opts) { this.name = name; this.definition = definition; this.token = token; this.key = key; + this.opts = opts; - debug(`endpoint:${this.name}`, this.definition); + debug(`endpoint:${ this.name }`, this.definition); } /** @@ -24,6 +27,7 @@ class Endpoint { * @returns {Response} */ async call(...args) { + const { name } = this; const opts = await this.definition.build( this.definition, this.token, @@ -31,17 +35,25 @@ class Endpoint { ...args ); - debug(`call:${this.name}`, opts); + debug(`call:${ name }`, opts); let response = null; try { - response = new Response(this, opts, null, await axios.request(opts)); + const result = await promiseRetry(this.opts.backoff, async (retry, number) => { + debug(`call:${ name }:try:${ number }`, opts); + + const response = await axios.request(opts); + + return response.status === 429 ? retry(response) : response; + }); + + response = new Response(this, opts, null, result); } catch (error) { response = new Response(this, opts, error, null); } - debug(`response:${this.name}`, response.toJSON()); + debug(`response:${ name }`, response.toJSON()); return response; } diff --git a/src/ubidots.js b/src/ubidots.js index e6f0733..bbc555d 100644 --- a/src/ubidots.js +++ b/src/ubidots.js @@ -1,3 +1,5 @@ +const mergeOptions = require('merge-options'); + const { ApiBase, api: apigen } = require('./api-definition'); const Api = require('./api'); const debug = require('./utils/debug')(__filename); @@ -6,11 +8,17 @@ const MissingApiNamespaceError = require('./error/missing-api-namespace'); class Ubidots { /** * @param {*} api + * @param {object} opts + * @param {object} opts.backoff - backoff options + * @param {number} opts.backoff.retries - amount of retries + * @param {number} opts.backoff.minTimeout - minimum amount of time between retries + * @param {number} opts.backoff.factor - factor of the timeout between retries */ - constructor(api) { + constructor(api, opts) { this._api = api; this.token = null; this.key = null; + this.opts = opts; debug('namespaces', this.apis); } @@ -46,7 +54,8 @@ class Ubidots { namespace, this._api[namespace], this.token, - this.key + this.key, + this.opts ); } @@ -78,14 +87,34 @@ class Ubidots { /** * Create an instance of Ubidots API Client * @param {string} baseURL + * @param {object} opts + * @param {object} opts.backoff - backoff options + * @param {number=} opts.backoff.retries - amount of retries + * @param {number=} opts.backoff.minTimeout - minimum amount of time between retries + * @param {number=} opts.backoff.factor - factor of the timeout between retries * @returns {Ubidots} */ - static create(baseURL = ApiBase.Industrial) { + static create(baseURL = ApiBase.Industrial, opts = Ubidots.DEFAULT_CONFIG) { debug('client', baseURL); const api = apigen(baseURL); - return new this(api); + const mergedOpts = mergeOptions(this.DEFAULT_CONFIG, opts); + + return new this(api, mergedOpts); + } + + /** + * @returns {{backoff: {minTimeout: number, retries: number, factor: number}}} + */ + static get DEFAULT_CONFIG() { + return { + backoff: { + retries: 5, + factor: 1.4, + minTimeout: 500, + } + }; } }