Skip to content

Commit 7d8ab39

Browse files
jcputneyclaude
andauthored
refactor: rename useBeaconInsteadOfFetch to asyncModeBeaconBehavior (#1326)
* refactor: rename useBeaconInsteadOfFetch to asyncModeBeaconBehavior - Renamed setting to clarify it only applies to async commit mode - In sync mode (default), termination always uses sendBeacon (hardcoded) - Added backwards compatibility with deprecation warning for old setting - Updated MIGRATION.md with: - Fixed sendBeacon documentation (section 23) - Added mastery_override default change (section 7) - Added API method parameters change (section 13) - Updated all settings references - Renumbered sections appropriately - Added new settings to tables * chore: remove unused useBeaconInsteadOfFetch backwards compatibility Setting was never released, so no backwards compatibility needed. * fix: allow boolean in ResultObject.result for backwards compatibility The result field now accepts string | boolean to maintain compatibility with custom response handlers that may return boolean values. * test: Update tests for asyncModeBeaconBehavior rename and add boolean result tests - Rename useBeaconInsteadOfFetch to asyncModeBeaconBehavior in test file - Add test case for boolean false in _isSuccessResponse - Add tests for boolean true/false results from LMS triggering correct events * fix: convert boolean ResultObject.result to string for internal use When LMS returns boolean true/false in result, convert to string "true"/"false" for internal API return values while maintaining backwards compatibility. --------- Co-authored-by: Claude <[email protected]>
1 parent 88bbc60 commit 7d8ab39

File tree

6 files changed

+135
-34
lines changed

6 files changed

+135
-34
lines changed

MIGRATION.md

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,18 @@ await api.internalFinish(); // Existed
168168

169169
These changes are likely to affect most users upgrading.
170170

171-
### 7. CMI Default Value Changes
171+
### 7. CMI and Setting Default Value Changes
172+
173+
**SCORM 1.2 `mastery_override` setting (IMPORTANT):**
174+
```javascript
175+
// v2.6.5: false (LMS does NOT override lesson_status based on score)
176+
// v3.0.0: true (LMS WILL override lesson_status based on mastery score - per SCORM 1.2 spec)
177+
```
178+
179+
This means if a learner's score meets or exceeds the mastery score, `lesson_status` will automatically be set to `passed` (or `failed` if below). To preserve v2.6.5 behavior:
180+
```javascript
181+
const api = new Scorm12API({ mastery_override: false });
182+
```
172183

173184
**SCORM 2004 `cmi.total_time`:**
174185
```javascript
@@ -253,13 +264,44 @@ CompletionStatus.COMPLETED // UPPERCASE
253264
SuccessStatus.PASSED // UPPERCASE
254265
```
255266

267+
### 13. API Methods Now Accept Optional Parameter
268+
269+
Per SCORM specification, `LMSInitialize()`, `LMSFinish()`, and `LMSCommit()` now accept an optional parameter that **must be an empty string** if provided:
270+
271+
```javascript
272+
// v2.6.5 - No parameter
273+
api.LMSInitialize();
274+
api.LMSFinish();
275+
api.LMSCommit();
276+
277+
// v3.0.0 - Parameter optional, but must be empty string if provided
278+
api.LMSInitialize(""); // OK
279+
api.LMSInitialize(); // OK (defaults to "")
280+
api.LMSInitialize("test"); // ERROR 310 (Argument Error), returns "false"
281+
```
282+
283+
Same applies to SCORM 2004: `Initialize("")`, `Terminate("")`, `Commit("")`.
284+
285+
**TypeScript signatures changed:**
286+
```typescript
287+
// v2.6.5
288+
LMSInitialize: () => string;
289+
LMSFinish: () => string;
290+
LMSCommit: () => string;
291+
292+
// v3.0.0
293+
LMSInitialize: (parameter?: string) => string;
294+
LMSFinish: (parameter?: string) => string;
295+
LMSCommit: (parameter?: string) => string;
296+
```
297+
256298
---
257299

258300
## Medium-Impact Changes
259301

260302
These changes may affect some users depending on their implementation.
261303

262-
### 13. SCORM 1.2 Validation Now Stricter
304+
### 15. SCORM 1.2 Validation Now Stricter
263305

264306
New validation added to previously unvalidated fields:
265307

@@ -269,7 +311,7 @@ New validation added to previously unvalidated fields:
269311
- `cmi.core.lesson_mode` - Now validates against CMILessonMode regex
270312
- `cmi.core.exit` - Accepts "normal" (SCORM 2004 value) with warning, normalizes to ""
271313

272-
### 14. `setStartTime()` Can Only Be Called Once
314+
### 16. `setStartTime()` Can Only Be Called Once
273315

274316
```javascript
275317
// v2.6.5 - Could be called multiple times
@@ -281,7 +323,7 @@ api.cmi.setStartTime();
281323
api.cmi.setStartTime(); // ERROR: "Start time has already been set."
282324
```
283325

284-
### 15. SCORM 2004 `adl.data` Validation Stricter
326+
### 17. SCORM 2004 `adl.data` Validation Stricter
285327

286328
```javascript
287329
// adl.data.n.id is READ-ONLY after Initialize()
@@ -298,7 +340,7 @@ api.SetValue("adl.data.0.store", "value"); // Now OK
298340
api.GetValue("adl.data.0.store"); // ERROR 403 if never set
299341
```
300342

301-
### 16. Type Changes: `RefObject` → `StringKeyMap`
343+
### 18. Type Changes: `RefObject` → `StringKeyMap`
302344

303345
```typescript
304346
// v2.6.5
@@ -310,7 +352,7 @@ type StringKeyMap = Record<string, unknown>;
310352
xhrHeaders?: StringKeyMap;
311353
```
312354

313-
### 17. Regex Pattern Changes
355+
### 19. Regex Pattern Changes
314356

315357
Most patterns are now more permissive:
316358

@@ -328,7 +370,7 @@ Most patterns are now more permissive:
328370

329371
These are non-breaking additions available in v3.0.0.
330372

331-
### 18. CrossFrame Communication
373+
### 20. CrossFrame Communication
332374

333375
New feature for sandboxed iframe SCORM content:
334376
- `CrossFrameAPI` - Child frame proxy with synchronous API
@@ -338,7 +380,7 @@ New feature for sandboxed iframe SCORM content:
338380

339381
See [Cross-Frame Communication](https://jcputney.github.io/scorm-again/docs/lms-integration/cross-frame-communication) for details.
340382

341-
### 19. SCORM 2004 Sequencing Engine
383+
### 21. SCORM 2004 Sequencing Engine
342384

343385
Complete implementation of SCORM 2004 4th Edition sequencing:
344386
- Activity tree management
@@ -350,30 +392,37 @@ Complete implementation of SCORM 2004 4th Edition sequencing:
350392

351393
**Note:** If you don't configure sequencing, behavior is unchanged from v2.6.5.
352394

353-
### 20. Offline Storage Support
395+
### 22. Offline Storage Support
354396

355397
New settings for offline operation:
356398
- `enableOfflineSupport` - Enable offline data storage
357399
- `syncOnInitialize` - Sync when API initializes
358400
- `syncOnTerminate` - Sync when API terminates
359401
- `maxSyncAttempts` - Maximum retry attempts
360402

361-
### 21. sendBeacon for Termination
403+
### 23. sendBeacon for Termination
404+
405+
**In synchronous mode (default):** Termination commits automatically use `navigator.sendBeacon()` for reliable delivery during page unload. This is hardcoded behavior and cannot be disabled.
362406

363-
Termination commits now use `navigator.sendBeacon()` by default for reliable delivery during page unload.
407+
**In asynchronous mode:** The `asyncModeBeaconBehavior` setting controls when sendBeacon is used:
408+
- `"never"` (default) - Always use fetch API
409+
- `"on-terminate"` - Use sendBeacon for termination commits only
410+
- `"always"` - Use sendBeacon for all commits
364411

365-
### 22. New Settings
412+
### 24. New Settings
366413

367414
| Setting | Type | Default | Description |
368415
|---------|------|---------|-------------|
369416
| `useAsynchronousCommits` | `boolean` | `false` | Use async HTTP (NOT SCORM-compliant) |
370417
| `throttleCommits` | `boolean` | `false` | Throttle rapid commits (requires async) |
371-
| `useBeaconInsteadOfFetch` | `string` | `"never"` | When to use sendBeacon |
418+
| `asyncModeBeaconBehavior` | `string` | `"never"` | When to use sendBeacon in async mode |
372419
| `enableOfflineSupport` | `boolean` | `false` | Enable offline storage |
373420
| `courseId` | `string` | `""` | Course identifier for offline storage |
374421
| `scoId` | `string` | `""` | SCO identifier for multi-SCO courses |
375422
| `httpService` | `IHttpService` | `null` | Custom HTTP service |
376423
| `sequencing` | `object` | `undefined` | SCORM 2004 sequencing config |
424+
| `autoCompleteLessonStatus` | `boolean` | `false` | SCORM 1.2: Auto-complete status on terminate |
425+
| `globalStudentPreferences` | `boolean` | `false` | SCORM 1.2: Share learner prefs across SCOs |
377426

378427
---
379428

@@ -393,6 +442,7 @@ Termination commits now use `navigator.sendBeacon()` by default for reliable del
393442
### High Priority
394443

395444
- [ ] Test commit behavior - commits now block by default
445+
- [ ] Set `mastery_override: false` if you need v2.6.5 SCORM 1.2 behavior
396446
- [ ] Update enum references to UPPERCASE (`COMPLETED`, `PASSED`, etc.)
397447
- [ ] Review custom error handling for new error format
398448
- [ ] Test `cmi.total_time` handling - now defaults to "PT0S" for SCORM 2004
@@ -410,7 +460,7 @@ Termination commits now use `navigator.sendBeacon()` by default for reliable del
410460
- [ ] Enable offline support for disconnected scenarios
411461
- [ ] Evaluate CrossFrame for sandboxed iframes
412462
- [ ] Explore SCORM 2004 sequencing for multi-SCO courses
413-
- [ ] Consider `useBeaconInsteadOfFetch: "on-terminate"` for reliability
463+
- [ ] Use tree-shakeable imports (`import Scorm12API from 'scorm-again/scorm12'`)
414464

415465
---
416466

@@ -425,11 +475,13 @@ Termination commits now use `navigator.sendBeacon()` by default for reliable del
425475
|---------------------|---------|---------|
426476
| `useAsynchronousCommits` | `false` | Enable async HTTP (legacy mode) |
427477
| `throttleCommits` | `false` | Batch rapid commits with 500ms delay |
428-
| `useBeaconInsteadOfFetch` | `"never"` | Use sendBeacon for commits |
478+
| `asyncModeBeaconBehavior` | `"never"` | Use sendBeacon in async mode |
429479
| `enableOfflineSupport` | `false` | Enable offline storage |
430480
| `httpService` | `null` | Inject custom HTTP service |
431481
| `sequencing` | `undefined` | SCORM 2004 sequencing config |
432482
| `xhrResponseHandler` | (built-in) | Handler for sync XHR responses |
483+
| `autoCompleteLessonStatus` | `false` | Auto-complete SCORM 1.2 lesson_status |
484+
| `globalStudentPreferences` | `false` | Share learner prefs across SCOs |
433485

434486
---
435487

src/BaseAPI.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,8 @@ export default abstract class BaseAPI implements IBaseAPI {
755755
this.currentState = global_constants.STATE_TERMINATED;
756756
// Only clear error if there was no error
757757
if (checkTerminated) this.lastErrorCode = "0";
758-
returnValue = result?.result ?? global_constants.SCORM_TRUE;
758+
const resultValue = result?.result ?? global_constants.SCORM_TRUE;
759+
returnValue = typeof resultValue === "boolean" ? String(resultValue) : resultValue;
759760
}
760761

761762
this.processListeners(callbackName);
@@ -924,7 +925,8 @@ export default abstract class BaseAPI implements IBaseAPI {
924925
}
925926
this.throwSCORMError("api", result.errorCode);
926927
}
927-
returnValue = result?.result ?? global_constants.SCORM_FALSE;
928+
const resultValue = result?.result ?? global_constants.SCORM_FALSE;
929+
returnValue = typeof resultValue === "boolean" ? String(resultValue) : resultValue;
928930

929931
this.apiLog(callbackName, " Result: " + returnValue, LogLevelEnum.DEBUG, "HttpRequest");
930932

src/constants/default_settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const DefaultSettings: InternalSettings = {
2424
xhrHeaders: {},
2525
xhrWithCredentials: false,
2626
fetchMode: "cors",
27-
useBeaconInsteadOfFetch: "never",
27+
asyncModeBeaconBehavior: "never",
2828
responseHandler: async function (response: Response): Promise<ResultObject> {
2929
if (typeof response !== "undefined") {
3030
let httpResult = null;

src/services/AsynchronousHttpService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class AsynchronousHttpService implements IHttpService {
8888
| Array<any>;
8989

9090
let response: Response;
91-
if (immediate && this.settings.useBeaconInsteadOfFetch !== "never") {
91+
if (immediate && this.settings.asyncModeBeaconBehavior !== "never") {
9292
response = await this.performBeacon(url, processedParams);
9393
} else {
9494
response = await this.performFetch(url, processedParams);
@@ -137,7 +137,7 @@ export class AsynchronousHttpService implements IHttpService {
137137
*/
138138
private async performFetch(url: string, params: StringKeyMap | Array<any>): Promise<Response> {
139139
// Use Beacon API if specified in settings
140-
if (this.settings.useBeaconInsteadOfFetch === "always") {
140+
if (this.settings.asyncModeBeaconBehavior === "always") {
141141
return this.performBeacon(url, params);
142142
}
143143

src/types/api_types.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,17 @@ export type Settings = {
4141
scoItemIdValidator?: false | ((scoItemId: string) => boolean) | undefined;
4242
globalObjectiveIds?: string[] | undefined;
4343
sequencing?: SequencingSettings | undefined;
44-
useBeaconInsteadOfFetch?: "always" | "on-terminate" | "never" | undefined;
44+
/**
45+
* Controls when sendBeacon is used instead of fetch for async HTTP commits.
46+
* Only applies when useAsynchronousCommits is true.
47+
* - "always": Use sendBeacon for all commits
48+
* - "on-terminate": Use sendBeacon only for termination commits
49+
* - "never": Always use fetch API
50+
*
51+
* Note: In synchronous mode (default), termination always uses sendBeacon
52+
* and regular commits use synchronous XMLHttpRequest - this setting is ignored.
53+
*/
54+
asyncModeBeaconBehavior?: "always" | "on-terminate" | "never" | undefined;
4555

4656
// Offline support settings
4757
enableOfflineSupport?: boolean | undefined;
@@ -97,7 +107,7 @@ export type InternalSettings = {
97107
scoItemIdValidator?: false | ((scoItemId: string) => boolean) | undefined;
98108
globalObjectiveIds?: string[] | undefined;
99109
sequencing?: SequencingSettings | undefined;
100-
useBeaconInsteadOfFetch: "always" | "on-terminate" | "never";
110+
asyncModeBeaconBehavior: "always" | "on-terminate" | "never";
101111

102112
// Offline support settings
103113
enableOfflineSupport?: boolean | undefined;
@@ -140,7 +150,7 @@ export type RefArray = ReadonlyArray<RefValue>;
140150
* Represents the result of an API operation.
141151
*/
142152
export type ResultObject = {
143-
result: string;
153+
result: string | boolean;
144154
errorCode: number;
145155
navRequest?: string | StringKeyMap;
146156
errorMessage?: string;

0 commit comments

Comments
 (0)