services/game-data-service.js

/**
 * AngularJS Service for loading and storing game data and locale information.
 *
 * @module gameDataService
 * @see https://docs.angularjs.org/api/ng/type/angular.Module#service
 *
 * @requires APIService
 * @requires debugService
 * @requires domainService
 * @requires URLParamsService
 */
angular.module('tgaApp').service('gameDataService', [
  '$log',
  '$document',
  'APIService',
  'debugService',
  'domainService',
  'URLParamsService',
  '$translate',
  'envService',
  'languageData',
  function gameDataService(
    $log,
    $document,
    APIService,
    debugService,
    domainService,
    URLParamsService,
    $translate,
    envService,
    languageData,
  ) {
    /**
     * @private
     * @member {?Object} gameData
     */
    let gameData = null;

    const { domain } = domainService;

    /**
     * Returns the game data from {@link module:gameDataService~gameData}
     *
     * @returns {?Object}
     */
    this.get = () => gameData;

    /**
     * Returns a deep copy of the game data from {@link module:gameDataService~gameData}
     *
     * @returns {?Object}
     */
    this.getCopy = () => {
      const dataStr = angular.toJson(gameData);
      return angular.fromJson(dataStr);
    };

    /**
     * Load game data.
     *
     * @param {Object} params
     * @param {number} params.game_id
     * @param {number} params.language_id
     * @param {Function} [params.callback]
     */
    this.load = ({ language_id, game_id, callback }) => {
      const { slug, token } = URLParamsService.getSiteParams();

      if (debugService.enabled() && debugService.use('gameData')) {
        APIService.getGameFile(debugService.get('gameData'),
          (game) => {
            gameData = game;
            $log.debug('debug game file loaded', game);
            if (callback) {
              callback(game);
            }
          });
      } else {
        APIService.getGame({
          ...(token && { token }),
          ...(language_id && { language_id }),
          ...(game_id && { game_id }),
          domain,
          slug,
          // eslint-disable-next-line no-use-before-define
        }, (game) => loaded(game, callback));
      }
    };

    /**
     * Return the leaderboard fields that will be in used in the game
     *
     */
    this.getLeaderboardFields = () => {
      if (gameData) {
        const defaultFields = gameData.registration.fields.filter((f) => f.show_in_leaderboard);
        const customFields = Object.values(gameData.registration.custom_fields).filter((f) => f.show_in_leaderboard);
        const leaderboardFields = [
          ...defaultFields,
          ...customFields,
        ];
        return leaderboardFields;
      }
      return [];
    };

    /**
     * Set locale by code (aka `en_us`) and load game data.
     *
     * @param {string} code
     * @param {Function} [callback]
     */
    this.setLocale = (code, callback) => {
      const { available_translations, language_id, game_id } = gameData;
      const t = available_translations.find((tr) => tr.code === code);
      if (t && t.language_id !== language_id) {
        this.load({ language_id: t.language_id, game_id, callback });
      }
    };

    /**
     * Search the available translations by a specific key/value.
     *
     * @example <caption>Both return the "English (US)" language description, if it exists:</caption>
     * findLanguageBy(0, 'language_id'); // searches by numeric ID.
     * findLanguageBy('en_us', 'code'); // searches by locale code.
     *
     * @param {string|number} search_value - The value to search for in the provided key.
     * @param {string} search_key - The name of the key to search.
     * @param {string} use_fallback_data - Use data array stored in wrapper as fallback
     * @return {?Object} The language description object, if found.
     */
    this.findLanguageBy = (search_value, search_key, use_fallback_data) => {
      const { available_translations } = gameData;
      const hasTranslations = angular.isArray(available_translations) && available_translations.length > 0;
      if (!hasTranslations) {
        // eslint-disable-next-line max-len
        $log.debug(`gameDataService: trying to find a language but available translations is empty. ${use_fallback_data ? 'Using fallback data.' : ''}`);
      }
      const langArray = use_fallback_data && !hasTranslations ? languageData : available_translations;
      for (let i = 0; i < langArray.length; i++) {
        const t = langArray[i];
        // loose comparsion in case int checked against string
        // eslint-disable-next-line eqeqeq
        if (t[search_key] == search_value) {
          return t;
        }
      }
      return null;
    };

    /**
     * Callback used by {@link module:gameDataService.load} to handle a
     * successful response from {@link module:APIService.getGame}.
     *
     * @private
     * @method loaded
     * @param {Object} game
     * @param {Function} [callback]
     */
    const loaded = (game, callback) => {
      // filter out custom fields with a 'disabled' status
      // eslint-disable-next-line no-param-reassign
      game.registration.custom_fields = game.registration.custom_fields.filter((f) => f.condition !== 'disabled');
      gameData = game;
      APIService.setGameId(game.game_id);
      if (envService.get() !== 'production') {
        $log.debug('game loaded', game);
      }
      const { language_id } = game;
      const currentLanguage = this.findLanguageBy(language_id, 'language_id', true);
      $translate.use(currentLanguage.code);
      angular.element($document[0].querySelector('body')).addClass(currentLanguage.code);
      const { language } = URLParamsService.getUserParams();
      if (language) {
        const l = this.findLanguageBy(language, 'code', true);
        if (l) {
          this.setLocale(l, callback);
          return;
        }
      }
      if (callback) {
        callback(game);
      }
    };
  },
]);