UNPKG

scorm-again

Version:

A modern SCORM JavaScript run-time library for SCORM 1.2 and SCORM 2004

1,317 lines (1,300 loc) 846 kB
this.Scorm2004API = (function () { 'use strict'; const SECONDS_PER_SECOND = 1; const SECONDS_PER_MINUTE = 60; const SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE; const SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR; const designations = { D: SECONDS_PER_DAY, H: SECONDS_PER_HOUR, M: SECONDS_PER_MINUTE, S: SECONDS_PER_SECOND }; const getSecondsAsISODuration = seconds => { if (!seconds || seconds <= 0) { return "PT0S"; } let duration = "P"; let remainder = seconds; const designationEntries = Object.entries(designations); designationEntries.forEach(_ref => { let [designationsKey, current_seconds] = _ref; let value = Math.floor(remainder / current_seconds); remainder = remainder % current_seconds; if (countDecimals(remainder) > 2) { remainder = Number(Number(remainder).toFixed(2)); } if (designationsKey === "S" && remainder > 0) { value += remainder; } if (value) { const needsTimeSeparator = (duration.indexOf("D") > 0 || ["H", "M", "S"].includes(designationsKey)) && duration.indexOf("T") === -1; if (needsTimeSeparator) { duration += "T"; } duration += `${value}${designationsKey}`; } }); return duration; }; const getDurationAsSeconds = memoize((duration, durationRegex) => { if (typeof durationRegex === "string") { durationRegex = new RegExp(durationRegex); } if (!duration || !duration?.match?.(durationRegex)) { return 0; } const [, years, months, weeks, days, hours, minutes, seconds] = new RegExp(durationRegex).exec?.(duration) ?? []; let result = 0; result += Number(seconds) || 0; result += Number(minutes) * 60 || 0; result += Number(hours) * 3600 || 0; result += Number(days) * (60 * 60 * 24) || 0; result += Number(weeks) * (60 * 60 * 24 * 7) || 0; result += Number(months) * (60 * 60 * 24 * 30) || 0; result += Number(years) * (60 * 60 * 24 * 365) || 0; return result; }, // Custom key function to handle RegExp objects which can't be stringified (duration, durationRegex) => { const durationStr = duration ?? ""; const regexStr = typeof durationRegex === "string" ? durationRegex : durationRegex?.toString() ?? ""; return `${durationStr}:${regexStr}`; }); const validateISO8601Duration = memoize((duration, durationRegex) => { if (typeof durationRegex === "string") { durationRegex = new RegExp(durationRegex); } return !(!duration || !duration?.match?.(durationRegex)); }); function addTwoDurations(first, second, durationRegex) { const regex = new RegExp(durationRegex) ; return getSecondsAsISODuration(getDurationAsSeconds(first, regex) + getDurationAsSeconds(second, regex)); } 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; let messageString = functionName ? `${String(functionName).padEnd(baseLength)}: ` : ""; 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; })(); }; } function parseNavigationRequest(navRequest) { const validCommands = /* @__PURE__ */new Set(["start", "resumeAll", "continue", "previous", "choice", "jump", "exit", "exitAll", "abandon", "abandonAll", "suspendAll", "_none_"]); const trimmed = navRequest.trim(); if (!trimmed) { return { command: "_none_", targetActivityId: null, valid: false, error: "Empty navigation request" }; } if (validCommands.has(trimmed)) { return { command: trimmed, targetActivityId: null, valid: true }; } const dotIndex = trimmed.indexOf("."); if (dotIndex > 0) { const command = trimmed.substring(0, dotIndex); const targetActivityId = trimmed.substring(dotIndex + 1); if ((command === "choice" || command === "jump") && targetActivityId) { if (/^[a-zA-Z0-9._-]+$/.test(targetActivityId)) { return { command, targetActivityId, valid: true }; } else { return { command: "_none_", targetActivityId: null, valid: false, error: `Invalid target activity ID: contains disallowed characters` }; } } } return { command: "_none_", targetActivityId: null, valid: false, error: `Unrecognized navigation command: "${trimmed}"` }; } var __defProp$15 = Object.defineProperty; var __defNormalProp$15 = (obj, key, value) => key in obj ? __defProp$15(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$15 = (obj, key, value) => __defNormalProp$15(obj, typeof key !== "symbol" ? key + "" : key, value); class BaseCMI { /** * Constructor for BaseCMI * @param {string} cmi_element */ constructor(cmi_element) { /** * Flag used during JSON serialization to allow getter access without initialization checks. * When true, getters can be accessed before the API is initialized, which is necessary * for serializing the CMI data structure to JSON format. */ __publicField$15(this, "jsonString", false); __publicField$15(this, "_cmi_element"); __publicField$15(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 { constructor() { super(...arguments); __publicField$15(this, "_start_time"); } /** * Start time of the session * @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."); } } } var __defProp$14 = Object.defineProperty; var __defNormalProp$14 = (obj, key, value) => key in obj ? __defProp$14(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$14 = (obj, key, value) => __defNormalProp$14(obj, typeof key !== "symbol" ? key + "" : key, value); class BaseScormValidationError extends Error { constructor(CMIElement, errorCode) { super(`${CMIElement} : ${errorCode.toString()}`); __publicField$14(this, "_errorCode"); 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); __publicField$14(this, "_errorMessage"); __publicField$14(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 global_constants = { SCORM_TRUE: "true", SCORM_FALSE: "false", STATE_NOT_INITIALIZED: 0, STATE_INITIALIZED: 1, STATE_TERMINATED: 2 }; const scorm12_constants = { score_children: "raw,min,max", error_descriptions: { "0": { basicMessage: "No Error", detailMessage: "No error occurred, the previous API call was successful." }, "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 scorm2004_constants = { // Children lists cmi_children: "_version,comments_from_learner,comments_from_lms,completion_status,completion_threshold,credit,entry,exit,interactions,launch_data,learner_id,learner_name,learner_preference,location,max_time_allowed,mode,objectives,progress_measure,scaled_passing_score,score,session_time,success_status,suspend_data,time_limit_action,total_time", comments_children: "comment,timestamp,location", score_children: "max,raw,scaled,min", objectives_children: "progress_measure,completion_status,success_status,description,score,id", correct_responses_children: "pattern", student_preference_children: "audio_level,audio_captioning,delivery_speed,language", interactions_children: "id,type,objectives,timestamp,correct_responses,weighting,learner_response,result,latency,description", adl_data_children: "id,store", error_descriptions: { "0": { basicMessage: "No Error", detailMessage: "No error occurred, the previous API call was successful." }, "101": { basicMessage: "General Exception", detailMessage: "No specific error code exists to describe the error." }, "102": { basicMessage: "General Initialization Failure", detailMessage: "Call to Initialize failed for an unknown reason." }, "103": { basicMessage: "Already Initialized", detailMessage: "Call to Initialize failed because Initialize was already called." }, "104": { basicMessage: "Content Instance Terminated", detailMessage: "Call to Initialize failed because Terminate was already called." }, "111": { basicMessage: "General Termination Failure", detailMessage: "Call to Terminate failed for an unknown reason." }, "112": { basicMessage: "Termination Before Initialization", detailMessage: "Call to Terminate failed because it was made before the call to Initialize." }, "113": { basicMessage: "Termination After Termination", detailMessage: "Call to Terminate failed because Terminate was already called." }, "122": { basicMessage: "Retrieve Data Before Initialization", detailMessage: "Call to GetValue failed because it was made before the call to Initialize." }, "123": { basicMessage: "Retrieve Data After Termination", detailMessage: "Call to GetValue failed because it was made after the call to Terminate." }, "132": { basicMessage: "Store Data Before Initialization", detailMessage: "Call to SetValue failed because it was made before the call to Initialize." }, "133": { basicMessage: "Store Data After Termination", detailMessage: "Call to SetValue failed because it was made after the call to Terminate." }, "142": { basicMessage: "Commit Before Initialization", detailMessage: "Call to Commit failed because it was made before the call to Initialize." }, "143": { basicMessage: "Commit After Termination", detailMessage: "Call to Commit failed because it was made after the call to Terminate." }, "201": { basicMessage: "General Argument Error", detailMessage: "An invalid argument was passed to an API method (usually indicates that Initialize, Commit or Terminate did not receive the expected empty string argument." }, "301": { basicMessage: "General Get Failure", detailMessage: "Indicates a failed GetValue call where no other specific error code is applicable. Use GetDiagnostic for more information." }, "351": { basicMessage: "General Set Failure", detailMessage: "Indicates a failed SetValue call where no other specific error code is applicable. Use GetDiagnostic for more information." }, "391": { basicMessage: "General Commit Failure", detailMessage: "Indicates a failed Commit call where no other specific error code is applicable. Use GetDiagnostic for more information." }, "401": { basicMessage: "Undefined Data Model Element", detailMessage: "The data model element name passed to GetValue or SetValue is not a valid SCORM data model element." }, "402": { basicMessage: "Unimplemented Data Model Element", detailMessage: "The data model element indicated in a call to GetValue or SetValue is valid, but was not implemented by this LMS. In SCORM 2004, this error would indicate an LMS that is not fully SCORM conformant." }, "403": { basicMessage: "Data Model Element Value Not Initialized", detailMessage: "Attempt to read a data model element that has not been initialized by the LMS or through a SetValue call. This error condition is often reached during normal execution of a SCO." }, "404": { basicMessage: "Data Model Element Is Read Only", detailMessage: "SetValue was called with a data model element that can only be read." }, "405": { basicMessage: "Data Model Element Is Write Only", detailMessage: "GetValue was called on a data model element that can only be written to." }, "406": { basicMessage: "Data Model Element Type Mismatch", detailMessage: "SetValue was called with a value that is not consistent with the data format of the supplied data model element." }, "407": { basicMessage: "Data Model Element Value Out Of Range", detailMessage: "The numeric value supplied to a SetValue 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 scorm12_errors$1 = 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$1, String(errorCode))) { super(CMIElement, errorCode, scorm12_errors$1[String(errorCode)]?.basicMessage || "Unknown error", scorm12_errors$1[String(errorCode)]?.detailMessage); } else { super(CMIElement, 101, scorm12_errors$1["101"]?.basicMessage, scorm12_errors$1["101"]?.detailMessage); } Object.setPrototypeOf(this, Scorm12ValidationError.prototype); } } const scorm2004_errors$1 = scorm2004_constants.error_descriptions; class Scorm2004ValidationError extends ValidationError { /** * Constructor to take in an error code * @param {string} CMIElement * @param {number} errorCode */ constructor(CMIElement, errorCode) { if ({}.hasOwnProperty.call(scorm2004_errors$1, String(errorCode))) { super(CMIElement, errorCode, scorm2004_errors$1[String(errorCode)]?.basicMessage || "Unknown error", scorm2004_errors$1[String(errorCode)]?.detailMessage); } else { super(CMIElement, 101, scorm2004_errors$1["101"]?.basicMessage, scorm2004_errors$1["101"]?.detailMessage); } Object.setPrototypeOf(this, Scorm2004ValidationError.prototype); } } 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 = { ...global_errors, INVALID_SET_VALUE: 402, READ_ONLY_ELEMENT: 403, TYPE_MISMATCH: 405, VALUE_OUT_OF_RANGE: 407}; const scorm2004_errors = { ...global_errors, INITIALIZATION_FAILED: 102, INITIALIZED: 103, TERMINATED: 104, TERMINATION_FAILURE: 111, TERMINATION_BEFORE_INIT: 112, MULTIPLE_TERMINATION: 113, MULTIPLE_TERMINATIONS: 113, RETRIEVE_BEFORE_INIT: 122, RETRIEVE_AFTER_TERM: 123, STORE_BEFORE_INIT: 132, STORE_AFTER_TERM: 133, COMMIT_BEFORE_INIT: 142, COMMIT_AFTER_TERM: 143, ARGUMENT_ERROR: 201, GENERAL_GET_FAILURE: 301, GENERAL_SET_FAILURE: 351, GENERAL_COMMIT_FAILURE: 391, UNDEFINED_DATA_MODEL: 401, UNIMPLEMENTED_ELEMENT: 402, VALUE_NOT_INITIALIZED: 403, READ_ONLY_ELEMENT: 404, WRITE_ONLY_ELEMENT: 405, TYPE_MISMATCH: 406, VALUE_OUT_OF_RANGE: 407, DEPENDENCY_NOT_ESTABLISHED: 408 }; var __defProp$13 = Object.defineProperty; var __defNormalProp$13 = (obj, key, value) => key in obj ? __defProp$13(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$13 = (obj, key, value) => __defNormalProp$13(obj, typeof key !== "symbol" ? key + "" : key, value); class CMIArray extends BaseCMI { /** * Constructor cmi *.n arrays * @param {object} params */ constructor(params) { super(params.CMIElement); __publicField$13(this, "_errorCode"); __publicField$13(this, "_errorClass"); __publicField$13(this, "__children"); __publicField$13(this, "childArray"); this.__children = params.children; this._errorCode = params.errorCode ?? scorm12_errors.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; } } const NAVBoolean = { UNKNOWN: "unknown", TRUE: "true", FALSE: "false" }; const SuccessStatus = { PASSED: "passed", FAILED: "failed", UNKNOWN: "unknown" }; const CompletionStatus = { COMPLETED: "completed", INCOMPLETE: "incomplete", UNKNOWN: "unknown" }; const LogLevelEnum = { _: 0, DEBUG: 1, INFO: 2, WARN: 3, ERROR: 4, NONE: 5 }; const DefaultSettings = { autocommit: false, autocommitSeconds: 10, throttleCommits: false, useAsynchronousCommits: false, sendFullCommit: true, lmsCommitUrl: false, dataCommitFormat: "json", commitRequestDataType: "application/json;charset=UTF-8", autoProgress: false, logLevel: LogLevelEnum.ERROR, selfReportSessionTime: false, alwaysSendTotalTime: false, renderCommonCommitFields: false, autoCompleteLessonStatus: false, strict_errors: true, xhrHeaders: {}, xhrWithCredentials: false, fetchMode: "cors", asyncModeBeaconBehavior: "never", responseHandler: async function (response) { if (typeof response !== "undefined") { let httpResult = null; try { if (typeof response.json === "function") { httpResult = await response.json(); } else if (typeof response.text === "function") { const responseText = await response.text(); if (responseText) { httpResult = JSON.parse(responseText); } } } catch (e) {} if (httpResult === null || !{}.hasOwnProperty.call(httpResult, "result")) { if (response.status === 200) { return { result: global_constants.SCORM_TRUE, errorCode: 0 }; } else { return { result: global_constants.SCORM_FALSE, errorCode: 101 }; } } else { return { result: httpResult.result, errorCode: typeof httpResult.errorCode === "number" ? httpResult.errorCode : httpResult.result === true || httpResult.result === global_constants.SCORM_TRUE ? 0 : 101 }; } } return { result: global_constants.SCORM_FALSE, errorCode: 101 }; }, xhrResponseHandler: function (xhr) { if (typeof xhr !== "undefined") { let httpResult = null; if (xhr.status >= 200 && xhr.status <= 299) { try { httpResult = JSON.parse(xhr.responseText); } catch (e) {} if (httpResult === null || !{}.hasOwnProperty.call(httpResult, "result")) { return { result: global_constants.SCORM_TRUE, errorCode: 0 }; } return { result: httpResult.result, errorCode: typeof httpResult.errorCode === "number" ? httpResult.errorCode : httpResult.result === true || httpResult.result === global_constants.SCORM_TRUE ? 0 : 101 }; } else { return { result: global_constants.SCORM_FALSE, errorCode: 101 }; } } return { result: global_constants.SCORM_FALSE, errorCode: 101 }; }, requestHandler: function (commitObject) { return commitObject; }, onLogMessage: defaultLogHandler, mastery_override: false, score_overrides_status: false, completion_status_on_failed: "completed", scoItemIds: [], scoItemIdValidator: false, globalObjectiveIds: [], // Offline support settings enableOfflineSupport: false, courseId: "", syncOnInitialize: true, syncOnTerminate: true, maxSyncAttempts: 5, // Multi-SCO support settings scoId: "", autoPopulateCommitMetadata: false, // HTTP service settings httpService: null, // Global learner preferences settings globalStudentPreferences: false }; function defaultLogHandler(messageLevel, logMessage) { switch (messageLevel) { case "4": case 4: case "ERROR": case LogLevelEnum.ERROR: console.error(logMessage); break; case "3": case 3: case "WARN": case LogLevelEnum.WARN: console.warn(logMessage); break; case "2": case 2: case "INFO": case LogLevelEnum.INFO: console.info(logMessage); break; case "1": case 1: case "DEBUG": case LogLevelEnum.DEBUG: if (console.debug) { console.debug(logMessage); } else { console.log(logMessage); } break; } } const scorm12_regex = { /** CMIString256 - Character string, max 255 chars (RTE A.1) */ CMIString256: "^[\\s\\S]{0,255}$", /** CMISInteger - Signed integer (RTE A.5) */ CMISInteger: "^-?([0-9]+)$", /** * CMIDecimal - Signed decimal (RTE A.6) * We set practical limits on decimals to prevent abuse while maintaining * broad compatibility with legacy content. * Increased from 3 to 10 digits before decimal to match SCORM 2004 behavior. */ CMIDecimal: "^-?([0-9]{0,10})(\\.[0-9]*)?$", /** score_range - Valid score range 0-100 (RTE 3.4.2.2.2) */ score_range: "0#100", /** audio_range - Audio level range -1 to 100 (RTE 3.4.2.3.1) */ audio_range: "-1#100", /** speed_range - Playback speed range -100 to 100 (RTE 3.4.2.3.2) */ speed_range: "-100#100", /** text_range - Text display preference -1 to 1 (RTE 3.4.2.3.3) */ text_range: "-1#1" }; const scorm2004_regex = { /** CMIString250 - Character string, max 250 chars (RTE C.1.1) */ CMIString250: "^[\\u0000-\\uFFFF]{0,250}$", /** CMIString1000 - Character string, max 1000 chars (RTE C.1.1) */ CMIString1000: "^[\\u0000-\\uFFFF]{0,1000}$", /** CMIString4000 - Character string, max 4000 chars (RTE C.1.1) */ CMIString4000: "^[\\u0000-\\uFFFF]{0,4000}$", /** CMIString64000 - Character string, max 64000 chars (RTE C.1.1) */ CMIString64000: "^[\\u0000-\\uFFFF]{0,64000}$", /** * CMILang - Language code per RFC 1766/RFC 3066 (RTE C.1.2) * Primary tag: 1-8 characters (ISO 639-1: 2, ISO 639-2: 3, or i/x for IANA/private) * Subtag: 2-8 alphanumeric characters */ CMILang: "^([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?$|^$", /** CMILangString250 - String with optional language tag, max 250 chars (RTE C.1.3) */ CMILangString250: "^({lang=([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?})?((?!{.*$).{0,250}$)?$", /** CMILangcr - Language tag pattern with content */ CMILangcr: "^(({lang=([a-zA-Z]{1,8}|i|x)?(-[a-zA-Z0-9-]{2,8})?}))(.*?)$", /** CMILangString250cr - String with optional language tag (carriage return variant) */ CMILangString250cr: "^(({lang=([a-zA-Z]{1,8}|i|x)?(-[a-zA-Z0-9-]{2,8})?})?(.{0,250})?)?$", /** CMILangString4000 - String with optional language tag, max 4000 chars (RTE C.1.3) */ CMILangString4000: "^({lang=([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?})?((?!{.*$).{0,4000}$)?$", /** * CMITime - ISO 8601 timestamp format (RTE C.1.4) * Year range expanded from 1970-2038 to 1970-9999 to support future dates */ CMITime: "^(19[7-9][0-9]|[2-9][0-9]{3})((-(0[1-9]|1[0-2]))((-(0[1-9]|[1-2][0-9]|3[0-1]))(T([0-1][0-9]|2[0-3])((:[0-5][0-9])((:[0-5][0-9])((\\.[0-9]{1,6})((Z|([+|-]([0-1][0-9]|2[0-3])))(:[0-5][0-9])?)?)?)?)?)?)?)?$", /** CMITimespan - ISO 8601 duration format (RTE C.1.5) */ CMITimespan: "^P(?:([.,\\d]+)Y)?(?:([.,\\d]+)M)?(?:([.,\\d]+)W)?(?:([.,\\d]+)D)?(?:T?(?:([.,\\d]+)H)?(?:([.,\\d]+)M)?(?:(\\d+(?:\\.\\d{1,2})?)S)?)?$", /** CMISInteger - Signed integer (RTE C.1.7) */ CMISInteger: "^-?([0-9]+)$", /** * CMIDecimal - Signed decimal (RTE C.1.8) * Spec allows unlimited digits, but we set practical limits to prevent abuse * while maintaining broad compatibility: * - Up to 10 digits before decimal (supports values up to 10 billion) * - Up to 18 digits after decimal (maintains precision for scientific use) */ CMIDecimal: "^-?([0-9]{1,10})(\\.[0-9]{1,18})?$", /** CMIShortIdentifier - Short identifier conforming to URI syntax, max 250 chars (RTE C.1.10) */ CMIShortIdentifier: "^(?=.*\\w)[\\w\\-\\(\\)\\+\\.\\:\\=\\@\\;\\$\\_\\!\\*\\'\\%\\/\\#]{1,250}$", /** CMILongIdentifier - Long identifier supporting URN format, max 4000 chars (RTE C.1.11) */ CMILongIdentifier: "^(?:(?!urn:)\\S{1,4000}|urn:[A-Za-z0-9-]{1,31}:\\S{1,4000}|.{1,4000})$", /** CMIFeedback - Unrestricted feedback text (RTE C.1.12) */ CMIFeedback: "^.*$", /** CMICStatus - Completion status vocabulary (RTE 4.1.4) */ CMICStatus: "^(completed|incomplete|not attempted|unknown)$", /** CMISStatus - Success status vocabulary (RTE 4.1.11) */ CMISStatus: "^(passed|failed|unknown)$", /** CMIExit - Exit vocabulary (RTE 4.1.3) */ CMIExit: "^(time-out|suspend|logout|normal)$", /** CMIType - Interaction type vocabulary (RTE 4.1.6.2) */ CMIType: "^(true-false|choice|fill-in|long-fill-in|matching|performance|sequencing|likert|numeric|other)$", /** CMIResult - Interaction result vocabulary (RTE 4.1.6.8) */ CMIResult: "^(correct|incorrect|unanticipated|neutral|-?([0-9]{1,4})(\\.[0-9]{1,18})?)$", /** NAVEvent - Navigation event vocabulary (SN Book Table 4.4.2) */ NAVEvent: "^(_?(start|resumeAll|previous|continue|exit|exitAll|abandon|abandonAll|suspendAll|retry|retryAll)|_none_|(\\{target=(?<choice_target>\\S{0,}[a-zA-Z0-9-_]+)})?choice|(\\{target=(?<jump_target>\\S{0,}[a-zA-Z0-9-_]+)})?jump)$", /** NAVBoolean - Navigation boolean vocabulary (SN Book) */ NAVBoolean: "^(unknown|true|false)$", /** NAVTarget - Navigation target pattern (SN Book) */ NAVTarget: "^{target=\\S{0,}[a-zA-Z0-9-_]+}$", /** scaled_range - Scaled score range -1 to 1 (RTE 4.1.10.1) */ scaled_range: "-1#1", /** audio_range - Audio level range 0 to 999.9999999 (RTE 4.1.7.1) */ audio_range: "0#999.9999999", /** speed_range - Playback speed range 0 to 999.9999999 (RTE 4.1.7.4) */ speed_range: "0#999.9999999", /** text_range - Text display preference -1 to 1 (RTE 4.1.7.5) */ text_range: "-1#1", /** progress_range - Progress measure range 0 to 1 (RTE 4.1.8) */ progress_range: "0#1" }; const PERFORMANCE_STEP_NAME = "^$|" + scorm2004_regex.CMIShortIdentifier; const PERFORMANCE_CHARACTERSTRING = "(?![\\s\\S]*(?:\\[,\\]|\\[\\.\\]|\\[:\\]))[\\s\\S]{1,250}"; const PERFORMANCE_NUMERIC_RANGE = "(?:-?\\d+(?:\\.\\d+)?)?\\[:\\](?:-?\\d+(?:\\.\\d+)?)?"; const CR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_NUMERIC_RANGE + "|" + PERFORMANCE_CHARACTERSTRING + ")$"; const LR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_CHARACTERSTRING + ")$"; const LearnerResponses = { "true-false": { format: "^true$|^false$", max: 1, delimiter: "", unique: false }, choice: { format: scorm2004_regex.CMILongIdentifier, max: 36, delimiter: "[,]", unique: true }, "fill-in": { format: scorm2004_regex.CMILangString250, max: 10, delimiter: "[,]", unique: false }, "long-fill-in": { format: scorm2004_regex.CMILangString4000, max: 1, delimiter: "", unique: false }, matching: { format: scorm2004_regex.CMIShortIdentifier, format2: scorm2004_regex.CMIShortIdentifier, max: 36, delimiter: "[,]", delimiter2: "[.]", unique: false }, performance: { format: PERFORMANCE_STEP_NAME, format2: LR_PERFORMANCE_STEP_ANSWER, max: 250, delimiter: "[,]", delimiter2: "[.]", unique: false }, sequencing: { format: scorm2004_regex.CMIShortIdentifier, max: 36, delimiter: "[,]", unique: false }, likert: { format: scorm2004_regex.CMIShortIdentifier, max: 1, delimiter: "", unique: false }, numeric: { format: scorm2004_regex.CMIDecimal, max: 1, delimiter: "", unique: false }, other: { format: scorm2004_regex.CMIString4000, max: 1, delimiter: "", unique: false } }; const CorrectResponses = { "true-false": { max: 1, delimiter: "", unique: false, duplicate: false, format: "^true$|^false$", limit: 1 }, choice: { max: 36, delimiter: "[,]", unique: true, duplicate: false, format: scorm2004_regex.CMILongIdentifier }, "fill-in": { max: 10, delimiter: "[,]", unique: false, duplicate: false, format: scorm2004_regex.CMILangString250cr }, "long-fill-in": { max: 1, delimiter: "", unique: false, duplicate: true, format: scorm2004_regex.CMILangString4000 }, matching: { max: 36, delimiter: "[,]", delimiter2: "[.]", unique: false, duplicate: false, format: scorm2004_regex.CMIShortIdentifier, format2: scorm2004_regex.CMIShortIdentifier }, performance: { max: 250, delimiter: "[,]", delimiter2: "[.]", unique: false, duplicate: false, // step_name: optional short_identifier_type format: PERFORMANCE_STEP_NAME, // step_answer: optional characterstring (spaces allowed) or numeric range format2: CR_PERFORMANCE_STEP_ANSWER }, sequencing: { max: 36, delimiter: "[,]", unique: false, duplicate: false, format: scorm2004_regex.CMIShortIdentifier }, likert: { max: 1, delimiter: "", unique: false, duplicate: false, format: scorm2004_regex.CMIShortIdentifier, limit: 1 }, numeric: { max: 2, delimiter: "[:]", unique: false, duplicate: false, format: scorm2004_regex.CMIDecimal, limit: 1 }, other: { max: 1, delimiter: "", unique: false, duplicate: false, format: scorm2004_regex.CMIString4000, limit: 1 } }; const ValidLanguages = ["aa", "ab", "ae", "af", "ak", "am", "an", "ar", "as", "av", "ay", "az", "ba", "be", "bg", "bh", "bi", "bm", "bn", "bo", "br", "bs", "ca", "ce", "ch", "co", "cr", "cs", "cu", "cv", "cy", "da", "de", "dv", "dz", "ee", "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi", "fj", "fo", "fr", "fy", "ga", "gd", "gl", "gn", "gu", "gv", "ha", "he", "hi", "ho", "hr", "ht", "hu", "hy", "hz", "ia", "id", "ie", "ig", "ii", "ik", "io", "is", "it", "iu", "ja", "jv", "ka", "kg", "ki", "kj", "kk", "kl", "km", "kn", "ko", "kr", "ks", "ku", "kv", "kw", "ky", "la", "lb", "lg", "li", "ln", "lo", "lt", "lu", "lv", "mg", "mh", "mi", "mk", "ml", "mn", "mo", "mr", "ms", "mt", "my", "na", "nb", "nd", "ne", "ng", "nl", "nn", "no", "nr", "nv", "ny", "oc", "oj", "om", "or", "os", "pa", "pi", "pl", "ps", "pt", "qu", "rm", "rn", "ro", "ru", "rw", "sa", "sc", "sd", "se", "sg", "sh", "si", "sk", "sl", "sm", "sn", "so", "sq", "sr", "ss", "st", "su", "sv", "sw", "ta", "te", "tg", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts", "tt", "tw", "ty", "ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa", "wo", "xh", "yi", "yo", "za", "zh", "zu", "aar", "abk", "ave", "afr", "aka", "amh", "arg", "ara", "asm", "ava", "aym", "aze", "bak", "bel", "bul", "bih", "bis", "bam", "ben", "tib", "bod", "bre", "bos", "cat", "che", "cha", "cos", "cre", "cze", "ces", "chu", "chv", "wel", "cym", "dan", "ger", "deu", "div", "dzo", "ewe", "gre", "ell", "eng", "epo", "spa", "est", "baq", "eus", "per", "fas", "ful", "fin", "fij", "fao", "fre", "fra", "fry", "gle", "gla", "glg", "grn", "guj", "glv", "hau", "heb", "hin", "hmo", "hrv", "hat", "hun", "arm", "hye", "her", "ina", "ind", "ile", "ibo", "iii", "ipk", "ido", "ice", "isl", "ita", "iku", "jpn", "jav", "geo", "kat", "kon", "kik", "kua", "kaz", "kal", "khm", "kan", "kor", "kau", "kas", "kur", "kom", "cor", "kir", "lat", "ltz", "lug", "lim", "lin", "lao", "lit", "lub", "lav", "mlg", "mah", "mao", "mri", "mac", "mkd", "mal", "mon", "mol", "mar", "may", "msa", "mlt", "bur", "mya", "nau", "nob", "nde", "nep", "ndo", "dut", "nld", "nno", "nor", "nbl", "nav", "nya", "oci", "oji", "orm", "ori", "oss", "pan", "pli", "pol", "pus", "por", "que", "roh", "run", "rum", "ron", "rus", "kin", "san", "srd", "snd", "sme", "sag", "slo", "sin", "slk", "slv", "smo", "sna", "som", "alb", "sqi", "srp", "ssw", "sot", "sun", "swe", "swa", "tam", "tel", "tgk", "tha", "tir", "tuk", "tgl", "tsn", "ton", "tur", "tso", "tat", "twi", "tah", "uig", "ukr", "urd", "uzb", "ven", "vie", "vol", "wln", "wol", "xho", "yid", "yor", "zha", "chi", "zho", "zul"]; var __defProp$12 = Object.defineProperty; var __defNormalProp$12 = (obj, key, value) => key in obj ? __defProp$12(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$12 = (obj, key, value) => __defNormalProp$12(obj, typeof key !== "symbol" ? key + "" : key, value); class ScheduledCommit { /** * Constructor for ScheduledCommit * @param {BaseAPI} API * @param {number} when * @param {string} callback */ constructor(API, when, callback) { __publicField$12(this, "_API"); __publicField$12(this, "_cancelled", false); __publicField$12(this, "_timeout"); __publicField$12(this, "_callback"); this._API = API; this._timeout = setTimeout(this.wrapper.bind(this), when); this._callback = callback; } /** * Cancel any currently scheduled commit */ cancel() { this._cancelled = true; if (this._timeout) { clearTimeout(this._timeout); } } /** * Wrap the API commit call to check if the call has already been canceled */ wrapper() { if (!this._cancelled) { if (this._API.isInitialized()) { (async () => await this._API.commit(this._callback))(); } } } } const HIDE_LMS_UI_TOKENS = ["continue", "previous", "exit", "exitAll", "abandon", "abandonAll", "suspendAll"]; var __defProp$11 = Object.defineProperty; var __defNormalProp$11 = (obj, key, value) => key in obj ? __defProp$11(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$11 = (obj, key, value) => __defNormalProp$11(obj, typeof key !== "symbol" ? key + "" : key, value); var RuleConditionOperator = /* @__PURE__ */(RuleConditionOperator2 => { RuleConditionOperator2["NOT"] = "not"; RuleConditionOperator2["AND"] = "and"; RuleConditionOperator2["OR"] = "or"; return RuleConditionOperator2; })(RuleConditionOperator || {}); var RuleActionType = /* @__PURE__ */(RuleActionType2 => { RuleActionType2["SKIP"] = "skip"; RuleActionType2["DISABLED"] = "disabled"; RuleActionType2["HIDE_FROM_CHOICE"] = "hiddenFromChoice"; RuleActionType2["STOP_FORWARD_TRAVERSAL"] = "stopForwardTraversal"; RuleActionType2["EXIT_PARENT"] = "exitParent"; RuleActionType2["EXIT_ALL"] = "exitAll"; RuleActionType2["RETRY"] = "retry"; RuleActionType2["RETRY_ALL"] = "retryAll"; RuleActionType2["CONTINUE"] = "continue"; RuleActionType2["PREVIOUS"] = "previous"; RuleActionType2["EXIT"] = "exit"; return RuleActionType2; })(RuleActionType || {}); const _RuleCondition = class _RuleCondition extends BaseCMI { /** * Constructor for RuleCondition * @param {RuleConditionType} condition - The condition type * @param {RuleConditionOperator | null} operator - The operator (null for no operator) * @param {Map<string, any>} parameters - Additional parameters for the condition */ constructor() { let condition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "always"; let operator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; let parameters = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : /* @__PURE__ */new Map(); super("ruleCondition"); __publicField$11(this, "_condition", "always" /* ALWAYS */); __publicField$11(this, "_operator", null); __publicField$11(this, "_parameters", /* @__PURE__ */new Map()); __publicField$11(this, "_referencedObjective", null); this._condition = condition; this._operator = operator; this._parameters = parameters; } /** * Allow integrators to override the clock used for time-based rules. */ static setNowProvider(now) { if (typeof now === "function") { _RuleCondition._now = now; } } /** * Allow integrators to set an elapsed seconds hook for time limit calculations */ static setElapsedSecondsHook(hook) { _RuleCondition._getElapsedSecondsHook = hook; } /** * Called when the API needs to be reset */ reset() { this._initialized = false; this._condition = "always" /* ALWAYS */; this._operator = null; this._parameters = /* @__PURE__ */new Map(); } /** * Getter for condition * @return {RuleConditionType} */ get condition() { return this._condition; } /** * Setter for condition * @param {RuleConditionType} condition */ set condition(condition) { this._condition = condition; } /** * Getter for operator * @return {RuleConditionOperator | null} */ get operator() { return this._operator; } /** * Setter for operator * @param {RuleConditionOperator | null} operator */ set operator(operator) { this._operator = operator; } /** * Getter for parameters * @return {Map<string, any>} */ get parameters() { return this._parameters; } /** * Setter for parameters * @param {Map<string, any>} parameters */ set parameters(parameters) { this._parameters = parameters; } get referencedObjective() { return this._referencedObjective; } set referencedObjective(objectiveId) { this._referencedObjective = objectiveId; } resolveReferencedObjective(activity) { if (!this._referencedObjective) { return null; } if (activity.primaryObjective?.id === this._referencedObjective) { return activity.primaryObjective; } const objectives = activity.objectives || []; return objectives.find(obj => obj.id === this._referencedObjective) || null; } /** * Evaluate the condition for an activity * @param {Activity} activity - The activity to evaluate the condition for * @return {boolean} - True if the condition is met, false otherwise */ evaluate(activity) { let result; const referencedObjective = this.resolveReferencedObjective(activity); switch (this._condition) { case "satisfied" /* SATISFIED */: case "objectiveSatisfied" /* OBJECTIVE_SATISFIED */: if (referencedObjective) { result = referencedObjective.satisfiedStatus === true; } else { result = activity.successStatus === SuccessStatus.PASSED || activity.objectiveSatisfiedStatus === true; } break; case "objectiveStatusKnown" /* OBJECTIVE_STATUS_KNOWN */: result = referencedObjective ? !!referencedObjective.measureStatus : !!activity.objectiveMeasureStatus; break; case "objectiveMeasureKnown" /* OBJECTIVE_MEASURE_KNOWN */: result = referencedObjective ? !!referencedObjective.measureStatus : !!activity.objectiveMeasureStatus; break; case "objectiveMeasureGreaterThan" /* OBJECTIVE_MEASURE_GREATER_THAN */: { const greaterThanValue = this._parameters.get("threshold") || 0; const measureStatus = referencedObjective ? referencedObjective.measureStatus : activity.objectiveMeasureStatus; const measureValue = referencedObjective ? referencedObjective.normalizedMeasure : activity.objectiveNormalizedMeasure; result = !!measureStatus && measureValue > greaterThanValue; break; } case "objectiveMeasureLessThan" /* OBJECTIVE_MEASURE_LESS_THAN */: { const lessThanValue = this._parameters.get("threshold") || 0; const measureStatus = referencedObjective ? referencedObjective.measureStatus : activity.objectiveMeasureStatus; const measureValue = referencedObjective ? referencedObjective.normalizedMeasure : activity.objectiveNormalizedMeasure; result = !!measureStatus && measureValue < lessThanValue; break; } case "completed" /* COMPLETE