scorm-again 3.1.0 → 3.1.1
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.
- package/dist/cross-frame-api.js +579 -320
- package/dist/cross-frame-api.js.map +1 -1
- package/dist/cross-frame-api.min.js +1 -1
- package/dist/cross-frame-api.min.js.map +1 -1
- package/dist/cross-frame-lms.js +226 -163
- package/dist/cross-frame-lms.js.map +1 -1
- package/dist/cross-frame-lms.min.js +1 -1
- package/dist/cross-frame-lms.min.js.map +1 -1
- package/dist/esm/scorm-again.js +3 -15
- package/dist/esm/scorm-again.js.map +1 -1
- package/dist/esm/scorm-again.min.js +1 -1
- package/dist/esm/scorm-again.min.js.map +1 -1
- package/dist/esm/scorm12.js +1 -7
- package/dist/esm/scorm12.js.map +1 -1
- package/dist/esm/scorm12.min.js +1 -1
- package/dist/esm/scorm12.min.js.map +1 -1
- package/dist/esm/scorm2004.js +2 -8
- package/dist/esm/scorm2004.js.map +1 -1
- package/dist/esm/scorm2004.min.js +1 -1
- package/dist/esm/scorm2004.min.js.map +1 -1
- package/dist/scorm-again.js +32455 -20215
- package/dist/scorm-again.js.map +1 -1
- package/dist/scorm-again.min.js +1 -1
- package/dist/scorm-again.min.js.map +1 -1
- package/dist/scorm12.js +9080 -5233
- package/dist/scorm12.js.map +1 -1
- package/dist/scorm12.min.js +1 -1
- package/dist/scorm12.min.js.map +1 -1
- package/dist/scorm2004.js +29973 -18633
- package/dist/scorm2004.js.map +1 -1
- package/dist/scorm2004.min.js +1 -1
- package/dist/scorm2004.min.js.map +1 -1
- package/package.json +21 -21
|
@@ -1 +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
|
+
{"version":3,"file":"cross-frame-lms.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
this.CrossFrameLMS=function(){"use strict";var
|
|
1
|
+
this.CrossFrameLMS=function(){"use strict";function e(e,t){for(var i=0;t.length>i;i++){var s=t[i];s.enumerable=s.enumerable||!1,s.configurable=!0,"value"in s&&(s.writable=!0),Object.defineProperty(e,s.key,s)}}function t(e,t){return null!=t&&"undefined"!=typeof Symbol&&t[Symbol.hasInstance]?!!t[Symbol.hasInstance](e):e instanceof t}function i(e){return e&&"undefined"!=typeof Symbol&&e.constructor===Symbol?"symbol":typeof e}var s=Object.defineProperty,n=function(e,t,n){return function(e,t,i){return t in e?s(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i}(e,"symbol"!==(void 0===t?"undefined":i(t))?t+"":t,n)},o=function(){function s(e){var t,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"*",o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,s),n(this,"_api"),n(this,"_origin"),n(this,"_rateLimit"),n(this,"_requestTimes",[]),n(this,"_destroyed",!1),n(this,"_boundOnMessage"),this._api=e,this._origin=i,this._rateLimit=null!==(t=o.rateLimit)&&void 0!==t?t:100,"*"===i&&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)}var o,r,a;return o=s,a=[{key:"_isValidMessageData",value:function(e){if("object"!==(void 0===e?"undefined":i(e))||null===e)return!1;var t=e;return!("string"!=typeof t.messageId||0===t.messageId.length||"string"!=typeof t.method||0===t.method.length||void 0!==t.params&&!Array.isArray(t.params)||void 0!==t.isHeartbeat&&"boolean"!=typeof t.isHeartbeat)}}],(r=[{key:"destroy",value:function(){this._destroyed||(this._destroyed=!0,window.removeEventListener("message",this._boundOnMessage),this._requestTimes.length=0)}},{key:"_isRateLimited",value:function(){var e=Date.now();return this._requestTimes=this._requestTimes.filter(function(t){return 1e3>e-t}),this._requestTimes.length>=this._rateLimit||(this._requestTimes.push(e),!1)}},{key:"_onMessage",value:function(e){if(!this._destroyed&&("*"===this._origin||e.origin===this._origin)&&s._isValidMessageData(e.data)){var t=e.data;if(e.source&&"postMessage"in e.source){var i=e.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):s.ALLOWED_METHODS.has(t.method)?this._process(t,i):i.postMessage({messageId:t.messageId,error:{message:"Method not allowed: ".concat(t.method),code:"101"}},this._origin)}}}},{key:"_process",value:function(e,s){var n=this,o=function(result,t){var i={messageId:e.messageId};void 0!==result&&(i.result=result),void 0!==t&&(i.error=t),s.postMessage(i,n._origin)};try{var r=this._api[e.method];if("function"!=typeof r)return void o(void 0,{message:"Method ".concat(e.method," not found")});var result=r.apply(this._api,Array.isArray(e.params)?e.params:[]);result&&"function"==typeof result.then?result.then(function(e){return o(e)}).catch(function(e){var s=t(e,Error)?e.message:"Unknown error",n=e&&"object"===(void 0===e?"undefined":i(e))&&"code"in e&&"string"==typeof e.code?e.code:void 0,r={message:s};void 0!==n&&(r.code=n),o(void 0,r)}):o(result)}catch(e){var a=t(e,Error)?e.message:"Unknown error",d=e&&"object"===(void 0===e?"undefined":i(e))&&"code"in e&&"string"==typeof e.code?e.code:void 0,u={message:a};void 0!==d&&(u.code=d),o(void 0,u)}}}])&&e(o.prototype,r),a&&e(o,a),s}();return n(o,"ALLOWED_METHODS",new Set(["LMSInitialize","LMSFinish","LMSGetValue","LMSSetValue","LMSCommit","LMSGetLastError","LMSGetErrorString","LMSGetDiagnostic","Initialize","Terminate","GetValue","SetValue","Commit","GetLastError","GetErrorString","GetDiagnostic","getFlattenedCMI"])),o}();
|
|
2
2
|
//# sourceMappingURL=cross-frame-lms.min.js.map
|
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"cross-frame-lms.min.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/esm/scorm-again.js
CHANGED
|
@@ -18917,18 +18917,12 @@ class Scorm12API extends BaseAPI {
|
|
|
18917
18917
|
if (terminateCommit || includeTotalTime) {
|
|
18918
18918
|
cmiExport.cmi.core.total_time = this.cmi.getCurrentTotalTime();
|
|
18919
18919
|
}
|
|
18920
|
-
const result = [];
|
|
18921
18920
|
const flattened = flatten(cmiExport);
|
|
18922
18921
|
switch (this.settings.dataCommitFormat) {
|
|
18923
18922
|
case "flattened":
|
|
18924
18923
|
return flattened;
|
|
18925
18924
|
case "params":
|
|
18926
|
-
|
|
18927
|
-
if ({}.hasOwnProperty.call(flattened, item)) {
|
|
18928
|
-
result.push(`${item}=${flattened[item]}`);
|
|
18929
|
-
}
|
|
18930
|
-
}
|
|
18931
|
-
return result;
|
|
18925
|
+
return Object.entries(flattened).map(([item, value]) => `${item}=${value}`);
|
|
18932
18926
|
case "json":
|
|
18933
18927
|
default:
|
|
18934
18928
|
return cmiExport;
|
|
@@ -24502,19 +24496,13 @@ class Scorm2004DataSerializer {
|
|
|
24502
24496
|
} else {
|
|
24503
24497
|
delete cmiExport.cmi.total_time;
|
|
24504
24498
|
}
|
|
24505
|
-
const result = [];
|
|
24506
24499
|
const flattened = flatten(cmiExport);
|
|
24507
24500
|
const settings = this.context.getSettings();
|
|
24508
24501
|
switch (settings.dataCommitFormat) {
|
|
24509
24502
|
case "flattened":
|
|
24510
|
-
return
|
|
24503
|
+
return flattened;
|
|
24511
24504
|
case "params":
|
|
24512
|
-
|
|
24513
|
-
if ({}.hasOwnProperty.call(flattened, item)) {
|
|
24514
|
-
result.push(`${item}=${flattened[item]}`);
|
|
24515
|
-
}
|
|
24516
|
-
}
|
|
24517
|
-
return result;
|
|
24505
|
+
return Object.entries(flattened).map(([item, value]) => `${item}=${value}`);
|
|
24518
24506
|
case "json":
|
|
24519
24507
|
default:
|
|
24520
24508
|
return cmiExport;
|