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/esm/scorm2004.js
CHANGED
|
@@ -210,6 +210,15 @@ function parseNavigationRequest(navRequest) {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
const appendQueryParam = (url, name, value) => {
|
|
214
|
+
const fragmentIndex = url.indexOf("#");
|
|
215
|
+
const baseUrl = fragmentIndex === -1 ? url : url.slice(0, fragmentIndex);
|
|
216
|
+
const fragment = fragmentIndex === -1 ? "" : url.slice(fragmentIndex);
|
|
217
|
+
const separator = baseUrl.includes("?") ? baseUrl.endsWith("?") || baseUrl.endsWith("&") ? "" : "&" : "?";
|
|
218
|
+
const queryParam = `${encodeURIComponent(name)}=${encodeURIComponent(String(value))}`;
|
|
219
|
+
return `${baseUrl}${separator}${queryParam}${fragment}`;
|
|
220
|
+
};
|
|
221
|
+
|
|
213
222
|
class BaseCMI {
|
|
214
223
|
/**
|
|
215
224
|
* Flag used during JSON serialization to allow getter access without initialization checks.
|
|
@@ -583,7 +592,10 @@ const scorm12_errors = {
|
|
|
583
592
|
INVALID_SET_VALUE: 402,
|
|
584
593
|
READ_ONLY_ELEMENT: 403,
|
|
585
594
|
TYPE_MISMATCH: 405,
|
|
586
|
-
|
|
595
|
+
// SCORM 1.2 has no out-of-range error code; an out-of-range value is reported
|
|
596
|
+
// as 405 (incorrect data type), per the SCORM 1.2 RTE error table and the ADL
|
|
597
|
+
// 1.2 CTS (DataModelValidator.checkScoreDecimal failure -> CMICategory 405).
|
|
598
|
+
VALUE_OUT_OF_RANGE: 405};
|
|
587
599
|
const scorm2004_errors = {
|
|
588
600
|
...global_errors,
|
|
589
601
|
INITIALIZATION_FAILED: 102,
|
|
@@ -729,6 +741,7 @@ const DefaultSettings = {
|
|
|
729
741
|
xhrWithCredentials: false,
|
|
730
742
|
fetchMode: "cors",
|
|
731
743
|
asyncModeBeaconBehavior: "never",
|
|
744
|
+
includeCommitSequence: false,
|
|
732
745
|
responseHandler: async function(response) {
|
|
733
746
|
if (typeof response !== "undefined") {
|
|
734
747
|
let httpResult = null;
|
|
@@ -940,6 +953,11 @@ const scorm2004_regex = {
|
|
|
940
953
|
progress_range: "0#1"
|
|
941
954
|
};
|
|
942
955
|
|
|
956
|
+
const PERFORMANCE_STEP_NAME = "^$|" + scorm2004_regex.CMIShortIdentifier;
|
|
957
|
+
const PERFORMANCE_CHARACTERSTRING = "(?![\\s\\S]*(?:\\[,\\]|\\[\\.\\]|\\[:\\]))[\\s\\S]{1,250}";
|
|
958
|
+
const PERFORMANCE_NUMERIC_RANGE = "(?:-?\\d+(?:\\.\\d+)?)?\\[:\\](?:-?\\d+(?:\\.\\d+)?)?";
|
|
959
|
+
const CR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_NUMERIC_RANGE + "|" + PERFORMANCE_CHARACTERSTRING + ")$";
|
|
960
|
+
const LR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_CHARACTERSTRING + ")$";
|
|
943
961
|
const LearnerResponses = {
|
|
944
962
|
"true-false": {
|
|
945
963
|
format: "^true$|^false$",
|
|
@@ -974,8 +992,8 @@ const LearnerResponses = {
|
|
|
974
992
|
unique: false
|
|
975
993
|
},
|
|
976
994
|
performance: {
|
|
977
|
-
format:
|
|
978
|
-
format2:
|
|
995
|
+
format: PERFORMANCE_STEP_NAME,
|
|
996
|
+
format2: LR_PERFORMANCE_STEP_ANSWER,
|
|
979
997
|
max: 250,
|
|
980
998
|
delimiter: "[,]",
|
|
981
999
|
delimiter2: "[.]",
|
|
@@ -1051,10 +1069,10 @@ const CorrectResponses = {
|
|
|
1051
1069
|
delimiter2: "[.]",
|
|
1052
1070
|
unique: false,
|
|
1053
1071
|
duplicate: false,
|
|
1054
|
-
// step_name
|
|
1055
|
-
format:
|
|
1056
|
-
// step_answer
|
|
1057
|
-
format2:
|
|
1072
|
+
// step_name: optional short_identifier_type
|
|
1073
|
+
format: PERFORMANCE_STEP_NAME,
|
|
1074
|
+
// step_answer: optional characterstring (spaces allowed) or numeric range
|
|
1075
|
+
format2: CR_PERFORMANCE_STEP_ANSWER
|
|
1058
1076
|
},
|
|
1059
1077
|
sequencing: {
|
|
1060
1078
|
max: 36,
|
|
@@ -1514,7 +1532,7 @@ class ScheduledCommit {
|
|
|
1514
1532
|
wrapper() {
|
|
1515
1533
|
if (!this._cancelled) {
|
|
1516
1534
|
if (this._API.isInitialized()) {
|
|
1517
|
-
(async () => await this._API.commit(this._callback))();
|
|
1535
|
+
(async () => await this._API.commit(this._callback, false, "autocommit"))();
|
|
1518
1536
|
}
|
|
1519
1537
|
}
|
|
1520
1538
|
}
|
|
@@ -4859,6 +4877,7 @@ class ActivityDeliveryService {
|
|
|
4859
4877
|
}
|
|
4860
4878
|
|
|
4861
4879
|
class AsynchronousHttpService {
|
|
4880
|
+
reportsRequestCompletion = true;
|
|
4862
4881
|
settings;
|
|
4863
4882
|
error_codes;
|
|
4864
4883
|
/**
|
|
@@ -4883,10 +4902,20 @@ class AsynchronousHttpService {
|
|
|
4883
4902
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
4884
4903
|
* @param {Function} apiLog - Function to log API messages with appropriate levels
|
|
4885
4904
|
* @param {Function} processListeners - Function to trigger event listeners for commit events
|
|
4905
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
4906
|
+
* @param {Function} onRequestComplete - Callback invoked after the background request settles
|
|
4886
4907
|
* @return {ResultObject} - Immediate optimistic success result
|
|
4887
4908
|
*/
|
|
4888
|
-
processHttpRequest(url, params, immediate = false, apiLog, processListeners) {
|
|
4889
|
-
this._performAsyncRequest(
|
|
4909
|
+
processHttpRequest(url, params, immediate = false, apiLog, processListeners, metadata, onRequestComplete) {
|
|
4910
|
+
this._performAsyncRequest(
|
|
4911
|
+
url,
|
|
4912
|
+
params,
|
|
4913
|
+
immediate,
|
|
4914
|
+
apiLog,
|
|
4915
|
+
processListeners,
|
|
4916
|
+
metadata,
|
|
4917
|
+
onRequestComplete
|
|
4918
|
+
);
|
|
4890
4919
|
return {
|
|
4891
4920
|
result: global_constants.SCORM_TRUE,
|
|
4892
4921
|
errorCode: 0
|
|
@@ -4899,11 +4928,14 @@ class AsynchronousHttpService {
|
|
|
4899
4928
|
* @param {boolean} immediate - Whether this is an immediate request
|
|
4900
4929
|
* @param apiLog - Function to log API messages
|
|
4901
4930
|
* @param {Function} processListeners - Function to process event listeners
|
|
4931
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
4932
|
+
* @param {Function} onRequestComplete - Callback invoked after the request settles
|
|
4902
4933
|
* @private
|
|
4903
4934
|
*/
|
|
4904
|
-
async _performAsyncRequest(url, params, immediate, apiLog, processListeners) {
|
|
4935
|
+
async _performAsyncRequest(url, params, immediate, apiLog, processListeners, metadata, onRequestComplete) {
|
|
4905
4936
|
try {
|
|
4906
|
-
const
|
|
4937
|
+
const handledParams = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
4938
|
+
const processedParams = handledParams;
|
|
4907
4939
|
let response;
|
|
4908
4940
|
if (immediate && this.settings.asyncModeBeaconBehavior !== "never") {
|
|
4909
4941
|
response = await this.performBeacon(url, processedParams);
|
|
@@ -4919,7 +4951,9 @@ class AsynchronousHttpService {
|
|
|
4919
4951
|
} catch (e) {
|
|
4920
4952
|
const message = e instanceof Error ? e.message : String(e);
|
|
4921
4953
|
apiLog("processHttpRequest", `Async request failed: ${message}`, LogLevelEnum.ERROR);
|
|
4922
|
-
processListeners("CommitError");
|
|
4954
|
+
processListeners("CommitError", void 0, this.error_codes.GENERAL_COMMIT_FAILURE || 391);
|
|
4955
|
+
} finally {
|
|
4956
|
+
onRequestComplete?.();
|
|
4923
4957
|
}
|
|
4924
4958
|
}
|
|
4925
4959
|
/**
|
|
@@ -5062,10 +5096,13 @@ class CMIValueAccessService {
|
|
|
5062
5096
|
}
|
|
5063
5097
|
/**
|
|
5064
5098
|
* Gets the appropriate error code for undefined data model elements.
|
|
5065
|
-
* SCORM 2004
|
|
5099
|
+
* Both SCORM 2004 and SCORM 1.2 use UNDEFINED_DATA_MODEL (401): an
|
|
5100
|
+
* unrecognized element is "Not implemented", not a general exception. SCORM
|
|
5101
|
+
* 1.2 previously returned GENERAL (101) here, which is non-conformant — the
|
|
5102
|
+
* ADL 1.2 CTS and the SCORM 1.2 RTE spec expect 401 for an unknown element.
|
|
5066
5103
|
*/
|
|
5067
|
-
getUndefinedDataModelErrorCode(
|
|
5068
|
-
return
|
|
5104
|
+
getUndefinedDataModelErrorCode() {
|
|
5105
|
+
return getErrorCode(this.context.errorCodes, "UNDEFINED_DATA_MODEL");
|
|
5069
5106
|
}
|
|
5070
5107
|
/**
|
|
5071
5108
|
* Sets a value on a CMI element path
|
|
@@ -5093,7 +5130,7 @@ class CMIValueAccessService {
|
|
|
5093
5130
|
let returnValue = global_constants.SCORM_FALSE;
|
|
5094
5131
|
let foundFirstIndex = false;
|
|
5095
5132
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
5096
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
5133
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
5097
5134
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
5098
5135
|
const attribute = structure[idx];
|
|
5099
5136
|
if (idx === structure.length - 1) {
|
|
@@ -5163,12 +5200,13 @@ class CMIValueAccessService {
|
|
|
5163
5200
|
);
|
|
5164
5201
|
return "";
|
|
5165
5202
|
}
|
|
5203
|
+
this.context.setLastErrorCode("0");
|
|
5166
5204
|
const structure = CMIElement.split(".");
|
|
5167
5205
|
let refObject = this.context.getDataModel();
|
|
5168
5206
|
let attribute = null;
|
|
5169
5207
|
const uninitializedErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) has not been initialized.`;
|
|
5170
5208
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
5171
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
5209
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
5172
5210
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
5173
5211
|
attribute = structure[idx];
|
|
5174
5212
|
const validationResult = this.validateGetAttribute(
|
|
@@ -5363,7 +5401,19 @@ class CMIValueAccessService {
|
|
|
5363
5401
|
if (!scorm2004) {
|
|
5364
5402
|
if (isFinalAttribute) {
|
|
5365
5403
|
if (typeof attribute === "undefined" || !this.context.checkObjectHasProperty(refObject, attribute)) {
|
|
5366
|
-
|
|
5404
|
+
if (attribute === "_children") {
|
|
5405
|
+
this.context.throwSCORMError(
|
|
5406
|
+
CMIElement,
|
|
5407
|
+
getErrorCode(this.context.errorCodes, "CHILDREN_ERROR")
|
|
5408
|
+
);
|
|
5409
|
+
} else if (attribute === "_count") {
|
|
5410
|
+
this.context.throwSCORMError(
|
|
5411
|
+
CMIElement,
|
|
5412
|
+
getErrorCode(this.context.errorCodes, "COUNT_ERROR")
|
|
5413
|
+
);
|
|
5414
|
+
} else {
|
|
5415
|
+
this.context.throwSCORMError(CMIElement, invalidErrorCode, invalidErrorMessage);
|
|
5416
|
+
}
|
|
5367
5417
|
return { error: true };
|
|
5368
5418
|
}
|
|
5369
5419
|
}
|
|
@@ -5868,8 +5918,9 @@ class EventService {
|
|
|
5868
5918
|
* @param {string} functionName - The name of the function that triggered the event
|
|
5869
5919
|
* @param {string} CMIElement - The CMI element that was affected
|
|
5870
5920
|
* @param {any} value - The value that was set
|
|
5921
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
5871
5922
|
*/
|
|
5872
|
-
processListeners(functionName, CMIElement, value) {
|
|
5923
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
5873
5924
|
this.apiLog(functionName, value, LogLevelEnum.INFO, CMIElement);
|
|
5874
5925
|
const listeners = this.listenerMap.get(functionName);
|
|
5875
5926
|
if (!listeners) return;
|
|
@@ -5894,9 +5945,17 @@ class EventService {
|
|
|
5894
5945
|
if (functionName.startsWith("Sequence")) {
|
|
5895
5946
|
listener.callback(value);
|
|
5896
5947
|
} else if (functionName === "CommitError") {
|
|
5897
|
-
|
|
5948
|
+
if (context !== void 0) {
|
|
5949
|
+
listener.callback(value, context);
|
|
5950
|
+
} else {
|
|
5951
|
+
listener.callback(value);
|
|
5952
|
+
}
|
|
5898
5953
|
} else if (functionName === "CommitSuccess") {
|
|
5899
|
-
|
|
5954
|
+
if (context !== void 0) {
|
|
5955
|
+
listener.callback(context);
|
|
5956
|
+
} else {
|
|
5957
|
+
listener.callback();
|
|
5958
|
+
}
|
|
5900
5959
|
} else {
|
|
5901
5960
|
listener.callback(CMIElement, value);
|
|
5902
5961
|
}
|
|
@@ -6030,16 +6089,19 @@ class OfflineStorageService {
|
|
|
6030
6089
|
* Store commit data offline
|
|
6031
6090
|
* @param {string} courseId - Identifier for the course
|
|
6032
6091
|
* @param {CommitObject} commitData - The data to store offline
|
|
6092
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
6033
6093
|
* @returns {ResultObject} - Result of the storage operation
|
|
6034
6094
|
*/
|
|
6035
|
-
storeOffline(courseId, commitData) {
|
|
6095
|
+
storeOffline(courseId, commitData, metadata) {
|
|
6036
6096
|
try {
|
|
6037
6097
|
const queueItem = {
|
|
6038
6098
|
id: `${courseId}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
6039
6099
|
courseId,
|
|
6040
6100
|
timestamp: Date.now(),
|
|
6041
6101
|
data: commitData,
|
|
6042
|
-
syncAttempts: 0
|
|
6102
|
+
syncAttempts: 0,
|
|
6103
|
+
...metadata?.isTerminateCommit !== void 0 ? { isTerminateCommit: metadata.isTerminateCommit } : {},
|
|
6104
|
+
...metadata?.sequence !== void 0 ? { sequence: metadata.sequence } : {}
|
|
6043
6105
|
};
|
|
6044
6106
|
const currentQueue = this.getFromStorage(this.syncQueue) || [];
|
|
6045
6107
|
currentQueue.push(queueItem);
|
|
@@ -6118,7 +6180,10 @@ class OfflineStorageService {
|
|
|
6118
6180
|
continue;
|
|
6119
6181
|
}
|
|
6120
6182
|
try {
|
|
6121
|
-
const syncResult = await this.sendDataToLMS(item.data
|
|
6183
|
+
const syncResult = await this.sendDataToLMS(item.data, {
|
|
6184
|
+
...item.isTerminateCommit !== void 0 ? { isTerminateCommit: item.isTerminateCommit } : {},
|
|
6185
|
+
...item.sequence !== void 0 ? { sequence: item.sequence } : {}
|
|
6186
|
+
});
|
|
6122
6187
|
if (syncResult.result === true || syncResult.result === global_constants.SCORM_TRUE) {
|
|
6123
6188
|
this.apiLog(
|
|
6124
6189
|
"OfflineStorageService",
|
|
@@ -6165,17 +6230,25 @@ class OfflineStorageService {
|
|
|
6165
6230
|
/**
|
|
6166
6231
|
* Send data to the LMS when online
|
|
6167
6232
|
* @param {CommitObject} data - The data to send to the LMS
|
|
6233
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
6168
6234
|
* @returns {Promise<ResultObject>} - Result of the sync operation
|
|
6169
6235
|
*/
|
|
6170
|
-
async sendDataToLMS(data) {
|
|
6171
|
-
|
|
6236
|
+
async sendDataToLMS(data, metadata) {
|
|
6237
|
+
const configuredCommitUrl = this.settings.lmsCommitUrl;
|
|
6238
|
+
if (!configuredCommitUrl) {
|
|
6172
6239
|
return {
|
|
6173
6240
|
result: global_constants.SCORM_FALSE,
|
|
6174
6241
|
errorCode: this.error_codes.GENERAL || 101
|
|
6175
6242
|
};
|
|
6176
6243
|
}
|
|
6177
6244
|
try {
|
|
6178
|
-
const
|
|
6245
|
+
const lmsCommitUrl = String(configuredCommitUrl);
|
|
6246
|
+
const processedData = this.settings.requestHandler(data, {
|
|
6247
|
+
isTerminateCommit: metadata?.isTerminateCommit ?? false,
|
|
6248
|
+
trigger: "offline-replay",
|
|
6249
|
+
...metadata?.sequence !== void 0 ? { sequence: metadata.sequence } : {}
|
|
6250
|
+
});
|
|
6251
|
+
const requestUrl = metadata?.isTerminateCommit && this.settings.terminateCommitParam ? appendQueryParam(lmsCommitUrl, this.settings.terminateCommitParam, "true") : lmsCommitUrl;
|
|
6179
6252
|
const init = {
|
|
6180
6253
|
method: "POST",
|
|
6181
6254
|
mode: this.settings.fetchMode,
|
|
@@ -6188,7 +6261,7 @@ class OfflineStorageService {
|
|
|
6188
6261
|
if (this.settings.xhrWithCredentials) {
|
|
6189
6262
|
init.credentials = "include";
|
|
6190
6263
|
}
|
|
6191
|
-
const response = await fetch(
|
|
6264
|
+
const response = await fetch(requestUrl, init);
|
|
6192
6265
|
const result = typeof this.settings.responseHandler === "function" ? await this.settings.responseHandler(response) : await response.json();
|
|
6193
6266
|
if (response.status >= 200 && response.status <= 299 && (result.result === true || result.result === global_constants.SCORM_TRUE)) {
|
|
6194
6267
|
if (!Object.hasOwnProperty.call(result, "errorCode")) {
|
|
@@ -15153,6 +15226,8 @@ class SynchronousHttpService {
|
|
|
15153
15226
|
* @param {boolean} immediate - Whether this is a termination commit (use sendBeacon)
|
|
15154
15227
|
* @param {Function} _apiLog - Function to log API messages (unused in synchronous mode - errors returned directly)
|
|
15155
15228
|
* @param {Function} _processListeners - Function to trigger event listeners (unused in synchronous mode - no async events)
|
|
15229
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
15230
|
+
* @param {Function} _onRequestComplete - Completion callback (unused because requests settle before return)
|
|
15156
15231
|
* @return {ResultObject} - The result of the request (synchronous)
|
|
15157
15232
|
*
|
|
15158
15233
|
* @remarks
|
|
@@ -15162,21 +15237,23 @@ class SynchronousHttpService {
|
|
|
15162
15237
|
* - No async events need to be triggered (CommitSuccess/CommitError) since results are synchronous
|
|
15163
15238
|
* - AsynchronousHttpService uses these parameters to handle background request results
|
|
15164
15239
|
*/
|
|
15165
|
-
processHttpRequest(url, params, immediate = false, _apiLog, _processListeners) {
|
|
15240
|
+
processHttpRequest(url, params, immediate = false, _apiLog, _processListeners, metadata, _onRequestComplete) {
|
|
15166
15241
|
if (immediate) {
|
|
15167
|
-
return this._handleImmediateRequest(url, params);
|
|
15242
|
+
return this._handleImmediateRequest(url, params, metadata);
|
|
15168
15243
|
}
|
|
15169
|
-
return this._performSyncXHR(url, params);
|
|
15244
|
+
return this._performSyncXHR(url, params, metadata);
|
|
15170
15245
|
}
|
|
15171
15246
|
/**
|
|
15172
15247
|
* Handles an immediate request using sendBeacon
|
|
15173
15248
|
* @param {string} url - The URL to send the request to
|
|
15174
15249
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
15250
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
15175
15251
|
* @return {ResultObject} - The result based on beacon success
|
|
15176
15252
|
* @private
|
|
15177
15253
|
*/
|
|
15178
|
-
_handleImmediateRequest(url, params) {
|
|
15179
|
-
const
|
|
15254
|
+
_handleImmediateRequest(url, params, metadata) {
|
|
15255
|
+
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
15256
|
+
const requestPayload = handledPayload ?? params;
|
|
15180
15257
|
const { body } = this._prepareRequestBody(requestPayload);
|
|
15181
15258
|
const beaconSuccess = navigator.sendBeacon(
|
|
15182
15259
|
url,
|
|
@@ -15191,11 +15268,13 @@ class SynchronousHttpService {
|
|
|
15191
15268
|
* Performs a synchronous XMLHttpRequest
|
|
15192
15269
|
* @param {string} url - The URL to send the request to
|
|
15193
15270
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
15271
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
15194
15272
|
* @return {ResultObject} - The result of the request
|
|
15195
15273
|
* @private
|
|
15196
15274
|
*/
|
|
15197
|
-
_performSyncXHR(url, params) {
|
|
15198
|
-
const
|
|
15275
|
+
_performSyncXHR(url, params, metadata) {
|
|
15276
|
+
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
15277
|
+
const requestPayload = handledPayload ?? params;
|
|
15199
15278
|
const { body, contentType } = this._prepareRequestBody(requestPayload);
|
|
15200
15279
|
const xhr = new XMLHttpRequest();
|
|
15201
15280
|
xhr.open("POST", url, false);
|
|
@@ -15274,9 +15353,15 @@ class ValidationService {
|
|
|
15274
15353
|
* @param {number} invalidTypeCode - The error code for invalid type
|
|
15275
15354
|
* @param {number} invalidRangeCode - The error code for invalid range
|
|
15276
15355
|
* @param {typeof BaseScormValidationError} errorClass - The error class to use for validation errors
|
|
15356
|
+
* @param {boolean} allowEmptyString - When true, an empty string is accepted (clears the value).
|
|
15357
|
+
* SCORM 1.2 score elements may be blank per the ADL 1.2 CTS
|
|
15358
|
+
* (DataModelValidator.checkScoreDecimal treats blank as valid).
|
|
15277
15359
|
* @return {boolean} - True if validation passes, throws an error otherwise
|
|
15278
15360
|
*/
|
|
15279
|
-
validateScore(CMIElement, value, decimalRegex, scoreRange, invalidTypeCode, invalidRangeCode, errorClass) {
|
|
15361
|
+
validateScore(CMIElement, value, decimalRegex, scoreRange, invalidTypeCode, invalidRangeCode, errorClass, allowEmptyString = false) {
|
|
15362
|
+
if (allowEmptyString && value === "") {
|
|
15363
|
+
return true;
|
|
15364
|
+
}
|
|
15280
15365
|
return checkValidFormat(CMIElement, value, decimalRegex, invalidTypeCode, errorClass) && (!scoreRange || checkValidRange(CMIElement, value, scoreRange, invalidRangeCode, errorClass));
|
|
15281
15366
|
}
|
|
15282
15367
|
/**
|
|
@@ -15350,6 +15435,21 @@ class BaseAPI {
|
|
|
15350
15435
|
_offlineStorageService;
|
|
15351
15436
|
_cmiValueAccessService;
|
|
15352
15437
|
_courseId = "";
|
|
15438
|
+
_pendingCommitCount = 0;
|
|
15439
|
+
/**
|
|
15440
|
+
* Monotonic sequence for commits captured by this API instance. It is
|
|
15441
|
+
* intentionally not reset by reset().
|
|
15442
|
+
*/
|
|
15443
|
+
_commitSequence = 0;
|
|
15444
|
+
_commitSettleWaiters = [];
|
|
15445
|
+
/**
|
|
15446
|
+
* Canonical paths of every CMI element that has been explicitly assigned a
|
|
15447
|
+
* value via SetValue / loadFromJSON (both funnel through _commonSetCMIValue).
|
|
15448
|
+
* Used by standards that must tell "implemented but never set" apart from a
|
|
15449
|
+
* legitimately empty value when answering GetValue (SCORM 2004 error 403).
|
|
15450
|
+
* Cleared on reset so a fresh SCO attempt starts with nothing "set".
|
|
15451
|
+
*/
|
|
15452
|
+
_setCMIElements = /* @__PURE__ */ new Set();
|
|
15353
15453
|
/**
|
|
15354
15454
|
* Constructor for Base API class. Sets some shared API fields, as well as
|
|
15355
15455
|
* sets up options for the API.
|
|
@@ -15535,6 +15635,7 @@ class BaseAPI {
|
|
|
15535
15635
|
this.lastErrorCode = "0";
|
|
15536
15636
|
this._eventService.reset();
|
|
15537
15637
|
this.startingData = {};
|
|
15638
|
+
this._setCMIElements.clear();
|
|
15538
15639
|
if (this._offlineStorageService) {
|
|
15539
15640
|
this._offlineStorageService.updateSettings(this.settings);
|
|
15540
15641
|
if (settings?.courseId) {
|
|
@@ -15619,6 +15720,52 @@ class BaseAPI {
|
|
|
15619
15720
|
this._loggingService?.setLogHandler(settings.onLogMessage);
|
|
15620
15721
|
}
|
|
15621
15722
|
}
|
|
15723
|
+
/**
|
|
15724
|
+
* Gets the number of captured commit requests that have not yet settled.
|
|
15725
|
+
*
|
|
15726
|
+
* @return {number} The number of in-flight commits
|
|
15727
|
+
*/
|
|
15728
|
+
get pendingCommitCount() {
|
|
15729
|
+
return this._pendingCommitCount;
|
|
15730
|
+
}
|
|
15731
|
+
/**
|
|
15732
|
+
* Resolves when all currently in-flight commits have settled. A timeout is
|
|
15733
|
+
* best-effort: the promise resolves when it elapses even if commits remain,
|
|
15734
|
+
* and callers can inspect pendingCommitCount afterward to detect that case.
|
|
15735
|
+
*
|
|
15736
|
+
* @param {Object} [options] - Settle options
|
|
15737
|
+
* @param {number} [options.timeoutMs] - Maximum time to wait in milliseconds
|
|
15738
|
+
* @return {Promise<void>} A promise that resolves after the drain or timeout
|
|
15739
|
+
*/
|
|
15740
|
+
whenCommitsSettled(options) {
|
|
15741
|
+
if (this._pendingCommitCount === 0) {
|
|
15742
|
+
return Promise.resolve();
|
|
15743
|
+
}
|
|
15744
|
+
return new Promise((resolve) => {
|
|
15745
|
+
const waiter = { resolve };
|
|
15746
|
+
if (options?.timeoutMs !== void 0) {
|
|
15747
|
+
waiter.timeoutId = setTimeout(() => {
|
|
15748
|
+
const waiterIndex = this._commitSettleWaiters.indexOf(waiter);
|
|
15749
|
+
if (waiterIndex === -1) {
|
|
15750
|
+
return;
|
|
15751
|
+
}
|
|
15752
|
+
this._commitSettleWaiters.splice(waiterIndex, 1);
|
|
15753
|
+
resolve();
|
|
15754
|
+
}, options.timeoutMs);
|
|
15755
|
+
}
|
|
15756
|
+
this._commitSettleWaiters.push(waiter);
|
|
15757
|
+
});
|
|
15758
|
+
}
|
|
15759
|
+
/** Resolve and clear every waiter after the pending count reaches zero. */
|
|
15760
|
+
_flushCommitSettleWaiters() {
|
|
15761
|
+
const waiters = this._commitSettleWaiters.splice(0);
|
|
15762
|
+
for (const waiter of waiters) {
|
|
15763
|
+
if (waiter.timeoutId !== void 0) {
|
|
15764
|
+
clearTimeout(waiter.timeoutId);
|
|
15765
|
+
}
|
|
15766
|
+
waiter.resolve();
|
|
15767
|
+
}
|
|
15768
|
+
}
|
|
15622
15769
|
/**
|
|
15623
15770
|
* Terminates the current run of the API
|
|
15624
15771
|
* @param {string} callbackName
|
|
@@ -15639,7 +15786,7 @@ class BaseAPI {
|
|
|
15639
15786
|
} else {
|
|
15640
15787
|
stateCheckPassed = true;
|
|
15641
15788
|
this.processListeners("BeforeTerminate");
|
|
15642
|
-
const result = this.storeData(true);
|
|
15789
|
+
const result = this.storeData(true, "terminate");
|
|
15643
15790
|
if ((result.errorCode ?? 0) > 0) {
|
|
15644
15791
|
if (result.errorMessage) {
|
|
15645
15792
|
this.apiLog(
|
|
@@ -15691,6 +15838,9 @@ class BaseAPI {
|
|
|
15691
15838
|
} catch (e) {
|
|
15692
15839
|
returnValue = this.handleValueAccessException(CMIElement, e, returnValue);
|
|
15693
15840
|
}
|
|
15841
|
+
if (this.lastErrorCode === "0") {
|
|
15842
|
+
this.checkUninitializedGet(CMIElement, returnValue);
|
|
15843
|
+
}
|
|
15694
15844
|
this.processListeners(callbackName, CMIElement);
|
|
15695
15845
|
}
|
|
15696
15846
|
this.apiLog(callbackName, ": returned: " + returnValue, LogLevelEnum.INFO, CMIElement);
|
|
@@ -15700,6 +15850,10 @@ class BaseAPI {
|
|
|
15700
15850
|
if (this.lastErrorCode === "0") {
|
|
15701
15851
|
this.clearSCORMError(returnValue);
|
|
15702
15852
|
}
|
|
15853
|
+
const rawReturn = returnValue;
|
|
15854
|
+
if (typeof rawReturn === "number" || typeof rawReturn === "boolean") {
|
|
15855
|
+
return String(rawReturn);
|
|
15856
|
+
}
|
|
15703
15857
|
return returnValue;
|
|
15704
15858
|
}
|
|
15705
15859
|
/**
|
|
@@ -15752,9 +15906,10 @@ class BaseAPI {
|
|
|
15752
15906
|
* Orders LMS to store all content parameters
|
|
15753
15907
|
* @param {string} callbackName
|
|
15754
15908
|
* @param {boolean} checkTerminated
|
|
15909
|
+
* @param {CommitTrigger} trigger - What initiated the commit
|
|
15755
15910
|
* @return {string}
|
|
15756
15911
|
*/
|
|
15757
|
-
commit(callbackName, checkTerminated = false) {
|
|
15912
|
+
commit(callbackName, checkTerminated = false, trigger = "manual") {
|
|
15758
15913
|
this.clearScheduledCommit();
|
|
15759
15914
|
let returnValue = global_constants.SCORM_TRUE;
|
|
15760
15915
|
if (this.isNotInitialized()) {
|
|
@@ -15766,8 +15921,9 @@ class BaseAPI {
|
|
|
15766
15921
|
this.throwSCORMError("api", errorCode);
|
|
15767
15922
|
if (errorCode === 143) returnValue = global_constants.SCORM_FALSE;
|
|
15768
15923
|
} else {
|
|
15769
|
-
const result = this.storeData(false);
|
|
15770
|
-
|
|
15924
|
+
const result = this.storeData(false, trigger);
|
|
15925
|
+
const errorCode = result.errorCode ?? 0;
|
|
15926
|
+
if (errorCode > 0) {
|
|
15771
15927
|
if (result.errorMessage) {
|
|
15772
15928
|
this.apiLog(
|
|
15773
15929
|
"commit",
|
|
@@ -15782,12 +15938,12 @@ class BaseAPI {
|
|
|
15782
15938
|
LogLevelEnum.DEBUG
|
|
15783
15939
|
);
|
|
15784
15940
|
}
|
|
15785
|
-
this.throwSCORMError("api",
|
|
15941
|
+
this.throwSCORMError("api", errorCode);
|
|
15786
15942
|
}
|
|
15787
15943
|
const resultValue = result?.result ?? global_constants.SCORM_FALSE;
|
|
15788
15944
|
returnValue = typeof resultValue === "boolean" ? String(resultValue) : resultValue;
|
|
15789
15945
|
this.apiLog(callbackName, " Result: " + returnValue, LogLevelEnum.DEBUG, "HttpRequest");
|
|
15790
|
-
if (checkTerminated) this.lastErrorCode = "0";
|
|
15946
|
+
if (checkTerminated && errorCode === 0) this.lastErrorCode = "0";
|
|
15791
15947
|
this.processListeners(callbackName);
|
|
15792
15948
|
if (this.settings.enableOfflineSupport && this._offlineStorageService && this._offlineStorageService.isDeviceOnline() && this._courseId) {
|
|
15793
15949
|
this._offlineStorageService.hasPendingOfflineData(this._courseId).then((hasPendingData) => {
|
|
@@ -15996,7 +16152,16 @@ class BaseAPI {
|
|
|
15996
16152
|
* @return {string}
|
|
15997
16153
|
*/
|
|
15998
16154
|
_commonSetCMIValue(methodName, scorm2004, CMIElement, value) {
|
|
15999
|
-
|
|
16155
|
+
const result = this._cmiValueAccessService.setCMIValue(
|
|
16156
|
+
methodName,
|
|
16157
|
+
scorm2004,
|
|
16158
|
+
CMIElement,
|
|
16159
|
+
value
|
|
16160
|
+
);
|
|
16161
|
+
if (result === global_constants.SCORM_TRUE) {
|
|
16162
|
+
this._setCMIElements.add(CMIElement);
|
|
16163
|
+
}
|
|
16164
|
+
return result;
|
|
16000
16165
|
}
|
|
16001
16166
|
/**
|
|
16002
16167
|
* Gets a value from the CMI Object.
|
|
@@ -16010,6 +16175,18 @@ class BaseAPI {
|
|
|
16010
16175
|
_commonGetCMIValue(methodName, scorm2004, CMIElement) {
|
|
16011
16176
|
return this._cmiValueAccessService.getCMIValue(methodName, scorm2004, CMIElement);
|
|
16012
16177
|
}
|
|
16178
|
+
/**
|
|
16179
|
+
* Hook invoked by getValue after a successful resolution. Standards that must
|
|
16180
|
+
* distinguish "implemented but never set, no default value" from a
|
|
16181
|
+
* legitimately empty value override this to raise VALUE_NOT_INITIALIZED.
|
|
16182
|
+
* Default is a no-op, so SCORM 1.2 / AICC keep returning "" with code 0.
|
|
16183
|
+
*
|
|
16184
|
+
* @param {string} _CMIElement - the element that was read
|
|
16185
|
+
* @param {any} _returnValue - the value getCMIValue resolved
|
|
16186
|
+
* @protected
|
|
16187
|
+
*/
|
|
16188
|
+
checkUninitializedGet(_CMIElement, _returnValue) {
|
|
16189
|
+
}
|
|
16013
16190
|
/**
|
|
16014
16191
|
* Returns true if the API's current state is STATE_INITIALIZED
|
|
16015
16192
|
*
|
|
@@ -16092,9 +16269,14 @@ class BaseAPI {
|
|
|
16092
16269
|
* @param {string} functionName - The name of the function/event that occurred
|
|
16093
16270
|
* @param {string} CMIElement - Optional CMI element involved in the event
|
|
16094
16271
|
* @param {any} value - Optional value associated with the event
|
|
16272
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
16095
16273
|
*/
|
|
16096
|
-
processListeners(functionName, CMIElement, value) {
|
|
16097
|
-
|
|
16274
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
16275
|
+
if (context !== void 0) {
|
|
16276
|
+
this._eventService.processListeners(functionName, CMIElement, value, context);
|
|
16277
|
+
} else {
|
|
16278
|
+
this._eventService.processListeners(functionName, CMIElement, value);
|
|
16279
|
+
}
|
|
16098
16280
|
}
|
|
16099
16281
|
/**
|
|
16100
16282
|
* Throws a SCORM error with the specified error number and optional message.
|
|
@@ -16227,36 +16409,106 @@ class BaseAPI {
|
|
|
16227
16409
|
* @param {string} url - The URL to send the request to
|
|
16228
16410
|
* @param {CommitObject | StringKeyMap | Array<any>} params - The parameters to send
|
|
16229
16411
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
16412
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
16230
16413
|
* @returns {ResultObject} - The result of the request
|
|
16231
16414
|
*/
|
|
16232
|
-
processHttpRequest(url, params, immediate = false) {
|
|
16233
|
-
|
|
16234
|
-
|
|
16235
|
-
|
|
16236
|
-
|
|
16237
|
-
|
|
16238
|
-
)
|
|
16239
|
-
|
|
16240
|
-
|
|
16241
|
-
|
|
16415
|
+
processHttpRequest(url, params, immediate = false, trigger) {
|
|
16416
|
+
const sequence = ++this._commitSequence;
|
|
16417
|
+
this._pendingCommitCount += 1;
|
|
16418
|
+
let settled = false;
|
|
16419
|
+
let completionDeferred = false;
|
|
16420
|
+
const settle = () => {
|
|
16421
|
+
if (settled) {
|
|
16422
|
+
return;
|
|
16423
|
+
}
|
|
16424
|
+
settled = true;
|
|
16425
|
+
this._pendingCommitCount -= 1;
|
|
16426
|
+
if (this._pendingCommitCount === 0) {
|
|
16427
|
+
this._flushCommitSettleWaiters();
|
|
16428
|
+
}
|
|
16429
|
+
};
|
|
16430
|
+
try {
|
|
16431
|
+
const resolvedTrigger = trigger ?? (immediate ? "terminate" : "manual");
|
|
16432
|
+
let finalParams = params;
|
|
16433
|
+
if (immediate && this.settings.terminateCommitPayloadField) {
|
|
16434
|
+
const field = this.settings.terminateCommitPayloadField;
|
|
16435
|
+
if (Array.isArray(finalParams)) {
|
|
16436
|
+
finalParams = [...finalParams, `${encodeURIComponent(field)}=true`];
|
|
16437
|
+
} else if (finalParams && typeof finalParams === "object") {
|
|
16438
|
+
finalParams = { ...finalParams, [field]: true };
|
|
16439
|
+
}
|
|
16440
|
+
}
|
|
16441
|
+
if (this.settings.includeCommitSequence === true) {
|
|
16442
|
+
if (Array.isArray(finalParams)) {
|
|
16443
|
+
finalParams = [...finalParams, `commitSequence=${sequence}`];
|
|
16444
|
+
} else if (finalParams && typeof finalParams === "object") {
|
|
16445
|
+
finalParams = { ...finalParams, commitSequence: sequence };
|
|
16446
|
+
}
|
|
16447
|
+
}
|
|
16448
|
+
const finalUrl = immediate && this.settings.terminateCommitParam ? appendQueryParam(url, this.settings.terminateCommitParam, "true") : url;
|
|
16449
|
+
const metadata = {
|
|
16450
|
+
isTerminateCommit: immediate,
|
|
16451
|
+
trigger: resolvedTrigger,
|
|
16452
|
+
sequence
|
|
16453
|
+
};
|
|
16454
|
+
const context = {
|
|
16455
|
+
url: finalUrl,
|
|
16456
|
+
trigger: resolvedTrigger,
|
|
16457
|
+
isTerminateCommit: immediate,
|
|
16458
|
+
sequence
|
|
16459
|
+
};
|
|
16460
|
+
if (this.settings.enableOfflineSupport && this._offlineStorageService && !this._offlineStorageService.isDeviceOnline() && this._courseId) {
|
|
16242
16461
|
this.apiLog(
|
|
16243
16462
|
"processHttpRequest",
|
|
16244
|
-
"
|
|
16245
|
-
LogLevelEnum.
|
|
16463
|
+
"Device is offline, storing data locally",
|
|
16464
|
+
LogLevelEnum.INFO
|
|
16246
16465
|
);
|
|
16247
|
-
|
|
16248
|
-
|
|
16249
|
-
|
|
16250
|
-
|
|
16466
|
+
if (finalParams && typeof finalParams === "object" && "cmi" in finalParams) {
|
|
16467
|
+
return this._offlineStorageService.storeOffline(
|
|
16468
|
+
this._courseId,
|
|
16469
|
+
finalParams,
|
|
16470
|
+
{ isTerminateCommit: immediate, sequence }
|
|
16471
|
+
);
|
|
16472
|
+
} else {
|
|
16473
|
+
this.apiLog(
|
|
16474
|
+
"processHttpRequest",
|
|
16475
|
+
"Invalid commit data format for offline storage",
|
|
16476
|
+
LogLevelEnum.ERROR
|
|
16477
|
+
);
|
|
16478
|
+
return {
|
|
16479
|
+
result: global_constants.SCORM_FALSE,
|
|
16480
|
+
errorCode: this._error_codes.GENERAL ?? 101
|
|
16481
|
+
};
|
|
16482
|
+
}
|
|
16483
|
+
}
|
|
16484
|
+
const apiLog = (functionName, message, level, element) => this.apiLog(functionName, message, level, element);
|
|
16485
|
+
const processListeners = (functionName, CMIElement, value) => {
|
|
16486
|
+
if (functionName === "CommitSuccess" || functionName === "CommitError") {
|
|
16487
|
+
if (functionName === "CommitError" && typeof value === "number") {
|
|
16488
|
+
context.errorCode = value;
|
|
16489
|
+
}
|
|
16490
|
+
settle();
|
|
16491
|
+
this.processListeners(functionName, CMIElement, value, context);
|
|
16492
|
+
} else {
|
|
16493
|
+
this.processListeners(functionName, CMIElement, value);
|
|
16494
|
+
}
|
|
16495
|
+
};
|
|
16496
|
+
const result = this._httpService.processHttpRequest(
|
|
16497
|
+
finalUrl,
|
|
16498
|
+
finalParams,
|
|
16499
|
+
immediate,
|
|
16500
|
+
apiLog,
|
|
16501
|
+
processListeners,
|
|
16502
|
+
metadata,
|
|
16503
|
+
settle
|
|
16504
|
+
);
|
|
16505
|
+
completionDeferred = this._httpService.reportsRequestCompletion === true;
|
|
16506
|
+
return result;
|
|
16507
|
+
} finally {
|
|
16508
|
+
if (!completionDeferred) {
|
|
16509
|
+
settle();
|
|
16251
16510
|
}
|
|
16252
16511
|
}
|
|
16253
|
-
return this._httpService.processHttpRequest(
|
|
16254
|
-
url,
|
|
16255
|
-
params,
|
|
16256
|
-
immediate,
|
|
16257
|
-
(functionName, message, level, element) => this.apiLog(functionName, message, level, element),
|
|
16258
|
-
(functionName, CMIElement, value) => this.processListeners(functionName, CMIElement, value)
|
|
16259
|
-
);
|
|
16260
16512
|
}
|
|
16261
16513
|
/**
|
|
16262
16514
|
* Schedules a commit operation to occur after a specified delay.
|
|
@@ -16559,6 +16811,49 @@ class CMILearnerPreference extends BaseCMI {
|
|
|
16559
16811
|
}
|
|
16560
16812
|
}
|
|
16561
16813
|
|
|
16814
|
+
function stripBrackets(delim) {
|
|
16815
|
+
return delim.replace(/[[\]]/g, "");
|
|
16816
|
+
}
|
|
16817
|
+
function escapeRegex(s) {
|
|
16818
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16819
|
+
}
|
|
16820
|
+
function splitDelimited(value, bracketed) {
|
|
16821
|
+
if (!bracketed) {
|
|
16822
|
+
return [value];
|
|
16823
|
+
}
|
|
16824
|
+
if (value.includes(bracketed)) {
|
|
16825
|
+
return value.split(bracketed);
|
|
16826
|
+
}
|
|
16827
|
+
const bare = stripBrackets(bracketed);
|
|
16828
|
+
if (!bare) {
|
|
16829
|
+
return [value];
|
|
16830
|
+
}
|
|
16831
|
+
const splitRe = new RegExp(`(?<!\\\\)${escapeRegex(bare)}`, "g");
|
|
16832
|
+
const unescapeRe = new RegExp(`\\\\${escapeRegex(bare)}`, "g");
|
|
16833
|
+
return value.split(splitRe).map((part) => part.replace(unescapeRe, bare));
|
|
16834
|
+
}
|
|
16835
|
+
function splitFirstDelimited(value, bracketed) {
|
|
16836
|
+
if (!bracketed) {
|
|
16837
|
+
return [value];
|
|
16838
|
+
}
|
|
16839
|
+
if (value.includes(bracketed)) {
|
|
16840
|
+
const idx = value.indexOf(bracketed);
|
|
16841
|
+
return [value.slice(0, idx), value.slice(idx + bracketed.length)];
|
|
16842
|
+
}
|
|
16843
|
+
const bare = stripBrackets(bracketed);
|
|
16844
|
+
if (!bare) {
|
|
16845
|
+
return [value];
|
|
16846
|
+
}
|
|
16847
|
+
const splitRe = new RegExp(`(?<!\\\\)${escapeRegex(bare)}`);
|
|
16848
|
+
const unescapeRe = new RegExp(`\\\\${escapeRegex(bare)}`, "g");
|
|
16849
|
+
const parts = value.split(splitRe);
|
|
16850
|
+
const first = (parts[0] ?? "").replace(unescapeRe, bare);
|
|
16851
|
+
if (parts.length === 1) {
|
|
16852
|
+
return [first];
|
|
16853
|
+
}
|
|
16854
|
+
return [first, parts.slice(1).join(bare).replace(unescapeRe, bare)];
|
|
16855
|
+
}
|
|
16856
|
+
|
|
16562
16857
|
class CMIInteractions extends CMIArray {
|
|
16563
16858
|
/**
|
|
16564
16859
|
* Constructor for `cmi.interactions` Array
|
|
@@ -16768,8 +17063,7 @@ class CMIInteractionsObject extends BaseCMI {
|
|
|
16768
17063
|
const response_type = LearnerResponses[this.type];
|
|
16769
17064
|
if (response_type) {
|
|
16770
17065
|
if (response_type?.delimiter) {
|
|
16771
|
-
|
|
16772
|
-
nodes = learner_response.split(delimiter);
|
|
17066
|
+
nodes = splitDelimited(learner_response, response_type.delimiter);
|
|
16773
17067
|
} else {
|
|
16774
17068
|
nodes[0] = learner_response;
|
|
16775
17069
|
}
|
|
@@ -16777,10 +17071,10 @@ class CMIInteractionsObject extends BaseCMI {
|
|
|
16777
17071
|
const formatRegex = new RegExp(response_type.format);
|
|
16778
17072
|
for (let i = 0; i < nodes.length; i++) {
|
|
16779
17073
|
if (response_type?.delimiter2) {
|
|
16780
|
-
const
|
|
16781
|
-
const values =
|
|
17074
|
+
const node = nodes[i] ?? "";
|
|
17075
|
+
const values = this.type === "performance" ? splitFirstDelimited(node, response_type.delimiter2) : splitDelimited(node, response_type.delimiter2);
|
|
16782
17076
|
if (values?.length === 2) {
|
|
16783
|
-
if (this.type === "performance" &&
|
|
17077
|
+
if (this.type === "performance" && values[0] === "" && values[1] === "") {
|
|
16784
17078
|
throw new Scorm2004ValidationError(
|
|
16785
17079
|
this._cmi_element + ".learner_response",
|
|
16786
17080
|
scorm2004_errors.TYPE_MISMATCH
|
|
@@ -16999,30 +17293,13 @@ class CMIInteractionsObjectivesObject extends BaseCMI {
|
|
|
16999
17293
|
return result;
|
|
17000
17294
|
}
|
|
17001
17295
|
}
|
|
17002
|
-
|
|
17003
|
-
|
|
17004
|
-
|
|
17005
|
-
|
|
17006
|
-
|
|
17007
|
-
}
|
|
17008
|
-
function splitUnescaped(text, delim) {
|
|
17009
|
-
const reDelim = escapeRegex(delim);
|
|
17010
|
-
const splitRe = new RegExp(`(?<!\\\\)${reDelim}`, "g");
|
|
17011
|
-
const unescapeRe = new RegExp(`\\\\${reDelim}`, "g");
|
|
17012
|
-
return text.split(splitRe).map((part) => part.replace(unescapeRe, delim));
|
|
17013
|
-
}
|
|
17014
|
-
function splitFirstUnescaped(text, delim) {
|
|
17015
|
-
const reDelim = escapeRegex(delim);
|
|
17016
|
-
const splitRe = new RegExp(`(?<!\\\\)${reDelim}`);
|
|
17017
|
-
const unescapeRe = new RegExp(`\\\\${reDelim}`, "g");
|
|
17018
|
-
const parts = text.split(splitRe);
|
|
17019
|
-
const firstPart = parts[0] ?? "";
|
|
17020
|
-
if (parts.length === 1) {
|
|
17021
|
-
return [firstPart.replace(unescapeRe, delim)];
|
|
17296
|
+
const RESPONSE_PREFIX_RE = /^\{(?:lang|case_matters|order_matters)=[^}]+\}/;
|
|
17297
|
+
function stripResponsePrefixes(node) {
|
|
17298
|
+
let result = node;
|
|
17299
|
+
while (RESPONSE_PREFIX_RE.test(result)) {
|
|
17300
|
+
result = result.replace(RESPONSE_PREFIX_RE, "");
|
|
17022
17301
|
}
|
|
17023
|
-
|
|
17024
|
-
const part2 = parts.slice(1).join(delim).replace(unescapeRe, delim);
|
|
17025
|
-
return [part1, part2];
|
|
17302
|
+
return result;
|
|
17026
17303
|
}
|
|
17027
17304
|
function validatePattern(type, pattern, responseDef) {
|
|
17028
17305
|
if (pattern.trim() !== pattern) {
|
|
@@ -17031,8 +17308,7 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17031
17308
|
scorm2004_errors.TYPE_MISMATCH
|
|
17032
17309
|
);
|
|
17033
17310
|
}
|
|
17034
|
-
const
|
|
17035
|
-
const rawNodes = subDelim1 ? splitUnescaped(pattern, subDelim1) : [pattern];
|
|
17311
|
+
const rawNodes = responseDef.delimiter ? splitDelimited(pattern, responseDef.delimiter) : [pattern];
|
|
17036
17312
|
for (const raw of rawNodes) {
|
|
17037
17313
|
if (raw.trim() !== raw) {
|
|
17038
17314
|
throw new Scorm2004ValidationError(
|
|
@@ -17044,20 +17320,14 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17044
17320
|
if (type === "fill-in" && pattern === "") {
|
|
17045
17321
|
return;
|
|
17046
17322
|
}
|
|
17047
|
-
const
|
|
17048
|
-
let nodes;
|
|
17049
|
-
if (delim1) {
|
|
17050
|
-
nodes = splitUnescaped(pattern, delim1);
|
|
17051
|
-
} else {
|
|
17052
|
-
nodes = [pattern];
|
|
17053
|
-
}
|
|
17323
|
+
const nodes = responseDef.delimiter ? splitDelimited(pattern, responseDef.delimiter) : [pattern];
|
|
17054
17324
|
if (!responseDef.delimiter && pattern.includes(",")) {
|
|
17055
17325
|
throw new Scorm2004ValidationError(
|
|
17056
17326
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
17057
17327
|
scorm2004_errors.TYPE_MISMATCH
|
|
17058
17328
|
);
|
|
17059
17329
|
}
|
|
17060
|
-
if (responseDef.unique || responseDef.duplicate === false) {
|
|
17330
|
+
if (type !== "numeric" && (responseDef.unique || responseDef.duplicate === false)) {
|
|
17061
17331
|
const seen = new Set(nodes);
|
|
17062
17332
|
if (seen.size !== nodes.length) {
|
|
17063
17333
|
throw new Scorm2004ValidationError(
|
|
@@ -17089,8 +17359,7 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17089
17359
|
scorm2004_errors.TYPE_MISMATCH
|
|
17090
17360
|
);
|
|
17091
17361
|
}
|
|
17092
|
-
const
|
|
17093
|
-
const parts = value.split(new RegExp(`(?<!\\\\)${escapeRegex(delim)}`, "g")).map((n) => n.replace(new RegExp(`\\\\${escapeRegex(delim)}`, "g"), delim));
|
|
17362
|
+
const parts = splitDelimited(value, delimBracketed);
|
|
17094
17363
|
if (parts.length !== 2 || parts[0] === "" || parts[1] === "") {
|
|
17095
17364
|
throw new Scorm2004ValidationError(
|
|
17096
17365
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
@@ -17107,15 +17376,17 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17107
17376
|
for (const node of nodes) {
|
|
17108
17377
|
switch (type) {
|
|
17109
17378
|
case "numeric": {
|
|
17110
|
-
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
|
|
17379
|
+
if (node === "") {
|
|
17380
|
+
const bracketedRange = nodes.length >= 2 && !!responseDef.delimiter && pattern.includes(responseDef.delimiter);
|
|
17381
|
+
if (!bracketedRange) {
|
|
17382
|
+
throw new Scorm2004ValidationError(
|
|
17383
|
+
"cmi.interactions.n.correct_responses.n.pattern",
|
|
17384
|
+
scorm2004_errors.TYPE_MISMATCH
|
|
17385
|
+
);
|
|
17386
|
+
}
|
|
17387
|
+
break;
|
|
17117
17388
|
}
|
|
17118
|
-
|
|
17389
|
+
checkSingle(node);
|
|
17119
17390
|
break;
|
|
17120
17391
|
}
|
|
17121
17392
|
case "performance": {
|
|
@@ -17126,8 +17397,8 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17126
17397
|
scorm2004_errors.TYPE_MISMATCH
|
|
17127
17398
|
);
|
|
17128
17399
|
}
|
|
17129
|
-
const
|
|
17130
|
-
const parts =
|
|
17400
|
+
const record = stripResponsePrefixes(node);
|
|
17401
|
+
const parts = splitFirstDelimited(record, delimBracketed);
|
|
17131
17402
|
if (parts.length !== 2) {
|
|
17132
17403
|
throw new Scorm2004ValidationError(
|
|
17133
17404
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
@@ -17135,7 +17406,7 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17135
17406
|
);
|
|
17136
17407
|
}
|
|
17137
17408
|
const [part1, part2] = parts;
|
|
17138
|
-
if (part1 === ""
|
|
17409
|
+
if (part1 === "" && part2 === "") {
|
|
17139
17410
|
throw new Scorm2004ValidationError(
|
|
17140
17411
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
17141
17412
|
scorm2004_errors.TYPE_MISMATCH
|
|
@@ -17221,6 +17492,11 @@ class CMIScore extends BaseCMI {
|
|
|
17221
17492
|
__invalid_range_code;
|
|
17222
17493
|
__decimal_regex;
|
|
17223
17494
|
__error_class;
|
|
17495
|
+
/**
|
|
17496
|
+
* When true, an empty string is a valid value (clears the element). SCORM 1.2
|
|
17497
|
+
* score elements may be blank per the ADL 1.2 CTS; SCORM 2004 leaves this off.
|
|
17498
|
+
*/
|
|
17499
|
+
__allow_empty_string;
|
|
17224
17500
|
_raw = "";
|
|
17225
17501
|
_min = "";
|
|
17226
17502
|
_max;
|
|
@@ -17255,6 +17531,7 @@ class CMIScore extends BaseCMI {
|
|
|
17255
17531
|
this.__invalid_range_code = params.invalidRangeCode || scorm12_errors.VALUE_OUT_OF_RANGE;
|
|
17256
17532
|
this.__decimal_regex = params.decimalRegex || scorm12_regex.CMIDecimal;
|
|
17257
17533
|
this.__error_class = params.errorClass;
|
|
17534
|
+
this.__allow_empty_string = params.allowEmptyString ?? false;
|
|
17258
17535
|
}
|
|
17259
17536
|
/**
|
|
17260
17537
|
* Called when the API has been reset
|
|
@@ -17301,7 +17578,8 @@ class CMIScore extends BaseCMI {
|
|
|
17301
17578
|
this.__score_range,
|
|
17302
17579
|
this.__invalid_type_code,
|
|
17303
17580
|
this.__invalid_range_code,
|
|
17304
|
-
this.__error_class
|
|
17581
|
+
this.__error_class,
|
|
17582
|
+
this.__allow_empty_string
|
|
17305
17583
|
)) {
|
|
17306
17584
|
this._raw = raw;
|
|
17307
17585
|
}
|
|
@@ -17325,7 +17603,8 @@ class CMIScore extends BaseCMI {
|
|
|
17325
17603
|
this.__score_range,
|
|
17326
17604
|
this.__invalid_type_code,
|
|
17327
17605
|
this.__invalid_range_code,
|
|
17328
|
-
this.__error_class
|
|
17606
|
+
this.__error_class,
|
|
17607
|
+
this.__allow_empty_string
|
|
17329
17608
|
)) {
|
|
17330
17609
|
this._min = min;
|
|
17331
17610
|
}
|
|
@@ -17349,7 +17628,8 @@ class CMIScore extends BaseCMI {
|
|
|
17349
17628
|
this.__score_range,
|
|
17350
17629
|
this.__invalid_type_code,
|
|
17351
17630
|
this.__invalid_range_code,
|
|
17352
|
-
this.__error_class
|
|
17631
|
+
this.__error_class,
|
|
17632
|
+
this.__allow_empty_string
|
|
17353
17633
|
)) {
|
|
17354
17634
|
this._max = max;
|
|
17355
17635
|
}
|
|
@@ -20199,7 +20479,7 @@ class Scorm2004ResponseValidator {
|
|
|
20199
20479
|
checkValidResponseType(CMIElement, response_type, value, interaction_type) {
|
|
20200
20480
|
let nodes = [];
|
|
20201
20481
|
if (response_type?.delimiter) {
|
|
20202
|
-
nodes = String(value)
|
|
20482
|
+
nodes = splitDelimited(String(value), response_type.delimiter);
|
|
20203
20483
|
} else {
|
|
20204
20484
|
nodes[0] = value;
|
|
20205
20485
|
}
|
|
@@ -20321,22 +20601,30 @@ class Scorm2004ResponseValidator {
|
|
|
20321
20601
|
nodes[i] = this.removeCorrectResponsePrefixes(CMIElement, nodes[i]);
|
|
20322
20602
|
}
|
|
20323
20603
|
if (response?.delimiter2) {
|
|
20324
|
-
const values = nodes[i].
|
|
20604
|
+
const values = interaction_type === "performance" ? splitFirstDelimited(nodes[i], response.delimiter2) : splitDelimited(nodes[i], response.delimiter2);
|
|
20325
20605
|
if (values.length === 2) {
|
|
20326
|
-
|
|
20327
|
-
if (!matches) {
|
|
20606
|
+
if (interaction_type === "performance" && values[0] === "" && values[1] === "") {
|
|
20328
20607
|
this.context.throwSCORMError(
|
|
20329
20608
|
CMIElement,
|
|
20330
20609
|
scorm2004_errors.TYPE_MISMATCH,
|
|
20331
20610
|
`${interaction_type}: ${value}`
|
|
20332
20611
|
);
|
|
20333
20612
|
} else {
|
|
20334
|
-
|
|
20613
|
+
const matches = values[0]?.match(formatRegex);
|
|
20614
|
+
if (!matches) {
|
|
20335
20615
|
this.context.throwSCORMError(
|
|
20336
20616
|
CMIElement,
|
|
20337
20617
|
scorm2004_errors.TYPE_MISMATCH,
|
|
20338
20618
|
`${interaction_type}: ${value}`
|
|
20339
20619
|
);
|
|
20620
|
+
} else {
|
|
20621
|
+
if (!response.format2 || !values[1]?.match(new RegExp(response.format2))) {
|
|
20622
|
+
this.context.throwSCORMError(
|
|
20623
|
+
CMIElement,
|
|
20624
|
+
scorm2004_errors.TYPE_MISMATCH,
|
|
20625
|
+
`${interaction_type}: ${value}`
|
|
20626
|
+
);
|
|
20627
|
+
}
|
|
20340
20628
|
}
|
|
20341
20629
|
}
|
|
20342
20630
|
} else {
|
|
@@ -20347,6 +20635,9 @@ class Scorm2004ResponseValidator {
|
|
|
20347
20635
|
);
|
|
20348
20636
|
}
|
|
20349
20637
|
} else {
|
|
20638
|
+
if (interaction_type === "numeric" && nodes.length > 1 && nodes[i] === "" && !!response.delimiter && String(value).includes(response.delimiter)) {
|
|
20639
|
+
continue;
|
|
20640
|
+
}
|
|
20350
20641
|
const matches = nodes[i].match(formatRegex);
|
|
20351
20642
|
if (!matches && value !== "" || !matches && interaction_type === "true-false") {
|
|
20352
20643
|
this.context.throwSCORMError(
|
|
@@ -20356,7 +20647,7 @@ class Scorm2004ResponseValidator {
|
|
|
20356
20647
|
);
|
|
20357
20648
|
} else {
|
|
20358
20649
|
if (interaction_type === "numeric" && nodes.length > 1) {
|
|
20359
|
-
if (Number(nodes[0]) > Number(nodes[1])) {
|
|
20650
|
+
if (nodes[0] !== "" && nodes[1] !== "" && Number(nodes[0]) > Number(nodes[1])) {
|
|
20360
20651
|
this.context.throwSCORMError(
|
|
20361
20652
|
CMIElement,
|
|
20362
20653
|
scorm2004_errors.TYPE_MISMATCH,
|
|
@@ -22132,6 +22423,35 @@ class Scorm2004DataSerializer {
|
|
|
22132
22423
|
}
|
|
22133
22424
|
}
|
|
22134
22425
|
|
|
22426
|
+
const NO_DEFAULT_2004_ELEMENTS = /* @__PURE__ */ new Set([
|
|
22427
|
+
"cmi.suspend_data",
|
|
22428
|
+
"cmi.location",
|
|
22429
|
+
"cmi.scaled_passing_score",
|
|
22430
|
+
"cmi.max_time_allowed",
|
|
22431
|
+
"cmi.completion_threshold",
|
|
22432
|
+
"cmi.progress_measure",
|
|
22433
|
+
"cmi.score.scaled",
|
|
22434
|
+
"cmi.score.raw",
|
|
22435
|
+
"cmi.score.min",
|
|
22436
|
+
"cmi.score.max",
|
|
22437
|
+
"cmi.objectives.N.score.scaled",
|
|
22438
|
+
"cmi.objectives.N.score.raw",
|
|
22439
|
+
"cmi.objectives.N.score.min",
|
|
22440
|
+
"cmi.objectives.N.score.max",
|
|
22441
|
+
"cmi.objectives.N.progress_measure",
|
|
22442
|
+
"cmi.objectives.N.description",
|
|
22443
|
+
"cmi.interactions.N.weighting",
|
|
22444
|
+
"cmi.interactions.N.type",
|
|
22445
|
+
"cmi.interactions.N.timestamp",
|
|
22446
|
+
"cmi.interactions.N.result",
|
|
22447
|
+
"cmi.interactions.N.latency",
|
|
22448
|
+
"cmi.interactions.N.learner_response",
|
|
22449
|
+
"cmi.interactions.N.description",
|
|
22450
|
+
"cmi.comments_from_learner.N.timestamp"
|
|
22451
|
+
]);
|
|
22452
|
+
function normalizeCMIIndices(CMIElement) {
|
|
22453
|
+
return CMIElement.replace(/\.\d+(?=\.|$)/g, ".N");
|
|
22454
|
+
}
|
|
22135
22455
|
class Scorm2004API extends BaseAPI {
|
|
22136
22456
|
_version = "1.0";
|
|
22137
22457
|
_sequencing;
|
|
@@ -22657,6 +22977,39 @@ class Scorm2004API extends BaseAPI {
|
|
|
22657
22977
|
getCMIValue(CMIElement) {
|
|
22658
22978
|
return this._commonGetCMIValue("GetValue", true, CMIElement);
|
|
22659
22979
|
}
|
|
22980
|
+
/**
|
|
22981
|
+
* Raises 403 (VALUE_NOT_INITIALIZED) when an implemented, no-default element is
|
|
22982
|
+
* read before it has ever been set, per IEEE 1484.11.2 / SCORM 2004 RTE.
|
|
22983
|
+
*
|
|
22984
|
+
* Invoked by BaseAPI.getValue after an otherwise-successful resolution, so it
|
|
22985
|
+
* only sees values the data model already returned cleanly. The decision is
|
|
22986
|
+
* deliberately gated to this public boundary (never the getters) to keep
|
|
22987
|
+
* serialization/commit/rollup — which read the getters directly — unaffected.
|
|
22988
|
+
*
|
|
22989
|
+
* A 403 is raised only when all hold:
|
|
22990
|
+
* - the resolved value is "" — any non-empty value is by definition set,
|
|
22991
|
+
* including values written by internal sequencing/activity-tree paths that
|
|
22992
|
+
* bypass _commonSetCMIValue (those only ever write non-empty values);
|
|
22993
|
+
* - the element was never set — _setCMIElements records every successful
|
|
22994
|
+
* SetValue, loadFromJSON, and global-objective restore (all route through
|
|
22995
|
+
* _commonSetCMIValue), so an explicit SetValue(x, "") still reads back as
|
|
22996
|
+
* "" with code 0; and
|
|
22997
|
+
* - the element has no spec-defined default ({@link NO_DEFAULT_2004_ELEMENTS}).
|
|
22998
|
+
*
|
|
22999
|
+
* @param {string} CMIElement
|
|
23000
|
+
* @param {*} returnValue - the value getCMIValue resolved
|
|
23001
|
+
* @protected
|
|
23002
|
+
*/
|
|
23003
|
+
checkUninitializedGet(CMIElement, returnValue) {
|
|
23004
|
+
if (returnValue !== "") return;
|
|
23005
|
+
if (this._setCMIElements.has(CMIElement)) return;
|
|
23006
|
+
if (!NO_DEFAULT_2004_ELEMENTS.has(normalizeCMIIndices(CMIElement))) return;
|
|
23007
|
+
this.throwSCORMError(
|
|
23008
|
+
CMIElement,
|
|
23009
|
+
this._error_codes.VALUE_NOT_INITIALIZED ?? 403,
|
|
23010
|
+
`The data model element passed to GetValue (${CMIElement}) has not been initialized.`
|
|
23011
|
+
);
|
|
23012
|
+
}
|
|
22660
23013
|
/**
|
|
22661
23014
|
* Returns the message that corresponds to errorNumber.
|
|
22662
23015
|
* @param {(string|number)} errorNumber
|
|
@@ -22703,9 +23056,10 @@ class Scorm2004API extends BaseAPI {
|
|
|
22703
23056
|
/**
|
|
22704
23057
|
* Attempts to store the data to the LMS
|
|
22705
23058
|
* @param {boolean} terminateCommit
|
|
23059
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
22706
23060
|
* @return {ResultObject}
|
|
22707
23061
|
*/
|
|
22708
|
-
storeData(terminateCommit) {
|
|
23062
|
+
storeData(terminateCommit, trigger) {
|
|
22709
23063
|
if (terminateCommit) {
|
|
22710
23064
|
if (this.cmi.mode === "normal") {
|
|
22711
23065
|
if (this.cmi.credit === "credit") {
|
|
@@ -22753,7 +23107,8 @@ class Scorm2004API extends BaseAPI {
|
|
|
22753
23107
|
const result = this.processHttpRequest(
|
|
22754
23108
|
this.settings.lmsCommitUrl,
|
|
22755
23109
|
commitObject,
|
|
22756
|
-
terminateCommit
|
|
23110
|
+
terminateCommit,
|
|
23111
|
+
trigger
|
|
22757
23112
|
);
|
|
22758
23113
|
if (navRequest && result.navRequest !== void 0 && result.navRequest !== "" && typeof result.navRequest === "string") {
|
|
22759
23114
|
const parsed = parseNavigationRequest(result.navRequest);
|