scorm-again 3.0.4 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -5
- package/dist/cross-frame-api.js.map +1 -1
- package/dist/esm/cross-frame-api.js.map +1 -1
- package/dist/esm/scorm-again.js +503 -138
- package/dist/esm/scorm-again.js.map +1 -1
- package/dist/esm/scorm-again.min.js +1 -1
- package/dist/esm/scorm-again.min.js.map +1 -1
- package/dist/esm/scorm12.js +339 -73
- package/dist/esm/scorm12.js.map +1 -1
- package/dist/esm/scorm12.min.js +1 -1
- package/dist/esm/scorm12.min.js.map +1 -1
- package/dist/esm/scorm2004.js +486 -131
- package/dist/esm/scorm2004.js.map +1 -1
- package/dist/esm/scorm2004.min.js +1 -1
- package/dist/esm/scorm2004.min.js.map +1 -1
- package/dist/scorm-again.js +443 -119
- package/dist/scorm-again.js.map +1 -1
- package/dist/scorm-again.min.js +1 -1
- package/dist/scorm-again.min.js.map +1 -1
- package/dist/scorm12.js +316 -57
- package/dist/scorm12.js.map +1 -1
- package/dist/scorm12.min.js +1 -1
- package/dist/scorm12.min.js.map +1 -1
- package/dist/scorm2004.js +431 -112
- package/dist/scorm2004.js.map +1 -1
- package/dist/scorm2004.min.js +1 -1
- package/dist/scorm2004.min.js.map +1 -1
- package/dist/types/BaseAPI.d.ts +15 -5
- package/dist/types/Scorm12API.d.ts +2 -2
- package/dist/types/Scorm2004API.d.ts +3 -2
- package/dist/types/cmi/common/score.d.ts +2 -0
- package/dist/types/cmi/scorm2004/interaction_delimiters.d.ts +4 -0
- package/dist/types/interfaces/services.d.ts +8 -4
- package/dist/types/services/AsynchronousHttpService.d.ts +3 -2
- package/dist/types/services/EventService.d.ts +2 -2
- package/dist/types/services/OfflineStorageService.d.ts +6 -1
- package/dist/types/services/SynchronousHttpService.d.ts +2 -2
- package/dist/types/services/ValidationService.d.ts +1 -1
- package/dist/types/types/api_types.d.ts +22 -2
- package/dist/types/utilities/index.d.ts +1 -0
- package/dist/types/utilities/url.d.ts +2 -0
- package/package.json +46 -25
package/dist/scorm-again.js
CHANGED
|
@@ -252,6 +252,15 @@
|
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
const appendQueryParam = (url, name, value) => {
|
|
256
|
+
const fragmentIndex = url.indexOf("#");
|
|
257
|
+
const baseUrl = fragmentIndex === -1 ? url : url.slice(0, fragmentIndex);
|
|
258
|
+
const fragment = fragmentIndex === -1 ? "" : url.slice(fragmentIndex);
|
|
259
|
+
const separator = baseUrl.includes("?") ? baseUrl.endsWith("?") || baseUrl.endsWith("&") ? "" : "&" : "?";
|
|
260
|
+
const queryParam = `${encodeURIComponent(name)}=${encodeURIComponent(String(value))}`;
|
|
261
|
+
return `${baseUrl}${separator}${queryParam}${fragment}`;
|
|
262
|
+
};
|
|
263
|
+
|
|
255
264
|
var __defProp$1d = Object.defineProperty;
|
|
256
265
|
var __defNormalProp$1d = (obj, key, value) => key in obj ? __defProp$1d(obj, key, {
|
|
257
266
|
enumerable: true,
|
|
@@ -642,7 +651,10 @@
|
|
|
642
651
|
READ_ONLY_ELEMENT: 403,
|
|
643
652
|
WRITE_ONLY_ELEMENT: 404,
|
|
644
653
|
TYPE_MISMATCH: 405,
|
|
645
|
-
|
|
654
|
+
// SCORM 1.2 has no out-of-range error code; an out-of-range value is reported
|
|
655
|
+
// as 405 (incorrect data type), per the SCORM 1.2 RTE error table and the ADL
|
|
656
|
+
// 1.2 CTS (DataModelValidator.checkScoreDecimal failure -> CMICategory 405).
|
|
657
|
+
VALUE_OUT_OF_RANGE: 405,
|
|
646
658
|
DEPENDENCY_NOT_ESTABLISHED: 408
|
|
647
659
|
};
|
|
648
660
|
const scorm2004_errors = {
|
|
@@ -799,6 +811,7 @@
|
|
|
799
811
|
xhrWithCredentials: false,
|
|
800
812
|
fetchMode: "cors",
|
|
801
813
|
asyncModeBeaconBehavior: "never",
|
|
814
|
+
includeCommitSequence: false,
|
|
802
815
|
responseHandler: async function (response) {
|
|
803
816
|
if (typeof response !== "undefined") {
|
|
804
817
|
let httpResult = null;
|
|
@@ -1159,6 +1172,11 @@
|
|
|
1159
1172
|
progress_range: "0#1"
|
|
1160
1173
|
};
|
|
1161
1174
|
|
|
1175
|
+
const PERFORMANCE_STEP_NAME = "^$|" + scorm2004_regex.CMIShortIdentifier;
|
|
1176
|
+
const PERFORMANCE_CHARACTERSTRING = "(?![\\s\\S]*(?:\\[,\\]|\\[\\.\\]|\\[:\\]))[\\s\\S]{1,250}";
|
|
1177
|
+
const PERFORMANCE_NUMERIC_RANGE = "(?:-?\\d+(?:\\.\\d+)?)?\\[:\\](?:-?\\d+(?:\\.\\d+)?)?";
|
|
1178
|
+
const CR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_NUMERIC_RANGE + "|" + PERFORMANCE_CHARACTERSTRING + ")$";
|
|
1179
|
+
const LR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_CHARACTERSTRING + ")$";
|
|
1162
1180
|
const LearnerResponses = {
|
|
1163
1181
|
"true-false": {
|
|
1164
1182
|
format: "^true$|^false$",
|
|
@@ -1193,8 +1211,8 @@
|
|
|
1193
1211
|
unique: false
|
|
1194
1212
|
},
|
|
1195
1213
|
performance: {
|
|
1196
|
-
format:
|
|
1197
|
-
format2:
|
|
1214
|
+
format: PERFORMANCE_STEP_NAME,
|
|
1215
|
+
format2: LR_PERFORMANCE_STEP_ANSWER,
|
|
1198
1216
|
max: 250,
|
|
1199
1217
|
delimiter: "[,]",
|
|
1200
1218
|
delimiter2: "[.]",
|
|
@@ -1270,10 +1288,10 @@
|
|
|
1270
1288
|
delimiter2: "[.]",
|
|
1271
1289
|
unique: false,
|
|
1272
1290
|
duplicate: false,
|
|
1273
|
-
// step_name
|
|
1274
|
-
format:
|
|
1275
|
-
// step_answer
|
|
1276
|
-
format2:
|
|
1291
|
+
// step_name: optional short_identifier_type
|
|
1292
|
+
format: PERFORMANCE_STEP_NAME,
|
|
1293
|
+
// step_answer: optional characterstring (spaces allowed) or numeric range
|
|
1294
|
+
format2: CR_PERFORMANCE_STEP_ANSWER
|
|
1277
1295
|
},
|
|
1278
1296
|
sequencing: {
|
|
1279
1297
|
max: 36,
|
|
@@ -1349,7 +1367,7 @@
|
|
|
1349
1367
|
wrapper() {
|
|
1350
1368
|
if (!this._cancelled) {
|
|
1351
1369
|
if (this._API.isInitialized()) {
|
|
1352
|
-
(async () => await this._API.commit(this._callback))();
|
|
1370
|
+
(async () => await this._API.commit(this._callback, false, "autocommit"))();
|
|
1353
1371
|
}
|
|
1354
1372
|
}
|
|
1355
1373
|
}
|
|
@@ -4822,6 +4840,7 @@
|
|
|
4822
4840
|
* @param {ErrorCode} error_codes - The error codes object
|
|
4823
4841
|
*/
|
|
4824
4842
|
constructor(settings, error_codes) {
|
|
4843
|
+
__publicField$Y(this, "reportsRequestCompletion", true);
|
|
4825
4844
|
__publicField$Y(this, "settings");
|
|
4826
4845
|
__publicField$Y(this, "error_codes");
|
|
4827
4846
|
this.settings = settings;
|
|
@@ -4840,13 +4859,17 @@
|
|
|
4840
4859
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
4841
4860
|
* @param {Function} apiLog - Function to log API messages with appropriate levels
|
|
4842
4861
|
* @param {Function} processListeners - Function to trigger event listeners for commit events
|
|
4862
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
4863
|
+
* @param {Function} onRequestComplete - Callback invoked after the background request settles
|
|
4843
4864
|
* @return {ResultObject} - Immediate optimistic success result
|
|
4844
4865
|
*/
|
|
4845
4866
|
processHttpRequest(url, params) {
|
|
4846
4867
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
4847
4868
|
let apiLog = arguments.length > 3 ? arguments[3] : undefined;
|
|
4848
4869
|
let processListeners = arguments.length > 4 ? arguments[4] : undefined;
|
|
4849
|
-
|
|
4870
|
+
let metadata = arguments.length > 5 ? arguments[5] : undefined;
|
|
4871
|
+
let onRequestComplete = arguments.length > 6 ? arguments[6] : undefined;
|
|
4872
|
+
this._performAsyncRequest(url, params, immediate, apiLog, processListeners, metadata, onRequestComplete);
|
|
4850
4873
|
return {
|
|
4851
4874
|
result: global_constants.SCORM_TRUE,
|
|
4852
4875
|
errorCode: 0
|
|
@@ -4859,11 +4882,14 @@
|
|
|
4859
4882
|
* @param {boolean} immediate - Whether this is an immediate request
|
|
4860
4883
|
* @param apiLog - Function to log API messages
|
|
4861
4884
|
* @param {Function} processListeners - Function to process event listeners
|
|
4885
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
4886
|
+
* @param {Function} onRequestComplete - Callback invoked after the request settles
|
|
4862
4887
|
* @private
|
|
4863
4888
|
*/
|
|
4864
|
-
async _performAsyncRequest(url, params, immediate, apiLog, processListeners) {
|
|
4889
|
+
async _performAsyncRequest(url, params, immediate, apiLog, processListeners, metadata, onRequestComplete) {
|
|
4865
4890
|
try {
|
|
4866
|
-
const
|
|
4891
|
+
const handledParams = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
4892
|
+
const processedParams = handledParams;
|
|
4867
4893
|
let response;
|
|
4868
4894
|
if (immediate && this.settings.asyncModeBeaconBehavior !== "never") {
|
|
4869
4895
|
response = await this.performBeacon(url, processedParams);
|
|
@@ -4879,7 +4905,9 @@
|
|
|
4879
4905
|
} catch (e) {
|
|
4880
4906
|
const message = e instanceof Error ? e.message : String(e);
|
|
4881
4907
|
apiLog("processHttpRequest", `Async request failed: ${message}`, LogLevelEnum.ERROR);
|
|
4882
|
-
processListeners("CommitError");
|
|
4908
|
+
processListeners("CommitError", void 0, this.error_codes.GENERAL_COMMIT_FAILURE || 391);
|
|
4909
|
+
} finally {
|
|
4910
|
+
onRequestComplete?.();
|
|
4883
4911
|
}
|
|
4884
4912
|
}
|
|
4885
4913
|
/**
|
|
@@ -5041,10 +5069,13 @@
|
|
|
5041
5069
|
}
|
|
5042
5070
|
/**
|
|
5043
5071
|
* Gets the appropriate error code for undefined data model elements.
|
|
5044
|
-
* SCORM 2004
|
|
5072
|
+
* Both SCORM 2004 and SCORM 1.2 use UNDEFINED_DATA_MODEL (401): an
|
|
5073
|
+
* unrecognized element is "Not implemented", not a general exception. SCORM
|
|
5074
|
+
* 1.2 previously returned GENERAL (101) here, which is non-conformant — the
|
|
5075
|
+
* ADL 1.2 CTS and the SCORM 1.2 RTE spec expect 401 for an unknown element.
|
|
5045
5076
|
*/
|
|
5046
|
-
getUndefinedDataModelErrorCode(
|
|
5047
|
-
return
|
|
5077
|
+
getUndefinedDataModelErrorCode() {
|
|
5078
|
+
return getErrorCode(this.context.errorCodes, "UNDEFINED_DATA_MODEL");
|
|
5048
5079
|
}
|
|
5049
5080
|
/**
|
|
5050
5081
|
* Sets a value on a CMI element path
|
|
@@ -5068,7 +5099,7 @@
|
|
|
5068
5099
|
let returnValue = global_constants.SCORM_FALSE;
|
|
5069
5100
|
let foundFirstIndex = false;
|
|
5070
5101
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
5071
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
5102
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
5072
5103
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
5073
5104
|
const attribute = structure[idx];
|
|
5074
5105
|
if (idx === structure.length - 1) {
|
|
@@ -5108,12 +5139,13 @@
|
|
|
5108
5139
|
this.context.throwSCORMError(CMIElement, getErrorCode(this.context.errorCodes, "GENERAL_GET_FAILURE"), "The _version keyword was used incorrectly");
|
|
5109
5140
|
return "";
|
|
5110
5141
|
}
|
|
5142
|
+
this.context.setLastErrorCode("0");
|
|
5111
5143
|
const structure = CMIElement.split(".");
|
|
5112
5144
|
let refObject = this.context.getDataModel();
|
|
5113
5145
|
let attribute = null;
|
|
5114
5146
|
const uninitializedErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) has not been initialized.`;
|
|
5115
5147
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
5116
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
5148
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
5117
5149
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
5118
5150
|
attribute = structure[idx];
|
|
5119
5151
|
const validationResult = this.validateGetAttribute(refObject, attribute, CMIElement, scorm2004, invalidErrorCode, invalidErrorMessage, idx === structure.length - 1);
|
|
@@ -5298,7 +5330,13 @@
|
|
|
5298
5330
|
if (!scorm2004) {
|
|
5299
5331
|
if (isFinalAttribute) {
|
|
5300
5332
|
if (typeof attribute === "undefined" || !this.context.checkObjectHasProperty(refObject, attribute)) {
|
|
5301
|
-
|
|
5333
|
+
if (attribute === "_children") {
|
|
5334
|
+
this.context.throwSCORMError(CMIElement, getErrorCode(this.context.errorCodes, "CHILDREN_ERROR"));
|
|
5335
|
+
} else if (attribute === "_count") {
|
|
5336
|
+
this.context.throwSCORMError(CMIElement, getErrorCode(this.context.errorCodes, "COUNT_ERROR"));
|
|
5337
|
+
} else {
|
|
5338
|
+
this.context.throwSCORMError(CMIElement, invalidErrorCode, invalidErrorMessage);
|
|
5339
|
+
}
|
|
5302
5340
|
return {
|
|
5303
5341
|
error: true
|
|
5304
5342
|
};
|
|
@@ -5833,8 +5871,9 @@ ${stackTrace}`);
|
|
|
5833
5871
|
* @param {string} functionName - The name of the function that triggered the event
|
|
5834
5872
|
* @param {string} CMIElement - The CMI element that was affected
|
|
5835
5873
|
* @param {any} value - The value that was set
|
|
5874
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
5836
5875
|
*/
|
|
5837
|
-
processListeners(functionName, CMIElement, value) {
|
|
5876
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
5838
5877
|
this.apiLog(functionName, value, LogLevelEnum.INFO, CMIElement);
|
|
5839
5878
|
const listeners = this.listenerMap.get(functionName);
|
|
5840
5879
|
if (!listeners) return;
|
|
@@ -5854,9 +5893,17 @@ ${stackTrace}`);
|
|
|
5854
5893
|
if (functionName.startsWith("Sequence")) {
|
|
5855
5894
|
listener.callback(value);
|
|
5856
5895
|
} else if (functionName === "CommitError") {
|
|
5857
|
-
|
|
5896
|
+
if (context !== void 0) {
|
|
5897
|
+
listener.callback(value, context);
|
|
5898
|
+
} else {
|
|
5899
|
+
listener.callback(value);
|
|
5900
|
+
}
|
|
5858
5901
|
} else if (functionName === "CommitSuccess") {
|
|
5859
|
-
|
|
5902
|
+
if (context !== void 0) {
|
|
5903
|
+
listener.callback(context);
|
|
5904
|
+
} else {
|
|
5905
|
+
listener.callback();
|
|
5906
|
+
}
|
|
5860
5907
|
} else {
|
|
5861
5908
|
listener.callback(CMIElement, value);
|
|
5862
5909
|
}
|
|
@@ -5965,16 +6012,23 @@ ${stackTrace}`);
|
|
|
5965
6012
|
* Store commit data offline
|
|
5966
6013
|
* @param {string} courseId - Identifier for the course
|
|
5967
6014
|
* @param {CommitObject} commitData - The data to store offline
|
|
6015
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
5968
6016
|
* @returns {ResultObject} - Result of the storage operation
|
|
5969
6017
|
*/
|
|
5970
|
-
storeOffline(courseId, commitData) {
|
|
6018
|
+
storeOffline(courseId, commitData, metadata) {
|
|
5971
6019
|
try {
|
|
5972
6020
|
const queueItem = {
|
|
5973
6021
|
id: `${courseId}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
5974
6022
|
courseId,
|
|
5975
6023
|
timestamp: Date.now(),
|
|
5976
6024
|
data: commitData,
|
|
5977
|
-
syncAttempts: 0
|
|
6025
|
+
syncAttempts: 0,
|
|
6026
|
+
...(metadata?.isTerminateCommit !== void 0 ? {
|
|
6027
|
+
isTerminateCommit: metadata.isTerminateCommit
|
|
6028
|
+
} : {}),
|
|
6029
|
+
...(metadata?.sequence !== void 0 ? {
|
|
6030
|
+
sequence: metadata.sequence
|
|
6031
|
+
} : {})
|
|
5978
6032
|
};
|
|
5979
6033
|
const currentQueue = this.getFromStorage(this.syncQueue) || [];
|
|
5980
6034
|
currentQueue.push(queueItem);
|
|
@@ -6033,7 +6087,14 @@ ${stackTrace}`);
|
|
|
6033
6087
|
continue;
|
|
6034
6088
|
}
|
|
6035
6089
|
try {
|
|
6036
|
-
const syncResult = await this.sendDataToLMS(item.data
|
|
6090
|
+
const syncResult = await this.sendDataToLMS(item.data, {
|
|
6091
|
+
...(item.isTerminateCommit !== void 0 ? {
|
|
6092
|
+
isTerminateCommit: item.isTerminateCommit
|
|
6093
|
+
} : {}),
|
|
6094
|
+
...(item.sequence !== void 0 ? {
|
|
6095
|
+
sequence: item.sequence
|
|
6096
|
+
} : {})
|
|
6097
|
+
});
|
|
6037
6098
|
if (syncResult.result === true || syncResult.result === global_constants.SCORM_TRUE) {
|
|
6038
6099
|
this.apiLog("OfflineStorageService", `Successfully synced item ${item.id}`, LogLevelEnum.INFO);
|
|
6039
6100
|
} else {
|
|
@@ -6060,17 +6121,27 @@ ${stackTrace}`);
|
|
|
6060
6121
|
/**
|
|
6061
6122
|
* Send data to the LMS when online
|
|
6062
6123
|
* @param {CommitObject} data - The data to send to the LMS
|
|
6124
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
6063
6125
|
* @returns {Promise<ResultObject>} - Result of the sync operation
|
|
6064
6126
|
*/
|
|
6065
|
-
async sendDataToLMS(data) {
|
|
6066
|
-
|
|
6127
|
+
async sendDataToLMS(data, metadata) {
|
|
6128
|
+
const configuredCommitUrl = this.settings.lmsCommitUrl;
|
|
6129
|
+
if (!configuredCommitUrl) {
|
|
6067
6130
|
return {
|
|
6068
6131
|
result: global_constants.SCORM_FALSE,
|
|
6069
6132
|
errorCode: this.error_codes.GENERAL || 101
|
|
6070
6133
|
};
|
|
6071
6134
|
}
|
|
6072
6135
|
try {
|
|
6073
|
-
const
|
|
6136
|
+
const lmsCommitUrl = String(configuredCommitUrl);
|
|
6137
|
+
const processedData = this.settings.requestHandler(data, {
|
|
6138
|
+
isTerminateCommit: metadata?.isTerminateCommit ?? false,
|
|
6139
|
+
trigger: "offline-replay",
|
|
6140
|
+
...(metadata?.sequence !== void 0 ? {
|
|
6141
|
+
sequence: metadata.sequence
|
|
6142
|
+
} : {})
|
|
6143
|
+
});
|
|
6144
|
+
const requestUrl = metadata?.isTerminateCommit && this.settings.terminateCommitParam ? appendQueryParam(lmsCommitUrl, this.settings.terminateCommitParam, "true") : lmsCommitUrl;
|
|
6074
6145
|
const init = {
|
|
6075
6146
|
method: "POST",
|
|
6076
6147
|
mode: this.settings.fetchMode,
|
|
@@ -6083,7 +6154,7 @@ ${stackTrace}`);
|
|
|
6083
6154
|
if (this.settings.xhrWithCredentials) {
|
|
6084
6155
|
init.credentials = "include";
|
|
6085
6156
|
}
|
|
6086
|
-
const response = await fetch(
|
|
6157
|
+
const response = await fetch(requestUrl, init);
|
|
6087
6158
|
const result = typeof this.settings.responseHandler === "function" ? await this.settings.responseHandler(response) : await response.json();
|
|
6088
6159
|
if (response.status >= 200 && response.status <= 299 && (result.result === true || result.result === global_constants.SCORM_TRUE)) {
|
|
6089
6160
|
if (!Object.hasOwnProperty.call(result, "errorCode")) {
|
|
@@ -14980,6 +15051,8 @@ ${stackTrace}`);
|
|
|
14980
15051
|
* @param {boolean} immediate - Whether this is a termination commit (use sendBeacon)
|
|
14981
15052
|
* @param {Function} _apiLog - Function to log API messages (unused in synchronous mode - errors returned directly)
|
|
14982
15053
|
* @param {Function} _processListeners - Function to trigger event listeners (unused in synchronous mode - no async events)
|
|
15054
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
15055
|
+
* @param {Function} _onRequestComplete - Completion callback (unused because requests settle before return)
|
|
14983
15056
|
* @return {ResultObject} - The result of the request (synchronous)
|
|
14984
15057
|
*
|
|
14985
15058
|
* @remarks
|
|
@@ -14991,20 +15064,23 @@ ${stackTrace}`);
|
|
|
14991
15064
|
*/
|
|
14992
15065
|
processHttpRequest(url, params) {
|
|
14993
15066
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
15067
|
+
let metadata = arguments.length > 5 ? arguments[5] : undefined;
|
|
14994
15068
|
if (immediate) {
|
|
14995
|
-
return this._handleImmediateRequest(url, params);
|
|
15069
|
+
return this._handleImmediateRequest(url, params, metadata);
|
|
14996
15070
|
}
|
|
14997
|
-
return this._performSyncXHR(url, params);
|
|
15071
|
+
return this._performSyncXHR(url, params, metadata);
|
|
14998
15072
|
}
|
|
14999
15073
|
/**
|
|
15000
15074
|
* Handles an immediate request using sendBeacon
|
|
15001
15075
|
* @param {string} url - The URL to send the request to
|
|
15002
15076
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
15077
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
15003
15078
|
* @return {ResultObject} - The result based on beacon success
|
|
15004
15079
|
* @private
|
|
15005
15080
|
*/
|
|
15006
|
-
_handleImmediateRequest(url, params) {
|
|
15007
|
-
const
|
|
15081
|
+
_handleImmediateRequest(url, params, metadata) {
|
|
15082
|
+
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
15083
|
+
const requestPayload = handledPayload ?? params;
|
|
15008
15084
|
const {
|
|
15009
15085
|
body
|
|
15010
15086
|
} = this._prepareRequestBody(requestPayload);
|
|
@@ -15020,11 +15096,13 @@ ${stackTrace}`);
|
|
|
15020
15096
|
* Performs a synchronous XMLHttpRequest
|
|
15021
15097
|
* @param {string} url - The URL to send the request to
|
|
15022
15098
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
15099
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
15023
15100
|
* @return {ResultObject} - The result of the request
|
|
15024
15101
|
* @private
|
|
15025
15102
|
*/
|
|
15026
|
-
_performSyncXHR(url, params) {
|
|
15027
|
-
const
|
|
15103
|
+
_performSyncXHR(url, params, metadata) {
|
|
15104
|
+
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
15105
|
+
const requestPayload = handledPayload ?? params;
|
|
15028
15106
|
const {
|
|
15029
15107
|
body,
|
|
15030
15108
|
contentType
|
|
@@ -15097,9 +15175,16 @@ ${stackTrace}`);
|
|
|
15097
15175
|
* @param {number} invalidTypeCode - The error code for invalid type
|
|
15098
15176
|
* @param {number} invalidRangeCode - The error code for invalid range
|
|
15099
15177
|
* @param {typeof BaseScormValidationError} errorClass - The error class to use for validation errors
|
|
15178
|
+
* @param {boolean} allowEmptyString - When true, an empty string is accepted (clears the value).
|
|
15179
|
+
* SCORM 1.2 score elements may be blank per the ADL 1.2 CTS
|
|
15180
|
+
* (DataModelValidator.checkScoreDecimal treats blank as valid).
|
|
15100
15181
|
* @return {boolean} - True if validation passes, throws an error otherwise
|
|
15101
15182
|
*/
|
|
15102
15183
|
validateScore(CMIElement, value, decimalRegex, scoreRange, invalidTypeCode, invalidRangeCode, errorClass) {
|
|
15184
|
+
let allowEmptyString = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : false;
|
|
15185
|
+
if (allowEmptyString && value === "") {
|
|
15186
|
+
return true;
|
|
15187
|
+
}
|
|
15103
15188
|
return checkValidFormat(CMIElement, value, decimalRegex, invalidTypeCode, errorClass) && (!scoreRange || checkValidRange(CMIElement, value, scoreRange, invalidRangeCode, errorClass));
|
|
15104
15189
|
}
|
|
15105
15190
|
/**
|
|
@@ -15195,6 +15280,21 @@ ${stackTrace}`);
|
|
|
15195
15280
|
__publicField$w(this, "_offlineStorageService");
|
|
15196
15281
|
__publicField$w(this, "_cmiValueAccessService");
|
|
15197
15282
|
__publicField$w(this, "_courseId", "");
|
|
15283
|
+
__publicField$w(this, "_pendingCommitCount", 0);
|
|
15284
|
+
/**
|
|
15285
|
+
* Monotonic sequence for commits captured by this API instance. It is
|
|
15286
|
+
* intentionally not reset by reset().
|
|
15287
|
+
*/
|
|
15288
|
+
__publicField$w(this, "_commitSequence", 0);
|
|
15289
|
+
__publicField$w(this, "_commitSettleWaiters", []);
|
|
15290
|
+
/**
|
|
15291
|
+
* Canonical paths of every CMI element that has been explicitly assigned a
|
|
15292
|
+
* value via SetValue / loadFromJSON (both funnel through _commonSetCMIValue).
|
|
15293
|
+
* Used by standards that must tell "implemented but never set" apart from a
|
|
15294
|
+
* legitimately empty value when answering GetValue (SCORM 2004 error 403).
|
|
15295
|
+
* Cleared on reset so a fresh SCO attempt starts with nothing "set".
|
|
15296
|
+
*/
|
|
15297
|
+
__publicField$w(this, "_setCMIElements", /* @__PURE__ */new Set());
|
|
15198
15298
|
__publicField$w(this, "startingData");
|
|
15199
15299
|
__publicField$w(this, "currentState");
|
|
15200
15300
|
if (new.target === BaseAPI) {
|
|
@@ -15341,6 +15441,7 @@ ${stackTrace}`);
|
|
|
15341
15441
|
this.lastErrorCode = "0";
|
|
15342
15442
|
this._eventService.reset();
|
|
15343
15443
|
this.startingData = {};
|
|
15444
|
+
this._setCMIElements.clear();
|
|
15344
15445
|
if (this._offlineStorageService) {
|
|
15345
15446
|
this._offlineStorageService.updateSettings(this.settings);
|
|
15346
15447
|
if (settings?.courseId) {
|
|
@@ -15424,6 +15525,54 @@ ${stackTrace}`);
|
|
|
15424
15525
|
this._loggingService?.setLogHandler(settings.onLogMessage);
|
|
15425
15526
|
}
|
|
15426
15527
|
}
|
|
15528
|
+
/**
|
|
15529
|
+
* Gets the number of captured commit requests that have not yet settled.
|
|
15530
|
+
*
|
|
15531
|
+
* @return {number} The number of in-flight commits
|
|
15532
|
+
*/
|
|
15533
|
+
get pendingCommitCount() {
|
|
15534
|
+
return this._pendingCommitCount;
|
|
15535
|
+
}
|
|
15536
|
+
/**
|
|
15537
|
+
* Resolves when all currently in-flight commits have settled. A timeout is
|
|
15538
|
+
* best-effort: the promise resolves when it elapses even if commits remain,
|
|
15539
|
+
* and callers can inspect pendingCommitCount afterward to detect that case.
|
|
15540
|
+
*
|
|
15541
|
+
* @param {Object} [options] - Settle options
|
|
15542
|
+
* @param {number} [options.timeoutMs] - Maximum time to wait in milliseconds
|
|
15543
|
+
* @return {Promise<void>} A promise that resolves after the drain or timeout
|
|
15544
|
+
*/
|
|
15545
|
+
whenCommitsSettled(options) {
|
|
15546
|
+
if (this._pendingCommitCount === 0) {
|
|
15547
|
+
return Promise.resolve();
|
|
15548
|
+
}
|
|
15549
|
+
return new Promise(resolve => {
|
|
15550
|
+
const waiter = {
|
|
15551
|
+
resolve
|
|
15552
|
+
};
|
|
15553
|
+
if (options?.timeoutMs !== void 0) {
|
|
15554
|
+
waiter.timeoutId = setTimeout(() => {
|
|
15555
|
+
const waiterIndex = this._commitSettleWaiters.indexOf(waiter);
|
|
15556
|
+
if (waiterIndex === -1) {
|
|
15557
|
+
return;
|
|
15558
|
+
}
|
|
15559
|
+
this._commitSettleWaiters.splice(waiterIndex, 1);
|
|
15560
|
+
resolve();
|
|
15561
|
+
}, options.timeoutMs);
|
|
15562
|
+
}
|
|
15563
|
+
this._commitSettleWaiters.push(waiter);
|
|
15564
|
+
});
|
|
15565
|
+
}
|
|
15566
|
+
/** Resolve and clear every waiter after the pending count reaches zero. */
|
|
15567
|
+
_flushCommitSettleWaiters() {
|
|
15568
|
+
const waiters = this._commitSettleWaiters.splice(0);
|
|
15569
|
+
for (const waiter of waiters) {
|
|
15570
|
+
if (waiter.timeoutId !== void 0) {
|
|
15571
|
+
clearTimeout(waiter.timeoutId);
|
|
15572
|
+
}
|
|
15573
|
+
waiter.resolve();
|
|
15574
|
+
}
|
|
15575
|
+
}
|
|
15427
15576
|
/**
|
|
15428
15577
|
* Terminates the current run of the API
|
|
15429
15578
|
* @param {string} callbackName
|
|
@@ -15444,7 +15593,7 @@ ${stackTrace}`);
|
|
|
15444
15593
|
} else {
|
|
15445
15594
|
stateCheckPassed = true;
|
|
15446
15595
|
this.processListeners("BeforeTerminate");
|
|
15447
|
-
const result = this.storeData(true);
|
|
15596
|
+
const result = this.storeData(true, "terminate");
|
|
15448
15597
|
if ((result.errorCode ?? 0) > 0) {
|
|
15449
15598
|
if (result.errorMessage) {
|
|
15450
15599
|
this.apiLog("terminate", `Terminate failed with error: ${result.errorMessage}`, LogLevelEnum.ERROR);
|
|
@@ -15484,6 +15633,9 @@ ${stackTrace}`);
|
|
|
15484
15633
|
} catch (e) {
|
|
15485
15634
|
returnValue = this.handleValueAccessException(CMIElement, e, returnValue);
|
|
15486
15635
|
}
|
|
15636
|
+
if (this.lastErrorCode === "0") {
|
|
15637
|
+
this.checkUninitializedGet(CMIElement, returnValue);
|
|
15638
|
+
}
|
|
15487
15639
|
this.processListeners(callbackName, CMIElement);
|
|
15488
15640
|
}
|
|
15489
15641
|
this.apiLog(callbackName, ": returned: " + returnValue, LogLevelEnum.INFO, CMIElement);
|
|
@@ -15493,6 +15645,10 @@ ${stackTrace}`);
|
|
|
15493
15645
|
if (this.lastErrorCode === "0") {
|
|
15494
15646
|
this.clearSCORMError(returnValue);
|
|
15495
15647
|
}
|
|
15648
|
+
const rawReturn = returnValue;
|
|
15649
|
+
if (typeof rawReturn === "number" || typeof rawReturn === "boolean") {
|
|
15650
|
+
return String(rawReturn);
|
|
15651
|
+
}
|
|
15496
15652
|
return returnValue;
|
|
15497
15653
|
}
|
|
15498
15654
|
/**
|
|
@@ -15536,10 +15692,12 @@ ${stackTrace}`);
|
|
|
15536
15692
|
* Orders LMS to store all content parameters
|
|
15537
15693
|
* @param {string} callbackName
|
|
15538
15694
|
* @param {boolean} checkTerminated
|
|
15695
|
+
* @param {CommitTrigger} trigger - What initiated the commit
|
|
15539
15696
|
* @return {string}
|
|
15540
15697
|
*/
|
|
15541
15698
|
commit(callbackName) {
|
|
15542
15699
|
let checkTerminated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
15700
|
+
let trigger = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "manual";
|
|
15543
15701
|
this.clearScheduledCommit();
|
|
15544
15702
|
let returnValue = global_constants.SCORM_TRUE;
|
|
15545
15703
|
if (this.isNotInitialized()) {
|
|
@@ -15551,20 +15709,21 @@ ${stackTrace}`);
|
|
|
15551
15709
|
this.throwSCORMError("api", errorCode);
|
|
15552
15710
|
if (errorCode === 143) returnValue = global_constants.SCORM_FALSE;
|
|
15553
15711
|
} else {
|
|
15554
|
-
const result = this.storeData(false);
|
|
15555
|
-
|
|
15712
|
+
const result = this.storeData(false, trigger);
|
|
15713
|
+
const errorCode = result.errorCode ?? 0;
|
|
15714
|
+
if (errorCode > 0) {
|
|
15556
15715
|
if (result.errorMessage) {
|
|
15557
15716
|
this.apiLog("commit", `Commit failed with error: ${result.errorMessage}`, LogLevelEnum.ERROR);
|
|
15558
15717
|
}
|
|
15559
15718
|
if (result.errorDetails) {
|
|
15560
15719
|
this.apiLog("commit", `Error details: ${JSON.stringify(result.errorDetails)}`, LogLevelEnum.DEBUG);
|
|
15561
15720
|
}
|
|
15562
|
-
this.throwSCORMError("api",
|
|
15721
|
+
this.throwSCORMError("api", errorCode);
|
|
15563
15722
|
}
|
|
15564
15723
|
const resultValue = result?.result ?? global_constants.SCORM_FALSE;
|
|
15565
15724
|
returnValue = typeof resultValue === "boolean" ? String(resultValue) : resultValue;
|
|
15566
15725
|
this.apiLog(callbackName, " Result: " + returnValue, LogLevelEnum.DEBUG, "HttpRequest");
|
|
15567
|
-
if (checkTerminated) this.lastErrorCode = "0";
|
|
15726
|
+
if (checkTerminated && errorCode === 0) this.lastErrorCode = "0";
|
|
15568
15727
|
this.processListeners(callbackName);
|
|
15569
15728
|
if (this.settings.enableOfflineSupport && this._offlineStorageService && this._offlineStorageService.isDeviceOnline() && this._courseId) {
|
|
15570
15729
|
this._offlineStorageService.hasPendingOfflineData(this._courseId).then(hasPendingData => {
|
|
@@ -15771,7 +15930,11 @@ ${stackTrace}`);
|
|
|
15771
15930
|
* @return {string}
|
|
15772
15931
|
*/
|
|
15773
15932
|
_commonSetCMIValue(methodName, scorm2004, CMIElement, value) {
|
|
15774
|
-
|
|
15933
|
+
const result = this._cmiValueAccessService.setCMIValue(methodName, scorm2004, CMIElement, value);
|
|
15934
|
+
if (result === global_constants.SCORM_TRUE) {
|
|
15935
|
+
this._setCMIElements.add(CMIElement);
|
|
15936
|
+
}
|
|
15937
|
+
return result;
|
|
15775
15938
|
}
|
|
15776
15939
|
/**
|
|
15777
15940
|
* Gets a value from the CMI Object.
|
|
@@ -15785,6 +15948,17 @@ ${stackTrace}`);
|
|
|
15785
15948
|
_commonGetCMIValue(methodName, scorm2004, CMIElement) {
|
|
15786
15949
|
return this._cmiValueAccessService.getCMIValue(methodName, scorm2004, CMIElement);
|
|
15787
15950
|
}
|
|
15951
|
+
/**
|
|
15952
|
+
* Hook invoked by getValue after a successful resolution. Standards that must
|
|
15953
|
+
* distinguish "implemented but never set, no default value" from a
|
|
15954
|
+
* legitimately empty value override this to raise VALUE_NOT_INITIALIZED.
|
|
15955
|
+
* Default is a no-op, so SCORM 1.2 / AICC keep returning "" with code 0.
|
|
15956
|
+
*
|
|
15957
|
+
* @param {string} _CMIElement - the element that was read
|
|
15958
|
+
* @param {any} _returnValue - the value getCMIValue resolved
|
|
15959
|
+
* @protected
|
|
15960
|
+
*/
|
|
15961
|
+
checkUninitializedGet(_CMIElement, _returnValue) {}
|
|
15788
15962
|
/**
|
|
15789
15963
|
* Returns true if the API's current state is STATE_INITIALIZED
|
|
15790
15964
|
*
|
|
@@ -15867,9 +16041,14 @@ ${stackTrace}`);
|
|
|
15867
16041
|
* @param {string} functionName - The name of the function/event that occurred
|
|
15868
16042
|
* @param {string} CMIElement - Optional CMI element involved in the event
|
|
15869
16043
|
* @param {any} value - Optional value associated with the event
|
|
16044
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
15870
16045
|
*/
|
|
15871
|
-
processListeners(functionName, CMIElement, value) {
|
|
15872
|
-
|
|
16046
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
16047
|
+
if (context !== void 0) {
|
|
16048
|
+
this._eventService.processListeners(functionName, CMIElement, value, context);
|
|
16049
|
+
} else {
|
|
16050
|
+
this._eventService.processListeners(functionName, CMIElement, value);
|
|
16051
|
+
}
|
|
15873
16052
|
}
|
|
15874
16053
|
/**
|
|
15875
16054
|
* Throws a SCORM error with the specified error number and optional message.
|
|
@@ -15991,23 +16170,97 @@ ${stackTrace}`);
|
|
|
15991
16170
|
* @param {string} url - The URL to send the request to
|
|
15992
16171
|
* @param {CommitObject | StringKeyMap | Array<any>} params - The parameters to send
|
|
15993
16172
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
16173
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
15994
16174
|
* @returns {ResultObject} - The result of the request
|
|
15995
16175
|
*/
|
|
15996
16176
|
processHttpRequest(url, params) {
|
|
15997
16177
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16178
|
+
let trigger = arguments.length > 3 ? arguments[3] : undefined;
|
|
16179
|
+
const sequence = ++this._commitSequence;
|
|
16180
|
+
this._pendingCommitCount += 1;
|
|
16181
|
+
let settled = false;
|
|
16182
|
+
let completionDeferred = false;
|
|
16183
|
+
const settle = () => {
|
|
16184
|
+
if (settled) {
|
|
16185
|
+
return;
|
|
16186
|
+
}
|
|
16187
|
+
settled = true;
|
|
16188
|
+
this._pendingCommitCount -= 1;
|
|
16189
|
+
if (this._pendingCommitCount === 0) {
|
|
16190
|
+
this._flushCommitSettleWaiters();
|
|
16191
|
+
}
|
|
16192
|
+
};
|
|
16193
|
+
try {
|
|
16194
|
+
const resolvedTrigger = trigger ?? (immediate ? "terminate" : "manual");
|
|
16195
|
+
let finalParams = params;
|
|
16196
|
+
if (immediate && this.settings.terminateCommitPayloadField) {
|
|
16197
|
+
const field = this.settings.terminateCommitPayloadField;
|
|
16198
|
+
if (Array.isArray(finalParams)) {
|
|
16199
|
+
finalParams = [...finalParams, `${encodeURIComponent(field)}=true`];
|
|
16200
|
+
} else if (finalParams && typeof finalParams === "object") {
|
|
16201
|
+
finalParams = {
|
|
16202
|
+
...finalParams,
|
|
16203
|
+
[field]: true
|
|
16204
|
+
};
|
|
16205
|
+
}
|
|
16206
|
+
}
|
|
16207
|
+
if (this.settings.includeCommitSequence === true) {
|
|
16208
|
+
if (Array.isArray(finalParams)) {
|
|
16209
|
+
finalParams = [...finalParams, `commitSequence=${sequence}`];
|
|
16210
|
+
} else if (finalParams && typeof finalParams === "object") {
|
|
16211
|
+
finalParams = {
|
|
16212
|
+
...finalParams,
|
|
16213
|
+
commitSequence: sequence
|
|
16214
|
+
};
|
|
16215
|
+
}
|
|
16216
|
+
}
|
|
16217
|
+
const finalUrl = immediate && this.settings.terminateCommitParam ? appendQueryParam(url, this.settings.terminateCommitParam, "true") : url;
|
|
16218
|
+
const metadata = {
|
|
16219
|
+
isTerminateCommit: immediate,
|
|
16220
|
+
trigger: resolvedTrigger,
|
|
16221
|
+
sequence
|
|
16222
|
+
};
|
|
16223
|
+
const context = {
|
|
16224
|
+
url: finalUrl,
|
|
16225
|
+
trigger: resolvedTrigger,
|
|
16226
|
+
isTerminateCommit: immediate,
|
|
16227
|
+
sequence
|
|
16228
|
+
};
|
|
16229
|
+
if (this.settings.enableOfflineSupport && this._offlineStorageService && !this._offlineStorageService.isDeviceOnline() && this._courseId) {
|
|
16230
|
+
this.apiLog("processHttpRequest", "Device is offline, storing data locally", LogLevelEnum.INFO);
|
|
16231
|
+
if (finalParams && typeof finalParams === "object" && "cmi" in finalParams) {
|
|
16232
|
+
return this._offlineStorageService.storeOffline(this._courseId, finalParams, {
|
|
16233
|
+
isTerminateCommit: immediate,
|
|
16234
|
+
sequence
|
|
16235
|
+
});
|
|
16236
|
+
} else {
|
|
16237
|
+
this.apiLog("processHttpRequest", "Invalid commit data format for offline storage", LogLevelEnum.ERROR);
|
|
16238
|
+
return {
|
|
16239
|
+
result: global_constants.SCORM_FALSE,
|
|
16240
|
+
errorCode: this._error_codes.GENERAL ?? 101
|
|
16241
|
+
};
|
|
16242
|
+
}
|
|
16243
|
+
}
|
|
16244
|
+
const apiLog = (functionName, message, level, element) => this.apiLog(functionName, message, level, element);
|
|
16245
|
+
const processListeners = (functionName, CMIElement, value) => {
|
|
16246
|
+
if (functionName === "CommitSuccess" || functionName === "CommitError") {
|
|
16247
|
+
if (functionName === "CommitError" && typeof value === "number") {
|
|
16248
|
+
context.errorCode = value;
|
|
16249
|
+
}
|
|
16250
|
+
settle();
|
|
16251
|
+
this.processListeners(functionName, CMIElement, value, context);
|
|
16252
|
+
} else {
|
|
16253
|
+
this.processListeners(functionName, CMIElement, value);
|
|
16254
|
+
}
|
|
16255
|
+
};
|
|
16256
|
+
const result = this._httpService.processHttpRequest(finalUrl, finalParams, immediate, apiLog, processListeners, metadata, settle);
|
|
16257
|
+
completionDeferred = this._httpService.reportsRequestCompletion === true;
|
|
16258
|
+
return result;
|
|
16259
|
+
} finally {
|
|
16260
|
+
if (!completionDeferred) {
|
|
16261
|
+
settle();
|
|
16008
16262
|
}
|
|
16009
16263
|
}
|
|
16010
|
-
return this._httpService.processHttpRequest(url, params, immediate, (functionName, message, level, element) => this.apiLog(functionName, message, level, element), (functionName, CMIElement, value) => this.processListeners(functionName, CMIElement, value));
|
|
16011
16264
|
}
|
|
16012
16265
|
/**
|
|
16013
16266
|
* Schedules a commit operation to occur after a specified delay.
|
|
@@ -16191,6 +16444,11 @@ ${stackTrace}`);
|
|
|
16191
16444
|
__publicField$v(this, "__invalid_range_code");
|
|
16192
16445
|
__publicField$v(this, "__decimal_regex");
|
|
16193
16446
|
__publicField$v(this, "__error_class");
|
|
16447
|
+
/**
|
|
16448
|
+
* When true, an empty string is a valid value (clears the element). SCORM 1.2
|
|
16449
|
+
* score elements may be blank per the ADL 1.2 CTS; SCORM 2004 leaves this off.
|
|
16450
|
+
*/
|
|
16451
|
+
__publicField$v(this, "__allow_empty_string");
|
|
16194
16452
|
__publicField$v(this, "_raw", "");
|
|
16195
16453
|
__publicField$v(this, "_min", "");
|
|
16196
16454
|
__publicField$v(this, "_max");
|
|
@@ -16202,6 +16460,7 @@ ${stackTrace}`);
|
|
|
16202
16460
|
this.__invalid_range_code = params.invalidRangeCode || scorm12_errors.VALUE_OUT_OF_RANGE;
|
|
16203
16461
|
this.__decimal_regex = params.decimalRegex || scorm12_regex.CMIDecimal;
|
|
16204
16462
|
this.__error_class = params.errorClass;
|
|
16463
|
+
this.__allow_empty_string = params.allowEmptyString ?? false;
|
|
16205
16464
|
}
|
|
16206
16465
|
/**
|
|
16207
16466
|
* Called when the API has been reset
|
|
@@ -16241,7 +16500,7 @@ ${stackTrace}`);
|
|
|
16241
16500
|
* @param {string} raw
|
|
16242
16501
|
*/
|
|
16243
16502
|
set raw(raw) {
|
|
16244
|
-
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)) {
|
|
16503
|
+
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.__allow_empty_string)) {
|
|
16245
16504
|
this._raw = raw;
|
|
16246
16505
|
}
|
|
16247
16506
|
}
|
|
@@ -16257,7 +16516,7 @@ ${stackTrace}`);
|
|
|
16257
16516
|
* @param {string} min
|
|
16258
16517
|
*/
|
|
16259
16518
|
set min(min) {
|
|
16260
|
-
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)) {
|
|
16519
|
+
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.__allow_empty_string)) {
|
|
16261
16520
|
this._min = min;
|
|
16262
16521
|
}
|
|
16263
16522
|
}
|
|
@@ -16273,7 +16532,7 @@ ${stackTrace}`);
|
|
|
16273
16532
|
* @param {string} max
|
|
16274
16533
|
*/
|
|
16275
16534
|
set max(max) {
|
|
16276
|
-
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)) {
|
|
16535
|
+
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.__allow_empty_string)) {
|
|
16277
16536
|
this._max = max;
|
|
16278
16537
|
}
|
|
16279
16538
|
}
|
|
@@ -16350,7 +16609,9 @@ ${stackTrace}`);
|
|
|
16350
16609
|
invalidErrorCode: scorm12_errors.INVALID_SET_VALUE,
|
|
16351
16610
|
invalidTypeCode: scorm12_errors.TYPE_MISMATCH,
|
|
16352
16611
|
invalidRangeCode: scorm12_errors.VALUE_OUT_OF_RANGE,
|
|
16353
|
-
errorClass: Scorm12ValidationError
|
|
16612
|
+
errorClass: Scorm12ValidationError,
|
|
16613
|
+
// SCORM 1.2 score elements may be set blank (clears the value), per ADL 1.2 CTS.
|
|
16614
|
+
allowEmptyString: true
|
|
16354
16615
|
});
|
|
16355
16616
|
}
|
|
16356
16617
|
/**
|
|
@@ -16718,7 +16979,9 @@ ${stackTrace}`);
|
|
|
16718
16979
|
invalidErrorCode: scorm12_errors.INVALID_SET_VALUE,
|
|
16719
16980
|
invalidTypeCode: scorm12_errors.TYPE_MISMATCH,
|
|
16720
16981
|
invalidRangeCode: scorm12_errors.VALUE_OUT_OF_RANGE,
|
|
16721
|
-
errorClass: Scorm12ValidationError
|
|
16982
|
+
errorClass: Scorm12ValidationError,
|
|
16983
|
+
// SCORM 1.2 score elements may be set blank (clears the value), per ADL 1.2 CTS.
|
|
16984
|
+
allowEmptyString: true
|
|
16722
16985
|
});
|
|
16723
16986
|
}
|
|
16724
16987
|
/**
|
|
@@ -18155,16 +18418,17 @@ ${stackTrace}`);
|
|
|
18155
18418
|
* Attempts to store the data to the LMS
|
|
18156
18419
|
*
|
|
18157
18420
|
* @param {boolean} terminateCommit
|
|
18421
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
18158
18422
|
* @return {ResultObject}
|
|
18159
18423
|
*/
|
|
18160
|
-
storeData(terminateCommit) {
|
|
18424
|
+
storeData(terminateCommit, trigger) {
|
|
18161
18425
|
if (terminateCommit) {
|
|
18162
18426
|
const originalStatus = this.cmi.core.lesson_status;
|
|
18163
18427
|
if (this.cmi.core.lesson_mode === "browse") {
|
|
18164
18428
|
const startingStatus = this.startingData?.cmi?.core?.lesson_status || "";
|
|
18165
18429
|
if (startingStatus === "" && originalStatus === "not attempted") {
|
|
18166
18430
|
this.cmi.core.lesson_status = "browsed";
|
|
18167
|
-
return this.processCommitData(terminateCommit);
|
|
18431
|
+
return this.processCommitData(terminateCommit, trigger);
|
|
18168
18432
|
}
|
|
18169
18433
|
}
|
|
18170
18434
|
if (!this.cmi.core.lesson_status || !this.statusSetByModule && this.cmi.core.lesson_status === "not attempted") {
|
|
@@ -18193,12 +18457,12 @@ ${stackTrace}`);
|
|
|
18193
18457
|
}
|
|
18194
18458
|
}
|
|
18195
18459
|
}
|
|
18196
|
-
return this.processCommitData(terminateCommit);
|
|
18460
|
+
return this.processCommitData(terminateCommit, trigger);
|
|
18197
18461
|
}
|
|
18198
|
-
processCommitData(terminateCommit) {
|
|
18462
|
+
processCommitData(terminateCommit, trigger) {
|
|
18199
18463
|
const commitObject = this.getCommitObject(terminateCommit);
|
|
18200
18464
|
if (typeof this.settings.lmsCommitUrl === "string") {
|
|
18201
|
-
return this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit);
|
|
18465
|
+
return this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit, trigger);
|
|
18202
18466
|
} else {
|
|
18203
18467
|
return {
|
|
18204
18468
|
result: global_constants.SCORM_TRUE,
|
|
@@ -18349,6 +18613,49 @@ ${stackTrace}`);
|
|
|
18349
18613
|
}
|
|
18350
18614
|
}
|
|
18351
18615
|
|
|
18616
|
+
function stripBrackets(delim) {
|
|
18617
|
+
return delim.replace(/[[\]]/g, "");
|
|
18618
|
+
}
|
|
18619
|
+
function escapeRegex(s) {
|
|
18620
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18621
|
+
}
|
|
18622
|
+
function splitDelimited(value, bracketed) {
|
|
18623
|
+
if (!bracketed) {
|
|
18624
|
+
return [value];
|
|
18625
|
+
}
|
|
18626
|
+
if (value.includes(bracketed)) {
|
|
18627
|
+
return value.split(bracketed);
|
|
18628
|
+
}
|
|
18629
|
+
const bare = stripBrackets(bracketed);
|
|
18630
|
+
if (!bare) {
|
|
18631
|
+
return [value];
|
|
18632
|
+
}
|
|
18633
|
+
const splitRe = new RegExp(`(?<!\\\\)${escapeRegex(bare)}`, "g");
|
|
18634
|
+
const unescapeRe = new RegExp(`\\\\${escapeRegex(bare)}`, "g");
|
|
18635
|
+
return value.split(splitRe).map(part => part.replace(unescapeRe, bare));
|
|
18636
|
+
}
|
|
18637
|
+
function splitFirstDelimited(value, bracketed) {
|
|
18638
|
+
if (!bracketed) {
|
|
18639
|
+
return [value];
|
|
18640
|
+
}
|
|
18641
|
+
if (value.includes(bracketed)) {
|
|
18642
|
+
const idx = value.indexOf(bracketed);
|
|
18643
|
+
return [value.slice(0, idx), value.slice(idx + bracketed.length)];
|
|
18644
|
+
}
|
|
18645
|
+
const bare = stripBrackets(bracketed);
|
|
18646
|
+
if (!bare) {
|
|
18647
|
+
return [value];
|
|
18648
|
+
}
|
|
18649
|
+
const splitRe = new RegExp(`(?<!\\\\)${escapeRegex(bare)}`);
|
|
18650
|
+
const unescapeRe = new RegExp(`\\\\${escapeRegex(bare)}`, "g");
|
|
18651
|
+
const parts = value.split(splitRe);
|
|
18652
|
+
const first = (parts[0] ?? "").replace(unescapeRe, bare);
|
|
18653
|
+
if (parts.length === 1) {
|
|
18654
|
+
return [first];
|
|
18655
|
+
}
|
|
18656
|
+
return [first, parts.slice(1).join(bare).replace(unescapeRe, bare)];
|
|
18657
|
+
}
|
|
18658
|
+
|
|
18352
18659
|
var __defProp$l = Object.defineProperty;
|
|
18353
18660
|
var __defNormalProp$l = (obj, key, value) => key in obj ? __defProp$l(obj, key, {
|
|
18354
18661
|
enumerable: true,
|
|
@@ -18544,8 +18851,7 @@ ${stackTrace}`);
|
|
|
18544
18851
|
const response_type = LearnerResponses[this.type];
|
|
18545
18852
|
if (response_type) {
|
|
18546
18853
|
if (response_type?.delimiter) {
|
|
18547
|
-
|
|
18548
|
-
nodes = learner_response.split(delimiter);
|
|
18854
|
+
nodes = splitDelimited(learner_response, response_type.delimiter);
|
|
18549
18855
|
} else {
|
|
18550
18856
|
nodes[0] = learner_response;
|
|
18551
18857
|
}
|
|
@@ -18553,10 +18859,10 @@ ${stackTrace}`);
|
|
|
18553
18859
|
const formatRegex = new RegExp(response_type.format);
|
|
18554
18860
|
for (let i = 0; i < nodes.length; i++) {
|
|
18555
18861
|
if (response_type?.delimiter2) {
|
|
18556
|
-
const
|
|
18557
|
-
const values =
|
|
18862
|
+
const node = nodes[i] ?? "";
|
|
18863
|
+
const values = this.type === "performance" ? splitFirstDelimited(node, response_type.delimiter2) : splitDelimited(node, response_type.delimiter2);
|
|
18558
18864
|
if (values?.length === 2) {
|
|
18559
|
-
if (this.type === "performance" &&
|
|
18865
|
+
if (this.type === "performance" && values[0] === "" && values[1] === "") {
|
|
18560
18866
|
throw new Scorm2004ValidationError(this._cmi_element + ".learner_response", scorm2004_errors.TYPE_MISMATCH);
|
|
18561
18867
|
}
|
|
18562
18868
|
if (!values[0]?.match(formatRegex)) {
|
|
@@ -18737,37 +19043,19 @@ ${stackTrace}`);
|
|
|
18737
19043
|
return result;
|
|
18738
19044
|
}
|
|
18739
19045
|
}
|
|
18740
|
-
|
|
18741
|
-
|
|
18742
|
-
|
|
18743
|
-
|
|
18744
|
-
|
|
18745
|
-
}
|
|
18746
|
-
function splitUnescaped(text, delim) {
|
|
18747
|
-
const reDelim = escapeRegex(delim);
|
|
18748
|
-
const splitRe = new RegExp(`(?<!\\\\)${reDelim}`, "g");
|
|
18749
|
-
const unescapeRe = new RegExp(`\\\\${reDelim}`, "g");
|
|
18750
|
-
return text.split(splitRe).map(part => part.replace(unescapeRe, delim));
|
|
18751
|
-
}
|
|
18752
|
-
function splitFirstUnescaped(text, delim) {
|
|
18753
|
-
const reDelim = escapeRegex(delim);
|
|
18754
|
-
const splitRe = new RegExp(`(?<!\\\\)${reDelim}`);
|
|
18755
|
-
const unescapeRe = new RegExp(`\\\\${reDelim}`, "g");
|
|
18756
|
-
const parts = text.split(splitRe);
|
|
18757
|
-
const firstPart = parts[0] ?? "";
|
|
18758
|
-
if (parts.length === 1) {
|
|
18759
|
-
return [firstPart.replace(unescapeRe, delim)];
|
|
19046
|
+
const RESPONSE_PREFIX_RE = /^\{(?:lang|case_matters|order_matters)=[^}]+\}/;
|
|
19047
|
+
function stripResponsePrefixes(node) {
|
|
19048
|
+
let result = node;
|
|
19049
|
+
while (RESPONSE_PREFIX_RE.test(result)) {
|
|
19050
|
+
result = result.replace(RESPONSE_PREFIX_RE, "");
|
|
18760
19051
|
}
|
|
18761
|
-
|
|
18762
|
-
const part2 = parts.slice(1).join(delim).replace(unescapeRe, delim);
|
|
18763
|
-
return [part1, part2];
|
|
19052
|
+
return result;
|
|
18764
19053
|
}
|
|
18765
19054
|
function validatePattern(type, pattern, responseDef) {
|
|
18766
19055
|
if (pattern.trim() !== pattern) {
|
|
18767
19056
|
throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
|
|
18768
19057
|
}
|
|
18769
|
-
const
|
|
18770
|
-
const rawNodes = subDelim1 ? splitUnescaped(pattern, subDelim1) : [pattern];
|
|
19058
|
+
const rawNodes = responseDef.delimiter ? splitDelimited(pattern, responseDef.delimiter) : [pattern];
|
|
18771
19059
|
for (const raw of rawNodes) {
|
|
18772
19060
|
if (raw.trim() !== raw) {
|
|
18773
19061
|
throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
|
|
@@ -18776,17 +19064,11 @@ ${stackTrace}`);
|
|
|
18776
19064
|
if (type === "fill-in" && pattern === "") {
|
|
18777
19065
|
return;
|
|
18778
19066
|
}
|
|
18779
|
-
const
|
|
18780
|
-
let nodes;
|
|
18781
|
-
if (delim1) {
|
|
18782
|
-
nodes = splitUnescaped(pattern, delim1);
|
|
18783
|
-
} else {
|
|
18784
|
-
nodes = [pattern];
|
|
18785
|
-
}
|
|
19067
|
+
const nodes = responseDef.delimiter ? splitDelimited(pattern, responseDef.delimiter) : [pattern];
|
|
18786
19068
|
if (!responseDef.delimiter && pattern.includes(",")) {
|
|
18787
19069
|
throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
|
|
18788
19070
|
}
|
|
18789
|
-
if (responseDef.unique || responseDef.duplicate === false) {
|
|
19071
|
+
if (type !== "numeric" && (responseDef.unique || responseDef.duplicate === false)) {
|
|
18790
19072
|
const seen = new Set(nodes);
|
|
18791
19073
|
if (seen.size !== nodes.length) {
|
|
18792
19074
|
throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
|
|
@@ -18806,8 +19088,7 @@ ${stackTrace}`);
|
|
|
18806
19088
|
if (!delimBracketed) {
|
|
18807
19089
|
throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
|
|
18808
19090
|
}
|
|
18809
|
-
const
|
|
18810
|
-
const parts = value.split(new RegExp(`(?<!\\\\)${escapeRegex(delim)}`, "g")).map(n => n.replace(new RegExp(`\\\\${escapeRegex(delim)}`, "g"), delim));
|
|
19091
|
+
const parts = splitDelimited(value, delimBracketed);
|
|
18811
19092
|
if (parts.length !== 2 || parts[0] === "" || parts[1] === "") {
|
|
18812
19093
|
throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
|
|
18813
19094
|
}
|
|
@@ -18819,12 +19100,14 @@ ${stackTrace}`);
|
|
|
18819
19100
|
switch (type) {
|
|
18820
19101
|
case "numeric":
|
|
18821
19102
|
{
|
|
18822
|
-
|
|
18823
|
-
|
|
18824
|
-
|
|
18825
|
-
|
|
19103
|
+
if (node === "") {
|
|
19104
|
+
const bracketedRange = nodes.length >= 2 && !!responseDef.delimiter && pattern.includes(responseDef.delimiter);
|
|
19105
|
+
if (!bracketedRange) {
|
|
19106
|
+
throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
|
|
19107
|
+
}
|
|
19108
|
+
break;
|
|
18826
19109
|
}
|
|
18827
|
-
|
|
19110
|
+
checkSingle(node);
|
|
18828
19111
|
break;
|
|
18829
19112
|
}
|
|
18830
19113
|
case "performance":
|
|
@@ -18833,13 +19116,13 @@ ${stackTrace}`);
|
|
|
18833
19116
|
if (!delimBracketed) {
|
|
18834
19117
|
throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
|
|
18835
19118
|
}
|
|
18836
|
-
const
|
|
18837
|
-
const parts =
|
|
19119
|
+
const record = stripResponsePrefixes(node);
|
|
19120
|
+
const parts = splitFirstDelimited(record, delimBracketed);
|
|
18838
19121
|
if (parts.length !== 2) {
|
|
18839
19122
|
throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
|
|
18840
19123
|
}
|
|
18841
19124
|
const [part1, part2] = parts;
|
|
18842
|
-
if (part1 === ""
|
|
19125
|
+
if (part1 === "" && part2 === "") {
|
|
18843
19126
|
throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
|
|
18844
19127
|
}
|
|
18845
19128
|
if (part1 === void 0 || !fmt1.test(part1)) {
|
|
@@ -21625,7 +21908,7 @@ ${stackTrace}`);
|
|
|
21625
21908
|
checkValidResponseType(CMIElement, response_type, value, interaction_type) {
|
|
21626
21909
|
let nodes = [];
|
|
21627
21910
|
if (response_type?.delimiter) {
|
|
21628
|
-
nodes = String(value)
|
|
21911
|
+
nodes = splitDelimited(String(value), response_type.delimiter);
|
|
21629
21912
|
} else {
|
|
21630
21913
|
nodes[0] = value;
|
|
21631
21914
|
}
|
|
@@ -21723,26 +22006,33 @@ ${stackTrace}`);
|
|
|
21723
22006
|
nodes[i] = this.removeCorrectResponsePrefixes(CMIElement, nodes[i]);
|
|
21724
22007
|
}
|
|
21725
22008
|
if (response?.delimiter2) {
|
|
21726
|
-
const values = nodes[i].
|
|
22009
|
+
const values = interaction_type === "performance" ? splitFirstDelimited(nodes[i], response.delimiter2) : splitDelimited(nodes[i], response.delimiter2);
|
|
21727
22010
|
if (values.length === 2) {
|
|
21728
|
-
|
|
21729
|
-
if (!matches) {
|
|
22011
|
+
if (interaction_type === "performance" && values[0] === "" && values[1] === "") {
|
|
21730
22012
|
this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
|
|
21731
22013
|
} else {
|
|
21732
|
-
|
|
22014
|
+
const matches = values[0]?.match(formatRegex);
|
|
22015
|
+
if (!matches) {
|
|
21733
22016
|
this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
|
|
22017
|
+
} else {
|
|
22018
|
+
if (!response.format2 || !values[1]?.match(new RegExp(response.format2))) {
|
|
22019
|
+
this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
|
|
22020
|
+
}
|
|
21734
22021
|
}
|
|
21735
22022
|
}
|
|
21736
22023
|
} else {
|
|
21737
22024
|
this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
|
|
21738
22025
|
}
|
|
21739
22026
|
} else {
|
|
22027
|
+
if (interaction_type === "numeric" && nodes.length > 1 && nodes[i] === "" && !!response.delimiter && String(value).includes(response.delimiter)) {
|
|
22028
|
+
continue;
|
|
22029
|
+
}
|
|
21740
22030
|
const matches = nodes[i].match(formatRegex);
|
|
21741
22031
|
if (!matches && value !== "" || !matches && interaction_type === "true-false") {
|
|
21742
22032
|
this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
|
|
21743
22033
|
} else {
|
|
21744
22034
|
if (interaction_type === "numeric" && nodes.length > 1) {
|
|
21745
|
-
if (Number(nodes[0]) > Number(nodes[1])) {
|
|
22035
|
+
if (nodes[0] !== "" && nodes[1] !== "" && Number(nodes[0]) > Number(nodes[1])) {
|
|
21746
22036
|
this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
|
|
21747
22037
|
}
|
|
21748
22038
|
} else {
|
|
@@ -23421,6 +23711,10 @@ ${stackTrace}`);
|
|
|
23421
23711
|
value
|
|
23422
23712
|
}) : obj[key] = value;
|
|
23423
23713
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
23714
|
+
const NO_DEFAULT_2004_ELEMENTS = /* @__PURE__ */new Set(["cmi.suspend_data", "cmi.location", "cmi.scaled_passing_score", "cmi.max_time_allowed", "cmi.completion_threshold", "cmi.progress_measure", "cmi.score.scaled", "cmi.score.raw", "cmi.score.min", "cmi.score.max", "cmi.objectives.N.score.scaled", "cmi.objectives.N.score.raw", "cmi.objectives.N.score.min", "cmi.objectives.N.score.max", "cmi.objectives.N.progress_measure", "cmi.objectives.N.description", "cmi.interactions.N.weighting", "cmi.interactions.N.type", "cmi.interactions.N.timestamp", "cmi.interactions.N.result", "cmi.interactions.N.latency", "cmi.interactions.N.learner_response", "cmi.interactions.N.description", "cmi.comments_from_learner.N.timestamp"]);
|
|
23715
|
+
function normalizeCMIIndices(CMIElement) {
|
|
23716
|
+
return CMIElement.replace(/\.\d+(?=\.|$)/g, ".N");
|
|
23717
|
+
}
|
|
23424
23718
|
class Scorm2004API extends BaseAPI {
|
|
23425
23719
|
/**
|
|
23426
23720
|
* Constructor for SCORM 2004 API
|
|
@@ -23920,6 +24214,35 @@ ${stackTrace}`);
|
|
|
23920
24214
|
getCMIValue(CMIElement) {
|
|
23921
24215
|
return this._commonGetCMIValue("GetValue", true, CMIElement);
|
|
23922
24216
|
}
|
|
24217
|
+
/**
|
|
24218
|
+
* Raises 403 (VALUE_NOT_INITIALIZED) when an implemented, no-default element is
|
|
24219
|
+
* read before it has ever been set, per IEEE 1484.11.2 / SCORM 2004 RTE.
|
|
24220
|
+
*
|
|
24221
|
+
* Invoked by BaseAPI.getValue after an otherwise-successful resolution, so it
|
|
24222
|
+
* only sees values the data model already returned cleanly. The decision is
|
|
24223
|
+
* deliberately gated to this public boundary (never the getters) to keep
|
|
24224
|
+
* serialization/commit/rollup — which read the getters directly — unaffected.
|
|
24225
|
+
*
|
|
24226
|
+
* A 403 is raised only when all hold:
|
|
24227
|
+
* - the resolved value is "" — any non-empty value is by definition set,
|
|
24228
|
+
* including values written by internal sequencing/activity-tree paths that
|
|
24229
|
+
* bypass _commonSetCMIValue (those only ever write non-empty values);
|
|
24230
|
+
* - the element was never set — _setCMIElements records every successful
|
|
24231
|
+
* SetValue, loadFromJSON, and global-objective restore (all route through
|
|
24232
|
+
* _commonSetCMIValue), so an explicit SetValue(x, "") still reads back as
|
|
24233
|
+
* "" with code 0; and
|
|
24234
|
+
* - the element has no spec-defined default ({@link NO_DEFAULT_2004_ELEMENTS}).
|
|
24235
|
+
*
|
|
24236
|
+
* @param {string} CMIElement
|
|
24237
|
+
* @param {*} returnValue - the value getCMIValue resolved
|
|
24238
|
+
* @protected
|
|
24239
|
+
*/
|
|
24240
|
+
checkUninitializedGet(CMIElement, returnValue) {
|
|
24241
|
+
if (returnValue !== "") return;
|
|
24242
|
+
if (this._setCMIElements.has(CMIElement)) return;
|
|
24243
|
+
if (!NO_DEFAULT_2004_ELEMENTS.has(normalizeCMIIndices(CMIElement))) return;
|
|
24244
|
+
this.throwSCORMError(CMIElement, this._error_codes.VALUE_NOT_INITIALIZED ?? 403, `The data model element passed to GetValue (${CMIElement}) has not been initialized.`);
|
|
24245
|
+
}
|
|
23923
24246
|
/**
|
|
23924
24247
|
* Returns the message that corresponds to errorNumber.
|
|
23925
24248
|
* @param {(string|number)} errorNumber
|
|
@@ -23968,9 +24291,10 @@ ${stackTrace}`);
|
|
|
23968
24291
|
/**
|
|
23969
24292
|
* Attempts to store the data to the LMS
|
|
23970
24293
|
* @param {boolean} terminateCommit
|
|
24294
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
23971
24295
|
* @return {ResultObject}
|
|
23972
24296
|
*/
|
|
23973
|
-
storeData(terminateCommit) {
|
|
24297
|
+
storeData(terminateCommit, trigger) {
|
|
23974
24298
|
if (terminateCommit) {
|
|
23975
24299
|
if (this.cmi.mode === "normal") {
|
|
23976
24300
|
if (this.cmi.credit === "credit") {
|
|
@@ -24011,7 +24335,7 @@ ${stackTrace}`);
|
|
|
24011
24335
|
}
|
|
24012
24336
|
this._globalObjectiveManager.syncCmiToSequencingActivity(completionStatusEnum, successStatusEnum, scoreObject);
|
|
24013
24337
|
if (typeof this.settings.lmsCommitUrl === "string") {
|
|
24014
|
-
const result = this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit);
|
|
24338
|
+
const result = this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit, trigger);
|
|
24015
24339
|
if (navRequest && result.navRequest !== void 0 && result.navRequest !== "" && typeof result.navRequest === "string") {
|
|
24016
24340
|
const parsed = parseNavigationRequest(result.navRequest);
|
|
24017
24341
|
if (!parsed.valid) {
|