scorm-again 3.0.5 → 3.1.1
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 +579 -320
- package/dist/cross-frame-api.js.map +1 -1
- package/dist/cross-frame-api.min.js +1 -1
- package/dist/cross-frame-api.min.js.map +1 -1
- package/dist/cross-frame-lms.js +226 -163
- package/dist/cross-frame-lms.js.map +1 -1
- package/dist/cross-frame-lms.min.js +1 -1
- package/dist/cross-frame-lms.min.js.map +1 -1
- package/dist/esm/cross-frame-api.js.map +1 -1
- package/dist/esm/scorm-again.js +391 -86
- 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 +323 -76
- 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 +373 -72
- 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 +32522 -19996
- 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 +9130 -5031
- 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 +30040 -18419
- 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 +27 -27
package/dist/esm/scorm12.js
CHANGED
|
@@ -161,6 +161,15 @@ function memoize(fn, keyFn) {
|
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
const appendQueryParam = (url, name, value) => {
|
|
165
|
+
const fragmentIndex = url.indexOf("#");
|
|
166
|
+
const baseUrl = fragmentIndex === -1 ? url : url.slice(0, fragmentIndex);
|
|
167
|
+
const fragment = fragmentIndex === -1 ? "" : url.slice(fragmentIndex);
|
|
168
|
+
const separator = baseUrl.includes("?") ? baseUrl.endsWith("?") || baseUrl.endsWith("&") ? "" : "&" : "?";
|
|
169
|
+
const queryParam = `${encodeURIComponent(name)}=${encodeURIComponent(String(value))}`;
|
|
170
|
+
return `${baseUrl}${separator}${queryParam}${fragment}`;
|
|
171
|
+
};
|
|
172
|
+
|
|
164
173
|
class BaseCMI {
|
|
165
174
|
/**
|
|
166
175
|
* Flag used during JSON serialization to allow getter access without initialization checks.
|
|
@@ -408,7 +417,10 @@ const scorm12_errors = {
|
|
|
408
417
|
READ_ONLY_ELEMENT: 403,
|
|
409
418
|
WRITE_ONLY_ELEMENT: 404,
|
|
410
419
|
TYPE_MISMATCH: 405,
|
|
411
|
-
|
|
420
|
+
// SCORM 1.2 has no out-of-range error code; an out-of-range value is reported
|
|
421
|
+
// as 405 (incorrect data type), per the SCORM 1.2 RTE error table and the ADL
|
|
422
|
+
// 1.2 CTS (DataModelValidator.checkScoreDecimal failure -> CMICategory 405).
|
|
423
|
+
VALUE_OUT_OF_RANGE: 405,
|
|
412
424
|
DEPENDENCY_NOT_ESTABLISHED: 408
|
|
413
425
|
};
|
|
414
426
|
|
|
@@ -523,6 +535,7 @@ const DefaultSettings = {
|
|
|
523
535
|
xhrWithCredentials: false,
|
|
524
536
|
fetchMode: "cors",
|
|
525
537
|
asyncModeBeaconBehavior: "never",
|
|
538
|
+
includeCommitSequence: false,
|
|
526
539
|
responseHandler: async function(response) {
|
|
527
540
|
if (typeof response !== "undefined") {
|
|
528
541
|
let httpResult = null;
|
|
@@ -907,13 +920,14 @@ class ScheduledCommit {
|
|
|
907
920
|
wrapper() {
|
|
908
921
|
if (!this._cancelled) {
|
|
909
922
|
if (this._API.isInitialized()) {
|
|
910
|
-
(async () => await this._API.commit(this._callback))();
|
|
923
|
+
(async () => await this._API.commit(this._callback, false, "autocommit"))();
|
|
911
924
|
}
|
|
912
925
|
}
|
|
913
926
|
}
|
|
914
927
|
}
|
|
915
928
|
|
|
916
929
|
class AsynchronousHttpService {
|
|
930
|
+
reportsRequestCompletion = true;
|
|
917
931
|
settings;
|
|
918
932
|
error_codes;
|
|
919
933
|
/**
|
|
@@ -938,10 +952,20 @@ class AsynchronousHttpService {
|
|
|
938
952
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
939
953
|
* @param {Function} apiLog - Function to log API messages with appropriate levels
|
|
940
954
|
* @param {Function} processListeners - Function to trigger event listeners for commit events
|
|
955
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
956
|
+
* @param {Function} onRequestComplete - Callback invoked after the background request settles
|
|
941
957
|
* @return {ResultObject} - Immediate optimistic success result
|
|
942
958
|
*/
|
|
943
|
-
processHttpRequest(url, params, immediate = false, apiLog, processListeners) {
|
|
944
|
-
this._performAsyncRequest(
|
|
959
|
+
processHttpRequest(url, params, immediate = false, apiLog, processListeners, metadata, onRequestComplete) {
|
|
960
|
+
this._performAsyncRequest(
|
|
961
|
+
url,
|
|
962
|
+
params,
|
|
963
|
+
immediate,
|
|
964
|
+
apiLog,
|
|
965
|
+
processListeners,
|
|
966
|
+
metadata,
|
|
967
|
+
onRequestComplete
|
|
968
|
+
);
|
|
945
969
|
return {
|
|
946
970
|
result: global_constants.SCORM_TRUE,
|
|
947
971
|
errorCode: 0
|
|
@@ -954,11 +978,14 @@ class AsynchronousHttpService {
|
|
|
954
978
|
* @param {boolean} immediate - Whether this is an immediate request
|
|
955
979
|
* @param apiLog - Function to log API messages
|
|
956
980
|
* @param {Function} processListeners - Function to process event listeners
|
|
981
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
982
|
+
* @param {Function} onRequestComplete - Callback invoked after the request settles
|
|
957
983
|
* @private
|
|
958
984
|
*/
|
|
959
|
-
async _performAsyncRequest(url, params, immediate, apiLog, processListeners) {
|
|
985
|
+
async _performAsyncRequest(url, params, immediate, apiLog, processListeners, metadata, onRequestComplete) {
|
|
960
986
|
try {
|
|
961
|
-
const
|
|
987
|
+
const handledParams = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
988
|
+
const processedParams = handledParams;
|
|
962
989
|
let response;
|
|
963
990
|
if (immediate && this.settings.asyncModeBeaconBehavior !== "never") {
|
|
964
991
|
response = await this.performBeacon(url, processedParams);
|
|
@@ -974,7 +1001,9 @@ class AsynchronousHttpService {
|
|
|
974
1001
|
} catch (e) {
|
|
975
1002
|
const message = e instanceof Error ? e.message : String(e);
|
|
976
1003
|
apiLog("processHttpRequest", `Async request failed: ${message}`, LogLevelEnum.ERROR);
|
|
977
|
-
processListeners("CommitError");
|
|
1004
|
+
processListeners("CommitError", void 0, this.error_codes.GENERAL_COMMIT_FAILURE || 391);
|
|
1005
|
+
} finally {
|
|
1006
|
+
onRequestComplete?.();
|
|
978
1007
|
}
|
|
979
1008
|
}
|
|
980
1009
|
/**
|
|
@@ -1117,10 +1146,13 @@ class CMIValueAccessService {
|
|
|
1117
1146
|
}
|
|
1118
1147
|
/**
|
|
1119
1148
|
* Gets the appropriate error code for undefined data model elements.
|
|
1120
|
-
* SCORM 2004
|
|
1149
|
+
* Both SCORM 2004 and SCORM 1.2 use UNDEFINED_DATA_MODEL (401): an
|
|
1150
|
+
* unrecognized element is "Not implemented", not a general exception. SCORM
|
|
1151
|
+
* 1.2 previously returned GENERAL (101) here, which is non-conformant — the
|
|
1152
|
+
* ADL 1.2 CTS and the SCORM 1.2 RTE spec expect 401 for an unknown element.
|
|
1121
1153
|
*/
|
|
1122
|
-
getUndefinedDataModelErrorCode(
|
|
1123
|
-
return
|
|
1154
|
+
getUndefinedDataModelErrorCode() {
|
|
1155
|
+
return getErrorCode(this.context.errorCodes, "UNDEFINED_DATA_MODEL");
|
|
1124
1156
|
}
|
|
1125
1157
|
/**
|
|
1126
1158
|
* Sets a value on a CMI element path
|
|
@@ -1148,7 +1180,7 @@ class CMIValueAccessService {
|
|
|
1148
1180
|
let returnValue = global_constants.SCORM_FALSE;
|
|
1149
1181
|
let foundFirstIndex = false;
|
|
1150
1182
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
1151
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
1183
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
1152
1184
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
1153
1185
|
const attribute = structure[idx];
|
|
1154
1186
|
if (idx === structure.length - 1) {
|
|
@@ -1218,12 +1250,13 @@ class CMIValueAccessService {
|
|
|
1218
1250
|
);
|
|
1219
1251
|
return "";
|
|
1220
1252
|
}
|
|
1253
|
+
this.context.setLastErrorCode("0");
|
|
1221
1254
|
const structure = CMIElement.split(".");
|
|
1222
1255
|
let refObject = this.context.getDataModel();
|
|
1223
1256
|
let attribute = null;
|
|
1224
1257
|
const uninitializedErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) has not been initialized.`;
|
|
1225
1258
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
1226
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
1259
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
1227
1260
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
1228
1261
|
attribute = structure[idx];
|
|
1229
1262
|
const validationResult = this.validateGetAttribute(
|
|
@@ -1935,8 +1968,9 @@ class EventService {
|
|
|
1935
1968
|
* @param {string} functionName - The name of the function that triggered the event
|
|
1936
1969
|
* @param {string} CMIElement - The CMI element that was affected
|
|
1937
1970
|
* @param {any} value - The value that was set
|
|
1971
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
1938
1972
|
*/
|
|
1939
|
-
processListeners(functionName, CMIElement, value) {
|
|
1973
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
1940
1974
|
this.apiLog(functionName, value, LogLevelEnum.INFO, CMIElement);
|
|
1941
1975
|
const listeners = this.listenerMap.get(functionName);
|
|
1942
1976
|
if (!listeners) return;
|
|
@@ -1961,9 +1995,17 @@ class EventService {
|
|
|
1961
1995
|
if (functionName.startsWith("Sequence")) {
|
|
1962
1996
|
listener.callback(value);
|
|
1963
1997
|
} else if (functionName === "CommitError") {
|
|
1964
|
-
|
|
1998
|
+
if (context !== void 0) {
|
|
1999
|
+
listener.callback(value, context);
|
|
2000
|
+
} else {
|
|
2001
|
+
listener.callback(value);
|
|
2002
|
+
}
|
|
1965
2003
|
} else if (functionName === "CommitSuccess") {
|
|
1966
|
-
|
|
2004
|
+
if (context !== void 0) {
|
|
2005
|
+
listener.callback(context);
|
|
2006
|
+
} else {
|
|
2007
|
+
listener.callback();
|
|
2008
|
+
}
|
|
1967
2009
|
} else {
|
|
1968
2010
|
listener.callback(CMIElement, value);
|
|
1969
2011
|
}
|
|
@@ -2097,16 +2139,19 @@ class OfflineStorageService {
|
|
|
2097
2139
|
* Store commit data offline
|
|
2098
2140
|
* @param {string} courseId - Identifier for the course
|
|
2099
2141
|
* @param {CommitObject} commitData - The data to store offline
|
|
2142
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
2100
2143
|
* @returns {ResultObject} - Result of the storage operation
|
|
2101
2144
|
*/
|
|
2102
|
-
storeOffline(courseId, commitData) {
|
|
2145
|
+
storeOffline(courseId, commitData, metadata) {
|
|
2103
2146
|
try {
|
|
2104
2147
|
const queueItem = {
|
|
2105
2148
|
id: `${courseId}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
2106
2149
|
courseId,
|
|
2107
2150
|
timestamp: Date.now(),
|
|
2108
2151
|
data: commitData,
|
|
2109
|
-
syncAttempts: 0
|
|
2152
|
+
syncAttempts: 0,
|
|
2153
|
+
...metadata?.isTerminateCommit !== void 0 ? { isTerminateCommit: metadata.isTerminateCommit } : {},
|
|
2154
|
+
...metadata?.sequence !== void 0 ? { sequence: metadata.sequence } : {}
|
|
2110
2155
|
};
|
|
2111
2156
|
const currentQueue = this.getFromStorage(this.syncQueue) || [];
|
|
2112
2157
|
currentQueue.push(queueItem);
|
|
@@ -2185,7 +2230,10 @@ class OfflineStorageService {
|
|
|
2185
2230
|
continue;
|
|
2186
2231
|
}
|
|
2187
2232
|
try {
|
|
2188
|
-
const syncResult = await this.sendDataToLMS(item.data
|
|
2233
|
+
const syncResult = await this.sendDataToLMS(item.data, {
|
|
2234
|
+
...item.isTerminateCommit !== void 0 ? { isTerminateCommit: item.isTerminateCommit } : {},
|
|
2235
|
+
...item.sequence !== void 0 ? { sequence: item.sequence } : {}
|
|
2236
|
+
});
|
|
2189
2237
|
if (syncResult.result === true || syncResult.result === global_constants.SCORM_TRUE) {
|
|
2190
2238
|
this.apiLog(
|
|
2191
2239
|
"OfflineStorageService",
|
|
@@ -2232,17 +2280,25 @@ class OfflineStorageService {
|
|
|
2232
2280
|
/**
|
|
2233
2281
|
* Send data to the LMS when online
|
|
2234
2282
|
* @param {CommitObject} data - The data to send to the LMS
|
|
2283
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
2235
2284
|
* @returns {Promise<ResultObject>} - Result of the sync operation
|
|
2236
2285
|
*/
|
|
2237
|
-
async sendDataToLMS(data) {
|
|
2238
|
-
|
|
2286
|
+
async sendDataToLMS(data, metadata) {
|
|
2287
|
+
const configuredCommitUrl = this.settings.lmsCommitUrl;
|
|
2288
|
+
if (!configuredCommitUrl) {
|
|
2239
2289
|
return {
|
|
2240
2290
|
result: global_constants.SCORM_FALSE,
|
|
2241
2291
|
errorCode: this.error_codes.GENERAL || 101
|
|
2242
2292
|
};
|
|
2243
2293
|
}
|
|
2244
2294
|
try {
|
|
2245
|
-
const
|
|
2295
|
+
const lmsCommitUrl = String(configuredCommitUrl);
|
|
2296
|
+
const processedData = this.settings.requestHandler(data, {
|
|
2297
|
+
isTerminateCommit: metadata?.isTerminateCommit ?? false,
|
|
2298
|
+
trigger: "offline-replay",
|
|
2299
|
+
...metadata?.sequence !== void 0 ? { sequence: metadata.sequence } : {}
|
|
2300
|
+
});
|
|
2301
|
+
const requestUrl = metadata?.isTerminateCommit && this.settings.terminateCommitParam ? appendQueryParam(lmsCommitUrl, this.settings.terminateCommitParam, "true") : lmsCommitUrl;
|
|
2246
2302
|
const init = {
|
|
2247
2303
|
method: "POST",
|
|
2248
2304
|
mode: this.settings.fetchMode,
|
|
@@ -2255,7 +2311,7 @@ class OfflineStorageService {
|
|
|
2255
2311
|
if (this.settings.xhrWithCredentials) {
|
|
2256
2312
|
init.credentials = "include";
|
|
2257
2313
|
}
|
|
2258
|
-
const response = await fetch(
|
|
2314
|
+
const response = await fetch(requestUrl, init);
|
|
2259
2315
|
const result = typeof this.settings.responseHandler === "function" ? await this.settings.responseHandler(response) : await response.json();
|
|
2260
2316
|
if (response.status >= 200 && response.status <= 299 && (result.result === true || result.result === global_constants.SCORM_TRUE)) {
|
|
2261
2317
|
if (!Object.hasOwnProperty.call(result, "errorCode")) {
|
|
@@ -2652,6 +2708,8 @@ class SynchronousHttpService {
|
|
|
2652
2708
|
* @param {boolean} immediate - Whether this is a termination commit (use sendBeacon)
|
|
2653
2709
|
* @param {Function} _apiLog - Function to log API messages (unused in synchronous mode - errors returned directly)
|
|
2654
2710
|
* @param {Function} _processListeners - Function to trigger event listeners (unused in synchronous mode - no async events)
|
|
2711
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
2712
|
+
* @param {Function} _onRequestComplete - Completion callback (unused because requests settle before return)
|
|
2655
2713
|
* @return {ResultObject} - The result of the request (synchronous)
|
|
2656
2714
|
*
|
|
2657
2715
|
* @remarks
|
|
@@ -2661,21 +2719,23 @@ class SynchronousHttpService {
|
|
|
2661
2719
|
* - No async events need to be triggered (CommitSuccess/CommitError) since results are synchronous
|
|
2662
2720
|
* - AsynchronousHttpService uses these parameters to handle background request results
|
|
2663
2721
|
*/
|
|
2664
|
-
processHttpRequest(url, params, immediate = false, _apiLog, _processListeners) {
|
|
2722
|
+
processHttpRequest(url, params, immediate = false, _apiLog, _processListeners, metadata, _onRequestComplete) {
|
|
2665
2723
|
if (immediate) {
|
|
2666
|
-
return this._handleImmediateRequest(url, params);
|
|
2724
|
+
return this._handleImmediateRequest(url, params, metadata);
|
|
2667
2725
|
}
|
|
2668
|
-
return this._performSyncXHR(url, params);
|
|
2726
|
+
return this._performSyncXHR(url, params, metadata);
|
|
2669
2727
|
}
|
|
2670
2728
|
/**
|
|
2671
2729
|
* Handles an immediate request using sendBeacon
|
|
2672
2730
|
* @param {string} url - The URL to send the request to
|
|
2673
2731
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
2732
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
2674
2733
|
* @return {ResultObject} - The result based on beacon success
|
|
2675
2734
|
* @private
|
|
2676
2735
|
*/
|
|
2677
|
-
_handleImmediateRequest(url, params) {
|
|
2678
|
-
const
|
|
2736
|
+
_handleImmediateRequest(url, params, metadata) {
|
|
2737
|
+
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
2738
|
+
const requestPayload = handledPayload ?? params;
|
|
2679
2739
|
const { body } = this._prepareRequestBody(requestPayload);
|
|
2680
2740
|
const beaconSuccess = navigator.sendBeacon(
|
|
2681
2741
|
url,
|
|
@@ -2690,11 +2750,13 @@ class SynchronousHttpService {
|
|
|
2690
2750
|
* Performs a synchronous XMLHttpRequest
|
|
2691
2751
|
* @param {string} url - The URL to send the request to
|
|
2692
2752
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
2753
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
2693
2754
|
* @return {ResultObject} - The result of the request
|
|
2694
2755
|
* @private
|
|
2695
2756
|
*/
|
|
2696
|
-
_performSyncXHR(url, params) {
|
|
2697
|
-
const
|
|
2757
|
+
_performSyncXHR(url, params, metadata) {
|
|
2758
|
+
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
2759
|
+
const requestPayload = handledPayload ?? params;
|
|
2698
2760
|
const { body, contentType } = this._prepareRequestBody(requestPayload);
|
|
2699
2761
|
const xhr = new XMLHttpRequest();
|
|
2700
2762
|
xhr.open("POST", url, false);
|
|
@@ -2773,9 +2835,15 @@ class ValidationService {
|
|
|
2773
2835
|
* @param {number} invalidTypeCode - The error code for invalid type
|
|
2774
2836
|
* @param {number} invalidRangeCode - The error code for invalid range
|
|
2775
2837
|
* @param {typeof BaseScormValidationError} errorClass - The error class to use for validation errors
|
|
2838
|
+
* @param {boolean} allowEmptyString - When true, an empty string is accepted (clears the value).
|
|
2839
|
+
* SCORM 1.2 score elements may be blank per the ADL 1.2 CTS
|
|
2840
|
+
* (DataModelValidator.checkScoreDecimal treats blank as valid).
|
|
2776
2841
|
* @return {boolean} - True if validation passes, throws an error otherwise
|
|
2777
2842
|
*/
|
|
2778
|
-
validateScore(CMIElement, value, decimalRegex, scoreRange, invalidTypeCode, invalidRangeCode, errorClass) {
|
|
2843
|
+
validateScore(CMIElement, value, decimalRegex, scoreRange, invalidTypeCode, invalidRangeCode, errorClass, allowEmptyString = false) {
|
|
2844
|
+
if (allowEmptyString && value === "") {
|
|
2845
|
+
return true;
|
|
2846
|
+
}
|
|
2779
2847
|
return checkValidFormat(CMIElement, value, decimalRegex, invalidTypeCode, errorClass) && (!scoreRange || checkValidRange(CMIElement, value, scoreRange, invalidRangeCode, errorClass));
|
|
2780
2848
|
}
|
|
2781
2849
|
/**
|
|
@@ -2849,6 +2917,21 @@ class BaseAPI {
|
|
|
2849
2917
|
_offlineStorageService;
|
|
2850
2918
|
_cmiValueAccessService;
|
|
2851
2919
|
_courseId = "";
|
|
2920
|
+
_pendingCommitCount = 0;
|
|
2921
|
+
/**
|
|
2922
|
+
* Monotonic sequence for commits captured by this API instance. It is
|
|
2923
|
+
* intentionally not reset by reset().
|
|
2924
|
+
*/
|
|
2925
|
+
_commitSequence = 0;
|
|
2926
|
+
_commitSettleWaiters = [];
|
|
2927
|
+
/**
|
|
2928
|
+
* Canonical paths of every CMI element that has been explicitly assigned a
|
|
2929
|
+
* value via SetValue / loadFromJSON (both funnel through _commonSetCMIValue).
|
|
2930
|
+
* Used by standards that must tell "implemented but never set" apart from a
|
|
2931
|
+
* legitimately empty value when answering GetValue (SCORM 2004 error 403).
|
|
2932
|
+
* Cleared on reset so a fresh SCO attempt starts with nothing "set".
|
|
2933
|
+
*/
|
|
2934
|
+
_setCMIElements = /* @__PURE__ */ new Set();
|
|
2852
2935
|
/**
|
|
2853
2936
|
* Constructor for Base API class. Sets some shared API fields, as well as
|
|
2854
2937
|
* sets up options for the API.
|
|
@@ -3034,6 +3117,7 @@ class BaseAPI {
|
|
|
3034
3117
|
this.lastErrorCode = "0";
|
|
3035
3118
|
this._eventService.reset();
|
|
3036
3119
|
this.startingData = {};
|
|
3120
|
+
this._setCMIElements.clear();
|
|
3037
3121
|
if (this._offlineStorageService) {
|
|
3038
3122
|
this._offlineStorageService.updateSettings(this.settings);
|
|
3039
3123
|
if (settings?.courseId) {
|
|
@@ -3118,6 +3202,52 @@ class BaseAPI {
|
|
|
3118
3202
|
this._loggingService?.setLogHandler(settings.onLogMessage);
|
|
3119
3203
|
}
|
|
3120
3204
|
}
|
|
3205
|
+
/**
|
|
3206
|
+
* Gets the number of captured commit requests that have not yet settled.
|
|
3207
|
+
*
|
|
3208
|
+
* @return {number} The number of in-flight commits
|
|
3209
|
+
*/
|
|
3210
|
+
get pendingCommitCount() {
|
|
3211
|
+
return this._pendingCommitCount;
|
|
3212
|
+
}
|
|
3213
|
+
/**
|
|
3214
|
+
* Resolves when all currently in-flight commits have settled. A timeout is
|
|
3215
|
+
* best-effort: the promise resolves when it elapses even if commits remain,
|
|
3216
|
+
* and callers can inspect pendingCommitCount afterward to detect that case.
|
|
3217
|
+
*
|
|
3218
|
+
* @param {Object} [options] - Settle options
|
|
3219
|
+
* @param {number} [options.timeoutMs] - Maximum time to wait in milliseconds
|
|
3220
|
+
* @return {Promise<void>} A promise that resolves after the drain or timeout
|
|
3221
|
+
*/
|
|
3222
|
+
whenCommitsSettled(options) {
|
|
3223
|
+
if (this._pendingCommitCount === 0) {
|
|
3224
|
+
return Promise.resolve();
|
|
3225
|
+
}
|
|
3226
|
+
return new Promise((resolve) => {
|
|
3227
|
+
const waiter = { resolve };
|
|
3228
|
+
if (options?.timeoutMs !== void 0) {
|
|
3229
|
+
waiter.timeoutId = setTimeout(() => {
|
|
3230
|
+
const waiterIndex = this._commitSettleWaiters.indexOf(waiter);
|
|
3231
|
+
if (waiterIndex === -1) {
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
this._commitSettleWaiters.splice(waiterIndex, 1);
|
|
3235
|
+
resolve();
|
|
3236
|
+
}, options.timeoutMs);
|
|
3237
|
+
}
|
|
3238
|
+
this._commitSettleWaiters.push(waiter);
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
/** Resolve and clear every waiter after the pending count reaches zero. */
|
|
3242
|
+
_flushCommitSettleWaiters() {
|
|
3243
|
+
const waiters = this._commitSettleWaiters.splice(0);
|
|
3244
|
+
for (const waiter of waiters) {
|
|
3245
|
+
if (waiter.timeoutId !== void 0) {
|
|
3246
|
+
clearTimeout(waiter.timeoutId);
|
|
3247
|
+
}
|
|
3248
|
+
waiter.resolve();
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3121
3251
|
/**
|
|
3122
3252
|
* Terminates the current run of the API
|
|
3123
3253
|
* @param {string} callbackName
|
|
@@ -3138,7 +3268,7 @@ class BaseAPI {
|
|
|
3138
3268
|
} else {
|
|
3139
3269
|
stateCheckPassed = true;
|
|
3140
3270
|
this.processListeners("BeforeTerminate");
|
|
3141
|
-
const result = this.storeData(true);
|
|
3271
|
+
const result = this.storeData(true, "terminate");
|
|
3142
3272
|
if ((result.errorCode ?? 0) > 0) {
|
|
3143
3273
|
if (result.errorMessage) {
|
|
3144
3274
|
this.apiLog(
|
|
@@ -3190,6 +3320,9 @@ class BaseAPI {
|
|
|
3190
3320
|
} catch (e) {
|
|
3191
3321
|
returnValue = this.handleValueAccessException(CMIElement, e, returnValue);
|
|
3192
3322
|
}
|
|
3323
|
+
if (this.lastErrorCode === "0") {
|
|
3324
|
+
this.checkUninitializedGet(CMIElement, returnValue);
|
|
3325
|
+
}
|
|
3193
3326
|
this.processListeners(callbackName, CMIElement);
|
|
3194
3327
|
}
|
|
3195
3328
|
this.apiLog(callbackName, ": returned: " + returnValue, LogLevelEnum.INFO, CMIElement);
|
|
@@ -3199,6 +3332,10 @@ class BaseAPI {
|
|
|
3199
3332
|
if (this.lastErrorCode === "0") {
|
|
3200
3333
|
this.clearSCORMError(returnValue);
|
|
3201
3334
|
}
|
|
3335
|
+
const rawReturn = returnValue;
|
|
3336
|
+
if (typeof rawReturn === "number" || typeof rawReturn === "boolean") {
|
|
3337
|
+
return String(rawReturn);
|
|
3338
|
+
}
|
|
3202
3339
|
return returnValue;
|
|
3203
3340
|
}
|
|
3204
3341
|
/**
|
|
@@ -3251,9 +3388,10 @@ class BaseAPI {
|
|
|
3251
3388
|
* Orders LMS to store all content parameters
|
|
3252
3389
|
* @param {string} callbackName
|
|
3253
3390
|
* @param {boolean} checkTerminated
|
|
3391
|
+
* @param {CommitTrigger} trigger - What initiated the commit
|
|
3254
3392
|
* @return {string}
|
|
3255
3393
|
*/
|
|
3256
|
-
commit(callbackName, checkTerminated = false) {
|
|
3394
|
+
commit(callbackName, checkTerminated = false, trigger = "manual") {
|
|
3257
3395
|
this.clearScheduledCommit();
|
|
3258
3396
|
let returnValue = global_constants.SCORM_TRUE;
|
|
3259
3397
|
if (this.isNotInitialized()) {
|
|
@@ -3265,7 +3403,7 @@ class BaseAPI {
|
|
|
3265
3403
|
this.throwSCORMError("api", errorCode);
|
|
3266
3404
|
if (errorCode === 143) returnValue = global_constants.SCORM_FALSE;
|
|
3267
3405
|
} else {
|
|
3268
|
-
const result = this.storeData(false);
|
|
3406
|
+
const result = this.storeData(false, trigger);
|
|
3269
3407
|
const errorCode = result.errorCode ?? 0;
|
|
3270
3408
|
if (errorCode > 0) {
|
|
3271
3409
|
if (result.errorMessage) {
|
|
@@ -3496,7 +3634,16 @@ class BaseAPI {
|
|
|
3496
3634
|
* @return {string}
|
|
3497
3635
|
*/
|
|
3498
3636
|
_commonSetCMIValue(methodName, scorm2004, CMIElement, value) {
|
|
3499
|
-
|
|
3637
|
+
const result = this._cmiValueAccessService.setCMIValue(
|
|
3638
|
+
methodName,
|
|
3639
|
+
scorm2004,
|
|
3640
|
+
CMIElement,
|
|
3641
|
+
value
|
|
3642
|
+
);
|
|
3643
|
+
if (result === global_constants.SCORM_TRUE) {
|
|
3644
|
+
this._setCMIElements.add(CMIElement);
|
|
3645
|
+
}
|
|
3646
|
+
return result;
|
|
3500
3647
|
}
|
|
3501
3648
|
/**
|
|
3502
3649
|
* Gets a value from the CMI Object.
|
|
@@ -3510,6 +3657,18 @@ class BaseAPI {
|
|
|
3510
3657
|
_commonGetCMIValue(methodName, scorm2004, CMIElement) {
|
|
3511
3658
|
return this._cmiValueAccessService.getCMIValue(methodName, scorm2004, CMIElement);
|
|
3512
3659
|
}
|
|
3660
|
+
/**
|
|
3661
|
+
* Hook invoked by getValue after a successful resolution. Standards that must
|
|
3662
|
+
* distinguish "implemented but never set, no default value" from a
|
|
3663
|
+
* legitimately empty value override this to raise VALUE_NOT_INITIALIZED.
|
|
3664
|
+
* Default is a no-op, so SCORM 1.2 / AICC keep returning "" with code 0.
|
|
3665
|
+
*
|
|
3666
|
+
* @param {string} _CMIElement - the element that was read
|
|
3667
|
+
* @param {any} _returnValue - the value getCMIValue resolved
|
|
3668
|
+
* @protected
|
|
3669
|
+
*/
|
|
3670
|
+
checkUninitializedGet(_CMIElement, _returnValue) {
|
|
3671
|
+
}
|
|
3513
3672
|
/**
|
|
3514
3673
|
* Returns true if the API's current state is STATE_INITIALIZED
|
|
3515
3674
|
*
|
|
@@ -3592,9 +3751,14 @@ class BaseAPI {
|
|
|
3592
3751
|
* @param {string} functionName - The name of the function/event that occurred
|
|
3593
3752
|
* @param {string} CMIElement - Optional CMI element involved in the event
|
|
3594
3753
|
* @param {any} value - Optional value associated with the event
|
|
3754
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
3595
3755
|
*/
|
|
3596
|
-
processListeners(functionName, CMIElement, value) {
|
|
3597
|
-
|
|
3756
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
3757
|
+
if (context !== void 0) {
|
|
3758
|
+
this._eventService.processListeners(functionName, CMIElement, value, context);
|
|
3759
|
+
} else {
|
|
3760
|
+
this._eventService.processListeners(functionName, CMIElement, value);
|
|
3761
|
+
}
|
|
3598
3762
|
}
|
|
3599
3763
|
/**
|
|
3600
3764
|
* Throws a SCORM error with the specified error number and optional message.
|
|
@@ -3727,36 +3891,106 @@ class BaseAPI {
|
|
|
3727
3891
|
* @param {string} url - The URL to send the request to
|
|
3728
3892
|
* @param {CommitObject | StringKeyMap | Array<any>} params - The parameters to send
|
|
3729
3893
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
3894
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
3730
3895
|
* @returns {ResultObject} - The result of the request
|
|
3731
3896
|
*/
|
|
3732
|
-
processHttpRequest(url, params, immediate = false) {
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
)
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3897
|
+
processHttpRequest(url, params, immediate = false, trigger) {
|
|
3898
|
+
const sequence = ++this._commitSequence;
|
|
3899
|
+
this._pendingCommitCount += 1;
|
|
3900
|
+
let settled = false;
|
|
3901
|
+
let completionDeferred = false;
|
|
3902
|
+
const settle = () => {
|
|
3903
|
+
if (settled) {
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
settled = true;
|
|
3907
|
+
this._pendingCommitCount -= 1;
|
|
3908
|
+
if (this._pendingCommitCount === 0) {
|
|
3909
|
+
this._flushCommitSettleWaiters();
|
|
3910
|
+
}
|
|
3911
|
+
};
|
|
3912
|
+
try {
|
|
3913
|
+
const resolvedTrigger = trigger ?? (immediate ? "terminate" : "manual");
|
|
3914
|
+
let finalParams = params;
|
|
3915
|
+
if (immediate && this.settings.terminateCommitPayloadField) {
|
|
3916
|
+
const field = this.settings.terminateCommitPayloadField;
|
|
3917
|
+
if (Array.isArray(finalParams)) {
|
|
3918
|
+
finalParams = [...finalParams, `${encodeURIComponent(field)}=true`];
|
|
3919
|
+
} else if (finalParams && typeof finalParams === "object") {
|
|
3920
|
+
finalParams = { ...finalParams, [field]: true };
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
if (this.settings.includeCommitSequence === true) {
|
|
3924
|
+
if (Array.isArray(finalParams)) {
|
|
3925
|
+
finalParams = [...finalParams, `commitSequence=${sequence}`];
|
|
3926
|
+
} else if (finalParams && typeof finalParams === "object") {
|
|
3927
|
+
finalParams = { ...finalParams, commitSequence: sequence };
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
const finalUrl = immediate && this.settings.terminateCommitParam ? appendQueryParam(url, this.settings.terminateCommitParam, "true") : url;
|
|
3931
|
+
const metadata = {
|
|
3932
|
+
isTerminateCommit: immediate,
|
|
3933
|
+
trigger: resolvedTrigger,
|
|
3934
|
+
sequence
|
|
3935
|
+
};
|
|
3936
|
+
const context = {
|
|
3937
|
+
url: finalUrl,
|
|
3938
|
+
trigger: resolvedTrigger,
|
|
3939
|
+
isTerminateCommit: immediate,
|
|
3940
|
+
sequence
|
|
3941
|
+
};
|
|
3942
|
+
if (this.settings.enableOfflineSupport && this._offlineStorageService && !this._offlineStorageService.isDeviceOnline() && this._courseId) {
|
|
3742
3943
|
this.apiLog(
|
|
3743
3944
|
"processHttpRequest",
|
|
3744
|
-
"
|
|
3745
|
-
LogLevelEnum.
|
|
3945
|
+
"Device is offline, storing data locally",
|
|
3946
|
+
LogLevelEnum.INFO
|
|
3746
3947
|
);
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3948
|
+
if (finalParams && typeof finalParams === "object" && "cmi" in finalParams) {
|
|
3949
|
+
return this._offlineStorageService.storeOffline(
|
|
3950
|
+
this._courseId,
|
|
3951
|
+
finalParams,
|
|
3952
|
+
{ isTerminateCommit: immediate, sequence }
|
|
3953
|
+
);
|
|
3954
|
+
} else {
|
|
3955
|
+
this.apiLog(
|
|
3956
|
+
"processHttpRequest",
|
|
3957
|
+
"Invalid commit data format for offline storage",
|
|
3958
|
+
LogLevelEnum.ERROR
|
|
3959
|
+
);
|
|
3960
|
+
return {
|
|
3961
|
+
result: global_constants.SCORM_FALSE,
|
|
3962
|
+
errorCode: this._error_codes.GENERAL ?? 101
|
|
3963
|
+
};
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
const apiLog = (functionName, message, level, element) => this.apiLog(functionName, message, level, element);
|
|
3967
|
+
const processListeners = (functionName, CMIElement, value) => {
|
|
3968
|
+
if (functionName === "CommitSuccess" || functionName === "CommitError") {
|
|
3969
|
+
if (functionName === "CommitError" && typeof value === "number") {
|
|
3970
|
+
context.errorCode = value;
|
|
3971
|
+
}
|
|
3972
|
+
settle();
|
|
3973
|
+
this.processListeners(functionName, CMIElement, value, context);
|
|
3974
|
+
} else {
|
|
3975
|
+
this.processListeners(functionName, CMIElement, value);
|
|
3976
|
+
}
|
|
3977
|
+
};
|
|
3978
|
+
const result = this._httpService.processHttpRequest(
|
|
3979
|
+
finalUrl,
|
|
3980
|
+
finalParams,
|
|
3981
|
+
immediate,
|
|
3982
|
+
apiLog,
|
|
3983
|
+
processListeners,
|
|
3984
|
+
metadata,
|
|
3985
|
+
settle
|
|
3986
|
+
);
|
|
3987
|
+
completionDeferred = this._httpService.reportsRequestCompletion === true;
|
|
3988
|
+
return result;
|
|
3989
|
+
} finally {
|
|
3990
|
+
if (!completionDeferred) {
|
|
3991
|
+
settle();
|
|
3751
3992
|
}
|
|
3752
3993
|
}
|
|
3753
|
-
return this._httpService.processHttpRequest(
|
|
3754
|
-
url,
|
|
3755
|
-
params,
|
|
3756
|
-
immediate,
|
|
3757
|
-
(functionName, message, level, element) => this.apiLog(functionName, message, level, element),
|
|
3758
|
-
(functionName, CMIElement, value) => this.processListeners(functionName, CMIElement, value)
|
|
3759
|
-
);
|
|
3760
3994
|
}
|
|
3761
3995
|
/**
|
|
3762
3996
|
* Schedules a commit operation to occur after a specified delay.
|
|
@@ -3916,6 +4150,11 @@ class CMIScore extends BaseCMI {
|
|
|
3916
4150
|
__invalid_range_code;
|
|
3917
4151
|
__decimal_regex;
|
|
3918
4152
|
__error_class;
|
|
4153
|
+
/**
|
|
4154
|
+
* When true, an empty string is a valid value (clears the element). SCORM 1.2
|
|
4155
|
+
* score elements may be blank per the ADL 1.2 CTS; SCORM 2004 leaves this off.
|
|
4156
|
+
*/
|
|
4157
|
+
__allow_empty_string;
|
|
3919
4158
|
_raw = "";
|
|
3920
4159
|
_min = "";
|
|
3921
4160
|
_max;
|
|
@@ -3950,6 +4189,7 @@ class CMIScore extends BaseCMI {
|
|
|
3950
4189
|
this.__invalid_range_code = params.invalidRangeCode || scorm12_errors.VALUE_OUT_OF_RANGE;
|
|
3951
4190
|
this.__decimal_regex = params.decimalRegex || scorm12_regex.CMIDecimal;
|
|
3952
4191
|
this.__error_class = params.errorClass;
|
|
4192
|
+
this.__allow_empty_string = params.allowEmptyString ?? false;
|
|
3953
4193
|
}
|
|
3954
4194
|
/**
|
|
3955
4195
|
* Called when the API has been reset
|
|
@@ -3996,7 +4236,8 @@ class CMIScore extends BaseCMI {
|
|
|
3996
4236
|
this.__score_range,
|
|
3997
4237
|
this.__invalid_type_code,
|
|
3998
4238
|
this.__invalid_range_code,
|
|
3999
|
-
this.__error_class
|
|
4239
|
+
this.__error_class,
|
|
4240
|
+
this.__allow_empty_string
|
|
4000
4241
|
)) {
|
|
4001
4242
|
this._raw = raw;
|
|
4002
4243
|
}
|
|
@@ -4020,7 +4261,8 @@ class CMIScore extends BaseCMI {
|
|
|
4020
4261
|
this.__score_range,
|
|
4021
4262
|
this.__invalid_type_code,
|
|
4022
4263
|
this.__invalid_range_code,
|
|
4023
|
-
this.__error_class
|
|
4264
|
+
this.__error_class,
|
|
4265
|
+
this.__allow_empty_string
|
|
4024
4266
|
)) {
|
|
4025
4267
|
this._min = min;
|
|
4026
4268
|
}
|
|
@@ -4044,7 +4286,8 @@ class CMIScore extends BaseCMI {
|
|
|
4044
4286
|
this.__score_range,
|
|
4045
4287
|
this.__invalid_type_code,
|
|
4046
4288
|
this.__invalid_range_code,
|
|
4047
|
-
this.__error_class
|
|
4289
|
+
this.__error_class,
|
|
4290
|
+
this.__allow_empty_string
|
|
4048
4291
|
)) {
|
|
4049
4292
|
this._max = max;
|
|
4050
4293
|
}
|
|
@@ -4101,7 +4344,9 @@ class CMICore extends BaseCMI {
|
|
|
4101
4344
|
invalidErrorCode: scorm12_errors.INVALID_SET_VALUE,
|
|
4102
4345
|
invalidTypeCode: scorm12_errors.TYPE_MISMATCH,
|
|
4103
4346
|
invalidRangeCode: scorm12_errors.VALUE_OUT_OF_RANGE,
|
|
4104
|
-
errorClass: Scorm12ValidationError
|
|
4347
|
+
errorClass: Scorm12ValidationError,
|
|
4348
|
+
// SCORM 1.2 score elements may be set blank (clears the value), per ADL 1.2 CTS.
|
|
4349
|
+
allowEmptyString: true
|
|
4105
4350
|
});
|
|
4106
4351
|
}
|
|
4107
4352
|
score;
|
|
@@ -4531,7 +4776,9 @@ class CMIObjectivesObject extends BaseCMI {
|
|
|
4531
4776
|
invalidErrorCode: scorm12_errors.INVALID_SET_VALUE,
|
|
4532
4777
|
invalidTypeCode: scorm12_errors.TYPE_MISMATCH,
|
|
4533
4778
|
invalidRangeCode: scorm12_errors.VALUE_OUT_OF_RANGE,
|
|
4534
|
-
errorClass: Scorm12ValidationError
|
|
4779
|
+
errorClass: Scorm12ValidationError,
|
|
4780
|
+
// SCORM 1.2 score elements may be set blank (clears the value), per ADL 1.2 CTS.
|
|
4781
|
+
allowEmptyString: true
|
|
4535
4782
|
});
|
|
4536
4783
|
}
|
|
4537
4784
|
score;
|
|
@@ -5932,18 +6179,12 @@ class Scorm12API extends BaseAPI {
|
|
|
5932
6179
|
if (terminateCommit || includeTotalTime) {
|
|
5933
6180
|
cmiExport.cmi.core.total_time = this.cmi.getCurrentTotalTime();
|
|
5934
6181
|
}
|
|
5935
|
-
const result = [];
|
|
5936
6182
|
const flattened = flatten(cmiExport);
|
|
5937
6183
|
switch (this.settings.dataCommitFormat) {
|
|
5938
6184
|
case "flattened":
|
|
5939
6185
|
return flattened;
|
|
5940
6186
|
case "params":
|
|
5941
|
-
|
|
5942
|
-
if ({}.hasOwnProperty.call(flattened, item)) {
|
|
5943
|
-
result.push(`${item}=${flattened[item]}`);
|
|
5944
|
-
}
|
|
5945
|
-
}
|
|
5946
|
-
return result;
|
|
6187
|
+
return Object.entries(flattened).map(([item, value]) => `${item}=${value}`);
|
|
5947
6188
|
case "json":
|
|
5948
6189
|
default:
|
|
5949
6190
|
return cmiExport;
|
|
@@ -6001,16 +6242,17 @@ class Scorm12API extends BaseAPI {
|
|
|
6001
6242
|
* Attempts to store the data to the LMS
|
|
6002
6243
|
*
|
|
6003
6244
|
* @param {boolean} terminateCommit
|
|
6245
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
6004
6246
|
* @return {ResultObject}
|
|
6005
6247
|
*/
|
|
6006
|
-
storeData(terminateCommit) {
|
|
6248
|
+
storeData(terminateCommit, trigger) {
|
|
6007
6249
|
if (terminateCommit) {
|
|
6008
6250
|
const originalStatus = this.cmi.core.lesson_status;
|
|
6009
6251
|
if (this.cmi.core.lesson_mode === "browse") {
|
|
6010
6252
|
const startingStatus = this.startingData?.cmi?.core?.lesson_status || "";
|
|
6011
6253
|
if (startingStatus === "" && originalStatus === "not attempted") {
|
|
6012
6254
|
this.cmi.core.lesson_status = "browsed";
|
|
6013
|
-
return this.processCommitData(terminateCommit);
|
|
6255
|
+
return this.processCommitData(terminateCommit, trigger);
|
|
6014
6256
|
}
|
|
6015
6257
|
}
|
|
6016
6258
|
if (!this.cmi.core.lesson_status || !this.statusSetByModule && this.cmi.core.lesson_status === "not attempted") {
|
|
@@ -6039,12 +6281,17 @@ class Scorm12API extends BaseAPI {
|
|
|
6039
6281
|
}
|
|
6040
6282
|
}
|
|
6041
6283
|
}
|
|
6042
|
-
return this.processCommitData(terminateCommit);
|
|
6284
|
+
return this.processCommitData(terminateCommit, trigger);
|
|
6043
6285
|
}
|
|
6044
|
-
processCommitData(terminateCommit) {
|
|
6286
|
+
processCommitData(terminateCommit, trigger) {
|
|
6045
6287
|
const commitObject = this.getCommitObject(terminateCommit);
|
|
6046
6288
|
if (typeof this.settings.lmsCommitUrl === "string") {
|
|
6047
|
-
return this.processHttpRequest(
|
|
6289
|
+
return this.processHttpRequest(
|
|
6290
|
+
this.settings.lmsCommitUrl,
|
|
6291
|
+
commitObject,
|
|
6292
|
+
terminateCommit,
|
|
6293
|
+
trigger
|
|
6294
|
+
);
|
|
6048
6295
|
} else {
|
|
6049
6296
|
return {
|
|
6050
6297
|
result: global_constants.SCORM_TRUE,
|