Source: jsoncache.js

'use strict';

const EventEmitter = require('events');

const retryCodes = [429].concat((process.env.JSON_CACHE_RETRY_CODES || '')
  .split(',').map(code => parseInt(code.trim(), 10)));

const defaultOpts = {
  parser: JSON.parse,
  promiseLib: Promise,
  logger: console,
  delayStart: false,
  opts: {},
  maxListeners: 10,
  useEmitter: false,
  maxRetry: 1,
  integrity: () => true,
};

class JSONCache extends EventEmitter {
  /**
   * Make a new cache
   * @param {string}    url                  url to fetch
   * @param {number}    [timeout=60000]      optional timeout
   * @param {Object}    options              Options object
   * @param {function}  options.parser       optional parser to parse data. defaults to JSON.parse
   * @param {Class}     options.promiseLib   optional promise library override
   * @param {Object}    options.logger       optional Logger
   * @param {boolean}   options.delayStart   whether or not to delay starting updating the cache
   *                                            until start is requested
   * @param {Object}    options.opts         options to pass to the parser
   * @param {number}    options.maxListeners maximum listeners
   *                                            (only applicable if leveraging emitter)
   * @param {boolean}   options.useEmitter   whether or not to use the optional node emitter
   * @param {number}    options.maxRetry     maximum number of attempts to retry getting data
   * @param {function}  options.integrity    optional function to check if the data is worth keeping
   */
  constructor(url, timeout, options) {
    super();

    // eslint-disable-next-line no-param-reassign
    options = {
      ...defaultOpts,
      ...options,
    };

    const {
      parser, promiseLib, logger, delayStart, opts, maxListeners, useEmitter, maxRetry, integrity,
    } = options;

    this.url = url;
    // eslint-disable-next-line global-require
    this.protocol = this.url.startsWith('https') ? require('https') : require('http');

    this.maxRetry = maxRetry;
    this.timeout = timeout || 60000;
    this.currentData = null;
    this.updating = null;
    this.Promise = promiseLib;
    this.parser = parser;
    this.hash = null;
    this.logger = logger;
    this.delayStart = delayStart;
    this.opts = opts;
    this.useEmitter = useEmitter;
    this.integrity = integrity;

    if (useEmitter) {
      this.setMaxListeners(maxListeners);
    }
    if (!delayStart) {
      this.startUpdating();
    }
  }

  getData() {
    if (this.delayStart && !this.currentData && !this.updating) {
      this.startUpdating();
    }
    if (this.updating) {
      return this.updating;
    }
    return this.Promise.resolve(this.currentData);
  }

  getDataJson() {
    return this.getData();
  }

  update() {
    this.updating = this.httpGet().then(async (data) => {
      const parsed = this.parser(data, this.opts);
      if (!this.integrity(parsed)) return this.currentData;

      // data passed integrity check
      this.currentData = parsed;
      if (this.useEmitter) {
        setTimeout(async () => this.emit('update', await this.currentData), 2000);
      }

      this.updating = null;
      return this.currentData;
    }).catch((err) => {
      this.updating = null;
      throw err;
    });
  }

  httpGet() {
    return new this.Promise((resolve) => {
      const request = this.protocol.get(this.url, (response) => {
        this.logger.debug(`beginning request to ${this.url}`);
        const body = [];

        if (response.statusCode < 200 || response.statusCode > 299) {
          if ((response.statusCode > 499 || retryCodes.includes(response.statusCode))
            && this.retryCount < this.maxRetry) {
            this.retryCount += 1;
            setTimeout(() => this.httpGet().then(resolve).catch(this.logger.error), 1000);
          } else {
            this.logger.error(`${response.statusCode}: Failed to load ${this.url}`);
            resolve('[]');
          }
        } else {
          response.on('data', chunk => body.push(chunk));
          response.on('end', () => {
            this.retryCount = 0;
            resolve(body.join(''));
          });
        }
      });
      request.on('error', (err) => {
        this.logger.error(`${err.statusCode}: ${this.url}`);
        resolve('[]');
      });
    });
  }

  startUpdating() {
    this.updateInterval = setInterval(() => this.update(), this.timeout);
    this.update();
  }

  stop() {
    clearInterval(this.updateInterval);
  }

  stopUpdating() {
    this.stop();
  }
}
module.exports = JSONCache;