Source: app/util/PathAliasManager.js

import flatten from 'lodash/flatten';
import debugLib from 'debug';

const debug = debugLib('SlimmingWorld:PathAliasManager');

/**
 * Utility that manages loading and resolving simple path aliases (mappings from one path
 * to another path).
 */
class PathAliasManager {
  aliasConfigs = [];

  /**
   * Resolves aliases for the given path. Will load in any configuration added with addConfig,
   * if they had not been loaded already.
   * @param path {string} The requested path
   * @param injectParams {Object} The parameters that will be injected to the getAliases callback
   * of the configs. These should contain the redux store dispatch and getState.
   * @returns {Promise<boolean|string>} A promise that resolves with the path that should be
   * redirected to, or `false` if no matching aliases have been found.
   */
  async handleRequest(path, injectParams) {
    const aliases = flatten(
      await Promise.all(
        this.aliasConfigs
          .filter(({ scope }) => path.startsWith(scope))
          .map(config => config.aliases || this.getAliases(config, injectParams)),
      ),
    );

    const matchingAlias = aliases.find(({ from }) => this.matchesFrom(path, from));
    if (matchingAlias) {
      debug(`Found alias "${matchingAlias.from} that matched path "${path}"`);
      return matchingAlias.to;
    }

    return false;
  }

  /**
   * Tests if the `from` property of an alias matches a given path. Currently does a strict
   * equality check, but we may add additional pattern matching here.
   * @param path {string} The path to check
   * @param from {string} The `from` property of an alias configuration
   * @returns {boolean}
   */
  matchesFrom = (path, from) => path === from;

  getAliases = (config, injectParams) => {
    debug(`Loading aliases for config with scope "${config.scope}"`);

    return Promise.resolve(config.getAliases(injectParams)).then(aliases => {
      debug(`Aliases for config with scope "${config.scope}" loaded`);
      config.aliases = aliases; // eslint-disable-line no-param-reassign
      return aliases;
    });
  };

  /**
   * Add aliases to the configuration
   * @param getAliases {function} A callback that retrieves the aliases configuration. This function
   * will only be called once the user navigates to a path that falls within the scope of this
   * configuration. The function will receive the parameters that the page renderer passes to the
   * `injectParams` parameter of the `handleRequest` method. This function should return an object
   * or a Promise that resolves with an object of the following shape:
   * ```
   * [
   *    { from: 'from path', to: 'to path' },
   *    { from: 'from path', to: 'to path' },
   *    ...
   * ]
   * ```
   * @param scope {string} The paths that are included in this config. Once the user navigates
   * to a path that starts with this string, the `getAliases` callback will be called to load
   * the aliases configuration.
   */
  addConfig(getAliases, scope = '/') {
    this.aliasConfigs.push({ getAliases, scope, aliases: null });
  }
}

export default PathAliasManager;