services/user-service.js

/**
 * AngularJS Service for getting/storing user data and managing logins/sessions.
 *
 * @module userService
 * @see https://docs.angularjs.org/api/ng/type/angular.Module#service
 *
 * @requires authService
 * @requires domainService
 * @requires gameDataService
 * @requires gameTrackingService
 * @requires instructorService
 * @requires URLParamsService
 * @requires utilityService
 */
angular.module('tgaApp').service('userService', [
  '$log',
  '$http',
  '$location',
  '$window',
  'authService',
  'localStorageService',
  'envService',
  'domainService',
  'gameDataService',
  'gameTrackingService',
  'utilityService',
  'instructorService',
  'URLParamsService',
  'scormService',
  function userService(
    $log,
    $http,
    $location,
    $window,
    authService,
    localStorageService,
    envService,
    domainService,
    gameDataService,
    gameTrackingService,
    utilityService,
    instructorService,
    URLParamsService,
    scormService,
  ) {
    const serverAddress = envService.read('serverAddress');
    const storageKey = 'player';

    /**
     * @private
     * @member {Object} user
     */
    let user = null;
    let session_limit_reached = false;
    /**
     *
     * @returns {boolean} to let game know that the user has reached their session limit.
     */
    this.checkSessionLimitReached = () => session_limit_reached;
    /**
     * Check authentication using the {@link module:authService}.
     *
     * @returns {boolean} Whether or not the user is authenticated.
     */
    this.isLoggedIn = () => authService.getAuthentication().isAuth;

    /**
     * Get the user via the API.
     * @see https://api.thetrainingarcade.com/documentation/#api-Player-GetPlayer
     *
     * @returns {Promise} Promise object, either:
     *   - Resolved with the HTTP response data.
     *   - Rejected with the full HTTP response object or an error.
     */
    this.getUser = () => {
      const { game_id } = gameDataService.get();
      return $http({
        method: 'GET',
        url: `${serverAddress}player?game_id=${game_id}`,
      }).then(
        (response) => response.data,
        (error) => Promise.reject(error),
      );
    };

    /**
     * Get the current user data from localStorage.
     *
     * @returns {Object}
     */
    this.getUserData = () => {
      if (!user) {
        user = localStorageService.get(storageKey);
      }

      return user ?? {};
    };

    /**
     * Set the current user data in localStorage.
     *
     * @param {Object} newUser
     */
    this.setUser = (newUser) => {
      user = newUser;
      localStorageService.set(storageKey, user);
    };

    /**
     * Update a user via the API.
     * @see https://api.thetrainingarcade.com/documentation/#api-Player-UpdatePlayer
     *
     * @param {Object} data Request message data
     * @returns {Promise} Promise object, either:
     *   - Resolved with the HTTP response data.
     *   - Rejected with the full HTTP response object or an error.
     */
    this.updateUser = (data) => $http({
      method: 'PUT',
      url: `${serverAddress}player`,
      data,
    }).then(
      (response) => {
        this.setUser(response.data);
        return response.data;
      },
      (error) => Promise.reject(error),
    );

    /**
     * Clear authentication and stored local user data.
     */
    this.logout = () => {
      authService.clearAuthentication();
      this.setUser(null);
    };

    /**
     * Start a session via the API.
     * @see https://api.thetrainingarcade.com/documentation/#api-Player-PostSession
     *
     * @param {Object} [data] Request message data.
     * @returns {Promise} Promise object, either:
     *   - Resolved with the HTTP response data.
     *   - Rejected with the full HTTP response object or an error.
     */
    this.startSession = (data) => {
      const { game_id, language_id } = gameDataService.get();
      const {
        hub_id, course_id, challenge_id, token, session_group_id, segment, team_id,
      } = URLParamsService.getUserParams();
      const sendData = {
        ...data,
        game_id,
        language_id,
        hub_id,
        course_id,
        challenge_id,
        team_id,
        token,
        segment,
        session_group_id: instructorService.getSessionGroupId() || session_group_id,
        is_scorm: scormService.isConnected(),
      };
      gameTrackingService.reset();
      return $http({
        method: 'POST',
        url: `${serverAddress}player/session`,
        data: sendData,
      }).then(
        (response) => {
          if (sendData.hub_id) {
            $window.parent.postMessage({ session_id: response.data.session_id }, '*');
          }
          session_limit_reached = response.data.session_limit_reached ?? false;
          return response.data;
        },
        (error) => Promise.reject(error),
      );
    };

    // function to clear the scores for a SCORM player if they are restarting the game in the LMS
    const clearSCORMScoresOrNot = () => {
      if (scormService.isConnected() && scormService.provideEntryStatus() === 'new') {
        $log.debug('clearSCORMScoresOrNot: clearing prior scores for SCORM');
        return this.clearScores({
          slug: gameDataService.get().slug,
        });
      }
      return new Promise((res) => {
        res();
      });
    };

    /**
     * Internal method used to handle a successful API response of login and
     * registration requests. It attempts to do the following:
     *  - Set authentication data in {@link module:authService}
     *  - Get the currently logged in user object (via {@link module:userService.getUser})
     *  - Set the currently stored user object (via {@link module:userService.SetUser})
     *  - Start a session (via {@link module:userService.startSession})
     *
     * @private
     * @method authDataApply
     * @param {Object} data Request message data.
     * @param {Object} response HTTP response object.
     * @returns {Promise} Promise object, either:
     *   - Resolved with the current user Object.
     *   - Rejected with the full HTTP response object or an error.
     */
    const authDataApply = (data, response, isSSOAuth) => {
      const authentication = authService.clearAuthentication();
      authentication.isAuth = true;
      authentication.username = data.username;
      authentication.token = response.data.key;
      authentication.userId = response.data.id;

      authService.setAuthentication(authentication, true);

      return this.getUser().then((currentUser) => {
        this.setUser(currentUser);
        if (!isSSOAuth) {
          clearSCORMScoresOrNot().then(() => {
            $log.debug('registered, starting session');
            this.startSession();
          });
        }
        return currentUser;
      });
    };

    /**
     * Login as a user.
     * Calls {@link module:userService~authDataApply} on success.
     *
     * @see https://api.thetrainingarcade.com/documentation/#api-Player-LoginPlayer
     *
     * @param {Object} data Request message data.
     * @param {string} [data.username]
     * @param {string} [data.email]
     * @param {string} [data.password]
     * @param {string} [data.token]
     * @param {string} [data.game_id]
     * @returns {Promise} Promise object, either:
     *   - Resolved with the current user Object.
     *   - Rejected with the full HTTP response object or an error.
     */
    this.login = (data) => {
      const sendData = { ...data };
      return $http({
        method: 'POST',
        url: `${serverAddress}player/login`,
        data: sendData,
      }).then(
        (response) => authDataApply(data, response, true),
        (error) => {
          this.logout();
          return Promise.reject(error);
        },
      );
    };

    /**
     * Login as a user.
     * Calls {@link module:userService~authDataApply} on success.
     *
     * @see https://api.thetrainingarcade.com/documentation/#api-Player-RegisterPlayer
     *
     * @param {Object} data Request message data.
     * @returns {Promise} Promise object, either:
     *   - Resolved with the current user Object.
     *   - Rejected with the full HTTP response object or an error.
     */
    this.register = (data) => {
      const sendData = { ...data };
      sendData.game_id = gameDataService.get().game_id;
      if (angular.isUndefined(sendData.username)) {
        sendData.username = utilityService.generateUUID();
      }
      return $http({
        method: 'POST',
        url: `${serverAddress}player/register`,
        data: sendData,
      }).then(
        (response) => authDataApply(sendData, response),
        (error) => Promise.reject(error),
      );
    };

    /**
     * Check authorization via the API.
     * @see https://api.thetrainingarcade.com/documentation/#api-Player-AuthPost
     *
     * @param {Object} data Request message data.
     * @returns {Promise} Promise object, either:
     *   - Resolved with the response data's `authorization` property.
     *   - Rejected with the full HTTP response object or an error.
     */
    this.checkAuthorizedUser = (data) => {
      const sendData = { ...data };
      sendData.domain = domainService.domain;
      return $http({
        method: 'POST',
        url: `${serverAddress}player/auth`,
        data: sendData,
      }).then(
        (response) => response.data.authorization,
        (error) => Promise.reject(error),
      );
    };

    /**
     * Clear player scores via the API.
     * @see https://api.thetrainingarcade.com/documentation/#api-Player-PostClearScores
     *
     * @param {Object} data Request message data.
     * @returns {Promise} Promise object, either:
     *   - Resolved with the ull HTTP response object.
     *   - Rejected with the full HTTP response object or an error.
     */
    this.clearScores = (data) => $http({
      method: 'POST',
      url: `${serverAddress}player/clear_scores`,
      data,
    }).then(
      (response) => {
        $log.debug('scores cleared');
        return response;
      },
      (error) => {
        $log.debug('error clearing scores');
        return Promise.reject(error);
      },
    );
  },
]);