scorm-again 3.0.4 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -5
- package/dist/cross-frame-api.js.map +1 -1
- package/dist/esm/cross-frame-api.js.map +1 -1
- package/dist/esm/scorm-again.js +503 -138
- package/dist/esm/scorm-again.js.map +1 -1
- package/dist/esm/scorm-again.min.js +1 -1
- package/dist/esm/scorm-again.min.js.map +1 -1
- package/dist/esm/scorm12.js +339 -73
- package/dist/esm/scorm12.js.map +1 -1
- package/dist/esm/scorm12.min.js +1 -1
- package/dist/esm/scorm12.min.js.map +1 -1
- package/dist/esm/scorm2004.js +486 -131
- package/dist/esm/scorm2004.js.map +1 -1
- package/dist/esm/scorm2004.min.js +1 -1
- package/dist/esm/scorm2004.min.js.map +1 -1
- package/dist/scorm-again.js +443 -119
- package/dist/scorm-again.js.map +1 -1
- package/dist/scorm-again.min.js +1 -1
- package/dist/scorm-again.min.js.map +1 -1
- package/dist/scorm12.js +316 -57
- package/dist/scorm12.js.map +1 -1
- package/dist/scorm12.min.js +1 -1
- package/dist/scorm12.min.js.map +1 -1
- package/dist/scorm2004.js +431 -112
- package/dist/scorm2004.js.map +1 -1
- package/dist/scorm2004.min.js +1 -1
- package/dist/scorm2004.min.js.map +1 -1
- package/dist/types/BaseAPI.d.ts +15 -5
- package/dist/types/Scorm12API.d.ts +2 -2
- package/dist/types/Scorm2004API.d.ts +3 -2
- package/dist/types/cmi/common/score.d.ts +2 -0
- package/dist/types/cmi/scorm2004/interaction_delimiters.d.ts +4 -0
- package/dist/types/interfaces/services.d.ts +8 -4
- package/dist/types/services/AsynchronousHttpService.d.ts +3 -2
- package/dist/types/services/EventService.d.ts +2 -2
- package/dist/types/services/OfflineStorageService.d.ts +6 -1
- package/dist/types/services/SynchronousHttpService.d.ts +2 -2
- package/dist/types/services/ValidationService.d.ts +1 -1
- package/dist/types/types/api_types.d.ts +22 -2
- package/dist/types/utilities/index.d.ts +1 -0
- package/dist/types/utilities/url.d.ts +2 -0
- package/package.json +46 -25
package/dist/scorm12.js
CHANGED
|
@@ -161,6 +161,15 @@ this.Scorm12API = (function () {
|
|
|
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
|
var __defProp$m = Object.defineProperty;
|
|
165
174
|
var __defNormalProp$m = (obj, key, value) => key in obj ? __defProp$m(obj, key, {
|
|
166
175
|
enumerable: true,
|
|
@@ -417,7 +426,10 @@ this.Scorm12API = (function () {
|
|
|
417
426
|
READ_ONLY_ELEMENT: 403,
|
|
418
427
|
WRITE_ONLY_ELEMENT: 404,
|
|
419
428
|
TYPE_MISMATCH: 405,
|
|
420
|
-
|
|
429
|
+
// SCORM 1.2 has no out-of-range error code; an out-of-range value is reported
|
|
430
|
+
// as 405 (incorrect data type), per the SCORM 1.2 RTE error table and the ADL
|
|
431
|
+
// 1.2 CTS (DataModelValidator.checkScoreDecimal failure -> CMICategory 405).
|
|
432
|
+
VALUE_OUT_OF_RANGE: 405,
|
|
421
433
|
DEPENDENCY_NOT_ESTABLISHED: 408
|
|
422
434
|
};
|
|
423
435
|
|
|
@@ -541,6 +553,7 @@ this.Scorm12API = (function () {
|
|
|
541
553
|
xhrWithCredentials: false,
|
|
542
554
|
fetchMode: "cors",
|
|
543
555
|
asyncModeBeaconBehavior: "never",
|
|
556
|
+
includeCommitSequence: false,
|
|
544
557
|
responseHandler: async function (response) {
|
|
545
558
|
if (typeof response !== "undefined") {
|
|
546
559
|
let httpResult = null;
|
|
@@ -940,7 +953,7 @@ this.Scorm12API = (function () {
|
|
|
940
953
|
wrapper() {
|
|
941
954
|
if (!this._cancelled) {
|
|
942
955
|
if (this._API.isInitialized()) {
|
|
943
|
-
(async () => await this._API.commit(this._callback))();
|
|
956
|
+
(async () => await this._API.commit(this._callback, false, "autocommit"))();
|
|
944
957
|
}
|
|
945
958
|
}
|
|
946
959
|
}
|
|
@@ -1430,6 +1443,7 @@ this.Scorm12API = (function () {
|
|
|
1430
1443
|
* @param {ErrorCode} error_codes - The error codes object
|
|
1431
1444
|
*/
|
|
1432
1445
|
constructor(settings, error_codes) {
|
|
1446
|
+
__publicField$h(this, "reportsRequestCompletion", true);
|
|
1433
1447
|
__publicField$h(this, "settings");
|
|
1434
1448
|
__publicField$h(this, "error_codes");
|
|
1435
1449
|
this.settings = settings;
|
|
@@ -1448,13 +1462,17 @@ this.Scorm12API = (function () {
|
|
|
1448
1462
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
1449
1463
|
* @param {Function} apiLog - Function to log API messages with appropriate levels
|
|
1450
1464
|
* @param {Function} processListeners - Function to trigger event listeners for commit events
|
|
1465
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
1466
|
+
* @param {Function} onRequestComplete - Callback invoked after the background request settles
|
|
1451
1467
|
* @return {ResultObject} - Immediate optimistic success result
|
|
1452
1468
|
*/
|
|
1453
1469
|
processHttpRequest(url, params) {
|
|
1454
1470
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
1455
1471
|
let apiLog = arguments.length > 3 ? arguments[3] : undefined;
|
|
1456
1472
|
let processListeners = arguments.length > 4 ? arguments[4] : undefined;
|
|
1457
|
-
|
|
1473
|
+
let metadata = arguments.length > 5 ? arguments[5] : undefined;
|
|
1474
|
+
let onRequestComplete = arguments.length > 6 ? arguments[6] : undefined;
|
|
1475
|
+
this._performAsyncRequest(url, params, immediate, apiLog, processListeners, metadata, onRequestComplete);
|
|
1458
1476
|
return {
|
|
1459
1477
|
result: global_constants.SCORM_TRUE,
|
|
1460
1478
|
errorCode: 0
|
|
@@ -1467,11 +1485,14 @@ this.Scorm12API = (function () {
|
|
|
1467
1485
|
* @param {boolean} immediate - Whether this is an immediate request
|
|
1468
1486
|
* @param apiLog - Function to log API messages
|
|
1469
1487
|
* @param {Function} processListeners - Function to process event listeners
|
|
1488
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
1489
|
+
* @param {Function} onRequestComplete - Callback invoked after the request settles
|
|
1470
1490
|
* @private
|
|
1471
1491
|
*/
|
|
1472
|
-
async _performAsyncRequest(url, params, immediate, apiLog, processListeners) {
|
|
1492
|
+
async _performAsyncRequest(url, params, immediate, apiLog, processListeners, metadata, onRequestComplete) {
|
|
1473
1493
|
try {
|
|
1474
|
-
const
|
|
1494
|
+
const handledParams = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
1495
|
+
const processedParams = handledParams;
|
|
1475
1496
|
let response;
|
|
1476
1497
|
if (immediate && this.settings.asyncModeBeaconBehavior !== "never") {
|
|
1477
1498
|
response = await this.performBeacon(url, processedParams);
|
|
@@ -1487,7 +1508,9 @@ this.Scorm12API = (function () {
|
|
|
1487
1508
|
} catch (e) {
|
|
1488
1509
|
const message = e instanceof Error ? e.message : String(e);
|
|
1489
1510
|
apiLog("processHttpRequest", `Async request failed: ${message}`, LogLevelEnum.ERROR);
|
|
1490
|
-
processListeners("CommitError");
|
|
1511
|
+
processListeners("CommitError", void 0, this.error_codes.GENERAL_COMMIT_FAILURE || 391);
|
|
1512
|
+
} finally {
|
|
1513
|
+
onRequestComplete?.();
|
|
1491
1514
|
}
|
|
1492
1515
|
}
|
|
1493
1516
|
/**
|
|
@@ -1649,10 +1672,13 @@ this.Scorm12API = (function () {
|
|
|
1649
1672
|
}
|
|
1650
1673
|
/**
|
|
1651
1674
|
* Gets the appropriate error code for undefined data model elements.
|
|
1652
|
-
* SCORM 2004
|
|
1675
|
+
* Both SCORM 2004 and SCORM 1.2 use UNDEFINED_DATA_MODEL (401): an
|
|
1676
|
+
* unrecognized element is "Not implemented", not a general exception. SCORM
|
|
1677
|
+
* 1.2 previously returned GENERAL (101) here, which is non-conformant — the
|
|
1678
|
+
* ADL 1.2 CTS and the SCORM 1.2 RTE spec expect 401 for an unknown element.
|
|
1653
1679
|
*/
|
|
1654
|
-
getUndefinedDataModelErrorCode(
|
|
1655
|
-
return
|
|
1680
|
+
getUndefinedDataModelErrorCode() {
|
|
1681
|
+
return getErrorCode(this.context.errorCodes, "UNDEFINED_DATA_MODEL");
|
|
1656
1682
|
}
|
|
1657
1683
|
/**
|
|
1658
1684
|
* Sets a value on a CMI element path
|
|
@@ -1676,7 +1702,7 @@ this.Scorm12API = (function () {
|
|
|
1676
1702
|
let returnValue = global_constants.SCORM_FALSE;
|
|
1677
1703
|
let foundFirstIndex = false;
|
|
1678
1704
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
1679
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
1705
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
1680
1706
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
1681
1707
|
const attribute = structure[idx];
|
|
1682
1708
|
if (idx === structure.length - 1) {
|
|
@@ -1716,12 +1742,13 @@ this.Scorm12API = (function () {
|
|
|
1716
1742
|
this.context.throwSCORMError(CMIElement, getErrorCode(this.context.errorCodes, "GENERAL_GET_FAILURE"), "The _version keyword was used incorrectly");
|
|
1717
1743
|
return "";
|
|
1718
1744
|
}
|
|
1745
|
+
this.context.setLastErrorCode("0");
|
|
1719
1746
|
const structure = CMIElement.split(".");
|
|
1720
1747
|
let refObject = this.context.getDataModel();
|
|
1721
1748
|
let attribute = null;
|
|
1722
1749
|
const uninitializedErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) has not been initialized.`;
|
|
1723
1750
|
const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
|
|
1724
|
-
const invalidErrorCode = this.getUndefinedDataModelErrorCode(
|
|
1751
|
+
const invalidErrorCode = this.getUndefinedDataModelErrorCode();
|
|
1725
1752
|
for (let idx = 0; idx < structure.length; idx++) {
|
|
1726
1753
|
attribute = structure[idx];
|
|
1727
1754
|
const validationResult = this.validateGetAttribute(refObject, attribute, CMIElement, scorm2004, invalidErrorCode, invalidErrorMessage, idx === structure.length - 1);
|
|
@@ -1906,7 +1933,13 @@ this.Scorm12API = (function () {
|
|
|
1906
1933
|
if (!scorm2004) {
|
|
1907
1934
|
if (isFinalAttribute) {
|
|
1908
1935
|
if (typeof attribute === "undefined" || !this.context.checkObjectHasProperty(refObject, attribute)) {
|
|
1909
|
-
|
|
1936
|
+
if (attribute === "_children") {
|
|
1937
|
+
this.context.throwSCORMError(CMIElement, getErrorCode(this.context.errorCodes, "CHILDREN_ERROR"));
|
|
1938
|
+
} else if (attribute === "_count") {
|
|
1939
|
+
this.context.throwSCORMError(CMIElement, getErrorCode(this.context.errorCodes, "COUNT_ERROR"));
|
|
1940
|
+
} else {
|
|
1941
|
+
this.context.throwSCORMError(CMIElement, invalidErrorCode, invalidErrorMessage);
|
|
1942
|
+
}
|
|
1910
1943
|
return {
|
|
1911
1944
|
error: true
|
|
1912
1945
|
};
|
|
@@ -2441,8 +2474,9 @@ ${stackTrace}`);
|
|
|
2441
2474
|
* @param {string} functionName - The name of the function that triggered the event
|
|
2442
2475
|
* @param {string} CMIElement - The CMI element that was affected
|
|
2443
2476
|
* @param {any} value - The value that was set
|
|
2477
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
2444
2478
|
*/
|
|
2445
|
-
processListeners(functionName, CMIElement, value) {
|
|
2479
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
2446
2480
|
this.apiLog(functionName, value, LogLevelEnum.INFO, CMIElement);
|
|
2447
2481
|
const listeners = this.listenerMap.get(functionName);
|
|
2448
2482
|
if (!listeners) return;
|
|
@@ -2462,9 +2496,17 @@ ${stackTrace}`);
|
|
|
2462
2496
|
if (functionName.startsWith("Sequence")) {
|
|
2463
2497
|
listener.callback(value);
|
|
2464
2498
|
} else if (functionName === "CommitError") {
|
|
2465
|
-
|
|
2499
|
+
if (context !== void 0) {
|
|
2500
|
+
listener.callback(value, context);
|
|
2501
|
+
} else {
|
|
2502
|
+
listener.callback(value);
|
|
2503
|
+
}
|
|
2466
2504
|
} else if (functionName === "CommitSuccess") {
|
|
2467
|
-
|
|
2505
|
+
if (context !== void 0) {
|
|
2506
|
+
listener.callback(context);
|
|
2507
|
+
} else {
|
|
2508
|
+
listener.callback();
|
|
2509
|
+
}
|
|
2468
2510
|
} else {
|
|
2469
2511
|
listener.callback(CMIElement, value);
|
|
2470
2512
|
}
|
|
@@ -2573,16 +2615,23 @@ ${stackTrace}`);
|
|
|
2573
2615
|
* Store commit data offline
|
|
2574
2616
|
* @param {string} courseId - Identifier for the course
|
|
2575
2617
|
* @param {CommitObject} commitData - The data to store offline
|
|
2618
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
2576
2619
|
* @returns {ResultObject} - Result of the storage operation
|
|
2577
2620
|
*/
|
|
2578
|
-
storeOffline(courseId, commitData) {
|
|
2621
|
+
storeOffline(courseId, commitData, metadata) {
|
|
2579
2622
|
try {
|
|
2580
2623
|
const queueItem = {
|
|
2581
2624
|
id: `${courseId}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
2582
2625
|
courseId,
|
|
2583
2626
|
timestamp: Date.now(),
|
|
2584
2627
|
data: commitData,
|
|
2585
|
-
syncAttempts: 0
|
|
2628
|
+
syncAttempts: 0,
|
|
2629
|
+
...(metadata?.isTerminateCommit !== void 0 ? {
|
|
2630
|
+
isTerminateCommit: metadata.isTerminateCommit
|
|
2631
|
+
} : {}),
|
|
2632
|
+
...(metadata?.sequence !== void 0 ? {
|
|
2633
|
+
sequence: metadata.sequence
|
|
2634
|
+
} : {})
|
|
2586
2635
|
};
|
|
2587
2636
|
const currentQueue = this.getFromStorage(this.syncQueue) || [];
|
|
2588
2637
|
currentQueue.push(queueItem);
|
|
@@ -2641,7 +2690,14 @@ ${stackTrace}`);
|
|
|
2641
2690
|
continue;
|
|
2642
2691
|
}
|
|
2643
2692
|
try {
|
|
2644
|
-
const syncResult = await this.sendDataToLMS(item.data
|
|
2693
|
+
const syncResult = await this.sendDataToLMS(item.data, {
|
|
2694
|
+
...(item.isTerminateCommit !== void 0 ? {
|
|
2695
|
+
isTerminateCommit: item.isTerminateCommit
|
|
2696
|
+
} : {}),
|
|
2697
|
+
...(item.sequence !== void 0 ? {
|
|
2698
|
+
sequence: item.sequence
|
|
2699
|
+
} : {})
|
|
2700
|
+
});
|
|
2645
2701
|
if (syncResult.result === true || syncResult.result === global_constants.SCORM_TRUE) {
|
|
2646
2702
|
this.apiLog("OfflineStorageService", `Successfully synced item ${item.id}`, LogLevelEnum.INFO);
|
|
2647
2703
|
} else {
|
|
@@ -2668,17 +2724,27 @@ ${stackTrace}`);
|
|
|
2668
2724
|
/**
|
|
2669
2725
|
* Send data to the LMS when online
|
|
2670
2726
|
* @param {CommitObject} data - The data to send to the LMS
|
|
2727
|
+
* @param {OfflineCommitMetadata} metadata - Metadata captured with the original commit
|
|
2671
2728
|
* @returns {Promise<ResultObject>} - Result of the sync operation
|
|
2672
2729
|
*/
|
|
2673
|
-
async sendDataToLMS(data) {
|
|
2674
|
-
|
|
2730
|
+
async sendDataToLMS(data, metadata) {
|
|
2731
|
+
const configuredCommitUrl = this.settings.lmsCommitUrl;
|
|
2732
|
+
if (!configuredCommitUrl) {
|
|
2675
2733
|
return {
|
|
2676
2734
|
result: global_constants.SCORM_FALSE,
|
|
2677
2735
|
errorCode: this.error_codes.GENERAL || 101
|
|
2678
2736
|
};
|
|
2679
2737
|
}
|
|
2680
2738
|
try {
|
|
2681
|
-
const
|
|
2739
|
+
const lmsCommitUrl = String(configuredCommitUrl);
|
|
2740
|
+
const processedData = this.settings.requestHandler(data, {
|
|
2741
|
+
isTerminateCommit: metadata?.isTerminateCommit ?? false,
|
|
2742
|
+
trigger: "offline-replay",
|
|
2743
|
+
...(metadata?.sequence !== void 0 ? {
|
|
2744
|
+
sequence: metadata.sequence
|
|
2745
|
+
} : {})
|
|
2746
|
+
});
|
|
2747
|
+
const requestUrl = metadata?.isTerminateCommit && this.settings.terminateCommitParam ? appendQueryParam(lmsCommitUrl, this.settings.terminateCommitParam, "true") : lmsCommitUrl;
|
|
2682
2748
|
const init = {
|
|
2683
2749
|
method: "POST",
|
|
2684
2750
|
mode: this.settings.fetchMode,
|
|
@@ -2691,7 +2757,7 @@ ${stackTrace}`);
|
|
|
2691
2757
|
if (this.settings.xhrWithCredentials) {
|
|
2692
2758
|
init.credentials = "include";
|
|
2693
2759
|
}
|
|
2694
|
-
const response = await fetch(
|
|
2760
|
+
const response = await fetch(requestUrl, init);
|
|
2695
2761
|
const result = typeof this.settings.responseHandler === "function" ? await this.settings.responseHandler(response) : await response.json();
|
|
2696
2762
|
if (response.status >= 200 && response.status <= 299 && (result.result === true || result.result === global_constants.SCORM_TRUE)) {
|
|
2697
2763
|
if (!Object.hasOwnProperty.call(result, "errorCode")) {
|
|
@@ -3422,6 +3488,8 @@ ${stackTrace}`);
|
|
|
3422
3488
|
* @param {boolean} immediate - Whether this is a termination commit (use sendBeacon)
|
|
3423
3489
|
* @param {Function} _apiLog - Function to log API messages (unused in synchronous mode - errors returned directly)
|
|
3424
3490
|
* @param {Function} _processListeners - Function to trigger event listeners (unused in synchronous mode - no async events)
|
|
3491
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
3492
|
+
* @param {Function} _onRequestComplete - Completion callback (unused because requests settle before return)
|
|
3425
3493
|
* @return {ResultObject} - The result of the request (synchronous)
|
|
3426
3494
|
*
|
|
3427
3495
|
* @remarks
|
|
@@ -3433,20 +3501,23 @@ ${stackTrace}`);
|
|
|
3433
3501
|
*/
|
|
3434
3502
|
processHttpRequest(url, params) {
|
|
3435
3503
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
3504
|
+
let metadata = arguments.length > 5 ? arguments[5] : undefined;
|
|
3436
3505
|
if (immediate) {
|
|
3437
|
-
return this._handleImmediateRequest(url, params);
|
|
3506
|
+
return this._handleImmediateRequest(url, params, metadata);
|
|
3438
3507
|
}
|
|
3439
|
-
return this._performSyncXHR(url, params);
|
|
3508
|
+
return this._performSyncXHR(url, params, metadata);
|
|
3440
3509
|
}
|
|
3441
3510
|
/**
|
|
3442
3511
|
* Handles an immediate request using sendBeacon
|
|
3443
3512
|
* @param {string} url - The URL to send the request to
|
|
3444
3513
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
3514
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
3445
3515
|
* @return {ResultObject} - The result based on beacon success
|
|
3446
3516
|
* @private
|
|
3447
3517
|
*/
|
|
3448
|
-
_handleImmediateRequest(url, params) {
|
|
3449
|
-
const
|
|
3518
|
+
_handleImmediateRequest(url, params, metadata) {
|
|
3519
|
+
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
3520
|
+
const requestPayload = handledPayload ?? params;
|
|
3450
3521
|
const {
|
|
3451
3522
|
body
|
|
3452
3523
|
} = this._prepareRequestBody(requestPayload);
|
|
@@ -3462,11 +3533,13 @@ ${stackTrace}`);
|
|
|
3462
3533
|
* Performs a synchronous XMLHttpRequest
|
|
3463
3534
|
* @param {string} url - The URL to send the request to
|
|
3464
3535
|
* @param {CommitObject|StringKeyMap|Array} params - The parameters to include in the request
|
|
3536
|
+
* @param {CommitMetadata} metadata - Metadata describing the captured commit
|
|
3465
3537
|
* @return {ResultObject} - The result of the request
|
|
3466
3538
|
* @private
|
|
3467
3539
|
*/
|
|
3468
|
-
_performSyncXHR(url, params) {
|
|
3469
|
-
const
|
|
3540
|
+
_performSyncXHR(url, params, metadata) {
|
|
3541
|
+
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
3542
|
+
const requestPayload = handledPayload ?? params;
|
|
3470
3543
|
const {
|
|
3471
3544
|
body,
|
|
3472
3545
|
contentType
|
|
@@ -3539,9 +3612,16 @@ ${stackTrace}`);
|
|
|
3539
3612
|
* @param {number} invalidTypeCode - The error code for invalid type
|
|
3540
3613
|
* @param {number} invalidRangeCode - The error code for invalid range
|
|
3541
3614
|
* @param {typeof BaseScormValidationError} errorClass - The error class to use for validation errors
|
|
3615
|
+
* @param {boolean} allowEmptyString - When true, an empty string is accepted (clears the value).
|
|
3616
|
+
* SCORM 1.2 score elements may be blank per the ADL 1.2 CTS
|
|
3617
|
+
* (DataModelValidator.checkScoreDecimal treats blank as valid).
|
|
3542
3618
|
* @return {boolean} - True if validation passes, throws an error otherwise
|
|
3543
3619
|
*/
|
|
3544
3620
|
validateScore(CMIElement, value, decimalRegex, scoreRange, invalidTypeCode, invalidRangeCode, errorClass) {
|
|
3621
|
+
let allowEmptyString = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : false;
|
|
3622
|
+
if (allowEmptyString && value === "") {
|
|
3623
|
+
return true;
|
|
3624
|
+
}
|
|
3545
3625
|
return checkValidFormat(CMIElement, value, decimalRegex, invalidTypeCode, errorClass) && (!scoreRange || checkValidRange(CMIElement, value, scoreRange, invalidRangeCode, errorClass));
|
|
3546
3626
|
}
|
|
3547
3627
|
/**
|
|
@@ -3637,6 +3717,21 @@ ${stackTrace}`);
|
|
|
3637
3717
|
__publicField$9(this, "_offlineStorageService");
|
|
3638
3718
|
__publicField$9(this, "_cmiValueAccessService");
|
|
3639
3719
|
__publicField$9(this, "_courseId", "");
|
|
3720
|
+
__publicField$9(this, "_pendingCommitCount", 0);
|
|
3721
|
+
/**
|
|
3722
|
+
* Monotonic sequence for commits captured by this API instance. It is
|
|
3723
|
+
* intentionally not reset by reset().
|
|
3724
|
+
*/
|
|
3725
|
+
__publicField$9(this, "_commitSequence", 0);
|
|
3726
|
+
__publicField$9(this, "_commitSettleWaiters", []);
|
|
3727
|
+
/**
|
|
3728
|
+
* Canonical paths of every CMI element that has been explicitly assigned a
|
|
3729
|
+
* value via SetValue / loadFromJSON (both funnel through _commonSetCMIValue).
|
|
3730
|
+
* Used by standards that must tell "implemented but never set" apart from a
|
|
3731
|
+
* legitimately empty value when answering GetValue (SCORM 2004 error 403).
|
|
3732
|
+
* Cleared on reset so a fresh SCO attempt starts with nothing "set".
|
|
3733
|
+
*/
|
|
3734
|
+
__publicField$9(this, "_setCMIElements", /* @__PURE__ */new Set());
|
|
3640
3735
|
__publicField$9(this, "startingData");
|
|
3641
3736
|
__publicField$9(this, "currentState");
|
|
3642
3737
|
if (new.target === BaseAPI) {
|
|
@@ -3783,6 +3878,7 @@ ${stackTrace}`);
|
|
|
3783
3878
|
this.lastErrorCode = "0";
|
|
3784
3879
|
this._eventService.reset();
|
|
3785
3880
|
this.startingData = {};
|
|
3881
|
+
this._setCMIElements.clear();
|
|
3786
3882
|
if (this._offlineStorageService) {
|
|
3787
3883
|
this._offlineStorageService.updateSettings(this.settings);
|
|
3788
3884
|
if (settings?.courseId) {
|
|
@@ -3866,6 +3962,54 @@ ${stackTrace}`);
|
|
|
3866
3962
|
this._loggingService?.setLogHandler(settings.onLogMessage);
|
|
3867
3963
|
}
|
|
3868
3964
|
}
|
|
3965
|
+
/**
|
|
3966
|
+
* Gets the number of captured commit requests that have not yet settled.
|
|
3967
|
+
*
|
|
3968
|
+
* @return {number} The number of in-flight commits
|
|
3969
|
+
*/
|
|
3970
|
+
get pendingCommitCount() {
|
|
3971
|
+
return this._pendingCommitCount;
|
|
3972
|
+
}
|
|
3973
|
+
/**
|
|
3974
|
+
* Resolves when all currently in-flight commits have settled. A timeout is
|
|
3975
|
+
* best-effort: the promise resolves when it elapses even if commits remain,
|
|
3976
|
+
* and callers can inspect pendingCommitCount afterward to detect that case.
|
|
3977
|
+
*
|
|
3978
|
+
* @param {Object} [options] - Settle options
|
|
3979
|
+
* @param {number} [options.timeoutMs] - Maximum time to wait in milliseconds
|
|
3980
|
+
* @return {Promise<void>} A promise that resolves after the drain or timeout
|
|
3981
|
+
*/
|
|
3982
|
+
whenCommitsSettled(options) {
|
|
3983
|
+
if (this._pendingCommitCount === 0) {
|
|
3984
|
+
return Promise.resolve();
|
|
3985
|
+
}
|
|
3986
|
+
return new Promise(resolve => {
|
|
3987
|
+
const waiter = {
|
|
3988
|
+
resolve
|
|
3989
|
+
};
|
|
3990
|
+
if (options?.timeoutMs !== void 0) {
|
|
3991
|
+
waiter.timeoutId = setTimeout(() => {
|
|
3992
|
+
const waiterIndex = this._commitSettleWaiters.indexOf(waiter);
|
|
3993
|
+
if (waiterIndex === -1) {
|
|
3994
|
+
return;
|
|
3995
|
+
}
|
|
3996
|
+
this._commitSettleWaiters.splice(waiterIndex, 1);
|
|
3997
|
+
resolve();
|
|
3998
|
+
}, options.timeoutMs);
|
|
3999
|
+
}
|
|
4000
|
+
this._commitSettleWaiters.push(waiter);
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
/** Resolve and clear every waiter after the pending count reaches zero. */
|
|
4004
|
+
_flushCommitSettleWaiters() {
|
|
4005
|
+
const waiters = this._commitSettleWaiters.splice(0);
|
|
4006
|
+
for (const waiter of waiters) {
|
|
4007
|
+
if (waiter.timeoutId !== void 0) {
|
|
4008
|
+
clearTimeout(waiter.timeoutId);
|
|
4009
|
+
}
|
|
4010
|
+
waiter.resolve();
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
3869
4013
|
/**
|
|
3870
4014
|
* Terminates the current run of the API
|
|
3871
4015
|
* @param {string} callbackName
|
|
@@ -3886,7 +4030,7 @@ ${stackTrace}`);
|
|
|
3886
4030
|
} else {
|
|
3887
4031
|
stateCheckPassed = true;
|
|
3888
4032
|
this.processListeners("BeforeTerminate");
|
|
3889
|
-
const result = this.storeData(true);
|
|
4033
|
+
const result = this.storeData(true, "terminate");
|
|
3890
4034
|
if ((result.errorCode ?? 0) > 0) {
|
|
3891
4035
|
if (result.errorMessage) {
|
|
3892
4036
|
this.apiLog("terminate", `Terminate failed with error: ${result.errorMessage}`, LogLevelEnum.ERROR);
|
|
@@ -3926,6 +4070,9 @@ ${stackTrace}`);
|
|
|
3926
4070
|
} catch (e) {
|
|
3927
4071
|
returnValue = this.handleValueAccessException(CMIElement, e, returnValue);
|
|
3928
4072
|
}
|
|
4073
|
+
if (this.lastErrorCode === "0") {
|
|
4074
|
+
this.checkUninitializedGet(CMIElement, returnValue);
|
|
4075
|
+
}
|
|
3929
4076
|
this.processListeners(callbackName, CMIElement);
|
|
3930
4077
|
}
|
|
3931
4078
|
this.apiLog(callbackName, ": returned: " + returnValue, LogLevelEnum.INFO, CMIElement);
|
|
@@ -3935,6 +4082,10 @@ ${stackTrace}`);
|
|
|
3935
4082
|
if (this.lastErrorCode === "0") {
|
|
3936
4083
|
this.clearSCORMError(returnValue);
|
|
3937
4084
|
}
|
|
4085
|
+
const rawReturn = returnValue;
|
|
4086
|
+
if (typeof rawReturn === "number" || typeof rawReturn === "boolean") {
|
|
4087
|
+
return String(rawReturn);
|
|
4088
|
+
}
|
|
3938
4089
|
return returnValue;
|
|
3939
4090
|
}
|
|
3940
4091
|
/**
|
|
@@ -3978,10 +4129,12 @@ ${stackTrace}`);
|
|
|
3978
4129
|
* Orders LMS to store all content parameters
|
|
3979
4130
|
* @param {string} callbackName
|
|
3980
4131
|
* @param {boolean} checkTerminated
|
|
4132
|
+
* @param {CommitTrigger} trigger - What initiated the commit
|
|
3981
4133
|
* @return {string}
|
|
3982
4134
|
*/
|
|
3983
4135
|
commit(callbackName) {
|
|
3984
4136
|
let checkTerminated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
4137
|
+
let trigger = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "manual";
|
|
3985
4138
|
this.clearScheduledCommit();
|
|
3986
4139
|
let returnValue = global_constants.SCORM_TRUE;
|
|
3987
4140
|
if (this.isNotInitialized()) {
|
|
@@ -3993,20 +4146,21 @@ ${stackTrace}`);
|
|
|
3993
4146
|
this.throwSCORMError("api", errorCode);
|
|
3994
4147
|
if (errorCode === 143) returnValue = global_constants.SCORM_FALSE;
|
|
3995
4148
|
} else {
|
|
3996
|
-
const result = this.storeData(false);
|
|
3997
|
-
|
|
4149
|
+
const result = this.storeData(false, trigger);
|
|
4150
|
+
const errorCode = result.errorCode ?? 0;
|
|
4151
|
+
if (errorCode > 0) {
|
|
3998
4152
|
if (result.errorMessage) {
|
|
3999
4153
|
this.apiLog("commit", `Commit failed with error: ${result.errorMessage}`, LogLevelEnum.ERROR);
|
|
4000
4154
|
}
|
|
4001
4155
|
if (result.errorDetails) {
|
|
4002
4156
|
this.apiLog("commit", `Error details: ${JSON.stringify(result.errorDetails)}`, LogLevelEnum.DEBUG);
|
|
4003
4157
|
}
|
|
4004
|
-
this.throwSCORMError("api",
|
|
4158
|
+
this.throwSCORMError("api", errorCode);
|
|
4005
4159
|
}
|
|
4006
4160
|
const resultValue = result?.result ?? global_constants.SCORM_FALSE;
|
|
4007
4161
|
returnValue = typeof resultValue === "boolean" ? String(resultValue) : resultValue;
|
|
4008
4162
|
this.apiLog(callbackName, " Result: " + returnValue, LogLevelEnum.DEBUG, "HttpRequest");
|
|
4009
|
-
if (checkTerminated) this.lastErrorCode = "0";
|
|
4163
|
+
if (checkTerminated && errorCode === 0) this.lastErrorCode = "0";
|
|
4010
4164
|
this.processListeners(callbackName);
|
|
4011
4165
|
if (this.settings.enableOfflineSupport && this._offlineStorageService && this._offlineStorageService.isDeviceOnline() && this._courseId) {
|
|
4012
4166
|
this._offlineStorageService.hasPendingOfflineData(this._courseId).then(hasPendingData => {
|
|
@@ -4213,7 +4367,11 @@ ${stackTrace}`);
|
|
|
4213
4367
|
* @return {string}
|
|
4214
4368
|
*/
|
|
4215
4369
|
_commonSetCMIValue(methodName, scorm2004, CMIElement, value) {
|
|
4216
|
-
|
|
4370
|
+
const result = this._cmiValueAccessService.setCMIValue(methodName, scorm2004, CMIElement, value);
|
|
4371
|
+
if (result === global_constants.SCORM_TRUE) {
|
|
4372
|
+
this._setCMIElements.add(CMIElement);
|
|
4373
|
+
}
|
|
4374
|
+
return result;
|
|
4217
4375
|
}
|
|
4218
4376
|
/**
|
|
4219
4377
|
* Gets a value from the CMI Object.
|
|
@@ -4227,6 +4385,17 @@ ${stackTrace}`);
|
|
|
4227
4385
|
_commonGetCMIValue(methodName, scorm2004, CMIElement) {
|
|
4228
4386
|
return this._cmiValueAccessService.getCMIValue(methodName, scorm2004, CMIElement);
|
|
4229
4387
|
}
|
|
4388
|
+
/**
|
|
4389
|
+
* Hook invoked by getValue after a successful resolution. Standards that must
|
|
4390
|
+
* distinguish "implemented but never set, no default value" from a
|
|
4391
|
+
* legitimately empty value override this to raise VALUE_NOT_INITIALIZED.
|
|
4392
|
+
* Default is a no-op, so SCORM 1.2 / AICC keep returning "" with code 0.
|
|
4393
|
+
*
|
|
4394
|
+
* @param {string} _CMIElement - the element that was read
|
|
4395
|
+
* @param {any} _returnValue - the value getCMIValue resolved
|
|
4396
|
+
* @protected
|
|
4397
|
+
*/
|
|
4398
|
+
checkUninitializedGet(_CMIElement, _returnValue) {}
|
|
4230
4399
|
/**
|
|
4231
4400
|
* Returns true if the API's current state is STATE_INITIALIZED
|
|
4232
4401
|
*
|
|
@@ -4309,9 +4478,14 @@ ${stackTrace}`);
|
|
|
4309
4478
|
* @param {string} functionName - The name of the function/event that occurred
|
|
4310
4479
|
* @param {string} CMIElement - Optional CMI element involved in the event
|
|
4311
4480
|
* @param {any} value - Optional value associated with the event
|
|
4481
|
+
* @param {CommitEventContext} context - Optional context for commit lifecycle events
|
|
4312
4482
|
*/
|
|
4313
|
-
processListeners(functionName, CMIElement, value) {
|
|
4314
|
-
|
|
4483
|
+
processListeners(functionName, CMIElement, value, context) {
|
|
4484
|
+
if (context !== void 0) {
|
|
4485
|
+
this._eventService.processListeners(functionName, CMIElement, value, context);
|
|
4486
|
+
} else {
|
|
4487
|
+
this._eventService.processListeners(functionName, CMIElement, value);
|
|
4488
|
+
}
|
|
4315
4489
|
}
|
|
4316
4490
|
/**
|
|
4317
4491
|
* Throws a SCORM error with the specified error number and optional message.
|
|
@@ -4433,23 +4607,97 @@ ${stackTrace}`);
|
|
|
4433
4607
|
* @param {string} url - The URL to send the request to
|
|
4434
4608
|
* @param {CommitObject | StringKeyMap | Array<any>} params - The parameters to send
|
|
4435
4609
|
* @param {boolean} immediate - Whether to send the request immediately without waiting
|
|
4610
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
4436
4611
|
* @returns {ResultObject} - The result of the request
|
|
4437
4612
|
*/
|
|
4438
4613
|
processHttpRequest(url, params) {
|
|
4439
4614
|
let immediate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4615
|
+
let trigger = arguments.length > 3 ? arguments[3] : undefined;
|
|
4616
|
+
const sequence = ++this._commitSequence;
|
|
4617
|
+
this._pendingCommitCount += 1;
|
|
4618
|
+
let settled = false;
|
|
4619
|
+
let completionDeferred = false;
|
|
4620
|
+
const settle = () => {
|
|
4621
|
+
if (settled) {
|
|
4622
|
+
return;
|
|
4623
|
+
}
|
|
4624
|
+
settled = true;
|
|
4625
|
+
this._pendingCommitCount -= 1;
|
|
4626
|
+
if (this._pendingCommitCount === 0) {
|
|
4627
|
+
this._flushCommitSettleWaiters();
|
|
4628
|
+
}
|
|
4629
|
+
};
|
|
4630
|
+
try {
|
|
4631
|
+
const resolvedTrigger = trigger ?? (immediate ? "terminate" : "manual");
|
|
4632
|
+
let finalParams = params;
|
|
4633
|
+
if (immediate && this.settings.terminateCommitPayloadField) {
|
|
4634
|
+
const field = this.settings.terminateCommitPayloadField;
|
|
4635
|
+
if (Array.isArray(finalParams)) {
|
|
4636
|
+
finalParams = [...finalParams, `${encodeURIComponent(field)}=true`];
|
|
4637
|
+
} else if (finalParams && typeof finalParams === "object") {
|
|
4638
|
+
finalParams = {
|
|
4639
|
+
...finalParams,
|
|
4640
|
+
[field]: true
|
|
4641
|
+
};
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
if (this.settings.includeCommitSequence === true) {
|
|
4645
|
+
if (Array.isArray(finalParams)) {
|
|
4646
|
+
finalParams = [...finalParams, `commitSequence=${sequence}`];
|
|
4647
|
+
} else if (finalParams && typeof finalParams === "object") {
|
|
4648
|
+
finalParams = {
|
|
4649
|
+
...finalParams,
|
|
4650
|
+
commitSequence: sequence
|
|
4651
|
+
};
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
const finalUrl = immediate && this.settings.terminateCommitParam ? appendQueryParam(url, this.settings.terminateCommitParam, "true") : url;
|
|
4655
|
+
const metadata = {
|
|
4656
|
+
isTerminateCommit: immediate,
|
|
4657
|
+
trigger: resolvedTrigger,
|
|
4658
|
+
sequence
|
|
4659
|
+
};
|
|
4660
|
+
const context = {
|
|
4661
|
+
url: finalUrl,
|
|
4662
|
+
trigger: resolvedTrigger,
|
|
4663
|
+
isTerminateCommit: immediate,
|
|
4664
|
+
sequence
|
|
4665
|
+
};
|
|
4666
|
+
if (this.settings.enableOfflineSupport && this._offlineStorageService && !this._offlineStorageService.isDeviceOnline() && this._courseId) {
|
|
4667
|
+
this.apiLog("processHttpRequest", "Device is offline, storing data locally", LogLevelEnum.INFO);
|
|
4668
|
+
if (finalParams && typeof finalParams === "object" && "cmi" in finalParams) {
|
|
4669
|
+
return this._offlineStorageService.storeOffline(this._courseId, finalParams, {
|
|
4670
|
+
isTerminateCommit: immediate,
|
|
4671
|
+
sequence
|
|
4672
|
+
});
|
|
4673
|
+
} else {
|
|
4674
|
+
this.apiLog("processHttpRequest", "Invalid commit data format for offline storage", LogLevelEnum.ERROR);
|
|
4675
|
+
return {
|
|
4676
|
+
result: global_constants.SCORM_FALSE,
|
|
4677
|
+
errorCode: this._error_codes.GENERAL ?? 101
|
|
4678
|
+
};
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
const apiLog = (functionName, message, level, element) => this.apiLog(functionName, message, level, element);
|
|
4682
|
+
const processListeners = (functionName, CMIElement, value) => {
|
|
4683
|
+
if (functionName === "CommitSuccess" || functionName === "CommitError") {
|
|
4684
|
+
if (functionName === "CommitError" && typeof value === "number") {
|
|
4685
|
+
context.errorCode = value;
|
|
4686
|
+
}
|
|
4687
|
+
settle();
|
|
4688
|
+
this.processListeners(functionName, CMIElement, value, context);
|
|
4689
|
+
} else {
|
|
4690
|
+
this.processListeners(functionName, CMIElement, value);
|
|
4691
|
+
}
|
|
4692
|
+
};
|
|
4693
|
+
const result = this._httpService.processHttpRequest(finalUrl, finalParams, immediate, apiLog, processListeners, metadata, settle);
|
|
4694
|
+
completionDeferred = this._httpService.reportsRequestCompletion === true;
|
|
4695
|
+
return result;
|
|
4696
|
+
} finally {
|
|
4697
|
+
if (!completionDeferred) {
|
|
4698
|
+
settle();
|
|
4450
4699
|
}
|
|
4451
4700
|
}
|
|
4452
|
-
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));
|
|
4453
4701
|
}
|
|
4454
4702
|
/**
|
|
4455
4703
|
* Schedules a commit operation to occur after a specified delay.
|
|
@@ -4633,6 +4881,11 @@ ${stackTrace}`);
|
|
|
4633
4881
|
__publicField$8(this, "__invalid_range_code");
|
|
4634
4882
|
__publicField$8(this, "__decimal_regex");
|
|
4635
4883
|
__publicField$8(this, "__error_class");
|
|
4884
|
+
/**
|
|
4885
|
+
* When true, an empty string is a valid value (clears the element). SCORM 1.2
|
|
4886
|
+
* score elements may be blank per the ADL 1.2 CTS; SCORM 2004 leaves this off.
|
|
4887
|
+
*/
|
|
4888
|
+
__publicField$8(this, "__allow_empty_string");
|
|
4636
4889
|
__publicField$8(this, "_raw", "");
|
|
4637
4890
|
__publicField$8(this, "_min", "");
|
|
4638
4891
|
__publicField$8(this, "_max");
|
|
@@ -4644,6 +4897,7 @@ ${stackTrace}`);
|
|
|
4644
4897
|
this.__invalid_range_code = params.invalidRangeCode || scorm12_errors.VALUE_OUT_OF_RANGE;
|
|
4645
4898
|
this.__decimal_regex = params.decimalRegex || scorm12_regex.CMIDecimal;
|
|
4646
4899
|
this.__error_class = params.errorClass;
|
|
4900
|
+
this.__allow_empty_string = params.allowEmptyString ?? false;
|
|
4647
4901
|
}
|
|
4648
4902
|
/**
|
|
4649
4903
|
* Called when the API has been reset
|
|
@@ -4683,7 +4937,7 @@ ${stackTrace}`);
|
|
|
4683
4937
|
* @param {string} raw
|
|
4684
4938
|
*/
|
|
4685
4939
|
set raw(raw) {
|
|
4686
|
-
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)) {
|
|
4940
|
+
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)) {
|
|
4687
4941
|
this._raw = raw;
|
|
4688
4942
|
}
|
|
4689
4943
|
}
|
|
@@ -4699,7 +4953,7 @@ ${stackTrace}`);
|
|
|
4699
4953
|
* @param {string} min
|
|
4700
4954
|
*/
|
|
4701
4955
|
set min(min) {
|
|
4702
|
-
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)) {
|
|
4956
|
+
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)) {
|
|
4703
4957
|
this._min = min;
|
|
4704
4958
|
}
|
|
4705
4959
|
}
|
|
@@ -4715,7 +4969,7 @@ ${stackTrace}`);
|
|
|
4715
4969
|
* @param {string} max
|
|
4716
4970
|
*/
|
|
4717
4971
|
set max(max) {
|
|
4718
|
-
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)) {
|
|
4972
|
+
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)) {
|
|
4719
4973
|
this._max = max;
|
|
4720
4974
|
}
|
|
4721
4975
|
}
|
|
@@ -4792,7 +5046,9 @@ ${stackTrace}`);
|
|
|
4792
5046
|
invalidErrorCode: scorm12_errors.INVALID_SET_VALUE,
|
|
4793
5047
|
invalidTypeCode: scorm12_errors.TYPE_MISMATCH,
|
|
4794
5048
|
invalidRangeCode: scorm12_errors.VALUE_OUT_OF_RANGE,
|
|
4795
|
-
errorClass: Scorm12ValidationError
|
|
5049
|
+
errorClass: Scorm12ValidationError,
|
|
5050
|
+
// SCORM 1.2 score elements may be set blank (clears the value), per ADL 1.2 CTS.
|
|
5051
|
+
allowEmptyString: true
|
|
4796
5052
|
});
|
|
4797
5053
|
}
|
|
4798
5054
|
/**
|
|
@@ -5160,7 +5416,9 @@ ${stackTrace}`);
|
|
|
5160
5416
|
invalidErrorCode: scorm12_errors.INVALID_SET_VALUE,
|
|
5161
5417
|
invalidTypeCode: scorm12_errors.TYPE_MISMATCH,
|
|
5162
5418
|
invalidRangeCode: scorm12_errors.VALUE_OUT_OF_RANGE,
|
|
5163
|
-
errorClass: Scorm12ValidationError
|
|
5419
|
+
errorClass: Scorm12ValidationError,
|
|
5420
|
+
// SCORM 1.2 score elements may be set blank (clears the value), per ADL 1.2 CTS.
|
|
5421
|
+
allowEmptyString: true
|
|
5164
5422
|
});
|
|
5165
5423
|
}
|
|
5166
5424
|
/**
|
|
@@ -6597,16 +6855,17 @@ ${stackTrace}`);
|
|
|
6597
6855
|
* Attempts to store the data to the LMS
|
|
6598
6856
|
*
|
|
6599
6857
|
* @param {boolean} terminateCommit
|
|
6858
|
+
* @param {CommitTrigger} [trigger] - What initiated the commit
|
|
6600
6859
|
* @return {ResultObject}
|
|
6601
6860
|
*/
|
|
6602
|
-
storeData(terminateCommit) {
|
|
6861
|
+
storeData(terminateCommit, trigger) {
|
|
6603
6862
|
if (terminateCommit) {
|
|
6604
6863
|
const originalStatus = this.cmi.core.lesson_status;
|
|
6605
6864
|
if (this.cmi.core.lesson_mode === "browse") {
|
|
6606
6865
|
const startingStatus = this.startingData?.cmi?.core?.lesson_status || "";
|
|
6607
6866
|
if (startingStatus === "" && originalStatus === "not attempted") {
|
|
6608
6867
|
this.cmi.core.lesson_status = "browsed";
|
|
6609
|
-
return this.processCommitData(terminateCommit);
|
|
6868
|
+
return this.processCommitData(terminateCommit, trigger);
|
|
6610
6869
|
}
|
|
6611
6870
|
}
|
|
6612
6871
|
if (!this.cmi.core.lesson_status || !this.statusSetByModule && this.cmi.core.lesson_status === "not attempted") {
|
|
@@ -6635,12 +6894,12 @@ ${stackTrace}`);
|
|
|
6635
6894
|
}
|
|
6636
6895
|
}
|
|
6637
6896
|
}
|
|
6638
|
-
return this.processCommitData(terminateCommit);
|
|
6897
|
+
return this.processCommitData(terminateCommit, trigger);
|
|
6639
6898
|
}
|
|
6640
|
-
processCommitData(terminateCommit) {
|
|
6899
|
+
processCommitData(terminateCommit, trigger) {
|
|
6641
6900
|
const commitObject = this.getCommitObject(terminateCommit);
|
|
6642
6901
|
if (typeof this.settings.lmsCommitUrl === "string") {
|
|
6643
|
-
return this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit);
|
|
6902
|
+
return this.processHttpRequest(this.settings.lmsCommitUrl, commitObject, terminateCommit, trigger);
|
|
6644
6903
|
} else {
|
|
6645
6904
|
return {
|
|
6646
6905
|
result: global_constants.SCORM_TRUE,
|