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
JavaScript
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