scorm-again 3.1.0 → 3.1.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.
- 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 -21
- 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 -14
- 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 +32441 -20207
- 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 +29962 -18628
- 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
|
@@ -10996,12 +10996,6 @@ class TerminationHandler {
|
|
|
10996
10996
|
}
|
|
10997
10997
|
}
|
|
10998
10998
|
} while (processedExit);
|
|
10999
|
-
if (!hasSequencingRequest && !postConditionResult.sequencingRequest) {
|
|
11000
|
-
const current = this.activityTree.currentActivity || currentActivity;
|
|
11001
|
-
if (current.parent) {
|
|
11002
|
-
this.activityTree.setCurrentActivityWithoutActivation(current.parent);
|
|
11003
|
-
}
|
|
11004
|
-
}
|
|
11005
10999
|
return {
|
|
11006
11000
|
terminationRequest: SequencingRequestType.EXIT,
|
|
11007
11001
|
sequencingRequest: postConditionResult.sequencingRequest,
|
|
@@ -18917,18 +18911,12 @@ class Scorm12API extends BaseAPI {
|
|
|
18917
18911
|
if (terminateCommit || includeTotalTime) {
|
|
18918
18912
|
cmiExport.cmi.core.total_time = this.cmi.getCurrentTotalTime();
|
|
18919
18913
|
}
|
|
18920
|
-
const result = [];
|
|
18921
18914
|
const flattened = flatten(cmiExport);
|
|
18922
18915
|
switch (this.settings.dataCommitFormat) {
|
|
18923
18916
|
case "flattened":
|
|
18924
18917
|
return flattened;
|
|
18925
18918
|
case "params":
|
|
18926
|
-
|
|
18927
|
-
if ({}.hasOwnProperty.call(flattened, item)) {
|
|
18928
|
-
result.push(`${item}=${flattened[item]}`);
|
|
18929
|
-
}
|
|
18930
|
-
}
|
|
18931
|
-
return result;
|
|
18919
|
+
return Object.entries(flattened).map(([item, value]) => `${item}=${value}`);
|
|
18932
18920
|
case "json":
|
|
18933
18921
|
default:
|
|
18934
18922
|
return cmiExport;
|
|
@@ -24502,19 +24490,13 @@ class Scorm2004DataSerializer {
|
|
|
24502
24490
|
} else {
|
|
24503
24491
|
delete cmiExport.cmi.total_time;
|
|
24504
24492
|
}
|
|
24505
|
-
const result = [];
|
|
24506
24493
|
const flattened = flatten(cmiExport);
|
|
24507
24494
|
const settings = this.context.getSettings();
|
|
24508
24495
|
switch (settings.dataCommitFormat) {
|
|
24509
24496
|
case "flattened":
|
|
24510
|
-
return
|
|
24497
|
+
return flattened;
|
|
24511
24498
|
case "params":
|
|
24512
|
-
|
|
24513
|
-
if ({}.hasOwnProperty.call(flattened, item)) {
|
|
24514
|
-
result.push(`${item}=${flattened[item]}`);
|
|
24515
|
-
}
|
|
24516
|
-
}
|
|
24517
|
-
return result;
|
|
24499
|
+
return Object.entries(flattened).map(([item, value]) => `${item}=${value}`);
|
|
24518
24500
|
case "json":
|
|
24519
24501
|
default:
|
|
24520
24502
|
return cmiExport;
|