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,444 @@
1
+ /**
2
+ * @file proxy-driver.js
3
+ * @description LMS driver for externally-hosted courses using postMessage bridge.
4
+ *
5
+ * Used by courses deployed to CDN when LMS has a proxy package installed.
6
+ * All LMS API calls are relayed via postMessage to the parent proxy frame,
7
+ * which bridges to the actual LMS API using pipwerks.
8
+ *
9
+ * Pre-fetches all needed values during initialize() for synchronous access,
10
+ * eliminating the broken _sendMessageSync hack.
11
+ */
12
+
13
+ import { logger } from '../utilities/logger.js';
14
+
15
+ // Message timeout (ms) — if proxy doesn't respond, something is wrong
16
+ const MESSAGE_TIMEOUT = 5000;
17
+
18
+ /**
19
+ * ProxyDriver — LMSDriver implementation via postMessage
20
+ */
21
+ export class ProxyDriver {
22
+ constructor(baseFormat = 'scorm1.2') {
23
+ this._baseFormat = baseFormat; // 'scorm1.2' or 'scorm2004'
24
+ this._isConnected = false;
25
+ this._isTerminated = false;
26
+ this._msgId = 0;
27
+ this._pending = new Map(); // id -> { resolve, reject, timeout }
28
+
29
+ // Origin of the parent proxy frame (set during initialize)
30
+ // Used for postMessage origin validation
31
+ this._parentOrigin = '*'; // Fallback — overridden with real origin when available
32
+
33
+ // Pre-fetched cache populated during initialize()
34
+ this._cache = {
35
+ entry: '',
36
+ bookmark: '',
37
+ learnerId: '',
38
+ learnerName: '',
39
+ completion: 'unknown',
40
+ success: 'unknown'
41
+ };
42
+
43
+ this._suspendDataCache = null;
44
+
45
+ // Listen for responses from proxy bridge
46
+ this._handleMessage = this._handleMessage.bind(this);
47
+ window.addEventListener('message', this._handleMessage);
48
+ }
49
+
50
+ // =========================================================================
51
+ // Interface Implementation
52
+ // =========================================================================
53
+
54
+ getFormat() {
55
+ return `${this._baseFormat}-proxy`;
56
+ }
57
+
58
+ isConnected() {
59
+ return this._isConnected;
60
+ }
61
+
62
+ isTerminated() {
63
+ return this._isTerminated;
64
+ }
65
+
66
+ getCapabilities() {
67
+ // Mirror base format capabilities, but note async commit
68
+ const isScorm2004 = this._baseFormat === 'scorm2004';
69
+ return {
70
+ supportsObjectives: true,
71
+ supportsInteractions: true,
72
+ supportsComments: isScorm2004,
73
+ supportsEmergencySave: false,
74
+ maxSuspendDataBytes: isScorm2004 ? 65536 : 4096,
75
+ asyncCommit: true // postMessage is inherently async
76
+ };
77
+ }
78
+
79
+ async initialize() {
80
+ if (this._isConnected) {
81
+ logger.warn('ProxyDriver: Already initialized');
82
+ return true;
83
+ }
84
+
85
+ // Must be in an iframe
86
+ if (window.parent === window) {
87
+ throw new Error('ProxyDriver: Not running in iframe - proxy mode requires parent frame');
88
+ }
89
+
90
+ // Derive expected parent origin from document.referrer
91
+ // The referrer is the LMS/proxy page that loaded this iframe
92
+ try {
93
+ if (document.referrer) {
94
+ const referrerUrl = new URL(document.referrer);
95
+ this._parentOrigin = referrerUrl.origin;
96
+ logger.debug('ProxyDriver: Parent origin from referrer:', this._parentOrigin);
97
+ } else {
98
+ logger.warn('ProxyDriver: No document.referrer available — using wildcard origin. Some LMS strip the Referer header.');
99
+ }
100
+ } catch {
101
+ logger.warn('ProxyDriver: Could not parse document.referrer — using wildcard origin');
102
+ }
103
+
104
+ try {
105
+ const result = await this._sendMessage('Initialize');
106
+ if (result === true || result === 'true') {
107
+ this._isConnected = true;
108
+ logger.info('ProxyDriver: Connected via proxy bridge');
109
+
110
+ // Pre-fetch all needed values for synchronous access
111
+ await this._prefetch();
112
+
113
+ return true;
114
+ }
115
+ throw new Error('ProxyDriver: Initialize returned false');
116
+ } catch (error) {
117
+ logger.error('ProxyDriver: Initialize failed', error);
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ async terminate() {
123
+ if (this._isTerminated) {
124
+ logger.warn('ProxyDriver: Already terminated');
125
+ return true;
126
+ }
127
+
128
+ if (!this._isConnected) {
129
+ logger.warn('ProxyDriver: Cannot terminate - not connected');
130
+ return false;
131
+ }
132
+
133
+ try {
134
+ const result = await this._sendMessage('Terminate');
135
+ this._isTerminated = true;
136
+ this._isConnected = false;
137
+ window.removeEventListener('message', this._handleMessage);
138
+ logger.info('ProxyDriver: Terminated');
139
+ return result === true || result === 'true';
140
+ } catch (error) {
141
+ logger.error('ProxyDriver: Terminate failed', error);
142
+ this._isTerminated = true;
143
+ this._isConnected = false;
144
+ throw error;
145
+ }
146
+ }
147
+
148
+ async commit() {
149
+ this._ensureConnected();
150
+ try {
151
+ const result = await this._sendMessage('Commit');
152
+ return result === true || result === 'true';
153
+ } catch (error) {
154
+ logger.error('ProxyDriver: Commit failed', error);
155
+ throw error;
156
+ }
157
+ }
158
+
159
+ ping() {
160
+ if (this._isConnected && !this._isTerminated) {
161
+ this._sendMessage('GetValue', 'cmi.learner_id').catch(() => {
162
+ // Ignore ping errors
163
+ });
164
+ }
165
+ }
166
+
167
+ // =========================================================================
168
+ // Semantic Reads (served from pre-fetched cache)
169
+ // =========================================================================
170
+
171
+ getEntryMode() {
172
+ return this._cache.entry;
173
+ }
174
+
175
+ getBookmark() {
176
+ return this._cache.bookmark;
177
+ }
178
+
179
+ getCompletion() {
180
+ return this._cache.completion;
181
+ }
182
+
183
+ getSuccess() {
184
+ return this._cache.success;
185
+ }
186
+
187
+ getLearnerInfo() {
188
+ return {
189
+ id: this._cache.learnerId,
190
+ name: this._cache.learnerName
191
+ };
192
+ }
193
+
194
+ // =========================================================================
195
+ // Semantic Writes (fire-and-forget to proxy)
196
+ // =========================================================================
197
+
198
+ setBookmark(location) {
199
+ this._cache.bookmark = location;
200
+ const key = this._baseFormat === 'scorm1.2' ? 'cmi.core.lesson_location' : 'cmi.location';
201
+ this._sendSetValue(key, location);
202
+ }
203
+
204
+ reportScore({ raw, scaled, min, max }) {
205
+ const prefix = this._baseFormat === 'scorm1.2' ? 'cmi.core.score' : 'cmi.score';
206
+ if (raw !== undefined) this._sendSetValue(`${prefix}.raw`, String(raw));
207
+ if (min !== undefined) this._sendSetValue(`${prefix}.min`, String(min));
208
+ if (max !== undefined) this._sendSetValue(`${prefix}.max`, String(max));
209
+ if (scaled !== undefined && this._baseFormat === 'scorm2004') {
210
+ this._sendSetValue('cmi.score.scaled', String(scaled));
211
+ }
212
+ }
213
+
214
+ reportCompletion(status) {
215
+ this._cache.completion = status;
216
+ if (this._baseFormat === 'scorm1.2') {
217
+ // SCORM 1.2 uses combined lesson_status — proxy bridge handles mapping
218
+ this._sendSetValue('cmi.completion_status', status);
219
+ } else {
220
+ this._sendSetValue('cmi.completion_status', status);
221
+ }
222
+ }
223
+
224
+ reportSuccess(status) {
225
+ this._cache.success = status;
226
+ if (this._baseFormat === 'scorm1.2') {
227
+ this._sendSetValue('cmi.success_status', status);
228
+ } else {
229
+ this._sendSetValue('cmi.success_status', status);
230
+ }
231
+ }
232
+
233
+ reportProgress(measure) {
234
+ if (this._baseFormat === 'scorm2004') {
235
+ this._sendSetValue('cmi.progress_measure', String(measure));
236
+ }
237
+ }
238
+
239
+ reportSessionTime(duration) {
240
+ if (this._baseFormat === 'scorm1.2') {
241
+ // Proxy bridge will handle time format conversion
242
+ this._sendSetValue('cmi.session_time', duration);
243
+ } else {
244
+ this._sendSetValue('cmi.session_time', duration);
245
+ }
246
+ }
247
+
248
+ reportObjective(objective) {
249
+ if (!objective || !objective.id) return;
250
+ // Objectives are stored in suspend_data, not via CMI in proxy mode
251
+ // The proxy bridge doesn't relay complex array writes reliably
252
+ }
253
+
254
+ reportInteraction(interaction) {
255
+ if (!interaction || !interaction.id) return;
256
+ // Interactions are stored in suspend_data, not via CMI in proxy mode
257
+ }
258
+
259
+ setExitMode(mode) {
260
+ const key = this._baseFormat === 'scorm1.2' ? 'cmi.core.exit' : 'cmi.exit';
261
+ this._sendSetValue(key, mode === 'suspend' ? 'suspend' : '');
262
+ }
263
+
264
+ // =========================================================================
265
+ // Suspend Data
266
+ // =========================================================================
267
+
268
+ getSuspendData() {
269
+ this._ensureConnected();
270
+ return this._suspendDataCache;
271
+ }
272
+
273
+ setSuspendData(data) {
274
+ this._ensureConnected();
275
+ const value = JSON.stringify(data);
276
+ this._sendSetValue('cmi.suspend_data', value);
277
+ this._suspendDataCache = data;
278
+ return true;
279
+ }
280
+
281
+ // =========================================================================
282
+ // Private: Pre-fetch Strategy (eliminates _sendMessageSync)
283
+ // =========================================================================
284
+
285
+ /**
286
+ * Pre-fetch all needed values during initialize() for synchronous access.
287
+ * This eliminates the broken _sendMessageSync hack.
288
+ */
289
+ async _prefetch() {
290
+ const is12 = this._baseFormat === 'scorm1.2';
291
+
292
+ // Batch all reads in parallel
293
+ const keys = is12 ? {
294
+ entry: 'cmi.core.entry',
295
+ bookmark: 'cmi.core.lesson_location',
296
+ learnerId: 'cmi.core.student_id',
297
+ learnerName: 'cmi.core.student_name',
298
+ status: 'cmi.core.lesson_status',
299
+ suspendData: 'cmi.suspend_data'
300
+ } : {
301
+ entry: 'cmi.entry',
302
+ bookmark: 'cmi.location',
303
+ learnerId: 'cmi.learner_id',
304
+ learnerName: 'cmi.learner_name',
305
+ completion: 'cmi.completion_status',
306
+ success: 'cmi.success_status',
307
+ suspendData: 'cmi.suspend_data'
308
+ };
309
+
310
+ // Fire all reads in parallel
311
+ const results = {};
312
+ const promises = Object.entries(keys).map(async ([fieldName, cmiKey]) => {
313
+ try {
314
+ results[fieldName] = await this._sendMessage('GetValue', cmiKey);
315
+ } catch {
316
+ results[fieldName] = '';
317
+ }
318
+ });
319
+
320
+ await Promise.all(promises);
321
+
322
+ // Populate cache from results
323
+ this._cache.bookmark = results.bookmark || '';
324
+ this._cache.learnerId = results.learnerId || '';
325
+ this._cache.learnerName = results.learnerName || '';
326
+
327
+ if (is12) {
328
+ // SCORM 1.2: single lesson_status → split into completion + success
329
+ const status = results.status || '';
330
+ if (status === 'passed') {
331
+ this._cache.completion = 'completed';
332
+ this._cache.success = 'passed';
333
+ } else if (status === 'failed') {
334
+ this._cache.completion = 'completed';
335
+ this._cache.success = 'failed';
336
+ } else if (status === 'completed') {
337
+ this._cache.completion = 'completed';
338
+ this._cache.success = 'unknown';
339
+ } else {
340
+ this._cache.completion = status || 'unknown';
341
+ this._cache.success = 'unknown';
342
+ }
343
+ this._cache.entry = results.entry || '';
344
+ } else {
345
+ this._cache.completion = results.completion || 'unknown';
346
+ this._cache.success = results.success || 'unknown';
347
+ this._cache.entry = results.entry || '';
348
+ }
349
+
350
+ // Parse suspend_data
351
+ if (results.suspendData) {
352
+ try {
353
+ this._suspendDataCache = JSON.parse(results.suspendData);
354
+ } catch {
355
+ this._suspendDataCache = null;
356
+ }
357
+ }
358
+
359
+ logger.debug('ProxyDriver: Pre-fetch complete', {
360
+ bookmark: this._cache.bookmark,
361
+ completion: this._cache.completion,
362
+ hasSuspendData: this._suspendDataCache !== null
363
+ });
364
+ }
365
+
366
+ // =========================================================================
367
+ // Private: postMessage Transport
368
+ // =========================================================================
369
+
370
+ _ensureConnected() {
371
+ if (!this._isConnected) {
372
+ throw new Error('ProxyDriver: Not connected');
373
+ }
374
+ if (this._isTerminated) {
375
+ throw new Error('ProxyDriver: Session terminated');
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Fire-and-forget setValue to proxy bridge.
381
+ */
382
+ _sendSetValue(key, value) {
383
+ this._sendMessage('SetValue', key, String(value)).catch(err => {
384
+ logger.error(`ProxyDriver: SetValue failed for ${key}`, err);
385
+ });
386
+ }
387
+
388
+ /**
389
+ * Send async message to proxy bridge.
390
+ */
391
+ _sendMessage(method, ...args) {
392
+ return new Promise((resolve, reject) => {
393
+ const id = ++this._msgId;
394
+
395
+ const timeout = setTimeout(() => {
396
+ this._pending.delete(id);
397
+ reject(new Error(`ProxyDriver: Timeout waiting for ${method} response`));
398
+ }, MESSAGE_TIMEOUT);
399
+
400
+ this._pending.set(id, { resolve, reject, timeout });
401
+
402
+ window.parent.postMessage({
403
+ type: 'scorm-proxy-request',
404
+ id,
405
+ method,
406
+ args
407
+ }, this._parentOrigin);
408
+ });
409
+ }
410
+
411
+ /**
412
+ * Handle response messages from proxy bridge.
413
+ */
414
+ _handleMessage(event) {
415
+ const { data } = event;
416
+
417
+ if (!data || data.type !== 'scorm-proxy-response') {
418
+ return;
419
+ }
420
+
421
+ // Validate origin when we have a known parent origin
422
+ if (this._parentOrigin !== '*' && event.origin !== this._parentOrigin) {
423
+ logger.warn('ProxyDriver: Rejected message from unexpected origin:', event.origin);
424
+ return;
425
+ }
426
+
427
+ const { id, result, error } = data;
428
+ const pending = this._pending.get(id);
429
+
430
+ if (!pending) {
431
+ logger.warn(`ProxyDriver: Received response for unknown message ${id}`);
432
+ return;
433
+ }
434
+
435
+ clearTimeout(pending.timeout);
436
+ this._pending.delete(id);
437
+
438
+ if (error) {
439
+ pending.reject(new Error(error));
440
+ } else {
441
+ pending.resolve(result);
442
+ }
443
+ }
444
+ }