scorm-again 3.0.0 → 3.0.2

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 (50) hide show
  1. package/dist/aicc.js.map +1 -0
  2. package/dist/aicc.min.js.map +1 -0
  3. package/dist/cross-frame-api.js +26 -11
  4. package/dist/cross-frame-api.js.map +1 -0
  5. package/dist/cross-frame-api.min.js +1 -1
  6. package/dist/cross-frame-api.min.js.map +1 -0
  7. package/dist/cross-frame-lms.js +16 -4
  8. package/dist/cross-frame-lms.js.map +1 -0
  9. package/dist/cross-frame-lms.min.js +1 -1
  10. package/dist/cross-frame-lms.min.js.map +1 -0
  11. package/dist/esm/aicc.js.map +1 -0
  12. package/dist/esm/aicc.min.js.map +1 -0
  13. package/dist/esm/cross-frame-api.js +115 -108
  14. package/dist/esm/cross-frame-api.js.map +1 -0
  15. package/dist/esm/cross-frame-api.min.js +1 -1
  16. package/dist/esm/cross-frame-api.min.js.map +1 -0
  17. package/dist/esm/cross-frame-lms.js +32 -30
  18. package/dist/esm/cross-frame-lms.js.map +1 -0
  19. package/dist/esm/cross-frame-lms.min.js +1 -1
  20. package/dist/esm/cross-frame-lms.min.js.map +1 -0
  21. package/dist/esm/scorm-again.js +804 -506
  22. package/dist/esm/scorm-again.js.map +1 -0
  23. package/dist/esm/scorm-again.min.js +1 -1
  24. package/dist/esm/scorm-again.min.js.map +1 -0
  25. package/dist/esm/scorm12.js +152 -373
  26. package/dist/esm/scorm12.js.map +1 -0
  27. package/dist/esm/scorm12.min.js +1 -1
  28. package/dist/esm/scorm12.min.js.map +1 -0
  29. package/dist/esm/scorm2004.js +582 -312
  30. package/dist/esm/scorm2004.js.map +1 -0
  31. package/dist/esm/scorm2004.min.js +1 -1
  32. package/dist/esm/scorm2004.min.js.map +1 -0
  33. package/dist/scorm-again.js +1205 -334
  34. package/dist/scorm-again.js.map +1 -0
  35. package/dist/scorm-again.min.js +1 -1
  36. package/dist/scorm-again.min.js.map +1 -0
  37. package/dist/scorm12.js +832 -71
  38. package/dist/scorm12.js.map +1 -0
  39. package/dist/scorm12.min.js +1 -1
  40. package/dist/scorm12.min.js.map +1 -0
  41. package/dist/scorm2004.js +1078 -292
  42. package/dist/scorm2004.js.map +1 -0
  43. package/dist/scorm2004.min.js +1 -1
  44. package/dist/scorm2004.min.js.map +1 -0
  45. package/dist/types/BaseAPI.d.ts +3 -3
  46. package/dist/types/Scorm2004API.d.ts +1 -0
  47. package/dist/types/cmi/scorm2004/sequencing/handlers/termination_handler.d.ts +2 -2
  48. package/dist/types/interfaces/services.d.ts +3 -2
  49. package/dist/types/services/EventService.d.ts +3 -3
  50. package/package.json +41 -44
@@ -4,6 +4,14 @@ this.CrossFrameAPI = (function () {
4
4
  const global_errors = {
5
5
  GENERAL: 101};
6
6
 
7
+ var __defProp = Object.defineProperty;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
9
+ enumerable: true,
10
+ configurable: true,
11
+ writable: true,
12
+ value
13
+ }) : obj[key] = value;
14
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
7
15
  class CrossFrameAPI {
8
16
  /**
9
17
  * Creates a new CrossFrameAPI instance.
@@ -15,16 +23,23 @@ this.CrossFrameAPI = (function () {
15
23
  let targetOrigin = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "*";
16
24
  let targetWindow = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : window.parent;
17
25
  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
18
- this._cache = /* @__PURE__ */new Map();
19
- this._cacheTimestamps = /* @__PURE__ */new Map();
20
- this._lastError = "0";
21
- this._pending = /* @__PURE__ */new Map();
22
- this._counter = 0;
23
- this._destroyed = false;
24
- this._connected = true;
25
- this._lastHeartbeatResponse = Date.now();
26
- this._eventListeners = /* @__PURE__ */new Map();
27
- this._handler = {
26
+ __publicField(this, "_cache", /* @__PURE__ */new Map());
27
+ __publicField(this, "_cacheTimestamps", /* @__PURE__ */new Map());
28
+ __publicField(this, "_lastError", "0");
29
+ __publicField(this, "_pending", /* @__PURE__ */new Map());
30
+ __publicField(this, "_counter", 0);
31
+ __publicField(this, "_origin");
32
+ __publicField(this, "_targetWindow");
33
+ __publicField(this, "_timeout");
34
+ __publicField(this, "_heartbeatInterval");
35
+ __publicField(this, "_heartbeatTimeout");
36
+ __publicField(this, "_destroyed", false);
37
+ __publicField(this, "_connected", true);
38
+ __publicField(this, "_lastHeartbeatResponse", Date.now());
39
+ __publicField(this, "_heartbeatTimer");
40
+ __publicField(this, "_eventListeners", /* @__PURE__ */new Map());
41
+ __publicField(this, "_boundOnMessage");
42
+ __publicField(this, "_handler", {
28
43
  get: (target, prop, receiver) => {
29
44
  if (typeof prop !== "string" || prop in target) {
30
45
  const v = Reflect.get(target, prop, receiver);
@@ -110,7 +125,7 @@ this.CrossFrameAPI = (function () {
110
125
  return "";
111
126
  };
112
127
  }
113
- };
128
+ });
114
129
  this._origin = targetOrigin;
115
130
  this._targetWindow = targetWindow;
116
131
  this._timeout = options.timeout ?? 5e3;
@@ -0,0 +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,2 +1,2 @@
1
- this.CrossFrameAPI=function(){"use strict";class e{constructor(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"*",r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:window.parent,s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this._cache=new Map,this._cacheTimestamps=new Map,this._lastError="0",this._pending=new Map,this._counter=0,this._destroyed=!1,this._connected=!0,this._lastHeartbeatResponse=Date.now(),this._eventListeners=new Map,this._handler={get:(t,r,s)=>{if("string"!=typeof r||r in t){const e=Reflect.get(t,r,s);return"function"==typeof e?e.bind(t):e}const i=r,n=i.endsWith("GetValue"),a=i.startsWith("LMSSet")||i.endsWith("SetValue"),o="Initialize"===i||"LMSInitialize"===i,h="Terminate"===i||"LMSFinish"===i,c="Commit"===i||"LMSCommit"===i,_="GetErrorString"===i||"LMSGetErrorString"===i,d="GetDiagnostic"===i||"LMSGetDiagnostic"===i;return function(){for(var r=arguments.length,s=Array(r),g=0;r>g;g++)s[g]=arguments[g];if(!e._validateArgs(s))return console.error("CrossFrameAPI: Invalid arguments for "+i),"";if(a&&s.length>=2){const e=s[0];t._cache.set(e,s[1]+""),t._cacheTimestamps.set(e,Date.now()),t._lastError="0"}const l=Date.now();if(t._post(i,s).then(e=>{if(n&&s.length>=1){const r=s[0],i=t._cacheTimestamps.get(r)??0;l>i&&(t._cache.set(r,e+""),t._cacheTimestamps.delete(r)),t._lastError="0"}_&&s.length>=1&&t._cache.set("error_"+s[0],e+""),d&&s.length>=1&&t._cache.set("diag_"+s[0],e+""),"GetLastError"!==i&&"LMSGetLastError"!==i||(t._lastError=e+"")}).catch(e=>t._capture(i,e)),n&&s.length>=1)return t._cache.get(s[0])??"";if(_&&s.length>=1)return t._cache.get("error_"+s[0])??"";if(d&&s.length>=1)return t._cache.get("diag_"+s[0])??"";if(o||h||c||a){const result="true";return t._post("getFlattenedCMI",[]).then(e=>{e&&"object"==typeof e&&Object.entries(e).forEach(e=>{let[r,s]=e;const i=t._cacheTimestamps.get(r)??0;l>i&&(t._cache.set(r,s),t._cacheTimestamps.delete(r))}),t._lastError="0"}).catch(e=>t._capture("getFlattenedCMI",e)),result}return"GetLastError"===i||"LMSGetLastError"===i?t._lastError:""}}},this._origin=t,this._targetWindow=r,this._timeout=s.timeout??5e3,this._heartbeatInterval=s.heartbeatInterval??3e4,this._heartbeatTimeout=s.heartbeatTimeout??6e4,"*"===t&&console.warn("CrossFrameAPI: Using wildcard origin ('*') allows any origin to receive messages. This is insecure for production use. Specify an explicit origin (e.g., 'https://lms.example.com') to restrict message recipients."),this._boundOnMessage=this._onMessage.bind(this),window.addEventListener("message",this._boundOnMessage),this._startHeartbeat(),new Proxy(this,this._handler)}static _isValidMessageResponse(e){if("object"!=typeof e||null===e)return!1;if("string"!=typeof e.messageId||0===e.messageId.length)return!1;if(void 0!==e.error){if("object"!=typeof e.error||null===e.error)return!1;const e=e.error;if("string"!=typeof e.message)return!1;if(void 0!==e.code&&"string"!=typeof e.code)return!1}return void 0===e.isHeartbeat||"boolean"==typeof e.isHeartbeat}static _validateArgs(e){return!!Array.isArray(e)}destroy(){if(!this._destroyed){this._destroyed=!0,window.removeEventListener("message",this._boundOnMessage),this._heartbeatTimer&&(clearInterval(this._heartbeatTimer),this._heartbeatTimer=void 0);for(const e of Array.from(this._pending.values()))clearTimeout(e.timer),e.reject(Error("CrossFrameAPI destroyed"));this._pending.clear(),this._cache.clear(),this._cacheTimestamps.clear(),this._eventListeners.clear()}}on(e,t){this._eventListeners.has(e)||this._eventListeners.set(e,new Set),this._eventListeners.get(e)?.add(t)}off(e,t){this._eventListeners.get(e)?.delete(t)}get connected(){return this._connected}_emit(e){this._eventListeners.get(e.type)?.forEach(t=>t(e))}_startHeartbeat(){this._heartbeatTimer&&clearInterval(this._heartbeatTimer),this._heartbeatTimer=setInterval(()=>{this._destroyed||(Date.now()-this._lastHeartbeatResponse>this._heartbeatTimeout&&this._connected&&(this._connected=!1,this._emit({type:"connectionLost"})),this._sendHeartbeat())},this._heartbeatInterval)}_sendHeartbeat(){const e=`hb-${Date.now()}-${this._counter++}`;this._targetWindow.postMessage({messageId:e,method:"__heartbeat__",params:[],isHeartbeat:!0},this._origin)}_post(e,t){if(this._destroyed)return Promise.reject(Error("CrossFrameAPI destroyed"));const r=`cfapi-${Date.now()}-${this._counter++}`,s=Date.now(),i=t.map(t=>{if("function"!=typeof t)return t;console.warn("Dropping function param when posting SCORM call:",e)});return new Promise((t,n)=>{const a=setTimeout(()=>{this._pending.has(r)&&(this._pending.delete(r),n(Error("Timeout calling "+e)))},this._timeout);this._pending.set(r,{resolve:t,reject:n,timer:a,requestTime:s,method:e}),this._targetWindow.postMessage({messageId:r,method:e,params:i},this._origin)})}_onMessage(t){if(this._destroyed)return;if("*"!==this._origin&&t.origin!==this._origin)return;if(t.source&&t.source!==this._targetWindow)return;if(!e._isValidMessageResponse(t.data))return;const r=t.data;if(r.isHeartbeat)return this._lastHeartbeatResponse=Date.now(),void(this._connected||(this._connected=!0,this._emit({type:"connectionRestored"})));const s=this._pending.get(r.messageId);s&&(clearTimeout(s.timer),this._pending.delete(r.messageId),r.error?("Rate limit exceeded"===r.error.message&&this._emit({type:"rateLimited",method:s.method}),s.reject(r.error)):s.resolve(r.result))}_capture(e,t){let r="Unknown error";t instanceof Error?r=t.message:"object"==typeof t&&null!==t&&"message"in t&&(r=t.message+""),console.error(`CrossFrameAPI ${e} error:`,t);const s=/(?:error code|code)?\s*(\d{3})\b/i.exec(r),i=s?.[1]??"101";this._lastError=i,this._cache.set("error_"+i,r)}}return e}();
1
+ this.CrossFrameAPI=function(){"use strict";var e=Object.defineProperty,t=(t,r,s)=>((t,r,s)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[r]=s)(t,"symbol"!=typeof r?r+"":r,s);class r{constructor(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"*",s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:window.parent,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return t(this,"_cache",new Map),t(this,"_cacheTimestamps",new Map),t(this,"_lastError","0"),t(this,"_pending",new Map),t(this,"_counter",0),t(this,"_origin"),t(this,"_targetWindow"),t(this,"_timeout"),t(this,"_heartbeatInterval"),t(this,"_heartbeatTimeout"),t(this,"_destroyed",!1),t(this,"_connected",!0),t(this,"_lastHeartbeatResponse",Date.now()),t(this,"_heartbeatTimer"),t(this,"_eventListeners",new Map),t(this,"_boundOnMessage"),t(this,"_handler",{get:(e,t,s)=>{if("string"!=typeof t||t in e){const r=Reflect.get(e,t,s);return"function"==typeof r?r.bind(e):r}const i=t,n=i.endsWith("GetValue"),a=i.startsWith("LMSSet")||i.endsWith("SetValue"),o="Initialize"===i||"LMSInitialize"===i,h="Terminate"===i||"LMSFinish"===i,c="Commit"===i||"LMSCommit"===i,_="GetErrorString"===i||"LMSGetErrorString"===i,d="GetDiagnostic"===i||"LMSGetDiagnostic"===i;return function(){for(var t=arguments.length,s=Array(t),g=0;t>g;g++)s[g]=arguments[g];if(!r._validateArgs(s))return console.error("CrossFrameAPI: Invalid arguments for "+i),"";if(a&&s.length>=2){const t=s[0];e._cache.set(t,s[1]+""),e._cacheTimestamps.set(t,Date.now()),e._lastError="0"}const l=Date.now();if(e._post(i,s).then(t=>{if(n&&s.length>=1){const r=s[0],i=e._cacheTimestamps.get(r)??0;l>i&&(e._cache.set(r,t+""),e._cacheTimestamps.delete(r)),e._lastError="0"}_&&s.length>=1&&e._cache.set("error_"+s[0],t+""),d&&s.length>=1&&e._cache.set("diag_"+s[0],t+""),"GetLastError"!==i&&"LMSGetLastError"!==i||(e._lastError=t+"")}).catch(t=>e._capture(i,t)),n&&s.length>=1)return e._cache.get(s[0])??"";if(_&&s.length>=1)return e._cache.get("error_"+s[0])??"";if(d&&s.length>=1)return e._cache.get("diag_"+s[0])??"";if(o||h||c||a){const result="true";return e._post("getFlattenedCMI",[]).then(t=>{t&&"object"==typeof t&&Object.entries(t).forEach(t=>{let[r,s]=t;const i=e._cacheTimestamps.get(r)??0;l>i&&(e._cache.set(r,s),e._cacheTimestamps.delete(r))}),e._lastError="0"}).catch(t=>e._capture("getFlattenedCMI",t)),result}return"GetLastError"===i||"LMSGetLastError"===i?e._lastError:""}}}),this._origin=e,this._targetWindow=s,this._timeout=i.timeout??5e3,this._heartbeatInterval=i.heartbeatInterval??3e4,this._heartbeatTimeout=i.heartbeatTimeout??6e4,"*"===e&&console.warn("CrossFrameAPI: Using wildcard origin ('*') allows any origin to receive messages. This is insecure for production use. Specify an explicit origin (e.g., 'https://lms.example.com') to restrict message recipients."),this._boundOnMessage=this._onMessage.bind(this),window.addEventListener("message",this._boundOnMessage),this._startHeartbeat(),new Proxy(this,this._handler)}static _isValidMessageResponse(e){if("object"!=typeof e||null===e)return!1;if("string"!=typeof e.messageId||0===e.messageId.length)return!1;if(void 0!==e.error){if("object"!=typeof e.error||null===e.error)return!1;const e=e.error;if("string"!=typeof e.message)return!1;if(void 0!==e.code&&"string"!=typeof e.code)return!1}return void 0===e.isHeartbeat||"boolean"==typeof e.isHeartbeat}static _validateArgs(e){return!!Array.isArray(e)}destroy(){if(!this._destroyed){this._destroyed=!0,window.removeEventListener("message",this._boundOnMessage),this._heartbeatTimer&&(clearInterval(this._heartbeatTimer),this._heartbeatTimer=void 0);for(const e of Array.from(this._pending.values()))clearTimeout(e.timer),e.reject(Error("CrossFrameAPI destroyed"));this._pending.clear(),this._cache.clear(),this._cacheTimestamps.clear(),this._eventListeners.clear()}}on(e,t){this._eventListeners.has(e)||this._eventListeners.set(e,new Set),this._eventListeners.get(e)?.add(t)}off(e,t){this._eventListeners.get(e)?.delete(t)}get connected(){return this._connected}_emit(e){this._eventListeners.get(e.type)?.forEach(t=>t(e))}_startHeartbeat(){this._heartbeatTimer&&clearInterval(this._heartbeatTimer),this._heartbeatTimer=setInterval(()=>{this._destroyed||(Date.now()-this._lastHeartbeatResponse>this._heartbeatTimeout&&this._connected&&(this._connected=!1,this._emit({type:"connectionLost"})),this._sendHeartbeat())},this._heartbeatInterval)}_sendHeartbeat(){const e=`hb-${Date.now()}-${this._counter++}`;this._targetWindow.postMessage({messageId:e,method:"__heartbeat__",params:[],isHeartbeat:!0},this._origin)}_post(e,t){if(this._destroyed)return Promise.reject(Error("CrossFrameAPI destroyed"));const r=`cfapi-${Date.now()}-${this._counter++}`,s=Date.now(),i=t.map(t=>{if("function"!=typeof t)return t;console.warn("Dropping function param when posting SCORM call:",e)});return new Promise((t,n)=>{const a=setTimeout(()=>{this._pending.has(r)&&(this._pending.delete(r),n(Error("Timeout calling "+e)))},this._timeout);this._pending.set(r,{resolve:t,reject:n,timer:a,requestTime:s,method:e}),this._targetWindow.postMessage({messageId:r,method:e,params:i},this._origin)})}_onMessage(e){if(this._destroyed)return;if("*"!==this._origin&&e.origin!==this._origin)return;if(e.source&&e.source!==this._targetWindow)return;if(!r._isValidMessageResponse(e.data))return;const t=e.data;if(t.isHeartbeat)return this._lastHeartbeatResponse=Date.now(),void(this._connected||(this._connected=!0,this._emit({type:"connectionRestored"})));const s=this._pending.get(t.messageId);s&&(clearTimeout(s.timer),this._pending.delete(t.messageId),t.error?("Rate limit exceeded"===t.error.message&&this._emit({type:"rateLimited",method:s.method}),s.reject(t.error)):s.resolve(t.result))}_capture(e,t){let r="Unknown error";t instanceof Error?r=t.message:"object"==typeof t&&null!==t&&"message"in t&&(r=t.message+""),console.error(`CrossFrameAPI ${e} error:`,t);const s=/(?:error code|code)?\s*(\d{3})\b/i.exec(r),i=s?.[1]??"101";this._lastError=i,this._cache.set("error_"+i,r)}}return r}();
2
2
  //# sourceMappingURL=cross-frame-api.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-frame-api.min.js","sources":["../src/CrossFrameAPI.ts"],"sourcesContent":["// 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":["CrossFrameAPI","constructor","targetOrigin","targetWindow","arguments","length","undefined","window","parent","options","__publicField","this","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","catch","err","_capture","result","all","Object","entries","forEach","_ref","val","_origin","_targetWindow","_timeout","timeout","_heartbeatInterval","heartbeatInterval","_heartbeatTimeout","heartbeatTimeout","warn","_boundOnMessage","_onMessage","addEventListener","_startHeartbeat","Proxy","_handler","_isValidMessageResponse","data","messageId","message","code","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","_lastHeartbeatResponse","_sendHeartbeat","_counter","postMessage","method","params","Promise","safeParams","map","p","resolve","setTimeout","ev","origin","source","errorMessage","match","exec"],"mappings":"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"}
@@ -1,6 +1,14 @@
1
1
  this.CrossFrameLMS = (function () {
2
2
  'use strict';
3
3
 
4
+ var __defProp = Object.defineProperty;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
6
+ enumerable: true,
7
+ configurable: true,
8
+ writable: true,
9
+ value
10
+ }) : obj[key] = value;
11
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
12
  const _CrossFrameLMS = class _CrossFrameLMS {
5
13
  /**
6
14
  * Creates a new CrossFrameLMS instance.
@@ -11,8 +19,12 @@ this.CrossFrameLMS = (function () {
11
19
  constructor(api) {
12
20
  let targetOrigin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "*";
13
21
  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
14
- this._requestTimes = [];
15
- this._destroyed = false;
22
+ __publicField(this, "_api");
23
+ __publicField(this, "_origin");
24
+ __publicField(this, "_rateLimit");
25
+ __publicField(this, "_requestTimes", []);
26
+ __publicField(this, "_destroyed", false);
27
+ __publicField(this, "_boundOnMessage");
16
28
  this._api = api;
17
29
  this._origin = targetOrigin;
18
30
  this._rateLimit = options.rateLimit ?? 100;
@@ -157,13 +169,13 @@ this.CrossFrameLMS = (function () {
157
169
  * Strict allowlist of methods that can be invoked via cross-frame messages.
158
170
  * Only SCORM API methods and internal helpers are permitted.
159
171
  */
160
- _CrossFrameLMS.ALLOWED_METHODS = /* @__PURE__ */new Set([
172
+ __publicField(_CrossFrameLMS, "ALLOWED_METHODS", /* @__PURE__ */new Set([
161
173
  // SCORM 1.2 methods
162
174
  "LMSInitialize", "LMSFinish", "LMSGetValue", "LMSSetValue", "LMSCommit", "LMSGetLastError", "LMSGetErrorString", "LMSGetDiagnostic",
163
175
  // SCORM 2004 methods
164
176
  "Initialize", "Terminate", "GetValue", "SetValue", "Commit", "GetLastError", "GetErrorString", "GetDiagnostic",
165
177
  // Internal method for cache warming
166
- "getFlattenedCMI"]);
178
+ "getFlattenedCMI"]));
167
179
  let CrossFrameLMS = _CrossFrameLMS;
168
180
 
169
181
  return CrossFrameLMS;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-frame-lms.js","sources":["../src/CrossFrameLMS.ts"],"sourcesContent":["// src/CrossFrameLMS.ts\nimport { CrossFrameLMSOptions, MessageData, MessageResponse } from \"./types/CrossFrame\";\nimport { IBaseAPI } from \"./interfaces/IBaseAPI\";\n\n/**\n * Server-side SCORM adapter running in your LMS frame (lms.example.com).\n * Listens for postMessage from child (content) frames, invokes real API,\n * and posts back { messageId, result, error }.\n */\nexport default class CrossFrameLMS {\n private readonly _api: IBaseAPI;\n private readonly _origin: string;\n private readonly _rateLimit: number;\n private _requestTimes: number[] = [];\n private _destroyed = false;\n private readonly _boundOnMessage: (ev: MessageEvent) => void;\n\n /**\n * Strict allowlist of methods that can be invoked via cross-frame messages.\n * Only SCORM API methods and internal helpers are permitted.\n */\n private static readonly ALLOWED_METHODS = new Set([\n // SCORM 1.2 methods\n \"LMSInitialize\",\n \"LMSFinish\",\n \"LMSGetValue\",\n \"LMSSetValue\",\n \"LMSCommit\",\n \"LMSGetLastError\",\n \"LMSGetErrorString\",\n \"LMSGetDiagnostic\",\n // SCORM 2004 methods\n \"Initialize\",\n \"Terminate\",\n \"GetValue\",\n \"SetValue\",\n \"Commit\",\n \"GetLastError\",\n \"GetErrorString\",\n \"GetDiagnostic\",\n // Internal method for cache warming\n \"getFlattenedCMI\",\n ]);\n\n /**\n * Creates a new CrossFrameLMS instance.\n * @param api - The SCORM API instance to delegate calls to\n * @param targetOrigin - Origin to accept messages from. Default \"*\" accepts all origins.\n * @param options - Configuration options\n */\n constructor(api: IBaseAPI, targetOrigin: string = \"*\", options: CrossFrameLMSOptions = {}) {\n this._api = api;\n this._origin = targetOrigin;\n this._rateLimit = options.rateLimit ?? 100;\n\n // Warn about wildcard origin security implications\n if (targetOrigin === \"*\") {\n console.warn(\n \"CrossFrameLMS: Using wildcard origin ('*') allows any origin to send messages. \" +\n \"This is insecure for production use. \" +\n \"Specify an explicit origin (e.g., 'https://content.example.com') to restrict message sources.\",\n );\n }\n\n this._boundOnMessage = this._onMessage.bind(this);\n window.addEventListener(\"message\", this._boundOnMessage);\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 this._requestTimes.length = 0;\n }\n\n /**\n * Checks if the rate limit has been exceeded.\n * Uses a sliding window of 1 second.\n * @returns true if rate limit exceeded, false otherwise\n */\n private _isRateLimited(): boolean {\n const now = Date.now();\n // Remove requests older than 1 second\n this._requestTimes = this._requestTimes.filter((t) => now - t < 1000);\n if (this._requestTimes.length >= this._rateLimit) {\n return true;\n }\n this._requestTimes.push(now);\n return false;\n }\n\n /**\n * Type guard to validate MessageData structure\n */\n private static _isValidMessageData(data: unknown): data is MessageData {\n if (typeof data !== \"object\" || data === null) return false;\n\n const msg = data as Partial<MessageData>;\n\n // Required fields\n if (typeof msg.messageId !== \"string\" || msg.messageId.length === 0) return false;\n if (typeof msg.method !== \"string\" || msg.method.length === 0) return false;\n\n // params must be an array if present (required for apply())\n if (msg.params !== undefined && !Array.isArray(msg.params)) return false;\n\n // isHeartbeat must be boolean if present\n if (msg.isHeartbeat !== undefined && typeof msg.isHeartbeat !== \"boolean\") return false;\n\n return true;\n }\n\n /**\n * Handles incoming postMessage events from child frames.\n */\n private _onMessage(ev: MessageEvent): void {\n // Ignore messages if destroyed\n if (this._destroyed) return;\n\n // Validate the message origin unless all origins are allowed\n if (this._origin !== \"*\" && ev.origin !== this._origin) {\n return;\n }\n\n // CF-LMS-02: Validate that ev.data has the expected MessageData structure\n if (!CrossFrameLMS._isValidMessageData(ev.data)) return;\n\n const msg = ev.data;\n if (!ev.source) return;\n\n // Validate that ev.source is a Window with postMessage capability\n // ev.source can be Window, MessagePort, ServiceWorker, or null\n // We only handle Window sources for iframe communication\n if (!(\"postMessage\" in ev.source)) return;\n\n const source = ev.source as Window;\n\n // Handle heartbeat separately (bypass rate limit and allowlist)\n if (msg.isHeartbeat) {\n const resp: MessageResponse = {\n messageId: msg.messageId,\n isHeartbeat: true,\n };\n source.postMessage(resp, this._origin);\n return;\n }\n\n // Check rate limit\n if (this._isRateLimited()) {\n // SCORM ERROR CODE USAGE: Using error code \"101\" for rate limiting\n //\n // While not a standard SCORM error code, \"101\" is used here to signal rate limiting\n // in cross-frame communication. Standard SCORM error codes are:\n // - SCORM 1.2: 0, 101, 201, 202, 203, 301, 401, 402, 403, 404, 405\n // - SCORM 2004: 0, 101, 102, 103, 201, 301, 351, 391, 401-408\n //\n // Using \"101\" (General Exception) is appropriate here because:\n // 1. It indicates a general error condition without exposing security details\n // 2. It's recognized by SCORM content as a non-zero error code\n // 3. It doesn't conflict with specific SCORM error semantics\n // 4. The actual error message \"Rate limit exceeded\" provides debug context\n //\n // The CrossFrameAPI detects this specific message to emit a \"rateLimited\" event,\n // allowing content to react appropriately (e.g., back off, show user message).\n const resp: MessageResponse = {\n messageId: msg.messageId,\n error: { message: \"Rate limit exceeded\", code: \"101\" },\n };\n source.postMessage(resp, this._origin);\n return;\n }\n\n // Check method allowlist\n if (!CrossFrameLMS.ALLOWED_METHODS.has(msg.method)) {\n const resp: MessageResponse = {\n messageId: msg.messageId,\n error: { message: `Method not allowed: ${msg.method}`, code: \"101\" },\n };\n source.postMessage(resp, this._origin);\n return;\n }\n\n this._process(msg, source);\n }\n\n /**\n * Processes a validated message by invoking the requested API method.\n */\n private _process(msg: MessageData, source: Window): void {\n const sendResponse = (result?: unknown, error?: { message: string; code?: string }) => {\n const resp: MessageResponse = { messageId: msg.messageId };\n if (result !== undefined) resp.result = result;\n if (error !== undefined) resp.error = error;\n source.postMessage(resp, this._origin);\n };\n\n try {\n const fn = (this._api as unknown as Record<string, unknown>)[msg.method];\n if (typeof fn !== \"function\") {\n sendResponse(undefined, { message: `Method ${msg.method} not found` });\n return;\n }\n\n // CF-LMS-01: Validate params is an array before apply()\n // This should never fail due to _isValidMessageData check, but defense in depth\n const params = Array.isArray(msg.params) ? msg.params : [];\n\n const result = fn.apply(this._api, params);\n\n if (result && typeof (result as Promise<unknown>).then === \"function\") {\n (result as Promise<unknown>)\n .then((r) => sendResponse(r))\n .catch((e: unknown) => {\n // ERROR CODE PRESERVATION: Extract error code from Error objects\n // SCORM API errors may include a numeric code property for specific error conditions.\n // We preserve this code to maintain proper error semantics across the cross-frame boundary.\n const message = e instanceof Error ? e.message : \"Unknown error\";\n const code =\n e && typeof e === \"object\" && \"code\" in e && typeof e.code === \"string\"\n ? e.code\n : undefined;\n const errorObj: { message: string; code?: string } = { message };\n if (code !== undefined) {\n errorObj.code = code;\n }\n sendResponse(undefined, errorObj);\n });\n } else {\n sendResponse(result);\n }\n } catch (e: unknown) {\n // ERROR CODE PRESERVATION: Extract error code from Error objects\n // SCORM API errors may include a numeric code property for specific error conditions.\n // We preserve this code to maintain proper error semantics across the cross-frame boundary.\n const message = e instanceof Error ? e.message : \"Unknown error\";\n const code =\n e && typeof e === \"object\" && \"code\" in e && typeof e.code === \"string\"\n ? e.code\n : undefined;\n const errorObj: { message: string; code?: string } = { message };\n if (code !== undefined) {\n errorObj.code = code;\n }\n sendResponse(undefined, errorObj);\n }\n }\n}\n"],"names":["_CrossFrameLMS","constructor","api","targetOrigin","arguments","length","undefined","options","__publicField","_api","_origin","_rateLimit","rateLimit","console","warn","_boundOnMessage","_onMessage","bind","window","addEventListener","destroy","_destroyed","removeEventListener","_requestTimes","_isRateLimited","now","Date","filter","t","push","_isValidMessageData","data","msg","messageId","method","params","Array","isArray","isHeartbeat","ev","origin","source","resp","postMessage","error","message","code","ALLOWED_METHODS","has","_process","sendResponse","result","fn","apply","then","r","catch","e","Error","errorObj","Set","CrossFrameLMS"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- this.CrossFrameLMS=function(){"use strict";const e=class e{constructor(e){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"*",t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};this._requestTimes=[],this._destroyed=!1,this._api=e,this._origin=s,this._rateLimit=t.rateLimit??100,"*"===s&&console.warn("CrossFrameLMS: Using wildcard origin ('*') allows any origin to send messages. This is insecure for production use. Specify an explicit origin (e.g., 'https://content.example.com') to restrict message sources."),this._boundOnMessage=this._onMessage.bind(this),window.addEventListener("message",this._boundOnMessage)}destroy(){this._destroyed||(this._destroyed=!0,window.removeEventListener("message",this._boundOnMessage),this._requestTimes.length=0)}_isRateLimited(){const e=Date.now();return this._requestTimes=this._requestTimes.filter(s=>1e3>e-s),this._requestTimes.length>=this._rateLimit||(this._requestTimes.push(e),!1)}static _isValidMessageData(e){if("object"!=typeof e||null===e)return!1;const s=e;return!("string"!=typeof s.messageId||0===s.messageId.length||"string"!=typeof s.method||0===s.method.length||void 0!==s.params&&!Array.isArray(s.params)||void 0!==s.isHeartbeat&&"boolean"!=typeof s.isHeartbeat)}_onMessage(s){if(this._destroyed)return;if("*"!==this._origin&&s.origin!==this._origin)return;if(!e._isValidMessageData(s.data))return;const t=s.data;if(!s.source)return;if(!("postMessage"in s.source))return;const i=s.source;t.isHeartbeat?i.postMessage({messageId:t.messageId,isHeartbeat:!0},this._origin):this._isRateLimited()?i.postMessage({messageId:t.messageId,error:{message:"Rate limit exceeded",code:"101"}},this._origin):e.ALLOWED_METHODS.has(t.method)?this._process(t,i):i.postMessage({messageId:t.messageId,error:{message:"Method not allowed: "+t.method,code:"101"}},this._origin)}_process(e,s){const t=(result,t)=>{const i={messageId:e.messageId};void 0!==result&&(i.result=result),void 0!==t&&(i.error=t),s.postMessage(i,this._origin)};try{const s=this._api[e.method];if("function"!=typeof s)return void t(void 0,{message:`Method ${e.method} not found`});const result=s.apply(this._api,Array.isArray(e.params)?e.params:[]);result&&"function"==typeof result.then?result.then(e=>t(e)).catch(e=>{const s=e&&"object"==typeof e&&"code"in e&&"string"==typeof e.code?e.code:void 0,i={message:e instanceof Error?e.message:"Unknown error"};void 0!==s&&(i.code=s),t(void 0,i)}):t(result)}catch(e){const s=e&&"object"==typeof e&&"code"in e&&"string"==typeof e.code?e.code:void 0,i={message:e instanceof Error?e.message:"Unknown error"};void 0!==s&&(i.code=s),t(void 0,i)}}};return e.ALLOWED_METHODS=new Set(["LMSInitialize","LMSFinish","LMSGetValue","LMSSetValue","LMSCommit","LMSGetLastError","LMSGetErrorString","LMSGetDiagnostic","Initialize","Terminate","GetValue","SetValue","Commit","GetLastError","GetErrorString","GetDiagnostic","getFlattenedCMI"]),e}();
1
+ this.CrossFrameLMS=function(){"use strict";var e=Object.defineProperty,s=(s,t,i)=>((s,t,i)=>t in s?e(s,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):s[t]=i)(s,"symbol"!=typeof t?t+"":t,i);const t=class e{constructor(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"*",i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};s(this,"_api"),s(this,"_origin"),s(this,"_rateLimit"),s(this,"_requestTimes",[]),s(this,"_destroyed",!1),s(this,"_boundOnMessage"),this._api=e,this._origin=t,this._rateLimit=i.rateLimit??100,"*"===t&&console.warn("CrossFrameLMS: Using wildcard origin ('*') allows any origin to send messages. This is insecure for production use. Specify an explicit origin (e.g., 'https://content.example.com') to restrict message sources."),this._boundOnMessage=this._onMessage.bind(this),window.addEventListener("message",this._boundOnMessage)}destroy(){this._destroyed||(this._destroyed=!0,window.removeEventListener("message",this._boundOnMessage),this._requestTimes.length=0)}_isRateLimited(){const e=Date.now();return this._requestTimes=this._requestTimes.filter(s=>1e3>e-s),this._requestTimes.length>=this._rateLimit||(this._requestTimes.push(e),!1)}static _isValidMessageData(e){if("object"!=typeof e||null===e)return!1;const s=e;return!("string"!=typeof s.messageId||0===s.messageId.length||"string"!=typeof s.method||0===s.method.length||void 0!==s.params&&!Array.isArray(s.params)||void 0!==s.isHeartbeat&&"boolean"!=typeof s.isHeartbeat)}_onMessage(s){if(this._destroyed)return;if("*"!==this._origin&&s.origin!==this._origin)return;if(!e._isValidMessageData(s.data))return;const t=s.data;if(!s.source)return;if(!("postMessage"in s.source))return;const i=s.source;t.isHeartbeat?i.postMessage({messageId:t.messageId,isHeartbeat:!0},this._origin):this._isRateLimited()?i.postMessage({messageId:t.messageId,error:{message:"Rate limit exceeded",code:"101"}},this._origin):e.ALLOWED_METHODS.has(t.method)?this._process(t,i):i.postMessage({messageId:t.messageId,error:{message:"Method not allowed: "+t.method,code:"101"}},this._origin)}_process(e,s){const t=(result,t)=>{const i={messageId:e.messageId};void 0!==result&&(i.result=result),void 0!==t&&(i.error=t),s.postMessage(i,this._origin)};try{const s=this._api[e.method];if("function"!=typeof s)return void t(void 0,{message:`Method ${e.method} not found`});const result=s.apply(this._api,Array.isArray(e.params)?e.params:[]);result&&"function"==typeof result.then?result.then(e=>t(e)).catch(e=>{const s=e&&"object"==typeof e&&"code"in e&&"string"==typeof e.code?e.code:void 0,i={message:e instanceof Error?e.message:"Unknown error"};void 0!==s&&(i.code=s),t(void 0,i)}):t(result)}catch(e){const s=e&&"object"==typeof e&&"code"in e&&"string"==typeof e.code?e.code:void 0,i={message:e instanceof Error?e.message:"Unknown error"};void 0!==s&&(i.code=s),t(void 0,i)}}};return s(t,"ALLOWED_METHODS",new Set(["LMSInitialize","LMSFinish","LMSGetValue","LMSSetValue","LMSCommit","LMSGetLastError","LMSGetErrorString","LMSGetDiagnostic","Initialize","Terminate","GetValue","SetValue","Commit","GetLastError","GetErrorString","GetDiagnostic","getFlattenedCMI"])),t}();
2
2
  //# sourceMappingURL=cross-frame-lms.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-frame-lms.min.js","sources":["../src/CrossFrameLMS.ts"],"sourcesContent":["// src/CrossFrameLMS.ts\nimport { CrossFrameLMSOptions, MessageData, MessageResponse } from \"./types/CrossFrame\";\nimport { IBaseAPI } from \"./interfaces/IBaseAPI\";\n\n/**\n * Server-side SCORM adapter running in your LMS frame (lms.example.com).\n * Listens for postMessage from child (content) frames, invokes real API,\n * and posts back { messageId, result, error }.\n */\nexport default class CrossFrameLMS {\n private readonly _api: IBaseAPI;\n private readonly _origin: string;\n private readonly _rateLimit: number;\n private _requestTimes: number[] = [];\n private _destroyed = false;\n private readonly _boundOnMessage: (ev: MessageEvent) => void;\n\n /**\n * Strict allowlist of methods that can be invoked via cross-frame messages.\n * Only SCORM API methods and internal helpers are permitted.\n */\n private static readonly ALLOWED_METHODS = new Set([\n // SCORM 1.2 methods\n \"LMSInitialize\",\n \"LMSFinish\",\n \"LMSGetValue\",\n \"LMSSetValue\",\n \"LMSCommit\",\n \"LMSGetLastError\",\n \"LMSGetErrorString\",\n \"LMSGetDiagnostic\",\n // SCORM 2004 methods\n \"Initialize\",\n \"Terminate\",\n \"GetValue\",\n \"SetValue\",\n \"Commit\",\n \"GetLastError\",\n \"GetErrorString\",\n \"GetDiagnostic\",\n // Internal method for cache warming\n \"getFlattenedCMI\",\n ]);\n\n /**\n * Creates a new CrossFrameLMS instance.\n * @param api - The SCORM API instance to delegate calls to\n * @param targetOrigin - Origin to accept messages from. Default \"*\" accepts all origins.\n * @param options - Configuration options\n */\n constructor(api: IBaseAPI, targetOrigin: string = \"*\", options: CrossFrameLMSOptions = {}) {\n this._api = api;\n this._origin = targetOrigin;\n this._rateLimit = options.rateLimit ?? 100;\n\n // Warn about wildcard origin security implications\n if (targetOrigin === \"*\") {\n console.warn(\n \"CrossFrameLMS: Using wildcard origin ('*') allows any origin to send messages. \" +\n \"This is insecure for production use. \" +\n \"Specify an explicit origin (e.g., 'https://content.example.com') to restrict message sources.\",\n );\n }\n\n this._boundOnMessage = this._onMessage.bind(this);\n window.addEventListener(\"message\", this._boundOnMessage);\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 this._requestTimes.length = 0;\n }\n\n /**\n * Checks if the rate limit has been exceeded.\n * Uses a sliding window of 1 second.\n * @returns true if rate limit exceeded, false otherwise\n */\n private _isRateLimited(): boolean {\n const now = Date.now();\n // Remove requests older than 1 second\n this._requestTimes = this._requestTimes.filter((t) => now - t < 1000);\n if (this._requestTimes.length >= this._rateLimit) {\n return true;\n }\n this._requestTimes.push(now);\n return false;\n }\n\n /**\n * Type guard to validate MessageData structure\n */\n private static _isValidMessageData(data: unknown): data is MessageData {\n if (typeof data !== \"object\" || data === null) return false;\n\n const msg = data as Partial<MessageData>;\n\n // Required fields\n if (typeof msg.messageId !== \"string\" || msg.messageId.length === 0) return false;\n if (typeof msg.method !== \"string\" || msg.method.length === 0) return false;\n\n // params must be an array if present (required for apply())\n if (msg.params !== undefined && !Array.isArray(msg.params)) return false;\n\n // isHeartbeat must be boolean if present\n if (msg.isHeartbeat !== undefined && typeof msg.isHeartbeat !== \"boolean\") return false;\n\n return true;\n }\n\n /**\n * Handles incoming postMessage events from child frames.\n */\n private _onMessage(ev: MessageEvent): void {\n // Ignore messages if destroyed\n if (this._destroyed) return;\n\n // Validate the message origin unless all origins are allowed\n if (this._origin !== \"*\" && ev.origin !== this._origin) {\n return;\n }\n\n // CF-LMS-02: Validate that ev.data has the expected MessageData structure\n if (!CrossFrameLMS._isValidMessageData(ev.data)) return;\n\n const msg = ev.data;\n if (!ev.source) return;\n\n // Validate that ev.source is a Window with postMessage capability\n // ev.source can be Window, MessagePort, ServiceWorker, or null\n // We only handle Window sources for iframe communication\n if (!(\"postMessage\" in ev.source)) return;\n\n const source = ev.source as Window;\n\n // Handle heartbeat separately (bypass rate limit and allowlist)\n if (msg.isHeartbeat) {\n const resp: MessageResponse = {\n messageId: msg.messageId,\n isHeartbeat: true,\n };\n source.postMessage(resp, this._origin);\n return;\n }\n\n // Check rate limit\n if (this._isRateLimited()) {\n // SCORM ERROR CODE USAGE: Using error code \"101\" for rate limiting\n //\n // While not a standard SCORM error code, \"101\" is used here to signal rate limiting\n // in cross-frame communication. Standard SCORM error codes are:\n // - SCORM 1.2: 0, 101, 201, 202, 203, 301, 401, 402, 403, 404, 405\n // - SCORM 2004: 0, 101, 102, 103, 201, 301, 351, 391, 401-408\n //\n // Using \"101\" (General Exception) is appropriate here because:\n // 1. It indicates a general error condition without exposing security details\n // 2. It's recognized by SCORM content as a non-zero error code\n // 3. It doesn't conflict with specific SCORM error semantics\n // 4. The actual error message \"Rate limit exceeded\" provides debug context\n //\n // The CrossFrameAPI detects this specific message to emit a \"rateLimited\" event,\n // allowing content to react appropriately (e.g., back off, show user message).\n const resp: MessageResponse = {\n messageId: msg.messageId,\n error: { message: \"Rate limit exceeded\", code: \"101\" },\n };\n source.postMessage(resp, this._origin);\n return;\n }\n\n // Check method allowlist\n if (!CrossFrameLMS.ALLOWED_METHODS.has(msg.method)) {\n const resp: MessageResponse = {\n messageId: msg.messageId,\n error: { message: `Method not allowed: ${msg.method}`, code: \"101\" },\n };\n source.postMessage(resp, this._origin);\n return;\n }\n\n this._process(msg, source);\n }\n\n /**\n * Processes a validated message by invoking the requested API method.\n */\n private _process(msg: MessageData, source: Window): void {\n const sendResponse = (result?: unknown, error?: { message: string; code?: string }) => {\n const resp: MessageResponse = { messageId: msg.messageId };\n if (result !== undefined) resp.result = result;\n if (error !== undefined) resp.error = error;\n source.postMessage(resp, this._origin);\n };\n\n try {\n const fn = (this._api as unknown as Record<string, unknown>)[msg.method];\n if (typeof fn !== \"function\") {\n sendResponse(undefined, { message: `Method ${msg.method} not found` });\n return;\n }\n\n // CF-LMS-01: Validate params is an array before apply()\n // This should never fail due to _isValidMessageData check, but defense in depth\n const params = Array.isArray(msg.params) ? msg.params : [];\n\n const result = fn.apply(this._api, params);\n\n if (result && typeof (result as Promise<unknown>).then === \"function\") {\n (result as Promise<unknown>)\n .then((r) => sendResponse(r))\n .catch((e: unknown) => {\n // ERROR CODE PRESERVATION: Extract error code from Error objects\n // SCORM API errors may include a numeric code property for specific error conditions.\n // We preserve this code to maintain proper error semantics across the cross-frame boundary.\n const message = e instanceof Error ? e.message : \"Unknown error\";\n const code =\n e && typeof e === \"object\" && \"code\" in e && typeof e.code === \"string\"\n ? e.code\n : undefined;\n const errorObj: { message: string; code?: string } = { message };\n if (code !== undefined) {\n errorObj.code = code;\n }\n sendResponse(undefined, errorObj);\n });\n } else {\n sendResponse(result);\n }\n } catch (e: unknown) {\n // ERROR CODE PRESERVATION: Extract error code from Error objects\n // SCORM API errors may include a numeric code property for specific error conditions.\n // We preserve this code to maintain proper error semantics across the cross-frame boundary.\n const message = e instanceof Error ? e.message : \"Unknown error\";\n const code =\n e && typeof e === \"object\" && \"code\" in e && typeof e.code === \"string\"\n ? e.code\n : undefined;\n const errorObj: { message: string; code?: string } = { message };\n if (code !== undefined) {\n errorObj.code = code;\n }\n sendResponse(undefined, errorObj);\n }\n }\n}\n"],"names":["_CrossFrameLMS","constructor","api","targetOrigin","arguments","length","undefined","options","__publicField","this","_api","_origin","_rateLimit","rateLimit","console","warn","_boundOnMessage","_onMessage","bind","window","addEventListener","destroy","_destroyed","removeEventListener","_requestTimes","_isRateLimited","now","Date","filter","t","push","_isValidMessageData","data","msg","messageId","method","params","Array","isArray","isHeartbeat","ev","origin","source","postMessage","error","message","code","ALLOWED_METHODS","has","_process","sendResponse","result","resp","fn","apply","then","r","catch","e","errorObj","Error","Set"],"mappings":"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"}