You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 22:54:17