scorm-again 3.1.4 → 3.2.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 +3 -1
- package/dist/cross-frame-api.js +6 -13
- package/dist/cross-frame-api.js.map +1 -1
- package/dist/cross-frame-api.min.js +1 -1
- package/dist/cross-frame-lms.js +2 -6
- package/dist/cross-frame-lms.js.map +1 -1
- package/dist/esm/scorm-again.js +1128 -198
- 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 +60 -13
- 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 +1128 -198
- 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 +2088 -1465
- package/dist/scorm-again.js.map +1 -1
- package/dist/scorm-again.min.js +1 -1
- package/dist/scorm12.js +347 -420
- package/dist/scorm12.js.map +1 -1
- package/dist/scorm12.min.js +1 -1
- package/dist/scorm2004.js +1983 -1301
- package/dist/scorm2004.js.map +1 -1
- package/dist/scorm2004.min.js +1 -1
- package/dist/types/BaseAPI.d.ts +1 -1
- package/dist/types/Scorm2004API.d.ts +11 -0
- package/dist/types/cmi/scorm2004/sequencing/activity.d.ts +47 -5
- package/dist/types/cmi/scorm2004/sequencing/handlers/rte_data_transfer.d.ts +1 -0
- package/dist/types/cmi/scorm2004/sequencing/objectives/global_objective_synchronizer.d.ts +17 -1
- package/dist/types/cmi/scorm2004/sequencing/sequencing_rules.d.ts +6 -1
- package/dist/types/cmi/scorm2004/sequencing/traversal/flow_traversal_service.d.ts +5 -2
- package/dist/types/constants/regex.d.ts +1 -0
- package/dist/types/interfaces/services.d.ts +1 -1
- package/dist/types/services/AsynchronousHttpService.d.ts +1 -0
- package/dist/types/services/ErrorHandlingService.d.ts +4 -4
- package/dist/types/services/SynchronousHttpService.d.ts +1 -0
- package/dist/types/types/api_types.d.ts +16 -0
- package/dist/types/types/sequencing_types.d.ts +7 -0
- package/dist/types/utilities/core.d.ts +6 -1
- package/package.json +4 -4
package/dist/esm/scorm-again.js
CHANGED
|
@@ -2,6 +2,23 @@ const SECONDS_PER_SECOND = 1;
|
|
|
2
2
|
const SECONDS_PER_MINUTE = 60;
|
|
3
3
|
const SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
|
|
4
4
|
const SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
|
|
5
|
+
const CORS_SAFELISTED_CONTENT_TYPES = [
|
|
6
|
+
"text/plain",
|
|
7
|
+
"application/x-www-form-urlencoded",
|
|
8
|
+
"multipart/form-data"
|
|
9
|
+
];
|
|
10
|
+
function isCorsSafelistedContentType(contentType) {
|
|
11
|
+
const essence = ((contentType || "").split(";")[0] ?? "").trim().toLowerCase();
|
|
12
|
+
return CORS_SAFELISTED_CONTENT_TYPES.includes(essence);
|
|
13
|
+
}
|
|
14
|
+
function isCrossOriginUrl(url) {
|
|
15
|
+
if (typeof location === "undefined" || !location || !location.origin) return false;
|
|
16
|
+
try {
|
|
17
|
+
return new URL(url, location.href).origin !== location.origin;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
5
22
|
const designations = {
|
|
6
23
|
D: SECONDS_PER_DAY,
|
|
7
24
|
H: SECONDS_PER_HOUR,
|
|
@@ -197,12 +214,18 @@ function stringMatches(str, tester) {
|
|
|
197
214
|
}
|
|
198
215
|
return new RegExp(tester).test(str);
|
|
199
216
|
}
|
|
200
|
-
function memoize(fn, keyFn) {
|
|
217
|
+
function memoize(fn, keyFn, options) {
|
|
201
218
|
const cache = /* @__PURE__ */ new Map();
|
|
202
219
|
return ((...args) => {
|
|
203
220
|
const key = keyFn ? keyFn(...args) : JSON.stringify(args);
|
|
221
|
+
if (options?.maxKeyLength !== void 0 && key.length > options.maxKeyLength) {
|
|
222
|
+
return fn(...args);
|
|
223
|
+
}
|
|
204
224
|
return cache.has(key) ? cache.get(key) : (() => {
|
|
205
225
|
const result = fn(...args);
|
|
226
|
+
if (options?.maxEntries !== void 0 && cache.size >= options.maxEntries) {
|
|
227
|
+
cache.delete(cache.keys().next().value);
|
|
228
|
+
}
|
|
206
229
|
cache.set(key, result);
|
|
207
230
|
return result;
|
|
208
231
|
})();
|
|
@@ -808,8 +831,10 @@ const DefaultSettings = {
|
|
|
808
831
|
lmsCommitUrl: false,
|
|
809
832
|
dataCommitFormat: "json",
|
|
810
833
|
commitRequestDataType: "application/json;charset=UTF-8",
|
|
834
|
+
terminationCommitContentType: "text/plain;charset=UTF-8",
|
|
811
835
|
autoProgress: false,
|
|
812
836
|
logLevel: LogLevelEnum.ERROR,
|
|
837
|
+
uninitializedGetLogLevel: LogLevelEnum.WARN,
|
|
813
838
|
selfReportSessionTime: false,
|
|
814
839
|
alwaysSendTotalTime: false,
|
|
815
840
|
renderCommonCommitFields: false,
|
|
@@ -1103,6 +1128,8 @@ const scorm2004_regex = {
|
|
|
1103
1128
|
CMILang: "^([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?$|^$",
|
|
1104
1129
|
/** CMILangString250 - String with optional language tag, max 250 chars (RTE C.1.3) */
|
|
1105
1130
|
CMILangString250: "^({lang=([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?})?((?!{.*$).{0,250}$)?$",
|
|
1131
|
+
/** CMILangString - Optional language tag, no length cap; RTE C.1.3 SPM is a floor and is deliberately not enforced */
|
|
1132
|
+
CMILangString: "^({lang=([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?})?((?!{.*$).*$)?$",
|
|
1106
1133
|
/** CMILangcr - Language tag pattern with content */
|
|
1107
1134
|
CMILangcr: "^(({lang=([a-zA-Z]{1,8}|i|x)?(-[a-zA-Z0-9-]{2,8})?}))(.*?)$",
|
|
1108
1135
|
/** CMILangString250cr - String with optional language tag (carriage return variant) */
|
|
@@ -1768,12 +1795,6 @@ const HIDE_LMS_UI_TOKENS = [
|
|
|
1768
1795
|
"suspendAll"
|
|
1769
1796
|
];
|
|
1770
1797
|
|
|
1771
|
-
var RuleConditionOperator = /* @__PURE__ */ ((RuleConditionOperator2) => {
|
|
1772
|
-
RuleConditionOperator2["NOT"] = "not";
|
|
1773
|
-
RuleConditionOperator2["AND"] = "and";
|
|
1774
|
-
RuleConditionOperator2["OR"] = "or";
|
|
1775
|
-
return RuleConditionOperator2;
|
|
1776
|
-
})(RuleConditionOperator || {});
|
|
1777
1798
|
var RuleActionType = /* @__PURE__ */ ((RuleActionType2) => {
|
|
1778
1799
|
RuleActionType2["SKIP"] = "skip";
|
|
1779
1800
|
RuleActionType2["DISABLED"] = "disabled";
|
|
@@ -1788,6 +1809,48 @@ var RuleActionType = /* @__PURE__ */ ((RuleActionType2) => {
|
|
|
1788
1809
|
RuleActionType2["EXIT"] = "exit";
|
|
1789
1810
|
return RuleActionType2;
|
|
1790
1811
|
})(RuleActionType || {});
|
|
1812
|
+
function kleeneNot(value) {
|
|
1813
|
+
if (value === "unknown") {
|
|
1814
|
+
return "unknown";
|
|
1815
|
+
}
|
|
1816
|
+
return !value;
|
|
1817
|
+
}
|
|
1818
|
+
function kleeneAnd(values) {
|
|
1819
|
+
let hasUnknown = false;
|
|
1820
|
+
for (const value of values) {
|
|
1821
|
+
if (value === false) {
|
|
1822
|
+
return false;
|
|
1823
|
+
}
|
|
1824
|
+
if (value === "unknown") {
|
|
1825
|
+
hasUnknown = true;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
return hasUnknown ? "unknown" : true;
|
|
1829
|
+
}
|
|
1830
|
+
function kleeneOr(values) {
|
|
1831
|
+
let hasUnknown = false;
|
|
1832
|
+
for (const value of values) {
|
|
1833
|
+
if (value === true) {
|
|
1834
|
+
return true;
|
|
1835
|
+
}
|
|
1836
|
+
if (value === "unknown") {
|
|
1837
|
+
hasUnknown = true;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
return hasUnknown ? "unknown" : false;
|
|
1841
|
+
}
|
|
1842
|
+
function combineRuleConditionResults(values, conditionCombination) {
|
|
1843
|
+
if (values.length === 0) {
|
|
1844
|
+
return false;
|
|
1845
|
+
}
|
|
1846
|
+
if (conditionCombination === "all" || conditionCombination === "and" /* AND */) {
|
|
1847
|
+
return kleeneAnd(values);
|
|
1848
|
+
}
|
|
1849
|
+
if (conditionCombination === "any" || conditionCombination === "or" /* OR */) {
|
|
1850
|
+
return kleeneOr(values);
|
|
1851
|
+
}
|
|
1852
|
+
return false;
|
|
1853
|
+
}
|
|
1791
1854
|
class RuleCondition extends BaseCMI {
|
|
1792
1855
|
_condition = "always" /* ALWAYS */;
|
|
1793
1856
|
_operator = null;
|
|
@@ -1893,51 +1956,72 @@ class RuleCondition extends BaseCMI {
|
|
|
1893
1956
|
/**
|
|
1894
1957
|
* Evaluate the condition for an activity
|
|
1895
1958
|
* @param {Activity} activity - The activity to evaluate the condition for
|
|
1896
|
-
* @return {
|
|
1959
|
+
* @return {RuleConditionEvaluation} - True, false, or unknown per SCORM 2004 4th Ed.
|
|
1897
1960
|
*/
|
|
1898
1961
|
evaluate(activity) {
|
|
1899
1962
|
let result;
|
|
1963
|
+
const hasReferencedObjective = this._referencedObjective !== null;
|
|
1900
1964
|
const referencedObjective = this.resolveReferencedObjective(activity);
|
|
1901
1965
|
switch (this._condition) {
|
|
1902
1966
|
case "satisfied" /* SATISFIED */:
|
|
1903
1967
|
case "objectiveSatisfied" /* OBJECTIVE_SATISFIED */:
|
|
1904
|
-
if (referencedObjective) {
|
|
1905
|
-
result =
|
|
1968
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1969
|
+
result = false;
|
|
1970
|
+
} else if (referencedObjective) {
|
|
1971
|
+
result = referencedObjective.satisfiedStatusKnown || referencedObjective.progressStatus ? referencedObjective.satisfiedStatus === true : "unknown";
|
|
1972
|
+
} else if (activity.objectiveSatisfiedStatusKnown) {
|
|
1973
|
+
result = activity.objectiveSatisfiedStatus === true;
|
|
1974
|
+
} else if (activity.successStatus !== SuccessStatus.UNKNOWN) {
|
|
1975
|
+
result = activity.successStatus === SuccessStatus.PASSED;
|
|
1906
1976
|
} else {
|
|
1907
|
-
result =
|
|
1977
|
+
result = "unknown";
|
|
1908
1978
|
}
|
|
1909
1979
|
break;
|
|
1910
1980
|
case "objectiveStatusKnown" /* OBJECTIVE_STATUS_KNOWN */:
|
|
1911
|
-
result = referencedObjective ? !!referencedObjective.
|
|
1981
|
+
result = hasReferencedObjective && !referencedObjective ? false : referencedObjective ? !!referencedObjective.satisfiedStatusKnown : !!activity.objectiveSatisfiedStatusKnown;
|
|
1912
1982
|
break;
|
|
1913
1983
|
case "objectiveMeasureKnown" /* OBJECTIVE_MEASURE_KNOWN */:
|
|
1914
|
-
result = referencedObjective ? !!referencedObjective.measureStatus : !!activity.objectiveMeasureStatus;
|
|
1984
|
+
result = hasReferencedObjective && !referencedObjective ? false : referencedObjective ? !!referencedObjective.measureStatus : !!activity.objectiveMeasureStatus;
|
|
1915
1985
|
break;
|
|
1916
1986
|
case "objectiveMeasureGreaterThan" /* OBJECTIVE_MEASURE_GREATER_THAN */: {
|
|
1987
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1988
|
+
result = false;
|
|
1989
|
+
break;
|
|
1990
|
+
}
|
|
1917
1991
|
const greaterThanValue = this._parameters.get("threshold") || 0;
|
|
1918
1992
|
const measureStatus = referencedObjective ? referencedObjective.measureStatus : activity.objectiveMeasureStatus;
|
|
1919
1993
|
const measureValue = referencedObjective ? referencedObjective.normalizedMeasure : activity.objectiveNormalizedMeasure;
|
|
1920
|
-
result =
|
|
1994
|
+
result = measureStatus ? measureValue > greaterThanValue : "unknown";
|
|
1921
1995
|
break;
|
|
1922
1996
|
}
|
|
1923
1997
|
case "objectiveMeasureLessThan" /* OBJECTIVE_MEASURE_LESS_THAN */: {
|
|
1998
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1999
|
+
result = false;
|
|
2000
|
+
break;
|
|
2001
|
+
}
|
|
1924
2002
|
const lessThanValue = this._parameters.get("threshold") || 0;
|
|
1925
2003
|
const measureStatus = referencedObjective ? referencedObjective.measureStatus : activity.objectiveMeasureStatus;
|
|
1926
2004
|
const measureValue = referencedObjective ? referencedObjective.normalizedMeasure : activity.objectiveNormalizedMeasure;
|
|
1927
|
-
result =
|
|
2005
|
+
result = measureStatus ? measureValue < lessThanValue : "unknown";
|
|
1928
2006
|
break;
|
|
1929
2007
|
}
|
|
1930
2008
|
case "completed" /* COMPLETED */:
|
|
1931
2009
|
case "activityCompleted" /* ACTIVITY_COMPLETED */:
|
|
1932
|
-
if (referencedObjective) {
|
|
1933
|
-
result =
|
|
2010
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
2011
|
+
result = false;
|
|
2012
|
+
} else if (referencedObjective) {
|
|
2013
|
+
result = referencedObjective.completionStatus === CompletionStatus.UNKNOWN ? "unknown" : referencedObjective.completionStatus === CompletionStatus.COMPLETED;
|
|
2014
|
+
} else if (activity.completionStatus === CompletionStatus.UNKNOWN) {
|
|
2015
|
+
result = "unknown";
|
|
1934
2016
|
} else {
|
|
1935
|
-
result = activity.
|
|
2017
|
+
result = activity.completionStatus === CompletionStatus.COMPLETED;
|
|
1936
2018
|
}
|
|
1937
2019
|
break;
|
|
1938
2020
|
case "progressKnown" /* PROGRESS_KNOWN */:
|
|
1939
2021
|
case "activityProgressKnown" /* ACTIVITY_PROGRESS_KNOWN */:
|
|
1940
|
-
if (referencedObjective) {
|
|
2022
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
2023
|
+
result = false;
|
|
2024
|
+
} else if (referencedObjective) {
|
|
1941
2025
|
result = referencedObjective.completionStatus !== CompletionStatus.UNKNOWN;
|
|
1942
2026
|
} else {
|
|
1943
2027
|
result = activity.completionStatus !== "unknown";
|
|
@@ -1966,7 +2050,7 @@ class RuleCondition extends BaseCMI {
|
|
|
1966
2050
|
break;
|
|
1967
2051
|
}
|
|
1968
2052
|
if (this._operator === "not" /* NOT */) {
|
|
1969
|
-
result =
|
|
2053
|
+
result = kleeneNot(result);
|
|
1970
2054
|
}
|
|
1971
2055
|
return result;
|
|
1972
2056
|
}
|
|
@@ -2173,15 +2257,10 @@ class SequencingRule extends BaseCMI {
|
|
|
2173
2257
|
* @return {boolean} - True if the rule conditions are met, false otherwise
|
|
2174
2258
|
*/
|
|
2175
2259
|
evaluate(activity) {
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
return this._conditions.every((condition) => condition.evaluate(activity));
|
|
2181
|
-
} else if (this._conditionCombination === "any" || this._conditionCombination === "or" /* OR */) {
|
|
2182
|
-
return this._conditions.some((condition) => condition.evaluate(activity));
|
|
2183
|
-
}
|
|
2184
|
-
return false;
|
|
2260
|
+
return combineRuleConditionResults(
|
|
2261
|
+
this._conditions.map((condition) => condition.evaluate(activity)),
|
|
2262
|
+
this._conditionCombination
|
|
2263
|
+
) === true;
|
|
2185
2264
|
}
|
|
2186
2265
|
/**
|
|
2187
2266
|
* toJSON for SequencingRule
|
|
@@ -3014,19 +3093,10 @@ class RuleEvaluationEngine {
|
|
|
3014
3093
|
* Evaluates individual sequencing rule conditions
|
|
3015
3094
|
* @param {Activity} activity - The activity to evaluate the rule for
|
|
3016
3095
|
* @param {SequencingRule} rule - The rule to evaluate
|
|
3017
|
-
* @return {boolean} - True
|
|
3096
|
+
* @return {boolean} - True only when the rule evaluates to definite true
|
|
3018
3097
|
*/
|
|
3019
3098
|
checkRuleSubprocess(activity, rule) {
|
|
3020
|
-
|
|
3021
|
-
return true;
|
|
3022
|
-
}
|
|
3023
|
-
const conditionCombination = rule.conditionCombination;
|
|
3024
|
-
if (conditionCombination === "all" || conditionCombination === RuleConditionOperator.AND) {
|
|
3025
|
-
return rule.conditions.every((condition) => condition.evaluate(activity));
|
|
3026
|
-
} else if (conditionCombination === "any" || conditionCombination === RuleConditionOperator.OR) {
|
|
3027
|
-
return rule.conditions.some((condition) => condition.evaluate(activity));
|
|
3028
|
-
}
|
|
3029
|
-
return false;
|
|
3099
|
+
return rule.evaluate(activity);
|
|
3030
3100
|
}
|
|
3031
3101
|
/**
|
|
3032
3102
|
* Exit Action Rules Subprocess (TB.2.1)
|
|
@@ -3898,16 +3968,21 @@ class FlowTraversalService {
|
|
|
3898
3968
|
* @param {Activity} fromActivity - The activity to flow from
|
|
3899
3969
|
* @param {FlowSubprocessMode} direction - The flow direction
|
|
3900
3970
|
* @return {FlowSubprocessResult} - Result containing the deliverable activity
|
|
3971
|
+
* @spec SN Book: SB.2.3 (Flow Subprocess) - preserves the SB.2.1 effective traversal direction for SB.2.2.
|
|
3972
|
+
* @spec SN Book: SB.2.2 (Flow Activity Traversal Subprocess) - evaluates candidates using the effective direction returned by SB.2.1.
|
|
3901
3973
|
*/
|
|
3902
3974
|
flowSubprocess(fromActivity, direction) {
|
|
3903
3975
|
let candidateActivity = fromActivity;
|
|
3904
3976
|
let firstIteration = true;
|
|
3905
3977
|
let lastCandidateHadNoChildren = false;
|
|
3978
|
+
let currentDirection = direction;
|
|
3979
|
+
let forwardOnlyCluster = null;
|
|
3906
3980
|
while (candidateActivity) {
|
|
3907
3981
|
const traversalResult = this.flowTreeTraversalSubprocess(
|
|
3908
3982
|
candidateActivity,
|
|
3909
|
-
|
|
3910
|
-
firstIteration
|
|
3983
|
+
currentDirection,
|
|
3984
|
+
firstIteration,
|
|
3985
|
+
forwardOnlyCluster
|
|
3911
3986
|
);
|
|
3912
3987
|
if (!traversalResult.activity) {
|
|
3913
3988
|
let exceptionCode = null;
|
|
@@ -3925,17 +4000,22 @@ class FlowTraversalService {
|
|
|
3925
4000
|
traversalResult.endSequencingSession
|
|
3926
4001
|
);
|
|
3927
4002
|
}
|
|
4003
|
+
const effectiveDirection = traversalResult.direction || currentDirection;
|
|
4004
|
+
if (traversalResult.forwardOnlyCluster) {
|
|
4005
|
+
forwardOnlyCluster = traversalResult.forwardOnlyCluster;
|
|
4006
|
+
}
|
|
3928
4007
|
lastCandidateHadNoChildren = traversalResult.activity.children.length > 0 && traversalResult.activity.getAvailableChildren().length === 0;
|
|
3929
4008
|
const deliverable = this.flowActivityTraversalSubprocess(
|
|
3930
4009
|
traversalResult.activity,
|
|
3931
|
-
|
|
4010
|
+
effectiveDirection === FlowSubprocessMode.FORWARD,
|
|
3932
4011
|
true,
|
|
3933
|
-
|
|
4012
|
+
effectiveDirection
|
|
3934
4013
|
);
|
|
3935
4014
|
if (deliverable) {
|
|
3936
4015
|
return new FlowSubprocessResult(deliverable, true, null, false);
|
|
3937
4016
|
}
|
|
3938
4017
|
candidateActivity = traversalResult.activity;
|
|
4018
|
+
currentDirection = effectiveDirection;
|
|
3939
4019
|
firstIteration = false;
|
|
3940
4020
|
}
|
|
3941
4021
|
return new FlowSubprocessResult(null, false, null, false);
|
|
@@ -3946,11 +4026,13 @@ class FlowTraversalService {
|
|
|
3946
4026
|
* @param {Activity} fromActivity - The activity to traverse from
|
|
3947
4027
|
* @param {FlowSubprocessMode} direction - The traversal direction
|
|
3948
4028
|
* @param {boolean} skipChildren - Whether to skip checking children
|
|
4029
|
+
* @param {Activity | null} forwardTraversalBoundary - Cluster boundary for an SB.2.1 forwardOnly direction reversal
|
|
3949
4030
|
* @return {FlowTreeTraversalResult} - The next activity and flags
|
|
4031
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - backward traversal into a forwardOnly cluster selects the first available child and reverses traversal direction to Forward.
|
|
3950
4032
|
*/
|
|
3951
|
-
flowTreeTraversalSubprocess(fromActivity, direction, skipChildren = false) {
|
|
4033
|
+
flowTreeTraversalSubprocess(fromActivity, direction, skipChildren = false, forwardTraversalBoundary = null) {
|
|
3952
4034
|
if (direction === FlowSubprocessMode.FORWARD) {
|
|
3953
|
-
return this.traverseForward(fromActivity, skipChildren);
|
|
4035
|
+
return this.traverseForward(fromActivity, skipChildren, forwardTraversalBoundary);
|
|
3954
4036
|
} else {
|
|
3955
4037
|
return this.traverseBackward(fromActivity);
|
|
3956
4038
|
}
|
|
@@ -3959,10 +4041,15 @@ class FlowTraversalService {
|
|
|
3959
4041
|
* Traverse forward in the activity tree
|
|
3960
4042
|
* @param {Activity} fromActivity - Starting activity
|
|
3961
4043
|
* @param {boolean} skipChildren - Whether to skip children
|
|
4044
|
+
* @param {Activity | null} forwardTraversalBoundary - Cluster boundary for an SB.2.1 forwardOnly direction reversal
|
|
3962
4045
|
* @return {FlowTreeTraversalResult}
|
|
4046
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - a reversed Forward traversal from a forwardOnly cluster remains within that cluster.
|
|
3963
4047
|
*/
|
|
3964
|
-
traverseForward(fromActivity, skipChildren) {
|
|
3965
|
-
if (
|
|
4048
|
+
traverseForward(fromActivity, skipChildren, forwardTraversalBoundary = null) {
|
|
4049
|
+
if (forwardTraversalBoundary && !this.isDescendantOfOrSelf(fromActivity, forwardTraversalBoundary)) {
|
|
4050
|
+
return { activity: null, endSequencingSession: false };
|
|
4051
|
+
}
|
|
4052
|
+
if (!forwardTraversalBoundary && skipChildren && this.isActivityLastOverall(fromActivity)) {
|
|
3966
4053
|
if (this.activityTree.root) {
|
|
3967
4054
|
this.terminateDescendentAttempts(this.activityTree.root);
|
|
3968
4055
|
}
|
|
@@ -3981,6 +4068,9 @@ class FlowTraversalService {
|
|
|
3981
4068
|
if (nextSibling) {
|
|
3982
4069
|
return { activity: nextSibling, endSequencingSession: false };
|
|
3983
4070
|
}
|
|
4071
|
+
if (forwardTraversalBoundary && (current === forwardTraversalBoundary || current.parent === forwardTraversalBoundary)) {
|
|
4072
|
+
return { activity: null, endSequencingSession: false };
|
|
4073
|
+
}
|
|
3984
4074
|
current = current.parent;
|
|
3985
4075
|
}
|
|
3986
4076
|
if (this.activityTree.root) {
|
|
@@ -3992,6 +4082,7 @@ class FlowTraversalService {
|
|
|
3992
4082
|
* Traverse backward in the activity tree
|
|
3993
4083
|
* @param {Activity} fromActivity - Starting activity
|
|
3994
4084
|
* @return {FlowTreeTraversalResult}
|
|
4085
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - backward traversal into a forwardOnly cluster selects the first available child and reverses traversal direction to Forward.
|
|
3995
4086
|
*/
|
|
3996
4087
|
traverseBackward(fromActivity) {
|
|
3997
4088
|
if (fromActivity.parent && fromActivity.parent.sequencingControls.forwardOnly) {
|
|
@@ -3999,10 +4090,7 @@ class FlowTraversalService {
|
|
|
3999
4090
|
}
|
|
4000
4091
|
const previousSibling = this.activityTree.getPreviousSibling(fromActivity);
|
|
4001
4092
|
if (previousSibling) {
|
|
4002
|
-
return
|
|
4003
|
-
activity: this.getLastDescendant(previousSibling),
|
|
4004
|
-
endSequencingSession: false
|
|
4005
|
-
};
|
|
4093
|
+
return this.getBackwardTraversalEntry(previousSibling);
|
|
4006
4094
|
}
|
|
4007
4095
|
let current = fromActivity;
|
|
4008
4096
|
let ancestorIterations = 0;
|
|
@@ -4013,38 +4101,64 @@ class FlowTraversalService {
|
|
|
4013
4101
|
}
|
|
4014
4102
|
const parentPreviousSibling = this.activityTree.getPreviousSibling(current.parent);
|
|
4015
4103
|
if (parentPreviousSibling) {
|
|
4016
|
-
return
|
|
4017
|
-
activity: this.getLastDescendant(parentPreviousSibling),
|
|
4018
|
-
endSequencingSession: false
|
|
4019
|
-
};
|
|
4104
|
+
return this.getBackwardTraversalEntry(parentPreviousSibling);
|
|
4020
4105
|
}
|
|
4021
4106
|
current = current.parent;
|
|
4022
4107
|
}
|
|
4023
4108
|
return { activity: null, endSequencingSession: false };
|
|
4024
4109
|
}
|
|
4025
4110
|
/**
|
|
4026
|
-
* Get the
|
|
4111
|
+
* Get the activity entered by backward traversal.
|
|
4027
4112
|
* @param {Activity} activity - The activity
|
|
4028
|
-
* @return {
|
|
4113
|
+
* @return {FlowTreeTraversalResult} - The entered activity and effective direction
|
|
4114
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - entering a forwardOnly cluster while moving Backward uses the first available child and changes direction to Forward.
|
|
4029
4115
|
*/
|
|
4030
|
-
|
|
4031
|
-
let
|
|
4116
|
+
getBackwardTraversalEntry(activity) {
|
|
4117
|
+
let enteredActivity = activity;
|
|
4032
4118
|
let iterations = 0;
|
|
4033
4119
|
const maxIterations = 1e4;
|
|
4034
4120
|
while (true) {
|
|
4035
4121
|
if (++iterations > maxIterations) {
|
|
4036
|
-
throw new Error("Infinite loop detected while getting
|
|
4122
|
+
throw new Error("Infinite loop detected while getting backward traversal entry");
|
|
4037
4123
|
}
|
|
4038
|
-
this.ensureSelectionAndRandomization(
|
|
4039
|
-
const children =
|
|
4124
|
+
this.ensureSelectionAndRandomization(enteredActivity);
|
|
4125
|
+
const children = enteredActivity.getAvailableChildren();
|
|
4040
4126
|
if (children.length === 0) {
|
|
4041
4127
|
break;
|
|
4042
4128
|
}
|
|
4129
|
+
if (enteredActivity.sequencingControls.forwardOnly) {
|
|
4130
|
+
return {
|
|
4131
|
+
activity: children[0] || null,
|
|
4132
|
+
endSequencingSession: false,
|
|
4133
|
+
direction: FlowSubprocessMode.FORWARD,
|
|
4134
|
+
forwardOnlyCluster: enteredActivity
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4043
4137
|
const lastChild = children[children.length - 1];
|
|
4044
4138
|
if (!lastChild) break;
|
|
4045
|
-
|
|
4139
|
+
enteredActivity = lastChild;
|
|
4140
|
+
}
|
|
4141
|
+
return {
|
|
4142
|
+
activity: enteredActivity,
|
|
4143
|
+
endSequencingSession: false
|
|
4144
|
+
};
|
|
4145
|
+
}
|
|
4146
|
+
/**
|
|
4147
|
+
* Check whether an activity is the same as or beneath an ancestor.
|
|
4148
|
+
* @param {Activity} activity - The activity to check
|
|
4149
|
+
* @param {Activity} ancestor - The expected ancestor
|
|
4150
|
+
* @return {boolean} - True when activity is within ancestor
|
|
4151
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - bounds Forward traversal after a forwardOnly direction reversal to the entered cluster.
|
|
4152
|
+
*/
|
|
4153
|
+
isDescendantOfOrSelf(activity, ancestor) {
|
|
4154
|
+
let current = activity;
|
|
4155
|
+
while (current) {
|
|
4156
|
+
if (current === ancestor) {
|
|
4157
|
+
return true;
|
|
4158
|
+
}
|
|
4159
|
+
current = current.parent;
|
|
4046
4160
|
}
|
|
4047
|
-
return
|
|
4161
|
+
return false;
|
|
4048
4162
|
}
|
|
4049
4163
|
/**
|
|
4050
4164
|
* Flow Activity Traversal Subprocess (SB.2.2)
|
|
@@ -5223,7 +5337,9 @@ class AsynchronousHttpService {
|
|
|
5223
5337
|
*/
|
|
5224
5338
|
async performBeacon(url, params) {
|
|
5225
5339
|
const { body, contentType } = this._prepareRequestBody(params);
|
|
5226
|
-
const
|
|
5340
|
+
const beaconContentType = Array.isArray(params) ? contentType : this.settings.terminationCommitContentType;
|
|
5341
|
+
this._warnIfBeaconContentTypeUnsafe(url, beaconContentType);
|
|
5342
|
+
const beaconSuccess = navigator.sendBeacon(url, new Blob([body], { type: beaconContentType }));
|
|
5227
5343
|
return Promise.resolve({
|
|
5228
5344
|
status: beaconSuccess ? 200 : 0,
|
|
5229
5345
|
ok: beaconSuccess,
|
|
@@ -5237,6 +5353,14 @@ class AsynchronousHttpService {
|
|
|
5237
5353
|
})
|
|
5238
5354
|
});
|
|
5239
5355
|
}
|
|
5356
|
+
_warnIfBeaconContentTypeUnsafe(url, contentType) {
|
|
5357
|
+
if (isCrossOriginUrl(url) && !isCorsSafelistedContentType(contentType)) {
|
|
5358
|
+
this.settings.onLogMessage?.(
|
|
5359
|
+
LogLevelEnum.WARN,
|
|
5360
|
+
`sendBeacon to cross-origin URL with non-CORS-safelisted Content-Type "${contentType}" may be silently dropped by the browser (Beacon cannot preflight). Use fetch (useAsynchronousCommits + asyncModeBeaconBehavior:"never") for cross-origin JSON/auth on terminate.`
|
|
5361
|
+
);
|
|
5362
|
+
}
|
|
5363
|
+
}
|
|
5240
5364
|
/**
|
|
5241
5365
|
* Transforms the response from the LMS to a ResultObject
|
|
5242
5366
|
* @param {Response} response - The response from the LMS
|
|
@@ -5904,14 +6028,14 @@ class ErrorHandlingService {
|
|
|
5904
6028
|
* @param {string} message - The error message
|
|
5905
6029
|
* @throws {ValidationError} - If throwException is true, throws a ValidationError
|
|
5906
6030
|
*/
|
|
5907
|
-
throwSCORMError(CMIElement, errorNumber, message) {
|
|
6031
|
+
throwSCORMError(CMIElement, errorNumber, message, messageLevel = LogLevelEnum.ERROR) {
|
|
5908
6032
|
this._lastDiagnostic = message || "";
|
|
5909
6033
|
if (!message) {
|
|
5910
6034
|
message = this._getLmsErrorMessageDetails(errorNumber, true);
|
|
5911
6035
|
}
|
|
5912
6036
|
const formattedMessage = `SCORM Error ${errorNumber}: ${message}${CMIElement ? ` [Element: ${CMIElement}]` : ""}`;
|
|
5913
|
-
this._apiLog("throwSCORMError", errorNumber + ": " + message,
|
|
5914
|
-
this._loggingService.
|
|
6037
|
+
this._apiLog("throwSCORMError", errorNumber + ": " + message, messageLevel, CMIElement);
|
|
6038
|
+
this._loggingService.log(messageLevel, formattedMessage);
|
|
5915
6039
|
this._lastErrorCode = String(errorNumber);
|
|
5916
6040
|
}
|
|
5917
6041
|
/**
|
|
@@ -6594,7 +6718,9 @@ const checkValidFormat = memoize(
|
|
|
6594
6718
|
(CMIElement, value, regexPattern, errorCode, _errorClass, allowEmptyString) => {
|
|
6595
6719
|
const valueKey = typeof value === "string" ? value : `[${typeof value}]`;
|
|
6596
6720
|
return `${CMIElement}:${valueKey}:${regexPattern}:${errorCode}:${allowEmptyString || false}`;
|
|
6597
|
-
}
|
|
6721
|
+
},
|
|
6722
|
+
// Normal capped CMI values and regexes fit within 2000 characters; large uncapped values bypass caching.
|
|
6723
|
+
{ maxEntries: 1e3, maxKeyLength: 2e3 }
|
|
6598
6724
|
);
|
|
6599
6725
|
const checkValidRange = memoize(
|
|
6600
6726
|
(CMIElement, value, rangePattern, errorCode, errorClass) => {
|
|
@@ -6617,7 +6743,8 @@ const checkValidRange = memoize(
|
|
|
6617
6743
|
},
|
|
6618
6744
|
// Custom key function that excludes the error class from the cache key
|
|
6619
6745
|
// since it can't be stringified and doesn't affect the validation result
|
|
6620
|
-
(CMIElement, value, rangePattern, errorCode, _errorClass) => `${CMIElement}:${value}:${rangePattern}:${errorCode}
|
|
6746
|
+
(CMIElement, value, rangePattern, errorCode, _errorClass) => `${CMIElement}:${value}:${rangePattern}:${errorCode}`,
|
|
6747
|
+
{ maxEntries: 1e3 }
|
|
6621
6748
|
);
|
|
6622
6749
|
|
|
6623
6750
|
function check2004ValidFormat(CMIElement, value, regexPattern, allowEmptyString) {
|
|
@@ -7126,6 +7253,12 @@ class ActivityObjective {
|
|
|
7126
7253
|
// objectives. It serves as a validity gate for other synced properties.
|
|
7127
7254
|
_measureStatus = false;
|
|
7128
7255
|
_normalizedMeasure = 0;
|
|
7256
|
+
_rawScore = "";
|
|
7257
|
+
_rawScoreKnown = false;
|
|
7258
|
+
_minScore = "";
|
|
7259
|
+
_minScoreKnown = false;
|
|
7260
|
+
_maxScore = "";
|
|
7261
|
+
_maxScoreKnown = false;
|
|
7129
7262
|
_progressMeasure = 0;
|
|
7130
7263
|
_progressMeasureStatus = false;
|
|
7131
7264
|
_completionStatus = CompletionStatus.UNKNOWN;
|
|
@@ -7135,6 +7268,9 @@ class ActivityObjective {
|
|
|
7135
7268
|
_normalizedMeasureDirty = false;
|
|
7136
7269
|
_completionStatusDirty = false;
|
|
7137
7270
|
_progressMeasureDirty = false;
|
|
7271
|
+
_rawScoreDirty = false;
|
|
7272
|
+
_minScoreDirty = false;
|
|
7273
|
+
_maxScoreDirty = false;
|
|
7138
7274
|
constructor(id, options = {}) {
|
|
7139
7275
|
this._id = id;
|
|
7140
7276
|
this._description = options.description ?? null;
|
|
@@ -7180,6 +7316,8 @@ class ActivityObjective {
|
|
|
7180
7316
|
if (this._satisfiedStatus !== value) {
|
|
7181
7317
|
this._satisfiedStatus = value;
|
|
7182
7318
|
this._satisfiedStatusDirty = true;
|
|
7319
|
+
this._satisfiedStatusKnown = true;
|
|
7320
|
+
this._progressStatus = true;
|
|
7183
7321
|
}
|
|
7184
7322
|
}
|
|
7185
7323
|
get satisfiedStatusKnown() {
|
|
@@ -7203,6 +7341,114 @@ class ActivityObjective {
|
|
|
7203
7341
|
this._normalizedMeasureDirty = true;
|
|
7204
7342
|
}
|
|
7205
7343
|
}
|
|
7344
|
+
/**
|
|
7345
|
+
* Return the known raw score value held for ADLSEQ objective score mapping.
|
|
7346
|
+
*
|
|
7347
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw score mapInfo
|
|
7348
|
+
*/
|
|
7349
|
+
get rawScore() {
|
|
7350
|
+
return this._rawScore;
|
|
7351
|
+
}
|
|
7352
|
+
/**
|
|
7353
|
+
* Store the RTE raw score associated with this objective.
|
|
7354
|
+
*
|
|
7355
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - raw score mapInfo
|
|
7356
|
+
*/
|
|
7357
|
+
set rawScore(value) {
|
|
7358
|
+
if (this._rawScore !== value) {
|
|
7359
|
+
this._rawScore = value;
|
|
7360
|
+
this._rawScoreDirty = true;
|
|
7361
|
+
}
|
|
7362
|
+
this._rawScoreKnown = value !== "";
|
|
7363
|
+
}
|
|
7364
|
+
/**
|
|
7365
|
+
* Return whether this objective's raw score is known.
|
|
7366
|
+
*
|
|
7367
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw score known state
|
|
7368
|
+
*/
|
|
7369
|
+
get rawScoreKnown() {
|
|
7370
|
+
return this._rawScoreKnown;
|
|
7371
|
+
}
|
|
7372
|
+
/**
|
|
7373
|
+
* Set whether this objective's raw score is known.
|
|
7374
|
+
*
|
|
7375
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw score known state
|
|
7376
|
+
*/
|
|
7377
|
+
set rawScoreKnown(value) {
|
|
7378
|
+
this._rawScoreKnown = value;
|
|
7379
|
+
}
|
|
7380
|
+
/**
|
|
7381
|
+
* Return the known minimum score value held for ADLSEQ objective score mapping.
|
|
7382
|
+
*
|
|
7383
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - min score mapInfo
|
|
7384
|
+
*/
|
|
7385
|
+
get minScore() {
|
|
7386
|
+
return this._minScore;
|
|
7387
|
+
}
|
|
7388
|
+
/**
|
|
7389
|
+
* Store the RTE minimum score associated with this objective.
|
|
7390
|
+
*
|
|
7391
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - min score mapInfo
|
|
7392
|
+
*/
|
|
7393
|
+
set minScore(value) {
|
|
7394
|
+
if (this._minScore !== value) {
|
|
7395
|
+
this._minScore = value;
|
|
7396
|
+
this._minScoreDirty = true;
|
|
7397
|
+
}
|
|
7398
|
+
this._minScoreKnown = value !== "";
|
|
7399
|
+
}
|
|
7400
|
+
/**
|
|
7401
|
+
* Return whether this objective's minimum score is known.
|
|
7402
|
+
*
|
|
7403
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - min score known state
|
|
7404
|
+
*/
|
|
7405
|
+
get minScoreKnown() {
|
|
7406
|
+
return this._minScoreKnown;
|
|
7407
|
+
}
|
|
7408
|
+
/**
|
|
7409
|
+
* Set whether this objective's minimum score is known.
|
|
7410
|
+
*
|
|
7411
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - min score known state
|
|
7412
|
+
*/
|
|
7413
|
+
set minScoreKnown(value) {
|
|
7414
|
+
this._minScoreKnown = value;
|
|
7415
|
+
}
|
|
7416
|
+
/**
|
|
7417
|
+
* Return the known maximum score value held for ADLSEQ objective score mapping.
|
|
7418
|
+
*
|
|
7419
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - max score mapInfo
|
|
7420
|
+
*/
|
|
7421
|
+
get maxScore() {
|
|
7422
|
+
return this._maxScore;
|
|
7423
|
+
}
|
|
7424
|
+
/**
|
|
7425
|
+
* Store the RTE maximum score associated with this objective.
|
|
7426
|
+
*
|
|
7427
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - max score mapInfo
|
|
7428
|
+
*/
|
|
7429
|
+
set maxScore(value) {
|
|
7430
|
+
if (this._maxScore !== value) {
|
|
7431
|
+
this._maxScore = value;
|
|
7432
|
+
this._maxScoreDirty = true;
|
|
7433
|
+
}
|
|
7434
|
+
this._maxScoreKnown = value !== "";
|
|
7435
|
+
}
|
|
7436
|
+
/**
|
|
7437
|
+
* Return whether this objective's maximum score is known.
|
|
7438
|
+
*
|
|
7439
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - max score known state
|
|
7440
|
+
*/
|
|
7441
|
+
get maxScoreKnown() {
|
|
7442
|
+
return this._maxScoreKnown;
|
|
7443
|
+
}
|
|
7444
|
+
/**
|
|
7445
|
+
* Set whether this objective's maximum score is known.
|
|
7446
|
+
*
|
|
7447
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - max score known state
|
|
7448
|
+
*/
|
|
7449
|
+
set maxScoreKnown(value) {
|
|
7450
|
+
this._maxScoreKnown = value;
|
|
7451
|
+
}
|
|
7206
7452
|
get progressMeasure() {
|
|
7207
7453
|
return this._progressMeasure;
|
|
7208
7454
|
}
|
|
@@ -7233,6 +7479,11 @@ class ActivityObjective {
|
|
|
7233
7479
|
set progressStatus(value) {
|
|
7234
7480
|
this._progressStatus = value;
|
|
7235
7481
|
}
|
|
7482
|
+
/**
|
|
7483
|
+
* Report whether a local objective field has changed since the last global write.
|
|
7484
|
+
*
|
|
7485
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - write maps use known local objective data
|
|
7486
|
+
*/
|
|
7236
7487
|
isDirty(property) {
|
|
7237
7488
|
switch (property) {
|
|
7238
7489
|
case "satisfiedStatus":
|
|
@@ -7243,8 +7494,19 @@ class ActivityObjective {
|
|
|
7243
7494
|
return this._completionStatusDirty;
|
|
7244
7495
|
case "progressMeasure":
|
|
7245
7496
|
return this._progressMeasureDirty;
|
|
7497
|
+
case "rawScore":
|
|
7498
|
+
return this._rawScoreDirty;
|
|
7499
|
+
case "minScore":
|
|
7500
|
+
return this._minScoreDirty;
|
|
7501
|
+
case "maxScore":
|
|
7502
|
+
return this._maxScoreDirty;
|
|
7246
7503
|
}
|
|
7247
7504
|
}
|
|
7505
|
+
/**
|
|
7506
|
+
* Clear a local objective dirty flag after a successful global write.
|
|
7507
|
+
*
|
|
7508
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - write maps update global objective state
|
|
7509
|
+
*/
|
|
7248
7510
|
clearDirty(property) {
|
|
7249
7511
|
switch (property) {
|
|
7250
7512
|
case "satisfiedStatus":
|
|
@@ -7259,13 +7521,31 @@ class ActivityObjective {
|
|
|
7259
7521
|
case "progressMeasure":
|
|
7260
7522
|
this._progressMeasureDirty = false;
|
|
7261
7523
|
break;
|
|
7524
|
+
case "rawScore":
|
|
7525
|
+
this._rawScoreDirty = false;
|
|
7526
|
+
break;
|
|
7527
|
+
case "minScore":
|
|
7528
|
+
this._minScoreDirty = false;
|
|
7529
|
+
break;
|
|
7530
|
+
case "maxScore":
|
|
7531
|
+
this._maxScoreDirty = false;
|
|
7532
|
+
break;
|
|
7262
7533
|
}
|
|
7263
7534
|
}
|
|
7535
|
+
/**
|
|
7536
|
+
* Clear all write-map dirty flags for this objective.
|
|
7537
|
+
*
|
|
7538
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - objective mapInfo writes are field-specific
|
|
7539
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score mapInfo writes are field-specific
|
|
7540
|
+
*/
|
|
7264
7541
|
clearAllDirty() {
|
|
7265
7542
|
this._satisfiedStatusDirty = false;
|
|
7266
7543
|
this._normalizedMeasureDirty = false;
|
|
7267
7544
|
this._completionStatusDirty = false;
|
|
7268
7545
|
this._progressMeasureDirty = false;
|
|
7546
|
+
this._rawScoreDirty = false;
|
|
7547
|
+
this._minScoreDirty = false;
|
|
7548
|
+
this._maxScoreDirty = false;
|
|
7269
7549
|
}
|
|
7270
7550
|
/**
|
|
7271
7551
|
* Initialize objective values from CMI data transfer
|
|
@@ -7275,6 +7555,8 @@ class ActivityObjective {
|
|
|
7275
7555
|
* @param satisfiedStatus - The satisfied status from CMI
|
|
7276
7556
|
* @param normalizedMeasure - The normalized measure from CMI
|
|
7277
7557
|
* @param measureStatus - Whether measure is valid
|
|
7558
|
+
*
|
|
7559
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - objective satisfaction and measure transfer
|
|
7278
7560
|
*/
|
|
7279
7561
|
initializeFromCMI(satisfiedStatus, normalizedMeasure, measureStatus) {
|
|
7280
7562
|
this._satisfiedStatus = satisfiedStatus;
|
|
@@ -7283,17 +7565,93 @@ class ActivityObjective {
|
|
|
7283
7565
|
this._normalizedMeasureDirty = true;
|
|
7284
7566
|
this._measureStatus = measureStatus;
|
|
7285
7567
|
}
|
|
7568
|
+
/**
|
|
7569
|
+
* Initialize raw/min/max objective score values from RTE data transfer.
|
|
7570
|
+
*
|
|
7571
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - objective score transfer
|
|
7572
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score mapInfo writes
|
|
7573
|
+
*/
|
|
7574
|
+
initializeScoreFromCMI(score) {
|
|
7575
|
+
if (score.rawScore !== void 0 && score.rawScore !== "") {
|
|
7576
|
+
this._rawScore = score.rawScore;
|
|
7577
|
+
this._rawScoreKnown = true;
|
|
7578
|
+
this._rawScoreDirty = true;
|
|
7579
|
+
}
|
|
7580
|
+
if (score.minScore !== void 0 && score.minScore !== "") {
|
|
7581
|
+
this._minScore = score.minScore;
|
|
7582
|
+
this._minScoreKnown = true;
|
|
7583
|
+
this._minScoreDirty = true;
|
|
7584
|
+
}
|
|
7585
|
+
if (score.maxScore !== void 0 && score.maxScore !== "") {
|
|
7586
|
+
this._maxScore = score.maxScore;
|
|
7587
|
+
this._maxScoreKnown = true;
|
|
7588
|
+
this._maxScoreDirty = true;
|
|
7589
|
+
}
|
|
7590
|
+
}
|
|
7591
|
+
/**
|
|
7592
|
+
* Apply read-mapped global objective state without marking the values dirty.
|
|
7593
|
+
*
|
|
7594
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps provide access to global objective state
|
|
7595
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score read maps are access-only
|
|
7596
|
+
*/
|
|
7597
|
+
applyReadMappedState(state) {
|
|
7598
|
+
if (state.satisfiedStatus !== void 0) {
|
|
7599
|
+
this._satisfiedStatus = state.satisfiedStatus;
|
|
7600
|
+
this._satisfiedStatusKnown = true;
|
|
7601
|
+
this._progressStatus = true;
|
|
7602
|
+
}
|
|
7603
|
+
if (state.normalizedMeasure !== void 0) {
|
|
7604
|
+
this._normalizedMeasure = state.normalizedMeasure;
|
|
7605
|
+
this._measureStatus = true;
|
|
7606
|
+
}
|
|
7607
|
+
if (state.completionStatus !== void 0) {
|
|
7608
|
+
this._completionStatus = state.completionStatus;
|
|
7609
|
+
}
|
|
7610
|
+
if (state.progressMeasure !== void 0) {
|
|
7611
|
+
this._progressMeasure = state.progressMeasure;
|
|
7612
|
+
this._progressMeasureStatus = true;
|
|
7613
|
+
}
|
|
7614
|
+
if (state.rawScore !== void 0) {
|
|
7615
|
+
this._rawScore = state.rawScore;
|
|
7616
|
+
this._rawScoreKnown = true;
|
|
7617
|
+
}
|
|
7618
|
+
if (state.minScore !== void 0) {
|
|
7619
|
+
this._minScore = state.minScore;
|
|
7620
|
+
this._minScoreKnown = true;
|
|
7621
|
+
}
|
|
7622
|
+
if (state.maxScore !== void 0) {
|
|
7623
|
+
this._maxScore = state.maxScore;
|
|
7624
|
+
this._maxScoreKnown = true;
|
|
7625
|
+
}
|
|
7626
|
+
}
|
|
7627
|
+
/**
|
|
7628
|
+
* Reset local objective state for a fresh activity attempt.
|
|
7629
|
+
*
|
|
7630
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - unknown objective state before tracking data exists
|
|
7631
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score map fields are unknown until transferred or read
|
|
7632
|
+
*/
|
|
7286
7633
|
resetState() {
|
|
7287
7634
|
this._satisfiedStatus = false;
|
|
7288
7635
|
this._satisfiedStatusKnown = false;
|
|
7289
7636
|
this._measureStatus = false;
|
|
7290
7637
|
this._normalizedMeasure = 0;
|
|
7638
|
+
this._rawScore = "";
|
|
7639
|
+
this._rawScoreKnown = false;
|
|
7640
|
+
this._minScore = "";
|
|
7641
|
+
this._minScoreKnown = false;
|
|
7642
|
+
this._maxScore = "";
|
|
7643
|
+
this._maxScoreKnown = false;
|
|
7291
7644
|
this._progressMeasure = 0;
|
|
7292
7645
|
this._progressMeasureStatus = false;
|
|
7293
7646
|
this._completionStatus = CompletionStatus.UNKNOWN;
|
|
7294
7647
|
this._progressStatus = false;
|
|
7295
7648
|
this.clearAllDirty();
|
|
7296
7649
|
}
|
|
7650
|
+
/**
|
|
7651
|
+
* Copy primary activity objective state back into the primary objective model.
|
|
7652
|
+
*
|
|
7653
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - primary objective state is available to sequencing
|
|
7654
|
+
*/
|
|
7297
7655
|
updateFromActivity(activity) {
|
|
7298
7656
|
if (this._satisfiedStatus !== activity.objectiveSatisfiedStatus) {
|
|
7299
7657
|
this._satisfiedStatus = activity.objectiveSatisfiedStatus;
|
|
@@ -7315,6 +7673,11 @@ class ActivityObjective {
|
|
|
7315
7673
|
this._completionStatusDirty = true;
|
|
7316
7674
|
}
|
|
7317
7675
|
}
|
|
7676
|
+
/**
|
|
7677
|
+
* Apply primary objective state to the owning activity for sequencing rules and rollup.
|
|
7678
|
+
*
|
|
7679
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - primary objective contributes activity state
|
|
7680
|
+
*/
|
|
7318
7681
|
applyToActivity(activity) {
|
|
7319
7682
|
if (!this._isPrimary) {
|
|
7320
7683
|
return;
|
|
@@ -7325,7 +7688,8 @@ class ActivityObjective {
|
|
|
7325
7688
|
this._normalizedMeasure,
|
|
7326
7689
|
this._progressMeasure,
|
|
7327
7690
|
this._progressMeasureStatus,
|
|
7328
|
-
this._completionStatus
|
|
7691
|
+
this._completionStatus,
|
|
7692
|
+
this._progressStatus || this._satisfiedStatusKnown
|
|
7329
7693
|
);
|
|
7330
7694
|
}
|
|
7331
7695
|
}
|
|
@@ -8395,7 +8759,7 @@ class Activity extends BaseCMI {
|
|
|
8395
8759
|
/**
|
|
8396
8760
|
* Setter for primary objective
|
|
8397
8761
|
* @param {ActivityObjective | null} objective
|
|
8398
|
-
|
|
8762
|
+
*/
|
|
8399
8763
|
set primaryObjective(objective) {
|
|
8400
8764
|
this._primaryObjective = objective;
|
|
8401
8765
|
if (this._primaryObjective) {
|
|
@@ -8417,7 +8781,7 @@ class Activity extends BaseCMI {
|
|
|
8417
8781
|
/**
|
|
8418
8782
|
* Replace objectives collection
|
|
8419
8783
|
* @param {ActivityObjective[]} objectives
|
|
8420
|
-
|
|
8784
|
+
*/
|
|
8421
8785
|
set objectives(objectives) {
|
|
8422
8786
|
this._objectives = [...objectives];
|
|
8423
8787
|
this.syncPrimaryObjectiveCollection();
|
|
@@ -8510,12 +8874,12 @@ class Activity extends BaseCMI {
|
|
|
8510
8874
|
this._objectiveNormalizedMeasureDirty = false;
|
|
8511
8875
|
this._objectiveMeasureStatusDirty = false;
|
|
8512
8876
|
}
|
|
8513
|
-
setPrimaryObjectiveState(satisfiedStatus, measureStatus, normalizedMeasure, progressMeasure, progressMeasureStatus, completionStatus) {
|
|
8877
|
+
setPrimaryObjectiveState(satisfiedStatus, measureStatus, normalizedMeasure, progressMeasure, progressMeasureStatus, completionStatus, objectiveProgressStatus = true) {
|
|
8514
8878
|
if (this._objectiveSatisfiedStatus !== satisfiedStatus) {
|
|
8515
8879
|
this._objectiveSatisfiedStatus = satisfiedStatus;
|
|
8516
8880
|
this._objectiveSatisfiedStatusDirty = true;
|
|
8517
8881
|
}
|
|
8518
|
-
this._objectiveSatisfiedStatusKnown =
|
|
8882
|
+
this._objectiveSatisfiedStatusKnown = objectiveProgressStatus;
|
|
8519
8883
|
if (this._objectiveMeasureStatus !== measureStatus) {
|
|
8520
8884
|
this._objectiveMeasureStatus = measureStatus;
|
|
8521
8885
|
this._objectiveMeasureStatusDirty = true;
|
|
@@ -8534,14 +8898,28 @@ class Activity extends BaseCMI {
|
|
|
8534
8898
|
this._primaryObjective.progressMeasure = progressMeasure;
|
|
8535
8899
|
this._primaryObjective.progressMeasureStatus = progressMeasureStatus;
|
|
8536
8900
|
this._primaryObjective.completionStatus = completionStatus;
|
|
8901
|
+
this._primaryObjective.satisfiedStatusKnown = objectiveProgressStatus;
|
|
8902
|
+
this._primaryObjective.progressStatus = objectiveProgressStatus;
|
|
8537
8903
|
}
|
|
8538
8904
|
}
|
|
8905
|
+
/**
|
|
8906
|
+
* Snapshot objective state for sequencing persistence.
|
|
8907
|
+
*
|
|
8908
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - objective state persists across attempts
|
|
8909
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score-map state persists with objective state
|
|
8910
|
+
*/
|
|
8539
8911
|
getObjectiveStateSnapshot() {
|
|
8540
8912
|
const primarySnapshot = this._primaryObjective ? {
|
|
8541
8913
|
id: this._primaryObjective.id,
|
|
8542
8914
|
satisfiedStatus: this.objectiveSatisfiedStatus,
|
|
8543
8915
|
measureStatus: this.objectiveMeasureStatus,
|
|
8544
8916
|
normalizedMeasure: this.objectiveNormalizedMeasure,
|
|
8917
|
+
rawScore: this._primaryObjective.rawScore,
|
|
8918
|
+
rawScoreKnown: this._primaryObjective.rawScoreKnown,
|
|
8919
|
+
minScore: this._primaryObjective.minScore,
|
|
8920
|
+
minScoreKnown: this._primaryObjective.minScoreKnown,
|
|
8921
|
+
maxScore: this._primaryObjective.maxScore,
|
|
8922
|
+
maxScoreKnown: this._primaryObjective.maxScoreKnown,
|
|
8545
8923
|
progressMeasure: this.progressMeasure ?? 0,
|
|
8546
8924
|
progressMeasureStatus: this.progressMeasureStatus,
|
|
8547
8925
|
progressStatus: this._primaryObjective.progressStatus,
|
|
@@ -8554,6 +8932,12 @@ class Activity extends BaseCMI {
|
|
|
8554
8932
|
satisfiedStatus: objective.satisfiedStatus,
|
|
8555
8933
|
measureStatus: objective.measureStatus,
|
|
8556
8934
|
normalizedMeasure: objective.normalizedMeasure,
|
|
8935
|
+
rawScore: objective.rawScore,
|
|
8936
|
+
rawScoreKnown: objective.rawScoreKnown,
|
|
8937
|
+
minScore: objective.minScore,
|
|
8938
|
+
minScoreKnown: objective.minScoreKnown,
|
|
8939
|
+
maxScore: objective.maxScore,
|
|
8940
|
+
maxScoreKnown: objective.maxScoreKnown,
|
|
8557
8941
|
progressMeasure: objective.progressMeasure,
|
|
8558
8942
|
progressMeasureStatus: objective.progressMeasureStatus,
|
|
8559
8943
|
progressStatus: objective.progressStatus,
|
|
@@ -8566,6 +8950,12 @@ class Activity extends BaseCMI {
|
|
|
8566
8950
|
objectives: additionalSnapshots
|
|
8567
8951
|
};
|
|
8568
8952
|
}
|
|
8953
|
+
/**
|
|
8954
|
+
* Restore objective state from a sequencing persistence snapshot.
|
|
8955
|
+
*
|
|
8956
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - persisted objective state restores sequencing state
|
|
8957
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - persisted score-map state restores objective score fields
|
|
8958
|
+
*/
|
|
8569
8959
|
applyObjectiveStateSnapshot(snapshot) {
|
|
8570
8960
|
if (snapshot.primary) {
|
|
8571
8961
|
const primary = this.getObjectiveById(snapshot.primary.id);
|
|
@@ -8579,8 +8969,10 @@ class Activity extends BaseCMI {
|
|
|
8579
8969
|
state.normalizedMeasure,
|
|
8580
8970
|
state.progressMeasure,
|
|
8581
8971
|
state.progressMeasureStatus,
|
|
8582
|
-
state.completionStatus
|
|
8972
|
+
state.completionStatus,
|
|
8973
|
+
state.progressStatus
|
|
8583
8974
|
);
|
|
8975
|
+
this.applyObjectiveScoreSnapshot(primary.objective, state);
|
|
8584
8976
|
}
|
|
8585
8977
|
}
|
|
8586
8978
|
for (const state of snapshot.objectives) {
|
|
@@ -8593,11 +8985,30 @@ class Activity extends BaseCMI {
|
|
|
8593
8985
|
objective.progressMeasure = state.progressMeasure;
|
|
8594
8986
|
objective.progressMeasureStatus = state.progressMeasureStatus;
|
|
8595
8987
|
objective.completionStatus = state.completionStatus;
|
|
8988
|
+
this.applyObjectiveScoreSnapshot(objective, state);
|
|
8596
8989
|
objective.satisfiedByMeasure = state.satisfiedByMeasure ?? objective.satisfiedByMeasure;
|
|
8597
8990
|
objective.minNormalizedMeasure = state.minNormalizedMeasure !== void 0 ? state.minNormalizedMeasure : objective.minNormalizedMeasure;
|
|
8598
8991
|
}
|
|
8599
8992
|
}
|
|
8600
8993
|
}
|
|
8994
|
+
/**
|
|
8995
|
+
* Restore known raw/min/max score fields from an objective state snapshot.
|
|
8996
|
+
*
|
|
8997
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score known flags restore independently
|
|
8998
|
+
*/
|
|
8999
|
+
applyObjectiveScoreSnapshot(objective, state) {
|
|
9000
|
+
const scoreState = {};
|
|
9001
|
+
if (state.rawScoreKnown) {
|
|
9002
|
+
scoreState.rawScore = state.rawScore;
|
|
9003
|
+
}
|
|
9004
|
+
if (state.minScoreKnown) {
|
|
9005
|
+
scoreState.minScore = state.minScore;
|
|
9006
|
+
}
|
|
9007
|
+
if (state.maxScoreKnown) {
|
|
9008
|
+
scoreState.maxScore = state.maxScore;
|
|
9009
|
+
}
|
|
9010
|
+
objective.applyReadMappedState(scoreState);
|
|
9011
|
+
}
|
|
8601
9012
|
/**
|
|
8602
9013
|
* Get available children with selection and randomization applied
|
|
8603
9014
|
* @return {Activity[]}
|
|
@@ -9988,11 +10399,18 @@ class GlobalObjectiveSynchronizer {
|
|
|
9988
10399
|
*
|
|
9989
10400
|
* @param activity - The activity to process
|
|
9990
10401
|
* @param globalObjectives - Global objective map
|
|
10402
|
+
*
|
|
10403
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - write mapInfo transfers local objective state to global objectives
|
|
10404
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score write maps
|
|
9991
10405
|
*/
|
|
9992
10406
|
syncGlobalObjectivesWritePhase(activity, globalObjectives) {
|
|
10407
|
+
if (!this.canWriteGlobalObjectives(activity)) {
|
|
10408
|
+
return;
|
|
10409
|
+
}
|
|
9993
10410
|
const objectives = activity.getAllObjectives();
|
|
9994
10411
|
for (const objective of objectives) {
|
|
9995
10412
|
const mapInfos = objective.mapInfo.length > 0 ? objective.mapInfo : [this.createDefaultMapInfo(objective)];
|
|
10413
|
+
const dirtyFieldsToClear = /* @__PURE__ */ new Set();
|
|
9996
10414
|
for (const mapInfo of mapInfos) {
|
|
9997
10415
|
const targetId = mapInfo.targetObjectiveID || objective.id;
|
|
9998
10416
|
const globalObjective = this.ensureGlobalObjectiveEntry(
|
|
@@ -10000,36 +10418,54 @@ class GlobalObjectiveSynchronizer {
|
|
|
10000
10418
|
targetId,
|
|
10001
10419
|
objective
|
|
10002
10420
|
);
|
|
10003
|
-
if (mapInfo.writeSatisfiedStatus && objective
|
|
10421
|
+
if (mapInfo.writeSatisfiedStatus && this.hasKnownSatisfiedStatus(objective) && objective.isDirty("satisfiedStatus")) {
|
|
10004
10422
|
globalObjective.satisfiedStatus = objective.satisfiedStatus;
|
|
10005
10423
|
globalObjective.satisfiedStatusKnown = true;
|
|
10006
|
-
|
|
10424
|
+
dirtyFieldsToClear.add("satisfiedStatus");
|
|
10007
10425
|
}
|
|
10008
10426
|
if (mapInfo.writeNormalizedMeasure && objective.measureStatus && objective.isDirty("normalizedMeasure")) {
|
|
10009
10427
|
globalObjective.normalizedMeasure = objective.normalizedMeasure;
|
|
10010
10428
|
globalObjective.normalizedMeasureKnown = true;
|
|
10011
|
-
|
|
10012
|
-
if (
|
|
10429
|
+
dirtyFieldsToClear.add("normalizedMeasure");
|
|
10430
|
+
if (mapInfo.writeSatisfiedStatus && objective.satisfiedByMeasure) {
|
|
10013
10431
|
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
10014
10432
|
globalObjective.satisfiedStatus = objective.normalizedMeasure >= threshold;
|
|
10015
10433
|
globalObjective.satisfiedStatusKnown = true;
|
|
10016
|
-
|
|
10434
|
+
dirtyFieldsToClear.add("satisfiedStatus");
|
|
10017
10435
|
}
|
|
10018
10436
|
}
|
|
10019
10437
|
if (mapInfo.writeCompletionStatus && objective.completionStatus !== CompletionStatus.UNKNOWN && objective.isDirty("completionStatus")) {
|
|
10020
10438
|
globalObjective.completionStatus = objective.completionStatus;
|
|
10021
10439
|
globalObjective.completionStatusKnown = true;
|
|
10022
|
-
|
|
10440
|
+
dirtyFieldsToClear.add("completionStatus");
|
|
10441
|
+
}
|
|
10442
|
+
if (mapInfo.writeRawScore && objective.rawScoreKnown && objective.isDirty("rawScore")) {
|
|
10443
|
+
globalObjective.rawScore = objective.rawScore;
|
|
10444
|
+
globalObjective.rawScoreKnown = true;
|
|
10445
|
+
dirtyFieldsToClear.add("rawScore");
|
|
10446
|
+
}
|
|
10447
|
+
if (mapInfo.writeMinScore && objective.minScoreKnown && objective.isDirty("minScore")) {
|
|
10448
|
+
globalObjective.minScore = objective.minScore;
|
|
10449
|
+
globalObjective.minScoreKnown = true;
|
|
10450
|
+
dirtyFieldsToClear.add("minScore");
|
|
10451
|
+
}
|
|
10452
|
+
if (mapInfo.writeMaxScore && objective.maxScoreKnown && objective.isDirty("maxScore")) {
|
|
10453
|
+
globalObjective.maxScore = objective.maxScore;
|
|
10454
|
+
globalObjective.maxScoreKnown = true;
|
|
10455
|
+
dirtyFieldsToClear.add("maxScore");
|
|
10023
10456
|
}
|
|
10024
10457
|
if (mapInfo.writeProgressMeasure && objective.progressMeasureStatus && objective.isDirty("progressMeasure")) {
|
|
10025
10458
|
globalObjective.progressMeasure = objective.progressMeasure;
|
|
10026
10459
|
globalObjective.progressMeasureKnown = true;
|
|
10027
|
-
|
|
10460
|
+
dirtyFieldsToClear.add("progressMeasure");
|
|
10028
10461
|
}
|
|
10029
10462
|
if (mapInfo.updateAttemptData) {
|
|
10030
10463
|
this.updateActivityAttemptData(activity, globalObjective, objective);
|
|
10031
10464
|
}
|
|
10032
10465
|
}
|
|
10466
|
+
for (const property of dirtyFieldsToClear) {
|
|
10467
|
+
objective.clearDirty(property);
|
|
10468
|
+
}
|
|
10033
10469
|
}
|
|
10034
10470
|
}
|
|
10035
10471
|
/**
|
|
@@ -10037,6 +10473,9 @@ class GlobalObjectiveSynchronizer {
|
|
|
10037
10473
|
*
|
|
10038
10474
|
* @param activity - The activity to process
|
|
10039
10475
|
* @param globalObjectives - Global objective map
|
|
10476
|
+
*
|
|
10477
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read mapInfo transfers global objective state into the local view
|
|
10478
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps
|
|
10040
10479
|
*/
|
|
10041
10480
|
syncGlobalObjectivesReadPhase(activity, globalObjectives) {
|
|
10042
10481
|
const objectives = activity.getAllObjectives();
|
|
@@ -10047,25 +10486,13 @@ class GlobalObjectiveSynchronizer {
|
|
|
10047
10486
|
const globalObjective = globalObjectives.get(targetId);
|
|
10048
10487
|
if (!globalObjective) continue;
|
|
10049
10488
|
const isPrimary = objective.isPrimary;
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
objective
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
if (globalObjective.satisfiedByMeasure || objective.satisfiedByMeasure) {
|
|
10058
|
-
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
10059
|
-
objective.satisfiedStatus = globalObjective.normalizedMeasure >= threshold;
|
|
10060
|
-
}
|
|
10061
|
-
}
|
|
10062
|
-
if (mapInfo.readProgressMeasure && globalObjective.progressMeasureKnown) {
|
|
10063
|
-
objective.progressMeasure = globalObjective.progressMeasure;
|
|
10064
|
-
objective.progressMeasureStatus = true;
|
|
10065
|
-
}
|
|
10066
|
-
if (mapInfo.readCompletionStatus && globalObjective.completionStatusKnown) {
|
|
10067
|
-
objective.completionStatus = globalObjective.completionStatus;
|
|
10068
|
-
}
|
|
10489
|
+
const readState = GlobalObjectiveSynchronizer.getGlobalObjectiveReadState(
|
|
10490
|
+
activity,
|
|
10491
|
+
objective,
|
|
10492
|
+
mapInfo,
|
|
10493
|
+
globalObjective
|
|
10494
|
+
);
|
|
10495
|
+
this.applyGlobalObjectiveReadState(objective, readState);
|
|
10069
10496
|
if (isPrimary) {
|
|
10070
10497
|
objective.applyToActivity(activity);
|
|
10071
10498
|
}
|
|
@@ -10108,56 +10535,61 @@ class GlobalObjectiveSynchronizer {
|
|
|
10108
10535
|
* @param objective - The objective to sync
|
|
10109
10536
|
* @param mapInfo - Map info for this objective
|
|
10110
10537
|
* @param globalObjective - The global objective
|
|
10538
|
+
*
|
|
10539
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - objective mapInfo read/write synchronization
|
|
10540
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score mapInfo synchronization
|
|
10111
10541
|
*/
|
|
10112
10542
|
syncObjectiveState(activity, objective, mapInfo, globalObjective) {
|
|
10113
10543
|
try {
|
|
10114
10544
|
const isPrimary = objective.isPrimary;
|
|
10115
10545
|
const localObjective = this.getLocalObjectiveState(activity, objective, isPrimary);
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
objective
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
if (globalObjective.satisfiedByMeasure || objective.satisfiedByMeasure) {
|
|
10124
|
-
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
10125
|
-
objective.satisfiedStatus = globalObjective.normalizedMeasure >= threshold;
|
|
10126
|
-
}
|
|
10127
|
-
}
|
|
10128
|
-
if (mapInfo.readProgressMeasure && globalObjective.progressMeasureKnown) {
|
|
10129
|
-
objective.progressMeasure = globalObjective.progressMeasure;
|
|
10130
|
-
objective.progressMeasureStatus = true;
|
|
10131
|
-
}
|
|
10132
|
-
if (mapInfo.readCompletionStatus && globalObjective.completionStatusKnown) {
|
|
10133
|
-
objective.completionStatus = globalObjective.completionStatus;
|
|
10134
|
-
}
|
|
10546
|
+
const readState = GlobalObjectiveSynchronizer.getGlobalObjectiveReadState(
|
|
10547
|
+
activity,
|
|
10548
|
+
objective,
|
|
10549
|
+
mapInfo,
|
|
10550
|
+
globalObjective
|
|
10551
|
+
);
|
|
10552
|
+
this.applyGlobalObjectiveReadState(objective, readState);
|
|
10135
10553
|
if (objective.isPrimary) {
|
|
10136
10554
|
objective.applyToActivity(activity);
|
|
10137
10555
|
}
|
|
10138
|
-
if (
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
}
|
|
10142
|
-
if (mapInfo.writeNormalizedMeasure && objective.measureStatus) {
|
|
10143
|
-
globalObjective.normalizedMeasure = objective.normalizedMeasure;
|
|
10144
|
-
globalObjective.normalizedMeasureKnown = true;
|
|
10145
|
-
if (globalObjective.satisfiedByMeasure || objective.satisfiedByMeasure) {
|
|
10146
|
-
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
10147
|
-
globalObjective.satisfiedStatus = objective.normalizedMeasure >= threshold;
|
|
10556
|
+
if (this.canWriteGlobalObjectives(activity)) {
|
|
10557
|
+
if (mapInfo.writeSatisfiedStatus && this.hasKnownSatisfiedStatus(objective)) {
|
|
10558
|
+
globalObjective.satisfiedStatus = objective.satisfiedStatus;
|
|
10148
10559
|
globalObjective.satisfiedStatusKnown = true;
|
|
10149
10560
|
}
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10561
|
+
if (mapInfo.writeNormalizedMeasure && objective.measureStatus) {
|
|
10562
|
+
globalObjective.normalizedMeasure = objective.normalizedMeasure;
|
|
10563
|
+
globalObjective.normalizedMeasureKnown = true;
|
|
10564
|
+
if (mapInfo.writeSatisfiedStatus && objective.satisfiedByMeasure) {
|
|
10565
|
+
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
10566
|
+
globalObjective.satisfiedStatus = objective.normalizedMeasure >= threshold;
|
|
10567
|
+
globalObjective.satisfiedStatusKnown = true;
|
|
10568
|
+
}
|
|
10569
|
+
}
|
|
10570
|
+
if (mapInfo.writeCompletionStatus && objective.completionStatus !== CompletionStatus.UNKNOWN) {
|
|
10571
|
+
globalObjective.completionStatus = objective.completionStatus;
|
|
10572
|
+
globalObjective.completionStatusKnown = true;
|
|
10573
|
+
}
|
|
10574
|
+
if (mapInfo.writeRawScore && objective.rawScoreKnown) {
|
|
10575
|
+
globalObjective.rawScore = objective.rawScore;
|
|
10576
|
+
globalObjective.rawScoreKnown = true;
|
|
10577
|
+
}
|
|
10578
|
+
if (mapInfo.writeMinScore && objective.minScoreKnown) {
|
|
10579
|
+
globalObjective.minScore = objective.minScore;
|
|
10580
|
+
globalObjective.minScoreKnown = true;
|
|
10581
|
+
}
|
|
10582
|
+
if (mapInfo.writeMaxScore && objective.maxScoreKnown) {
|
|
10583
|
+
globalObjective.maxScore = objective.maxScore;
|
|
10584
|
+
globalObjective.maxScoreKnown = true;
|
|
10585
|
+
}
|
|
10586
|
+
if (mapInfo.writeProgressMeasure && objective.progressMeasureStatus) {
|
|
10587
|
+
globalObjective.progressMeasure = objective.progressMeasure;
|
|
10588
|
+
globalObjective.progressMeasureKnown = true;
|
|
10589
|
+
}
|
|
10590
|
+
if (mapInfo.updateAttemptData) {
|
|
10591
|
+
this.updateActivityAttemptData(activity, globalObjective, objective);
|
|
10592
|
+
}
|
|
10161
10593
|
}
|
|
10162
10594
|
this.eventCallback?.("objective_synchronized", {
|
|
10163
10595
|
activityId: activity.id,
|
|
@@ -10175,6 +10607,50 @@ class GlobalObjectiveSynchronizer {
|
|
|
10175
10607
|
});
|
|
10176
10608
|
}
|
|
10177
10609
|
}
|
|
10610
|
+
/**
|
|
10611
|
+
* Project a global objective through one local objective's read mapInfo.
|
|
10612
|
+
*
|
|
10613
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps provide access to mapped global objective fields
|
|
10614
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps are independent fields
|
|
10615
|
+
*/
|
|
10616
|
+
static getGlobalObjectiveReadState(activity, objective, mapInfo, globalObjective) {
|
|
10617
|
+
const readState = {};
|
|
10618
|
+
if (mapInfo.readSatisfiedStatus && globalObjective.satisfiedStatusKnown) {
|
|
10619
|
+
readState.satisfiedStatus = globalObjective.satisfiedStatus;
|
|
10620
|
+
}
|
|
10621
|
+
if (mapInfo.readNormalizedMeasure && globalObjective.normalizedMeasureKnown) {
|
|
10622
|
+
readState.normalizedMeasure = globalObjective.normalizedMeasure;
|
|
10623
|
+
if (objective.satisfiedByMeasure) {
|
|
10624
|
+
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
10625
|
+
readState.satisfiedStatus = globalObjective.normalizedMeasure >= threshold;
|
|
10626
|
+
}
|
|
10627
|
+
}
|
|
10628
|
+
if (mapInfo.readCompletionStatus && globalObjective.completionStatusKnown) {
|
|
10629
|
+
readState.completionStatus = globalObjective.completionStatus;
|
|
10630
|
+
}
|
|
10631
|
+
if (mapInfo.readProgressMeasure && globalObjective.progressMeasureKnown) {
|
|
10632
|
+
readState.progressMeasure = globalObjective.progressMeasure;
|
|
10633
|
+
}
|
|
10634
|
+
if (mapInfo.readRawScore && globalObjective.rawScoreKnown) {
|
|
10635
|
+
readState.rawScore = globalObjective.rawScore;
|
|
10636
|
+
}
|
|
10637
|
+
if (mapInfo.readMinScore && globalObjective.minScoreKnown) {
|
|
10638
|
+
readState.minScore = globalObjective.minScore;
|
|
10639
|
+
}
|
|
10640
|
+
if (mapInfo.readMaxScore && globalObjective.maxScoreKnown) {
|
|
10641
|
+
readState.maxScore = globalObjective.maxScore;
|
|
10642
|
+
}
|
|
10643
|
+
return readState;
|
|
10644
|
+
}
|
|
10645
|
+
/**
|
|
10646
|
+
* Apply read-mapped state to an objective without marking those fields dirty.
|
|
10647
|
+
*
|
|
10648
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps are access to global state, not local writes
|
|
10649
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score read maps do not imply score writes
|
|
10650
|
+
*/
|
|
10651
|
+
applyGlobalObjectiveReadState(objective, readState) {
|
|
10652
|
+
objective.applyReadMappedState(readState);
|
|
10653
|
+
}
|
|
10178
10654
|
/**
|
|
10179
10655
|
* Ensure global objective entry exists
|
|
10180
10656
|
*
|
|
@@ -10182,19 +10658,30 @@ class GlobalObjectiveSynchronizer {
|
|
|
10182
10658
|
* @param targetId - Target objective ID
|
|
10183
10659
|
* @param objective - Source objective
|
|
10184
10660
|
* @returns The global objective entry
|
|
10661
|
+
*
|
|
10662
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective entries hold mapped objective state
|
|
10663
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - global entries hold score-map state
|
|
10185
10664
|
*/
|
|
10186
10665
|
ensureGlobalObjectiveEntry(globalObjectives, targetId, objective) {
|
|
10187
10666
|
if (!globalObjectives.has(targetId)) {
|
|
10188
10667
|
globalObjectives.set(targetId, {
|
|
10189
10668
|
id: targetId,
|
|
10190
|
-
satisfiedStatus:
|
|
10191
|
-
satisfiedStatusKnown:
|
|
10192
|
-
normalizedMeasure:
|
|
10193
|
-
normalizedMeasureKnown:
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10669
|
+
satisfiedStatus: false,
|
|
10670
|
+
satisfiedStatusKnown: false,
|
|
10671
|
+
normalizedMeasure: 0,
|
|
10672
|
+
normalizedMeasureKnown: false,
|
|
10673
|
+
// @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score fields
|
|
10674
|
+
// remain unknown until their corresponding write map explicitly writes them.
|
|
10675
|
+
rawScore: "",
|
|
10676
|
+
rawScoreKnown: false,
|
|
10677
|
+
minScore: "",
|
|
10678
|
+
minScoreKnown: false,
|
|
10679
|
+
maxScore: "",
|
|
10680
|
+
maxScoreKnown: false,
|
|
10681
|
+
progressMeasure: 0,
|
|
10682
|
+
progressMeasureKnown: false,
|
|
10683
|
+
completionStatus: CompletionStatus.UNKNOWN,
|
|
10684
|
+
completionStatusKnown: false,
|
|
10198
10685
|
satisfiedByMeasure: objective.satisfiedByMeasure,
|
|
10199
10686
|
minNormalizedMeasure: objective.minNormalizedMeasure
|
|
10200
10687
|
});
|
|
@@ -10209,6 +10696,9 @@ class GlobalObjectiveSynchronizer {
|
|
|
10209
10696
|
*
|
|
10210
10697
|
* @param objective - The objective to create default map info for
|
|
10211
10698
|
* @returns Default map info
|
|
10699
|
+
*
|
|
10700
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - mapInfo defaults are applied before objective synchronization
|
|
10701
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score maps require explicit read/write flags
|
|
10212
10702
|
*/
|
|
10213
10703
|
createDefaultMapInfo(objective) {
|
|
10214
10704
|
return {
|
|
@@ -10221,9 +10711,34 @@ class GlobalObjectiveSynchronizer {
|
|
|
10221
10711
|
writeCompletionStatus: true,
|
|
10222
10712
|
readProgressMeasure: false,
|
|
10223
10713
|
writeProgressMeasure: true,
|
|
10714
|
+
readRawScore: false,
|
|
10715
|
+
writeRawScore: false,
|
|
10716
|
+
readMinScore: false,
|
|
10717
|
+
writeMinScore: false,
|
|
10718
|
+
readMaxScore: false,
|
|
10719
|
+
writeMaxScore: false,
|
|
10224
10720
|
updateAttemptData: objective.isPrimary
|
|
10225
10721
|
};
|
|
10226
10722
|
}
|
|
10723
|
+
/**
|
|
10724
|
+
* Return whether the local objective has known satisfaction state to write.
|
|
10725
|
+
*
|
|
10726
|
+
* @spec SCORM 2004 4th Ed. SN 4.2.1 Tracking Model - Objective Progress
|
|
10727
|
+
* Status identifies whether Objective Satisfied Status is known; Objective
|
|
10728
|
+
* Measure Status is independent measure knowledge.
|
|
10729
|
+
*/
|
|
10730
|
+
hasKnownSatisfiedStatus(objective) {
|
|
10731
|
+
return objective.progressStatus || objective.satisfiedStatusKnown;
|
|
10732
|
+
}
|
|
10733
|
+
/**
|
|
10734
|
+
* Return whether this activity is allowed to write tracked state to globals.
|
|
10735
|
+
*
|
|
10736
|
+
* @spec SCORM 2004 4th Ed. SN 3.13.1 Tracked - when False, the LMS
|
|
10737
|
+
* "does not initialize, manage or access any tracking status information".
|
|
10738
|
+
*/
|
|
10739
|
+
canWriteGlobalObjectives(activity) {
|
|
10740
|
+
return activity.sequencingControls.tracked !== false;
|
|
10741
|
+
}
|
|
10227
10742
|
/**
|
|
10228
10743
|
* Get local objective state
|
|
10229
10744
|
*
|
|
@@ -10231,6 +10746,9 @@ class GlobalObjectiveSynchronizer {
|
|
|
10231
10746
|
* @param objective - The objective
|
|
10232
10747
|
* @param isPrimary - Whether this is the primary objective
|
|
10233
10748
|
* @returns Local objective state
|
|
10749
|
+
*
|
|
10750
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - local objective state used for synchronization
|
|
10751
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - local score fields are part of mapped objective state
|
|
10234
10752
|
*/
|
|
10235
10753
|
getLocalObjectiveState(activity, objective, isPrimary) {
|
|
10236
10754
|
if (isPrimary) {
|
|
@@ -10239,6 +10757,12 @@ class GlobalObjectiveSynchronizer {
|
|
|
10239
10757
|
satisfiedStatus: activity.objectiveSatisfiedStatus,
|
|
10240
10758
|
measureStatus: activity.objectiveMeasureStatus,
|
|
10241
10759
|
normalizedMeasure: activity.objectiveNormalizedMeasure,
|
|
10760
|
+
rawScore: objective.rawScore,
|
|
10761
|
+
rawScoreKnown: objective.rawScoreKnown,
|
|
10762
|
+
minScore: objective.minScore,
|
|
10763
|
+
minScoreKnown: objective.minScoreKnown,
|
|
10764
|
+
maxScore: objective.maxScore,
|
|
10765
|
+
maxScoreKnown: objective.maxScoreKnown,
|
|
10242
10766
|
progressMeasure: activity.progressMeasure,
|
|
10243
10767
|
progressMeasureStatus: activity.progressMeasureStatus,
|
|
10244
10768
|
completionStatus: activity.completionStatus,
|
|
@@ -10250,6 +10774,12 @@ class GlobalObjectiveSynchronizer {
|
|
|
10250
10774
|
satisfiedStatus: objective.satisfiedStatus,
|
|
10251
10775
|
measureStatus: objective.measureStatus,
|
|
10252
10776
|
normalizedMeasure: objective.normalizedMeasure,
|
|
10777
|
+
rawScore: objective.rawScore,
|
|
10778
|
+
rawScoreKnown: objective.rawScoreKnown,
|
|
10779
|
+
minScore: objective.minScore,
|
|
10780
|
+
minScoreKnown: objective.minScoreKnown,
|
|
10781
|
+
maxScore: objective.maxScore,
|
|
10782
|
+
maxScoreKnown: objective.maxScoreKnown,
|
|
10253
10783
|
progressMeasure: objective.progressMeasure,
|
|
10254
10784
|
progressMeasureStatus: objective.progressMeasureStatus,
|
|
10255
10785
|
completionStatus: objective.completionStatus,
|
|
@@ -10272,7 +10802,7 @@ class GlobalObjectiveSynchronizer {
|
|
|
10272
10802
|
(rule) => rule.action === "completed" || rule.action === "incomplete"
|
|
10273
10803
|
);
|
|
10274
10804
|
if (globalObjective.satisfiedStatusKnown && globalObjective.satisfiedStatus) {
|
|
10275
|
-
if (!hasCompletionRollupRules && (activity.completionStatus === CompletionStatus.UNKNOWN || activity.completionStatus === CompletionStatus.INCOMPLETE)) {
|
|
10805
|
+
if (!hasCompletionRollupRules && !activity.sequencingControls.completionSetByContent && !activity.attemptProgressStatus && (activity.completionStatus === CompletionStatus.UNKNOWN || activity.completionStatus === CompletionStatus.INCOMPLETE)) {
|
|
10276
10806
|
activity.completionStatus = CompletionStatus.COMPLETED;
|
|
10277
10807
|
}
|
|
10278
10808
|
if (activity.successStatus === "unknown") {
|
|
@@ -10718,6 +11248,9 @@ class RteDataTransferService {
|
|
|
10718
11248
|
* Transfer primary objective data from CMI to activity
|
|
10719
11249
|
* @param {Activity} activity - The activity to transfer data to
|
|
10720
11250
|
* @param {CMIDataForTransfer} cmiData - CMI data from runtime
|
|
11251
|
+
*
|
|
11252
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - primary objective status and score data
|
|
11253
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score write-map source data
|
|
10721
11254
|
*/
|
|
10722
11255
|
transferPrimaryObjective(activity, cmiData) {
|
|
10723
11256
|
let hasProgressMeasure = false;
|
|
@@ -10764,9 +11297,9 @@ class RteDataTransferService {
|
|
|
10764
11297
|
activity.objectiveSatisfiedStatus = successStatus;
|
|
10765
11298
|
activity.objectiveSatisfiedStatusKnown = true;
|
|
10766
11299
|
activity.successStatus = validatedSuccessStatus;
|
|
10767
|
-
activity.objectiveMeasureStatus = true;
|
|
10768
11300
|
}
|
|
10769
11301
|
if (cmiData.score) {
|
|
11302
|
+
activity.primaryObjective?.initializeScoreFromCMI(this.getObjectiveScoreState(cmiData.score));
|
|
10770
11303
|
const normalized = this.normalizeScore(cmiData.score);
|
|
10771
11304
|
if (normalized !== null) {
|
|
10772
11305
|
normalizedScore = normalized;
|
|
@@ -10778,7 +11311,7 @@ class RteDataTransferService {
|
|
|
10778
11311
|
if (activity.primaryObjective && (hasSuccessStatus || hasNormalizedMeasure)) {
|
|
10779
11312
|
const finalStatus = hasSuccessStatus ? successStatus : activity.primaryObjective.satisfiedStatus;
|
|
10780
11313
|
const finalMeasure = hasNormalizedMeasure ? normalizedScore : activity.primaryObjective.normalizedMeasure;
|
|
10781
|
-
const measureStatus =
|
|
11314
|
+
const measureStatus = hasNormalizedMeasure;
|
|
10782
11315
|
activity.primaryObjective.initializeFromCMI(finalStatus, finalMeasure, measureStatus);
|
|
10783
11316
|
if (hasSuccessStatus) {
|
|
10784
11317
|
activity.primaryObjective.satisfiedStatusKnown = true;
|
|
@@ -10787,10 +11320,16 @@ class RteDataTransferService {
|
|
|
10787
11320
|
}
|
|
10788
11321
|
}
|
|
10789
11322
|
/**
|
|
10790
|
-
* Transfer
|
|
10791
|
-
* Only transfers changed values to protect global objectives
|
|
11323
|
+
* Transfer objective-array data from CMI to matching activity objectives.
|
|
11324
|
+
* Only transfers changed values to protect global objectives.
|
|
10792
11325
|
* @param {Activity} activity - The activity to transfer data to
|
|
10793
11326
|
* @param {CMIDataForTransfer} cmiData - CMI data from runtime
|
|
11327
|
+
*
|
|
11328
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17 - cmi.objectives.n data, including
|
|
11329
|
+
* the primary objective entry, is part of the RTE objective data model.
|
|
11330
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 and ADLSEQ objectives extension -
|
|
11331
|
+
* mapped objective status and raw/min/max score data transfer through
|
|
11332
|
+
* objective maps to sequencing state.
|
|
10794
11333
|
*/
|
|
10795
11334
|
transferNonPrimaryObjectives(activity, cmiData) {
|
|
10796
11335
|
if (!cmiData.objectives || cmiData.objectives.length === 0) {
|
|
@@ -10801,14 +11340,17 @@ class RteDataTransferService {
|
|
|
10801
11340
|
continue;
|
|
10802
11341
|
}
|
|
10803
11342
|
const activityObjectiveMatch = activity.getObjectiveById(cmiObjective.id);
|
|
10804
|
-
if (!activityObjectiveMatch
|
|
11343
|
+
if (!activityObjectiveMatch) {
|
|
10805
11344
|
continue;
|
|
10806
11345
|
}
|
|
10807
11346
|
const activityObjective = activityObjectiveMatch.objective;
|
|
11347
|
+
const isPrimaryObjective = activityObjectiveMatch.isPrimary;
|
|
10808
11348
|
let hasSuccessStatus = false;
|
|
10809
11349
|
let successStatus = false;
|
|
10810
11350
|
let hasNormalizedMeasure = false;
|
|
10811
11351
|
let normalizedScore = 0;
|
|
11352
|
+
let hasCompletionStatus = false;
|
|
11353
|
+
let hasProgressMeasure = false;
|
|
10812
11354
|
const validatedObjSuccessStatus = validateSuccessStatus(cmiObjective.success_status);
|
|
10813
11355
|
if (validatedObjSuccessStatus && validatedObjSuccessStatus !== SuccessStatus.UNKNOWN) {
|
|
10814
11356
|
successStatus = validatedObjSuccessStatus === SuccessStatus.PASSED;
|
|
@@ -10818,8 +11360,10 @@ class RteDataTransferService {
|
|
|
10818
11360
|
const validatedObjCompletionStatus = validateCompletionStatus(cmiObjective.completion_status);
|
|
10819
11361
|
if (validatedObjCompletionStatus && validatedObjCompletionStatus !== CompletionStatus.UNKNOWN) {
|
|
10820
11362
|
activityObjective.completionStatus = validatedObjCompletionStatus;
|
|
11363
|
+
hasCompletionStatus = true;
|
|
10821
11364
|
}
|
|
10822
11365
|
if (cmiObjective.score) {
|
|
11366
|
+
activityObjective.initializeScoreFromCMI(this.getObjectiveScoreState(cmiObjective.score));
|
|
10823
11367
|
const normalized = this.normalizeScore(cmiObjective.score);
|
|
10824
11368
|
if (normalized !== null) {
|
|
10825
11369
|
normalizedScore = normalized;
|
|
@@ -10831,12 +11375,29 @@ class RteDataTransferService {
|
|
|
10831
11375
|
const finalMeasure = hasNormalizedMeasure ? normalizedScore : activityObjective.normalizedMeasure;
|
|
10832
11376
|
const measureStatus = hasNormalizedMeasure;
|
|
10833
11377
|
activityObjective.initializeFromCMI(finalStatus, finalMeasure, measureStatus);
|
|
11378
|
+
if (hasSuccessStatus) {
|
|
11379
|
+
activityObjective.satisfiedStatusKnown = true;
|
|
11380
|
+
}
|
|
10834
11381
|
}
|
|
10835
11382
|
if (cmiObjective.progress_measure && cmiObjective.progress_measure !== "") {
|
|
10836
11383
|
const progressMeasure = parseFloat(cmiObjective.progress_measure);
|
|
10837
11384
|
if (!isNaN(progressMeasure)) {
|
|
10838
11385
|
activityObjective.progressMeasure = progressMeasure;
|
|
10839
11386
|
activityObjective.progressMeasureStatus = true;
|
|
11387
|
+
hasProgressMeasure = true;
|
|
11388
|
+
}
|
|
11389
|
+
}
|
|
11390
|
+
if (isPrimaryObjective && (hasSuccessStatus || hasNormalizedMeasure || hasCompletionStatus || hasProgressMeasure)) {
|
|
11391
|
+
activityObjective.applyToActivity(activity);
|
|
11392
|
+
if (validatedObjSuccessStatus && validatedObjSuccessStatus !== SuccessStatus.UNKNOWN) {
|
|
11393
|
+
activity.successStatus = validatedObjSuccessStatus;
|
|
11394
|
+
}
|
|
11395
|
+
if (hasCompletionStatus) {
|
|
11396
|
+
activity.attemptProgressStatus = true;
|
|
11397
|
+
}
|
|
11398
|
+
if (hasProgressMeasure) {
|
|
11399
|
+
activity.attemptCompletionAmount = activityObjective.progressMeasure;
|
|
11400
|
+
activity.attemptCompletionAmountStatus = true;
|
|
10840
11401
|
}
|
|
10841
11402
|
}
|
|
10842
11403
|
}
|
|
@@ -10865,6 +11426,25 @@ class RteDataTransferService {
|
|
|
10865
11426
|
}
|
|
10866
11427
|
return null;
|
|
10867
11428
|
}
|
|
11429
|
+
/**
|
|
11430
|
+
* Convert RTE score data into objective score-map state without numeric reformatting.
|
|
11431
|
+
*
|
|
11432
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - score values transfer from RTE to sequencing state
|
|
11433
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score map fields are independent
|
|
11434
|
+
*/
|
|
11435
|
+
getObjectiveScoreState(score) {
|
|
11436
|
+
const scoreState = {};
|
|
11437
|
+
if (score.raw !== void 0) {
|
|
11438
|
+
scoreState.rawScore = score.raw;
|
|
11439
|
+
}
|
|
11440
|
+
if (score.min !== void 0) {
|
|
11441
|
+
scoreState.minScore = score.min;
|
|
11442
|
+
}
|
|
11443
|
+
if (score.max !== void 0) {
|
|
11444
|
+
scoreState.maxScore = score.max;
|
|
11445
|
+
}
|
|
11446
|
+
return scoreState;
|
|
11447
|
+
}
|
|
10868
11448
|
}
|
|
10869
11449
|
|
|
10870
11450
|
class TerminationHandler {
|
|
@@ -11226,13 +11806,7 @@ class TerminationHandler {
|
|
|
11226
11806
|
recursionDepth++;
|
|
11227
11807
|
const exitRules = activity.sequencingRules.exitConditionRules;
|
|
11228
11808
|
for (const rule of exitRules) {
|
|
11229
|
-
|
|
11230
|
-
if (rule.conditionCombination === "all") {
|
|
11231
|
-
conditionsMet = rule.conditions.every((condition) => condition.evaluate(activity));
|
|
11232
|
-
} else {
|
|
11233
|
-
conditionsMet = rule.conditions.some((condition) => condition.evaluate(activity));
|
|
11234
|
-
}
|
|
11235
|
-
if (conditionsMet) {
|
|
11809
|
+
if (rule.evaluate(activity)) {
|
|
11236
11810
|
if (rule.action === RuleActionType.EXIT) {
|
|
11237
11811
|
return { action: "EXIT", recursionDepth };
|
|
11238
11812
|
} else if (rule.action === RuleActionType.EXIT_PARENT) {
|
|
@@ -11398,13 +11972,7 @@ class TerminationHandler {
|
|
|
11398
11972
|
exitActionRulesSubprocess(activity) {
|
|
11399
11973
|
const exitRules = activity.sequencingRules.exitConditionRules;
|
|
11400
11974
|
for (const rule of exitRules) {
|
|
11401
|
-
|
|
11402
|
-
if (rule.conditionCombination === "all") {
|
|
11403
|
-
conditionsMet = rule.conditions.every((condition) => condition.evaluate(activity));
|
|
11404
|
-
} else {
|
|
11405
|
-
conditionsMet = rule.conditions.some((condition) => condition.evaluate(activity));
|
|
11406
|
-
}
|
|
11407
|
-
if (conditionsMet) {
|
|
11975
|
+
if (rule.evaluate(activity)) {
|
|
11408
11976
|
if (rule.action === RuleActionType.EXIT) {
|
|
11409
11977
|
return "EXIT";
|
|
11410
11978
|
} else if (rule.action === RuleActionType.EXIT_PARENT) {
|
|
@@ -12923,6 +13491,9 @@ class GlobalObjectiveService {
|
|
|
12923
13491
|
* Collect Global Objectives
|
|
12924
13492
|
* Recursively collects global objectives from the activity tree
|
|
12925
13493
|
* @param {Activity} activity - Activity to collect objectives from
|
|
13494
|
+
*
|
|
13495
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective map contains mapped objective state
|
|
13496
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score fields are unknown until written
|
|
12926
13497
|
*/
|
|
12927
13498
|
collectObjectives(activity) {
|
|
12928
13499
|
const objectives = activity.getAllObjectives();
|
|
@@ -12932,9 +13503,15 @@ class GlobalObjectiveService {
|
|
|
12932
13503
|
this.globalObjectiveMap.set(defaultId, {
|
|
12933
13504
|
id: defaultId,
|
|
12934
13505
|
satisfiedStatus: activity.objectiveSatisfiedStatus,
|
|
12935
|
-
satisfiedStatusKnown: activity.
|
|
13506
|
+
satisfiedStatusKnown: activity.objectiveSatisfiedStatusKnown,
|
|
12936
13507
|
normalizedMeasure: activity.objectiveNormalizedMeasure,
|
|
12937
13508
|
normalizedMeasureKnown: activity.objectiveMeasureStatus,
|
|
13509
|
+
rawScore: "",
|
|
13510
|
+
rawScoreKnown: false,
|
|
13511
|
+
minScore: "",
|
|
13512
|
+
minScoreKnown: false,
|
|
13513
|
+
maxScore: "",
|
|
13514
|
+
maxScoreKnown: false,
|
|
12938
13515
|
progressMeasure: activity.progressMeasure,
|
|
12939
13516
|
progressMeasureKnown: activity.progressMeasureStatus,
|
|
12940
13517
|
completionStatus: activity.completionStatus,
|
|
@@ -12947,6 +13524,12 @@ class GlobalObjectiveService {
|
|
|
12947
13524
|
writeCompletionStatus: true,
|
|
12948
13525
|
readProgressMeasure: true,
|
|
12949
13526
|
writeProgressMeasure: true,
|
|
13527
|
+
readRawScore: false,
|
|
13528
|
+
writeRawScore: false,
|
|
13529
|
+
readMinScore: false,
|
|
13530
|
+
writeMinScore: false,
|
|
13531
|
+
readMaxScore: false,
|
|
13532
|
+
writeMaxScore: false,
|
|
12950
13533
|
satisfiedByMeasure: activity.scaledPassingScore !== null,
|
|
12951
13534
|
minNormalizedMeasure: activity.scaledPassingScore,
|
|
12952
13535
|
updateAttemptData: true
|
|
@@ -12974,9 +13557,15 @@ class GlobalObjectiveService {
|
|
|
12974
13557
|
this.globalObjectiveMap.set(targetId, {
|
|
12975
13558
|
id: targetId,
|
|
12976
13559
|
satisfiedStatus: objective.satisfiedStatus,
|
|
12977
|
-
satisfiedStatusKnown: objective.
|
|
13560
|
+
satisfiedStatusKnown: objective.satisfiedStatusKnown || objective.progressStatus,
|
|
12978
13561
|
normalizedMeasure: objective.normalizedMeasure,
|
|
12979
13562
|
normalizedMeasureKnown: objective.measureStatus,
|
|
13563
|
+
rawScore: "",
|
|
13564
|
+
rawScoreKnown: false,
|
|
13565
|
+
minScore: "",
|
|
13566
|
+
minScoreKnown: false,
|
|
13567
|
+
maxScore: "",
|
|
13568
|
+
maxScoreKnown: false,
|
|
12980
13569
|
progressMeasure: objective.progressMeasure,
|
|
12981
13570
|
progressMeasureKnown: objective.progressMeasureStatus,
|
|
12982
13571
|
completionStatus: objective.completionStatus,
|
|
@@ -14959,6 +15548,9 @@ class SequencingService {
|
|
|
14959
15548
|
}
|
|
14960
15549
|
/**
|
|
14961
15550
|
* Update activity properties from current CMI values
|
|
15551
|
+
*
|
|
15552
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - current CMI values update activity objective state
|
|
15553
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max scores are available to objective write maps
|
|
14962
15554
|
*/
|
|
14963
15555
|
updateActivityFromCMI(activity) {
|
|
14964
15556
|
let hasProgressMeasure = false;
|
|
@@ -14990,7 +15582,6 @@ class SequencingService {
|
|
|
14990
15582
|
if (this.cmi.success_status !== "unknown") {
|
|
14991
15583
|
activity.successStatus = this.cmi.success_status;
|
|
14992
15584
|
activity.objectiveSatisfiedStatus = this.cmi.success_status === "passed";
|
|
14993
|
-
activity.objectiveMeasureStatus = true;
|
|
14994
15585
|
if (activity.primaryObjective) {
|
|
14995
15586
|
activity.primaryObjective.progressStatus = true;
|
|
14996
15587
|
}
|
|
@@ -15005,6 +15596,17 @@ class SequencingService {
|
|
|
15005
15596
|
}
|
|
15006
15597
|
}
|
|
15007
15598
|
}
|
|
15599
|
+
if (activity.primaryObjective && this.cmi.score) {
|
|
15600
|
+
if (this.cmi.score.raw !== "") {
|
|
15601
|
+
activity.primaryObjective.rawScore = this.cmi.score.raw;
|
|
15602
|
+
}
|
|
15603
|
+
if (this.cmi.score.min !== "") {
|
|
15604
|
+
activity.primaryObjective.minScore = this.cmi.score.min;
|
|
15605
|
+
}
|
|
15606
|
+
if (this.cmi.score.max !== "") {
|
|
15607
|
+
activity.primaryObjective.maxScore = this.cmi.score.max;
|
|
15608
|
+
}
|
|
15609
|
+
}
|
|
15008
15610
|
if (activity.primaryObjective) {
|
|
15009
15611
|
activity.primaryObjective.updateFromActivity(activity);
|
|
15010
15612
|
}
|
|
@@ -15578,15 +16180,22 @@ class SynchronousHttpService {
|
|
|
15578
16180
|
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
15579
16181
|
const requestPayload = handledPayload ?? params;
|
|
15580
16182
|
const { body } = this._prepareRequestBody(requestPayload);
|
|
15581
|
-
const
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
);
|
|
16183
|
+
const beaconContentType = this.settings.terminationCommitContentType;
|
|
16184
|
+
this._warnIfBeaconContentTypeUnsafe(url, beaconContentType);
|
|
16185
|
+
const beaconSuccess = navigator.sendBeacon(url, new Blob([body], { type: beaconContentType }));
|
|
15585
16186
|
return {
|
|
15586
16187
|
result: beaconSuccess ? "true" : "false",
|
|
15587
16188
|
errorCode: beaconSuccess ? 0 : this.error_codes.GENERAL_COMMIT_FAILURE || 391
|
|
15588
16189
|
};
|
|
15589
16190
|
}
|
|
16191
|
+
_warnIfBeaconContentTypeUnsafe(url, contentType) {
|
|
16192
|
+
if (isCrossOriginUrl(url) && !isCorsSafelistedContentType(contentType)) {
|
|
16193
|
+
this.settings.onLogMessage?.(
|
|
16194
|
+
LogLevelEnum.WARN,
|
|
16195
|
+
`sendBeacon to cross-origin URL with non-CORS-safelisted Content-Type "${contentType}" may be silently dropped by the browser (Beacon cannot preflight). Use fetch (useAsynchronousCommits + asyncModeBeaconBehavior:"never") for cross-origin JSON/auth on terminate.`
|
|
16196
|
+
);
|
|
16197
|
+
}
|
|
16198
|
+
}
|
|
15590
16199
|
/**
|
|
15591
16200
|
* Performs a synchronous XMLHttpRequest
|
|
15592
16201
|
* @param {string} url - The URL to send the request to
|
|
@@ -16613,8 +17222,8 @@ class BaseAPI {
|
|
|
16613
17222
|
* // Throw a "not initialized" error
|
|
16614
17223
|
* this.throwSCORMError(301, "The API must be initialized before calling GetValue");
|
|
16615
17224
|
*/
|
|
16616
|
-
throwSCORMError(CMIElement, errorNumber, message) {
|
|
16617
|
-
this._errorHandlingService.throwSCORMError(CMIElement, errorNumber ?? 0, message);
|
|
17225
|
+
throwSCORMError(CMIElement, errorNumber, message, messageLevel) {
|
|
17226
|
+
this._errorHandlingService.throwSCORMError(CMIElement, errorNumber ?? 0, message, messageLevel);
|
|
16618
17227
|
}
|
|
16619
17228
|
/**
|
|
16620
17229
|
* Clears the last SCORM error code when an operation succeeds.
|
|
@@ -19682,7 +20291,7 @@ class CMIInteractionsObject extends BaseCMI {
|
|
|
19682
20291
|
if (check2004ValidFormat(
|
|
19683
20292
|
this._cmi_element + ".description",
|
|
19684
20293
|
description,
|
|
19685
|
-
scorm2004_regex.
|
|
20294
|
+
scorm2004_regex.CMILangString,
|
|
19686
20295
|
true
|
|
19687
20296
|
)) {
|
|
19688
20297
|
this._description = description;
|
|
@@ -20392,7 +21001,7 @@ class CMIObjectivesObject extends BaseCMI {
|
|
|
20392
21001
|
if (check2004ValidFormat(
|
|
20393
21002
|
this._cmi_element + ".description",
|
|
20394
21003
|
description,
|
|
20395
|
-
scorm2004_regex.
|
|
21004
|
+
scorm2004_regex.CMILangString,
|
|
20396
21005
|
true
|
|
20397
21006
|
)) {
|
|
20398
21007
|
this._description = description;
|
|
@@ -23235,6 +23844,9 @@ class SequencingConfigurationBuilder {
|
|
|
23235
23844
|
if (settings.objectiveSetByContent !== void 0) {
|
|
23236
23845
|
target.objectiveSetByContent = settings.objectiveSetByContent;
|
|
23237
23846
|
}
|
|
23847
|
+
if (settings.tracked !== void 0) {
|
|
23848
|
+
target.tracked = settings.tracked;
|
|
23849
|
+
}
|
|
23238
23850
|
}
|
|
23239
23851
|
/**
|
|
23240
23852
|
* Applies the sequencing rules settings to the specified target object.
|
|
@@ -23772,6 +24384,12 @@ class ActivityTreeBuilder {
|
|
|
23772
24384
|
activitySettings.sequencingControls
|
|
23773
24385
|
);
|
|
23774
24386
|
}
|
|
24387
|
+
if (activitySettings.deliveryControls) {
|
|
24388
|
+
this.sequencingConfigBuilder.applySequencingControlsSettings(
|
|
24389
|
+
activity.sequencingControls,
|
|
24390
|
+
activitySettings.deliveryControls
|
|
24391
|
+
);
|
|
24392
|
+
}
|
|
23775
24393
|
if (activitySettings.sequencingRules) {
|
|
23776
24394
|
this.sequencingConfigBuilder.applySequencingRulesSettings(
|
|
23777
24395
|
activity.sequencingRules,
|
|
@@ -23861,19 +24479,19 @@ class ActivityTreeBuilder {
|
|
|
23861
24479
|
createActivityObjectiveFromSettings(objectiveSettings, isPrimary) {
|
|
23862
24480
|
const mapInfo = (objectiveSettings.mapInfo || []).map((info) => ({
|
|
23863
24481
|
targetObjectiveID: info.targetObjectiveID,
|
|
23864
|
-
readSatisfiedStatus: info.readSatisfiedStatus ??
|
|
23865
|
-
readNormalizedMeasure: info.readNormalizedMeasure ??
|
|
24482
|
+
readSatisfiedStatus: info.readSatisfiedStatus ?? true,
|
|
24483
|
+
readNormalizedMeasure: info.readNormalizedMeasure ?? true,
|
|
23866
24484
|
writeSatisfiedStatus: info.writeSatisfiedStatus ?? false,
|
|
23867
24485
|
writeNormalizedMeasure: info.writeNormalizedMeasure ?? false,
|
|
23868
|
-
readCompletionStatus: info.readCompletionStatus ??
|
|
24486
|
+
readCompletionStatus: info.readCompletionStatus ?? true,
|
|
23869
24487
|
writeCompletionStatus: info.writeCompletionStatus ?? false,
|
|
23870
|
-
readProgressMeasure: info.readProgressMeasure ??
|
|
24488
|
+
readProgressMeasure: info.readProgressMeasure ?? true,
|
|
23871
24489
|
writeProgressMeasure: info.writeProgressMeasure ?? false,
|
|
23872
|
-
readRawScore: info.readRawScore ??
|
|
24490
|
+
readRawScore: info.readRawScore ?? true,
|
|
23873
24491
|
writeRawScore: info.writeRawScore ?? false,
|
|
23874
|
-
readMinScore: info.readMinScore ??
|
|
24492
|
+
readMinScore: info.readMinScore ?? true,
|
|
23875
24493
|
writeMinScore: info.writeMinScore ?? false,
|
|
23876
|
-
readMaxScore: info.readMaxScore ??
|
|
24494
|
+
readMaxScore: info.readMaxScore ?? true,
|
|
23877
24495
|
writeMaxScore: info.writeMaxScore ?? false,
|
|
23878
24496
|
updateAttemptData: info.updateAttemptData ?? false
|
|
23879
24497
|
}));
|
|
@@ -24055,6 +24673,9 @@ class GlobalObjectiveManager {
|
|
|
24055
24673
|
*
|
|
24056
24674
|
* @param {string} objectiveId - The global objective ID
|
|
24057
24675
|
* @param {CMIObjectivesObject} objective - The CMI objective object with updated values
|
|
24676
|
+
*
|
|
24677
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objectives synchronize CMI objective data
|
|
24678
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score fields synchronize independently
|
|
24058
24679
|
*/
|
|
24059
24680
|
updateGlobalObjectiveFromCMI(objectiveId, objective) {
|
|
24060
24681
|
if (!objectiveId || !this.context.sequencingService) {
|
|
@@ -24080,6 +24701,18 @@ class GlobalObjectiveManager {
|
|
|
24080
24701
|
updatePayload.normalizedMeasure = normalizedMeasure;
|
|
24081
24702
|
updatePayload.normalizedMeasureKnown = true;
|
|
24082
24703
|
}
|
|
24704
|
+
if (objective.score?.raw !== void 0 && objective.score.raw !== "") {
|
|
24705
|
+
updatePayload.rawScore = objective.score.raw;
|
|
24706
|
+
updatePayload.rawScoreKnown = true;
|
|
24707
|
+
}
|
|
24708
|
+
if (objective.score?.min !== void 0 && objective.score.min !== "") {
|
|
24709
|
+
updatePayload.minScore = objective.score.min;
|
|
24710
|
+
updatePayload.minScoreKnown = true;
|
|
24711
|
+
}
|
|
24712
|
+
if (objective.score?.max !== void 0 && objective.score.max !== "") {
|
|
24713
|
+
updatePayload.maxScore = objective.score.max;
|
|
24714
|
+
updatePayload.maxScoreKnown = true;
|
|
24715
|
+
}
|
|
24083
24716
|
const progressMeasure = this.parseObjectiveNumber(objective.progress_measure);
|
|
24084
24717
|
if (progressMeasure !== null) {
|
|
24085
24718
|
updatePayload.progressMeasure = progressMeasure;
|
|
@@ -24099,12 +24732,21 @@ class GlobalObjectiveManager {
|
|
|
24099
24732
|
*
|
|
24100
24733
|
* @param {CMIObjectivesObject} objective - The CMI objectives object containing data about a specific learning objective.
|
|
24101
24734
|
* @return {GlobalObjectiveMapEntry} An object containing mapped properties and their values based on the provided objective.
|
|
24735
|
+
*
|
|
24736
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective map entries capture objective state
|
|
24737
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score entries carry per-field known flags
|
|
24102
24738
|
*/
|
|
24103
24739
|
buildObjectiveMapEntryFromCMI(objective) {
|
|
24104
24740
|
const entry = {
|
|
24105
24741
|
id: objective.id,
|
|
24106
24742
|
satisfiedStatusKnown: false,
|
|
24107
24743
|
normalizedMeasureKnown: false,
|
|
24744
|
+
rawScore: "",
|
|
24745
|
+
rawScoreKnown: false,
|
|
24746
|
+
minScore: "",
|
|
24747
|
+
minScoreKnown: false,
|
|
24748
|
+
maxScore: "",
|
|
24749
|
+
maxScoreKnown: false,
|
|
24108
24750
|
progressMeasureKnown: false,
|
|
24109
24751
|
completionStatusKnown: false,
|
|
24110
24752
|
readSatisfiedStatus: true,
|
|
@@ -24114,7 +24756,13 @@ class GlobalObjectiveManager {
|
|
|
24114
24756
|
readCompletionStatus: true,
|
|
24115
24757
|
writeCompletionStatus: true,
|
|
24116
24758
|
readProgressMeasure: true,
|
|
24117
|
-
writeProgressMeasure: true
|
|
24759
|
+
writeProgressMeasure: true,
|
|
24760
|
+
readRawScore: true,
|
|
24761
|
+
writeRawScore: true,
|
|
24762
|
+
readMinScore: true,
|
|
24763
|
+
writeMinScore: true,
|
|
24764
|
+
readMaxScore: true,
|
|
24765
|
+
writeMaxScore: true
|
|
24118
24766
|
};
|
|
24119
24767
|
if (objective.success_status && objective.success_status !== SuccessStatus.UNKNOWN) {
|
|
24120
24768
|
entry.satisfiedStatus = objective.success_status === SuccessStatus.PASSED;
|
|
@@ -24125,6 +24773,18 @@ class GlobalObjectiveManager {
|
|
|
24125
24773
|
entry.normalizedMeasure = normalizedMeasure;
|
|
24126
24774
|
entry.normalizedMeasureKnown = true;
|
|
24127
24775
|
}
|
|
24776
|
+
if (objective.score?.raw !== void 0 && objective.score.raw !== "") {
|
|
24777
|
+
entry.rawScore = objective.score.raw;
|
|
24778
|
+
entry.rawScoreKnown = true;
|
|
24779
|
+
}
|
|
24780
|
+
if (objective.score?.min !== void 0 && objective.score.min !== "") {
|
|
24781
|
+
entry.minScore = objective.score.min;
|
|
24782
|
+
entry.minScoreKnown = true;
|
|
24783
|
+
}
|
|
24784
|
+
if (objective.score?.max !== void 0 && objective.score.max !== "") {
|
|
24785
|
+
entry.maxScore = objective.score.max;
|
|
24786
|
+
entry.maxScoreKnown = true;
|
|
24787
|
+
}
|
|
24128
24788
|
const progressMeasure = this.parseObjectiveNumber(objective.progress_measure);
|
|
24129
24789
|
if (progressMeasure !== null) {
|
|
24130
24790
|
entry.progressMeasure = progressMeasure;
|
|
@@ -24145,6 +24805,9 @@ class GlobalObjectiveManager {
|
|
|
24145
24805
|
* @return {CMIObjectivesObject[]} An array of `CMIObjectivesObject` instances built
|
|
24146
24806
|
* from the provided snapshot map. Returns an empty array
|
|
24147
24807
|
* if the snapshot is invalid or no valid objectives can be created.
|
|
24808
|
+
*
|
|
24809
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective snapshots restore CMI objective state
|
|
24810
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score known fields restore independently
|
|
24148
24811
|
*/
|
|
24149
24812
|
buildCMIObjectivesFromMap(snapshot) {
|
|
24150
24813
|
const objectives = [];
|
|
@@ -24164,6 +24827,15 @@ class GlobalObjectiveManager {
|
|
|
24164
24827
|
if (entry.normalizedMeasureKnown === true && normalizedMeasure !== null) {
|
|
24165
24828
|
objective.score.scaled = String(normalizedMeasure);
|
|
24166
24829
|
}
|
|
24830
|
+
if (entry.rawScoreKnown === true) {
|
|
24831
|
+
objective.score.raw = String(entry.rawScore ?? "");
|
|
24832
|
+
}
|
|
24833
|
+
if (entry.minScoreKnown === true) {
|
|
24834
|
+
objective.score.min = String(entry.minScore ?? "");
|
|
24835
|
+
}
|
|
24836
|
+
if (entry.maxScoreKnown === true) {
|
|
24837
|
+
objective.score.max = String(entry.maxScore ?? "");
|
|
24838
|
+
}
|
|
24167
24839
|
const progressMeasure = this.parseObjectiveNumber(entry.progressMeasure);
|
|
24168
24840
|
if (entry.progressMeasureKnown === true && progressMeasure !== null) {
|
|
24169
24841
|
objective.progress_measure = String(progressMeasure);
|
|
@@ -24274,6 +24946,9 @@ class GlobalObjectiveManager {
|
|
|
24274
24946
|
* @param {CompletionStatus} completionStatus
|
|
24275
24947
|
* @param {SuccessStatus} successStatus
|
|
24276
24948
|
* @param {ScoreObject} scoreObject
|
|
24949
|
+
*
|
|
24950
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - CMI score data updates the current primary objective
|
|
24951
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score data is available for write maps
|
|
24277
24952
|
*/
|
|
24278
24953
|
syncCmiToSequencingActivity(completionStatus, successStatus, scoreObject) {
|
|
24279
24954
|
if (!this.context.sequencing) {
|
|
@@ -24299,6 +24974,15 @@ class GlobalObjectiveManager {
|
|
|
24299
24974
|
primaryObjective.normalizedMeasure = scoreObject.scaled;
|
|
24300
24975
|
primaryObjective.measureStatus = true;
|
|
24301
24976
|
}
|
|
24977
|
+
if (scoreObject?.raw !== void 0 && scoreObject.raw !== null) {
|
|
24978
|
+
primaryObjective.rawScore = String(scoreObject.raw);
|
|
24979
|
+
}
|
|
24980
|
+
if (scoreObject?.min !== void 0 && scoreObject.min !== null) {
|
|
24981
|
+
primaryObjective.minScore = String(scoreObject.min);
|
|
24982
|
+
}
|
|
24983
|
+
if (scoreObject?.max !== void 0 && scoreObject.max !== null) {
|
|
24984
|
+
primaryObjective.maxScore = String(scoreObject.max);
|
|
24985
|
+
}
|
|
24302
24986
|
}
|
|
24303
24987
|
/**
|
|
24304
24988
|
* Find or create a global objective by ID
|
|
@@ -24851,16 +25535,232 @@ class Scorm2004API extends BaseAPI {
|
|
|
24851
25535
|
/**
|
|
24852
25536
|
* Apply launch-static activity data to CMI while the new SCO is pre-initialize.
|
|
24853
25537
|
*
|
|
24854
|
-
* @spec SCORM 2004 4th Ed. RTE 4.2.
|
|
25538
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold
|
|
25539
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
24855
25540
|
*/
|
|
24856
25541
|
applyCurrentActivityLaunchData() {
|
|
24857
25542
|
const currentActivity = this._sequencing?.getCurrentActivity();
|
|
24858
|
-
if (!
|
|
25543
|
+
if (!currentActivity) {
|
|
25544
|
+
return;
|
|
25545
|
+
}
|
|
25546
|
+
this.applyActivityLaunchData(currentActivity);
|
|
25547
|
+
}
|
|
25548
|
+
/**
|
|
25549
|
+
* Apply launch-static CMI data when sequencing delivers a new activity before SCO Initialize.
|
|
25550
|
+
*
|
|
25551
|
+
* @spec SCORM 2004 4th Ed. SN DB.2 - Content Delivery Environment Process establishes the delivered activity.
|
|
25552
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold is initialized before SCO access.
|
|
25553
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives is initialized before SCO access.
|
|
25554
|
+
*/
|
|
25555
|
+
applyDeliveredActivityLaunchData(activity) {
|
|
25556
|
+
if (!this.isNotInitialized()) {
|
|
25557
|
+
return;
|
|
25558
|
+
}
|
|
25559
|
+
this.applyActivityLaunchData(activity);
|
|
25560
|
+
}
|
|
25561
|
+
/**
|
|
25562
|
+
* Copy the delivered activity's static launch data into the fresh CMI model.
|
|
25563
|
+
*
|
|
25564
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold
|
|
25565
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
25566
|
+
*/
|
|
25567
|
+
applyActivityLaunchData(currentActivity) {
|
|
25568
|
+
if (!this.cmi) {
|
|
24859
25569
|
return;
|
|
24860
25570
|
}
|
|
24861
25571
|
const contentActivityData = this._sequencing.overallSequencingProcess?.getContentActivityData(currentActivity);
|
|
24862
25572
|
const completionThreshold = contentActivityData?.completionThreshold ?? currentActivity.completionThreshold?.toString() ?? "";
|
|
24863
25573
|
this.cmi.completion_threshold = completionThreshold;
|
|
25574
|
+
this.seedCurrentActivityObjectives(currentActivity);
|
|
25575
|
+
}
|
|
25576
|
+
/**
|
|
25577
|
+
* Seed CMI objective ids after Initialize when automatic sequencing starts during Initialize.
|
|
25578
|
+
*
|
|
25579
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
25580
|
+
*/
|
|
25581
|
+
applyCurrentActivityObjectiveData() {
|
|
25582
|
+
const currentActivity = this._sequencing?.getCurrentActivity();
|
|
25583
|
+
if (!this.cmi || !currentActivity) {
|
|
25584
|
+
return;
|
|
25585
|
+
}
|
|
25586
|
+
this.seedCurrentActivityObjectives(currentActivity);
|
|
25587
|
+
}
|
|
25588
|
+
/**
|
|
25589
|
+
* Seed CMI objectives from primary and secondary activity objectives.
|
|
25590
|
+
*
|
|
25591
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17 - Initialization of Run-Time Objectives from Sequencing Information
|
|
25592
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.id and success_status
|
|
25593
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - objective read maps seed the RTE view
|
|
25594
|
+
*/
|
|
25595
|
+
seedCurrentActivityObjectives(currentActivity) {
|
|
25596
|
+
const activityObjectives = [];
|
|
25597
|
+
if (currentActivity.primaryObjective) {
|
|
25598
|
+
activityObjectives.push(currentActivity.primaryObjective);
|
|
25599
|
+
}
|
|
25600
|
+
activityObjectives.push(...currentActivity.objectives);
|
|
25601
|
+
const seededObjectiveIds = /* @__PURE__ */ new Set();
|
|
25602
|
+
for (const activityObjective of activityObjectives) {
|
|
25603
|
+
const objectiveId = this.getSeedableObjectiveId(activityObjective);
|
|
25604
|
+
if (!objectiveId || seededObjectiveIds.has(objectiveId)) {
|
|
25605
|
+
continue;
|
|
25606
|
+
}
|
|
25607
|
+
seededObjectiveIds.add(objectiveId);
|
|
25608
|
+
const index = this.findOrSeedCMIObjective(objectiveId);
|
|
25609
|
+
if (index === null) {
|
|
25610
|
+
continue;
|
|
25611
|
+
}
|
|
25612
|
+
const cmiObjective = this.cmi.objectives.findObjectiveByIndex(index);
|
|
25613
|
+
if (cmiObjective && this.cmi.objectives.initialized && !cmiObjective.initialized) {
|
|
25614
|
+
cmiObjective.initialize();
|
|
25615
|
+
}
|
|
25616
|
+
const successStatus = this.getActivityObjectiveSuccessStatus(activityObjective);
|
|
25617
|
+
if (successStatus) {
|
|
25618
|
+
this._commonSetCMIValue(
|
|
25619
|
+
"SeedActivityObjective",
|
|
25620
|
+
true,
|
|
25621
|
+
`cmi.objectives.${index}.success_status`,
|
|
25622
|
+
successStatus
|
|
25623
|
+
);
|
|
25624
|
+
}
|
|
25625
|
+
this.seedObjectiveReadMapValues(currentActivity, activityObjective, index);
|
|
25626
|
+
}
|
|
25627
|
+
}
|
|
25628
|
+
/**
|
|
25629
|
+
* Seed CMI objective fields from this objective's read-mapped global objectives.
|
|
25630
|
+
*
|
|
25631
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17 - Run-Time Objectives are initialized from sequencing information
|
|
25632
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read mapInfo grants access to mapped global objective state
|
|
25633
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps seed RTE score fields
|
|
25634
|
+
*/
|
|
25635
|
+
seedObjectiveReadMapValues(currentActivity, activityObjective, objectiveIndex) {
|
|
25636
|
+
const globalObjectiveMap = this._sequencing.overallSequencingProcess?.getGlobalObjectiveMap();
|
|
25637
|
+
if (!globalObjectiveMap || activityObjective.mapInfo.length === 0) {
|
|
25638
|
+
return;
|
|
25639
|
+
}
|
|
25640
|
+
for (const mapInfo of activityObjective.mapInfo) {
|
|
25641
|
+
const targetObjectiveId = mapInfo.targetObjectiveID || activityObjective.id;
|
|
25642
|
+
const globalObjective = globalObjectiveMap.get(targetObjectiveId);
|
|
25643
|
+
if (!globalObjective) {
|
|
25644
|
+
continue;
|
|
25645
|
+
}
|
|
25646
|
+
const readState = GlobalObjectiveSynchronizer.getGlobalObjectiveReadState(
|
|
25647
|
+
currentActivity,
|
|
25648
|
+
activityObjective,
|
|
25649
|
+
mapInfo,
|
|
25650
|
+
globalObjective
|
|
25651
|
+
);
|
|
25652
|
+
this.applyObjectiveReadStateToCMI(objectiveIndex, readState);
|
|
25653
|
+
}
|
|
25654
|
+
}
|
|
25655
|
+
/**
|
|
25656
|
+
* Copy mapped global objective state into the seeded CMI objective entry.
|
|
25657
|
+
*
|
|
25658
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives launch-time initialization
|
|
25659
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps populate the RTE view without creating local writes
|
|
25660
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps populate CMI objective scores
|
|
25661
|
+
*/
|
|
25662
|
+
applyObjectiveReadStateToCMI(objectiveIndex, readState) {
|
|
25663
|
+
if (readState.satisfiedStatus !== void 0) {
|
|
25664
|
+
this._commonSetCMIValue(
|
|
25665
|
+
"SeedActivityObjectiveReadMap",
|
|
25666
|
+
true,
|
|
25667
|
+
`cmi.objectives.${objectiveIndex}.success_status`,
|
|
25668
|
+
readState.satisfiedStatus ? SuccessStatus.PASSED : SuccessStatus.FAILED
|
|
25669
|
+
);
|
|
25670
|
+
}
|
|
25671
|
+
if (readState.normalizedMeasure !== void 0) {
|
|
25672
|
+
this._commonSetCMIValue(
|
|
25673
|
+
"SeedActivityObjectiveReadMap",
|
|
25674
|
+
true,
|
|
25675
|
+
`cmi.objectives.${objectiveIndex}.score.scaled`,
|
|
25676
|
+
String(readState.normalizedMeasure)
|
|
25677
|
+
);
|
|
25678
|
+
}
|
|
25679
|
+
if (readState.completionStatus !== void 0) {
|
|
25680
|
+
this._commonSetCMIValue(
|
|
25681
|
+
"SeedActivityObjectiveReadMap",
|
|
25682
|
+
true,
|
|
25683
|
+
`cmi.objectives.${objectiveIndex}.completion_status`,
|
|
25684
|
+
readState.completionStatus
|
|
25685
|
+
);
|
|
25686
|
+
}
|
|
25687
|
+
if (readState.progressMeasure !== void 0) {
|
|
25688
|
+
this._commonSetCMIValue(
|
|
25689
|
+
"SeedActivityObjectiveReadMap",
|
|
25690
|
+
true,
|
|
25691
|
+
`cmi.objectives.${objectiveIndex}.progress_measure`,
|
|
25692
|
+
String(readState.progressMeasure)
|
|
25693
|
+
);
|
|
25694
|
+
}
|
|
25695
|
+
if (readState.rawScore !== void 0) {
|
|
25696
|
+
this._commonSetCMIValue(
|
|
25697
|
+
"SeedActivityObjectiveReadMap",
|
|
25698
|
+
true,
|
|
25699
|
+
`cmi.objectives.${objectiveIndex}.score.raw`,
|
|
25700
|
+
readState.rawScore
|
|
25701
|
+
);
|
|
25702
|
+
}
|
|
25703
|
+
if (readState.minScore !== void 0) {
|
|
25704
|
+
this._commonSetCMIValue(
|
|
25705
|
+
"SeedActivityObjectiveReadMap",
|
|
25706
|
+
true,
|
|
25707
|
+
`cmi.objectives.${objectiveIndex}.score.min`,
|
|
25708
|
+
readState.minScore
|
|
25709
|
+
);
|
|
25710
|
+
}
|
|
25711
|
+
if (readState.maxScore !== void 0) {
|
|
25712
|
+
this._commonSetCMIValue(
|
|
25713
|
+
"SeedActivityObjectiveReadMap",
|
|
25714
|
+
true,
|
|
25715
|
+
`cmi.objectives.${objectiveIndex}.score.max`,
|
|
25716
|
+
readState.maxScore
|
|
25717
|
+
);
|
|
25718
|
+
}
|
|
25719
|
+
}
|
|
25720
|
+
/**
|
|
25721
|
+
* Return a manifest-defined objective id that can initialize cmi.objectives.n.id.
|
|
25722
|
+
*
|
|
25723
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.id
|
|
25724
|
+
*/
|
|
25725
|
+
getSeedableObjectiveId(activityObjective) {
|
|
25726
|
+
const objectiveId = activityObjective.id;
|
|
25727
|
+
if (typeof objectiveId !== "string" || objectiveId.trim() === "") {
|
|
25728
|
+
return null;
|
|
25729
|
+
}
|
|
25730
|
+
return objectiveId;
|
|
25731
|
+
}
|
|
25732
|
+
/**
|
|
25733
|
+
* Find an existing CMI objective id or create the next CMI objective array entry.
|
|
25734
|
+
*
|
|
25735
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives._count
|
|
25736
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.id
|
|
25737
|
+
*/
|
|
25738
|
+
findOrSeedCMIObjective(objectiveId) {
|
|
25739
|
+
const existingIndex = this.cmi.objectives.childArray.findIndex((objective) => {
|
|
25740
|
+
return objective.id === objectiveId;
|
|
25741
|
+
});
|
|
25742
|
+
if (existingIndex >= 0) {
|
|
25743
|
+
return existingIndex;
|
|
25744
|
+
}
|
|
25745
|
+
const index = this.cmi.objectives.childArray.length;
|
|
25746
|
+
const result = this._commonSetCMIValue(
|
|
25747
|
+
"SeedActivityObjective",
|
|
25748
|
+
true,
|
|
25749
|
+
`cmi.objectives.${index}.id`,
|
|
25750
|
+
objectiveId
|
|
25751
|
+
);
|
|
25752
|
+
return result === global_constants.SCORM_TRUE ? index : null;
|
|
25753
|
+
}
|
|
25754
|
+
/**
|
|
25755
|
+
* Translate a known activity objective satisfied status to cmi.objectives.n.success_status.
|
|
25756
|
+
*
|
|
25757
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.success_status
|
|
25758
|
+
*/
|
|
25759
|
+
getActivityObjectiveSuccessStatus(activityObjective) {
|
|
25760
|
+
if (activityObjective.progressStatus || activityObjective.satisfiedStatusKnown) {
|
|
25761
|
+
return activityObjective.satisfiedStatus ? SuccessStatus.PASSED : SuccessStatus.FAILED;
|
|
25762
|
+
}
|
|
25763
|
+
return null;
|
|
24864
25764
|
}
|
|
24865
25765
|
/**
|
|
24866
25766
|
* Getter for _version
|
|
@@ -24941,6 +25841,7 @@ class Scorm2004API extends BaseAPI {
|
|
|
24941
25841
|
);
|
|
24942
25842
|
if (result === global_constants.SCORM_TRUE && this._sequencingService) {
|
|
24943
25843
|
this._sequencingService.initialize();
|
|
25844
|
+
this.applyCurrentActivityObjectiveData();
|
|
24944
25845
|
}
|
|
24945
25846
|
if (result === global_constants.SCORM_TRUE) {
|
|
24946
25847
|
this._globalObjectiveManager.restoreGlobalObjectivesToCMI();
|
|
@@ -25195,7 +26096,7 @@ class Scorm2004API extends BaseAPI {
|
|
|
25195
26096
|
objective_id = objective ? objective.id : void 0;
|
|
25196
26097
|
}
|
|
25197
26098
|
const is_global = objective_id && this.settings.globalObjectiveIds?.includes(objective_id);
|
|
25198
|
-
if (is_global) {
|
|
26099
|
+
if (is_global && this.currentActivityAllowsGlobalObjectiveWrites()) {
|
|
25199
26100
|
const { index: global_index } = this._globalObjectiveManager.findOrCreateGlobalObjective(objective_id);
|
|
25200
26101
|
const global_element = CMIElement.replace(
|
|
25201
26102
|
element_base,
|
|
@@ -25210,6 +26111,15 @@ class Scorm2004API extends BaseAPI {
|
|
|
25210
26111
|
}
|
|
25211
26112
|
return this._commonSetCMIValue("SetValue", true, CMIElement, value);
|
|
25212
26113
|
}
|
|
26114
|
+
/**
|
|
26115
|
+
* Return whether the current activity can update shared global objectives.
|
|
26116
|
+
*
|
|
26117
|
+
* @spec SCORM 2004 4th Ed. SN 3.13.1 Tracked - when False, the LMS
|
|
26118
|
+
* "does not initialize, manage or access any tracking status information".
|
|
26119
|
+
*/
|
|
26120
|
+
currentActivityAllowsGlobalObjectiveWrites() {
|
|
26121
|
+
return this._sequencing?.getCurrentActivity()?.sequencingControls.tracked !== false;
|
|
26122
|
+
}
|
|
25213
26123
|
/**
|
|
25214
26124
|
* Gets or builds a new child element to add to the array
|
|
25215
26125
|
* @param {string} CMIElement
|
|
@@ -25315,7 +26225,8 @@ class Scorm2004API extends BaseAPI {
|
|
|
25315
26225
|
this.throwSCORMError(
|
|
25316
26226
|
CMIElement,
|
|
25317
26227
|
this._error_codes.VALUE_NOT_INITIALIZED ?? 403,
|
|
25318
|
-
`The data model element passed to GetValue (${CMIElement}) has not been initialized
|
|
26228
|
+
`The data model element passed to GetValue (${CMIElement}) has not been initialized.`,
|
|
26229
|
+
this.settings.uninitializedGetLogLevel
|
|
25319
26230
|
);
|
|
25320
26231
|
}
|
|
25321
26232
|
/**
|
|
@@ -25554,9 +26465,9 @@ class Scorm2004API extends BaseAPI {
|
|
|
25554
26465
|
this.loggingService,
|
|
25555
26466
|
sequencingConfig
|
|
25556
26467
|
);
|
|
25557
|
-
|
|
25558
|
-
this.
|
|
25559
|
-
|
|
26468
|
+
this._sequencingService.setEventListeners(
|
|
26469
|
+
this.buildSequencingEventListeners(settings?.sequencing?.eventListeners)
|
|
26470
|
+
);
|
|
25560
26471
|
this._globalObjectiveManager.updateSequencingService(this._sequencingService);
|
|
25561
26472
|
this._dataSerializer.updateSequencingService(this._sequencingService);
|
|
25562
26473
|
if (settings?.sequencingStatePersistence) {
|
|
@@ -25579,6 +26490,22 @@ class Scorm2004API extends BaseAPI {
|
|
|
25579
26490
|
this._sequencingService = null;
|
|
25580
26491
|
}
|
|
25581
26492
|
}
|
|
26493
|
+
/**
|
|
26494
|
+
* Wrap LMS-provided sequencing listeners with API-owned delivery bookkeeping.
|
|
26495
|
+
*
|
|
26496
|
+
* @spec SCORM 2004 4th Ed. SN DB.2 - Content Delivery Environment Process
|
|
26497
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold
|
|
26498
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
26499
|
+
*/
|
|
26500
|
+
buildSequencingEventListeners(listeners) {
|
|
26501
|
+
return {
|
|
26502
|
+
...listeners,
|
|
26503
|
+
onActivityDelivery: (activity) => {
|
|
26504
|
+
this.applyDeliveredActivityLaunchData(activity);
|
|
26505
|
+
listeners?.onActivityDelivery?.(activity);
|
|
26506
|
+
}
|
|
26507
|
+
};
|
|
26508
|
+
}
|
|
25582
26509
|
/**
|
|
25583
26510
|
* Get the sequencing service
|
|
25584
26511
|
* @return {SequencingService | null}
|
|
@@ -25589,10 +26516,13 @@ class Scorm2004API extends BaseAPI {
|
|
|
25589
26516
|
/**
|
|
25590
26517
|
* Set sequencing event listeners
|
|
25591
26518
|
* @param {SequencingEventListeners} listeners
|
|
26519
|
+
*
|
|
26520
|
+
* @spec SCORM 2004 4th Ed. SN DB.2 - Content Delivery Environment Process
|
|
26521
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5 / 4.2.17 - launch-static CMI data
|
|
25592
26522
|
*/
|
|
25593
26523
|
setSequencingEventListeners(listeners) {
|
|
25594
26524
|
if (this._sequencingService) {
|
|
25595
|
-
this._sequencingService.setEventListeners(listeners);
|
|
26525
|
+
this._sequencingService.setEventListeners(this.buildSequencingEventListeners(listeners));
|
|
25596
26526
|
}
|
|
25597
26527
|
}
|
|
25598
26528
|
/**
|