coursecode 0.1.0
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/LICENSE +21 -0
- package/README.md +322 -0
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/bin/cli.js +331 -0
- package/framework/assets/logo-coursecode-black.svg +14 -0
- package/framework/assets/logo-coursecode-white.svg +14 -0
- package/framework/assets/logo-coursecode.svg +14 -0
- package/framework/css/01-base.css +160 -0
- package/framework/css/02-layout.css +499 -0
- package/framework/css/accessibility.css +834 -0
- package/framework/css/components/accordions.css +710 -0
- package/framework/css/components/assessments.css +520 -0
- package/framework/css/components/audio-player.css +570 -0
- package/framework/css/components/badges.css +80 -0
- package/framework/css/components/breadcrumbs.css +87 -0
- package/framework/css/components/buttons.css +707 -0
- package/framework/css/components/callouts.css +1280 -0
- package/framework/css/components/cards.css +475 -0
- package/framework/css/components/carousel.css +193 -0
- package/framework/css/components/checkbox-group.css +123 -0
- package/framework/css/components/checklist.css +203 -0
- package/framework/css/components/collapse.css +96 -0
- package/framework/css/components/comparison.css +33 -0
- package/framework/css/components/content-image.css +36 -0
- package/framework/css/components/document-gallery.css +425 -0
- package/framework/css/components/dropdown.css +115 -0
- package/framework/css/components/embed-frame.css +142 -0
- package/framework/css/components/engagement.css +412 -0
- package/framework/css/components/features.css +35 -0
- package/framework/css/components/flip-cards.css +253 -0
- package/framework/css/components/footer.css +353 -0
- package/framework/css/components/forms.css +294 -0
- package/framework/css/components/hero.css +216 -0
- package/framework/css/components/images.css +528 -0
- package/framework/css/components/interactive-timeline.css +274 -0
- package/framework/css/components/intro-cards.css +30 -0
- package/framework/css/components/lightbox.css +666 -0
- package/framework/css/components/loading.css +65 -0
- package/framework/css/components/modals.css +235 -0
- package/framework/css/components/notifications.css +107 -0
- package/framework/css/components/quote.css +150 -0
- package/framework/css/components/sidebar.css +684 -0
- package/framework/css/components/slide-header.css +52 -0
- package/framework/css/components/spinner.css +62 -0
- package/framework/css/components/stats.css +44 -0
- package/framework/css/components/steps.css +232 -0
- package/framework/css/components/tables.css +90 -0
- package/framework/css/components/tabs.css +347 -0
- package/framework/css/components/timeline.css +154 -0
- package/framework/css/components/toggle.css +95 -0
- package/framework/css/components/tooltip.css +226 -0
- package/framework/css/components/video-player.css +438 -0
- package/framework/css/design-tokens.css +707 -0
- package/framework/css/framework.css +86 -0
- package/framework/css/interactions/accessibility.css +75 -0
- package/framework/css/interactions/base.css +92 -0
- package/framework/css/interactions/drag-drop.css +295 -0
- package/framework/css/interactions/fill-in-the-blank.css +236 -0
- package/framework/css/interactions/hotspots.css +69 -0
- package/framework/css/interactions/index.css +45 -0
- package/framework/css/interactions/interactive-image.css +359 -0
- package/framework/css/interactions/likert.css +126 -0
- package/framework/css/interactions/matching.css +354 -0
- package/framework/css/interactions/numeric-input.css +78 -0
- package/framework/css/interactions/sequencing.css +378 -0
- package/framework/css/interactions/true-false.css +177 -0
- package/framework/css/layouts/article.css +258 -0
- package/framework/css/layouts/base.css +30 -0
- package/framework/css/layouts/canvas.css +38 -0
- package/framework/css/layouts/focused.css +236 -0
- package/framework/css/layouts/index.css +29 -0
- package/framework/css/layouts/presentation.css +191 -0
- package/framework/css/layouts/traditional.css +52 -0
- package/framework/css/responsive.css +439 -0
- package/framework/css/utilities/accessibility-utils.css +59 -0
- package/framework/css/utilities/animations.css +419 -0
- package/framework/css/utilities/borders.css +72 -0
- package/framework/css/utilities/colors.css +76 -0
- package/framework/css/utilities/container.css +46 -0
- package/framework/css/utilities/decorative.css +442 -0
- package/framework/css/utilities/display.css +257 -0
- package/framework/css/utilities/flexbox.css +80 -0
- package/framework/css/utilities/grid.css +69 -0
- package/framework/css/utilities/icons.css +534 -0
- package/framework/css/utilities/lists.css +190 -0
- package/framework/css/utilities/spacing.css +167 -0
- package/framework/css/utilities/tables.css +81 -0
- package/framework/css/utilities/typography.css +159 -0
- package/framework/css/utilities/visibility.css +117 -0
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
- package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
- package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
- package/framework/docs/DATA_MODEL.md +409 -0
- package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
- package/framework/docs/USER_GUIDE.md +583 -0
- package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
- package/framework/docs/examples/cloudflare-data-worker.js +102 -0
- package/framework/docs/examples/cloudflare-error-worker.js +228 -0
- package/framework/index.html +175 -0
- package/framework/js/app/AppActions.js +410 -0
- package/framework/js/app/AppState.js +225 -0
- package/framework/js/app/AppUI.js +616 -0
- package/framework/js/assessment/AssessmentActions.js +615 -0
- package/framework/js/assessment/AssessmentFactory.js +471 -0
- package/framework/js/assessment/AssessmentState.js +322 -0
- package/framework/js/assessment/AssessmentUI.js +451 -0
- package/framework/js/automation/api-engagement.js +196 -0
- package/framework/js/automation/api-interactions.js +167 -0
- package/framework/js/automation/api.js +242 -0
- package/framework/js/automation/index.js +41 -0
- package/framework/js/components/interactions/drag-drop.js +884 -0
- package/framework/js/components/interactions/fill-in.js +535 -0
- package/framework/js/components/interactions/hotspot.js +702 -0
- package/framework/js/components/interactions/interaction-base.js +511 -0
- package/framework/js/components/interactions/likert.js +301 -0
- package/framework/js/components/interactions/matching.js +699 -0
- package/framework/js/components/interactions/multiple-choice.js +377 -0
- package/framework/js/components/interactions/numeric.js +271 -0
- package/framework/js/components/interactions/sequencing.js +423 -0
- package/framework/js/components/interactions/true-false.js +241 -0
- package/framework/js/components/ui-components/accordion.js +442 -0
- package/framework/js/components/ui-components/alert.js +88 -0
- package/framework/js/components/ui-components/audio-player.js +1193 -0
- package/framework/js/components/ui-components/callout.js +121 -0
- package/framework/js/components/ui-components/carousel.js +145 -0
- package/framework/js/components/ui-components/checkbox-group.js +87 -0
- package/framework/js/components/ui-components/checklist.js +40 -0
- package/framework/js/components/ui-components/collapse.js +114 -0
- package/framework/js/components/ui-components/comparison.js +30 -0
- package/framework/js/components/ui-components/conditional-display.js +150 -0
- package/framework/js/components/ui-components/content-image.js +41 -0
- package/framework/js/components/ui-components/dropdown.js +262 -0
- package/framework/js/components/ui-components/embed-frame.js +274 -0
- package/framework/js/components/ui-components/features.js +33 -0
- package/framework/js/components/ui-components/flip-card.js +230 -0
- package/framework/js/components/ui-components/form-validator.js +76 -0
- package/framework/js/components/ui-components/hero.js +49 -0
- package/framework/js/components/ui-components/index.js +12 -0
- package/framework/js/components/ui-components/interactive-image.js +235 -0
- package/framework/js/components/ui-components/interactive-timeline.js +285 -0
- package/framework/js/components/ui-components/intro-cards.js +35 -0
- package/framework/js/components/ui-components/lightbox.js +652 -0
- package/framework/js/components/ui-components/modal.js +386 -0
- package/framework/js/components/ui-components/notifications.js +145 -0
- package/framework/js/components/ui-components/progress.js +88 -0
- package/framework/js/components/ui-components/quote.js +41 -0
- package/framework/js/components/ui-components/stats.js +33 -0
- package/framework/js/components/ui-components/steps.js +41 -0
- package/framework/js/components/ui-components/tabs.js +255 -0
- package/framework/js/components/ui-components/timeline.js +42 -0
- package/framework/js/components/ui-components/toggle-group.js +73 -0
- package/framework/js/components/ui-components/tooltip.js +458 -0
- package/framework/js/components/ui-components/value-display.js +133 -0
- package/framework/js/components/ui-components/video-player.js +686 -0
- package/framework/js/core/component-catalog.js +121 -0
- package/framework/js/core/event-bus.js +178 -0
- package/framework/js/core/interaction-catalog.js +149 -0
- package/framework/js/dev/runtime-linter.js +1725 -0
- package/framework/js/drivers/cmi5-driver.js +768 -0
- package/framework/js/drivers/driver-factory.js +77 -0
- package/framework/js/drivers/driver-interface.js +110 -0
- package/framework/js/drivers/http-driver-base.js +241 -0
- package/framework/js/drivers/lti-driver.js +508 -0
- package/framework/js/drivers/proxy-driver.js +444 -0
- package/framework/js/drivers/scorm-12-driver.js +560 -0
- package/framework/js/drivers/scorm-2004-driver.js +775 -0
- package/framework/js/drivers/scorm-driver-base.js +112 -0
- package/framework/js/engagement/engagement-manager.js +404 -0
- package/framework/js/engagement/engagement-progress.js +191 -0
- package/framework/js/engagement/engagement-trackers.js +215 -0
- package/framework/js/engagement/requirement-strategies.js +268 -0
- package/framework/js/main.js +727 -0
- package/framework/js/managers/accessibility-manager.js +499 -0
- package/framework/js/managers/assessment-manager.js +230 -0
- package/framework/js/managers/audio-manager.js +944 -0
- package/framework/js/managers/comment-manager.js +88 -0
- package/framework/js/managers/flag-manager.js +86 -0
- package/framework/js/managers/interaction-manager.js +254 -0
- package/framework/js/managers/interaction-registry.js +96 -0
- package/framework/js/managers/objective-manager.js +423 -0
- package/framework/js/managers/score-manager.js +441 -0
- package/framework/js/managers/video-manager.js +536 -0
- package/framework/js/navigation/Breadcrumbs.js +234 -0
- package/framework/js/navigation/NavigationActions.js +1132 -0
- package/framework/js/navigation/NavigationState.js +276 -0
- package/framework/js/navigation/NavigationUI.js +574 -0
- package/framework/js/navigation/document-gallery.js +357 -0
- package/framework/js/navigation/navigation-helpers.js +175 -0
- package/framework/js/navigation/navigation-validators.js +174 -0
- package/framework/js/state/index.js +8 -0
- package/framework/js/state/lms-connection.js +482 -0
- package/framework/js/state/lms-error-utils.js +58 -0
- package/framework/js/state/state-commits.js +200 -0
- package/framework/js/state/state-domains.js +86 -0
- package/framework/js/state/state-manager.js +502 -0
- package/framework/js/state/state-validation.js +311 -0
- package/framework/js/state/transaction-log.js +41 -0
- package/framework/js/state/xapi-statement-service.js +325 -0
- package/framework/js/utilities/access-control.js +99 -0
- package/framework/js/utilities/breakpoint-manager.js +315 -0
- package/framework/js/utilities/canvas-slide.js +35 -0
- package/framework/js/utilities/conditional-display.js +388 -0
- package/framework/js/utilities/course-channel.js +214 -0
- package/framework/js/utilities/course-helpers.js +420 -0
- package/framework/js/utilities/data-reporter.js +273 -0
- package/framework/js/utilities/error-reporter.js +313 -0
- package/framework/js/utilities/hotspot-helper.js +341 -0
- package/framework/js/utilities/icons.js +348 -0
- package/framework/js/utilities/logger.js +92 -0
- package/framework/js/utilities/markdown-renderer.js +45 -0
- package/framework/js/utilities/scroll-tracker.js +68 -0
- package/framework/js/utilities/ui-initializer.js +146 -0
- package/framework/js/utilities/utilities.js +293 -0
- package/framework/js/utilities/view-manager.js +227 -0
- package/framework/js/validation/html-validators.js +422 -0
- package/framework/js/validation/scorm-validators.js +438 -0
- package/framework/js/vendor/pipwerks.js +931 -0
- package/framework/scripts/generate-narration.js +629 -0
- package/framework/scripts/tts-providers/azure-provider.js +178 -0
- package/framework/scripts/tts-providers/base-provider.js +81 -0
- package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
- package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
- package/framework/scripts/tts-providers/google-provider.js +272 -0
- package/framework/scripts/tts-providers/index.js +158 -0
- package/framework/scripts/tts-providers/openai-provider.js +143 -0
- package/framework/version.json +63 -0
- package/lib/authoring-api.js +919 -0
- package/lib/build-linter.js +450 -0
- package/lib/build-packaging.js +186 -0
- package/lib/build.js +88 -0
- package/lib/cloud.js +691 -0
- package/lib/convert.js +341 -0
- package/lib/course-parser.js +936 -0
- package/lib/course-writer.js +258 -0
- package/lib/create.js +248 -0
- package/lib/css-index.js +237 -0
- package/lib/dev.js +51 -0
- package/lib/export-content.js +1246 -0
- package/lib/headless-browser.js +413 -0
- package/lib/import.js +377 -0
- package/lib/index.js +80 -0
- package/lib/info.js +79 -0
- package/lib/interaction-formatters.js +568 -0
- package/lib/manifest/cmi5-manifest.js +63 -0
- package/lib/manifest/lti-tool-config.js +53 -0
- package/lib/manifest/manifest-factory.js +99 -0
- package/lib/manifest/scorm-12-manifest.js +61 -0
- package/lib/manifest/scorm-2004-manifest.js +94 -0
- package/lib/manifest/scorm-proxy-manifest.js +104 -0
- package/lib/manifest-parser.js +96 -0
- package/lib/mcp-prompts.js +753 -0
- package/lib/mcp-server.js +316 -0
- package/lib/narration.js +53 -0
- package/lib/pdf-structure.js +142 -0
- package/lib/preview-export.js +231 -0
- package/lib/preview-routes-api.js +662 -0
- package/lib/preview-routes-editing.js +159 -0
- package/lib/preview-routes-lms.js +230 -0
- package/lib/preview-server.js +564 -0
- package/lib/project-utils.js +269 -0
- package/lib/proxy-templates/proxy.html +68 -0
- package/lib/proxy-templates/scorm-bridge.js +112 -0
- package/lib/scaffold.js +193 -0
- package/lib/schema-extractor.js +361 -0
- package/lib/slide-source-editor.js +586 -0
- package/lib/stub-player/app-viewer.js +195 -0
- package/lib/stub-player/app.js +370 -0
- package/lib/stub-player/catalog-panel.js +312 -0
- package/lib/stub-player/config-panel.js +1303 -0
- package/lib/stub-player/content-generator.js +586 -0
- package/lib/stub-player/content-viewer.js +173 -0
- package/lib/stub-player/debug-panel.js +420 -0
- package/lib/stub-player/edit-mode.js +922 -0
- package/lib/stub-player/edit-utils.js +400 -0
- package/lib/stub-player/header-bar.js +354 -0
- package/lib/stub-player/interaction-editor.js +210 -0
- package/lib/stub-player/interactions-panel.js +565 -0
- package/lib/stub-player/lms-api.js +1094 -0
- package/lib/stub-player/login-screen.js +74 -0
- package/lib/stub-player/outline-mode.js +689 -0
- package/lib/stub-player/styles/_assessments-panel.css +245 -0
- package/lib/stub-player/styles/_base.css +89 -0
- package/lib/stub-player/styles/_catalog-icons.css +96 -0
- package/lib/stub-player/styles/_catalog-panel.css +291 -0
- package/lib/stub-player/styles/_config-panel.css +636 -0
- package/lib/stub-player/styles/_content-viewer.css +834 -0
- package/lib/stub-player/styles/_debug-panel.css +576 -0
- package/lib/stub-player/styles/_edit-mode.css +128 -0
- package/lib/stub-player/styles/_header-bar.css +343 -0
- package/lib/stub-player/styles/_interaction-editor.css +140 -0
- package/lib/stub-player/styles/_interactions-panel.css +1038 -0
- package/lib/stub-player/styles/_login-screen.css +102 -0
- package/lib/stub-player/styles/_outline-mode.css +752 -0
- package/lib/stub-player/styles.css +15 -0
- package/lib/stub-player.js +160 -0
- package/lib/test-data-reporting.js +176 -0
- package/lib/test-error-reporting.js +146 -0
- package/lib/token.js +86 -0
- package/lib/upgrade.js +257 -0
- package/lib/validation-rules.js +517 -0
- package/lib/vite-plugin-content-discovery.js +296 -0
- package/package.json +108 -0
- package/schemas/XMLSchema.dtd +402 -0
- package/schemas/adlcp_v1p3.xsd +111 -0
- package/schemas/adlnav_v1p3.xsd +61 -0
- package/schemas/adlseq_v1p3.xsd +93 -0
- package/schemas/common/anyElement.xsd +27 -0
- package/schemas/common/dataTypes.xsd +138 -0
- package/schemas/common/elementNames.xsd +767 -0
- package/schemas/common/elementTypes.xsd +786 -0
- package/schemas/common/rootElement.xsd +31 -0
- package/schemas/common/vocabTypes.xsd +345 -0
- package/schemas/common/vocabValues.xsd +257 -0
- package/schemas/datatypes.dtd +203 -0
- package/schemas/ims_xml.xsd +35 -0
- package/schemas/imscp_v1p1.xsd +368 -0
- package/schemas/imsss_v1p0.xsd +67 -0
- package/schemas/imsss_v1p0auxresource.xsd +19 -0
- package/schemas/imsss_v1p0control.xsd +20 -0
- package/schemas/imsss_v1p0delivery.xsd +17 -0
- package/schemas/imsss_v1p0limit.xsd +47 -0
- package/schemas/imsss_v1p0objective.xsd +67 -0
- package/schemas/imsss_v1p0random.xsd +16 -0
- package/schemas/imsss_v1p0rollup.xsd +46 -0
- package/schemas/imsss_v1p0seqrule.xsd +108 -0
- package/schemas/imsss_v1p0util.xsd +94 -0
- package/schemas/license.txt +17 -0
- package/schemas/lom.xsd +102 -0
- package/schemas/lomCustom.xsd +62 -0
- package/schemas/lomLoose.xsd +62 -0
- package/schemas/lomStrict.xsd +62 -0
- package/schemas/xml.xsd +81 -0
- package/template/.env.example +92 -0
- package/template/course/assets/audio/example-intro.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
- package/template/course/assets/docs/example_md_1.md +39 -0
- package/template/course/assets/docs/example_md_2.md +41 -0
- package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
- package/template/course/assets/docs/example_pdf_2.pdf +0 -0
- package/template/course/assets/images/course-architecture.svg +36 -0
- package/template/course/assets/images/logo.svg +14 -0
- package/template/course/assets/widgets/counter-demo.html +190 -0
- package/template/course/assets/widgets/gravity-painter.html +384 -0
- package/template/course/course-config.js +539 -0
- package/template/course/icons.js +19 -0
- package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
- package/template/course/slides/example-course-structure.js +138 -0
- package/template/course/slides/example-final-exam.js +144 -0
- package/template/course/slides/example-finishing.js +127 -0
- package/template/course/slides/example-interactions-showcase.js +615 -0
- package/template/course/slides/example-preview-tour.js +129 -0
- package/template/course/slides/example-remedial.js +143 -0
- package/template/course/slides/example-summary.js +103 -0
- package/template/course/slides/example-ui-showcase.js +1805 -0
- package/template/course/slides/example-welcome.js +123 -0
- package/template/course/slides/example-workflow.js +140 -0
- package/template/course/theme.css +165 -0
- package/template/eslint.config.js +47 -0
- package/template/package.json +28 -0
- package/template/vite.config.js +339 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file driver-factory.js
|
|
3
|
+
* @description Factory for creating LMS drivers based on format configuration.
|
|
4
|
+
*
|
|
5
|
+
* All drivers are included as lazy chunks in the universal build.
|
|
6
|
+
* Only the driver matching the runtime format is loaded (via dynamic import).
|
|
7
|
+
* This enables a single build to serve any LMS format — the format is
|
|
8
|
+
* determined at runtime from a <meta name="lms-format"> tag in index.html.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { validateDriverInterface } from './driver-interface.js';
|
|
12
|
+
|
|
13
|
+
// Cached driver instances (drivers are singletons)
|
|
14
|
+
let cachedDriver = null;
|
|
15
|
+
let cachedFormat = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates the appropriate LMS driver based on format.
|
|
19
|
+
* All drivers exist as lazy chunks; only the matching one is loaded at runtime.
|
|
20
|
+
* @param {string} format - 'cmi5' | 'cmi5-remote' | 'scorm2004' | 'scorm1.2' | 'scorm1.2-proxy' | 'scorm2004-proxy' | 'lti'
|
|
21
|
+
* @returns {Promise<LMSDriver>} The driver instance
|
|
22
|
+
*/
|
|
23
|
+
export async function createDriver(format = 'cmi5') {
|
|
24
|
+
// Return cached driver if same format
|
|
25
|
+
if (cachedDriver && cachedFormat === format) {
|
|
26
|
+
return cachedDriver;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let driver;
|
|
30
|
+
|
|
31
|
+
switch (format) {
|
|
32
|
+
case 'scorm2004': {
|
|
33
|
+
const { Scorm2004Driver } = await import('./scorm-2004-driver.js');
|
|
34
|
+
driver = new Scorm2004Driver();
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
case 'scorm1.2': {
|
|
39
|
+
const { Scorm12Driver } = await import('./scorm-12-driver.js');
|
|
40
|
+
driver = new Scorm12Driver();
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
case 'scorm1.2-proxy':
|
|
45
|
+
case 'scorm2004-proxy': {
|
|
46
|
+
const { ProxyDriver } = await import('./proxy-driver.js');
|
|
47
|
+
// Extract base format (e.g., 'scorm1.2-proxy' -> 'scorm1.2')
|
|
48
|
+
const baseFormat = format.replace('-proxy', '');
|
|
49
|
+
driver = new ProxyDriver(baseFormat);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
case 'lti': {
|
|
54
|
+
const { LtiDriver } = await import('./lti-driver.js');
|
|
55
|
+
driver = new LtiDriver();
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case 'cmi5':
|
|
60
|
+
case 'cmi5-remote':
|
|
61
|
+
default: {
|
|
62
|
+
const { Cmi5Driver } = await import('./cmi5-driver.js');
|
|
63
|
+
driver = new Cmi5Driver();
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Validate interface in development
|
|
69
|
+
if (import.meta.env.DEV) {
|
|
70
|
+
validateDriverInterface(driver);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
cachedDriver = driver;
|
|
74
|
+
cachedFormat = format;
|
|
75
|
+
|
|
76
|
+
return driver;
|
|
77
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file driver-interface.js
|
|
3
|
+
* @description Defines the semantic interface contract for LMS drivers.
|
|
4
|
+
* All drivers (SCORM 2004, SCORM 1.2, cmi5, LTI, Proxy) must implement this interface.
|
|
5
|
+
*
|
|
6
|
+
* The interface uses semantic methods instead of CMI keys. Each driver translates
|
|
7
|
+
* to its native protocol (CMI for SCORM, xAPI for cmi5, HTTP for LTI, postMessage for Proxy).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} DriverCapabilities
|
|
12
|
+
* @property {boolean} supportsObjectives - Can store learning objectives
|
|
13
|
+
* @property {boolean} supportsInteractions - Can store interaction records
|
|
14
|
+
* @property {boolean} supportsComments - Can store learner comments
|
|
15
|
+
* @property {boolean} supportsEmergencySave - Has sendBeacon fallback for page unload
|
|
16
|
+
* @property {number} maxSuspendDataBytes - Max suspend_data size (0 = unlimited)
|
|
17
|
+
* @property {boolean} asyncCommit - Whether commit() is async (HTTP-based)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} LMSDriver
|
|
22
|
+
*
|
|
23
|
+
* Lifecycle:
|
|
24
|
+
* @property {function(): Promise<boolean>} initialize - Initialize connection
|
|
25
|
+
* @property {function(): Promise<boolean>} terminate - Terminate connection
|
|
26
|
+
* @property {function(): Promise<boolean>} commit - Commit buffered writes
|
|
27
|
+
* @property {function(): DriverCapabilities} getCapabilities - Declare format capabilities
|
|
28
|
+
* @property {function(): 'scorm2004'|'scorm1.2'|'cmi5'|'lti'} getFormat - Get format identifier
|
|
29
|
+
* @property {function(): boolean} isConnected - Check if driver is connected
|
|
30
|
+
* @property {function(): boolean} isTerminated - Check if driver is terminated
|
|
31
|
+
*
|
|
32
|
+
* State persistence (suspend_data blob):
|
|
33
|
+
* @property {function(): object|null} getSuspendData - Get parsed suspend_data object
|
|
34
|
+
* @property {function(object): boolean} setSuspendData - Set suspend_data from object
|
|
35
|
+
*
|
|
36
|
+
* Semantic reads:
|
|
37
|
+
* @property {function(): string} getEntryMode - Returns 'ab-initio' | 'resume' | ''
|
|
38
|
+
* @property {function(): string} getBookmark - Returns current slide location or ''
|
|
39
|
+
* @property {function(): string} getCompletion - Returns 'completed' | 'incomplete' | 'unknown'
|
|
40
|
+
* @property {function(): string} getSuccess - Returns 'passed' | 'failed' | 'unknown'
|
|
41
|
+
* @property {function(): {scaled: number, raw: number, min: number, max: number}|null} getScore - Returns last reported score or null
|
|
42
|
+
* @property {function(): {id: string, name: string}} getLearnerInfo - Returns learner identity
|
|
43
|
+
*
|
|
44
|
+
* Semantic writes:
|
|
45
|
+
* @property {function(string): void} setBookmark - Persist slide position
|
|
46
|
+
* @property {function({raw: number, scaled: number, min: number, max: number}): void} reportScore - Report score
|
|
47
|
+
* @property {function(string): void} reportCompletion - 'completed' | 'incomplete'
|
|
48
|
+
* @property {function(string): void} reportSuccess - 'passed' | 'failed' | 'unknown'
|
|
49
|
+
* @property {function(number): void} reportProgress - 0.0 to 1.0
|
|
50
|
+
* @property {function(string): void} reportSessionTime - ISO 8601 duration string
|
|
51
|
+
* @property {function(Object): void} reportObjective - Report objective status
|
|
52
|
+
* @property {function(Object): void} reportInteraction - Report interaction data
|
|
53
|
+
* @property {function(string): void} setExitMode - 'suspend' | 'normal'
|
|
54
|
+
*
|
|
55
|
+
* Optional:
|
|
56
|
+
* @property {function(): void} [ping] - Keep-alive ping
|
|
57
|
+
* @property {function(): void} [emergencySave] - Synchronous emergency save using sendBeacon
|
|
58
|
+
*
|
|
59
|
+
* Optional xAPI (cmi5 only):
|
|
60
|
+
* @property {function(Object): Promise<void>} [sendObjectiveStatement] - Send objective xAPI statement
|
|
61
|
+
* @property {function(Object): Promise<void>} [sendInteractionStatement] - Send interaction xAPI statement
|
|
62
|
+
* @property {function(Object): Promise<void>} [sendAssessmentStatement] - Send assessment xAPI statement
|
|
63
|
+
* @property {function(Object): Promise<void>} [sendSlideStatement] - Send slide experienced xAPI statement
|
|
64
|
+
* @property {function(): Object|null} [getLaunchData] - Get cmi5 launch data
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Creates a driver interface validator (development-time check).
|
|
70
|
+
* @param {object} driver - Driver instance to validate
|
|
71
|
+
* @throws {Error} If driver doesn't implement required methods
|
|
72
|
+
*/
|
|
73
|
+
export function validateDriverInterface(driver) {
|
|
74
|
+
const requiredMethods = [
|
|
75
|
+
// Lifecycle
|
|
76
|
+
'initialize',
|
|
77
|
+
'terminate',
|
|
78
|
+
'commit',
|
|
79
|
+
'getCapabilities',
|
|
80
|
+
'getFormat',
|
|
81
|
+
'isConnected',
|
|
82
|
+
'isTerminated',
|
|
83
|
+
// State persistence
|
|
84
|
+
'getSuspendData',
|
|
85
|
+
'setSuspendData',
|
|
86
|
+
// Semantic reads
|
|
87
|
+
'getEntryMode',
|
|
88
|
+
'getBookmark',
|
|
89
|
+
'getCompletion',
|
|
90
|
+
'getSuccess',
|
|
91
|
+
'getScore',
|
|
92
|
+
'getLearnerInfo',
|
|
93
|
+
// Semantic writes
|
|
94
|
+
'setBookmark',
|
|
95
|
+
'reportScore',
|
|
96
|
+
'reportCompletion',
|
|
97
|
+
'reportSuccess',
|
|
98
|
+
'reportProgress',
|
|
99
|
+
'reportSessionTime',
|
|
100
|
+
'reportObjective',
|
|
101
|
+
'reportInteraction',
|
|
102
|
+
'setExitMode'
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
for (const method of requiredMethods) {
|
|
106
|
+
if (typeof driver[method] !== 'function') {
|
|
107
|
+
throw new Error(`LMS Driver missing required method: ${method}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file http-driver-base.js
|
|
3
|
+
* @description Base class for HTTP-based LMS drivers (cmi5, LTI).
|
|
4
|
+
* Consolidates shared mock state management, suspend data caching,
|
|
5
|
+
* semantic reads/writes, and commit logic.
|
|
6
|
+
*
|
|
7
|
+
* Subclasses must implement:
|
|
8
|
+
* - getFormat()
|
|
9
|
+
* - getCapabilities()
|
|
10
|
+
* - initialize()
|
|
11
|
+
* - terminate()
|
|
12
|
+
* - emergencySave()
|
|
13
|
+
* - getLearnerInfo()
|
|
14
|
+
* - getLaunchData()
|
|
15
|
+
* - _loadMockState()
|
|
16
|
+
* - _saveMockState()
|
|
17
|
+
* - _persistState()
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { eventBus } from '../core/event-bus.js';
|
|
21
|
+
import { logger } from '../utilities/logger.js';
|
|
22
|
+
|
|
23
|
+
export class HttpDriverBase {
|
|
24
|
+
constructor() {
|
|
25
|
+
this._isConnected = false;
|
|
26
|
+
this._isTerminated = false;
|
|
27
|
+
this._mock = false;
|
|
28
|
+
this._mockState = {};
|
|
29
|
+
this._devApi = null;
|
|
30
|
+
|
|
31
|
+
// Local caches for synchronous access (pre-fetched on initialize)
|
|
32
|
+
this._suspendDataCache = null;
|
|
33
|
+
this._bookmarkCache = null;
|
|
34
|
+
this._suspendDataDirty = false;
|
|
35
|
+
this._bookmarkDirty = false;
|
|
36
|
+
|
|
37
|
+
// Cached status values
|
|
38
|
+
this._completionStatus = 'unknown';
|
|
39
|
+
this._successStatus = 'unknown';
|
|
40
|
+
this._score = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// =========================================================================
|
|
44
|
+
// Interface: Lifecycle Queries
|
|
45
|
+
// =========================================================================
|
|
46
|
+
|
|
47
|
+
isConnected() {
|
|
48
|
+
return this._isConnected;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
isTerminated() {
|
|
52
|
+
return this._isTerminated;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Keep-alive ping — no-op for HTTP-based drivers (stateless).
|
|
57
|
+
*/
|
|
58
|
+
ping() {
|
|
59
|
+
// HTTP-based drivers use stateless requests — no session keep-alive needed
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// =========================================================================
|
|
63
|
+
// Interface: Commit
|
|
64
|
+
// =========================================================================
|
|
65
|
+
|
|
66
|
+
async commit() {
|
|
67
|
+
this._ensureInitialized();
|
|
68
|
+
|
|
69
|
+
if (this._isTerminated) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (this._mock) {
|
|
74
|
+
this._saveMockState();
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await this._persistState();
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// =========================================================================
|
|
83
|
+
// Interface: Semantic Reads
|
|
84
|
+
// =========================================================================
|
|
85
|
+
|
|
86
|
+
getEntryMode() {
|
|
87
|
+
return this._bookmarkCache ? 'resume' : 'ab-initio';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getBookmark() {
|
|
91
|
+
return this._bookmarkCache || '';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getCompletion() {
|
|
95
|
+
return this._completionStatus;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getSuccess() {
|
|
99
|
+
return this._successStatus;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getScore() {
|
|
103
|
+
if (this._score === null) return null;
|
|
104
|
+
const raw = Math.round(this._score * 100 * 100) / 100;
|
|
105
|
+
return { scaled: this._score, raw, min: 0, max: 100 };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// =========================================================================
|
|
109
|
+
// Interface: Semantic Writes
|
|
110
|
+
// =========================================================================
|
|
111
|
+
|
|
112
|
+
setBookmark(location) {
|
|
113
|
+
this._bookmarkCache = location;
|
|
114
|
+
this._bookmarkDirty = true;
|
|
115
|
+
if (this._mock) this._saveMockState();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
reportScore({ raw, scaled, min: _min, max: _max }) {
|
|
119
|
+
if (scaled !== undefined) {
|
|
120
|
+
this._score = scaled;
|
|
121
|
+
} else if (raw !== undefined) {
|
|
122
|
+
this._score = raw / 100;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
reportCompletion(status) {
|
|
127
|
+
this._completionStatus = status;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
reportSuccess(status) {
|
|
131
|
+
this._successStatus = status;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
reportProgress(_measure) {
|
|
135
|
+
// HTTP-based drivers don't have a native progress_measure
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
reportSessionTime(_duration) {
|
|
139
|
+
// Session time handled by protocol-specific mechanisms
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
reportObjective(_objective) {
|
|
143
|
+
// Objectives stored via suspend_data only
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
reportInteraction(_interaction) {
|
|
147
|
+
// Interactions stored via suspend_data only
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setExitMode(_mode) {
|
|
151
|
+
// HTTP-based drivers don't have an exit mode concept
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// =========================================================================
|
|
155
|
+
// Interface: Suspend Data
|
|
156
|
+
// =========================================================================
|
|
157
|
+
|
|
158
|
+
getSuspendData() {
|
|
159
|
+
this._ensureInitialized();
|
|
160
|
+
|
|
161
|
+
if (this._mock) {
|
|
162
|
+
return this._mockState.suspendData || null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return this._suspendDataCache;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
setSuspendData(data) {
|
|
169
|
+
this._ensureInitialized();
|
|
170
|
+
|
|
171
|
+
if (data === undefined || data === null) {
|
|
172
|
+
throw new Error('Cannot set suspend_data: data is null or undefined');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (this._mock) {
|
|
176
|
+
this._mockState.suspendData = data;
|
|
177
|
+
this._saveMockState();
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this._suspendDataCache = data;
|
|
182
|
+
this._suspendDataDirty = true;
|
|
183
|
+
|
|
184
|
+
const sizeKB = (JSON.stringify(data).length / 1024).toFixed(2);
|
|
185
|
+
logger.debug(`[${this.constructor.name}] suspend_data cached: ${sizeKB}KB`);
|
|
186
|
+
|
|
187
|
+
eventBus.emit('suspend-data:size', {
|
|
188
|
+
bytes: JSON.stringify(data).length,
|
|
189
|
+
kilobytes: parseFloat(sizeKB),
|
|
190
|
+
format: this.getFormat()
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// =========================================================================
|
|
197
|
+
// Shared Mock Helpers
|
|
198
|
+
// =========================================================================
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Shared mock terminate flow — logs completion/success/failure/terminated
|
|
202
|
+
* statements and calls devApi.terminate(). Returns true.
|
|
203
|
+
*/
|
|
204
|
+
_terminateMock() {
|
|
205
|
+
this._saveMockState();
|
|
206
|
+
if (this._completionStatus === 'completed') {
|
|
207
|
+
this._logMockStatement('completed', { verb: 'completed' });
|
|
208
|
+
}
|
|
209
|
+
if (this._successStatus === 'passed') {
|
|
210
|
+
this._logMockStatement('passed', { verb: 'passed', score: this._score });
|
|
211
|
+
} else if (this._successStatus === 'failed') {
|
|
212
|
+
this._logMockStatement('failed', { verb: 'failed', score: this._score });
|
|
213
|
+
}
|
|
214
|
+
this._logMockStatement('terminated', { verb: 'terminated' });
|
|
215
|
+
if (this._devApi) {
|
|
216
|
+
this._devApi.terminate();
|
|
217
|
+
}
|
|
218
|
+
this._isTerminated = true;
|
|
219
|
+
logger.debug(`[${this.constructor.name}] Mock session terminated`);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_logMockStatement(type, data) {
|
|
224
|
+
const statement = { type, data, timestamp: new Date().toISOString() };
|
|
225
|
+
logger.debug(`[${this.constructor.name}] Mock ${type} statement:`, statement);
|
|
226
|
+
eventBus.emit('xapi:statement', statement);
|
|
227
|
+
if (this._devApi && typeof this._devApi.recordStatement === 'function') {
|
|
228
|
+
this._devApi.recordStatement(statement);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// =========================================================================
|
|
233
|
+
// Internal Utilities
|
|
234
|
+
// =========================================================================
|
|
235
|
+
|
|
236
|
+
_ensureInitialized() {
|
|
237
|
+
if (!this._isConnected) {
|
|
238
|
+
throw new Error(`${this.getFormat()} not initialized. Call initialize() first.`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|