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.
- package/README.md +19 -4
- package/dist/esm/scorm-again.js +115 -67
- package/dist/esm/scorm-again.js.map +1 -1
- package/dist/esm/scorm-again.min.js +1 -1
- package/dist/esm/scorm-again.min.js.map +1 -1
- package/dist/esm/scorm12.js +17 -4
- package/dist/esm/scorm12.js.map +1 -1
- package/dist/esm/scorm12.min.js +1 -1
- package/dist/esm/scorm12.min.js.map +1 -1
- package/dist/esm/scorm2004.js +115 -67
- package/dist/esm/scorm2004.js.map +1 -1
- package/dist/esm/scorm2004.min.js +1 -1
- package/dist/esm/scorm2004.min.js.map +1 -1
- package/dist/scorm-again.js +102 -64
- package/dist/scorm-again.js.map +1 -1
- package/dist/scorm-again.min.js +1 -1
- package/dist/scorm-again.min.js.map +1 -1
- package/dist/scorm12.js +11 -4
- package/dist/scorm12.js.map +1 -1
- package/dist/scorm12.min.js +1 -1
- package/dist/scorm12.min.js.map +1 -1
- package/dist/scorm2004.js +102 -64
- package/dist/scorm2004.js.map +1 -1
- package/dist/scorm2004.min.js +1 -1
- package/dist/scorm2004.min.js.map +1 -1
- package/dist/types/cmi/scorm2004/interaction_delimiters.d.ts +4 -0
- package/package.json +42 -21
package/dist/esm/scorm2004.js
CHANGED
|
@@ -940,6 +940,11 @@ const scorm2004_regex = {
|
|
|
940
940
|
progress_range: "0#1"
|
|
941
941
|
};
|
|
942
942
|
|
|
943
|
+
const PERFORMANCE_STEP_NAME = "^$|" + scorm2004_regex.CMIShortIdentifier;
|
|
944
|
+
const PERFORMANCE_CHARACTERSTRING = "(?![\\s\\S]*(?:\\[,\\]|\\[\\.\\]|\\[:\\]))[\\s\\S]{1,250}";
|
|
945
|
+
const PERFORMANCE_NUMERIC_RANGE = "(?:-?\\d+(?:\\.\\d+)?)?\\[:\\](?:-?\\d+(?:\\.\\d+)?)?";
|
|
946
|
+
const CR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_NUMERIC_RANGE + "|" + PERFORMANCE_CHARACTERSTRING + ")$";
|
|
947
|
+
const LR_PERFORMANCE_STEP_ANSWER = "^(?:|" + PERFORMANCE_CHARACTERSTRING + ")$";
|
|
943
948
|
const LearnerResponses = {
|
|
944
949
|
"true-false": {
|
|
945
950
|
format: "^true$|^false$",
|
|
@@ -974,8 +979,8 @@ const LearnerResponses = {
|
|
|
974
979
|
unique: false
|
|
975
980
|
},
|
|
976
981
|
performance: {
|
|
977
|
-
format:
|
|
978
|
-
format2:
|
|
982
|
+
format: PERFORMANCE_STEP_NAME,
|
|
983
|
+
format2: LR_PERFORMANCE_STEP_ANSWER,
|
|
979
984
|
max: 250,
|
|
980
985
|
delimiter: "[,]",
|
|
981
986
|
delimiter2: "[.]",
|
|
@@ -1051,10 +1056,10 @@ const CorrectResponses = {
|
|
|
1051
1056
|
delimiter2: "[.]",
|
|
1052
1057
|
unique: false,
|
|
1053
1058
|
duplicate: false,
|
|
1054
|
-
// step_name
|
|
1055
|
-
format:
|
|
1056
|
-
// step_answer
|
|
1057
|
-
format2:
|
|
1059
|
+
// step_name: optional short_identifier_type
|
|
1060
|
+
format: PERFORMANCE_STEP_NAME,
|
|
1061
|
+
// step_answer: optional characterstring (spaces allowed) or numeric range
|
|
1062
|
+
format2: CR_PERFORMANCE_STEP_ANSWER
|
|
1058
1063
|
},
|
|
1059
1064
|
sequencing: {
|
|
1060
1065
|
max: 36,
|
|
@@ -5363,7 +5368,19 @@ class CMIValueAccessService {
|
|
|
5363
5368
|
if (!scorm2004) {
|
|
5364
5369
|
if (isFinalAttribute) {
|
|
5365
5370
|
if (typeof attribute === "undefined" || !this.context.checkObjectHasProperty(refObject, attribute)) {
|
|
5366
|
-
|
|
5371
|
+
if (attribute === "_children") {
|
|
5372
|
+
this.context.throwSCORMError(
|
|
5373
|
+
CMIElement,
|
|
5374
|
+
getErrorCode(this.context.errorCodes, "CHILDREN_ERROR")
|
|
5375
|
+
);
|
|
5376
|
+
} else if (attribute === "_count") {
|
|
5377
|
+
this.context.throwSCORMError(
|
|
5378
|
+
CMIElement,
|
|
5379
|
+
getErrorCode(this.context.errorCodes, "COUNT_ERROR")
|
|
5380
|
+
);
|
|
5381
|
+
} else {
|
|
5382
|
+
this.context.throwSCORMError(CMIElement, invalidErrorCode, invalidErrorMessage);
|
|
5383
|
+
}
|
|
5367
5384
|
return { error: true };
|
|
5368
5385
|
}
|
|
5369
5386
|
}
|
|
@@ -15767,7 +15784,8 @@ class BaseAPI {
|
|
|
15767
15784
|
if (errorCode === 143) returnValue = global_constants.SCORM_FALSE;
|
|
15768
15785
|
} else {
|
|
15769
15786
|
const result = this.storeData(false);
|
|
15770
|
-
|
|
15787
|
+
const errorCode = result.errorCode ?? 0;
|
|
15788
|
+
if (errorCode > 0) {
|
|
15771
15789
|
if (result.errorMessage) {
|
|
15772
15790
|
this.apiLog(
|
|
15773
15791
|
"commit",
|
|
@@ -15782,12 +15800,12 @@ class BaseAPI {
|
|
|
15782
15800
|
LogLevelEnum.DEBUG
|
|
15783
15801
|
);
|
|
15784
15802
|
}
|
|
15785
|
-
this.throwSCORMError("api",
|
|
15803
|
+
this.throwSCORMError("api", errorCode);
|
|
15786
15804
|
}
|
|
15787
15805
|
const resultValue = result?.result ?? global_constants.SCORM_FALSE;
|
|
15788
15806
|
returnValue = typeof resultValue === "boolean" ? String(resultValue) : resultValue;
|
|
15789
15807
|
this.apiLog(callbackName, " Result: " + returnValue, LogLevelEnum.DEBUG, "HttpRequest");
|
|
15790
|
-
if (checkTerminated) this.lastErrorCode = "0";
|
|
15808
|
+
if (checkTerminated && errorCode === 0) this.lastErrorCode = "0";
|
|
15791
15809
|
this.processListeners(callbackName);
|
|
15792
15810
|
if (this.settings.enableOfflineSupport && this._offlineStorageService && this._offlineStorageService.isDeviceOnline() && this._courseId) {
|
|
15793
15811
|
this._offlineStorageService.hasPendingOfflineData(this._courseId).then((hasPendingData) => {
|
|
@@ -16559,6 +16577,49 @@ class CMILearnerPreference extends BaseCMI {
|
|
|
16559
16577
|
}
|
|
16560
16578
|
}
|
|
16561
16579
|
|
|
16580
|
+
function stripBrackets(delim) {
|
|
16581
|
+
return delim.replace(/[[\]]/g, "");
|
|
16582
|
+
}
|
|
16583
|
+
function escapeRegex(s) {
|
|
16584
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16585
|
+
}
|
|
16586
|
+
function splitDelimited(value, bracketed) {
|
|
16587
|
+
if (!bracketed) {
|
|
16588
|
+
return [value];
|
|
16589
|
+
}
|
|
16590
|
+
if (value.includes(bracketed)) {
|
|
16591
|
+
return value.split(bracketed);
|
|
16592
|
+
}
|
|
16593
|
+
const bare = stripBrackets(bracketed);
|
|
16594
|
+
if (!bare) {
|
|
16595
|
+
return [value];
|
|
16596
|
+
}
|
|
16597
|
+
const splitRe = new RegExp(`(?<!\\\\)${escapeRegex(bare)}`, "g");
|
|
16598
|
+
const unescapeRe = new RegExp(`\\\\${escapeRegex(bare)}`, "g");
|
|
16599
|
+
return value.split(splitRe).map((part) => part.replace(unescapeRe, bare));
|
|
16600
|
+
}
|
|
16601
|
+
function splitFirstDelimited(value, bracketed) {
|
|
16602
|
+
if (!bracketed) {
|
|
16603
|
+
return [value];
|
|
16604
|
+
}
|
|
16605
|
+
if (value.includes(bracketed)) {
|
|
16606
|
+
const idx = value.indexOf(bracketed);
|
|
16607
|
+
return [value.slice(0, idx), value.slice(idx + bracketed.length)];
|
|
16608
|
+
}
|
|
16609
|
+
const bare = stripBrackets(bracketed);
|
|
16610
|
+
if (!bare) {
|
|
16611
|
+
return [value];
|
|
16612
|
+
}
|
|
16613
|
+
const splitRe = new RegExp(`(?<!\\\\)${escapeRegex(bare)}`);
|
|
16614
|
+
const unescapeRe = new RegExp(`\\\\${escapeRegex(bare)}`, "g");
|
|
16615
|
+
const parts = value.split(splitRe);
|
|
16616
|
+
const first = (parts[0] ?? "").replace(unescapeRe, bare);
|
|
16617
|
+
if (parts.length === 1) {
|
|
16618
|
+
return [first];
|
|
16619
|
+
}
|
|
16620
|
+
return [first, parts.slice(1).join(bare).replace(unescapeRe, bare)];
|
|
16621
|
+
}
|
|
16622
|
+
|
|
16562
16623
|
class CMIInteractions extends CMIArray {
|
|
16563
16624
|
/**
|
|
16564
16625
|
* Constructor for `cmi.interactions` Array
|
|
@@ -16768,8 +16829,7 @@ class CMIInteractionsObject extends BaseCMI {
|
|
|
16768
16829
|
const response_type = LearnerResponses[this.type];
|
|
16769
16830
|
if (response_type) {
|
|
16770
16831
|
if (response_type?.delimiter) {
|
|
16771
|
-
|
|
16772
|
-
nodes = learner_response.split(delimiter);
|
|
16832
|
+
nodes = splitDelimited(learner_response, response_type.delimiter);
|
|
16773
16833
|
} else {
|
|
16774
16834
|
nodes[0] = learner_response;
|
|
16775
16835
|
}
|
|
@@ -16777,10 +16837,10 @@ class CMIInteractionsObject extends BaseCMI {
|
|
|
16777
16837
|
const formatRegex = new RegExp(response_type.format);
|
|
16778
16838
|
for (let i = 0; i < nodes.length; i++) {
|
|
16779
16839
|
if (response_type?.delimiter2) {
|
|
16780
|
-
const
|
|
16781
|
-
const values =
|
|
16840
|
+
const node = nodes[i] ?? "";
|
|
16841
|
+
const values = this.type === "performance" ? splitFirstDelimited(node, response_type.delimiter2) : splitDelimited(node, response_type.delimiter2);
|
|
16782
16842
|
if (values?.length === 2) {
|
|
16783
|
-
if (this.type === "performance" &&
|
|
16843
|
+
if (this.type === "performance" && values[0] === "" && values[1] === "") {
|
|
16784
16844
|
throw new Scorm2004ValidationError(
|
|
16785
16845
|
this._cmi_element + ".learner_response",
|
|
16786
16846
|
scorm2004_errors.TYPE_MISMATCH
|
|
@@ -16999,30 +17059,13 @@ class CMIInteractionsObjectivesObject extends BaseCMI {
|
|
|
16999
17059
|
return result;
|
|
17000
17060
|
}
|
|
17001
17061
|
}
|
|
17002
|
-
|
|
17003
|
-
|
|
17004
|
-
|
|
17005
|
-
|
|
17006
|
-
|
|
17007
|
-
}
|
|
17008
|
-
function splitUnescaped(text, delim) {
|
|
17009
|
-
const reDelim = escapeRegex(delim);
|
|
17010
|
-
const splitRe = new RegExp(`(?<!\\\\)${reDelim}`, "g");
|
|
17011
|
-
const unescapeRe = new RegExp(`\\\\${reDelim}`, "g");
|
|
17012
|
-
return text.split(splitRe).map((part) => part.replace(unescapeRe, delim));
|
|
17013
|
-
}
|
|
17014
|
-
function splitFirstUnescaped(text, delim) {
|
|
17015
|
-
const reDelim = escapeRegex(delim);
|
|
17016
|
-
const splitRe = new RegExp(`(?<!\\\\)${reDelim}`);
|
|
17017
|
-
const unescapeRe = new RegExp(`\\\\${reDelim}`, "g");
|
|
17018
|
-
const parts = text.split(splitRe);
|
|
17019
|
-
const firstPart = parts[0] ?? "";
|
|
17020
|
-
if (parts.length === 1) {
|
|
17021
|
-
return [firstPart.replace(unescapeRe, delim)];
|
|
17062
|
+
const RESPONSE_PREFIX_RE = /^\{(?:lang|case_matters|order_matters)=[^}]+\}/;
|
|
17063
|
+
function stripResponsePrefixes(node) {
|
|
17064
|
+
let result = node;
|
|
17065
|
+
while (RESPONSE_PREFIX_RE.test(result)) {
|
|
17066
|
+
result = result.replace(RESPONSE_PREFIX_RE, "");
|
|
17022
17067
|
}
|
|
17023
|
-
|
|
17024
|
-
const part2 = parts.slice(1).join(delim).replace(unescapeRe, delim);
|
|
17025
|
-
return [part1, part2];
|
|
17068
|
+
return result;
|
|
17026
17069
|
}
|
|
17027
17070
|
function validatePattern(type, pattern, responseDef) {
|
|
17028
17071
|
if (pattern.trim() !== pattern) {
|
|
@@ -17031,8 +17074,7 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17031
17074
|
scorm2004_errors.TYPE_MISMATCH
|
|
17032
17075
|
);
|
|
17033
17076
|
}
|
|
17034
|
-
const
|
|
17035
|
-
const rawNodes = subDelim1 ? splitUnescaped(pattern, subDelim1) : [pattern];
|
|
17077
|
+
const rawNodes = responseDef.delimiter ? splitDelimited(pattern, responseDef.delimiter) : [pattern];
|
|
17036
17078
|
for (const raw of rawNodes) {
|
|
17037
17079
|
if (raw.trim() !== raw) {
|
|
17038
17080
|
throw new Scorm2004ValidationError(
|
|
@@ -17044,20 +17086,14 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17044
17086
|
if (type === "fill-in" && pattern === "") {
|
|
17045
17087
|
return;
|
|
17046
17088
|
}
|
|
17047
|
-
const
|
|
17048
|
-
let nodes;
|
|
17049
|
-
if (delim1) {
|
|
17050
|
-
nodes = splitUnescaped(pattern, delim1);
|
|
17051
|
-
} else {
|
|
17052
|
-
nodes = [pattern];
|
|
17053
|
-
}
|
|
17089
|
+
const nodes = responseDef.delimiter ? splitDelimited(pattern, responseDef.delimiter) : [pattern];
|
|
17054
17090
|
if (!responseDef.delimiter && pattern.includes(",")) {
|
|
17055
17091
|
throw new Scorm2004ValidationError(
|
|
17056
17092
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
17057
17093
|
scorm2004_errors.TYPE_MISMATCH
|
|
17058
17094
|
);
|
|
17059
17095
|
}
|
|
17060
|
-
if (responseDef.unique || responseDef.duplicate === false) {
|
|
17096
|
+
if (type !== "numeric" && (responseDef.unique || responseDef.duplicate === false)) {
|
|
17061
17097
|
const seen = new Set(nodes);
|
|
17062
17098
|
if (seen.size !== nodes.length) {
|
|
17063
17099
|
throw new Scorm2004ValidationError(
|
|
@@ -17089,8 +17125,7 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17089
17125
|
scorm2004_errors.TYPE_MISMATCH
|
|
17090
17126
|
);
|
|
17091
17127
|
}
|
|
17092
|
-
const
|
|
17093
|
-
const parts = value.split(new RegExp(`(?<!\\\\)${escapeRegex(delim)}`, "g")).map((n) => n.replace(new RegExp(`\\\\${escapeRegex(delim)}`, "g"), delim));
|
|
17128
|
+
const parts = splitDelimited(value, delimBracketed);
|
|
17094
17129
|
if (parts.length !== 2 || parts[0] === "" || parts[1] === "") {
|
|
17095
17130
|
throw new Scorm2004ValidationError(
|
|
17096
17131
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
@@ -17107,15 +17142,17 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17107
17142
|
for (const node of nodes) {
|
|
17108
17143
|
switch (type) {
|
|
17109
17144
|
case "numeric": {
|
|
17110
|
-
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
|
|
17145
|
+
if (node === "") {
|
|
17146
|
+
const bracketedRange = nodes.length >= 2 && !!responseDef.delimiter && pattern.includes(responseDef.delimiter);
|
|
17147
|
+
if (!bracketedRange) {
|
|
17148
|
+
throw new Scorm2004ValidationError(
|
|
17149
|
+
"cmi.interactions.n.correct_responses.n.pattern",
|
|
17150
|
+
scorm2004_errors.TYPE_MISMATCH
|
|
17151
|
+
);
|
|
17152
|
+
}
|
|
17153
|
+
break;
|
|
17117
17154
|
}
|
|
17118
|
-
|
|
17155
|
+
checkSingle(node);
|
|
17119
17156
|
break;
|
|
17120
17157
|
}
|
|
17121
17158
|
case "performance": {
|
|
@@ -17126,8 +17163,8 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17126
17163
|
scorm2004_errors.TYPE_MISMATCH
|
|
17127
17164
|
);
|
|
17128
17165
|
}
|
|
17129
|
-
const
|
|
17130
|
-
const parts =
|
|
17166
|
+
const record = stripResponsePrefixes(node);
|
|
17167
|
+
const parts = splitFirstDelimited(record, delimBracketed);
|
|
17131
17168
|
if (parts.length !== 2) {
|
|
17132
17169
|
throw new Scorm2004ValidationError(
|
|
17133
17170
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
@@ -17135,7 +17172,7 @@ function validatePattern(type, pattern, responseDef) {
|
|
|
17135
17172
|
);
|
|
17136
17173
|
}
|
|
17137
17174
|
const [part1, part2] = parts;
|
|
17138
|
-
if (part1 === ""
|
|
17175
|
+
if (part1 === "" && part2 === "") {
|
|
17139
17176
|
throw new Scorm2004ValidationError(
|
|
17140
17177
|
"cmi.interactions.n.correct_responses.n.pattern",
|
|
17141
17178
|
scorm2004_errors.TYPE_MISMATCH
|
|
@@ -20199,7 +20236,7 @@ class Scorm2004ResponseValidator {
|
|
|
20199
20236
|
checkValidResponseType(CMIElement, response_type, value, interaction_type) {
|
|
20200
20237
|
let nodes = [];
|
|
20201
20238
|
if (response_type?.delimiter) {
|
|
20202
|
-
nodes = String(value)
|
|
20239
|
+
nodes = splitDelimited(String(value), response_type.delimiter);
|
|
20203
20240
|
} else {
|
|
20204
20241
|
nodes[0] = value;
|
|
20205
20242
|
}
|
|
@@ -20321,22 +20358,30 @@ class Scorm2004ResponseValidator {
|
|
|
20321
20358
|
nodes[i] = this.removeCorrectResponsePrefixes(CMIElement, nodes[i]);
|
|
20322
20359
|
}
|
|
20323
20360
|
if (response?.delimiter2) {
|
|
20324
|
-
const values = nodes[i].
|
|
20361
|
+
const values = interaction_type === "performance" ? splitFirstDelimited(nodes[i], response.delimiter2) : splitDelimited(nodes[i], response.delimiter2);
|
|
20325
20362
|
if (values.length === 2) {
|
|
20326
|
-
|
|
20327
|
-
if (!matches) {
|
|
20363
|
+
if (interaction_type === "performance" && values[0] === "" && values[1] === "") {
|
|
20328
20364
|
this.context.throwSCORMError(
|
|
20329
20365
|
CMIElement,
|
|
20330
20366
|
scorm2004_errors.TYPE_MISMATCH,
|
|
20331
20367
|
`${interaction_type}: ${value}`
|
|
20332
20368
|
);
|
|
20333
20369
|
} else {
|
|
20334
|
-
|
|
20370
|
+
const matches = values[0]?.match(formatRegex);
|
|
20371
|
+
if (!matches) {
|
|
20335
20372
|
this.context.throwSCORMError(
|
|
20336
20373
|
CMIElement,
|
|
20337
20374
|
scorm2004_errors.TYPE_MISMATCH,
|
|
20338
20375
|
`${interaction_type}: ${value}`
|
|
20339
20376
|
);
|
|
20377
|
+
} else {
|
|
20378
|
+
if (!response.format2 || !values[1]?.match(new RegExp(response.format2))) {
|
|
20379
|
+
this.context.throwSCORMError(
|
|
20380
|
+
CMIElement,
|
|
20381
|
+
scorm2004_errors.TYPE_MISMATCH,
|
|
20382
|
+
`${interaction_type}: ${value}`
|
|
20383
|
+
);
|
|
20384
|
+
}
|
|
20340
20385
|
}
|
|
20341
20386
|
}
|
|
20342
20387
|
} else {
|
|
@@ -20347,6 +20392,9 @@ class Scorm2004ResponseValidator {
|
|
|
20347
20392
|
);
|
|
20348
20393
|
}
|
|
20349
20394
|
} else {
|
|
20395
|
+
if (interaction_type === "numeric" && nodes.length > 1 && nodes[i] === "" && !!response.delimiter && String(value).includes(response.delimiter)) {
|
|
20396
|
+
continue;
|
|
20397
|
+
}
|
|
20350
20398
|
const matches = nodes[i].match(formatRegex);
|
|
20351
20399
|
if (!matches && value !== "" || !matches && interaction_type === "true-false") {
|
|
20352
20400
|
this.context.throwSCORMError(
|
|
@@ -20356,7 +20404,7 @@ class Scorm2004ResponseValidator {
|
|
|
20356
20404
|
);
|
|
20357
20405
|
} else {
|
|
20358
20406
|
if (interaction_type === "numeric" && nodes.length > 1) {
|
|
20359
|
-
if (Number(nodes[0]) > Number(nodes[1])) {
|
|
20407
|
+
if (nodes[0] !== "" && nodes[1] !== "" && Number(nodes[0]) > Number(nodes[1])) {
|
|
20360
20408
|
this.context.throwSCORMError(
|
|
20361
20409
|
CMIElement,
|
|
20362
20410
|
scorm2004_errors.TYPE_MISMATCH,
|