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.
Files changed (362) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/THIRD_PARTY_NOTICES.md +22 -0
  4. package/bin/cli.js +331 -0
  5. package/framework/assets/logo-coursecode-black.svg +14 -0
  6. package/framework/assets/logo-coursecode-white.svg +14 -0
  7. package/framework/assets/logo-coursecode.svg +14 -0
  8. package/framework/css/01-base.css +160 -0
  9. package/framework/css/02-layout.css +499 -0
  10. package/framework/css/accessibility.css +834 -0
  11. package/framework/css/components/accordions.css +710 -0
  12. package/framework/css/components/assessments.css +520 -0
  13. package/framework/css/components/audio-player.css +570 -0
  14. package/framework/css/components/badges.css +80 -0
  15. package/framework/css/components/breadcrumbs.css +87 -0
  16. package/framework/css/components/buttons.css +707 -0
  17. package/framework/css/components/callouts.css +1280 -0
  18. package/framework/css/components/cards.css +475 -0
  19. package/framework/css/components/carousel.css +193 -0
  20. package/framework/css/components/checkbox-group.css +123 -0
  21. package/framework/css/components/checklist.css +203 -0
  22. package/framework/css/components/collapse.css +96 -0
  23. package/framework/css/components/comparison.css +33 -0
  24. package/framework/css/components/content-image.css +36 -0
  25. package/framework/css/components/document-gallery.css +425 -0
  26. package/framework/css/components/dropdown.css +115 -0
  27. package/framework/css/components/embed-frame.css +142 -0
  28. package/framework/css/components/engagement.css +412 -0
  29. package/framework/css/components/features.css +35 -0
  30. package/framework/css/components/flip-cards.css +253 -0
  31. package/framework/css/components/footer.css +353 -0
  32. package/framework/css/components/forms.css +294 -0
  33. package/framework/css/components/hero.css +216 -0
  34. package/framework/css/components/images.css +528 -0
  35. package/framework/css/components/interactive-timeline.css +274 -0
  36. package/framework/css/components/intro-cards.css +30 -0
  37. package/framework/css/components/lightbox.css +666 -0
  38. package/framework/css/components/loading.css +65 -0
  39. package/framework/css/components/modals.css +235 -0
  40. package/framework/css/components/notifications.css +107 -0
  41. package/framework/css/components/quote.css +150 -0
  42. package/framework/css/components/sidebar.css +684 -0
  43. package/framework/css/components/slide-header.css +52 -0
  44. package/framework/css/components/spinner.css +62 -0
  45. package/framework/css/components/stats.css +44 -0
  46. package/framework/css/components/steps.css +232 -0
  47. package/framework/css/components/tables.css +90 -0
  48. package/framework/css/components/tabs.css +347 -0
  49. package/framework/css/components/timeline.css +154 -0
  50. package/framework/css/components/toggle.css +95 -0
  51. package/framework/css/components/tooltip.css +226 -0
  52. package/framework/css/components/video-player.css +438 -0
  53. package/framework/css/design-tokens.css +707 -0
  54. package/framework/css/framework.css +86 -0
  55. package/framework/css/interactions/accessibility.css +75 -0
  56. package/framework/css/interactions/base.css +92 -0
  57. package/framework/css/interactions/drag-drop.css +295 -0
  58. package/framework/css/interactions/fill-in-the-blank.css +236 -0
  59. package/framework/css/interactions/hotspots.css +69 -0
  60. package/framework/css/interactions/index.css +45 -0
  61. package/framework/css/interactions/interactive-image.css +359 -0
  62. package/framework/css/interactions/likert.css +126 -0
  63. package/framework/css/interactions/matching.css +354 -0
  64. package/framework/css/interactions/numeric-input.css +78 -0
  65. package/framework/css/interactions/sequencing.css +378 -0
  66. package/framework/css/interactions/true-false.css +177 -0
  67. package/framework/css/layouts/article.css +258 -0
  68. package/framework/css/layouts/base.css +30 -0
  69. package/framework/css/layouts/canvas.css +38 -0
  70. package/framework/css/layouts/focused.css +236 -0
  71. package/framework/css/layouts/index.css +29 -0
  72. package/framework/css/layouts/presentation.css +191 -0
  73. package/framework/css/layouts/traditional.css +52 -0
  74. package/framework/css/responsive.css +439 -0
  75. package/framework/css/utilities/accessibility-utils.css +59 -0
  76. package/framework/css/utilities/animations.css +419 -0
  77. package/framework/css/utilities/borders.css +72 -0
  78. package/framework/css/utilities/colors.css +76 -0
  79. package/framework/css/utilities/container.css +46 -0
  80. package/framework/css/utilities/decorative.css +442 -0
  81. package/framework/css/utilities/display.css +257 -0
  82. package/framework/css/utilities/flexbox.css +80 -0
  83. package/framework/css/utilities/grid.css +69 -0
  84. package/framework/css/utilities/icons.css +534 -0
  85. package/framework/css/utilities/lists.css +190 -0
  86. package/framework/css/utilities/spacing.css +167 -0
  87. package/framework/css/utilities/tables.css +81 -0
  88. package/framework/css/utilities/typography.css +159 -0
  89. package/framework/css/utilities/visibility.css +117 -0
  90. package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
  91. package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
  92. package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
  93. package/framework/docs/DATA_MODEL.md +409 -0
  94. package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
  95. package/framework/docs/USER_GUIDE.md +583 -0
  96. package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
  97. package/framework/docs/examples/cloudflare-data-worker.js +102 -0
  98. package/framework/docs/examples/cloudflare-error-worker.js +228 -0
  99. package/framework/index.html +175 -0
  100. package/framework/js/app/AppActions.js +410 -0
  101. package/framework/js/app/AppState.js +225 -0
  102. package/framework/js/app/AppUI.js +616 -0
  103. package/framework/js/assessment/AssessmentActions.js +615 -0
  104. package/framework/js/assessment/AssessmentFactory.js +471 -0
  105. package/framework/js/assessment/AssessmentState.js +322 -0
  106. package/framework/js/assessment/AssessmentUI.js +451 -0
  107. package/framework/js/automation/api-engagement.js +196 -0
  108. package/framework/js/automation/api-interactions.js +167 -0
  109. package/framework/js/automation/api.js +242 -0
  110. package/framework/js/automation/index.js +41 -0
  111. package/framework/js/components/interactions/drag-drop.js +884 -0
  112. package/framework/js/components/interactions/fill-in.js +535 -0
  113. package/framework/js/components/interactions/hotspot.js +702 -0
  114. package/framework/js/components/interactions/interaction-base.js +511 -0
  115. package/framework/js/components/interactions/likert.js +301 -0
  116. package/framework/js/components/interactions/matching.js +699 -0
  117. package/framework/js/components/interactions/multiple-choice.js +377 -0
  118. package/framework/js/components/interactions/numeric.js +271 -0
  119. package/framework/js/components/interactions/sequencing.js +423 -0
  120. package/framework/js/components/interactions/true-false.js +241 -0
  121. package/framework/js/components/ui-components/accordion.js +442 -0
  122. package/framework/js/components/ui-components/alert.js +88 -0
  123. package/framework/js/components/ui-components/audio-player.js +1193 -0
  124. package/framework/js/components/ui-components/callout.js +121 -0
  125. package/framework/js/components/ui-components/carousel.js +145 -0
  126. package/framework/js/components/ui-components/checkbox-group.js +87 -0
  127. package/framework/js/components/ui-components/checklist.js +40 -0
  128. package/framework/js/components/ui-components/collapse.js +114 -0
  129. package/framework/js/components/ui-components/comparison.js +30 -0
  130. package/framework/js/components/ui-components/conditional-display.js +150 -0
  131. package/framework/js/components/ui-components/content-image.js +41 -0
  132. package/framework/js/components/ui-components/dropdown.js +262 -0
  133. package/framework/js/components/ui-components/embed-frame.js +274 -0
  134. package/framework/js/components/ui-components/features.js +33 -0
  135. package/framework/js/components/ui-components/flip-card.js +230 -0
  136. package/framework/js/components/ui-components/form-validator.js +76 -0
  137. package/framework/js/components/ui-components/hero.js +49 -0
  138. package/framework/js/components/ui-components/index.js +12 -0
  139. package/framework/js/components/ui-components/interactive-image.js +235 -0
  140. package/framework/js/components/ui-components/interactive-timeline.js +285 -0
  141. package/framework/js/components/ui-components/intro-cards.js +35 -0
  142. package/framework/js/components/ui-components/lightbox.js +652 -0
  143. package/framework/js/components/ui-components/modal.js +386 -0
  144. package/framework/js/components/ui-components/notifications.js +145 -0
  145. package/framework/js/components/ui-components/progress.js +88 -0
  146. package/framework/js/components/ui-components/quote.js +41 -0
  147. package/framework/js/components/ui-components/stats.js +33 -0
  148. package/framework/js/components/ui-components/steps.js +41 -0
  149. package/framework/js/components/ui-components/tabs.js +255 -0
  150. package/framework/js/components/ui-components/timeline.js +42 -0
  151. package/framework/js/components/ui-components/toggle-group.js +73 -0
  152. package/framework/js/components/ui-components/tooltip.js +458 -0
  153. package/framework/js/components/ui-components/value-display.js +133 -0
  154. package/framework/js/components/ui-components/video-player.js +686 -0
  155. package/framework/js/core/component-catalog.js +121 -0
  156. package/framework/js/core/event-bus.js +178 -0
  157. package/framework/js/core/interaction-catalog.js +149 -0
  158. package/framework/js/dev/runtime-linter.js +1725 -0
  159. package/framework/js/drivers/cmi5-driver.js +768 -0
  160. package/framework/js/drivers/driver-factory.js +77 -0
  161. package/framework/js/drivers/driver-interface.js +110 -0
  162. package/framework/js/drivers/http-driver-base.js +241 -0
  163. package/framework/js/drivers/lti-driver.js +508 -0
  164. package/framework/js/drivers/proxy-driver.js +444 -0
  165. package/framework/js/drivers/scorm-12-driver.js +560 -0
  166. package/framework/js/drivers/scorm-2004-driver.js +775 -0
  167. package/framework/js/drivers/scorm-driver-base.js +112 -0
  168. package/framework/js/engagement/engagement-manager.js +404 -0
  169. package/framework/js/engagement/engagement-progress.js +191 -0
  170. package/framework/js/engagement/engagement-trackers.js +215 -0
  171. package/framework/js/engagement/requirement-strategies.js +268 -0
  172. package/framework/js/main.js +727 -0
  173. package/framework/js/managers/accessibility-manager.js +499 -0
  174. package/framework/js/managers/assessment-manager.js +230 -0
  175. package/framework/js/managers/audio-manager.js +944 -0
  176. package/framework/js/managers/comment-manager.js +88 -0
  177. package/framework/js/managers/flag-manager.js +86 -0
  178. package/framework/js/managers/interaction-manager.js +254 -0
  179. package/framework/js/managers/interaction-registry.js +96 -0
  180. package/framework/js/managers/objective-manager.js +423 -0
  181. package/framework/js/managers/score-manager.js +441 -0
  182. package/framework/js/managers/video-manager.js +536 -0
  183. package/framework/js/navigation/Breadcrumbs.js +234 -0
  184. package/framework/js/navigation/NavigationActions.js +1132 -0
  185. package/framework/js/navigation/NavigationState.js +276 -0
  186. package/framework/js/navigation/NavigationUI.js +574 -0
  187. package/framework/js/navigation/document-gallery.js +357 -0
  188. package/framework/js/navigation/navigation-helpers.js +175 -0
  189. package/framework/js/navigation/navigation-validators.js +174 -0
  190. package/framework/js/state/index.js +8 -0
  191. package/framework/js/state/lms-connection.js +482 -0
  192. package/framework/js/state/lms-error-utils.js +58 -0
  193. package/framework/js/state/state-commits.js +200 -0
  194. package/framework/js/state/state-domains.js +86 -0
  195. package/framework/js/state/state-manager.js +502 -0
  196. package/framework/js/state/state-validation.js +311 -0
  197. package/framework/js/state/transaction-log.js +41 -0
  198. package/framework/js/state/xapi-statement-service.js +325 -0
  199. package/framework/js/utilities/access-control.js +99 -0
  200. package/framework/js/utilities/breakpoint-manager.js +315 -0
  201. package/framework/js/utilities/canvas-slide.js +35 -0
  202. package/framework/js/utilities/conditional-display.js +388 -0
  203. package/framework/js/utilities/course-channel.js +214 -0
  204. package/framework/js/utilities/course-helpers.js +420 -0
  205. package/framework/js/utilities/data-reporter.js +273 -0
  206. package/framework/js/utilities/error-reporter.js +313 -0
  207. package/framework/js/utilities/hotspot-helper.js +341 -0
  208. package/framework/js/utilities/icons.js +348 -0
  209. package/framework/js/utilities/logger.js +92 -0
  210. package/framework/js/utilities/markdown-renderer.js +45 -0
  211. package/framework/js/utilities/scroll-tracker.js +68 -0
  212. package/framework/js/utilities/ui-initializer.js +146 -0
  213. package/framework/js/utilities/utilities.js +293 -0
  214. package/framework/js/utilities/view-manager.js +227 -0
  215. package/framework/js/validation/html-validators.js +422 -0
  216. package/framework/js/validation/scorm-validators.js +438 -0
  217. package/framework/js/vendor/pipwerks.js +931 -0
  218. package/framework/scripts/generate-narration.js +629 -0
  219. package/framework/scripts/tts-providers/azure-provider.js +178 -0
  220. package/framework/scripts/tts-providers/base-provider.js +81 -0
  221. package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
  222. package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
  223. package/framework/scripts/tts-providers/google-provider.js +272 -0
  224. package/framework/scripts/tts-providers/index.js +158 -0
  225. package/framework/scripts/tts-providers/openai-provider.js +143 -0
  226. package/framework/version.json +63 -0
  227. package/lib/authoring-api.js +919 -0
  228. package/lib/build-linter.js +450 -0
  229. package/lib/build-packaging.js +186 -0
  230. package/lib/build.js +88 -0
  231. package/lib/cloud.js +691 -0
  232. package/lib/convert.js +341 -0
  233. package/lib/course-parser.js +936 -0
  234. package/lib/course-writer.js +258 -0
  235. package/lib/create.js +248 -0
  236. package/lib/css-index.js +237 -0
  237. package/lib/dev.js +51 -0
  238. package/lib/export-content.js +1246 -0
  239. package/lib/headless-browser.js +413 -0
  240. package/lib/import.js +377 -0
  241. package/lib/index.js +80 -0
  242. package/lib/info.js +79 -0
  243. package/lib/interaction-formatters.js +568 -0
  244. package/lib/manifest/cmi5-manifest.js +63 -0
  245. package/lib/manifest/lti-tool-config.js +53 -0
  246. package/lib/manifest/manifest-factory.js +99 -0
  247. package/lib/manifest/scorm-12-manifest.js +61 -0
  248. package/lib/manifest/scorm-2004-manifest.js +94 -0
  249. package/lib/manifest/scorm-proxy-manifest.js +104 -0
  250. package/lib/manifest-parser.js +96 -0
  251. package/lib/mcp-prompts.js +753 -0
  252. package/lib/mcp-server.js +316 -0
  253. package/lib/narration.js +53 -0
  254. package/lib/pdf-structure.js +142 -0
  255. package/lib/preview-export.js +231 -0
  256. package/lib/preview-routes-api.js +662 -0
  257. package/lib/preview-routes-editing.js +159 -0
  258. package/lib/preview-routes-lms.js +230 -0
  259. package/lib/preview-server.js +564 -0
  260. package/lib/project-utils.js +269 -0
  261. package/lib/proxy-templates/proxy.html +68 -0
  262. package/lib/proxy-templates/scorm-bridge.js +112 -0
  263. package/lib/scaffold.js +193 -0
  264. package/lib/schema-extractor.js +361 -0
  265. package/lib/slide-source-editor.js +586 -0
  266. package/lib/stub-player/app-viewer.js +195 -0
  267. package/lib/stub-player/app.js +370 -0
  268. package/lib/stub-player/catalog-panel.js +312 -0
  269. package/lib/stub-player/config-panel.js +1303 -0
  270. package/lib/stub-player/content-generator.js +586 -0
  271. package/lib/stub-player/content-viewer.js +173 -0
  272. package/lib/stub-player/debug-panel.js +420 -0
  273. package/lib/stub-player/edit-mode.js +922 -0
  274. package/lib/stub-player/edit-utils.js +400 -0
  275. package/lib/stub-player/header-bar.js +354 -0
  276. package/lib/stub-player/interaction-editor.js +210 -0
  277. package/lib/stub-player/interactions-panel.js +565 -0
  278. package/lib/stub-player/lms-api.js +1094 -0
  279. package/lib/stub-player/login-screen.js +74 -0
  280. package/lib/stub-player/outline-mode.js +689 -0
  281. package/lib/stub-player/styles/_assessments-panel.css +245 -0
  282. package/lib/stub-player/styles/_base.css +89 -0
  283. package/lib/stub-player/styles/_catalog-icons.css +96 -0
  284. package/lib/stub-player/styles/_catalog-panel.css +291 -0
  285. package/lib/stub-player/styles/_config-panel.css +636 -0
  286. package/lib/stub-player/styles/_content-viewer.css +834 -0
  287. package/lib/stub-player/styles/_debug-panel.css +576 -0
  288. package/lib/stub-player/styles/_edit-mode.css +128 -0
  289. package/lib/stub-player/styles/_header-bar.css +343 -0
  290. package/lib/stub-player/styles/_interaction-editor.css +140 -0
  291. package/lib/stub-player/styles/_interactions-panel.css +1038 -0
  292. package/lib/stub-player/styles/_login-screen.css +102 -0
  293. package/lib/stub-player/styles/_outline-mode.css +752 -0
  294. package/lib/stub-player/styles.css +15 -0
  295. package/lib/stub-player.js +160 -0
  296. package/lib/test-data-reporting.js +176 -0
  297. package/lib/test-error-reporting.js +146 -0
  298. package/lib/token.js +86 -0
  299. package/lib/upgrade.js +257 -0
  300. package/lib/validation-rules.js +517 -0
  301. package/lib/vite-plugin-content-discovery.js +296 -0
  302. package/package.json +108 -0
  303. package/schemas/XMLSchema.dtd +402 -0
  304. package/schemas/adlcp_v1p3.xsd +111 -0
  305. package/schemas/adlnav_v1p3.xsd +61 -0
  306. package/schemas/adlseq_v1p3.xsd +93 -0
  307. package/schemas/common/anyElement.xsd +27 -0
  308. package/schemas/common/dataTypes.xsd +138 -0
  309. package/schemas/common/elementNames.xsd +767 -0
  310. package/schemas/common/elementTypes.xsd +786 -0
  311. package/schemas/common/rootElement.xsd +31 -0
  312. package/schemas/common/vocabTypes.xsd +345 -0
  313. package/schemas/common/vocabValues.xsd +257 -0
  314. package/schemas/datatypes.dtd +203 -0
  315. package/schemas/ims_xml.xsd +35 -0
  316. package/schemas/imscp_v1p1.xsd +368 -0
  317. package/schemas/imsss_v1p0.xsd +67 -0
  318. package/schemas/imsss_v1p0auxresource.xsd +19 -0
  319. package/schemas/imsss_v1p0control.xsd +20 -0
  320. package/schemas/imsss_v1p0delivery.xsd +17 -0
  321. package/schemas/imsss_v1p0limit.xsd +47 -0
  322. package/schemas/imsss_v1p0objective.xsd +67 -0
  323. package/schemas/imsss_v1p0random.xsd +16 -0
  324. package/schemas/imsss_v1p0rollup.xsd +46 -0
  325. package/schemas/imsss_v1p0seqrule.xsd +108 -0
  326. package/schemas/imsss_v1p0util.xsd +94 -0
  327. package/schemas/license.txt +17 -0
  328. package/schemas/lom.xsd +102 -0
  329. package/schemas/lomCustom.xsd +62 -0
  330. package/schemas/lomLoose.xsd +62 -0
  331. package/schemas/lomStrict.xsd +62 -0
  332. package/schemas/xml.xsd +81 -0
  333. package/template/.env.example +92 -0
  334. package/template/course/assets/audio/example-intro.mp3 +0 -0
  335. package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
  336. package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
  337. package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
  338. package/template/course/assets/docs/example_md_1.md +39 -0
  339. package/template/course/assets/docs/example_md_2.md +41 -0
  340. package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
  341. package/template/course/assets/docs/example_pdf_2.pdf +0 -0
  342. package/template/course/assets/images/course-architecture.svg +36 -0
  343. package/template/course/assets/images/logo.svg +14 -0
  344. package/template/course/assets/widgets/counter-demo.html +190 -0
  345. package/template/course/assets/widgets/gravity-painter.html +384 -0
  346. package/template/course/course-config.js +539 -0
  347. package/template/course/icons.js +19 -0
  348. package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
  349. package/template/course/slides/example-course-structure.js +138 -0
  350. package/template/course/slides/example-final-exam.js +144 -0
  351. package/template/course/slides/example-finishing.js +127 -0
  352. package/template/course/slides/example-interactions-showcase.js +615 -0
  353. package/template/course/slides/example-preview-tour.js +129 -0
  354. package/template/course/slides/example-remedial.js +143 -0
  355. package/template/course/slides/example-summary.js +103 -0
  356. package/template/course/slides/example-ui-showcase.js +1805 -0
  357. package/template/course/slides/example-welcome.js +123 -0
  358. package/template/course/slides/example-workflow.js +140 -0
  359. package/template/course/theme.css +165 -0
  360. package/template/eslint.config.js +47 -0
  361. package/template/package.json +28 -0
  362. 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
+ }