/**
* AngularJS Service for SCORM wrapper library.
*
* @module scormService
* @see https://docs.angularjs.org/api/ng/type/angular.Module#service
*/
angular.module('tgaApp').service('scormService', ['$interval', '$window', '$log',
function scormService($interval, $window, $log) {
/**
* Service for SCORM Wrapper Library.
* https://github.com/pipwerks/scorm-api-wrapper
*
* @private
* @todo Check whether this is actually used by anything.
*/
this.scorm = undefined;
/**
* Internal storage for SCORM entry status.
*
* @private
* @type {?string}
*/
this.entryStatus = undefined;
/**
* Internal storage for SCORM service data.
*
* @private
* @type {Object}
*/
this.saved = {
score: undefined,
lesson_status: undefined,
lmsConnected: undefined,
user: {
name: null,
id: null,
},
version: undefined,
};
/**
* Heartbeat interval (in milliseconds).
*
* @type {number}
* @default
*/
this.heartbeatDelay = 1500000;
/**
* Checks whether game was loaded in an iframe
*
* @returns {boolean}
*/
this.inIframe = () => {
try {
return $window.self !== $window.top;
} catch (e) {
return true;
}
};
/**
* Initialize SCORM service and listeners.
*
* @listens window.message
*/
this.init = () => {
$log.debug('scorm-service init');
$window.addEventListener('message', (event) => {
// $log.debug(event.origin, event.currentTarget.location.origin);
const { data } = event;
// Make sure this is a SCORM message from the parent $window
if (!this.inIframe() || !data.type || data.type !== 'scorm'
|| event.origin === event.currentTarget.location.origin) {
return;
}
$log.debug('message received from SCORM: ', event);
this.saved.version = data.version;
switch (data.method) {
case 'get':
this.getHandler(data);
break;
case 'set':
$log.debug(`set: ${data.model}: success: ${data.success}`);
this.setHandler(data);
break;
case 'version':
this.versionCallback();
break;
default:
break;
}
}, false);
this.getVersion();
};
/**
* Handler for SCORM `version` message.
*
* @private
*/
this.versionCallback = () => {
this.getScore();
this.getLessonStatus();
this.getEntryStatus();
this.getUser();
this.startHeartbeat();
};
/**
* Handler for SCORM `get` messages.
*
* @private
* @param {Object} data - Message object.
* @param {string} data.model - SCORM data model element.
* @param {*} data.value - Value for SCORM element.
*/
this.getHandler = (data) => {
if (!this.saved.lmsConnected) {
this.saved.lmsConnected = true;
}
switch (data.model) {
case 'cmi.completion_status':
case 'cmi.core.lesson_status':
this.lessonStatusCallback(data);
break;
case 'cmi.score.raw':
case 'cmi.core.score.raw':
this.getScoreCallback(data);
break;
case 'cmi.learner_id':
case 'cmi.core.student_id':
this.saved.user.id = data.value;
break;
case 'cmi.learner_name':
case 'cmi.core.student_name':
this.saved.user.name = data.value;
break;
case 'cmi.entry':
case 'cmi.core.entry':
this.entryStatusCallback(data);
break;
default:
break;
}
};
/**
* Check whether SCORM service is connected.
*
* @returns {boolean}
*/
this.isConnected = () => this.saved.lmsConnected;
/**
* Get stored value from {@link module:scormService.entryStatus}
* @return {?string}
*/
this.provideEntryStatus = () => this.entryStatus;
/**
* Get stored SCORM `score`.
*
* @returns {?number}
*/
this.provideSavedScore = () => this.saved.score;
/**
* Get stored SCORM `lesson_status`.
*
* @return {?string}
*/
this.provideLessonStatus = () => this.saved.lesson_status;
/**
* Get stored SCORM `user`.
*
* @returns {Object}
*/
this.provideUser = () => this.saved.user;
/**
* Handler for SCORM `set` messages.
*
* @private
* @param {Object} data - Message object.
* @param {string} data.model - SCORM data model element.
* @param {*} data.value - Value for element.
*/
this.setHandler = (data) => {
if (data.success) {
switch (data.model) {
case 'cmi.completion_status':
case 'cmi.core.lesson_status':
this.saved.lesson_status = data.value;
break;
case 'cmi.score.raw':
case 'cmi.core.score.raw':
this.saved.score = data.value;
this.save();
break;
default:
break;
}
}
};
/**
* Initialize scores in SCORM service.
*/
this.initScores = () => {
if (this.saved.version === '2004') {
this.set('cmi.score.min', 0);
this.set('cmi.score.max', 100);
} else {
this.set('cmi.core.score.min', 0);
this.set('cmi.core.score.max', 100);
}
};
/**
* Sent `set` message for score to SCORM service.
*
* @param {number} number - The score value.
*/
this.setScore = (number) => {
if (this.saved.version === '2004') {
this.set('cmi.score.raw', number);
this.set('cmi.score.scaled', number / 100);
} else {
this.set('cmi.core.score.raw', number);
}
};
/**
* Send `get` message for score from SCORM service.
*
* @see {@link module:scormService.provideSavedScore}
*/
this.getScore = () => {
if (this.saved.version === '2004') {
this.get('cmi.score.raw');
} else {
this.get('cmi.core.score.raw');
}
};
/**
* Callback for SCORM `get` message for score.
* Used by {@link module:scormService.getHandler}
*
* @private
* @param {Object} data - Message object.
* @param {number} data.value - Score value.
*/
this.getScoreCallback = (data) => {
this.saved.score = Number(data.value);
};
/**
* Send `get` message for lesson status from SCORM service.
*
* @see {@link module:scormService.provideLessonStatus}
*/
this.getLessonStatus = () => {
if (this.saved.version === '2004') {
this.get('cmi.completion_status');
} else {
this.get('cmi.core.lesson_status');
}
};
/**
* Callback for SCORM `get` messages for lesson status.
* Used by {@link module:scormService.getHandler}
*
* @private
* @param {Object} data - Message object.
* @param {string} data.value - Lesson status value.
*/
this.lessonStatusCallback = (data) => {
const status = data.value;
this.lesson_status = status;
$log.debug(status);
};
/**
* Send `get` message for entry status from SCORM service.
*
* @see {@link module:scormService.provideEntryStatus}
*/
this.getEntryStatus = () => {
if (this.saved.version === '2004') {
this.get('cmi.entry');
} else {
this.get('cmi.core.entry');
}
};
/**
* Callback for SCORM `get` messages for entry status.
* Used by {@link module:scormService.getHandler}
*
* @private
* @param {Object} data - Message object.
* @param {string} data.value - Entry status value.
*/
this.entryStatusCallback = (data) => {
const status = data.value;
$log.debug(status);
if (status === 'ab_initio' || status === 'ab-initio') {
$log.debug('first time, initialize scores');
this.entryStatus = 'new';
this.initScores();
this.setCompletion(false);
}
};
/**
* Send `get` message for user from SCORM service.
*
* @see {@link module:scormService.provideUser}
*/
this.getUser = () => {
if (this.saved.version === '2004') {
this.get('cmi.learner_name');
this.get('cmi.learner_id');
} else {
this.get('cmi.core.student_name');
this.get('cmi.core.student_id');
}
};
/**
* Check whether lesson status is completed.
*
* @returns {boolean}
*/
this.checkCompletion = () => this.saved.lesson_status === 'completed';
/**
* Send `set` message for completion status to SCORM service.
* @param {boolean} complete - The completion state.
*/
this.setCompletion = function (complete) {
const c_str = complete ? 'completed' : 'incomplete';
if (this.saved.version === '2004') {
this.set('cmi.completion_status', c_str);
if (complete) {
this.set('cmi.success_status', 'passed');
}
} else {
this.set('cmi.core.lesson_status', c_str);
}
};
/**
* Start SCORM heartbeat.
*
* @see {@link module:scormService.heartbeatDelay}
*/
this.startHeartbeat = () => {
const func = () => {
const msg = {
type: 'scorm',
method: 'heartbeat',
};
$window.parent.postMessage(msg, '*');
};
this.heartbeatInterval = $interval(func, this.heartbeatDelay);
};
/**
* Stop SCORM heartbeat.
*/
this.stopHeartbeat = () => {
if (this.heartbeatInterval) {
$interval.cancel(this.heartbeatInterval);
}
};
/**
* Send `version` message to SCORM service.
*/
this.getVersion = () => {
const msg = {
type: 'scorm',
method: 'version',
};
$window.parent.postMessage(msg, '*');
};
/**
* Send `get` message for an arbitrary data model element from SCORM service.
*
* @param {string} model - SCORM data model element.
*/
this.get = (model) => {
const msg = {
type: 'scorm',
method: 'get',
model,
};
$window.parent.postMessage(msg, '*');
};
/**
* Send `set` message for an arbitrary data model element from SCORM service.
*
* @param {string} model - SCORM data model element.
* @param {*} value - Value for SCORM element.
*/
this.set = (model, value) => {
const msg = {
type: 'scorm',
method: 'set',
model,
value,
};
$window.parent.postMessage(msg, '*');
};
/**
* Calls `pipwerks.SCORM.data.save()` which calls `LMSCommit()`.
* Instructs the LMS to persist all data to this point in the session.
*/
this.save = () => {
const msg = {
type: 'scorm',
method: 'save',
};
$window.parent.postMessage(msg, '*');
};
/**
* Closes parent $window if parent $window is top most $window.
* Closing $window triggers `pipwerks.SCORM.connection.terminate()`.
*/
this.finish = () => {
const msg = {
type: 'scorm',
method: 'finish',
};
$window.parent.postMessage(msg, '*');
};
},
]);