Source: client.js

import { CatalogItem } from './catalogItem/catalogItem';
import { PriceGuide } from './catalogItem/priceGuide';
import { KnownColor } from './catalogItem/knownColor';
import { ItemImage } from './catalogItem/itemImage';
import { Subset } from './catalogItem/subsets';
import { Superset } from './catalogItem/supersets';
import { logger } from './logger';
import { BricklinkRequest } from './request';
import { BrickLinkApiError } from './brickLinkApiError';
import { OAuth } from 'oauth';

/**
 * Create a client to perform
 */
export class Client {
  /**
   * Create an instance of the Bricklin Node Client.
   * @param {object} [options] Options that are used to create a new client.
   * @param {string} [options.token] The `TokenValue` from {@link https://www.bricklink.com/v2/api/register_consumer.page}
   * @param {string} [options.token_secret] The `TokenSecret` from {@link https://www.bricklink.com/v2/api/register_consumer.page}
   * @param {string} [options.consumer_key] The `ConsumerKey` from {@link https://www.bricklink.com/v2/api/register_consumer.page}
   * @param {string} [options.consumer_secret] The `ConsumerSecret` from {@link https://www.bricklink.com/v2/api/register_consumer.page}
   * @param {string} [options.endpoint='https://api.bricklink.com/api/store/v1/'] The url of the Bricklink API.
   */
  constructor(options) {
    options = options || {};

    /** @type {string} */
    this.token = options.token || '';
    /** @type {string} */
    this.token_secret = options.token_secret || '';
    /** @type {string} */
    this.consumer_key = options.consumer_key || '';
    /** @type {string} */
    this.consumer_secret = options.consumer_secret || '';
    /** @type {string} */
    this.endpoint =
      options.endpoint || 'https://api.bricklink.com/api/store/v1/';
    /** @type {Function[]} */
    this.requestQueue = [];
    /** @type {object} */
    this._oauth = new OAuth(
      null,
      null,
      this.consumer_key,
      this.consumer_secret,
      '1.0',
      null,
      'HMAC-SHA1',
    );
  }

  /**
   * Performs a concurrent-safe bricklink request and the callback upon success.
   * @param {BricklinkRequest} req The request to perform.
   * @return {Promise} The data that has been return from the API request and any callbacks.
   */
  send(req) {
    const promise = new Promise((resolve, reject) => {
      const callback = () =>
        this.dispatch(req)
          .then(resolve)
          .catch(reject)
          .then(() => {
            const deleteIndex = this.requestQueue.indexOf(callback);
            this.requestQueue.splice(deleteIndex, 1);
            if (this.requestQueue.length > 0) {
              const continueQueue = this.requestQueue[0];
              continueQueue();
            }
          });
      this.requestQueue.push(callback);
      if (this.requestQueue.length === 1) {
        const startQueue = this.requestQueue[0];
        startQueue();
      }
    });
    return promise;
  }

  /**
   * Performs a bricklink request and the callback upon success.
   * @param {BricklinkRequest} req The request to perform.
   * @return {Promise} The data that has been return from the API request and any callbacks.
   */
  dispatch(req) {
    const resourceURL =
      this.endpoint + req.uri.replace(/^\//, '') + req.params.toQueryString();
    /** @type {RequestInit} */
    const init = {
      method: req.method,
      headers: {},
    };

    if (req.resource) {
      init.headers['content-type'] = 'application/json';
      init.body = JSON.stringify(req.resource);
    }

    init.headers['Authorization'] = this._oauth.authHeader(
      resourceURL,
      this.token,
      this.token_secret,
      req.method,
    );

    const promise = fetch(resourceURL, init)
      .then((response) => response.json())
      .then(
        /**
         * @param {any} payload Any object
         */
        (payload) => {
          if (payload.meta.code >= 300) {
            const error = new BrickLinkApiError(payload.meta);
            logger(
              JSON.stringify(
                {
                  reqestURI: resourceURL,
                  responseMetadata: payload.meta,
                },
                null,
                2,
              ),
            );
            throw error;
          } else {
            return payload.data;
          }
        },
      );

    promise.catch((error) => {
      logger(error);
    });

    if (req.callback) {
      return promise.then(req.callback);
    }

    return promise;
  }

  /**
   * Get a catalog item by type and identification number.
   * @param {string} itemType An item type as can be foud at {@link ItemType}.
   * @param {string} itemNumber An item identification number.
   * @return {Promise<CatalogItem>} A promise that resolves to a catalog item.
   */
  getCatalogItem(itemType, itemNumber) {
    const req = CatalogItem.get(itemType, itemNumber);

    return this.send(req);
  }

  /**
   * Get the price guide for a given catalog item.
   * @param {string} itemType An item type as can be foud at {@link ItemType}.
   * @param {string} itemNumber An item identification number.
   * @param {object} params Options for the price guide as outlined in {@link PriceGuideOptions}.
   * @return {Promise<PriceGuide>} A promise that resolves to a price guide.
   */
  getPriceGuide(itemType, itemNumber, params) {
    const req = PriceGuide.get(itemType, itemNumber, params);

    return this.send(req);
  }

  /**
   * Get known colors for a catalog item.
   * @param {string} itemType An item type as can be foud at {@link ItemType}.
   * @param {string} itemNumber An item identification number.
   * @return {Promise<Array>} A promise that resolves to a list of {@link KnownColor}.
   */
  getKnownColors(itemType, itemNumber) {
    const req = KnownColor.get(itemType, itemNumber);

    return this.send(req);
  }

  /**
   * Can get an image for a specific image color of a known catalog item.
   * @param {string} itemType An item type as can be foud at {@link ItemType}.
   * @param {string} itemNumber An item identification number.
   * @param {number} colorId The color id of the item.
   * @return {Promise<ItemImage>} A promise that resolves to an Item Image.
   */
  getItemImage(itemType, itemNumber, colorId) {
    const req = ItemImage.get(itemType, itemNumber, colorId);
    return this.send(req);
  }

  /**
   * Gets a subset of a catalog item.
   * @param {string} itemType An item type as can be foud at {@link ItemType}.
   * @param {string} itemNumber An item identification number.
   * @param {object} [params] Options for the price guide as outlined in {@link SubsetOptions}.
   * @return {Promise<Array>} A promise that resolves to a  list of {@link Subset}.
   */
  getItemSubset(itemType, itemNumber, params) {
    const req = Subset.get(itemType, itemNumber, params);
    return this.send(req);
  }

  /**
   * Gets a superset of a catalog item.
   * @param {string} itemType An item type as can be foud at {@link ItemType}.
   * @param {string} itemNumber An item identification number.
   * @param {object} [params] Options for the price guide as outlined in {@link SupersetOptions}.
   * @return {Promise<Array>} A promise that resolves to a  list of {@link Superset}.
   */
  getItemSuperset(itemType, itemNumber, params) {
    const req = Superset.get(itemType, itemNumber, params);
    return this.send(req);
  }
}