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
package/src/BaseAPI.ts ADDED
@@ -0,0 +1,1300 @@
1
+ import { CMIArray } from "./cmi/common/array";
2
+ import { ValidationError } from "./exceptions";
3
+ import ErrorCodes, { ErrorCode } from "./constants/error_codes";
4
+ import APIConstants from "./constants/api_constants";
5
+ import { formatMessage, stringMatches, unflatten } from "./utilities";
6
+ import { BaseCMI } from "./cmi/common/base_cmi";
7
+ import { debounce } from "./utilities/debounce";
8
+ import { RefObject, ResultObject, Settings } from "./types/api_types";
9
+ import { DefaultSettings } from "./constants/default_settings";
10
+ import { IBaseAPI } from "./interfaces/IBaseAPI";
11
+ import { ScheduledCommit } from "./helpers/scheduled_commit";
12
+
13
+ /**
14
+ * Base API class for AICC, SCORM 1.2, and SCORM 2004. Should be considered
15
+ * abstract, and never initialized on its own.
16
+ */
17
+ export default abstract class BaseAPI implements IBaseAPI {
18
+ private _timeout?: ScheduledCommit;
19
+ private readonly _error_codes: ErrorCode;
20
+ private _settings: Settings = DefaultSettings;
21
+
22
+ /**
23
+ * Constructor for Base API class. Sets some shared API fields, as well as
24
+ * sets up options for the API.
25
+ * @param {ErrorCode} error_codes
26
+ * @param {Settings} settings
27
+ */
28
+ protected constructor(error_codes: ErrorCode, settings?: Settings) {
29
+ if (new.target === BaseAPI) {
30
+ throw new TypeError("Cannot construct BaseAPI instances directly");
31
+ }
32
+ this.currentState = APIConstants.global.STATE_NOT_INITIALIZED;
33
+ this.lastErrorCode = "0";
34
+ this.listenerArray = [];
35
+
36
+ this._error_codes = error_codes;
37
+
38
+ if (settings) {
39
+ this.settings = settings;
40
+ }
41
+ this.apiLogLevel = this.settings.logLevel;
42
+ this.selfReportSessionTime = this.settings.selfReportSessionTime;
43
+ }
44
+
45
+ public abstract cmi: BaseCMI;
46
+ public startingData?: RefObject;
47
+
48
+ public currentState: number;
49
+ public lastErrorCode: string;
50
+ public listenerArray: any[];
51
+ public apiLogLevel: number;
52
+ public selfReportSessionTime: boolean;
53
+
54
+ abstract reset(settings?: Settings): void;
55
+
56
+ /**
57
+ * Common reset method for all APIs. New settings are merged with the existing settings.
58
+ * @param {Settings} settings
59
+ * @protected
60
+ */
61
+ commonReset(settings?: Settings): void {
62
+ this.settings = { ...this.settings, ...settings };
63
+
64
+ this.currentState = APIConstants.global.STATE_NOT_INITIALIZED;
65
+ this.lastErrorCode = "0";
66
+ this.listenerArray = [];
67
+ }
68
+
69
+ /**
70
+ * Initialize the API
71
+ * @param {string} callbackName
72
+ * @param {string} initializeMessage
73
+ * @param {string} terminationMessage
74
+ * @return {string}
75
+ */
76
+ initialize(
77
+ callbackName: string,
78
+ initializeMessage?: string,
79
+ terminationMessage?: string,
80
+ ): string {
81
+ let returnValue = APIConstants.global.SCORM_FALSE;
82
+
83
+ if (this.isInitialized()) {
84
+ this.throwSCORMError(this._error_codes.INITIALIZED, initializeMessage);
85
+ } else if (this.isTerminated()) {
86
+ this.throwSCORMError(this._error_codes.TERMINATED, terminationMessage);
87
+ } else {
88
+ if (this.selfReportSessionTime) {
89
+ this.cmi.setStartTime();
90
+ }
91
+
92
+ this.currentState = APIConstants.global.STATE_INITIALIZED;
93
+ this.lastErrorCode = "0";
94
+ returnValue = APIConstants.global.SCORM_TRUE;
95
+ this.processListeners(callbackName);
96
+ }
97
+
98
+ this.apiLog(
99
+ callbackName,
100
+ "returned: " + returnValue,
101
+ APIConstants.global.LOG_LEVEL_INFO,
102
+ );
103
+ this.clearSCORMError(returnValue);
104
+
105
+ return returnValue;
106
+ }
107
+
108
+ abstract lmsInitialize(): string;
109
+
110
+ abstract lmsFinish(): string;
111
+
112
+ abstract lmsGetValue(CMIElement: string): string;
113
+
114
+ abstract lmsSetValue(CMIElement: string, value: any): string;
115
+
116
+ abstract lmsCommit(): string;
117
+
118
+ abstract lmsGetLastError(): string;
119
+
120
+ abstract lmsGetErrorString(CMIErrorCode: string | number): string;
121
+
122
+ abstract lmsGetDiagnostic(CMIErrorCode: string | number): string;
123
+
124
+ /**
125
+ * Abstract method for validating that a response is correct.
126
+ *
127
+ * @param {string} _CMIElement
128
+ * @param {any} _value
129
+ */
130
+ abstract validateCorrectResponse(_CMIElement: string, _value: any): void;
131
+
132
+ /**
133
+ * Gets or builds a new child element to add to the array.
134
+ * APIs that inherit BaseAPI should override this method.
135
+ *
136
+ * @param {string} _CMIElement - unused
137
+ * @param {*} _value - unused
138
+ * @param {boolean} _foundFirstIndex - unused
139
+ * @return {BaseCMI|null}
140
+ * @abstract
141
+ */
142
+ abstract getChildElement(
143
+ _CMIElement: string,
144
+ _value: any,
145
+ _foundFirstIndex: boolean,
146
+ ): BaseCMI | null;
147
+
148
+ /**
149
+ * Attempts to store the data to the LMS, logs data if no LMS configured
150
+ * APIs that inherit BaseAPI should override this function
151
+ *
152
+ * @param {boolean} _calculateTotalTime
153
+ * @return {ResultObject}
154
+ * @abstract
155
+ */
156
+ abstract storeData(_calculateTotalTime: boolean): Promise<ResultObject>;
157
+
158
+ /**
159
+ * Render the cmi object to the proper format for LMS commit
160
+ * APIs that inherit BaseAPI should override this function
161
+ *
162
+ * @param {boolean} _terminateCommit
163
+ * @return {RefObject|Array}
164
+ * @abstract
165
+ */
166
+ abstract renderCommitCMI(_terminateCommit: boolean): RefObject | Array<any>;
167
+
168
+ /**
169
+ * Logging for all SCORM actions
170
+ *
171
+ * @param {string} functionName
172
+ * @param {string} logMessage
173
+ * @param {number} messageLevel
174
+ * @param {string} CMIElement
175
+ */
176
+ apiLog(
177
+ functionName: string,
178
+ logMessage: string,
179
+ messageLevel: number,
180
+ CMIElement?: string,
181
+ ) {
182
+ logMessage = formatMessage(functionName, logMessage, CMIElement);
183
+
184
+ if (messageLevel >= this.apiLogLevel) {
185
+ this.settings.onLogMessage(messageLevel, logMessage);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Getter for _error_codes
191
+ * @return {ErrorCode}
192
+ */
193
+ get error_codes(): ErrorCode {
194
+ return this._error_codes;
195
+ }
196
+
197
+ /**
198
+ * Getter for _settings
199
+ * @return {Settings}
200
+ */
201
+ get settings(): Settings {
202
+ return this._settings;
203
+ }
204
+
205
+ /**
206
+ * Setter for _settings
207
+ * @param {Settings} settings
208
+ */
209
+ set settings(settings: Settings) {
210
+ this._settings = { ...this._settings, ...settings };
211
+ }
212
+
213
+ /**
214
+ * Terminates the current run of the API
215
+ * @param {string} callbackName
216
+ * @param {boolean} checkTerminated
217
+ * @return {string}
218
+ */
219
+ async terminate(
220
+ callbackName: string,
221
+ checkTerminated: boolean,
222
+ ): Promise<string> {
223
+ let returnValue = APIConstants.global.SCORM_FALSE;
224
+
225
+ if (
226
+ this.checkState(
227
+ checkTerminated,
228
+ this._error_codes.TERMINATION_BEFORE_INIT,
229
+ this._error_codes.MULTIPLE_TERMINATION,
230
+ )
231
+ ) {
232
+ this.currentState = APIConstants.global.STATE_TERMINATED;
233
+
234
+ const result: ResultObject = await this.storeData(true);
235
+ if (typeof result.errorCode !== "undefined" && result.errorCode > 0) {
236
+ this.throwSCORMError(result.errorCode);
237
+ }
238
+ returnValue =
239
+ typeof result !== "undefined" && result.result
240
+ ? result.result
241
+ : APIConstants.global.SCORM_FALSE;
242
+
243
+ if (checkTerminated) this.lastErrorCode = "0";
244
+
245
+ returnValue = APIConstants.global.SCORM_TRUE;
246
+ this.processListeners(callbackName);
247
+ }
248
+
249
+ this.apiLog(
250
+ callbackName,
251
+ "returned: " + returnValue,
252
+ APIConstants.global.LOG_LEVEL_INFO,
253
+ );
254
+ this.clearSCORMError(returnValue);
255
+
256
+ return returnValue;
257
+ }
258
+
259
+ /**
260
+ * Get the value of the CMIElement.
261
+ *
262
+ * @param {string} callbackName
263
+ * @param {boolean} checkTerminated
264
+ * @param {string} CMIElement
265
+ * @return {string}
266
+ */
267
+ getValue(
268
+ callbackName: string,
269
+ checkTerminated: boolean,
270
+ CMIElement: string,
271
+ ): string {
272
+ let returnValue: string = "";
273
+
274
+ if (
275
+ this.checkState(
276
+ checkTerminated,
277
+ this._error_codes.RETRIEVE_BEFORE_INIT,
278
+ this._error_codes.RETRIEVE_AFTER_TERM,
279
+ )
280
+ ) {
281
+ if (checkTerminated) this.lastErrorCode = "0";
282
+ try {
283
+ returnValue = this.getCMIValue(CMIElement);
284
+ } catch (e) {
285
+ returnValue = this.handleValueAccessException(e, returnValue);
286
+ }
287
+ this.processListeners(callbackName, CMIElement);
288
+ }
289
+
290
+ this.apiLog(
291
+ callbackName,
292
+ ": returned: " + returnValue,
293
+ APIConstants.global.LOG_LEVEL_INFO,
294
+ CMIElement,
295
+ );
296
+
297
+ if (returnValue === undefined) {
298
+ return "";
299
+ }
300
+
301
+ this.clearSCORMError(returnValue);
302
+
303
+ return returnValue;
304
+ }
305
+
306
+ /**
307
+ * Sets the value of the CMIElement.
308
+ *
309
+ * @param {string} callbackName
310
+ * @param {string} commitCallback
311
+ * @param {boolean} checkTerminated
312
+ * @param {string} CMIElement
313
+ * @param {*} value
314
+ * @return {string}
315
+ */
316
+ setValue(
317
+ callbackName: string,
318
+ commitCallback: string,
319
+ checkTerminated: boolean,
320
+ CMIElement: string,
321
+ value: any,
322
+ ): string {
323
+ if (value !== undefined) {
324
+ value = String(value);
325
+ }
326
+ let returnValue: string = APIConstants.global.SCORM_FALSE;
327
+
328
+ if (
329
+ this.checkState(
330
+ checkTerminated,
331
+ this._error_codes.STORE_BEFORE_INIT,
332
+ this._error_codes.STORE_AFTER_TERM,
333
+ )
334
+ ) {
335
+ if (checkTerminated) this.lastErrorCode = "0";
336
+ try {
337
+ returnValue = this.setCMIValue(CMIElement, value);
338
+ } catch (e) {
339
+ this.handleValueAccessException(e, returnValue);
340
+ }
341
+ this.processListeners(callbackName, CMIElement, value);
342
+ }
343
+
344
+ if (returnValue === undefined) {
345
+ returnValue = APIConstants.global.SCORM_FALSE;
346
+ }
347
+
348
+ // If we didn't have any errors while setting the data, go ahead and
349
+ // schedule a commit, if autocommit is turned on
350
+ if (String(this.lastErrorCode) === "0") {
351
+ if (this.settings.autocommit && !this._timeout) {
352
+ this.scheduleCommit(
353
+ this.settings.autocommitSeconds * 1000,
354
+ commitCallback,
355
+ );
356
+ }
357
+ }
358
+
359
+ this.apiLog(
360
+ callbackName,
361
+ ": " + value + ": result: " + returnValue,
362
+ APIConstants.global.LOG_LEVEL_INFO,
363
+ CMIElement,
364
+ );
365
+ this.clearSCORMError(returnValue);
366
+
367
+ return returnValue;
368
+ }
369
+
370
+ /**
371
+ * Orders LMS to store all content parameters
372
+ * @param {string} callbackName
373
+ * @param {boolean} checkTerminated
374
+ * @return {string}
375
+ */
376
+ async commit(
377
+ callbackName: string,
378
+ checkTerminated: boolean = false,
379
+ ): Promise<string> {
380
+ this.clearScheduledCommit();
381
+
382
+ let returnValue = APIConstants.global.SCORM_FALSE;
383
+
384
+ if (
385
+ this.checkState(
386
+ checkTerminated,
387
+ this._error_codes.COMMIT_BEFORE_INIT,
388
+ this._error_codes.COMMIT_AFTER_TERM,
389
+ )
390
+ ) {
391
+ const result = await this.storeData(false);
392
+ if (result.errorCode && result.errorCode > 0) {
393
+ this.throwSCORMError(result.errorCode);
394
+ }
395
+ returnValue =
396
+ typeof result !== "undefined" && result.result
397
+ ? result.result
398
+ : APIConstants.global.SCORM_FALSE;
399
+
400
+ this.apiLog(
401
+ callbackName,
402
+ " Result: " + returnValue,
403
+ APIConstants.global.LOG_LEVEL_DEBUG,
404
+ "HttpRequest",
405
+ );
406
+
407
+ if (checkTerminated) this.lastErrorCode = "0";
408
+
409
+ this.processListeners(callbackName);
410
+ }
411
+
412
+ this.apiLog(
413
+ callbackName,
414
+ "returned: " + returnValue,
415
+ APIConstants.global.LOG_LEVEL_INFO,
416
+ );
417
+ this.clearSCORMError(returnValue);
418
+
419
+ return returnValue;
420
+ }
421
+
422
+ /**
423
+ * Returns last error code
424
+ * @param {string} callbackName
425
+ * @return {string}
426
+ */
427
+ getLastError(callbackName: string): string {
428
+ const returnValue = String(this.lastErrorCode);
429
+
430
+ this.processListeners(callbackName);
431
+
432
+ this.apiLog(
433
+ callbackName,
434
+ "returned: " + returnValue,
435
+ APIConstants.global.LOG_LEVEL_INFO,
436
+ );
437
+
438
+ return returnValue;
439
+ }
440
+
441
+ /**
442
+ * Returns the errorNumber error description
443
+ *
444
+ * @param {string} callbackName
445
+ * @param {(string|number)} CMIErrorCode
446
+ * @return {string}
447
+ */
448
+ getErrorString(callbackName: string, CMIErrorCode: string | number): string {
449
+ let returnValue = "";
450
+
451
+ if (CMIErrorCode !== null && CMIErrorCode !== "") {
452
+ returnValue = this.getLmsErrorMessageDetails(CMIErrorCode);
453
+ this.processListeners(callbackName);
454
+ }
455
+
456
+ this.apiLog(
457
+ callbackName,
458
+ "returned: " + returnValue,
459
+ APIConstants.global.LOG_LEVEL_INFO,
460
+ );
461
+
462
+ return returnValue;
463
+ }
464
+
465
+ /**
466
+ * Returns a comprehensive description of the errorNumber error.
467
+ *
468
+ * @param {string} callbackName
469
+ * @param {(string|number)} CMIErrorCode
470
+ * @return {string}
471
+ */
472
+ getDiagnostic(callbackName: string, CMIErrorCode: string | number): string {
473
+ let returnValue = "";
474
+
475
+ if (CMIErrorCode !== null && CMIErrorCode !== "") {
476
+ returnValue = this.getLmsErrorMessageDetails(CMIErrorCode, true);
477
+ this.processListeners(callbackName);
478
+ }
479
+
480
+ this.apiLog(
481
+ callbackName,
482
+ "returned: " + returnValue,
483
+ APIConstants.global.LOG_LEVEL_INFO,
484
+ );
485
+
486
+ return returnValue;
487
+ }
488
+
489
+ /**
490
+ * Checks the LMS state and ensures it has been initialized.
491
+ *
492
+ * @param {boolean} checkTerminated
493
+ * @param {number} beforeInitError
494
+ * @param {number} afterTermError
495
+ * @return {boolean}
496
+ */
497
+ checkState(
498
+ checkTerminated: boolean,
499
+ beforeInitError: number,
500
+ afterTermError: number,
501
+ ): boolean {
502
+ if (this.isNotInitialized()) {
503
+ this.throwSCORMError(beforeInitError);
504
+ return false;
505
+ } else if (checkTerminated && this.isTerminated()) {
506
+ this.throwSCORMError(afterTermError);
507
+ return false;
508
+ }
509
+
510
+ return true;
511
+ }
512
+
513
+ /**
514
+ * Returns the message that corresponds to errorNumber
515
+ * APIs that inherit BaseAPI should override this function
516
+ *
517
+ * @param {(string|number)} _errorNumber
518
+ * @param {boolean} _detail
519
+ * @return {string}
520
+ * @abstract
521
+ */
522
+ getLmsErrorMessageDetails(
523
+ _errorNumber: string | number,
524
+ _detail: boolean = false,
525
+ ): string {
526
+ throw new Error(
527
+ "The getLmsErrorMessageDetails method has not been implemented",
528
+ );
529
+ }
530
+
531
+ /**
532
+ * Gets the value for the specific element.
533
+ * APIs that inherit BaseAPI should override this function
534
+ *
535
+ * @param {string} _CMIElement
536
+ * @return {string}
537
+ * @abstract
538
+ */
539
+ getCMIValue(_CMIElement: string): string {
540
+ throw new Error("The getCMIValue method has not been implemented");
541
+ }
542
+
543
+ /**
544
+ * Sets the value for the specific element.
545
+ * APIs that inherit BaseAPI should override this function
546
+ *
547
+ * @param {string} _CMIElement
548
+ * @param {any} _value
549
+ * @return {string}
550
+ * @abstract
551
+ */
552
+ setCMIValue(_CMIElement: string, _value: any): string {
553
+ throw new Error("The setCMIValue method has not been implemented");
554
+ }
555
+
556
+ /**
557
+ * Shared API method to set a valid for a given element.
558
+ *
559
+ * @param {string} methodName
560
+ * @param {boolean} scorm2004
561
+ * @param {string} CMIElement
562
+ * @param {any} value
563
+ * @return {string}
564
+ */
565
+ _commonSetCMIValue(
566
+ methodName: string,
567
+ scorm2004: boolean,
568
+ CMIElement: string,
569
+ value: any,
570
+ ): string {
571
+ if (!CMIElement || CMIElement === "") {
572
+ return APIConstants.global.SCORM_FALSE;
573
+ }
574
+
575
+ const structure = CMIElement.split(".");
576
+ let refObject: RefObject = this;
577
+ let returnValue = APIConstants.global.SCORM_FALSE;
578
+ let foundFirstIndex = false;
579
+
580
+ const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
581
+ const invalidErrorCode = scorm2004
582
+ ? this._error_codes.UNDEFINED_DATA_MODEL
583
+ : this._error_codes.GENERAL;
584
+
585
+ for (let idx = 0; idx < structure.length; idx++) {
586
+ const attribute = structure[idx];
587
+
588
+ if (idx === structure.length - 1) {
589
+ if (scorm2004 && attribute.substring(0, 8) === "{target=") {
590
+ if (this.isInitialized()) {
591
+ this.throwSCORMError(this._error_codes.READ_ONLY_ELEMENT);
592
+ } else {
593
+ refObject = {
594
+ ...refObject,
595
+ attribute: value,
596
+ };
597
+ }
598
+ } else if (!this._checkObjectHasProperty(refObject, attribute)) {
599
+ this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
600
+ } else {
601
+ if (
602
+ stringMatches(CMIElement, "\\.correct_responses\\.\\d+") &&
603
+ this.isInitialized()
604
+ ) {
605
+ this.validateCorrectResponse(CMIElement, value);
606
+ }
607
+
608
+ if (!scorm2004 || this.lastErrorCode === "0") {
609
+ refObject[attribute] = value;
610
+ returnValue = APIConstants.global.SCORM_TRUE;
611
+ }
612
+ }
613
+ } else {
614
+ refObject = refObject[attribute];
615
+ if (!refObject) {
616
+ this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
617
+ break;
618
+ }
619
+
620
+ if (refObject instanceof CMIArray) {
621
+ const index = parseInt(structure[idx + 1], 10);
622
+
623
+ // SCO is trying to set an item on an array
624
+ if (!isNaN(index)) {
625
+ const item = refObject.childArray[index];
626
+
627
+ if (item) {
628
+ refObject = item;
629
+ foundFirstIndex = true;
630
+ } else {
631
+ const newChild = this.getChildElement(
632
+ CMIElement,
633
+ value,
634
+ foundFirstIndex,
635
+ );
636
+ foundFirstIndex = true;
637
+
638
+ if (!newChild) {
639
+ this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
640
+ } else {
641
+ if (refObject.initialized) newChild.initialize();
642
+
643
+ refObject.childArray.push(newChild);
644
+ refObject = newChild;
645
+ }
646
+ }
647
+
648
+ // Have to update idx value to skip the array position
649
+ idx++;
650
+ }
651
+ }
652
+ }
653
+ }
654
+
655
+ if (returnValue === APIConstants.global.SCORM_FALSE) {
656
+ this.apiLog(
657
+ methodName,
658
+ `There was an error setting the value for: ${CMIElement}, value of: ${value}`,
659
+ APIConstants.global.LOG_LEVEL_WARNING,
660
+ );
661
+ }
662
+
663
+ return returnValue;
664
+ }
665
+
666
+ /**
667
+ * Gets a value from the CMI Object
668
+ *
669
+ * @param {string} methodName
670
+ * @param {boolean} scorm2004
671
+ * @param {string} CMIElement
672
+ * @return {any}
673
+ */
674
+ _commonGetCMIValue(
675
+ methodName: string,
676
+ scorm2004: boolean,
677
+ CMIElement: string,
678
+ ): any {
679
+ if (!CMIElement || CMIElement === "") {
680
+ return "";
681
+ }
682
+
683
+ const structure = CMIElement.split(".");
684
+ let refObject: RefObject = this;
685
+ let attribute = null;
686
+
687
+ const uninitializedErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) has not been initialized.`;
688
+ const invalidErrorMessage = `The data model element passed to ${methodName} (${CMIElement}) is not a valid SCORM data model element.`;
689
+ const invalidErrorCode = scorm2004
690
+ ? this._error_codes.UNDEFINED_DATA_MODEL
691
+ : this._error_codes.GENERAL;
692
+
693
+ for (let idx = 0; idx < structure.length; idx++) {
694
+ attribute = structure[idx];
695
+
696
+ if (!scorm2004) {
697
+ if (idx === structure.length - 1) {
698
+ if (!this._checkObjectHasProperty(refObject, attribute)) {
699
+ this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
700
+ return;
701
+ }
702
+ }
703
+ } else {
704
+ if (
705
+ String(attribute).substring(0, 8) === "{target=" &&
706
+ typeof refObject._isTargetValid == "function"
707
+ ) {
708
+ const target = String(attribute).substring(
709
+ 8,
710
+ String(attribute).length - 9,
711
+ );
712
+ return refObject._isTargetValid(target);
713
+ } else if (!this._checkObjectHasProperty(refObject, attribute)) {
714
+ this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
715
+ return;
716
+ }
717
+ }
718
+
719
+ refObject = refObject[attribute];
720
+ if (refObject === undefined) {
721
+ this.throwSCORMError(invalidErrorCode, invalidErrorMessage);
722
+ break;
723
+ }
724
+
725
+ if (refObject instanceof CMIArray) {
726
+ const index = parseInt(structure[idx + 1], 10);
727
+
728
+ // SCO is trying to set an item on an array
729
+ if (!isNaN(index)) {
730
+ const item = refObject.childArray[index];
731
+
732
+ if (item) {
733
+ refObject = item;
734
+ } else {
735
+ this.throwSCORMError(
736
+ this._error_codes.VALUE_NOT_INITIALIZED,
737
+ uninitializedErrorMessage,
738
+ );
739
+ break;
740
+ }
741
+
742
+ // Have to update idx value to skip the array position
743
+ idx++;
744
+ }
745
+ }
746
+ }
747
+
748
+ if (refObject === null || refObject === undefined) {
749
+ if (!scorm2004) {
750
+ if (attribute === "_children") {
751
+ this.throwSCORMError(ErrorCodes.scorm12.CHILDREN_ERROR);
752
+ } else if (attribute === "_count") {
753
+ this.throwSCORMError(ErrorCodes.scorm12.COUNT_ERROR);
754
+ }
755
+ }
756
+ } else {
757
+ return refObject;
758
+ }
759
+ }
760
+
761
+ /**
762
+ * Returns true if the API's current state is STATE_INITIALIZED
763
+ *
764
+ * @return {boolean}
765
+ */
766
+ isInitialized(): boolean {
767
+ return this.currentState === APIConstants.global.STATE_INITIALIZED;
768
+ }
769
+
770
+ /**
771
+ * Returns true if the API's current state is STATE_NOT_INITIALIZED
772
+ *
773
+ * @return {boolean}
774
+ */
775
+ isNotInitialized(): boolean {
776
+ return this.currentState === APIConstants.global.STATE_NOT_INITIALIZED;
777
+ }
778
+
779
+ /**
780
+ * Returns true if the API's current state is STATE_TERMINATED
781
+ *
782
+ * @return {boolean}
783
+ */
784
+ isTerminated(): boolean {
785
+ return this.currentState === APIConstants.global.STATE_TERMINATED;
786
+ }
787
+
788
+ /**
789
+ * Provides a mechanism for attaching to a specific SCORM event
790
+ *
791
+ * @param {string} listenerName
792
+ * @param {function} callback
793
+ */
794
+ on(listenerName: string, callback: Function) {
795
+ if (!callback) return;
796
+
797
+ const listenerFunctions = listenerName.split(" ");
798
+ for (let i = 0; i < listenerFunctions.length; i++) {
799
+ const listenerSplit = listenerFunctions[i].split(".");
800
+ if (listenerSplit.length === 0) return;
801
+
802
+ const functionName = listenerSplit[0];
803
+
804
+ let CMIElement = null;
805
+ if (listenerSplit.length > 1) {
806
+ CMIElement = listenerName.replace(functionName + ".", "");
807
+ }
808
+
809
+ this.listenerArray.push({
810
+ functionName: functionName,
811
+ CMIElement: CMIElement,
812
+ callback: callback,
813
+ });
814
+
815
+ this.apiLog(
816
+ "on",
817
+ `Added event listener: ${this.listenerArray.length}`,
818
+ APIConstants.global.LOG_LEVEL_INFO,
819
+ functionName,
820
+ );
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Provides a mechanism for detaching a specific SCORM event listener
826
+ *
827
+ * @param {string} listenerName
828
+ * @param {function} callback
829
+ */
830
+ off(listenerName: string, callback: Function) {
831
+ if (!callback) return;
832
+
833
+ const listenerFunctions = listenerName.split(" ");
834
+ for (let i = 0; i < listenerFunctions.length; i++) {
835
+ const listenerSplit = listenerFunctions[i].split(".");
836
+ if (listenerSplit.length === 0) return;
837
+
838
+ const functionName = listenerSplit[0];
839
+
840
+ let CMIElement = null;
841
+ if (listenerSplit.length > 1) {
842
+ CMIElement = listenerName.replace(functionName + ".", "");
843
+ }
844
+
845
+ const removeIndex = this.listenerArray.findIndex(
846
+ (obj) =>
847
+ obj.functionName === functionName &&
848
+ obj.CMIElement === CMIElement &&
849
+ obj.callback === callback,
850
+ );
851
+ if (removeIndex !== -1) {
852
+ this.listenerArray.splice(removeIndex, 1);
853
+ this.apiLog(
854
+ "off",
855
+ `Removed event listener: ${this.listenerArray.length}`,
856
+ APIConstants.global.LOG_LEVEL_INFO,
857
+ functionName,
858
+ );
859
+ }
860
+ }
861
+ }
862
+
863
+ /**
864
+ * Provides a mechanism for clearing all listeners from a specific SCORM event
865
+ *
866
+ * @param {string} listenerName
867
+ */
868
+ clear(listenerName: string) {
869
+ const listenerFunctions = listenerName.split(" ");
870
+ for (let i = 0; i < listenerFunctions.length; i++) {
871
+ const listenerSplit = listenerFunctions[i].split(".");
872
+ if (listenerSplit.length === 0) return;
873
+
874
+ const functionName = listenerSplit[0];
875
+
876
+ let CMIElement = null;
877
+ if (listenerSplit.length > 1) {
878
+ CMIElement = listenerName.replace(functionName + ".", "");
879
+ }
880
+
881
+ this.listenerArray = this.listenerArray.filter(
882
+ (obj) =>
883
+ obj.functionName !== functionName && obj.CMIElement !== CMIElement,
884
+ );
885
+ }
886
+ }
887
+
888
+ /**
889
+ * Processes any 'on' listeners that have been created
890
+ *
891
+ * @param {string} functionName
892
+ * @param {string} CMIElement
893
+ * @param {any} value
894
+ */
895
+ processListeners(functionName: string, CMIElement?: string, value?: any) {
896
+ this.apiLog(
897
+ functionName,
898
+ value,
899
+ APIConstants.global.LOG_LEVEL_INFO,
900
+ CMIElement,
901
+ );
902
+ for (let i = 0; i < this.listenerArray.length; i++) {
903
+ const listener = this.listenerArray[i];
904
+ const functionsMatch = listener.functionName === functionName;
905
+ const listenerHasCMIElement = !!listener.CMIElement;
906
+ let CMIElementsMatch = false;
907
+ if (
908
+ CMIElement &&
909
+ listener.CMIElement &&
910
+ listener.CMIElement.substring(listener.CMIElement.length - 1) === "*"
911
+ ) {
912
+ CMIElementsMatch =
913
+ CMIElement.indexOf(
914
+ listener.CMIElement.substring(0, listener.CMIElement.length - 1),
915
+ ) === 0;
916
+ } else {
917
+ CMIElementsMatch = listener.CMIElement === CMIElement;
918
+ }
919
+
920
+ if (functionsMatch && (!listenerHasCMIElement || CMIElementsMatch)) {
921
+ this.apiLog(
922
+ "processListeners",
923
+ `Processing listener: ${listener.functionName}`,
924
+ APIConstants.global.LOG_LEVEL_INFO,
925
+ CMIElement,
926
+ );
927
+ listener.callback(CMIElement, value);
928
+ }
929
+ }
930
+ }
931
+
932
+ /**
933
+ * Throws a SCORM error
934
+ *
935
+ * @param {number} errorNumber
936
+ * @param {string} message
937
+ */
938
+ throwSCORMError(errorNumber: number, message?: string) {
939
+ if (!message) {
940
+ message = this.getLmsErrorMessageDetails(errorNumber);
941
+ }
942
+
943
+ this.apiLog(
944
+ "throwSCORMError",
945
+ errorNumber + ": " + message,
946
+ APIConstants.global.LOG_LEVEL_ERROR,
947
+ );
948
+
949
+ this.lastErrorCode = String(errorNumber);
950
+ }
951
+
952
+ /**
953
+ * Clears the last SCORM error code on success.
954
+ *
955
+ * @param {string} success
956
+ */
957
+ clearSCORMError(success: string) {
958
+ if (success !== undefined && success !== APIConstants.global.SCORM_FALSE) {
959
+ this.lastErrorCode = "0";
960
+ }
961
+ }
962
+
963
+ /**
964
+ * Load the CMI from a flattened JSON object
965
+ * @param {RefObject} json
966
+ * @param {string} CMIElement
967
+ */
968
+ loadFromFlattenedJSON(json: RefObject, CMIElement?: string) {
969
+ if (!CMIElement) {
970
+ // by default, we start from a blank string because we're expecting each element to start with `cmi`
971
+ CMIElement = "";
972
+ }
973
+ if (!this.isNotInitialized()) {
974
+ console.error(
975
+ "loadFromFlattenedJSON can only be called before the call to lmsInitialize.",
976
+ );
977
+ return;
978
+ }
979
+
980
+ /**
981
+ * Tests two strings against a given regular expression pattern and determines a numeric or null result based on the matching criterion.
982
+ *
983
+ * @param {string} a - The first string to be tested against the pattern.
984
+ * @param {string} c - The second string to be tested against the pattern.
985
+ * @param {RegExp} a_pattern - The regular expression pattern to test the strings against.
986
+ * @return {number | null} A numeric result based on the matching criterion, or null if the strings do not match the pattern.
987
+ */
988
+ function testPattern(
989
+ a: string,
990
+ c: string,
991
+ a_pattern: RegExp,
992
+ ): number | null {
993
+ const a_match = a.match(a_pattern);
994
+
995
+ let c_match;
996
+ if (a_match !== null && (c_match = c.match(a_pattern)) !== null) {
997
+ const a_num = Number(a_match[2]);
998
+ const c_num = Number(c_match[2]);
999
+ if (a_num === c_num) {
1000
+ if (a_match[3] === "id") {
1001
+ return -1;
1002
+ } else if (a_match[3] === "type") {
1003
+ if (c_match[3] === "id") {
1004
+ return 1;
1005
+ } else {
1006
+ return -1;
1007
+ }
1008
+ } else {
1009
+ return 1;
1010
+ }
1011
+ }
1012
+ return a_num - c_num;
1013
+ }
1014
+
1015
+ return null;
1016
+ }
1017
+
1018
+ const int_pattern = /^(cmi\.interactions\.)(\d+)\.(.*)$/;
1019
+ const obj_pattern = /^(cmi\.objectives\.)(\d+)\.(.*)$/;
1020
+
1021
+ const result = Object.keys(json).map(function (key) {
1022
+ return [String(key), json[key]];
1023
+ });
1024
+
1025
+ // CMI interactions need to have id and type loaded before any other fields
1026
+ result.sort(function ([a, _b], [c, _d]) {
1027
+ let test;
1028
+ if ((test = testPattern(a, c, int_pattern)) !== null) {
1029
+ return test;
1030
+ }
1031
+ if ((test = testPattern(a, c, obj_pattern)) !== null) {
1032
+ return test;
1033
+ }
1034
+
1035
+ if (a < c) {
1036
+ return -1;
1037
+ }
1038
+ if (a > c) {
1039
+ return 1;
1040
+ }
1041
+ return 0;
1042
+ });
1043
+
1044
+ let obj: RefObject;
1045
+ result.forEach((element) => {
1046
+ obj = {};
1047
+ obj[element[0]] = element[1];
1048
+ this.loadFromJSON(unflatten(obj), CMIElement);
1049
+ });
1050
+ }
1051
+
1052
+ /**
1053
+ * Loads CMI data from a JSON object.
1054
+ *
1055
+ * @param {RefObject} json
1056
+ * @param {string} CMIElement
1057
+ */
1058
+ loadFromJSON(json: RefObject, CMIElement: string) {
1059
+ if (!this.isNotInitialized()) {
1060
+ console.error(
1061
+ "loadFromJSON can only be called before the call to lmsInitialize.",
1062
+ );
1063
+ return;
1064
+ }
1065
+
1066
+ CMIElement = CMIElement !== undefined ? CMIElement : "cmi";
1067
+
1068
+ this.startingData = json;
1069
+
1070
+ // could this be refactored down to flatten(json) then setCMIValue on each?
1071
+ for (const key in json) {
1072
+ if ({}.hasOwnProperty.call(json, key) && json[key]) {
1073
+ const currentCMIElement = (CMIElement ? CMIElement + "." : "") + key;
1074
+ const value = json[key];
1075
+
1076
+ if (value["childArray"]) {
1077
+ for (let i = 0; i < value["childArray"].length; i++) {
1078
+ this.loadFromJSON(
1079
+ value["childArray"][i],
1080
+ currentCMIElement + "." + i,
1081
+ );
1082
+ }
1083
+ } else if (value.constructor === Object) {
1084
+ this.loadFromJSON(value, currentCMIElement);
1085
+ } else {
1086
+ this.setCMIValue(currentCMIElement, value);
1087
+ }
1088
+ }
1089
+ }
1090
+ }
1091
+
1092
+ /**
1093
+ * Render the CMI object to JSON for sending to an LMS.
1094
+ *
1095
+ * @return {string}
1096
+ */
1097
+ renderCMIToJSONString(): string {
1098
+ const cmi = this.cmi;
1099
+ // Do we want/need to return fields that have no set value?
1100
+ if (this.settings.sendFullCommit) {
1101
+ return JSON.stringify({ cmi });
1102
+ }
1103
+ return JSON.stringify({ cmi }, (k, v) => (v === undefined ? null : v), 2);
1104
+ }
1105
+
1106
+ /**
1107
+ * Returns a JS object representing the current cmi
1108
+ * @return {object}
1109
+ */
1110
+ renderCMIToJSONObject(): object {
1111
+ return JSON.parse(this.renderCMIToJSONString());
1112
+ }
1113
+
1114
+ /**
1115
+ * Send the request to the LMS
1116
+ * @param {string} url
1117
+ * @param {RefObject|Array} params
1118
+ * @param {boolean} immediate
1119
+ * @return {ResultObject}
1120
+ */
1121
+ async processHttpRequest(
1122
+ url: string,
1123
+ params: RefObject | Array<any>,
1124
+ immediate: boolean = false,
1125
+ ): Promise<ResultObject> {
1126
+ const api = this;
1127
+ const genericError: ResultObject = {
1128
+ result: APIConstants.global.SCORM_FALSE,
1129
+ errorCode: this.error_codes.GENERAL,
1130
+ };
1131
+
1132
+ // if we are terminating the module or closing the browser window/tab, we need to make this fetch ASAP.
1133
+ // Some browsers, especially Chrome, do not like synchronous requests to be made when the window is closing.
1134
+ if (immediate) {
1135
+ this.performFetch(url, params).then(async (response) => {
1136
+ await this.transformResponse(response);
1137
+ });
1138
+ return {
1139
+ result: APIConstants.global.SCORM_TRUE,
1140
+ errorCode: 0,
1141
+ };
1142
+ }
1143
+
1144
+ const process = async (
1145
+ url: string,
1146
+ params: RefObject | Array<any>,
1147
+ settings: Settings,
1148
+ ): Promise<ResultObject> => {
1149
+ try {
1150
+ params = settings.requestHandler(params);
1151
+ const response = await this.performFetch(url, params);
1152
+
1153
+ return this.transformResponse(response);
1154
+ } catch (e) {
1155
+ this.apiLog(
1156
+ "processHttpRequest",
1157
+ e,
1158
+ APIConstants.global.LOG_LEVEL_ERROR,
1159
+ );
1160
+ api.processListeners("CommitError");
1161
+ return genericError;
1162
+ }
1163
+ };
1164
+
1165
+ if (this.settings.asyncCommit) {
1166
+ const debouncedProcess = debounce(process, 500, immediate);
1167
+ debouncedProcess(url, params, this.settings);
1168
+
1169
+ return {
1170
+ result: APIConstants.global.SCORM_TRUE,
1171
+ errorCode: 0,
1172
+ };
1173
+ } else {
1174
+ return await process(url, params, this.settings);
1175
+ }
1176
+ }
1177
+
1178
+ /**
1179
+ * Throws a SCORM error
1180
+ *
1181
+ * @param {number} when - the number of milliseconds to wait before committing
1182
+ * @param {string} callback - the name of the commit event callback
1183
+ */
1184
+ scheduleCommit(when: number, callback: string) {
1185
+ this._timeout = new ScheduledCommit(this, when, callback);
1186
+ this.apiLog(
1187
+ "scheduleCommit",
1188
+ "scheduled",
1189
+ APIConstants.global.LOG_LEVEL_DEBUG,
1190
+ "",
1191
+ );
1192
+ }
1193
+
1194
+ /**
1195
+ * Clears and cancels any currently scheduled commits
1196
+ */
1197
+ clearScheduledCommit() {
1198
+ if (this._timeout) {
1199
+ this._timeout.cancel();
1200
+ this._timeout = undefined;
1201
+ this.apiLog(
1202
+ "clearScheduledCommit",
1203
+ "cleared",
1204
+ APIConstants.global.LOG_LEVEL_DEBUG,
1205
+ "",
1206
+ );
1207
+ }
1208
+ }
1209
+
1210
+ /**
1211
+ * Check to see if the specific object has the given property
1212
+ * @param {RefObject} refObject
1213
+ * @param {string} attribute
1214
+ * @return {boolean}
1215
+ * @private
1216
+ */
1217
+ private _checkObjectHasProperty(
1218
+ refObject: RefObject,
1219
+ attribute: string,
1220
+ ): boolean {
1221
+ return (
1222
+ Object.hasOwnProperty.call(refObject, attribute) ||
1223
+ Object.getOwnPropertyDescriptor(
1224
+ Object.getPrototypeOf(refObject),
1225
+ attribute,
1226
+ ) != null ||
1227
+ attribute in refObject
1228
+ );
1229
+ }
1230
+
1231
+ /**
1232
+ * Handles the error that occurs when trying to access a value
1233
+ * @param {any} e
1234
+ * @param {string} returnValue
1235
+ * @return {string}
1236
+ * @private
1237
+ */
1238
+ private handleValueAccessException(e: any, returnValue: string): string {
1239
+ if (e instanceof ValidationError) {
1240
+ this.lastErrorCode = String(e.errorCode);
1241
+ returnValue = APIConstants.global.SCORM_FALSE;
1242
+ } else {
1243
+ if (e instanceof Error && e.message) {
1244
+ console.error(e.message);
1245
+ } else {
1246
+ console.error(e);
1247
+ }
1248
+ this.throwSCORMError(this._error_codes.GENERAL);
1249
+ }
1250
+ return returnValue;
1251
+ }
1252
+
1253
+ /**
1254
+ * Perform the fetch request to the LMS
1255
+ * @param {string} url
1256
+ * @param {RefObject|Array} params
1257
+ * @return {Promise<Response>}
1258
+ * @private
1259
+ */
1260
+ private async performFetch(
1261
+ url: string,
1262
+ params: RefObject | Array<any>,
1263
+ ): Promise<Response> {
1264
+ return fetch(url, {
1265
+ method: "POST",
1266
+ body: params instanceof Array ? params.join("&") : JSON.stringify(params),
1267
+ headers: {
1268
+ ...this.settings.xhrHeaders,
1269
+ "Content-Type": this.settings.commitRequestDataType,
1270
+ },
1271
+ credentials: this.settings.xhrWithCredentials ? "include" : undefined,
1272
+ keepalive: true,
1273
+ });
1274
+ }
1275
+
1276
+ /**
1277
+ * Transforms the response from the LMS to a ResultObject
1278
+ * @param {Response} response
1279
+ * @return {Promise<ResultObject>}
1280
+ * @private
1281
+ */
1282
+ private async transformResponse(response: Response): Promise<ResultObject> {
1283
+ const result =
1284
+ typeof this.settings.responseHandler === "function"
1285
+ ? await this.settings.responseHandler(response)
1286
+ : await response.json();
1287
+
1288
+ if (
1289
+ response.status >= 200 &&
1290
+ response.status <= 299 &&
1291
+ (result.result === true ||
1292
+ result.result === APIConstants.global.SCORM_TRUE)
1293
+ ) {
1294
+ this.processListeners("CommitSuccess");
1295
+ } else {
1296
+ this.processListeners("CommitError");
1297
+ }
1298
+ return result;
1299
+ }
1300
+ }