Home Reference Source Test Repository

lib/classes/EmbedEngine.js

'use strict';

/** @ignore */
let async = require('async');

/** @ignore */
let URLEmbedProvider = require('./URLEmbedProvider');

/** @ignore */
let OEmbedProvider = require('./OEmbedProvider');

/** @ignore */
let utils = require('../utils');

/** @ignore */
let UnknownProviderError = require('./errors/UnknownProviderError');

/**
 * Resolves urls to embeds from a registered list of embed providers
 */
class EmbedEngine {
  /**
  * @param {Object} [engineOptions] - configuration options
  * @param {number} [engineOptions.timeoutMs] - request timeout in milliseconds
  */
  constructor (engineOptions) {
    /**
    * Utility references to provider superclasses
    * @type {{URLEmbedProvider: URLEmbedProvider, OEmbedProvider: OEmbedProvider}}
    */
    this.providerSuperClasses = {
      URLEmbedProvider: URLEmbedProvider,
      OEmbedProvider: OEmbedProvider
    };

    /**
    * The directory containing the default provider classes
    * @type {String}
    */
    this.defaultProviderDirectory = __dirname + '/default_providers';

    /**
    * The registry of provider instances
    * @type {Array<URLEmbedProvider>}
    */
    this.providerRegistry = [];

    /**
     * Array of provider classes included in this library
     * @type {Array<URLEmbedProvider>} defaultProviderClasses - array of provider classes included in this library
     */
    this.defaultProviderClasses = utils.loadClassesFromDirectory(this.defaultProviderDirectory);

    /**
    * Configuration options. These will also be passed to the configure method of each provider instance
    * @type {Object} engineOptions - configuration options
    * @property {number} engineOptions.timeoutMs - request timeout in milliseconds
    */
    this.engineOptions = engineOptions;
  }

  /**
  * Registers a provider instance.
  *
  * __Highlander Rule__: this provider will replace an already registered provider if they have the same name property.
  *
  * @param {URLProvider} provider - instance of <code>URLProvider</code> or <code>OEmbedProvider</code>
  */
  registerProvider (provider) {
    let providerExists = false;

    this._configureProvider(provider);

    for (let idx = 0; idx < this.providerRegistry.length; idx++) {
      if (this.providerRegistry[idx].name === provider.name) {
        this.providerRegistry[idx] = provider;
        providerExists = true;
      }
    }

    if (!providerExists) {
      this.providerRegistry.push(provider);
    }
  }

  _configureProvider (provider) {
    provider.configure(this.engineOptions);
  }

  /**
  * Registers all the default providers in this library
  * @param {String} [path] - directory of provider classes (if not specified it will default to the ones in this library)
  */
  registerDefaultProviders (path) {
    for (let i = 0; i < this.defaultProviderClasses.length; i++) {
      let ProviderClass = this.defaultProviderClasses[i];
      let provider = new ProviderClass();
      this._configureProvider(provider);
      this.registerProvider(provider);
    }
  }

  /**
  * Resolves an Embed object, populating its data property and calling the callback
  * @param {Embed} embed - Embed object
  * @param {function(embed: Embed)} callback - callback to invoke after resolving Embed
  */
  getEmbed (embed, callback) {
    embed.markStarted();

    let providerFound = false;

    for (let idx = 0; idx < this.providerRegistry.length; idx++) {
      let provider = this.providerRegistry[idx];
      let self = this;

      if (provider.isMatch(embed.embedURL)) {
        providerFound = true;

        provider.getEmbed(embed, function (embed) {
          self.filterData(embed.data);
          embed.markFinished();
          callback(embed);
          return;
        });

        idx = this.providerRegistry.length;
      }
    }

    if (!providerFound) {
      embed.error = new UnknownProviderError('Unknown embed provider for url: ' + embed.embedURL);
      embed.data = {
        html: this.errorMarkupNoMatchingProvider(embed)
      };
      embed.markFinished();
      callback(embed);
    }
  }

  /*
  * Resolves an array of embedOptions.embedURL's in parallel
  * @param {Array} optArray - Array of embed options objects
  * @param {function(error: Error, results: Array)} callback - callback to invoke after batch is complete
  */
  getMultipleEmbeds (optArray, callback) {
    let self = this;

    async.map(optArray, function (options, callbackIterator) {
      self.getEmbed(options, function (embed) {
        callbackIterator(null, embed);
      });
    },
    function (error, results) {
      callback(error, results);
    });
  }

  /**
  * Returns error markup if there was no provider that matched the embedURL
  * @param {Embed} embed - Embed object
  */
  errorMarkupNoMatchingProvider (embed) {
    return '<a href="' + embed.embedURL + '">' + embed.embedURL + '</a>';
  }

  /**
  * Can be overridden/replaced to modify any provider's data before it is passed to the client callback.<br />
  * *Note:* if you only need to modify a specific provider's data then there's a similar <code>filterData</code> method on each provider object, too.
  * @param {{html: string}} data - Data object containing embed html
  */
  filterData (data) {}
}

module.exports = EmbedEngine;