Add SCORM API shim for proper content communication
SCORM content expects window.API (1.2) or window.API_1484_11 (2004) on the parent frame. Without it, content fails to initialize. Implements all 8 required methods for both SCORM versions: - Initialize/LMSInitialize, Terminate/LMSFinish - GetValue/LMSGetValue, SetValue/LMSSetValue - Commit/LMSCommit, error methods - Auto-marks lesson complete when content reports completion - Removed iframe sandbox to allow parent frame API access Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
db98c6ae69
commit
84c899a814
1 changed files with 88 additions and 5 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { useParams, useNavigate, Link } from 'react-router-dom'
|
||||
import Markdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
|
@ -9,6 +9,85 @@ import 'katex/dist/katex.min.css'
|
|||
import { useAuth } from '../context/AuthContext'
|
||||
import api from '../api/client'
|
||||
|
||||
// ── SCORM Player with API shim ──────────────────────────────────────
|
||||
function ScormPlayer({ src, title, lessonId, courseId, onComplete }) {
|
||||
const iframeRef = useRef(null)
|
||||
const dataRef = useRef({})
|
||||
const completedRef = useRef(false)
|
||||
|
||||
const setupApi = useCallback(() => {
|
||||
const data = dataRef.current
|
||||
// SCORM 1.2 API
|
||||
const API = {
|
||||
LMSInitialize: () => { return "true" },
|
||||
LMSFinish: () => {
|
||||
const status = data["cmi.core.lesson_status"]
|
||||
if ((status === "completed" || status === "passed") && !completedRef.current) {
|
||||
completedRef.current = true
|
||||
onComplete?.()
|
||||
}
|
||||
return "true"
|
||||
},
|
||||
LMSGetValue: (key) => data[key] || "",
|
||||
LMSSetValue: (key, val) => { data[key] = val; return "true" },
|
||||
LMSCommit: () => {
|
||||
const status = data["cmi.core.lesson_status"]
|
||||
if ((status === "completed" || status === "passed") && !completedRef.current) {
|
||||
completedRef.current = true
|
||||
onComplete?.()
|
||||
}
|
||||
return "true"
|
||||
},
|
||||
LMSGetLastError: () => "0",
|
||||
LMSGetErrorString: () => "No error",
|
||||
LMSGetDiagnostic: () => "No diagnostic",
|
||||
}
|
||||
// SCORM 2004 API
|
||||
const API_1484_11 = {
|
||||
Initialize: () => "true",
|
||||
Terminate: () => {
|
||||
const status = data["cmi.completion_status"]
|
||||
if ((status === "completed") && !completedRef.current) {
|
||||
completedRef.current = true
|
||||
onComplete?.()
|
||||
}
|
||||
return "true"
|
||||
},
|
||||
GetValue: (key) => data[key] || "",
|
||||
SetValue: (key, val) => { data[key] = val; return "true" },
|
||||
Commit: () => {
|
||||
const status = data["cmi.completion_status"]
|
||||
if ((status === "completed") && !completedRef.current) {
|
||||
completedRef.current = true
|
||||
onComplete?.()
|
||||
}
|
||||
return "true"
|
||||
},
|
||||
GetLastError: () => "0",
|
||||
GetErrorString: () => "No error",
|
||||
GetDiagnostic: () => "No diagnostic",
|
||||
}
|
||||
// Expose on window so iframe content can find it by walking up parent frames
|
||||
window.API = API
|
||||
window.API_1484_11 = API_1484_11
|
||||
}, [onComplete])
|
||||
|
||||
useEffect(() => {
|
||||
setupApi()
|
||||
return () => { delete window.API; delete window.API_1484_11 }
|
||||
}, [setupApi])
|
||||
|
||||
return (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={src}
|
||||
style={{ width: '100%', height: 600, border: '1px solid var(--border)', borderRadius: 8 }}
|
||||
title={title}
|
||||
allow="fullscreen"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const LESSON_ICONS = {
|
||||
text: '\u{1F4C4}',
|
||||
video: '\u{1F3AC}',
|
||||
|
|
@ -475,12 +554,16 @@ export default function CourseDetailPage() {
|
|||
{!loadingLesson && activeLesson.lesson_type === 'scorm_package' && (
|
||||
<div>
|
||||
{activeLesson.local_file_path ? (
|
||||
<iframe
|
||||
<ScormPlayer
|
||||
src={activeLesson.local_file_path}
|
||||
style={{ width: '100%', height: 600, border: '1px solid var(--border)', borderRadius: 8 }}
|
||||
title={activeLesson.title}
|
||||
allow="fullscreen"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
||||
lessonId={activeLesson.id}
|
||||
courseId={courseId}
|
||||
onComplete={() => {
|
||||
if (!completedLessons.has(activeLesson.id)) {
|
||||
markComplete(activeLesson.id)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<p style={{ color: 'var(--text-muted)', textAlign: 'center', padding: 20 }}>No SCORM package uploaded.</p>
|
||||
|
|
|
|||
Loading…
Reference in a new issue