scorm-again 2.0.0 → 2.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 (77) hide show
  1. package/.github/workflows/stale.yml +14 -0
  2. package/.run/{Mocha Unit Tests.run.xml → Mocha Unit Tests (watch).run.xml } +1 -1
  3. package/.run/Template Mocha.run.xml +17 -0
  4. package/README.md +180 -72
  5. package/dist/aicc.js +1520 -1149
  6. package/dist/aicc.js.map +1 -1
  7. package/dist/aicc.min.js +1 -1
  8. package/dist/aicc.min.js.map +1 -1
  9. package/dist/scorm-again.js +2812 -2205
  10. package/dist/scorm-again.js.map +1 -1
  11. package/dist/scorm-again.min.js +1 -1
  12. package/dist/scorm-again.min.js.map +1 -1
  13. package/dist/scorm12.js +1129 -842
  14. package/dist/scorm12.js.map +1 -1
  15. package/dist/scorm12.min.js +1 -1
  16. package/dist/scorm12.min.js.map +1 -1
  17. package/dist/scorm2004.js +1921 -1564
  18. package/dist/scorm2004.js.map +1 -1
  19. package/dist/scorm2004.min.js +1 -1
  20. package/dist/scorm2004.min.js.map +1 -1
  21. package/package.json +20 -17
  22. package/src/AICC.ts +15 -17
  23. package/src/BaseAPI.ts +283 -420
  24. package/src/Scorm12API.ts +133 -41
  25. package/src/Scorm2004API.ts +224 -120
  26. package/src/cmi/aicc/attempts.ts +94 -0
  27. package/src/cmi/aicc/cmi.ts +100 -0
  28. package/src/cmi/aicc/core.ts +360 -0
  29. package/src/cmi/aicc/evaluation.ts +157 -0
  30. package/src/cmi/aicc/paths.ts +180 -0
  31. package/src/cmi/aicc/student_data.ts +86 -0
  32. package/src/cmi/aicc/student_demographics.ts +367 -0
  33. package/src/cmi/aicc/student_preferences.ts +176 -0
  34. package/src/cmi/aicc/tries.ts +116 -0
  35. package/src/cmi/aicc/validation.ts +25 -0
  36. package/src/cmi/common/array.ts +77 -0
  37. package/src/cmi/common/base_cmi.ts +46 -0
  38. package/src/cmi/common/score.ts +203 -0
  39. package/src/cmi/common/validation.ts +60 -0
  40. package/src/cmi/scorm12/cmi.ts +224 -0
  41. package/src/cmi/scorm12/interactions.ts +368 -0
  42. package/src/cmi/scorm12/nav.ts +54 -0
  43. package/src/cmi/scorm12/objectives.ts +112 -0
  44. package/src/cmi/scorm12/student_data.ts +130 -0
  45. package/src/cmi/scorm12/student_preference.ts +158 -0
  46. package/src/cmi/scorm12/validation.ts +48 -0
  47. package/src/cmi/scorm2004/adl.ts +272 -0
  48. package/src/cmi/scorm2004/cmi.ts +599 -0
  49. package/src/cmi/scorm2004/comments.ts +163 -0
  50. package/src/cmi/scorm2004/interactions.ts +466 -0
  51. package/src/cmi/scorm2004/learner_preference.ts +152 -0
  52. package/src/cmi/scorm2004/objectives.ts +212 -0
  53. package/src/cmi/scorm2004/score.ts +78 -0
  54. package/src/cmi/scorm2004/validation.ts +42 -0
  55. package/src/constants/default_settings.ts +82 -0
  56. package/src/constants/enums.ts +17 -0
  57. package/src/constants/regex.ts +2 -2
  58. package/src/constants/response_constants.ts +2 -0
  59. package/src/exceptions.ts +22 -1
  60. package/src/helpers/scheduled_commit.ts +42 -0
  61. package/src/interfaces/IBaseAPI.ts +35 -0
  62. package/src/types/api_types.ts +50 -0
  63. package/src/utilities/debounce.ts +31 -0
  64. package/src/utilities.ts +56 -0
  65. package/test/AICC.spec.ts +11 -1
  66. package/test/Scorm12API.spec.ts +372 -9
  67. package/test/Scorm2004API.spec.ts +558 -2
  68. package/test/cmi/aicc_cmi.spec.ts +188 -11
  69. package/test/cmi/scorm12_cmi.spec.ts +5 -5
  70. package/test/cmi/scorm2004_cmi.spec.ts +8 -8
  71. package/test/cmi_helpers.ts +1 -1
  72. package/test/types/api_types.spec.ts +126 -0
  73. package/test/utilities/debounce.spec.ts +56 -0
  74. package/src/cmi/aicc_cmi.ts +0 -1248
  75. package/src/cmi/common.ts +0 -411
  76. package/src/cmi/scorm12_cmi.ts +0 -1426
  77. package/src/cmi/scorm2004_cmi.ts +0 -1874
@@ -1,26 +1,31 @@
1
- import BaseAPI, { RefObject, ResultObject, Settings } from "./BaseAPI";
2
- import {
3
- ADL,
4
- CMI,
5
- CMICommentsObject,
6
- CMIInteractionsCorrectResponsesObject,
7
- CMIInteractionsObject,
8
- CMIInteractionsObjectivesObject,
9
- CMIObjectivesObject,
10
- } from "./cmi/scorm2004_cmi";
1
+ import BaseAPI from "./BaseAPI";
2
+ import { CMI } from "./cmi/scorm2004/cmi";
11
3
  import * as Utilities from "./utilities";
4
+ import { stringMatches } from "./utilities";
12
5
  import APIConstants from "./constants/api_constants";
13
6
  import ErrorCodes from "./constants/error_codes";
14
7
  import { CorrectResponses, ResponseType } from "./constants/response_constants";
15
8
  import ValidLanguages from "./constants/language_constants";
16
9
  import Regex from "./constants/regex";
17
- import { BaseCMI, CMIArray } from "./cmi/common";
18
-
19
- const scorm2004_constants = APIConstants.scorm2004;
20
- const global_constants = APIConstants.global;
21
- const scorm2004_error_codes = ErrorCodes.scorm2004;
22
- const correct_responses = CorrectResponses;
23
- const scorm2004_regex = Regex.scorm2004;
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 {
22
+ CommitObject,
23
+ RefObject,
24
+ ResultObject,
25
+ ScoreObject,
26
+ Settings,
27
+ } from "./types/api_types";
28
+ import { CompletionStatus, SuccessStatus } from "./constants/enums";
24
29
 
25
30
  /**
26
31
  * API class for SCORM 2004
@@ -39,7 +44,7 @@ export default class Scorm2004API extends BaseAPI {
39
44
  }
40
45
  }
41
46
 
42
- super(scorm2004_error_codes, settings);
47
+ super(ErrorCodes.scorm2004, settings);
43
48
 
44
49
  this.cmi = new CMI();
45
50
  this.adl = new ADL();
@@ -67,6 +72,16 @@ export default class Scorm2004API extends BaseAPI {
67
72
  public GetErrorString: (CMIErrorCode: string | number) => string;
68
73
  public GetDiagnostic: (CMIErrorCode: string | number) => string;
69
74
 
75
+ /**
76
+ * Called when the API needs to be reset
77
+ */
78
+ reset(settings?: Settings) {
79
+ this.commonReset(settings);
80
+
81
+ this.cmi = new CMI();
82
+ this.adl = new ADL();
83
+ }
84
+
70
85
  /**
71
86
  * Getter for _version
72
87
  * @return {string}
@@ -87,32 +102,39 @@ export default class Scorm2004API extends BaseAPI {
87
102
  * @return {string} bool
88
103
  */
89
104
  lmsFinish(): string {
90
- const result = this.terminate("Terminate", true);
105
+ (async () => {
106
+ await this.internalFinish();
107
+ })();
108
+ return APIConstants.global.SCORM_TRUE;
109
+ }
110
+
111
+ async internalFinish(): Promise<string> {
112
+ const result = await this.terminate("Terminate", true);
91
113
 
92
- if (result === global_constants.SCORM_TRUE) {
114
+ if (result === APIConstants.global.SCORM_TRUE) {
93
115
  if (this.adl.nav.request !== "_none_") {
94
- switch (this.adl.nav.request) {
95
- case "continue":
96
- this.processListeners("SequenceNext");
97
- break;
98
- case "previous":
99
- this.processListeners("SequencePrevious");
100
- break;
101
- case "choice":
102
- this.processListeners("SequenceChoice");
103
- break;
104
- case "exit":
105
- this.processListeners("SequenceExit");
106
- break;
107
- case "exitAll":
108
- this.processListeners("SequenceExitAll");
109
- break;
110
- case "abandon":
111
- this.processListeners("SequenceAbandon");
112
- break;
113
- case "abandonAll":
114
- this.processListeners("SequenceAbandonAll");
115
- break;
116
+ const navActions: { [key: string]: string } = {
117
+ continue: "SequenceNext",
118
+ previous: "SequencePrevious",
119
+ choice: "SequenceChoice",
120
+ jump: "SequenceJump",
121
+ exit: "SequenceExit",
122
+ exitAll: "SequenceExitAll",
123
+ abandon: "SequenceAbandon",
124
+ abandonAll: "SequenceAbandonAll",
125
+ };
126
+
127
+ let request = this.adl.nav.request;
128
+ const choiceJumpRegex = new RegExp(regex.scorm2004.NAVEvent);
129
+ const matches = request.match(choiceJumpRegex);
130
+ let target = "";
131
+ if (matches && matches.length > 2) {
132
+ target = matches[2];
133
+ request = matches[1].replace(target, "");
134
+ }
135
+ const action = navActions[request];
136
+ if (action) {
137
+ this.processListeners(action, "adl.nav.request", target);
116
138
  }
117
139
  } else if (this.settings.autoProgress) {
118
140
  this.processListeners("SequenceNext");
@@ -127,6 +149,19 @@ export default class Scorm2004API extends BaseAPI {
127
149
  * @return {string}
128
150
  */
129
151
  lmsGetValue(CMIElement: string): string {
152
+ const adlNavRequestRegex =
153
+ "^adl\\.nav\\.request_valid\\.(choice|jump)\\.{target=\\S{0,}([a-zA-Z0-9-_]+)}$";
154
+ if (stringMatches(CMIElement, adlNavRequestRegex)) {
155
+ const matches = CMIElement.match(adlNavRequestRegex);
156
+ const request = matches[1];
157
+ const target = matches[2].replace("{target=", "").replace("}", "");
158
+ if (request === "choice" || request === "jump") {
159
+ if (this.settings.scoItemIdValidator) {
160
+ return String(this.settings.scoItemIdValidator(target));
161
+ }
162
+ return String(this.settings.scoItemIds.includes(target));
163
+ }
164
+ }
130
165
  return this.getValue("GetValue", true, CMIElement);
131
166
  }
132
167
 
@@ -145,7 +180,10 @@ export default class Scorm2004API extends BaseAPI {
145
180
  * @return {string} bool
146
181
  */
147
182
  lmsCommit(): string {
148
- return this.commit("Commit");
183
+ (async () => {
184
+ await this.commit("Commit");
185
+ })();
186
+ return APIConstants.global.SCORM_TRUE;
149
187
  }
150
188
 
151
189
  /**
@@ -201,66 +239,71 @@ export default class Scorm2004API extends BaseAPI {
201
239
  value: any,
202
240
  foundFirstIndex: boolean,
203
241
  ): BaseCMI | null {
204
- if (this.stringMatches(CMIElement, "cmi\\.objectives\\.\\d+")) {
242
+ if (stringMatches(CMIElement, "cmi\\.objectives\\.\\d+")) {
205
243
  return new CMIObjectivesObject();
206
- } else if (
207
- foundFirstIndex &&
208
- this.stringMatches(
209
- CMIElement,
210
- "cmi\\.interactions\\.\\d+\\.correct_responses\\.\\d+",
211
- )
212
- ) {
213
- const parts = CMIElement.split(".");
214
- const index = Number(parts[2]);
215
- const interaction = this.cmi.interactions.childArray[index];
216
- if (this.isInitialized()) {
217
- if (!interaction.type) {
218
- this.throwSCORMError(
219
- scorm2004_error_codes.DEPENDENCY_NOT_ESTABLISHED,
220
- );
221
- } else {
222
- this.checkDuplicateChoiceResponse(interaction, value);
244
+ }
223
245
 
224
- const response_type = correct_responses[interaction.type];
225
- if (response_type) {
226
- this.checkValidResponseType(response_type, value, interaction.type);
227
- } else {
228
- this.throwSCORMError(
229
- scorm2004_error_codes.GENERAL_SET_FAILURE,
230
- "Incorrect Response Type: " + interaction.type,
231
- );
232
- }
233
- }
234
- }
235
- if (this.lastErrorCode === "0") {
236
- return new CMIInteractionsCorrectResponsesObject();
246
+ if (foundFirstIndex) {
247
+ if (
248
+ stringMatches(
249
+ CMIElement,
250
+ "cmi\\.interactions\\.\\d+\\.correct_responses\\.\\d+",
251
+ )
252
+ ) {
253
+ return this.createCorrectResponsesObject(CMIElement, value);
254
+ } else if (
255
+ stringMatches(
256
+ CMIElement,
257
+ "cmi\\.interactions\\.\\d+\\.objectives\\.\\d+",
258
+ )
259
+ ) {
260
+ return new CMIInteractionsObjectivesObject();
237
261
  }
238
- } else if (
239
- foundFirstIndex &&
240
- this.stringMatches(
241
- CMIElement,
242
- "cmi\\.interactions\\.\\d+\\.objectives\\.\\d+",
243
- )
244
- ) {
245
- return new CMIInteractionsObjectivesObject();
246
- } else if (
247
- !foundFirstIndex &&
248
- this.stringMatches(CMIElement, "cmi\\.interactions\\.\\d+")
249
- ) {
262
+ } else if (stringMatches(CMIElement, "cmi\\.interactions\\.\\d+")) {
250
263
  return new CMIInteractionsObject();
251
- } else if (
252
- this.stringMatches(CMIElement, "cmi\\.comments_from_learner\\.\\d+")
253
- ) {
264
+ }
265
+
266
+ if (stringMatches(CMIElement, "cmi\\.comments_from_learner\\.\\d+")) {
254
267
  return new CMICommentsObject();
255
- } else if (
256
- this.stringMatches(CMIElement, "cmi\\.comments_from_lms\\.\\d+")
257
- ) {
268
+ } else if (stringMatches(CMIElement, "cmi\\.comments_from_lms\\.\\d+")) {
258
269
  return new CMICommentsObject(true);
259
270
  }
260
271
 
261
272
  return null;
262
273
  }
263
274
 
275
+ private createCorrectResponsesObject(
276
+ CMIElement: string,
277
+ value: any,
278
+ ): BaseCMI | null {
279
+ const parts = CMIElement.split(".");
280
+ const index = Number(parts[2]);
281
+ const interaction = this.cmi.interactions.childArray[index];
282
+
283
+ if (this.isInitialized()) {
284
+ if (!interaction.type) {
285
+ this.throwSCORMError(ErrorCodes.scorm2004.DEPENDENCY_NOT_ESTABLISHED);
286
+ } else {
287
+ this.checkDuplicateChoiceResponse(interaction, value);
288
+ const response_type = CorrectResponses[interaction.type];
289
+ if (response_type) {
290
+ this.checkValidResponseType(response_type, value, interaction.type);
291
+ } else {
292
+ this.throwSCORMError(
293
+ ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
294
+ "Incorrect Response Type: " + interaction.type,
295
+ );
296
+ }
297
+ }
298
+ }
299
+
300
+ if (this.lastErrorCode === "0") {
301
+ return new CMIInteractionsCorrectResponsesObject();
302
+ }
303
+
304
+ return null;
305
+ }
306
+
264
307
  /**
265
308
  * Checks for valid response types
266
309
  * @param {object} response_type
@@ -283,7 +326,7 @@ export default class Scorm2004API extends BaseAPI {
283
326
  this.checkCorrectResponseValue(interaction_type, nodes, value);
284
327
  } else if (nodes.length > response_type.max) {
285
328
  this.throwSCORMError(
286
- scorm2004_error_codes.GENERAL_SET_FAILURE,
329
+ ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
287
330
  "Data Model Element Pattern Too Long",
288
331
  );
289
332
  }
@@ -304,7 +347,7 @@ export default class Scorm2004API extends BaseAPI {
304
347
  ) {
305
348
  const response = interaction.correct_responses.childArray[i];
306
349
  if (response.pattern === value) {
307
- this.throwSCORMError(scorm2004_error_codes.GENERAL_SET_FAILURE);
350
+ this.throwSCORMError(ErrorCodes.scorm2004.GENERAL_SET_FAILURE);
308
351
  }
309
352
  }
310
353
  }
@@ -324,7 +367,7 @@ export default class Scorm2004API extends BaseAPI {
324
367
  const interaction_count = interaction.correct_responses._count;
325
368
  this.checkDuplicateChoiceResponse(interaction, value);
326
369
 
327
- const response_type = correct_responses[interaction.type];
370
+ const response_type = CorrectResponses[interaction.type];
328
371
  if (
329
372
  typeof response_type.limit === "undefined" ||
330
373
  interaction_count <= response_type.limit
@@ -345,14 +388,14 @@ export default class Scorm2004API extends BaseAPI {
345
388
  } else {
346
389
  if (this.lastErrorCode === "0") {
347
390
  this.throwSCORMError(
348
- scorm2004_error_codes.GENERAL_SET_FAILURE,
391
+ ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
349
392
  "Data Model Element Pattern Already Exists",
350
393
  );
351
394
  }
352
395
  }
353
396
  } else {
354
397
  this.throwSCORMError(
355
- scorm2004_error_codes.GENERAL_SET_FAILURE,
398
+ ErrorCodes.scorm2004.GENERAL_SET_FAILURE,
356
399
  "Data Model Element Collection Limit Reached",
357
400
  );
358
401
  }
@@ -384,11 +427,11 @@ export default class Scorm2004API extends BaseAPI {
384
427
 
385
428
  // Set error number to string since inconsistent from modules if string or number
386
429
  errorNumber = String(errorNumber);
387
- if (scorm2004_constants.error_descriptions[errorNumber]) {
430
+ if (APIConstants.scorm2004.error_descriptions[errorNumber]) {
388
431
  basicMessage =
389
- scorm2004_constants.error_descriptions[errorNumber].basicMessage;
432
+ APIConstants.scorm2004.error_descriptions[errorNumber].basicMessage;
390
433
  detailMessage =
391
- scorm2004_constants.error_descriptions[errorNumber].detailMessage;
434
+ APIConstants.scorm2004.error_descriptions[errorNumber].detailMessage;
392
435
  }
393
436
 
394
437
  return detail ? detailMessage : basicMessage;
@@ -427,7 +470,7 @@ export default class Scorm2004API extends BaseAPI {
427
470
  nodes: Array<any>,
428
471
  value: any,
429
472
  ) {
430
- const response = correct_responses[interaction_type];
473
+ const response = CorrectResponses[interaction_type];
431
474
  const formatRegex = new RegExp(response.format);
432
475
  for (let i = 0; i < nodes.length && this.lastErrorCode === "0"; i++) {
433
476
  if (
@@ -443,17 +486,17 @@ export default class Scorm2004API extends BaseAPI {
443
486
  if (values.length === 2) {
444
487
  const matches = values[0].match(formatRegex);
445
488
  if (!matches) {
446
- this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
489
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
447
490
  } else {
448
491
  if (
449
492
  !response.format2 ||
450
493
  !values[1].match(new RegExp(response.format2))
451
494
  ) {
452
- this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
495
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
453
496
  }
454
497
  }
455
498
  } else {
456
- this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
499
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
457
500
  }
458
501
  } else {
459
502
  const matches = nodes[i].match(formatRegex);
@@ -461,17 +504,17 @@ export default class Scorm2004API extends BaseAPI {
461
504
  (!matches && value !== "") ||
462
505
  (!matches && interaction_type === "true-false")
463
506
  ) {
464
- this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
507
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
465
508
  } else {
466
509
  if (interaction_type === "numeric" && nodes.length > 1) {
467
510
  if (Number(nodes[0]) > Number(nodes[1])) {
468
- this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
511
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
469
512
  }
470
513
  } else {
471
514
  if (nodes[i] !== "" && response.unique) {
472
515
  for (let j = 0; j < i && this.lastErrorCode === "0"; j++) {
473
516
  if (nodes[i] === nodes[j]) {
474
- this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
517
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
475
518
  }
476
519
  }
477
520
  }
@@ -499,12 +542,12 @@ export default class Scorm2004API extends BaseAPI {
499
542
  while (matches) {
500
543
  switch (matches[2]) {
501
544
  case "lang":
502
- langMatches = node.match(scorm2004_regex.CMILangcr);
545
+ langMatches = node.match(Regex.scorm2004.CMILangcr);
503
546
  if (langMatches) {
504
547
  const lang = langMatches[3];
505
548
  if (lang !== undefined && lang.length > 0) {
506
549
  if (!ValidLanguages.includes(lang.toLowerCase())) {
507
- this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
550
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
508
551
  }
509
552
  }
510
553
  }
@@ -513,7 +556,7 @@ export default class Scorm2004API extends BaseAPI {
513
556
  case "case_matters":
514
557
  if (!seenLang && !seenOrder && !seenCase) {
515
558
  if (matches[3] !== "true" && matches[3] !== "false") {
516
- this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
559
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
517
560
  }
518
561
  }
519
562
 
@@ -522,14 +565,12 @@ export default class Scorm2004API extends BaseAPI {
522
565
  case "order_matters":
523
566
  if (!seenCase && !seenLang && !seenOrder) {
524
567
  if (matches[3] !== "true" && matches[3] !== "false") {
525
- this.throwSCORMError(scorm2004_error_codes.TYPE_MISMATCH);
568
+ this.throwSCORMError(ErrorCodes.scorm2004.TYPE_MISMATCH);
526
569
  }
527
570
  }
528
571
 
529
572
  seenOrder = true;
530
573
  break;
531
- default:
532
- break;
533
574
  }
534
575
  node = node.substring(matches[1].length);
535
576
  matches = node.match(prefixRegex);
@@ -579,13 +620,74 @@ export default class Scorm2004API extends BaseAPI {
579
620
  }
580
621
  }
581
622
 
623
+ /**
624
+ * Render the cmi object to the proper format for LMS commit
625
+ * @param {boolean} terminateCommit
626
+ * @return {CommitObject}
627
+ */
628
+ renderCommitObject(terminateCommit: boolean): CommitObject {
629
+ const cmiExport = this.renderCommitCMI(terminateCommit);
630
+ const totalTimeDuration = this.cmi.getCurrentTotalTime();
631
+ const totalTimeSeconds = Utilities.getDurationAsSeconds(
632
+ totalTimeDuration,
633
+ Regex.scorm2004.CMITimespan,
634
+ );
635
+
636
+ let completionStatus = CompletionStatus.unknown;
637
+ let successStatus = SuccessStatus.unknown;
638
+ if (this.cmi.completion_status) {
639
+ if (this.cmi.completion_status === "completed") {
640
+ completionStatus = CompletionStatus.completed;
641
+ } else if (this.cmi.completion_status === "incomplete") {
642
+ completionStatus = CompletionStatus.incomplete;
643
+ }
644
+ }
645
+ if (this.cmi.success_status) {
646
+ if (this.cmi.success_status === "passed") {
647
+ successStatus = SuccessStatus.passed;
648
+ } else if (this.cmi.success_status === "failed") {
649
+ successStatus = SuccessStatus.failed;
650
+ }
651
+ }
652
+
653
+ const score = this.cmi.score;
654
+ let scoreObject: ScoreObject = null;
655
+ if (score) {
656
+ scoreObject = {};
657
+
658
+ if (!Number.isNaN(Number.parseFloat(score.raw))) {
659
+ scoreObject.raw = Number.parseFloat(score.raw);
660
+ }
661
+ if (!Number.isNaN(Number.parseFloat(score.min))) {
662
+ scoreObject.min = Number.parseFloat(score.min);
663
+ }
664
+ if (!Number.isNaN(Number.parseFloat(score.max))) {
665
+ scoreObject.max = Number.parseFloat(score.max);
666
+ }
667
+ if (!Number.isNaN(Number.parseFloat(score.scaled))) {
668
+ scoreObject.scaled = Number.parseFloat(score.scaled);
669
+ }
670
+ }
671
+
672
+ const commitObject: CommitObject = {
673
+ completionStatus: completionStatus,
674
+ successStatus: successStatus,
675
+ totalTimeSeconds: totalTimeSeconds,
676
+ runtimeData: cmiExport,
677
+ };
678
+ if (scoreObject) {
679
+ commitObject.score = scoreObject;
680
+ }
681
+ return commitObject;
682
+ }
683
+
582
684
  /**
583
685
  * Attempts to store the data to the LMS
584
686
  *
585
687
  * @param {boolean} terminateCommit
586
688
  * @return {ResultObject}
587
689
  */
588
- storeData(terminateCommit: boolean): ResultObject {
690
+ async storeData(terminateCommit: boolean): Promise<ResultObject> {
589
691
  if (terminateCommit) {
590
692
  if (this.cmi.mode === "normal") {
591
693
  if (this.cmi.credit === "credit") {
@@ -616,18 +718,20 @@ export default class Scorm2004API extends BaseAPI {
616
718
  navRequest = true;
617
719
  }
618
720
 
619
- const commitObject = this.renderCommitCMI(
620
- terminateCommit || this.settings.alwaysSendTotalTime,
621
- );
721
+ const shouldTerminateCommit =
722
+ terminateCommit || this.settings.alwaysSendTotalTime;
723
+ const commitObject = this.settings.renderCommonCommitFields
724
+ ? this.renderCommitObject(shouldTerminateCommit)
725
+ : this.renderCommitCMI(shouldTerminateCommit);
622
726
 
623
- if (this.apiLogLevel === global_constants.LOG_LEVEL_DEBUG) {
727
+ if (this.apiLogLevel === APIConstants.global.LOG_LEVEL_DEBUG) {
624
728
  console.debug(
625
729
  "Commit (terminated: " + (terminateCommit ? "yes" : "no") + "): ",
626
730
  );
627
731
  console.debug(commitObject);
628
732
  }
629
733
  if (typeof this.settings.lmsCommitUrl === "string") {
630
- const result = this.processHttpRequest(
734
+ const result = await this.processHttpRequest(
631
735
  this.settings.lmsCommitUrl,
632
736
  commitObject,
633
737
  terminateCommit,
@@ -646,7 +750,7 @@ export default class Scorm2004API extends BaseAPI {
646
750
  return result;
647
751
  } else {
648
752
  return {
649
- result: global_constants.SCORM_TRUE,
753
+ result: APIConstants.global.SCORM_TRUE,
650
754
  errorCode: 0,
651
755
  };
652
756
  }
@@ -0,0 +1,94 @@
1
+ import {BaseCMI} from "../common/base_cmi";
2
+ import {CMIScore} from "../common/score";
3
+ import APIConstants from "../../constants/api_constants";
4
+ import Regex from "../../constants/regex";
5
+ import ErrorCodes from "../../constants/error_codes";
6
+ import {AICCValidationError} from "../../exceptions";
7
+ import {checkAICCValidFormat} from "./validation";
8
+ import {CMIArray} from "../common/array";
9
+
10
+ /**
11
+ * Class for cmi.student_data.attempt_records array
12
+ */
13
+ export class CMIAttemptRecords extends CMIArray {
14
+ /**
15
+ * Constructor for inline Tries Array class
16
+ */
17
+ constructor() {
18
+ super({
19
+ children: APIConstants.aicc.attempt_records_children,
20
+ });
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Class for AICC Attempt Records
26
+ */
27
+ export class CMIAttemptRecordsObject extends BaseCMI {
28
+ /**
29
+ * Constructor for AICC Attempt Records object
30
+ */
31
+ constructor() {
32
+ super();
33
+ this.score = new CMIScore({
34
+ score_children: APIConstants.aicc.score_children,
35
+ score_range: Regex.aicc.score_range,
36
+ invalidErrorCode: ErrorCodes.scorm12.INVALID_SET_VALUE,
37
+ invalidTypeCode: ErrorCodes.scorm12.TYPE_MISMATCH,
38
+ invalidRangeCode: ErrorCodes.scorm12.VALUE_OUT_OF_RANGE,
39
+ errorClass: AICCValidationError,
40
+ });
41
+ }
42
+
43
+ public score: CMIScore;
44
+
45
+ /**
46
+ * Called when the API has been initialized after the CMI has been created
47
+ */
48
+ initialize() {
49
+ super.initialize();
50
+ this.score?.initialize();
51
+ }
52
+
53
+ private _lesson_status = "";
54
+
55
+ /**
56
+ * Getter for _lesson_status
57
+ * @return {string}
58
+ */
59
+ get lesson_status(): string {
60
+ return this._lesson_status;
61
+ }
62
+
63
+ /**
64
+ * Setter for _lesson_status
65
+ * @param {string} lesson_status
66
+ */
67
+ set lesson_status(lesson_status: string) {
68
+ if (checkAICCValidFormat(lesson_status, Regex.aicc.CMIStatus2)) {
69
+ this._lesson_status = lesson_status;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * toJSON for cmi.student_data.attempt_records.n object
75
+ * @return {
76
+ * {
77
+ * lesson_status: string,
78
+ * score: CMIScore
79
+ * }
80
+ * }
81
+ */
82
+ toJSON(): {
83
+ lesson_status: string;
84
+ score: CMIScore;
85
+ } {
86
+ this.jsonString = true;
87
+ const result = {
88
+ lesson_status: this.lesson_status,
89
+ score: this.score,
90
+ };
91
+ delete this.jsonString;
92
+ return result;
93
+ }
94
+ }