scorm-again 2.6.4 → 2.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.claude/.claude/settings.local.json +13 -0
  2. package/.claude/settings.local.json +33 -0
  3. package/dist/aicc.js +1 -0
  4. package/dist/aicc.js.map +1 -1
  5. package/dist/aicc.min.js +1 -1
  6. package/dist/aicc.min.js.map +1 -1
  7. package/dist/esm/aicc.js +1 -0
  8. package/dist/esm/aicc.js.map +1 -1
  9. package/dist/esm/aicc.min.js +1 -1
  10. package/dist/esm/aicc.min.js.map +1 -1
  11. package/dist/esm/scorm-again.js +17 -9
  12. package/dist/esm/scorm-again.js.map +1 -1
  13. package/dist/esm/scorm-again.min.js +1 -1
  14. package/dist/esm/scorm-again.min.js.map +1 -1
  15. package/dist/esm/scorm12.js +1 -0
  16. package/dist/esm/scorm12.js.map +1 -1
  17. package/dist/esm/scorm12.min.js +1 -1
  18. package/dist/esm/scorm12.min.js.map +1 -1
  19. package/dist/esm/scorm2004.js +17 -9
  20. package/dist/esm/scorm2004.js.map +1 -1
  21. package/dist/esm/scorm2004.min.js +1 -1
  22. package/dist/esm/scorm2004.min.js.map +1 -1
  23. package/dist/scorm-again.js +17 -9
  24. package/dist/scorm-again.js.map +1 -1
  25. package/dist/scorm-again.min.js +1 -1
  26. package/dist/scorm-again.min.js.map +1 -1
  27. package/dist/scorm12.js +1 -0
  28. package/dist/scorm12.js.map +1 -1
  29. package/dist/scorm12.min.js +1 -1
  30. package/dist/scorm12.min.js.map +1 -1
  31. package/dist/scorm2004.js +17 -9
  32. package/dist/scorm2004.js.map +1 -1
  33. package/dist/scorm2004.min.js +1 -1
  34. package/dist/scorm2004.min.js.map +1 -1
  35. package/dist/types/cmi/scorm2004/adl.d.ts +8 -2
  36. package/dist/types/cmi/scorm2004/cmi.d.ts +1 -1
  37. package/package.json +1 -1
  38. package/src/BaseAPI.ts +2 -0
  39. package/src/cmi/scorm2004/adl.ts +20 -17
  40. package/src/cmi/scorm2004/cmi.ts +2 -2
  41. package/test/BaseAPI.requestHandler.spec.ts +126 -0
@@ -56,16 +56,22 @@ export declare class ADLNavRequestValid extends BaseCMI {
56
56
  [key: string]: NAVBoolean;
57
57
  };
58
58
  set choice(choice: {
59
- [key: string]: string;
59
+ [key: string]: string | NAVBoolean;
60
60
  });
61
61
  get jump(): {
62
62
  [key: string]: NAVBoolean;
63
63
  };
64
64
  set jump(jump: {
65
- [key: string]: string;
65
+ [key: string]: string | NAVBoolean;
66
66
  });
67
67
  toJSON(): {
68
68
  previous: string;
69
69
  continue: string;
70
+ choice: {
71
+ [key: string]: NAVBoolean;
72
+ };
73
+ jump: {
74
+ [key: string]: NAVBoolean;
75
+ };
70
76
  };
71
77
  }
@@ -37,7 +37,7 @@ export declare class CMI extends BaseRootCMI {
37
37
  get _version(): string;
38
38
  set _version(_version: string);
39
39
  get _children(): string;
40
- set _children(_children: number);
40
+ set _children(_children: string);
41
41
  get completion_status(): string;
42
42
  set completion_status(completion_status: string);
43
43
  get completion_threshold(): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scorm-again",
3
- "version": "2.6.4",
3
+ "version": "2.6.5",
4
4
  "description": "A modern SCORM JavaScript run-time library for AICC, SCORM 1.2, and SCORM 2004",
5
5
  "main": "dist/scorm-again.js",
6
6
  "types": "index.d.ts",
package/src/BaseAPI.ts CHANGED
@@ -1147,6 +1147,8 @@ export default abstract class BaseAPI implements IBaseAPI {
1147
1147
  // if we are terminating the module or closing the browser window/tab, we need to make this fetch ASAP.
1148
1148
  // Some browsers, especially Chrome, do not like synchronous requests to be made when the window is closing.
1149
1149
  if (immediate) {
1150
+ // Apply requestHandler even for immediate requests
1151
+ params = this.settings.requestHandler(params);
1150
1152
  this.performFetch(url, params).then(async (response) => {
1151
1153
  await this.transformResponse(response);
1152
1154
  });
@@ -312,9 +312,9 @@ export class ADLNavRequestValid extends BaseCMI {
312
312
 
313
313
  /**
314
314
  * Setter for _choice
315
- * @param {{ [key: string]: string }} choice
315
+ * @param {{ [key: string]: string | NAVBoolean }} choice
316
316
  */
317
- set choice(choice: { [key: string]: string }) {
317
+ set choice(choice: { [key: string]: string | NAVBoolean }) {
318
318
  if (this.initialized) {
319
319
  throw new Scorm2004ValidationError(
320
320
  scorm2004_errors.READ_ONLY_ELEMENT as number,
@@ -327,13 +327,13 @@ export class ADLNavRequestValid extends BaseCMI {
327
327
  }
328
328
  for (const key in choice) {
329
329
  if ({}.hasOwnProperty.call(choice, key)) {
330
- if (
331
- choice[key] !== undefined &&
332
- check2004ValidFormat(choice[key], scorm2004_regex.NAVBoolean) &&
333
- check2004ValidFormat(key, scorm2004_regex.NAVTarget)
334
- ) {
335
- this._choice[key] =
336
- NAVBoolean[choice[key] as keyof typeof NAVBoolean];
330
+ if (choice[key] !== undefined && check2004ValidFormat(key, scorm2004_regex.NAVTarget)) {
331
+ const value = choice[key];
332
+ if (typeof value === 'string' && check2004ValidFormat(value, scorm2004_regex.NAVBoolean)) {
333
+ this._choice[key] = NAVBoolean[value as keyof typeof NAVBoolean];
334
+ } else if (Object.values(NAVBoolean).includes(value as NAVBoolean)) {
335
+ this._choice[key] = value as NAVBoolean;
336
+ }
337
337
  }
338
338
  }
339
339
  }
@@ -349,9 +349,9 @@ export class ADLNavRequestValid extends BaseCMI {
349
349
 
350
350
  /**
351
351
  * Setter for _jump
352
- * @param {{ [key: string]: string }} jump
352
+ * @param {{ [key: string]: string | NAVBoolean }} jump
353
353
  */
354
- set jump(jump: { [key: string]: string }) {
354
+ set jump(jump: { [key: string]: string | NAVBoolean }) {
355
355
  if (this.initialized) {
356
356
  throw new Scorm2004ValidationError(
357
357
  scorm2004_errors.READ_ONLY_ELEMENT as number,
@@ -364,12 +364,13 @@ export class ADLNavRequestValid extends BaseCMI {
364
364
  }
365
365
  for (const key in jump) {
366
366
  if ({}.hasOwnProperty.call(jump, key)) {
367
- if (
368
- jump[key] !== undefined &&
369
- check2004ValidFormat(jump[key], scorm2004_regex.NAVBoolean) &&
370
- check2004ValidFormat(key, scorm2004_regex.NAVTarget)
371
- ) {
372
- this._jump[key] = NAVBoolean[jump[key] as keyof typeof NAVBoolean];
367
+ if (jump[key] !== undefined && check2004ValidFormat(key, scorm2004_regex.NAVTarget)) {
368
+ const value = jump[key];
369
+ if (typeof value === 'string' && check2004ValidFormat(value, scorm2004_regex.NAVBoolean)) {
370
+ this._jump[key] = NAVBoolean[value as keyof typeof NAVBoolean];
371
+ } else if (Object.values(NAVBoolean).includes(value as NAVBoolean)) {
372
+ this._jump[key] = value as NAVBoolean;
373
+ }
373
374
  }
374
375
  }
375
376
  }
@@ -388,6 +389,8 @@ export class ADLNavRequestValid extends BaseCMI {
388
389
  toJSON(): {
389
390
  previous: string;
390
391
  continue: string;
392
+ choice: { [key: string]: NAVBoolean };
393
+ jump: { [key: string]: NAVBoolean };
391
394
  } {
392
395
  this.jsonString = true;
393
396
  const result = {
@@ -123,10 +123,10 @@ export class CMI extends BaseRootCMI {
123
123
 
124
124
  /**
125
125
  * Setter for __children. Just throws an error.
126
- * @param {number} _children
126
+ * @param {string} _children
127
127
  * @private
128
128
  */
129
- set _children(_children: number) {
129
+ set _children(_children: string) {
130
130
  throw new Scorm2004ValidationError(
131
131
  scorm2004_errors.READ_ONLY_ELEMENT as number,
132
132
  );
@@ -0,0 +1,126 @@
1
+ import { expect } from "expect";
2
+ import * as sinon from "sinon";
3
+ import { Scorm12API } from "../src/Scorm12API";
4
+ import { global_constants } from "../src/constants/api_constants";
5
+
6
+ describe("BaseAPI requestHandler Tests", () => {
7
+ let api: Scorm12API;
8
+ let requestHandlerSpy: sinon.SinonSpy;
9
+ let fetchStub: sinon.SinonStub;
10
+
11
+ beforeEach(() => {
12
+ // Stub the global fetch to prevent actual HTTP requests
13
+ fetchStub = sinon.stub(global, "fetch");
14
+ fetchStub.resolves(
15
+ new Response(
16
+ JSON.stringify({
17
+ result: global_constants.SCORM_TRUE,
18
+ errorCode: 0,
19
+ }),
20
+ {
21
+ status: 200,
22
+ headers: { "Content-Type": "application/json" },
23
+ }
24
+ )
25
+ );
26
+
27
+ // Create a spy for requestHandler
28
+ requestHandlerSpy = sinon.spy((params) => {
29
+ // Add a marker to verify the handler was called
30
+ if (typeof params === "object" && !Array.isArray(params)) {
31
+ params._requestHandlerCalled = true;
32
+ }
33
+ return params;
34
+ });
35
+
36
+ // Initialize API with requestHandler
37
+ api = new Scorm12API({
38
+ lmsCommitUrl: "http://example.com/commit",
39
+ requestHandler: requestHandlerSpy,
40
+ });
41
+ });
42
+
43
+ afterEach(() => {
44
+ sinon.restore();
45
+ });
46
+
47
+ describe("requestHandler with immediate requests", () => {
48
+ it("should call requestHandler during normal commit", async () => {
49
+ api.lmsInitialize();
50
+ api.lmsSetValue("cmi.core.student_name", "Test Student");
51
+
52
+ // Force a commit
53
+ await api.lmsCommit();
54
+
55
+ // Wait a bit for async operations
56
+ await new Promise(resolve => setTimeout(resolve, 100));
57
+
58
+ expect(requestHandlerSpy.calledOnce).toBe(true);
59
+
60
+ // Verify the params were transformed
61
+ const callArgs = fetchStub.firstCall.args[1];
62
+ const bodyData = JSON.parse(callArgs.body);
63
+ expect(bodyData._requestHandlerCalled).toBe(true);
64
+ });
65
+
66
+ it("should call requestHandler during terminate (immediate request)", async () => {
67
+ api.lmsInitialize();
68
+ api.lmsSetValue("cmi.core.student_name", "Test Student");
69
+
70
+ // Call finish which triggers immediate commit
71
+ const result = api.lmsFinish();
72
+ expect(result).toBe(global_constants.SCORM_TRUE);
73
+
74
+ // Wait a bit for async operations
75
+ await new Promise(resolve => setTimeout(resolve, 100));
76
+
77
+ // requestHandler should have been called
78
+ expect(requestHandlerSpy.called).toBe(true);
79
+
80
+ // Verify the params were transformed even for immediate request
81
+ const callArgs = fetchStub.firstCall.args[1];
82
+ const bodyData = JSON.parse(callArgs.body);
83
+ expect(bodyData._requestHandlerCalled).toBe(true);
84
+ });
85
+
86
+ it("should pass through params when no requestHandler is provided", async () => {
87
+ // Create API without requestHandler
88
+ const apiNoHandler = new Scorm12API({
89
+ lmsCommitUrl: "http://example.com/commit",
90
+ });
91
+
92
+ apiNoHandler.lmsInitialize();
93
+ apiNoHandler.lmsSetValue("cmi.core.student_name", "Test Student");
94
+
95
+ // Call finish
96
+ const result = apiNoHandler.lmsFinish();
97
+ expect(result).toBe(global_constants.SCORM_TRUE);
98
+
99
+ // Wait a bit for async operations
100
+ await new Promise(resolve => setTimeout(resolve, 100));
101
+
102
+ // Verify fetch was called but params don't have the marker
103
+ expect(fetchStub.called).toBe(true);
104
+ const callArgs = fetchStub.firstCall.args[1];
105
+ const bodyData = JSON.parse(callArgs.body);
106
+ expect(bodyData._requestHandlerCalled).toBeUndefined();
107
+ });
108
+
109
+ it("should handle requestHandler errors gracefully", async () => {
110
+ // Create API with error-throwing requestHandler
111
+ const errorApi = new Scorm12API({
112
+ lmsCommitUrl: "http://example.com/commit",
113
+ requestHandler: () => {
114
+ throw new Error("RequestHandler error");
115
+ },
116
+ });
117
+
118
+ errorApi.lmsInitialize();
119
+ errorApi.lmsSetValue("cmi.core.student_name", "Test Student");
120
+
121
+ // Call finish - should not throw
122
+ const result = errorApi.lmsFinish();
123
+ expect(result).toBe(global_constants.SCORM_TRUE);
124
+ });
125
+ });
126
+ });