scorm-again
Version:
A modern SCORM JavaScript run-time library for SCORM 1.2 and SCORM 2004
1,611 lines (1,593 loc) • 188 kB
JavaScript
this.AICC = (function () {
'use strict';
const global_constants = {
SCORM_TRUE: "true",
SCORM_FALSE: "false",
STATE_NOT_INITIALIZED: 0,
STATE_INITIALIZED: 1,
STATE_TERMINATED: 2
};
const scorm12_constants = {
// Children lists
cmi_children: "core,suspend_data,launch_data,comments,objectives,student_data,student_preference,interactions",
core_children: "student_id,student_name,lesson_location,credit,lesson_status,entry,score,total_time,lesson_mode,exit,session_time",
score_children: "raw,min,max",
comments_children: "content,location,time",
objectives_children: "id,score,status",
correct_responses_children: "pattern",
student_data_children: "mastery_score,max_time_allowed,time_limit_action",
student_preference_children: "audio,language,speed,text",
interactions_children: "id,objectives,time,type,correct_responses,weighting,student_response,result,latency",
error_descriptions: {
"101": {
basicMessage: "General Exception",
detailMessage: "No specific error code exists to describe the error."
},
"201": {
basicMessage: "Invalid argument error",
detailMessage: "Indicates that an argument represents an invalid data model element or is otherwise incorrect."
},
"202": {
basicMessage: "Element cannot have children",
detailMessage: 'Indicates that LMSGetValue was called with a data model element name that ends in "_children" for a data model element that does not support the "_children" suffix.'
},
"203": {
basicMessage: "Element not an array - cannot have count",
detailMessage: 'Indicates that LMSGetValue was called with a data model element name that ends in "_count" for a data model element that does not support the "_count" suffix.'
},
"301": {
basicMessage: "Not initialized",
detailMessage: "Indicates that an API call was made before the call to lmsInitialize."
},
"401": {
basicMessage: "Not implemented error",
detailMessage: "The data model element indicated in a call to LMSGetValue or LMSSetValue is valid, but was not implemented by this LMS. SCORM 1.2 defines a set of data model elements as being optional for an LMS to implement."
},
"402": {
basicMessage: "Invalid set value, element is a keyword",
detailMessage: 'Indicates that LMSSetValue was called on a data model element that represents a keyword (elements that end in "_children" and "_count").'
},
"403": {
basicMessage: "Element is read only",
detailMessage: "LMSSetValue was called with a data model element that can only be read."
},
"404": {
basicMessage: "Element is write only",
detailMessage: "LMSGetValue was called on a data model element that can only be written to."
},
"405": {
basicMessage: "Incorrect Data Type",
detailMessage: "LMSSetValue was called with a value that is not consistent with the data format of the supplied data model element."
},
"407": {
basicMessage: "Element Value Out Of Range",
detailMessage: "The numeric value supplied to a LMSSetValue call is outside of the numeric range allowed for the supplied data model element."
},
"408": {
basicMessage: "Data Model Dependency Not Established",
detailMessage: "Some data model elements cannot be set until another data model element was set. This error condition indicates that the prerequisite element was not set before the dependent element."
}
}
};
const aicc_constants = {
...scorm12_constants,
...{
cmi_children: "core,suspend_data,launch_data,comments,objectives,student_data,student_preference,interactions,evaluation",
student_preference_children: "audio,language,lesson_type,speed,text,text_color,text_location,text_size,video,windows",
student_data_children: "attempt_number,tries,mastery_score,max_time_allowed,time_limit_action",
student_demographics_children: "city,class,company,country,experience,familiar_name,instructor_name,title,native_language,state,street_address,telephone,years_experience",
tries_children: "time,status,score",
attempt_records_children: "score,lesson_status",
paths_children: "location_id,date,time,status,why_left,time_in_element"
}
};
const global_errors = {
GENERAL: 101,
INITIALIZATION_FAILED: 101,
INITIALIZED: 101,
TERMINATED: 101,
TERMINATION_FAILURE: 101,
TERMINATION_BEFORE_INIT: 101,
MULTIPLE_TERMINATION: 101,
RETRIEVE_BEFORE_INIT: 101,
RETRIEVE_AFTER_TERM: 101,
STORE_BEFORE_INIT: 101,
STORE_AFTER_TERM: 101,
COMMIT_BEFORE_INIT: 101,
COMMIT_AFTER_TERM: 101,
ARGUMENT_ERROR: 101,
CHILDREN_ERROR: 101,
COUNT_ERROR: 101,
GENERAL_GET_FAILURE: 101,
GENERAL_SET_FAILURE: 101,
GENERAL_COMMIT_FAILURE: 101,
UNDEFINED_DATA_MODEL: 101,
UNIMPLEMENTED_ELEMENT: 101,
VALUE_NOT_INITIALIZED: 101,
INVALID_SET_VALUE: 101,
READ_ONLY_ELEMENT: 101,
WRITE_ONLY_ELEMENT: 101,
TYPE_MISMATCH: 101,
VALUE_OUT_OF_RANGE: 101,
DEPENDENCY_NOT_ESTABLISHED: 101
};
const scorm12_errors$1 = {
...global_errors,
RETRIEVE_BEFORE_INIT: 301,
STORE_BEFORE_INIT: 301,
COMMIT_BEFORE_INIT: 301,
ARGUMENT_ERROR: 201,
CHILDREN_ERROR: 202,
COUNT_ERROR: 203,
UNDEFINED_DATA_MODEL: 401,
UNIMPLEMENTED_ELEMENT: 401,
VALUE_NOT_INITIALIZED: 301,
INVALID_SET_VALUE: 402,
READ_ONLY_ELEMENT: 403,
WRITE_ONLY_ELEMENT: 404,
TYPE_MISMATCH: 405,
VALUE_OUT_OF_RANGE: 407,
DEPENDENCY_NOT_ESTABLISHED: 408
};
const scorm12_regex = {
CMIString256: "^[\\s\\S]{0,255}$",
CMIString4096: "^[\\s\\S]{0,4096}$",
CMITime: "^(?:[01]\\d|2[0123]):(?:[012345]\\d):(?:[012345]\\d)$",
CMITimespan: "^([0-9]{2,}):([0-9]{2}):([0-9]{2})(.[0-9]{1,2})?$",
CMIInteger: "^\\d+$",
CMISInteger: "^-?([0-9]+)$",
CMIDecimal: "^-?([0-9]{0,3})(\\.[0-9]*)?$",
CMIIdentifier: "^[\\u0021-\\u007E\\s]{0,255}$",
// Allow storing larger responses for interactions
// Some content packages may exceed the 255 character limit
// defined in the SCORM 1.2 specification. The previous
// expression truncated these values which resulted in
// a "101: General Exception" being thrown when long
// answers were supplied. To support these packages we
// relax the limitation and accept any length string.
CMIFeedback: "^.*$",
// This must be redefined
CMIIndex: "[._](\\d+).",
// Vocabulary Data Type Definition
CMIStatus: "^(passed|completed|failed|incomplete|browsed)$",
CMIStatus2: "^(passed|completed|failed|incomplete|browsed|not attempted)$",
CMIExit: "^(time-out|suspend|logout|)$",
CMIType: "^(true-false|choice|fill-in|matching|performance|sequencing|likert|numeric)$",
CMIResult: "^(correct|wrong|unanticipated|neutral|([0-9]{0,3})?(\\.[0-9]*)?)$",
NAVEvent: "^(previous|continue|start|resumeAll|choice|jump|exit|exitAll|abandon|abandonAll|suspendAll|retry|retryAll|_none_)$",
// Data ranges
score_range: "0#100",
audio_range: "-1#100",
speed_range: "-100#100",
weighting_range: "-100#100",
text_range: "-1#1"
};
const aicc_regex = {
...scorm12_regex,
...{
// AICC identifiers may contain letters, numbers, underscores,
// periods, and hyphens up to 255 characters in length.
// The previous expression only allowed "\w" characters which
// excluded periods and hyphens.
CMIIdentifier: "^[A-Za-z0-9._-]{1,255}$"
}
};
class BaseScormValidationError extends Error {
constructor(CMIElement, errorCode) {
super(`${CMIElement} : ${errorCode.toString()}`);
this._errorCode = errorCode;
Object.setPrototypeOf(this, BaseScormValidationError.prototype);
}
/**
* Getter for _errorCode
* @return {number}
*/
get errorCode() {
return this._errorCode;
}
}
class ValidationError extends BaseScormValidationError {
/**
* Constructor to take in an error message and code
* @param {string} CMIElement
* @param {number} errorCode
* @param {string} errorMessage
* @param {string} detailedMessage
*/
constructor(CMIElement, errorCode, errorMessage, detailedMessage) {
super(CMIElement, errorCode);
this._detailedMessage = "";
this.message = `${CMIElement} : ${errorMessage}`;
this._errorMessage = errorMessage;
if (detailedMessage) {
this._detailedMessage = detailedMessage;
}
Object.setPrototypeOf(this, ValidationError.prototype);
}
/**
* Getter for _errorMessage
* @return {string}
*/
get errorMessage() {
return this._errorMessage;
}
/**
* Getter for _detailedMessage
* @return {string}
*/
get detailedMessage() {
return this._detailedMessage;
}
}
const scorm12_errors = scorm12_constants.error_descriptions;
class Scorm12ValidationError extends ValidationError {
/**
* Constructor to take in an error code
* @param {string} CMIElement
* @param {number} errorCode
*/
constructor(CMIElement, errorCode) {
if ({}.hasOwnProperty.call(scorm12_errors, String(errorCode))) {
super(CMIElement, errorCode, scorm12_errors[String(errorCode)]?.basicMessage || "Unknown error", scorm12_errors[String(errorCode)]?.detailMessage);
} else {
super(CMIElement, 101, scorm12_errors["101"]?.basicMessage ?? "General error", scorm12_errors["101"]?.detailMessage);
}
Object.setPrototypeOf(this, Scorm12ValidationError.prototype);
}
}
class BaseCMI {
/**
* Constructor for BaseCMI
* @param {string} cmi_element
*/
constructor(cmi_element) {
this.jsonString = false;
this._initialized = false;
this._cmi_element = cmi_element;
}
/**
* Getter for _initialized
* @return {boolean}
*/
get initialized() {
return this._initialized;
}
/**
* Called when the API has been initialized after the CMI has been created
*/
initialize() {
this._initialized = true;
}
}
class BaseRootCMI extends BaseCMI {
/**
* Start time of the course
* @type {number | undefined}
* @protected
*/
get start_time() {
return this._start_time;
}
/**
* Setter for start_time. Can only be called once.
*/
setStartTime() {
if (this._start_time === void 0) {
this._start_time = (/* @__PURE__ */new Date()).getTime();
} else {
throw new Error("Start time has already been set.");
}
}
}
const SECONDS_PER_MINUTE = 60;
const SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
const getSecondsAsHHMMSS = memoize(totalSeconds => {
if (!totalSeconds || totalSeconds <= 0) {
return "00:00:00";
}
const hours = Math.floor(totalSeconds / SECONDS_PER_HOUR);
const dateObj = new Date(totalSeconds * 1e3);
const minutes = dateObj.getUTCMinutes();
const seconds = dateObj.getSeconds();
const ms = totalSeconds % 1;
let msStr = "";
if (countDecimals(ms) > 0) {
if (countDecimals(ms) > 2) {
msStr = ms.toFixed(2);
} else {
msStr = String(ms);
}
msStr = "." + msStr.split(".")[1];
}
return (hours + ":" + minutes + ":" + seconds).replace(/\b\d\b/g, "0$&") + msStr;
});
const getTimeAsSeconds = memoize((timeString, timeRegex) => {
if (typeof timeString === "number" || typeof timeString === "boolean") {
timeString = String(timeString);
}
if (typeof timeRegex === "string") {
timeRegex = new RegExp(timeRegex);
}
if (!timeString) {
return 0;
}
if (!timeString.match(timeRegex)) {
if (/^\d+(?:\.\d+)?$/.test(timeString)) {
return Number(timeString);
}
return 0;
}
const parts = timeString.split(":");
const hours = Number(parts[0]);
const minutes = Number(parts[1]);
const seconds = Number(parts[2]);
return hours * 3600 + minutes * 60 + seconds;
},
// Custom key function to handle RegExp objects which can't be stringified
(timeString, timeRegex) => {
const timeStr = typeof timeString === "string" ? timeString : String(timeString ?? "");
const regexStr = typeof timeRegex === "string" ? timeRegex : timeRegex?.toString() ?? "";
return `${timeStr}:${regexStr}`;
});
function addHHMMSSTimeStrings(first, second, timeRegex) {
if (typeof timeRegex === "string") {
timeRegex = new RegExp(timeRegex);
}
return getSecondsAsHHMMSS(getTimeAsSeconds(first, timeRegex) + getTimeAsSeconds(second, timeRegex));
}
function flatten(data) {
const result = {};
function recurse(cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
cur.forEach((item, i) => {
recurse(item, `${prop}[${i}]`);
});
if (cur.length === 0) result[prop] = [];
} else {
const keys = Object.keys(cur).filter(p => Object.prototype.hasOwnProperty.call(cur, p));
const isEmpty = keys.length === 0;
keys.forEach(p => {
recurse(cur[p], prop ? `${prop}.${p}` : p);
});
if (isEmpty && prop) result[prop] = {};
}
}
recurse(data, "");
return result;
}
function unflatten(data) {
if (Object(data) !== data || Array.isArray(data)) return data;
const result = {};
const pattern = /\.?([^.[\]]+)|\[(\d+)]/g;
Object.keys(data).filter(p => Object.prototype.hasOwnProperty.call(data, p)).forEach(p => {
let cur = result;
let prop = "";
const regex = new RegExp(pattern);
Array.from({
length: p.match(new RegExp(pattern, "g"))?.length ?? 0
}, () => regex.exec(p)).forEach(m => {
if (m) {
cur = cur[prop] ?? (cur[prop] = m[2] ? [] : {});
prop = m[2] || m[1] || "";
}
});
cur[prop] = data[p];
});
return result[""] ?? result;
}
function countDecimals(num) {
if (Math.floor(num) === num || String(num)?.indexOf?.(".") < 0) return 0;
const parts = num.toString().split(".")?.[1];
return parts?.length ?? 0;
}
function formatMessage(functionName, message, CMIElement) {
const baseLength = 20;
const paddedFunction = functionName.padEnd(baseLength);
let messageString = `${paddedFunction}: `;
if (CMIElement) {
const CMIElementBaseLength = 70;
messageString += CMIElement;
messageString = messageString.padEnd(CMIElementBaseLength);
}
messageString += message ?? "";
return messageString;
}
function stringMatches(str, tester) {
if (typeof str !== "string") {
return false;
}
return new RegExp(tester).test(str);
}
function memoize(fn, keyFn) {
const cache = /* @__PURE__ */new Map();
return function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
const key = keyFn ? keyFn(...args) : JSON.stringify(args);
return cache.has(key) ? cache.get(key) : (() => {
const result = fn(...args);
cache.set(key, result);
return result;
})();
};
}
const checkValidFormat = memoize((CMIElement, value, regexPattern, errorCode, errorClass, allowEmptyString) => {
if (typeof value !== "string") {
return false;
}
const formatRegex = new RegExp(regexPattern);
const matches = value.match(formatRegex);
if (allowEmptyString && value === "") {
return true;
}
if (value === void 0 || !matches || matches[0] === "") {
throw new errorClass(CMIElement, errorCode);
}
return true;
},
// Custom key function that excludes the error class from the cache key
// since it can't be stringified and doesn't affect the validation result
(CMIElement, value, regexPattern, errorCode, _errorClass, allowEmptyString) => {
const valueKey = typeof value === "string" ? value : `[${typeof value}]`;
return `${CMIElement}:${valueKey}:${regexPattern}:${errorCode}:${allowEmptyString || false}`;
});
const checkValidRange = memoize((CMIElement, value, rangePattern, errorCode, errorClass) => {
const ranges = rangePattern.split("#");
value = value * 1;
if (ranges[0] && value >= ranges[0]) {
if (ranges[1] && (ranges[1] === "*" || value <= ranges[1])) {
return true;
} else {
throw new errorClass(CMIElement, errorCode);
}
} else {
throw new errorClass(CMIElement, errorCode);
}
},
// Custom key function that excludes the error class from the cache key
// since it can't be stringified and doesn't affect the validation result
(CMIElement, value, rangePattern, errorCode, _errorClass) => `${CMIElement}:${value}:${rangePattern}:${errorCode}`);
function check12ValidFormat(CMIElement, value, regexPattern, allowEmptyString) {
return checkValidFormat(CMIElement, value, regexPattern, scorm12_errors$1.TYPE_MISMATCH, Scorm12ValidationError, allowEmptyString);
}
function check12ValidRange(CMIElement, value, rangePattern, allowEmptyString) {
if (value === "") {
throw new Scorm12ValidationError(CMIElement, scorm12_errors$1.VALUE_OUT_OF_RANGE);
}
return checkValidRange(CMIElement, value, rangePattern, scorm12_errors$1.VALUE_OUT_OF_RANGE, Scorm12ValidationError);
}
class ValidationService {
/**
* Validates a score property (raw, min, max)
*
* @param {string} CMIElement
* @param {string} value - The value to validate
* @param {string} decimalRegex - The regex pattern for decimal validation
* @param {string | false} scoreRange - The range pattern for score validation, or false if no range validation is needed
* @param {number} invalidTypeCode - The error code for invalid type
* @param {number} invalidRangeCode - The error code for invalid range
* @param {typeof BaseScormValidationError} errorClass - The error class to use for validation errors
* @return {boolean} - True if validation passes, throws an error otherwise
*/
validateScore(CMIElement, value, decimalRegex, scoreRange, invalidTypeCode, invalidRangeCode, errorClass) {
return checkValidFormat(CMIElement, value, decimalRegex, invalidTypeCode, errorClass) && (!scoreRange || checkValidRange(CMIElement, value, scoreRange, invalidRangeCode, errorClass));
}
/**
* Validates a SCORM 1.2 audio property
*
* @param {string} CMIElement
* @param {string} value - The value to validate
* @return {boolean} - True if validation passes, throws an error otherwise
*/
validateScorm12Audio(CMIElement, value) {
return check12ValidFormat(CMIElement, value, scorm12_regex.CMISInteger) && check12ValidRange(CMIElement, value, scorm12_regex.audio_range);
}
/**
* Validates a SCORM 1.2 language property
*
* @param {string} CMIElement
* @param {string} value - The value to validate
* @return {boolean} - True if validation passes, throws an error otherwise
*/
validateScorm12Language(CMIElement, value) {
return check12ValidFormat(CMIElement, value, scorm12_regex.CMIString256);
}
/**
* Validates a SCORM 1.2 speed property
*
* @param {string} CMIElement
* @param {string} value - The value to validate
* @return {boolean} - True if validation passes, throws an error otherwise
*/
validateScorm12Speed(CMIElement, value) {
return check12ValidFormat(CMIElement, value, scorm12_regex.CMISInteger) && check12ValidRange(CMIElement, value, scorm12_regex.speed_range);
}
/**
* Validates a SCORM 1.2 text property
*
* @param {string} CMIElement
* @param {string} value - The value to validate
* @return {boolean} - True if validation passes, throws an error otherwise
*/
validateScorm12Text(CMIElement, value) {
return check12ValidFormat(CMIElement, value, scorm12_regex.CMISInteger) && check12ValidRange(CMIElement, value, scorm12_regex.text_range);
}
/**
* Validates if a property is read-only
*
* @param {string} CMIElement
* @param {boolean} initialized - Whether the object is initialized
* @throws {BaseScormValidationError} - Throws an error if the object is initialized
*/
validateReadOnly(CMIElement, initialized) {
if (initialized) {
throw new Scorm12ValidationError(CMIElement, scorm12_errors$1.READ_ONLY_ELEMENT);
}
}
}
const validationService = new ValidationService();
class CMIScore extends BaseCMI {
/**
* Constructor for *.score
* @param {
* score_children: string,
* score_range: string,
* max: string,
* invalidErrorCode: number,
* invalidTypeCode: number,
* invalidRangeCode: number,
* decimalRegex: string,
* errorClass: typeof BaseScormValidationError
* } params
*/
constructor(params) {
super(params.CMIElement);
this._raw = "";
this._min = "";
this.__children = params.score_children || scorm12_constants.score_children;
this.__score_range = !params.score_range ? false : scorm12_regex.score_range;
this._max = params.max || params.max === "" ? params.max : "100";
this.__invalid_error_code = params.invalidErrorCode || scorm12_errors$1.INVALID_SET_VALUE;
this.__invalid_type_code = params.invalidTypeCode || scorm12_errors$1.TYPE_MISMATCH;
this.__invalid_range_code = params.invalidRangeCode || scorm12_errors$1.VALUE_OUT_OF_RANGE;
this.__decimal_regex = params.decimalRegex || scorm12_regex.CMIDecimal;
this.__error_class = params.errorClass;
}
/**
* Called when the API has been reset
*/
reset() {
this._initialized = false;
}
/**
* Getter for _children
* @return {string}
*/
get _children() {
return this.__children;
}
/**
* Setter for _children. Just throws an error.
* @param {string} _children
*/
set _children(_children) {
throw new this.__error_class(this._cmi_element + "._children", this.__invalid_error_code);
}
/**
* Getter for _raw
* @return {string}
*/
get raw() {
return this._raw;
}
/**
* Setter for _raw
* @param {string} raw
*/
set raw(raw) {
if (validationService.validateScore(this._cmi_element + ".raw", raw, this.__decimal_regex, this.__score_range, this.__invalid_type_code, this.__invalid_range_code, this.__error_class)) {
this._raw = raw;
}
}
/**
* Getter for _min
* @return {string}
*/
get min() {
return this._min;
}
/**
* Setter for _min
* @param {string} min
*/
set min(min) {
if (validationService.validateScore(this._cmi_element + ".min", min, this.__decimal_regex, this.__score_range, this.__invalid_type_code, this.__invalid_range_code, this.__error_class)) {
this._min = min;
}
}
/**
* Getter for _max
* @return {string}
*/
get max() {
return this._max;
}
/**
* Setter for _max
* @param {string} max
*/
set max(max) {
if (validationService.validateScore(this._cmi_element + ".max", max, this.__decimal_regex, this.__score_range, this.__invalid_type_code, this.__invalid_range_code, this.__error_class)) {
this._max = max;
}
}
/**
* Getter for _score_range
* @return {string | false}
*/
getScoreObject() {
const scoreObject = {};
if (!Number.isNaN(Number.parseFloat(this.raw))) {
scoreObject.raw = Number.parseFloat(this.raw);
}
if (!Number.isNaN(Number.parseFloat(this.min))) {
scoreObject.min = Number.parseFloat(this.min);
}
if (!Number.isNaN(Number.parseFloat(this.max))) {
scoreObject.max = Number.parseFloat(this.max);
}
return scoreObject;
}
/**
* toJSON for *.score
* @return {
* {
* min: string,
* max: string,
* raw: string
* }
* }
*/
toJSON() {
this.jsonString = true;
const result = {
raw: this.raw,
min: this.min,
max: this.max
};
this.jsonString = false;
return result;
}
}
class CMICore extends BaseCMI {
/**
* Constructor for `cmi.core`
*/
constructor() {
super("cmi.core");
this.__children = scorm12_constants.core_children;
this._student_id = "";
this._student_name = "";
this._lesson_location = "";
this._credit = "";
this._lesson_status = "not attempted";
this._entry = "";
this._total_time = "";
this._lesson_mode = "normal";
this._exit = "";
this._session_time = "00:00:00";
this._suspend_data = "";
this.score = new CMIScore({
CMIElement: "cmi.core.score",
score_children: scorm12_constants.score_children,
score_range: scorm12_regex.score_range,
invalidErrorCode: scorm12_errors$1.INVALID_SET_VALUE,
invalidTypeCode: scorm12_errors$1.TYPE_MISMATCH,
invalidRangeCode: scorm12_errors$1.VALUE_OUT_OF_RANGE,
errorClass: Scorm12ValidationError
});
}
/**
* Called when the API has been initialized after the CMI has been created
*/
initialize() {
super.initialize();
this.score?.initialize();
}
/**
* Called when the API has been reset
*/
reset() {
this._initialized = false;
this._exit = "";
this._entry = "";
this._session_time = "00:00:00";
this.score?.reset();
}
/**
* Getter for __children
* @return {string}
* @private
*/
get _children() {
return this.__children;
}
/**
* Setter for __children. Just throws an error.
* @param {string} _children
* @private
*/
set _children(_children) {
throw new Scorm12ValidationError(this._cmi_element + "._children", scorm12_errors$1.INVALID_SET_VALUE);
}
/**
* Getter for _student_id
* @return {string}
*/
get student_id() {
return this._student_id;
}
/**
* Setter for _student_id. Can only be called before initialization.
* @param {string} student_id
*/
set student_id(student_id) {
if (this.initialized) {
throw new Scorm12ValidationError(this._cmi_element + ".student_id", scorm12_errors$1.READ_ONLY_ELEMENT);
} else {
this._student_id = student_id;
}
}
/**
* Getter for _student_name
* @return {string}
*/
get student_name() {
return this._student_name;
}
/**
* Setter for _student_name. Can only be called before initialization.
* @param {string} student_name
*/
set student_name(student_name) {
if (this.initialized) {
throw new Scorm12ValidationError(this._cmi_element + ".student_name", scorm12_errors$1.READ_ONLY_ELEMENT);
} else {
this._student_name = student_name;
}
}
/**
* Getter for _lesson_location
* @return {string}
*/
get lesson_location() {
return this._lesson_location;
}
/**
* Setter for _lesson_location
* @param {string} lesson_location
*/
set lesson_location(lesson_location) {
if (check12ValidFormat(this._cmi_element + ".lesson_location", lesson_location, scorm12_regex.CMIString256, true)) {
this._lesson_location = lesson_location;
}
}
/**
* Getter for _credit
* @return {string}
*/
get credit() {
return this._credit;
}
/**
* Setter for _credit. Can only be called before initialization.
* @param {string} credit
*/
set credit(credit) {
if (this.initialized) {
throw new Scorm12ValidationError(this._cmi_element + ".credit", scorm12_errors$1.READ_ONLY_ELEMENT);
} else {
this._credit = credit;
}
}
/**
* Getter for _lesson_status
* @return {string}
*/
get lesson_status() {
return this._lesson_status;
}
/**
* Setter for _lesson_status
* @param {string} lesson_status
*/
set lesson_status(lesson_status) {
if (this.initialized) {
if (check12ValidFormat(this._cmi_element + ".lesson_status", lesson_status, scorm12_regex.CMIStatus)) {
this._lesson_status = lesson_status;
}
} else {
if (check12ValidFormat(this._cmi_element + ".lesson_status", lesson_status, scorm12_regex.CMIStatus2)) {
this._lesson_status = lesson_status;
}
}
}
/**
* Getter for _entry
* @return {string}
*/
get entry() {
return this._entry;
}
/**
* Setter for _entry. Can only be called before initialization.
* @param {string} entry
*/
set entry(entry) {
if (this.initialized) {
throw new Scorm12ValidationError(this._cmi_element + ".entry", scorm12_errors$1.READ_ONLY_ELEMENT);
} else {
this._entry = entry;
}
}
/**
* Getter for _total_time
* @return {string}
*/
get total_time() {
return this._total_time;
}
/**
* Setter for _total_time. Can only be called before initialization.
* @param {string} total_time
*/
set total_time(total_time) {
if (this.initialized) {
throw new Scorm12ValidationError(this._cmi_element + ".total_time", scorm12_errors$1.READ_ONLY_ELEMENT);
} else {
this._total_time = total_time;
}
}
/**
* Getter for _lesson_mode
* @return {string}
*/
get lesson_mode() {
return this._lesson_mode;
}
/**
* Setter for _lesson_mode. Can only be called before initialization.
* @param {string} lesson_mode
*/
set lesson_mode(lesson_mode) {
if (this.initialized) {
throw new Scorm12ValidationError(this._cmi_element + ".lesson_mode", scorm12_errors$1.READ_ONLY_ELEMENT);
} else {
this._lesson_mode = lesson_mode;
}
}
/**
* Getter for _exit. Should only be called during JSON export.
* @return {string}
*/
get exit() {
if (!this.jsonString) {
throw new Scorm12ValidationError(this._cmi_element + ".exit", scorm12_errors$1.WRITE_ONLY_ELEMENT);
}
return this._exit;
}
/**
* Setter for _exit
* @param {string} exit
*/
set exit(exit) {
if (check12ValidFormat(this._cmi_element + ".exit", exit, scorm12_regex.CMIExit, true)) {
this._exit = exit;
}
}
/**
* Getter for _session_time. Should only be called during JSON export.
* @return {string}
*/
get session_time() {
if (!this.jsonString) {
throw new Scorm12ValidationError(this._cmi_element + ".session_time", scorm12_errors$1.WRITE_ONLY_ELEMENT);
}
return this._session_time;
}
/**
* Setter for _session_time
* @param {string} session_time
*/
set session_time(session_time) {
if (check12ValidFormat(this._cmi_element + ".session_time", session_time, scorm12_regex.CMITimespan)) {
this._session_time = session_time;
}
}
/**
* Getter for _suspend_data
* @return {string}
*/
get suspend_data() {
return this._suspend_data;
}
/**
* Setter for _suspend_data
* @param {string} suspend_data
*/
set suspend_data(suspend_data) {
if (check12ValidFormat(this._cmi_element + ".suspend_data", suspend_data, scorm12_regex.CMIString4096, true)) {
this._suspend_data = suspend_data;
}
}
/**
* Adds the current session time to the existing total time.
* @param {number} start_time
* @return {string}
*/
getCurrentTotalTime(start_time) {
let sessionTime = this._session_time;
if (typeof start_time !== "undefined" && start_time !== null) {
const seconds = (/* @__PURE__ */new Date()).getTime() - start_time;
sessionTime = getSecondsAsHHMMSS(seconds / 1e3);
}
return addHHMMSSTimeStrings(this._total_time, sessionTime, new RegExp(scorm12_regex.CMITimespan));
}
/**
* toJSON for cmi.core
*
* @return {
* {
* student_name: string,
* entry: string,
* exit: string,
* score: CMIScore,
* student_id: string,
* lesson_mode: string,
* lesson_location: string,
* lesson_status: string,
* credit: string,
* session_time: string
* }
* }
*/
toJSON() {
this.jsonString = true;
const result = {
student_id: this.student_id,
student_name: this.student_name,
lesson_location: this.lesson_location,
credit: this.credit,
lesson_status: this.lesson_status,
entry: this.entry,
lesson_mode: this.lesson_mode,
exit: this.exit,
session_time: this.session_time,
score: this.score
};
this.jsonString = false;
return result;
}
}
class CMIArray extends BaseCMI {
/**
* Constructor cmi *.n arrays
* @param {object} params
*/
constructor(params) {
super(params.CMIElement);
this.__children = params.children;
this._errorCode = params.errorCode || scorm12_errors$1.GENERAL;
this._errorClass = params.errorClass || BaseScormValidationError;
this.childArray = [];
}
/**
* Called when the API has been reset
*/
reset() {
let wipe = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
this._initialized = false;
if (wipe) {
this.childArray = [];
} else {
for (let i = 0; i < this.childArray.length; i++) {
this.childArray[i].reset();
}
}
}
/**
* Getter for _children
* @return {string}
*/
get _children() {
return this.__children;
}
/**
* Setter for _children. Just throws an error.
* @param {string} _children
*/
set _children(_children) {
throw new this._errorClass(this._cmi_element + "._children", this._errorCode);
}
/**
* Getter for _count
* @return {number}
*/
get _count() {
return this.childArray.length;
}
/**
* Setter for _count. Just throws an error.
* @param {number} _count
*/
set _count(_count) {
throw new this._errorClass(this._cmi_element + "._count", this._errorCode);
}
/**
* toJSON for *.n arrays
* @return {object}
*/
toJSON() {
this.jsonString = true;
const result = {};
for (let i = 0; i < this.childArray.length; i++) {
result[i + ""] = this.childArray[i];
}
this.jsonString = false;
return result;
}
}
class CMIObjectives extends CMIArray {
/**
* Constructor for `cmi.objectives`
*/
constructor() {
super({
CMIElement: "cmi.objectives",
children: scorm12_constants.objectives_children,
errorCode: scorm12_errors$1.INVALID_SET_VALUE,
errorClass: Scorm12ValidationError
});
}
}
class CMIObjectivesObject extends BaseCMI {
/**
* Constructor for cmi.objectives.n
*/
constructor() {
super("cmi.objectives.n");
this._id = "";
this._status = "";
this.score = new CMIScore({
CMIElement: "cmi.objectives.n.score",
score_children: scorm12_constants.score_children,
score_range: scorm12_regex.score_range,
invalidErrorCode: scorm12_errors$1.INVALID_SET_VALUE,
invalidTypeCode: scorm12_errors$1.TYPE_MISMATCH,
invalidRangeCode: scorm12_errors$1.VALUE_OUT_OF_RANGE,
errorClass: Scorm12ValidationError
});
}
/**
* Called when the API has been reset
*/
reset() {
this._initialized = false;
this._id = "";
this._status = "";
this.score?.reset();
}
/**
* Getter for _id
* @return {string}
*/
get id() {
return this._id;
}
/**
* Setter for _id
* @param {string} id
*/
set id(id) {
if (check12ValidFormat(this._cmi_element + ".id", id, scorm12_regex.CMIIdentifier)) {
this._id = id;
}
}
/**
* Getter for _status
* @return {string}
*/
get status() {
return this._status;
}
/**
* Setter for _status
* @param {string} status
*/
set status(status) {
if (check12ValidFormat(this._cmi_element + ".status", status, scorm12_regex.CMIStatus2)) {
this._status = status;
}
}
/**
* toJSON for cmi.objectives.n
* @return {
* {
* id: string,
* status: string,
* score: CMIScore
* }
* }
*/
toJSON() {
this.jsonString = true;
const result = {
id: this.id,
status: this.status,
score: this.score
};
this.jsonString = false;
return result;
}
}
class CMIStudentData extends BaseCMI {
/**
* Constructor for cmi.student_data
* @param {string} student_data_children
*/
constructor(student_data_children) {
super("cmi.student_data");
this._mastery_score = "";
this._max_time_allowed = "";
this._time_limit_action = "";
this.__children = student_data_children ? student_data_children : scorm12_constants.student_data_children;
}
/**
* Called when the API has been reset
*/
reset() {
this._initialized = false;
}
/**
* Getter for __children
* @return {string}
* @private
*/
get _children() {
return this.__children;
}
/**
* Setter for __children. Just throws an error.
* @param {string} _children
* @private
*/
set _children(_children) {
throw new Scorm12ValidationError(this._cmi_element + "._children", scorm12_errors$1.INVALID_SET_VALUE);
}
/**
* Getter for _master_score
* @return {string}
*/
get mastery_score() {
return this._mastery_score;
}
/**
* Setter for _master_score. Can only be called before initialization.
* @param {string} mastery_score
*/
set mastery_score(mastery_score) {
validationService.validateReadOnly(this._cmi_element + ".mastery_score", this.initialized);
this._mastery_score = mastery_score;
}
/**
* Getter for _max_time_allowed
* @return {string}
*/
get max_time_allowed() {
return this._max_time_allowed;
}
/**
* Setter for _max_time_allowed. Can only be called before initialization.
* @param {string} max_time_allowed
*/
set max_time_allowed(max_time_allowed) {
validationService.validateReadOnly(this._cmi_element + ".max_time_allowed", this.initialized);
this._max_time_allowed = max_time_allowed;
}
/**
* Getter for _time_limit_action
* @return {string}
*/
get time_limit_action() {
return this._time_limit_action;
}
/**
* Setter for _time_limit_action. Can only be called before initialization.
* @param {string} time_limit_action
*/
set time_limit_action(time_limit_action) {
validationService.validateReadOnly(this._cmi_element + ".time_limit_action", this.initialized);
this._time_limit_action = time_limit_action;
}
/**
* toJSON for cmi.student_data
*
* @return {
* {
* max_time_allowed: string,
* time_limit_action: string,
* mastery_score: string
* }
* }
*/
toJSON() {
this.jsonString = true;
const result = {
mastery_score: this.mastery_score,
max_time_allowed: this.max_time_allowed,
time_limit_action: this.time_limit_action
};
this.jsonString = false;
return result;
}
}
class CMIStudentPreference extends BaseCMI {
/**
* Constructor for cmi.student_preference
* @param {string} student_preference_children
*/
constructor(student_preference_children) {
super("cmi.student_preference");
this._audio = "";
this._language = "";
this._speed = "";
this._text = "";
this.__children = student_preference_children ? student_preference_children : scorm12_constants.student_preference_children;
}
/**
* Called when the API has been reset
*/
reset() {
this._initialized = false;
}
/**
* Getter for __children
* @return {string}
* @private
*/
get _children() {
return this.__children;
}
/**
* Setter for __children. Just throws an error.
* @param {string} _children
* @private
*/
set _children(_children) {
throw new Scorm12ValidationError(this._cmi_element + "._children", scorm12_errors$1.INVALID_SET_VALUE);
}
/**
* Getter for _audio
* @return {string}
*/
get audio() {
return this._audio;
}
/**
* Setter for _audio
* @param {string} audio
*/
set audio(audio) {
if (validationService.validateScorm12Audio(this._cmi_element + ".audio", audio)) {
this._audio = audio;
}
}
/**
* Getter for _language
* @return {string}
*/
get language() {
return this._language;
}
/**
* Setter for _language
* @param {string} language
*/
set language(language) {
if (validationService.validateScorm12Language(this._cmi_element + ".language", language)) {
this._language = language;
}
}
/**
* Getter for _speed
* @return {string}
*/
get speed() {
return this._speed;
}
/**
* Setter for _speed
* @param {string} speed
*/
set speed(speed) {
if (validationService.validateScorm12Speed(this._cmi_element + ".speed", speed)) {
this._speed = speed;
}
}
/**
* Getter for _text
* @return {string}
*/
get text() {
return this._text;
}
/**
* Setter for _text
* @param {string} text
*/
set text(text) {
if (validationService.validateScorm12Text(this._cmi_element + ".text", text)) {
this._text = text;
}
}
/**
* toJSON for cmi.student_preference
*
* @return {
* {
* audio: string,
* language: string,
* speed: string,
* text: string
* }
* }
*/
toJSON() {
this.jsonString = true;
const result = {
audio: this.audio,
language: this.language,
speed: this.speed,
text: this.text
};
this.jsonString = false;
return result;
}
}
class CMIInteractions extends CMIArray {
/**
* Constructor for `cmi.interactions`
*/
constructor() {
super({
CMIElement: "cmi.interactions",
children: scorm12_constants.interactions_children,
errorCode: scorm12_errors$1.INVALID_SET_VALUE,
errorClass: Scorm12ValidationError
});
}
}
class CMIInteractionsObject extends BaseCMI {
/**
* Constructor for cmi.interactions.n object
*/
constructor() {
super("cmi.interactions.n");
this._id = "";
this._time = "";
this._type = "";
this._weighting = "";
this._student_response = "";
this._result = "";
this._latency = "";
this.objectives = new CMIArray({
CMIElement: "cmi.interactions.n.objectives",
errorCode: scorm12_errors$1.INVALID_SET_VALUE,
errorClass: Scorm12ValidationError,
children: scorm12_constants.objectives_children
});
this.correct_responses = new CMIArray({
CMIElement: "cmi.interactions.correct_responses",
errorCode: scorm12_errors$1.INVALID_SET_VALUE,
errorClass: Scorm12ValidationError,
children: scorm12_constants.correct_responses_children
});
}
/**
* Called when the API has been initialized after the CMI has been created
*/
initialize() {
super.initialize();
this.objectives?.initialize();
this.correct_responses?.initialize();
}
/**
* Called when the API has been reset
*/
reset() {
this._initialized = false;
this._id = "";
this._time = "";
this._type = "";
this._weighting = "";
this._student_response = "";
this._result = "";
this._latency = "";
this.objectives?.reset();
this.correct_responses?.reset();
}
/**
* Getter for _id. Should only be called during JSON export.
* @return {string}
*/
get id() {
if (!this.jsonString) {
throw new Scorm12ValidationError(this._cmi_element + ".id", scorm12_errors$1.WRITE_ONLY_ELEMENT);
}
return this._id;
}
/**
* Setter for _id
* @param {string} id
*/
set id(id) {
if (check12ValidFormat(this._cmi_element + ".id", id, scorm12_regex.CMIIdentifier)) {
this._id = id;
}
}
/**
* Getter for _time. Should only be called during JSON export.
* @return {string}
*/
get time() {
if (!this.jsonString) {
throw new Scorm12ValidationError(this._cmi_element + ".time", scorm12_errors$1.WRITE_ONLY_ELEMENT);
}
return this._time;
}
/**
* Setter for _time
* @param {string} time
*/
set time(time) {
if (check12ValidFormat(this._cmi_element + ".time", time, scorm12_regex.CMITime)) {
this._time = time;
}
}
/**
* Getter for _type. Should only be called during JSON export.
* @return {string}
*/
get type() {
if (!this.jsonString) {
throw new Scorm12ValidationError(this._cmi_element + ".type", scorm12_errors$1.WRITE_ONLY_ELEMENT);
}
return this._type;
}
/**
* Setter for _type
* @param {string} type
*/
set type(type) {
if (check12ValidFormat(this._cmi_element + ".type", type, scorm12_regex.CMIType)) {
this._type = type;
}
}
/**
* Getter for _weighting. Should only be called during JSON export.
* @return {string}
*/
get weighting() {
if (!this.jsonString) {
throw new Scorm12ValidationError(this._cmi_element + ".weighting", scorm12_errors$1.WRITE_ONLY_ELEMENT);
}
return this._weighting;
}
/**
* Setter for _weighting
* @param {string} weighting
*/
set weighting(weighting) {
if (check12ValidFormat(this._cmi_element + ".weighting", weighting, scorm12_regex.CMIDecimal) && check12ValidRange(this._cmi_element + ".weighting", weighting, scorm12_regex.weighting_range)) {
this._weighting = weighting;
}
}
/**
* Getter for _student_response. Should only be called during JSON export.
* @return {string}
*/
get student_response() {
if (!this.jsonString) {
throw new Scorm12ValidationError(this._cmi_element + ".student_response", scorm12_errors$1.WRITE_ONLY_ELEMENT);
}
return this._student_response;
}
/**
* Setter for _student_response
* @param {string} student_response
*/
set student_response(student_response) {
if (check12ValidFormat(this._cmi_element + ".student_response", student_response, scorm12_regex.CMIFeedback, true)) {
this._student_response = student_response;
}
}
/**
* Getter for _result. Should only be called during JSON export.
* @return {string}
*/
get result() {
if (!this.jsonString) {
throw new Scorm12ValidationError(this._cmi_element + ".result", scorm12_errors$1.WRITE_ONLY_ELEMENT);
}
return this._result;
}
/**
* Setter for _result
* @param {string} result
*/
set result(result) {
if (check12ValidFormat(this._cmi_element + ".result", result, scorm12_regex.CMIResult)) {
this._result = result;
}
}
/**
* Getter for _latency. Should only be called during JSON export.
* @return {string}
*/
get latency() {
if (!this.jsonString) {
throw new Scorm12ValidationError(this._cmi_element + ".latency", scorm12_errors$1.WRITE_ONLY_ELEMENT);
}
return this._latency;
}
/**
* Setter for _latency
* @param {string} latency
*/
set latency(latency) {
if (check12ValidFormat(this._cmi_element + ".latency", latency, scorm12_regex.CMITimespan)) {
this._latency = latency;
}
}
/**
* toJSON for cmi.interactions.n
*
* @return {
* {
* id: string,
* time: string,
* type: string,
* weighting: string,
* student_response: string,
* result: string,
* latency: string,
* objectives: CMIArray,
* correct_responses: CMIArray
* }
* }
*/
toJSON() {
this.jsonString = true;
const result = {
id: this.id,
time: this.time,
type: this.type,
weighting: this.weighting,
student_response: this.student_response,
result: this.result,
latency: this.latency,
objectives: this.objectives,
correct_responses: this.correct_responses
};
this.jsonString = false;
return result;
}
}
class CMIInteractionsObjectivesObject extends BaseCMI {
/**
* Constructor for cmi.interactions.n.objectives.n
*/
c