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/scorm2004.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,
|
|
@@ -139,12 +156,18 @@ function stringMatches(str, tester) {
|
|
|
139
156
|
}
|
|
140
157
|
return new RegExp(tester).test(str);
|
|
141
158
|
}
|
|
142
|
-
function memoize(fn, keyFn) {
|
|
159
|
+
function memoize(fn, keyFn, options) {
|
|
143
160
|
const cache = /* @__PURE__ */ new Map();
|
|
144
161
|
return ((...args) => {
|
|
145
162
|
const key = keyFn ? keyFn(...args) : JSON.stringify(args);
|
|
163
|
+
if (options?.maxKeyLength !== void 0 && key.length > options.maxKeyLength) {
|
|
164
|
+
return fn(...args);
|
|
165
|
+
}
|
|
146
166
|
return cache.has(key) ? cache.get(key) : (() => {
|
|
147
167
|
const result = fn(...args);
|
|
168
|
+
if (options?.maxEntries !== void 0 && cache.size >= options.maxEntries) {
|
|
169
|
+
cache.delete(cache.keys().next().value);
|
|
170
|
+
}
|
|
148
171
|
cache.set(key, result);
|
|
149
172
|
return result;
|
|
150
173
|
})();
|
|
@@ -730,8 +753,10 @@ const DefaultSettings = {
|
|
|
730
753
|
lmsCommitUrl: false,
|
|
731
754
|
dataCommitFormat: "json",
|
|
732
755
|
commitRequestDataType: "application/json;charset=UTF-8",
|
|
756
|
+
terminationCommitContentType: "text/plain;charset=UTF-8",
|
|
733
757
|
autoProgress: false,
|
|
734
758
|
logLevel: LogLevelEnum.ERROR,
|
|
759
|
+
uninitializedGetLogLevel: LogLevelEnum.WARN,
|
|
735
760
|
selfReportSessionTime: false,
|
|
736
761
|
alwaysSendTotalTime: false,
|
|
737
762
|
renderCommonCommitFields: false,
|
|
@@ -896,6 +921,8 @@ const scorm2004_regex = {
|
|
|
896
921
|
CMILang: "^([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?$|^$",
|
|
897
922
|
/** CMILangString250 - String with optional language tag, max 250 chars (RTE C.1.3) */
|
|
898
923
|
CMILangString250: "^({lang=([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?})?((?!{.*$).{0,250}$)?$",
|
|
924
|
+
/** CMILangString - Optional language tag, no length cap; RTE C.1.3 SPM is a floor and is deliberately not enforced */
|
|
925
|
+
CMILangString: "^({lang=([a-zA-Z]{1,8}|i|x)(-[a-zA-Z0-9-]{2,8})?})?((?!{.*$).*$)?$",
|
|
899
926
|
/** CMILangcr - Language tag pattern with content */
|
|
900
927
|
CMILangcr: "^(({lang=([a-zA-Z]{1,8}|i|x)?(-[a-zA-Z0-9-]{2,8})?}))(.*?)$",
|
|
901
928
|
/** CMILangString250cr - String with optional language tag (carriage return variant) */
|
|
@@ -1548,12 +1575,6 @@ const HIDE_LMS_UI_TOKENS = [
|
|
|
1548
1575
|
"suspendAll"
|
|
1549
1576
|
];
|
|
1550
1577
|
|
|
1551
|
-
var RuleConditionOperator = /* @__PURE__ */ ((RuleConditionOperator2) => {
|
|
1552
|
-
RuleConditionOperator2["NOT"] = "not";
|
|
1553
|
-
RuleConditionOperator2["AND"] = "and";
|
|
1554
|
-
RuleConditionOperator2["OR"] = "or";
|
|
1555
|
-
return RuleConditionOperator2;
|
|
1556
|
-
})(RuleConditionOperator || {});
|
|
1557
1578
|
var RuleActionType = /* @__PURE__ */ ((RuleActionType2) => {
|
|
1558
1579
|
RuleActionType2["SKIP"] = "skip";
|
|
1559
1580
|
RuleActionType2["DISABLED"] = "disabled";
|
|
@@ -1568,6 +1589,48 @@ var RuleActionType = /* @__PURE__ */ ((RuleActionType2) => {
|
|
|
1568
1589
|
RuleActionType2["EXIT"] = "exit";
|
|
1569
1590
|
return RuleActionType2;
|
|
1570
1591
|
})(RuleActionType || {});
|
|
1592
|
+
function kleeneNot(value) {
|
|
1593
|
+
if (value === "unknown") {
|
|
1594
|
+
return "unknown";
|
|
1595
|
+
}
|
|
1596
|
+
return !value;
|
|
1597
|
+
}
|
|
1598
|
+
function kleeneAnd(values) {
|
|
1599
|
+
let hasUnknown = false;
|
|
1600
|
+
for (const value of values) {
|
|
1601
|
+
if (value === false) {
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
if (value === "unknown") {
|
|
1605
|
+
hasUnknown = true;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return hasUnknown ? "unknown" : true;
|
|
1609
|
+
}
|
|
1610
|
+
function kleeneOr(values) {
|
|
1611
|
+
let hasUnknown = false;
|
|
1612
|
+
for (const value of values) {
|
|
1613
|
+
if (value === true) {
|
|
1614
|
+
return true;
|
|
1615
|
+
}
|
|
1616
|
+
if (value === "unknown") {
|
|
1617
|
+
hasUnknown = true;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
return hasUnknown ? "unknown" : false;
|
|
1621
|
+
}
|
|
1622
|
+
function combineRuleConditionResults(values, conditionCombination) {
|
|
1623
|
+
if (values.length === 0) {
|
|
1624
|
+
return false;
|
|
1625
|
+
}
|
|
1626
|
+
if (conditionCombination === "all" || conditionCombination === "and" /* AND */) {
|
|
1627
|
+
return kleeneAnd(values);
|
|
1628
|
+
}
|
|
1629
|
+
if (conditionCombination === "any" || conditionCombination === "or" /* OR */) {
|
|
1630
|
+
return kleeneOr(values);
|
|
1631
|
+
}
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1571
1634
|
class RuleCondition extends BaseCMI {
|
|
1572
1635
|
_condition = "always" /* ALWAYS */;
|
|
1573
1636
|
_operator = null;
|
|
@@ -1673,51 +1736,72 @@ class RuleCondition extends BaseCMI {
|
|
|
1673
1736
|
/**
|
|
1674
1737
|
* Evaluate the condition for an activity
|
|
1675
1738
|
* @param {Activity} activity - The activity to evaluate the condition for
|
|
1676
|
-
* @return {
|
|
1739
|
+
* @return {RuleConditionEvaluation} - True, false, or unknown per SCORM 2004 4th Ed.
|
|
1677
1740
|
*/
|
|
1678
1741
|
evaluate(activity) {
|
|
1679
1742
|
let result;
|
|
1743
|
+
const hasReferencedObjective = this._referencedObjective !== null;
|
|
1680
1744
|
const referencedObjective = this.resolveReferencedObjective(activity);
|
|
1681
1745
|
switch (this._condition) {
|
|
1682
1746
|
case "satisfied" /* SATISFIED */:
|
|
1683
1747
|
case "objectiveSatisfied" /* OBJECTIVE_SATISFIED */:
|
|
1684
|
-
if (referencedObjective) {
|
|
1685
|
-
result =
|
|
1748
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1749
|
+
result = false;
|
|
1750
|
+
} else if (referencedObjective) {
|
|
1751
|
+
result = referencedObjective.satisfiedStatusKnown || referencedObjective.progressStatus ? referencedObjective.satisfiedStatus === true : "unknown";
|
|
1752
|
+
} else if (activity.objectiveSatisfiedStatusKnown) {
|
|
1753
|
+
result = activity.objectiveSatisfiedStatus === true;
|
|
1754
|
+
} else if (activity.successStatus !== SuccessStatus.UNKNOWN) {
|
|
1755
|
+
result = activity.successStatus === SuccessStatus.PASSED;
|
|
1686
1756
|
} else {
|
|
1687
|
-
result =
|
|
1757
|
+
result = "unknown";
|
|
1688
1758
|
}
|
|
1689
1759
|
break;
|
|
1690
1760
|
case "objectiveStatusKnown" /* OBJECTIVE_STATUS_KNOWN */:
|
|
1691
|
-
result = referencedObjective ? !!referencedObjective.
|
|
1761
|
+
result = hasReferencedObjective && !referencedObjective ? false : referencedObjective ? !!referencedObjective.satisfiedStatusKnown : !!activity.objectiveSatisfiedStatusKnown;
|
|
1692
1762
|
break;
|
|
1693
1763
|
case "objectiveMeasureKnown" /* OBJECTIVE_MEASURE_KNOWN */:
|
|
1694
|
-
result = referencedObjective ? !!referencedObjective.measureStatus : !!activity.objectiveMeasureStatus;
|
|
1764
|
+
result = hasReferencedObjective && !referencedObjective ? false : referencedObjective ? !!referencedObjective.measureStatus : !!activity.objectiveMeasureStatus;
|
|
1695
1765
|
break;
|
|
1696
1766
|
case "objectiveMeasureGreaterThan" /* OBJECTIVE_MEASURE_GREATER_THAN */: {
|
|
1767
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1768
|
+
result = false;
|
|
1769
|
+
break;
|
|
1770
|
+
}
|
|
1697
1771
|
const greaterThanValue = this._parameters.get("threshold") || 0;
|
|
1698
1772
|
const measureStatus = referencedObjective ? referencedObjective.measureStatus : activity.objectiveMeasureStatus;
|
|
1699
1773
|
const measureValue = referencedObjective ? referencedObjective.normalizedMeasure : activity.objectiveNormalizedMeasure;
|
|
1700
|
-
result =
|
|
1774
|
+
result = measureStatus ? measureValue > greaterThanValue : "unknown";
|
|
1701
1775
|
break;
|
|
1702
1776
|
}
|
|
1703
1777
|
case "objectiveMeasureLessThan" /* OBJECTIVE_MEASURE_LESS_THAN */: {
|
|
1778
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1779
|
+
result = false;
|
|
1780
|
+
break;
|
|
1781
|
+
}
|
|
1704
1782
|
const lessThanValue = this._parameters.get("threshold") || 0;
|
|
1705
1783
|
const measureStatus = referencedObjective ? referencedObjective.measureStatus : activity.objectiveMeasureStatus;
|
|
1706
1784
|
const measureValue = referencedObjective ? referencedObjective.normalizedMeasure : activity.objectiveNormalizedMeasure;
|
|
1707
|
-
result =
|
|
1785
|
+
result = measureStatus ? measureValue < lessThanValue : "unknown";
|
|
1708
1786
|
break;
|
|
1709
1787
|
}
|
|
1710
1788
|
case "completed" /* COMPLETED */:
|
|
1711
1789
|
case "activityCompleted" /* ACTIVITY_COMPLETED */:
|
|
1712
|
-
if (referencedObjective) {
|
|
1713
|
-
result =
|
|
1790
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1791
|
+
result = false;
|
|
1792
|
+
} else if (referencedObjective) {
|
|
1793
|
+
result = referencedObjective.completionStatus === CompletionStatus.UNKNOWN ? "unknown" : referencedObjective.completionStatus === CompletionStatus.COMPLETED;
|
|
1794
|
+
} else if (activity.completionStatus === CompletionStatus.UNKNOWN) {
|
|
1795
|
+
result = "unknown";
|
|
1714
1796
|
} else {
|
|
1715
|
-
result = activity.
|
|
1797
|
+
result = activity.completionStatus === CompletionStatus.COMPLETED;
|
|
1716
1798
|
}
|
|
1717
1799
|
break;
|
|
1718
1800
|
case "progressKnown" /* PROGRESS_KNOWN */:
|
|
1719
1801
|
case "activityProgressKnown" /* ACTIVITY_PROGRESS_KNOWN */:
|
|
1720
|
-
if (referencedObjective) {
|
|
1802
|
+
if (hasReferencedObjective && !referencedObjective) {
|
|
1803
|
+
result = false;
|
|
1804
|
+
} else if (referencedObjective) {
|
|
1721
1805
|
result = referencedObjective.completionStatus !== CompletionStatus.UNKNOWN;
|
|
1722
1806
|
} else {
|
|
1723
1807
|
result = activity.completionStatus !== "unknown";
|
|
@@ -1746,7 +1830,7 @@ class RuleCondition extends BaseCMI {
|
|
|
1746
1830
|
break;
|
|
1747
1831
|
}
|
|
1748
1832
|
if (this._operator === "not" /* NOT */) {
|
|
1749
|
-
result =
|
|
1833
|
+
result = kleeneNot(result);
|
|
1750
1834
|
}
|
|
1751
1835
|
return result;
|
|
1752
1836
|
}
|
|
@@ -1953,15 +2037,10 @@ class SequencingRule extends BaseCMI {
|
|
|
1953
2037
|
* @return {boolean} - True if the rule conditions are met, false otherwise
|
|
1954
2038
|
*/
|
|
1955
2039
|
evaluate(activity) {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
return this._conditions.every((condition) => condition.evaluate(activity));
|
|
1961
|
-
} else if (this._conditionCombination === "any" || this._conditionCombination === "or" /* OR */) {
|
|
1962
|
-
return this._conditions.some((condition) => condition.evaluate(activity));
|
|
1963
|
-
}
|
|
1964
|
-
return false;
|
|
2040
|
+
return combineRuleConditionResults(
|
|
2041
|
+
this._conditions.map((condition) => condition.evaluate(activity)),
|
|
2042
|
+
this._conditionCombination
|
|
2043
|
+
) === true;
|
|
1965
2044
|
}
|
|
1966
2045
|
/**
|
|
1967
2046
|
* toJSON for SequencingRule
|
|
@@ -2794,19 +2873,10 @@ class RuleEvaluationEngine {
|
|
|
2794
2873
|
* Evaluates individual sequencing rule conditions
|
|
2795
2874
|
* @param {Activity} activity - The activity to evaluate the rule for
|
|
2796
2875
|
* @param {SequencingRule} rule - The rule to evaluate
|
|
2797
|
-
* @return {boolean} - True
|
|
2876
|
+
* @return {boolean} - True only when the rule evaluates to definite true
|
|
2798
2877
|
*/
|
|
2799
2878
|
checkRuleSubprocess(activity, rule) {
|
|
2800
|
-
|
|
2801
|
-
return true;
|
|
2802
|
-
}
|
|
2803
|
-
const conditionCombination = rule.conditionCombination;
|
|
2804
|
-
if (conditionCombination === "all" || conditionCombination === RuleConditionOperator.AND) {
|
|
2805
|
-
return rule.conditions.every((condition) => condition.evaluate(activity));
|
|
2806
|
-
} else if (conditionCombination === "any" || conditionCombination === RuleConditionOperator.OR) {
|
|
2807
|
-
return rule.conditions.some((condition) => condition.evaluate(activity));
|
|
2808
|
-
}
|
|
2809
|
-
return false;
|
|
2879
|
+
return rule.evaluate(activity);
|
|
2810
2880
|
}
|
|
2811
2881
|
/**
|
|
2812
2882
|
* Exit Action Rules Subprocess (TB.2.1)
|
|
@@ -3678,16 +3748,21 @@ class FlowTraversalService {
|
|
|
3678
3748
|
* @param {Activity} fromActivity - The activity to flow from
|
|
3679
3749
|
* @param {FlowSubprocessMode} direction - The flow direction
|
|
3680
3750
|
* @return {FlowSubprocessResult} - Result containing the deliverable activity
|
|
3751
|
+
* @spec SN Book: SB.2.3 (Flow Subprocess) - preserves the SB.2.1 effective traversal direction for SB.2.2.
|
|
3752
|
+
* @spec SN Book: SB.2.2 (Flow Activity Traversal Subprocess) - evaluates candidates using the effective direction returned by SB.2.1.
|
|
3681
3753
|
*/
|
|
3682
3754
|
flowSubprocess(fromActivity, direction) {
|
|
3683
3755
|
let candidateActivity = fromActivity;
|
|
3684
3756
|
let firstIteration = true;
|
|
3685
3757
|
let lastCandidateHadNoChildren = false;
|
|
3758
|
+
let currentDirection = direction;
|
|
3759
|
+
let forwardOnlyCluster = null;
|
|
3686
3760
|
while (candidateActivity) {
|
|
3687
3761
|
const traversalResult = this.flowTreeTraversalSubprocess(
|
|
3688
3762
|
candidateActivity,
|
|
3689
|
-
|
|
3690
|
-
firstIteration
|
|
3763
|
+
currentDirection,
|
|
3764
|
+
firstIteration,
|
|
3765
|
+
forwardOnlyCluster
|
|
3691
3766
|
);
|
|
3692
3767
|
if (!traversalResult.activity) {
|
|
3693
3768
|
let exceptionCode = null;
|
|
@@ -3705,17 +3780,22 @@ class FlowTraversalService {
|
|
|
3705
3780
|
traversalResult.endSequencingSession
|
|
3706
3781
|
);
|
|
3707
3782
|
}
|
|
3783
|
+
const effectiveDirection = traversalResult.direction || currentDirection;
|
|
3784
|
+
if (traversalResult.forwardOnlyCluster) {
|
|
3785
|
+
forwardOnlyCluster = traversalResult.forwardOnlyCluster;
|
|
3786
|
+
}
|
|
3708
3787
|
lastCandidateHadNoChildren = traversalResult.activity.children.length > 0 && traversalResult.activity.getAvailableChildren().length === 0;
|
|
3709
3788
|
const deliverable = this.flowActivityTraversalSubprocess(
|
|
3710
3789
|
traversalResult.activity,
|
|
3711
|
-
|
|
3790
|
+
effectiveDirection === FlowSubprocessMode.FORWARD,
|
|
3712
3791
|
true,
|
|
3713
|
-
|
|
3792
|
+
effectiveDirection
|
|
3714
3793
|
);
|
|
3715
3794
|
if (deliverable) {
|
|
3716
3795
|
return new FlowSubprocessResult(deliverable, true, null, false);
|
|
3717
3796
|
}
|
|
3718
3797
|
candidateActivity = traversalResult.activity;
|
|
3798
|
+
currentDirection = effectiveDirection;
|
|
3719
3799
|
firstIteration = false;
|
|
3720
3800
|
}
|
|
3721
3801
|
return new FlowSubprocessResult(null, false, null, false);
|
|
@@ -3726,11 +3806,13 @@ class FlowTraversalService {
|
|
|
3726
3806
|
* @param {Activity} fromActivity - The activity to traverse from
|
|
3727
3807
|
* @param {FlowSubprocessMode} direction - The traversal direction
|
|
3728
3808
|
* @param {boolean} skipChildren - Whether to skip checking children
|
|
3809
|
+
* @param {Activity | null} forwardTraversalBoundary - Cluster boundary for an SB.2.1 forwardOnly direction reversal
|
|
3729
3810
|
* @return {FlowTreeTraversalResult} - The next activity and flags
|
|
3811
|
+
* @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.
|
|
3730
3812
|
*/
|
|
3731
|
-
flowTreeTraversalSubprocess(fromActivity, direction, skipChildren = false) {
|
|
3813
|
+
flowTreeTraversalSubprocess(fromActivity, direction, skipChildren = false, forwardTraversalBoundary = null) {
|
|
3732
3814
|
if (direction === FlowSubprocessMode.FORWARD) {
|
|
3733
|
-
return this.traverseForward(fromActivity, skipChildren);
|
|
3815
|
+
return this.traverseForward(fromActivity, skipChildren, forwardTraversalBoundary);
|
|
3734
3816
|
} else {
|
|
3735
3817
|
return this.traverseBackward(fromActivity);
|
|
3736
3818
|
}
|
|
@@ -3739,10 +3821,15 @@ class FlowTraversalService {
|
|
|
3739
3821
|
* Traverse forward in the activity tree
|
|
3740
3822
|
* @param {Activity} fromActivity - Starting activity
|
|
3741
3823
|
* @param {boolean} skipChildren - Whether to skip children
|
|
3824
|
+
* @param {Activity | null} forwardTraversalBoundary - Cluster boundary for an SB.2.1 forwardOnly direction reversal
|
|
3742
3825
|
* @return {FlowTreeTraversalResult}
|
|
3826
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - a reversed Forward traversal from a forwardOnly cluster remains within that cluster.
|
|
3743
3827
|
*/
|
|
3744
|
-
traverseForward(fromActivity, skipChildren) {
|
|
3745
|
-
if (
|
|
3828
|
+
traverseForward(fromActivity, skipChildren, forwardTraversalBoundary = null) {
|
|
3829
|
+
if (forwardTraversalBoundary && !this.isDescendantOfOrSelf(fromActivity, forwardTraversalBoundary)) {
|
|
3830
|
+
return { activity: null, endSequencingSession: false };
|
|
3831
|
+
}
|
|
3832
|
+
if (!forwardTraversalBoundary && skipChildren && this.isActivityLastOverall(fromActivity)) {
|
|
3746
3833
|
if (this.activityTree.root) {
|
|
3747
3834
|
this.terminateDescendentAttempts(this.activityTree.root);
|
|
3748
3835
|
}
|
|
@@ -3761,6 +3848,9 @@ class FlowTraversalService {
|
|
|
3761
3848
|
if (nextSibling) {
|
|
3762
3849
|
return { activity: nextSibling, endSequencingSession: false };
|
|
3763
3850
|
}
|
|
3851
|
+
if (forwardTraversalBoundary && (current === forwardTraversalBoundary || current.parent === forwardTraversalBoundary)) {
|
|
3852
|
+
return { activity: null, endSequencingSession: false };
|
|
3853
|
+
}
|
|
3764
3854
|
current = current.parent;
|
|
3765
3855
|
}
|
|
3766
3856
|
if (this.activityTree.root) {
|
|
@@ -3772,6 +3862,7 @@ class FlowTraversalService {
|
|
|
3772
3862
|
* Traverse backward in the activity tree
|
|
3773
3863
|
* @param {Activity} fromActivity - Starting activity
|
|
3774
3864
|
* @return {FlowTreeTraversalResult}
|
|
3865
|
+
* @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.
|
|
3775
3866
|
*/
|
|
3776
3867
|
traverseBackward(fromActivity) {
|
|
3777
3868
|
if (fromActivity.parent && fromActivity.parent.sequencingControls.forwardOnly) {
|
|
@@ -3779,10 +3870,7 @@ class FlowTraversalService {
|
|
|
3779
3870
|
}
|
|
3780
3871
|
const previousSibling = this.activityTree.getPreviousSibling(fromActivity);
|
|
3781
3872
|
if (previousSibling) {
|
|
3782
|
-
return
|
|
3783
|
-
activity: this.getLastDescendant(previousSibling),
|
|
3784
|
-
endSequencingSession: false
|
|
3785
|
-
};
|
|
3873
|
+
return this.getBackwardTraversalEntry(previousSibling);
|
|
3786
3874
|
}
|
|
3787
3875
|
let current = fromActivity;
|
|
3788
3876
|
let ancestorIterations = 0;
|
|
@@ -3793,38 +3881,64 @@ class FlowTraversalService {
|
|
|
3793
3881
|
}
|
|
3794
3882
|
const parentPreviousSibling = this.activityTree.getPreviousSibling(current.parent);
|
|
3795
3883
|
if (parentPreviousSibling) {
|
|
3796
|
-
return
|
|
3797
|
-
activity: this.getLastDescendant(parentPreviousSibling),
|
|
3798
|
-
endSequencingSession: false
|
|
3799
|
-
};
|
|
3884
|
+
return this.getBackwardTraversalEntry(parentPreviousSibling);
|
|
3800
3885
|
}
|
|
3801
3886
|
current = current.parent;
|
|
3802
3887
|
}
|
|
3803
3888
|
return { activity: null, endSequencingSession: false };
|
|
3804
3889
|
}
|
|
3805
3890
|
/**
|
|
3806
|
-
* Get the
|
|
3891
|
+
* Get the activity entered by backward traversal.
|
|
3807
3892
|
* @param {Activity} activity - The activity
|
|
3808
|
-
* @return {
|
|
3893
|
+
* @return {FlowTreeTraversalResult} - The entered activity and effective direction
|
|
3894
|
+
* @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.
|
|
3809
3895
|
*/
|
|
3810
|
-
|
|
3811
|
-
let
|
|
3896
|
+
getBackwardTraversalEntry(activity) {
|
|
3897
|
+
let enteredActivity = activity;
|
|
3812
3898
|
let iterations = 0;
|
|
3813
3899
|
const maxIterations = 1e4;
|
|
3814
3900
|
while (true) {
|
|
3815
3901
|
if (++iterations > maxIterations) {
|
|
3816
|
-
throw new Error("Infinite loop detected while getting
|
|
3902
|
+
throw new Error("Infinite loop detected while getting backward traversal entry");
|
|
3817
3903
|
}
|
|
3818
|
-
this.ensureSelectionAndRandomization(
|
|
3819
|
-
const children =
|
|
3904
|
+
this.ensureSelectionAndRandomization(enteredActivity);
|
|
3905
|
+
const children = enteredActivity.getAvailableChildren();
|
|
3820
3906
|
if (children.length === 0) {
|
|
3821
3907
|
break;
|
|
3822
3908
|
}
|
|
3909
|
+
if (enteredActivity.sequencingControls.forwardOnly) {
|
|
3910
|
+
return {
|
|
3911
|
+
activity: children[0] || null,
|
|
3912
|
+
endSequencingSession: false,
|
|
3913
|
+
direction: FlowSubprocessMode.FORWARD,
|
|
3914
|
+
forwardOnlyCluster: enteredActivity
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3823
3917
|
const lastChild = children[children.length - 1];
|
|
3824
3918
|
if (!lastChild) break;
|
|
3825
|
-
|
|
3919
|
+
enteredActivity = lastChild;
|
|
3920
|
+
}
|
|
3921
|
+
return {
|
|
3922
|
+
activity: enteredActivity,
|
|
3923
|
+
endSequencingSession: false
|
|
3924
|
+
};
|
|
3925
|
+
}
|
|
3926
|
+
/**
|
|
3927
|
+
* Check whether an activity is the same as or beneath an ancestor.
|
|
3928
|
+
* @param {Activity} activity - The activity to check
|
|
3929
|
+
* @param {Activity} ancestor - The expected ancestor
|
|
3930
|
+
* @return {boolean} - True when activity is within ancestor
|
|
3931
|
+
* @spec SN Book: SB.2.1 (Flow Tree Traversal Subprocess) - bounds Forward traversal after a forwardOnly direction reversal to the entered cluster.
|
|
3932
|
+
*/
|
|
3933
|
+
isDescendantOfOrSelf(activity, ancestor) {
|
|
3934
|
+
let current = activity;
|
|
3935
|
+
while (current) {
|
|
3936
|
+
if (current === ancestor) {
|
|
3937
|
+
return true;
|
|
3938
|
+
}
|
|
3939
|
+
current = current.parent;
|
|
3826
3940
|
}
|
|
3827
|
-
return
|
|
3941
|
+
return false;
|
|
3828
3942
|
}
|
|
3829
3943
|
/**
|
|
3830
3944
|
* Flow Activity Traversal Subprocess (SB.2.2)
|
|
@@ -5003,7 +5117,9 @@ class AsynchronousHttpService {
|
|
|
5003
5117
|
*/
|
|
5004
5118
|
async performBeacon(url, params) {
|
|
5005
5119
|
const { body, contentType } = this._prepareRequestBody(params);
|
|
5006
|
-
const
|
|
5120
|
+
const beaconContentType = Array.isArray(params) ? contentType : this.settings.terminationCommitContentType;
|
|
5121
|
+
this._warnIfBeaconContentTypeUnsafe(url, beaconContentType);
|
|
5122
|
+
const beaconSuccess = navigator.sendBeacon(url, new Blob([body], { type: beaconContentType }));
|
|
5007
5123
|
return Promise.resolve({
|
|
5008
5124
|
status: beaconSuccess ? 200 : 0,
|
|
5009
5125
|
ok: beaconSuccess,
|
|
@@ -5017,6 +5133,14 @@ class AsynchronousHttpService {
|
|
|
5017
5133
|
})
|
|
5018
5134
|
});
|
|
5019
5135
|
}
|
|
5136
|
+
_warnIfBeaconContentTypeUnsafe(url, contentType) {
|
|
5137
|
+
if (isCrossOriginUrl(url) && !isCorsSafelistedContentType(contentType)) {
|
|
5138
|
+
this.settings.onLogMessage?.(
|
|
5139
|
+
LogLevelEnum.WARN,
|
|
5140
|
+
`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.`
|
|
5141
|
+
);
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5020
5144
|
/**
|
|
5021
5145
|
* Transforms the response from the LMS to a ResultObject
|
|
5022
5146
|
* @param {Response} response - The response from the LMS
|
|
@@ -5684,14 +5808,14 @@ class ErrorHandlingService {
|
|
|
5684
5808
|
* @param {string} message - The error message
|
|
5685
5809
|
* @throws {ValidationError} - If throwException is true, throws a ValidationError
|
|
5686
5810
|
*/
|
|
5687
|
-
throwSCORMError(CMIElement, errorNumber, message) {
|
|
5811
|
+
throwSCORMError(CMIElement, errorNumber, message, messageLevel = LogLevelEnum.ERROR) {
|
|
5688
5812
|
this._lastDiagnostic = message || "";
|
|
5689
5813
|
if (!message) {
|
|
5690
5814
|
message = this._getLmsErrorMessageDetails(errorNumber, true);
|
|
5691
5815
|
}
|
|
5692
5816
|
const formattedMessage = `SCORM Error ${errorNumber}: ${message}${CMIElement ? ` [Element: ${CMIElement}]` : ""}`;
|
|
5693
|
-
this._apiLog("throwSCORMError", errorNumber + ": " + message,
|
|
5694
|
-
this._loggingService.
|
|
5817
|
+
this._apiLog("throwSCORMError", errorNumber + ": " + message, messageLevel, CMIElement);
|
|
5818
|
+
this._loggingService.log(messageLevel, formattedMessage);
|
|
5695
5819
|
this._lastErrorCode = String(errorNumber);
|
|
5696
5820
|
}
|
|
5697
5821
|
/**
|
|
@@ -6374,7 +6498,9 @@ const checkValidFormat = memoize(
|
|
|
6374
6498
|
(CMIElement, value, regexPattern, errorCode, _errorClass, allowEmptyString) => {
|
|
6375
6499
|
const valueKey = typeof value === "string" ? value : `[${typeof value}]`;
|
|
6376
6500
|
return `${CMIElement}:${valueKey}:${regexPattern}:${errorCode}:${allowEmptyString || false}`;
|
|
6377
|
-
}
|
|
6501
|
+
},
|
|
6502
|
+
// Normal capped CMI values and regexes fit within 2000 characters; large uncapped values bypass caching.
|
|
6503
|
+
{ maxEntries: 1e3, maxKeyLength: 2e3 }
|
|
6378
6504
|
);
|
|
6379
6505
|
const checkValidRange = memoize(
|
|
6380
6506
|
(CMIElement, value, rangePattern, errorCode, errorClass) => {
|
|
@@ -6397,7 +6523,8 @@ const checkValidRange = memoize(
|
|
|
6397
6523
|
},
|
|
6398
6524
|
// Custom key function that excludes the error class from the cache key
|
|
6399
6525
|
// since it can't be stringified and doesn't affect the validation result
|
|
6400
|
-
(CMIElement, value, rangePattern, errorCode, _errorClass) => `${CMIElement}:${value}:${rangePattern}:${errorCode}
|
|
6526
|
+
(CMIElement, value, rangePattern, errorCode, _errorClass) => `${CMIElement}:${value}:${rangePattern}:${errorCode}`,
|
|
6527
|
+
{ maxEntries: 1e3 }
|
|
6401
6528
|
);
|
|
6402
6529
|
|
|
6403
6530
|
function check2004ValidFormat(CMIElement, value, regexPattern, allowEmptyString) {
|
|
@@ -6906,6 +7033,12 @@ class ActivityObjective {
|
|
|
6906
7033
|
// objectives. It serves as a validity gate for other synced properties.
|
|
6907
7034
|
_measureStatus = false;
|
|
6908
7035
|
_normalizedMeasure = 0;
|
|
7036
|
+
_rawScore = "";
|
|
7037
|
+
_rawScoreKnown = false;
|
|
7038
|
+
_minScore = "";
|
|
7039
|
+
_minScoreKnown = false;
|
|
7040
|
+
_maxScore = "";
|
|
7041
|
+
_maxScoreKnown = false;
|
|
6909
7042
|
_progressMeasure = 0;
|
|
6910
7043
|
_progressMeasureStatus = false;
|
|
6911
7044
|
_completionStatus = CompletionStatus.UNKNOWN;
|
|
@@ -6915,6 +7048,9 @@ class ActivityObjective {
|
|
|
6915
7048
|
_normalizedMeasureDirty = false;
|
|
6916
7049
|
_completionStatusDirty = false;
|
|
6917
7050
|
_progressMeasureDirty = false;
|
|
7051
|
+
_rawScoreDirty = false;
|
|
7052
|
+
_minScoreDirty = false;
|
|
7053
|
+
_maxScoreDirty = false;
|
|
6918
7054
|
constructor(id, options = {}) {
|
|
6919
7055
|
this._id = id;
|
|
6920
7056
|
this._description = options.description ?? null;
|
|
@@ -6960,6 +7096,8 @@ class ActivityObjective {
|
|
|
6960
7096
|
if (this._satisfiedStatus !== value) {
|
|
6961
7097
|
this._satisfiedStatus = value;
|
|
6962
7098
|
this._satisfiedStatusDirty = true;
|
|
7099
|
+
this._satisfiedStatusKnown = true;
|
|
7100
|
+
this._progressStatus = true;
|
|
6963
7101
|
}
|
|
6964
7102
|
}
|
|
6965
7103
|
get satisfiedStatusKnown() {
|
|
@@ -6983,6 +7121,114 @@ class ActivityObjective {
|
|
|
6983
7121
|
this._normalizedMeasureDirty = true;
|
|
6984
7122
|
}
|
|
6985
7123
|
}
|
|
7124
|
+
/**
|
|
7125
|
+
* Return the known raw score value held for ADLSEQ objective score mapping.
|
|
7126
|
+
*
|
|
7127
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw score mapInfo
|
|
7128
|
+
*/
|
|
7129
|
+
get rawScore() {
|
|
7130
|
+
return this._rawScore;
|
|
7131
|
+
}
|
|
7132
|
+
/**
|
|
7133
|
+
* Store the RTE raw score associated with this objective.
|
|
7134
|
+
*
|
|
7135
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - raw score mapInfo
|
|
7136
|
+
*/
|
|
7137
|
+
set rawScore(value) {
|
|
7138
|
+
if (this._rawScore !== value) {
|
|
7139
|
+
this._rawScore = value;
|
|
7140
|
+
this._rawScoreDirty = true;
|
|
7141
|
+
}
|
|
7142
|
+
this._rawScoreKnown = value !== "";
|
|
7143
|
+
}
|
|
7144
|
+
/**
|
|
7145
|
+
* Return whether this objective's raw score is known.
|
|
7146
|
+
*
|
|
7147
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw score known state
|
|
7148
|
+
*/
|
|
7149
|
+
get rawScoreKnown() {
|
|
7150
|
+
return this._rawScoreKnown;
|
|
7151
|
+
}
|
|
7152
|
+
/**
|
|
7153
|
+
* Set whether this objective's raw score is known.
|
|
7154
|
+
*
|
|
7155
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw score known state
|
|
7156
|
+
*/
|
|
7157
|
+
set rawScoreKnown(value) {
|
|
7158
|
+
this._rawScoreKnown = value;
|
|
7159
|
+
}
|
|
7160
|
+
/**
|
|
7161
|
+
* Return the known minimum score value held for ADLSEQ objective score mapping.
|
|
7162
|
+
*
|
|
7163
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - min score mapInfo
|
|
7164
|
+
*/
|
|
7165
|
+
get minScore() {
|
|
7166
|
+
return this._minScore;
|
|
7167
|
+
}
|
|
7168
|
+
/**
|
|
7169
|
+
* Store the RTE minimum score associated with this objective.
|
|
7170
|
+
*
|
|
7171
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - min score mapInfo
|
|
7172
|
+
*/
|
|
7173
|
+
set minScore(value) {
|
|
7174
|
+
if (this._minScore !== value) {
|
|
7175
|
+
this._minScore = value;
|
|
7176
|
+
this._minScoreDirty = true;
|
|
7177
|
+
}
|
|
7178
|
+
this._minScoreKnown = value !== "";
|
|
7179
|
+
}
|
|
7180
|
+
/**
|
|
7181
|
+
* Return whether this objective's minimum score is known.
|
|
7182
|
+
*
|
|
7183
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - min score known state
|
|
7184
|
+
*/
|
|
7185
|
+
get minScoreKnown() {
|
|
7186
|
+
return this._minScoreKnown;
|
|
7187
|
+
}
|
|
7188
|
+
/**
|
|
7189
|
+
* Set whether this objective's minimum score is known.
|
|
7190
|
+
*
|
|
7191
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - min score known state
|
|
7192
|
+
*/
|
|
7193
|
+
set minScoreKnown(value) {
|
|
7194
|
+
this._minScoreKnown = value;
|
|
7195
|
+
}
|
|
7196
|
+
/**
|
|
7197
|
+
* Return the known maximum score value held for ADLSEQ objective score mapping.
|
|
7198
|
+
*
|
|
7199
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - max score mapInfo
|
|
7200
|
+
*/
|
|
7201
|
+
get maxScore() {
|
|
7202
|
+
return this._maxScore;
|
|
7203
|
+
}
|
|
7204
|
+
/**
|
|
7205
|
+
* Store the RTE maximum score associated with this objective.
|
|
7206
|
+
*
|
|
7207
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - max score mapInfo
|
|
7208
|
+
*/
|
|
7209
|
+
set maxScore(value) {
|
|
7210
|
+
if (this._maxScore !== value) {
|
|
7211
|
+
this._maxScore = value;
|
|
7212
|
+
this._maxScoreDirty = true;
|
|
7213
|
+
}
|
|
7214
|
+
this._maxScoreKnown = value !== "";
|
|
7215
|
+
}
|
|
7216
|
+
/**
|
|
7217
|
+
* Return whether this objective's maximum score is known.
|
|
7218
|
+
*
|
|
7219
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - max score known state
|
|
7220
|
+
*/
|
|
7221
|
+
get maxScoreKnown() {
|
|
7222
|
+
return this._maxScoreKnown;
|
|
7223
|
+
}
|
|
7224
|
+
/**
|
|
7225
|
+
* Set whether this objective's maximum score is known.
|
|
7226
|
+
*
|
|
7227
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - max score known state
|
|
7228
|
+
*/
|
|
7229
|
+
set maxScoreKnown(value) {
|
|
7230
|
+
this._maxScoreKnown = value;
|
|
7231
|
+
}
|
|
6986
7232
|
get progressMeasure() {
|
|
6987
7233
|
return this._progressMeasure;
|
|
6988
7234
|
}
|
|
@@ -7013,6 +7259,11 @@ class ActivityObjective {
|
|
|
7013
7259
|
set progressStatus(value) {
|
|
7014
7260
|
this._progressStatus = value;
|
|
7015
7261
|
}
|
|
7262
|
+
/**
|
|
7263
|
+
* Report whether a local objective field has changed since the last global write.
|
|
7264
|
+
*
|
|
7265
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - write maps use known local objective data
|
|
7266
|
+
*/
|
|
7016
7267
|
isDirty(property) {
|
|
7017
7268
|
switch (property) {
|
|
7018
7269
|
case "satisfiedStatus":
|
|
@@ -7023,8 +7274,19 @@ class ActivityObjective {
|
|
|
7023
7274
|
return this._completionStatusDirty;
|
|
7024
7275
|
case "progressMeasure":
|
|
7025
7276
|
return this._progressMeasureDirty;
|
|
7277
|
+
case "rawScore":
|
|
7278
|
+
return this._rawScoreDirty;
|
|
7279
|
+
case "minScore":
|
|
7280
|
+
return this._minScoreDirty;
|
|
7281
|
+
case "maxScore":
|
|
7282
|
+
return this._maxScoreDirty;
|
|
7026
7283
|
}
|
|
7027
7284
|
}
|
|
7285
|
+
/**
|
|
7286
|
+
* Clear a local objective dirty flag after a successful global write.
|
|
7287
|
+
*
|
|
7288
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - write maps update global objective state
|
|
7289
|
+
*/
|
|
7028
7290
|
clearDirty(property) {
|
|
7029
7291
|
switch (property) {
|
|
7030
7292
|
case "satisfiedStatus":
|
|
@@ -7039,13 +7301,31 @@ class ActivityObjective {
|
|
|
7039
7301
|
case "progressMeasure":
|
|
7040
7302
|
this._progressMeasureDirty = false;
|
|
7041
7303
|
break;
|
|
7304
|
+
case "rawScore":
|
|
7305
|
+
this._rawScoreDirty = false;
|
|
7306
|
+
break;
|
|
7307
|
+
case "minScore":
|
|
7308
|
+
this._minScoreDirty = false;
|
|
7309
|
+
break;
|
|
7310
|
+
case "maxScore":
|
|
7311
|
+
this._maxScoreDirty = false;
|
|
7312
|
+
break;
|
|
7042
7313
|
}
|
|
7043
7314
|
}
|
|
7315
|
+
/**
|
|
7316
|
+
* Clear all write-map dirty flags for this objective.
|
|
7317
|
+
*
|
|
7318
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - objective mapInfo writes are field-specific
|
|
7319
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score mapInfo writes are field-specific
|
|
7320
|
+
*/
|
|
7044
7321
|
clearAllDirty() {
|
|
7045
7322
|
this._satisfiedStatusDirty = false;
|
|
7046
7323
|
this._normalizedMeasureDirty = false;
|
|
7047
7324
|
this._completionStatusDirty = false;
|
|
7048
7325
|
this._progressMeasureDirty = false;
|
|
7326
|
+
this._rawScoreDirty = false;
|
|
7327
|
+
this._minScoreDirty = false;
|
|
7328
|
+
this._maxScoreDirty = false;
|
|
7049
7329
|
}
|
|
7050
7330
|
/**
|
|
7051
7331
|
* Initialize objective values from CMI data transfer
|
|
@@ -7055,6 +7335,8 @@ class ActivityObjective {
|
|
|
7055
7335
|
* @param satisfiedStatus - The satisfied status from CMI
|
|
7056
7336
|
* @param normalizedMeasure - The normalized measure from CMI
|
|
7057
7337
|
* @param measureStatus - Whether measure is valid
|
|
7338
|
+
*
|
|
7339
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - objective satisfaction and measure transfer
|
|
7058
7340
|
*/
|
|
7059
7341
|
initializeFromCMI(satisfiedStatus, normalizedMeasure, measureStatus) {
|
|
7060
7342
|
this._satisfiedStatus = satisfiedStatus;
|
|
@@ -7063,17 +7345,93 @@ class ActivityObjective {
|
|
|
7063
7345
|
this._normalizedMeasureDirty = true;
|
|
7064
7346
|
this._measureStatus = measureStatus;
|
|
7065
7347
|
}
|
|
7348
|
+
/**
|
|
7349
|
+
* Initialize raw/min/max objective score values from RTE data transfer.
|
|
7350
|
+
*
|
|
7351
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - objective score transfer
|
|
7352
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score mapInfo writes
|
|
7353
|
+
*/
|
|
7354
|
+
initializeScoreFromCMI(score) {
|
|
7355
|
+
if (score.rawScore !== void 0 && score.rawScore !== "") {
|
|
7356
|
+
this._rawScore = score.rawScore;
|
|
7357
|
+
this._rawScoreKnown = true;
|
|
7358
|
+
this._rawScoreDirty = true;
|
|
7359
|
+
}
|
|
7360
|
+
if (score.minScore !== void 0 && score.minScore !== "") {
|
|
7361
|
+
this._minScore = score.minScore;
|
|
7362
|
+
this._minScoreKnown = true;
|
|
7363
|
+
this._minScoreDirty = true;
|
|
7364
|
+
}
|
|
7365
|
+
if (score.maxScore !== void 0 && score.maxScore !== "") {
|
|
7366
|
+
this._maxScore = score.maxScore;
|
|
7367
|
+
this._maxScoreKnown = true;
|
|
7368
|
+
this._maxScoreDirty = true;
|
|
7369
|
+
}
|
|
7370
|
+
}
|
|
7371
|
+
/**
|
|
7372
|
+
* Apply read-mapped global objective state without marking the values dirty.
|
|
7373
|
+
*
|
|
7374
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps provide access to global objective state
|
|
7375
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score read maps are access-only
|
|
7376
|
+
*/
|
|
7377
|
+
applyReadMappedState(state) {
|
|
7378
|
+
if (state.satisfiedStatus !== void 0) {
|
|
7379
|
+
this._satisfiedStatus = state.satisfiedStatus;
|
|
7380
|
+
this._satisfiedStatusKnown = true;
|
|
7381
|
+
this._progressStatus = true;
|
|
7382
|
+
}
|
|
7383
|
+
if (state.normalizedMeasure !== void 0) {
|
|
7384
|
+
this._normalizedMeasure = state.normalizedMeasure;
|
|
7385
|
+
this._measureStatus = true;
|
|
7386
|
+
}
|
|
7387
|
+
if (state.completionStatus !== void 0) {
|
|
7388
|
+
this._completionStatus = state.completionStatus;
|
|
7389
|
+
}
|
|
7390
|
+
if (state.progressMeasure !== void 0) {
|
|
7391
|
+
this._progressMeasure = state.progressMeasure;
|
|
7392
|
+
this._progressMeasureStatus = true;
|
|
7393
|
+
}
|
|
7394
|
+
if (state.rawScore !== void 0) {
|
|
7395
|
+
this._rawScore = state.rawScore;
|
|
7396
|
+
this._rawScoreKnown = true;
|
|
7397
|
+
}
|
|
7398
|
+
if (state.minScore !== void 0) {
|
|
7399
|
+
this._minScore = state.minScore;
|
|
7400
|
+
this._minScoreKnown = true;
|
|
7401
|
+
}
|
|
7402
|
+
if (state.maxScore !== void 0) {
|
|
7403
|
+
this._maxScore = state.maxScore;
|
|
7404
|
+
this._maxScoreKnown = true;
|
|
7405
|
+
}
|
|
7406
|
+
}
|
|
7407
|
+
/**
|
|
7408
|
+
* Reset local objective state for a fresh activity attempt.
|
|
7409
|
+
*
|
|
7410
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - unknown objective state before tracking data exists
|
|
7411
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score map fields are unknown until transferred or read
|
|
7412
|
+
*/
|
|
7066
7413
|
resetState() {
|
|
7067
7414
|
this._satisfiedStatus = false;
|
|
7068
7415
|
this._satisfiedStatusKnown = false;
|
|
7069
7416
|
this._measureStatus = false;
|
|
7070
7417
|
this._normalizedMeasure = 0;
|
|
7418
|
+
this._rawScore = "";
|
|
7419
|
+
this._rawScoreKnown = false;
|
|
7420
|
+
this._minScore = "";
|
|
7421
|
+
this._minScoreKnown = false;
|
|
7422
|
+
this._maxScore = "";
|
|
7423
|
+
this._maxScoreKnown = false;
|
|
7071
7424
|
this._progressMeasure = 0;
|
|
7072
7425
|
this._progressMeasureStatus = false;
|
|
7073
7426
|
this._completionStatus = CompletionStatus.UNKNOWN;
|
|
7074
7427
|
this._progressStatus = false;
|
|
7075
7428
|
this.clearAllDirty();
|
|
7076
7429
|
}
|
|
7430
|
+
/**
|
|
7431
|
+
* Copy primary activity objective state back into the primary objective model.
|
|
7432
|
+
*
|
|
7433
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - primary objective state is available to sequencing
|
|
7434
|
+
*/
|
|
7077
7435
|
updateFromActivity(activity) {
|
|
7078
7436
|
if (this._satisfiedStatus !== activity.objectiveSatisfiedStatus) {
|
|
7079
7437
|
this._satisfiedStatus = activity.objectiveSatisfiedStatus;
|
|
@@ -7095,6 +7453,11 @@ class ActivityObjective {
|
|
|
7095
7453
|
this._completionStatusDirty = true;
|
|
7096
7454
|
}
|
|
7097
7455
|
}
|
|
7456
|
+
/**
|
|
7457
|
+
* Apply primary objective state to the owning activity for sequencing rules and rollup.
|
|
7458
|
+
*
|
|
7459
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - primary objective contributes activity state
|
|
7460
|
+
*/
|
|
7098
7461
|
applyToActivity(activity) {
|
|
7099
7462
|
if (!this._isPrimary) {
|
|
7100
7463
|
return;
|
|
@@ -7105,7 +7468,8 @@ class ActivityObjective {
|
|
|
7105
7468
|
this._normalizedMeasure,
|
|
7106
7469
|
this._progressMeasure,
|
|
7107
7470
|
this._progressMeasureStatus,
|
|
7108
|
-
this._completionStatus
|
|
7471
|
+
this._completionStatus,
|
|
7472
|
+
this._progressStatus || this._satisfiedStatusKnown
|
|
7109
7473
|
);
|
|
7110
7474
|
}
|
|
7111
7475
|
}
|
|
@@ -8175,7 +8539,7 @@ class Activity extends BaseCMI {
|
|
|
8175
8539
|
/**
|
|
8176
8540
|
* Setter for primary objective
|
|
8177
8541
|
* @param {ActivityObjective | null} objective
|
|
8178
|
-
|
|
8542
|
+
*/
|
|
8179
8543
|
set primaryObjective(objective) {
|
|
8180
8544
|
this._primaryObjective = objective;
|
|
8181
8545
|
if (this._primaryObjective) {
|
|
@@ -8197,7 +8561,7 @@ class Activity extends BaseCMI {
|
|
|
8197
8561
|
/**
|
|
8198
8562
|
* Replace objectives collection
|
|
8199
8563
|
* @param {ActivityObjective[]} objectives
|
|
8200
|
-
|
|
8564
|
+
*/
|
|
8201
8565
|
set objectives(objectives) {
|
|
8202
8566
|
this._objectives = [...objectives];
|
|
8203
8567
|
this.syncPrimaryObjectiveCollection();
|
|
@@ -8290,12 +8654,12 @@ class Activity extends BaseCMI {
|
|
|
8290
8654
|
this._objectiveNormalizedMeasureDirty = false;
|
|
8291
8655
|
this._objectiveMeasureStatusDirty = false;
|
|
8292
8656
|
}
|
|
8293
|
-
setPrimaryObjectiveState(satisfiedStatus, measureStatus, normalizedMeasure, progressMeasure, progressMeasureStatus, completionStatus) {
|
|
8657
|
+
setPrimaryObjectiveState(satisfiedStatus, measureStatus, normalizedMeasure, progressMeasure, progressMeasureStatus, completionStatus, objectiveProgressStatus = true) {
|
|
8294
8658
|
if (this._objectiveSatisfiedStatus !== satisfiedStatus) {
|
|
8295
8659
|
this._objectiveSatisfiedStatus = satisfiedStatus;
|
|
8296
8660
|
this._objectiveSatisfiedStatusDirty = true;
|
|
8297
8661
|
}
|
|
8298
|
-
this._objectiveSatisfiedStatusKnown =
|
|
8662
|
+
this._objectiveSatisfiedStatusKnown = objectiveProgressStatus;
|
|
8299
8663
|
if (this._objectiveMeasureStatus !== measureStatus) {
|
|
8300
8664
|
this._objectiveMeasureStatus = measureStatus;
|
|
8301
8665
|
this._objectiveMeasureStatusDirty = true;
|
|
@@ -8314,14 +8678,28 @@ class Activity extends BaseCMI {
|
|
|
8314
8678
|
this._primaryObjective.progressMeasure = progressMeasure;
|
|
8315
8679
|
this._primaryObjective.progressMeasureStatus = progressMeasureStatus;
|
|
8316
8680
|
this._primaryObjective.completionStatus = completionStatus;
|
|
8681
|
+
this._primaryObjective.satisfiedStatusKnown = objectiveProgressStatus;
|
|
8682
|
+
this._primaryObjective.progressStatus = objectiveProgressStatus;
|
|
8317
8683
|
}
|
|
8318
8684
|
}
|
|
8685
|
+
/**
|
|
8686
|
+
* Snapshot objective state for sequencing persistence.
|
|
8687
|
+
*
|
|
8688
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - objective state persists across attempts
|
|
8689
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score-map state persists with objective state
|
|
8690
|
+
*/
|
|
8319
8691
|
getObjectiveStateSnapshot() {
|
|
8320
8692
|
const primarySnapshot = this._primaryObjective ? {
|
|
8321
8693
|
id: this._primaryObjective.id,
|
|
8322
8694
|
satisfiedStatus: this.objectiveSatisfiedStatus,
|
|
8323
8695
|
measureStatus: this.objectiveMeasureStatus,
|
|
8324
8696
|
normalizedMeasure: this.objectiveNormalizedMeasure,
|
|
8697
|
+
rawScore: this._primaryObjective.rawScore,
|
|
8698
|
+
rawScoreKnown: this._primaryObjective.rawScoreKnown,
|
|
8699
|
+
minScore: this._primaryObjective.minScore,
|
|
8700
|
+
minScoreKnown: this._primaryObjective.minScoreKnown,
|
|
8701
|
+
maxScore: this._primaryObjective.maxScore,
|
|
8702
|
+
maxScoreKnown: this._primaryObjective.maxScoreKnown,
|
|
8325
8703
|
progressMeasure: this.progressMeasure ?? 0,
|
|
8326
8704
|
progressMeasureStatus: this.progressMeasureStatus,
|
|
8327
8705
|
progressStatus: this._primaryObjective.progressStatus,
|
|
@@ -8334,6 +8712,12 @@ class Activity extends BaseCMI {
|
|
|
8334
8712
|
satisfiedStatus: objective.satisfiedStatus,
|
|
8335
8713
|
measureStatus: objective.measureStatus,
|
|
8336
8714
|
normalizedMeasure: objective.normalizedMeasure,
|
|
8715
|
+
rawScore: objective.rawScore,
|
|
8716
|
+
rawScoreKnown: objective.rawScoreKnown,
|
|
8717
|
+
minScore: objective.minScore,
|
|
8718
|
+
minScoreKnown: objective.minScoreKnown,
|
|
8719
|
+
maxScore: objective.maxScore,
|
|
8720
|
+
maxScoreKnown: objective.maxScoreKnown,
|
|
8337
8721
|
progressMeasure: objective.progressMeasure,
|
|
8338
8722
|
progressMeasureStatus: objective.progressMeasureStatus,
|
|
8339
8723
|
progressStatus: objective.progressStatus,
|
|
@@ -8346,6 +8730,12 @@ class Activity extends BaseCMI {
|
|
|
8346
8730
|
objectives: additionalSnapshots
|
|
8347
8731
|
};
|
|
8348
8732
|
}
|
|
8733
|
+
/**
|
|
8734
|
+
* Restore objective state from a sequencing persistence snapshot.
|
|
8735
|
+
*
|
|
8736
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - persisted objective state restores sequencing state
|
|
8737
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - persisted score-map state restores objective score fields
|
|
8738
|
+
*/
|
|
8349
8739
|
applyObjectiveStateSnapshot(snapshot) {
|
|
8350
8740
|
if (snapshot.primary) {
|
|
8351
8741
|
const primary = this.getObjectiveById(snapshot.primary.id);
|
|
@@ -8359,8 +8749,10 @@ class Activity extends BaseCMI {
|
|
|
8359
8749
|
state.normalizedMeasure,
|
|
8360
8750
|
state.progressMeasure,
|
|
8361
8751
|
state.progressMeasureStatus,
|
|
8362
|
-
state.completionStatus
|
|
8752
|
+
state.completionStatus,
|
|
8753
|
+
state.progressStatus
|
|
8363
8754
|
);
|
|
8755
|
+
this.applyObjectiveScoreSnapshot(primary.objective, state);
|
|
8364
8756
|
}
|
|
8365
8757
|
}
|
|
8366
8758
|
for (const state of snapshot.objectives) {
|
|
@@ -8373,11 +8765,30 @@ class Activity extends BaseCMI {
|
|
|
8373
8765
|
objective.progressMeasure = state.progressMeasure;
|
|
8374
8766
|
objective.progressMeasureStatus = state.progressMeasureStatus;
|
|
8375
8767
|
objective.completionStatus = state.completionStatus;
|
|
8768
|
+
this.applyObjectiveScoreSnapshot(objective, state);
|
|
8376
8769
|
objective.satisfiedByMeasure = state.satisfiedByMeasure ?? objective.satisfiedByMeasure;
|
|
8377
8770
|
objective.minNormalizedMeasure = state.minNormalizedMeasure !== void 0 ? state.minNormalizedMeasure : objective.minNormalizedMeasure;
|
|
8378
8771
|
}
|
|
8379
8772
|
}
|
|
8380
8773
|
}
|
|
8774
|
+
/**
|
|
8775
|
+
* Restore known raw/min/max score fields from an objective state snapshot.
|
|
8776
|
+
*
|
|
8777
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score known flags restore independently
|
|
8778
|
+
*/
|
|
8779
|
+
applyObjectiveScoreSnapshot(objective, state) {
|
|
8780
|
+
const scoreState = {};
|
|
8781
|
+
if (state.rawScoreKnown) {
|
|
8782
|
+
scoreState.rawScore = state.rawScore;
|
|
8783
|
+
}
|
|
8784
|
+
if (state.minScoreKnown) {
|
|
8785
|
+
scoreState.minScore = state.minScore;
|
|
8786
|
+
}
|
|
8787
|
+
if (state.maxScoreKnown) {
|
|
8788
|
+
scoreState.maxScore = state.maxScore;
|
|
8789
|
+
}
|
|
8790
|
+
objective.applyReadMappedState(scoreState);
|
|
8791
|
+
}
|
|
8381
8792
|
/**
|
|
8382
8793
|
* Get available children with selection and randomization applied
|
|
8383
8794
|
* @return {Activity[]}
|
|
@@ -9768,11 +10179,18 @@ class GlobalObjectiveSynchronizer {
|
|
|
9768
10179
|
*
|
|
9769
10180
|
* @param activity - The activity to process
|
|
9770
10181
|
* @param globalObjectives - Global objective map
|
|
10182
|
+
*
|
|
10183
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - write mapInfo transfers local objective state to global objectives
|
|
10184
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score write maps
|
|
9771
10185
|
*/
|
|
9772
10186
|
syncGlobalObjectivesWritePhase(activity, globalObjectives) {
|
|
10187
|
+
if (!this.canWriteGlobalObjectives(activity)) {
|
|
10188
|
+
return;
|
|
10189
|
+
}
|
|
9773
10190
|
const objectives = activity.getAllObjectives();
|
|
9774
10191
|
for (const objective of objectives) {
|
|
9775
10192
|
const mapInfos = objective.mapInfo.length > 0 ? objective.mapInfo : [this.createDefaultMapInfo(objective)];
|
|
10193
|
+
const dirtyFieldsToClear = /* @__PURE__ */ new Set();
|
|
9776
10194
|
for (const mapInfo of mapInfos) {
|
|
9777
10195
|
const targetId = mapInfo.targetObjectiveID || objective.id;
|
|
9778
10196
|
const globalObjective = this.ensureGlobalObjectiveEntry(
|
|
@@ -9780,36 +10198,54 @@ class GlobalObjectiveSynchronizer {
|
|
|
9780
10198
|
targetId,
|
|
9781
10199
|
objective
|
|
9782
10200
|
);
|
|
9783
|
-
if (mapInfo.writeSatisfiedStatus && objective
|
|
10201
|
+
if (mapInfo.writeSatisfiedStatus && this.hasKnownSatisfiedStatus(objective) && objective.isDirty("satisfiedStatus")) {
|
|
9784
10202
|
globalObjective.satisfiedStatus = objective.satisfiedStatus;
|
|
9785
10203
|
globalObjective.satisfiedStatusKnown = true;
|
|
9786
|
-
|
|
10204
|
+
dirtyFieldsToClear.add("satisfiedStatus");
|
|
9787
10205
|
}
|
|
9788
10206
|
if (mapInfo.writeNormalizedMeasure && objective.measureStatus && objective.isDirty("normalizedMeasure")) {
|
|
9789
10207
|
globalObjective.normalizedMeasure = objective.normalizedMeasure;
|
|
9790
10208
|
globalObjective.normalizedMeasureKnown = true;
|
|
9791
|
-
|
|
9792
|
-
if (
|
|
10209
|
+
dirtyFieldsToClear.add("normalizedMeasure");
|
|
10210
|
+
if (mapInfo.writeSatisfiedStatus && objective.satisfiedByMeasure) {
|
|
9793
10211
|
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
9794
10212
|
globalObjective.satisfiedStatus = objective.normalizedMeasure >= threshold;
|
|
9795
10213
|
globalObjective.satisfiedStatusKnown = true;
|
|
9796
|
-
|
|
10214
|
+
dirtyFieldsToClear.add("satisfiedStatus");
|
|
9797
10215
|
}
|
|
9798
10216
|
}
|
|
9799
10217
|
if (mapInfo.writeCompletionStatus && objective.completionStatus !== CompletionStatus.UNKNOWN && objective.isDirty("completionStatus")) {
|
|
9800
10218
|
globalObjective.completionStatus = objective.completionStatus;
|
|
9801
10219
|
globalObjective.completionStatusKnown = true;
|
|
9802
|
-
|
|
10220
|
+
dirtyFieldsToClear.add("completionStatus");
|
|
10221
|
+
}
|
|
10222
|
+
if (mapInfo.writeRawScore && objective.rawScoreKnown && objective.isDirty("rawScore")) {
|
|
10223
|
+
globalObjective.rawScore = objective.rawScore;
|
|
10224
|
+
globalObjective.rawScoreKnown = true;
|
|
10225
|
+
dirtyFieldsToClear.add("rawScore");
|
|
10226
|
+
}
|
|
10227
|
+
if (mapInfo.writeMinScore && objective.minScoreKnown && objective.isDirty("minScore")) {
|
|
10228
|
+
globalObjective.minScore = objective.minScore;
|
|
10229
|
+
globalObjective.minScoreKnown = true;
|
|
10230
|
+
dirtyFieldsToClear.add("minScore");
|
|
10231
|
+
}
|
|
10232
|
+
if (mapInfo.writeMaxScore && objective.maxScoreKnown && objective.isDirty("maxScore")) {
|
|
10233
|
+
globalObjective.maxScore = objective.maxScore;
|
|
10234
|
+
globalObjective.maxScoreKnown = true;
|
|
10235
|
+
dirtyFieldsToClear.add("maxScore");
|
|
9803
10236
|
}
|
|
9804
10237
|
if (mapInfo.writeProgressMeasure && objective.progressMeasureStatus && objective.isDirty("progressMeasure")) {
|
|
9805
10238
|
globalObjective.progressMeasure = objective.progressMeasure;
|
|
9806
10239
|
globalObjective.progressMeasureKnown = true;
|
|
9807
|
-
|
|
10240
|
+
dirtyFieldsToClear.add("progressMeasure");
|
|
9808
10241
|
}
|
|
9809
10242
|
if (mapInfo.updateAttemptData) {
|
|
9810
10243
|
this.updateActivityAttemptData(activity, globalObjective, objective);
|
|
9811
10244
|
}
|
|
9812
10245
|
}
|
|
10246
|
+
for (const property of dirtyFieldsToClear) {
|
|
10247
|
+
objective.clearDirty(property);
|
|
10248
|
+
}
|
|
9813
10249
|
}
|
|
9814
10250
|
}
|
|
9815
10251
|
/**
|
|
@@ -9817,6 +10253,9 @@ class GlobalObjectiveSynchronizer {
|
|
|
9817
10253
|
*
|
|
9818
10254
|
* @param activity - The activity to process
|
|
9819
10255
|
* @param globalObjectives - Global objective map
|
|
10256
|
+
*
|
|
10257
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read mapInfo transfers global objective state into the local view
|
|
10258
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps
|
|
9820
10259
|
*/
|
|
9821
10260
|
syncGlobalObjectivesReadPhase(activity, globalObjectives) {
|
|
9822
10261
|
const objectives = activity.getAllObjectives();
|
|
@@ -9827,25 +10266,13 @@ class GlobalObjectiveSynchronizer {
|
|
|
9827
10266
|
const globalObjective = globalObjectives.get(targetId);
|
|
9828
10267
|
if (!globalObjective) continue;
|
|
9829
10268
|
const isPrimary = objective.isPrimary;
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
objective
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
if (globalObjective.satisfiedByMeasure || objective.satisfiedByMeasure) {
|
|
9838
|
-
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
9839
|
-
objective.satisfiedStatus = globalObjective.normalizedMeasure >= threshold;
|
|
9840
|
-
}
|
|
9841
|
-
}
|
|
9842
|
-
if (mapInfo.readProgressMeasure && globalObjective.progressMeasureKnown) {
|
|
9843
|
-
objective.progressMeasure = globalObjective.progressMeasure;
|
|
9844
|
-
objective.progressMeasureStatus = true;
|
|
9845
|
-
}
|
|
9846
|
-
if (mapInfo.readCompletionStatus && globalObjective.completionStatusKnown) {
|
|
9847
|
-
objective.completionStatus = globalObjective.completionStatus;
|
|
9848
|
-
}
|
|
10269
|
+
const readState = GlobalObjectiveSynchronizer.getGlobalObjectiveReadState(
|
|
10270
|
+
activity,
|
|
10271
|
+
objective,
|
|
10272
|
+
mapInfo,
|
|
10273
|
+
globalObjective
|
|
10274
|
+
);
|
|
10275
|
+
this.applyGlobalObjectiveReadState(objective, readState);
|
|
9849
10276
|
if (isPrimary) {
|
|
9850
10277
|
objective.applyToActivity(activity);
|
|
9851
10278
|
}
|
|
@@ -9888,56 +10315,61 @@ class GlobalObjectiveSynchronizer {
|
|
|
9888
10315
|
* @param objective - The objective to sync
|
|
9889
10316
|
* @param mapInfo - Map info for this objective
|
|
9890
10317
|
* @param globalObjective - The global objective
|
|
10318
|
+
*
|
|
10319
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - objective mapInfo read/write synchronization
|
|
10320
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score mapInfo synchronization
|
|
9891
10321
|
*/
|
|
9892
10322
|
syncObjectiveState(activity, objective, mapInfo, globalObjective) {
|
|
9893
10323
|
try {
|
|
9894
10324
|
const isPrimary = objective.isPrimary;
|
|
9895
10325
|
const localObjective = this.getLocalObjectiveState(activity, objective, isPrimary);
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
objective
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
if (globalObjective.satisfiedByMeasure || objective.satisfiedByMeasure) {
|
|
9904
|
-
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
9905
|
-
objective.satisfiedStatus = globalObjective.normalizedMeasure >= threshold;
|
|
9906
|
-
}
|
|
9907
|
-
}
|
|
9908
|
-
if (mapInfo.readProgressMeasure && globalObjective.progressMeasureKnown) {
|
|
9909
|
-
objective.progressMeasure = globalObjective.progressMeasure;
|
|
9910
|
-
objective.progressMeasureStatus = true;
|
|
9911
|
-
}
|
|
9912
|
-
if (mapInfo.readCompletionStatus && globalObjective.completionStatusKnown) {
|
|
9913
|
-
objective.completionStatus = globalObjective.completionStatus;
|
|
9914
|
-
}
|
|
10326
|
+
const readState = GlobalObjectiveSynchronizer.getGlobalObjectiveReadState(
|
|
10327
|
+
activity,
|
|
10328
|
+
objective,
|
|
10329
|
+
mapInfo,
|
|
10330
|
+
globalObjective
|
|
10331
|
+
);
|
|
10332
|
+
this.applyGlobalObjectiveReadState(objective, readState);
|
|
9915
10333
|
if (objective.isPrimary) {
|
|
9916
10334
|
objective.applyToActivity(activity);
|
|
9917
10335
|
}
|
|
9918
|
-
if (
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
}
|
|
9922
|
-
if (mapInfo.writeNormalizedMeasure && objective.measureStatus) {
|
|
9923
|
-
globalObjective.normalizedMeasure = objective.normalizedMeasure;
|
|
9924
|
-
globalObjective.normalizedMeasureKnown = true;
|
|
9925
|
-
if (globalObjective.satisfiedByMeasure || objective.satisfiedByMeasure) {
|
|
9926
|
-
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
9927
|
-
globalObjective.satisfiedStatus = objective.normalizedMeasure >= threshold;
|
|
10336
|
+
if (this.canWriteGlobalObjectives(activity)) {
|
|
10337
|
+
if (mapInfo.writeSatisfiedStatus && this.hasKnownSatisfiedStatus(objective)) {
|
|
10338
|
+
globalObjective.satisfiedStatus = objective.satisfiedStatus;
|
|
9928
10339
|
globalObjective.satisfiedStatusKnown = true;
|
|
9929
10340
|
}
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
10341
|
+
if (mapInfo.writeNormalizedMeasure && objective.measureStatus) {
|
|
10342
|
+
globalObjective.normalizedMeasure = objective.normalizedMeasure;
|
|
10343
|
+
globalObjective.normalizedMeasureKnown = true;
|
|
10344
|
+
if (mapInfo.writeSatisfiedStatus && objective.satisfiedByMeasure) {
|
|
10345
|
+
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
10346
|
+
globalObjective.satisfiedStatus = objective.normalizedMeasure >= threshold;
|
|
10347
|
+
globalObjective.satisfiedStatusKnown = true;
|
|
10348
|
+
}
|
|
10349
|
+
}
|
|
10350
|
+
if (mapInfo.writeCompletionStatus && objective.completionStatus !== CompletionStatus.UNKNOWN) {
|
|
10351
|
+
globalObjective.completionStatus = objective.completionStatus;
|
|
10352
|
+
globalObjective.completionStatusKnown = true;
|
|
10353
|
+
}
|
|
10354
|
+
if (mapInfo.writeRawScore && objective.rawScoreKnown) {
|
|
10355
|
+
globalObjective.rawScore = objective.rawScore;
|
|
10356
|
+
globalObjective.rawScoreKnown = true;
|
|
10357
|
+
}
|
|
10358
|
+
if (mapInfo.writeMinScore && objective.minScoreKnown) {
|
|
10359
|
+
globalObjective.minScore = objective.minScore;
|
|
10360
|
+
globalObjective.minScoreKnown = true;
|
|
10361
|
+
}
|
|
10362
|
+
if (mapInfo.writeMaxScore && objective.maxScoreKnown) {
|
|
10363
|
+
globalObjective.maxScore = objective.maxScore;
|
|
10364
|
+
globalObjective.maxScoreKnown = true;
|
|
10365
|
+
}
|
|
10366
|
+
if (mapInfo.writeProgressMeasure && objective.progressMeasureStatus) {
|
|
10367
|
+
globalObjective.progressMeasure = objective.progressMeasure;
|
|
10368
|
+
globalObjective.progressMeasureKnown = true;
|
|
10369
|
+
}
|
|
10370
|
+
if (mapInfo.updateAttemptData) {
|
|
10371
|
+
this.updateActivityAttemptData(activity, globalObjective, objective);
|
|
10372
|
+
}
|
|
9941
10373
|
}
|
|
9942
10374
|
this.eventCallback?.("objective_synchronized", {
|
|
9943
10375
|
activityId: activity.id,
|
|
@@ -9955,6 +10387,50 @@ class GlobalObjectiveSynchronizer {
|
|
|
9955
10387
|
});
|
|
9956
10388
|
}
|
|
9957
10389
|
}
|
|
10390
|
+
/**
|
|
10391
|
+
* Project a global objective through one local objective's read mapInfo.
|
|
10392
|
+
*
|
|
10393
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps provide access to mapped global objective fields
|
|
10394
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps are independent fields
|
|
10395
|
+
*/
|
|
10396
|
+
static getGlobalObjectiveReadState(activity, objective, mapInfo, globalObjective) {
|
|
10397
|
+
const readState = {};
|
|
10398
|
+
if (mapInfo.readSatisfiedStatus && globalObjective.satisfiedStatusKnown) {
|
|
10399
|
+
readState.satisfiedStatus = globalObjective.satisfiedStatus;
|
|
10400
|
+
}
|
|
10401
|
+
if (mapInfo.readNormalizedMeasure && globalObjective.normalizedMeasureKnown) {
|
|
10402
|
+
readState.normalizedMeasure = globalObjective.normalizedMeasure;
|
|
10403
|
+
if (objective.satisfiedByMeasure) {
|
|
10404
|
+
const threshold = objective.minNormalizedMeasure ?? activity.scaledPassingScore ?? 0.7;
|
|
10405
|
+
readState.satisfiedStatus = globalObjective.normalizedMeasure >= threshold;
|
|
10406
|
+
}
|
|
10407
|
+
}
|
|
10408
|
+
if (mapInfo.readCompletionStatus && globalObjective.completionStatusKnown) {
|
|
10409
|
+
readState.completionStatus = globalObjective.completionStatus;
|
|
10410
|
+
}
|
|
10411
|
+
if (mapInfo.readProgressMeasure && globalObjective.progressMeasureKnown) {
|
|
10412
|
+
readState.progressMeasure = globalObjective.progressMeasure;
|
|
10413
|
+
}
|
|
10414
|
+
if (mapInfo.readRawScore && globalObjective.rawScoreKnown) {
|
|
10415
|
+
readState.rawScore = globalObjective.rawScore;
|
|
10416
|
+
}
|
|
10417
|
+
if (mapInfo.readMinScore && globalObjective.minScoreKnown) {
|
|
10418
|
+
readState.minScore = globalObjective.minScore;
|
|
10419
|
+
}
|
|
10420
|
+
if (mapInfo.readMaxScore && globalObjective.maxScoreKnown) {
|
|
10421
|
+
readState.maxScore = globalObjective.maxScore;
|
|
10422
|
+
}
|
|
10423
|
+
return readState;
|
|
10424
|
+
}
|
|
10425
|
+
/**
|
|
10426
|
+
* Apply read-mapped state to an objective without marking those fields dirty.
|
|
10427
|
+
*
|
|
10428
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps are access to global state, not local writes
|
|
10429
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score read maps do not imply score writes
|
|
10430
|
+
*/
|
|
10431
|
+
applyGlobalObjectiveReadState(objective, readState) {
|
|
10432
|
+
objective.applyReadMappedState(readState);
|
|
10433
|
+
}
|
|
9958
10434
|
/**
|
|
9959
10435
|
* Ensure global objective entry exists
|
|
9960
10436
|
*
|
|
@@ -9962,19 +10438,30 @@ class GlobalObjectiveSynchronizer {
|
|
|
9962
10438
|
* @param targetId - Target objective ID
|
|
9963
10439
|
* @param objective - Source objective
|
|
9964
10440
|
* @returns The global objective entry
|
|
10441
|
+
*
|
|
10442
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective entries hold mapped objective state
|
|
10443
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - global entries hold score-map state
|
|
9965
10444
|
*/
|
|
9966
10445
|
ensureGlobalObjectiveEntry(globalObjectives, targetId, objective) {
|
|
9967
10446
|
if (!globalObjectives.has(targetId)) {
|
|
9968
10447
|
globalObjectives.set(targetId, {
|
|
9969
10448
|
id: targetId,
|
|
9970
|
-
satisfiedStatus:
|
|
9971
|
-
satisfiedStatusKnown:
|
|
9972
|
-
normalizedMeasure:
|
|
9973
|
-
normalizedMeasureKnown:
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
10449
|
+
satisfiedStatus: false,
|
|
10450
|
+
satisfiedStatusKnown: false,
|
|
10451
|
+
normalizedMeasure: 0,
|
|
10452
|
+
normalizedMeasureKnown: false,
|
|
10453
|
+
// @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score fields
|
|
10454
|
+
// remain unknown until their corresponding write map explicitly writes them.
|
|
10455
|
+
rawScore: "",
|
|
10456
|
+
rawScoreKnown: false,
|
|
10457
|
+
minScore: "",
|
|
10458
|
+
minScoreKnown: false,
|
|
10459
|
+
maxScore: "",
|
|
10460
|
+
maxScoreKnown: false,
|
|
10461
|
+
progressMeasure: 0,
|
|
10462
|
+
progressMeasureKnown: false,
|
|
10463
|
+
completionStatus: CompletionStatus.UNKNOWN,
|
|
10464
|
+
completionStatusKnown: false,
|
|
9978
10465
|
satisfiedByMeasure: objective.satisfiedByMeasure,
|
|
9979
10466
|
minNormalizedMeasure: objective.minNormalizedMeasure
|
|
9980
10467
|
});
|
|
@@ -9989,6 +10476,9 @@ class GlobalObjectiveSynchronizer {
|
|
|
9989
10476
|
*
|
|
9990
10477
|
* @param objective - The objective to create default map info for
|
|
9991
10478
|
* @returns Default map info
|
|
10479
|
+
*
|
|
10480
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - mapInfo defaults are applied before objective synchronization
|
|
10481
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - score maps require explicit read/write flags
|
|
9992
10482
|
*/
|
|
9993
10483
|
createDefaultMapInfo(objective) {
|
|
9994
10484
|
return {
|
|
@@ -10001,9 +10491,34 @@ class GlobalObjectiveSynchronizer {
|
|
|
10001
10491
|
writeCompletionStatus: true,
|
|
10002
10492
|
readProgressMeasure: false,
|
|
10003
10493
|
writeProgressMeasure: true,
|
|
10494
|
+
readRawScore: false,
|
|
10495
|
+
writeRawScore: false,
|
|
10496
|
+
readMinScore: false,
|
|
10497
|
+
writeMinScore: false,
|
|
10498
|
+
readMaxScore: false,
|
|
10499
|
+
writeMaxScore: false,
|
|
10004
10500
|
updateAttemptData: objective.isPrimary
|
|
10005
10501
|
};
|
|
10006
10502
|
}
|
|
10503
|
+
/**
|
|
10504
|
+
* Return whether the local objective has known satisfaction state to write.
|
|
10505
|
+
*
|
|
10506
|
+
* @spec SCORM 2004 4th Ed. SN 4.2.1 Tracking Model - Objective Progress
|
|
10507
|
+
* Status identifies whether Objective Satisfied Status is known; Objective
|
|
10508
|
+
* Measure Status is independent measure knowledge.
|
|
10509
|
+
*/
|
|
10510
|
+
hasKnownSatisfiedStatus(objective) {
|
|
10511
|
+
return objective.progressStatus || objective.satisfiedStatusKnown;
|
|
10512
|
+
}
|
|
10513
|
+
/**
|
|
10514
|
+
* Return whether this activity is allowed to write tracked state to globals.
|
|
10515
|
+
*
|
|
10516
|
+
* @spec SCORM 2004 4th Ed. SN 3.13.1 Tracked - when False, the LMS
|
|
10517
|
+
* "does not initialize, manage or access any tracking status information".
|
|
10518
|
+
*/
|
|
10519
|
+
canWriteGlobalObjectives(activity) {
|
|
10520
|
+
return activity.sequencingControls.tracked !== false;
|
|
10521
|
+
}
|
|
10007
10522
|
/**
|
|
10008
10523
|
* Get local objective state
|
|
10009
10524
|
*
|
|
@@ -10011,6 +10526,9 @@ class GlobalObjectiveSynchronizer {
|
|
|
10011
10526
|
* @param objective - The objective
|
|
10012
10527
|
* @param isPrimary - Whether this is the primary objective
|
|
10013
10528
|
* @returns Local objective state
|
|
10529
|
+
*
|
|
10530
|
+
* @spec SCORM 2004 4th Ed. SN 3.10 Objective Description - local objective state used for synchronization
|
|
10531
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - local score fields are part of mapped objective state
|
|
10014
10532
|
*/
|
|
10015
10533
|
getLocalObjectiveState(activity, objective, isPrimary) {
|
|
10016
10534
|
if (isPrimary) {
|
|
@@ -10019,6 +10537,12 @@ class GlobalObjectiveSynchronizer {
|
|
|
10019
10537
|
satisfiedStatus: activity.objectiveSatisfiedStatus,
|
|
10020
10538
|
measureStatus: activity.objectiveMeasureStatus,
|
|
10021
10539
|
normalizedMeasure: activity.objectiveNormalizedMeasure,
|
|
10540
|
+
rawScore: objective.rawScore,
|
|
10541
|
+
rawScoreKnown: objective.rawScoreKnown,
|
|
10542
|
+
minScore: objective.minScore,
|
|
10543
|
+
minScoreKnown: objective.minScoreKnown,
|
|
10544
|
+
maxScore: objective.maxScore,
|
|
10545
|
+
maxScoreKnown: objective.maxScoreKnown,
|
|
10022
10546
|
progressMeasure: activity.progressMeasure,
|
|
10023
10547
|
progressMeasureStatus: activity.progressMeasureStatus,
|
|
10024
10548
|
completionStatus: activity.completionStatus,
|
|
@@ -10030,6 +10554,12 @@ class GlobalObjectiveSynchronizer {
|
|
|
10030
10554
|
satisfiedStatus: objective.satisfiedStatus,
|
|
10031
10555
|
measureStatus: objective.measureStatus,
|
|
10032
10556
|
normalizedMeasure: objective.normalizedMeasure,
|
|
10557
|
+
rawScore: objective.rawScore,
|
|
10558
|
+
rawScoreKnown: objective.rawScoreKnown,
|
|
10559
|
+
minScore: objective.minScore,
|
|
10560
|
+
minScoreKnown: objective.minScoreKnown,
|
|
10561
|
+
maxScore: objective.maxScore,
|
|
10562
|
+
maxScoreKnown: objective.maxScoreKnown,
|
|
10033
10563
|
progressMeasure: objective.progressMeasure,
|
|
10034
10564
|
progressMeasureStatus: objective.progressMeasureStatus,
|
|
10035
10565
|
completionStatus: objective.completionStatus,
|
|
@@ -10052,7 +10582,7 @@ class GlobalObjectiveSynchronizer {
|
|
|
10052
10582
|
(rule) => rule.action === "completed" || rule.action === "incomplete"
|
|
10053
10583
|
);
|
|
10054
10584
|
if (globalObjective.satisfiedStatusKnown && globalObjective.satisfiedStatus) {
|
|
10055
|
-
if (!hasCompletionRollupRules && (activity.completionStatus === CompletionStatus.UNKNOWN || activity.completionStatus === CompletionStatus.INCOMPLETE)) {
|
|
10585
|
+
if (!hasCompletionRollupRules && !activity.sequencingControls.completionSetByContent && !activity.attemptProgressStatus && (activity.completionStatus === CompletionStatus.UNKNOWN || activity.completionStatus === CompletionStatus.INCOMPLETE)) {
|
|
10056
10586
|
activity.completionStatus = CompletionStatus.COMPLETED;
|
|
10057
10587
|
}
|
|
10058
10588
|
if (activity.successStatus === "unknown") {
|
|
@@ -10498,6 +11028,9 @@ class RteDataTransferService {
|
|
|
10498
11028
|
* Transfer primary objective data from CMI to activity
|
|
10499
11029
|
* @param {Activity} activity - The activity to transfer data to
|
|
10500
11030
|
* @param {CMIDataForTransfer} cmiData - CMI data from runtime
|
|
11031
|
+
*
|
|
11032
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - primary objective status and score data
|
|
11033
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score write-map source data
|
|
10501
11034
|
*/
|
|
10502
11035
|
transferPrimaryObjective(activity, cmiData) {
|
|
10503
11036
|
let hasProgressMeasure = false;
|
|
@@ -10544,9 +11077,9 @@ class RteDataTransferService {
|
|
|
10544
11077
|
activity.objectiveSatisfiedStatus = successStatus;
|
|
10545
11078
|
activity.objectiveSatisfiedStatusKnown = true;
|
|
10546
11079
|
activity.successStatus = validatedSuccessStatus;
|
|
10547
|
-
activity.objectiveMeasureStatus = true;
|
|
10548
11080
|
}
|
|
10549
11081
|
if (cmiData.score) {
|
|
11082
|
+
activity.primaryObjective?.initializeScoreFromCMI(this.getObjectiveScoreState(cmiData.score));
|
|
10550
11083
|
const normalized = this.normalizeScore(cmiData.score);
|
|
10551
11084
|
if (normalized !== null) {
|
|
10552
11085
|
normalizedScore = normalized;
|
|
@@ -10558,7 +11091,7 @@ class RteDataTransferService {
|
|
|
10558
11091
|
if (activity.primaryObjective && (hasSuccessStatus || hasNormalizedMeasure)) {
|
|
10559
11092
|
const finalStatus = hasSuccessStatus ? successStatus : activity.primaryObjective.satisfiedStatus;
|
|
10560
11093
|
const finalMeasure = hasNormalizedMeasure ? normalizedScore : activity.primaryObjective.normalizedMeasure;
|
|
10561
|
-
const measureStatus =
|
|
11094
|
+
const measureStatus = hasNormalizedMeasure;
|
|
10562
11095
|
activity.primaryObjective.initializeFromCMI(finalStatus, finalMeasure, measureStatus);
|
|
10563
11096
|
if (hasSuccessStatus) {
|
|
10564
11097
|
activity.primaryObjective.satisfiedStatusKnown = true;
|
|
@@ -10567,10 +11100,16 @@ class RteDataTransferService {
|
|
|
10567
11100
|
}
|
|
10568
11101
|
}
|
|
10569
11102
|
/**
|
|
10570
|
-
* Transfer
|
|
10571
|
-
* Only transfers changed values to protect global objectives
|
|
11103
|
+
* Transfer objective-array data from CMI to matching activity objectives.
|
|
11104
|
+
* Only transfers changed values to protect global objectives.
|
|
10572
11105
|
* @param {Activity} activity - The activity to transfer data to
|
|
10573
11106
|
* @param {CMIDataForTransfer} cmiData - CMI data from runtime
|
|
11107
|
+
*
|
|
11108
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17 - cmi.objectives.n data, including
|
|
11109
|
+
* the primary objective entry, is part of the RTE objective data model.
|
|
11110
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 and ADLSEQ objectives extension -
|
|
11111
|
+
* mapped objective status and raw/min/max score data transfer through
|
|
11112
|
+
* objective maps to sequencing state.
|
|
10574
11113
|
*/
|
|
10575
11114
|
transferNonPrimaryObjectives(activity, cmiData) {
|
|
10576
11115
|
if (!cmiData.objectives || cmiData.objectives.length === 0) {
|
|
@@ -10581,14 +11120,17 @@ class RteDataTransferService {
|
|
|
10581
11120
|
continue;
|
|
10582
11121
|
}
|
|
10583
11122
|
const activityObjectiveMatch = activity.getObjectiveById(cmiObjective.id);
|
|
10584
|
-
if (!activityObjectiveMatch
|
|
11123
|
+
if (!activityObjectiveMatch) {
|
|
10585
11124
|
continue;
|
|
10586
11125
|
}
|
|
10587
11126
|
const activityObjective = activityObjectiveMatch.objective;
|
|
11127
|
+
const isPrimaryObjective = activityObjectiveMatch.isPrimary;
|
|
10588
11128
|
let hasSuccessStatus = false;
|
|
10589
11129
|
let successStatus = false;
|
|
10590
11130
|
let hasNormalizedMeasure = false;
|
|
10591
11131
|
let normalizedScore = 0;
|
|
11132
|
+
let hasCompletionStatus = false;
|
|
11133
|
+
let hasProgressMeasure = false;
|
|
10592
11134
|
const validatedObjSuccessStatus = validateSuccessStatus(cmiObjective.success_status);
|
|
10593
11135
|
if (validatedObjSuccessStatus && validatedObjSuccessStatus !== SuccessStatus.UNKNOWN) {
|
|
10594
11136
|
successStatus = validatedObjSuccessStatus === SuccessStatus.PASSED;
|
|
@@ -10598,8 +11140,10 @@ class RteDataTransferService {
|
|
|
10598
11140
|
const validatedObjCompletionStatus = validateCompletionStatus(cmiObjective.completion_status);
|
|
10599
11141
|
if (validatedObjCompletionStatus && validatedObjCompletionStatus !== CompletionStatus.UNKNOWN) {
|
|
10600
11142
|
activityObjective.completionStatus = validatedObjCompletionStatus;
|
|
11143
|
+
hasCompletionStatus = true;
|
|
10601
11144
|
}
|
|
10602
11145
|
if (cmiObjective.score) {
|
|
11146
|
+
activityObjective.initializeScoreFromCMI(this.getObjectiveScoreState(cmiObjective.score));
|
|
10603
11147
|
const normalized = this.normalizeScore(cmiObjective.score);
|
|
10604
11148
|
if (normalized !== null) {
|
|
10605
11149
|
normalizedScore = normalized;
|
|
@@ -10611,12 +11155,29 @@ class RteDataTransferService {
|
|
|
10611
11155
|
const finalMeasure = hasNormalizedMeasure ? normalizedScore : activityObjective.normalizedMeasure;
|
|
10612
11156
|
const measureStatus = hasNormalizedMeasure;
|
|
10613
11157
|
activityObjective.initializeFromCMI(finalStatus, finalMeasure, measureStatus);
|
|
11158
|
+
if (hasSuccessStatus) {
|
|
11159
|
+
activityObjective.satisfiedStatusKnown = true;
|
|
11160
|
+
}
|
|
10614
11161
|
}
|
|
10615
11162
|
if (cmiObjective.progress_measure && cmiObjective.progress_measure !== "") {
|
|
10616
11163
|
const progressMeasure = parseFloat(cmiObjective.progress_measure);
|
|
10617
11164
|
if (!isNaN(progressMeasure)) {
|
|
10618
11165
|
activityObjective.progressMeasure = progressMeasure;
|
|
10619
11166
|
activityObjective.progressMeasureStatus = true;
|
|
11167
|
+
hasProgressMeasure = true;
|
|
11168
|
+
}
|
|
11169
|
+
}
|
|
11170
|
+
if (isPrimaryObjective && (hasSuccessStatus || hasNormalizedMeasure || hasCompletionStatus || hasProgressMeasure)) {
|
|
11171
|
+
activityObjective.applyToActivity(activity);
|
|
11172
|
+
if (validatedObjSuccessStatus && validatedObjSuccessStatus !== SuccessStatus.UNKNOWN) {
|
|
11173
|
+
activity.successStatus = validatedObjSuccessStatus;
|
|
11174
|
+
}
|
|
11175
|
+
if (hasCompletionStatus) {
|
|
11176
|
+
activity.attemptProgressStatus = true;
|
|
11177
|
+
}
|
|
11178
|
+
if (hasProgressMeasure) {
|
|
11179
|
+
activity.attemptCompletionAmount = activityObjective.progressMeasure;
|
|
11180
|
+
activity.attemptCompletionAmountStatus = true;
|
|
10620
11181
|
}
|
|
10621
11182
|
}
|
|
10622
11183
|
}
|
|
@@ -10645,6 +11206,25 @@ class RteDataTransferService {
|
|
|
10645
11206
|
}
|
|
10646
11207
|
return null;
|
|
10647
11208
|
}
|
|
11209
|
+
/**
|
|
11210
|
+
* Convert RTE score data into objective score-map state without numeric reformatting.
|
|
11211
|
+
*
|
|
11212
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - score values transfer from RTE to sequencing state
|
|
11213
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score map fields are independent
|
|
11214
|
+
*/
|
|
11215
|
+
getObjectiveScoreState(score) {
|
|
11216
|
+
const scoreState = {};
|
|
11217
|
+
if (score.raw !== void 0) {
|
|
11218
|
+
scoreState.rawScore = score.raw;
|
|
11219
|
+
}
|
|
11220
|
+
if (score.min !== void 0) {
|
|
11221
|
+
scoreState.minScore = score.min;
|
|
11222
|
+
}
|
|
11223
|
+
if (score.max !== void 0) {
|
|
11224
|
+
scoreState.maxScore = score.max;
|
|
11225
|
+
}
|
|
11226
|
+
return scoreState;
|
|
11227
|
+
}
|
|
10648
11228
|
}
|
|
10649
11229
|
|
|
10650
11230
|
class TerminationHandler {
|
|
@@ -11006,13 +11586,7 @@ class TerminationHandler {
|
|
|
11006
11586
|
recursionDepth++;
|
|
11007
11587
|
const exitRules = activity.sequencingRules.exitConditionRules;
|
|
11008
11588
|
for (const rule of exitRules) {
|
|
11009
|
-
|
|
11010
|
-
if (rule.conditionCombination === "all") {
|
|
11011
|
-
conditionsMet = rule.conditions.every((condition) => condition.evaluate(activity));
|
|
11012
|
-
} else {
|
|
11013
|
-
conditionsMet = rule.conditions.some((condition) => condition.evaluate(activity));
|
|
11014
|
-
}
|
|
11015
|
-
if (conditionsMet) {
|
|
11589
|
+
if (rule.evaluate(activity)) {
|
|
11016
11590
|
if (rule.action === RuleActionType.EXIT) {
|
|
11017
11591
|
return { action: "EXIT", recursionDepth };
|
|
11018
11592
|
} else if (rule.action === RuleActionType.EXIT_PARENT) {
|
|
@@ -11178,13 +11752,7 @@ class TerminationHandler {
|
|
|
11178
11752
|
exitActionRulesSubprocess(activity) {
|
|
11179
11753
|
const exitRules = activity.sequencingRules.exitConditionRules;
|
|
11180
11754
|
for (const rule of exitRules) {
|
|
11181
|
-
|
|
11182
|
-
if (rule.conditionCombination === "all") {
|
|
11183
|
-
conditionsMet = rule.conditions.every((condition) => condition.evaluate(activity));
|
|
11184
|
-
} else {
|
|
11185
|
-
conditionsMet = rule.conditions.some((condition) => condition.evaluate(activity));
|
|
11186
|
-
}
|
|
11187
|
-
if (conditionsMet) {
|
|
11755
|
+
if (rule.evaluate(activity)) {
|
|
11188
11756
|
if (rule.action === RuleActionType.EXIT) {
|
|
11189
11757
|
return "EXIT";
|
|
11190
11758
|
} else if (rule.action === RuleActionType.EXIT_PARENT) {
|
|
@@ -12703,6 +13271,9 @@ class GlobalObjectiveService {
|
|
|
12703
13271
|
* Collect Global Objectives
|
|
12704
13272
|
* Recursively collects global objectives from the activity tree
|
|
12705
13273
|
* @param {Activity} activity - Activity to collect objectives from
|
|
13274
|
+
*
|
|
13275
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective map contains mapped objective state
|
|
13276
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score fields are unknown until written
|
|
12706
13277
|
*/
|
|
12707
13278
|
collectObjectives(activity) {
|
|
12708
13279
|
const objectives = activity.getAllObjectives();
|
|
@@ -12712,9 +13283,15 @@ class GlobalObjectiveService {
|
|
|
12712
13283
|
this.globalObjectiveMap.set(defaultId, {
|
|
12713
13284
|
id: defaultId,
|
|
12714
13285
|
satisfiedStatus: activity.objectiveSatisfiedStatus,
|
|
12715
|
-
satisfiedStatusKnown: activity.
|
|
13286
|
+
satisfiedStatusKnown: activity.objectiveSatisfiedStatusKnown,
|
|
12716
13287
|
normalizedMeasure: activity.objectiveNormalizedMeasure,
|
|
12717
13288
|
normalizedMeasureKnown: activity.objectiveMeasureStatus,
|
|
13289
|
+
rawScore: "",
|
|
13290
|
+
rawScoreKnown: false,
|
|
13291
|
+
minScore: "",
|
|
13292
|
+
minScoreKnown: false,
|
|
13293
|
+
maxScore: "",
|
|
13294
|
+
maxScoreKnown: false,
|
|
12718
13295
|
progressMeasure: activity.progressMeasure,
|
|
12719
13296
|
progressMeasureKnown: activity.progressMeasureStatus,
|
|
12720
13297
|
completionStatus: activity.completionStatus,
|
|
@@ -12727,6 +13304,12 @@ class GlobalObjectiveService {
|
|
|
12727
13304
|
writeCompletionStatus: true,
|
|
12728
13305
|
readProgressMeasure: true,
|
|
12729
13306
|
writeProgressMeasure: true,
|
|
13307
|
+
readRawScore: false,
|
|
13308
|
+
writeRawScore: false,
|
|
13309
|
+
readMinScore: false,
|
|
13310
|
+
writeMinScore: false,
|
|
13311
|
+
readMaxScore: false,
|
|
13312
|
+
writeMaxScore: false,
|
|
12730
13313
|
satisfiedByMeasure: activity.scaledPassingScore !== null,
|
|
12731
13314
|
minNormalizedMeasure: activity.scaledPassingScore,
|
|
12732
13315
|
updateAttemptData: true
|
|
@@ -12754,9 +13337,15 @@ class GlobalObjectiveService {
|
|
|
12754
13337
|
this.globalObjectiveMap.set(targetId, {
|
|
12755
13338
|
id: targetId,
|
|
12756
13339
|
satisfiedStatus: objective.satisfiedStatus,
|
|
12757
|
-
satisfiedStatusKnown: objective.
|
|
13340
|
+
satisfiedStatusKnown: objective.satisfiedStatusKnown || objective.progressStatus,
|
|
12758
13341
|
normalizedMeasure: objective.normalizedMeasure,
|
|
12759
13342
|
normalizedMeasureKnown: objective.measureStatus,
|
|
13343
|
+
rawScore: "",
|
|
13344
|
+
rawScoreKnown: false,
|
|
13345
|
+
minScore: "",
|
|
13346
|
+
minScoreKnown: false,
|
|
13347
|
+
maxScore: "",
|
|
13348
|
+
maxScoreKnown: false,
|
|
12760
13349
|
progressMeasure: objective.progressMeasure,
|
|
12761
13350
|
progressMeasureKnown: objective.progressMeasureStatus,
|
|
12762
13351
|
completionStatus: objective.completionStatus,
|
|
@@ -14739,6 +15328,9 @@ class SequencingService {
|
|
|
14739
15328
|
}
|
|
14740
15329
|
/**
|
|
14741
15330
|
* Update activity properties from current CMI values
|
|
15331
|
+
*
|
|
15332
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - current CMI values update activity objective state
|
|
15333
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max scores are available to objective write maps
|
|
14742
15334
|
*/
|
|
14743
15335
|
updateActivityFromCMI(activity) {
|
|
14744
15336
|
let hasProgressMeasure = false;
|
|
@@ -14770,7 +15362,6 @@ class SequencingService {
|
|
|
14770
15362
|
if (this.cmi.success_status !== "unknown") {
|
|
14771
15363
|
activity.successStatus = this.cmi.success_status;
|
|
14772
15364
|
activity.objectiveSatisfiedStatus = this.cmi.success_status === "passed";
|
|
14773
|
-
activity.objectiveMeasureStatus = true;
|
|
14774
15365
|
if (activity.primaryObjective) {
|
|
14775
15366
|
activity.primaryObjective.progressStatus = true;
|
|
14776
15367
|
}
|
|
@@ -14785,6 +15376,17 @@ class SequencingService {
|
|
|
14785
15376
|
}
|
|
14786
15377
|
}
|
|
14787
15378
|
}
|
|
15379
|
+
if (activity.primaryObjective && this.cmi.score) {
|
|
15380
|
+
if (this.cmi.score.raw !== "") {
|
|
15381
|
+
activity.primaryObjective.rawScore = this.cmi.score.raw;
|
|
15382
|
+
}
|
|
15383
|
+
if (this.cmi.score.min !== "") {
|
|
15384
|
+
activity.primaryObjective.minScore = this.cmi.score.min;
|
|
15385
|
+
}
|
|
15386
|
+
if (this.cmi.score.max !== "") {
|
|
15387
|
+
activity.primaryObjective.maxScore = this.cmi.score.max;
|
|
15388
|
+
}
|
|
15389
|
+
}
|
|
14788
15390
|
if (activity.primaryObjective) {
|
|
14789
15391
|
activity.primaryObjective.updateFromActivity(activity);
|
|
14790
15392
|
}
|
|
@@ -15358,15 +15960,22 @@ class SynchronousHttpService {
|
|
|
15358
15960
|
const handledPayload = metadata === void 0 ? this.settings.requestHandler(params) : this.settings.requestHandler(params, metadata);
|
|
15359
15961
|
const requestPayload = handledPayload ?? params;
|
|
15360
15962
|
const { body } = this._prepareRequestBody(requestPayload);
|
|
15361
|
-
const
|
|
15362
|
-
|
|
15363
|
-
|
|
15364
|
-
);
|
|
15963
|
+
const beaconContentType = this.settings.terminationCommitContentType;
|
|
15964
|
+
this._warnIfBeaconContentTypeUnsafe(url, beaconContentType);
|
|
15965
|
+
const beaconSuccess = navigator.sendBeacon(url, new Blob([body], { type: beaconContentType }));
|
|
15365
15966
|
return {
|
|
15366
15967
|
result: beaconSuccess ? "true" : "false",
|
|
15367
15968
|
errorCode: beaconSuccess ? 0 : this.error_codes.GENERAL_COMMIT_FAILURE || 391
|
|
15368
15969
|
};
|
|
15369
15970
|
}
|
|
15971
|
+
_warnIfBeaconContentTypeUnsafe(url, contentType) {
|
|
15972
|
+
if (isCrossOriginUrl(url) && !isCorsSafelistedContentType(contentType)) {
|
|
15973
|
+
this.settings.onLogMessage?.(
|
|
15974
|
+
LogLevelEnum.WARN,
|
|
15975
|
+
`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.`
|
|
15976
|
+
);
|
|
15977
|
+
}
|
|
15978
|
+
}
|
|
15370
15979
|
/**
|
|
15371
15980
|
* Performs a synchronous XMLHttpRequest
|
|
15372
15981
|
* @param {string} url - The URL to send the request to
|
|
@@ -16393,8 +17002,8 @@ class BaseAPI {
|
|
|
16393
17002
|
* // Throw a "not initialized" error
|
|
16394
17003
|
* this.throwSCORMError(301, "The API must be initialized before calling GetValue");
|
|
16395
17004
|
*/
|
|
16396
|
-
throwSCORMError(CMIElement, errorNumber, message) {
|
|
16397
|
-
this._errorHandlingService.throwSCORMError(CMIElement, errorNumber ?? 0, message);
|
|
17005
|
+
throwSCORMError(CMIElement, errorNumber, message, messageLevel) {
|
|
17006
|
+
this._errorHandlingService.throwSCORMError(CMIElement, errorNumber ?? 0, message, messageLevel);
|
|
16398
17007
|
}
|
|
16399
17008
|
/**
|
|
16400
17009
|
* Clears the last SCORM error code when an operation succeeds.
|
|
@@ -17297,7 +17906,7 @@ class CMIInteractionsObject extends BaseCMI {
|
|
|
17297
17906
|
if (check2004ValidFormat(
|
|
17298
17907
|
this._cmi_element + ".description",
|
|
17299
17908
|
description,
|
|
17300
|
-
scorm2004_regex.
|
|
17909
|
+
scorm2004_regex.CMILangString,
|
|
17301
17910
|
true
|
|
17302
17911
|
)) {
|
|
17303
17912
|
this._description = description;
|
|
@@ -18201,7 +18810,7 @@ class CMIObjectivesObject extends BaseCMI {
|
|
|
18201
18810
|
if (check2004ValidFormat(
|
|
18202
18811
|
this._cmi_element + ".description",
|
|
18203
18812
|
description,
|
|
18204
|
-
scorm2004_regex.
|
|
18813
|
+
scorm2004_regex.CMILangString,
|
|
18205
18814
|
true
|
|
18206
18815
|
)) {
|
|
18207
18816
|
this._description = description;
|
|
@@ -21044,6 +21653,9 @@ class SequencingConfigurationBuilder {
|
|
|
21044
21653
|
if (settings.objectiveSetByContent !== void 0) {
|
|
21045
21654
|
target.objectiveSetByContent = settings.objectiveSetByContent;
|
|
21046
21655
|
}
|
|
21656
|
+
if (settings.tracked !== void 0) {
|
|
21657
|
+
target.tracked = settings.tracked;
|
|
21658
|
+
}
|
|
21047
21659
|
}
|
|
21048
21660
|
/**
|
|
21049
21661
|
* Applies the sequencing rules settings to the specified target object.
|
|
@@ -21581,6 +22193,12 @@ class ActivityTreeBuilder {
|
|
|
21581
22193
|
activitySettings.sequencingControls
|
|
21582
22194
|
);
|
|
21583
22195
|
}
|
|
22196
|
+
if (activitySettings.deliveryControls) {
|
|
22197
|
+
this.sequencingConfigBuilder.applySequencingControlsSettings(
|
|
22198
|
+
activity.sequencingControls,
|
|
22199
|
+
activitySettings.deliveryControls
|
|
22200
|
+
);
|
|
22201
|
+
}
|
|
21584
22202
|
if (activitySettings.sequencingRules) {
|
|
21585
22203
|
this.sequencingConfigBuilder.applySequencingRulesSettings(
|
|
21586
22204
|
activity.sequencingRules,
|
|
@@ -21670,19 +22288,19 @@ class ActivityTreeBuilder {
|
|
|
21670
22288
|
createActivityObjectiveFromSettings(objectiveSettings, isPrimary) {
|
|
21671
22289
|
const mapInfo = (objectiveSettings.mapInfo || []).map((info) => ({
|
|
21672
22290
|
targetObjectiveID: info.targetObjectiveID,
|
|
21673
|
-
readSatisfiedStatus: info.readSatisfiedStatus ??
|
|
21674
|
-
readNormalizedMeasure: info.readNormalizedMeasure ??
|
|
22291
|
+
readSatisfiedStatus: info.readSatisfiedStatus ?? true,
|
|
22292
|
+
readNormalizedMeasure: info.readNormalizedMeasure ?? true,
|
|
21675
22293
|
writeSatisfiedStatus: info.writeSatisfiedStatus ?? false,
|
|
21676
22294
|
writeNormalizedMeasure: info.writeNormalizedMeasure ?? false,
|
|
21677
|
-
readCompletionStatus: info.readCompletionStatus ??
|
|
22295
|
+
readCompletionStatus: info.readCompletionStatus ?? true,
|
|
21678
22296
|
writeCompletionStatus: info.writeCompletionStatus ?? false,
|
|
21679
|
-
readProgressMeasure: info.readProgressMeasure ??
|
|
22297
|
+
readProgressMeasure: info.readProgressMeasure ?? true,
|
|
21680
22298
|
writeProgressMeasure: info.writeProgressMeasure ?? false,
|
|
21681
|
-
readRawScore: info.readRawScore ??
|
|
22299
|
+
readRawScore: info.readRawScore ?? true,
|
|
21682
22300
|
writeRawScore: info.writeRawScore ?? false,
|
|
21683
|
-
readMinScore: info.readMinScore ??
|
|
22301
|
+
readMinScore: info.readMinScore ?? true,
|
|
21684
22302
|
writeMinScore: info.writeMinScore ?? false,
|
|
21685
|
-
readMaxScore: info.readMaxScore ??
|
|
22303
|
+
readMaxScore: info.readMaxScore ?? true,
|
|
21686
22304
|
writeMaxScore: info.writeMaxScore ?? false,
|
|
21687
22305
|
updateAttemptData: info.updateAttemptData ?? false
|
|
21688
22306
|
}));
|
|
@@ -21864,6 +22482,9 @@ class GlobalObjectiveManager {
|
|
|
21864
22482
|
*
|
|
21865
22483
|
* @param {string} objectiveId - The global objective ID
|
|
21866
22484
|
* @param {CMIObjectivesObject} objective - The CMI objective object with updated values
|
|
22485
|
+
*
|
|
22486
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objectives synchronize CMI objective data
|
|
22487
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score fields synchronize independently
|
|
21867
22488
|
*/
|
|
21868
22489
|
updateGlobalObjectiveFromCMI(objectiveId, objective) {
|
|
21869
22490
|
if (!objectiveId || !this.context.sequencingService) {
|
|
@@ -21889,6 +22510,18 @@ class GlobalObjectiveManager {
|
|
|
21889
22510
|
updatePayload.normalizedMeasure = normalizedMeasure;
|
|
21890
22511
|
updatePayload.normalizedMeasureKnown = true;
|
|
21891
22512
|
}
|
|
22513
|
+
if (objective.score?.raw !== void 0 && objective.score.raw !== "") {
|
|
22514
|
+
updatePayload.rawScore = objective.score.raw;
|
|
22515
|
+
updatePayload.rawScoreKnown = true;
|
|
22516
|
+
}
|
|
22517
|
+
if (objective.score?.min !== void 0 && objective.score.min !== "") {
|
|
22518
|
+
updatePayload.minScore = objective.score.min;
|
|
22519
|
+
updatePayload.minScoreKnown = true;
|
|
22520
|
+
}
|
|
22521
|
+
if (objective.score?.max !== void 0 && objective.score.max !== "") {
|
|
22522
|
+
updatePayload.maxScore = objective.score.max;
|
|
22523
|
+
updatePayload.maxScoreKnown = true;
|
|
22524
|
+
}
|
|
21892
22525
|
const progressMeasure = this.parseObjectiveNumber(objective.progress_measure);
|
|
21893
22526
|
if (progressMeasure !== null) {
|
|
21894
22527
|
updatePayload.progressMeasure = progressMeasure;
|
|
@@ -21908,12 +22541,21 @@ class GlobalObjectiveManager {
|
|
|
21908
22541
|
*
|
|
21909
22542
|
* @param {CMIObjectivesObject} objective - The CMI objectives object containing data about a specific learning objective.
|
|
21910
22543
|
* @return {GlobalObjectiveMapEntry} An object containing mapped properties and their values based on the provided objective.
|
|
22544
|
+
*
|
|
22545
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective map entries capture objective state
|
|
22546
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score entries carry per-field known flags
|
|
21911
22547
|
*/
|
|
21912
22548
|
buildObjectiveMapEntryFromCMI(objective) {
|
|
21913
22549
|
const entry = {
|
|
21914
22550
|
id: objective.id,
|
|
21915
22551
|
satisfiedStatusKnown: false,
|
|
21916
22552
|
normalizedMeasureKnown: false,
|
|
22553
|
+
rawScore: "",
|
|
22554
|
+
rawScoreKnown: false,
|
|
22555
|
+
minScore: "",
|
|
22556
|
+
minScoreKnown: false,
|
|
22557
|
+
maxScore: "",
|
|
22558
|
+
maxScoreKnown: false,
|
|
21917
22559
|
progressMeasureKnown: false,
|
|
21918
22560
|
completionStatusKnown: false,
|
|
21919
22561
|
readSatisfiedStatus: true,
|
|
@@ -21923,7 +22565,13 @@ class GlobalObjectiveManager {
|
|
|
21923
22565
|
readCompletionStatus: true,
|
|
21924
22566
|
writeCompletionStatus: true,
|
|
21925
22567
|
readProgressMeasure: true,
|
|
21926
|
-
writeProgressMeasure: true
|
|
22568
|
+
writeProgressMeasure: true,
|
|
22569
|
+
readRawScore: true,
|
|
22570
|
+
writeRawScore: true,
|
|
22571
|
+
readMinScore: true,
|
|
22572
|
+
writeMinScore: true,
|
|
22573
|
+
readMaxScore: true,
|
|
22574
|
+
writeMaxScore: true
|
|
21927
22575
|
};
|
|
21928
22576
|
if (objective.success_status && objective.success_status !== SuccessStatus.UNKNOWN) {
|
|
21929
22577
|
entry.satisfiedStatus = objective.success_status === SuccessStatus.PASSED;
|
|
@@ -21934,6 +22582,18 @@ class GlobalObjectiveManager {
|
|
|
21934
22582
|
entry.normalizedMeasure = normalizedMeasure;
|
|
21935
22583
|
entry.normalizedMeasureKnown = true;
|
|
21936
22584
|
}
|
|
22585
|
+
if (objective.score?.raw !== void 0 && objective.score.raw !== "") {
|
|
22586
|
+
entry.rawScore = objective.score.raw;
|
|
22587
|
+
entry.rawScoreKnown = true;
|
|
22588
|
+
}
|
|
22589
|
+
if (objective.score?.min !== void 0 && objective.score.min !== "") {
|
|
22590
|
+
entry.minScore = objective.score.min;
|
|
22591
|
+
entry.minScoreKnown = true;
|
|
22592
|
+
}
|
|
22593
|
+
if (objective.score?.max !== void 0 && objective.score.max !== "") {
|
|
22594
|
+
entry.maxScore = objective.score.max;
|
|
22595
|
+
entry.maxScoreKnown = true;
|
|
22596
|
+
}
|
|
21937
22597
|
const progressMeasure = this.parseObjectiveNumber(objective.progress_measure);
|
|
21938
22598
|
if (progressMeasure !== null) {
|
|
21939
22599
|
entry.progressMeasure = progressMeasure;
|
|
@@ -21954,6 +22614,9 @@ class GlobalObjectiveManager {
|
|
|
21954
22614
|
* @return {CMIObjectivesObject[]} An array of `CMIObjectivesObject` instances built
|
|
21955
22615
|
* from the provided snapshot map. Returns an empty array
|
|
21956
22616
|
* if the snapshot is invalid or no valid objectives can be created.
|
|
22617
|
+
*
|
|
22618
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - global objective snapshots restore CMI objective state
|
|
22619
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score known fields restore independently
|
|
21957
22620
|
*/
|
|
21958
22621
|
buildCMIObjectivesFromMap(snapshot) {
|
|
21959
22622
|
const objectives = [];
|
|
@@ -21973,6 +22636,15 @@ class GlobalObjectiveManager {
|
|
|
21973
22636
|
if (entry.normalizedMeasureKnown === true && normalizedMeasure !== null) {
|
|
21974
22637
|
objective.score.scaled = String(normalizedMeasure);
|
|
21975
22638
|
}
|
|
22639
|
+
if (entry.rawScoreKnown === true) {
|
|
22640
|
+
objective.score.raw = String(entry.rawScore ?? "");
|
|
22641
|
+
}
|
|
22642
|
+
if (entry.minScoreKnown === true) {
|
|
22643
|
+
objective.score.min = String(entry.minScore ?? "");
|
|
22644
|
+
}
|
|
22645
|
+
if (entry.maxScoreKnown === true) {
|
|
22646
|
+
objective.score.max = String(entry.maxScore ?? "");
|
|
22647
|
+
}
|
|
21976
22648
|
const progressMeasure = this.parseObjectiveNumber(entry.progressMeasure);
|
|
21977
22649
|
if (entry.progressMeasureKnown === true && progressMeasure !== null) {
|
|
21978
22650
|
objective.progress_measure = String(progressMeasure);
|
|
@@ -22083,6 +22755,9 @@ class GlobalObjectiveManager {
|
|
|
22083
22755
|
* @param {CompletionStatus} completionStatus
|
|
22084
22756
|
* @param {SuccessStatus} successStatus
|
|
22085
22757
|
* @param {ScoreObject} scoreObject
|
|
22758
|
+
*
|
|
22759
|
+
* @spec SCORM 2004 4th Ed. RTE-to-SN Data Transfer - CMI score data updates the current primary objective
|
|
22760
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score data is available for write maps
|
|
22086
22761
|
*/
|
|
22087
22762
|
syncCmiToSequencingActivity(completionStatus, successStatus, scoreObject) {
|
|
22088
22763
|
if (!this.context.sequencing) {
|
|
@@ -22108,6 +22783,15 @@ class GlobalObjectiveManager {
|
|
|
22108
22783
|
primaryObjective.normalizedMeasure = scoreObject.scaled;
|
|
22109
22784
|
primaryObjective.measureStatus = true;
|
|
22110
22785
|
}
|
|
22786
|
+
if (scoreObject?.raw !== void 0 && scoreObject.raw !== null) {
|
|
22787
|
+
primaryObjective.rawScore = String(scoreObject.raw);
|
|
22788
|
+
}
|
|
22789
|
+
if (scoreObject?.min !== void 0 && scoreObject.min !== null) {
|
|
22790
|
+
primaryObjective.minScore = String(scoreObject.min);
|
|
22791
|
+
}
|
|
22792
|
+
if (scoreObject?.max !== void 0 && scoreObject.max !== null) {
|
|
22793
|
+
primaryObjective.maxScore = String(scoreObject.max);
|
|
22794
|
+
}
|
|
22111
22795
|
}
|
|
22112
22796
|
/**
|
|
22113
22797
|
* Find or create a global objective by ID
|
|
@@ -22660,16 +23344,232 @@ class Scorm2004API extends BaseAPI {
|
|
|
22660
23344
|
/**
|
|
22661
23345
|
* Apply launch-static activity data to CMI while the new SCO is pre-initialize.
|
|
22662
23346
|
*
|
|
22663
|
-
* @spec SCORM 2004 4th Ed. RTE 4.2.
|
|
23347
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold
|
|
23348
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
22664
23349
|
*/
|
|
22665
23350
|
applyCurrentActivityLaunchData() {
|
|
22666
23351
|
const currentActivity = this._sequencing?.getCurrentActivity();
|
|
22667
|
-
if (!
|
|
23352
|
+
if (!currentActivity) {
|
|
23353
|
+
return;
|
|
23354
|
+
}
|
|
23355
|
+
this.applyActivityLaunchData(currentActivity);
|
|
23356
|
+
}
|
|
23357
|
+
/**
|
|
23358
|
+
* Apply launch-static CMI data when sequencing delivers a new activity before SCO Initialize.
|
|
23359
|
+
*
|
|
23360
|
+
* @spec SCORM 2004 4th Ed. SN DB.2 - Content Delivery Environment Process establishes the delivered activity.
|
|
23361
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold is initialized before SCO access.
|
|
23362
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives is initialized before SCO access.
|
|
23363
|
+
*/
|
|
23364
|
+
applyDeliveredActivityLaunchData(activity) {
|
|
23365
|
+
if (!this.isNotInitialized()) {
|
|
23366
|
+
return;
|
|
23367
|
+
}
|
|
23368
|
+
this.applyActivityLaunchData(activity);
|
|
23369
|
+
}
|
|
23370
|
+
/**
|
|
23371
|
+
* Copy the delivered activity's static launch data into the fresh CMI model.
|
|
23372
|
+
*
|
|
23373
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold
|
|
23374
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
23375
|
+
*/
|
|
23376
|
+
applyActivityLaunchData(currentActivity) {
|
|
23377
|
+
if (!this.cmi) {
|
|
22668
23378
|
return;
|
|
22669
23379
|
}
|
|
22670
23380
|
const contentActivityData = this._sequencing.overallSequencingProcess?.getContentActivityData(currentActivity);
|
|
22671
23381
|
const completionThreshold = contentActivityData?.completionThreshold ?? currentActivity.completionThreshold?.toString() ?? "";
|
|
22672
23382
|
this.cmi.completion_threshold = completionThreshold;
|
|
23383
|
+
this.seedCurrentActivityObjectives(currentActivity);
|
|
23384
|
+
}
|
|
23385
|
+
/**
|
|
23386
|
+
* Seed CMI objective ids after Initialize when automatic sequencing starts during Initialize.
|
|
23387
|
+
*
|
|
23388
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
23389
|
+
*/
|
|
23390
|
+
applyCurrentActivityObjectiveData() {
|
|
23391
|
+
const currentActivity = this._sequencing?.getCurrentActivity();
|
|
23392
|
+
if (!this.cmi || !currentActivity) {
|
|
23393
|
+
return;
|
|
23394
|
+
}
|
|
23395
|
+
this.seedCurrentActivityObjectives(currentActivity);
|
|
23396
|
+
}
|
|
23397
|
+
/**
|
|
23398
|
+
* Seed CMI objectives from primary and secondary activity objectives.
|
|
23399
|
+
*
|
|
23400
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17 - Initialization of Run-Time Objectives from Sequencing Information
|
|
23401
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.id and success_status
|
|
23402
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 / ADLSEQ objectives extension - objective read maps seed the RTE view
|
|
23403
|
+
*/
|
|
23404
|
+
seedCurrentActivityObjectives(currentActivity) {
|
|
23405
|
+
const activityObjectives = [];
|
|
23406
|
+
if (currentActivity.primaryObjective) {
|
|
23407
|
+
activityObjectives.push(currentActivity.primaryObjective);
|
|
23408
|
+
}
|
|
23409
|
+
activityObjectives.push(...currentActivity.objectives);
|
|
23410
|
+
const seededObjectiveIds = /* @__PURE__ */ new Set();
|
|
23411
|
+
for (const activityObjective of activityObjectives) {
|
|
23412
|
+
const objectiveId = this.getSeedableObjectiveId(activityObjective);
|
|
23413
|
+
if (!objectiveId || seededObjectiveIds.has(objectiveId)) {
|
|
23414
|
+
continue;
|
|
23415
|
+
}
|
|
23416
|
+
seededObjectiveIds.add(objectiveId);
|
|
23417
|
+
const index = this.findOrSeedCMIObjective(objectiveId);
|
|
23418
|
+
if (index === null) {
|
|
23419
|
+
continue;
|
|
23420
|
+
}
|
|
23421
|
+
const cmiObjective = this.cmi.objectives.findObjectiveByIndex(index);
|
|
23422
|
+
if (cmiObjective && this.cmi.objectives.initialized && !cmiObjective.initialized) {
|
|
23423
|
+
cmiObjective.initialize();
|
|
23424
|
+
}
|
|
23425
|
+
const successStatus = this.getActivityObjectiveSuccessStatus(activityObjective);
|
|
23426
|
+
if (successStatus) {
|
|
23427
|
+
this._commonSetCMIValue(
|
|
23428
|
+
"SeedActivityObjective",
|
|
23429
|
+
true,
|
|
23430
|
+
`cmi.objectives.${index}.success_status`,
|
|
23431
|
+
successStatus
|
|
23432
|
+
);
|
|
23433
|
+
}
|
|
23434
|
+
this.seedObjectiveReadMapValues(currentActivity, activityObjective, index);
|
|
23435
|
+
}
|
|
23436
|
+
}
|
|
23437
|
+
/**
|
|
23438
|
+
* Seed CMI objective fields from this objective's read-mapped global objectives.
|
|
23439
|
+
*
|
|
23440
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17 - Run-Time Objectives are initialized from sequencing information
|
|
23441
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read mapInfo grants access to mapped global objective state
|
|
23442
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps seed RTE score fields
|
|
23443
|
+
*/
|
|
23444
|
+
seedObjectiveReadMapValues(currentActivity, activityObjective, objectiveIndex) {
|
|
23445
|
+
const globalObjectiveMap = this._sequencing.overallSequencingProcess?.getGlobalObjectiveMap();
|
|
23446
|
+
if (!globalObjectiveMap || activityObjective.mapInfo.length === 0) {
|
|
23447
|
+
return;
|
|
23448
|
+
}
|
|
23449
|
+
for (const mapInfo of activityObjective.mapInfo) {
|
|
23450
|
+
const targetObjectiveId = mapInfo.targetObjectiveID || activityObjective.id;
|
|
23451
|
+
const globalObjective = globalObjectiveMap.get(targetObjectiveId);
|
|
23452
|
+
if (!globalObjective) {
|
|
23453
|
+
continue;
|
|
23454
|
+
}
|
|
23455
|
+
const readState = GlobalObjectiveSynchronizer.getGlobalObjectiveReadState(
|
|
23456
|
+
currentActivity,
|
|
23457
|
+
activityObjective,
|
|
23458
|
+
mapInfo,
|
|
23459
|
+
globalObjective
|
|
23460
|
+
);
|
|
23461
|
+
this.applyObjectiveReadStateToCMI(objectiveIndex, readState);
|
|
23462
|
+
}
|
|
23463
|
+
}
|
|
23464
|
+
/**
|
|
23465
|
+
* Copy mapped global objective state into the seeded CMI objective entry.
|
|
23466
|
+
*
|
|
23467
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives launch-time initialization
|
|
23468
|
+
* @spec SCORM 2004 4th Ed. SN 3.10.3 - read maps populate the RTE view without creating local writes
|
|
23469
|
+
* @spec SCORM 2004 4th Ed. ADLSEQ objectives extension - raw/min/max score read maps populate CMI objective scores
|
|
23470
|
+
*/
|
|
23471
|
+
applyObjectiveReadStateToCMI(objectiveIndex, readState) {
|
|
23472
|
+
if (readState.satisfiedStatus !== void 0) {
|
|
23473
|
+
this._commonSetCMIValue(
|
|
23474
|
+
"SeedActivityObjectiveReadMap",
|
|
23475
|
+
true,
|
|
23476
|
+
`cmi.objectives.${objectiveIndex}.success_status`,
|
|
23477
|
+
readState.satisfiedStatus ? SuccessStatus.PASSED : SuccessStatus.FAILED
|
|
23478
|
+
);
|
|
23479
|
+
}
|
|
23480
|
+
if (readState.normalizedMeasure !== void 0) {
|
|
23481
|
+
this._commonSetCMIValue(
|
|
23482
|
+
"SeedActivityObjectiveReadMap",
|
|
23483
|
+
true,
|
|
23484
|
+
`cmi.objectives.${objectiveIndex}.score.scaled`,
|
|
23485
|
+
String(readState.normalizedMeasure)
|
|
23486
|
+
);
|
|
23487
|
+
}
|
|
23488
|
+
if (readState.completionStatus !== void 0) {
|
|
23489
|
+
this._commonSetCMIValue(
|
|
23490
|
+
"SeedActivityObjectiveReadMap",
|
|
23491
|
+
true,
|
|
23492
|
+
`cmi.objectives.${objectiveIndex}.completion_status`,
|
|
23493
|
+
readState.completionStatus
|
|
23494
|
+
);
|
|
23495
|
+
}
|
|
23496
|
+
if (readState.progressMeasure !== void 0) {
|
|
23497
|
+
this._commonSetCMIValue(
|
|
23498
|
+
"SeedActivityObjectiveReadMap",
|
|
23499
|
+
true,
|
|
23500
|
+
`cmi.objectives.${objectiveIndex}.progress_measure`,
|
|
23501
|
+
String(readState.progressMeasure)
|
|
23502
|
+
);
|
|
23503
|
+
}
|
|
23504
|
+
if (readState.rawScore !== void 0) {
|
|
23505
|
+
this._commonSetCMIValue(
|
|
23506
|
+
"SeedActivityObjectiveReadMap",
|
|
23507
|
+
true,
|
|
23508
|
+
`cmi.objectives.${objectiveIndex}.score.raw`,
|
|
23509
|
+
readState.rawScore
|
|
23510
|
+
);
|
|
23511
|
+
}
|
|
23512
|
+
if (readState.minScore !== void 0) {
|
|
23513
|
+
this._commonSetCMIValue(
|
|
23514
|
+
"SeedActivityObjectiveReadMap",
|
|
23515
|
+
true,
|
|
23516
|
+
`cmi.objectives.${objectiveIndex}.score.min`,
|
|
23517
|
+
readState.minScore
|
|
23518
|
+
);
|
|
23519
|
+
}
|
|
23520
|
+
if (readState.maxScore !== void 0) {
|
|
23521
|
+
this._commonSetCMIValue(
|
|
23522
|
+
"SeedActivityObjectiveReadMap",
|
|
23523
|
+
true,
|
|
23524
|
+
`cmi.objectives.${objectiveIndex}.score.max`,
|
|
23525
|
+
readState.maxScore
|
|
23526
|
+
);
|
|
23527
|
+
}
|
|
23528
|
+
}
|
|
23529
|
+
/**
|
|
23530
|
+
* Return a manifest-defined objective id that can initialize cmi.objectives.n.id.
|
|
23531
|
+
*
|
|
23532
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.id
|
|
23533
|
+
*/
|
|
23534
|
+
getSeedableObjectiveId(activityObjective) {
|
|
23535
|
+
const objectiveId = activityObjective.id;
|
|
23536
|
+
if (typeof objectiveId !== "string" || objectiveId.trim() === "") {
|
|
23537
|
+
return null;
|
|
23538
|
+
}
|
|
23539
|
+
return objectiveId;
|
|
23540
|
+
}
|
|
23541
|
+
/**
|
|
23542
|
+
* Find an existing CMI objective id or create the next CMI objective array entry.
|
|
23543
|
+
*
|
|
23544
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives._count
|
|
23545
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.id
|
|
23546
|
+
*/
|
|
23547
|
+
findOrSeedCMIObjective(objectiveId) {
|
|
23548
|
+
const existingIndex = this.cmi.objectives.childArray.findIndex((objective) => {
|
|
23549
|
+
return objective.id === objectiveId;
|
|
23550
|
+
});
|
|
23551
|
+
if (existingIndex >= 0) {
|
|
23552
|
+
return existingIndex;
|
|
23553
|
+
}
|
|
23554
|
+
const index = this.cmi.objectives.childArray.length;
|
|
23555
|
+
const result = this._commonSetCMIValue(
|
|
23556
|
+
"SeedActivityObjective",
|
|
23557
|
+
true,
|
|
23558
|
+
`cmi.objectives.${index}.id`,
|
|
23559
|
+
objectiveId
|
|
23560
|
+
);
|
|
23561
|
+
return result === global_constants.SCORM_TRUE ? index : null;
|
|
23562
|
+
}
|
|
23563
|
+
/**
|
|
23564
|
+
* Translate a known activity objective satisfied status to cmi.objectives.n.success_status.
|
|
23565
|
+
*
|
|
23566
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives.n.success_status
|
|
23567
|
+
*/
|
|
23568
|
+
getActivityObjectiveSuccessStatus(activityObjective) {
|
|
23569
|
+
if (activityObjective.progressStatus || activityObjective.satisfiedStatusKnown) {
|
|
23570
|
+
return activityObjective.satisfiedStatus ? SuccessStatus.PASSED : SuccessStatus.FAILED;
|
|
23571
|
+
}
|
|
23572
|
+
return null;
|
|
22673
23573
|
}
|
|
22674
23574
|
/**
|
|
22675
23575
|
* Getter for _version
|
|
@@ -22750,6 +23650,7 @@ class Scorm2004API extends BaseAPI {
|
|
|
22750
23650
|
);
|
|
22751
23651
|
if (result === global_constants.SCORM_TRUE && this._sequencingService) {
|
|
22752
23652
|
this._sequencingService.initialize();
|
|
23653
|
+
this.applyCurrentActivityObjectiveData();
|
|
22753
23654
|
}
|
|
22754
23655
|
if (result === global_constants.SCORM_TRUE) {
|
|
22755
23656
|
this._globalObjectiveManager.restoreGlobalObjectivesToCMI();
|
|
@@ -23004,7 +23905,7 @@ class Scorm2004API extends BaseAPI {
|
|
|
23004
23905
|
objective_id = objective ? objective.id : void 0;
|
|
23005
23906
|
}
|
|
23006
23907
|
const is_global = objective_id && this.settings.globalObjectiveIds?.includes(objective_id);
|
|
23007
|
-
if (is_global) {
|
|
23908
|
+
if (is_global && this.currentActivityAllowsGlobalObjectiveWrites()) {
|
|
23008
23909
|
const { index: global_index } = this._globalObjectiveManager.findOrCreateGlobalObjective(objective_id);
|
|
23009
23910
|
const global_element = CMIElement.replace(
|
|
23010
23911
|
element_base,
|
|
@@ -23019,6 +23920,15 @@ class Scorm2004API extends BaseAPI {
|
|
|
23019
23920
|
}
|
|
23020
23921
|
return this._commonSetCMIValue("SetValue", true, CMIElement, value);
|
|
23021
23922
|
}
|
|
23923
|
+
/**
|
|
23924
|
+
* Return whether the current activity can update shared global objectives.
|
|
23925
|
+
*
|
|
23926
|
+
* @spec SCORM 2004 4th Ed. SN 3.13.1 Tracked - when False, the LMS
|
|
23927
|
+
* "does not initialize, manage or access any tracking status information".
|
|
23928
|
+
*/
|
|
23929
|
+
currentActivityAllowsGlobalObjectiveWrites() {
|
|
23930
|
+
return this._sequencing?.getCurrentActivity()?.sequencingControls.tracked !== false;
|
|
23931
|
+
}
|
|
23022
23932
|
/**
|
|
23023
23933
|
* Gets or builds a new child element to add to the array
|
|
23024
23934
|
* @param {string} CMIElement
|
|
@@ -23124,7 +24034,8 @@ class Scorm2004API extends BaseAPI {
|
|
|
23124
24034
|
this.throwSCORMError(
|
|
23125
24035
|
CMIElement,
|
|
23126
24036
|
this._error_codes.VALUE_NOT_INITIALIZED ?? 403,
|
|
23127
|
-
`The data model element passed to GetValue (${CMIElement}) has not been initialized
|
|
24037
|
+
`The data model element passed to GetValue (${CMIElement}) has not been initialized.`,
|
|
24038
|
+
this.settings.uninitializedGetLogLevel
|
|
23128
24039
|
);
|
|
23129
24040
|
}
|
|
23130
24041
|
/**
|
|
@@ -23363,9 +24274,9 @@ class Scorm2004API extends BaseAPI {
|
|
|
23363
24274
|
this.loggingService,
|
|
23364
24275
|
sequencingConfig
|
|
23365
24276
|
);
|
|
23366
|
-
|
|
23367
|
-
this.
|
|
23368
|
-
|
|
24277
|
+
this._sequencingService.setEventListeners(
|
|
24278
|
+
this.buildSequencingEventListeners(settings?.sequencing?.eventListeners)
|
|
24279
|
+
);
|
|
23369
24280
|
this._globalObjectiveManager.updateSequencingService(this._sequencingService);
|
|
23370
24281
|
this._dataSerializer.updateSequencingService(this._sequencingService);
|
|
23371
24282
|
if (settings?.sequencingStatePersistence) {
|
|
@@ -23388,6 +24299,22 @@ class Scorm2004API extends BaseAPI {
|
|
|
23388
24299
|
this._sequencingService = null;
|
|
23389
24300
|
}
|
|
23390
24301
|
}
|
|
24302
|
+
/**
|
|
24303
|
+
* Wrap LMS-provided sequencing listeners with API-owned delivery bookkeeping.
|
|
24304
|
+
*
|
|
24305
|
+
* @spec SCORM 2004 4th Ed. SN DB.2 - Content Delivery Environment Process
|
|
24306
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5, Table 4.2.5a - cmi.completion_threshold
|
|
24307
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.17, Table 4.2.17a - cmi.objectives
|
|
24308
|
+
*/
|
|
24309
|
+
buildSequencingEventListeners(listeners) {
|
|
24310
|
+
return {
|
|
24311
|
+
...listeners,
|
|
24312
|
+
onActivityDelivery: (activity) => {
|
|
24313
|
+
this.applyDeliveredActivityLaunchData(activity);
|
|
24314
|
+
listeners?.onActivityDelivery?.(activity);
|
|
24315
|
+
}
|
|
24316
|
+
};
|
|
24317
|
+
}
|
|
23391
24318
|
/**
|
|
23392
24319
|
* Get the sequencing service
|
|
23393
24320
|
* @return {SequencingService | null}
|
|
@@ -23398,10 +24325,13 @@ class Scorm2004API extends BaseAPI {
|
|
|
23398
24325
|
/**
|
|
23399
24326
|
* Set sequencing event listeners
|
|
23400
24327
|
* @param {SequencingEventListeners} listeners
|
|
24328
|
+
*
|
|
24329
|
+
* @spec SCORM 2004 4th Ed. SN DB.2 - Content Delivery Environment Process
|
|
24330
|
+
* @spec SCORM 2004 4th Ed. RTE 4.2.5 / 4.2.17 - launch-static CMI data
|
|
23401
24331
|
*/
|
|
23402
24332
|
setSequencingEventListeners(listeners) {
|
|
23403
24333
|
if (this._sequencingService) {
|
|
23404
|
-
this._sequencingService.setEventListeners(listeners);
|
|
24334
|
+
this._sequencingService.setEventListeners(this.buildSequencingEventListeners(listeners));
|
|
23405
24335
|
}
|
|
23406
24336
|
}
|
|
23407
24337
|
/**
|