/**
* AngularJS Service for managing generic API calls.
* Also stores default settings for API calls such as `game_id`.
*
* @module APIService
* @see https://docs.angularjs.org/api/ng/type/angular.Module#service
*
* @requires debugService
* @requires domainService
* @requires instructorService
* @requires URLParamsService
* @requires utilityService
*/
angular.module('tgaApp').service('APIService', [
'$http',
'$log',
'URLParamsService',
'localStorageService',
'envService',
'debugService',
'utilityService',
'domainService',
'instructorService',
'security',
'messagingService',
function APIService(
$http,
$log,
URLParamsService,
localStorageService,
envService,
debugService,
utilityService,
domainService,
instructorService,
security,
messagingService,
) {
const serverAddress = envService.read('serverAddress');
const useCachingServer = envService.read('useCaching');
const cachingServerAddress = useCachingServer ? envService.read('cachingServerAddress') : null;
/**
* Stored `game_id` to use as default when API calls do not specify one.
*
* @private
* @member {?number} game_id
*/
let game_id = null;
/**
* Set stored default game ID {@link module:APIService~game_id}
*
* @param {?number} id
*/
this.setGameId = (id) => {
game_id = id;
};
/**
* Get stored default game ID {@link module:APIService~game_id}
*
* @returns {?number} The default `game_id` currently stored in the module instance.
*/
this.getGameId = () => game_id;
this.isSuccess = (status) => status < 400;
/**
* Generic wrapper function for API calls using angular $http service. Used by most public methods in the module.
*
* If `debugService.get('noAPI')` is true, this will not perform any API request, and will immediately resolve
* as if the request responded successfully with the data `{ noAPI: true }`.
*
* @param {Object} params
* @param {string} params.method - HTTP method
* @param {string} params.url - URL endpoint (added to the environment's `serverAddress`)
* @param {Object} [params.params] - Query parameters.
* @param {Object} [params.data] - Request message data.
* @param {Object} [params.config] - Set $http config properties, can override call function args
* @param {$httpDataCallback} [params.onSuccess] - Callback to run on success (receives response data only).
* @param {$httpCallback} [params.onError] - Callback to run on error (receives full response object).
* @returns {Promise} Promise object, either:
* - Resolved with the result of the `onSuccess` callback or the HTTP response data.
* - Rejected with the result of the `onError` callback, the full HTTP response object, or an error.
*/
this.call = (req) => {
const {
method, url, host, params, data, config = {}, onSuccess, onError,
} = req;
if (debugService.enabled() && debugService.get('noAPI')) {
$log.debug(`noAPI set in debug, skipping call to ${url}`);
return Promise.resolve(
onSuccess ? onSuccess({ noAPI: true }) : { noAPI: true },
);
}
return $http({
method,
url: `${host ?? serverAddress}${url}`,
params,
data,
...config,
}).then(
// eslint-disable-next-line max-len
(response) => (onSuccess ? onSuccess(response.data, this.isSuccess(response.status), response.status) : response.data),
(error) => (host ? this.call({ ...req, ...{ host: null } }) : Promise.reject(onError ? onError(error) : error)),
);
};
/**
* Get game information.
* @see https://api.thetrainingarcade.com/documentation/#api-Game-GetGame
*
* @param {Object} params
* @param {string} params.domain
* @param {number} [params.game_id]
* @param {string} [params.slug]
* @param {string} [params.token]
* @param {$httpDataCallback} [onSuccess]
* @returns {Promise} Promise object, either:
* - Resolved with the result of the `onSuccess` callback or the HTTP response data.
* - Rejected with the full HTTP response object or an error.
*/
this.getGame = (params, onSuccess) => this.call({
method: 'GET',
url: 'game',
host: cachingServerAddress,
params,
onSuccess,
});
/**
* Get Game Data from a file
*
* @param {string} url
* @param {$httpDataCallback} [onSuccess]
* @returns {Promise} Promise object, either:
* - Resolved with the result of the `onSuccess` callback or the HTTP response data.
* - Rejected with the full HTTP response object or an error.
*/
this.getGameFile = (url, onSuccess) => $http({
method: 'GET',
url,
}).then(
(response) => (onSuccess ? onSuccess(response.data) : response.data),
(error) => Promise.reject(error),
);
/**
* Get player's high score for a game.
* @see https://api.thetrainingarcade.com/documentation/#api-Player-GetScore
*
* @param {Object} params
* @param {number} [params.game_id={@link module:APIService~game_id}]
* @param {string} [params.best='highest']
* @param {$httpDataCallback} [onSuccess]
* @returns {Promise} Promise object, either:
* - Resolved with the result of the `onSuccess` callback or the HTTP response data.
* - Rejected with the full HTTP response object or an error.
*/
this.getScore = (params, onSuccess) => this.call({
method: 'GET',
url: 'player/score',
params: {
game_id,
best: 'highest',
...params,
},
onSuccess,
});
/**
* Add player's score for a game. Only the player's high score for each game
* is reported in Score and Leaderboard calls.
* @see https://api.thetrainingarcade.com/documentation/#api-Player-PostScore
*
* @param {Object} data Request message data (may include entries besides those listed below).
* @param {number} [data.game_id={@link module:APIService~game_id}]
* @param {number} [data.points=0]
* @param {number} [data.time=0]
* @param {number} [data.level_id]
* @param {$httpDataCallback} [onSuccess]
* @param {boolean} [noSV=false]
* @returns {Promise} Promise object, either:
* - Resolved with the result of the `onSuccess` callback or the HTTP response data.
* - Rejected with the full HTTP response object or an error.
*/
this.reportScore = (data, onSuccess, noSV = false) => {
messagingService.score(data.points || 0, data.time || 0);
const {
hub_id,
course_id,
challenge_id,
session_group_id,
} = URLParamsService.getUserParams();
const sendData = {
...data,
hub_id,
course_id,
challenge_id,
session_group_id:
instructorService.getSessionGroupId() || session_group_id,
};
const string = [
data.game_id || game_id || 0,
data.points || 0,
data.time || 0,
data.hub_id || hub_id || 0,
data.course_id || course_id || 0,
sendData.session_group_id || session_group_id || 0,
data.challenge_id || challenge_id || 0,
security.r,
].join('');
$log.debug('sv string vars:', {
game_id: data.game_id || 0,
points: data.points || 0,
time: data.time || 0,
hub_id: data.hub_id || hub_id || 0,
course_id: data.course_id || course_id || 0,
session_group_id: sendData.session_group_id || session_group_id || 0,
challenge_id: data.challenge_id || challenge_id || 0,
r: security.r,
});
if (!noSV) {
sendData.sv = utilityService.sha256(string);
}
return this.call({
method: 'POST',
url: 'player/score',
data: {
game_id,
...sendData,
},
onSuccess,
});
};
/**
* Add an answer attempt.
* @see https://api.thetrainingarcade.com/documentation/#api-Player-PostAnswer
*
* @param {Object} data Request message data (may include entries besides those listed below).
* @param {number} [data.game_id={@link module:APIService~game_id}]
* @param {number} [data.level_id]
* @param {number} data.question_id
* @param {Boolean} data.correct
* @param {string} [data.answer_id]
* @param {string} [data.answer_text]
* @param {number} data.time
* @param {$httpDataCallback} [onSuccess]
* * @param {$httpDataCallback} [onError]
* @returns {Promise} Promise object, either:
* - Resolved with the result of the `onSuccess` callback or the HTTP response data.
* - Rejected with the full HTTP response object or an error.
*/
this.reportAnswer = (data, onSuccess, onError) => {
const { session_group_id } = URLParamsService.getUserParams();
return this.call({
method: 'POST',
url: 'player/answer',
data: {
game_id,
...data,
session_group_id:
instructorService.getSessionGroupId() || session_group_id,
},
onSuccess: (resData, successVal, status) => {
messagingService.answer(resData.correct, data.time || 0);
if (onSuccess) {
onSuccess(resData, successVal, status);
}
},
onError,
});
};
/**
* Get leaderboard for a game for a specific question
* @see https://api.thetrainingarcade.com/documentation/#api-Question-GetLeaderboard
*
* @param {number} question_id
* @param {Object} params Query parameters (may include entries besides those listed below).
* @param {string} [params.game_id={@link module:APIService~game_id}]
* @param {$httpDataCallback} onSuccess
* @returns {Promise} Promise object, either:
* - Resolved with the result of the `onSuccess` callback or the HTTP response data.
* - Rejected with the full HTTP response object or an error.
*/
this.getQuestionLeaderboard = (question_id, params, onSuccess) => {
const { session_group_id, hub_id } = URLParamsService.getUserParams();
return this.call({
method: 'GET',
url: `question/leaderboard/${question_id}`,
params: {
game_id,
session_group_id,
hub_id,
...params,
},
onSuccess,
});
};
/**
* Get leaderboard for a game
* @see https://api.thetrainingarcade.com/documentation/#api-Stats-GetLeaderboard
*
* @param {Object} params Query parameters (may include entries besides those listed below).
* @param {string} [params.game_id={@link module:APIService~game_id}]
* @param {string} [params.best='highest']
* @param {number} [params.limit]
* @param {string} [params.start_date]
* @param {string} [params.end_date]
* @param {$httpDataCallback} onSuccess
* @returns {Promise} Promise object, either:
* - Resolved with the result of the `onSuccess` callback or the HTTP response data.
* - Rejected with the full HTTP response object or an error.
*/
this.getLeaderboard = (params, onSuccess) => {
const { session_group_id, hub_id } = URLParamsService.getUserParams();
return this.call({
method: 'GET',
url: 'stats/leaderboard',
params: {
game_id,
hub_id,
best: 'highest',
...params,
session_group_id:
instructorService.getSessionGroupId() || session_group_id,
},
onSuccess,
});
};
/**
* Get instructor leaderboard
*
* @param {Object} params Query parameters (may include entries besides those listed below).
* @param {string} [params.slug]
* @param {$httpDataCallback} onSuccess
* @returns {Promise} Promise object, either:
* - Resolved with the HTTP response object
* - Rejected with the full HTTP response object or an error.
*/
this.getInstructorLeaderboard = (params, onSuccess) => this.call({
method: 'GET',
url: 'instructor/leaderboard',
params,
onSuccess,
});
/**
* Get instructor state
* @see https://api.thetrainingarcade.com/documentation/#api-Instructor-GetInstructor
*
* @param {string} slug
* @returns {Promise} Promise object, either:
* - Resolved with the result of {@link module:instructorService.getStates}
* - Rejected with the full HTTP response object or an error.
*/
this.getInstructorState = (slug) => this.call({
method: 'GET',
url: 'instructor',
params: {
domain: domainService.domain,
slug,
},
});
/**
* Get instructor players
*
* @param {object} data
* @param {number} data.session_group_id
* @returns {Promise} Promise object, either:
* - Resolved with the number of players.
* - Rejected with the full HTTP response object or an error.
*/
this.getInstructorPlayers = (data) => this.call({
method: 'GET',
url: `instructor/players/${data.session_group_id}`,
params: {
total: true,
},
});
/**
* Get results of question
*
* @param {object} data
* @param {number} data.session_group_id
* @param {number} data.question_id
* @returns {Promise} Promise object, either:
* - Resolved with the HTTP response object.
* - Rejected with the full HTTP response object or an error.
*/
this.getQuestionResults = (data) => this.call({
method: 'GET',
// eslint-disable-next-line max-len
url: `question/results/${data.question_id}${data.session_group_id ? `?session_group_id=${data.session_group_id}` : ''}`,
});
/**
* Get audio data for a question ID.
* @see https://api.thetrainingarcade.com/documentation/#api-Question-GetAudio
*
* @param {number} question_id
* @returns {Promise} Promise object, either:
* - Resolved with the HTTP response data.
* - Rejected with the full HTTP response object or an error.
*/
this.getQuestionAudio = (question_id) => this.call({
method: 'GET',
url: `question/audio/${question_id}`,
});
},
]);