scorm-again
Version:
A modern SCORM JavaScript run-time library for SCORM 1.2 and SCORM 2004
1,857 lines (1,840 loc) • 179 kB
JavaScript
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 ((...args) => {
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(wipe = 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
*/
constructor() {
super("cmi.interactions.n.objectives.n");
this._id = "";
}
/**
* Called when the API has been reset
*/
reset() {
this._initialized = false;
this._id = "";
}
/**
* 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;
}
}
/**
* toJSON for cmi.interactions.n.objectives.n
* @return {
* {
* id: string
* }
* }
*/
toJSON() {
this.jsonString = true;
const result = {
id: this.id
};
this.jsonString = false;
return result;
}
}
class CMIInteractionsCorrectResponsesObject extends BaseCMI {
/**
* Constructor for cmi.interactions.correct_responses.n
*/
constructor() {
super("cmi.interactions.correct_responses.n");
this._pattern = "";
}
/**
* Called when the API has been reset
*/
reset() {
this._initialized = false;
this._pattern = "";
}
/**
* Getter for _pattern
* @return {string}
*/
get pattern() {
if (!this.jsonString) {
throw new Scorm12ValidationError(
this._cmi_element + ".pattern",
scorm12_errors$1.WRITE_ONLY_ELEMENT
);
}
return this._pattern;
}
/**
* Setter for _pattern
* @param {string} pattern
*/
set pattern(pattern) {
if (check12ValidFormat(this._cmi_element + ".pattern", pattern, scorm12_regex.CMIFeedback, true)) {
this._pattern = pattern;
}
}
/**
* toJSON for cmi.interactions.correct_responses.n
* @return {
* {
* pattern: string
* }
* }
*/
toJSON() {
this.jsonString = true;
const result = {
pattern: this._pattern
};
this.jsonString = false;
return result;
}
}
let CMI$1 = class CMI extends BaseRootCMI {
/**
* Constructor for the SCORM 1.2 cmi object
* @param {string} cmi_children
* @param {(CMIStudentData|AICCCMIStudentData)} student_data
* @param {boolean} initialized
*/
constructor(cmi_children, student_data, initialized) {
super("cmi");
this.__children = "";
this.__version = "3.4";
this._launch_data = "";
this._comments = "";
this._comments_from_lms = "";
if (initialized) this.initial