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