Home Manual Reference Source Repository

src/service/Service.js

/**
 * Default timeout for any pending request.
 * @type {number}
 */
const DEFAULT_REQUEST_TIMEOUT = 60000

/**
 * Item storage which is separate for each service. The `Map` key is the service `id`.
 */
const itemsStorage = new Map()

/**
 * Pending request map. Pending request is when a service uses a Promise
 * which will be fulfilled or rejected somewhere else in code. For exemple when
 * a peer is waiting for a feedback from another peer before Promise has completed.
 * @type {Map}
 */
const requestsStorage = new Map()

/**
 * Abstract class which each service should inherit. Each service is independent
 * and can store data temporarly in order to accomplish its task(s).
 */
class Service {

  /**
   * It should be invoked only by calling `super` from the children constructor.
   *
   * @param {number} id The service unique identifier
   */
  constructor (id) {
    /**
     * The service unique identifier.
     * @type {number}
     */
    this.id = id
    if (!itemsStorage.has(this.id)) itemsStorage.set(this.id, new WeakMap())
    if (!requestsStorage.has(this.id)) requestsStorage.set(this.id, new WeakMap())
  }

  /**
   * Add a new pending request identified by `obj` and `id`.
   * @param {Object} obj
   * @param {number} id
   * @param {{resolve: Promise.resolve, reject:Promise.reject}} data
   * @param {number} [timeout=DEFAULT_REQUEST_TIMEOUT] Timeout in milliseconds
   */
  setPendingRequest (obj, id, data, timeout = DEFAULT_REQUEST_TIMEOUT) {
    this.setTo(requestsStorage, obj, id, data)
    setTimeout(() => { data.reject('Pending request timeout') }, timeout)
  }

  /**
   * Get pending request identified by `obj` and `id`.
   *
   * @param  {Object} obj
   * @param  {number} id
   * @returns {{resolve: Promise.resolve, reject:Promise.reject}}
   */
  getPendingRequest (obj, id) {
    return this.getFrom(requestsStorage, obj, id)
  }

  /**
   * Add item with `obj` and `ìd` as identifier.
   * @param {Object} obj
   * @param {number} id
   * @param {Object} data
   */
  setItem (obj, id, data) {
    this.setTo(itemsStorage, obj, id, data)
  }

  /**
   * Get item identified by `obj` and `id`.
   *
   * @param {Object} obj
   * @param {number} id
   *
   * @returns {Object}
   */
  getItem (obj, id) {
    return this.getFrom(itemsStorage, obj, id)
  }

  /**
   * Get all items belonging to `obj`.
   *
   * @param {Object} obj
   * @returns {Map}
   */
  getItems (obj) {
    const items = itemsStorage.get(this.id).get(obj)
    if (items) return items
    else return new Map()
  }

  /**
   * Remove item identified by `obj` and `id`.
   *
   * @param {Object} obj
   * @param {number} id
   */
  removeItem (obj, id) {
    const currentServiceTemp = itemsStorage.get(this.id)
    const idMap = currentServiceTemp.get(obj)
    currentServiceTemp.get(obj).delete(id)
    if (idMap.size === 0) currentServiceTemp.delete(obj)
  }

  /**
   * @private
   * @param {Map} storage
   * @param {Object} obj
   * @param {number} id
   *
   * @returns {Object}
   */
  getFrom (storage, obj, id) {
    const idMap = storage.get(this.id).get(obj)
    if (idMap !== undefined) {
      const item = idMap.get(id)
      if (item !== undefined) return item
    }
    return null
  }

  /**
   * @private
   * @param {Map} storage
   * @param {WebChannel} obj
   * @param {number} id
   * @param {Object} data
   *
   */
  setTo (storage, obj, id, data) {
    const currentServiceTemp = storage.get(this.id)
    let idMap
    if (currentServiceTemp.has(obj)) {
      idMap = currentServiceTemp.get(obj)
    } else {
      idMap = new Map()
      currentServiceTemp.set(obj, idMap)
    }
    if (!idMap.has(id)) idMap.set(id, data)
  }
}

export default Service