scorm-again 3.0.4 → 3.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 (42) hide show
  1. package/README.md +96 -5
  2. package/dist/cross-frame-api.js.map +1 -1
  3. package/dist/esm/cross-frame-api.js.map +1 -1
  4. package/dist/esm/scorm-again.js +503 -138
  5. package/dist/esm/scorm-again.js.map +1 -1
  6. package/dist/esm/scorm-again.min.js +1 -1
  7. package/dist/esm/scorm-again.min.js.map +1 -1
  8. package/dist/esm/scorm12.js +339 -73
  9. package/dist/esm/scorm12.js.map +1 -1
  10. package/dist/esm/scorm12.min.js +1 -1
  11. package/dist/esm/scorm12.min.js.map +1 -1
  12. package/dist/esm/scorm2004.js +486 -131
  13. package/dist/esm/scorm2004.js.map +1 -1
  14. package/dist/esm/scorm2004.min.js +1 -1
  15. package/dist/esm/scorm2004.min.js.map +1 -1
  16. package/dist/scorm-again.js +443 -119
  17. package/dist/scorm-again.js.map +1 -1
  18. package/dist/scorm-again.min.js +1 -1
  19. package/dist/scorm-again.min.js.map +1 -1
  20. package/dist/scorm12.js +316 -57
  21. package/dist/scorm12.js.map +1 -1
  22. package/dist/scorm12.min.js +1 -1
  23. package/dist/scorm12.min.js.map +1 -1
  24. package/dist/scorm2004.js +431 -112
  25. package/dist/scorm2004.js.map +1 -1
  26. package/dist/scorm2004.min.js +1 -1
  27. package/dist/scorm2004.min.js.map +1 -1
  28. package/dist/types/BaseAPI.d.ts +15 -5
  29. package/dist/types/Scorm12API.d.ts +2 -2
  30. package/dist/types/Scorm2004API.d.ts +3 -2
  31. package/dist/types/cmi/common/score.d.ts +2 -0
  32. package/dist/types/cmi/scorm2004/interaction_delimiters.d.ts +4 -0
  33. package/dist/types/interfaces/services.d.ts +8 -4
  34. package/dist/types/services/AsynchronousHttpService.d.ts +3 -2
  35. package/dist/types/services/EventService.d.ts +2 -2
  36. package/dist/types/services/OfflineStorageService.d.ts +6 -1
  37. package/dist/types/services/SynchronousHttpService.d.ts +2 -2
  38. package/dist/types/services/ValidationService.d.ts +1 -1
  39. package/dist/types/types/api_types.d.ts +22 -2
  40. package/dist/types/utilities/index.d.ts +1 -0
  41. package/dist/types/utilities/url.d.ts +2 -0
  42. package/package.json +46 -25
package/README.md CHANGED
@@ -1,3 +1,22 @@
1
+ # scorm-again
2
+
3
+ A modern, fully-tested JavaScript runtime for SCORM 1.2 and SCORM 2004.
4
+
5
+ ---
6
+ <details>
7
+ <summary><b>Audits & consulting →</b></summary>
8
+
9
+ I do one-week, fixed-price code audits for B2B SaaS companies on AWS:
10
+
11
+ - Multi-Tenant SaaS on AWS: architecture, cost, and SOC 2 alignment
12
+ - AI-on-AWS for Regulated SaaS: Bedrock, RAG, and guardrails under SOC 2, HIPAA, or 21 CFR Part 11
13
+
14
+ Fixed price, written report, walkthrough call. Details at [audits.putney.io](https://audits.putney.io).
15
+
16
+ </details>
17
+
18
+ ---
19
+
1
20
  [![Github Actions](https://img.shields.io/github/actions/workflow/status/jcputney/scorm-again/main.yml?style=for-the-badge "Build Status")](https://github.com/jcputney/scorm-again/actions)
2
21
  [![Codecov](https://img.shields.io/codecov/c/github/jcputney/scorm-again?style=for-the-badge "Code Coverage")](https://codecov.io/gh/jcputney/scorm-again)
3
22
  ![jsDelivr hits (npm)](https://img.shields.io/jsdelivr/npm/hy/scorm-again?style=for-the-badge&label=jsDelivr%20Downloads)
@@ -7,10 +26,6 @@
7
26
  ![GitHub License](https://img.shields.io/github/license/jcputney/scorm-again?style=for-the-badge)
8
27
  [![donate](https://img.shields.io/badge/paypal-donate-success?style=for-the-badge)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NF5MPZJAV26LE)
9
28
 
10
- # scorm-again
11
-
12
- A modern, fully-tested JavaScript runtime for SCORM 1.2 and SCORM 2004.
13
-
14
29
  > **Upgrading from v2.x?** See the [Migration Guide](MIGRATION.md) for breaking changes and migration steps.
15
30
 
16
31
  ## Overview
@@ -363,6 +378,9 @@ The APIs include several settings to customize the functionality of each API:
363
378
  | `commitRequestDataType` | 'application/json;charset=UTF-8' | string | This setting is provided in case your LMS expects a different content type or character set. |
364
379
  | `renderCommonCommitFields` | false | true/false | Determines whether the API should render the common fields in the commit object. Common fields are `successStatus`, `completionStatus`, `totalTimeSeconds`, `score`, and `runtimeData`. The `runtimeData` field contains the render CMI object. This allows for easier processing on the LMS. |
365
380
  | `autoCompleteLessonStatus` | false | true/false | Controls termination behaviour for SCOs that never set `cmi.core.lesson_status`. When false (default), the API leaves the status as `incomplete`; when true, it restores the legacy behaviour of auto-upgrading to `completed`. |
381
+ | `terminateCommitParam` | undefined | string | When set, the terminate-time commit's URL gets `?<name>=true` appended (or `&<name>=true` if `lmsCommitUrl` already has a query string), letting the server distinguish the final commit from heartbeat commits. Applies to normal terminate commits, sendBeacon terminate commits, and offline replays of a terminate commit. |
382
+ | `terminateCommitPayloadField` | undefined | string | When set, the terminate-time commit's payload gets `<name>: true` added (object formats) or `<name>=true` appended (`params` format). Independent of `terminateCommitParam`; either or both may be used. |
383
+ | `includeCommitSequence` | false | true/false | When enabled, every commit payload includes a `commitSequence` field with a monotonically increasing number assigned when the commit is captured. Offline-replayed commits keep their original sequence, so servers can reject stale, out-of-order commits. |
366
384
  | `autoProgress` | false | true/false | In case Sequencing is being used, you can tell the API to automatically throw the `SequenceNext` event. |
367
385
  | `logLevel` | 4 | number \| string \| LogLevelEnum<br><br>`1` => DEBUG<br>`2` => INFO<br>`3` => WARN<br>`4` => ERROR<br>`5` => NONE | By default, the APIs only log error messages. |
368
386
  | `mastery_override` | false | true/false | (SCORM 1.2) Used to override a module's `cmi.core.lesson_status` so that a pass/fail is determined based on a mastery score and the user's raw score, rather than using whatever status is provided by the module. An example of this would be if a module is published using a `Complete/Incomplete` final status, but the LMS always wants to receive a `Passed/Failed` for quizzes, then we can use this setting to override the given final status. |
@@ -375,7 +393,7 @@ The APIs include several settings to customize the functionality of each API:
375
393
  | `httpService` | null | IHttpService | Advanced: Inject custom HTTP service implementation. Overrides `useAsynchronousCommits`. |
376
394
  | `xhrResponseHandler` | function | function(xhr) => ResultObject | Custom response handler for synchronous XMLHttpRequest responses. Called when `useAsynchronousCommits=false`. |
377
395
  | `responseHandler` | function | async function(response) => Promise<ResultObject> | Custom response handler for async fetch Response objects. Called when `useAsynchronousCommits=true`. The APIs expect the result from the LMS to be in the following format (errorCode is optional): `{ "result": true, "errorCode": 0 }` |
378
- | `requestHandler` | function | | A function to transform the commit object before sending it to `lmsCommitUrl`. By default it's the identity function (no transformation). |
396
+ | `requestHandler` | function | | A function to transform the commit object before sending it to `lmsCommitUrl`. Receives an optional second `CommitMetadata` argument (`{ isTerminateCommit, trigger, sequence }`); existing one-argument handlers keep working unchanged. By default it's the identity function (no transformation). |
379
397
  | `onLogMessage` | function | | A function to be called whenever a message is logged. Defaults to console.{error,warn,info,debug,log} |
380
398
  | `scoItemIds` | [] | string[] | A list of valid SCO IDs to be used for choice/jump sequence validation. If a `sequencing` configuration is provided with an activity tree, this list will be automatically populated with all activity IDs from the tree. |
381
399
  | `scoItemIdValidator` | false | false / function | A function to be called during choice/jump sequence checks to determine if a SCO ID is valid. Could be used to call an API to check validity. |
@@ -418,6 +436,64 @@ var settings = {
418
436
  };
419
437
  ```
420
438
 
439
+ The handler also receives an optional second argument describing the commit, so a single
440
+ handler can react to terminate-time commits or offline replays:
441
+
442
+ ```javascript
443
+ var settings = {
444
+ requestHandler: function (commitObject, metadata) {
445
+ // metadata = { isTerminateCommit, trigger, sequence }
446
+ // trigger is "manual" | "autocommit" | "terminate" | "offline-replay"
447
+ if (metadata && metadata.isTerminateCommit) {
448
+ commitObject.final = true;
449
+ }
450
+ return commitObject;
451
+ }
452
+ };
453
+ ```
454
+
455
+ ## Commit Lifecycle Observability
456
+
457
+ Every commit sent through the API is tracked while it is in flight. This is aimed at LMS
458
+ player pages that need a reliable teardown sequence — for example, draining pending
459
+ autocommits before calling Terminate, then waiting for the final commit to resolve before
460
+ navigating away or logging the learner out.
461
+
462
+ ```javascript
463
+ // Number of commits currently in flight. Always 0 with the default synchronous
464
+ // HTTP service, which blocks until the LMS responds.
465
+ api.pendingCommitCount;
466
+
467
+ // Resolves once all in-flight commits have settled. If timeoutMs elapses first,
468
+ // the promise resolves anyway (best-effort drain) — check pendingCommitCount
469
+ // afterward to detect that case.
470
+ await api.whenCommitsSettled({ timeoutMs: 4000 });
471
+ ```
472
+
473
+ `CommitSuccess` and `CommitError` listeners receive a context object describing the commit.
474
+ These events are fired by the asynchronous HTTP service (`useAsynchronousCommits: true`);
475
+ the default synchronous service returns results directly to the SCO instead.
476
+
477
+ ```javascript
478
+ api.on("CommitSuccess", function (context) {
479
+ // context = { url, trigger, isTerminateCommit, sequence }
480
+ });
481
+
482
+ api.on("CommitError", function (errorCode, context) {
483
+ // errorCode stays the first argument for backward compatibility
484
+ });
485
+ ```
486
+
487
+ A typical teardown for a player using asynchronous commits:
488
+
489
+ ```javascript
490
+ // Force the terminate commit through fetch so its result is observable
491
+ api.settings.asyncModeBeaconBehavior = "never";
492
+ await api.whenCommitsSettled({ timeoutMs: 1000 }); // drain heartbeat commits
493
+ api.Terminate(""); // fires the final commit
494
+ await api.whenCommitsSettled({ timeoutMs: 4000 }); // wait for it to resolve
495
+ ```
496
+
421
497
  ### onLogMessage
422
498
 
423
499
  The onLogMessage function is used to log messages. By default, it logs messages to the console.
@@ -800,6 +876,21 @@ When implementing scorm-again, consider these security best practices:
800
876
  - Use HTTPS for all LMS communications to prevent data interception
801
877
  - Review the `xhrHeaders` and `responseHandler` settings for potential security implications
802
878
 
879
+ ### Network Access & Data Flow
880
+
881
+ Supply-chain scanners (e.g. Socket) flag scorm-again as having "network access" because it
882
+ references `fetch` and `XMLHttpRequest`. This is expected: committing learner tracking data to an
883
+ LMS is the library's core function. To be clear about what that access does and does not include:
884
+
885
+ - **No hardcoded endpoints.** scorm-again never contacts any server of its own. Data is only ever
886
+ sent to the URL you provide via the `lmsCommitUrl` setting.
887
+ - **Silent by default.** `lmsCommitUrl` defaults to `false`. With no commit URL configured, the
888
+ library makes no network requests at all.
889
+ - **You control the payload and headers.** Outgoing requests can be inspected and rewritten via
890
+ `requestHandler`, and `xhrHeaders` / `xhrWithCredentials` govern what is attached to them.
891
+ - **No telemetry or analytics.** The library does not phone home, collect usage data, or contact
892
+ any third party.
893
+
803
894
  ## Performance Considerations
804
895
 
805
896
  For optimal performance:
@@ -1 +1 @@
1
- {"version":3,"file":"cross-frame-api.js","sources":["../src/constants/error_codes.ts","../src/CrossFrameAPI.ts"],"sourcesContent":["export type ErrorCode = {\n [key: string]: number;\n};\n\nexport const global_errors: ErrorCode = {\n GENERAL: 101,\n INITIALIZATION_FAILED: 101,\n INITIALIZED: 101,\n TERMINATED: 101,\n TERMINATION_FAILURE: 101,\n TERMINATION_BEFORE_INIT: 101,\n MULTIPLE_TERMINATION: 101,\n RETRIEVE_BEFORE_INIT: 101,\n RETRIEVE_AFTER_TERM: 101,\n STORE_BEFORE_INIT: 101,\n STORE_AFTER_TERM: 101,\n COMMIT_BEFORE_INIT: 101,\n COMMIT_AFTER_TERM: 101,\n ARGUMENT_ERROR: 101,\n CHILDREN_ERROR: 101,\n COUNT_ERROR: 101,\n GENERAL_GET_FAILURE: 101,\n GENERAL_SET_FAILURE: 101,\n GENERAL_COMMIT_FAILURE: 101,\n UNDEFINED_DATA_MODEL: 101,\n UNIMPLEMENTED_ELEMENT: 101,\n VALUE_NOT_INITIALIZED: 101,\n INVALID_SET_VALUE: 101,\n READ_ONLY_ELEMENT: 101,\n WRITE_ONLY_ELEMENT: 101,\n TYPE_MISMATCH: 101,\n VALUE_OUT_OF_RANGE: 101,\n DEPENDENCY_NOT_ESTABLISHED: 101,\n};\n\nexport const scorm12_errors: ErrorCode = {\n ...global_errors,\n RETRIEVE_BEFORE_INIT: 301,\n STORE_BEFORE_INIT: 301,\n COMMIT_BEFORE_INIT: 301,\n ARGUMENT_ERROR: 201,\n CHILDREN_ERROR: 202,\n COUNT_ERROR: 203,\n UNDEFINED_DATA_MODEL: 401,\n UNIMPLEMENTED_ELEMENT: 401,\n VALUE_NOT_INITIALIZED: 301,\n INVALID_SET_VALUE: 402,\n READ_ONLY_ELEMENT: 403,\n WRITE_ONLY_ELEMENT: 404,\n TYPE_MISMATCH: 405,\n VALUE_OUT_OF_RANGE: 407,\n DEPENDENCY_NOT_ESTABLISHED: 408,\n};\n\nexport const scorm2004_errors: ErrorCode = {\n ...global_errors,\n INITIALIZATION_FAILED: 102,\n INITIALIZED: 103,\n TERMINATED: 104,\n TERMINATION_FAILURE: 111,\n TERMINATION_BEFORE_INIT: 112,\n MULTIPLE_TERMINATION: 113,\n MULTIPLE_TERMINATIONS: 113,\n RETRIEVE_BEFORE_INIT: 122,\n RETRIEVE_AFTER_TERM: 123,\n STORE_BEFORE_INIT: 132,\n STORE_AFTER_TERM: 133,\n COMMIT_BEFORE_INIT: 142,\n COMMIT_AFTER_TERM: 143,\n ARGUMENT_ERROR: 201,\n GENERAL_GET_FAILURE: 301,\n GENERAL_SET_FAILURE: 351,\n GENERAL_COMMIT_FAILURE: 391,\n UNDEFINED_DATA_MODEL: 401,\n UNIMPLEMENTED_ELEMENT: 402,\n VALUE_NOT_INITIALIZED: 403,\n READ_ONLY_ELEMENT: 404,\n WRITE_ONLY_ELEMENT: 405,\n TYPE_MISMATCH: 406,\n VALUE_OUT_OF_RANGE: 407,\n DEPENDENCY_NOT_ESTABLISHED: 408,\n};\n","// src/CrossFrameAPI.ts\nimport {\n CrossFrameAPIOptions,\n CrossFrameEvent,\n CrossFrameEventCallback,\n MessageData,\n MessageResponse,\n} from \"./types/CrossFrame\";\nimport { global_errors } from \"./constants/error_codes\";\n\n/**\n * Pending request tracking with timestamp for cache merge protection\n */\ninterface PendingRequest {\n resolve: (v: unknown) => void;\n reject: (e: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n /**\n * Timestamp when the request was sent, used for cache merge protection.\n * Ensures local modifications made after the request was sent aren't overwritten\n * by stale server responses that arrive later.\n */\n requestTime: number;\n method: string; // CF-API-02: Track method name for better error reporting\n}\n\n/**\n * Client-side SCORM facade running in your content frame.\n * Returns cached values synchronously, then fires off an async\n * postMessage to the LMS frame to refresh cache and error state.\n */\nexport default class CrossFrameAPI {\n private _cache = new Map<string, string>();\n private _cacheTimestamps = new Map<string, number>();\n private _lastError = \"0\";\n private _pending = new Map<string, PendingRequest>();\n private _counter = 0;\n private readonly _origin: string;\n private readonly _targetWindow: Window;\n private readonly _timeout: number;\n private readonly _heartbeatInterval: number;\n private readonly _heartbeatTimeout: number;\n\n private _destroyed = false;\n private _connected = true;\n private _lastHeartbeatResponse = Date.now();\n private _heartbeatTimer: ReturnType<typeof setInterval> | undefined;\n private _eventListeners = new Map<string, Set<CrossFrameEventCallback>>();\n private readonly _boundOnMessage: (ev: MessageEvent) => void;\n\n /**\n * Type guard to validate MessageResponse structure\n */\n private static _isValidMessageResponse(data: unknown): data is MessageResponse {\n if (typeof data !== \"object\" || data === null) return false;\n\n const resp = data as Partial<MessageResponse>;\n\n // messageId is required\n if (typeof resp.messageId !== \"string\" || resp.messageId.length === 0) return false;\n\n // error must have correct shape if present\n if (resp.error !== undefined) {\n if (typeof resp.error !== \"object\" || resp.error === null) return false;\n const err = resp.error as Record<string, unknown>;\n if (typeof err.message !== \"string\") return false;\n if (err.code !== undefined && typeof err.code !== \"string\") return false;\n }\n\n // isHeartbeat must be boolean if present\n if (resp.isHeartbeat !== undefined && typeof resp.isHeartbeat !== \"boolean\") return false;\n\n return true;\n }\n\n /**\n * Validates that args is an array and sanitizes it for safe use\n */\n private static _validateArgs(args: unknown[]): args is unknown[] {\n if (!Array.isArray(args)) return false;\n // Additional validation could check for specific types based on method\n return true;\n }\n\n private _handler: ProxyHandler<CrossFrameAPI> = {\n get: (target, prop, receiver) => {\n // If it's an existing property/method, return it\n if (typeof prop !== \"string\" || prop in target) {\n const v = Reflect.get(target, prop, receiver);\n return typeof v === \"function\" ? v.bind(target) : v;\n }\n\n // Otherwise treat prop as a SCORM call\n const methodName = prop;\n const isGet = methodName.endsWith(\"GetValue\");\n const isSet = methodName.startsWith(\"LMSSet\") || methodName.endsWith(\"SetValue\");\n const isInit = methodName === \"Initialize\" || methodName === \"LMSInitialize\";\n const isFinish = methodName === \"Terminate\" || methodName === \"LMSFinish\";\n const isCommit = methodName === \"Commit\" || methodName === \"LMSCommit\";\n const isErrorString = methodName === \"GetErrorString\" || methodName === \"LMSGetErrorString\";\n const isDiagnostic = methodName === \"GetDiagnostic\" || methodName === \"LMSGetDiagnostic\";\n\n return (...args: unknown[]): string => {\n // CF-API-03: Validate args is an array (TypeScript guarantees this, but runtime safety)\n if (!CrossFrameAPI._validateArgs(args)) {\n console.error(`CrossFrameAPI: Invalid arguments for ${methodName}`);\n return \"\";\n }\n\n // Synchronous cache update for setter calls\n if (isSet && args.length >= 2) {\n const key = args[0] as string;\n target._cache.set(key, String(args[1]));\n target._cacheTimestamps.set(key, Date.now());\n target._lastError = \"0\";\n }\n\n // Capture request time for cache merge protection\n const requestTime = Date.now();\n\n // Fire off async postMessage to refresh cache and error\n target\n ._post(methodName, args)\n .then((res) => {\n if (isGet && args.length >= 1) {\n const key = args[0] as string;\n // Only update if not locally modified after request was sent\n const localModTime = target._cacheTimestamps.get(key) ?? 0;\n if (localModTime < requestTime) {\n target._cache.set(key, String(res));\n target._cacheTimestamps.delete(key);\n }\n target._lastError = \"0\";\n }\n if (isErrorString && args.length >= 1) {\n const code = String(args[0]);\n target._cache.set(`error_${code}`, String(res));\n }\n if (isDiagnostic && args.length >= 1) {\n const code = String(args[0]);\n target._cache.set(`diag_${code}`, String(res));\n }\n if (methodName === \"GetLastError\" || methodName === \"LMSGetLastError\") {\n target._lastError = String(res);\n }\n })\n .catch((err) => target._capture(methodName, err));\n\n // Return synchronously\n if (isGet && args.length >= 1) {\n return target._cache.get(args[0] as string) ?? \"\";\n }\n if (isErrorString && args.length >= 1) {\n const code = String(args[0]);\n return target._cache.get(`error_${code}`) ?? \"\";\n }\n if (isDiagnostic && args.length >= 1) {\n const code = String(args[0]);\n return target._cache.get(`diag_${code}`) ?? \"\";\n }\n if (isInit || isFinish || isCommit || isSet) {\n // Immediately return \"true\"\n const result = \"true\";\n // Then warm cache with timestamp protection:\n target\n ._post(\"getFlattenedCMI\", [])\n .then((all: unknown) => {\n if (all && typeof all === \"object\") {\n const entries = Object.entries(all as Record<string, string>);\n entries.forEach(([key, val]) => {\n // Only update if not locally modified after request was sent\n const localModTime = target._cacheTimestamps.get(key) ?? 0;\n if (localModTime < requestTime) {\n target._cache.set(key, val);\n target._cacheTimestamps.delete(key);\n }\n });\n }\n // reset error\n target._lastError = \"0\";\n })\n .catch((err) => target._capture(\"getFlattenedCMI\", err));\n return result;\n }\n if (methodName === \"GetLastError\" || methodName === \"LMSGetLastError\") {\n return target._lastError;\n }\n return \"\";\n };\n },\n };\n\n /**\n * Creates a new CrossFrameAPI instance.\n * @param targetOrigin - Origin to send messages to. Default \"*\" sends to any origin.\n * @param targetWindow - Window to send messages to. Default is window.parent.\n * @param options - Configuration options\n */\n constructor(\n targetOrigin: string = \"*\",\n targetWindow: Window = window.parent,\n options: CrossFrameAPIOptions = {},\n ) {\n this._origin = targetOrigin;\n this._targetWindow = targetWindow;\n this._timeout = options.timeout ?? 5000;\n this._heartbeatInterval = options.heartbeatInterval ?? 30000;\n this._heartbeatTimeout = options.heartbeatTimeout ?? 60000;\n\n // Warn about wildcard origin security implications\n if (targetOrigin === \"*\") {\n console.warn(\n \"CrossFrameAPI: Using wildcard origin ('*') allows any origin to receive messages. \" +\n \"This is insecure for production use. \" +\n \"Specify an explicit origin (e.g., 'https://lms.example.com') to restrict message recipients.\",\n );\n }\n\n this._boundOnMessage = this._onMessage.bind(this);\n window.addEventListener(\"message\", this._boundOnMessage);\n this._startHeartbeat();\n\n return new Proxy(this, this._handler);\n }\n\n /**\n * Destroys this instance, removing event listeners and preventing further message processing.\n * Once destroyed, the instance cannot be reused.\n */\n destroy(): void {\n if (this._destroyed) return;\n this._destroyed = true;\n window.removeEventListener(\"message\", this._boundOnMessage);\n if (this._heartbeatTimer) {\n clearInterval(this._heartbeatTimer);\n this._heartbeatTimer = undefined;\n }\n // Reject all pending requests\n for (const pending of Array.from(this._pending.values())) {\n clearTimeout(pending.timer);\n pending.reject(new Error(\"CrossFrameAPI destroyed\"));\n }\n this._pending.clear();\n this._cache.clear();\n this._cacheTimestamps.clear();\n this._eventListeners.clear();\n }\n\n /**\n * Subscribes to a CrossFrame event.\n * @param event - Event type to listen for\n * @param callback - Function to call when event occurs\n */\n on(event: string, callback: CrossFrameEventCallback): void {\n if (!this._eventListeners.has(event)) {\n this._eventListeners.set(event, new Set());\n }\n this._eventListeners.get(event)?.add(callback);\n }\n\n /**\n * Unsubscribes from a CrossFrame event.\n * @param event - Event type to stop listening for\n * @param callback - Function to remove\n */\n off(event: string, callback: CrossFrameEventCallback): void {\n this._eventListeners.get(event)?.delete(callback);\n }\n\n /**\n * Returns whether the connection to the LMS frame is currently active.\n */\n get connected(): boolean {\n return this._connected;\n }\n\n /**\n * Emits an event to all registered listeners.\n */\n private _emit(event: CrossFrameEvent): void {\n this._eventListeners.get(event.type)?.forEach((cb) => cb(event));\n }\n\n /**\n * Starts the heartbeat mechanism for connection detection.\n */\n private _startHeartbeat(): void {\n // CF-API-01: Clear any existing heartbeat timer before starting a new one\n if (this._heartbeatTimer) {\n clearInterval(this._heartbeatTimer);\n }\n\n this._heartbeatTimer = setInterval(() => {\n if (this._destroyed) return;\n\n // Check if we've missed heartbeats\n const timeSinceLastResponse = Date.now() - this._lastHeartbeatResponse;\n if (timeSinceLastResponse > this._heartbeatTimeout && this._connected) {\n this._connected = false;\n this._emit({ type: \"connectionLost\" });\n }\n\n // Send heartbeat ping\n this._sendHeartbeat();\n }, this._heartbeatInterval);\n }\n\n /**\n * Sends a heartbeat ping to the LMS frame.\n */\n private _sendHeartbeat(): void {\n const messageId = `hb-${Date.now()}-${this._counter++}`;\n const msg: MessageData = {\n messageId,\n method: \"__heartbeat__\",\n params: [],\n isHeartbeat: true,\n };\n this._targetWindow.postMessage(msg, this._origin);\n }\n\n /**\n * Send a message to the LMS frame and return a promise for its response.\n */\n private _post(method: string, params: unknown[]): Promise<unknown> {\n if (this._destroyed) {\n return Promise.reject(new Error(\"CrossFrameAPI destroyed\"));\n }\n\n const messageId = `cfapi-${Date.now()}-${this._counter++}`;\n const requestTime = Date.now();\n\n // Deep-clean params of non-cloneables (e.g. functions)\n const safeParams = params.map((p) => {\n if (typeof p === \"function\") {\n // DEFENSIVE CODING: Warn about non-serializable function parameters.\n // This console.warn is intentionally kept in production builds because:\n // 1. Functions in SCORM API params indicate a programming error by content authors\n // 2. The warning helps diagnose integration issues during content development\n // 3. It alerts developers to potential data loss (function becomes undefined)\n // 4. The frequency should be very low in production (only on misconfigured content)\n //\n // Alternative: Could be gated by a debug flag if production console noise is a concern\n console.warn(\"Dropping function param when posting SCORM call:\", method);\n return undefined;\n }\n return p;\n });\n\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n if (this._pending.has(messageId)) {\n this._pending.delete(messageId);\n reject(new Error(`Timeout calling ${method}`));\n }\n }, this._timeout);\n\n // CF-API-02: Store method name in pending request for better error reporting\n this._pending.set(messageId, { resolve, reject, timer, requestTime, method });\n const msg: MessageData = { messageId, method, params: safeParams };\n this._targetWindow.postMessage(msg, this._origin);\n });\n }\n\n /**\n * Handle incoming postMessage responses from the LMS frame.\n */\n private _onMessage(ev: MessageEvent): void {\n if (this._destroyed) return;\n\n // Validate the message origin and source unless all origins are allowed\n if (this._origin !== \"*\" && ev.origin !== this._origin) {\n return;\n }\n if (ev.source && ev.source !== this._targetWindow) {\n return;\n }\n\n // CF-API-04: Validate that ev.data has the expected MessageResponse structure\n if (!CrossFrameAPI._isValidMessageResponse(ev.data)) return;\n\n const data = ev.data;\n\n // Handle heartbeat response\n if (data.isHeartbeat) {\n this._lastHeartbeatResponse = Date.now();\n if (!this._connected) {\n this._connected = true;\n this._emit({ type: \"connectionRestored\" });\n }\n return;\n }\n\n // Handle regular response\n const pending = this._pending.get(data.messageId);\n if (!pending) return;\n\n clearTimeout(pending.timer);\n this._pending.delete(data.messageId);\n\n if (data.error) {\n // Check if rate limited and emit event\n // CF-API-02: Use the actual method name from the pending request\n if (data.error.message === \"Rate limit exceeded\") {\n this._emit({ type: \"rateLimited\", method: pending.method });\n }\n pending.reject(data.error);\n } else {\n pending.resolve(data.result);\n }\n }\n\n /**\n * Capture and cache SCORM errors.\n */\n private _capture(method: string, err: unknown): void {\n let errorMessage = \"Unknown error\";\n\n if (err instanceof Error) {\n errorMessage = err.message;\n } else if (typeof err === \"object\" && err !== null && \"message\" in err) {\n errorMessage = String((err as { message: unknown }).message);\n }\n\n console.error(`CrossFrameAPI ${method} error:`, err);\n // Match SCORM error codes (3 digits) from error messages\n const match = /(?:error code|code)?\\s*(\\d{3})\\b/i.exec(errorMessage);\n const code = match?.[1] ?? String(global_errors.GENERAL);\n this._lastError = code;\n this._cache.set(`error_${code}`, errorMessage);\n }\n}\n"],"names":["global_errors","GENERAL","CrossFrameAPI","constructor","targetOrigin","targetWindow","arguments","length","undefined","window","parent","options","__publicField","Map","Date","now","get","target","prop","receiver","v","Reflect","bind","methodName","isGet","endsWith","isSet","startsWith","isInit","isFinish","isCommit","isErrorString","isDiagnostic","_len","args","Array","_key","_validateArgs","console","error","key","_cache","set","String","_cacheTimestamps","_lastError","requestTime","_post","then","res","localModTime","delete","code","catch","err","_capture","result","all","entries","Object","forEach","_ref","val","_origin","_targetWindow","_timeout","timeout","_heartbeatInterval","heartbeatInterval","_heartbeatTimeout","heartbeatTimeout","warn","_boundOnMessage","_onMessage","addEventListener","_startHeartbeat","Proxy","_handler","_isValidMessageResponse","data","resp","messageId","message","isHeartbeat","isArray","destroy","_destroyed","removeEventListener","_heartbeatTimer","clearInterval","pending","from","_pending","values","clearTimeout","timer","reject","Error","clear","_eventListeners","on","event","callback","has","Set","add","off","connected","_connected","_emit","type","cb","setInterval","timeSinceLastResponse","_lastHeartbeatResponse","_sendHeartbeat","_counter","msg","method","params","postMessage","Promise","safeParams","map","p","resolve","setTimeout","ev","origin","source","errorMessage","match","exec"],"mappings":"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"}
1
+ {"version":3,"file":"cross-frame-api.js","sources":["../src/constants/error_codes.ts","../src/CrossFrameAPI.ts"],"sourcesContent":["export type ErrorCode = {\n [key: string]: number;\n};\n\nexport const global_errors: ErrorCode = {\n GENERAL: 101,\n INITIALIZATION_FAILED: 101,\n INITIALIZED: 101,\n TERMINATED: 101,\n TERMINATION_FAILURE: 101,\n TERMINATION_BEFORE_INIT: 101,\n MULTIPLE_TERMINATION: 101,\n RETRIEVE_BEFORE_INIT: 101,\n RETRIEVE_AFTER_TERM: 101,\n STORE_BEFORE_INIT: 101,\n STORE_AFTER_TERM: 101,\n COMMIT_BEFORE_INIT: 101,\n COMMIT_AFTER_TERM: 101,\n ARGUMENT_ERROR: 101,\n CHILDREN_ERROR: 101,\n COUNT_ERROR: 101,\n GENERAL_GET_FAILURE: 101,\n GENERAL_SET_FAILURE: 101,\n GENERAL_COMMIT_FAILURE: 101,\n UNDEFINED_DATA_MODEL: 101,\n UNIMPLEMENTED_ELEMENT: 101,\n VALUE_NOT_INITIALIZED: 101,\n INVALID_SET_VALUE: 101,\n READ_ONLY_ELEMENT: 101,\n WRITE_ONLY_ELEMENT: 101,\n TYPE_MISMATCH: 101,\n VALUE_OUT_OF_RANGE: 101,\n DEPENDENCY_NOT_ESTABLISHED: 101,\n};\n\nexport const scorm12_errors: ErrorCode = {\n ...global_errors,\n RETRIEVE_BEFORE_INIT: 301,\n STORE_BEFORE_INIT: 301,\n COMMIT_BEFORE_INIT: 301,\n ARGUMENT_ERROR: 201,\n CHILDREN_ERROR: 202,\n COUNT_ERROR: 203,\n UNDEFINED_DATA_MODEL: 401,\n UNIMPLEMENTED_ELEMENT: 401,\n VALUE_NOT_INITIALIZED: 301,\n INVALID_SET_VALUE: 402,\n READ_ONLY_ELEMENT: 403,\n WRITE_ONLY_ELEMENT: 404,\n TYPE_MISMATCH: 405,\n // SCORM 1.2 has no out-of-range error code; an out-of-range value is reported\n // as 405 (incorrect data type), per the SCORM 1.2 RTE error table and the ADL\n // 1.2 CTS (DataModelValidator.checkScoreDecimal failure -> CMICategory 405).\n VALUE_OUT_OF_RANGE: 405,\n DEPENDENCY_NOT_ESTABLISHED: 408,\n};\n\nexport const scorm2004_errors: ErrorCode = {\n ...global_errors,\n INITIALIZATION_FAILED: 102,\n INITIALIZED: 103,\n TERMINATED: 104,\n TERMINATION_FAILURE: 111,\n TERMINATION_BEFORE_INIT: 112,\n MULTIPLE_TERMINATION: 113,\n MULTIPLE_TERMINATIONS: 113,\n RETRIEVE_BEFORE_INIT: 122,\n RETRIEVE_AFTER_TERM: 123,\n STORE_BEFORE_INIT: 132,\n STORE_AFTER_TERM: 133,\n COMMIT_BEFORE_INIT: 142,\n COMMIT_AFTER_TERM: 143,\n ARGUMENT_ERROR: 201,\n GENERAL_GET_FAILURE: 301,\n GENERAL_SET_FAILURE: 351,\n GENERAL_COMMIT_FAILURE: 391,\n UNDEFINED_DATA_MODEL: 401,\n UNIMPLEMENTED_ELEMENT: 402,\n VALUE_NOT_INITIALIZED: 403,\n READ_ONLY_ELEMENT: 404,\n WRITE_ONLY_ELEMENT: 405,\n TYPE_MISMATCH: 406,\n VALUE_OUT_OF_RANGE: 407,\n DEPENDENCY_NOT_ESTABLISHED: 408,\n};\n","// src/CrossFrameAPI.ts\nimport {\n CrossFrameAPIOptions,\n CrossFrameEvent,\n CrossFrameEventCallback,\n MessageData,\n MessageResponse,\n} from \"./types/CrossFrame\";\nimport { global_errors } from \"./constants/error_codes\";\n\n/**\n * Pending request tracking with timestamp for cache merge protection\n */\ninterface PendingRequest {\n resolve: (v: unknown) => void;\n reject: (e: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n /**\n * Timestamp when the request was sent, used for cache merge protection.\n * Ensures local modifications made after the request was sent aren't overwritten\n * by stale server responses that arrive later.\n */\n requestTime: number;\n method: string; // CF-API-02: Track method name for better error reporting\n}\n\n/**\n * Client-side SCORM facade running in your content frame.\n * Returns cached values synchronously, then fires off an async\n * postMessage to the LMS frame to refresh cache and error state.\n */\nexport default class CrossFrameAPI {\n private _cache = new Map<string, string>();\n private _cacheTimestamps = new Map<string, number>();\n private _lastError = \"0\";\n private _pending = new Map<string, PendingRequest>();\n private _counter = 0;\n private readonly _origin: string;\n private readonly _targetWindow: Window;\n private readonly _timeout: number;\n private readonly _heartbeatInterval: number;\n private readonly _heartbeatTimeout: number;\n\n private _destroyed = false;\n private _connected = true;\n private _lastHeartbeatResponse = Date.now();\n private _heartbeatTimer: ReturnType<typeof setInterval> | undefined;\n private _eventListeners = new Map<string, Set<CrossFrameEventCallback>>();\n private readonly _boundOnMessage: (ev: MessageEvent) => void;\n\n /**\n * Type guard to validate MessageResponse structure\n */\n private static _isValidMessageResponse(data: unknown): data is MessageResponse {\n if (typeof data !== \"object\" || data === null) return false;\n\n const resp = data as Partial<MessageResponse>;\n\n // messageId is required\n if (typeof resp.messageId !== \"string\" || resp.messageId.length === 0) return false;\n\n // error must have correct shape if present\n if (resp.error !== undefined) {\n if (typeof resp.error !== \"object\" || resp.error === null) return false;\n const err = resp.error as Record<string, unknown>;\n if (typeof err.message !== \"string\") return false;\n if (err.code !== undefined && typeof err.code !== \"string\") return false;\n }\n\n // isHeartbeat must be boolean if present\n if (resp.isHeartbeat !== undefined && typeof resp.isHeartbeat !== \"boolean\") return false;\n\n return true;\n }\n\n /**\n * Validates that args is an array and sanitizes it for safe use\n */\n private static _validateArgs(args: unknown[]): args is unknown[] {\n if (!Array.isArray(args)) return false;\n // Additional validation could check for specific types based on method\n return true;\n }\n\n private _handler: ProxyHandler<CrossFrameAPI> = {\n get: (target, prop, receiver) => {\n // If it's an existing property/method, return it\n if (typeof prop !== \"string\" || prop in target) {\n const v = Reflect.get(target, prop, receiver);\n return typeof v === \"function\" ? v.bind(target) : v;\n }\n\n // Otherwise treat prop as a SCORM call\n const methodName = prop;\n const isGet = methodName.endsWith(\"GetValue\");\n const isSet = methodName.startsWith(\"LMSSet\") || methodName.endsWith(\"SetValue\");\n const isInit = methodName === \"Initialize\" || methodName === \"LMSInitialize\";\n const isFinish = methodName === \"Terminate\" || methodName === \"LMSFinish\";\n const isCommit = methodName === \"Commit\" || methodName === \"LMSCommit\";\n const isErrorString = methodName === \"GetErrorString\" || methodName === \"LMSGetErrorString\";\n const isDiagnostic = methodName === \"GetDiagnostic\" || methodName === \"LMSGetDiagnostic\";\n\n return (...args: unknown[]): string => {\n // CF-API-03: Validate args is an array (TypeScript guarantees this, but runtime safety)\n if (!CrossFrameAPI._validateArgs(args)) {\n console.error(`CrossFrameAPI: Invalid arguments for ${methodName}`);\n return \"\";\n }\n\n // Synchronous cache update for setter calls\n if (isSet && args.length >= 2) {\n const key = args[0] as string;\n target._cache.set(key, String(args[1]));\n target._cacheTimestamps.set(key, Date.now());\n target._lastError = \"0\";\n }\n\n // Capture request time for cache merge protection\n const requestTime = Date.now();\n\n // Fire off async postMessage to refresh cache and error\n target\n ._post(methodName, args)\n .then((res) => {\n if (isGet && args.length >= 1) {\n const key = args[0] as string;\n // Only update if not locally modified after request was sent\n const localModTime = target._cacheTimestamps.get(key) ?? 0;\n if (localModTime < requestTime) {\n target._cache.set(key, String(res));\n target._cacheTimestamps.delete(key);\n }\n target._lastError = \"0\";\n }\n if (isErrorString && args.length >= 1) {\n const code = String(args[0]);\n target._cache.set(`error_${code}`, String(res));\n }\n if (isDiagnostic && args.length >= 1) {\n const code = String(args[0]);\n target._cache.set(`diag_${code}`, String(res));\n }\n if (methodName === \"GetLastError\" || methodName === \"LMSGetLastError\") {\n target._lastError = String(res);\n }\n })\n .catch((err) => target._capture(methodName, err));\n\n // Return synchronously\n if (isGet && args.length >= 1) {\n return target._cache.get(args[0] as string) ?? \"\";\n }\n if (isErrorString && args.length >= 1) {\n const code = String(args[0]);\n return target._cache.get(`error_${code}`) ?? \"\";\n }\n if (isDiagnostic && args.length >= 1) {\n const code = String(args[0]);\n return target._cache.get(`diag_${code}`) ?? \"\";\n }\n if (isInit || isFinish || isCommit || isSet) {\n // Immediately return \"true\"\n const result = \"true\";\n // Then warm cache with timestamp protection:\n target\n ._post(\"getFlattenedCMI\", [])\n .then((all: unknown) => {\n if (all && typeof all === \"object\") {\n const entries = Object.entries(all as Record<string, string>);\n entries.forEach(([key, val]) => {\n // Only update if not locally modified after request was sent\n const localModTime = target._cacheTimestamps.get(key) ?? 0;\n if (localModTime < requestTime) {\n target._cache.set(key, val);\n target._cacheTimestamps.delete(key);\n }\n });\n }\n // reset error\n target._lastError = \"0\";\n })\n .catch((err) => target._capture(\"getFlattenedCMI\", err));\n return result;\n }\n if (methodName === \"GetLastError\" || methodName === \"LMSGetLastError\") {\n return target._lastError;\n }\n return \"\";\n };\n },\n };\n\n /**\n * Creates a new CrossFrameAPI instance.\n * @param targetOrigin - Origin to send messages to. Default \"*\" sends to any origin.\n * @param targetWindow - Window to send messages to. Default is window.parent.\n * @param options - Configuration options\n */\n constructor(\n targetOrigin: string = \"*\",\n targetWindow: Window = window.parent,\n options: CrossFrameAPIOptions = {},\n ) {\n this._origin = targetOrigin;\n this._targetWindow = targetWindow;\n this._timeout = options.timeout ?? 5000;\n this._heartbeatInterval = options.heartbeatInterval ?? 30000;\n this._heartbeatTimeout = options.heartbeatTimeout ?? 60000;\n\n // Warn about wildcard origin security implications\n if (targetOrigin === \"*\") {\n console.warn(\n \"CrossFrameAPI: Using wildcard origin ('*') allows any origin to receive messages. \" +\n \"This is insecure for production use. \" +\n \"Specify an explicit origin (e.g., 'https://lms.example.com') to restrict message recipients.\",\n );\n }\n\n this._boundOnMessage = this._onMessage.bind(this);\n window.addEventListener(\"message\", this._boundOnMessage);\n this._startHeartbeat();\n\n return new Proxy(this, this._handler);\n }\n\n /**\n * Destroys this instance, removing event listeners and preventing further message processing.\n * Once destroyed, the instance cannot be reused.\n */\n destroy(): void {\n if (this._destroyed) return;\n this._destroyed = true;\n window.removeEventListener(\"message\", this._boundOnMessage);\n if (this._heartbeatTimer) {\n clearInterval(this._heartbeatTimer);\n this._heartbeatTimer = undefined;\n }\n // Reject all pending requests\n for (const pending of Array.from(this._pending.values())) {\n clearTimeout(pending.timer);\n pending.reject(new Error(\"CrossFrameAPI destroyed\"));\n }\n this._pending.clear();\n this._cache.clear();\n this._cacheTimestamps.clear();\n this._eventListeners.clear();\n }\n\n /**\n * Subscribes to a CrossFrame event.\n * @param event - Event type to listen for\n * @param callback - Function to call when event occurs\n */\n on(event: string, callback: CrossFrameEventCallback): void {\n if (!this._eventListeners.has(event)) {\n this._eventListeners.set(event, new Set());\n }\n this._eventListeners.get(event)?.add(callback);\n }\n\n /**\n * Unsubscribes from a CrossFrame event.\n * @param event - Event type to stop listening for\n * @param callback - Function to remove\n */\n off(event: string, callback: CrossFrameEventCallback): void {\n this._eventListeners.get(event)?.delete(callback);\n }\n\n /**\n * Returns whether the connection to the LMS frame is currently active.\n */\n get connected(): boolean {\n return this._connected;\n }\n\n /**\n * Emits an event to all registered listeners.\n */\n private _emit(event: CrossFrameEvent): void {\n this._eventListeners.get(event.type)?.forEach((cb) => cb(event));\n }\n\n /**\n * Starts the heartbeat mechanism for connection detection.\n */\n private _startHeartbeat(): void {\n // CF-API-01: Clear any existing heartbeat timer before starting a new one\n if (this._heartbeatTimer) {\n clearInterval(this._heartbeatTimer);\n }\n\n this._heartbeatTimer = setInterval(() => {\n if (this._destroyed) return;\n\n // Check if we've missed heartbeats\n const timeSinceLastResponse = Date.now() - this._lastHeartbeatResponse;\n if (timeSinceLastResponse > this._heartbeatTimeout && this._connected) {\n this._connected = false;\n this._emit({ type: \"connectionLost\" });\n }\n\n // Send heartbeat ping\n this._sendHeartbeat();\n }, this._heartbeatInterval);\n }\n\n /**\n * Sends a heartbeat ping to the LMS frame.\n */\n private _sendHeartbeat(): void {\n const messageId = `hb-${Date.now()}-${this._counter++}`;\n const msg: MessageData = {\n messageId,\n method: \"__heartbeat__\",\n params: [],\n isHeartbeat: true,\n };\n this._targetWindow.postMessage(msg, this._origin);\n }\n\n /**\n * Send a message to the LMS frame and return a promise for its response.\n */\n private _post(method: string, params: unknown[]): Promise<unknown> {\n if (this._destroyed) {\n return Promise.reject(new Error(\"CrossFrameAPI destroyed\"));\n }\n\n const messageId = `cfapi-${Date.now()}-${this._counter++}`;\n const requestTime = Date.now();\n\n // Deep-clean params of non-cloneables (e.g. functions)\n const safeParams = params.map((p) => {\n if (typeof p === \"function\") {\n // DEFENSIVE CODING: Warn about non-serializable function parameters.\n // This console.warn is intentionally kept in production builds because:\n // 1. Functions in SCORM API params indicate a programming error by content authors\n // 2. The warning helps diagnose integration issues during content development\n // 3. It alerts developers to potential data loss (function becomes undefined)\n // 4. The frequency should be very low in production (only on misconfigured content)\n //\n // Alternative: Could be gated by a debug flag if production console noise is a concern\n console.warn(\"Dropping function param when posting SCORM call:\", method);\n return undefined;\n }\n return p;\n });\n\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n if (this._pending.has(messageId)) {\n this._pending.delete(messageId);\n reject(new Error(`Timeout calling ${method}`));\n }\n }, this._timeout);\n\n // CF-API-02: Store method name in pending request for better error reporting\n this._pending.set(messageId, { resolve, reject, timer, requestTime, method });\n const msg: MessageData = { messageId, method, params: safeParams };\n this._targetWindow.postMessage(msg, this._origin);\n });\n }\n\n /**\n * Handle incoming postMessage responses from the LMS frame.\n */\n private _onMessage(ev: MessageEvent): void {\n if (this._destroyed) return;\n\n // Validate the message origin and source unless all origins are allowed\n if (this._origin !== \"*\" && ev.origin !== this._origin) {\n return;\n }\n if (ev.source && ev.source !== this._targetWindow) {\n return;\n }\n\n // CF-API-04: Validate that ev.data has the expected MessageResponse structure\n if (!CrossFrameAPI._isValidMessageResponse(ev.data)) return;\n\n const data = ev.data;\n\n // Handle heartbeat response\n if (data.isHeartbeat) {\n this._lastHeartbeatResponse = Date.now();\n if (!this._connected) {\n this._connected = true;\n this._emit({ type: \"connectionRestored\" });\n }\n return;\n }\n\n // Handle regular response\n const pending = this._pending.get(data.messageId);\n if (!pending) return;\n\n clearTimeout(pending.timer);\n this._pending.delete(data.messageId);\n\n if (data.error) {\n // Check if rate limited and emit event\n // CF-API-02: Use the actual method name from the pending request\n if (data.error.message === \"Rate limit exceeded\") {\n this._emit({ type: \"rateLimited\", method: pending.method });\n }\n pending.reject(data.error);\n } else {\n pending.resolve(data.result);\n }\n }\n\n /**\n * Capture and cache SCORM errors.\n */\n private _capture(method: string, err: unknown): void {\n let errorMessage = \"Unknown error\";\n\n if (err instanceof Error) {\n errorMessage = err.message;\n } else if (typeof err === \"object\" && err !== null && \"message\" in err) {\n errorMessage = String((err as { message: unknown }).message);\n }\n\n console.error(`CrossFrameAPI ${method} error:`, err);\n // Match SCORM error codes (3 digits) from error messages\n const match = /(?:error code|code)?\\s*(\\d{3})\\b/i.exec(errorMessage);\n const code = match?.[1] ?? String(global_errors.GENERAL);\n this._lastError = code;\n this._cache.set(`error_${code}`, errorMessage);\n }\n}\n"],"names":["global_errors","GENERAL","CrossFrameAPI","constructor","targetOrigin","targetWindow","arguments","length","undefined","window","parent","options","__publicField","Map","Date","now","get","target","prop","receiver","v","Reflect","bind","methodName","isGet","endsWith","isSet","startsWith","isInit","isFinish","isCommit","isErrorString","isDiagnostic","_len","args","Array","_key","_validateArgs","console","error","key","_cache","set","String","_cacheTimestamps","_lastError","requestTime","_post","then","res","localModTime","delete","code","catch","err","_capture","result","all","entries","Object","forEach","_ref","val","_origin","_targetWindow","_timeout","timeout","_heartbeatInterval","heartbeatInterval","_heartbeatTimeout","heartbeatTimeout","warn","_boundOnMessage","_onMessage","addEventListener","_startHeartbeat","Proxy","_handler","_isValidMessageResponse","data","resp","messageId","message","isHeartbeat","isArray","destroy","_destroyed","removeEventListener","_heartbeatTimer","clearInterval","pending","from","_pending","values","clearTimeout","timer","reject","Error","clear","_eventListeners","on","event","callback","has","Set","add","off","connected","_connected","_emit","type","cb","setInterval","timeSinceLastResponse","_lastHeartbeatResponse","_sendHeartbeat","_counter","msg","method","params","postMessage","Promise","safeParams","map","p","resolve","setTimeout","ev","origin","source","errorMessage","match","exec"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cross-frame-api.js","sources":["../../src/constants/error_codes.ts","../../src/CrossFrameAPI.ts"],"sourcesContent":["export type ErrorCode = {\n [key: string]: number;\n};\n\nexport const global_errors: ErrorCode = {\n GENERAL: 101,\n INITIALIZATION_FAILED: 101,\n INITIALIZED: 101,\n TERMINATED: 101,\n TERMINATION_FAILURE: 101,\n TERMINATION_BEFORE_INIT: 101,\n MULTIPLE_TERMINATION: 101,\n RETRIEVE_BEFORE_INIT: 101,\n RETRIEVE_AFTER_TERM: 101,\n STORE_BEFORE_INIT: 101,\n STORE_AFTER_TERM: 101,\n COMMIT_BEFORE_INIT: 101,\n COMMIT_AFTER_TERM: 101,\n ARGUMENT_ERROR: 101,\n CHILDREN_ERROR: 101,\n COUNT_ERROR: 101,\n GENERAL_GET_FAILURE: 101,\n GENERAL_SET_FAILURE: 101,\n GENERAL_COMMIT_FAILURE: 101,\n UNDEFINED_DATA_MODEL: 101,\n UNIMPLEMENTED_ELEMENT: 101,\n VALUE_NOT_INITIALIZED: 101,\n INVALID_SET_VALUE: 101,\n READ_ONLY_ELEMENT: 101,\n WRITE_ONLY_ELEMENT: 101,\n TYPE_MISMATCH: 101,\n VALUE_OUT_OF_RANGE: 101,\n DEPENDENCY_NOT_ESTABLISHED: 101,\n};\n\nexport const scorm12_errors: ErrorCode = {\n ...global_errors,\n RETRIEVE_BEFORE_INIT: 301,\n STORE_BEFORE_INIT: 301,\n COMMIT_BEFORE_INIT: 301,\n ARGUMENT_ERROR: 201,\n CHILDREN_ERROR: 202,\n COUNT_ERROR: 203,\n UNDEFINED_DATA_MODEL: 401,\n UNIMPLEMENTED_ELEMENT: 401,\n VALUE_NOT_INITIALIZED: 301,\n INVALID_SET_VALUE: 402,\n READ_ONLY_ELEMENT: 403,\n WRITE_ONLY_ELEMENT: 404,\n TYPE_MISMATCH: 405,\n VALUE_OUT_OF_RANGE: 407,\n DEPENDENCY_NOT_ESTABLISHED: 408,\n};\n\nexport const scorm2004_errors: ErrorCode = {\n ...global_errors,\n INITIALIZATION_FAILED: 102,\n INITIALIZED: 103,\n TERMINATED: 104,\n TERMINATION_FAILURE: 111,\n TERMINATION_BEFORE_INIT: 112,\n MULTIPLE_TERMINATION: 113,\n MULTIPLE_TERMINATIONS: 113,\n RETRIEVE_BEFORE_INIT: 122,\n RETRIEVE_AFTER_TERM: 123,\n STORE_BEFORE_INIT: 132,\n STORE_AFTER_TERM: 133,\n COMMIT_BEFORE_INIT: 142,\n COMMIT_AFTER_TERM: 143,\n ARGUMENT_ERROR: 201,\n GENERAL_GET_FAILURE: 301,\n GENERAL_SET_FAILURE: 351,\n GENERAL_COMMIT_FAILURE: 391,\n UNDEFINED_DATA_MODEL: 401,\n UNIMPLEMENTED_ELEMENT: 402,\n VALUE_NOT_INITIALIZED: 403,\n READ_ONLY_ELEMENT: 404,\n WRITE_ONLY_ELEMENT: 405,\n TYPE_MISMATCH: 406,\n VALUE_OUT_OF_RANGE: 407,\n DEPENDENCY_NOT_ESTABLISHED: 408,\n};\n","// src/CrossFrameAPI.ts\nimport {\n CrossFrameAPIOptions,\n CrossFrameEvent,\n CrossFrameEventCallback,\n MessageData,\n MessageResponse,\n} from \"./types/CrossFrame\";\nimport { global_errors } from \"./constants/error_codes\";\n\n/**\n * Pending request tracking with timestamp for cache merge protection\n */\ninterface PendingRequest {\n resolve: (v: unknown) => void;\n reject: (e: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n /**\n * Timestamp when the request was sent, used for cache merge protection.\n * Ensures local modifications made after the request was sent aren't overwritten\n * by stale server responses that arrive later.\n */\n requestTime: number;\n method: string; // CF-API-02: Track method name for better error reporting\n}\n\n/**\n * Client-side SCORM facade running in your content frame.\n * Returns cached values synchronously, then fires off an async\n * postMessage to the LMS frame to refresh cache and error state.\n */\nexport default class CrossFrameAPI {\n private _cache = new Map<string, string>();\n private _cacheTimestamps = new Map<string, number>();\n private _lastError = \"0\";\n private _pending = new Map<string, PendingRequest>();\n private _counter = 0;\n private readonly _origin: string;\n private readonly _targetWindow: Window;\n private readonly _timeout: number;\n private readonly _heartbeatInterval: number;\n private readonly _heartbeatTimeout: number;\n\n private _destroyed = false;\n private _connected = true;\n private _lastHeartbeatResponse = Date.now();\n private _heartbeatTimer: ReturnType<typeof setInterval> | undefined;\n private _eventListeners = new Map<string, Set<CrossFrameEventCallback>>();\n private readonly _boundOnMessage: (ev: MessageEvent) => void;\n\n /**\n * Type guard to validate MessageResponse structure\n */\n private static _isValidMessageResponse(data: unknown): data is MessageResponse {\n if (typeof data !== \"object\" || data === null) return false;\n\n const resp = data as Partial<MessageResponse>;\n\n // messageId is required\n if (typeof resp.messageId !== \"string\" || resp.messageId.length === 0) return false;\n\n // error must have correct shape if present\n if (resp.error !== undefined) {\n if (typeof resp.error !== \"object\" || resp.error === null) return false;\n const err = resp.error as Record<string, unknown>;\n if (typeof err.message !== \"string\") return false;\n if (err.code !== undefined && typeof err.code !== \"string\") return false;\n }\n\n // isHeartbeat must be boolean if present\n if (resp.isHeartbeat !== undefined && typeof resp.isHeartbeat !== \"boolean\") return false;\n\n return true;\n }\n\n /**\n * Validates that args is an array and sanitizes it for safe use\n */\n private static _validateArgs(args: unknown[]): args is unknown[] {\n if (!Array.isArray(args)) return false;\n // Additional validation could check for specific types based on method\n return true;\n }\n\n private _handler: ProxyHandler<CrossFrameAPI> = {\n get: (target, prop, receiver) => {\n // If it's an existing property/method, return it\n if (typeof prop !== \"string\" || prop in target) {\n const v = Reflect.get(target, prop, receiver);\n return typeof v === \"function\" ? v.bind(target) : v;\n }\n\n // Otherwise treat prop as a SCORM call\n const methodName = prop;\n const isGet = methodName.endsWith(\"GetValue\");\n const isSet = methodName.startsWith(\"LMSSet\") || methodName.endsWith(\"SetValue\");\n const isInit = methodName === \"Initialize\" || methodName === \"LMSInitialize\";\n const isFinish = methodName === \"Terminate\" || methodName === \"LMSFinish\";\n const isCommit = methodName === \"Commit\" || methodName === \"LMSCommit\";\n const isErrorString = methodName === \"GetErrorString\" || methodName === \"LMSGetErrorString\";\n const isDiagnostic = methodName === \"GetDiagnostic\" || methodName === \"LMSGetDiagnostic\";\n\n return (...args: unknown[]): string => {\n // CF-API-03: Validate args is an array (TypeScript guarantees this, but runtime safety)\n if (!CrossFrameAPI._validateArgs(args)) {\n console.error(`CrossFrameAPI: Invalid arguments for ${methodName}`);\n return \"\";\n }\n\n // Synchronous cache update for setter calls\n if (isSet && args.length >= 2) {\n const key = args[0] as string;\n target._cache.set(key, String(args[1]));\n target._cacheTimestamps.set(key, Date.now());\n target._lastError = \"0\";\n }\n\n // Capture request time for cache merge protection\n const requestTime = Date.now();\n\n // Fire off async postMessage to refresh cache and error\n target\n ._post(methodName, args)\n .then((res) => {\n if (isGet && args.length >= 1) {\n const key = args[0] as string;\n // Only update if not locally modified after request was sent\n const localModTime = target._cacheTimestamps.get(key) ?? 0;\n if (localModTime < requestTime) {\n target._cache.set(key, String(res));\n target._cacheTimestamps.delete(key);\n }\n target._lastError = \"0\";\n }\n if (isErrorString && args.length >= 1) {\n const code = String(args[0]);\n target._cache.set(`error_${code}`, String(res));\n }\n if (isDiagnostic && args.length >= 1) {\n const code = String(args[0]);\n target._cache.set(`diag_${code}`, String(res));\n }\n if (methodName === \"GetLastError\" || methodName === \"LMSGetLastError\") {\n target._lastError = String(res);\n }\n })\n .catch((err) => target._capture(methodName, err));\n\n // Return synchronously\n if (isGet && args.length >= 1) {\n return target._cache.get(args[0] as string) ?? \"\";\n }\n if (isErrorString && args.length >= 1) {\n const code = String(args[0]);\n return target._cache.get(`error_${code}`) ?? \"\";\n }\n if (isDiagnostic && args.length >= 1) {\n const code = String(args[0]);\n return target._cache.get(`diag_${code}`) ?? \"\";\n }\n if (isInit || isFinish || isCommit || isSet) {\n // Immediately return \"true\"\n const result = \"true\";\n // Then warm cache with timestamp protection:\n target\n ._post(\"getFlattenedCMI\", [])\n .then((all: unknown) => {\n if (all && typeof all === \"object\") {\n const entries = Object.entries(all as Record<string, string>);\n entries.forEach(([key, val]) => {\n // Only update if not locally modified after request was sent\n const localModTime = target._cacheTimestamps.get(key) ?? 0;\n if (localModTime < requestTime) {\n target._cache.set(key, val);\n target._cacheTimestamps.delete(key);\n }\n });\n }\n // reset error\n target._lastError = \"0\";\n })\n .catch((err) => target._capture(\"getFlattenedCMI\", err));\n return result;\n }\n if (methodName === \"GetLastError\" || methodName === \"LMSGetLastError\") {\n return target._lastError;\n }\n return \"\";\n };\n },\n };\n\n /**\n * Creates a new CrossFrameAPI instance.\n * @param targetOrigin - Origin to send messages to. Default \"*\" sends to any origin.\n * @param targetWindow - Window to send messages to. Default is window.parent.\n * @param options - Configuration options\n */\n constructor(\n targetOrigin: string = \"*\",\n targetWindow: Window = window.parent,\n options: CrossFrameAPIOptions = {},\n ) {\n this._origin = targetOrigin;\n this._targetWindow = targetWindow;\n this._timeout = options.timeout ?? 5000;\n this._heartbeatInterval = options.heartbeatInterval ?? 30000;\n this._heartbeatTimeout = options.heartbeatTimeout ?? 60000;\n\n // Warn about wildcard origin security implications\n if (targetOrigin === \"*\") {\n console.warn(\n \"CrossFrameAPI: Using wildcard origin ('*') allows any origin to receive messages. \" +\n \"This is insecure for production use. \" +\n \"Specify an explicit origin (e.g., 'https://lms.example.com') to restrict message recipients.\",\n );\n }\n\n this._boundOnMessage = this._onMessage.bind(this);\n window.addEventListener(\"message\", this._boundOnMessage);\n this._startHeartbeat();\n\n return new Proxy(this, this._handler);\n }\n\n /**\n * Destroys this instance, removing event listeners and preventing further message processing.\n * Once destroyed, the instance cannot be reused.\n */\n destroy(): void {\n if (this._destroyed) return;\n this._destroyed = true;\n window.removeEventListener(\"message\", this._boundOnMessage);\n if (this._heartbeatTimer) {\n clearInterval(this._heartbeatTimer);\n this._heartbeatTimer = undefined;\n }\n // Reject all pending requests\n for (const pending of Array.from(this._pending.values())) {\n clearTimeout(pending.timer);\n pending.reject(new Error(\"CrossFrameAPI destroyed\"));\n }\n this._pending.clear();\n this._cache.clear();\n this._cacheTimestamps.clear();\n this._eventListeners.clear();\n }\n\n /**\n * Subscribes to a CrossFrame event.\n * @param event - Event type to listen for\n * @param callback - Function to call when event occurs\n */\n on(event: string, callback: CrossFrameEventCallback): void {\n if (!this._eventListeners.has(event)) {\n this._eventListeners.set(event, new Set());\n }\n this._eventListeners.get(event)?.add(callback);\n }\n\n /**\n * Unsubscribes from a CrossFrame event.\n * @param event - Event type to stop listening for\n * @param callback - Function to remove\n */\n off(event: string, callback: CrossFrameEventCallback): void {\n this._eventListeners.get(event)?.delete(callback);\n }\n\n /**\n * Returns whether the connection to the LMS frame is currently active.\n */\n get connected(): boolean {\n return this._connected;\n }\n\n /**\n * Emits an event to all registered listeners.\n */\n private _emit(event: CrossFrameEvent): void {\n this._eventListeners.get(event.type)?.forEach((cb) => cb(event));\n }\n\n /**\n * Starts the heartbeat mechanism for connection detection.\n */\n private _startHeartbeat(): void {\n // CF-API-01: Clear any existing heartbeat timer before starting a new one\n if (this._heartbeatTimer) {\n clearInterval(this._heartbeatTimer);\n }\n\n this._heartbeatTimer = setInterval(() => {\n if (this._destroyed) return;\n\n // Check if we've missed heartbeats\n const timeSinceLastResponse = Date.now() - this._lastHeartbeatResponse;\n if (timeSinceLastResponse > this._heartbeatTimeout && this._connected) {\n this._connected = false;\n this._emit({ type: \"connectionLost\" });\n }\n\n // Send heartbeat ping\n this._sendHeartbeat();\n }, this._heartbeatInterval);\n }\n\n /**\n * Sends a heartbeat ping to the LMS frame.\n */\n private _sendHeartbeat(): void {\n const messageId = `hb-${Date.now()}-${this._counter++}`;\n const msg: MessageData = {\n messageId,\n method: \"__heartbeat__\",\n params: [],\n isHeartbeat: true,\n };\n this._targetWindow.postMessage(msg, this._origin);\n }\n\n /**\n * Send a message to the LMS frame and return a promise for its response.\n */\n private _post(method: string, params: unknown[]): Promise<unknown> {\n if (this._destroyed) {\n return Promise.reject(new Error(\"CrossFrameAPI destroyed\"));\n }\n\n const messageId = `cfapi-${Date.now()}-${this._counter++}`;\n const requestTime = Date.now();\n\n // Deep-clean params of non-cloneables (e.g. functions)\n const safeParams = params.map((p) => {\n if (typeof p === \"function\") {\n // DEFENSIVE CODING: Warn about non-serializable function parameters.\n // This console.warn is intentionally kept in production builds because:\n // 1. Functions in SCORM API params indicate a programming error by content authors\n // 2. The warning helps diagnose integration issues during content development\n // 3. It alerts developers to potential data loss (function becomes undefined)\n // 4. The frequency should be very low in production (only on misconfigured content)\n //\n // Alternative: Could be gated by a debug flag if production console noise is a concern\n console.warn(\"Dropping function param when posting SCORM call:\", method);\n return undefined;\n }\n return p;\n });\n\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n if (this._pending.has(messageId)) {\n this._pending.delete(messageId);\n reject(new Error(`Timeout calling ${method}`));\n }\n }, this._timeout);\n\n // CF-API-02: Store method name in pending request for better error reporting\n this._pending.set(messageId, { resolve, reject, timer, requestTime, method });\n const msg: MessageData = { messageId, method, params: safeParams };\n this._targetWindow.postMessage(msg, this._origin);\n });\n }\n\n /**\n * Handle incoming postMessage responses from the LMS frame.\n */\n private _onMessage(ev: MessageEvent): void {\n if (this._destroyed) return;\n\n // Validate the message origin and source unless all origins are allowed\n if (this._origin !== \"*\" && ev.origin !== this._origin) {\n return;\n }\n if (ev.source && ev.source !== this._targetWindow) {\n return;\n }\n\n // CF-API-04: Validate that ev.data has the expected MessageResponse structure\n if (!CrossFrameAPI._isValidMessageResponse(ev.data)) return;\n\n const data = ev.data;\n\n // Handle heartbeat response\n if (data.isHeartbeat) {\n this._lastHeartbeatResponse = Date.now();\n if (!this._connected) {\n this._connected = true;\n this._emit({ type: \"connectionRestored\" });\n }\n return;\n }\n\n // Handle regular response\n const pending = this._pending.get(data.messageId);\n if (!pending) return;\n\n clearTimeout(pending.timer);\n this._pending.delete(data.messageId);\n\n if (data.error) {\n // Check if rate limited and emit event\n // CF-API-02: Use the actual method name from the pending request\n if (data.error.message === \"Rate limit exceeded\") {\n this._emit({ type: \"rateLimited\", method: pending.method });\n }\n pending.reject(data.error);\n } else {\n pending.resolve(data.result);\n }\n }\n\n /**\n * Capture and cache SCORM errors.\n */\n private _capture(method: string, err: unknown): void {\n let errorMessage = \"Unknown error\";\n\n if (err instanceof Error) {\n errorMessage = err.message;\n } else if (typeof err === \"object\" && err !== null && \"message\" in err) {\n errorMessage = String((err as { message: unknown }).message);\n }\n\n console.error(`CrossFrameAPI ${method} error:`, err);\n // Match SCORM error codes (3 digits) from error messages\n const match = /(?:error code|code)?\\s*(\\d{3})\\b/i.exec(errorMessage);\n const code = match?.[1] ?? String(global_errors.GENERAL);\n this._lastError = code;\n this._cache.set(`error_${code}`, errorMessage);\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cross-frame-api.js","sources":["../../src/constants/error_codes.ts","../../src/CrossFrameAPI.ts"],"sourcesContent":["export type ErrorCode = {\n [key: string]: number;\n};\n\nexport const global_errors: ErrorCode = {\n GENERAL: 101,\n INITIALIZATION_FAILED: 101,\n INITIALIZED: 101,\n TERMINATED: 101,\n TERMINATION_FAILURE: 101,\n TERMINATION_BEFORE_INIT: 101,\n MULTIPLE_TERMINATION: 101,\n RETRIEVE_BEFORE_INIT: 101,\n RETRIEVE_AFTER_TERM: 101,\n STORE_BEFORE_INIT: 101,\n STORE_AFTER_TERM: 101,\n COMMIT_BEFORE_INIT: 101,\n COMMIT_AFTER_TERM: 101,\n ARGUMENT_ERROR: 101,\n CHILDREN_ERROR: 101,\n COUNT_ERROR: 101,\n GENERAL_GET_FAILURE: 101,\n GENERAL_SET_FAILURE: 101,\n GENERAL_COMMIT_FAILURE: 101,\n UNDEFINED_DATA_MODEL: 101,\n UNIMPLEMENTED_ELEMENT: 101,\n VALUE_NOT_INITIALIZED: 101,\n INVALID_SET_VALUE: 101,\n READ_ONLY_ELEMENT: 101,\n WRITE_ONLY_ELEMENT: 101,\n TYPE_MISMATCH: 101,\n VALUE_OUT_OF_RANGE: 101,\n DEPENDENCY_NOT_ESTABLISHED: 101,\n};\n\nexport const scorm12_errors: ErrorCode = {\n ...global_errors,\n RETRIEVE_BEFORE_INIT: 301,\n STORE_BEFORE_INIT: 301,\n COMMIT_BEFORE_INIT: 301,\n ARGUMENT_ERROR: 201,\n CHILDREN_ERROR: 202,\n COUNT_ERROR: 203,\n UNDEFINED_DATA_MODEL: 401,\n UNIMPLEMENTED_ELEMENT: 401,\n VALUE_NOT_INITIALIZED: 301,\n INVALID_SET_VALUE: 402,\n READ_ONLY_ELEMENT: 403,\n WRITE_ONLY_ELEMENT: 404,\n TYPE_MISMATCH: 405,\n // SCORM 1.2 has no out-of-range error code; an out-of-range value is reported\n // as 405 (incorrect data type), per the SCORM 1.2 RTE error table and the ADL\n // 1.2 CTS (DataModelValidator.checkScoreDecimal failure -> CMICategory 405).\n VALUE_OUT_OF_RANGE: 405,\n DEPENDENCY_NOT_ESTABLISHED: 408,\n};\n\nexport const scorm2004_errors: ErrorCode = {\n ...global_errors,\n INITIALIZATION_FAILED: 102,\n INITIALIZED: 103,\n TERMINATED: 104,\n TERMINATION_FAILURE: 111,\n TERMINATION_BEFORE_INIT: 112,\n MULTIPLE_TERMINATION: 113,\n MULTIPLE_TERMINATIONS: 113,\n RETRIEVE_BEFORE_INIT: 122,\n RETRIEVE_AFTER_TERM: 123,\n STORE_BEFORE_INIT: 132,\n STORE_AFTER_TERM: 133,\n COMMIT_BEFORE_INIT: 142,\n COMMIT_AFTER_TERM: 143,\n ARGUMENT_ERROR: 201,\n GENERAL_GET_FAILURE: 301,\n GENERAL_SET_FAILURE: 351,\n GENERAL_COMMIT_FAILURE: 391,\n UNDEFINED_DATA_MODEL: 401,\n UNIMPLEMENTED_ELEMENT: 402,\n VALUE_NOT_INITIALIZED: 403,\n READ_ONLY_ELEMENT: 404,\n WRITE_ONLY_ELEMENT: 405,\n TYPE_MISMATCH: 406,\n VALUE_OUT_OF_RANGE: 407,\n DEPENDENCY_NOT_ESTABLISHED: 408,\n};\n","// src/CrossFrameAPI.ts\nimport {\n CrossFrameAPIOptions,\n CrossFrameEvent,\n CrossFrameEventCallback,\n MessageData,\n MessageResponse,\n} from \"./types/CrossFrame\";\nimport { global_errors } from \"./constants/error_codes\";\n\n/**\n * Pending request tracking with timestamp for cache merge protection\n */\ninterface PendingRequest {\n resolve: (v: unknown) => void;\n reject: (e: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n /**\n * Timestamp when the request was sent, used for cache merge protection.\n * Ensures local modifications made after the request was sent aren't overwritten\n * by stale server responses that arrive later.\n */\n requestTime: number;\n method: string; // CF-API-02: Track method name for better error reporting\n}\n\n/**\n * Client-side SCORM facade running in your content frame.\n * Returns cached values synchronously, then fires off an async\n * postMessage to the LMS frame to refresh cache and error state.\n */\nexport default class CrossFrameAPI {\n private _cache = new Map<string, string>();\n private _cacheTimestamps = new Map<string, number>();\n private _lastError = \"0\";\n private _pending = new Map<string, PendingRequest>();\n private _counter = 0;\n private readonly _origin: string;\n private readonly _targetWindow: Window;\n private readonly _timeout: number;\n private readonly _heartbeatInterval: number;\n private readonly _heartbeatTimeout: number;\n\n private _destroyed = false;\n private _connected = true;\n private _lastHeartbeatResponse = Date.now();\n private _heartbeatTimer: ReturnType<typeof setInterval> | undefined;\n private _eventListeners = new Map<string, Set<CrossFrameEventCallback>>();\n private readonly _boundOnMessage: (ev: MessageEvent) => void;\n\n /**\n * Type guard to validate MessageResponse structure\n */\n private static _isValidMessageResponse(data: unknown): data is MessageResponse {\n if (typeof data !== \"object\" || data === null) return false;\n\n const resp = data as Partial<MessageResponse>;\n\n // messageId is required\n if (typeof resp.messageId !== \"string\" || resp.messageId.length === 0) return false;\n\n // error must have correct shape if present\n if (resp.error !== undefined) {\n if (typeof resp.error !== \"object\" || resp.error === null) return false;\n const err = resp.error as Record<string, unknown>;\n if (typeof err.message !== \"string\") return false;\n if (err.code !== undefined && typeof err.code !== \"string\") return false;\n }\n\n // isHeartbeat must be boolean if present\n if (resp.isHeartbeat !== undefined && typeof resp.isHeartbeat !== \"boolean\") return false;\n\n return true;\n }\n\n /**\n * Validates that args is an array and sanitizes it for safe use\n */\n private static _validateArgs(args: unknown[]): args is unknown[] {\n if (!Array.isArray(args)) return false;\n // Additional validation could check for specific types based on method\n return true;\n }\n\n private _handler: ProxyHandler<CrossFrameAPI> = {\n get: (target, prop, receiver) => {\n // If it's an existing property/method, return it\n if (typeof prop !== \"string\" || prop in target) {\n const v = Reflect.get(target, prop, receiver);\n return typeof v === \"function\" ? v.bind(target) : v;\n }\n\n // Otherwise treat prop as a SCORM call\n const methodName = prop;\n const isGet = methodName.endsWith(\"GetValue\");\n const isSet = methodName.startsWith(\"LMSSet\") || methodName.endsWith(\"SetValue\");\n const isInit = methodName === \"Initialize\" || methodName === \"LMSInitialize\";\n const isFinish = methodName === \"Terminate\" || methodName === \"LMSFinish\";\n const isCommit = methodName === \"Commit\" || methodName === \"LMSCommit\";\n const isErrorString = methodName === \"GetErrorString\" || methodName === \"LMSGetErrorString\";\n const isDiagnostic = methodName === \"GetDiagnostic\" || methodName === \"LMSGetDiagnostic\";\n\n return (...args: unknown[]): string => {\n // CF-API-03: Validate args is an array (TypeScript guarantees this, but runtime safety)\n if (!CrossFrameAPI._validateArgs(args)) {\n console.error(`CrossFrameAPI: Invalid arguments for ${methodName}`);\n return \"\";\n }\n\n // Synchronous cache update for setter calls\n if (isSet && args.length >= 2) {\n const key = args[0] as string;\n target._cache.set(key, String(args[1]));\n target._cacheTimestamps.set(key, Date.now());\n target._lastError = \"0\";\n }\n\n // Capture request time for cache merge protection\n const requestTime = Date.now();\n\n // Fire off async postMessage to refresh cache and error\n target\n ._post(methodName, args)\n .then((res) => {\n if (isGet && args.length >= 1) {\n const key = args[0] as string;\n // Only update if not locally modified after request was sent\n const localModTime = target._cacheTimestamps.get(key) ?? 0;\n if (localModTime < requestTime) {\n target._cache.set(key, String(res));\n target._cacheTimestamps.delete(key);\n }\n target._lastError = \"0\";\n }\n if (isErrorString && args.length >= 1) {\n const code = String(args[0]);\n target._cache.set(`error_${code}`, String(res));\n }\n if (isDiagnostic && args.length >= 1) {\n const code = String(args[0]);\n target._cache.set(`diag_${code}`, String(res));\n }\n if (methodName === \"GetLastError\" || methodName === \"LMSGetLastError\") {\n target._lastError = String(res);\n }\n })\n .catch((err) => target._capture(methodName, err));\n\n // Return synchronously\n if (isGet && args.length >= 1) {\n return target._cache.get(args[0] as string) ?? \"\";\n }\n if (isErrorString && args.length >= 1) {\n const code = String(args[0]);\n return target._cache.get(`error_${code}`) ?? \"\";\n }\n if (isDiagnostic && args.length >= 1) {\n const code = String(args[0]);\n return target._cache.get(`diag_${code}`) ?? \"\";\n }\n if (isInit || isFinish || isCommit || isSet) {\n // Immediately return \"true\"\n const result = \"true\";\n // Then warm cache with timestamp protection:\n target\n ._post(\"getFlattenedCMI\", [])\n .then((all: unknown) => {\n if (all && typeof all === \"object\") {\n const entries = Object.entries(all as Record<string, string>);\n entries.forEach(([key, val]) => {\n // Only update if not locally modified after request was sent\n const localModTime = target._cacheTimestamps.get(key) ?? 0;\n if (localModTime < requestTime) {\n target._cache.set(key, val);\n target._cacheTimestamps.delete(key);\n }\n });\n }\n // reset error\n target._lastError = \"0\";\n })\n .catch((err) => target._capture(\"getFlattenedCMI\", err));\n return result;\n }\n if (methodName === \"GetLastError\" || methodName === \"LMSGetLastError\") {\n return target._lastError;\n }\n return \"\";\n };\n },\n };\n\n /**\n * Creates a new CrossFrameAPI instance.\n * @param targetOrigin - Origin to send messages to. Default \"*\" sends to any origin.\n * @param targetWindow - Window to send messages to. Default is window.parent.\n * @param options - Configuration options\n */\n constructor(\n targetOrigin: string = \"*\",\n targetWindow: Window = window.parent,\n options: CrossFrameAPIOptions = {},\n ) {\n this._origin = targetOrigin;\n this._targetWindow = targetWindow;\n this._timeout = options.timeout ?? 5000;\n this._heartbeatInterval = options.heartbeatInterval ?? 30000;\n this._heartbeatTimeout = options.heartbeatTimeout ?? 60000;\n\n // Warn about wildcard origin security implications\n if (targetOrigin === \"*\") {\n console.warn(\n \"CrossFrameAPI: Using wildcard origin ('*') allows any origin to receive messages. \" +\n \"This is insecure for production use. \" +\n \"Specify an explicit origin (e.g., 'https://lms.example.com') to restrict message recipients.\",\n );\n }\n\n this._boundOnMessage = this._onMessage.bind(this);\n window.addEventListener(\"message\", this._boundOnMessage);\n this._startHeartbeat();\n\n return new Proxy(this, this._handler);\n }\n\n /**\n * Destroys this instance, removing event listeners and preventing further message processing.\n * Once destroyed, the instance cannot be reused.\n */\n destroy(): void {\n if (this._destroyed) return;\n this._destroyed = true;\n window.removeEventListener(\"message\", this._boundOnMessage);\n if (this._heartbeatTimer) {\n clearInterval(this._heartbeatTimer);\n this._heartbeatTimer = undefined;\n }\n // Reject all pending requests\n for (const pending of Array.from(this._pending.values())) {\n clearTimeout(pending.timer);\n pending.reject(new Error(\"CrossFrameAPI destroyed\"));\n }\n this._pending.clear();\n this._cache.clear();\n this._cacheTimestamps.clear();\n this._eventListeners.clear();\n }\n\n /**\n * Subscribes to a CrossFrame event.\n * @param event - Event type to listen for\n * @param callback - Function to call when event occurs\n */\n on(event: string, callback: CrossFrameEventCallback): void {\n if (!this._eventListeners.has(event)) {\n this._eventListeners.set(event, new Set());\n }\n this._eventListeners.get(event)?.add(callback);\n }\n\n /**\n * Unsubscribes from a CrossFrame event.\n * @param event - Event type to stop listening for\n * @param callback - Function to remove\n */\n off(event: string, callback: CrossFrameEventCallback): void {\n this._eventListeners.get(event)?.delete(callback);\n }\n\n /**\n * Returns whether the connection to the LMS frame is currently active.\n */\n get connected(): boolean {\n return this._connected;\n }\n\n /**\n * Emits an event to all registered listeners.\n */\n private _emit(event: CrossFrameEvent): void {\n this._eventListeners.get(event.type)?.forEach((cb) => cb(event));\n }\n\n /**\n * Starts the heartbeat mechanism for connection detection.\n */\n private _startHeartbeat(): void {\n // CF-API-01: Clear any existing heartbeat timer before starting a new one\n if (this._heartbeatTimer) {\n clearInterval(this._heartbeatTimer);\n }\n\n this._heartbeatTimer = setInterval(() => {\n if (this._destroyed) return;\n\n // Check if we've missed heartbeats\n const timeSinceLastResponse = Date.now() - this._lastHeartbeatResponse;\n if (timeSinceLastResponse > this._heartbeatTimeout && this._connected) {\n this._connected = false;\n this._emit({ type: \"connectionLost\" });\n }\n\n // Send heartbeat ping\n this._sendHeartbeat();\n }, this._heartbeatInterval);\n }\n\n /**\n * Sends a heartbeat ping to the LMS frame.\n */\n private _sendHeartbeat(): void {\n const messageId = `hb-${Date.now()}-${this._counter++}`;\n const msg: MessageData = {\n messageId,\n method: \"__heartbeat__\",\n params: [],\n isHeartbeat: true,\n };\n this._targetWindow.postMessage(msg, this._origin);\n }\n\n /**\n * Send a message to the LMS frame and return a promise for its response.\n */\n private _post(method: string, params: unknown[]): Promise<unknown> {\n if (this._destroyed) {\n return Promise.reject(new Error(\"CrossFrameAPI destroyed\"));\n }\n\n const messageId = `cfapi-${Date.now()}-${this._counter++}`;\n const requestTime = Date.now();\n\n // Deep-clean params of non-cloneables (e.g. functions)\n const safeParams = params.map((p) => {\n if (typeof p === \"function\") {\n // DEFENSIVE CODING: Warn about non-serializable function parameters.\n // This console.warn is intentionally kept in production builds because:\n // 1. Functions in SCORM API params indicate a programming error by content authors\n // 2. The warning helps diagnose integration issues during content development\n // 3. It alerts developers to potential data loss (function becomes undefined)\n // 4. The frequency should be very low in production (only on misconfigured content)\n //\n // Alternative: Could be gated by a debug flag if production console noise is a concern\n console.warn(\"Dropping function param when posting SCORM call:\", method);\n return undefined;\n }\n return p;\n });\n\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n if (this._pending.has(messageId)) {\n this._pending.delete(messageId);\n reject(new Error(`Timeout calling ${method}`));\n }\n }, this._timeout);\n\n // CF-API-02: Store method name in pending request for better error reporting\n this._pending.set(messageId, { resolve, reject, timer, requestTime, method });\n const msg: MessageData = { messageId, method, params: safeParams };\n this._targetWindow.postMessage(msg, this._origin);\n });\n }\n\n /**\n * Handle incoming postMessage responses from the LMS frame.\n */\n private _onMessage(ev: MessageEvent): void {\n if (this._destroyed) return;\n\n // Validate the message origin and source unless all origins are allowed\n if (this._origin !== \"*\" && ev.origin !== this._origin) {\n return;\n }\n if (ev.source && ev.source !== this._targetWindow) {\n return;\n }\n\n // CF-API-04: Validate that ev.data has the expected MessageResponse structure\n if (!CrossFrameAPI._isValidMessageResponse(ev.data)) return;\n\n const data = ev.data;\n\n // Handle heartbeat response\n if (data.isHeartbeat) {\n this._lastHeartbeatResponse = Date.now();\n if (!this._connected) {\n this._connected = true;\n this._emit({ type: \"connectionRestored\" });\n }\n return;\n }\n\n // Handle regular response\n const pending = this._pending.get(data.messageId);\n if (!pending) return;\n\n clearTimeout(pending.timer);\n this._pending.delete(data.messageId);\n\n if (data.error) {\n // Check if rate limited and emit event\n // CF-API-02: Use the actual method name from the pending request\n if (data.error.message === \"Rate limit exceeded\") {\n this._emit({ type: \"rateLimited\", method: pending.method });\n }\n pending.reject(data.error);\n } else {\n pending.resolve(data.result);\n }\n }\n\n /**\n * Capture and cache SCORM errors.\n */\n private _capture(method: string, err: unknown): void {\n let errorMessage = \"Unknown error\";\n\n if (err instanceof Error) {\n errorMessage = err.message;\n } else if (typeof err === \"object\" && err !== null && \"message\" in err) {\n errorMessage = String((err as { message: unknown }).message);\n }\n\n console.error(`CrossFrameAPI ${method} error:`, err);\n // Match SCORM error codes (3 digits) from error messages\n const match = /(?:error code|code)?\\s*(\\d{3})\\b/i.exec(errorMessage);\n const code = match?.[1] ?? String(global_errors.GENERAL);\n this._lastError = code;\n this._cache.set(`error_${code}`, errorMessage);\n }\n}\n"],"names":[],"mappings":"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"}