From 84c899a8141e422d91a8618670f391ba1a84625e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 11 Apr 2026 01:36:44 +0200 Subject: [PATCH] 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) --- frontend/src/pages/CourseDetailPage.jsx | 93 +++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/CourseDetailPage.jsx b/frontend/src/pages/CourseDetailPage.jsx index 8c42e2d..2fd665c 100644 --- a/frontend/src/pages/CourseDetailPage.jsx +++ b/frontend/src/pages/CourseDetailPage.jsx @@ -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 ( +