Source: proxapi.js

'use strict';

/**
 * Initialize an instance of ProxAPI with an optional retry delay and a _translate_ function which must have the following structure :  
 *
 *     function(params, handleResults){ 
 *       // Modify the following line to call the desired API 
 *       some_api.some_request(some_parameters, function(some_return_values) {
 *         var proxapiStatus = { quota: false };
 *         var err = null;
 *      
 *         // Transformation from the API response format to the handleResults format
 *         // Do something here with the API return values and compute _err_, _results_ and _proxapiStatus_
 *      
 *         //Finally return to ProxAPI
 *         handleResults(err, results, proxapiStatus); 
 *       });
 *     }
 *
 *  This function : 
 *  * calls the API with parmaters collected in _params_ object
 *  * gets the results
 *  * catch errors and detect quota limits
 *  * returns to ProxAPI by calling _handleResults_ with the following arguments:
 *    * _err_ : errors not associated to usage limitations
 *    * _results_ : API request results
 *    * _proxapiStatus_ : information about the request, you must set at least _proxapiStatus.quota_ boolean value ( _true_ if the request failed due to usage limitations, _false_ if there wasn't any quota error).
 *
 * @namespace
 * @constructor
 * @param {object} settings - Settings of the proxy
 * @param {integer} settings.retryDelay - Retry delay in seconds
 * @param {function} settings.translate - Interface beetween ProxAPI and the API, allowing ProxAPI to call the API and to understand the results.  
 */
var ProxAPI = function(settings){
  this.apiCall = settings.translate;

  /**
   * Retry_delay Retry delay in milliseconds
   * @member {integer} 
   * @default 60000
   */
  this.retryDelay = (settings.retryDelay || 60) * 1000;
};

/**
 * Calls the API and apply a strategy if it encounter a quota limit.  
 * Calls the _onEvent_ method if provided to notice when such cases occur.  
 * Finally calls the _onEnd_ function whith the API results
 *
 * @param {object} params - Parameters needed by the API calling function. The  _translate_ function is called with this same object.
 * @param {object} options - Options
 * @param {string} [options.strategy] - Strategy to apply when a quota limit is reached. Possible values : 
 *   * "retry" : wait for the end of the limited period and retry
 *   * "abort" : abort the request and return an informative error message
 * @param {ProxAPI~onEvent} [options.onEvent] - Callback function called each time an event occurs
 * @param {ProxAPI~onEnd} onEnd - Callback function called after the API call has been made
 */
ProxAPI.prototype.call = function(params, options, onEnd){
  var self = this;
  this.apiCall(params, function(error, data, status){
      if (error){
        onEnd(error, null);
      } else {
        if (status.retryDelay){
          self.retryDelay = status.retryDelay * 1000;
        }

        if (status.quota){
          if (options.strategy == "retry"){
            var message = 'Rate limit reached. Retrying in ' + Math.ceil(self.retryDelay/1000) + ' seconds';
            if (options.onEvent) options.onEvent('retrying', message);
            setTimeout(function(){
                self.call(params, options, onEnd);
              }, self.retryDelay);
          } else {
            onEnd("Rate limit exceeded", null);
          }
        } else {
          onEnd(error, data);
        }
      }
    });
};
/**
 * @callback ProxAPI~onEnd
 * @param {string} error
 * @param {object} data - API request results
 */

/**
 * @callback ProxAPI~onEvent
 * @param {string} eventName - Name of the event (ie "retrying")
 * @param {string} data  - Event message
 */

 /**
  * callUntil
  *
  * @param {object} untilSettings - Settings for calling the API recursively
  * @param {ProxAPI~newParams} untilSettings.newParams - Callback calculating the new parameters for the next API call
  * @param {ProxAPI~aggregate} untilSettings.aggregate - Callback aggregating results of successives API calls
  * @param {ProxAPI~endCondition} untilSettings.endCondition - Callback deciding if more API calls are needed
  * @param {object} params - Parameters needed by the API calling function. The  _translate_ function is called with this same object.
  * @param {object} options - Options
  * @param {string} [options.strategy] - Strategy to apply when a quota limit is reached. Possible values : 
  *   * "retry" : wait for the end of the limited period and retry
  *   * "abort" : abort the request and return an informative error message
  * @param {ProxAPI~onEvent} [options.onEvent] - Callback function called each time an event occurs
  * @param {ProxAPI~onEnd} onEnd - Callback function called after the API call has been made
  * @param {object} [results] - results from preceding API calls to aggregate
  */
 ProxAPI.prototype.callUntil = function(untilSettings, params, options, onEnd, results){
   var self = this;
   results = results || [];
   this.call(params, options, function(err, data){
     results = untilSettings.aggregate(results, data);
     params = untilSettings.newParams(err, data, params);
     if (untilSettings.endCondition(err, data)){
       onEnd(err, results);
     } else {
       self.callUntil(untilSettings, params, options, onEnd, results);
     }
   });
 };

/**
 * @callback ProxAPI~newParams
 * @param {string} error - API request error
 * @param {object} data - API request results
 * @return {object} params - Parameters used for the next API call
 */

/**
 * @callback ProxAPI~aggregate
 * @param {string} results - results from preceding API calls
 * @param {object} data - API request results
 * @return {object} results - Aggregated results
 */

/**
 * @callback ProxAPI~endCondition
 * @param {string} error - API request error
 * @param {object} data - API request results
 * @return {boolean} stop - True if all results have been fetched, False if more API calls are needed
 */

/**
 * Provides informations about the API limitations
 *
 * @return {LimitInfo} infos - Informations about the API limitations {@link ProxAPI.LimitInfo}
 */
ProxAPI.prototype.getLimitInfo = function(){
  return {
    retryDelay: (this.retryDelay / 1000)
  };
};

/**
    @memberof ProxAPI
    @typedef LimitInfo
    @type {object}
    @property {integer} retryDelay - Number of seconds to wait before retrying a call to the API
 */

module.exports = ProxAPI;