如何在JavaScript中获取SCORM事件并存储至数据库?求可用NPM包
SCORM LMS开发问题与解决方案推荐
问题背景
我正在基于SCORM开发LMS应用,但SCORM官方文档过于复杂。找到的示例代码集成到本地系统后完全无法正常运行,核心需求是在JavaScript中捕获所有SCORM事件并保存到数据库,寻求适用于Angular或React的可用NPM包。相关示例文件可从指定文件夹获取,需从res文件夹提取index文件。
现有示例代码问题分析
你提供的示例代码存在以下关键问题,导致无法正常运行:
LMSInitialize方法错误调用未定义的this.api.LMSInitialize,直接引发报错- 交互计数更新时硬编码占位符
{{scorm.id}},未使用传入的前缀变量,导致存储键异常 - 缺少SCORM规范要求的错误处理返回值,无法完成合法的API交互
- 跨iframe域名不一致时,可能存在SCORM API访问权限问题
原父页面代码
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Trivial SCORM Player Example</title> <script src="tsp.js"></script> <script> function supports_html5_storage() { try { return 'localStorage' in window && window['localStorage'] !== null; } catch (e) { return false; } } //users of old browsers will not be able to save their progress localy (but they will be able to store it server side) if (!supports_html5_storage()){ window.localStorage = {}; } tspInit( window, window.localStorage, //this has to be unique per each scorm you serve 'SCORM_ID.', function(progress){ //this will be called whenever student makes a progress in test. console.log(progress); }); </script> </head> <body> <iframe src="cb980550-6523-48ea-9825-23dca8bb0219/res/index.html" name="course" frameborder="0" style="overflow:hidden;overflow-x:hidden;overflow-y:hidden;height:600px;width:800px" height="600" width="800"> </iframe> </body> </html>
原tsp.js代码
function tspInit(window, storage, prefix, callback){ prefix = typeof prefix !== 'undefined' ? prefix : ''; callback = typeof callback !== 'undefined' ? callback : console.log; debugger window.API = {}; window.scormStatus = { lesson_status: '', score_raw: 0, score_max: 100, score_min: 0, session_time: 0, detailed_answers: {} }; window.API.LMSInitialize = function(arg){ var success = this.api.LMSInitialize(arg); console.log(success, 'LMSInitialize'); } window.API.LMSTerminate = function() { console.log('LMSTerminate'); } window.API.LMSGetValue = function(varname) { varname = prefix + varname; var ret = storage.getItem(varname); if (ret == null && (varname.indexOf('_count', this.length - '_count'.length) !== -1)) { ret = 0; storage.setItem(varname, ret); } console.log('LMSGetValue', varname, '=', ret); return ret; } window.API.LMSSetValue = function(varname, varvalue) { varname = prefix + varname; var m = varname.match(/([0-9]+)\.cmi\.interactions\.([0-9]+)\.id/); if (m != null) { storage.setItem('{{scorm.id}}.cmi.interactions._count', parseInt(m[2]) + 1); } m = varname.match(/([0-9]+)\.cmi\.interactions\.([0-9]+)\.result/); if (m != null) { var key = storage.getItem(prefix + 'cmi.interactions.' + parseInt(m[2]) + '.id'); window.scormStatus.detailed_answers[key] = varvalue; } if (varname == prefix + 'cmi.core.lesson_status') window.scormStatus.lesson_status = varvalue; if (varname == prefix + 'cmi.core.score.raw') window.scormStatus.score_raw = varvalue; if (varname == prefix + 'cmi.core.score.max') window.scormStatus.score_max = varvalue; if (varname == prefix + 'cmi.core.score.min') window.scormStatus.score_min = varvalue; if (varname == prefix + 'cmi.core.session_time') window.scormStatus.session_time = varvalue; storage.setItem(varname, varvalue); console.log('LMSSetValue', varname, '=', varvalue); } window.API.LMSCommit = function() { console.log('LMSCommit'); //saving to API callback(window.scormStatus); return true; } window.API.LMSFinish = function() { console.log('LMSFinish'); } window.API.LMSGetLastError = function() { console.log('LMSGetLastError'); } window.API.LMSGetErrorString = function() { console.log('LMSGetErrorString'); } window.API.LMSGetDiagnostic = function() { console.log('LMSGetDiagnostic'); } }
适用于Angular/React的NPM包推荐
React生态
- react-scorm-provider:基于React上下文封装SCORM API,提供hooks快速获取SCORM状态,自动监听事件并同步数据,直接支持将状态提交到后端数据库
- scorm-api-wrapper:轻量级SCORM API封装库,Promise化所有方法调用,简化事件监听逻辑,可在React组件中直接集成
Angular生态
- ngx-scorm:Angular专属SCORM集成服务,提供组件级状态订阅,内置错误处理与数据持久化逻辑,可通过依赖注入快速接入
- scorm-wrapper:通用SCORM封装库,可封装为Angular服务使用,支持监听SCORM事件并将数据同步到后端
修复后的代码示例
修复后的tsp.js
function tspInit(window, storage, prefix, callback){ prefix = typeof prefix !== 'undefined' ? prefix : ''; callback = typeof callback !== 'undefined' ? callback : console.log; window.API = {}; window.scormStatus = { lesson_status: '', score_raw: 0, score_max: 100, score_min: 0, session_time: 0, detailed_answers: {} }; // 修复初始化方法,返回SCORM规范要求的true window.API.LMSInitialize = function(arg){ console.log('LMSInitialize called with:', arg); return true; } window.API.LMSTerminate = function() { console.log('LMSTerminate'); return true; } window.API.LMSGetValue = function(varname) { const fullVarName = prefix + varname; let ret = storage.getItem(fullVarName); // 处理_count类型变量,默认返回0字符串(符合SCORM类型要求) if (ret == null && varname.endsWith('_count')) { ret = '0'; storage.setItem(fullVarName, ret); } console.log('LMSGetValue', fullVarName, '=', ret); return ret; } window.API.LMSSetValue = function(varname, varvalue) { const fullVarName = prefix + varname; // 修复交互计数更新逻辑,使用正确的前缀变量 const idMatch = fullVarName.match(new RegExp(`${prefix}cmi\\.interactions\\.([0-9]+)\\.id`)); if (idMatch != null) { storage.setItem(`${prefix}cmi.interactions._count`, parseInt(idMatch[1]) + 1); } const resultMatch = fullVarName.match(new RegExp(`${prefix}cmi\\.interactions\\.([0-9]+)\\.result`)); if (resultMatch != null) { const key = storage.getItem(`${prefix}cmi.interactions.${resultMatch[1]}.id`); window.scormStatus.detailed_answers[key] = varvalue; } // 更新状态对象 if (fullVarName === `${prefix}cmi.core.lesson_status`) window.scormStatus.lesson_status = varvalue; if (fullVarName === `${prefix}cmi.core.score.raw`) window.scormStatus.score_raw = varvalue; if (fullVarName === `${prefix}cmi.core.score.max`) window.scormStatus.score_max = varvalue; if (fullVarName === `${prefix}cmi.core.score.min`) window.scormStatus.score_min = varvalue; if (fullVarName === `${prefix}cmi.core.session_time`) window.scormStatus.session_time = varvalue; storage.setItem(fullVarName, varvalue); console.log('LMSSetValue', fullVarName, '=', varvalue); return true; } window.API.LMSCommit = function() { console.log('LMSCommit'); // 在此回调中添加数据库保存逻辑 callback(window.scormStatus); return true; } window.API.LMSFinish = function() { console.log('LMSFinish'); return true; } window.API.LMSGetLastError = function() { console.log('LMSGetLastError'); return '0'; // 无错误代码 } window.API.LMSGetErrorString = function(errorCode) { console.log('LMSGetErrorString for code:', errorCode); return 'No Error'; } window.API.LMSGetDiagnostic = function(errorCode) { console.log('LMSGetDiagnostic for code:', errorCode); return ''; } }
修复后的父页面代码
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Trivial SCORM Player Example</title> <script src="tsp.js"></script> <script> function supports_html5_storage() { try { return 'localStorage' in window && window['localStorage'] !== null; } catch (e) { return false; } } // 为不支持localStorage的浏览器提供空实现 if (!supports_html5_storage()){ window.localStorage = { getItem: () => null, setItem: () => {} }; } tspInit( window, window.localStorage, 'SCORM_ID.', function(progress){ // 替换为实际的数据库保存逻辑 console.log('需要保存的学习进度:', progress); // 示例: // fetch('/api/lms/progress', { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify(progress) // }); }); </script> </head> <body> <iframe src="cb980550-6523-48ea-9825-23dca8bb0219/res/index.html" name="course" frameborder="0" style="overflow:hidden;overflow-x:hidden;overflow-y:hidden;height:600px;width:800px" height="600" width="800"> </iframe> </body> </html>
内容的提问来源于stack exchange,提问作者Sajin Satheesan
相关产品推荐
相关产品推荐

