scorm-again 1.7.1 → 2.1.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 (124) hide show
  1. package/.babelrc +18 -7
  2. package/.github/dependabot.yml +5 -0
  3. package/.github/workflows/main.yml +79 -0
  4. package/.github/workflows/stale.yml +14 -0
  5. package/.jsdoc.json +4 -5
  6. package/.mocharc.json +8 -0
  7. package/.run/{Mocha Unit Tests.run.xml → Mocha Unit Tests (watch).run.xml } +6 -3
  8. package/.run/Template Mocha.run.xml +17 -0
  9. package/CONTRIBUTING.md +1 -1
  10. package/README.md +183 -71
  11. package/dist/aicc.js +3822 -7030
  12. package/dist/aicc.js.map +1 -1
  13. package/dist/aicc.min.js +2 -40
  14. package/dist/aicc.min.js.map +1 -0
  15. package/dist/scorm-again.js +5965 -10498
  16. package/dist/scorm-again.js.map +1 -1
  17. package/dist/scorm-again.min.js +2 -52
  18. package/dist/scorm-again.min.js.map +1 -0
  19. package/dist/scorm12.js +3028 -5373
  20. package/dist/scorm12.js.map +1 -1
  21. package/dist/scorm12.min.js +2 -34
  22. package/dist/scorm12.min.js.map +1 -0
  23. package/dist/scorm2004.js +4054 -6693
  24. package/dist/scorm2004.js.map +1 -1
  25. package/dist/scorm2004.min.js +2 -40
  26. package/dist/scorm2004.min.js.map +1 -0
  27. package/eslint.config.js +21 -0
  28. package/package.json +76 -34
  29. package/results.json +34254 -0
  30. package/src/AICC.ts +72 -0
  31. package/src/BaseAPI.ts +1300 -0
  32. package/src/Scorm12API.ts +387 -0
  33. package/src/Scorm2004API.ts +688 -0
  34. package/src/cmi/aicc/attempts.ts +94 -0
  35. package/src/cmi/aicc/cmi.ts +100 -0
  36. package/src/cmi/aicc/core.ts +360 -0
  37. package/src/cmi/aicc/evaluation.ts +157 -0
  38. package/src/cmi/aicc/paths.ts +180 -0
  39. package/src/cmi/aicc/student_data.ts +86 -0
  40. package/src/cmi/aicc/student_demographics.ts +367 -0
  41. package/src/cmi/aicc/student_preferences.ts +176 -0
  42. package/src/cmi/aicc/tries.ts +116 -0
  43. package/src/cmi/aicc/validation.ts +25 -0
  44. package/src/cmi/common/array.ts +77 -0
  45. package/src/cmi/common/base_cmi.ts +46 -0
  46. package/src/cmi/common/score.ts +203 -0
  47. package/src/cmi/common/validation.ts +60 -0
  48. package/src/cmi/scorm12/cmi.ts +224 -0
  49. package/src/cmi/scorm12/interactions.ts +368 -0
  50. package/src/cmi/scorm12/nav.ts +54 -0
  51. package/src/cmi/scorm12/objectives.ts +112 -0
  52. package/src/cmi/scorm12/student_data.ts +130 -0
  53. package/src/cmi/scorm12/student_preference.ts +158 -0
  54. package/src/cmi/scorm12/validation.ts +48 -0
  55. package/src/cmi/scorm2004/adl.ts +272 -0
  56. package/src/cmi/scorm2004/cmi.ts +599 -0
  57. package/src/cmi/scorm2004/comments.ts +163 -0
  58. package/src/cmi/scorm2004/interactions.ts +466 -0
  59. package/src/cmi/scorm2004/learner_preference.ts +152 -0
  60. package/src/cmi/scorm2004/objectives.ts +212 -0
  61. package/src/cmi/scorm2004/score.ts +78 -0
  62. package/src/cmi/scorm2004/validation.ts +42 -0
  63. package/src/constants/api_constants.ts +318 -0
  64. package/src/constants/default_settings.ts +81 -0
  65. package/src/constants/enums.ts +5 -0
  66. package/src/constants/error_codes.ts +88 -0
  67. package/src/constants/language_constants.ts +394 -0
  68. package/src/constants/regex.ts +97 -0
  69. package/src/constants/{response_constants.js → response_constants.ts} +69 -62
  70. package/src/exceptions.ts +154 -0
  71. package/src/exports/aicc.js +1 -1
  72. package/src/exports/scorm-again.js +3 -3
  73. package/src/exports/scorm12.js +1 -1
  74. package/src/exports/scorm2004.js +1 -1
  75. package/src/helpers/scheduled_commit.ts +42 -0
  76. package/src/interfaces/IBaseAPI.ts +35 -0
  77. package/src/types/api_types.ts +32 -0
  78. package/src/utilities/debounce.ts +31 -0
  79. package/src/utilities.ts +338 -0
  80. package/tea.yaml +6 -0
  81. package/test/{AICC.spec.js → AICC.spec.ts} +79 -71
  82. package/test/Scorm12API.spec.ts +833 -0
  83. package/test/Scorm2004API.spec.ts +1298 -0
  84. package/test/api_helpers.ts +176 -0
  85. package/test/cmi/aicc_cmi.spec.ts +845 -0
  86. package/test/cmi/{scorm12_cmi.spec.js → scorm12_cmi.spec.ts} +253 -271
  87. package/test/cmi/scorm2004_cmi.spec.ts +1031 -0
  88. package/test/cmi_helpers.ts +207 -0
  89. package/test/exceptions.spec.ts +79 -0
  90. package/test/field_values.ts +202 -0
  91. package/test/types/api_types.spec.ts +126 -0
  92. package/test/utilities/debounce.spec.ts +56 -0
  93. package/test/utilities.spec.ts +322 -0
  94. package/tsconfig.json +18 -0
  95. package/webpack.config.js +65 -0
  96. package/.circleci/config.yml +0 -99
  97. package/.codeclimate.yml +0 -7
  98. package/.eslintrc.js +0 -36
  99. package/src/.flowconfig +0 -11
  100. package/src/AICC.js +0 -68
  101. package/src/BaseAPI.js +0 -1275
  102. package/src/Scorm12API.js +0 -308
  103. package/src/Scorm2004API.js +0 -572
  104. package/src/cmi/aicc_cmi.js +0 -1141
  105. package/src/cmi/common.js +0 -328
  106. package/src/cmi/scorm12_cmi.js +0 -1312
  107. package/src/cmi/scorm2004_cmi.js +0 -1692
  108. package/src/constants/api_constants.js +0 -218
  109. package/src/constants/error_codes.js +0 -87
  110. package/src/constants/language_constants.js +0 -76
  111. package/src/constants/regex.js +0 -84
  112. package/src/exceptions.js +0 -104
  113. package/src/utilities.js +0 -242
  114. package/test/Scorm12API.spec.js +0 -528
  115. package/test/Scorm2004API.spec.js +0 -775
  116. package/test/abstract_classes.spec.js +0 -17
  117. package/test/api_helpers.js +0 -128
  118. package/test/cmi/aicc_cmi.spec.js +0 -684
  119. package/test/cmi/scorm2004_cmi.spec.js +0 -1066
  120. package/test/cmi_helpers.js +0 -161
  121. package/test/exceptions.spec.js +0 -71
  122. package/test/field_values.js +0 -353
  123. package/test/utilities.spec.js +0 -339
  124. package/webpack.js +0 -78
@@ -0,0 +1,688 @@
1
+ import BaseAPI from "./BaseAPI";
2
+ import { CMI } from "./cmi/scorm2004/cmi";
3
+ import * as Utilities from "./utilities";
4
+ import { stringMatches } from "./utilities";
5
+ import APIConstants from "./constants/api_constants";
6
+ import ErrorCodes from "./constants/error_codes";
7
+ import { CorrectResponses, ResponseType } from "./constants/response_constants";
8
+ import ValidLanguages from "./constants/language_constants";
9
+ import Regex from "./constants/regex";
10
+ import regex from "./constants/regex";
11
+ import { CMIArray } from "./cmi/common/array";
12
+ import { BaseCMI } from "./cmi/common/base_cmi";
13
+ import {
14
+ CMIInteractionsCorrectResponsesObject,
15
+ CMIInteractionsObject,
16
+ CMIInteractionsObjectivesObject,
17
+ } from "./cmi/scorm2004/interactions";
18
+ import { CMICommentsObject } from "./cmi/scorm2004/comments";
19
+ import { CMIObjectivesObject } from "./cmi/scorm2004/objectives";
20
+ import { ADL } from "./cmi/scorm2004/adl";
21
+ import { RefObject, ResultObject, Settings } from "./types/api_types";
22
+
23
+ /**
24
+ * API class for SCORM 2004
25
+ */
26
+ export default class Scorm2004API extends BaseAPI {
27
+ private _version: string = "1.0";
28
+
29
+ /**
30
+ * Constructor for SCORM 2004 API
31
+ * @param {Settings} settings
32
+ */
33
+ constructor(settings?: Settings) {
34
+ if (settings) {
35
+ if (settings.mastery_override === undefined) {
36
+ settings.mastery_override = false;
37
+ }
38
+ }
39
+
40
+ super(ErrorCodes.scorm2004, settings);
41
+
42
+ this.cmi = new CMI();
43
+ this.adl = new ADL();
44
+
45
+ // Rename functions to match 2004 Spec and expose to modules
46
+ this.Initialize = this.lmsInitialize;
47
+ this.Terminate = this.lmsFinish;
48
+ this.GetValue = this.lmsGetValue;
49
+ this.SetValue = this.lmsSetValue;
50
+ this.Commit = this.lmsCommit;
51
+ this.GetLastError = this.lmsGetLastError;
52
+ this.GetErrorString = this.lmsGetErrorString;
53
+ this.GetDiagnostic = this.lmsGetDiagnostic;
54
+ }
55
+
56
+ public cmi: CMI;
57
+ public adl: ADL;
58
+
59
+ public Initialize: () => string;
60
+ public Terminate: () => string;
61
+ public GetValue: (CMIElement: string) => string;
62
+ public SetValue: (CMIElement: string, value: any) => string;
63
+ public Commit: () => string;
64
+ public GetLastError: () => string;
65
+ public GetErrorString: (CMIErrorCode: string | number) => string;
66
+ public GetDiagnostic: (CMIErrorCode: string | number) => string;
67
+
68
+ /**
69
+ * Called when the API needs to be reset
70
+ */
71
+ reset(settings?: Settings) {
72
+ this.commonReset(settings);
73
+
74
+ this.cmi = new CMI();
75
+ this.adl = new ADL();
76
+ }
77
+
78
+ /**
79
+ * Getter for _version
80
+ * @return {string}
81
+ */
82
+ get version(): string {
83
+ return this._version;
84
+ }
85
+
86
+ /**
87
+ * @return {string} bool
88
+ */
89
+ lmsInitialize(): string {
90
+ this.cmi.initialize();
91
+ return this.initialize("Initialize");
92
+ }
93
+
94
+ /**
95
+ * @return {string} bool
96
+ */
97
+ lmsFinish(): string {
98
+ (async () => {
99
+ await this.internalFinish();
100
+ })();
101
+ return APIConstants.global.SCORM_TRUE;
102
+ }
103
+
104
+ async internalFinish(): Promise<string> {
105
+ const result = await this.terminate("Terminate", true);
106
+
107
+ if (result === APIConstants.global.SCORM_TRUE) {
108
+ if (this.adl.nav.request !== "_none_") {
109
+ const navActions: { [key: string]: string } = {
110
+ continue: "SequenceNext",
111
+ previous: "SequencePrevious",
112
+ choice: "SequenceChoice",
113
+ jump: "SequenceJump",
114
+ exit: "SequenceExit",
115
+ exitAll: "SequenceExitAll",
116
+ abandon: "SequenceAbandon",
117
+ abandonAll: "SequenceAbandonAll",
118
+ };
119
+
120
+ let request = this.adl.nav.request;
121
+ const choiceJumpRegex = new RegExp(regex.scorm2004.NAVEvent);
122
+ const matches = request.match(choiceJumpRegex);
123
+ let target = "";
124
+ if (matches && matches.length > 2) {
125
+ target = matches[2];
126
+ request = matches[1].replace(target, "");
127
+ }
128
+ const action = navActions[request];
129
+ if (action) {
130
+ this.processListeners(action, "adl.nav.request", target);
131
+ }
132
+ } else if (this.settings.autoProgress) {
133
+ this.processListeners("SequenceNext");
134
+ }
135
+ }
136
+
137
+ return result;
138
+ }
139
+
140
+ /**
141
+ * @param {string} CMIElement
142
+ * @return {string}
143
+ */
144
+ lmsGetValue(CMIElement: string): string {
145
+ const adlNavRequestRegex =
146
+ "^adl\\.nav\\.request_valid\\.(choice|jump)\\.{target=\\S{0,}([a-zA-Z0-9-_]+)}$";
147
+ if (stringMatches(CMIElement, adlNavRequestRegex)) {
148
+ const matches = CMIElement.match(adlNavRequestRegex);
149
+ const request = matches[1];
150
+ const target = matches[2].replace("{target=", "").replace("}", "");
151
+ if (request === "choice" || request === "jump") {
152
+ if (this.settings.scoItemIdValidator) {
153
+ return String(this.settings.scoItemIdValidator(target));
154
+ }
155
+ return String(this.settings.scoItemIds.includes(target));
156
+ }
157
+ }
158
+ return this.getValue("GetValue", true, CMIElement);
159
+ }
160
+
161
+ /**
162
+ * @param {string} CMIElement
163
+ * @param {any} value
164
+ * @return {string}
165
+ */
166
+ lmsSetValue(CMIElement: string, value: any): string {
167
+ return this.setValue("SetValue", "Commit", true, CMIElement, value);
168
+ }
169
+
170
+ /**
171
+ * Orders LMS to store all content parameters
172
+ *
173
+ * @return {string} bool
174
+ */
175
+ lmsCommit(): string {
176
+ (async () => {
177
+ await this.commit("Commit");
178
+ })();
179
+ return APIConstants.global.SCORM_TRUE;
180
+ }
181
+
182
+ /**
183
+ * Returns last error code
184
+ *
185
+ * @return {string}
186
+ */
187
+ lmsGetLastError(): string {
188
+ return this.getLastError("GetLastError");
189
+ }
190
+
191
+ /**
192
+ * Returns the errorNumber error description
193
+ *
194
+ * @param {(string|number)} CMIErrorCode
195
+ * @return {string}
196
+ */
197
+ lmsGetErrorString(CMIErrorCode: string | number): string {
198
+ return this.getErrorString("GetErrorString", CMIErrorCode);
199
+ }
200
+
201
+ /**
202
+ * Returns a comprehensive description of the errorNumber error.
203
+ *
204
+ * @param {(string|number)} CMIErrorCode
205
+ * @return {string}
206
+ */
207
+ lmsGetDiagnostic(CMIErrorCode: string | number): string {
208
+ return this.getDiagnostic("GetDiagnostic", CMIErrorCode);
209
+ }
210
+
211
+ /**
212
+ * Sets a value on the CMI Object
213
+ *
214
+ * @param {string} CMIElement
215
+ * @param {any} value
216
+ * @return {string}
217
+ */
218
+ setCMIValue(CMIElement: string, value: any): string {
219
+ return this._commonSetCMIValue("SetValue", true, CMIElement, value);
220
+ }
221
+
222
+ /**
223
+ * Gets or builds a new child element to add to the array.
224
+ *
225
+ * @param {string} CMIElement
226
+ * @param {any} value
227
+ * @param {boolean} foundFirstIndex
228
+ * @return {BaseCMI|null}
229
+ */
230
+ getChildElement(
231
+ CMIElement: string,
232
+ value: any,
233
+ foundFirstIndex: boolean,
234
+ ): BaseCMI | null {
235
+ if (stringMatches(CMIElement, "cmi\\.objectives\\.\\d+")) {
236
+ return new CMIObjectivesObject();
237
+ }
238
+
239
+ if (foundFirstIndex) {
240
+ if (
241
+ stringMatches(
242
+ CMIElement,
243
+ "cmi\\.interactions\\.\\d+\\.correct_responses\\.\\d+",
244
+ )
245
+ ) {
246
+ return this.createCorrectResponsesObject(CMIElement, value);
247
+ } else if (
248
+ stringMatches(
249
+ CMIElement,
250
+ "cmi\\.interactions\\.\\d+\\.objectives\\.\\d+",
251
+ )
252
+ ) {
253
+ return new CMIInteractionsObjectivesObject();
254
+ }
255
+ } else if (stringMatches(CMIElement, "cmi\\.interactions\\.\\d+")) {
256
+ return new CMIInteractionsObject();
257
+ }
258
+
259
+ if (stringMatches(CMIElement, "cmi\\.comments_from_learner\\.\\d+")) {
260
+ return new CMICommentsObject();
261
+ } else if (stringMatches(CMIElement, "cmi\\.comments_from_lms\\.\\d+")) {
262
+ return new CMICommentsObject(true);
263
+ }
264
+
265
+ return null;
266
+ }
267
+
268
+ private createCorrectResponsesObject(
269
+ CMIElement: string,
270
+ value: any,
271
+ ): BaseCMI | null {
272
+ const parts = CMIElement.split(".");
273
+ const index = Number(parts[2]);
274
+ const interaction = this.cmi.interactions.childArray[index];
275
+
276
+ if (this.isInitialized()) {
277
+ if (!interaction.type) {
278
+ this.throwSCORMError(ErrorCodes.scorm2004.DEPENDENCY_NOT_ESTABLISHED);
279
+ } else {
280
+ this.checkDuplicateChoiceResponse(interaction, value);
281
+ const response_type = CorrectResponses[interaction.type];
282
+ if (response_type) {
283
+ this.checkValidResponseType(response_type, value, interaction.type);
284
+ } else {
285
+ this.throwSCORMError(
286
+ ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
287
+ "Incorrect Response Type: " + interaction.type,
288
+ );
289
+ }
290
+ }
291
+ }
292
+
293
+ if (this.lastErrorCode === "0") {
294
+ return new CMIInteractionsCorrectResponsesObject();
295
+ }
296
+
297
+ return null;
298
+ }
299
+
300
+ /**
301
+ * Checks for valid response types
302
+ * @param {object} response_type
303
+ * @param {any} value
304
+ * @param {string} interaction_type
305
+ */
306
+ checkValidResponseType(
307
+ response_type: ResponseType,
308
+ value: any,
309
+ interaction_type: string,
310
+ ) {
311
+ let nodes = [];
312
+ if (response_type?.delimiter) {
313
+ nodes = String(value).split(response_type.delimiter);
314
+ } else {
315
+ nodes[0] = value;
316
+ }
317
+
318
+ if (nodes.length > 0 && nodes.length <= response_type.max) {
319
+ this.checkCorrectResponseValue(interaction_type, nodes, value);
320
+ } else if (nodes.length > response_type.max) {
321
+ this.throwSCORMError(
322
+ ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
323
+ "Data Model Element Pattern Too Long",
324
+ );
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Checks for duplicate 'choice' responses.
330
+ * @param {CMIInteractionsObject} interaction
331
+ * @param {any} value
332
+ */
333
+ checkDuplicateChoiceResponse(interaction: CMIInteractionsObject, value: any) {
334
+ const interaction_count = interaction.correct_responses._count;
335
+ if (interaction.type === "choice") {
336
+ for (
337
+ let i = 0;
338
+ i < interaction_count && this.lastErrorCode === "0";
339
+ i++
340
+ ) {
341
+ const response = interaction.correct_responses.childArray[i];
342
+ if (response.pattern === value) {
343
+ this.throwSCORMError(ErrorCodes.scorm2004.GENERAL_SET_FAILURE);
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Validate correct response.
351
+ * @param {string} CMIElement
352
+ * @param {*} value
353
+ */
354
+ validateCorrectResponse(CMIElement: string, value: any) {
355
+ const parts = CMIElement.split(".");
356
+ const index = Number(parts[2]);
357
+ const pattern_index = Number(parts[4]);
358
+ const interaction = this.cmi.interactions.childArray[index];
359
+
360
+ const interaction_count = interaction.correct_responses._count;
361
+ this.checkDuplicateChoiceResponse(interaction, value);
362
+
363
+ const response_type = CorrectResponses[interaction.type];
364
+ if (
365
+ typeof response_type.limit === "undefined" ||
366
+ interaction_count <= response_type.limit
367
+ ) {
368
+ this.checkValidResponseType(response_type, value, interaction.type);
369
+
370
+ if (
371
+ (this.lastErrorCode === "0" &&
372
+ (!response_type.duplicate ||
373
+ !this.checkDuplicatedPattern(
374
+ interaction.correct_responses,
375
+ pattern_index,
376
+ value,
377
+ ))) ||
378
+ (this.lastErrorCode === "0" && value === "")
379
+ ) {
380
+ // do nothing, we want the inverse
381
+ } else {
382
+ if (this.lastErrorCode === "0") {
383
+ this.throwSCORMError(
384
+ ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
385
+ "Data Model Element Pattern Already Exists",
386
+ );
387
+ }
388
+ }
389
+ } else {
390
+ this.throwSCORMError(
391
+ ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
392
+ "Data Model Element Collection Limit Reached",
393
+ );
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Gets a value from the CMI Object
399
+ *
400
+ * @param {string} CMIElement
401
+ * @return {*}
402
+ */
403
+ getCMIValue(CMIElement: string): any {
404
+ return this._commonGetCMIValue("GetValue", true, CMIElement);
405
+ }
406
+
407
+ /**
408
+ * Returns the message that corresponds to errorNumber.
409
+ *
410
+ * @param {(string|number)} errorNumber
411
+ * @param {boolean} detail
412
+ * @return {string}
413
+ */
414
+ getLmsErrorMessageDetails(
415
+ errorNumber: string | number,
416
+ detail: boolean,
417
+ ): string {
418
+ let basicMessage = "";
419
+ let detailMessage = "";
420
+
421
+ // Set error number to string since inconsistent from modules if string or number
422
+ errorNumber = String(errorNumber);
423
+ if (APIConstants.scorm2004.error_descriptions[errorNumber]) {
424
+ basicMessage =
425
+ APIConstants.scorm2004.error_descriptions[errorNumber].basicMessage;
426
+ detailMessage =
427
+ APIConstants.scorm2004.error_descriptions[errorNumber].detailMessage;
428
+ }
429
+
430
+ return detail ? detailMessage : basicMessage;
431
+ }
432
+
433
+ /**
434
+ * Check to see if a correct_response value has been duplicated
435
+ * @param {CMIArray} correct_response
436
+ * @param {number} current_index
437
+ * @param {*} value
438
+ * @return {boolean}
439
+ */
440
+ checkDuplicatedPattern(
441
+ correct_response: CMIArray,
442
+ current_index: number,
443
+ value: any,
444
+ ): boolean {
445
+ let found = false;
446
+ const count = correct_response._count;
447
+ for (let i = 0; i < count && !found; i++) {
448
+ if (i !== current_index && correct_response.childArray[i] === value) {
449
+ found = true;
450
+ }
451
+ }
452
+ return found;
453
+ }
454
+
455
+ /**
456
+ * Checks for a valid correct_response value
457
+ * @param {string} interaction_type
458
+ * @param {Array} nodes
459
+ * @param {*} value
460
+ */
461
+ checkCorrectResponseValue(
462
+ interaction_type: string,
463
+ nodes: Array<any>,
464
+ value: any,
465
+ ) {
466
+ const response = CorrectResponses[interaction_type];
467
+ const formatRegex = new RegExp(response.format);
468
+ for (let i = 0; i < nodes.length && this.lastErrorCode === "0"; i++) {
469
+ if (
470
+ interaction_type.match(
471
+ "^(fill-in|long-fill-in|matching|performance|sequencing)$",
472
+ )
473
+ ) {
474
+ nodes[i] = this.removeCorrectResponsePrefixes(nodes[i]);
475
+ }
476
+
477
+ if (response?.delimiter2) {
478
+ const values = nodes[i].split(response.delimiter2);
479
+ if (values.length === 2) {
480
+ const matches = values[0].match(formatRegex);
481
+ if (!matches) {
482
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
483
+ } else {
484
+ if (
485
+ !response.format2 ||
486
+ !values[1].match(new RegExp(response.format2))
487
+ ) {
488
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
489
+ }
490
+ }
491
+ } else {
492
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
493
+ }
494
+ } else {
495
+ const matches = nodes[i].match(formatRegex);
496
+ if (
497
+ (!matches && value !== "") ||
498
+ (!matches && interaction_type === "true-false")
499
+ ) {
500
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
501
+ } else {
502
+ if (interaction_type === "numeric" && nodes.length > 1) {
503
+ if (Number(nodes[0]) > Number(nodes[1])) {
504
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
505
+ }
506
+ } else {
507
+ if (nodes[i] !== "" && response.unique) {
508
+ for (let j = 0; j < i && this.lastErrorCode === "0"; j++) {
509
+ if (nodes[i] === nodes[j]) {
510
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
511
+ }
512
+ }
513
+ }
514
+ }
515
+ }
516
+ }
517
+ }
518
+ }
519
+
520
+ /**
521
+ * Remove prefixes from correct_response
522
+ * @param {string} node
523
+ * @return {*}
524
+ */
525
+ removeCorrectResponsePrefixes(node: string): any {
526
+ let seenOrder = false;
527
+ let seenCase = false;
528
+ let seenLang = false;
529
+
530
+ const prefixRegex = new RegExp(
531
+ "^({(lang|case_matters|order_matters)=([^}]+)})",
532
+ );
533
+ let matches = node.match(prefixRegex);
534
+ let langMatches = null;
535
+ while (matches) {
536
+ switch (matches[2]) {
537
+ case "lang":
538
+ langMatches = node.match(Regex.scorm2004.CMILangcr);
539
+ if (langMatches) {
540
+ const lang = langMatches[3];
541
+ if (lang !== undefined && lang.length > 0) {
542
+ if (!ValidLanguages.includes(lang.toLowerCase())) {
543
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
544
+ }
545
+ }
546
+ }
547
+ seenLang = true;
548
+ break;
549
+ case "case_matters":
550
+ if (!seenLang && !seenOrder && !seenCase) {
551
+ if (matches[3] !== "true" && matches[3] !== "false") {
552
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
553
+ }
554
+ }
555
+
556
+ seenCase = true;
557
+ break;
558
+ case "order_matters":
559
+ if (!seenCase && !seenLang && !seenOrder) {
560
+ if (matches[3] !== "true" && matches[3] !== "false") {
561
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
562
+ }
563
+ }
564
+
565
+ seenOrder = true;
566
+ break;
567
+ }
568
+ node = node.substring(matches[1].length);
569
+ matches = node.match(prefixRegex);
570
+ }
571
+
572
+ return node;
573
+ }
574
+
575
+ /**
576
+ * Replace the whole API with another
577
+ * @param {Scorm2004API} newAPI
578
+ */
579
+ replaceWithAnotherScormAPI(newAPI: Scorm2004API) {
580
+ // Data Model
581
+ this.cmi = newAPI.cmi;
582
+ this.adl = newAPI.adl;
583
+ }
584
+
585
+ /**
586
+ * Render the cmi object to the proper format for LMS commit
587
+ *
588
+ * @param {boolean} terminateCommit
589
+ * @return {object|Array}
590
+ */
591
+ renderCommitCMI(terminateCommit: boolean): object | Array<any> {
592
+ const cmiExport: RefObject = this.renderCMIToJSONObject();
593
+
594
+ if (terminateCommit) {
595
+ cmiExport.cmi.total_time = this.cmi.getCurrentTotalTime();
596
+ }
597
+
598
+ const result = [];
599
+ const flattened: RefObject = Utilities.flatten(cmiExport);
600
+ switch (this.settings.dataCommitFormat) {
601
+ case "flattened":
602
+ return Utilities.flatten(cmiExport);
603
+ case "params":
604
+ for (const item in flattened) {
605
+ if ({}.hasOwnProperty.call(flattened, item)) {
606
+ result.push(`${item}=${flattened[item]}`);
607
+ }
608
+ }
609
+ return result;
610
+ case "json":
611
+ default:
612
+ return cmiExport;
613
+ }
614
+ }
615
+
616
+ /**
617
+ * Attempts to store the data to the LMS
618
+ *
619
+ * @param {boolean} terminateCommit
620
+ * @return {ResultObject}
621
+ */
622
+ async storeData(terminateCommit: boolean): Promise<ResultObject> {
623
+ if (terminateCommit) {
624
+ if (this.cmi.mode === "normal") {
625
+ if (this.cmi.credit === "credit") {
626
+ if (this.cmi.completion_threshold && this.cmi.progress_measure) {
627
+ if (this.cmi.progress_measure >= this.cmi.completion_threshold) {
628
+ this.cmi.completion_status = "completed";
629
+ } else {
630
+ this.cmi.completion_status = "incomplete";
631
+ }
632
+ }
633
+ if (this.cmi.scaled_passing_score && this.cmi.score.scaled) {
634
+ if (this.cmi.score.scaled >= this.cmi.scaled_passing_score) {
635
+ this.cmi.success_status = "passed";
636
+ } else {
637
+ this.cmi.success_status = "failed";
638
+ }
639
+ }
640
+ }
641
+ }
642
+ }
643
+
644
+ let navRequest = false;
645
+ if (
646
+ this.adl.nav.request !== this.startingData?.adl?.nav?.request &&
647
+ this.adl.nav.request !== "_none_"
648
+ ) {
649
+ this.adl.nav.request = encodeURIComponent(this.adl.nav.request);
650
+ navRequest = true;
651
+ }
652
+
653
+ const commitObject = this.renderCommitCMI(
654
+ terminateCommit || this.settings.alwaysSendTotalTime,
655
+ );
656
+
657
+ if (this.apiLogLevel === APIConstants.global.LOG_LEVEL_DEBUG) {
658
+ console.debug(
659
+ "Commit (terminated: " + (terminateCommit ? "yes" : "no") + "): ",
660
+ );
661
+ console.debug(commitObject);
662
+ }
663
+ if (typeof this.settings.lmsCommitUrl === "string") {
664
+ const result = await this.processHttpRequest(
665
+ this.settings.lmsCommitUrl,
666
+ commitObject,
667
+ terminateCommit,
668
+ );
669
+
670
+ // check if this is a sequencing call, and then call the necessary JS
671
+ {
672
+ if (
673
+ navRequest &&
674
+ result.navRequest !== undefined &&
675
+ result.navRequest !== ""
676
+ ) {
677
+ Function(`"use strict";(() => { ${result.navRequest} })()`)();
678
+ }
679
+ }
680
+ return result;
681
+ } else {
682
+ return {
683
+ result: APIConstants.global.SCORM_TRUE,
684
+ errorCode: 0,
685
+ };
686
+ }
687
+ }
688
+ }