scorm-again 3.0.4 → 3.0.5

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.
@@ -1159,6 +1159,11 @@
1159
1159
  progress_range: "0#1"
1160
1160
  };
1161
1161
 
1162
+ const PERFORMANCE_STEP_NAME = "^$|" + scorm2004_regex.CMIShortIdentifier;
1163
+ const PERFORMANCE_CHARACTERSTRING = "(?![\\s\\S]*(?:\\[,\\]|\\[\\.\\]|\\[:\\]))[\\s\\S]{1,250}";
1164
+ const PERFORMANCE_NUMERIC_RANGE = "(?:-?\\d+(?:\\.\\d+)?)?\\[:\\](?:-?\\d+(?:\\.\\d+)?)?";
1165
+ const CR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_NUMERIC_RANGE + "|" + PERFORMANCE_CHARACTERSTRING + ")$";
1166
+ const LR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_CHARACTERSTRING + ")$";
1162
1167
  const LearnerResponses = {
1163
1168
  "true-false": {
1164
1169
  format: "^true$|^false$",
@@ -1193,8 +1198,8 @@
1193
1198
  unique: false
1194
1199
  },
1195
1200
  performance: {
1196
- format: "^$|" + scorm2004_regex.CMIShortIdentifier,
1197
- format2: scorm2004_regex.CMIDecimal + "|^$|" + scorm2004_regex.CMIShortIdentifier,
1201
+ format: PERFORMANCE_STEP_NAME,
1202
+ format2: LR_PERFORMANCE_STEP_ANSWER,
1198
1203
  max: 250,
1199
1204
  delimiter: "[,]",
1200
1205
  delimiter2: "[.]",
@@ -1270,10 +1275,10 @@
1270
1275
  delimiter2: "[.]",
1271
1276
  unique: false,
1272
1277
  duplicate: false,
1273
- // step_name must be a non-empty short identifier
1274
- format: scorm2004_regex.CMIShortIdentifier,
1275
- // step_answer may be short identifier or numeric range (<decimal>[:<decimal>])
1276
- format2: `^(${scorm2004_regex.CMIShortIdentifier})$|^(?:\\d+(?:\\.\\d+)?(?::\\d+(?:\\.\\d+)?)?)$`
1278
+ // step_name: optional short_identifier_type
1279
+ format: PERFORMANCE_STEP_NAME,
1280
+ // step_answer: optional characterstring (spaces allowed) or numeric range
1281
+ format2: CR_PERFORMANCE_STEP_ANSWER
1277
1282
  },
1278
1283
  sequencing: {
1279
1284
  max: 36,
@@ -5298,7 +5303,13 @@
5298
5303
  if (!scorm2004) {
5299
5304
  if (isFinalAttribute) {
5300
5305
  if (typeof attribute === "undefined" || !this.context.checkObjectHasProperty(refObject, attribute)) {
5301
- this.context.throwSCORMError(CMIElement, invalidErrorCode, invalidErrorMessage);
5306
+ if (attribute === "_children") {
5307
+ this.context.throwSCORMError(CMIElement, getErrorCode(this.context.errorCodes, "CHILDREN_ERROR"));
5308
+ } else if (attribute === "_count") {
5309
+ this.context.throwSCORMError(CMIElement, getErrorCode(this.context.errorCodes, "COUNT_ERROR"));
5310
+ } else {
5311
+ this.context.throwSCORMError(CMIElement, invalidErrorCode, invalidErrorMessage);
5312
+ }
5302
5313
  return {
5303
5314
  error: true
5304
5315
  };
@@ -15552,19 +15563,20 @@ ${stackTrace}`);
15552
15563
  if (errorCode === 143) returnValue = global_constants.SCORM_FALSE;
15553
15564
  } else {
15554
15565
  const result = this.storeData(false);
15555
- if ((result.errorCode ?? 0) > 0) {
15566
+ const errorCode = result.errorCode ?? 0;
15567
+ if (errorCode > 0) {
15556
15568
  if (result.errorMessage) {
15557
15569
  this.apiLog("commit", `Commit failed with error: ${result.errorMessage}`, LogLevelEnum.ERROR);
15558
15570
  }
15559
15571
  if (result.errorDetails) {
15560
15572
  this.apiLog("commit", `Error details: ${JSON.stringify(result.errorDetails)}`, LogLevelEnum.DEBUG);
15561
15573
  }
15562
- this.throwSCORMError("api", result.errorCode);
15574
+ this.throwSCORMError("api", errorCode);
15563
15575
  }
15564
15576
  const resultValue = result?.result ?? global_constants.SCORM_FALSE;
15565
15577
  returnValue = typeof resultValue === "boolean" ? String(resultValue) : resultValue;
15566
15578
  this.apiLog(callbackName, " Result: " + returnValue, LogLevelEnum.DEBUG, "HttpRequest");
15567
- if (checkTerminated) this.lastErrorCode = "0";
15579
+ if (checkTerminated && errorCode === 0) this.lastErrorCode = "0";
15568
15580
  this.processListeners(callbackName);
15569
15581
  if (this.settings.enableOfflineSupport && this._offlineStorageService && this._offlineStorageService.isDeviceOnline() && this._courseId) {
15570
15582
  this._offlineStorageService.hasPendingOfflineData(this._courseId).then(hasPendingData => {
@@ -18349,6 +18361,49 @@ ${stackTrace}`);
18349
18361
  }
18350
18362
  }
18351
18363
 
18364
+ function stripBrackets(delim) {
18365
+ return delim.replace(/[[\]]/g, "");
18366
+ }
18367
+ function escapeRegex(s) {
18368
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18369
+ }
18370
+ function splitDelimited(value, bracketed) {
18371
+ if (!bracketed) {
18372
+ return [value];
18373
+ }
18374
+ if (value.includes(bracketed)) {
18375
+ return value.split(bracketed);
18376
+ }
18377
+ const bare = stripBrackets(bracketed);
18378
+ if (!bare) {
18379
+ return [value];
18380
+ }
18381
+ const splitRe = new RegExp(`(?<!\\\\)${escapeRegex(bare)}`, "g");
18382
+ const unescapeRe = new RegExp(`\\\\${escapeRegex(bare)}`, "g");
18383
+ return value.split(splitRe).map(part => part.replace(unescapeRe, bare));
18384
+ }
18385
+ function splitFirstDelimited(value, bracketed) {
18386
+ if (!bracketed) {
18387
+ return [value];
18388
+ }
18389
+ if (value.includes(bracketed)) {
18390
+ const idx = value.indexOf(bracketed);
18391
+ return [value.slice(0, idx), value.slice(idx + bracketed.length)];
18392
+ }
18393
+ const bare = stripBrackets(bracketed);
18394
+ if (!bare) {
18395
+ return [value];
18396
+ }
18397
+ const splitRe = new RegExp(`(?<!\\\\)${escapeRegex(bare)}`);
18398
+ const unescapeRe = new RegExp(`\\\\${escapeRegex(bare)}`, "g");
18399
+ const parts = value.split(splitRe);
18400
+ const first = (parts[0] ?? "").replace(unescapeRe, bare);
18401
+ if (parts.length === 1) {
18402
+ return [first];
18403
+ }
18404
+ return [first, parts.slice(1).join(bare).replace(unescapeRe, bare)];
18405
+ }
18406
+
18352
18407
  var __defProp$l = Object.defineProperty;
18353
18408
  var __defNormalProp$l = (obj, key, value) => key in obj ? __defProp$l(obj, key, {
18354
18409
  enumerable: true,
@@ -18544,8 +18599,7 @@ ${stackTrace}`);
18544
18599
  const response_type = LearnerResponses[this.type];
18545
18600
  if (response_type) {
18546
18601
  if (response_type?.delimiter) {
18547
- const delimiter = response_type.delimiter === "[,]" ? "," : response_type.delimiter;
18548
- nodes = learner_response.split(delimiter);
18602
+ nodes = splitDelimited(learner_response, response_type.delimiter);
18549
18603
  } else {
18550
18604
  nodes[0] = learner_response;
18551
18605
  }
@@ -18553,10 +18607,10 @@ ${stackTrace}`);
18553
18607
  const formatRegex = new RegExp(response_type.format);
18554
18608
  for (let i = 0; i < nodes.length; i++) {
18555
18609
  if (response_type?.delimiter2) {
18556
- const delimiter2 = response_type.delimiter2 === "[.]" ? "." : response_type.delimiter2;
18557
- const values = nodes[i]?.split(delimiter2);
18610
+ const node = nodes[i] ?? "";
18611
+ const values = this.type === "performance" ? splitFirstDelimited(node, response_type.delimiter2) : splitDelimited(node, response_type.delimiter2);
18558
18612
  if (values?.length === 2) {
18559
- if (this.type === "performance" && (values[0] === "" || values[1] === "")) {
18613
+ if (this.type === "performance" && values[0] === "" && values[1] === "") {
18560
18614
  throw new Scorm2004ValidationError(this._cmi_element + ".learner_response", scorm2004_errors.TYPE_MISMATCH);
18561
18615
  }
18562
18616
  if (!values[0]?.match(formatRegex)) {
@@ -18737,37 +18791,19 @@ ${stackTrace}`);
18737
18791
  return result;
18738
18792
  }
18739
18793
  }
18740
- function stripBrackets(delim) {
18741
- return delim.replace(/[[\]]/g, "");
18742
- }
18743
- function escapeRegex(s) {
18744
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18745
- }
18746
- function splitUnescaped(text, delim) {
18747
- const reDelim = escapeRegex(delim);
18748
- const splitRe = new RegExp(`(?<!\\\\)${reDelim}`, "g");
18749
- const unescapeRe = new RegExp(`\\\\${reDelim}`, "g");
18750
- return text.split(splitRe).map(part => part.replace(unescapeRe, delim));
18751
- }
18752
- function splitFirstUnescaped(text, delim) {
18753
- const reDelim = escapeRegex(delim);
18754
- const splitRe = new RegExp(`(?<!\\\\)${reDelim}`);
18755
- const unescapeRe = new RegExp(`\\\\${reDelim}`, "g");
18756
- const parts = text.split(splitRe);
18757
- const firstPart = parts[0] ?? "";
18758
- if (parts.length === 1) {
18759
- return [firstPart.replace(unescapeRe, delim)];
18794
+ const RESPONSE_PREFIX_RE = /^\{(?:lang|case_matters|order_matters)=[^}]+\}/;
18795
+ function stripResponsePrefixes(node) {
18796
+ let result = node;
18797
+ while (RESPONSE_PREFIX_RE.test(result)) {
18798
+ result = result.replace(RESPONSE_PREFIX_RE, "");
18760
18799
  }
18761
- const part1 = firstPart.replace(unescapeRe, delim);
18762
- const part2 = parts.slice(1).join(delim).replace(unescapeRe, delim);
18763
- return [part1, part2];
18800
+ return result;
18764
18801
  }
18765
18802
  function validatePattern(type, pattern, responseDef) {
18766
18803
  if (pattern.trim() !== pattern) {
18767
18804
  throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
18768
18805
  }
18769
- const subDelim1 = responseDef.delimiter ? stripBrackets(responseDef.delimiter) : null;
18770
- const rawNodes = subDelim1 ? splitUnescaped(pattern, subDelim1) : [pattern];
18806
+ const rawNodes = responseDef.delimiter ? splitDelimited(pattern, responseDef.delimiter) : [pattern];
18771
18807
  for (const raw of rawNodes) {
18772
18808
  if (raw.trim() !== raw) {
18773
18809
  throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
@@ -18776,17 +18812,11 @@ ${stackTrace}`);
18776
18812
  if (type === "fill-in" && pattern === "") {
18777
18813
  return;
18778
18814
  }
18779
- const delim1 = responseDef.delimiter ? stripBrackets(responseDef.delimiter) : null;
18780
- let nodes;
18781
- if (delim1) {
18782
- nodes = splitUnescaped(pattern, delim1);
18783
- } else {
18784
- nodes = [pattern];
18785
- }
18815
+ const nodes = responseDef.delimiter ? splitDelimited(pattern, responseDef.delimiter) : [pattern];
18786
18816
  if (!responseDef.delimiter && pattern.includes(",")) {
18787
18817
  throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
18788
18818
  }
18789
- if (responseDef.unique || responseDef.duplicate === false) {
18819
+ if (type !== "numeric" && (responseDef.unique || responseDef.duplicate === false)) {
18790
18820
  const seen = new Set(nodes);
18791
18821
  if (seen.size !== nodes.length) {
18792
18822
  throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
@@ -18806,8 +18836,7 @@ ${stackTrace}`);
18806
18836
  if (!delimBracketed) {
18807
18837
  throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
18808
18838
  }
18809
- const delim = stripBrackets(delimBracketed);
18810
- const parts = value.split(new RegExp(`(?<!\\\\)${escapeRegex(delim)}`, "g")).map(n => n.replace(new RegExp(`\\\\${escapeRegex(delim)}`, "g"), delim));
18839
+ const parts = splitDelimited(value, delimBracketed);
18811
18840
  if (parts.length !== 2 || parts[0] === "" || parts[1] === "") {
18812
18841
  throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
18813
18842
  }
@@ -18819,12 +18848,14 @@ ${stackTrace}`);
18819
18848
  switch (type) {
18820
18849
  case "numeric":
18821
18850
  {
18822
- const numDelim = responseDef.delimiter ? stripBrackets(responseDef.delimiter) : ":";
18823
- const nums = node.split(numDelim);
18824
- if (nums.length < 1 || nums.length > 2) {
18825
- throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
18851
+ if (node === "") {
18852
+ const bracketedRange = nodes.length >= 2 && !!responseDef.delimiter && pattern.includes(responseDef.delimiter);
18853
+ if (!bracketedRange) {
18854
+ throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
18855
+ }
18856
+ break;
18826
18857
  }
18827
- nums.forEach(checkSingle);
18858
+ checkSingle(node);
18828
18859
  break;
18829
18860
  }
18830
18861
  case "performance":
@@ -18833,13 +18864,13 @@ ${stackTrace}`);
18833
18864
  if (!delimBracketed) {
18834
18865
  throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
18835
18866
  }
18836
- const delim = stripBrackets(delimBracketed);
18837
- const parts = splitFirstUnescaped(node, delim);
18867
+ const record = stripResponsePrefixes(node);
18868
+ const parts = splitFirstDelimited(record, delimBracketed);
18838
18869
  if (parts.length !== 2) {
18839
18870
  throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
18840
18871
  }
18841
18872
  const [part1, part2] = parts;
18842
- if (part1 === "" || part2 === "" || part1 === part2) {
18873
+ if (part1 === "" && part2 === "") {
18843
18874
  throw new Scorm2004ValidationError("cmi.interactions.n.correct_responses.n.pattern", scorm2004_errors.TYPE_MISMATCH);
18844
18875
  }
18845
18876
  if (part1 === void 0 || !fmt1.test(part1)) {
@@ -21625,7 +21656,7 @@ ${stackTrace}`);
21625
21656
  checkValidResponseType(CMIElement, response_type, value, interaction_type) {
21626
21657
  let nodes = [];
21627
21658
  if (response_type?.delimiter) {
21628
- nodes = String(value).split(response_type.delimiter);
21659
+ nodes = splitDelimited(String(value), response_type.delimiter);
21629
21660
  } else {
21630
21661
  nodes[0] = value;
21631
21662
  }
@@ -21723,26 +21754,33 @@ ${stackTrace}`);
21723
21754
  nodes[i] = this.removeCorrectResponsePrefixes(CMIElement, nodes[i]);
21724
21755
  }
21725
21756
  if (response?.delimiter2) {
21726
- const values = nodes[i].split(response.delimiter2);
21757
+ const values = interaction_type === "performance" ? splitFirstDelimited(nodes[i], response.delimiter2) : splitDelimited(nodes[i], response.delimiter2);
21727
21758
  if (values.length === 2) {
21728
- const matches = values[0].match(formatRegex);
21729
- if (!matches) {
21759
+ if (interaction_type === "performance" && values[0] === "" && values[1] === "") {
21730
21760
  this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
21731
21761
  } else {
21732
- if (!response.format2 || !values[1].match(new RegExp(response.format2))) {
21762
+ const matches = values[0]?.match(formatRegex);
21763
+ if (!matches) {
21733
21764
  this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
21765
+ } else {
21766
+ if (!response.format2 || !values[1]?.match(new RegExp(response.format2))) {
21767
+ this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
21768
+ }
21734
21769
  }
21735
21770
  }
21736
21771
  } else {
21737
21772
  this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
21738
21773
  }
21739
21774
  } else {
21775
+ if (interaction_type === "numeric" && nodes.length > 1 && nodes[i] === "" && !!response.delimiter && String(value).includes(response.delimiter)) {
21776
+ continue;
21777
+ }
21740
21778
  const matches = nodes[i].match(formatRegex);
21741
21779
  if (!matches && value !== "" || !matches && interaction_type === "true-false") {
21742
21780
  this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
21743
21781
  } else {
21744
21782
  if (interaction_type === "numeric" && nodes.length > 1) {
21745
- if (Number(nodes[0]) > Number(nodes[1])) {
21783
+ if (nodes[0] !== "" && nodes[1] !== "" && Number(nodes[0]) > Number(nodes[1])) {
21746
21784
  this.context.throwSCORMError(CMIElement, scorm2004_errors.TYPE_MISMATCH, `${interaction_type}: ${value}`);
21747
21785
  }
21748
21786
  } else {