scorm-again 3.0.5 → 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 +77 -1
- 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 +388 -71
- 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 +322 -69
- 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 +371 -64
- 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 +341 -55
- 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 +305 -53
- 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 +329 -48
- 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/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 +21 -21
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;
|
|
@@ -1354,7 +1367,7 @@
|
|
|
1354
1367
|
wrapper() {
|
|
1355
1368
|
if (!this._cancelled) {
|
|
1356
1369
|
if (this._API.isInitialized()) {
|
|
1357
|
-
(async () => await this._API.commit(this._callback))();
|
|
1370
|
+
(async () => await this._API.commit(this._callback, false, "autocommit"))();
|
|
1358
1371
|
}
|
|
1359
1372
|
}
|
|
1360
1373
|
}
|
|
@@ -4827,6 +4840,7 @@
|
|
|
4827
4840
|
* @param {ErrorCode} error_codes - The error codes object
|
|
4828
4841
|
*/
|
|
4829
4842
|
constructor(settings, error_codes) {
|
|
4843
|
+
__publicField$Y(this, "reportsRequestCompletion", true);
|
|
4830
4844
|
__publicField$Y(this, "settings");
|
|
4831
4845
|
__publicField$Y(this, "error_codes");
|
|
4832
4846
|
this.settings = settings;
|
|
@@ -4845,13 +4859,17 @@
|
|
|
4845
4859
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
4846
4860
|
* @param {Function} apiLog - Function to log API messages with appropriate levels
|
|
4847
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
|
|
4848
4864
|
* @return {ResultObject} - Immediate optimistic success result
|
|
4849
4865
|
*/
|
|
4850
4866
|
processHttpRequest(url, params) {
|
|
4851
4867
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
4852
4868
|
let apiLog = arguments.length > 3 ? arguments[3] : undefined;
|
|
4853
4869
|
let processListeners = arguments.length > 4 ? arguments[4] : undefined;
|
|
4854
|
-
|
|
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);
|
|
4855
4873
|
return {
|
|
4856
4874
|
result: global_constants.SCORM_TRUE,
|
|
4857
4875
|
errorCode: 0
|
|
@@ -4864,11 +4882,14 @@
|
|
|
4864
4882
|
* @param {boolean} immediate - Whether this is an immediate request
|
|
4865
4883
|
* @param apiLog - Function to log API messages
|
|
4866
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
|
|
4867
4887
|
* @private
|
|
4868
4888
|
*/
|
|
4869
|
-
async _performAsyncRequest(url, params, immediate, apiLog, processListeners) {
|
|
4889
|
+
async _performAsyncRequest(url, params, immediate, apiLog, processListeners, metadata, onRequestComplete) {
|
|
4870
4890
|
try {
|
|
4871
|
-
const
|
|
4891
|
+
const handledParams = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
4892
|
+
const processedParams = handledParams;
|
|
4872
4893
|
let response;
|
|
4873
4894
|
if (immediate && this.settings.asyncModeBeaconBehavior !== "never") {
|
|
4874
4895
|
response = await this.performBeacon(url, processedParams);
|
|
@@ -4884,7 +4905,9 @@
|
|
|
4884
4905
|
} catch (e) {
|
|
4885
4906
|
const message = e instanceof Error ? e.message : String(e);
|
|
4886
4907
|
apiLog("processHttpRequest", `Async request failed: ${message}`, LogLevelEnum.ERROR);
|
|
4887
|
-
processListeners("CommitError");
|
|
4908
|
+
processListeners("CommitError", void 0, this.error_codes.GENERAL_COMMIT_FAILURE || 391);
|
|
4909
|
+
} finally {
|
|
4910
|
+
onRequestComplete?.();
|
|
4888
4911
|
}
|
|
4889
4912
|
}
|
|
4890
4913
|
/**
|
|
@@ -5046,10 +5069,13 @@
|
|
|
5046
5069
|
}
|
|
5047
5070
|
/**
|
|
5048
5071
|
* Gets the appropriate error code for undefined data model elements.
|
|
5049
|
-
* 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.
|
|
5050
5076
|
*/
|
|
5051
|
-
getUndefinedDataModelErrorCode(
|
|
5052
|
-
return
|
|
5077
|
+
getUndefinedDataModelErrorCode() {
|
|
5078
|
+
return getErrorCode(this.context.errorCodes, "UNDEFINED_DATA_MODEL");
|
|
5053
5079
|
}
|
|
5054
5080
|
/**
|
|
5055
5081
|
* Sets a value on a CMI element path
|
|
@@ -5073,7 +5099,7 @@
|
|
|
5073
5099
|
let returnValue = global_constants.SCORM_FALSE;
|
|
5074
5100
|
let foundFirstIndex = false;
|
|
5075
5101
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
5076
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
5102
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
5077
5103
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
5078
5104
|
const attribute = structure[idx];
|
|
5079
5105
|
if (idx === structure.length - 1) {
|
|
@@ -5113,12 +5139,13 @@
|
|
|
5113
5139
|
this.context.throwSCORMError(CMIElement, getErrorCode(this.context.errorCodes, "GENERAL_GET_FAILURE"), "The _version keyword was used incorrectly");
|
|
5114
5140
|
return "";
|
|
5115
5141
|
}
|
|
5142
|
+
this.context.setLastErrorCode("0");
|
|
5116
5143
|
const structure = CMIElement.split(".");
|
|
5117
5144
|
let refObject = this.context.getDataModel();
|
|
5118
5145
|
let attribute = null;
|
|
5119
5146
|
const uninitializedErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) has not been initialized.`;
|
|
5120
5147
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
5121
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
5148
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
5122
5149
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
5123
5150
|
attribute = structure[idx];
|
|
5124
5151
|
const validationResult = this.validateGetAttribute(refObject, attribute, CMIElement, scorm2004, invalidErrorCode, invalidErrorMessage, idx === structure.length - 1);
|
|
@@ -5844,8 +5871,9 @@ ${stackTrace}`);
|
|
|
5844
5871
|
* @param {string} functionName - The name of the function that triggered the event
|
|
5845
5872
|
* @param {string} CMIElement - The CMI element that was affected
|
|
5846
5873
|
* @param {any} value - The value that was set
|
|
5874
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
5847
5875
|
*/
|
|
5848
|
-
processListeners(functionName, CMIElement, value) {
|
|
5876
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
5849
5877
|
this.apiLog(functionName, value, LogLevelEnum.INFO, CMIElement);
|
|
5850
5878
|
const listeners = this.listenerMap.get(functionName);
|
|
5851
5879
|
if (!listeners) return;
|
|
@@ -5865,9 +5893,17 @@ ${stackTrace}`);
|
|
|
5865
5893
|
if (functionName.startsWith("Sequence")) {
|
|
5866
5894
|
listener.callback(value);
|
|
5867
5895
|
} else if (functionName === "CommitError") {
|
|
5868
|
-
|
|
5896
|
+
if (context !== void 0) {
|
|
5897
|
+
listener.callback(value, context);
|
|
5898
|
+
} else {
|
|
5899
|
+
listener.callback(value);
|
|
5900
|
+
}
|
|
5869
5901
|
} else if (functionName === "CommitSuccess") {
|
|
5870
|
-
|
|
5902
|
+
if (context !== void 0) {
|
|
5903
|
+
listener.callback(context);
|
|
5904
|
+
} else {
|
|
5905
|
+
listener.callback();
|
|
5906
|
+
}
|
|
5871
5907
|
} else {
|
|
5872
5908
|
listener.callback(CMIElement, value);
|
|
5873
5909
|
}
|
|
@@ -5976,16 +6012,23 @@ ${stackTrace}`);
|
|
|
5976
6012
|
* Store commit data offline
|
|
5977
6013
|
* @param {string} courseId - Identifier for the course
|
|
5978
6014
|
* @param {CommitObject} commitData - The data to store offline
|
|
6015
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
5979
6016
|
* @returns {ResultObject} - Result of the storage operation
|
|
5980
6017
|
*/
|
|
5981
|
-
storeOffline(courseId, commitData) {
|
|
6018
|
+
storeOffline(courseId, commitData, metadata) {
|
|
5982
6019
|
try {
|
|
5983
6020
|
const queueItem = {
|
|
5984
6021
|
id: `${courseId}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
5985
6022
|
courseId,
|
|
5986
6023
|
timestamp: Date.now(),
|
|
5987
6024
|
data: commitData,
|
|
5988
|
-
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
|
+
} : {})
|
|
5989
6032
|
};
|
|
5990
6033
|
const currentQueue = this.getFromStorage(this.syncQueue) || [];
|
|
5991
6034
|
currentQueue.push(queueItem);
|
|
@@ -6044,7 +6087,14 @@ ${stackTrace}`);
|
|
|
6044
6087
|
continue;
|
|
6045
6088
|
}
|
|
6046
6089
|
try {
|
|
6047
|
-
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
|
+
});
|
|
6048
6098
|
if (syncResult.result === true || syncResult.result === global_constants.SCORM_TRUE) {
|
|
6049
6099
|
this.apiLog("OfflineStorageService", `Successfully synced item ${item.id}`, LogLevelEnum.INFO);
|
|
6050
6100
|
} else {
|
|
@@ -6071,17 +6121,27 @@ ${stackTrace}`);
|
|
|
6071
6121
|
/**
|
|
6072
6122
|
* Send data to the LMS when online
|
|
6073
6123
|
* @param {CommitObject} data - The data to send to the LMS
|
|
6124
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
6074
6125
|
* @returns {Promise<ResultObject>} - Result of the sync operation
|
|
6075
6126
|
*/
|
|
6076
|
-
async sendDataToLMS(data) {
|
|
6077
|
-
|
|
6127
|
+
async sendDataToLMS(data, metadata) {
|
|
6128
|
+
const configuredCommitUrl = this.settings.lmsCommitUrl;
|
|
6129
|
+
if (!configuredCommitUrl) {
|
|
6078
6130
|
return {
|
|
6079
6131
|
result: global_constants.SCORM_FALSE,
|
|
6080
6132
|
errorCode: this.error_codes.GENERAL || 101
|
|
6081
6133
|
};
|
|
6082
6134
|
}
|
|
6083
6135
|
try {
|
|
6084
|
-
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;
|
|
6085
6145
|
const init = {
|
|
6086
6146
|
method: "POST",
|
|
6087
6147
|
mode: this.settings.fetchMode,
|
|
@@ -6094,7 +6154,7 @@ ${stackTrace}`);
|
|
|
6094
6154
|
if (this.settings.xhrWithCredentials) {
|
|
6095
6155
|
init.credentials = "include";
|
|
6096
6156
|
}
|
|
6097
|
-
const response = await fetch(
|
|
6157
|
+
const response = await fetch(requestUrl, init);
|
|
6098
6158
|
const result = typeof this.settings.responseHandler === "function" ? await this.settings.responseHandler(response) : await response.json();
|
|
6099
6159
|
if (response.status >= 200 && response.status <= 299 && (result.result === true || result.result === global_constants.SCORM_TRUE)) {
|
|
6100
6160
|
if (!Object.hasOwnProperty.call(result, "errorCode")) {
|
|
@@ -14991,6 +15051,8 @@ ${stackTrace}`);
|
|
|
14991
15051
|
* @param {boolean} immediate - Whether this is a termination commit (use sendBeacon)
|
|
14992
15052
|
* @param {Function} _apiLog - Function to log API messages (unused in synchronous mode - errors returned directly)
|
|
14993
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)
|
|
14994
15056
|
* @return {ResultObject} - The result of the request (synchronous)
|
|
14995
15057
|
*
|
|
14996
15058
|
* @remarks
|
|
@@ -15002,20 +15064,23 @@ ${stackTrace}`);
|
|
|
15002
15064
|
*/
|
|
15003
15065
|
processHttpRequest(url, params) {
|
|
15004
15066
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
15067
|
+
let metadata = arguments.length > 5 ? arguments[5] : undefined;
|
|
15005
15068
|
if (immediate) {
|
|
15006
|
-
return this._handleImmediateRequest(url, params);
|
|
15069
|
+
return this._handleImmediateRequest(url, params, metadata);
|
|
15007
15070
|
}
|
|
15008
|
-
return this._performSyncXHR(url, params);
|
|
15071
|
+
return this._performSyncXHR(url, params, metadata);
|
|
15009
15072
|
}
|
|
15010
15073
|
/**
|
|
15011
15074
|
* Handles an immediate request using sendBeacon
|
|
15012
15075
|
* @param {string} url - The URL to send the request to
|
|
15013
15076
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
15077
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
15014
15078
|
* @return {ResultObject} - The result based on beacon success
|
|
15015
15079
|
* @private
|
|
15016
15080
|
*/
|
|
15017
|
-
_handleImmediateRequest(url, params) {
|
|
15018
|
-
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;
|
|
15019
15084
|
const {
|
|
15020
15085
|
body
|
|
15021
15086
|
} = this._prepareRequestBody(requestPayload);
|
|
@@ -15031,11 +15096,13 @@ ${stackTrace}`);
|
|
|
15031
15096
|
* Performs a synchronous XMLHttpRequest
|
|
15032
15097
|
* @param {string} url - The URL to send the request to
|
|
15033
15098
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
15099
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
15034
15100
|
* @return {ResultObject} - The result of the request
|
|
15035
15101
|
* @private
|
|
15036
15102
|
*/
|
|
15037
|
-
_performSyncXHR(url, params) {
|
|
15038
|
-
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;
|
|
15039
15106
|
const {
|
|
15040
15107
|
body,
|
|
15041
15108
|
contentType
|
|
@@ -15108,9 +15175,16 @@ ${stackTrace}`);
|
|
|
15108
15175
|
* @param {number} invalidTypeCode - The error code for invalid type
|
|
15109
15176
|
* @param {number} invalidRangeCode - The error code for invalid range
|
|
15110
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).
|
|
15111
15181
|
* @return {boolean} - True if validation passes, throws an error otherwise
|
|
15112
15182
|
*/
|
|
15113
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
|
+
}
|
|
15114
15188
|
return checkValidFormat(CMIElement, value, decimalRegex, invalidTypeCode, errorClass) && (!scoreRange || checkValidRange(CMIElement, value, scoreRange, invalidRangeCode, errorClass));
|
|
15115
15189
|
}
|
|
15116
15190
|
/**
|
|
@@ -15206,6 +15280,21 @@ ${stackTrace}`);
|
|
|
15206
15280
|
__publicField$w(this, "_offlineStorageService");
|
|
15207
15281
|
__publicField$w(this, "_cmiValueAccessService");
|
|
15208
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());
|
|
15209
15298
|
__publicField$w(this, "startingData");
|
|
15210
15299
|
__publicField$w(this, "currentState");
|
|
15211
15300
|
if (new.target === BaseAPI) {
|
|
@@ -15352,6 +15441,7 @@ ${stackTrace}`);
|
|
|
15352
15441
|
this.lastErrorCode = "0";
|
|
15353
15442
|
this._eventService.reset();
|
|
15354
15443
|
this.startingData = {};
|
|
15444
|
+
this._setCMIElements.clear();
|
|
15355
15445
|
if (this._offlineStorageService) {
|
|
15356
15446
|
this._offlineStorageService.updateSettings(this.settings);
|
|
15357
15447
|
if (settings?.courseId) {
|
|
@@ -15435,6 +15525,54 @@ ${stackTrace}`);
|
|
|
15435
15525
|
this._loggingService?.setLogHandler(settings.onLogMessage);
|
|
15436
15526
|
}
|
|
15437
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
|
+
}
|
|
15438
15576
|
/**
|
|
15439
15577
|
* Terminates the current run of the API
|
|
15440
15578
|
* @param {string} callbackName
|
|
@@ -15455,7 +15593,7 @@ ${stackTrace}`);
|
|
|
15455
15593
|
} else {
|
|
15456
15594
|
stateCheckPassed = true;
|
|
15457
15595
|
this.processListeners("BeforeTerminate");
|
|
15458
|
-
const result = this.storeData(true);
|
|
15596
|
+
const result = this.storeData(true, "terminate");
|
|
15459
15597
|
if ((result.errorCode ?? 0) > 0) {
|
|
15460
15598
|
if (result.errorMessage) {
|
|
15461
15599
|
this.apiLog("terminate", `Terminate failed with error: ${result.errorMessage}`, LogLevelEnum.ERROR);
|
|
@@ -15495,6 +15633,9 @@ ${stackTrace}`);
|
|
|
15495
15633
|
} catch (e) {
|
|
15496
15634
|
returnValue = this.handleValueAccessException(CMIElement, e, returnValue);
|
|
15497
15635
|
}
|
|
15636
|
+
if (this.lastErrorCode === "0") {
|
|
15637
|
+
this.checkUninitializedGet(CMIElement, returnValue);
|
|
15638
|
+
}
|
|
15498
15639
|
this.processListeners(callbackName, CMIElement);
|
|
15499
15640
|
}
|
|
15500
15641
|
this.apiLog(callbackName, ": returned: " + returnValue, LogLevelEnum.INFO, CMIElement);
|
|
@@ -15504,6 +15645,10 @@ ${stackTrace}`);
|
|
|
15504
15645
|
if (this.lastErrorCode === "0") {
|
|
15505
15646
|
this.clearSCORMError(returnValue);
|
|
15506
15647
|
}
|
|
15648
|
+
const rawReturn = returnValue;
|
|
15649
|
+
if (typeof rawReturn === "number" || typeof rawReturn === "boolean") {
|
|
15650
|
+
return String(rawReturn);
|
|
15651
|
+
}
|
|
15507
15652
|
return returnValue;
|
|
15508
15653
|
}
|
|
15509
15654
|
/**
|
|
@@ -15547,10 +15692,12 @@ ${stackTrace}`);
|
|
|
15547
15692
|
* Orders LMS to store all content parameters
|
|
15548
15693
|
* @param {string} callbackName
|
|
15549
15694
|
* @param {boolean} checkTerminated
|
|
15695
|
+
* @param {CommitTrigger} trigger - What initiated the commit
|
|
15550
15696
|
* @return {string}
|
|
15551
15697
|
*/
|
|
15552
15698
|
commit(callbackName) {
|
|
15553
15699
|
let checkTerminated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
15700
|
+
let trigger = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "manual";
|
|
15554
15701
|
this.clearScheduledCommit();
|
|
15555
15702
|
let returnValue = global_constants.SCORM_TRUE;
|
|
15556
15703
|
if (this.isNotInitialized()) {
|
|
@@ -15562,7 +15709,7 @@ ${stackTrace}`);
|
|
|
15562
15709
|
this.throwSCORMError("api", errorCode);
|
|
15563
15710
|
if (errorCode === 143) returnValue = global_constants.SCORM_FALSE;
|
|
15564
15711
|
} else {
|
|
15565
|
-
const result = this.storeData(false);
|
|
15712
|
+
const result = this.storeData(false, trigger);
|
|
15566
15713
|
const errorCode = result.errorCode ?? 0;
|
|
15567
15714
|
if (errorCode > 0) {
|
|
15568
15715
|
if (result.errorMessage) {
|
|
@@ -15783,7 +15930,11 @@ ${stackTrace}`);
|
|
|
15783
15930
|
* @return {string}
|
|
15784
15931
|
*/
|
|
15785
15932
|
_commonSetCMIValue(methodName, scorm2004, CMIElement, value) {
|
|
15786
|
-
|
|
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;
|
|
15787
15938
|
}
|
|
15788
15939
|
/**
|
|
15789
15940
|
* Gets a value from the CMI Object.
|
|
@@ -15797,6 +15948,17 @@ ${stackTrace}`);
|
|
|
15797
15948
|
_commonGetCMIValue(methodName, scorm2004, CMIElement) {
|
|
15798
15949
|
return this._cmiValueAccessService.getCMIValue(methodName, scorm2004, CMIElement);
|
|
15799
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) {}
|
|
15800
15962
|
/**
|
|
15801
15963
|
* Returns true if the API's current state is STATE_INITIALIZED
|
|
15802
15964
|
*
|
|
@@ -15879,9 +16041,14 @@ ${stackTrace}`);
|
|
|
15879
16041
|
* @param {string} functionName - The name of the function/event that occurred
|
|
15880
16042
|
* @param {string} CMIElement - Optional CMI element involved in the event
|
|
15881
16043
|
* @param {any} value - Optional value associated with the event
|
|
16044
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
15882
16045
|
*/
|
|
15883
|
-
processListeners(functionName, CMIElement, value) {
|
|
15884
|
-
|
|
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
|
+
}
|
|
15885
16052
|
}
|
|
15886
16053
|
/**
|
|
15887
16054
|
* Throws a SCORM error with the specified error number and optional message.
|
|
@@ -16003,23 +16170,97 @@ ${stackTrace}`);
|
|
|
16003
16170
|
* @param {string} url - The URL to send the request to
|
|
16004
16171
|
* @param {CommitObject | StringKeyMap | Array<any>} params - The parameters to send
|
|
16005
16172
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
16173
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
16006
16174
|
* @returns {ResultObject} - The result of the request
|
|
16007
16175
|
*/
|
|
16008
16176
|
processHttpRequest(url, params) {
|
|
16009
16177
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
|
|
16016
|
-
|
|
16017
|
-
|
|
16018
|
-
|
|
16019
|
-
|
|
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();
|
|
16020
16262
|
}
|
|
16021
16263
|
}
|
|
16022
|
-
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));
|
|
16023
16264
|
}
|
|
16024
16265
|
/**
|
|
16025
16266
|
* Schedules a commit operation to occur after a specified delay.
|
|
@@ -16203,6 +16444,11 @@ ${stackTrace}`);
|
|
|
16203
16444
|
__publicField$v(this, "__invalid_range_code");
|
|
16204
16445
|
__publicField$v(this, "__decimal_regex");
|
|
16205
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");
|
|
16206
16452
|
__publicField$v(this, "_raw", "");
|
|
16207
16453
|
__publicField$v(this, "_min", "");
|
|
16208
16454
|
__publicField$v(this, "_max");
|
|
@@ -16214,6 +16460,7 @@ ${stackTrace}`);
|
|
|
16214
16460
|
this.__invalid_range_code = params.invalidRangeCode || scorm12_errors.VALUE_OUT_OF_RANGE;
|
|
16215
16461
|
this.__decimal_regex = params.decimalRegex || scorm12_regex.CMIDecimal;
|
|
16216
16462
|
this.__error_class = params.errorClass;
|
|
16463
|
+
this.__allow_empty_string = params.allowEmptyString ?? false;
|
|
16217
16464
|
}
|
|
16218
16465
|
/**
|
|
16219
16466
|
* Called when the API has been reset
|
|
@@ -16253,7 +16500,7 @@ ${stackTrace}`);
|
|
|
16253
16500
|
* @param {string} raw
|
|
16254
16501
|
*/
|
|
16255
16502
|
set raw(raw) {
|
|
16256
|
-
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)) {
|
|
16257
16504
|
this._raw = raw;
|
|
16258
16505
|
}
|
|
16259
16506
|
}
|
|
@@ -16269,7 +16516,7 @@ ${stackTrace}`);
|
|
|
16269
16516
|
* @param {string} min
|
|
16270
16517
|
*/
|
|
16271
16518
|
set min(min) {
|
|
16272
|
-
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)) {
|
|
16273
16520
|
this._min = min;
|
|
16274
16521
|
}
|
|
16275
16522
|
}
|
|
@@ -16285,7 +16532,7 @@ ${stackTrace}`);
|
|
|
16285
16532
|
* @param {string} max
|
|
16286
16533
|
*/
|
|
16287
16534
|
set max(max) {
|
|
16288
|
-
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)) {
|
|
16289
16536
|
this._max = max;
|
|
16290
16537
|
}
|
|
16291
16538
|
}
|
|
@@ -16362,7 +16609,9 @@ ${stackTrace}`);
|
|
|
16362
16609
|
invalidErrorCode: scorm12_errors.INVALID_SET_VALUE,
|
|
16363
16610
|
invalidTypeCode: scorm12_errors.TYPE_MISMATCH,
|
|
16364
16611
|
invalidRangeCode: scorm12_errors.VALUE_OUT_OF_RANGE,
|
|
16365
|
-
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
|
|
16366
16615
|
});
|
|
16367
16616
|
}
|
|
16368
16617
|
/**
|
|
@@ -16730,7 +16979,9 @@ ${stackTrace}`);
|
|
|
16730
16979
|
invalidErrorCode: scorm12_errors.INVALID_SET_VALUE,
|
|
16731
16980
|
invalidTypeCode: scorm12_errors.TYPE_MISMATCH,
|
|
16732
16981
|
invalidRangeCode: scorm12_errors.VALUE_OUT_OF_RANGE,
|
|
16733
|
-
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
|
|
16734
16985
|
});
|
|
16735
16986
|
}
|
|
16736
16987
|
/**
|
|
@@ -18167,16 +18418,17 @@ ${stackTrace}`);
|
|
|
18167
18418
|
* Attempts to store the data to the LMS
|
|
18168
18419
|
*
|
|
18169
18420
|
* @param {boolean} terminateCommit
|
|
18421
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
18170
18422
|
* @return {ResultObject}
|
|
18171
18423
|
*/
|
|
18172
|
-
storeData(terminateCommit) {
|
|
18424
|
+
storeData(terminateCommit, trigger) {
|
|
18173
18425
|
if (terminateCommit) {
|
|
18174
18426
|
const originalStatus = this.cmi.core.lesson_status;
|
|
18175
18427
|
if (this.cmi.core.lesson_mode === "browse") {
|
|
18176
18428
|
const startingStatus = this.startingData?.cmi?.core?.lesson_status || "";
|
|
18177
18429
|
if (startingStatus === "" && originalStatus === "not attempted") {
|
|
18178
18430
|
this.cmi.core.lesson_status = "browsed";
|
|
18179
|
-
return this.processCommitData(terminateCommit);
|
|
18431
|
+
return this.processCommitData(terminateCommit, trigger);
|
|
18180
18432
|
}
|
|
18181
18433
|
}
|
|
18182
18434
|
if (!this.cmi.core.lesson_status || !this.statusSetByModule && this.cmi.core.lesson_status === "not attempted") {
|
|
@@ -18205,12 +18457,12 @@ ${stackTrace}`);
|
|
|
18205
18457
|
}
|
|
18206
18458
|
}
|
|
18207
18459
|
}
|
|
18208
|
-
return this.processCommitData(terminateCommit);
|
|
18460
|
+
return this.processCommitData(terminateCommit, trigger);
|
|
18209
18461
|
}
|
|
18210
|
-
processCommitData(terminateCommit) {
|
|
18462
|
+
processCommitData(terminateCommit, trigger) {
|
|
18211
18463
|
const commitObject = this.getCommitObject(terminateCommit);
|
|
18212
18464
|
if (typeof this.settings.lmsCommitUrl === "string") {
|
|
18213
|
-
return this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit);
|
|
18465
|
+
return this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit, trigger);
|
|
18214
18466
|
} else {
|
|
18215
18467
|
return {
|
|
18216
18468
|
result: global_constants.SCORM_TRUE,
|
|
@@ -23459,6 +23711,10 @@ ${stackTrace}`);
|
|
|
23459
23711
|
value
|
|
23460
23712
|
}) : obj[key] = value;
|
|
23461
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
|
+
}
|
|
23462
23718
|
class Scorm2004API extends BaseAPI {
|
|
23463
23719
|
/**
|
|
23464
23720
|
* Constructor for SCORM 2004 API
|
|
@@ -23958,6 +24214,35 @@ ${stackTrace}`);
|
|
|
23958
24214
|
getCMIValue(CMIElement) {
|
|
23959
24215
|
return this._commonGetCMIValue("GetValue", true, CMIElement);
|
|
23960
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
|
+
}
|
|
23961
24246
|
/**
|
|
23962
24247
|
* Returns the message that corresponds to errorNumber.
|
|
23963
24248
|
* @param {(string|number)} errorNumber
|
|
@@ -24006,9 +24291,10 @@ ${stackTrace}`);
|
|
|
24006
24291
|
/**
|
|
24007
24292
|
* Attempts to store the data to the LMS
|
|
24008
24293
|
* @param {boolean} terminateCommit
|
|
24294
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
24009
24295
|
* @return {ResultObject}
|
|
24010
24296
|
*/
|
|
24011
|
-
storeData(terminateCommit) {
|
|
24297
|
+
storeData(terminateCommit, trigger) {
|
|
24012
24298
|
if (terminateCommit) {
|
|
24013
24299
|
if (this.cmi.mode === "normal") {
|
|
24014
24300
|
if (this.cmi.credit === "credit") {
|
|
@@ -24049,7 +24335,7 @@ ${stackTrace}`);
|
|
|
24049
24335
|
}
|
|
24050
24336
|
this._globalObjectiveManager.syncCmiToSequencingActivity(completionStatusEnum, successStatusEnum, scoreObject);
|
|
24051
24337
|
if (typeof this.settings.lmsCommitUrl === "string") {
|
|
24052
|
-
const result = this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit);
|
|
24338
|
+
const result = this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit, trigger);
|
|
24053
24339
|
if (navRequest && result.navRequest !== void 0 && result.navRequest !== "" && typeof result.navRequest === "string") {
|
|
24054
24340
|
const parsed = parseNavigationRequest(result.navRequest);
|
|
24055
24341
|
if (!parsed.valid) {
|