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/scorm2004.js
CHANGED
|
@@ -198,6 +198,15 @@ this.Scorm2004API = (function () {
|
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
const appendQueryParam = (url, name, value) => {
|
|
202
|
+
const fragmentIndex = url.indexOf("#");
|
|
203
|
+
const baseUrl = fragmentIndex === -1 ? url : url.slice(0, fragmentIndex);
|
|
204
|
+
const fragment = fragmentIndex === -1 ? "" : url.slice(fragmentIndex);
|
|
205
|
+
const separator = baseUrl.includes("?") ? baseUrl.endsWith("?") || baseUrl.endsWith("&") ? "" : "&" : "?";
|
|
206
|
+
const queryParam = `${encodeURIComponent(name)}=${encodeURIComponent(String(value))}`;
|
|
207
|
+
return `${baseUrl}${separator}${queryParam}${fragment}`;
|
|
208
|
+
};
|
|
209
|
+
|
|
201
210
|
var __defProp$15 = Object.defineProperty;
|
|
202
211
|
var __defNormalProp$15 = (obj, key, value) => key in obj ? __defProp$15(obj, key, {
|
|
203
212
|
enumerable: true,
|
|
@@ -570,7 +579,10 @@ this.Scorm2004API = (function () {
|
|
|
570
579
|
INVALID_SET_VALUE: 402,
|
|
571
580
|
READ_ONLY_ELEMENT: 403,
|
|
572
581
|
TYPE_MISMATCH: 405,
|
|
573
|
-
|
|
582
|
+
// SCORM 1.2 has no out-of-range error code; an out-of-range value is reported
|
|
583
|
+
// as 405 (incorrect data type), per the SCORM 1.2 RTE error table and the ADL
|
|
584
|
+
// 1.2 CTS (DataModelValidator.checkScoreDecimal failure -> CMICategory 405).
|
|
585
|
+
VALUE_OUT_OF_RANGE: 405};
|
|
574
586
|
const scorm2004_errors = {
|
|
575
587
|
...global_errors,
|
|
576
588
|
INITIALIZATION_FAILED: 102,
|
|
@@ -725,6 +737,7 @@ this.Scorm2004API = (function () {
|
|
|
725
737
|
xhrWithCredentials: false,
|
|
726
738
|
fetchMode: "cors",
|
|
727
739
|
asyncModeBeaconBehavior: "never",
|
|
740
|
+
includeCommitSequence: false,
|
|
728
741
|
responseHandler: async function (response) {
|
|
729
742
|
if (typeof response !== "undefined") {
|
|
730
743
|
let httpResult = null;
|
|
@@ -1138,7 +1151,7 @@ this.Scorm2004API = (function () {
|
|
|
1138
1151
|
wrapper() {
|
|
1139
1152
|
if (!this._cancelled) {
|
|
1140
1153
|
if (this._API.isInitialized()) {
|
|
1141
|
-
(async () => await this._API.commit(this._callback))();
|
|
1154
|
+
(async () => await this._API.commit(this._callback, false, "autocommit"))();
|
|
1142
1155
|
}
|
|
1143
1156
|
}
|
|
1144
1157
|
}
|
|
@@ -4611,6 +4624,7 @@ this.Scorm2004API = (function () {
|
|
|
4611
4624
|
* @param {ErrorCode} error_codes - The error codes object
|
|
4612
4625
|
*/
|
|
4613
4626
|
constructor(settings, error_codes) {
|
|
4627
|
+
__publicField$Q(this, "reportsRequestCompletion", true);
|
|
4614
4628
|
__publicField$Q(this, "settings");
|
|
4615
4629
|
__publicField$Q(this, "error_codes");
|
|
4616
4630
|
this.settings = settings;
|
|
@@ -4629,13 +4643,17 @@ this.Scorm2004API = (function () {
|
|
|
4629
4643
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
4630
4644
|
* @param {Function} apiLog - Function to log API messages with appropriate levels
|
|
4631
4645
|
* @param {Function} processListeners - Function to trigger event listeners for commit events
|
|
4646
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
4647
|
+
* @param {Function} onRequestComplete - Callback invoked after the background request settles
|
|
4632
4648
|
* @return {ResultObject} - Immediate optimistic success result
|
|
4633
4649
|
*/
|
|
4634
4650
|
processHttpRequest(url, params) {
|
|
4635
4651
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
4636
4652
|
let apiLog = arguments.length > 3 ? arguments[3] : undefined;
|
|
4637
4653
|
let processListeners = arguments.length > 4 ? arguments[4] : undefined;
|
|
4638
|
-
|
|
4654
|
+
let metadata = arguments.length > 5 ? arguments[5] : undefined;
|
|
4655
|
+
let onRequestComplete = arguments.length > 6 ? arguments[6] : undefined;
|
|
4656
|
+
this._performAsyncRequest(url, params, immediate, apiLog, processListeners, metadata, onRequestComplete);
|
|
4639
4657
|
return {
|
|
4640
4658
|
result: global_constants.SCORM_TRUE,
|
|
4641
4659
|
errorCode: 0
|
|
@@ -4648,11 +4666,14 @@ this.Scorm2004API = (function () {
|
|
|
4648
4666
|
* @param {boolean} immediate - Whether this is an immediate request
|
|
4649
4667
|
* @param apiLog - Function to log API messages
|
|
4650
4668
|
* @param {Function} processListeners - Function to process event listeners
|
|
4669
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
4670
|
+
* @param {Function} onRequestComplete - Callback invoked after the request settles
|
|
4651
4671
|
* @private
|
|
4652
4672
|
*/
|
|
4653
|
-
async _performAsyncRequest(url, params, immediate, apiLog, processListeners) {
|
|
4673
|
+
async _performAsyncRequest(url, params, immediate, apiLog, processListeners, metadata, onRequestComplete) {
|
|
4654
4674
|
try {
|
|
4655
|
-
const
|
|
4675
|
+
const handledParams = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
4676
|
+
const processedParams = handledParams;
|
|
4656
4677
|
let response;
|
|
4657
4678
|
if (immediate && this.settings.asyncModeBeaconBehavior !== "never") {
|
|
4658
4679
|
response = await this.performBeacon(url, processedParams);
|
|
@@ -4668,7 +4689,9 @@ this.Scorm2004API = (function () {
|
|
|
4668
4689
|
} catch (e) {
|
|
4669
4690
|
const message = e instanceof Error ? e.message : String(e);
|
|
4670
4691
|
apiLog("processHttpRequest", `Async request failed: ${message}`, LogLevelEnum.ERROR);
|
|
4671
|
-
processListeners("CommitError");
|
|
4692
|
+
processListeners("CommitError", void 0, this.error_codes.GENERAL_COMMIT_FAILURE || 391);
|
|
4693
|
+
} finally {
|
|
4694
|
+
onRequestComplete?.();
|
|
4672
4695
|
}
|
|
4673
4696
|
}
|
|
4674
4697
|
/**
|
|
@@ -4830,10 +4853,13 @@ this.Scorm2004API = (function () {
|
|
|
4830
4853
|
}
|
|
4831
4854
|
/**
|
|
4832
4855
|
* Gets the appropriate error code for undefined data model elements.
|
|
4833
|
-
* SCORM 2004
|
|
4856
|
+
* Both SCORM 2004 and SCORM 1.2 use UNDEFINED_DATA_MODEL (401): an
|
|
4857
|
+
* unrecognized element is "Not implemented", not a general exception. SCORM
|
|
4858
|
+
* 1.2 previously returned GENERAL (101) here, which is non-conformant — the
|
|
4859
|
+
* ADL 1.2 CTS and the SCORM 1.2 RTE spec expect 401 for an unknown element.
|
|
4834
4860
|
*/
|
|
4835
|
-
getUndefinedDataModelErrorCode(
|
|
4836
|
-
return
|
|
4861
|
+
getUndefinedDataModelErrorCode() {
|
|
4862
|
+
return getErrorCode(this.context.errorCodes, "UNDEFINED_DATA_MODEL");
|
|
4837
4863
|
}
|
|
4838
4864
|
/**
|
|
4839
4865
|
* Sets a value on a CMI element path
|
|
@@ -4857,7 +4883,7 @@ this.Scorm2004API = (function () {
|
|
|
4857
4883
|
let returnValue = global_constants.SCORM_FALSE;
|
|
4858
4884
|
let foundFirstIndex = false;
|
|
4859
4885
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
4860
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
4886
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
4861
4887
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
4862
4888
|
const attribute = structure[idx];
|
|
4863
4889
|
if (idx === structure.length - 1) {
|
|
@@ -4897,12 +4923,13 @@ this.Scorm2004API = (function () {
|
|
|
4897
4923
|
this.context.throwSCORMError(CMIElement, getErrorCode(this.context.errorCodes, "GENERAL_GET_FAILURE"), "The _version keyword was used incorrectly");
|
|
4898
4924
|
return "";
|
|
4899
4925
|
}
|
|
4926
|
+
this.context.setLastErrorCode("0");
|
|
4900
4927
|
const structure = CMIElement.split(".");
|
|
4901
4928
|
let refObject = this.context.getDataModel();
|
|
4902
4929
|
let attribute = null;
|
|
4903
4930
|
const uninitializedErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) has not been initialized.`;
|
|
4904
4931
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
4905
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
4932
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
4906
4933
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
4907
4934
|
attribute = structure[idx];
|
|
4908
4935
|
const validationResult = this.validateGetAttribute(refObject, attribute, CMIElement, scorm2004, invalidErrorCode, invalidErrorMessage, idx === structure.length - 1);
|
|
@@ -5628,8 +5655,9 @@ ${stackTrace}`);
|
|
|
5628
5655
|
* @param {string} functionName - The name of the function that triggered the event
|
|
5629
5656
|
* @param {string} CMIElement - The CMI element that was affected
|
|
5630
5657
|
* @param {any} value - The value that was set
|
|
5658
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
5631
5659
|
*/
|
|
5632
|
-
processListeners(functionName, CMIElement, value) {
|
|
5660
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
5633
5661
|
this.apiLog(functionName, value, LogLevelEnum.INFO, CMIElement);
|
|
5634
5662
|
const listeners = this.listenerMap.get(functionName);
|
|
5635
5663
|
if (!listeners) return;
|
|
@@ -5649,9 +5677,17 @@ ${stackTrace}`);
|
|
|
5649
5677
|
if (functionName.startsWith("Sequence")) {
|
|
5650
5678
|
listener.callback(value);
|
|
5651
5679
|
} else if (functionName === "CommitError") {
|
|
5652
|
-
|
|
5680
|
+
if (context !== void 0) {
|
|
5681
|
+
listener.callback(value, context);
|
|
5682
|
+
} else {
|
|
5683
|
+
listener.callback(value);
|
|
5684
|
+
}
|
|
5653
5685
|
} else if (functionName === "CommitSuccess") {
|
|
5654
|
-
|
|
5686
|
+
if (context !== void 0) {
|
|
5687
|
+
listener.callback(context);
|
|
5688
|
+
} else {
|
|
5689
|
+
listener.callback();
|
|
5690
|
+
}
|
|
5655
5691
|
} else {
|
|
5656
5692
|
listener.callback(CMIElement, value);
|
|
5657
5693
|
}
|
|
@@ -5760,16 +5796,23 @@ ${stackTrace}`);
|
|
|
5760
5796
|
* Store commit data offline
|
|
5761
5797
|
* @param {string} courseId - Identifier for the course
|
|
5762
5798
|
* @param {CommitObject} commitData - The data to store offline
|
|
5799
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
5763
5800
|
* @returns {ResultObject} - Result of the storage operation
|
|
5764
5801
|
*/
|
|
5765
|
-
storeOffline(courseId, commitData) {
|
|
5802
|
+
storeOffline(courseId, commitData, metadata) {
|
|
5766
5803
|
try {
|
|
5767
5804
|
const queueItem = {
|
|
5768
5805
|
id: `${courseId}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
5769
5806
|
courseId,
|
|
5770
5807
|
timestamp: Date.now(),
|
|
5771
5808
|
data: commitData,
|
|
5772
|
-
syncAttempts: 0
|
|
5809
|
+
syncAttempts: 0,
|
|
5810
|
+
...(metadata?.isTerminateCommit !== void 0 ? {
|
|
5811
|
+
isTerminateCommit: metadata.isTerminateCommit
|
|
5812
|
+
} : {}),
|
|
5813
|
+
...(metadata?.sequence !== void 0 ? {
|
|
5814
|
+
sequence: metadata.sequence
|
|
5815
|
+
} : {})
|
|
5773
5816
|
};
|
|
5774
5817
|
const currentQueue = this.getFromStorage(this.syncQueue) || [];
|
|
5775
5818
|
currentQueue.push(queueItem);
|
|
@@ -5828,7 +5871,14 @@ ${stackTrace}`);
|
|
|
5828
5871
|
continue;
|
|
5829
5872
|
}
|
|
5830
5873
|
try {
|
|
5831
|
-
const syncResult = await this.sendDataToLMS(item.data
|
|
5874
|
+
const syncResult = await this.sendDataToLMS(item.data, {
|
|
5875
|
+
...(item.isTerminateCommit !== void 0 ? {
|
|
5876
|
+
isTerminateCommit: item.isTerminateCommit
|
|
5877
|
+
} : {}),
|
|
5878
|
+
...(item.sequence !== void 0 ? {
|
|
5879
|
+
sequence: item.sequence
|
|
5880
|
+
} : {})
|
|
5881
|
+
});
|
|
5832
5882
|
if (syncResult.result === true || syncResult.result === global_constants.SCORM_TRUE) {
|
|
5833
5883
|
this.apiLog("OfflineStorageService", `Successfully synced item ${item.id}`, LogLevelEnum.INFO);
|
|
5834
5884
|
} else {
|
|
@@ -5855,17 +5905,27 @@ ${stackTrace}`);
|
|
|
5855
5905
|
/**
|
|
5856
5906
|
* Send data to the LMS when online
|
|
5857
5907
|
* @param {CommitObject} data - The data to send to the LMS
|
|
5908
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
5858
5909
|
* @returns {Promise<ResultObject>} - Result of the sync operation
|
|
5859
5910
|
*/
|
|
5860
|
-
async sendDataToLMS(data) {
|
|
5861
|
-
|
|
5911
|
+
async sendDataToLMS(data, metadata) {
|
|
5912
|
+
const configuredCommitUrl = this.settings.lmsCommitUrl;
|
|
5913
|
+
if (!configuredCommitUrl) {
|
|
5862
5914
|
return {
|
|
5863
5915
|
result: global_constants.SCORM_FALSE,
|
|
5864
5916
|
errorCode: this.error_codes.GENERAL || 101
|
|
5865
5917
|
};
|
|
5866
5918
|
}
|
|
5867
5919
|
try {
|
|
5868
|
-
const
|
|
5920
|
+
const lmsCommitUrl = String(configuredCommitUrl);
|
|
5921
|
+
const processedData = this.settings.requestHandler(data, {
|
|
5922
|
+
isTerminateCommit: metadata?.isTerminateCommit ?? false,
|
|
5923
|
+
trigger: "offline-replay",
|
|
5924
|
+
...(metadata?.sequence !== void 0 ? {
|
|
5925
|
+
sequence: metadata.sequence
|
|
5926
|
+
} : {})
|
|
5927
|
+
});
|
|
5928
|
+
const requestUrl = metadata?.isTerminateCommit && this.settings.terminateCommitParam ? appendQueryParam(lmsCommitUrl, this.settings.terminateCommitParam, "true") : lmsCommitUrl;
|
|
5869
5929
|
const init = {
|
|
5870
5930
|
method: "POST",
|
|
5871
5931
|
mode: this.settings.fetchMode,
|
|
@@ -5878,7 +5938,7 @@ ${stackTrace}`);
|
|
|
5878
5938
|
if (this.settings.xhrWithCredentials) {
|
|
5879
5939
|
init.credentials = "include";
|
|
5880
5940
|
}
|
|
5881
|
-
const response = await fetch(
|
|
5941
|
+
const response = await fetch(requestUrl, init);
|
|
5882
5942
|
const result = typeof this.settings.responseHandler === "function" ? await this.settings.responseHandler(response) : await response.json();
|
|
5883
5943
|
if (response.status >= 200 && response.status <= 299 && (result.result === true || result.result === global_constants.SCORM_TRUE)) {
|
|
5884
5944
|
if (!Object.hasOwnProperty.call(result, "errorCode")) {
|
|
@@ -14775,6 +14835,8 @@ ${stackTrace}`);
|
|
|
14775
14835
|
* @param {boolean} immediate - Whether this is a termination commit (use sendBeacon)
|
|
14776
14836
|
* @param {Function} _apiLog - Function to log API messages (unused in synchronous mode - errors returned directly)
|
|
14777
14837
|
* @param {Function} _processListeners - Function to trigger event listeners (unused in synchronous mode - no async events)
|
|
14838
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
14839
|
+
* @param {Function} _onRequestComplete - Completion callback (unused because requests settle before return)
|
|
14778
14840
|
* @return {ResultObject} - The result of the request (synchronous)
|
|
14779
14841
|
*
|
|
14780
14842
|
* @remarks
|
|
@@ -14786,20 +14848,23 @@ ${stackTrace}`);
|
|
|
14786
14848
|
*/
|
|
14787
14849
|
processHttpRequest(url, params) {
|
|
14788
14850
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
14851
|
+
let metadata = arguments.length > 5 ? arguments[5] : undefined;
|
|
14789
14852
|
if (immediate) {
|
|
14790
|
-
return this._handleImmediateRequest(url, params);
|
|
14853
|
+
return this._handleImmediateRequest(url, params, metadata);
|
|
14791
14854
|
}
|
|
14792
|
-
return this._performSyncXHR(url, params);
|
|
14855
|
+
return this._performSyncXHR(url, params, metadata);
|
|
14793
14856
|
}
|
|
14794
14857
|
/**
|
|
14795
14858
|
* Handles an immediate request using sendBeacon
|
|
14796
14859
|
* @param {string} url - The URL to send the request to
|
|
14797
14860
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
14861
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
14798
14862
|
* @return {ResultObject} - The result based on beacon success
|
|
14799
14863
|
* @private
|
|
14800
14864
|
*/
|
|
14801
|
-
_handleImmediateRequest(url, params) {
|
|
14802
|
-
const
|
|
14865
|
+
_handleImmediateRequest(url, params, metadata) {
|
|
14866
|
+
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
14867
|
+
const requestPayload = handledPayload ?? params;
|
|
14803
14868
|
const {
|
|
14804
14869
|
body
|
|
14805
14870
|
} = this._prepareRequestBody(requestPayload);
|
|
@@ -14815,11 +14880,13 @@ ${stackTrace}`);
|
|
|
14815
14880
|
* Performs a synchronous XMLHttpRequest
|
|
14816
14881
|
* @param {string} url - The URL to send the request to
|
|
14817
14882
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
14883
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
14818
14884
|
* @return {ResultObject} - The result of the request
|
|
14819
14885
|
* @private
|
|
14820
14886
|
*/
|
|
14821
|
-
_performSyncXHR(url, params) {
|
|
14822
|
-
const
|
|
14887
|
+
_performSyncXHR(url, params, metadata) {
|
|
14888
|
+
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
14889
|
+
const requestPayload = handledPayload ?? params;
|
|
14823
14890
|
const {
|
|
14824
14891
|
body,
|
|
14825
14892
|
contentType
|
|
@@ -14892,9 +14959,16 @@ ${stackTrace}`);
|
|
|
14892
14959
|
* @param {number} invalidTypeCode - The error code for invalid type
|
|
14893
14960
|
* @param {number} invalidRangeCode - The error code for invalid range
|
|
14894
14961
|
* @param {typeof BaseScormValidationError} errorClass - The error class to use for validation errors
|
|
14962
|
+
* @param {boolean} allowEmptyString - When true, an empty string is accepted (clears the value).
|
|
14963
|
+
* SCORM 1.2 score elements may be blank per the ADL 1.2 CTS
|
|
14964
|
+
* (DataModelValidator.checkScoreDecimal treats blank as valid).
|
|
14895
14965
|
* @return {boolean} - True if validation passes, throws an error otherwise
|
|
14896
14966
|
*/
|
|
14897
14967
|
validateScore(CMIElement, value, decimalRegex, scoreRange, invalidTypeCode, invalidRangeCode, errorClass) {
|
|
14968
|
+
let allowEmptyString = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : false;
|
|
14969
|
+
if (allowEmptyString && value === "") {
|
|
14970
|
+
return true;
|
|
14971
|
+
}
|
|
14898
14972
|
return checkValidFormat(CMIElement, value, decimalRegex, invalidTypeCode, errorClass) && (!scoreRange || checkValidRange(CMIElement, value, scoreRange, invalidRangeCode, errorClass));
|
|
14899
14973
|
}
|
|
14900
14974
|
/**
|
|
@@ -14990,6 +15064,21 @@ ${stackTrace}`);
|
|
|
14990
15064
|
__publicField$o(this, "_offlineStorageService");
|
|
14991
15065
|
__publicField$o(this, "_cmiValueAccessService");
|
|
14992
15066
|
__publicField$o(this, "_courseId", "");
|
|
15067
|
+
__publicField$o(this, "_pendingCommitCount", 0);
|
|
15068
|
+
/**
|
|
15069
|
+
* Monotonic sequence for commits captured by this API instance. It is
|
|
15070
|
+
* intentionally not reset by reset().
|
|
15071
|
+
*/
|
|
15072
|
+
__publicField$o(this, "_commitSequence", 0);
|
|
15073
|
+
__publicField$o(this, "_commitSettleWaiters", []);
|
|
15074
|
+
/**
|
|
15075
|
+
* Canonical paths of every CMI element that has been explicitly assigned a
|
|
15076
|
+
* value via SetValue / loadFromJSON (both funnel through _commonSetCMIValue).
|
|
15077
|
+
* Used by standards that must tell "implemented but never set" apart from a
|
|
15078
|
+
* legitimately empty value when answering GetValue (SCORM 2004 error 403).
|
|
15079
|
+
* Cleared on reset so a fresh SCO attempt starts with nothing "set".
|
|
15080
|
+
*/
|
|
15081
|
+
__publicField$o(this, "_setCMIElements", /* @__PURE__ */new Set());
|
|
14993
15082
|
__publicField$o(this, "startingData");
|
|
14994
15083
|
__publicField$o(this, "currentState");
|
|
14995
15084
|
if (new.target === BaseAPI) {
|
|
@@ -15136,6 +15225,7 @@ ${stackTrace}`);
|
|
|
15136
15225
|
this.lastErrorCode = "0";
|
|
15137
15226
|
this._eventService.reset();
|
|
15138
15227
|
this.startingData = {};
|
|
15228
|
+
this._setCMIElements.clear();
|
|
15139
15229
|
if (this._offlineStorageService) {
|
|
15140
15230
|
this._offlineStorageService.updateSettings(this.settings);
|
|
15141
15231
|
if (settings?.courseId) {
|
|
@@ -15219,6 +15309,54 @@ ${stackTrace}`);
|
|
|
15219
15309
|
this._loggingService?.setLogHandler(settings.onLogMessage);
|
|
15220
15310
|
}
|
|
15221
15311
|
}
|
|
15312
|
+
/**
|
|
15313
|
+
* Gets the number of captured commit requests that have not yet settled.
|
|
15314
|
+
*
|
|
15315
|
+
* @return {number} The number of in-flight commits
|
|
15316
|
+
*/
|
|
15317
|
+
get pendingCommitCount() {
|
|
15318
|
+
return this._pendingCommitCount;
|
|
15319
|
+
}
|
|
15320
|
+
/**
|
|
15321
|
+
* Resolves when all currently in-flight commits have settled. A timeout is
|
|
15322
|
+
* best-effort: the promise resolves when it elapses even if commits remain,
|
|
15323
|
+
* and callers can inspect pendingCommitCount afterward to detect that case.
|
|
15324
|
+
*
|
|
15325
|
+
* @param {Object} [options] - Settle options
|
|
15326
|
+
* @param {number} [options.timeoutMs] - Maximum time to wait in milliseconds
|
|
15327
|
+
* @return {Promise<void>} A promise that resolves after the drain or timeout
|
|
15328
|
+
*/
|
|
15329
|
+
whenCommitsSettled(options) {
|
|
15330
|
+
if (this._pendingCommitCount === 0) {
|
|
15331
|
+
return Promise.resolve();
|
|
15332
|
+
}
|
|
15333
|
+
return new Promise(resolve => {
|
|
15334
|
+
const waiter = {
|
|
15335
|
+
resolve
|
|
15336
|
+
};
|
|
15337
|
+
if (options?.timeoutMs !== void 0) {
|
|
15338
|
+
waiter.timeoutId = setTimeout(() => {
|
|
15339
|
+
const waiterIndex = this._commitSettleWaiters.indexOf(waiter);
|
|
15340
|
+
if (waiterIndex === -1) {
|
|
15341
|
+
return;
|
|
15342
|
+
}
|
|
15343
|
+
this._commitSettleWaiters.splice(waiterIndex, 1);
|
|
15344
|
+
resolve();
|
|
15345
|
+
}, options.timeoutMs);
|
|
15346
|
+
}
|
|
15347
|
+
this._commitSettleWaiters.push(waiter);
|
|
15348
|
+
});
|
|
15349
|
+
}
|
|
15350
|
+
/** Resolve and clear every waiter after the pending count reaches zero. */
|
|
15351
|
+
_flushCommitSettleWaiters() {
|
|
15352
|
+
const waiters = this._commitSettleWaiters.splice(0);
|
|
15353
|
+
for (const waiter of waiters) {
|
|
15354
|
+
if (waiter.timeoutId !== void 0) {
|
|
15355
|
+
clearTimeout(waiter.timeoutId);
|
|
15356
|
+
}
|
|
15357
|
+
waiter.resolve();
|
|
15358
|
+
}
|
|
15359
|
+
}
|
|
15222
15360
|
/**
|
|
15223
15361
|
* Terminates the current run of the API
|
|
15224
15362
|
* @param {string} callbackName
|
|
@@ -15239,7 +15377,7 @@ ${stackTrace}`);
|
|
|
15239
15377
|
} else {
|
|
15240
15378
|
stateCheckPassed = true;
|
|
15241
15379
|
this.processListeners("BeforeTerminate");
|
|
15242
|
-
const result = this.storeData(true);
|
|
15380
|
+
const result = this.storeData(true, "terminate");
|
|
15243
15381
|
if ((result.errorCode ?? 0) > 0) {
|
|
15244
15382
|
if (result.errorMessage) {
|
|
15245
15383
|
this.apiLog("terminate", `Terminate failed with error: ${result.errorMessage}`, LogLevelEnum.ERROR);
|
|
@@ -15279,6 +15417,9 @@ ${stackTrace}`);
|
|
|
15279
15417
|
} catch (e) {
|
|
15280
15418
|
returnValue = this.handleValueAccessException(CMIElement, e, returnValue);
|
|
15281
15419
|
}
|
|
15420
|
+
if (this.lastErrorCode === "0") {
|
|
15421
|
+
this.checkUninitializedGet(CMIElement, returnValue);
|
|
15422
|
+
}
|
|
15282
15423
|
this.processListeners(callbackName, CMIElement);
|
|
15283
15424
|
}
|
|
15284
15425
|
this.apiLog(callbackName, ": returned: " + returnValue, LogLevelEnum.INFO, CMIElement);
|
|
@@ -15288,6 +15429,10 @@ ${stackTrace}`);
|
|
|
15288
15429
|
if (this.lastErrorCode === "0") {
|
|
15289
15430
|
this.clearSCORMError(returnValue);
|
|
15290
15431
|
}
|
|
15432
|
+
const rawReturn = returnValue;
|
|
15433
|
+
if (typeof rawReturn === "number" || typeof rawReturn === "boolean") {
|
|
15434
|
+
return String(rawReturn);
|
|
15435
|
+
}
|
|
15291
15436
|
return returnValue;
|
|
15292
15437
|
}
|
|
15293
15438
|
/**
|
|
@@ -15331,10 +15476,12 @@ ${stackTrace}`);
|
|
|
15331
15476
|
* Orders LMS to store all content parameters
|
|
15332
15477
|
* @param {string} callbackName
|
|
15333
15478
|
* @param {boolean} checkTerminated
|
|
15479
|
+
* @param {CommitTrigger} trigger - What initiated the commit
|
|
15334
15480
|
* @return {string}
|
|
15335
15481
|
*/
|
|
15336
15482
|
commit(callbackName) {
|
|
15337
15483
|
let checkTerminated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
15484
|
+
let trigger = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "manual";
|
|
15338
15485
|
this.clearScheduledCommit();
|
|
15339
15486
|
let returnValue = global_constants.SCORM_TRUE;
|
|
15340
15487
|
if (this.isNotInitialized()) {
|
|
@@ -15346,7 +15493,7 @@ ${stackTrace}`);
|
|
|
15346
15493
|
this.throwSCORMError("api", errorCode);
|
|
15347
15494
|
if (errorCode === 143) returnValue = global_constants.SCORM_FALSE;
|
|
15348
15495
|
} else {
|
|
15349
|
-
const result = this.storeData(false);
|
|
15496
|
+
const result = this.storeData(false, trigger);
|
|
15350
15497
|
const errorCode = result.errorCode ?? 0;
|
|
15351
15498
|
if (errorCode > 0) {
|
|
15352
15499
|
if (result.errorMessage) {
|
|
@@ -15567,7 +15714,11 @@ ${stackTrace}`);
|
|
|
15567
15714
|
* @return {string}
|
|
15568
15715
|
*/
|
|
15569
15716
|
_commonSetCMIValue(methodName, scorm2004, CMIElement, value) {
|
|
15570
|
-
|
|
15717
|
+
const result = this._cmiValueAccessService.setCMIValue(methodName, scorm2004, CMIElement, value);
|
|
15718
|
+
if (result === global_constants.SCORM_TRUE) {
|
|
15719
|
+
this._setCMIElements.add(CMIElement);
|
|
15720
|
+
}
|
|
15721
|
+
return result;
|
|
15571
15722
|
}
|
|
15572
15723
|
/**
|
|
15573
15724
|
* Gets a value from the CMI Object.
|
|
@@ -15581,6 +15732,17 @@ ${stackTrace}`);
|
|
|
15581
15732
|
_commonGetCMIValue(methodName, scorm2004, CMIElement) {
|
|
15582
15733
|
return this._cmiValueAccessService.getCMIValue(methodName, scorm2004, CMIElement);
|
|
15583
15734
|
}
|
|
15735
|
+
/**
|
|
15736
|
+
* Hook invoked by getValue after a successful resolution. Standards that must
|
|
15737
|
+
* distinguish "implemented but never set, no default value" from a
|
|
15738
|
+
* legitimately empty value override this to raise VALUE_NOT_INITIALIZED.
|
|
15739
|
+
* Default is a no-op, so SCORM 1.2 / AICC keep returning "" with code 0.
|
|
15740
|
+
*
|
|
15741
|
+
* @param {string} _CMIElement - the element that was read
|
|
15742
|
+
* @param {any} _returnValue - the value getCMIValue resolved
|
|
15743
|
+
* @protected
|
|
15744
|
+
*/
|
|
15745
|
+
checkUninitializedGet(_CMIElement, _returnValue) {}
|
|
15584
15746
|
/**
|
|
15585
15747
|
* Returns true if the API's current state is STATE_INITIALIZED
|
|
15586
15748
|
*
|
|
@@ -15663,9 +15825,14 @@ ${stackTrace}`);
|
|
|
15663
15825
|
* @param {string} functionName - The name of the function/event that occurred
|
|
15664
15826
|
* @param {string} CMIElement - Optional CMI element involved in the event
|
|
15665
15827
|
* @param {any} value - Optional value associated with the event
|
|
15828
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
15666
15829
|
*/
|
|
15667
|
-
processListeners(functionName, CMIElement, value) {
|
|
15668
|
-
|
|
15830
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
15831
|
+
if (context !== void 0) {
|
|
15832
|
+
this._eventService.processListeners(functionName, CMIElement, value, context);
|
|
15833
|
+
} else {
|
|
15834
|
+
this._eventService.processListeners(functionName, CMIElement, value);
|
|
15835
|
+
}
|
|
15669
15836
|
}
|
|
15670
15837
|
/**
|
|
15671
15838
|
* Throws a SCORM error with the specified error number and optional message.
|
|
@@ -15787,23 +15954,97 @@ ${stackTrace}`);
|
|
|
15787
15954
|
* @param {string} url - The URL to send the request to
|
|
15788
15955
|
* @param {CommitObject | StringKeyMap | Array<any>} params - The parameters to send
|
|
15789
15956
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
15957
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
15790
15958
|
* @returns {ResultObject} - The result of the request
|
|
15791
15959
|
*/
|
|
15792
15960
|
processHttpRequest(url, params) {
|
|
15793
15961
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15962
|
+
let trigger = arguments.length > 3 ? arguments[3] : undefined;
|
|
15963
|
+
const sequence = ++this._commitSequence;
|
|
15964
|
+
this._pendingCommitCount += 1;
|
|
15965
|
+
let settled = false;
|
|
15966
|
+
let completionDeferred = false;
|
|
15967
|
+
const settle = () => {
|
|
15968
|
+
if (settled) {
|
|
15969
|
+
return;
|
|
15970
|
+
}
|
|
15971
|
+
settled = true;
|
|
15972
|
+
this._pendingCommitCount -= 1;
|
|
15973
|
+
if (this._pendingCommitCount === 0) {
|
|
15974
|
+
this._flushCommitSettleWaiters();
|
|
15975
|
+
}
|
|
15976
|
+
};
|
|
15977
|
+
try {
|
|
15978
|
+
const resolvedTrigger = trigger ?? (immediate ? "terminate" : "manual");
|
|
15979
|
+
let finalParams = params;
|
|
15980
|
+
if (immediate && this.settings.terminateCommitPayloadField) {
|
|
15981
|
+
const field = this.settings.terminateCommitPayloadField;
|
|
15982
|
+
if (Array.isArray(finalParams)) {
|
|
15983
|
+
finalParams = [...finalParams, `${encodeURIComponent(field)}=true`];
|
|
15984
|
+
} else if (finalParams && typeof finalParams === "object") {
|
|
15985
|
+
finalParams = {
|
|
15986
|
+
...finalParams,
|
|
15987
|
+
[field]: true
|
|
15988
|
+
};
|
|
15989
|
+
}
|
|
15990
|
+
}
|
|
15991
|
+
if (this.settings.includeCommitSequence === true) {
|
|
15992
|
+
if (Array.isArray(finalParams)) {
|
|
15993
|
+
finalParams = [...finalParams, `commitSequence=${sequence}`];
|
|
15994
|
+
} else if (finalParams && typeof finalParams === "object") {
|
|
15995
|
+
finalParams = {
|
|
15996
|
+
...finalParams,
|
|
15997
|
+
commitSequence: sequence
|
|
15998
|
+
};
|
|
15999
|
+
}
|
|
16000
|
+
}
|
|
16001
|
+
const finalUrl = immediate && this.settings.terminateCommitParam ? appendQueryParam(url, this.settings.terminateCommitParam, "true") : url;
|
|
16002
|
+
const metadata = {
|
|
16003
|
+
isTerminateCommit: immediate,
|
|
16004
|
+
trigger: resolvedTrigger,
|
|
16005
|
+
sequence
|
|
16006
|
+
};
|
|
16007
|
+
const context = {
|
|
16008
|
+
url: finalUrl,
|
|
16009
|
+
trigger: resolvedTrigger,
|
|
16010
|
+
isTerminateCommit: immediate,
|
|
16011
|
+
sequence
|
|
16012
|
+
};
|
|
16013
|
+
if (this.settings.enableOfflineSupport && this._offlineStorageService && !this._offlineStorageService.isDeviceOnline() && this._courseId) {
|
|
16014
|
+
this.apiLog("processHttpRequest", "Device is offline, storing data locally", LogLevelEnum.INFO);
|
|
16015
|
+
if (finalParams && typeof finalParams === "object" && "cmi" in finalParams) {
|
|
16016
|
+
return this._offlineStorageService.storeOffline(this._courseId, finalParams, {
|
|
16017
|
+
isTerminateCommit: immediate,
|
|
16018
|
+
sequence
|
|
16019
|
+
});
|
|
16020
|
+
} else {
|
|
16021
|
+
this.apiLog("processHttpRequest", "Invalid commit data format for offline storage", LogLevelEnum.ERROR);
|
|
16022
|
+
return {
|
|
16023
|
+
result: global_constants.SCORM_FALSE,
|
|
16024
|
+
errorCode: this._error_codes.GENERAL ?? 101
|
|
16025
|
+
};
|
|
16026
|
+
}
|
|
16027
|
+
}
|
|
16028
|
+
const apiLog = (functionName, message, level, element) => this.apiLog(functionName, message, level, element);
|
|
16029
|
+
const processListeners = (functionName, CMIElement, value) => {
|
|
16030
|
+
if (functionName === "CommitSuccess" || functionName === "CommitError") {
|
|
16031
|
+
if (functionName === "CommitError" && typeof value === "number") {
|
|
16032
|
+
context.errorCode = value;
|
|
16033
|
+
}
|
|
16034
|
+
settle();
|
|
16035
|
+
this.processListeners(functionName, CMIElement, value, context);
|
|
16036
|
+
} else {
|
|
16037
|
+
this.processListeners(functionName, CMIElement, value);
|
|
16038
|
+
}
|
|
16039
|
+
};
|
|
16040
|
+
const result = this._httpService.processHttpRequest(finalUrl, finalParams, immediate, apiLog, processListeners, metadata, settle);
|
|
16041
|
+
completionDeferred = this._httpService.reportsRequestCompletion === true;
|
|
16042
|
+
return result;
|
|
16043
|
+
} finally {
|
|
16044
|
+
if (!completionDeferred) {
|
|
16045
|
+
settle();
|
|
15804
16046
|
}
|
|
15805
16047
|
}
|
|
15806
|
-
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));
|
|
15807
16048
|
}
|
|
15808
16049
|
/**
|
|
15809
16050
|
* Schedules a commit operation to occur after a specified delay.
|
|
@@ -16696,6 +16937,11 @@ ${stackTrace}`);
|
|
|
16696
16937
|
__publicField$l(this, "__invalid_range_code");
|
|
16697
16938
|
__publicField$l(this, "__decimal_regex");
|
|
16698
16939
|
__publicField$l(this, "__error_class");
|
|
16940
|
+
/**
|
|
16941
|
+
* When true, an empty string is a valid value (clears the element). SCORM 1.2
|
|
16942
|
+
* score elements may be blank per the ADL 1.2 CTS; SCORM 2004 leaves this off.
|
|
16943
|
+
*/
|
|
16944
|
+
__publicField$l(this, "__allow_empty_string");
|
|
16699
16945
|
__publicField$l(this, "_raw", "");
|
|
16700
16946
|
__publicField$l(this, "_min", "");
|
|
16701
16947
|
__publicField$l(this, "_max");
|
|
@@ -16707,6 +16953,7 @@ ${stackTrace}`);
|
|
|
16707
16953
|
this.__invalid_range_code = params.invalidRangeCode || scorm12_errors.VALUE_OUT_OF_RANGE;
|
|
16708
16954
|
this.__decimal_regex = params.decimalRegex || scorm12_regex.CMIDecimal;
|
|
16709
16955
|
this.__error_class = params.errorClass;
|
|
16956
|
+
this.__allow_empty_string = params.allowEmptyString ?? false;
|
|
16710
16957
|
}
|
|
16711
16958
|
/**
|
|
16712
16959
|
* Called when the API has been reset
|
|
@@ -16746,7 +16993,7 @@ ${stackTrace}`);
|
|
|
16746
16993
|
* @param {string} raw
|
|
16747
16994
|
*/
|
|
16748
16995
|
set raw(raw) {
|
|
16749
|
-
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)) {
|
|
16996
|
+
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)) {
|
|
16750
16997
|
this._raw = raw;
|
|
16751
16998
|
}
|
|
16752
16999
|
}
|
|
@@ -16762,7 +17009,7 @@ ${stackTrace}`);
|
|
|
16762
17009
|
* @param {string} min
|
|
16763
17010
|
*/
|
|
16764
17011
|
set min(min) {
|
|
16765
|
-
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)) {
|
|
17012
|
+
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)) {
|
|
16766
17013
|
this._min = min;
|
|
16767
17014
|
}
|
|
16768
17015
|
}
|
|
@@ -16778,7 +17025,7 @@ ${stackTrace}`);
|
|
|
16778
17025
|
* @param {string} max
|
|
16779
17026
|
*/
|
|
16780
17027
|
set max(max) {
|
|
16781
|
-
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)) {
|
|
17028
|
+
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)) {
|
|
16782
17029
|
this._max = max;
|
|
16783
17030
|
}
|
|
16784
17031
|
}
|
|
@@ -21344,6 +21591,10 @@ ${stackTrace}`);
|
|
|
21344
21591
|
value
|
|
21345
21592
|
}) : obj[key] = value;
|
|
21346
21593
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21594
|
+
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"]);
|
|
21595
|
+
function normalizeCMIIndices(CMIElement) {
|
|
21596
|
+
return CMIElement.replace(/\.\d+(?=\.|$)/g, ".N");
|
|
21597
|
+
}
|
|
21347
21598
|
class Scorm2004API extends BaseAPI {
|
|
21348
21599
|
/**
|
|
21349
21600
|
* Constructor for SCORM 2004 API
|
|
@@ -21843,6 +22094,35 @@ ${stackTrace}`);
|
|
|
21843
22094
|
getCMIValue(CMIElement) {
|
|
21844
22095
|
return this._commonGetCMIValue("GetValue", true, CMIElement);
|
|
21845
22096
|
}
|
|
22097
|
+
/**
|
|
22098
|
+
* Raises 403 (VALUE_NOT_INITIALIZED) when an implemented, no-default element is
|
|
22099
|
+
* read before it has ever been set, per IEEE 1484.11.2 / SCORM 2004 RTE.
|
|
22100
|
+
*
|
|
22101
|
+
* Invoked by BaseAPI.getValue after an otherwise-successful resolution, so it
|
|
22102
|
+
* only sees values the data model already returned cleanly. The decision is
|
|
22103
|
+
* deliberately gated to this public boundary (never the getters) to keep
|
|
22104
|
+
* serialization/commit/rollup — which read the getters directly — unaffected.
|
|
22105
|
+
*
|
|
22106
|
+
* A 403 is raised only when all hold:
|
|
22107
|
+
* - the resolved value is "" — any non-empty value is by definition set,
|
|
22108
|
+
* including values written by internal sequencing/activity-tree paths that
|
|
22109
|
+
* bypass _commonSetCMIValue (those only ever write non-empty values);
|
|
22110
|
+
* - the element was never set — _setCMIElements records every successful
|
|
22111
|
+
* SetValue, loadFromJSON, and global-objective restore (all route through
|
|
22112
|
+
* _commonSetCMIValue), so an explicit SetValue(x, "") still reads back as
|
|
22113
|
+
* "" with code 0; and
|
|
22114
|
+
* - the element has no spec-defined default ({@link NO_DEFAULT_2004_ELEMENTS}).
|
|
22115
|
+
*
|
|
22116
|
+
* @param {string} CMIElement
|
|
22117
|
+
* @param {*} returnValue - the value getCMIValue resolved
|
|
22118
|
+
* @protected
|
|
22119
|
+
*/
|
|
22120
|
+
checkUninitializedGet(CMIElement, returnValue) {
|
|
22121
|
+
if (returnValue !== "") return;
|
|
22122
|
+
if (this._setCMIElements.has(CMIElement)) return;
|
|
22123
|
+
if (!NO_DEFAULT_2004_ELEMENTS.has(normalizeCMIIndices(CMIElement))) return;
|
|
22124
|
+
this.throwSCORMError(CMIElement, this._error_codes.VALUE_NOT_INITIALIZED ?? 403, `The data model element passed to GetValue (${CMIElement}) has not been initialized.`);
|
|
22125
|
+
}
|
|
21846
22126
|
/**
|
|
21847
22127
|
* Returns the message that corresponds to errorNumber.
|
|
21848
22128
|
* @param {(string|number)} errorNumber
|
|
@@ -21891,9 +22171,10 @@ ${stackTrace}`);
|
|
|
21891
22171
|
/**
|
|
21892
22172
|
* Attempts to store the data to the LMS
|
|
21893
22173
|
* @param {boolean} terminateCommit
|
|
22174
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
21894
22175
|
* @return {ResultObject}
|
|
21895
22176
|
*/
|
|
21896
|
-
storeData(terminateCommit) {
|
|
22177
|
+
storeData(terminateCommit, trigger) {
|
|
21897
22178
|
if (terminateCommit) {
|
|
21898
22179
|
if (this.cmi.mode === "normal") {
|
|
21899
22180
|
if (this.cmi.credit === "credit") {
|
|
@@ -21934,7 +22215,7 @@ ${stackTrace}`);
|
|
|
21934
22215
|
}
|
|
21935
22216
|
this._globalObjectiveManager.syncCmiToSequencingActivity(completionStatusEnum, successStatusEnum, scoreObject);
|
|
21936
22217
|
if (typeof this.settings.lmsCommitUrl === "string") {
|
|
21937
|
-
const result = this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit);
|
|
22218
|
+
const result = this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit, trigger);
|
|
21938
22219
|
if (navRequest && result.navRequest !== void 0 && result.navRequest !== "" && typeof result.navRequest === "string") {
|
|
21939
22220
|
const parsed = parseNavigationRequest(result.navRequest);
|
|
21940
22221
|
if (!parsed.valid) {
|