@patch-adams/core 1.4.20 → 1.4.21
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/dist/cli.cjs +28 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +28 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +28 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/schema.ts","../src/config/defaults.ts","../src/config/loader.ts","../src/templates/css-before.ts","../src/templates/css-after.ts","../src/templates/lrs-bridge.ts","../src/templates/js-before.ts","../src/templates/js-after.ts","../src/patcher/html-injector.ts","../src/patcher/storyline-html-injector.ts","../src/patcher/manifest-updater.ts","../src/detectors/format-detector.ts","../src/detectors/authoring-tool-detector.ts","../src/fingerprint/course-metadata.ts","../src/plugins/registry.ts","../src/patcher/index.ts","../src/fingerprint/course-fingerprint.ts","../src/utils/logger.ts"],"names":["z","resolve","existsSync","extname","readFileSync","pathToFileURL","AdmZip","archiver","PassThrough","extractFromImsManifest","extractFromTincan","extractFromCmi5","createHash","chalk"],"mappings":"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A;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AA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","file":"index.cjs","sourcesContent":["import { z } from 'zod';\n\n/**\n * LRS Bridge configuration for xAPI/LRS communication\n * Enables Rise courses to communicate with Bravais LRS like Xyleme native courses\n */\nexport const LrsBridgeConfigSchema = z.object({\n /** Whether LRS bridge is enabled (default: true) */\n enabled: z.boolean().default(true),\n /** Track video/audio plays - play, pause, complete events (default: true) */\n trackMedia: z.boolean().default(true),\n /** Track page/lesson navigation (default: true) */\n trackNavigation: z.boolean().default(true),\n /** Track quiz/knowledge check answers (default: true) */\n trackQuizzes: z.boolean().default(true),\n /** Track interactive element clicks - tabs, accordions, flashcards, etc. (default: true) */\n trackInteractions: z.boolean().default(true),\n /** Allow custom xAPI statements via window.pa_patcher.lrs.sendCustomStatement() (default: true) */\n customStatements: z.boolean().default(true),\n /** Enable debug logging to console (default: false) */\n debug: z.boolean().default(false),\n /** LRS endpoint URL for direct xAPI posting - if not set, auto-detects from Bravais domain */\n lrsEndpoint: z.string().optional(),\n /** Whether to include credentials (cookies) in LRS requests (default: true) */\n lrsWithCredentials: z.boolean().default(true),\n /** Course homepage IRI for xAPI context */\n courseHomepage: z.string().optional(),\n /** Auto-detect Bravais LRS endpoint from parent frame URL (default: true) */\n autoDetectLrs: z.boolean().default(true),\n /** Basic auth credentials for LRS (Base64 encoded \"username:password\") - used when no cookie session exists */\n lrsAuth: z.string().optional(),\n /** LRS proxy endpoint URL - statements are sent here instead of direct LRS, proxy handles auth server-side */\n lrsProxyEndpoint: z.string().optional(),\n /** Employee API endpoint URL for user identity lookup (e.g., https://node.example.com/admin_api/users/employees/) */\n employeeApiEndpoint: z.string().optional(),\n});\n\n/**\n * Configuration for a blocking asset (CSS before, JS before)\n * These load synchronously and block page rendering\n */\nexport const BlockingAssetConfigSchema = z.object({\n /** Filename for the asset */\n filename: z.string().min(1),\n /** Whether this asset is enabled */\n enabled: z.boolean().default(true),\n});\n\n/**\n * Configuration for an async asset (CSS after, JS after)\n * These load asynchronously with remote-first, local fallback\n */\nexport const AsyncAssetConfigSchema = z.object({\n /** Filename for the asset */\n filename: z.string().min(1),\n /** Whether this asset is enabled */\n enabled: z.boolean().default(true),\n /** Timeout in milliseconds for remote loading before falling back to local */\n timeout: z.number().min(1000).max(30000).default(5000),\n});\n\n/**\n * Local folder configuration for fallback files\n */\nexport const LocalFoldersConfigSchema = z.object({\n /** Folder name for CSS files within scormcontent */\n css: z.string().default('css'),\n /** Folder name for JS files within scormcontent */\n js: z.string().default('js'),\n});\n\n/**\n * Base schema for plugin configuration\n * Each plugin can extend this with their own options\n */\nexport const PluginConfigBaseSchema = z.object({\n /** Whether this plugin is enabled */\n enabled: z.boolean().default(false),\n}).passthrough(); // Allow additional properties from individual plugins\n\n/**\n * Plugins configuration - map of plugin name to plugin config\n */\nexport const PluginsConfigSchema = z.record(z.string(), PluginConfigBaseSchema).default({});\n\n/**\n * Main configuration schema for Patch-Adams\n */\nexport const PatchAdamsConfigSchema = z.object({\n /** Remote domain for assets (e.g., \"https://cdn.example.com/rise-overrides\") */\n remoteDomain: z.string().url(),\n\n /** CSS class added to <html> tag for specificity (default: \"pa-patched\") */\n htmlClass: z.string().default('pa-patched'),\n\n /** CSS class added during loading, removed when ready (default: \"pa-loading\") */\n loadingClass: z.string().default('pa-loading'),\n\n /** CSS loaded at start of <head> - blocking, prevents FOUC */\n cssBefore: BlockingAssetConfigSchema.default({\n filename: 'before.css',\n enabled: true,\n }),\n\n /** CSS loaded at end of <head> - async with fallback, for overrides */\n cssAfter: AsyncAssetConfigSchema.default({\n filename: 'after.css',\n enabled: true,\n timeout: 5000,\n }),\n\n /** JS loaded at start of <head> - blocking, for setup/interception */\n jsBefore: BlockingAssetConfigSchema.default({\n filename: 'before.js',\n enabled: true,\n }),\n\n /** JS loaded at end of <body> - async with fallback, removes loading class */\n jsAfter: AsyncAssetConfigSchema.default({\n filename: 'after.js',\n enabled: true,\n timeout: 5000,\n }),\n\n /** Local folder names for fallback files */\n localFolders: LocalFoldersConfigSchema.default({}),\n\n /** Whether to update manifest files (imsmanifest.xml, etc.) for SCORM compliance */\n updateManifests: z.boolean().default(true),\n\n /** Whether to fetch remote files and bake them as local fallbacks */\n fetchFallbacks: z.boolean().default(true),\n\n /** LRS Bridge configuration for xAPI/LRS communication with Bravais */\n lrsBridge: LrsBridgeConfigSchema.default({}),\n\n /** Plugin configurations - each plugin is keyed by its name */\n plugins: PluginsConfigSchema,\n});\n\nexport type PatchAdamsConfig = z.infer<typeof PatchAdamsConfigSchema>;\nexport type PluginsConfig = z.infer<typeof PluginsConfigSchema>;\nexport type LrsBridgeConfig = z.infer<typeof LrsBridgeConfigSchema>;\nexport type BlockingAssetConfig = z.infer<typeof BlockingAssetConfigSchema>;\nexport type AsyncAssetConfig = z.infer<typeof AsyncAssetConfigSchema>;\nexport type LocalFoldersConfig = z.infer<typeof LocalFoldersConfigSchema>;\n","import type { PatchAdamsConfig } from './schema.js';\n\n/**\n * Default configuration values for Patch-Adams\n */\nexport const defaultConfig: PatchAdamsConfig = {\n remoteDomain: 'https://cdn.example.com/rise-overrides',\n\n htmlClass: 'pa-patched',\n loadingClass: 'pa-loading',\n\n cssBefore: {\n filename: 'before.css',\n enabled: true,\n },\n\n cssAfter: {\n filename: 'after.css',\n enabled: true,\n timeout: 5000,\n },\n\n jsBefore: {\n filename: 'before.js',\n enabled: true,\n },\n\n jsAfter: {\n filename: 'after.js',\n enabled: true,\n timeout: 5000,\n },\n\n localFolders: {\n css: 'css',\n js: 'js',\n },\n\n updateManifests: true,\n\n fetchFallbacks: true,\n\n lrsBridge: {\n enabled: true,\n trackMedia: true,\n trackNavigation: true,\n trackQuizzes: true,\n trackInteractions: true,\n customStatements: true,\n debug: false,\n lrsEndpoint: undefined,\n lrsWithCredentials: true,\n courseHomepage: undefined,\n autoDetectLrs: true,\n },\n\n plugins: {},\n};\n","import { readFileSync, existsSync } from 'fs';\nimport { resolve, extname } from 'path';\nimport { pathToFileURL } from 'url';\nimport { PatchAdamsConfigSchema, type PatchAdamsConfig } from './schema.js';\nimport { defaultConfig } from './defaults.js';\n\n/**\n * Load and validate a Patch-Adams configuration file\n * Supports .ts, .js, and .json files\n */\nexport async function loadConfig(configPath: string): Promise<PatchAdamsConfig> {\n const absolutePath = resolve(configPath);\n\n if (!existsSync(absolutePath)) {\n throw new Error(`Configuration file not found: ${absolutePath}`);\n }\n\n const ext = extname(absolutePath).toLowerCase();\n let rawConfig: unknown;\n\n if (ext === '.json') {\n const content = readFileSync(absolutePath, 'utf-8');\n rawConfig = JSON.parse(content);\n } else if (ext === '.ts' || ext === '.js' || ext === '.mjs') {\n // Dynamic import for ESM modules\n const fileUrl = pathToFileURL(absolutePath).href;\n const module = await import(fileUrl);\n rawConfig = module.default ?? module;\n } else {\n throw new Error(`Unsupported config file extension: ${ext}. Use .ts, .js, .mjs, or .json`);\n }\n\n // Validate and return\n const validated = PatchAdamsConfigSchema.parse(rawConfig);\n return validated;\n}\n\n/**\n * Create a merged config with defaults\n */\nexport function mergeWithDefaults(partial: Partial<PatchAdamsConfig>): PatchAdamsConfig {\n return PatchAdamsConfigSchema.parse({\n ...defaultConfig,\n ...partial,\n });\n}\n\n/**\n * Generate a configuration file template\n */\nexport function generateConfigTemplate(): string {\n return `import type { PatchAdamsConfig } from '@patch-adams/core';\n\nconst config: PatchAdamsConfig = {\n // Remote domain where your CSS/JS files are hosted\n remoteDomain: 'https://cdn.example.com/rise-overrides',\n\n // HTML classes for specificity and loading state\n htmlClass: 'pa-patched', // Always present on <html>\n loadingClass: 'pa-loading', // Removed when assets are loaded\n\n // CSS loaded at start of <head> - BLOCKING\n // Use this to hide content and prevent flash of unstyled content\n cssBefore: {\n filename: 'before.css',\n enabled: true,\n },\n\n // CSS loaded at end of <head> - ASYNC with fallback\n // Use this for style overrides\n cssAfter: {\n filename: 'after.css',\n enabled: true,\n timeout: 5000, // ms before falling back to local\n },\n\n // JS loaded at start of <head> - BLOCKING\n // Use this for setup, API interception, globals\n jsBefore: {\n filename: 'before.js',\n enabled: true,\n },\n\n // JS loaded at end of <body> - ASYNC with fallback\n // Use this for DOM manipulation after Rise loads\n // This should remove the loadingClass when done\n jsAfter: {\n filename: 'after.js',\n enabled: true,\n timeout: 5000, // ms before falling back to local\n },\n\n // Folder names for local fallback files within scormcontent/\n localFolders: {\n css: 'css',\n js: 'js',\n },\n\n // Update manifest files for SCORM compliance\n updateManifests: true,\n};\n\nexport default config;\n`;\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface CssBeforeOptions {\n remoteUrl: string;\n localPath: string;\n htmlClass: string;\n loadingClass: string;\n}\n\n/**\n * Generate the blocking CSS loader for the \"before\" slot\n * This loads at the start of <head> and blocks rendering until loaded\n *\n * Strategy:\n * 1. First, inline the critical CSS to hide content immediately\n * 2. Then load the remote CSS synchronously (or local fallback)\n */\nexport function generateCssBeforeLoader(options: CssBeforeOptions): string {\n const { remoteUrl, localPath, htmlClass, loadingClass } = options;\n\n return `<!-- === PATCH-ADAMS: CSS BEFORE (blocking) === -->\n<style data-pa=\"css-before-critical\">\n/* Critical: Hide content until ready */\nhtml.${htmlClass}.${loadingClass} body {\n visibility: hidden !important;\n}\n</style>\n<script data-pa=\"css-before-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n\n function loadCSSSync(url) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.setAttribute('data-pa', 'css-before');\n document.head.appendChild(link);\n return link;\n }\n\n // Try remote first\n var link = loadCSSSync(REMOTE_URL);\n\n link.onerror = function() {\n console.warn('[Patch-Adams] CSS before failed to load from remote, using local fallback');\n document.head.removeChild(link);\n loadCSSSync(LOCAL_PATH);\n };\n\n link.onload = function() {\n console.log('[Patch-Adams] CSS before loaded from remote:', REMOTE_URL);\n };\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildCssBeforeOptions(config: PatchAdamsConfig): CssBeforeOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.css}/${config.cssBefore.filename}`,\n localPath: `${config.localFolders.css}/${config.cssBefore.filename}`,\n htmlClass: config.htmlClass,\n loadingClass: config.loadingClass,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface CssAfterOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n}\n\n/**\n * Generate the async CSS loader for the \"after\" slot\n * This loads at the end of <head> with remote-first, local fallback\n */\nexport function generateCssAfterLoader(options: CssAfterOptions): string {\n const { remoteUrl, localPath, timeout } = options;\n\n return `<!-- === PATCH-ADAMS: CSS AFTER (async with fallback) === -->\n<script data-pa=\"css-after-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n\n function loadCSS(url, onSuccess, onError) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.setAttribute('data-pa', 'css-after');\n\n link.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n link.onerror = function() {\n if (onError) onError();\n };\n\n document.head.appendChild(link);\n return link;\n }\n\n function loadCSSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteLink = loadCSS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[Patch-Adams] CSS after loaded from remote:', REMOTE_URL);\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[Patch-Adams] CSS after timed out, using local fallback');\n if (remoteLink.parentNode) {\n document.head.removeChild(remoteLink);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadCSS(\n LOCAL_PATH,\n function() {\n console.log('[Patch-Adams] CSS after loaded from local fallback:', LOCAL_PATH);\n },\n function() {\n console.error('[Patch-Adams] CSS after failed to load from both remote and local');\n }\n );\n }\n\n // Execute immediately\n loadCSSWithFallback();\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildCssAfterOptions(config: PatchAdamsConfig): CssAfterOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.css}/${config.cssAfter.filename}`,\n localPath: `${config.localFolders.css}/${config.cssAfter.filename}`,\n timeout: config.cssAfter.timeout,\n };\n}\n","/**\n * LRS Bridge Template for Patch-Adams\n *\n * Generates JavaScript code that bridges Rise courses to an LRS endpoint.\n * Supports direct xAPI statement posting when PlayerIntegration is unavailable.\n * Version 2.0.0 - Full xAPI support with direct LRS communication\n */\n\nexport interface LrsBridgeOptions {\n /** Whether LRS bridge is enabled */\n enabled: boolean;\n /** Track video/audio plays (play, pause, complete) */\n trackMedia: boolean;\n /** Track page/lesson navigation */\n trackNavigation: boolean;\n /** Track quiz/knowledge check answers */\n trackQuizzes: boolean;\n /** Track interactive element clicks (tabs, accordions, etc.) */\n trackInteractions: boolean;\n /** Allow custom xAPI statements via API */\n customStatements: boolean;\n /** Enable debug logging */\n debug: boolean;\n /** LRS endpoint URL for direct xAPI posting - if not provided, auto-detects from Bravais domain */\n lrsEndpoint?: string;\n /** Whether to include credentials (cookies) in LRS requests */\n lrsWithCredentials?: boolean;\n /** Course homepage IRI for xAPI context */\n courseHomepage?: string;\n /** Auto-detect Bravais LRS endpoint from parent frame URL (default: true) */\n autoDetectLrs?: boolean;\n /** Employee API endpoint for user lookup (e.g., https://node.example.com/admin_api/users/employees/) */\n employeeApiEndpoint?: string;\n /** Basic auth credentials for LRS (Base64 encoded \"username:password\") - used when no cookie session exists */\n lrsAuth?: string;\n /** LRS proxy endpoint URL - statements are sent here instead of direct LRS, proxy handles auth server-side */\n lrsProxyEndpoint?: string;\n}\n\nexport const DEFAULT_LRS_OPTIONS: LrsBridgeOptions = {\n enabled: true,\n trackMedia: true,\n trackNavigation: true,\n trackQuizzes: true,\n trackInteractions: true,\n customStatements: true,\n debug: false,\n lrsEndpoint: '',\n lrsWithCredentials: true,\n courseHomepage: '',\n autoDetectLrs: true,\n employeeApiEndpoint: '',\n lrsAuth: '',\n lrsProxyEndpoint: '',\n};\n\n/**\n * Generate the LRS bridge JavaScript code\n * This gets embedded into the js-before loader and runs immediately\n */\nexport function generateLrsBridgeCode(options: LrsBridgeOptions): string {\n if (!options.enabled) {\n return `\n // LRS Bridge disabled\n window.pa_patcher.lrs = { enabled: false, initialized: false };\n`;\n }\n\n // Escape strings for embedding in JS\n const lrsEndpoint = options.lrsEndpoint || '';\n const courseHomepage = options.courseHomepage || '';\n const employeeApiEndpoint = options.employeeApiEndpoint || '';\n const lrsAuth = options.lrsAuth || '';\n const lrsProxyEndpoint = options.lrsProxyEndpoint || '';\n\n return `\n // ==========================================================================\n // PATCH-ADAMS LRS BRIDGE v2.2.0\n // Full xAPI support with direct LRS communication for Rise courses\n // ALL statements use DOCUMENT as main object for Bravais aggregation\n // Activity type (video, assessment, etc.) stored in object.definition.type\n // Page/lesson info stored in context.extensions for navigation tracking\n // ==========================================================================\n (function() {\n 'use strict';\n\n var DEBUG = ${options.debug};\n // Allow URL-based debug toggle: ?pa_debug=1 or #pa_debug\n try {\n if (window.location.search.indexOf('pa_debug') > -1 || window.location.hash.indexOf('pa_debug') > -1) {\n DEBUG = true;\n }\n } catch (e) {}\n var TRACK_MEDIA = ${options.trackMedia};\n var TRACK_NAVIGATION = ${options.trackNavigation};\n var TRACK_QUIZZES = ${options.trackQuizzes};\n var TRACK_INTERACTIONS = ${options.trackInteractions};\n var CUSTOM_STATEMENTS = ${options.customStatements};\n var LRS_ENDPOINT_CONFIG = '${lrsEndpoint}';\n var LRS_WITH_CREDENTIALS = ${options.lrsWithCredentials ?? true};\n var COURSE_HOMEPAGE = '${courseHomepage}';\n var AUTO_DETECT_LRS = ${options.autoDetectLrs ?? true};\n var EMPLOYEE_API_ENDPOINT = '${employeeApiEndpoint}';\n var LRS_AUTH = '${lrsAuth}';\n var LRS_PROXY_ENDPOINT = '${lrsProxyEndpoint}';\n\n // Bravais LRS endpoint pattern: https://lrs-{tenant}.bravais.com/XAPI/statements\n // Example: core-acme.bravais.com -> lrs-acme.bravais.com\n var LRS_ENDPOINT = ''; // Will be set by auto-detection or config\n\n function log() {\n if (DEBUG && window.console && window.console.log) {\n var args = Array.prototype.slice.call(arguments);\n args.unshift('[PA-LRS]');\n console.log.apply(console, args);\n }\n }\n\n function warn() {\n if (window.console && window.console.warn) {\n var args = Array.prototype.slice.call(arguments);\n args.unshift('[PA-LRS]');\n console.warn.apply(console, args);\n }\n }\n\n // ========================================================================\n // XAPI VERB DEFINITIONS (ADL standard IRIs)\n // ========================================================================\n var VERBS = {\n launched: {\n id: 'http://adlnet.gov/expapi/verbs/launched',\n display: { 'en-US': 'launched' }\n },\n initialized: {\n id: 'http://adlnet.gov/expapi/verbs/initialized',\n display: { 'en-US': 'initialized' }\n },\n viewed: {\n id: 'http://id.tincanapi.com/verb/viewed',\n display: { 'en-US': 'viewed' }\n },\n experienced: {\n id: 'http://adlnet.gov/expapi/verbs/experienced',\n display: { 'en-US': 'experienced' }\n },\n played: {\n id: 'https://w3id.org/xapi/video/verbs/played',\n display: { 'en-US': 'played' }\n },\n paused: {\n id: 'https://w3id.org/xapi/video/verbs/paused',\n display: { 'en-US': 'paused' }\n },\n completed: {\n id: 'http://adlnet.gov/expapi/verbs/completed',\n display: { 'en-US': 'completed' }\n },\n attempted: {\n id: 'http://adlnet.gov/expapi/verbs/attempted',\n display: { 'en-US': 'attempted' }\n },\n answered: {\n id: 'http://adlnet.gov/expapi/verbs/answered',\n display: { 'en-US': 'answered' }\n },\n passed: {\n id: 'http://adlnet.gov/expapi/verbs/passed',\n display: { 'en-US': 'passed' }\n },\n failed: {\n id: 'http://adlnet.gov/expapi/verbs/failed',\n display: { 'en-US': 'failed' }\n },\n interacted: {\n id: 'http://adlnet.gov/expapi/verbs/interacted',\n display: { 'en-US': 'interacted' }\n },\n progressed: {\n id: 'http://adlnet.gov/expapi/verbs/progressed',\n display: { 'en-US': 'progressed' }\n },\n terminated: {\n id: 'http://adlnet.gov/expapi/verbs/terminated',\n display: { 'en-US': 'terminated' }\n },\n // ActivityStrea.ms verbs for Xyleme/Bravais compatibility\n opened: {\n id: 'http://activitystrea.ms/schema/1.0/open',\n display: { 'en-US': 'opened' }\n },\n closed: {\n id: 'http://activitystrea.ms/schema/1.0/close',\n display: { 'en-US': 'closed' }\n },\n exited: {\n id: 'http://activitystrea.ms/schema/1.0/close',\n display: { 'en-US': 'exited' }\n }\n };\n\n // ========================================================================\n // ACTIVITY TYPES\n // ========================================================================\n var ACTIVITY_TYPES = {\n course: 'http://adlnet.gov/expapi/activities/course',\n module: 'http://adlnet.gov/expapi/activities/module',\n lesson: 'http://adlnet.gov/expapi/activities/lesson',\n assessment: 'http://adlnet.gov/expapi/activities/assessment',\n question: 'http://adlnet.gov/expapi/activities/question',\n interaction: 'http://adlnet.gov/expapi/activities/interaction',\n media: 'http://adlnet.gov/expapi/activities/media',\n video: 'https://w3id.org/xapi/video/activity-type/video',\n audio: 'https://w3id.org/xapi/audio/activity-type/audio'\n };\n\n // ========================================================================\n // XYLEME RESOURCE TYPES (for Bravais Analytics)\n // ========================================================================\n var RESOURCE_TYPES = {\n course: 'Course',\n document: 'Course',\n lesson: 'Topic',\n page: 'Topic',\n topic: 'Topic',\n assessment: 'Assessment',\n question: 'Question',\n interaction: 'Topic',\n media: 'Topic',\n video: 'Topic',\n audio: 'Topic'\n };\n\n // Xyleme-specific activity type IRIs\n var XYLEME_ACTIVITY_TYPES = {\n page: 'http://activitystrea.ms/schema/1.0/page',\n document: 'http://xyleme.com/bravais/activities/document',\n assessment: 'http://adlnet.gov/expapi/activities/assessment',\n question: 'http://adlnet.gov/expapi/activities/question'\n };\n\n // ========================================================================\n // LRS NAMESPACE & STATE\n // ========================================================================\n var LRS = window.pa_patcher.lrs = {\n version: '2.2.0',\n enabled: true,\n initialized: false,\n mode: null, // 'playerIntegration', 'directLRS', 'scormOnly'\n integration: null, // Xyleme PlayerIntegration (if available)\n integrationLevel: null,\n scormApi: null,\n scormApiLevel: null,\n scormApiWindow: null,\n scormApiFound: false,\n scormApiType: null,\n lrsEndpoint: LRS_ENDPOINT,\n statementQueue: [], // xAPI statements queued for sending\n eventLog: [], // All events logged (for debugging)\n currentLesson: null,\n courseInfo: null, // Course metadata\n actor: null, // xAPI actor object\n sessionId: null, // Session UUID\n launchTime: null, // ISO timestamp of course launch\n courseAttemptId: null, // Unique ID for this course attempt session (Xyleme format)\n // Statistics\n stats: {\n statementsSent: 0,\n statementsQueued: 0,\n statementsFailed: 0\n }\n };\n\n // Generate UUID for session tracking\n function generateUUID() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0;\n var v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n }\n\n LRS.sessionId = generateUUID();\n LRS.courseAttemptId = generateUUID(); // Unique per session for Xyleme correlation\n LRS.launchTime = new Date().toISOString();\n log('Generated courseAttemptId:', LRS.courseAttemptId);\n\n // ========================================================================\n // 1. BRAVAIS LRS ENDPOINT AUTO-DETECTION\n // ========================================================================\n\n /**\n * Auto-detect Bravais LRS endpoint from current URL or parent frames\n * Bravais pattern: https://core-{tenant}.bravais.com -> https://lrs-{tenant}.bravais.com/XAPI/statements\n */\n function detectBravaisLrsEndpoint() {\n var urlsToCheck = [];\n\n // Check current window\n urlsToCheck.push(window.location.href);\n\n // Check parent frames\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) {\n // Cross-origin, try document.referrer\n break;\n }\n } else {\n break;\n }\n }\n } catch (e) {}\n\n // Also check referrer\n if (document.referrer) {\n urlsToCheck.push(document.referrer);\n }\n\n log('Checking URLs for Bravais domain:', urlsToCheck);\n\n // Look for Bravais domain pattern\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: core-{tenant}.bravais.com or {tenant}.bravais.com\n var match = url.match(/(?:core-)?([a-zA-Z0-9_-]+)\\\\.bravais\\\\.com/);\n if (match) {\n var tenant = match[1];\n var lrsEndpoint = 'https://lrs-' + tenant + '.bravais.com/XAPI/statements';\n log('Detected Bravais tenant:', tenant);\n log('Auto-detected LRS endpoint:', lrsEndpoint);\n return lrsEndpoint;\n }\n }\n\n log('No Bravais domain detected');\n return null;\n }\n\n // ========================================================================\n // 2. SCORM/PLAYERINTEGRATION DETECTION\n // ========================================================================\n\n function findAPIInFrameHierarchy(apiName, maxLevels) {\n maxLevels = maxLevels || 10;\n var win = window;\n var level = 0;\n\n while (level < maxLevels) {\n try {\n if (win[apiName]) {\n log('Found ' + apiName + ' at level ' + level);\n return { api: win[apiName], level: level, window: win };\n }\n } catch (e) {\n log('Cross-origin block at level ' + level);\n }\n\n try {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n level++;\n } else {\n break;\n }\n } catch (e) {\n break;\n }\n }\n\n return null;\n }\n\n function findPlayerIntegration(maxLevels) {\n maxLevels = maxLevels || 10;\n var win = window;\n var level = 0;\n\n while (level < maxLevels) {\n try {\n if (win.PlayerIntegration) {\n log('Found PlayerIntegration at level ' + level);\n return { integration: win.PlayerIntegration, level: level, window: win };\n }\n } catch (e) {}\n\n try {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n level++;\n } else {\n break;\n }\n } catch (e) {\n break;\n }\n }\n\n return null;\n }\n\n function setupBridge() {\n try {\n // 1. Try PlayerIntegration first (Xyleme native mode) - for SCORM APIs\n var integrationResult = findPlayerIntegration(10);\n if (integrationResult) {\n LRS.integration = integrationResult.integration;\n LRS.integrationLevel = integrationResult.level;\n log('Found PlayerIntegration at level', integrationResult.level);\n\n if (LRS.integration.Internal_SCORM_2004_API) {\n window.API_1484_11 = LRS.integration.Internal_SCORM_2004_API;\n LRS.scormApi = window.API_1484_11;\n LRS.scormApiFound = true;\n LRS.scormApiType = '2004';\n }\n if (LRS.integration.Internal_SCORM_12_API) {\n window.API = LRS.integration.Internal_SCORM_12_API;\n if (!LRS.scormApiFound) {\n LRS.scormApi = window.API;\n LRS.scormApiFound = true;\n LRS.scormApiType = '1.2';\n }\n }\n // Don't return early - continue to detect LRS endpoint for direct xAPI\n } else {\n // 2. Search for SCORM APIs directly\n log('No PlayerIntegration found, searching for SCORM APIs...');\n\n var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);\n if (api2004) {\n LRS.scormApi = api2004.api;\n LRS.scormApiLevel = api2004.level;\n LRS.scormApiWindow = api2004.window;\n LRS.scormApiFound = true;\n LRS.scormApiType = '2004';\n log('Found SCORM 2004 API at level ' + api2004.level);\n } else {\n var api12 = findAPIInFrameHierarchy('API', 10);\n if (api12) {\n LRS.scormApi = api12.api;\n LRS.scormApiLevel = api12.level;\n LRS.scormApiWindow = api12.window;\n LRS.scormApiFound = true;\n LRS.scormApiType = '1.2';\n log('Found SCORM 1.2 API at level ' + api12.level);\n }\n }\n }\n\n // 3. ALWAYS try to determine LRS endpoint for direct xAPI statements\n // This is critical for detailed tracking (media, interactions, quizzes)\n // Priority: 1) Configured endpoint, 2) Auto-detected Bravais endpoint\n if (LRS_ENDPOINT_CONFIG && LRS_ENDPOINT_CONFIG.length > 0) {\n LRS_ENDPOINT = LRS_ENDPOINT_CONFIG;\n log('Using configured LRS endpoint:', LRS_ENDPOINT);\n } else if (AUTO_DETECT_LRS) {\n var detectedEndpoint = detectBravaisLrsEndpoint();\n if (detectedEndpoint) {\n LRS_ENDPOINT = detectedEndpoint;\n log('Using auto-detected Bravais LRS endpoint:', LRS_ENDPOINT);\n }\n }\n\n LRS.lrsEndpoint = LRS_ENDPOINT;\n\n // 4. Determine mode based on what we found\n // Prefer directLRS when available for detailed tracking\n if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {\n LRS.mode = 'directLRS';\n log('Using direct LRS mode with endpoint:', LRS_ENDPOINT);\n if (LRS.integration) {\n log('PlayerIntegration also available for SCORM state');\n }\n return true;\n } else if (LRS.integration) {\n LRS.mode = 'playerIntegration';\n log('Using PlayerIntegration mode (no direct LRS endpoint)');\n return true;\n } else if (LRS.scormApiFound) {\n LRS.mode = 'scormOnly';\n log('Using SCORM-only mode (no LRS endpoint detected)');\n return true;\n }\n\n warn('No LRS endpoint or SCORM API found');\n return false;\n\n } catch (e) {\n warn('Error setting up bridge:', e);\n return false;\n }\n }\n\n // ========================================================================\n // 3. ACTOR EXTRACTION (multiple sources with fallbacks)\n // ========================================================================\n\n // Store for Bravais session data\n var bravaisSessionData = null;\n var bravaisSessionFetched = false;\n\n /**\n * Parse actor info from URL query parameters\n * Bravais thin packs often pass user info via URL params\n */\n function getActorFromUrlParams() {\n try {\n var params = new URLSearchParams(window.location.search);\n\n // Check various URL parameter names used by Bravais\n var userId = params.get('learner_id') || params.get('learnerId') ||\n params.get('user_id') || params.get('userId') ||\n params.get('actor_id') || params.get('actorId');\n var userName = params.get('learner_name') || params.get('learnerName') ||\n params.get('user_name') || params.get('userName') ||\n params.get('actor_name') || params.get('actorName');\n var userEmail = params.get('learner_email') || params.get('learnerEmail') ||\n params.get('user_email') || params.get('userEmail') ||\n params.get('actor_email') || params.get('actorEmail') ||\n params.get('email');\n var homePage = params.get('homepage') || params.get('homePage') ||\n params.get('tenant') || params.get('tenantId');\n\n if (userId || userEmail) {\n log('Found actor info in URL params');\n var actor = { objectType: 'Agent' };\n\n if (userName) {\n actor.name = userName;\n }\n if (userEmail) {\n actor.mbox = userEmail.indexOf('mailto:') === 0 ? userEmail : 'mailto:' + userEmail;\n }\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: userId\n };\n }\n return actor;\n }\n\n // Check for base64 encoded actor parameter\n var actorParam = params.get('actor');\n if (actorParam) {\n try {\n var actorJson = atob(actorParam);\n var actor = JSON.parse(actorJson);\n log('Found base64 encoded actor in URL');\n return actor;\n } catch (e) {\n log('Could not decode actor param:', e.message);\n }\n }\n\n } catch (e) {\n log('Error parsing URL params:', e.message);\n }\n return null;\n }\n\n /**\n * Parse actor from Bravais launch data (stored in SCORM launch_data)\n * Bravais stores JSON in cmi.launch_data with user context\n */\n function getActorFromLaunchData() {\n if (!LRS.scormApi) return null;\n\n try {\n var launchData = null;\n if (LRS.scormApiType === '2004') {\n launchData = LRS.scormApi.GetValue('cmi.launch_data');\n } else {\n launchData = LRS.scormApi.LMSGetValue('cmi.launch_data');\n }\n\n if (launchData) {\n // Bravais stores base64 encoded JSON in launch_data\n try {\n var decoded = atob(launchData);\n var data = JSON.parse(decoded);\n log('Parsed launch data:', Object.keys(data));\n\n // Look for actor/user info in launch data\n if (data.actor) {\n log('Found actor in launch data');\n return data.actor;\n }\n\n // Build actor from launch data fields\n if (data.userId || data.userID || data.learnerId) {\n var actor = { objectType: 'Agent' };\n var userId = data.userId || data.userID || data.learnerId;\n var userName = data.userName || data.userFirstName ?\n ((data.userFirstName || '') + ' ' + (data.userLastName || '')).trim() :\n data.learnerName;\n var userEmail = data.userEmail || data.learnerEmail || data.email;\n var homePage = data.xApiTenantId || data.tenantId || data.homePage;\n\n if (userName) actor.name = userName;\n if (userEmail) actor.mbox = 'mailto:' + userEmail;\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: String(userId)\n };\n }\n log('Built actor from launch data fields');\n return actor;\n }\n } catch (e) {\n // Not base64/JSON, might be plain text\n log('Launch data is not base64 JSON:', e.message);\n }\n }\n } catch (e) {\n log('Error reading launch data:', e.message);\n }\n return null;\n }\n\n /**\n * Try to get user info from Bravais context cookie\n * Cookie name pattern: Bravais-{env}-{region}-Context\n * Cookie value is base64 encoded JSON with user info\n */\n function getActorFromBravaisCookie() {\n try {\n var cookies = document.cookie.split(';');\n for (var i = 0; i < cookies.length; i++) {\n var cookie = cookies[i].trim();\n // Look for Bravais context cookies\n if (cookie.indexOf('Bravais-') === 0 && cookie.indexOf('-Context=') > -1) {\n var cookieName = cookie.split('=')[0];\n var cookieValue = cookie.substring(cookieName.length + 1);\n log('Found Bravais context cookie:', cookieName);\n\n try {\n // Cookie value is base64 encoded JSON\n var decoded = atob(cookieValue);\n var contextData = JSON.parse(decoded);\n log('Decoded Bravais cookie data keys:', Object.keys(contextData));\n\n // Extract user info from context\n if (contextData.userId || contextData.userID || contextData.user) {\n var actor = { objectType: 'Agent' };\n var user = contextData.user || contextData;\n\n var userId = user.userId || user.userID || user.id;\n var userName = user.userName || user.name ||\n ((user.firstName || user.userFirstName || '') + ' ' +\n (user.lastName || user.userLastName || '')).trim();\n var userEmail = user.email || user.userEmail;\n var homePage = contextData.xApiTenantId || contextData.tenantId ||\n contextData.portalUrl || contextData.coreUrl;\n\n if (userName) actor.name = userName;\n if (userEmail) actor.mbox = 'mailto:' + userEmail;\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: String(userId)\n };\n }\n log('Built actor from Bravais cookie');\n return actor;\n }\n } catch (e) {\n log('Could not decode Bravais cookie:', e.message);\n }\n }\n }\n log('No Bravais context cookie found or not accessible');\n } catch (e) {\n log('Cannot access cookies (cross-origin or blocked)');\n }\n return null;\n }\n\n /**\n * Try to get actor from parent frame's data\n * Bravais thin packs inject sessionData, actor, and PARAMS into the parent page\n */\n function getActorFromParentFrame() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n // Check for Bravais PARAMS object (contains actor directly)\n if (win.PARAMS && win.PARAMS.actor) {\n log('Found PARAMS.actor at level', level);\n var actor = win.PARAMS.actor;\n // Ensure it's properly formatted\n if (actor.objectType !== 'Agent') {\n actor.objectType = 'Agent';\n }\n return actor;\n }\n\n // Check for Bravais sessionData (server-injected user info)\n if (win.sessionData && (win.sessionData.userId || win.sessionData.userID)) {\n log('Found sessionData at level', level);\n var sd = win.sessionData;\n var actor = { objectType: 'Agent' };\n\n // Name from firstName + lastName\n var firstName = sd.userFirstName || '';\n var lastName = sd.userLastName || '';\n if (firstName || lastName) {\n actor.name = (firstName + ' ' + lastName).trim();\n }\n\n // Account with homePage and userId\n var homePage = sd.xApiTenantId || sd.webPortalUrl || sd.coreUrl;\n var userId = sd.userId || sd.userID;\n if (homePage && userId) {\n actor.account = {\n homePage: homePage,\n name: String(userId)\n };\n }\n\n // mbox from email\n if (sd.userEmail) {\n actor.mbox = 'mailto:' + sd.userEmail;\n }\n\n log('Built actor from sessionData:', actor);\n return actor;\n }\n\n // Check for standalone actor variable (Bravais sets this from sessionData)\n if (win.actor && (win.actor.account || win.actor.mbox)) {\n log('Found actor variable at level', level);\n var actor = win.actor;\n if (actor.objectType !== 'Agent') {\n actor.objectType = 'Agent';\n }\n return actor;\n }\n\n // Check for Bravais player data\n if (win.BRAVAIS_LEARNER) {\n log('Found BRAVAIS_LEARNER at level', level);\n var learner = win.BRAVAIS_LEARNER;\n var actor = { objectType: 'Agent' };\n if (learner.name) actor.name = learner.name;\n if (learner.email) actor.mbox = 'mailto:' + learner.email;\n if (learner.id) {\n actor.account = {\n homePage: learner.homePage || window.location.origin,\n name: String(learner.id)\n };\n }\n return actor;\n }\n\n // Check for xAPI actor\n if (win.xAPILearner || win.XAPI_ACTOR) {\n log('Found xAPI actor at level', level);\n return win.xAPILearner || win.XAPI_ACTOR;\n }\n\n // Check for PA_ACTOR (set by patch-adams)\n if (win.PA_ACTOR) {\n log('Found PA_ACTOR at level', level);\n return win.PA_ACTOR;\n }\n\n // Check for generic learner data\n if (win.learnerData || win.userData) {\n var data = win.learnerData || win.userData;\n log('Found learner/user data at level', level);\n if (data.id || data.userId || data.email) {\n var actor = { objectType: 'Agent' };\n if (data.name) actor.name = data.name;\n if (data.email) actor.mbox = 'mailto:' + data.email;\n if (data.id || data.userId) {\n actor.account = {\n homePage: data.homePage || window.location.origin,\n name: String(data.id || data.userId)\n };\n }\n return actor;\n }\n }\n\n } catch (e) {\n // Cross-origin blocked at this level\n log('Cross-origin block at level', level, '- trying session API instead');\n break;\n }\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {\n log('Error accessing parent frames:', e.message);\n }\n return null;\n }\n\n /**\n * Fetch user info from Bravais session API\n * Returns session data or null if not available\n */\n function fetchBravaisSessionData(callback) {\n if (bravaisSessionFetched) {\n callback(bravaisSessionData);\n return;\n }\n\n // Detect Bravais core URL from parent frames or current URL\n var coreUrl = null;\n var urlsToCheck = [window.location.href, document.referrer];\n\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n log('URLs to check for core URL:', urlsToCheck);\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var match = urlsToCheck[j].match(/(https?:\\\\/\\\\/core-[a-zA-Z0-9_-]+\\\\.bravais\\\\.com)/);\n if (match) {\n coreUrl = match[1];\n break;\n }\n }\n\n if (!coreUrl) {\n log('No Bravais core URL detected for session fetch');\n bravaisSessionFetched = true;\n callback(null);\n return;\n }\n\n log('Fetching session data from:', coreUrl + '/api/shared/sessionData');\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', coreUrl + '/api/shared/sessionData', true);\n xhr.withCredentials = true; // Send cookies for auth\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n bravaisSessionFetched = true;\n log('Session API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n bravaisSessionData = JSON.parse(xhr.responseText);\n log('Bravais session data received:', Object.keys(bravaisSessionData));\n callback(bravaisSessionData);\n } catch (e) {\n warn('Error parsing session data:', e);\n callback(null);\n }\n } else {\n log('Session data fetch failed (CORS or auth). Status:', xhr.status);\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n bravaisSessionFetched = true;\n log('Network error fetching session data (CORS blocked)');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n bravaisSessionFetched = true;\n warn('Error sending session request:', e);\n callback(null);\n }\n }\n\n /**\n * Build actor from Bravais session data\n */\n function buildActorFromBravaisSession(sessionData) {\n if (!sessionData) return null;\n\n var actor = {\n objectType: 'Agent'\n };\n\n // Name from first + last name\n var firstName = sessionData.userFirstName || '';\n var lastName = sessionData.userLastName || '';\n if (firstName || lastName) {\n actor.name = (firstName + ' ' + lastName).trim();\n }\n\n // Account with homePage and user ID\n var homePage = sessionData.xApiTenantId || sessionData.webPortalUrl || sessionData.coreUrl;\n if (homePage && sessionData.userID) {\n actor.account = {\n homePage: homePage,\n name: String(sessionData.userID)\n };\n }\n\n // mbox from email (preferred by Bravais LRS)\n if (sessionData.userEmail) {\n actor.mbox = 'mailto:' + sessionData.userEmail;\n }\n\n return actor;\n }\n\n // Store for employee lookup data\n var employeeLookupData = null;\n var employeeLookupFetched = false;\n\n /**\n * Fetch user email from employee API using employee ID\n * Endpoint configured via EMPLOYEE_API_ENDPOINT (e.g., https://node.example.com/admin_api/users/employees/)\n */\n function fetchEmployeeEmail(employeeId, callback) {\n if (!employeeId) {\n callback(null);\n return;\n }\n\n // Skip if no employee API endpoint configured\n if (!EMPLOYEE_API_ENDPOINT) {\n log('Employee API endpoint not configured, skipping lookup');\n callback(null);\n return;\n }\n\n // In-memory cache check\n if (employeeLookupFetched && employeeLookupData) {\n callback(employeeLookupData);\n return;\n }\n\n // localStorage cache check (persists across page loads / sessions)\n var cacheKey = 'pa_employee_' + employeeId;\n try {\n var cached = localStorage.getItem(cacheKey);\n if (cached) {\n var parsed = JSON.parse(cached);\n // TTL: 7 days = 604800000 ms\n if (parsed.timestamp && (Date.now() - parsed.timestamp) < 604800000) {\n log('Employee data from localStorage cache');\n employeeLookupData = parsed;\n employeeLookupFetched = true;\n callback(parsed);\n return;\n }\n localStorage.removeItem(cacheKey);\n }\n } catch (e) {\n // localStorage unavailable (private browsing, iframe restrictions)\n }\n\n // Build URL: endpoint should end with ? or include query param structure\n var apiUrl = EMPLOYEE_API_ENDPOINT + (EMPLOYEE_API_ENDPOINT.indexOf('?') >= 0 ? '&' : '?') + 'q=' + encodeURIComponent(employeeId);\n log('Fetching employee data from:', apiUrl);\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n employeeLookupFetched = true;\n log('Employee API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n var data = JSON.parse(xhr.responseText);\n log('Employee data received:', data);\n\n // Response could be array or single object\n var employee = Array.isArray(data) ? data[0] : data;\n if (employee) {\n // API returns: business_email, fname, lname, emp_id\n employeeLookupData = {\n email: employee.business_email || employee.email || employee.Email || employee.userEmail,\n name: employee.name || employee.displayName || employee.fullName ||\n ((employee.fname || employee.firstName || '') + ' ' + (employee.lname || employee.lastName || '')).trim(),\n firstName: employee.fname || employee.firstName || employee.first_name,\n lastName: employee.lname || employee.lastName || employee.last_name,\n employeeId: employee.emp_id || employee.employeeId || employee.employee_id || employeeId\n };\n log('Parsed employee data:', employeeLookupData);\n // Persist to localStorage for cross-session caching\n try {\n var cacheData = {};\n for (var k in employeeLookupData) { if (employeeLookupData.hasOwnProperty(k)) cacheData[k] = employeeLookupData[k]; }\n cacheData.timestamp = Date.now();\n localStorage.setItem(cacheKey, JSON.stringify(cacheData));\n log('Employee data cached in localStorage');\n } catch (e) { /* localStorage unavailable */ }\n callback(employeeLookupData);\n } else {\n callback(null);\n }\n } catch (e) {\n warn('Error parsing employee data:', e);\n callback(null);\n }\n } else {\n log('Employee lookup failed. Status:', xhr.status);\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n employeeLookupFetched = true;\n log('Network error fetching employee data');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n employeeLookupFetched = true;\n warn('Error sending employee request:', e);\n callback(null);\n }\n }\n\n /**\n * Enhance actor with email from employee lookup\n */\n function enhanceActorWithEmail(actor, callback) {\n if (!actor) {\n callback(actor);\n return;\n }\n\n // If actor already has email, no need to lookup\n if (actor.mbox && actor.mbox !== 'mailto:' && actor.mbox !== 'mailto:unknown') {\n callback(actor);\n return;\n }\n\n // Get employee ID from actor account name (SCORM learner_id is often employee ID)\n var employeeId = actor.account ? actor.account.name : null;\n if (!employeeId || employeeId === 'unknown' || employeeId === 'preview_user') {\n callback(actor);\n return;\n }\n\n log('Looking up email for employee ID:', employeeId);\n\n fetchEmployeeEmail(employeeId, function(employeeData) {\n if (employeeData && employeeData.email) {\n actor.mbox = 'mailto:' + employeeData.email;\n log('Enhanced actor with email:', employeeData.email);\n\n // Also update name if we got a better one\n if (employeeData.name && (!actor.name || actor.name === 'Unknown Learner')) {\n actor.name = employeeData.name;\n }\n }\n callback(actor);\n });\n }\n\n function extractActor() {\n var defaultActor = {\n objectType: 'Agent',\n name: 'Unknown Learner',\n account: {\n homePage: COURSE_HOMEPAGE || window.location.origin,\n name: 'unknown'\n }\n };\n\n var actor = null;\n\n log('Extracting actor from available sources...');\n\n // Priority 1: URL parameters (most reliable for thin packs)\n actor = getActorFromUrlParams();\n if (actor && isValidActor(actor)) {\n log('Actor source: URL parameters');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 2: Parent frame data\n actor = getActorFromParentFrame();\n if (actor && isValidActor(actor)) {\n log('Actor source: Parent frame');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 3: Bravais context cookie (if accessible)\n actor = getActorFromBravaisCookie();\n if (actor && isValidActor(actor)) {\n log('Actor source: Bravais cookie');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 4: SCORM launch data (may contain user info)\n actor = getActorFromLaunchData();\n if (actor && isValidActor(actor)) {\n log('Actor source: SCORM launch data');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 5: Standard SCORM learner fields\n if (LRS.scormApi) {\n try {\n var scormLearnerId = null;\n var scormLearnerName = null;\n\n if (LRS.scormApiType === '2004') {\n scormLearnerId = LRS.scormApi.GetValue('cmi.learner_id');\n scormLearnerName = LRS.scormApi.GetValue('cmi.learner_name');\n } else {\n scormLearnerId = LRS.scormApi.LMSGetValue('cmi.core.student_id');\n scormLearnerName = LRS.scormApi.LMSGetValue('cmi.core.student_name');\n }\n\n log('SCORM learner_id:', scormLearnerId);\n log('SCORM learner_name:', scormLearnerName);\n\n // Check if we have valid SCORM data (not preview/unknown)\n var isValidScormId = scormLearnerId &&\n scormLearnerId !== 'preview_user' &&\n scormLearnerId !== 'unknown' &&\n scormLearnerId !== '';\n\n if (isValidScormId) {\n actor = {\n objectType: 'Agent',\n account: {\n homePage: COURSE_HOMEPAGE || window.location.origin,\n name: scormLearnerId\n }\n };\n if (scormLearnerName) {\n // Handle \"LastName, FirstName\" format\n if (scormLearnerName.indexOf(',') > -1) {\n var parts = scormLearnerName.split(',');\n actor.name = (parts[1] || '').trim() + ' ' + (parts[0] || '').trim();\n } else {\n actor.name = scormLearnerName;\n }\n }\n log('Actor source: SCORM API');\n LRS.actor = actor;\n return actor;\n }\n\n log('SCORM learner ID is preview/unknown, continuing search...');\n } catch (e) {\n warn('Error extracting actor from SCORM:', e);\n }\n }\n\n // Priority 6: Already-fetched Bravais session data\n if (bravaisSessionData) {\n var bravaisActor = buildActorFromBravaisSession(bravaisSessionData);\n if (bravaisActor && isValidActor(bravaisActor)) {\n log('Actor source: Bravais session (cached)');\n LRS.actor = bravaisActor;\n return bravaisActor;\n }\n }\n\n log('No valid actor found, using default (Unknown Learner)');\n LRS.actor = defaultActor;\n return defaultActor;\n }\n\n /**\n * Check if an actor has valid identifying information\n */\n function isValidActor(actor) {\n if (!actor) return false;\n\n // Must have at least one valid identifier\n var hasValidMbox = actor.mbox && actor.mbox !== 'mailto:' && actor.mbox !== 'mailto:unknown';\n var hasValidAccount = actor.account && actor.account.name &&\n actor.account.name !== 'unknown' &&\n actor.account.name !== 'preview_user' &&\n actor.account.name !== '';\n\n return hasValidMbox || hasValidAccount;\n }\n\n /**\n * Async version that fetches Bravais session if needed\n * Also re-checks all sources in case they became available\n * Finally enhances actor with email from employee lookup if needed\n */\n function extractActorAsync(callback) {\n // Helper to finalize actor with email lookup\n function finalizeActor(actor) {\n // Try to enhance actor with email if missing\n enhanceActorWithEmail(actor, function(enhancedActor) {\n LRS.actor = enhancedActor;\n callback(enhancedActor);\n });\n }\n\n // If we already have a valid actor with email, use it\n if (LRS.actor && isValidActor(LRS.actor) && LRS.actor.mbox && LRS.actor.mbox !== 'mailto:' && LRS.actor.mbox !== 'mailto:unknown') {\n log('Using existing valid actor with email');\n callback(LRS.actor);\n return;\n }\n\n // If we have a valid actor but no email, try to enhance it\n if (LRS.actor && isValidActor(LRS.actor)) {\n log('Valid actor found, checking for email enhancement');\n finalizeActor(LRS.actor);\n return;\n }\n\n // Re-check sync sources first (they might have loaded since init)\n var syncActor = extractActor();\n if (isValidActor(syncActor)) {\n log('Sync actor found, enhancing with email');\n finalizeActor(syncActor);\n return;\n }\n\n // Try Bravais session API as last resort\n fetchBravaisSessionData(function(sessionData) {\n if (sessionData) {\n var bravaisActor = buildActorFromBravaisSession(sessionData);\n if (isValidActor(bravaisActor)) {\n log('Updated actor from Bravais session API:', bravaisActor);\n finalizeActor(bravaisActor);\n return;\n }\n }\n\n // Fall back to whatever we have, still try email enhancement\n log('Async extraction complete, enhancing:', LRS.actor);\n finalizeActor(LRS.actor);\n });\n }\n\n // ========================================================================\n // 4. COURSE INFO EXTRACTION (Xyleme/Bravais format)\n // ========================================================================\n\n /**\n * Extract Bravais shared link token from URL\n * Pattern: /api/shared/{token}/files/...\n */\n function extractSharedLinkToken() {\n var urlsToCheck = [window.location.href];\n\n try {\n var win = window;\n for (var i = 0; i < 5; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: /api/shared/{token}/files/\n var match = url.match(/\\\\/api\\\\/shared\\\\/([a-zA-Z0-9_-]+)\\\\/files/);\n if (match) {\n log('Extracted shared link token:', match[1]);\n return match[1];\n }\n }\n return null;\n }\n\n // Store for document API data\n var documentApiData = null;\n var documentApiFetched = false;\n var sharedLinkApiData = null;\n\n /**\n * Detect Bravais core URL from current page context\n */\n function detectCoreUrl() {\n var urlsToCheck = [window.location.href, document.referrer];\n\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var match = urlsToCheck[j].match(/(https?:\\\\/\\\\/core-[a-zA-Z0-9_-]+\\\\.bravais\\\\.com)/);\n if (match) {\n return match[1];\n }\n }\n return null;\n }\n\n /**\n * Fetch shared link data from Bravais API to get the real document ID\n * Endpoint: /api/v3/sharedLinks/token/{token}\n * Returns: { documentId, documentName, format, ... }\n */\n function fetchSharedLinkData(token, callback) {\n if (!token) {\n callback(null);\n return;\n }\n\n var coreUrl = detectCoreUrl();\n if (!coreUrl) {\n log('No Bravais core URL detected for shared link fetch');\n callback(null);\n return;\n }\n\n var apiUrl = coreUrl + '/api/v3/sharedLinks/token/' + token;\n log('Fetching shared link data from:', apiUrl);\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n log('Shared link API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n sharedLinkApiData = JSON.parse(xhr.responseText);\n log('Shared link data received:', sharedLinkApiData);\n if (sharedLinkApiData.documentId) {\n log('Real document ID from shared link:', sharedLinkApiData.documentId);\n }\n callback(sharedLinkApiData);\n } catch (e) {\n warn('Error parsing shared link data:', e);\n callback(null);\n }\n } else {\n // 401/403 is expected - API requires auth that may not be available from iframe\n if (xhr.status === 401 || xhr.status === 403) {\n log('Shared link API requires auth (expected), will use fallback');\n } else {\n log('Shared link fetch failed. Status:', xhr.status);\n }\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n log('Network error fetching shared link data');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n warn('Error sending shared link request:', e);\n callback(null);\n }\n }\n\n /**\n * Fetch document metadata from Bravais shared API to get GUIDs\n * PRIMARY endpoint: /api/shared/{token}/documents (does NOT require auth)\n * FALLBACK endpoint: /api/v3/documents/{documentId} (requires auth)\n */\n function fetchDocumentMetadata(documentIdOrToken, callback) {\n if (documentApiFetched) {\n callback(documentApiData);\n return;\n }\n\n var coreUrl = detectCoreUrl();\n if (!coreUrl) {\n log('No Bravais core URL detected for document metadata fetch');\n documentApiFetched = true;\n callback(null);\n return;\n }\n\n // Determine which endpoint to use\n // If we have a shared link token, use /api/shared/{token}/documents (no auth required)\n // This is the same endpoint Xyleme Cloud Player uses\n var sharedToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;\n var apiUrl;\n\n if (sharedToken) {\n apiUrl = coreUrl + '/api/shared/' + sharedToken + '/documents';\n log('Fetching document via shared API (no auth required):', apiUrl);\n } else if (documentIdOrToken) {\n apiUrl = coreUrl + '/api/v3/documents/' + documentIdOrToken;\n log('Fetching document via v3 API (requires auth):', apiUrl);\n } else {\n log('No shared token or document ID available for metadata fetch');\n documentApiFetched = true;\n callback(null);\n return;\n }\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n documentApiFetched = true;\n log('Document API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n documentApiData = JSON.parse(xhr.responseText);\n log('Document metadata received:', documentApiData);\n // Validate that we got the required fields\n if (documentApiData.guid) {\n log('Document GUID found:', documentApiData.guid);\n } else {\n warn('Document API response missing guid field!', documentApiData);\n }\n if (documentApiData.latestVersion && documentApiData.latestVersion.guid) {\n log('Version GUID found:', documentApiData.latestVersion.guid);\n } else {\n warn('Document API response missing latestVersion.guid field!', documentApiData);\n }\n callback(documentApiData);\n } catch (e) {\n warn('Error parsing document metadata:', e);\n callback(null);\n }\n } else {\n // 401/403 is expected - API requires auth that may not be available from iframe\n if (xhr.status === 401 || xhr.status === 403) {\n log('Document API requires auth (expected), will use fallback');\n } else {\n log('Document metadata fetch failed. Status:', xhr.status);\n }\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n documentApiFetched = true;\n log('Network error fetching document metadata');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n documentApiFetched = true;\n warn('Error sending document request:', e);\n callback(null);\n }\n }\n\n /**\n * Update course info with data from document API\n */\n function updateCourseInfoFromApi(docData) {\n if (!docData || !LRS.courseInfo) return;\n\n // Document GUID\n if (docData.guid && !LRS.courseInfo.guid) {\n LRS.courseInfo.guid = docData.guid;\n LRS.courseInfo.id = 'http://xyleme.com/bravais/document/' + docData.guid;\n log('Updated course guid from API:', docData.guid);\n }\n\n // Version GUID from latestVersion\n if (docData.latestVersion && docData.latestVersion.guid && !LRS.courseInfo.versionGuid) {\n LRS.courseInfo.versionGuid = docData.latestVersion.guid;\n log('Updated version guid from API:', docData.latestVersion.guid);\n }\n\n // Other metadata\n if (docData.name && LRS.courseInfo.title === 'Rise Course') {\n LRS.courseInfo.title = docData.name;\n }\n if (docData.format) {\n LRS.courseInfo.format = docData.format;\n }\n if (docData.resourceType) {\n LRS.courseInfo.resourceType = docData.resourceType;\n }\n\n log('Course info updated from API:', LRS.courseInfo);\n }\n\n /**\n * Extract Bravais document ID from URL\n * Pattern: /api/shared/.../files/{documentId}/scormcontent/\n */\n function extractBravaisDocumentId() {\n var urlsToCheck = [window.location.href];\n\n try {\n var win = window;\n for (var i = 0; i < 5; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: /files/{documentId}/scormcontent/ or /files/{documentId}/\n var match = url.match(/\\\\/files\\\\/([0-9]+)(?:\\\\/|$)/);\n if (match) {\n log('Extracted Bravais document ID:', match[1]);\n return match[1];\n }\n }\n return null;\n }\n\n /**\n * Extract course metadata from SCORM launch data\n * Bravais stores course info in base64 JSON in cmi.launch_data\n */\n function extractCourseFromLaunchData() {\n if (!LRS.scormApi) return null;\n\n try {\n var launchData = null;\n if (LRS.scormApiType === '2004') {\n launchData = LRS.scormApi.GetValue('cmi.launch_data');\n } else {\n launchData = LRS.scormApi.LMSGetValue('cmi.launch_data');\n }\n\n if (launchData) {\n try {\n var decoded = atob(launchData);\n var data = JSON.parse(decoded);\n log('Launch data for course info:', Object.keys(data));\n log('Launch data contents:', JSON.stringify(data));\n\n return {\n // Document/Course GUIDs\n guid: data.guid || data.courseGuid || data.documentGuid,\n versionGuid: data.versionGuid || data.courseVersionGuid || data.documentVersionGuid,\n // Numeric IDs\n documentId: data.documentId || data.courseId || data.cdsId,\n versionId: data.versionId || data.courseVersionId,\n // Metadata\n title: data.title || data.courseTitle || data.documentTitle || data.name,\n description: data.description || data.courseDescription,\n format: data.format || 'SCORM by Rise',\n resourceType: data.resourceType || 'Course',\n language: data.language || '',\n version: data.version || 1,\n cpv: data.cpv,\n // Shared link info\n sharedLinkName: data.sharedLinkName,\n sharedLinkToken: data.sharedLinkToken\n };\n } catch (e) {\n log('Launch data not JSON:', e.message);\n }\n }\n } catch (e) {\n log('Error reading launch data for course:', e.message);\n }\n return null;\n }\n\n /**\n * Extract course title from getCourseTitle() if it exists (from preloadIntegrity.js)\n */\n function extractCourseTitleFromPreload() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n if (typeof win.getCourseTitle === 'function') {\n var title = win.getCourseTitle();\n if (title && title !== 'Rise Course') {\n log('Found course title from getCourseTitle() at level', level, ':', title);\n return title;\n }\n }\n } catch (e) {}\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {}\n return null;\n }\n\n /**\n * Try to extract document data from Xyleme Cloud Player's internal state\n * The Cloud Player stores this after fetching /api/shared/{token}/documents\n */\n function extractFromXylemeCloudPlayer() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n // Check for Xyleme Cloud Player's document data in various locations\n // The player stores data in multiple places depending on version\n\n // Check for CdsDataService data\n if (win._cdsDataService && win._cdsDataService.documentData) {\n var docData = win._cdsDataService.documentData;\n log('Found document data in _cdsDataService at level', level);\n return extractGuidsFromDocumentData(docData);\n }\n\n // Check for window.documentData\n if (win.documentData && win.documentData.guid) {\n log('Found document data in window.documentData at level', level);\n return extractGuidsFromDocumentData(win.documentData);\n }\n\n // Check for PlayerIntegration's internal data\n if (win.playerIntegration && win.playerIntegration._documentData) {\n log('Found document data in playerIntegration at level', level);\n return extractGuidsFromDocumentData(win.playerIntegration._documentData);\n }\n\n // Check for __xyleme_data\n if (win.__xyleme_data && win.__xyleme_data.document) {\n log('Found document data in __xyleme_data at level', level);\n return extractGuidsFromDocumentData(win.__xyleme_data.document);\n }\n\n // Check for documentVersionData which has the versionGuid\n if (win.documentVersionData) {\n log('Found documentVersionData at level', level);\n return {\n versionGuid: win.documentVersionData.guid || win.documentVersionData.versionGuid,\n documentId: win.documentVersionData.documentId\n };\n }\n\n } catch (e) {}\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {}\n return null;\n }\n\n function extractGuidsFromDocumentData(data) {\n if (!data) return null;\n\n var result = {};\n\n // Document GUID\n if (data.guid) result.guid = data.guid;\n if (data.documentGuid) result.guid = data.documentGuid;\n\n // Version GUID - usually in latestVersion or currentVersion\n if (data.latestVersion && data.latestVersion.guid) {\n result.versionGuid = data.latestVersion.guid;\n }\n if (data.currentVersion && data.currentVersion.guid) {\n result.versionGuid = data.currentVersion.guid;\n }\n if (data.versionGuid) result.versionGuid = data.versionGuid;\n\n // Numeric IDs\n if (data.id) result.documentId = data.id;\n if (data.documentId) result.documentId = data.documentId;\n if (data.cdsId) result.documentId = data.cdsId;\n\n // Title\n if (data.name) result.title = data.name;\n if (data.title) result.title = data.title;\n\n log('Extracted GUIDs from Cloud Player:', result);\n return Object.keys(result).length > 0 ? result : null;\n }\n\n function extractCourseInfo() {\n var info = {\n // Xyleme IRI format: http://xyleme.com/bravais/document/{guid}\n id: null,\n // GUIDs\n guid: null, // Document GUID (fe57e5f8-c46b-4b2c-a0c7-884af6b0001d)\n versionGuid: null, // Version GUID (4c884a96-8da8-472b-8d59-c1d2bce0008f)\n // Numeric IDs\n documentId: null, // Bravais document CDS ID (numeric)\n versionId: null, // Version number\n // Metadata\n title: 'Rise Course',\n description: '',\n format: 'SCORM by Rise',\n resourceType: 'Course',\n language: '',\n version: 1,\n cpv: null,\n // Shared link\n sharedLinkToken: null,\n sharedLinkName: null,\n // Tenant info\n homepage: COURSE_HOMEPAGE || window.location.origin,\n tenantHomepage: null // https://{tenant}.bravais.com format\n };\n\n // 1. Extract shared link token and document ID from URL\n info.sharedLinkToken = extractSharedLinkToken();\n info.documentId = extractBravaisDocumentId();\n\n // 2. Detect tenant homepage from URL\n var tenantMatch = window.location.href.match(/core-([a-zA-Z0-9_-]+)\\\\.bravais\\\\.com/);\n if (tenantMatch) {\n info.tenantHomepage = 'https://' + tenantMatch[1] + '.bravais.com';\n log('Detected tenant homepage:', info.tenantHomepage);\n }\n\n // 3. Extract from SCORM launch data (most reliable for Bravais)\n var launchInfo = extractCourseFromLaunchData();\n if (launchInfo) {\n info.guid = launchInfo.guid || info.guid;\n info.versionGuid = launchInfo.versionGuid || info.versionGuid;\n info.title = launchInfo.title || info.title;\n info.versionId = launchInfo.versionId || info.versionId;\n info.documentId = launchInfo.documentId || info.documentId;\n info.description = launchInfo.description || info.description;\n info.format = launchInfo.format || info.format;\n info.resourceType = launchInfo.resourceType || info.resourceType;\n info.language = launchInfo.language || info.language;\n info.version = launchInfo.version || info.version;\n info.cpv = launchInfo.cpv || info.cpv;\n info.sharedLinkToken = launchInfo.sharedLinkToken || info.sharedLinkToken;\n info.sharedLinkName = launchInfo.sharedLinkName || info.sharedLinkName;\n }\n\n // 3b. Try to extract GUIDs from Xyleme Cloud Player's internal data\n // This is more reliable than API calls which may fail with 401\n if (!info.guid || !info.versionGuid) {\n var cloudPlayerData = extractFromXylemeCloudPlayer();\n if (cloudPlayerData) {\n info.guid = cloudPlayerData.guid || info.guid;\n info.versionGuid = cloudPlayerData.versionGuid || info.versionGuid;\n info.documentId = cloudPlayerData.documentId || info.documentId;\n info.title = cloudPlayerData.title || info.title;\n log('Updated course info from Cloud Player:', {\n guid: info.guid,\n versionGuid: info.versionGuid,\n documentId: info.documentId\n });\n }\n }\n\n // 4. Try getCourseTitle() from preloadIntegrity.js\n var preloadTitle = extractCourseTitleFromPreload();\n if (preloadTitle) {\n info.title = preloadTitle;\n }\n\n // 5. Try Rise's data\n try {\n if (window.__RISE_COURSE_DATA__) {\n info.title = info.title === 'Rise Course' ?\n (window.__RISE_COURSE_DATA__.title || info.title) : info.title;\n info.guid = window.__RISE_COURSE_DATA__.id || info.guid;\n }\n\n var metaTitle = document.querySelector('meta[property=\"og:title\"]');\n if (metaTitle && metaTitle.content && info.title === 'Rise Course') {\n info.title = metaTitle.content;\n }\n\n var metaDesc = document.querySelector('meta[property=\"og:description\"]');\n if (metaDesc && metaDesc.content) {\n info.description = metaDesc.content;\n }\n } catch (e) {}\n\n // 6. Check document title\n if (info.title === 'Rise Course' && document.title && document.title !== 'Rise Course') {\n info.title = document.title;\n }\n\n // 7. Get from PA metadata if available\n if (window.pa_patcher && window.pa_patcher.courseMetadata) {\n var meta = window.pa_patcher.courseMetadata;\n info.documentId = meta.documentId || meta.courseId || info.documentId;\n info.guid = meta.guid || meta.courseGuid || info.guid;\n info.versionGuid = meta.versionGuid || info.versionGuid;\n info.title = meta.title || meta.courseTitle || info.title;\n info.versionId = meta.versionId || info.versionId;\n }\n\n // 8. Build the course ID in Xyleme IRI format\n // Native format: http://xyleme.com/bravais/document/{guid}\n if (info.guid) {\n info.id = 'http://xyleme.com/bravais/document/' + info.guid;\n } else if (info.documentId) {\n // Fallback: use document ID\n info.id = 'http://xyleme.com/bravais/document/' + info.documentId;\n } else {\n info.id = window.location.href.split('#')[0].split('?')[0];\n }\n\n // Build shared link name if not provided\n if (!info.sharedLinkName && info.title && info.sharedLinkToken) {\n info.sharedLinkName = info.title + ' - LMS Thin Pack';\n }\n\n LRS.courseInfo = info;\n log('Course info extracted:', info);\n return info;\n }\n\n // ========================================================================\n // 4b. LESSON/SECTION NAME EXTRACTION\n // ========================================================================\n\n /**\n * Get the current lesson/section information from Rise DOM\n * Returns { id, name, lessonIndex, sectionName }\n */\n function getCurrentLessonInfo() {\n var lessonInfo = {\n id: window.location.hash || '#/',\n name: null,\n lessonIndex: null,\n sectionName: null\n };\n\n try {\n // 1. Try to get from Rise's navigation sidebar\n // Rise shows active lesson with specific classes\n var activeLesson = document.querySelector(\n '.sidebar__link--current, ' +\n '.sidebar-lesson--active, ' +\n '.nav-sidebar__lesson--active, ' +\n '[class*=\"sidebar\"] [class*=\"current\"], ' +\n '[class*=\"sidebar\"] [class*=\"active\"], ' +\n '[aria-current=\"page\"], ' +\n '[aria-current=\"true\"]'\n );\n\n if (activeLesson) {\n var lessonTitle = activeLesson.querySelector(\n '.sidebar__link-text, ' +\n '.lesson-title, ' +\n '.sidebar-lesson__title, ' +\n '[class*=\"title\"], ' +\n '[class*=\"label\"]'\n );\n if (lessonTitle) {\n lessonInfo.name = lessonTitle.textContent.trim();\n } else {\n lessonInfo.name = activeLesson.textContent.trim();\n }\n\n // Try to get the section/module name (parent group)\n var section = activeLesson.closest(\n '.sidebar__section, ' +\n '.sidebar-section, ' +\n '[class*=\"section\"], ' +\n '[class*=\"module\"]'\n );\n if (section) {\n var sectionTitle = section.querySelector(\n '.sidebar__section-title, ' +\n '.section-title, ' +\n '[class*=\"section-title\"], ' +\n '[class*=\"module-title\"], ' +\n 'h2, h3'\n );\n if (sectionTitle) {\n lessonInfo.sectionName = sectionTitle.textContent.trim();\n }\n }\n }\n\n // 2. Try Rise's header/breadcrumb\n if (!lessonInfo.name) {\n var headerTitle = document.querySelector(\n '.lesson-header__title, ' +\n '.lesson__title, ' +\n '.content-header__title, ' +\n '[class*=\"lesson\"] [class*=\"header\"] [class*=\"title\"], ' +\n '.blocks-lesson-header__title'\n );\n if (headerTitle) {\n lessonInfo.name = headerTitle.textContent.trim();\n }\n }\n\n // 3. Try breadcrumbs\n if (!lessonInfo.name || !lessonInfo.sectionName) {\n var breadcrumbs = document.querySelectorAll(\n '.breadcrumb__item, ' +\n '.breadcrumbs li, ' +\n '[class*=\"breadcrumb\"] span, ' +\n '[class*=\"breadcrumb\"] a'\n );\n if (breadcrumbs.length >= 2) {\n // Usually: Course > Section > Lesson\n if (!lessonInfo.sectionName && breadcrumbs.length >= 2) {\n lessonInfo.sectionName = breadcrumbs[breadcrumbs.length - 2].textContent.trim();\n }\n if (!lessonInfo.name) {\n lessonInfo.name = breadcrumbs[breadcrumbs.length - 1].textContent.trim();\n }\n }\n }\n\n // 4. Try Rise's internal data\n if (window.__RISE_COURSE_DATA__ && window.__RISE_COURSE_DATA__.lessons) {\n var lessonId = lessonInfo.id.replace('#/lessons/', '').replace('#/', '');\n var lessons = window.__RISE_COURSE_DATA__.lessons;\n for (var i = 0; i < lessons.length; i++) {\n if (lessons[i].id === lessonId || lessons[i].slug === lessonId) {\n lessonInfo.name = lessons[i].title || lessonInfo.name;\n lessonInfo.lessonIndex = i;\n break;\n }\n }\n }\n\n // 5. Clean up - remove any \"Home\" or generic names if we're on a real lesson\n if (lessonInfo.name) {\n lessonInfo.name = lessonInfo.name.replace(/^\\\\\\\\d+\\\\\\\\.\\\\\\\\s*/, ''); // Remove leading numbers \"1. \"\n if (lessonInfo.name.length > 200) {\n lessonInfo.name = lessonInfo.name.substring(0, 200) + '...';\n }\n }\n\n if (lessonInfo.sectionName) {\n lessonInfo.sectionName = lessonInfo.sectionName.replace(/^\\\\\\\\d+\\\\\\\\.\\\\\\\\s*/, '');\n if (lessonInfo.sectionName.length > 200) {\n lessonInfo.sectionName = lessonInfo.sectionName.substring(0, 200) + '...';\n }\n }\n\n } catch (e) {\n warn('Error extracting lesson info:', e);\n }\n\n log('Current lesson info:', lessonInfo);\n return lessonInfo;\n }\n\n /**\n * Cache the current lesson info (updates on navigation)\n */\n var cachedLessonInfo = null;\n var cachedLessonHash = null;\n\n function getCachedLessonInfo() {\n var currentHash = window.location.hash;\n if (cachedLessonHash !== currentHash) {\n cachedLessonInfo = getCurrentLessonInfo();\n cachedLessonHash = currentHash;\n }\n return cachedLessonInfo || getCurrentLessonInfo();\n }\n\n // ========================================================================\n // 5. XAPI STATEMENT BUILDING\n // ========================================================================\n\n /**\n * Build an xAPI statement with COURSE as the main object\n * This ensures ALL statements show up when searching by document name in Bravais Analytics\n *\n * The activity type (video, assessment, interaction, etc.) is stored in object.definition.type\n * The specific activity details (media src, page ID, etc.) are stored in object.definition.extensions\n */\n function buildStatement(verb, activityType, activityDetails, result, context) {\n // ALWAYS use course as the main object\n var courseObj = buildCourseActivityObject();\n if (!courseObj) {\n // Fallback if course info not available\n courseObj = {\n objectType: 'Activity',\n id: window.location.href.split('#')[0].split('?')[0],\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': document.title || 'Rise Course' }\n }\n };\n }\n\n // Override the activity type based on the statement being sent\n // This allows filtering by type (video, assessment, interaction) in Analytics\n if (activityType) {\n courseObj.definition.type = activityType;\n\n // Also update resourceType extension to human-readable form for Bravais Type column\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n var resourceTypeMap = {\n 'https://w3id.org/xapi/video/activity-type/video': 'Video',\n 'https://w3id.org/xapi/audio/activity-type/audio': 'Audio',\n 'http://adlnet.gov/expapi/activities/media': 'Media',\n 'http://adlnet.gov/expapi/activities/assessment': 'Assessment',\n 'http://adlnet.gov/expapi/activities/question': 'Question',\n 'http://adlnet.gov/expapi/activities/interaction': 'Interaction',\n 'http://adlnet.gov/expapi/activities/lesson': 'Lesson',\n 'http://adlnet.gov/expapi/activities/module': 'Module',\n 'http://adlnet.gov/expapi/activities/course': 'Course',\n 'http://xyleme.com/bravais/activities/document': 'Course'\n };\n courseObj.definition.extensions['resourceType'] = resourceTypeMap[activityType] || 'Course';\n }\n\n // Add activity-specific details to extensions\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n\n if (activityDetails) {\n // Merge activity details into extensions\n for (var key in activityDetails) {\n if (activityDetails.hasOwnProperty(key)) {\n courseObj.definition.extensions[key] = activityDetails[key];\n }\n }\n }\n\n var statement = {\n id: generateUUID(),\n actor: LRS.actor || extractActor(),\n verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,\n object: courseObj,\n timestamp: new Date().toISOString()\n };\n\n if (result) {\n statement.result = result;\n }\n\n // Build context with all Xyleme extensions using unified builder\n statement.context = buildXylemeContext(context);\n\n return statement;\n }\n\n /**\n * Legacy buildStatement for backward compatibility\n * Routes to new signature\n */\n function buildStatementLegacy(verb, object, result, context) {\n // Extract type and details from the legacy object\n var activityType = object && object.definition ? object.definition.type : null;\n var activityDetails = {};\n\n if (object) {\n activityDetails.activityId = object.id;\n if (object.definition) {\n if (object.definition.name && object.definition.name['en-US']) {\n activityDetails.activityName = object.definition.name['en-US'];\n }\n if (object.definition.description && object.definition.description['en-US']) {\n activityDetails.activityDescription = object.definition.description['en-US'];\n }\n if (object.definition.interactionType) {\n activityDetails.interactionType = object.definition.interactionType;\n }\n // Copy any existing extensions\n if (object.definition.extensions) {\n for (var key in object.definition.extensions) {\n if (object.definition.extensions.hasOwnProperty(key)) {\n activityDetails[key] = object.definition.extensions[key];\n }\n }\n }\n }\n }\n\n return buildStatement(verb, activityType, activityDetails, result, context);\n }\n\n /**\n * Build the course activity object in Xyleme native format\n * Object ID: http://xyleme.com/bravais/document/{guid}\n * Object type: http://xyleme.com/bravais/activities/document\n */\n function buildCourseActivityObject() {\n if (!LRS.courseInfo) return null;\n\n // Warn if we don't have the required GUIDs for Bravais aggregation\n if (!LRS.courseInfo.guid) {\n warn('Missing document GUID - statement may fail Bravais aggregation. Using numeric ID:', LRS.courseInfo.documentId);\n }\n if (!LRS.courseInfo.versionGuid) {\n warn('Missing version GUID - statement may fail Bravais aggregation (document_version_id will be null)');\n }\n\n var obj = {\n objectType: 'Activity',\n id: LRS.courseInfo.id,\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': LRS.courseInfo.title }\n }\n };\n\n // Add Xyleme-format extensions\n obj.definition.extensions = {};\n\n // Version ID in Xyleme IRI format - REQUIRED for Bravais aggregation\n if (LRS.courseInfo.versionGuid) {\n obj.definition.extensions['versionId'] =\n 'http://xyleme.com/bravais/document.version/' + LRS.courseInfo.versionGuid;\n } else {\n // Log error - this will cause aggregation failure\n warn('versionId extension not set - this statement will fail Bravais aggregation!');\n }\n\n // Format (e.g., \"SCORM by Rise\")\n obj.definition.extensions['format'] = LRS.courseInfo.format || 'SCORM by Rise';\n\n // Language\n obj.definition.extensions['language'] = LRS.courseInfo.language || '';\n\n // Version number\n obj.definition.extensions['version'] = LRS.courseInfo.version || 1;\n\n // Document CDS ID (numeric)\n if (LRS.courseInfo.documentId) {\n obj.definition.extensions['documentCdsId'] =\n parseInt(LRS.courseInfo.documentId, 10) || LRS.courseInfo.documentId;\n }\n\n // Resource type\n obj.definition.extensions['resourceType'] = LRS.courseInfo.resourceType || 'Course';\n\n return obj;\n }\n\n /**\n * Build the parent course activity for context.contextActivities.parent\n * This links sub-activities (media, pages, interactions) to the course document\n * CRITICAL: This is what allows statements to show up when searching by document name in Analytics\n */\n function buildParentCourseActivity() {\n if (!LRS.courseInfo || !LRS.courseInfo.id) return null;\n\n var parent = {\n id: LRS.courseInfo.id,\n objectType: 'Activity',\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': LRS.courseInfo.title || 'Rise Course' }\n }\n };\n\n // Add documentCdsId - this is what Bravais uses for document search\n if (LRS.courseInfo.documentId) {\n parent.definition.extensions = {\n documentCdsId: parseInt(LRS.courseInfo.documentId, 10) || LRS.courseInfo.documentId\n };\n }\n\n return parent;\n }\n\n // ========================================================================\n // XYLEME NATIVE FORMAT BUILDERS\n // ========================================================================\n\n /**\n * Build context object in Xyleme native format\n * Includes all required context extensions for Bravais correlation\n */\n function buildXylemeContext(additionalContext) {\n var ctx = additionalContext || {};\n\n // Platform: \"Cloud Player\" for Xyleme compatibility\n ctx.platform = 'Cloud Player';\n\n ctx.extensions = ctx.extensions || {};\n\n if (LRS.courseInfo) {\n // Document URI (required for document correlation)\n if (LRS.courseInfo.guid) {\n ctx.extensions['document'] = 'http://xyleme.com/bravais/document/' + LRS.courseInfo.guid;\n } else if (LRS.courseInfo.documentId) {\n ctx.extensions['document'] = 'http://xyleme.com/bravais/document/' + LRS.courseInfo.documentId;\n }\n\n // Document version URI\n if (LRS.courseInfo.versionGuid) {\n ctx.extensions['documentVersion'] = 'http://xyleme.com/bravais/document.version/' + LRS.courseInfo.versionGuid;\n }\n\n // Shared link info\n if (LRS.courseInfo.sharedLinkToken) {\n ctx.extensions['sharedLinkToken'] = LRS.courseInfo.sharedLinkToken;\n }\n if (LRS.courseInfo.sharedLinkName) {\n ctx.extensions['sharedLinkName'] = LRS.courseInfo.sharedLinkName;\n }\n }\n\n // Course attempt ID (unique per session)\n ctx.extensions['courseAttemptId'] = LRS.courseAttemptId;\n\n // Publish index (default to 1)\n ctx.extensions['publishIndex'] = (LRS.courseInfo && LRS.courseInfo.publishIndex) ? LRS.courseInfo.publishIndex : 1;\n\n // Xyleme schema version and PII flag\n ctx.extensions['http://xyleme.com/bravais/extensions/statement-schema'] = 'xyleme_10';\n ctx.extensions['http://xyleme.com/bravais/extensions/protect_pii'] = false;\n\n // Add parent course activity for document-level aggregation in Bravais\n // This allows statements with activity-specific objects to still be found\n // when searching by document name in Analytics\n var parentActivity = buildParentCourseActivity();\n if (parentActivity) {\n ctx.contextActivities = ctx.contextActivities || {};\n ctx.contextActivities.parent = [parentActivity];\n }\n\n return ctx;\n }\n\n /**\n * Build activity object for page/topic-level statements\n * Uses Xyleme page URI format instead of course/document\n */\n function buildPageActivityObject(pageInfo) {\n var pageGuid = pageInfo.pageGuid || pageInfo.id || generateUUID();\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/page/' + pageGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.page,\n name: { 'en-US': pageInfo.name || pageInfo.title || 'Page' },\n extensions: {\n resourceType: pageInfo.resourceType || RESOURCE_TYPES[pageInfo.type] || 'Topic'\n }\n }\n };\n }\n\n /**\n * Build activity object for assessments in Xyleme format\n */\n function buildAssessmentActivityObject(assessmentInfo) {\n var assessmentGuid = assessmentInfo.assessmentGuid || assessmentInfo.id || generateUUID();\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/assessment/' + assessmentGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.assessment,\n name: { 'en-US': assessmentInfo.title || 'Assessment' },\n extensions: {\n resourceType: 'Assessment'\n }\n }\n };\n }\n\n /**\n * Build activity object for questions in Xyleme format\n * Name format: \"Assessment Name - Q#: Question text...\"\n */\n function buildQuestionActivityObject(questionInfo) {\n var questionGuid = questionInfo.questionGuid || questionInfo.id || generateUUID();\n\n // Build human-readable display name\n var displayName = (questionInfo.assessmentName || 'Knowledge Check');\n if (questionInfo.questionNumber) {\n displayName += ' - Q' + questionInfo.questionNumber;\n }\n var questionText = questionInfo.text || questionInfo.questionText || '';\n if (questionText.length > 50) {\n displayName += ': ' + questionText.substring(0, 47) + '...';\n } else if (questionText.length > 0) {\n displayName += ': ' + questionText;\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/question/' + questionGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.question,\n name: { 'en-US': displayName },\n description: { 'en-US': questionText || 'Question' },\n extensions: {\n resourceType: 'Question',\n questionNumber: questionInfo.questionNumber || null\n }\n }\n };\n }\n\n /**\n * Format seconds into MM:SS or HH:MM:SS format\n */\n function formatMediaTime(seconds) {\n if (typeof seconds !== 'number' || isNaN(seconds)) return '0:00';\n var totalSeconds = Math.floor(seconds);\n var hours = Math.floor(totalSeconds / 3600);\n var minutes = Math.floor((totalSeconds % 3600) / 60);\n var secs = totalSeconds % 60;\n\n if (hours > 0) {\n return hours + ':' + (minutes < 10 ? '0' : '') + minutes + ':' + (secs < 10 ? '0' : '') + secs;\n }\n return minutes + ':' + (secs < 10 ? '0' : '') + secs;\n }\n\n /**\n * Build activity object for media (video/audio) with human-readable name\n * Name format varies by action:\n * - played: \"Video: Title (started at 1:23)\"\n * - paused: \"Video: Title (paused at 2:45)\"\n * - completed: \"Video: Title (completed)\"\n */\n function buildMediaActivityObject(mediaInfo) {\n var mediaGuid = mediaInfo.mediaGuid || generateUUID();\n var mediaType = mediaInfo.type === 'audio' ? ACTIVITY_TYPES.audio : ACTIVITY_TYPES.video;\n var resourceType = mediaInfo.type === 'audio' ? 'Audio' : 'Video';\n\n // Build human-readable display name\n var baseName = mediaInfo.name || '';\n if (!baseName || baseName === 'video' || baseName === 'audio' || baseName === 'Media') {\n baseName = mediaInfo.lessonName || 'Media';\n }\n\n // Add time context based on action\n var displayName = resourceType + ': ' + baseName;\n var currentTime = mediaInfo.currentTime || 0;\n var action = mediaInfo.action;\n\n if (action === 'play' || action === 'played') {\n displayName += ' (started at ' + formatMediaTime(currentTime) + ')';\n } else if (action === 'pause' || action === 'paused') {\n displayName += ' (paused at ' + formatMediaTime(currentTime) + ')';\n } else if (action === 'completed' || action === 'complete') {\n displayName += ' (completed)';\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/media/' + mediaGuid,\n definition: {\n type: mediaType,\n name: { 'en-US': displayName },\n extensions: {\n resourceType: resourceType,\n mediaSrc: mediaInfo.src || '',\n mediaType: mediaInfo.type || 'video',\n duration: mediaInfo.duration || 0\n }\n }\n };\n }\n\n /**\n * Build activity object for interactions (tabs, accordions, flashcards, etc.)\n * Name format: \"Tab: Label\" or \"Accordion: Label\"\n */\n function buildInteractionActivityObject(interactionInfo) {\n var typeLabels = {\n 'tab': 'Tab',\n 'accordion': 'Accordion',\n 'flashcard': 'Flashcard',\n 'hotspot': 'Hotspot',\n 'process-step': 'Process Step',\n 'process': 'Process Step',\n 'sorting': 'Sorting Activity',\n 'button': 'Button',\n 'labeled-graphic': 'Labeled Graphic',\n 'timeline': 'Timeline',\n 'scenario': 'Scenario',\n 'checklist': 'Checklist',\n 'marker': 'Marker'\n };\n\n var interactionType = interactionInfo.type || interactionInfo.interactionType || 'interaction';\n var typeLabel = typeLabels[interactionType] || 'Interaction';\n\n // Build human-readable display name\n var displayName = interactionInfo.name;\n if (!displayName) {\n displayName = typeLabel + ': ' + (interactionInfo.label || interactionInfo.id || 'Item');\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/interaction/' + (interactionInfo.id || generateUUID()),\n definition: {\n type: ACTIVITY_TYPES.interaction,\n name: { 'en-US': displayName },\n extensions: {\n resourceType: 'Interaction',\n interactionType: interactionType\n }\n }\n };\n }\n\n /**\n * Build xAPI statement in Xyleme native format\n * Supports page-level objects while maintaining document context\n */\n function buildStatementXyleme(verb, activityObject, result, additionalContext) {\n var statement = {\n id: generateUUID(),\n actor: LRS.actor || extractActor(),\n verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,\n object: activityObject,\n timestamp: new Date().toISOString()\n };\n\n if (result) {\n statement.result = result;\n }\n\n // Build context with all Xyleme extensions\n statement.context = buildXylemeContext(additionalContext);\n\n return statement;\n }\n\n function buildActivityObject(id, type, name, description) {\n var obj = {\n objectType: 'Activity',\n id: id,\n definition: {\n type: type || ACTIVITY_TYPES.lesson\n }\n };\n\n if (name) {\n obj.definition.name = { 'en-US': name };\n }\n if (description) {\n obj.definition.description = { 'en-US': description };\n }\n\n return obj;\n }\n\n // ========================================================================\n // 6. STATEMENT SENDING\n // ========================================================================\n\n function sendStatement(statement) {\n log('Sending statement:', statement.verb.display['en-US'] || statement.verb.id, statement);\n\n // Log first statement visibly (not gated by DEBUG) for production diagnostics\n if (LRS.stats.statementsSent === 0 && LRS.stats.statementsFailed === 0 && LRS.stats.statementsQueued === 0) {\n if (window.console && window.console.info) {\n var verb = statement.verb.display ? (statement.verb.display['en-US'] || statement.verb.id) : statement.verb.id;\n console.info('[PA-LRS] First statement: verb=' + verb +\n ', endpoint=' + (LRS_ENDPOINT || 'NONE') +\n ', proxy=' + (LRS_PROXY_ENDPOINT ? 'yes' : 'no') +\n ', mode=' + LRS.mode);\n }\n }\n\n // Store in event log\n LRS.eventLog.push({\n statement: statement,\n timestamp: new Date().toISOString(),\n mode: LRS.mode,\n lrsEndpoint: LRS_ENDPOINT\n });\n\n // ALWAYS prefer direct LRS when endpoint is available\n // This ensures all our detailed tracking (media, interactions, etc.) goes to the LRS\n // PlayerIntegration is used only for SCORM state, not for xAPI statements\n if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {\n log('Sending via directLRS:', LRS_ENDPOINT);\n return sendViaDirectLRS(statement);\n } else if (LRS.mode === 'playerIntegration' && LRS.integration) {\n // Fallback to PlayerIntegration only if no direct LRS endpoint\n log('No LRS endpoint, falling back to PlayerIntegration');\n return sendViaPlayerIntegration(statement);\n } else {\n // SCORM-only or offline mode - just log\n log('Statement logged (no LRS):', statement.verb.display['en-US']);\n LRS.stats.statementsQueued++;\n return Promise.resolve({ logged: true });\n }\n }\n\n function sendViaPlayerIntegration(statement) {\n // Map xAPI verb to PlayerIntegration method\n var verbId = statement.verb.id;\n var data = {\n ...statement.object.definition,\n id: statement.object.id,\n timestamp: statement.timestamp\n };\n\n if (statement.result) {\n data.result = statement.result;\n }\n\n try {\n if (verbId.indexOf('played') > -1 || verbId.indexOf('paused') > -1 || verbId.indexOf('completed') > -1) {\n if (statement.object.definition && statement.object.definition.type &&\n (statement.object.definition.type.indexOf('video') > -1 || statement.object.definition.type.indexOf('audio') > -1)) {\n LRS.integration.mediaPlayed(data);\n }\n } else if (verbId.indexOf('experienced') > -1 || verbId.indexOf('viewed') > -1 || verbId.indexOf('launched') > -1) {\n LRS.integration.contentOpened(data);\n } else if (verbId.indexOf('attempted') > -1) {\n LRS.integration.assessmentStarted(data);\n } else if (verbId.indexOf('passed') > -1 || verbId.indexOf('failed') > -1) {\n LRS.integration.assessmentEnded(data);\n } else if (verbId.indexOf('answered') > -1) {\n LRS.integration.questionAnswered(data);\n } else if (verbId.indexOf('interacted') > -1) {\n LRS.integration.sendCustomStatement({ verb: 'interacted', data: data });\n } else {\n LRS.integration.sendCustomStatement({ verb: verbId, data: data });\n }\n\n LRS.stats.statementsSent++;\n return Promise.resolve({ sent: true, via: 'playerIntegration' });\n\n } catch (e) {\n warn('Error sending via PlayerIntegration:', e);\n LRS.stats.statementsFailed++;\n return Promise.reject(e);\n }\n }\n\n function sendViaDirectLRS(statement) {\n // Use proxy endpoint if configured, otherwise direct LRS\n var useProxy = LRS_PROXY_ENDPOINT && LRS_PROXY_ENDPOINT.length > 0;\n var endpoint = useProxy ? LRS_PROXY_ENDPOINT : LRS_ENDPOINT;\n\n if (!endpoint || endpoint.length === 0) {\n warn('No LRS endpoint available');\n LRS.stats.statementsFailed++;\n return Promise.reject(new Error('No LRS endpoint'));\n }\n\n return new Promise(function(resolve, reject) {\n var xhr = new XMLHttpRequest();\n // POST to proxy (proxy handles PUT to LRS), POST to direct LRS\n xhr.open('POST', endpoint, true);\n\n // xAPI required headers\n xhr.setRequestHeader('Content-Type', 'application/json');\n xhr.setRequestHeader('X-Experience-API-Version', '1.0');\n\n if (useProxy) {\n // Proxy handles auth server-side, no auth headers needed\n log('Sending via LRS proxy:', endpoint);\n } else if (LRS_AUTH && LRS_AUTH.length > 0) {\n xhr.setRequestHeader('Authorization', 'Basic ' + LRS_AUTH);\n log('Using Basic Auth for LRS request');\n } else if (LRS_WITH_CREDENTIALS) {\n xhr.withCredentials = true;\n }\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n if (xhr.status >= 200 && xhr.status < 300) {\n log('Statement sent successfully:', statement.verb.display['en-US'], useProxy ? '(via proxy)' : '(direct)');\n LRS.stats.statementsSent++;\n resolve({ sent: true, via: useProxy ? 'proxy' : 'directLRS', status: xhr.status });\n } else {\n warn('Statement send failed:', xhr.status, xhr.statusText);\n LRS.stats.statementsFailed++;\n\n // Queue for retry if server error\n if (xhr.status >= 500) {\n LRS.statementQueue.push(statement);\n }\n\n reject(new Error('HTTP ' + xhr.status + ': ' + xhr.statusText));\n }\n }\n };\n\n xhr.onerror = function() {\n warn('Network error sending statement');\n LRS.stats.statementsFailed++;\n LRS.statementQueue.push(statement);\n reject(new Error('Network error'));\n };\n\n try {\n xhr.send(JSON.stringify(statement));\n } catch (e) {\n warn('Error sending statement:', e);\n LRS.stats.statementsFailed++;\n reject(e);\n }\n });\n }\n\n // Retry queue processing\n function processStatementQueue() {\n if (LRS.statementQueue.length === 0) return;\n\n log('Processing statement queue:', LRS.statementQueue.length, 'statements');\n\n var queue = LRS.statementQueue.slice();\n LRS.statementQueue = [];\n\n queue.forEach(function(statement) {\n sendViaDirectLRS(statement).catch(function(e) {\n // Already re-queued on failure\n });\n });\n }\n\n // ========================================================================\n // 7. PUBLIC API METHODS\n // ========================================================================\n\n // Course launched - uses Xyleme native format for course object\n LRS.courseLaunched = function() {\n if (!TRACK_NAVIGATION) return;\n\n // Course launch uses the document activity type (default)\n var statement = buildStatement(\n 'launched',\n 'http://xyleme.com/bravais/activities/document',\n { event: 'course_launched' }\n );\n sendStatement(statement);\n };\n\n // Content/Page viewed - uses page-specific object for human-readable display\n // Object shows page/lesson title, parent context maintains document aggregation\n LRS.contentOpened = function(data) {\n if (!TRACK_NAVIGATION) return;\n\n // Get current lesson info from DOM (includes name)\n var lessonInfo = getCurrentLessonInfo();\n\n var result = null;\n if (data.duration) {\n result = { duration: data.duration };\n }\n\n // Extract clean page/lesson ID from Rise hash paths\n var lessonId = data.pageGuid || data.lessonId || lessonInfo.id || '';\n if (lessonId.indexOf('#/lessons/') === 0) {\n lessonId = lessonId.substring(10); // Remove '#/lessons/'\n } else if (lessonId.indexOf('#/') === 0) {\n lessonId = lessonId.substring(2); // Remove '#/'\n }\n\n // Get the page title - prefer lesson name from DOM\n var pageTitle = data.title || lessonInfo.name || 'Page';\n\n // Build page activity object with human-readable name\n // This shows the page/lesson title in the Object column\n var pageObject = buildPageActivityObject({\n pageGuid: lessonId,\n name: pageTitle,\n resourceType: 'Lesson'\n });\n\n // Add page info to context extensions\n var additionalContext = {\n extensions: {\n pageId: lessonId || 'home',\n pageTitle: pageTitle,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName,\n pageUrl: data.url || window.location.href\n }\n };\n\n // Use 'experienced' verb for viewing content\n // Parent course activity added by buildXylemeContext ensures document aggregation\n var statement = buildStatementXyleme('experienced', pageObject, result, additionalContext);\n sendStatement(statement);\n };\n\n // Media events - uses activity-specific object with human-readable name\n // Parent context (added by buildXylemeContext) ensures document aggregation\n LRS.mediaPlayed = function(data) {\n if (!TRACK_MEDIA) return;\n\n // Get current lesson context\n var lessonInfo = getCachedLessonInfo();\n\n var verbKey = data.action === 'play' ? 'played' :\n data.action === 'pause' ? 'paused' :\n data.action === 'completed' ? 'completed' : 'played';\n\n // Build activity-specific object with human-readable name including time context\n // e.g., \"Video: Introduction (started at 1:23)\" or \"Audio: Podcast (paused at 2:45)\"\n var mediaObject = buildMediaActivityObject({\n type: data.type,\n src: data.src,\n name: data.name,\n duration: data.duration,\n lessonName: lessonInfo.name,\n action: data.action,\n currentTime: data.currentTime\n });\n\n var result = {\n extensions: {\n 'https://w3id.org/xapi/video/extensions/time': data.currentTime || 0,\n 'https://w3id.org/xapi/video/extensions/progress': data.duration > 0 ?\n Math.round((data.currentTime / data.duration) * 100) / 100 : 0,\n 'https://w3id.org/xapi/video/extensions/length': data.duration || 0\n }\n };\n\n if (data.action === 'completed') {\n result.completion = true;\n }\n\n // Context includes lesson info; parent course activity added by buildXylemeContext\n var additionalContext = {\n extensions: {\n lessonId: lessonInfo.id,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n }\n };\n\n var statement = buildStatementXyleme(verbKey, mediaObject, result, additionalContext);\n sendStatement(statement);\n };\n\n // Assessment started\n LRS.assessmentStarted = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context\n var lessonInfo = getCachedLessonInfo();\n\n // Build assessment activity object for Xyleme format\n var assessmentObject = buildAssessmentActivityObject({\n assessmentGuid: data.assessmentGuid || data.id,\n title: data.title || data.assessmentName || 'Assessment'\n });\n\n // Add lesson context to extensions\n assessmentObject.definition = assessmentObject.definition || {};\n assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};\n assessmentObject.definition.extensions.lessonName = lessonInfo.name;\n assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;\n\n var statement = buildStatementXyleme('attempted', assessmentObject);\n sendStatement(statement);\n };\n\n // Assessment ended\n LRS.assessmentEnded = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context (may be passed in from extractQuizResult)\n var lessonInfo = data.lessonName ? { name: data.lessonName, sectionName: data.sectionName } : getCachedLessonInfo();\n\n // Build assessment activity object for Xyleme format\n var assessmentObject = buildAssessmentActivityObject({\n assessmentGuid: data.assessmentGuid || data.assessmentId || data.id,\n title: data.title || data.assessmentName || 'Assessment'\n });\n\n // Add lesson context to extensions\n assessmentObject.definition = assessmentObject.definition || {};\n assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};\n assessmentObject.definition.extensions.lessonName = lessonInfo.name;\n assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;\n assessmentObject.definition.extensions.assessmentName = data.assessmentName || data.title;\n\n var result = {\n completion: true,\n extensions: {\n 'https://w3id.org/xapi/acrossx/extensions/assessmentName': data.assessmentName || data.title || null,\n 'https://w3id.org/xapi/acrossx/extensions/lessonName': lessonInfo.name || null,\n 'https://w3id.org/xapi/acrossx/extensions/sectionName': lessonInfo.sectionName || null,\n 'https://w3id.org/xapi/acrossx/extensions/questionCount': data.questionCount || null\n }\n };\n\n if (typeof data.score === 'number') {\n result.score = {\n scaled: data.score / 100,\n raw: data.score,\n max: 100,\n min: 0\n };\n result.success = data.score >= (data.passingScore || 70);\n }\n\n if (data.duration) {\n result.duration = data.duration;\n }\n\n var verbKey = result.success !== false ? 'passed' : 'failed';\n if (typeof result.success === 'undefined') {\n verbKey = 'completed';\n }\n\n var statement = buildStatementXyleme(verbKey, assessmentObject, result);\n sendStatement(statement);\n };\n\n // Question answered\n LRS.questionAnswered = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context if not provided\n var lessonInfo = data.lessonName ? data : getCachedLessonInfo();\n\n // Build question activity object for Xyleme format\n var questionObject = buildQuestionActivityObject({\n questionGuid: data.questionGuid || data.questionId,\n text: data.questionText || 'Question'\n });\n\n // Build comprehensive result with human-readable data\n var result = {\n response: data.answer || data.response || '',\n extensions: {\n 'https://w3id.org/xapi/acrossx/extensions/questionNumber': data.questionNumber || null,\n 'https://w3id.org/xapi/acrossx/extensions/questionText': data.questionText || null,\n 'https://w3id.org/xapi/acrossx/extensions/answerText': data.answer || data.response || null,\n 'https://w3id.org/xapi/acrossx/extensions/correctAnswer': data.correctAnswer || null,\n 'https://w3id.org/xapi/acrossx/extensions/assessmentName': data.assessmentName || null,\n 'https://w3id.org/xapi/acrossx/extensions/lessonName': lessonInfo.lessonName || lessonInfo.name || null,\n 'https://w3id.org/xapi/acrossx/extensions/sectionName': lessonInfo.sectionName || null\n }\n };\n\n if (data.result === 'correct' || data.correct === true) {\n result.success = true;\n } else if (data.result === 'incorrect' || data.correct === false) {\n result.success = false;\n }\n\n var statement = buildStatementXyleme('answered', questionObject, result);\n sendStatement(statement);\n };\n\n // Submit all questions\n LRS.questionsSubmitAll = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Send individual question statements\n if (data.questions && Array.isArray(data.questions)) {\n data.questions.forEach(function(q) {\n LRS.questionAnswered(q);\n });\n }\n\n // Then send assessment completed\n LRS.assessmentEnded(data);\n };\n\n // Interactions (tabs, accordions, etc.) - uses activity-specific object\n // Object name shows the interaction type and label, e.g., \"Tab: Overview\"\n // Parent context ensures these appear in document search results\n LRS.interacted = function(data) {\n if (!TRACK_INTERACTIONS) return;\n\n // Get current lesson context\n var lessonInfo = getCachedLessonInfo();\n\n // Build activity-specific object with human-readable name\n // e.g., \"Tab: Getting Started\", \"Accordion: FAQ\"\n var interactionObject = buildInteractionActivityObject({\n type: data.type || data.interactionType,\n id: data.id,\n name: data.name,\n label: data.name || data.type\n });\n\n // Context includes lesson info; parent course activity added by buildXylemeContext\n var additionalContext = {\n extensions: {\n interactionId: data.id || 'interaction-' + Date.now(),\n lessonId: lessonInfo.id,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n }\n };\n\n var statement = buildStatementXyleme('interacted', interactionObject, null, additionalContext);\n sendStatement(statement);\n };\n\n // Custom statement\n LRS.sendCustomStatement = function(data) {\n if (!CUSTOM_STATEMENTS) return;\n\n var verb = data.verb || 'interacted';\n var objectData = data.object || {};\n\n // Activity type from data or default to interaction\n var activityType = objectData.type || ACTIVITY_TYPES.interaction;\n\n // Activity details\n var activityDetails = {\n customActivityId: objectData.id || data.id || 'custom-' + Date.now(),\n customActivityName: objectData.name || data.name || 'Custom Activity'\n };\n\n // Merge any extra data\n if (data.data) {\n for (var key in data.data) {\n if (data.data.hasOwnProperty(key)) {\n activityDetails[key] = data.data[key];\n }\n }\n }\n\n var statement = buildStatement(verb, activityType, activityDetails, data.result, data.context);\n sendStatement(statement);\n };\n\n // Course terminated/exited\n LRS.courseTerminated = function() {\n // Calculate duration\n var duration = null;\n if (LRS.launchTime) {\n var launchMs = new Date(LRS.launchTime).getTime();\n var nowMs = Date.now();\n var durationMs = nowMs - launchMs;\n // Convert to ISO 8601 duration\n var seconds = Math.floor(durationMs / 1000);\n var minutes = Math.floor(seconds / 60);\n var hours = Math.floor(minutes / 60);\n seconds = seconds % 60;\n minutes = minutes % 60;\n duration = 'PT' + hours + 'H' + minutes + 'M' + seconds + 'S';\n }\n\n var result = duration ? { duration: duration } : null;\n\n // Use course object for termination (document-level) with 'exited' verb for Xyleme\n var courseObj = buildCourseActivityObject();\n if (courseObj) {\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n courseObj.definition.extensions.resourceType = 'Course';\n }\n\n var statement = buildStatementXyleme('exited', courseObj, result);\n sendStatement(statement);\n };\n\n // Legacy compatibility methods\n LRS.choiceBranchSelected = function(data) {\n LRS.interacted({ type: 'choice-branch', id: data.id, name: 'Choice Branch Selected' });\n };\n\n LRS.choiceBranchDiscarded = function(data) {\n LRS.interacted({ type: 'choice-branch', id: data.id, name: 'Choice Branch Discarded' });\n };\n\n // ========================================================================\n // 8. RISE EVENT INTERCEPTORS\n // ========================================================================\n\n function setupMediaInterceptors() {\n if (!TRACK_MEDIA) return;\n\n // Track which elements we've already attached listeners to\n var trackedMedia = new WeakSet();\n\n // Helper to get media name/title from element or surrounding context\n function getMediaName(el) {\n // Check element attributes\n var name = el.title || el.getAttribute('aria-label') || el.getAttribute('data-name');\n if (name) return name;\n\n // Check parent Rise video block for title\n var videoBlock = findClosest(el, '[data-block-type=\"video\"], .blocks-video, .video-block');\n if (videoBlock) {\n var titleEl = videoBlock.querySelector('.video-title, .block-title, [class*=\"title\"]');\n if (titleEl) return titleEl.textContent.trim();\n }\n\n // Use src filename as fallback\n var src = el.src || el.currentSrc || '';\n if (src) {\n var filename = src.split('/').pop().split('?')[0];\n if (filename && filename.length < 100) return filename;\n }\n\n return el.tagName.toLowerCase();\n }\n\n // Attach media event listeners to a single element\n function attachMediaListeners(el) {\n if (trackedMedia.has(el)) return;\n trackedMedia.add(el);\n\n var mediaType = el.tagName.toLowerCase();\n log('Attaching media listeners to:', mediaType, el.src || el.currentSrc || 'no-src');\n\n el.addEventListener('play', function() {\n log('Media play event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.currentTime || 0,\n duration: el.duration || 0,\n action: 'play'\n });\n });\n\n el.addEventListener('pause', function() {\n // Ignore pause events that fire right before ended\n if (el.ended) return;\n log('Media pause event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.currentTime || 0,\n duration: el.duration || 0,\n action: 'pause'\n });\n });\n\n el.addEventListener('ended', function() {\n log('Media ended event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.duration || 0,\n duration: el.duration || 0,\n action: 'completed'\n });\n });\n }\n\n // Scan for and attach listeners to all video/audio elements\n function scanForMedia(root) {\n var elements = (root || document).querySelectorAll('video, audio');\n log('Scanning for media elements, found:', elements.length);\n for (var i = 0; i < elements.length; i++) {\n attachMediaListeners(elements[i]);\n }\n }\n\n // Initial scan\n scanForMedia();\n\n // Document-level capture for any we might miss\n document.addEventListener('play', function(e) {\n var target = e.target;\n if (target.tagName === 'VIDEO' || target.tagName === 'AUDIO') {\n // Attach listeners if not already tracked\n if (!trackedMedia.has(target)) {\n log('Captured play from untracked media, attaching listeners');\n attachMediaListeners(target);\n }\n }\n }, true);\n\n // MutationObserver to detect dynamically added video/audio elements\n var mediaObserver = new MutationObserver(function(mutations) {\n var needsScan = false;\n mutations.forEach(function(mutation) {\n mutation.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'VIDEO' || node.tagName === 'AUDIO') {\n attachMediaListeners(node);\n } else if (node.querySelectorAll) {\n // Check for nested video/audio\n var nested = node.querySelectorAll('video, audio');\n if (nested.length > 0) {\n needsScan = true;\n }\n }\n }\n });\n });\n if (needsScan) {\n scanForMedia();\n }\n });\n\n // Start observing once DOM is ready\n function startMediaObserver() {\n if (document.body) {\n mediaObserver.observe(document.body, {\n childList: true,\n subtree: true\n });\n log('Media mutation observer started');\n } else {\n setTimeout(startMediaObserver, 100);\n }\n }\n startMediaObserver();\n\n // Periodic rescan for Rise lazy-loaded content\n setInterval(function() {\n scanForMedia();\n }, 3000);\n\n log('Media interceptors set up (enhanced)');\n }\n\n function setupNavigationInterceptors() {\n if (!TRACK_NAVIGATION) return;\n\n window.addEventListener('hashchange', function() {\n var newLesson = window.location.hash || '#/';\n if (newLesson !== LRS.currentLesson) {\n LRS.currentLesson = newLesson;\n LRS.contentOpened({\n lessonId: newLesson,\n url: window.location.href,\n action: 'navigated'\n });\n }\n });\n\n LRS.currentLesson = window.location.hash || '#/';\n\n log('Navigation interceptors set up');\n }\n\n function setupQuizInterceptors() {\n if (!TRACK_QUIZZES) return;\n\n // Set up Knowledge Check specific interceptors for Rise blocks\n setupKnowledgeCheckInterceptors();\n\n var quizObserver = new MutationObserver(function(mutations) {\n mutations.forEach(function(mutation) {\n mutation.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n detectQuizElements(node);\n }\n });\n });\n });\n\n function startQuizObserver() {\n if (document.body) {\n quizObserver.observe(document.body, {\n childList: true,\n subtree: true\n });\n log('Quiz observer started');\n } else {\n setTimeout(startQuizObserver, 100);\n }\n }\n startQuizObserver();\n }\n\n function detectQuizElements(node) {\n var resultClasses = ['quiz-result', 'knowledge-result', 'quiz-feedback', 'assessment-result',\n 'blocks-quiz__result', 'block-quiz-results'];\n\n resultClasses.forEach(function(cls) {\n if (node.classList && node.classList.contains(cls)) {\n extractQuizResult(node);\n }\n var results = node.querySelectorAll ? node.querySelectorAll('.' + cls) : [];\n results.forEach(function(resultNode) {\n extractQuizResult(resultNode);\n });\n });\n\n var startClasses = ['quiz-start', 'knowledge-check', 'assessment-intro', 'blocks-quiz'];\n startClasses.forEach(function(cls) {\n if (node.classList && node.classList.contains(cls)) {\n LRS.assessmentStarted({\n assessmentId: extractAssessmentId(node)\n });\n }\n });\n }\n\n function extractAssessmentId(node) {\n return node.getAttribute('data-block-id') ||\n node.getAttribute('data-assessment-id') ||\n node.getAttribute('id') ||\n 'quiz-' + Date.now();\n }\n\n /**\n * Extract quiz/assessment name from the DOM\n */\n function extractAssessmentName(node) {\n // Try various selectors for assessment/quiz titles\n var titleEl = node.querySelector(\n '.quiz-title, .assessment-title, .knowledge-check-title, ' +\n '.blocks-quiz__title, .block-title, ' +\n '[class*=\"quiz\"] [class*=\"title\"], ' +\n '[class*=\"assessment\"] [class*=\"title\"], ' +\n 'h2, h3'\n );\n if (titleEl) {\n return titleEl.textContent.trim().substring(0, 200);\n }\n\n // Try to get from parent block\n var parentBlock = node.closest('[data-block-type]');\n if (parentBlock) {\n var blockTitle = parentBlock.querySelector('.block-title, h2, h3');\n if (blockTitle) {\n return blockTitle.textContent.trim().substring(0, 200);\n }\n }\n\n // Get current lesson name as context\n var lessonInfo = getCachedLessonInfo();\n if (lessonInfo.name) {\n return 'Quiz in ' + lessonInfo.name;\n }\n\n return 'Knowledge Check';\n }\n\n function extractQuizResult(node) {\n var scoreEl = node.querySelector('.score-value, .quiz-score, .score-percentage, [class*=\"score\"]');\n var score = scoreEl ? parseFloat(scoreEl.textContent.replace(/[^0-9.]/g, '')) : null;\n\n // Get assessment name and lesson context\n var assessmentName = extractAssessmentName(node);\n var lessonInfo = getCachedLessonInfo();\n\n // Find all question containers - Rise uses various structures\n var questions = node.querySelectorAll(\n '.question-result, .question-feedback, .question-item, ' +\n '.blocks-quiz__question, .quiz-question, ' +\n '[class*=\"question-\"][class*=\"result\"], ' +\n '[class*=\"question-\"][class*=\"feedback\"], ' +\n '[data-question-id]'\n );\n\n log('Found', questions.length, 'question elements in quiz result');\n\n if (questions.length > 0) {\n questions.forEach(function(q, index) {\n // Determine if correct - check multiple indicators\n var isCorrect = q.classList.contains('correct') ||\n q.classList.contains('is-correct') ||\n q.querySelector('.correct, .is-correct, [class*=\"correct\"]') !== null ||\n q.getAttribute('data-correct') === 'true' ||\n q.getAttribute('data-result') === 'correct';\n\n // Get the question text - try multiple selectors\n var questionText = '';\n var questionEl = q.querySelector(\n '.question-text, .question-stem, .question-title, ' +\n '.blocks-quiz__question-text, .quiz-question__text, ' +\n '[class*=\"question-text\"], [class*=\"question-stem\"], ' +\n '[class*=\"prompt\"], p:first-of-type'\n );\n if (questionEl) {\n questionText = questionEl.textContent.trim();\n }\n\n // Get the learner's selected answer text\n var answerText = '';\n var answerEl = q.querySelector(\n '.selected-answer, .learner-response, .answer-text, ' +\n '.user-answer, .chosen-answer, .response-text, ' +\n '.blocks-quiz__response, .quiz-question__response, ' +\n '[class*=\"selected\"], [class*=\"chosen\"], [class*=\"response\"], ' +\n '[class*=\"user-answer\"]'\n );\n if (answerEl) {\n answerText = answerEl.textContent.trim();\n }\n\n // If no specific answer element, try to find selected radio/checkbox label\n if (!answerText) {\n var selectedInput = q.querySelector('input:checked, [aria-checked=\"true\"]');\n if (selectedInput) {\n var label = q.querySelector('label[for=\"' + selectedInput.id + '\"]');\n if (label) {\n answerText = label.textContent.trim();\n } else {\n var parentLabel = selectedInput.closest('label');\n if (parentLabel) {\n answerText = parentLabel.textContent.trim();\n }\n }\n }\n }\n\n // Get question ID if available\n var questionId = q.getAttribute('data-question-id') ||\n q.getAttribute('data-block-id') ||\n q.getAttribute('id') ||\n 'q' + (index + 1);\n\n // Get correct answer text if available (for reporting)\n var correctAnswerText = '';\n var correctEl = q.querySelector(\n '.correct-answer, .right-answer, ' +\n '[class*=\"correct-answer\"], [class*=\"right-answer\"]'\n );\n if (correctEl) {\n correctAnswerText = correctEl.textContent.trim();\n }\n\n log('Question', index + 1, ':', {\n questionText: questionText.substring(0, 50) + '...',\n answerText: answerText.substring(0, 50) + '...',\n isCorrect: isCorrect\n });\n\n LRS.questionAnswered({\n questionId: questionId,\n questionNumber: index + 1,\n questionText: questionText.substring(0, 500),\n answer: answerText.substring(0, 500),\n correctAnswer: correctAnswerText.substring(0, 500),\n result: isCorrect ? 'correct' : 'incorrect',\n assessmentName: assessmentName,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n });\n }\n\n LRS.assessmentEnded({\n assessmentId: extractAssessmentId(node),\n assessmentName: assessmentName,\n score: score,\n questionCount: questions.length,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n }\n\n // Track submitted Knowledge Check blocks to avoid duplicates\n var submittedKnowledgeChecks = {};\n\n /**\n * Set up interceptors specifically for Rise Knowledge Check blocks\n * These blocks have a different DOM structure than standard quiz results\n */\n function setupKnowledgeCheckInterceptors() {\n if (!TRACK_QUIZZES) return;\n\n // Intercept submit button clicks on Knowledge Check blocks\n document.addEventListener('click', function(e) {\n var submitBtn = e.target.closest('.quiz-card__button');\n if (!submitBtn) return;\n\n // Find the Knowledge Check block\n var kcBlock = submitBtn.closest('[data-test-id=\"block-kc-card\"]') ||\n submitBtn.closest('.block-knowledge');\n if (!kcBlock) return;\n\n // Wait for feedback to appear after submission\n setTimeout(function() {\n extractKnowledgeCheckResult(kcBlock);\n }, 500);\n }, true);\n\n log('Knowledge Check interceptors set up');\n }\n\n /**\n * Extract and send xAPI statement for a Knowledge Check submission\n */\n function extractKnowledgeCheckResult(kcBlock) {\n // Get block ID for deduplication\n var blockContainer = kcBlock.closest('[data-block-id]');\n var blockId = blockContainer ? blockContainer.getAttribute('data-block-id') : null;\n\n // Get question ID from the title element\n var questionTitleEl = kcBlock.querySelector('.quiz-card__title');\n var questionId = questionTitleEl ? questionTitleEl.id : (blockId ? 'q-' + blockId : 'q-' + generateUUID());\n\n // Check if we already processed this submission (avoid duplicates)\n var submissionKey = blockId || questionId;\n var feedbackLabel = kcBlock.querySelector('.quiz-card__feedback-label');\n if (!feedbackLabel) {\n log('Knowledge Check: No feedback visible yet');\n return;\n }\n\n var feedbackText = feedbackLabel.textContent.trim().toLowerCase();\n var submissionId = submissionKey + '-' + feedbackText;\n\n if (submittedKnowledgeChecks[submissionId]) {\n log('Knowledge Check: Already processed this submission');\n return;\n }\n submittedKnowledgeChecks[submissionId] = true;\n\n // Get question text\n var questionText = '';\n var questionTextEl = kcBlock.querySelector('.quiz-card__title .fr-view, .quiz-card__title');\n if (questionTextEl) {\n questionText = questionTextEl.textContent.trim();\n }\n\n // Determine question type from aria-label\n var wrapper = kcBlock.querySelector('[data-test-id=\"block-knowledge-wrapper\"]');\n var ariaLabel = wrapper ? wrapper.getAttribute('aria-label') : '';\n var questionType = 'unknown';\n if (ariaLabel.indexOf('Multiple choice') > -1) questionType = 'multiple-choice';\n else if (ariaLabel.indexOf('Multiple response') > -1) questionType = 'multiple-response';\n else if (ariaLabel.indexOf('Fill in the blank') > -1) questionType = 'fill-in-blank';\n else if (ariaLabel.indexOf('Matching') > -1) questionType = 'matching';\n\n // Get selected answer(s) based on question type\n var answerText = extractKnowledgeCheckAnswer(kcBlock, questionType);\n\n // Get correct/incorrect from feedback\n var isCorrect = feedbackText === 'correct';\n\n // Get lesson context\n var lessonInfo = getCachedLessonInfo();\n\n log('Knowledge Check submitted:', {\n questionId: questionId,\n questionText: questionText.substring(0, 50) + '...',\n questionType: questionType,\n answer: answerText.substring(0, 50) + '...',\n correct: isCorrect\n });\n\n // Send question answered statement using existing LRS method\n LRS.questionAnswered({\n questionId: questionId,\n questionGuid: blockId || generateUUID(),\n questionNumber: 1,\n questionText: questionText.substring(0, 500),\n questionType: questionType,\n answer: answerText.substring(0, 500),\n correct: isCorrect,\n result: isCorrect ? 'correct' : 'incorrect',\n assessmentName: 'Knowledge Check',\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n }\n\n /**\n * Extract the selected answer text from a Knowledge Check block\n * based on the question type\n */\n function extractKnowledgeCheckAnswer(kcBlock, questionType) {\n var answerText = '';\n\n if (questionType === 'multiple-choice') {\n // Find checked radio button\n var checkedInput = kcBlock.querySelector('.quiz-multiple-choice-option__input:checked');\n if (checkedInput) {\n var label = checkedInput.closest('.quiz-multiple-choice-option');\n var textEl = label ? label.querySelector('.quiz-multiple-choice-option__label .fr-view, .quiz-multiple-choice-option__label') : null;\n answerText = textEl ? textEl.textContent.trim() : '';\n }\n }\n else if (questionType === 'multiple-response') {\n // Find all checked checkboxes\n var checkedInputs = kcBlock.querySelectorAll('.quiz-multiple-response-option__input:checked');\n var answers = [];\n checkedInputs.forEach(function(input) {\n var label = input.closest('.quiz-multiple-response-option');\n var textEl = label ? label.querySelector('.quiz-multiple-response-option__text .fr-view, .quiz-multiple-response-option__text') : null;\n if (textEl) answers.push(textEl.textContent.trim());\n });\n answerText = answers.join('; ');\n }\n else if (questionType === 'fill-in-blank') {\n // Get text input value\n var textInput = kcBlock.querySelector('.quiz-fill__input');\n answerText = textInput ? textInput.value.trim() : '';\n }\n else if (questionType === 'matching') {\n // Extract matching pairs from the drop zones\n var dropZones = kcBlock.querySelectorAll('.matching-drop-zone');\n var pairs = [];\n dropZones.forEach(function(zone) {\n var prompt = zone.querySelector('.matching-prompt-content');\n var response = zone.querySelector('.matching-interaction-piece-content');\n if (prompt && response) {\n pairs.push(prompt.textContent.trim() + ' → ' + response.textContent.trim());\n }\n });\n answerText = pairs.length > 0 ? pairs.join('; ') : 'Matching submitted';\n }\n\n return answerText;\n }\n\n function setupInteractionInterceptors() {\n if (!TRACK_INTERACTIONS) return;\n\n document.addEventListener('click', function(e) {\n var target = e.target;\n\n // Tabs\n var tabTrigger = findClosest(target, '[data-block-type=\"tabs\"] .tab-item, .tabs__tab, .tab-trigger, [role=\"tab\"]');\n if (tabTrigger) {\n LRS.interacted({\n type: 'tab',\n id: tabTrigger.getAttribute('data-tab-id') ||\n tabTrigger.getAttribute('aria-controls') ||\n tabTrigger.textContent.trim().substring(0, 50),\n name: 'Tab: ' + (tabTrigger.textContent.trim().substring(0, 30) || 'Tab')\n });\n return;\n }\n\n // Accordions\n var accordionTrigger = findClosest(target, '[data-block-type=\"accordion\"] .accordion-trigger, .accordion__header, .accordion-item__header, [aria-expanded]');\n if (accordionTrigger && accordionTrigger.getAttribute('aria-expanded')) {\n LRS.interacted({\n type: 'accordion',\n id: accordionTrigger.getAttribute('data-accordion-id') ||\n accordionTrigger.getAttribute('aria-controls') ||\n accordionTrigger.textContent.trim().substring(0, 50),\n name: 'Accordion: ' + (accordionTrigger.textContent.trim().substring(0, 30) || 'Section')\n });\n return;\n }\n\n // Flashcards\n var flashcard = findClosest(target, '.flashcard, [data-block-type=\"flashcard\"], .blocks-flashcard');\n if (flashcard) {\n LRS.interacted({\n type: 'flashcard',\n id: flashcard.getAttribute('data-card-id') || 'flashcard-' + Date.now(),\n name: 'Flashcard'\n });\n return;\n }\n\n // Process/Timeline\n var processItem = findClosest(target, '.process-item, .timeline-item, [data-block-type=\"process\"] .step, .blocks-process__item');\n if (processItem) {\n LRS.interacted({\n type: 'process-step',\n id: processItem.getAttribute('data-step-id') ||\n processItem.getAttribute('data-index') ||\n 'step-' + Date.now(),\n name: 'Process Step'\n });\n return;\n }\n\n // Hotspots/Markers\n var hotspot = findClosest(target, '.hotspot-marker, .marker-item, [data-block-type=\"labeled-graphic\"] .marker, .blocks-labeled-graphic__marker');\n if (hotspot) {\n LRS.interacted({\n type: 'hotspot',\n id: hotspot.getAttribute('data-marker-id') ||\n hotspot.getAttribute('data-index') ||\n 'hotspot-' + Date.now(),\n name: 'Hotspot'\n });\n return;\n }\n\n // Sorting activities\n var sortItem = findClosest(target, '.sort-item, [data-block-type=\"sorting\"] .item, .blocks-sorting__item');\n if (sortItem) {\n LRS.interacted({\n type: 'sorting',\n id: sortItem.getAttribute('data-item-id') || 'sort-item',\n name: 'Sorting Activity'\n });\n return;\n }\n\n // Buttons / Continue\n var button = findClosest(target, '.continue-button, .nav-button, [data-block-type=\"button\"], .blocks-button');\n if (button) {\n LRS.interacted({\n type: 'button',\n id: button.getAttribute('data-button-id') || 'button',\n name: 'Button: ' + (button.textContent.trim().substring(0, 30) || 'Continue')\n });\n }\n\n }, true);\n\n log('Interaction interceptors set up');\n }\n\n function findClosest(el, selector) {\n if (!el || !selector) return null;\n if (el.closest) {\n return el.closest(selector);\n }\n var current = el;\n while (current && current !== document) {\n if (matchesSelector(current, selector)) {\n return current;\n }\n current = current.parentElement;\n }\n return null;\n }\n\n function matchesSelector(el, selector) {\n var fn = el.matches || el.webkitMatchesSelector || el.msMatchesSelector;\n return fn ? fn.call(el, selector) : false;\n }\n\n // ========================================================================\n // 9. INITIALIZATION\n // ========================================================================\n\n function init() {\n log('Initializing LRS bridge v2.6.0...');\n\n // Extract course info early\n extractCourseInfo();\n\n // Setup connection\n var bridgeReady = setupBridge();\n\n if (bridgeReady) {\n LRS.initialized = true;\n\n // Extract actor (sync first for immediate use)\n extractActor();\n\n log('Bridge initialized in mode:', LRS.mode);\n log('Actor:', LRS.actor);\n log('Course:', LRS.courseInfo);\n\n // Then try async actor extraction from Bravais session\n // This will update the actor with real user info if available\n extractActorAsync(function(actor) {\n log('Actor updated after async fetch:', actor);\n });\n } else {\n warn('Bridge setup failed - operating in offline mode');\n LRS.mode = 'offline';\n }\n\n // Always-visible bridge summary (not gated by DEBUG)\n // This ensures diagnostics are available even in production builds\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Bridge: mode=' + LRS.mode +\n ', endpoint=' + (LRS_ENDPOINT ? LRS_ENDPOINT.substring(0, 30) + '...' : 'NONE') +\n ', proxy=' + (LRS_PROXY_ENDPOINT ? 'yes' : 'no') +\n ', actor=' + (LRS.actor ? (LRS.actor.name || 'anonymous') : 'none'));\n }\n\n // Set up event interceptors\n setupMediaInterceptors();\n setupNavigationInterceptors();\n setupQuizInterceptors();\n setupInteractionInterceptors();\n\n // Fetch document metadata from API to get GUIDs (async)\n // Then send course launched event\n var sharedLinkToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;\n var documentId = LRS.courseInfo ? LRS.courseInfo.documentId : null;\n\n function sendLaunchEvents() {\n // Log the course info state before sending statements\n log('Sending launch events with course info:', {\n id: LRS.courseInfo ? LRS.courseInfo.id : null,\n guid: LRS.courseInfo ? LRS.courseInfo.guid : null,\n versionGuid: LRS.courseInfo ? LRS.courseInfo.versionGuid : null,\n documentId: LRS.courseInfo ? LRS.courseInfo.documentId : null\n });\n\n if (TRACK_NAVIGATION) {\n LRS.courseLaunched();\n LRS.contentOpened({\n lessonId: window.location.hash || '#/',\n url: window.location.href,\n action: 'launched'\n });\n }\n }\n\n function fetchDocDataAndSend(docId) {\n fetchDocumentMetadata(docId, function(docData) {\n if (docData) {\n updateCourseInfoFromApi(docData);\n }\n // Send launch events after API fetch (success or failure)\n setTimeout(sendLaunchEvents, 100);\n });\n }\n\n if (!LRS.courseInfo.guid) {\n // When we have a shared link token, use /api/shared/{token}/documents directly\n // This endpoint does NOT require authentication and returns both document GUID and version GUID\n if (sharedLinkToken) {\n log('Have shared link token, fetching document data via shared API...');\n // fetchDocumentMetadata will use /api/shared/{token}/documents when sharedLinkToken is present\n fetchDocumentMetadata(null, function(docData) {\n if (docData) {\n updateCourseInfoFromApi(docData);\n // Also update document name if available\n if (docData.name) {\n LRS.courseInfo.sharedLinkName = docData.name;\n }\n } else {\n warn('Could not fetch document data from shared API - statements may fail aggregation');\n }\n setTimeout(sendLaunchEvents, 100);\n });\n } else if (documentId) {\n // No shared link token, try with extracted document ID (requires auth)\n log('No shared link token, fetching document data with ID:', documentId);\n fetchDocDataAndSend(documentId);\n } else {\n // No identifiers available\n warn('No shared link token or document ID - statements may fail aggregation');\n setTimeout(sendLaunchEvents, 500);\n }\n } else {\n // Already have GUID - send after short delay\n setTimeout(sendLaunchEvents, 500);\n }\n\n // Setup beforeunload to send terminated\n window.addEventListener('beforeunload', function() {\n LRS.courseTerminated();\n });\n\n // Retry queue every 30 seconds\n setInterval(processStatementQueue, 30000);\n\n // Retry bridge connection if failed\n if (!LRS.initialized && LRS.mode !== 'directLRS') {\n var retryCount = 0;\n var maxRetries = 10;\n var retryInterval = setInterval(function() {\n retryCount++;\n if (setupBridge()) {\n LRS.initialized = true;\n extractActorAsync(function(actor) {\n log('Actor extracted on retry:', actor);\n });\n log('Bridge connected on retry ' + retryCount);\n clearInterval(retryInterval);\n } else if (retryCount >= maxRetries) {\n clearInterval(retryInterval);\n }\n }, 1000);\n }\n\n log('LRS bridge setup complete');\n }\n\n // Initialize\n init();\n\n })();\n`;\n}\n","import type { PatchAdamsConfig, LrsBridgeConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport { generateLrsBridgeCode, type LrsBridgeOptions } from './lrs-bridge.js';\n\nexport interface JsBeforeOptions {\n remoteUrl: string;\n localPath: string;\n htmlClass: string;\n loadingClass: string;\n /** Full course metadata */\n metadata: CourseMetadata | null;\n /** LRS Bridge configuration */\n lrsBridge: LrsBridgeOptions;\n}\n\n/**\n * Escape a string for safe inclusion in JavaScript\n */\nfunction escapeJs(str: string | null | undefined): string {\n if (!str) return '';\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r');\n}\n\n/**\n * Generate the blocking JS loader for the \"before\" slot\n * This loads at the start of <head> and blocks rendering until loaded\n *\n * Use this for:\n * - Setting up global variables\n * - Intercepting Rise APIs before they initialize\n * - Preparing the environment\n * - LRS Bridge for xAPI/SCORM communication\n */\nexport function generateJsBeforeLoader(options: JsBeforeOptions): string {\n const { remoteUrl, localPath, htmlClass, loadingClass, metadata, lrsBridge } = options;\n\n // Generate LRS bridge code\n const lrsBridgeCode = generateLrsBridgeCode(lrsBridge);\n\n // Build course object with all available metadata\n const courseLines: string[] = [];\n\n if (metadata) {\n courseLines.push(` courseId: '${escapeJs(metadata.courseId)}',`);\n courseLines.push(` courseIdSource: '${metadata.courseIdSource}',`);\n courseLines.push(` format: '${metadata.format}',`);\n\n if (metadata.title) {\n courseLines.push(` title: '${escapeJs(metadata.title)}',`);\n }\n if (metadata.description) {\n courseLines.push(` description: '${escapeJs(metadata.description)}',`);\n }\n if (metadata.organizationId) {\n courseLines.push(` organizationId: '${escapeJs(metadata.organizationId)}',`);\n }\n if (metadata.launchUrl) {\n courseLines.push(` launchUrl: '${escapeJs(metadata.launchUrl)}',`);\n }\n if (metadata.masteryScore !== null) {\n courseLines.push(` masteryScore: ${metadata.masteryScore},`);\n }\n if (metadata.duration) {\n courseLines.push(` duration: '${escapeJs(metadata.duration)}',`);\n }\n if (metadata.language) {\n courseLines.push(` language: '${escapeJs(metadata.language)}',`);\n }\n if (metadata.version) {\n courseLines.push(` version: '${escapeJs(metadata.version)}',`);\n }\n courseLines.push(` extractedAt: '${metadata.extractedAt}',`);\n }\n\n const courseBlock = courseLines.length > 0\n ? `\\n course: {\\n${courseLines.join('\\n')}\\n },`\n : '';\n\n return `<!-- === PATCH-ADAMS: JS BEFORE (blocking) === -->\n<script data-pa=\"js-before-loader\">\n// Initialize Patch-Adams global namespace IMMEDIATELY (before IIFE)\nwindow.pa_patcher = window.pa_patcher || {\n version: '1.0.25',\n htmlClass: '${htmlClass}',\n loadingClass: '${loadingClass}',${courseBlock}\n loaded: {\n cssBefore: false,\n cssAfter: false,\n jsBefore: false,\n jsAfter: false\n }\n};\n\n// Filter out browser extension errors that can interfere with LMS error handling\n// These are not course errors - they come from extensions like SingleFile, LastPass, etc.\n(function() {\n var extensionPatterns = [\n 'extension',\n 'chrome-extension',\n 'moz-extension',\n 'safari-extension',\n 'message channel closed',\n 'listener indicated an asynchronous response',\n 'Receiving end does not exist',\n 'single-file',\n 'singlefile'\n ];\n\n function isExtensionError(error) {\n if (!error) return false;\n var msg = (error.message || error.reason || String(error)).toLowerCase();\n for (var i = 0; i < extensionPatterns.length; i++) {\n if (msg.indexOf(extensionPatterns[i]) !== -1) return true;\n }\n // Check stack trace for extension URLs\n var stack = (error.stack || '').toLowerCase();\n if (stack.indexOf('extension') !== -1 || stack.indexOf('moz-extension') !== -1) return true;\n return false;\n }\n\n // Handle unhandled promise rejections from extensions\n window.addEventListener('unhandledrejection', function(event) {\n if (isExtensionError(event.reason)) {\n event.preventDefault();\n event.stopImmediatePropagation();\n console.debug('[Patch-Adams] Suppressed browser extension error:', event.reason);\n return false;\n }\n }, true);\n\n // Handle regular errors from extensions\n window.addEventListener('error', function(event) {\n if (isExtensionError(event.error || event.message)) {\n event.preventDefault();\n event.stopImmediatePropagation();\n console.debug('[Patch-Adams] Suppressed browser extension error:', event.message);\n return false;\n }\n }, true);\n})();\n\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n\n function loadJSSync(url) {\n var script = document.createElement('script');\n script.src = url;\n script.setAttribute('data-pa', 'js-before');\n // Note: For truly blocking behavior, we use document.write\n // but that's fragile. Instead we'll handle errors gracefully.\n document.head.appendChild(script);\n return script;\n }\n\n // Try remote first\n var script = loadJSSync(REMOTE_URL);\n\n script.onerror = function() {\n console.warn('[Patch-Adams] JS before failed to load from remote, using local fallback');\n document.head.removeChild(script);\n var fallback = loadJSSync(LOCAL_PATH);\n fallback.onload = function() {\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsBefore = true;\n }\n console.log('[Patch-Adams] JS before loaded from local fallback:', LOCAL_PATH);\n };\n fallback.onerror = function() {\n console.error('[Patch-Adams] JS before failed to load from both remote and local');\n };\n };\n\n script.onload = function() {\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsBefore = true;\n }\n console.log('[Patch-Adams] JS before loaded from remote:', REMOTE_URL);\n };\n})();\n${lrsBridgeCode}\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildJsBeforeOptions(config: PatchAdamsConfig, metadata?: CourseMetadata | null): JsBeforeOptions {\n // Convert LrsBridgeConfig to LrsBridgeOptions with defaults for backwards compatibility\n // This handles configs from older versions that don't have lrsBridge\n const lrsBridgeConfig = config.lrsBridge ?? {};\n const lrsBridge: LrsBridgeOptions = {\n enabled: lrsBridgeConfig.enabled ?? true,\n trackMedia: lrsBridgeConfig.trackMedia ?? true,\n trackNavigation: lrsBridgeConfig.trackNavigation ?? true,\n trackQuizzes: lrsBridgeConfig.trackQuizzes ?? true,\n trackInteractions: lrsBridgeConfig.trackInteractions ?? true,\n customStatements: lrsBridgeConfig.customStatements ?? true,\n debug: lrsBridgeConfig.debug ?? false,\n lrsEndpoint: lrsBridgeConfig.lrsEndpoint,\n lrsWithCredentials: lrsBridgeConfig.lrsWithCredentials,\n courseHomepage: lrsBridgeConfig.courseHomepage,\n autoDetectLrs: lrsBridgeConfig.autoDetectLrs,\n lrsAuth: lrsBridgeConfig.lrsAuth,\n lrsProxyEndpoint: lrsBridgeConfig.lrsProxyEndpoint,\n employeeApiEndpoint: lrsBridgeConfig.employeeApiEndpoint,\n };\n\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.js}/${config.jsBefore.filename}`,\n localPath: `${config.localFolders.js}/${config.jsBefore.filename}`,\n htmlClass: config.htmlClass,\n loadingClass: config.loadingClass,\n metadata: metadata ?? null,\n lrsBridge,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface JsAfterOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n loadingClass: string;\n}\n\n/**\n * Generate the async JS loader for the \"after\" slot\n * This loads at the end of <body> with remote-first, local fallback\n *\n * This script is responsible for:\n * - Loading your override JS after Rise has initialized\n * - Removing the loading class to reveal the content\n */\nexport function generateJsAfterLoader(options: JsAfterOptions): string {\n const { remoteUrl, localPath, timeout, loadingClass } = options;\n\n return `<!-- === PATCH-ADAMS: JS AFTER (async with fallback) === -->\n<script data-pa=\"js-after-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n var LOADING_CLASS = \"${loadingClass}\";\n\n function loadJS(url, onSuccess, onError) {\n var script = document.createElement('script');\n script.src = url;\n script.async = true;\n script.setAttribute('data-pa', 'js-after');\n\n script.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n script.onerror = function() {\n if (onError) onError();\n };\n\n document.body.appendChild(script);\n return script;\n }\n\n function removeLoadingClass() {\n document.documentElement.classList.remove(LOADING_CLASS);\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsAfter = true;\n }\n console.log('[Patch-Adams] Loading complete, content revealed');\n\n // Add visual badge to indicate patching is active\n var badge = document.createElement('div');\n badge.setAttribute('data-pa', 'badge');\n badge.style.cssText = 'position:fixed;bottom:10px;right:10px;background:#4CAF50;color:white;padding:4px 8px;border-radius:4px;font-size:11px;font-family:sans-serif;z-index:999999;opacity:0.8;cursor:pointer;';\n badge.textContent = 'PA';\n badge.title = 'Patch-Adams Active';\n badge.onclick = function() { badge.style.display = 'none'; };\n document.body.appendChild(badge);\n }\n\n function loadJSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteScript = loadJS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[Patch-Adams] JS after loaded from remote:', REMOTE_URL);\n // Give the script a moment to execute, then remove loading class\n setTimeout(removeLoadingClass, 50);\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[Patch-Adams] JS after timed out, using local fallback');\n if (remoteScript.parentNode) {\n document.body.removeChild(remoteScript);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadJS(\n LOCAL_PATH,\n function() {\n console.log('[Patch-Adams] JS after loaded from local fallback:', LOCAL_PATH);\n setTimeout(removeLoadingClass, 50);\n },\n function() {\n console.error('[Patch-Adams] JS after failed to load from both remote and local');\n // Still remove loading class so content is visible even if JS fails\n removeLoadingClass();\n }\n );\n }\n\n // Execute after DOM is ready to ensure Rise has loaded\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function() {\n // Small delay to ensure Rise has initialized\n setTimeout(loadJSWithFallback, 100);\n });\n } else {\n // DOM is already ready\n setTimeout(loadJSWithFallback, 100);\n }\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildJsAfterOptions(config: PatchAdamsConfig): JsAfterOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.js}/${config.jsAfter.filename}`,\n localPath: `${config.localFolders.js}/${config.jsAfter.filename}`,\n timeout: config.jsAfter.timeout,\n loadingClass: config.loadingClass,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport type { GeneratedPluginAssets } from '../plugins/types.js';\nimport {\n generateCssBeforeLoader,\n buildCssBeforeOptions,\n generateCssAfterLoader,\n buildCssAfterOptions,\n generateJsBeforeLoader,\n buildJsBeforeOptions,\n generateJsAfterLoader,\n buildJsAfterOptions,\n} from '../templates/index.js';\n\n/**\n * HTML Injector for patching Rise course index.html\n *\n * Injection points:\n * 1. CSS Before - Start of <head> (blocking)\n * 2. JS Before - Start of <head>, after CSS Before (blocking)\n * 3. CSS After - End of <head> (async with fallback)\n * 4. JS After - End of <body>, after __loadEntry() (async with fallback)\n * 5. Plugin Assets - Inline CSS/JS injected directly (always executes)\n *\n * Also adds classes and data attributes to <html> tag\n */\nexport class HtmlInjector {\n private config: PatchAdamsConfig;\n private metadata: CourseMetadata | null = null;\n private pluginAssets: GeneratedPluginAssets | null = null;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Set plugin-generated assets to inject inline into the HTML.\n * These are injected directly (not via external files) so they always execute.\n */\n setPluginAssets(assets: GeneratedPluginAssets): void {\n this.pluginAssets = assets;\n }\n\n /**\n * Set the course metadata to be injected into the HTML\n */\n setMetadata(metadata: CourseMetadata): void {\n this.metadata = metadata;\n }\n\n /**\n * @deprecated Use setMetadata instead\n * Set the course ID to be injected into the HTML\n */\n setCourseId(courseId: string): void {\n // Create minimal metadata for backwards compatibility\n if (!this.metadata) {\n this.metadata = {\n courseId,\n courseIdSource: 'fallback',\n format: 'unknown',\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n } else {\n this.metadata.courseId = courseId;\n }\n }\n\n /**\n * Inject all loaders into HTML\n */\n inject(html: string): string {\n let result = html;\n\n // Step 1: Add classes and data attributes to <html> tag\n result = this.addHtmlAttributes(result);\n\n // Step 2: Inject CSS Before at start of <head>\n if (this.config.cssBefore.enabled) {\n result = this.injectCssBefore(result);\n }\n\n // Step 3: Inject JS Before at start of <head> (after CSS Before)\n if (this.config.jsBefore.enabled) {\n result = this.injectJsBefore(result);\n }\n\n // Step 4: Inject CSS After at end of <head>\n if (this.config.cssAfter.enabled) {\n result = this.injectCssAfter(result);\n }\n\n // Step 5: Inject JS After at end of <body>\n if (this.config.jsAfter.enabled) {\n result = this.injectJsAfter(result);\n }\n\n // Step 6: Inject plugin assets inline (always executes, independent of remote/local loading)\n if (this.pluginAssets) {\n result = this.injectPluginAssets(result);\n }\n\n return result;\n }\n\n /**\n * Inject plugin assets inline into the HTML.\n * Plugin CSS goes at end of <head>, plugin JS goes at end of <body>.\n * These are injected directly so they always execute regardless of\n * whether remote or local fallback files are loaded.\n */\n private injectPluginAssets(html: string): string {\n let result = html;\n\n // Inject plugin CSS (before + after combined) at end of <head>\n const pluginCss = [\n this.pluginAssets?.cssBefore || '',\n this.pluginAssets?.cssAfter || '',\n ].filter(Boolean).join('\\n');\n\n if (pluginCss) {\n const cssBlock = `<!-- === PATCH-ADAMS: PLUGIN CSS (inline) === -->\n<style data-pa=\"plugin-css\">\n${pluginCss}\n</style>`;\n result = result.replace(/<\\/head>/i, `${cssBlock}\\n</head>`);\n console.log('[HtmlInjector] Injected plugin CSS inline');\n }\n\n // Inject plugin JS (before + after combined) at end of <body>\n const pluginJs = [\n this.pluginAssets?.jsBefore || '',\n this.pluginAssets?.jsAfter || '',\n ].filter(Boolean).join('\\n');\n\n if (pluginJs) {\n const jsBlock = `<!-- === PATCH-ADAMS: PLUGIN JS (inline) === -->\n<script data-pa=\"plugin-js\">\n${pluginJs}\n</script>`;\n result = result.replace(/<\\/body>/i, `${jsBlock}\\n</body>`);\n console.log('[HtmlInjector] Injected plugin JS inline');\n }\n\n return result;\n }\n\n /**\n * Add pa-patched and pa-loading classes to <html> tag\n * Also adds data attributes for course metadata\n */\n private addHtmlAttributes(html: string): string {\n const { htmlClass, loadingClass } = this.config;\n const classes = `${htmlClass} ${loadingClass}`;\n\n // Build data attributes from metadata\n const dataAttrs: string[] = [];\n if (this.metadata) {\n dataAttrs.push(`data-pa-course-id=\"${this.escapeAttr(this.metadata.courseId)}\"`);\n dataAttrs.push(`data-pa-format=\"${this.metadata.format}\"`);\n if (this.metadata.title) {\n dataAttrs.push(`data-pa-title=\"${this.escapeAttr(this.metadata.title)}\"`);\n }\n }\n const dataAttrString = dataAttrs.length > 0 ? ' ' + dataAttrs.join(' ') : '';\n\n // Match <html> tag with or without existing class attribute\n const htmlTagPattern = /<html([^>]*)>/i;\n const match = html.match(htmlTagPattern);\n\n if (!match) {\n return html;\n }\n\n const attributes = match[1];\n\n // Check if class attribute exists\n if (/class\\s*=\\s*[\"'][^\"']*[\"']/i.test(attributes)) {\n // Append to existing class\n return html.replace(\n htmlTagPattern,\n (_match: string, attrs: string) => {\n const newAttrs = attrs.replace(\n /class\\s*=\\s*[\"']([^\"']*)[\"']/i,\n (_fullMatch: string, existingClasses: string) => `class=\"${existingClasses} ${classes}\"`\n );\n return `<html${newAttrs}${dataAttrString}>`;\n }\n );\n } else {\n // Add class attribute\n return html.replace(htmlTagPattern, `<html${attributes} class=\"${classes}\"${dataAttrString}>`);\n }\n }\n\n /**\n * Escape a string for use in HTML attributes\n */\n private escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n /**\n * Inject CSS Before loader at start of <head>\n */\n private injectCssBefore(html: string): string {\n const options = buildCssBeforeOptions(this.config);\n const loader = generateCssBeforeLoader(options);\n\n // Insert right after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject JS Before loader at start of <head> (after CSS Before if present)\n */\n private injectJsBefore(html: string): string {\n const options = buildJsBeforeOptions(this.config, this.metadata);\n const loader = generateJsBeforeLoader(options);\n\n // Look for the CSS Before marker to insert after it\n const cssBeforeMarker = '<!-- === PATCH-ADAMS: CSS BEFORE';\n if (html.includes(cssBeforeMarker)) {\n // Find end of CSS Before block and insert after\n const cssBeforeEndPattern = /<\\/script>\\s*(?=\\n|<)/;\n const cssBeforeStart = html.indexOf(cssBeforeMarker);\n const afterCssBefore = html.substring(cssBeforeStart);\n const endMatch = afterCssBefore.match(cssBeforeEndPattern);\n\n if (endMatch && endMatch.index !== undefined) {\n const insertPos = cssBeforeStart + endMatch.index + endMatch[0].length;\n return html.slice(0, insertPos) + '\\n' + loader + html.slice(insertPos);\n }\n }\n\n // Fallback: insert after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject CSS After loader at end of <head>\n */\n private injectCssAfter(html: string): string {\n const options = buildCssAfterOptions(this.config);\n const loader = generateCssAfterLoader(options);\n\n // Insert before </head>\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject JS After loader at end of <body> (after __loadEntry)\n */\n private injectJsAfter(html: string): string {\n const options = buildJsAfterOptions(this.config);\n const loader = generateJsAfterLoader(options);\n\n // Try to find __loadEntry() call and insert after it\n const loadEntryPattern = /(<script>__loadEntry\\(\\)<\\/script>)/i;\n if (loadEntryPattern.test(html)) {\n return html.replace(loadEntryPattern, `$1\\n${loader}`);\n }\n\n // Fallback: insert before </body>\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport type { GeneratedPluginAssets } from '../plugins/types.js';\nimport {\n generateCssBeforeLoader,\n buildCssBeforeOptions,\n generateCssAfterLoader,\n buildCssAfterOptions,\n generateJsBeforeLoader,\n buildJsBeforeOptions,\n generateJsAfterLoader,\n buildJsAfterOptions,\n} from '../templates/index.js';\n\n/**\n * HTML Injector for patching Articulate Storyline course HTML files\n *\n * Storyline structure:\n * - Main entry: story.html (standalone) or index_lms.html (LMS)\n * - Player scripts: html5/lib/scripts/bootstrapper.min.js (loaded at end of body)\n * - Course data: html5/data/js/*.js\n * - CSS: html5/data/css/output.min.css\n *\n * Injection points:\n * 1. CSS Before - Start of <head> (blocking)\n * 2. JS Before - Start of <head>, after CSS Before (blocking)\n * 3. CSS After - End of <head>, before </head>\n * 4. JS After - End of <body>, before </body> (after content, before bootstrapper runs)\n * 5. Plugin Assets - Inline CSS/JS injected directly (always executes)\n *\n * Also adds classes and data attributes to <html> tag\n */\nexport class StorylineHtmlInjector {\n private config: PatchAdamsConfig;\n private metadata: CourseMetadata | null = null;\n private pluginAssets: GeneratedPluginAssets | null = null;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Set plugin-generated assets to inject inline into the HTML.\n * These are injected directly (not via external files) so they always execute.\n */\n setPluginAssets(assets: GeneratedPluginAssets): void {\n this.pluginAssets = assets;\n }\n\n /**\n * Set the course metadata to be injected into the HTML\n */\n setMetadata(metadata: CourseMetadata): void {\n this.metadata = metadata;\n }\n\n /**\n * Inject all loaders into HTML\n */\n inject(html: string): string {\n let result = html;\n\n // Step 1: Add classes and data attributes to <html> tag\n result = this.addHtmlAttributes(result);\n\n // Step 2: Inject CSS Before at start of <head>\n if (this.config.cssBefore.enabled) {\n result = this.injectCssBefore(result);\n }\n\n // Step 3: Inject JS Before at start of <head> (after CSS Before)\n if (this.config.jsBefore.enabled) {\n result = this.injectJsBefore(result);\n }\n\n // Step 4: Inject CSS After at end of <head>\n if (this.config.cssAfter.enabled) {\n result = this.injectCssAfter(result);\n }\n\n // Step 5: Inject JS After at end of <body>\n if (this.config.jsAfter.enabled) {\n result = this.injectJsAfter(result);\n }\n\n // Step 6: Inject plugin assets inline (always executes, independent of remote/local loading)\n if (this.pluginAssets) {\n result = this.injectPluginAssets(result);\n }\n\n return result;\n }\n\n /**\n * Inject plugin assets inline into the HTML.\n * Plugin CSS goes at end of <head>, plugin JS goes at end of <body>.\n */\n private injectPluginAssets(html: string): string {\n let result = html;\n\n // Inject plugin CSS at end of <head>\n const pluginCss = [\n this.pluginAssets?.cssBefore || '',\n this.pluginAssets?.cssAfter || '',\n ]\n .filter(Boolean)\n .join('\\n');\n\n if (pluginCss) {\n const cssBlock = `<!-- === PATCH-ADAMS: PLUGIN CSS (inline) === -->\n<style data-pa=\"plugin-css\">\n${pluginCss}\n</style>`;\n result = result.replace(/<\\/head>/i, `${cssBlock}\\n</head>`);\n console.log('[StorylineHtmlInjector] Injected plugin CSS inline');\n }\n\n // Inject plugin JS at end of <body>\n const pluginJs = [\n this.pluginAssets?.jsBefore || '',\n this.pluginAssets?.jsAfter || '',\n ]\n .filter(Boolean)\n .join('\\n');\n\n if (pluginJs) {\n const jsBlock = `<!-- === PATCH-ADAMS: PLUGIN JS (inline) === -->\n<script data-pa=\"plugin-js\">\n${pluginJs}\n</script>`;\n result = result.replace(/<\\/body>/i, `${jsBlock}\\n</body>`);\n console.log('[StorylineHtmlInjector] Injected plugin JS inline');\n }\n\n return result;\n }\n\n /**\n * Add pa-patched and pa-loading classes to <html> tag\n * Also adds data attributes for course metadata\n */\n private addHtmlAttributes(html: string): string {\n const { htmlClass, loadingClass } = this.config;\n const classes = `${htmlClass} ${loadingClass}`;\n\n // Build data attributes from metadata\n const dataAttrs: string[] = [];\n if (this.metadata) {\n dataAttrs.push(`data-pa-course-id=\"${this.escapeAttr(this.metadata.courseId)}\"`);\n dataAttrs.push(`data-pa-format=\"${this.metadata.format}\"`);\n dataAttrs.push('data-pa-tool=\"storyline\"');\n if (this.metadata.title) {\n dataAttrs.push(`data-pa-title=\"${this.escapeAttr(this.metadata.title)}\"`);\n }\n }\n const dataAttrString = dataAttrs.length > 0 ? ' ' + dataAttrs.join(' ') : '';\n\n // Match <html> tag with or without existing class attribute\n const htmlTagPattern = /<html([^>]*)>/i;\n const match = html.match(htmlTagPattern);\n\n if (!match) {\n return html;\n }\n\n const attributes = match[1];\n\n // Check if class attribute exists\n if (/class\\s*=\\s*[\"'][^\"']*[\"']/i.test(attributes)) {\n // Append to existing class\n return html.replace(htmlTagPattern, (_match: string, attrs: string) => {\n const newAttrs = attrs.replace(\n /class\\s*=\\s*[\"']([^\"']*)[\"']/i,\n (_fullMatch: string, existingClasses: string) =>\n `class=\"${existingClasses} ${classes}\"`\n );\n return `<html${newAttrs}${dataAttrString}>`;\n });\n } else {\n // Add class attribute\n return html.replace(\n htmlTagPattern,\n `<html${attributes} class=\"${classes}\"${dataAttrString}>`\n );\n }\n }\n\n /**\n * Escape a string for use in HTML attributes\n */\n private escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n /**\n * Inject CSS Before loader at start of <head>\n */\n private injectCssBefore(html: string): string {\n const options = buildCssBeforeOptions(this.config);\n const loader = generateCssBeforeLoader(options);\n\n // Insert right after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject JS Before loader at start of <head> (after CSS Before if present)\n */\n private injectJsBefore(html: string): string {\n const options = buildJsBeforeOptions(this.config, this.metadata);\n const loader = generateJsBeforeLoader(options);\n\n // Look for the CSS Before marker to insert after it\n const cssBeforeMarker = '<!-- === PATCH-ADAMS: CSS BEFORE';\n if (html.includes(cssBeforeMarker)) {\n // Find end of CSS Before block and insert after\n const cssBeforeEndPattern = /<\\/script>\\s*(?=\\n|<)/;\n const cssBeforeStart = html.indexOf(cssBeforeMarker);\n const afterCssBefore = html.substring(cssBeforeStart);\n const endMatch = afterCssBefore.match(cssBeforeEndPattern);\n\n if (endMatch && endMatch.index !== undefined) {\n const insertPos = cssBeforeStart + endMatch.index + endMatch[0].length;\n return html.slice(0, insertPos) + '\\n' + loader + html.slice(insertPos);\n }\n }\n\n // Fallback: insert after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject CSS After loader at end of <head>\n */\n private injectCssAfter(html: string): string {\n const options = buildCssAfterOptions(this.config);\n const loader = generateCssAfterLoader(options);\n\n // Insert before </head>\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject JS After loader at end of <body>\n *\n * Storyline note: The bootstrapper.min.js is loaded via <script src=\"...\">\n * AFTER </body> tag (unusual but that's how Storyline works).\n * We inject before </body> so our code runs before the bootstrapper.\n */\n private injectJsAfter(html: string): string {\n const options = buildJsAfterOptions(this.config);\n const loader = generateJsAfterLoader(options);\n\n // Insert before </body>\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n}\n","import type AdmZip from 'adm-zip';\nimport type { PatchAdamsConfig } from '../config/schema.js';\nimport type { PackageFormat } from '../detectors/format-detector.js';\n\nexport interface ManifestPaths {\n cssBefore?: string;\n cssAfter?: string;\n jsBefore?: string;\n jsAfter?: string;\n}\n\n/**\n * Update manifest files to include injected assets\n * This ensures SCORM compliance for strict LMS systems\n */\nexport class ManifestUpdater {\n private config: PatchAdamsConfig;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Update manifest files based on package format\n */\n update(zip: AdmZip, format: PackageFormat, paths: ManifestPaths): string[] {\n const modified: string[] = [];\n\n switch (format) {\n case 'scorm12':\n case 'scorm2004-3':\n case 'scorm2004-4':\n modified.push(...this.updateImsManifest(zip, paths));\n break;\n\n case 'cmi5':\n // cmi5 packages also typically have an imsmanifest.xml\n modified.push(...this.updateImsManifest(zip, paths));\n break;\n\n case 'xapi':\n // xAPI/tincan.xml doesn't have file manifests\n // Files are loaded directly by HTML\n break;\n\n case 'aicc':\n // AICC may have imsmanifest.xml\n const aiccManifest = zip.getEntry('imsmanifest.xml');\n if (aiccManifest) {\n modified.push(...this.updateImsManifest(zip, paths));\n }\n break;\n }\n\n return modified;\n }\n\n /**\n * Update imsmanifest.xml to include new file references\n * Uses string manipulation to preserve original XML formatting exactly\n */\n private updateImsManifest(zip: AdmZip, paths: ManifestPaths): string[] {\n const entry = zip.getEntry('imsmanifest.xml');\n if (!entry) {\n return [];\n }\n\n try {\n const xmlContent = entry.getData().toString('utf-8');\n\n // Build file entries to add\n const filesToAdd = [\n paths.cssBefore,\n paths.cssAfter,\n paths.jsBefore,\n paths.jsAfter,\n ].filter(Boolean) as string[];\n\n if (filesToAdd.length === 0) {\n return [];\n }\n\n // Generate new file entry XML strings\n const newFileEntries = filesToAdd\n .map((filePath) => ` <file href=\"${filePath}\" />`)\n .join('\\n');\n\n // Find the closing </resource> tag and insert before it\n // This preserves all original XML formatting, comments, etc.\n const resourceClosePattern = /(\\s*)<\\/resource>/i;\n const match = xmlContent.match(resourceClosePattern);\n\n if (!match) {\n console.warn('[Patch-Adams] Could not find </resource> tag in imsmanifest.xml');\n return [];\n }\n\n const updatedXml = xmlContent.replace(\n resourceClosePattern,\n `\\n${newFileEntries}\\n$1</resource>`\n );\n\n zip.updateFile('imsmanifest.xml', Buffer.from(updatedXml, 'utf-8'));\n\n return ['imsmanifest.xml'];\n } catch (error) {\n console.error('[Patch-Adams] Failed to update imsmanifest.xml:', error);\n return [];\n }\n }\n\n /**\n * Get the file paths that will be added to the package\n */\n getFilePaths(): ManifestPaths {\n const paths: ManifestPaths = {};\n\n if (this.config.cssBefore.enabled) {\n paths.cssBefore = `scormcontent/${this.config.localFolders.css}/${this.config.cssBefore.filename}`;\n }\n\n if (this.config.cssAfter.enabled) {\n paths.cssAfter = `scormcontent/${this.config.localFolders.css}/${this.config.cssAfter.filename}`;\n }\n\n if (this.config.jsBefore.enabled) {\n paths.jsBefore = `scormcontent/${this.config.localFolders.js}/${this.config.jsBefore.filename}`;\n }\n\n if (this.config.jsAfter.enabled) {\n paths.jsAfter = `scormcontent/${this.config.localFolders.js}/${this.config.jsAfter.filename}`;\n }\n\n return paths;\n }\n}\n","import type AdmZip from 'adm-zip';\n\n/**\n * Supported package formats\n */\nexport type PackageFormat =\n | 'scorm12'\n | 'scorm2004-3'\n | 'scorm2004-4'\n | 'cmi5'\n | 'xapi'\n | 'aicc'\n | 'unknown';\n\n/**\n * Detect the package format by examining manifest files\n */\nexport class FormatDetector {\n /**\n * Detect the package format from a ZIP file\n */\n detect(zip: AdmZip): PackageFormat {\n // Check for cmi5.xml first (most specific)\n if (zip.getEntry('cmi5.xml')) {\n return 'cmi5';\n }\n\n // Check for xAPI (tincan.xml)\n if (zip.getEntry('tincan.xml')) {\n return 'xapi';\n }\n\n // Check for SCORM via imsmanifest.xml\n const manifest = zip.getEntry('imsmanifest.xml');\n if (manifest) {\n const content = manifest.getData().toString('utf-8');\n return this.detectScormVersion(content);\n }\n\n // Check for AICC-specific files\n if (this.hasAiccFiles(zip)) {\n return 'aicc';\n }\n\n return 'unknown';\n }\n\n /**\n * Detect SCORM version from manifest content\n */\n private detectScormVersion(content: string): PackageFormat {\n // Check for SCORM 2004 4th Edition\n if (content.includes('4th Edition') || content.includes('CAM 1.4')) {\n return 'scorm2004-4';\n }\n\n // Check for SCORM 2004 3rd Edition\n if (\n content.includes('3rd Edition') ||\n content.includes('2004 3rd') ||\n content.includes('adlcp_v1p3')\n ) {\n return 'scorm2004-3';\n }\n\n // Check for SCORM 2004 (generic)\n if (content.includes('2004') && content.includes('adlseq')) {\n return 'scorm2004-3'; // Default to 3rd edition for generic 2004\n }\n\n // Check for SCORM 1.2\n if (\n content.includes('1.2') ||\n content.includes('adlcp_rootv1p2') ||\n content.includes('imscp_rootv1p1p2')\n ) {\n return 'scorm12';\n }\n\n // Check for AICC markers in manifest\n if (content.toLowerCase().includes('aicc')) {\n return 'aicc';\n }\n\n // Default to SCORM 1.2 if we have a manifest but can't determine version\n return 'scorm12';\n }\n\n /**\n * Check for AICC-specific files\n */\n private hasAiccFiles(zip: AdmZip): boolean {\n const aiccExtensions = ['.crs', '.au', '.des', '.cst'];\n const entries = zip.getEntries();\n\n for (const entry of entries) {\n const name = entry.entryName.toLowerCase();\n if (aiccExtensions.some((ext) => name.endsWith(ext))) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Get a human-readable format name\n */\n getFormatDisplayName(format: PackageFormat): string {\n const names: Record<PackageFormat, string> = {\n scorm12: 'SCORM 1.2',\n 'scorm2004-3': 'SCORM 2004 3rd Edition',\n 'scorm2004-4': 'SCORM 2004 4th Edition',\n cmi5: 'cmi5',\n xapi: 'xAPI (Tin Can)',\n aicc: 'AICC',\n unknown: 'Unknown',\n };\n\n return names[format];\n }\n}\n","import type AdmZip from 'adm-zip';\n\n/**\n * Supported e-learning authoring tools\n */\nexport type AuthoringTool =\n | 'rise'\n | 'storyline'\n | 'captivate'\n | 'lectora'\n | 'xyleme'\n | 'unknown';\n\n/**\n * Information about the detected authoring tool\n */\nexport interface AuthoringToolInfo {\n tool: AuthoringTool;\n displayName: string;\n /** Path to the main HTML file to patch */\n mainHtmlPath: string;\n /** Additional HTML files that should also be patched (e.g., index_lms.html for Storyline) */\n additionalHtmlPaths?: string[];\n /** Version of the authoring tool if detectable */\n version?: string;\n}\n\n/**\n * Detect the authoring tool used to create a course package.\n * This determines which HTML injection strategy to use.\n */\nexport class AuthoringToolDetector {\n /**\n * Detect the authoring tool from a ZIP file\n */\n detect(zip: AdmZip): AuthoringToolInfo {\n // Check for Rise first (most common in our use case)\n const riseResult = this.detectRise(zip);\n if (riseResult) return riseResult;\n\n // Check for Storyline\n const storylineResult = this.detectStoryline(zip);\n if (storylineResult) return storylineResult;\n\n // Check for Adobe Captivate\n const captivateResult = this.detectCaptivate(zip);\n if (captivateResult) return captivateResult;\n\n // Check for Lectora\n const lectoraResult = this.detectLectora(zip);\n if (lectoraResult) return lectoraResult;\n\n // Check for Xyleme\n const xylemeResult = this.detectXyleme(zip);\n if (xylemeResult) return xylemeResult;\n\n // Unknown - try to find any index.html\n return this.detectUnknown(zip);\n }\n\n /**\n * Detect Articulate Rise packages\n * Rise packages have: scormcontent/index.html with __loadEntry() call\n */\n private detectRise(zip: AdmZip): AuthoringToolInfo | null {\n const indexEntry = zip.getEntry('scormcontent/index.html');\n if (!indexEntry) return null;\n\n const content = indexEntry.getData().toString('utf-8');\n\n // Rise has a distinctive __loadEntry() call\n if (content.includes('__loadEntry()')) {\n // Try to extract version from comments\n const versionMatch = content.match(/Rise\\s+v?(\\d+\\.\\d+(?:\\.\\d+)?)/i);\n return {\n tool: 'rise',\n displayName: 'Articulate Rise',\n mainHtmlPath: 'scormcontent/index.html',\n version: versionMatch?.[1],\n };\n }\n\n // Also check for Rise-specific lib structure\n if (zip.getEntry('scormcontent/lib/rise/')) {\n return {\n tool: 'rise',\n displayName: 'Articulate Rise',\n mainHtmlPath: 'scormcontent/index.html',\n };\n }\n\n return null;\n }\n\n /**\n * Detect Articulate Storyline packages\n * Storyline packages have: story.html with window.globals.publishSource = 'storyline'\n * or html5/lib/scripts/bootstrapper.min.js\n *\n * IMPORTANT: Storyline has TWO entry points that need patching:\n * - story.html: for standalone viewing\n * - index_lms.html: for LMS launch (what SCORM/xAPI uses)\n */\n private detectStoryline(zip: AdmZip): AuthoringToolInfo | null {\n const storyEntry = zip.getEntry('story.html');\n const lmsEntry = zip.getEntry('index_lms.html');\n\n // Build list of HTML files to patch\n const htmlPaths: string[] = [];\n if (storyEntry) htmlPaths.push('story.html');\n if (lmsEntry) htmlPaths.push('index_lms.html');\n\n // Check for story.html (primary entry point for standalone)\n if (storyEntry) {\n const content = storyEntry.getData().toString('utf-8');\n\n // Storyline marker in window.globals\n if (\n content.includes(\"publishSource: 'storyline'\") ||\n content.includes('publishSource:\"storyline\"')\n ) {\n const versionMatch = content.match(/playerVersion:\\s*['\"](\\d+\\.\\d+\\.\\d+\\.\\d+)['\"]/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n\n // Also check HTML comment for Storyline\n if (content.includes('Created using Storyline')) {\n const versionMatch = content.match(/version:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n }\n\n // Check for index_lms.html (LMS entry point) as fallback detection\n if (lmsEntry) {\n const content = lmsEntry.getData().toString('utf-8');\n if (\n content.includes(\"publishSource: 'storyline'\") ||\n content.includes('Created using Storyline')\n ) {\n const versionMatch = content.match(/version:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n }\n\n // Check for Storyline's distinctive folder structure\n if (zip.getEntry('html5/lib/scripts/bootstrapper.min.js')) {\n // Found Storyline structure, determine main HTML\n if (htmlPaths.length > 0) {\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Adobe Captivate packages\n * Captivate packages typically have: project.txt or Captivate.js\n */\n private detectCaptivate(zip: AdmZip): AuthoringToolInfo | null {\n // Check for Captivate markers\n const entries = zip.getEntries();\n for (const entry of entries) {\n const name = entry.entryName.toLowerCase();\n if (name.includes('captivate') && name.endsWith('.js')) {\n // Find main HTML\n const indexHtml = zip.getEntry('index.html');\n return {\n tool: 'captivate',\n displayName: 'Adobe Captivate',\n mainHtmlPath: indexHtml ? 'index.html' : 'index.html',\n };\n }\n }\n\n // Check index.html for Captivate markers\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n if (\n content.includes('Adobe Captivate') ||\n content.includes('cp.') ||\n content.includes('cpAPIInterface')\n ) {\n return {\n tool: 'captivate',\n displayName: 'Adobe Captivate',\n mainHtmlPath: 'index.html',\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Lectora packages\n * Lectora packages have distinctive trivantis markers\n */\n private detectLectora(zip: AdmZip): AuthoringToolInfo | null {\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n if (\n content.includes('Trivantis') ||\n content.includes('Lectora') ||\n content.includes('trivDojo')\n ) {\n return {\n tool: 'lectora',\n displayName: 'Lectora',\n mainHtmlPath: 'index.html',\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Xyleme/Bravais packages (thin pack or full)\n * Xyleme packages have: window.Xyleme namespace, cds-import.xml, or manifest.xml\n */\n private detectXyleme(zip: AdmZip): AuthoringToolInfo | null {\n // Check for Xyleme-specific files\n const hasCdsImport = zip.getEntry('cds-import.xml') !== null;\n const hasXylemeManifest = zip.getEntry('manifest.xml') !== null;\n\n // Check index.html for Xyleme markers\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n\n // Xyleme has distinctive window.Xyleme namespace\n if (\n content.includes('window.Xyleme') ||\n content.includes('Xyleme.environment') ||\n hasCdsImport ||\n hasXylemeManifest\n ) {\n // Try to extract version\n const versionMatch = content.match(/XPE_PUBLIC_VERSION\\s*=\\s*['\"]([^'\"]+)['\"]/);\n\n // Collect all HTML pages that need patching (Xyleme has many page HTMLs)\n const additionalHtmlPaths: string[] = [];\n const entries = zip.getEntries();\n for (const entry of entries) {\n // Xyleme pages are GUIDs like: 9ee5c376-a354-45ca-9565-6472233fc13e.html\n if (\n entry.entryName.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\\.html$/i)\n ) {\n additionalHtmlPaths.push(entry.entryName);\n }\n }\n\n return {\n tool: 'xyleme',\n displayName: 'Xyleme/Bravais',\n mainHtmlPath: 'index.html',\n additionalHtmlPaths: additionalHtmlPaths.length > 0 ? additionalHtmlPaths : undefined,\n version: versionMatch?.[1],\n };\n }\n }\n\n return null;\n }\n\n /**\n * Fallback detection for unknown authoring tools\n * Try to find any suitable HTML file to patch\n */\n private detectUnknown(zip: AdmZip): AuthoringToolInfo {\n // Priority list of HTML files to look for\n const htmlFiles = [\n 'index.html',\n 'index_lms.html',\n 'story.html',\n 'launch.html',\n 'player.html',\n 'scormcontent/index.html',\n ];\n\n for (const htmlFile of htmlFiles) {\n if (zip.getEntry(htmlFile)) {\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: htmlFile,\n };\n }\n }\n\n // Last resort: find any .html file at root or one level deep\n const entries = zip.getEntries();\n for (const entry of entries) {\n if (\n entry.entryName.endsWith('.html') &&\n !entry.entryName.includes('/') // root level\n ) {\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: entry.entryName,\n };\n }\n }\n\n // Absolutely nothing found\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: 'index.html', // Will fail later if doesn't exist\n };\n }\n\n /**\n * Get a human-readable tool name\n */\n getToolDisplayName(tool: AuthoringTool): string {\n const names: Record<AuthoringTool, string> = {\n rise: 'Articulate Rise',\n storyline: 'Articulate Storyline',\n captivate: 'Adobe Captivate',\n lectora: 'Lectora',\n xyleme: 'Xyleme/Bravais',\n unknown: 'Unknown',\n };\n return names[tool];\n }\n}\n","import type AdmZip from 'adm-zip';\n\n/**\n * Comprehensive course metadata extracted from manifests\n */\nexport interface CourseMetadata {\n /** The Rise course ID (from manifest identifier) */\n courseId: string;\n /** Source of the course ID */\n courseIdSource: 'manifest' | 'tincan' | 'cmi5' | 'fallback';\n /** Package format */\n format: 'scorm12' | 'scorm2004-3' | 'scorm2004-4' | 'cmi5' | 'xapi' | 'aicc' | 'unknown';\n /** Course title */\n title: string | null;\n /** Course description */\n description: string | null;\n /** Organization/item identifier within the manifest */\n organizationId: string | null;\n /** Launch URL/entry point */\n launchUrl: string | null;\n /** Mastery score (if specified) */\n masteryScore: number | null;\n /** Duration/time limit (if specified) */\n duration: string | null;\n /** Language code */\n language: string | null;\n /** Version info from manifest */\n version: string | null;\n /** Timestamp when this metadata was extracted */\n extractedAt: string;\n /** Unique ID generated for this specific patch operation */\n patchId?: string;\n}\n\n/**\n * Extract comprehensive course metadata from a course package\n */\nexport function extractCourseMetadata(zip: AdmZip, format: string): CourseMetadata {\n const metadata: CourseMetadata = {\n courseId: 'unknown',\n courseIdSource: 'fallback',\n format: format as CourseMetadata['format'],\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n\n // Try to extract from imsmanifest.xml (SCORM)\n const manifest = zip.getEntry('imsmanifest.xml');\n if (manifest) {\n extractFromImsManifest(manifest.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract from tincan.xml (xAPI)\n const tincan = zip.getEntry('tincan.xml');\n if (tincan) {\n extractFromTincan(tincan.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract from cmi5.xml\n const cmi5 = zip.getEntry('cmi5.xml');\n if (cmi5) {\n extractFromCmi5(cmi5.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract title from index.html as fallback\n if (!metadata.title) {\n const indexHtml = zip.getEntry('scormcontent/index.html');\n if (indexHtml) {\n const content = indexHtml.getData().toString('utf-8');\n const titleMatch = content.match(/<title>([^<]+)<\\/title>/i);\n if (titleMatch) {\n metadata.title = titleMatch[1].trim();\n }\n }\n }\n\n return metadata;\n}\n\n/**\n * Extract metadata from imsmanifest.xml (SCORM 1.2 and 2004)\n */\nfunction extractFromImsManifest(content: string, metadata: CourseMetadata): void {\n // Course ID from manifest identifier\n const identifierMatch = content.match(/<manifest[^>]+identifier\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (identifierMatch) {\n metadata.courseId = identifierMatch[1];\n metadata.courseIdSource = 'manifest';\n }\n\n // Version from manifest version attribute\n const versionMatch = content.match(/<manifest[^>]+version\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (versionMatch) {\n metadata.version = versionMatch[1];\n }\n\n // Title - try multiple locations\n // 1. From <title> within <organization>\n const orgTitleMatch = content.match(/<organization[^>]*>[\\s\\S]*?<title>([^<]+)<\\/title>/i);\n if (orgTitleMatch) {\n metadata.title = orgTitleMatch[1].trim();\n }\n\n // 2. From <lom:title> or <general><title>\n if (!metadata.title) {\n const lomTitleMatch = content.match(/<(?:lom:)?title>\\s*<(?:lom:)?langstring[^>]*>([^<]+)<\\/(?:lom:)?langstring>/i);\n if (lomTitleMatch) {\n metadata.title = lomTitleMatch[1].trim();\n }\n }\n\n // Description from LOM metadata\n const descMatch = content.match(/<(?:lom:)?description>\\s*<(?:lom:)?langstring[^>]*>([^<]+)<\\/(?:lom:)?langstring>/i);\n if (descMatch) {\n metadata.description = descMatch[1].trim();\n }\n\n // Organization identifier\n const orgIdMatch = content.match(/<organization[^>]+identifier\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (orgIdMatch) {\n metadata.organizationId = orgIdMatch[1];\n }\n\n // Launch URL from <resource> href\n const resourceMatch = content.match(/<resource[^>]+href\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (resourceMatch) {\n metadata.launchUrl = resourceMatch[1];\n }\n\n // Mastery score (SCORM 1.2 style)\n const masteryMatch = content.match(/<adlcp:masteryscore>([^<]+)<\\/adlcp:masteryscore>/i);\n if (masteryMatch) {\n const score = parseFloat(masteryMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score;\n }\n }\n\n // Mastery score (SCORM 2004 style - completion threshold)\n if (metadata.masteryScore === null) {\n const thresholdMatch = content.match(/<imsss:minNormalizedMeasure>([^<]+)<\\/imsss:minNormalizedMeasure>/i);\n if (thresholdMatch) {\n const score = parseFloat(thresholdMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score * 100; // Convert 0-1 to 0-100\n }\n }\n }\n\n // Duration/time limit\n const durationMatch = content.match(/<(?:adlcp:)?timelimitaction>([^<]+)<\\/(?:adlcp:)?timelimitaction>/i);\n if (durationMatch) {\n metadata.duration = durationMatch[1].trim();\n }\n\n // Language from xml:lang or lom:language\n const langMatch = content.match(/xml:lang\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (langMatch) {\n metadata.language = langMatch[1];\n }\n if (!metadata.language) {\n const lomLangMatch = content.match(/<(?:lom:)?language>([^<]+)<\\/(?:lom:)?language>/i);\n if (lomLangMatch) {\n metadata.language = lomLangMatch[1].trim();\n }\n }\n}\n\n/**\n * Extract metadata from tincan.xml (xAPI courses)\n */\nfunction extractFromTincan(content: string, metadata: CourseMetadata): void {\n // Activity ID as course ID\n const idMatch = content.match(/<activity[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (idMatch && metadata.courseIdSource === 'fallback') {\n metadata.courseId = idMatch[1];\n metadata.courseIdSource = 'tincan';\n }\n\n // Activity name/title\n const nameMatch = content.match(/<name[^>]*>([^<]+)<\\/name>/i);\n if (nameMatch && !metadata.title) {\n metadata.title = nameMatch[1].trim();\n }\n\n // Activity description\n const descMatch = content.match(/<description[^>]*>([^<]+)<\\/description>/i);\n if (descMatch && !metadata.description) {\n metadata.description = descMatch[1].trim();\n }\n\n // Launch URL\n const launchMatch = content.match(/<launch[^>]*>([^<]+)<\\/launch>/i);\n if (launchMatch) {\n metadata.launchUrl = launchMatch[1].trim();\n }\n}\n\n/**\n * Extract metadata from cmi5.xml\n */\nfunction extractFromCmi5(content: string, metadata: CourseMetadata): void {\n // Course ID\n const courseIdMatch = content.match(/<course[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (courseIdMatch && metadata.courseIdSource === 'fallback') {\n metadata.courseId = courseIdMatch[1];\n metadata.courseIdSource = 'cmi5';\n }\n\n // Course title\n const titleMatch = content.match(/<title[^>]*>([^<]+)<\\/title>/i);\n if (titleMatch && !metadata.title) {\n metadata.title = titleMatch[1].trim();\n }\n\n // Course description\n const descMatch = content.match(/<description[^>]*>([^<]+)<\\/description>/i);\n if (descMatch && !metadata.description) {\n metadata.description = descMatch[1].trim();\n }\n\n // AU launch URL\n const launchMatch = content.match(/<url[^>]*>([^<]+)<\\/url>/i);\n if (launchMatch) {\n metadata.launchUrl = launchMatch[1].trim();\n }\n\n // Mastery score from moveOn\n const moveOnMatch = content.match(/moveOn\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (moveOnMatch) {\n // cmi5 uses moveOn values like \"Passed\", \"Completed\", \"CompletedAndPassed\"\n // If PassedOrFailed is required, there's likely a mastery score\n if (moveOnMatch[1].toLowerCase().includes('passed')) {\n const masteryMatch = content.match(/masteryScore\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (masteryMatch) {\n const score = parseFloat(masteryMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score;\n }\n }\n }\n }\n}\n","import type {\n PatchAdamsPlugin,\n PluginConfig,\n PluginsConfig,\n GeneratedPluginAssets,\n} from './types.js';\n\n/**\n * Plugin Registry for Patch-Adams\n *\n * Manages registration and asset generation for plugins.\n * Plugins are registered at module load time and invoked during patching\n * based on the configuration.\n */\nexport class PluginRegistry {\n private plugins: Map<string, PatchAdamsPlugin> = new Map();\n\n /**\n * Register a plugin with the registry.\n * Plugins should be registered at module initialization time.\n *\n * @param plugin - The plugin to register\n * @throws Error if a plugin with the same name is already registered\n */\n register(plugin: PatchAdamsPlugin): void {\n if (this.plugins.has(plugin.name)) {\n throw new Error(`Plugin \"${plugin.name}\" is already registered`);\n }\n this.plugins.set(plugin.name, plugin);\n console.log(`[PA-Plugins] Registered: ${plugin.name} v${plugin.version}`);\n }\n\n /**\n * Unregister a plugin by name.\n *\n * @param name - The plugin name to unregister\n * @returns true if the plugin was removed, false if it wasn't registered\n */\n unregister(name: string): boolean {\n const removed = this.plugins.delete(name);\n if (removed) {\n console.log(`[PA-Plugins] Unregistered: ${name}`);\n }\n return removed;\n }\n\n /**\n * Get a registered plugin by name.\n *\n * @param name - The plugin name\n * @returns The plugin or undefined if not found\n */\n getPlugin(name: string): PatchAdamsPlugin | undefined {\n return this.plugins.get(name);\n }\n\n /**\n * Get all registered plugins.\n *\n * @returns Array of all registered plugins\n */\n getAllPlugins(): PatchAdamsPlugin[] {\n return Array.from(this.plugins.values());\n }\n\n /**\n * Get names of all registered plugins.\n *\n * @returns Array of plugin names\n */\n getPluginNames(): string[] {\n return Array.from(this.plugins.keys());\n }\n\n /**\n * Check if a plugin is registered.\n *\n * @param name - The plugin name to check\n * @returns true if registered\n */\n hasPlugin(name: string): boolean {\n return this.plugins.has(name);\n }\n\n /**\n * Generate combined assets from all enabled plugins.\n *\n * @param pluginConfigs - Configuration for each plugin (keyed by plugin name)\n * @returns Combined CSS and JS assets from all enabled plugins\n */\n generateAssets(pluginConfigs: PluginsConfig): GeneratedPluginAssets {\n const result: GeneratedPluginAssets = {\n cssBefore: '',\n cssAfter: '',\n jsBefore: '',\n jsAfter: '',\n };\n\n for (const [pluginName, rawConfig] of Object.entries(pluginConfigs)) {\n // Skip if plugin is not enabled\n if (!rawConfig.enabled) {\n console.log(`[PA-Plugins] Skipping disabled plugin: ${pluginName}`);\n continue;\n }\n\n // Get the plugin\n const plugin = this.plugins.get(pluginName);\n if (!plugin) {\n console.warn(`[PA-Plugins] Plugin \"${pluginName}\" not found in registry, skipping`);\n continue;\n }\n\n try {\n // Validate configuration against plugin schema\n const validatedConfig = plugin.configSchema.parse(rawConfig) as PluginConfig;\n console.log(`[PA-Plugins] Generating assets for: ${pluginName}`);\n\n // Generate CSS\n if (plugin.generateCss) {\n const css = plugin.generateCss(validatedConfig);\n if (css.before) {\n result.cssBefore += `/* === Plugin: ${pluginName} === */\\n${css.before}\\n\\n`;\n }\n if (css.after) {\n result.cssAfter += `/* === Plugin: ${pluginName} === */\\n${css.after}\\n\\n`;\n }\n }\n\n // Generate JS\n if (plugin.generateJs) {\n const js = plugin.generateJs(validatedConfig);\n if (js.before) {\n result.jsBefore += `// === Plugin: ${pluginName} ===\\n${js.before}\\n\\n`;\n }\n if (js.after) {\n result.jsAfter += `// === Plugin: ${pluginName} ===\\n${js.after}\\n\\n`;\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`[PA-Plugins] Error processing plugin \"${pluginName}\": ${message}`);\n // Continue with other plugins\n }\n }\n\n return result;\n }\n\n /**\n * Clear all registered plugins.\n * Useful for testing.\n */\n clear(): void {\n this.plugins.clear();\n console.log('[PA-Plugins] Registry cleared');\n }\n}\n\n/**\n * Global plugin registry instance.\n * Use this singleton to register and access plugins.\n */\nexport const pluginRegistry = new PluginRegistry();\n","import AdmZip from 'adm-zip';\nimport archiver from 'archiver';\nimport { PassThrough } from 'stream';\nimport type { PatchAdamsConfig } from '../config/schema.js';\nimport { mergeWithDefaults } from '../config/loader.js';\nimport { HtmlInjector } from './html-injector.js';\nimport { StorylineHtmlInjector } from './storyline-html-injector.js';\nimport { ManifestUpdater } from './manifest-updater.js';\nimport { FormatDetector, type PackageFormat } from '../detectors/format-detector.js';\nimport {\n AuthoringToolDetector,\n type AuthoringTool,\n type AuthoringToolInfo,\n} from '../detectors/authoring-tool-detector.js';\nimport { extractCourseMetadata, type CourseMetadata } from '../fingerprint/course-metadata.js';\nimport { pluginRegistry, type GeneratedPluginAssets } from '../plugins/index.js';\n\n/**\n * Result of a patch operation\n */\nexport interface PatchResult {\n success: boolean;\n format: PackageFormat;\n formatDisplayName: string;\n /** Detected authoring tool */\n authoringTool: AuthoringTool;\n authoringToolDisplayName: string;\n /** Full course metadata */\n metadata: CourseMetadata;\n /** @deprecated Use metadata.courseId instead */\n courseId: string;\n /** @deprecated Use metadata.courseIdSource instead */\n courseIdSource: 'manifest' | 'tincan' | 'cmi5' | 'fallback';\n filesModified: string[];\n filesAdded: string[];\n errors: string[];\n warnings: string[];\n}\n\n/**\n * Options for patching\n */\nexport interface PatchOptions {\n /** Content for CSS before (local fallback) - overrides fetched content */\n cssBeforeContent?: string;\n /** Content for CSS after (local fallback) - overrides fetched content */\n cssAfterContent?: string;\n /** Content for JS before (local fallback) - overrides fetched content */\n jsBeforeContent?: string;\n /** Content for JS after (local fallback) - overrides fetched content */\n jsAfterContent?: string;\n /** Skip fetching remote files even if fetchFallbacks is enabled in config */\n skipFetch?: boolean;\n}\n\n/**\n * Fetched fallback content from remote server\n */\ninterface FetchedFallbacks {\n cssBefore?: string;\n cssAfter?: string;\n jsBefore?: string;\n jsAfter?: string;\n}\n\n/**\n * Default fallback file contents\n */\nconst DEFAULT_CSS_BEFORE = `/* Patch-Adams: CSS Before (blocking)\n * This file loads at the start of <head> and blocks rendering.\n * Use it to hide content and prevent flash of unstyled content.\n *\n * Example:\n * html.pa-patched.pa-loading body {\n * visibility: hidden !important;\n * }\n */\n`;\n\nconst DEFAULT_CSS_AFTER = `/* Patch-Adams: CSS After (async)\n * This file loads at the end of <head> with remote fallback.\n * Use it for style overrides that take precedence over Rise styles.\n *\n * Example:\n * html.pa-patched .blocks-text {\n * font-family: 'Your Font', sans-serif !important;\n * }\n */\n`;\n\nconst DEFAULT_JS_BEFORE = `// Patch-Adams: JS Before (blocking)\n// This file loads at the start of <head> and blocks rendering.\n// Use it for setup, API interception, and preparing globals.\n//\n// Example:\n// window.PatchAdams.customConfig = {\n// theme: 'dark',\n// analytics: true\n// };\n\nconsole.log('[Patch-Adams] JS Before loaded');\n`;\n\nconst DEFAULT_JS_AFTER = `// Patch-Adams: JS After (async)\n// This file loads at the end of <body> with remote fallback.\n// Use it for DOM manipulation after Rise has initialized.\n//\n// IMPORTANT: This script should NOT remove the loading class.\n// The loader script handles that automatically after this script loads.\n//\n// Example:\n// document.querySelectorAll('.blocks-text').forEach(function(el) {\n// // Your modifications here\n// });\n\nconsole.log('[Patch-Adams] JS After loaded');\n`;\n\n/**\n * Main Patcher class for patching e-learning course packages\n * Supports Rise, Storyline, and other authoring tools\n */\nexport class Patcher {\n private config: PatchAdamsConfig;\n private riseHtmlInjector: HtmlInjector;\n private storylineHtmlInjector: StorylineHtmlInjector;\n private manifestUpdater: ManifestUpdater;\n private formatDetector: FormatDetector;\n private authoringToolDetector: AuthoringToolDetector;\n\n constructor(config: Partial<PatchAdamsConfig>) {\n // Merge with defaults so partial configs work correctly\n this.config = mergeWithDefaults(config);\n this.riseHtmlInjector = new HtmlInjector(this.config);\n this.storylineHtmlInjector = new StorylineHtmlInjector(this.config);\n this.manifestUpdater = new ManifestUpdater(this.config);\n this.formatDetector = new FormatDetector();\n this.authoringToolDetector = new AuthoringToolDetector();\n }\n\n /**\n * Fetch a single file from a URL\n * Returns undefined if fetch fails (non-blocking)\n */\n private async fetchFile(url: string): Promise<string | undefined> {\n try {\n console.log(`[Patcher] Fetching: ${url}`);\n const response = await fetch(url);\n if (!response.ok) {\n console.warn(`[Patcher] Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n return undefined;\n }\n const content = await response.text();\n console.log(`[Patcher] Fetched ${url} (${content.length} bytes)`);\n return content;\n } catch (error) {\n console.warn(`[Patcher] Error fetching ${url}:`, error);\n return undefined;\n }\n }\n\n /**\n * Fetch all remote fallback files\n * Non-blocking: if any fetch fails, we use the default content\n */\n private async fetchRemoteFallbacks(): Promise<FetchedFallbacks> {\n const fetched: FetchedFallbacks = {};\n const { remoteDomain, localFolders, cssBefore, cssAfter, jsBefore, jsAfter } = this.config;\n\n const fetchPromises: Promise<void>[] = [];\n\n if (cssBefore.enabled) {\n const url = `${remoteDomain}/${localFolders.css}/${cssBefore.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.cssBefore = content; })\n );\n }\n\n if (cssAfter.enabled) {\n const url = `${remoteDomain}/${localFolders.css}/${cssAfter.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.cssAfter = content; })\n );\n }\n\n if (jsBefore.enabled) {\n const url = `${remoteDomain}/${localFolders.js}/${jsBefore.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.jsBefore = content; })\n );\n }\n\n if (jsAfter.enabled) {\n const url = `${remoteDomain}/${localFolders.js}/${jsAfter.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.jsAfter = content; })\n );\n }\n\n // Fetch all in parallel\n await Promise.all(fetchPromises);\n\n return fetched;\n }\n\n /**\n * Patch a Rise course ZIP file\n * @param inputBuffer - Input ZIP file as Buffer\n * @param options - Additional patching options\n * @returns Patched ZIP file as Buffer and result details\n */\n async patch(\n inputBuffer: Buffer,\n options: PatchOptions = {}\n ): Promise<{ buffer: Buffer; result: PatchResult }> {\n // Create placeholder metadata for initialization\n const placeholderMetadata: CourseMetadata = {\n courseId: '',\n courseIdSource: 'fallback',\n format: 'unknown',\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n\n const result: PatchResult = {\n success: false,\n format: 'unknown',\n formatDisplayName: 'Unknown',\n authoringTool: 'unknown',\n authoringToolDisplayName: 'Unknown',\n metadata: placeholderMetadata,\n courseId: '',\n courseIdSource: 'fallback',\n filesModified: [],\n filesAdded: [],\n errors: [],\n warnings: [],\n };\n\n try {\n // 1. Open ZIP with adm-zip for reading\n const zip = new AdmZip(inputBuffer);\n\n // 2. Detect package format\n result.format = this.formatDetector.detect(zip);\n result.formatDisplayName = this.formatDetector.getFormatDisplayName(result.format);\n\n if (result.format === 'unknown') {\n result.warnings.push('Could not determine package format, proceeding anyway');\n }\n\n // 2b. Detect authoring tool\n const toolInfo = this.authoringToolDetector.detect(zip);\n result.authoringTool = toolInfo.tool;\n result.authoringToolDisplayName = toolInfo.displayName;\n\n console.log(`[Patcher] Detected authoring tool: ${toolInfo.displayName}`);\n console.log(` - Main HTML: ${toolInfo.mainHtmlPath}`);\n if (toolInfo.additionalHtmlPaths?.length) {\n console.log(` - Additional HTML: ${toolInfo.additionalHtmlPaths.join(', ')}`);\n }\n if (toolInfo.version) console.log(` - Version: ${toolInfo.version}`);\n\n // 3. Extract comprehensive course metadata\n const metadata = extractCourseMetadata(zip, result.format);\n result.metadata = metadata;\n result.courseId = metadata.courseId;\n result.courseIdSource = metadata.courseIdSource;\n\n console.log(`[Patcher] Course metadata extracted:`);\n console.log(` - Course ID: ${metadata.courseId} (source: ${metadata.courseIdSource})`);\n console.log(` - Format: ${metadata.format}`);\n if (metadata.title) console.log(` - Title: ${metadata.title}`);\n if (metadata.description) console.log(` - Description: ${metadata.description.substring(0, 50)}...`);\n if (metadata.language) console.log(` - Language: ${metadata.language}`);\n if (metadata.masteryScore !== null) console.log(` - Mastery Score: ${metadata.masteryScore}`);\n\n // 4. Set course metadata on appropriate HTML injector\n const htmlInjector = this.getHtmlInjector(toolInfo.tool);\n htmlInjector.setMetadata(metadata);\n\n // 5. Fetch remote fallback files if enabled\n let fetchedFallbacks: FetchedFallbacks = {};\n if (this.config.fetchFallbacks && !options.skipFetch) {\n console.log('[Patcher] Fetching remote files for local fallbacks...');\n fetchedFallbacks = await this.fetchRemoteFallbacks();\n const fetchedCount = Object.values(fetchedFallbacks).filter(Boolean).length;\n console.log(`[Patcher] Fetched ${fetchedCount} remote files`);\n }\n\n // 5b. Generate plugin assets and pass to HTML injector for inline injection\n const pluginAssets = this.generatePluginAssets();\n const hasPluginAssets =\n pluginAssets.cssBefore || pluginAssets.cssAfter || pluginAssets.jsBefore || pluginAssets.jsAfter;\n if (hasPluginAssets) {\n htmlInjector.setPluginAssets(pluginAssets);\n console.log('[Patcher] Plugin assets will be injected inline into HTML');\n }\n\n // 6. Find and patch HTML files (varies by authoring tool)\n // Some tools like Storyline have multiple entry points (story.html, index_lms.html)\n const htmlPathsToPatch = [toolInfo.mainHtmlPath, ...(toolInfo.additionalHtmlPaths || [])];\n\n for (const htmlPath of htmlPathsToPatch) {\n const htmlEntry = zip.getEntry(htmlPath);\n if (!htmlEntry) {\n if (htmlPath === toolInfo.mainHtmlPath) {\n // Main HTML is required\n throw new Error(\n `Could not find ${htmlPath} in package. Is this a valid ${toolInfo.displayName} export?`\n );\n }\n // Additional HTML files are optional\n console.log(`[Patcher] Optional HTML file not found: ${htmlPath}`);\n continue;\n }\n\n console.log(`[Patcher] Patching HTML file: ${htmlPath}`);\n const originalHtml = htmlEntry.getData().toString('utf-8');\n const patchedHtml = htmlInjector.inject(originalHtml);\n zip.updateFile(htmlPath, Buffer.from(patchedHtml, 'utf-8'));\n result.filesModified.push(htmlPath);\n }\n\n // 7. Add local fallback files (using fetched content or defaults)\n const addedFiles = this.addFallbackFiles(zip, options, fetchedFallbacks, toolInfo);\n result.filesAdded.push(...addedFiles);\n\n // 8. Update manifest files\n if (this.config.updateManifests) {\n // Build manifest paths from the actual files added (not hardcoded paths)\n const manifestPaths = this.buildManifestPaths(addedFiles);\n const modifiedManifests = this.manifestUpdater.update(zip, result.format, manifestPaths);\n result.filesModified.push(...modifiedManifests);\n }\n\n // 9. Write output using archiver for better ZIP compatibility\n // adm-zip's toBuffer() produces ZIPs that some parsers don't handle well\n const outputBuffer = await this.writeZipWithArchiver(zip);\n\n result.success = true;\n return { buffer: outputBuffer, result };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n result.errors.push(message);\n throw error;\n }\n }\n\n /**\n * Write ZIP using archiver for better compatibility\n * adm-zip's toBuffer() produces ZIPs that some parsers don't handle well.\n * archiver produces more standard ZIP output.\n */\n private writeZipWithArchiver(admZip: AdmZip): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const archive = archiver('zip', {\n zlib: { level: 9 }, // Maximum compression\n });\n\n const chunks: Buffer[] = [];\n const passThrough = new PassThrough();\n\n passThrough.on('data', (chunk: Buffer) => chunks.push(chunk));\n passThrough.on('end', () => resolve(Buffer.concat(chunks)));\n passThrough.on('error', reject);\n\n archive.on('error', reject);\n archive.pipe(passThrough);\n\n const entries = admZip.getEntries();\n\n for (const entry of entries) {\n const entryName = entry.entryName;\n const mtime = entry.header.time ? new Date(entry.header.time) : new Date();\n\n if (entry.isDirectory) {\n // Preserve explicit directory entries - some LMS systems require them\n archive.append('', {\n name: entryName.endsWith('/') ? entryName : entryName + '/',\n date: mtime,\n store: true,\n });\n continue;\n }\n\n const entryData = entry.getData();\n\n // Determine if we should store or compress based on original\n const store = entry.header.method === 0;\n\n archive.append(entryData, {\n name: entryName,\n date: mtime,\n store: store,\n });\n }\n\n archive.finalize();\n });\n }\n\n /**\n * Get the appropriate HTML injector for the authoring tool\n */\n private getHtmlInjector(tool: AuthoringTool): HtmlInjector | StorylineHtmlInjector {\n switch (tool) {\n case 'storyline':\n return this.storylineHtmlInjector;\n case 'rise':\n default:\n // Use Rise injector as default (works for most SCORM packages)\n return this.riseHtmlInjector;\n }\n }\n\n /**\n * Get the base folder for fallback files based on authoring tool\n */\n private getFallbackBaseFolder(toolInfo: AuthoringToolInfo): string {\n switch (toolInfo.tool) {\n case 'storyline':\n // Storyline uses html5/ folder structure\n return 'html5';\n case 'rise':\n return 'scormcontent';\n case 'xyleme':\n // Xyleme thin packs have files at root level\n return '';\n default:\n // For unknown tools, try to determine from main HTML path\n const htmlPath = toolInfo.mainHtmlPath;\n if (htmlPath.includes('/')) {\n return htmlPath.split('/')[0];\n }\n // Root level - use empty string (files at root)\n return '';\n }\n }\n\n /**\n * Build manifest paths from the actual file paths that were added\n * This ensures the manifest references the correct paths regardless of authoring tool\n */\n private buildManifestPaths(addedFiles: string[]): import('./manifest-updater.js').ManifestPaths {\n const paths: import('./manifest-updater.js').ManifestPaths = {};\n\n for (const filePath of addedFiles) {\n if (filePath.endsWith(this.config.cssBefore.filename)) {\n paths.cssBefore = filePath;\n } else if (filePath.endsWith(this.config.cssAfter.filename)) {\n paths.cssAfter = filePath;\n } else if (filePath.endsWith(this.config.jsBefore.filename)) {\n paths.jsBefore = filePath;\n } else if (filePath.endsWith(this.config.jsAfter.filename)) {\n paths.jsAfter = filePath;\n }\n }\n\n return paths;\n }\n\n /**\n * Generate assets from all enabled plugins\n */\n private generatePluginAssets(): GeneratedPluginAssets {\n const plugins = this.config.plugins || {};\n const enabledPlugins = Object.entries(plugins).filter(([_, cfg]) => cfg.enabled);\n\n if (enabledPlugins.length === 0) {\n return { cssBefore: '', cssAfter: '', jsBefore: '', jsAfter: '' };\n }\n\n console.log(`[Patcher] Generating assets for ${enabledPlugins.length} enabled plugin(s)`);\n return pluginRegistry.generateAssets(plugins);\n }\n\n /**\n * Add local fallback files to the ZIP\n * Priority: options > fetched > defaults\n * Plugin assets are appended after the main content\n */\n private addFallbackFiles(\n zip: AdmZip,\n options: PatchOptions,\n fetched: FetchedFallbacks = {},\n toolInfo?: AuthoringToolInfo\n ): string[] {\n const added: string[] = [];\n const baseFolder = toolInfo ? this.getFallbackBaseFolder(toolInfo) : 'scormcontent';\n const basePath = baseFolder ? `${baseFolder}/` : '';\n const cssFolder = `${basePath}${this.config.localFolders.css}`;\n const jsFolder = `${basePath}${this.config.localFolders.js}`;\n\n // Generate plugin assets\n const pluginAssets = this.generatePluginAssets();\n const hasPluginCssBefore = pluginAssets.cssBefore.length > 0;\n const hasPluginCssAfter = pluginAssets.cssAfter.length > 0;\n const hasPluginJsBefore = pluginAssets.jsBefore.length > 0;\n const hasPluginJsAfter = pluginAssets.jsAfter.length > 0;\n\n // CSS Before (priority: options > fetched > default) + plugins\n if (this.config.cssBefore.enabled) {\n const path = `${cssFolder}/${this.config.cssBefore.filename}`;\n let content = options.cssBeforeContent ?? fetched.cssBefore ?? DEFAULT_CSS_BEFORE;\n if (hasPluginCssBefore) {\n content += '\\n\\n/* === PLUGIN CSS (Before) === */\\n' + pluginAssets.cssBefore;\n console.log(`[Patcher] Added plugin CSS (before) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.cssBefore) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // CSS After (priority: options > fetched > default) + plugins\n if (this.config.cssAfter.enabled) {\n const path = `${cssFolder}/${this.config.cssAfter.filename}`;\n let content = options.cssAfterContent ?? fetched.cssAfter ?? DEFAULT_CSS_AFTER;\n if (hasPluginCssAfter) {\n content += '\\n\\n/* === PLUGIN CSS (After) === */\\n' + pluginAssets.cssAfter;\n console.log(`[Patcher] Added plugin CSS (after) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.cssAfter) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // JS Before (priority: options > fetched > default) + plugins\n if (this.config.jsBefore.enabled) {\n const path = `${jsFolder}/${this.config.jsBefore.filename}`;\n let content = options.jsBeforeContent ?? fetched.jsBefore ?? DEFAULT_JS_BEFORE;\n if (hasPluginJsBefore) {\n content += '\\n\\n// === PLUGIN JS (Before) ===\\n' + pluginAssets.jsBefore;\n console.log(`[Patcher] Added plugin JS (before) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.jsBefore) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // JS After (priority: options > fetched > default) + plugins\n if (this.config.jsAfter.enabled) {\n const path = `${jsFolder}/${this.config.jsAfter.filename}`;\n let content = options.jsAfterContent ?? fetched.jsAfter ?? DEFAULT_JS_AFTER;\n if (hasPluginJsAfter) {\n content += '\\n\\n// === PLUGIN JS (After) ===\\n' + pluginAssets.jsAfter;\n console.log(`[Patcher] Added plugin JS (after) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.jsAfter) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n return added;\n }\n\n /**\n * Get the current configuration\n */\n getConfig(): PatchAdamsConfig {\n return this.config;\n }\n\n /**\n * Convenience method to patch a buffer and return only the patched buffer.\n * Used by package-uploader integration.\n * @param buffer - Input ZIP file as Buffer\n * @returns Patched ZIP file as Buffer\n */\n async patchBuffer(buffer: Buffer): Promise<Buffer> {\n console.log(`[Patcher] patchBuffer called with ${buffer.length} bytes`);\n const { buffer: patchedBuffer, result } = await this.patch(buffer);\n console.log(`[Patcher] Patch result:`, JSON.stringify(result, null, 2));\n console.log(`[Patcher] Returning ${patchedBuffer.length} bytes`);\n return patchedBuffer;\n }\n}\n\nexport { HtmlInjector } from './html-injector.js';\nexport { StorylineHtmlInjector } from './storyline-html-injector.js';\nexport { ManifestUpdater, type ManifestPaths } from './manifest-updater.js';\n","import { createHash } from 'crypto';\nimport type AdmZip from 'adm-zip';\n\n/**\n * Course fingerprint result\n */\nexport interface CourseFingerprint {\n /** The Rise course ID extracted from the manifest (same as in Rise editor URL) */\n courseId: string;\n /** Source of the fingerprint (manifest, tincan, cmi5, fallback) */\n source: 'manifest' | 'tincan' | 'cmi5' | 'fallback';\n}\n\n/**\n * Extract the Rise course ID from a course package\n *\n * This extracts the course identifier that Rise uses internally.\n * It's the same ID you see in the Rise editor URL when editing a course.\n * This ID is stable across re-exports of the same course.\n *\n * Supports all Rise export formats:\n * - SCORM 1.2 / 2004 (from imsmanifest.xml)\n * - xAPI (from tincan.xml)\n * - cmi5 (from cmi5.xml)\n */\nexport function extractCourseFingerprint(zip: AdmZip): CourseFingerprint {\n // Try imsmanifest.xml first (SCORM 1.2 and 2004)\n const manifestResult = extractFromImsManifest(zip);\n if (manifestResult) {\n return manifestResult;\n }\n\n // Try tincan.xml for xAPI courses\n const tincanResult = extractFromTincan(zip);\n if (tincanResult) {\n return tincanResult;\n }\n\n // Try cmi5.xml\n const cmi5Result = extractFromCmi5(zip);\n if (cmi5Result) {\n return cmi5Result;\n }\n\n // Fallback: generate from index.html content hash\n return extractFallbackFingerprint(zip);\n}\n\n/**\n * Extract course ID from imsmanifest.xml (SCORM 1.2 and 2004)\n */\nfunction extractFromImsManifest(zip: AdmZip): CourseFingerprint | null {\n const entry = zip.getEntry('imsmanifest.xml');\n if (!entry) {\n return null;\n }\n\n const content = entry.getData().toString('utf-8');\n\n // Extract manifest identifier attribute\n // Pattern: <manifest identifier=\"jA6iIYVKCYYlQi-GWD65BK6K1EzvuI7PKs1OB6TL\"\n const identifierMatch = content.match(/<manifest[^>]+identifier\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (!identifierMatch) {\n return null;\n }\n\n return {\n courseId: identifierMatch[1],\n source: 'manifest',\n };\n}\n\n/**\n * Extract course ID from tincan.xml (xAPI courses)\n */\nfunction extractFromTincan(zip: AdmZip): CourseFingerprint | null {\n const entry = zip.getEntry('tincan.xml');\n if (!entry) {\n return null;\n }\n\n const content = entry.getData().toString('utf-8');\n\n // Try to extract activity ID from <activity id=\"...\">\n // Rise xAPI exports use this pattern\n const idMatch = content.match(/<activity[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (!idMatch) {\n return null;\n }\n\n return {\n courseId: idMatch[1],\n source: 'tincan',\n };\n}\n\n/**\n * Extract course ID from cmi5.xml\n */\nfunction extractFromCmi5(zip: AdmZip): CourseFingerprint | null {\n const entry = zip.getEntry('cmi5.xml');\n if (!entry) {\n return null;\n }\n\n const content = entry.getData().toString('utf-8');\n\n // Try to extract course ID from <course id=\"...\">\n const idMatch = content.match(/<course[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (!idMatch) {\n return null;\n }\n\n return {\n courseId: idMatch[1],\n source: 'cmi5',\n };\n}\n\n/**\n * Fallback: generate fingerprint from index.html title or content hash\n */\nfunction extractFallbackFingerprint(zip: AdmZip): CourseFingerprint {\n const entry = zip.getEntry('scormcontent/index.html');\n let courseId = 'unknown-course';\n\n if (entry) {\n const content = entry.getData().toString('utf-8');\n\n // Try to extract title\n const titleMatch = content.match(/<title>([^<]+)<\\/title>/i);\n if (titleMatch) {\n // Create a hash from the title to use as ID\n courseId = createHash('sha256')\n .update(titleMatch[1].trim())\n .digest('hex')\n .substring(0, 40);\n } else {\n // Use content hash as last resort\n courseId = createHash('sha256')\n .update(content)\n .digest('hex')\n .substring(0, 40);\n }\n }\n\n return {\n courseId,\n source: 'fallback',\n };\n}\n","import chalk from 'chalk';\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';\n\n/**\n * Simple logger utility for Patch-Adams\n */\nexport class Logger {\n private level: LogLevel;\n\n constructor(level: LogLevel = 'info') {\n this.level = level;\n }\n\n private shouldLog(level: LogLevel): boolean {\n const levels: LogLevel[] = ['debug', 'info', 'warn', 'error', 'silent'];\n return levels.indexOf(level) >= levels.indexOf(this.level);\n }\n\n debug(...args: unknown[]): void {\n if (this.shouldLog('debug')) {\n console.log(chalk.gray('[DEBUG]'), ...args);\n }\n }\n\n info(...args: unknown[]): void {\n if (this.shouldLog('info')) {\n console.log(chalk.blue('[INFO]'), ...args);\n }\n }\n\n success(...args: unknown[]): void {\n if (this.shouldLog('info')) {\n console.log(chalk.green('[SUCCESS]'), ...args);\n }\n }\n\n warn(...args: unknown[]): void {\n if (this.shouldLog('warn')) {\n console.log(chalk.yellow('[WARN]'), ...args);\n }\n }\n\n error(...args: unknown[]): void {\n if (this.shouldLog('error')) {\n console.error(chalk.red('[ERROR]'), ...args);\n }\n }\n\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/config/schema.ts","../src/config/defaults.ts","../src/config/loader.ts","../src/templates/css-before.ts","../src/templates/css-after.ts","../src/templates/lrs-bridge.ts","../src/templates/js-before.ts","../src/templates/js-after.ts","../src/patcher/html-injector.ts","../src/patcher/storyline-html-injector.ts","../src/patcher/manifest-updater.ts","../src/detectors/format-detector.ts","../src/detectors/authoring-tool-detector.ts","../src/fingerprint/course-metadata.ts","../src/plugins/registry.ts","../src/patcher/index.ts","../src/fingerprint/course-fingerprint.ts","../src/utils/logger.ts"],"names":["z","resolve","existsSync","extname","readFileSync","pathToFileURL","AdmZip","archiver","PassThrough","extractFromImsManifest","extractFromTincan","extractFromCmi5","createHash","chalk"],"mappings":"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A;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAA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","file":"index.cjs","sourcesContent":["import { z } from 'zod';\n\n/**\n * LRS Bridge configuration for xAPI/LRS communication\n * Enables Rise courses to communicate with Bravais LRS like Xyleme native courses\n */\nexport const LrsBridgeConfigSchema = z.object({\n /** Whether LRS bridge is enabled (default: true) */\n enabled: z.boolean().default(true),\n /** Track video/audio plays - play, pause, complete events (default: true) */\n trackMedia: z.boolean().default(true),\n /** Track page/lesson navigation (default: true) */\n trackNavigation: z.boolean().default(true),\n /** Track quiz/knowledge check answers (default: true) */\n trackQuizzes: z.boolean().default(true),\n /** Track interactive element clicks - tabs, accordions, flashcards, etc. (default: true) */\n trackInteractions: z.boolean().default(true),\n /** Allow custom xAPI statements via window.pa_patcher.lrs.sendCustomStatement() (default: true) */\n customStatements: z.boolean().default(true),\n /** Enable debug logging to console (default: false) */\n debug: z.boolean().default(false),\n /** LRS endpoint URL for direct xAPI posting - if not set, auto-detects from Bravais domain */\n lrsEndpoint: z.string().optional(),\n /** Whether to include credentials (cookies) in LRS requests (default: true) */\n lrsWithCredentials: z.boolean().default(true),\n /** Course homepage IRI for xAPI context */\n courseHomepage: z.string().optional(),\n /** Auto-detect Bravais LRS endpoint from parent frame URL (default: true) */\n autoDetectLrs: z.boolean().default(true),\n /** Basic auth credentials for LRS (Base64 encoded \"username:password\") - used when no cookie session exists */\n lrsAuth: z.string().optional(),\n /** LRS proxy endpoint URL - statements are sent here instead of direct LRS, proxy handles auth server-side */\n lrsProxyEndpoint: z.string().optional(),\n /** Employee API endpoint URL for user identity lookup (e.g., https://node.example.com/admin_api/users/employees/) */\n employeeApiEndpoint: z.string().optional(),\n});\n\n/**\n * Configuration for a blocking asset (CSS before, JS before)\n * These load synchronously and block page rendering\n */\nexport const BlockingAssetConfigSchema = z.object({\n /** Filename for the asset */\n filename: z.string().min(1),\n /** Whether this asset is enabled */\n enabled: z.boolean().default(true),\n});\n\n/**\n * Configuration for an async asset (CSS after, JS after)\n * These load asynchronously with remote-first, local fallback\n */\nexport const AsyncAssetConfigSchema = z.object({\n /** Filename for the asset */\n filename: z.string().min(1),\n /** Whether this asset is enabled */\n enabled: z.boolean().default(true),\n /** Timeout in milliseconds for remote loading before falling back to local */\n timeout: z.number().min(1000).max(30000).default(5000),\n});\n\n/**\n * Local folder configuration for fallback files\n */\nexport const LocalFoldersConfigSchema = z.object({\n /** Folder name for CSS files within scormcontent */\n css: z.string().default('css'),\n /** Folder name for JS files within scormcontent */\n js: z.string().default('js'),\n});\n\n/**\n * Base schema for plugin configuration\n * Each plugin can extend this with their own options\n */\nexport const PluginConfigBaseSchema = z.object({\n /** Whether this plugin is enabled */\n enabled: z.boolean().default(false),\n}).passthrough(); // Allow additional properties from individual plugins\n\n/**\n * Plugins configuration - map of plugin name to plugin config\n */\nexport const PluginsConfigSchema = z.record(z.string(), PluginConfigBaseSchema).default({});\n\n/**\n * Main configuration schema for Patch-Adams\n */\nexport const PatchAdamsConfigSchema = z.object({\n /** Remote domain for assets (e.g., \"https://cdn.example.com/rise-overrides\") */\n remoteDomain: z.string().url(),\n\n /** CSS class added to <html> tag for specificity (default: \"pa-patched\") */\n htmlClass: z.string().default('pa-patched'),\n\n /** CSS class added during loading, removed when ready (default: \"pa-loading\") */\n loadingClass: z.string().default('pa-loading'),\n\n /** CSS loaded at start of <head> - blocking, prevents FOUC */\n cssBefore: BlockingAssetConfigSchema.default({\n filename: 'before.css',\n enabled: true,\n }),\n\n /** CSS loaded at end of <head> - async with fallback, for overrides */\n cssAfter: AsyncAssetConfigSchema.default({\n filename: 'after.css',\n enabled: true,\n timeout: 5000,\n }),\n\n /** JS loaded at start of <head> - blocking, for setup/interception */\n jsBefore: BlockingAssetConfigSchema.default({\n filename: 'before.js',\n enabled: true,\n }),\n\n /** JS loaded at end of <body> - async with fallback, removes loading class */\n jsAfter: AsyncAssetConfigSchema.default({\n filename: 'after.js',\n enabled: true,\n timeout: 5000,\n }),\n\n /** Local folder names for fallback files */\n localFolders: LocalFoldersConfigSchema.default({}),\n\n /** Whether to update manifest files (imsmanifest.xml, etc.) for SCORM compliance */\n updateManifests: z.boolean().default(true),\n\n /** Whether to fetch remote files and bake them as local fallbacks */\n fetchFallbacks: z.boolean().default(true),\n\n /** LRS Bridge configuration for xAPI/LRS communication with Bravais */\n lrsBridge: LrsBridgeConfigSchema.default({}),\n\n /** Plugin configurations - each plugin is keyed by its name */\n plugins: PluginsConfigSchema,\n});\n\nexport type PatchAdamsConfig = z.infer<typeof PatchAdamsConfigSchema>;\nexport type PluginsConfig = z.infer<typeof PluginsConfigSchema>;\nexport type LrsBridgeConfig = z.infer<typeof LrsBridgeConfigSchema>;\nexport type BlockingAssetConfig = z.infer<typeof BlockingAssetConfigSchema>;\nexport type AsyncAssetConfig = z.infer<typeof AsyncAssetConfigSchema>;\nexport type LocalFoldersConfig = z.infer<typeof LocalFoldersConfigSchema>;\n","import type { PatchAdamsConfig } from './schema.js';\n\n/**\n * Default configuration values for Patch-Adams\n */\nexport const defaultConfig: PatchAdamsConfig = {\n remoteDomain: 'https://cdn.example.com/rise-overrides',\n\n htmlClass: 'pa-patched',\n loadingClass: 'pa-loading',\n\n cssBefore: {\n filename: 'before.css',\n enabled: true,\n },\n\n cssAfter: {\n filename: 'after.css',\n enabled: true,\n timeout: 5000,\n },\n\n jsBefore: {\n filename: 'before.js',\n enabled: true,\n },\n\n jsAfter: {\n filename: 'after.js',\n enabled: true,\n timeout: 5000,\n },\n\n localFolders: {\n css: 'css',\n js: 'js',\n },\n\n updateManifests: true,\n\n fetchFallbacks: true,\n\n lrsBridge: {\n enabled: true,\n trackMedia: true,\n trackNavigation: true,\n trackQuizzes: true,\n trackInteractions: true,\n customStatements: true,\n debug: false,\n lrsEndpoint: undefined,\n lrsWithCredentials: true,\n courseHomepage: undefined,\n autoDetectLrs: true,\n },\n\n plugins: {},\n};\n","import { readFileSync, existsSync } from 'fs';\nimport { resolve, extname } from 'path';\nimport { pathToFileURL } from 'url';\nimport { PatchAdamsConfigSchema, type PatchAdamsConfig } from './schema.js';\nimport { defaultConfig } from './defaults.js';\n\n/**\n * Load and validate a Patch-Adams configuration file\n * Supports .ts, .js, and .json files\n */\nexport async function loadConfig(configPath: string): Promise<PatchAdamsConfig> {\n const absolutePath = resolve(configPath);\n\n if (!existsSync(absolutePath)) {\n throw new Error(`Configuration file not found: ${absolutePath}`);\n }\n\n const ext = extname(absolutePath).toLowerCase();\n let rawConfig: unknown;\n\n if (ext === '.json') {\n const content = readFileSync(absolutePath, 'utf-8');\n rawConfig = JSON.parse(content);\n } else if (ext === '.ts' || ext === '.js' || ext === '.mjs') {\n // Dynamic import for ESM modules\n const fileUrl = pathToFileURL(absolutePath).href;\n const module = await import(fileUrl);\n rawConfig = module.default ?? module;\n } else {\n throw new Error(`Unsupported config file extension: ${ext}. Use .ts, .js, .mjs, or .json`);\n }\n\n // Validate and return\n const validated = PatchAdamsConfigSchema.parse(rawConfig);\n return validated;\n}\n\n/**\n * Create a merged config with defaults\n */\nexport function mergeWithDefaults(partial: Partial<PatchAdamsConfig>): PatchAdamsConfig {\n return PatchAdamsConfigSchema.parse({\n ...defaultConfig,\n ...partial,\n });\n}\n\n/**\n * Generate a configuration file template\n */\nexport function generateConfigTemplate(): string {\n return `import type { PatchAdamsConfig } from '@patch-adams/core';\n\nconst config: PatchAdamsConfig = {\n // Remote domain where your CSS/JS files are hosted\n remoteDomain: 'https://cdn.example.com/rise-overrides',\n\n // HTML classes for specificity and loading state\n htmlClass: 'pa-patched', // Always present on <html>\n loadingClass: 'pa-loading', // Removed when assets are loaded\n\n // CSS loaded at start of <head> - BLOCKING\n // Use this to hide content and prevent flash of unstyled content\n cssBefore: {\n filename: 'before.css',\n enabled: true,\n },\n\n // CSS loaded at end of <head> - ASYNC with fallback\n // Use this for style overrides\n cssAfter: {\n filename: 'after.css',\n enabled: true,\n timeout: 5000, // ms before falling back to local\n },\n\n // JS loaded at start of <head> - BLOCKING\n // Use this for setup, API interception, globals\n jsBefore: {\n filename: 'before.js',\n enabled: true,\n },\n\n // JS loaded at end of <body> - ASYNC with fallback\n // Use this for DOM manipulation after Rise loads\n // This should remove the loadingClass when done\n jsAfter: {\n filename: 'after.js',\n enabled: true,\n timeout: 5000, // ms before falling back to local\n },\n\n // Folder names for local fallback files within scormcontent/\n localFolders: {\n css: 'css',\n js: 'js',\n },\n\n // Update manifest files for SCORM compliance\n updateManifests: true,\n};\n\nexport default config;\n`;\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface CssBeforeOptions {\n remoteUrl: string;\n localPath: string;\n htmlClass: string;\n loadingClass: string;\n}\n\n/**\n * Generate the blocking CSS loader for the \"before\" slot\n * This loads at the start of <head> and blocks rendering until loaded\n *\n * Strategy:\n * 1. First, inline the critical CSS to hide content immediately\n * 2. Then load the remote CSS synchronously (or local fallback)\n */\nexport function generateCssBeforeLoader(options: CssBeforeOptions): string {\n const { remoteUrl, localPath, htmlClass, loadingClass } = options;\n\n return `<!-- === PATCH-ADAMS: CSS BEFORE (blocking) === -->\n<style data-pa=\"css-before-critical\">\n/* Critical: Hide content until ready */\nhtml.${htmlClass}.${loadingClass} body {\n visibility: hidden !important;\n}\n</style>\n<script data-pa=\"css-before-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n\n function loadCSSSync(url) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.setAttribute('data-pa', 'css-before');\n document.head.appendChild(link);\n return link;\n }\n\n // Try remote first\n var link = loadCSSSync(REMOTE_URL);\n\n link.onerror = function() {\n console.warn('[Patch-Adams] CSS before failed to load from remote, using local fallback');\n document.head.removeChild(link);\n loadCSSSync(LOCAL_PATH);\n };\n\n link.onload = function() {\n console.log('[Patch-Adams] CSS before loaded from remote:', REMOTE_URL);\n };\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildCssBeforeOptions(config: PatchAdamsConfig): CssBeforeOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.css}/${config.cssBefore.filename}`,\n localPath: `${config.localFolders.css}/${config.cssBefore.filename}`,\n htmlClass: config.htmlClass,\n loadingClass: config.loadingClass,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface CssAfterOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n}\n\n/**\n * Generate the async CSS loader for the \"after\" slot\n * This loads at the end of <head> with remote-first, local fallback\n */\nexport function generateCssAfterLoader(options: CssAfterOptions): string {\n const { remoteUrl, localPath, timeout } = options;\n\n return `<!-- === PATCH-ADAMS: CSS AFTER (async with fallback) === -->\n<script data-pa=\"css-after-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n\n function loadCSS(url, onSuccess, onError) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.setAttribute('data-pa', 'css-after');\n\n link.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n link.onerror = function() {\n if (onError) onError();\n };\n\n document.head.appendChild(link);\n return link;\n }\n\n function loadCSSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteLink = loadCSS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[Patch-Adams] CSS after loaded from remote:', REMOTE_URL);\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[Patch-Adams] CSS after timed out, using local fallback');\n if (remoteLink.parentNode) {\n document.head.removeChild(remoteLink);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadCSS(\n LOCAL_PATH,\n function() {\n console.log('[Patch-Adams] CSS after loaded from local fallback:', LOCAL_PATH);\n },\n function() {\n console.error('[Patch-Adams] CSS after failed to load from both remote and local');\n }\n );\n }\n\n // Execute immediately\n loadCSSWithFallback();\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildCssAfterOptions(config: PatchAdamsConfig): CssAfterOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.css}/${config.cssAfter.filename}`,\n localPath: `${config.localFolders.css}/${config.cssAfter.filename}`,\n timeout: config.cssAfter.timeout,\n };\n}\n","/**\n * LRS Bridge Template for Patch-Adams\n *\n * Generates JavaScript code that bridges Rise courses to an LRS endpoint.\n * Supports direct xAPI statement posting when PlayerIntegration is unavailable.\n * Version 2.0.0 - Full xAPI support with direct LRS communication\n */\n\nexport interface LrsBridgeOptions {\n /** Whether LRS bridge is enabled */\n enabled: boolean;\n /** Track video/audio plays (play, pause, complete) */\n trackMedia: boolean;\n /** Track page/lesson navigation */\n trackNavigation: boolean;\n /** Track quiz/knowledge check answers */\n trackQuizzes: boolean;\n /** Track interactive element clicks (tabs, accordions, etc.) */\n trackInteractions: boolean;\n /** Allow custom xAPI statements via API */\n customStatements: boolean;\n /** Enable debug logging */\n debug: boolean;\n /** LRS endpoint URL for direct xAPI posting - if not provided, auto-detects from Bravais domain */\n lrsEndpoint?: string;\n /** Whether to include credentials (cookies) in LRS requests */\n lrsWithCredentials?: boolean;\n /** Course homepage IRI for xAPI context */\n courseHomepage?: string;\n /** Auto-detect Bravais LRS endpoint from parent frame URL (default: true) */\n autoDetectLrs?: boolean;\n /** Employee API endpoint for user lookup (e.g., https://node.example.com/admin_api/users/employees/) */\n employeeApiEndpoint?: string;\n /** Basic auth credentials for LRS (Base64 encoded \"username:password\") - used when no cookie session exists */\n lrsAuth?: string;\n /** LRS proxy endpoint URL - statements are sent here instead of direct LRS, proxy handles auth server-side */\n lrsProxyEndpoint?: string;\n}\n\nexport const DEFAULT_LRS_OPTIONS: LrsBridgeOptions = {\n enabled: true,\n trackMedia: true,\n trackNavigation: true,\n trackQuizzes: true,\n trackInteractions: true,\n customStatements: true,\n debug: false,\n lrsEndpoint: '',\n lrsWithCredentials: true,\n courseHomepage: '',\n autoDetectLrs: true,\n employeeApiEndpoint: '',\n lrsAuth: '',\n lrsProxyEndpoint: '',\n};\n\n/**\n * Generate the LRS bridge JavaScript code\n * This gets embedded into the js-before loader and runs immediately\n */\nexport function generateLrsBridgeCode(options: LrsBridgeOptions): string {\n if (!options.enabled) {\n return `\n // LRS Bridge disabled\n window.pa_patcher.lrs = { enabled: false, initialized: false };\n`;\n }\n\n // Escape strings for embedding in JS\n const lrsEndpoint = options.lrsEndpoint || '';\n const courseHomepage = options.courseHomepage || '';\n const employeeApiEndpoint = options.employeeApiEndpoint || '';\n const lrsAuth = options.lrsAuth || '';\n const lrsProxyEndpoint = options.lrsProxyEndpoint || '';\n\n return `\n // ==========================================================================\n // PATCH-ADAMS LRS BRIDGE v2.2.0\n // Full xAPI support with direct LRS communication for Rise courses\n // ALL statements use DOCUMENT as main object for Bravais aggregation\n // Activity type (video, assessment, etc.) stored in object.definition.type\n // Page/lesson info stored in context.extensions for navigation tracking\n // ==========================================================================\n (function() {\n 'use strict';\n\n var DEBUG = ${options.debug};\n // Allow URL-based debug toggle: ?pa_debug=1 or #pa_debug\n try {\n if (window.location.search.indexOf('pa_debug') > -1 || window.location.hash.indexOf('pa_debug') > -1) {\n DEBUG = true;\n }\n } catch (e) {}\n var TRACK_MEDIA = ${options.trackMedia};\n var TRACK_NAVIGATION = ${options.trackNavigation};\n var TRACK_QUIZZES = ${options.trackQuizzes};\n var TRACK_INTERACTIONS = ${options.trackInteractions};\n var CUSTOM_STATEMENTS = ${options.customStatements};\n var LRS_ENDPOINT_CONFIG = '${lrsEndpoint}';\n var LRS_WITH_CREDENTIALS = ${options.lrsWithCredentials ?? true};\n var COURSE_HOMEPAGE = '${courseHomepage}';\n var AUTO_DETECT_LRS = ${options.autoDetectLrs ?? true};\n var EMPLOYEE_API_ENDPOINT = '${employeeApiEndpoint}';\n var LRS_AUTH = '${lrsAuth}';\n var LRS_PROXY_ENDPOINT = '${lrsProxyEndpoint}';\n\n // Bravais LRS endpoint pattern: https://lrs-{tenant}.bravais.com/XAPI/statements\n // Example: core-acme.bravais.com -> lrs-acme.bravais.com\n var LRS_ENDPOINT = ''; // Will be set by auto-detection or config\n\n function log() {\n if (DEBUG && window.console && window.console.log) {\n var args = Array.prototype.slice.call(arguments);\n args.unshift('[PA-LRS]');\n console.log.apply(console, args);\n }\n }\n\n function warn() {\n if (window.console && window.console.warn) {\n var args = Array.prototype.slice.call(arguments);\n args.unshift('[PA-LRS]');\n console.warn.apply(console, args);\n }\n }\n\n // ========================================================================\n // XAPI VERB DEFINITIONS (ADL standard IRIs)\n // ========================================================================\n var VERBS = {\n launched: {\n id: 'http://adlnet.gov/expapi/verbs/launched',\n display: { 'en-US': 'launched' }\n },\n initialized: {\n id: 'http://adlnet.gov/expapi/verbs/initialized',\n display: { 'en-US': 'initialized' }\n },\n viewed: {\n id: 'http://id.tincanapi.com/verb/viewed',\n display: { 'en-US': 'viewed' }\n },\n experienced: {\n id: 'http://adlnet.gov/expapi/verbs/experienced',\n display: { 'en-US': 'experienced' }\n },\n played: {\n id: 'https://w3id.org/xapi/video/verbs/played',\n display: { 'en-US': 'played' }\n },\n paused: {\n id: 'https://w3id.org/xapi/video/verbs/paused',\n display: { 'en-US': 'paused' }\n },\n completed: {\n id: 'http://adlnet.gov/expapi/verbs/completed',\n display: { 'en-US': 'completed' }\n },\n attempted: {\n id: 'http://adlnet.gov/expapi/verbs/attempted',\n display: { 'en-US': 'attempted' }\n },\n answered: {\n id: 'http://adlnet.gov/expapi/verbs/answered',\n display: { 'en-US': 'answered' }\n },\n passed: {\n id: 'http://adlnet.gov/expapi/verbs/passed',\n display: { 'en-US': 'passed' }\n },\n failed: {\n id: 'http://adlnet.gov/expapi/verbs/failed',\n display: { 'en-US': 'failed' }\n },\n interacted: {\n id: 'http://adlnet.gov/expapi/verbs/interacted',\n display: { 'en-US': 'interacted' }\n },\n progressed: {\n id: 'http://adlnet.gov/expapi/verbs/progressed',\n display: { 'en-US': 'progressed' }\n },\n terminated: {\n id: 'http://adlnet.gov/expapi/verbs/terminated',\n display: { 'en-US': 'terminated' }\n },\n // ActivityStrea.ms verbs for Xyleme/Bravais compatibility\n opened: {\n id: 'http://activitystrea.ms/schema/1.0/open',\n display: { 'en-US': 'opened' }\n },\n closed: {\n id: 'http://activitystrea.ms/schema/1.0/close',\n display: { 'en-US': 'closed' }\n },\n exited: {\n id: 'http://activitystrea.ms/schema/1.0/close',\n display: { 'en-US': 'exited' }\n }\n };\n\n // ========================================================================\n // ACTIVITY TYPES\n // ========================================================================\n var ACTIVITY_TYPES = {\n course: 'http://adlnet.gov/expapi/activities/course',\n module: 'http://adlnet.gov/expapi/activities/module',\n lesson: 'http://adlnet.gov/expapi/activities/lesson',\n assessment: 'http://adlnet.gov/expapi/activities/assessment',\n question: 'http://adlnet.gov/expapi/activities/question',\n interaction: 'http://adlnet.gov/expapi/activities/interaction',\n media: 'http://adlnet.gov/expapi/activities/media',\n video: 'https://w3id.org/xapi/video/activity-type/video',\n audio: 'https://w3id.org/xapi/audio/activity-type/audio'\n };\n\n // ========================================================================\n // XYLEME RESOURCE TYPES (for Bravais Analytics)\n // ========================================================================\n var RESOURCE_TYPES = {\n course: 'Course',\n document: 'Course',\n lesson: 'Topic',\n page: 'Topic',\n topic: 'Topic',\n assessment: 'Assessment',\n question: 'Question',\n interaction: 'Topic',\n media: 'Topic',\n video: 'Topic',\n audio: 'Topic'\n };\n\n // Xyleme-specific activity type IRIs\n var XYLEME_ACTIVITY_TYPES = {\n page: 'http://activitystrea.ms/schema/1.0/page',\n document: 'http://xyleme.com/bravais/activities/document',\n assessment: 'http://adlnet.gov/expapi/activities/assessment',\n question: 'http://adlnet.gov/expapi/activities/question'\n };\n\n // ========================================================================\n // LRS NAMESPACE & STATE\n // ========================================================================\n var LRS = window.pa_patcher.lrs = {\n version: '2.2.0',\n enabled: true,\n initialized: false,\n mode: null, // 'playerIntegration', 'directLRS', 'scormOnly'\n integration: null, // Xyleme PlayerIntegration (if available)\n integrationLevel: null,\n scormApi: null,\n scormApiLevel: null,\n scormApiWindow: null,\n scormApiFound: false,\n scormApiType: null,\n lrsEndpoint: LRS_ENDPOINT,\n statementQueue: [], // xAPI statements queued for sending\n eventLog: [], // All events logged (for debugging)\n currentLesson: null,\n courseInfo: null, // Course metadata\n actor: null, // xAPI actor object\n sessionId: null, // Session UUID\n launchTime: null, // ISO timestamp of course launch\n courseAttemptId: null, // Unique ID for this course attempt session (Xyleme format)\n // Statistics\n stats: {\n statementsSent: 0,\n statementsQueued: 0,\n statementsFailed: 0\n }\n };\n\n // Generate UUID for session tracking\n function generateUUID() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0;\n var v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n }\n\n LRS.sessionId = generateUUID();\n LRS.courseAttemptId = generateUUID(); // Unique per session for Xyleme correlation\n LRS.launchTime = new Date().toISOString();\n log('Generated courseAttemptId:', LRS.courseAttemptId);\n\n // ========================================================================\n // 1. BRAVAIS LRS ENDPOINT AUTO-DETECTION\n // ========================================================================\n\n /**\n * Auto-detect Bravais LRS endpoint from current URL or parent frames\n * Bravais pattern: https://core-{tenant}.bravais.com -> https://lrs-{tenant}.bravais.com/XAPI/statements\n */\n function detectBravaisLrsEndpoint() {\n var urlsToCheck = [];\n\n // Check current window\n urlsToCheck.push(window.location.href);\n\n // Check parent frames\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) {\n // Cross-origin, try document.referrer\n break;\n }\n } else {\n break;\n }\n }\n } catch (e) {}\n\n // Also check referrer\n if (document.referrer) {\n urlsToCheck.push(document.referrer);\n }\n\n log('Checking URLs for Bravais domain:', urlsToCheck);\n\n // Look for Bravais domain pattern\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: core-{tenant}.bravais.com or {tenant}.bravais.com\n var match = url.match(/(?:core-)?([a-zA-Z0-9_-]+)\\\\.bravais\\\\.com/);\n if (match) {\n var tenant = match[1];\n var lrsEndpoint = 'https://lrs-' + tenant + '.bravais.com/XAPI/statements';\n log('Detected Bravais tenant:', tenant);\n log('Auto-detected LRS endpoint:', lrsEndpoint);\n return lrsEndpoint;\n }\n }\n\n log('No Bravais domain detected');\n return null;\n }\n\n // ========================================================================\n // 2. SCORM/PLAYERINTEGRATION DETECTION\n // ========================================================================\n\n function findAPIInFrameHierarchy(apiName, maxLevels) {\n maxLevels = maxLevels || 10;\n var win = window;\n var level = 0;\n\n while (level < maxLevels) {\n try {\n if (win[apiName]) {\n log('Found ' + apiName + ' at level ' + level);\n return { api: win[apiName], level: level, window: win };\n }\n } catch (e) {\n log('Cross-origin block at level ' + level);\n }\n\n try {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n level++;\n } else {\n break;\n }\n } catch (e) {\n break;\n }\n }\n\n return null;\n }\n\n function findPlayerIntegration(maxLevels) {\n maxLevels = maxLevels || 10;\n var win = window;\n var level = 0;\n\n while (level < maxLevels) {\n try {\n if (win.PlayerIntegration) {\n log('Found PlayerIntegration at level ' + level);\n return { integration: win.PlayerIntegration, level: level, window: win };\n }\n } catch (e) {}\n\n try {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n level++;\n } else {\n break;\n }\n } catch (e) {\n break;\n }\n }\n\n return null;\n }\n\n function setupBridge() {\n try {\n // 1. Try PlayerIntegration first (Xyleme native mode) - for SCORM APIs\n var integrationResult = findPlayerIntegration(10);\n if (integrationResult) {\n LRS.integration = integrationResult.integration;\n LRS.integrationLevel = integrationResult.level;\n log('Found PlayerIntegration at level', integrationResult.level);\n\n if (LRS.integration.Internal_SCORM_2004_API) {\n window.API_1484_11 = LRS.integration.Internal_SCORM_2004_API;\n LRS.scormApi = window.API_1484_11;\n LRS.scormApiFound = true;\n LRS.scormApiType = '2004';\n }\n if (LRS.integration.Internal_SCORM_12_API) {\n window.API = LRS.integration.Internal_SCORM_12_API;\n if (!LRS.scormApiFound) {\n LRS.scormApi = window.API;\n LRS.scormApiFound = true;\n LRS.scormApiType = '1.2';\n }\n }\n // Don't return early - continue to detect LRS endpoint for direct xAPI\n } else {\n // 2. Search for SCORM APIs directly\n log('No PlayerIntegration found, searching for SCORM APIs...');\n\n var api2004 = findAPIInFrameHierarchy('API_1484_11', 10);\n if (api2004) {\n LRS.scormApi = api2004.api;\n LRS.scormApiLevel = api2004.level;\n LRS.scormApiWindow = api2004.window;\n LRS.scormApiFound = true;\n LRS.scormApiType = '2004';\n log('Found SCORM 2004 API at level ' + api2004.level);\n } else {\n var api12 = findAPIInFrameHierarchy('API', 10);\n if (api12) {\n LRS.scormApi = api12.api;\n LRS.scormApiLevel = api12.level;\n LRS.scormApiWindow = api12.window;\n LRS.scormApiFound = true;\n LRS.scormApiType = '1.2';\n log('Found SCORM 1.2 API at level ' + api12.level);\n }\n }\n }\n\n // 3. ALWAYS try to determine LRS endpoint for direct xAPI statements\n // This is critical for detailed tracking (media, interactions, quizzes)\n // Priority: 1) Configured endpoint, 2) Auto-detected Bravais endpoint\n if (LRS_ENDPOINT_CONFIG && LRS_ENDPOINT_CONFIG.length > 0) {\n LRS_ENDPOINT = LRS_ENDPOINT_CONFIG;\n log('Using configured LRS endpoint:', LRS_ENDPOINT);\n } else if (AUTO_DETECT_LRS) {\n var detectedEndpoint = detectBravaisLrsEndpoint();\n if (detectedEndpoint) {\n LRS_ENDPOINT = detectedEndpoint;\n log('Using auto-detected Bravais LRS endpoint:', LRS_ENDPOINT);\n }\n }\n\n LRS.lrsEndpoint = LRS_ENDPOINT;\n\n // 4. Determine mode based on what we found\n // Prefer directLRS when available for detailed tracking\n if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {\n LRS.mode = 'directLRS';\n log('Using direct LRS mode with endpoint:', LRS_ENDPOINT);\n if (LRS.integration) {\n log('PlayerIntegration also available for SCORM state');\n }\n return true;\n } else if (LRS.integration) {\n LRS.mode = 'playerIntegration';\n log('Using PlayerIntegration mode (no direct LRS endpoint)');\n return true;\n } else if (LRS.scormApiFound) {\n LRS.mode = 'scormOnly';\n log('Using SCORM-only mode (no LRS endpoint detected)');\n return true;\n }\n\n warn('No LRS endpoint or SCORM API found');\n return false;\n\n } catch (e) {\n warn('Error setting up bridge:', e);\n return false;\n }\n }\n\n // ========================================================================\n // 3. ACTOR EXTRACTION (multiple sources with fallbacks)\n // ========================================================================\n\n // Store for Bravais session data\n var bravaisSessionData = null;\n var bravaisSessionFetched = false;\n\n /**\n * Parse actor info from URL query parameters\n * Bravais thin packs often pass user info via URL params\n */\n function getActorFromUrlParams() {\n try {\n var params = new URLSearchParams(window.location.search);\n\n // Check various URL parameter names used by Bravais\n var userId = params.get('learner_id') || params.get('learnerId') ||\n params.get('user_id') || params.get('userId') ||\n params.get('actor_id') || params.get('actorId');\n var userName = params.get('learner_name') || params.get('learnerName') ||\n params.get('user_name') || params.get('userName') ||\n params.get('actor_name') || params.get('actorName');\n var userEmail = params.get('learner_email') || params.get('learnerEmail') ||\n params.get('user_email') || params.get('userEmail') ||\n params.get('actor_email') || params.get('actorEmail') ||\n params.get('email');\n var homePage = params.get('homepage') || params.get('homePage') ||\n params.get('tenant') || params.get('tenantId');\n\n if (userId || userEmail) {\n log('Found actor info in URL params');\n var actor = { objectType: 'Agent' };\n\n if (userName) {\n actor.name = userName;\n }\n if (userEmail) {\n actor.mbox = userEmail.indexOf('mailto:') === 0 ? userEmail : 'mailto:' + userEmail;\n }\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: userId\n };\n }\n return actor;\n }\n\n // Check for base64 encoded actor parameter\n var actorParam = params.get('actor');\n if (actorParam) {\n try {\n var actorJson = atob(actorParam);\n var actor = JSON.parse(actorJson);\n log('Found base64 encoded actor in URL');\n return actor;\n } catch (e) {\n log('Could not decode actor param:', e.message);\n }\n }\n\n } catch (e) {\n log('Error parsing URL params:', e.message);\n }\n return null;\n }\n\n /**\n * Parse actor from Bravais launch data (stored in SCORM launch_data)\n * Bravais stores JSON in cmi.launch_data with user context\n */\n function getActorFromLaunchData() {\n if (!LRS.scormApi) return null;\n\n try {\n var launchData = null;\n if (LRS.scormApiType === '2004') {\n launchData = LRS.scormApi.GetValue('cmi.launch_data');\n } else {\n launchData = LRS.scormApi.LMSGetValue('cmi.launch_data');\n }\n\n if (launchData) {\n // Bravais stores base64 encoded JSON in launch_data\n try {\n var decoded = atob(launchData);\n var data = JSON.parse(decoded);\n log('Parsed launch data:', Object.keys(data));\n\n // Look for actor/user info in launch data\n if (data.actor) {\n log('Found actor in launch data');\n return data.actor;\n }\n\n // Build actor from launch data fields\n if (data.userId || data.userID || data.learnerId) {\n var actor = { objectType: 'Agent' };\n var userId = data.userId || data.userID || data.learnerId;\n var userName = data.userName || data.userFirstName ?\n ((data.userFirstName || '') + ' ' + (data.userLastName || '')).trim() :\n data.learnerName;\n var userEmail = data.userEmail || data.learnerEmail || data.email;\n var homePage = data.xApiTenantId || data.tenantId || data.homePage;\n\n if (userName) actor.name = userName;\n if (userEmail) actor.mbox = 'mailto:' + userEmail;\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: String(userId)\n };\n }\n log('Built actor from launch data fields');\n return actor;\n }\n } catch (e) {\n // Not base64/JSON, might be plain text\n log('Launch data is not base64 JSON:', e.message);\n }\n }\n } catch (e) {\n log('Error reading launch data:', e.message);\n }\n return null;\n }\n\n /**\n * Try to get user info from Bravais context cookie\n * Cookie name pattern: Bravais-{env}-{region}-Context\n * Cookie value is base64 encoded JSON with user info\n */\n function getActorFromBravaisCookie() {\n try {\n var cookies = document.cookie.split(';');\n for (var i = 0; i < cookies.length; i++) {\n var cookie = cookies[i].trim();\n // Look for Bravais context cookies\n if (cookie.indexOf('Bravais-') === 0 && cookie.indexOf('-Context=') > -1) {\n var cookieName = cookie.split('=')[0];\n var cookieValue = cookie.substring(cookieName.length + 1);\n log('Found Bravais context cookie:', cookieName);\n\n try {\n // Cookie value is base64 encoded JSON\n var decoded = atob(cookieValue);\n var contextData = JSON.parse(decoded);\n log('Decoded Bravais cookie data keys:', Object.keys(contextData));\n\n // Extract user info from context\n if (contextData.userId || contextData.userID || contextData.user) {\n var actor = { objectType: 'Agent' };\n var user = contextData.user || contextData;\n\n var userId = user.userId || user.userID || user.id;\n var userName = user.userName || user.name ||\n ((user.firstName || user.userFirstName || '') + ' ' +\n (user.lastName || user.userLastName || '')).trim();\n var userEmail = user.email || user.userEmail;\n var homePage = contextData.xApiTenantId || contextData.tenantId ||\n contextData.portalUrl || contextData.coreUrl;\n\n if (userName) actor.name = userName;\n if (userEmail) actor.mbox = 'mailto:' + userEmail;\n if (userId) {\n actor.account = {\n homePage: homePage || window.location.origin,\n name: String(userId)\n };\n }\n log('Built actor from Bravais cookie');\n return actor;\n }\n } catch (e) {\n log('Could not decode Bravais cookie:', e.message);\n }\n }\n }\n log('No Bravais context cookie found or not accessible');\n } catch (e) {\n log('Cannot access cookies (cross-origin or blocked)');\n }\n return null;\n }\n\n /**\n * Try to get actor from parent frame's data\n * Bravais thin packs inject sessionData, actor, and PARAMS into the parent page\n */\n function getActorFromParentFrame() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n // Check for Bravais PARAMS object (contains actor directly)\n if (win.PARAMS && win.PARAMS.actor) {\n log('Found PARAMS.actor at level', level);\n var actor = win.PARAMS.actor;\n // Ensure it's properly formatted\n if (actor.objectType !== 'Agent') {\n actor.objectType = 'Agent';\n }\n return actor;\n }\n\n // Check for Bravais sessionData (server-injected user info)\n if (win.sessionData && (win.sessionData.userId || win.sessionData.userID)) {\n log('Found sessionData at level', level);\n var sd = win.sessionData;\n var actor = { objectType: 'Agent' };\n\n // Name from firstName + lastName\n var firstName = sd.userFirstName || '';\n var lastName = sd.userLastName || '';\n if (firstName || lastName) {\n actor.name = (firstName + ' ' + lastName).trim();\n }\n\n // Account with homePage and userId\n var homePage = sd.xApiTenantId || sd.webPortalUrl || sd.coreUrl;\n var userId = sd.userId || sd.userID;\n if (homePage && userId) {\n actor.account = {\n homePage: homePage,\n name: String(userId)\n };\n }\n\n // mbox from email\n if (sd.userEmail) {\n actor.mbox = 'mailto:' + sd.userEmail;\n }\n\n log('Built actor from sessionData:', actor);\n return actor;\n }\n\n // Check for standalone actor variable (Bravais sets this from sessionData)\n if (win.actor && (win.actor.account || win.actor.mbox)) {\n log('Found actor variable at level', level);\n var actor = win.actor;\n if (actor.objectType !== 'Agent') {\n actor.objectType = 'Agent';\n }\n return actor;\n }\n\n // Check for Bravais player data\n if (win.BRAVAIS_LEARNER) {\n log('Found BRAVAIS_LEARNER at level', level);\n var learner = win.BRAVAIS_LEARNER;\n var actor = { objectType: 'Agent' };\n if (learner.name) actor.name = learner.name;\n if (learner.email) actor.mbox = 'mailto:' + learner.email;\n if (learner.id) {\n actor.account = {\n homePage: learner.homePage || window.location.origin,\n name: String(learner.id)\n };\n }\n return actor;\n }\n\n // Check for xAPI actor\n if (win.xAPILearner || win.XAPI_ACTOR) {\n log('Found xAPI actor at level', level);\n return win.xAPILearner || win.XAPI_ACTOR;\n }\n\n // Check for PA_ACTOR (set by patch-adams)\n if (win.PA_ACTOR) {\n log('Found PA_ACTOR at level', level);\n return win.PA_ACTOR;\n }\n\n // Check for generic learner data\n if (win.learnerData || win.userData) {\n var data = win.learnerData || win.userData;\n log('Found learner/user data at level', level);\n if (data.id || data.userId || data.email) {\n var actor = { objectType: 'Agent' };\n if (data.name) actor.name = data.name;\n if (data.email) actor.mbox = 'mailto:' + data.email;\n if (data.id || data.userId) {\n actor.account = {\n homePage: data.homePage || window.location.origin,\n name: String(data.id || data.userId)\n };\n }\n return actor;\n }\n }\n\n } catch (e) {\n // Cross-origin blocked at this level\n log('Cross-origin block at level', level, '- trying session API instead');\n break;\n }\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {\n log('Error accessing parent frames:', e.message);\n }\n return null;\n }\n\n /**\n * Fetch user info from Bravais session API\n * Returns session data or null if not available\n */\n function fetchBravaisSessionData(callback) {\n if (bravaisSessionFetched) {\n callback(bravaisSessionData);\n return;\n }\n\n // Detect Bravais core URL from parent frames or current URL\n var coreUrl = null;\n var urlsToCheck = [window.location.href, document.referrer];\n\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n log('URLs to check for core URL:', urlsToCheck);\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var match = urlsToCheck[j].match(/(https?:\\\\/\\\\/core-[a-zA-Z0-9_-]+\\\\.bravais\\\\.com)/);\n if (match) {\n coreUrl = match[1];\n break;\n }\n }\n\n if (!coreUrl) {\n log('No Bravais core URL detected for session fetch');\n bravaisSessionFetched = true;\n callback(null);\n return;\n }\n\n log('Fetching session data from:', coreUrl + '/api/shared/sessionData');\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', coreUrl + '/api/shared/sessionData', true);\n xhr.withCredentials = true; // Send cookies for auth\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n bravaisSessionFetched = true;\n log('Session API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n bravaisSessionData = JSON.parse(xhr.responseText);\n log('Bravais session data received:', Object.keys(bravaisSessionData));\n callback(bravaisSessionData);\n } catch (e) {\n warn('Error parsing session data:', e);\n callback(null);\n }\n } else {\n log('Session data fetch failed (CORS or auth). Status:', xhr.status);\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n bravaisSessionFetched = true;\n log('Network error fetching session data (CORS blocked)');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n bravaisSessionFetched = true;\n warn('Error sending session request:', e);\n callback(null);\n }\n }\n\n /**\n * Build actor from Bravais session data\n */\n function buildActorFromBravaisSession(sessionData) {\n if (!sessionData) return null;\n\n var actor = {\n objectType: 'Agent'\n };\n\n // Name from first + last name\n var firstName = sessionData.userFirstName || '';\n var lastName = sessionData.userLastName || '';\n if (firstName || lastName) {\n actor.name = (firstName + ' ' + lastName).trim();\n }\n\n // Account with homePage and user ID\n var homePage = sessionData.xApiTenantId || sessionData.webPortalUrl || sessionData.coreUrl;\n if (homePage && sessionData.userID) {\n actor.account = {\n homePage: homePage,\n name: String(sessionData.userID)\n };\n }\n\n // mbox from email (preferred by Bravais LRS)\n if (sessionData.userEmail) {\n actor.mbox = 'mailto:' + sessionData.userEmail;\n }\n\n return actor;\n }\n\n // Store for employee lookup data\n var employeeLookupData = null;\n var employeeLookupFetched = false;\n\n /**\n * Fetch user email from employee API using employee ID\n * Endpoint configured via EMPLOYEE_API_ENDPOINT (e.g., https://node.example.com/admin_api/users/employees/)\n */\n function fetchEmployeeEmail(employeeId, callback) {\n if (!employeeId) {\n callback(null);\n return;\n }\n\n // Skip if no employee API endpoint configured\n if (!EMPLOYEE_API_ENDPOINT) {\n log('Employee API endpoint not configured, skipping lookup');\n callback(null);\n return;\n }\n\n // In-memory cache check\n if (employeeLookupFetched && employeeLookupData) {\n callback(employeeLookupData);\n return;\n }\n\n // localStorage cache check (persists across page loads / sessions)\n var cacheKey = 'pa_employee_' + employeeId;\n try {\n var cached = localStorage.getItem(cacheKey);\n if (cached) {\n var parsed = JSON.parse(cached);\n // TTL: 7 days = 604800000 ms\n if (parsed.timestamp && (Date.now() - parsed.timestamp) < 604800000) {\n log('Employee data from localStorage cache');\n employeeLookupData = parsed;\n employeeLookupFetched = true;\n callback(parsed);\n return;\n }\n localStorage.removeItem(cacheKey);\n }\n } catch (e) {\n // localStorage unavailable (private browsing, iframe restrictions)\n }\n\n // Build URL: endpoint should end with ? or include query param structure\n var apiUrl = EMPLOYEE_API_ENDPOINT + (EMPLOYEE_API_ENDPOINT.indexOf('?') >= 0 ? '&' : '?') + 'q=' + encodeURIComponent(employeeId);\n log('Fetching employee data from:', apiUrl);\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n employeeLookupFetched = true;\n log('Employee API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n var data = JSON.parse(xhr.responseText);\n log('Employee data received:', data);\n\n // Response could be array or single object\n var employee = Array.isArray(data) ? data[0] : data;\n if (employee) {\n // API returns: business_email, fname, lname, emp_id\n employeeLookupData = {\n email: employee.business_email || employee.email || employee.Email || employee.userEmail,\n name: employee.name || employee.displayName || employee.fullName ||\n ((employee.fname || employee.firstName || '') + ' ' + (employee.lname || employee.lastName || '')).trim(),\n firstName: employee.fname || employee.firstName || employee.first_name,\n lastName: employee.lname || employee.lastName || employee.last_name,\n employeeId: employee.emp_id || employee.employeeId || employee.employee_id || employeeId\n };\n log('Parsed employee data:', employeeLookupData);\n // Persist to localStorage for cross-session caching\n try {\n var cacheData = {};\n for (var k in employeeLookupData) { if (employeeLookupData.hasOwnProperty(k)) cacheData[k] = employeeLookupData[k]; }\n cacheData.timestamp = Date.now();\n localStorage.setItem(cacheKey, JSON.stringify(cacheData));\n log('Employee data cached in localStorage');\n } catch (e) { /* localStorage unavailable */ }\n callback(employeeLookupData);\n } else {\n callback(null);\n }\n } catch (e) {\n warn('Error parsing employee data:', e);\n callback(null);\n }\n } else {\n log('Employee lookup failed. Status:', xhr.status);\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n employeeLookupFetched = true;\n log('Network error fetching employee data');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n employeeLookupFetched = true;\n warn('Error sending employee request:', e);\n callback(null);\n }\n }\n\n /**\n * Enhance actor with email from employee lookup\n */\n function enhanceActorWithEmail(actor, callback) {\n if (!actor) {\n callback(actor);\n return;\n }\n\n // If actor already has email, no need to lookup\n if (actor.mbox && actor.mbox !== 'mailto:' && actor.mbox !== 'mailto:unknown') {\n callback(actor);\n return;\n }\n\n // Get employee ID from actor account name (SCORM learner_id is often employee ID)\n var employeeId = actor.account ? actor.account.name : null;\n if (!employeeId || employeeId === 'unknown' || employeeId === 'preview_user') {\n callback(actor);\n return;\n }\n\n log('Looking up email for employee ID:', employeeId);\n\n fetchEmployeeEmail(employeeId, function(employeeData) {\n if (employeeData && employeeData.email) {\n actor.mbox = 'mailto:' + employeeData.email;\n log('Enhanced actor with email:', employeeData.email);\n\n // Also update name if we got a better one\n if (employeeData.name && (!actor.name || actor.name === 'Unknown Learner')) {\n actor.name = employeeData.name;\n }\n }\n callback(actor);\n });\n }\n\n function extractActor() {\n var defaultActor = {\n objectType: 'Agent',\n name: 'Unknown Learner',\n account: {\n homePage: COURSE_HOMEPAGE || window.location.origin,\n name: 'unknown'\n }\n };\n\n var actor = null;\n\n log('Extracting actor from available sources...');\n\n // Priority 1: URL parameters (most reliable for thin packs)\n actor = getActorFromUrlParams();\n if (actor && isValidActor(actor)) {\n log('Actor source: URL parameters');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 2: Parent frame data\n actor = getActorFromParentFrame();\n if (actor && isValidActor(actor)) {\n log('Actor source: Parent frame');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 3: Bravais context cookie (if accessible)\n actor = getActorFromBravaisCookie();\n if (actor && isValidActor(actor)) {\n log('Actor source: Bravais cookie');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 4: SCORM launch data (may contain user info)\n actor = getActorFromLaunchData();\n if (actor && isValidActor(actor)) {\n log('Actor source: SCORM launch data');\n LRS.actor = actor;\n return actor;\n }\n\n // Priority 5: Standard SCORM learner fields\n if (LRS.scormApi) {\n try {\n var scormLearnerId = null;\n var scormLearnerName = null;\n\n if (LRS.scormApiType === '2004') {\n scormLearnerId = LRS.scormApi.GetValue('cmi.learner_id');\n scormLearnerName = LRS.scormApi.GetValue('cmi.learner_name');\n } else {\n scormLearnerId = LRS.scormApi.LMSGetValue('cmi.core.student_id');\n scormLearnerName = LRS.scormApi.LMSGetValue('cmi.core.student_name');\n }\n\n log('SCORM learner_id:', scormLearnerId);\n log('SCORM learner_name:', scormLearnerName);\n\n // Check if we have valid SCORM data (not preview/unknown)\n var isValidScormId = scormLearnerId &&\n scormLearnerId !== 'preview_user' &&\n scormLearnerId !== 'unknown' &&\n scormLearnerId !== '';\n\n if (isValidScormId) {\n actor = {\n objectType: 'Agent',\n account: {\n homePage: COURSE_HOMEPAGE || window.location.origin,\n name: scormLearnerId\n }\n };\n if (scormLearnerName) {\n // Handle \"LastName, FirstName\" format\n if (scormLearnerName.indexOf(',') > -1) {\n var parts = scormLearnerName.split(',');\n actor.name = (parts[1] || '').trim() + ' ' + (parts[0] || '').trim();\n } else {\n actor.name = scormLearnerName;\n }\n }\n log('Actor source: SCORM API');\n LRS.actor = actor;\n return actor;\n }\n\n log('SCORM learner ID is preview/unknown, continuing search...');\n } catch (e) {\n warn('Error extracting actor from SCORM:', e);\n }\n }\n\n // Priority 6: Already-fetched Bravais session data\n if (bravaisSessionData) {\n var bravaisActor = buildActorFromBravaisSession(bravaisSessionData);\n if (bravaisActor && isValidActor(bravaisActor)) {\n log('Actor source: Bravais session (cached)');\n LRS.actor = bravaisActor;\n return bravaisActor;\n }\n }\n\n log('No valid actor found, using default (Unknown Learner)');\n LRS.actor = defaultActor;\n return defaultActor;\n }\n\n /**\n * Check if an actor has valid identifying information\n */\n function isValidActor(actor) {\n if (!actor) return false;\n\n // Must have at least one valid identifier\n var hasValidMbox = actor.mbox && actor.mbox !== 'mailto:' && actor.mbox !== 'mailto:unknown';\n var hasValidAccount = actor.account && actor.account.name &&\n actor.account.name !== 'unknown' &&\n actor.account.name !== 'preview_user' &&\n actor.account.name !== '';\n\n return hasValidMbox || hasValidAccount;\n }\n\n /**\n * Async version that fetches Bravais session if needed\n * Also re-checks all sources in case they became available\n * Finally enhances actor with email from employee lookup if needed\n */\n function extractActorAsync(callback) {\n // Helper to finalize actor with email lookup\n function finalizeActor(actor) {\n // Try to enhance actor with email if missing\n enhanceActorWithEmail(actor, function(enhancedActor) {\n LRS.actor = enhancedActor;\n callback(enhancedActor);\n });\n }\n\n // If we already have a valid actor with email, use it\n if (LRS.actor && isValidActor(LRS.actor) && LRS.actor.mbox && LRS.actor.mbox !== 'mailto:' && LRS.actor.mbox !== 'mailto:unknown') {\n log('Using existing valid actor with email');\n callback(LRS.actor);\n return;\n }\n\n // If we have a valid actor but no email, try to enhance it\n if (LRS.actor && isValidActor(LRS.actor)) {\n log('Valid actor found, checking for email enhancement');\n finalizeActor(LRS.actor);\n return;\n }\n\n // Re-check sync sources first (they might have loaded since init)\n var syncActor = extractActor();\n if (isValidActor(syncActor)) {\n log('Sync actor found, enhancing with email');\n finalizeActor(syncActor);\n return;\n }\n\n // Try Bravais session API as last resort\n fetchBravaisSessionData(function(sessionData) {\n if (sessionData) {\n var bravaisActor = buildActorFromBravaisSession(sessionData);\n if (isValidActor(bravaisActor)) {\n log('Updated actor from Bravais session API:', bravaisActor);\n finalizeActor(bravaisActor);\n return;\n }\n }\n\n // Fall back to whatever we have, still try email enhancement\n log('Async extraction complete, enhancing:', LRS.actor);\n finalizeActor(LRS.actor);\n });\n }\n\n // ========================================================================\n // 4. COURSE INFO EXTRACTION (Xyleme/Bravais format)\n // ========================================================================\n\n /**\n * Extract Bravais shared link token from URL\n * Pattern: /api/shared/{token}/files/...\n */\n function extractSharedLinkToken() {\n var urlsToCheck = [window.location.href];\n\n try {\n var win = window;\n for (var i = 0; i < 5; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: /api/shared/{token}/files/\n var match = url.match(/\\\\/api\\\\/shared\\\\/([a-zA-Z0-9_-]+)\\\\/files/);\n if (match) {\n log('Extracted shared link token:', match[1]);\n return match[1];\n }\n }\n return null;\n }\n\n // Store for document API data\n var documentApiData = null;\n var documentApiFetched = false;\n var sharedLinkApiData = null;\n\n /**\n * Detect Bravais core URL from current page context\n */\n function detectCoreUrl() {\n var urlsToCheck = [window.location.href, document.referrer];\n\n try {\n var win = window;\n for (var i = 0; i < 10; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var match = urlsToCheck[j].match(/(https?:\\\\/\\\\/core-[a-zA-Z0-9_-]+\\\\.bravais\\\\.com)/);\n if (match) {\n return match[1];\n }\n }\n return null;\n }\n\n /**\n * Fetch shared link data from Bravais API to get the real document ID\n * Endpoint: /api/v3/sharedLinks/token/{token}\n * Returns: { documentId, documentName, format, ... }\n */\n function fetchSharedLinkData(token, callback) {\n if (!token) {\n callback(null);\n return;\n }\n\n var coreUrl = detectCoreUrl();\n if (!coreUrl) {\n log('No Bravais core URL detected for shared link fetch');\n callback(null);\n return;\n }\n\n var apiUrl = coreUrl + '/api/v3/sharedLinks/token/' + token;\n log('Fetching shared link data from:', apiUrl);\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n log('Shared link API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n sharedLinkApiData = JSON.parse(xhr.responseText);\n log('Shared link data received:', sharedLinkApiData);\n if (sharedLinkApiData.documentId) {\n log('Real document ID from shared link:', sharedLinkApiData.documentId);\n }\n callback(sharedLinkApiData);\n } catch (e) {\n warn('Error parsing shared link data:', e);\n callback(null);\n }\n } else {\n // 401/403 is expected - API requires auth that may not be available from iframe\n if (xhr.status === 401 || xhr.status === 403) {\n log('Shared link API requires auth (expected), will use fallback');\n } else {\n log('Shared link fetch failed. Status:', xhr.status);\n }\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n log('Network error fetching shared link data');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n warn('Error sending shared link request:', e);\n callback(null);\n }\n }\n\n /**\n * Fetch document metadata from Bravais shared API to get GUIDs\n * PRIMARY endpoint: /api/shared/{token}/documents (does NOT require auth)\n * FALLBACK endpoint: /api/v3/documents/{documentId} (requires auth)\n */\n function fetchDocumentMetadata(documentIdOrToken, callback) {\n if (documentApiFetched) {\n callback(documentApiData);\n return;\n }\n\n var coreUrl = detectCoreUrl();\n if (!coreUrl) {\n log('No Bravais core URL detected for document metadata fetch');\n documentApiFetched = true;\n callback(null);\n return;\n }\n\n // Determine which endpoint to use\n // If we have a shared link token, use /api/shared/{token}/documents (no auth required)\n // This is the same endpoint Xyleme Cloud Player uses\n var sharedToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;\n var apiUrl;\n\n if (sharedToken) {\n apiUrl = coreUrl + '/api/shared/' + sharedToken + '/documents';\n log('Fetching document via shared API (no auth required):', apiUrl);\n } else if (documentIdOrToken) {\n apiUrl = coreUrl + '/api/v3/documents/' + documentIdOrToken;\n log('Fetching document via v3 API (requires auth):', apiUrl);\n } else {\n log('No shared token or document ID available for metadata fetch');\n documentApiFetched = true;\n callback(null);\n return;\n }\n\n var xhr = new XMLHttpRequest();\n xhr.open('GET', apiUrl, true);\n xhr.withCredentials = true;\n xhr.setRequestHeader('Accept', 'application/json');\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n documentApiFetched = true;\n log('Document API response status:', xhr.status);\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n documentApiData = JSON.parse(xhr.responseText);\n log('Document metadata received:', documentApiData);\n // Validate that we got the required fields\n if (documentApiData.guid) {\n log('Document GUID found:', documentApiData.guid);\n } else {\n warn('Document API response missing guid field!', documentApiData);\n }\n if (documentApiData.latestVersion && documentApiData.latestVersion.guid) {\n log('Version GUID found:', documentApiData.latestVersion.guid);\n } else {\n warn('Document API response missing latestVersion.guid field!', documentApiData);\n }\n callback(documentApiData);\n } catch (e) {\n warn('Error parsing document metadata:', e);\n callback(null);\n }\n } else {\n // 401/403 is expected - API requires auth that may not be available from iframe\n if (xhr.status === 401 || xhr.status === 403) {\n log('Document API requires auth (expected), will use fallback');\n } else {\n log('Document metadata fetch failed. Status:', xhr.status);\n }\n callback(null);\n }\n }\n };\n\n xhr.onerror = function() {\n documentApiFetched = true;\n log('Network error fetching document metadata');\n callback(null);\n };\n\n try {\n xhr.send();\n } catch (e) {\n documentApiFetched = true;\n warn('Error sending document request:', e);\n callback(null);\n }\n }\n\n /**\n * Update course info with data from document API\n */\n function updateCourseInfoFromApi(docData) {\n if (!docData || !LRS.courseInfo) return;\n\n // Document GUID\n if (docData.guid && !LRS.courseInfo.guid) {\n LRS.courseInfo.guid = docData.guid;\n LRS.courseInfo.id = 'http://xyleme.com/bravais/document/' + docData.guid;\n log('Updated course guid from API:', docData.guid);\n }\n\n // Version GUID from latestVersion\n if (docData.latestVersion && docData.latestVersion.guid && !LRS.courseInfo.versionGuid) {\n LRS.courseInfo.versionGuid = docData.latestVersion.guid;\n log('Updated version guid from API:', docData.latestVersion.guid);\n }\n\n // Other metadata\n if (docData.name && LRS.courseInfo.title === 'Rise Course') {\n LRS.courseInfo.title = docData.name;\n }\n if (docData.format) {\n LRS.courseInfo.format = docData.format;\n }\n if (docData.resourceType) {\n LRS.courseInfo.resourceType = docData.resourceType;\n }\n\n log('Course info updated from API:', LRS.courseInfo);\n }\n\n /**\n * Extract Bravais document ID from URL\n * Pattern: /api/shared/.../files/{documentId}/scormcontent/\n */\n function extractBravaisDocumentId() {\n var urlsToCheck = [window.location.href];\n\n try {\n var win = window;\n for (var i = 0; i < 5; i++) {\n if (win.parent && win.parent !== win) {\n win = win.parent;\n try {\n urlsToCheck.push(win.location.href);\n } catch (e) { break; }\n } else { break; }\n }\n } catch (e) {}\n\n for (var j = 0; j < urlsToCheck.length; j++) {\n var url = urlsToCheck[j];\n // Match: /files/{documentId}/scormcontent/ or /files/{documentId}/\n var match = url.match(/\\\\/files\\\\/([0-9]+)(?:\\\\/|$)/);\n if (match) {\n log('Extracted Bravais document ID:', match[1]);\n return match[1];\n }\n }\n return null;\n }\n\n /**\n * Extract course metadata from SCORM launch data\n * Bravais stores course info in base64 JSON in cmi.launch_data\n */\n function extractCourseFromLaunchData() {\n if (!LRS.scormApi) return null;\n\n try {\n var launchData = null;\n if (LRS.scormApiType === '2004') {\n launchData = LRS.scormApi.GetValue('cmi.launch_data');\n } else {\n launchData = LRS.scormApi.LMSGetValue('cmi.launch_data');\n }\n\n if (launchData) {\n try {\n var decoded = atob(launchData);\n var data = JSON.parse(decoded);\n log('Launch data for course info:', Object.keys(data));\n log('Launch data contents:', JSON.stringify(data));\n\n return {\n // Document/Course GUIDs\n guid: data.guid || data.courseGuid || data.documentGuid,\n versionGuid: data.versionGuid || data.courseVersionGuid || data.documentVersionGuid,\n // Numeric IDs\n documentId: data.documentId || data.courseId || data.cdsId,\n versionId: data.versionId || data.courseVersionId,\n // Metadata\n title: data.title || data.courseTitle || data.documentTitle || data.name,\n description: data.description || data.courseDescription,\n format: data.format || 'SCORM by Rise',\n resourceType: data.resourceType || 'Course',\n language: data.language || '',\n version: data.version || 1,\n cpv: data.cpv,\n // Shared link info\n sharedLinkName: data.sharedLinkName,\n sharedLinkToken: data.sharedLinkToken\n };\n } catch (e) {\n log('Launch data not JSON:', e.message);\n }\n }\n } catch (e) {\n log('Error reading launch data for course:', e.message);\n }\n return null;\n }\n\n /**\n * Extract course title from getCourseTitle() if it exists (from preloadIntegrity.js)\n */\n function extractCourseTitleFromPreload() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n if (typeof win.getCourseTitle === 'function') {\n var title = win.getCourseTitle();\n if (title && title !== 'Rise Course') {\n log('Found course title from getCourseTitle() at level', level, ':', title);\n return title;\n }\n }\n } catch (e) {}\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {}\n return null;\n }\n\n /**\n * Try to extract document data from Xyleme Cloud Player's internal state\n * The Cloud Player stores this after fetching /api/shared/{token}/documents\n */\n function extractFromXylemeCloudPlayer() {\n try {\n var win = window;\n for (var level = 0; level < 10; level++) {\n try {\n // Check for Xyleme Cloud Player's document data in various locations\n // The player stores data in multiple places depending on version\n\n // Check for CdsDataService data\n if (win._cdsDataService && win._cdsDataService.documentData) {\n var docData = win._cdsDataService.documentData;\n log('Found document data in _cdsDataService at level', level);\n return extractGuidsFromDocumentData(docData);\n }\n\n // Check for window.documentData\n if (win.documentData && win.documentData.guid) {\n log('Found document data in window.documentData at level', level);\n return extractGuidsFromDocumentData(win.documentData);\n }\n\n // Check for PlayerIntegration's internal data\n if (win.playerIntegration && win.playerIntegration._documentData) {\n log('Found document data in playerIntegration at level', level);\n return extractGuidsFromDocumentData(win.playerIntegration._documentData);\n }\n\n // Check for __xyleme_data\n if (win.__xyleme_data && win.__xyleme_data.document) {\n log('Found document data in __xyleme_data at level', level);\n return extractGuidsFromDocumentData(win.__xyleme_data.document);\n }\n\n // Check for documentVersionData which has the versionGuid\n if (win.documentVersionData) {\n log('Found documentVersionData at level', level);\n return {\n versionGuid: win.documentVersionData.guid || win.documentVersionData.versionGuid,\n documentId: win.documentVersionData.documentId\n };\n }\n\n } catch (e) {}\n\n if (win.parent && win.parent !== win) {\n win = win.parent;\n } else {\n break;\n }\n }\n } catch (e) {}\n return null;\n }\n\n function extractGuidsFromDocumentData(data) {\n if (!data) return null;\n\n var result = {};\n\n // Document GUID\n if (data.guid) result.guid = data.guid;\n if (data.documentGuid) result.guid = data.documentGuid;\n\n // Version GUID - usually in latestVersion or currentVersion\n if (data.latestVersion && data.latestVersion.guid) {\n result.versionGuid = data.latestVersion.guid;\n }\n if (data.currentVersion && data.currentVersion.guid) {\n result.versionGuid = data.currentVersion.guid;\n }\n if (data.versionGuid) result.versionGuid = data.versionGuid;\n\n // Numeric IDs\n if (data.id) result.documentId = data.id;\n if (data.documentId) result.documentId = data.documentId;\n if (data.cdsId) result.documentId = data.cdsId;\n\n // Title\n if (data.name) result.title = data.name;\n if (data.title) result.title = data.title;\n\n log('Extracted GUIDs from Cloud Player:', result);\n return Object.keys(result).length > 0 ? result : null;\n }\n\n function extractCourseInfo() {\n var info = {\n // Xyleme IRI format: http://xyleme.com/bravais/document/{guid}\n id: null,\n // GUIDs\n guid: null, // Document GUID (fe57e5f8-c46b-4b2c-a0c7-884af6b0001d)\n versionGuid: null, // Version GUID (4c884a96-8da8-472b-8d59-c1d2bce0008f)\n // Numeric IDs\n documentId: null, // Bravais document CDS ID (numeric)\n versionId: null, // Version number\n // Metadata\n title: 'Rise Course',\n description: '',\n format: 'SCORM by Rise',\n resourceType: 'Course',\n language: '',\n version: 1,\n cpv: null,\n // Shared link\n sharedLinkToken: null,\n sharedLinkName: null,\n // Tenant info\n homepage: COURSE_HOMEPAGE || window.location.origin,\n tenantHomepage: null // https://{tenant}.bravais.com format\n };\n\n // 1. Extract shared link token and document ID from URL\n info.sharedLinkToken = extractSharedLinkToken();\n info.documentId = extractBravaisDocumentId();\n\n // 2. Detect tenant homepage from URL\n var tenantMatch = window.location.href.match(/core-([a-zA-Z0-9_-]+)\\\\.bravais\\\\.com/);\n if (tenantMatch) {\n info.tenantHomepage = 'https://' + tenantMatch[1] + '.bravais.com';\n log('Detected tenant homepage:', info.tenantHomepage);\n }\n\n // 3. Extract from SCORM launch data (most reliable for Bravais)\n var launchInfo = extractCourseFromLaunchData();\n if (launchInfo) {\n info.guid = launchInfo.guid || info.guid;\n info.versionGuid = launchInfo.versionGuid || info.versionGuid;\n info.title = launchInfo.title || info.title;\n info.versionId = launchInfo.versionId || info.versionId;\n info.documentId = launchInfo.documentId || info.documentId;\n info.description = launchInfo.description || info.description;\n info.format = launchInfo.format || info.format;\n info.resourceType = launchInfo.resourceType || info.resourceType;\n info.language = launchInfo.language || info.language;\n info.version = launchInfo.version || info.version;\n info.cpv = launchInfo.cpv || info.cpv;\n info.sharedLinkToken = launchInfo.sharedLinkToken || info.sharedLinkToken;\n info.sharedLinkName = launchInfo.sharedLinkName || info.sharedLinkName;\n }\n\n // 3b. Try to extract GUIDs from Xyleme Cloud Player's internal data\n // This is more reliable than API calls which may fail with 401\n if (!info.guid || !info.versionGuid) {\n var cloudPlayerData = extractFromXylemeCloudPlayer();\n if (cloudPlayerData) {\n info.guid = cloudPlayerData.guid || info.guid;\n info.versionGuid = cloudPlayerData.versionGuid || info.versionGuid;\n info.documentId = cloudPlayerData.documentId || info.documentId;\n info.title = cloudPlayerData.title || info.title;\n log('Updated course info from Cloud Player:', {\n guid: info.guid,\n versionGuid: info.versionGuid,\n documentId: info.documentId\n });\n }\n }\n\n // 4. Try getCourseTitle() from preloadIntegrity.js\n var preloadTitle = extractCourseTitleFromPreload();\n if (preloadTitle) {\n info.title = preloadTitle;\n }\n\n // 5. Try Rise's data\n try {\n if (window.__RISE_COURSE_DATA__) {\n info.title = info.title === 'Rise Course' ?\n (window.__RISE_COURSE_DATA__.title || info.title) : info.title;\n info.guid = window.__RISE_COURSE_DATA__.id || info.guid;\n }\n\n var metaTitle = document.querySelector('meta[property=\"og:title\"]');\n if (metaTitle && metaTitle.content && info.title === 'Rise Course') {\n info.title = metaTitle.content;\n }\n\n var metaDesc = document.querySelector('meta[property=\"og:description\"]');\n if (metaDesc && metaDesc.content) {\n info.description = metaDesc.content;\n }\n } catch (e) {}\n\n // 6. Check document title\n if (info.title === 'Rise Course' && document.title && document.title !== 'Rise Course') {\n info.title = document.title;\n }\n\n // 7. Get from PA metadata if available\n if (window.pa_patcher && window.pa_patcher.courseMetadata) {\n var meta = window.pa_patcher.courseMetadata;\n info.documentId = meta.documentId || meta.courseId || info.documentId;\n info.guid = meta.guid || meta.courseGuid || info.guid;\n info.versionGuid = meta.versionGuid || info.versionGuid;\n info.title = meta.title || meta.courseTitle || info.title;\n info.versionId = meta.versionId || info.versionId;\n }\n\n // 8. Build the course ID in Xyleme IRI format\n // Native format: http://xyleme.com/bravais/document/{guid}\n if (info.guid) {\n info.id = 'http://xyleme.com/bravais/document/' + info.guid;\n } else if (info.documentId) {\n // Fallback: use document ID\n info.id = 'http://xyleme.com/bravais/document/' + info.documentId;\n } else {\n info.id = window.location.href.split('#')[0].split('?')[0];\n }\n\n // Build shared link name if not provided\n if (!info.sharedLinkName && info.title && info.sharedLinkToken) {\n info.sharedLinkName = info.title + ' - LMS Thin Pack';\n }\n\n LRS.courseInfo = info;\n log('Course info extracted:', info);\n return info;\n }\n\n // ========================================================================\n // 4b. LESSON/SECTION NAME EXTRACTION\n // ========================================================================\n\n /**\n * Get the current lesson/section information from Rise DOM\n * Returns { id, name, lessonIndex, sectionName }\n */\n function getCurrentLessonInfo() {\n var lessonInfo = {\n id: window.location.hash || '#/',\n name: null,\n lessonIndex: null,\n sectionName: null\n };\n\n try {\n // 1. Try to get from Rise's navigation sidebar\n // Rise shows active lesson with specific classes\n var activeLesson = document.querySelector(\n '.sidebar__link--current, ' +\n '.sidebar-lesson--active, ' +\n '.nav-sidebar__lesson--active, ' +\n '[class*=\"sidebar\"] [class*=\"current\"], ' +\n '[class*=\"sidebar\"] [class*=\"active\"], ' +\n '[aria-current=\"page\"], ' +\n '[aria-current=\"true\"]'\n );\n\n if (activeLesson) {\n var lessonTitle = activeLesson.querySelector(\n '.sidebar__link-text, ' +\n '.lesson-title, ' +\n '.sidebar-lesson__title, ' +\n '[class*=\"title\"], ' +\n '[class*=\"label\"]'\n );\n if (lessonTitle) {\n lessonInfo.name = lessonTitle.textContent.trim();\n } else {\n lessonInfo.name = activeLesson.textContent.trim();\n }\n\n // Try to get the section/module name (parent group)\n var section = activeLesson.closest(\n '.sidebar__section, ' +\n '.sidebar-section, ' +\n '[class*=\"section\"], ' +\n '[class*=\"module\"]'\n );\n if (section) {\n var sectionTitle = section.querySelector(\n '.sidebar__section-title, ' +\n '.section-title, ' +\n '[class*=\"section-title\"], ' +\n '[class*=\"module-title\"], ' +\n 'h2, h3'\n );\n if (sectionTitle) {\n lessonInfo.sectionName = sectionTitle.textContent.trim();\n }\n }\n }\n\n // 2. Try Rise's header/breadcrumb\n if (!lessonInfo.name) {\n var headerTitle = document.querySelector(\n '.lesson-header__title, ' +\n '.lesson__title, ' +\n '.content-header__title, ' +\n '[class*=\"lesson\"] [class*=\"header\"] [class*=\"title\"], ' +\n '.blocks-lesson-header__title'\n );\n if (headerTitle) {\n lessonInfo.name = headerTitle.textContent.trim();\n }\n }\n\n // 3. Try breadcrumbs\n if (!lessonInfo.name || !lessonInfo.sectionName) {\n var breadcrumbs = document.querySelectorAll(\n '.breadcrumb__item, ' +\n '.breadcrumbs li, ' +\n '[class*=\"breadcrumb\"] span, ' +\n '[class*=\"breadcrumb\"] a'\n );\n if (breadcrumbs.length >= 2) {\n // Usually: Course > Section > Lesson\n if (!lessonInfo.sectionName && breadcrumbs.length >= 2) {\n lessonInfo.sectionName = breadcrumbs[breadcrumbs.length - 2].textContent.trim();\n }\n if (!lessonInfo.name) {\n lessonInfo.name = breadcrumbs[breadcrumbs.length - 1].textContent.trim();\n }\n }\n }\n\n // 4. Try Rise's internal data\n if (window.__RISE_COURSE_DATA__ && window.__RISE_COURSE_DATA__.lessons) {\n var lessonId = lessonInfo.id.replace('#/lessons/', '').replace('#/', '');\n var lessons = window.__RISE_COURSE_DATA__.lessons;\n for (var i = 0; i < lessons.length; i++) {\n if (lessons[i].id === lessonId || lessons[i].slug === lessonId) {\n lessonInfo.name = lessons[i].title || lessonInfo.name;\n lessonInfo.lessonIndex = i;\n break;\n }\n }\n }\n\n // 5. Clean up - remove any \"Home\" or generic names if we're on a real lesson\n if (lessonInfo.name) {\n lessonInfo.name = lessonInfo.name.replace(/^\\\\\\\\d+\\\\\\\\.\\\\\\\\s*/, ''); // Remove leading numbers \"1. \"\n if (lessonInfo.name.length > 200) {\n lessonInfo.name = lessonInfo.name.substring(0, 200) + '...';\n }\n }\n\n if (lessonInfo.sectionName) {\n lessonInfo.sectionName = lessonInfo.sectionName.replace(/^\\\\\\\\d+\\\\\\\\.\\\\\\\\s*/, '');\n if (lessonInfo.sectionName.length > 200) {\n lessonInfo.sectionName = lessonInfo.sectionName.substring(0, 200) + '...';\n }\n }\n\n } catch (e) {\n warn('Error extracting lesson info:', e);\n }\n\n log('Current lesson info:', lessonInfo);\n return lessonInfo;\n }\n\n /**\n * Cache the current lesson info (updates on navigation)\n */\n var cachedLessonInfo = null;\n var cachedLessonHash = null;\n\n function getCachedLessonInfo() {\n var currentHash = window.location.hash;\n if (cachedLessonHash !== currentHash) {\n cachedLessonInfo = getCurrentLessonInfo();\n cachedLessonHash = currentHash;\n }\n return cachedLessonInfo || getCurrentLessonInfo();\n }\n\n // ========================================================================\n // 5. XAPI STATEMENT BUILDING\n // ========================================================================\n\n /**\n * Build an xAPI statement with COURSE as the main object\n * This ensures ALL statements show up when searching by document name in Bravais Analytics\n *\n * The activity type (video, assessment, interaction, etc.) is stored in object.definition.type\n * The specific activity details (media src, page ID, etc.) are stored in object.definition.extensions\n */\n function buildStatement(verb, activityType, activityDetails, result, context) {\n // ALWAYS use course as the main object\n var courseObj = buildCourseActivityObject();\n if (!courseObj) {\n // Fallback if course info not available\n courseObj = {\n objectType: 'Activity',\n id: window.location.href.split('#')[0].split('?')[0],\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': document.title || 'Rise Course' }\n }\n };\n }\n\n // Override the activity type based on the statement being sent\n // This allows filtering by type (video, assessment, interaction) in Analytics\n if (activityType) {\n courseObj.definition.type = activityType;\n\n // Also update resourceType extension to human-readable form for Bravais Type column\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n var resourceTypeMap = {\n 'https://w3id.org/xapi/video/activity-type/video': 'Video',\n 'https://w3id.org/xapi/audio/activity-type/audio': 'Audio',\n 'http://adlnet.gov/expapi/activities/media': 'Media',\n 'http://adlnet.gov/expapi/activities/assessment': 'Assessment',\n 'http://adlnet.gov/expapi/activities/question': 'Question',\n 'http://adlnet.gov/expapi/activities/interaction': 'Interaction',\n 'http://adlnet.gov/expapi/activities/lesson': 'Lesson',\n 'http://adlnet.gov/expapi/activities/module': 'Module',\n 'http://adlnet.gov/expapi/activities/course': 'Course',\n 'http://xyleme.com/bravais/activities/document': 'Course'\n };\n courseObj.definition.extensions['resourceType'] = resourceTypeMap[activityType] || 'Course';\n }\n\n // Add activity-specific details to extensions\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n\n if (activityDetails) {\n // Merge activity details into extensions\n for (var key in activityDetails) {\n if (activityDetails.hasOwnProperty(key)) {\n courseObj.definition.extensions[key] = activityDetails[key];\n }\n }\n }\n\n var statement = {\n id: generateUUID(),\n actor: LRS.actor || extractActor(),\n verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,\n object: courseObj,\n timestamp: new Date().toISOString()\n };\n\n if (result) {\n statement.result = result;\n }\n\n // Build context with all Xyleme extensions using unified builder\n statement.context = buildXylemeContext(context);\n\n return statement;\n }\n\n /**\n * Legacy buildStatement for backward compatibility\n * Routes to new signature\n */\n function buildStatementLegacy(verb, object, result, context) {\n // Extract type and details from the legacy object\n var activityType = object && object.definition ? object.definition.type : null;\n var activityDetails = {};\n\n if (object) {\n activityDetails.activityId = object.id;\n if (object.definition) {\n if (object.definition.name && object.definition.name['en-US']) {\n activityDetails.activityName = object.definition.name['en-US'];\n }\n if (object.definition.description && object.definition.description['en-US']) {\n activityDetails.activityDescription = object.definition.description['en-US'];\n }\n if (object.definition.interactionType) {\n activityDetails.interactionType = object.definition.interactionType;\n }\n // Copy any existing extensions\n if (object.definition.extensions) {\n for (var key in object.definition.extensions) {\n if (object.definition.extensions.hasOwnProperty(key)) {\n activityDetails[key] = object.definition.extensions[key];\n }\n }\n }\n }\n }\n\n return buildStatement(verb, activityType, activityDetails, result, context);\n }\n\n /**\n * Build the course activity object in Xyleme native format\n * Object ID: http://xyleme.com/bravais/document/{guid}\n * Object type: http://xyleme.com/bravais/activities/document\n */\n function buildCourseActivityObject() {\n if (!LRS.courseInfo) return null;\n\n // Warn if we don't have the required GUIDs for Bravais aggregation\n if (!LRS.courseInfo.guid) {\n warn('Missing document GUID - statement may fail Bravais aggregation. Using numeric ID:', LRS.courseInfo.documentId);\n }\n if (!LRS.courseInfo.versionGuid) {\n warn('Missing version GUID - statement may fail Bravais aggregation (document_version_id will be null)');\n }\n\n var obj = {\n objectType: 'Activity',\n id: LRS.courseInfo.id,\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': LRS.courseInfo.title }\n }\n };\n\n // Add Xyleme-format extensions\n obj.definition.extensions = {};\n\n // Version ID in Xyleme IRI format - REQUIRED for Bravais aggregation\n if (LRS.courseInfo.versionGuid) {\n obj.definition.extensions['versionId'] =\n 'http://xyleme.com/bravais/document.version/' + LRS.courseInfo.versionGuid;\n } else {\n // Log error - this will cause aggregation failure\n warn('versionId extension not set - this statement will fail Bravais aggregation!');\n }\n\n // Format (e.g., \"SCORM by Rise\")\n obj.definition.extensions['format'] = LRS.courseInfo.format || 'SCORM by Rise';\n\n // Language\n obj.definition.extensions['language'] = LRS.courseInfo.language || '';\n\n // Version number\n obj.definition.extensions['version'] = LRS.courseInfo.version || 1;\n\n // Document CDS ID (numeric)\n if (LRS.courseInfo.documentId) {\n obj.definition.extensions['documentCdsId'] =\n parseInt(LRS.courseInfo.documentId, 10) || LRS.courseInfo.documentId;\n }\n\n // Resource type\n obj.definition.extensions['resourceType'] = LRS.courseInfo.resourceType || 'Course';\n\n return obj;\n }\n\n /**\n * Build the parent course activity for context.contextActivities.parent\n * This links sub-activities (media, pages, interactions) to the course document\n * CRITICAL: This is what allows statements to show up when searching by document name in Analytics\n */\n function buildParentCourseActivity() {\n if (!LRS.courseInfo || !LRS.courseInfo.id) return null;\n\n var parent = {\n id: LRS.courseInfo.id,\n objectType: 'Activity',\n definition: {\n type: 'http://xyleme.com/bravais/activities/document',\n name: { 'en-US': LRS.courseInfo.title || 'Rise Course' }\n }\n };\n\n // Add documentCdsId - this is what Bravais uses for document search\n if (LRS.courseInfo.documentId) {\n parent.definition.extensions = {\n documentCdsId: parseInt(LRS.courseInfo.documentId, 10) || LRS.courseInfo.documentId\n };\n }\n\n return parent;\n }\n\n // ========================================================================\n // XYLEME NATIVE FORMAT BUILDERS\n // ========================================================================\n\n /**\n * Build context object in Xyleme native format\n * Includes all required context extensions for Bravais correlation\n */\n function buildXylemeContext(additionalContext) {\n var ctx = additionalContext || {};\n\n // Platform: \"Cloud Player\" for Xyleme compatibility\n ctx.platform = 'Cloud Player';\n\n ctx.extensions = ctx.extensions || {};\n\n if (LRS.courseInfo) {\n // Document URI (required for document correlation)\n if (LRS.courseInfo.guid) {\n ctx.extensions['document'] = 'http://xyleme.com/bravais/document/' + LRS.courseInfo.guid;\n } else if (LRS.courseInfo.documentId) {\n ctx.extensions['document'] = 'http://xyleme.com/bravais/document/' + LRS.courseInfo.documentId;\n }\n\n // Document version URI\n if (LRS.courseInfo.versionGuid) {\n ctx.extensions['documentVersion'] = 'http://xyleme.com/bravais/document.version/' + LRS.courseInfo.versionGuid;\n }\n\n // Shared link info\n if (LRS.courseInfo.sharedLinkToken) {\n ctx.extensions['sharedLinkToken'] = LRS.courseInfo.sharedLinkToken;\n }\n if (LRS.courseInfo.sharedLinkName) {\n ctx.extensions['sharedLinkName'] = LRS.courseInfo.sharedLinkName;\n }\n }\n\n // Course attempt ID (unique per session)\n ctx.extensions['courseAttemptId'] = LRS.courseAttemptId;\n\n // Publish index (default to 1)\n ctx.extensions['publishIndex'] = (LRS.courseInfo && LRS.courseInfo.publishIndex) ? LRS.courseInfo.publishIndex : 1;\n\n // Xyleme schema version and PII flag\n ctx.extensions['http://xyleme.com/bravais/extensions/statement-schema'] = 'xyleme_10';\n ctx.extensions['http://xyleme.com/bravais/extensions/protect_pii'] = false;\n\n // Add parent course activity for document-level aggregation in Bravais\n // This allows statements with activity-specific objects to still be found\n // when searching by document name in Analytics\n var parentActivity = buildParentCourseActivity();\n if (parentActivity) {\n ctx.contextActivities = ctx.contextActivities || {};\n ctx.contextActivities.parent = [parentActivity];\n }\n\n return ctx;\n }\n\n /**\n * Build activity object for page/topic-level statements\n * Uses Xyleme page URI format instead of course/document\n */\n function buildPageActivityObject(pageInfo) {\n var pageGuid = pageInfo.pageGuid || pageInfo.id || generateUUID();\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/page/' + pageGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.page,\n name: { 'en-US': pageInfo.name || pageInfo.title || 'Page' },\n extensions: {\n resourceType: pageInfo.resourceType || RESOURCE_TYPES[pageInfo.type] || 'Topic'\n }\n }\n };\n }\n\n /**\n * Build activity object for assessments in Xyleme format\n */\n function buildAssessmentActivityObject(assessmentInfo) {\n var assessmentGuid = assessmentInfo.assessmentGuid || assessmentInfo.id || generateUUID();\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/assessment/' + assessmentGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.assessment,\n name: { 'en-US': assessmentInfo.title || 'Assessment' },\n extensions: {\n resourceType: 'Assessment'\n }\n }\n };\n }\n\n /**\n * Build activity object for questions in Xyleme format\n * Name format: \"Assessment Name - Q#: Question text...\"\n */\n function buildQuestionActivityObject(questionInfo) {\n var questionGuid = questionInfo.questionGuid || questionInfo.id || generateUUID();\n\n // Build human-readable display name\n var displayName = (questionInfo.assessmentName || 'Knowledge Check');\n if (questionInfo.questionNumber) {\n displayName += ' - Q' + questionInfo.questionNumber;\n }\n var questionText = questionInfo.text || questionInfo.questionText || '';\n if (questionText.length > 50) {\n displayName += ': ' + questionText.substring(0, 47) + '...';\n } else if (questionText.length > 0) {\n displayName += ': ' + questionText;\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/question/' + questionGuid,\n definition: {\n type: XYLEME_ACTIVITY_TYPES.question,\n name: { 'en-US': displayName },\n description: { 'en-US': questionText || 'Question' },\n extensions: {\n resourceType: 'Question',\n questionNumber: questionInfo.questionNumber || null\n }\n }\n };\n }\n\n /**\n * Format seconds into MM:SS or HH:MM:SS format\n */\n function formatMediaTime(seconds) {\n if (typeof seconds !== 'number' || isNaN(seconds)) return '0:00';\n var totalSeconds = Math.floor(seconds);\n var hours = Math.floor(totalSeconds / 3600);\n var minutes = Math.floor((totalSeconds % 3600) / 60);\n var secs = totalSeconds % 60;\n\n if (hours > 0) {\n return hours + ':' + (minutes < 10 ? '0' : '') + minutes + ':' + (secs < 10 ? '0' : '') + secs;\n }\n return minutes + ':' + (secs < 10 ? '0' : '') + secs;\n }\n\n /**\n * Build activity object for media (video/audio) with human-readable name\n * Name format varies by action:\n * - played: \"Video: Title (started at 1:23)\"\n * - paused: \"Video: Title (paused at 2:45)\"\n * - completed: \"Video: Title (completed)\"\n */\n function buildMediaActivityObject(mediaInfo) {\n var mediaGuid = mediaInfo.mediaGuid || generateUUID();\n var mediaType = mediaInfo.type === 'audio' ? ACTIVITY_TYPES.audio : ACTIVITY_TYPES.video;\n var resourceType = mediaInfo.type === 'audio' ? 'Audio' : 'Video';\n\n // Build human-readable display name\n var baseName = mediaInfo.name || '';\n if (!baseName || baseName === 'video' || baseName === 'audio' || baseName === 'Media') {\n baseName = mediaInfo.lessonName || 'Media';\n }\n\n // Add time context based on action\n var displayName = resourceType + ': ' + baseName;\n var currentTime = mediaInfo.currentTime || 0;\n var action = mediaInfo.action;\n\n if (action === 'play' || action === 'played') {\n displayName += ' (started at ' + formatMediaTime(currentTime) + ')';\n } else if (action === 'pause' || action === 'paused') {\n displayName += ' (paused at ' + formatMediaTime(currentTime) + ')';\n } else if (action === 'completed' || action === 'complete') {\n displayName += ' (completed)';\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/media/' + mediaGuid,\n definition: {\n type: mediaType,\n name: { 'en-US': displayName },\n extensions: {\n resourceType: resourceType,\n mediaSrc: mediaInfo.src || '',\n mediaType: mediaInfo.type || 'video',\n duration: mediaInfo.duration || 0\n }\n }\n };\n }\n\n /**\n * Build activity object for interactions (tabs, accordions, flashcards, etc.)\n * Name format: \"Tab: Label\" or \"Accordion: Label\"\n */\n function buildInteractionActivityObject(interactionInfo) {\n var typeLabels = {\n 'tab': 'Tab',\n 'accordion': 'Accordion',\n 'flashcard': 'Flashcard',\n 'hotspot': 'Hotspot',\n 'process-step': 'Process Step',\n 'process': 'Process Step',\n 'sorting': 'Sorting Activity',\n 'button': 'Button',\n 'labeled-graphic': 'Labeled Graphic',\n 'timeline': 'Timeline',\n 'scenario': 'Scenario',\n 'checklist': 'Checklist',\n 'marker': 'Marker'\n };\n\n var interactionType = interactionInfo.type || interactionInfo.interactionType || 'interaction';\n var typeLabel = typeLabels[interactionType] || 'Interaction';\n\n // Build human-readable display name\n var displayName = interactionInfo.name;\n if (!displayName) {\n displayName = typeLabel + ': ' + (interactionInfo.label || interactionInfo.id || 'Item');\n }\n\n return {\n objectType: 'Activity',\n id: 'http://xyleme.com/bravais/interaction/' + (interactionInfo.id || generateUUID()),\n definition: {\n type: ACTIVITY_TYPES.interaction,\n name: { 'en-US': displayName },\n extensions: {\n resourceType: 'Interaction',\n interactionType: interactionType\n }\n }\n };\n }\n\n /**\n * Build xAPI statement in Xyleme native format\n * Supports page-level objects while maintaining document context\n */\n function buildStatementXyleme(verb, activityObject, result, additionalContext) {\n var statement = {\n id: generateUUID(),\n actor: LRS.actor || extractActor(),\n verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,\n object: activityObject,\n timestamp: new Date().toISOString()\n };\n\n if (result) {\n statement.result = result;\n }\n\n // Build context with all Xyleme extensions\n statement.context = buildXylemeContext(additionalContext);\n\n return statement;\n }\n\n function buildActivityObject(id, type, name, description) {\n var obj = {\n objectType: 'Activity',\n id: id,\n definition: {\n type: type || ACTIVITY_TYPES.lesson\n }\n };\n\n if (name) {\n obj.definition.name = { 'en-US': name };\n }\n if (description) {\n obj.definition.description = { 'en-US': description };\n }\n\n return obj;\n }\n\n // ========================================================================\n // 6. STATEMENT SENDING\n // ========================================================================\n\n function sendStatement(statement) {\n log('Sending statement:', statement.verb.display['en-US'] || statement.verb.id, statement);\n\n // Log first statement visibly (not gated by DEBUG) for production diagnostics\n if (LRS.stats.statementsSent === 0 && LRS.stats.statementsFailed === 0 && LRS.stats.statementsQueued === 0) {\n if (window.console && window.console.info) {\n var verb = statement.verb.display ? (statement.verb.display['en-US'] || statement.verb.id) : statement.verb.id;\n console.info('[PA-LRS] First statement: verb=' + verb +\n ', endpoint=' + (LRS_ENDPOINT || 'NONE') +\n ', proxy=' + (LRS_PROXY_ENDPOINT ? 'yes' : 'no') +\n ', mode=' + LRS.mode);\n }\n }\n\n // Store in event log\n LRS.eventLog.push({\n statement: statement,\n timestamp: new Date().toISOString(),\n mode: LRS.mode,\n lrsEndpoint: LRS_ENDPOINT\n });\n\n // ALWAYS prefer direct LRS when endpoint is available\n // This ensures all our detailed tracking (media, interactions, etc.) goes to the LRS\n // PlayerIntegration is used only for SCORM state, not for xAPI statements\n if (LRS_ENDPOINT && LRS_ENDPOINT.length > 0) {\n log('Sending via directLRS:', LRS_ENDPOINT);\n return sendViaDirectLRS(statement);\n } else if (LRS.mode === 'playerIntegration' && LRS.integration) {\n // Fallback to PlayerIntegration only if no direct LRS endpoint\n log('No LRS endpoint, falling back to PlayerIntegration');\n return sendViaPlayerIntegration(statement);\n } else {\n // SCORM-only or offline mode - just log\n log('Statement logged (no LRS):', statement.verb.display['en-US']);\n LRS.stats.statementsQueued++;\n return Promise.resolve({ logged: true });\n }\n }\n\n function sendViaPlayerIntegration(statement) {\n // Map xAPI verb to PlayerIntegration method\n var verbId = statement.verb.id;\n var data = {\n ...statement.object.definition,\n id: statement.object.id,\n timestamp: statement.timestamp\n };\n\n if (statement.result) {\n data.result = statement.result;\n }\n\n try {\n if (verbId.indexOf('played') > -1 || verbId.indexOf('paused') > -1 || verbId.indexOf('completed') > -1) {\n if (statement.object.definition && statement.object.definition.type &&\n (statement.object.definition.type.indexOf('video') > -1 || statement.object.definition.type.indexOf('audio') > -1)) {\n LRS.integration.mediaPlayed(data);\n }\n } else if (verbId.indexOf('experienced') > -1 || verbId.indexOf('viewed') > -1 || verbId.indexOf('launched') > -1) {\n LRS.integration.contentOpened(data);\n } else if (verbId.indexOf('attempted') > -1) {\n LRS.integration.assessmentStarted(data);\n } else if (verbId.indexOf('passed') > -1 || verbId.indexOf('failed') > -1) {\n LRS.integration.assessmentEnded(data);\n } else if (verbId.indexOf('answered') > -1) {\n LRS.integration.questionAnswered(data);\n } else if (verbId.indexOf('interacted') > -1) {\n LRS.integration.sendCustomStatement({ verb: 'interacted', data: data });\n } else {\n LRS.integration.sendCustomStatement({ verb: verbId, data: data });\n }\n\n LRS.stats.statementsSent++;\n return Promise.resolve({ sent: true, via: 'playerIntegration' });\n\n } catch (e) {\n warn('Error sending via PlayerIntegration:', e);\n LRS.stats.statementsFailed++;\n return Promise.reject(e);\n }\n }\n\n function sendViaDirectLRS(statement) {\n // Use proxy endpoint if configured, otherwise direct LRS\n var useProxy = LRS_PROXY_ENDPOINT && LRS_PROXY_ENDPOINT.length > 0;\n var endpoint = useProxy ? LRS_PROXY_ENDPOINT : LRS_ENDPOINT;\n\n if (!endpoint || endpoint.length === 0) {\n warn('No LRS endpoint available');\n LRS.stats.statementsFailed++;\n return Promise.reject(new Error('No LRS endpoint'));\n }\n\n return new Promise(function(resolve, reject) {\n var xhr = new XMLHttpRequest();\n // POST to proxy (proxy handles PUT to LRS), POST to direct LRS\n xhr.open('POST', endpoint, true);\n\n // xAPI required headers\n xhr.setRequestHeader('Content-Type', 'application/json');\n xhr.setRequestHeader('X-Experience-API-Version', '1.0');\n\n if (useProxy) {\n // Proxy handles auth server-side, no auth headers needed\n log('Sending via LRS proxy:', endpoint);\n } else if (LRS_AUTH && LRS_AUTH.length > 0) {\n xhr.setRequestHeader('Authorization', 'Basic ' + LRS_AUTH);\n log('Using Basic Auth for LRS request');\n } else if (LRS_WITH_CREDENTIALS) {\n xhr.withCredentials = true;\n }\n\n xhr.onreadystatechange = function() {\n if (xhr.readyState === 4) {\n if (xhr.status >= 200 && xhr.status < 300) {\n log('Statement sent successfully:', statement.verb.display['en-US'], useProxy ? '(via proxy)' : '(direct)');\n LRS.stats.statementsSent++;\n resolve({ sent: true, via: useProxy ? 'proxy' : 'directLRS', status: xhr.status });\n } else {\n warn('Statement send failed:', xhr.status, xhr.statusText);\n LRS.stats.statementsFailed++;\n\n // Queue for retry if server error\n if (xhr.status >= 500) {\n LRS.statementQueue.push(statement);\n }\n\n reject(new Error('HTTP ' + xhr.status + ': ' + xhr.statusText));\n }\n }\n };\n\n xhr.onerror = function() {\n warn('Network error sending statement');\n LRS.stats.statementsFailed++;\n LRS.statementQueue.push(statement);\n reject(new Error('Network error'));\n };\n\n try {\n xhr.send(JSON.stringify(statement));\n } catch (e) {\n warn('Error sending statement:', e);\n LRS.stats.statementsFailed++;\n reject(e);\n }\n });\n }\n\n // Retry queue processing\n function processStatementQueue() {\n if (LRS.statementQueue.length === 0) return;\n\n log('Processing statement queue:', LRS.statementQueue.length, 'statements');\n\n var queue = LRS.statementQueue.slice();\n LRS.statementQueue = [];\n\n queue.forEach(function(statement) {\n sendViaDirectLRS(statement).catch(function(e) {\n // Already re-queued on failure\n });\n });\n }\n\n // ========================================================================\n // 7. PUBLIC API METHODS\n // ========================================================================\n\n // Course launched - uses Xyleme native format for course object\n LRS.courseLaunched = function() {\n if (!TRACK_NAVIGATION) return;\n\n // Course launch uses the document activity type (default)\n var statement = buildStatement(\n 'launched',\n 'http://xyleme.com/bravais/activities/document',\n { event: 'course_launched' }\n );\n sendStatement(statement);\n };\n\n // Content/Page viewed - uses page-specific object for human-readable display\n // Object shows page/lesson title, parent context maintains document aggregation\n LRS.contentOpened = function(data) {\n if (!TRACK_NAVIGATION) return;\n\n // Get current lesson info from DOM (includes name)\n var lessonInfo = getCurrentLessonInfo();\n\n var result = null;\n if (data.duration) {\n result = { duration: data.duration };\n }\n\n // Extract clean page/lesson ID from Rise hash paths\n var lessonId = data.pageGuid || data.lessonId || lessonInfo.id || '';\n if (lessonId.indexOf('#/lessons/') === 0) {\n lessonId = lessonId.substring(10); // Remove '#/lessons/'\n } else if (lessonId.indexOf('#/') === 0) {\n lessonId = lessonId.substring(2); // Remove '#/'\n }\n\n // Get the page title - prefer lesson name from DOM\n var pageTitle = data.title || lessonInfo.name || 'Page';\n\n // Build page activity object with human-readable name\n // This shows the page/lesson title in the Object column\n var pageObject = buildPageActivityObject({\n pageGuid: lessonId,\n name: pageTitle,\n resourceType: 'Lesson'\n });\n\n // Add page info to context extensions\n var additionalContext = {\n extensions: {\n pageId: lessonId || 'home',\n pageTitle: pageTitle,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName,\n pageUrl: data.url || window.location.href\n }\n };\n\n // Use 'experienced' verb for viewing content\n // Parent course activity added by buildXylemeContext ensures document aggregation\n var statement = buildStatementXyleme('experienced', pageObject, result, additionalContext);\n sendStatement(statement);\n };\n\n // Media events - uses activity-specific object with human-readable name\n // Parent context (added by buildXylemeContext) ensures document aggregation\n LRS.mediaPlayed = function(data) {\n if (!TRACK_MEDIA) return;\n\n // Get current lesson context\n var lessonInfo = getCachedLessonInfo();\n\n var verbKey = data.action === 'play' ? 'played' :\n data.action === 'pause' ? 'paused' :\n data.action === 'completed' ? 'completed' : 'played';\n\n // Build activity-specific object with human-readable name including time context\n // e.g., \"Video: Introduction (started at 1:23)\" or \"Audio: Podcast (paused at 2:45)\"\n var mediaObject = buildMediaActivityObject({\n type: data.type,\n src: data.src,\n name: data.name,\n duration: data.duration,\n lessonName: lessonInfo.name,\n action: data.action,\n currentTime: data.currentTime\n });\n\n var result = {\n extensions: {\n 'https://w3id.org/xapi/video/extensions/time': data.currentTime || 0,\n 'https://w3id.org/xapi/video/extensions/progress': data.duration > 0 ?\n Math.round((data.currentTime / data.duration) * 100) / 100 : 0,\n 'https://w3id.org/xapi/video/extensions/length': data.duration || 0\n }\n };\n\n if (data.action === 'completed') {\n result.completion = true;\n }\n\n // Context includes lesson info; parent course activity added by buildXylemeContext\n var additionalContext = {\n extensions: {\n lessonId: lessonInfo.id,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n }\n };\n\n var statement = buildStatementXyleme(verbKey, mediaObject, result, additionalContext);\n sendStatement(statement);\n };\n\n // Assessment started\n LRS.assessmentStarted = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context\n var lessonInfo = getCachedLessonInfo();\n\n // Build assessment activity object for Xyleme format\n var assessmentObject = buildAssessmentActivityObject({\n assessmentGuid: data.assessmentGuid || data.id,\n title: data.title || data.assessmentName || 'Assessment'\n });\n\n // Add lesson context to extensions\n assessmentObject.definition = assessmentObject.definition || {};\n assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};\n assessmentObject.definition.extensions.lessonName = lessonInfo.name;\n assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;\n\n var statement = buildStatementXyleme('attempted', assessmentObject);\n sendStatement(statement);\n };\n\n // Assessment ended\n LRS.assessmentEnded = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context (may be passed in from extractQuizResult)\n var lessonInfo = data.lessonName ? { name: data.lessonName, sectionName: data.sectionName } : getCachedLessonInfo();\n\n // Build assessment activity object for Xyleme format\n var assessmentObject = buildAssessmentActivityObject({\n assessmentGuid: data.assessmentGuid || data.assessmentId || data.id,\n title: data.title || data.assessmentName || 'Assessment'\n });\n\n // Add lesson context to extensions\n assessmentObject.definition = assessmentObject.definition || {};\n assessmentObject.definition.extensions = assessmentObject.definition.extensions || {};\n assessmentObject.definition.extensions.lessonName = lessonInfo.name;\n assessmentObject.definition.extensions.sectionName = lessonInfo.sectionName;\n assessmentObject.definition.extensions.assessmentName = data.assessmentName || data.title;\n\n var result = {\n completion: true,\n extensions: {\n 'https://w3id.org/xapi/acrossx/extensions/assessmentName': data.assessmentName || data.title || null,\n 'https://w3id.org/xapi/acrossx/extensions/lessonName': lessonInfo.name || null,\n 'https://w3id.org/xapi/acrossx/extensions/sectionName': lessonInfo.sectionName || null,\n 'https://w3id.org/xapi/acrossx/extensions/questionCount': data.questionCount || null\n }\n };\n\n if (typeof data.score === 'number') {\n result.score = {\n scaled: data.score / 100,\n raw: data.score,\n max: 100,\n min: 0\n };\n result.success = data.score >= (data.passingScore || 70);\n }\n\n if (data.duration) {\n result.duration = data.duration;\n }\n\n var verbKey = result.success !== false ? 'passed' : 'failed';\n if (typeof result.success === 'undefined') {\n verbKey = 'completed';\n }\n\n var statement = buildStatementXyleme(verbKey, assessmentObject, result);\n sendStatement(statement);\n };\n\n // Question answered\n LRS.questionAnswered = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Get lesson context if not provided\n var lessonInfo = data.lessonName ? data : getCachedLessonInfo();\n\n // Build question activity object for Xyleme format\n var questionObject = buildQuestionActivityObject({\n questionGuid: data.questionGuid || data.questionId,\n text: data.questionText || 'Question'\n });\n\n // Build comprehensive result with human-readable data\n var result = {\n response: data.answer || data.response || '',\n extensions: {\n 'https://w3id.org/xapi/acrossx/extensions/questionNumber': data.questionNumber || null,\n 'https://w3id.org/xapi/acrossx/extensions/questionText': data.questionText || null,\n 'https://w3id.org/xapi/acrossx/extensions/answerText': data.answer || data.response || null,\n 'https://w3id.org/xapi/acrossx/extensions/correctAnswer': data.correctAnswer || null,\n 'https://w3id.org/xapi/acrossx/extensions/assessmentName': data.assessmentName || null,\n 'https://w3id.org/xapi/acrossx/extensions/lessonName': lessonInfo.lessonName || lessonInfo.name || null,\n 'https://w3id.org/xapi/acrossx/extensions/sectionName': lessonInfo.sectionName || null\n }\n };\n\n if (data.result === 'correct' || data.correct === true) {\n result.success = true;\n } else if (data.result === 'incorrect' || data.correct === false) {\n result.success = false;\n }\n\n var statement = buildStatementXyleme('answered', questionObject, result);\n sendStatement(statement);\n };\n\n // Submit all questions\n LRS.questionsSubmitAll = function(data) {\n if (!TRACK_QUIZZES) return;\n\n // Send individual question statements\n if (data.questions && Array.isArray(data.questions)) {\n data.questions.forEach(function(q) {\n LRS.questionAnswered(q);\n });\n }\n\n // Then send assessment completed\n LRS.assessmentEnded(data);\n };\n\n // Interactions (tabs, accordions, etc.) - uses activity-specific object\n // Object name shows the interaction type and label, e.g., \"Tab: Overview\"\n // Parent context ensures these appear in document search results\n LRS.interacted = function(data) {\n if (!TRACK_INTERACTIONS) return;\n\n // Get current lesson context\n var lessonInfo = getCachedLessonInfo();\n\n // Build activity-specific object with human-readable name\n // e.g., \"Tab: Getting Started\", \"Accordion: FAQ\"\n var interactionObject = buildInteractionActivityObject({\n type: data.type || data.interactionType,\n id: data.id,\n name: data.name,\n label: data.name || data.type\n });\n\n // Context includes lesson info; parent course activity added by buildXylemeContext\n var additionalContext = {\n extensions: {\n interactionId: data.id || 'interaction-' + Date.now(),\n lessonId: lessonInfo.id,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n }\n };\n\n var statement = buildStatementXyleme('interacted', interactionObject, null, additionalContext);\n sendStatement(statement);\n };\n\n // Custom statement\n LRS.sendCustomStatement = function(data) {\n if (!CUSTOM_STATEMENTS) return;\n\n var verb = data.verb || 'interacted';\n var objectData = data.object || {};\n\n // Activity type from data or default to interaction\n var activityType = objectData.type || ACTIVITY_TYPES.interaction;\n\n // Activity details\n var activityDetails = {\n customActivityId: objectData.id || data.id || 'custom-' + Date.now(),\n customActivityName: objectData.name || data.name || 'Custom Activity'\n };\n\n // Merge any extra data\n if (data.data) {\n for (var key in data.data) {\n if (data.data.hasOwnProperty(key)) {\n activityDetails[key] = data.data[key];\n }\n }\n }\n\n var statement = buildStatement(verb, activityType, activityDetails, data.result, data.context);\n sendStatement(statement);\n };\n\n /**\n * Re-extract actor from SCORM after LMSInitialize has been called.\n * Call this from the SCORM wrapper after scormInit() succeeds,\n * because the bridge initializes in <head> before LMSInitialize runs.\n */\n LRS.refreshActor = function(callback) {\n log('refreshActor called - re-extracting from SCORM...');\n var previousName = LRS.actor ? LRS.actor.name : 'none';\n\n // Re-run sync extraction (SCORM data should now be available)\n var newActor = extractActor();\n\n if (isValidActor(newActor) && newActor.account && newActor.account.name !== 'unknown') {\n log('refreshActor: got valid actor:', newActor.name, newActor.account ? newActor.account.name : '');\n // Enhance with email lookup (async)\n extractActorAsync(function(enhancedActor) {\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Actor refreshed: ' + previousName + ' -> ' + enhancedActor.name +\n (enhancedActor.mbox ? ' (' + enhancedActor.mbox + ')' : ''));\n }\n if (callback) callback(enhancedActor);\n });\n } else {\n log('refreshActor: no valid actor found after re-extraction');\n if (callback) callback(LRS.actor);\n }\n };\n\n // Course terminated/exited\n LRS.courseTerminated = function() {\n // Calculate duration\n var duration = null;\n if (LRS.launchTime) {\n var launchMs = new Date(LRS.launchTime).getTime();\n var nowMs = Date.now();\n var durationMs = nowMs - launchMs;\n // Convert to ISO 8601 duration\n var seconds = Math.floor(durationMs / 1000);\n var minutes = Math.floor(seconds / 60);\n var hours = Math.floor(minutes / 60);\n seconds = seconds % 60;\n minutes = minutes % 60;\n duration = 'PT' + hours + 'H' + minutes + 'M' + seconds + 'S';\n }\n\n var result = duration ? { duration: duration } : null;\n\n // Use course object for termination (document-level) with 'exited' verb for Xyleme\n var courseObj = buildCourseActivityObject();\n if (courseObj) {\n courseObj.definition.extensions = courseObj.definition.extensions || {};\n courseObj.definition.extensions.resourceType = 'Course';\n }\n\n var statement = buildStatementXyleme('exited', courseObj, result);\n sendStatement(statement);\n };\n\n // Legacy compatibility methods\n LRS.choiceBranchSelected = function(data) {\n LRS.interacted({ type: 'choice-branch', id: data.id, name: 'Choice Branch Selected' });\n };\n\n LRS.choiceBranchDiscarded = function(data) {\n LRS.interacted({ type: 'choice-branch', id: data.id, name: 'Choice Branch Discarded' });\n };\n\n // ========================================================================\n // 8. RISE EVENT INTERCEPTORS\n // ========================================================================\n\n function setupMediaInterceptors() {\n if (!TRACK_MEDIA) return;\n\n // Track which elements we've already attached listeners to\n var trackedMedia = new WeakSet();\n\n // Helper to get media name/title from element or surrounding context\n function getMediaName(el) {\n // Check element attributes\n var name = el.title || el.getAttribute('aria-label') || el.getAttribute('data-name');\n if (name) return name;\n\n // Check parent Rise video block for title\n var videoBlock = findClosest(el, '[data-block-type=\"video\"], .blocks-video, .video-block');\n if (videoBlock) {\n var titleEl = videoBlock.querySelector('.video-title, .block-title, [class*=\"title\"]');\n if (titleEl) return titleEl.textContent.trim();\n }\n\n // Use src filename as fallback\n var src = el.src || el.currentSrc || '';\n if (src) {\n var filename = src.split('/').pop().split('?')[0];\n if (filename && filename.length < 100) return filename;\n }\n\n return el.tagName.toLowerCase();\n }\n\n // Attach media event listeners to a single element\n function attachMediaListeners(el) {\n if (trackedMedia.has(el)) return;\n trackedMedia.add(el);\n\n var mediaType = el.tagName.toLowerCase();\n log('Attaching media listeners to:', mediaType, el.src || el.currentSrc || 'no-src');\n\n el.addEventListener('play', function() {\n log('Media play event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.currentTime || 0,\n duration: el.duration || 0,\n action: 'play'\n });\n });\n\n el.addEventListener('pause', function() {\n // Ignore pause events that fire right before ended\n if (el.ended) return;\n log('Media pause event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.currentTime || 0,\n duration: el.duration || 0,\n action: 'pause'\n });\n });\n\n el.addEventListener('ended', function() {\n log('Media ended event captured:', mediaType);\n LRS.mediaPlayed({\n type: mediaType,\n src: el.src || el.currentSrc || '',\n name: getMediaName(el),\n currentTime: el.duration || 0,\n duration: el.duration || 0,\n action: 'completed'\n });\n });\n }\n\n // Scan for and attach listeners to all video/audio elements\n function scanForMedia(root) {\n var elements = (root || document).querySelectorAll('video, audio');\n log('Scanning for media elements, found:', elements.length);\n for (var i = 0; i < elements.length; i++) {\n attachMediaListeners(elements[i]);\n }\n }\n\n // Initial scan\n scanForMedia();\n\n // Document-level capture for any we might miss\n document.addEventListener('play', function(e) {\n var target = e.target;\n if (target.tagName === 'VIDEO' || target.tagName === 'AUDIO') {\n // Attach listeners if not already tracked\n if (!trackedMedia.has(target)) {\n log('Captured play from untracked media, attaching listeners');\n attachMediaListeners(target);\n }\n }\n }, true);\n\n // MutationObserver to detect dynamically added video/audio elements\n var mediaObserver = new MutationObserver(function(mutations) {\n var needsScan = false;\n mutations.forEach(function(mutation) {\n mutation.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'VIDEO' || node.tagName === 'AUDIO') {\n attachMediaListeners(node);\n } else if (node.querySelectorAll) {\n // Check for nested video/audio\n var nested = node.querySelectorAll('video, audio');\n if (nested.length > 0) {\n needsScan = true;\n }\n }\n }\n });\n });\n if (needsScan) {\n scanForMedia();\n }\n });\n\n // Start observing once DOM is ready\n function startMediaObserver() {\n if (document.body) {\n mediaObserver.observe(document.body, {\n childList: true,\n subtree: true\n });\n log('Media mutation observer started');\n } else {\n setTimeout(startMediaObserver, 100);\n }\n }\n startMediaObserver();\n\n // Periodic rescan for Rise lazy-loaded content\n setInterval(function() {\n scanForMedia();\n }, 3000);\n\n log('Media interceptors set up (enhanced)');\n }\n\n function setupNavigationInterceptors() {\n if (!TRACK_NAVIGATION) return;\n\n window.addEventListener('hashchange', function() {\n var newLesson = window.location.hash || '#/';\n if (newLesson !== LRS.currentLesson) {\n LRS.currentLesson = newLesson;\n LRS.contentOpened({\n lessonId: newLesson,\n url: window.location.href,\n action: 'navigated'\n });\n }\n });\n\n LRS.currentLesson = window.location.hash || '#/';\n\n log('Navigation interceptors set up');\n }\n\n function setupQuizInterceptors() {\n if (!TRACK_QUIZZES) return;\n\n // Set up Knowledge Check specific interceptors for Rise blocks\n setupKnowledgeCheckInterceptors();\n\n var quizObserver = new MutationObserver(function(mutations) {\n mutations.forEach(function(mutation) {\n mutation.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n detectQuizElements(node);\n }\n });\n });\n });\n\n function startQuizObserver() {\n if (document.body) {\n quizObserver.observe(document.body, {\n childList: true,\n subtree: true\n });\n log('Quiz observer started');\n } else {\n setTimeout(startQuizObserver, 100);\n }\n }\n startQuizObserver();\n }\n\n function detectQuizElements(node) {\n var resultClasses = ['quiz-result', 'knowledge-result', 'quiz-feedback', 'assessment-result',\n 'blocks-quiz__result', 'block-quiz-results'];\n\n resultClasses.forEach(function(cls) {\n if (node.classList && node.classList.contains(cls)) {\n extractQuizResult(node);\n }\n var results = node.querySelectorAll ? node.querySelectorAll('.' + cls) : [];\n results.forEach(function(resultNode) {\n extractQuizResult(resultNode);\n });\n });\n\n var startClasses = ['quiz-start', 'knowledge-check', 'assessment-intro', 'blocks-quiz'];\n startClasses.forEach(function(cls) {\n if (node.classList && node.classList.contains(cls)) {\n LRS.assessmentStarted({\n assessmentId: extractAssessmentId(node)\n });\n }\n });\n }\n\n function extractAssessmentId(node) {\n return node.getAttribute('data-block-id') ||\n node.getAttribute('data-assessment-id') ||\n node.getAttribute('id') ||\n 'quiz-' + Date.now();\n }\n\n /**\n * Extract quiz/assessment name from the DOM\n */\n function extractAssessmentName(node) {\n // Try various selectors for assessment/quiz titles\n var titleEl = node.querySelector(\n '.quiz-title, .assessment-title, .knowledge-check-title, ' +\n '.blocks-quiz__title, .block-title, ' +\n '[class*=\"quiz\"] [class*=\"title\"], ' +\n '[class*=\"assessment\"] [class*=\"title\"], ' +\n 'h2, h3'\n );\n if (titleEl) {\n return titleEl.textContent.trim().substring(0, 200);\n }\n\n // Try to get from parent block\n var parentBlock = node.closest('[data-block-type]');\n if (parentBlock) {\n var blockTitle = parentBlock.querySelector('.block-title, h2, h3');\n if (blockTitle) {\n return blockTitle.textContent.trim().substring(0, 200);\n }\n }\n\n // Get current lesson name as context\n var lessonInfo = getCachedLessonInfo();\n if (lessonInfo.name) {\n return 'Quiz in ' + lessonInfo.name;\n }\n\n return 'Knowledge Check';\n }\n\n function extractQuizResult(node) {\n var scoreEl = node.querySelector('.score-value, .quiz-score, .score-percentage, [class*=\"score\"]');\n var score = scoreEl ? parseFloat(scoreEl.textContent.replace(/[^0-9.]/g, '')) : null;\n\n // Get assessment name and lesson context\n var assessmentName = extractAssessmentName(node);\n var lessonInfo = getCachedLessonInfo();\n\n // Find all question containers - Rise uses various structures\n var questions = node.querySelectorAll(\n '.question-result, .question-feedback, .question-item, ' +\n '.blocks-quiz__question, .quiz-question, ' +\n '[class*=\"question-\"][class*=\"result\"], ' +\n '[class*=\"question-\"][class*=\"feedback\"], ' +\n '[data-question-id]'\n );\n\n log('Found', questions.length, 'question elements in quiz result');\n\n if (questions.length > 0) {\n questions.forEach(function(q, index) {\n // Determine if correct - check multiple indicators\n var isCorrect = q.classList.contains('correct') ||\n q.classList.contains('is-correct') ||\n q.querySelector('.correct, .is-correct, [class*=\"correct\"]') !== null ||\n q.getAttribute('data-correct') === 'true' ||\n q.getAttribute('data-result') === 'correct';\n\n // Get the question text - try multiple selectors\n var questionText = '';\n var questionEl = q.querySelector(\n '.question-text, .question-stem, .question-title, ' +\n '.blocks-quiz__question-text, .quiz-question__text, ' +\n '[class*=\"question-text\"], [class*=\"question-stem\"], ' +\n '[class*=\"prompt\"], p:first-of-type'\n );\n if (questionEl) {\n questionText = questionEl.textContent.trim();\n }\n\n // Get the learner's selected answer text\n var answerText = '';\n var answerEl = q.querySelector(\n '.selected-answer, .learner-response, .answer-text, ' +\n '.user-answer, .chosen-answer, .response-text, ' +\n '.blocks-quiz__response, .quiz-question__response, ' +\n '[class*=\"selected\"], [class*=\"chosen\"], [class*=\"response\"], ' +\n '[class*=\"user-answer\"]'\n );\n if (answerEl) {\n answerText = answerEl.textContent.trim();\n }\n\n // If no specific answer element, try to find selected radio/checkbox label\n if (!answerText) {\n var selectedInput = q.querySelector('input:checked, [aria-checked=\"true\"]');\n if (selectedInput) {\n var label = q.querySelector('label[for=\"' + selectedInput.id + '\"]');\n if (label) {\n answerText = label.textContent.trim();\n } else {\n var parentLabel = selectedInput.closest('label');\n if (parentLabel) {\n answerText = parentLabel.textContent.trim();\n }\n }\n }\n }\n\n // Get question ID if available\n var questionId = q.getAttribute('data-question-id') ||\n q.getAttribute('data-block-id') ||\n q.getAttribute('id') ||\n 'q' + (index + 1);\n\n // Get correct answer text if available (for reporting)\n var correctAnswerText = '';\n var correctEl = q.querySelector(\n '.correct-answer, .right-answer, ' +\n '[class*=\"correct-answer\"], [class*=\"right-answer\"]'\n );\n if (correctEl) {\n correctAnswerText = correctEl.textContent.trim();\n }\n\n log('Question', index + 1, ':', {\n questionText: questionText.substring(0, 50) + '...',\n answerText: answerText.substring(0, 50) + '...',\n isCorrect: isCorrect\n });\n\n LRS.questionAnswered({\n questionId: questionId,\n questionNumber: index + 1,\n questionText: questionText.substring(0, 500),\n answer: answerText.substring(0, 500),\n correctAnswer: correctAnswerText.substring(0, 500),\n result: isCorrect ? 'correct' : 'incorrect',\n assessmentName: assessmentName,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n });\n }\n\n LRS.assessmentEnded({\n assessmentId: extractAssessmentId(node),\n assessmentName: assessmentName,\n score: score,\n questionCount: questions.length,\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n }\n\n // Track submitted Knowledge Check blocks to avoid duplicates\n var submittedKnowledgeChecks = {};\n\n /**\n * Set up interceptors specifically for Rise Knowledge Check blocks\n * These blocks have a different DOM structure than standard quiz results\n */\n function setupKnowledgeCheckInterceptors() {\n if (!TRACK_QUIZZES) return;\n\n // Intercept submit button clicks on Knowledge Check blocks\n document.addEventListener('click', function(e) {\n var submitBtn = e.target.closest('.quiz-card__button');\n if (!submitBtn) return;\n\n // Find the Knowledge Check block\n var kcBlock = submitBtn.closest('[data-test-id=\"block-kc-card\"]') ||\n submitBtn.closest('.block-knowledge');\n if (!kcBlock) return;\n\n // Wait for feedback to appear after submission\n setTimeout(function() {\n extractKnowledgeCheckResult(kcBlock);\n }, 500);\n }, true);\n\n log('Knowledge Check interceptors set up');\n }\n\n /**\n * Extract and send xAPI statement for a Knowledge Check submission\n */\n function extractKnowledgeCheckResult(kcBlock) {\n // Get block ID for deduplication\n var blockContainer = kcBlock.closest('[data-block-id]');\n var blockId = blockContainer ? blockContainer.getAttribute('data-block-id') : null;\n\n // Get question ID from the title element\n var questionTitleEl = kcBlock.querySelector('.quiz-card__title');\n var questionId = questionTitleEl ? questionTitleEl.id : (blockId ? 'q-' + blockId : 'q-' + generateUUID());\n\n // Check if we already processed this submission (avoid duplicates)\n var submissionKey = blockId || questionId;\n var feedbackLabel = kcBlock.querySelector('.quiz-card__feedback-label');\n if (!feedbackLabel) {\n log('Knowledge Check: No feedback visible yet');\n return;\n }\n\n var feedbackText = feedbackLabel.textContent.trim().toLowerCase();\n var submissionId = submissionKey + '-' + feedbackText;\n\n if (submittedKnowledgeChecks[submissionId]) {\n log('Knowledge Check: Already processed this submission');\n return;\n }\n submittedKnowledgeChecks[submissionId] = true;\n\n // Get question text\n var questionText = '';\n var questionTextEl = kcBlock.querySelector('.quiz-card__title .fr-view, .quiz-card__title');\n if (questionTextEl) {\n questionText = questionTextEl.textContent.trim();\n }\n\n // Determine question type from aria-label\n var wrapper = kcBlock.querySelector('[data-test-id=\"block-knowledge-wrapper\"]');\n var ariaLabel = wrapper ? wrapper.getAttribute('aria-label') : '';\n var questionType = 'unknown';\n if (ariaLabel.indexOf('Multiple choice') > -1) questionType = 'multiple-choice';\n else if (ariaLabel.indexOf('Multiple response') > -1) questionType = 'multiple-response';\n else if (ariaLabel.indexOf('Fill in the blank') > -1) questionType = 'fill-in-blank';\n else if (ariaLabel.indexOf('Matching') > -1) questionType = 'matching';\n\n // Get selected answer(s) based on question type\n var answerText = extractKnowledgeCheckAnswer(kcBlock, questionType);\n\n // Get correct/incorrect from feedback\n var isCorrect = feedbackText === 'correct';\n\n // Get lesson context\n var lessonInfo = getCachedLessonInfo();\n\n log('Knowledge Check submitted:', {\n questionId: questionId,\n questionText: questionText.substring(0, 50) + '...',\n questionType: questionType,\n answer: answerText.substring(0, 50) + '...',\n correct: isCorrect\n });\n\n // Send question answered statement using existing LRS method\n LRS.questionAnswered({\n questionId: questionId,\n questionGuid: blockId || generateUUID(),\n questionNumber: 1,\n questionText: questionText.substring(0, 500),\n questionType: questionType,\n answer: answerText.substring(0, 500),\n correct: isCorrect,\n result: isCorrect ? 'correct' : 'incorrect',\n assessmentName: 'Knowledge Check',\n lessonName: lessonInfo.name,\n sectionName: lessonInfo.sectionName\n });\n }\n\n /**\n * Extract the selected answer text from a Knowledge Check block\n * based on the question type\n */\n function extractKnowledgeCheckAnswer(kcBlock, questionType) {\n var answerText = '';\n\n if (questionType === 'multiple-choice') {\n // Find checked radio button\n var checkedInput = kcBlock.querySelector('.quiz-multiple-choice-option__input:checked');\n if (checkedInput) {\n var label = checkedInput.closest('.quiz-multiple-choice-option');\n var textEl = label ? label.querySelector('.quiz-multiple-choice-option__label .fr-view, .quiz-multiple-choice-option__label') : null;\n answerText = textEl ? textEl.textContent.trim() : '';\n }\n }\n else if (questionType === 'multiple-response') {\n // Find all checked checkboxes\n var checkedInputs = kcBlock.querySelectorAll('.quiz-multiple-response-option__input:checked');\n var answers = [];\n checkedInputs.forEach(function(input) {\n var label = input.closest('.quiz-multiple-response-option');\n var textEl = label ? label.querySelector('.quiz-multiple-response-option__text .fr-view, .quiz-multiple-response-option__text') : null;\n if (textEl) answers.push(textEl.textContent.trim());\n });\n answerText = answers.join('; ');\n }\n else if (questionType === 'fill-in-blank') {\n // Get text input value\n var textInput = kcBlock.querySelector('.quiz-fill__input');\n answerText = textInput ? textInput.value.trim() : '';\n }\n else if (questionType === 'matching') {\n // Extract matching pairs from the drop zones\n var dropZones = kcBlock.querySelectorAll('.matching-drop-zone');\n var pairs = [];\n dropZones.forEach(function(zone) {\n var prompt = zone.querySelector('.matching-prompt-content');\n var response = zone.querySelector('.matching-interaction-piece-content');\n if (prompt && response) {\n pairs.push(prompt.textContent.trim() + ' → ' + response.textContent.trim());\n }\n });\n answerText = pairs.length > 0 ? pairs.join('; ') : 'Matching submitted';\n }\n\n return answerText;\n }\n\n function setupInteractionInterceptors() {\n if (!TRACK_INTERACTIONS) return;\n\n document.addEventListener('click', function(e) {\n var target = e.target;\n\n // Tabs\n var tabTrigger = findClosest(target, '[data-block-type=\"tabs\"] .tab-item, .tabs__tab, .tab-trigger, [role=\"tab\"]');\n if (tabTrigger) {\n LRS.interacted({\n type: 'tab',\n id: tabTrigger.getAttribute('data-tab-id') ||\n tabTrigger.getAttribute('aria-controls') ||\n tabTrigger.textContent.trim().substring(0, 50),\n name: 'Tab: ' + (tabTrigger.textContent.trim().substring(0, 30) || 'Tab')\n });\n return;\n }\n\n // Accordions\n var accordionTrigger = findClosest(target, '[data-block-type=\"accordion\"] .accordion-trigger, .accordion__header, .accordion-item__header, [aria-expanded]');\n if (accordionTrigger && accordionTrigger.getAttribute('aria-expanded')) {\n LRS.interacted({\n type: 'accordion',\n id: accordionTrigger.getAttribute('data-accordion-id') ||\n accordionTrigger.getAttribute('aria-controls') ||\n accordionTrigger.textContent.trim().substring(0, 50),\n name: 'Accordion: ' + (accordionTrigger.textContent.trim().substring(0, 30) || 'Section')\n });\n return;\n }\n\n // Flashcards\n var flashcard = findClosest(target, '.flashcard, [data-block-type=\"flashcard\"], .blocks-flashcard');\n if (flashcard) {\n LRS.interacted({\n type: 'flashcard',\n id: flashcard.getAttribute('data-card-id') || 'flashcard-' + Date.now(),\n name: 'Flashcard'\n });\n return;\n }\n\n // Process/Timeline\n var processItem = findClosest(target, '.process-item, .timeline-item, [data-block-type=\"process\"] .step, .blocks-process__item');\n if (processItem) {\n LRS.interacted({\n type: 'process-step',\n id: processItem.getAttribute('data-step-id') ||\n processItem.getAttribute('data-index') ||\n 'step-' + Date.now(),\n name: 'Process Step'\n });\n return;\n }\n\n // Hotspots/Markers\n var hotspot = findClosest(target, '.hotspot-marker, .marker-item, [data-block-type=\"labeled-graphic\"] .marker, .blocks-labeled-graphic__marker');\n if (hotspot) {\n LRS.interacted({\n type: 'hotspot',\n id: hotspot.getAttribute('data-marker-id') ||\n hotspot.getAttribute('data-index') ||\n 'hotspot-' + Date.now(),\n name: 'Hotspot'\n });\n return;\n }\n\n // Sorting activities\n var sortItem = findClosest(target, '.sort-item, [data-block-type=\"sorting\"] .item, .blocks-sorting__item');\n if (sortItem) {\n LRS.interacted({\n type: 'sorting',\n id: sortItem.getAttribute('data-item-id') || 'sort-item',\n name: 'Sorting Activity'\n });\n return;\n }\n\n // Buttons / Continue\n var button = findClosest(target, '.continue-button, .nav-button, [data-block-type=\"button\"], .blocks-button');\n if (button) {\n LRS.interacted({\n type: 'button',\n id: button.getAttribute('data-button-id') || 'button',\n name: 'Button: ' + (button.textContent.trim().substring(0, 30) || 'Continue')\n });\n }\n\n }, true);\n\n log('Interaction interceptors set up');\n }\n\n function findClosest(el, selector) {\n if (!el || !selector) return null;\n if (el.closest) {\n return el.closest(selector);\n }\n var current = el;\n while (current && current !== document) {\n if (matchesSelector(current, selector)) {\n return current;\n }\n current = current.parentElement;\n }\n return null;\n }\n\n function matchesSelector(el, selector) {\n var fn = el.matches || el.webkitMatchesSelector || el.msMatchesSelector;\n return fn ? fn.call(el, selector) : false;\n }\n\n // ========================================================================\n // 9. INITIALIZATION\n // ========================================================================\n\n function init() {\n log('Initializing LRS bridge v2.6.0...');\n\n // Extract course info early\n extractCourseInfo();\n\n // Setup connection\n var bridgeReady = setupBridge();\n\n if (bridgeReady) {\n LRS.initialized = true;\n\n // Extract actor (sync first for immediate use)\n extractActor();\n\n log('Bridge initialized in mode:', LRS.mode);\n log('Actor:', LRS.actor);\n log('Course:', LRS.courseInfo);\n\n // Then try async actor extraction from Bravais session\n // This will update the actor with real user info if available\n extractActorAsync(function(actor) {\n log('Actor updated after async fetch:', actor);\n });\n } else {\n warn('Bridge setup failed - operating in offline mode');\n LRS.mode = 'offline';\n }\n\n // Always-visible bridge summary (not gated by DEBUG)\n // This ensures diagnostics are available even in production builds\n if (window.console && window.console.info) {\n console.info('[PA-LRS] Bridge: mode=' + LRS.mode +\n ', endpoint=' + (LRS_ENDPOINT ? LRS_ENDPOINT.substring(0, 30) + '...' : 'NONE') +\n ', proxy=' + (LRS_PROXY_ENDPOINT ? 'yes' : 'no') +\n ', actor=' + (LRS.actor ? (LRS.actor.name || 'anonymous') : 'none'));\n }\n\n // Set up event interceptors\n setupMediaInterceptors();\n setupNavigationInterceptors();\n setupQuizInterceptors();\n setupInteractionInterceptors();\n\n // Fetch document metadata from API to get GUIDs (async)\n // Then send course launched event\n var sharedLinkToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;\n var documentId = LRS.courseInfo ? LRS.courseInfo.documentId : null;\n\n function sendLaunchEvents() {\n // Log the course info state before sending statements\n log('Sending launch events with course info:', {\n id: LRS.courseInfo ? LRS.courseInfo.id : null,\n guid: LRS.courseInfo ? LRS.courseInfo.guid : null,\n versionGuid: LRS.courseInfo ? LRS.courseInfo.versionGuid : null,\n documentId: LRS.courseInfo ? LRS.courseInfo.documentId : null\n });\n\n if (TRACK_NAVIGATION) {\n LRS.courseLaunched();\n LRS.contentOpened({\n lessonId: window.location.hash || '#/',\n url: window.location.href,\n action: 'launched'\n });\n }\n }\n\n function fetchDocDataAndSend(docId) {\n fetchDocumentMetadata(docId, function(docData) {\n if (docData) {\n updateCourseInfoFromApi(docData);\n }\n // Send launch events after API fetch (success or failure)\n setTimeout(sendLaunchEvents, 100);\n });\n }\n\n if (!LRS.courseInfo.guid) {\n // When we have a shared link token, use /api/shared/{token}/documents directly\n // This endpoint does NOT require authentication and returns both document GUID and version GUID\n if (sharedLinkToken) {\n log('Have shared link token, fetching document data via shared API...');\n // fetchDocumentMetadata will use /api/shared/{token}/documents when sharedLinkToken is present\n fetchDocumentMetadata(null, function(docData) {\n if (docData) {\n updateCourseInfoFromApi(docData);\n // Also update document name if available\n if (docData.name) {\n LRS.courseInfo.sharedLinkName = docData.name;\n }\n } else {\n warn('Could not fetch document data from shared API - statements may fail aggregation');\n }\n setTimeout(sendLaunchEvents, 100);\n });\n } else if (documentId) {\n // No shared link token, try with extracted document ID (requires auth)\n log('No shared link token, fetching document data with ID:', documentId);\n fetchDocDataAndSend(documentId);\n } else {\n // No identifiers available\n warn('No shared link token or document ID - statements may fail aggregation');\n setTimeout(sendLaunchEvents, 500);\n }\n } else {\n // Already have GUID - send after short delay\n setTimeout(sendLaunchEvents, 500);\n }\n\n // Setup beforeunload to send terminated\n window.addEventListener('beforeunload', function() {\n LRS.courseTerminated();\n });\n\n // Retry queue every 30 seconds\n setInterval(processStatementQueue, 30000);\n\n // Retry bridge connection if failed\n if (!LRS.initialized && LRS.mode !== 'directLRS') {\n var retryCount = 0;\n var maxRetries = 10;\n var retryInterval = setInterval(function() {\n retryCount++;\n if (setupBridge()) {\n LRS.initialized = true;\n extractActorAsync(function(actor) {\n log('Actor extracted on retry:', actor);\n });\n log('Bridge connected on retry ' + retryCount);\n clearInterval(retryInterval);\n } else if (retryCount >= maxRetries) {\n clearInterval(retryInterval);\n }\n }, 1000);\n }\n\n log('LRS bridge setup complete');\n }\n\n // Initialize\n init();\n\n })();\n`;\n}\n","import type { PatchAdamsConfig, LrsBridgeConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport { generateLrsBridgeCode, type LrsBridgeOptions } from './lrs-bridge.js';\n\nexport interface JsBeforeOptions {\n remoteUrl: string;\n localPath: string;\n htmlClass: string;\n loadingClass: string;\n /** Full course metadata */\n metadata: CourseMetadata | null;\n /** LRS Bridge configuration */\n lrsBridge: LrsBridgeOptions;\n}\n\n/**\n * Escape a string for safe inclusion in JavaScript\n */\nfunction escapeJs(str: string | null | undefined): string {\n if (!str) return '';\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r');\n}\n\n/**\n * Generate the blocking JS loader for the \"before\" slot\n * This loads at the start of <head> and blocks rendering until loaded\n *\n * Use this for:\n * - Setting up global variables\n * - Intercepting Rise APIs before they initialize\n * - Preparing the environment\n * - LRS Bridge for xAPI/SCORM communication\n */\nexport function generateJsBeforeLoader(options: JsBeforeOptions): string {\n const { remoteUrl, localPath, htmlClass, loadingClass, metadata, lrsBridge } = options;\n\n // Generate LRS bridge code\n const lrsBridgeCode = generateLrsBridgeCode(lrsBridge);\n\n // Build course object with all available metadata\n const courseLines: string[] = [];\n\n if (metadata) {\n courseLines.push(` courseId: '${escapeJs(metadata.courseId)}',`);\n courseLines.push(` courseIdSource: '${metadata.courseIdSource}',`);\n courseLines.push(` format: '${metadata.format}',`);\n\n if (metadata.title) {\n courseLines.push(` title: '${escapeJs(metadata.title)}',`);\n }\n if (metadata.description) {\n courseLines.push(` description: '${escapeJs(metadata.description)}',`);\n }\n if (metadata.organizationId) {\n courseLines.push(` organizationId: '${escapeJs(metadata.organizationId)}',`);\n }\n if (metadata.launchUrl) {\n courseLines.push(` launchUrl: '${escapeJs(metadata.launchUrl)}',`);\n }\n if (metadata.masteryScore !== null) {\n courseLines.push(` masteryScore: ${metadata.masteryScore},`);\n }\n if (metadata.duration) {\n courseLines.push(` duration: '${escapeJs(metadata.duration)}',`);\n }\n if (metadata.language) {\n courseLines.push(` language: '${escapeJs(metadata.language)}',`);\n }\n if (metadata.version) {\n courseLines.push(` version: '${escapeJs(metadata.version)}',`);\n }\n courseLines.push(` extractedAt: '${metadata.extractedAt}',`);\n }\n\n const courseBlock = courseLines.length > 0\n ? `\\n course: {\\n${courseLines.join('\\n')}\\n },`\n : '';\n\n return `<!-- === PATCH-ADAMS: JS BEFORE (blocking) === -->\n<script data-pa=\"js-before-loader\">\n// Initialize Patch-Adams global namespace IMMEDIATELY (before IIFE)\nwindow.pa_patcher = window.pa_patcher || {\n version: '1.0.25',\n htmlClass: '${htmlClass}',\n loadingClass: '${loadingClass}',${courseBlock}\n loaded: {\n cssBefore: false,\n cssAfter: false,\n jsBefore: false,\n jsAfter: false\n }\n};\n\n// Filter out browser extension errors that can interfere with LMS error handling\n// These are not course errors - they come from extensions like SingleFile, LastPass, etc.\n(function() {\n var extensionPatterns = [\n 'extension',\n 'chrome-extension',\n 'moz-extension',\n 'safari-extension',\n 'message channel closed',\n 'listener indicated an asynchronous response',\n 'Receiving end does not exist',\n 'single-file',\n 'singlefile'\n ];\n\n function isExtensionError(error) {\n if (!error) return false;\n var msg = (error.message || error.reason || String(error)).toLowerCase();\n for (var i = 0; i < extensionPatterns.length; i++) {\n if (msg.indexOf(extensionPatterns[i]) !== -1) return true;\n }\n // Check stack trace for extension URLs\n var stack = (error.stack || '').toLowerCase();\n if (stack.indexOf('extension') !== -1 || stack.indexOf('moz-extension') !== -1) return true;\n return false;\n }\n\n // Handle unhandled promise rejections from extensions\n window.addEventListener('unhandledrejection', function(event) {\n if (isExtensionError(event.reason)) {\n event.preventDefault();\n event.stopImmediatePropagation();\n console.debug('[Patch-Adams] Suppressed browser extension error:', event.reason);\n return false;\n }\n }, true);\n\n // Handle regular errors from extensions\n window.addEventListener('error', function(event) {\n if (isExtensionError(event.error || event.message)) {\n event.preventDefault();\n event.stopImmediatePropagation();\n console.debug('[Patch-Adams] Suppressed browser extension error:', event.message);\n return false;\n }\n }, true);\n})();\n\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n\n function loadJSSync(url) {\n var script = document.createElement('script');\n script.src = url;\n script.setAttribute('data-pa', 'js-before');\n // Note: For truly blocking behavior, we use document.write\n // but that's fragile. Instead we'll handle errors gracefully.\n document.head.appendChild(script);\n return script;\n }\n\n // Try remote first\n var script = loadJSSync(REMOTE_URL);\n\n script.onerror = function() {\n console.warn('[Patch-Adams] JS before failed to load from remote, using local fallback');\n document.head.removeChild(script);\n var fallback = loadJSSync(LOCAL_PATH);\n fallback.onload = function() {\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsBefore = true;\n }\n console.log('[Patch-Adams] JS before loaded from local fallback:', LOCAL_PATH);\n };\n fallback.onerror = function() {\n console.error('[Patch-Adams] JS before failed to load from both remote and local');\n };\n };\n\n script.onload = function() {\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsBefore = true;\n }\n console.log('[Patch-Adams] JS before loaded from remote:', REMOTE_URL);\n };\n})();\n${lrsBridgeCode}\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildJsBeforeOptions(config: PatchAdamsConfig, metadata?: CourseMetadata | null): JsBeforeOptions {\n // Convert LrsBridgeConfig to LrsBridgeOptions with defaults for backwards compatibility\n // This handles configs from older versions that don't have lrsBridge\n const lrsBridgeConfig = config.lrsBridge ?? {};\n const lrsBridge: LrsBridgeOptions = {\n enabled: lrsBridgeConfig.enabled ?? true,\n trackMedia: lrsBridgeConfig.trackMedia ?? true,\n trackNavigation: lrsBridgeConfig.trackNavigation ?? true,\n trackQuizzes: lrsBridgeConfig.trackQuizzes ?? true,\n trackInteractions: lrsBridgeConfig.trackInteractions ?? true,\n customStatements: lrsBridgeConfig.customStatements ?? true,\n debug: lrsBridgeConfig.debug ?? false,\n lrsEndpoint: lrsBridgeConfig.lrsEndpoint,\n lrsWithCredentials: lrsBridgeConfig.lrsWithCredentials,\n courseHomepage: lrsBridgeConfig.courseHomepage,\n autoDetectLrs: lrsBridgeConfig.autoDetectLrs,\n lrsAuth: lrsBridgeConfig.lrsAuth,\n lrsProxyEndpoint: lrsBridgeConfig.lrsProxyEndpoint,\n employeeApiEndpoint: lrsBridgeConfig.employeeApiEndpoint,\n };\n\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.js}/${config.jsBefore.filename}`,\n localPath: `${config.localFolders.js}/${config.jsBefore.filename}`,\n htmlClass: config.htmlClass,\n loadingClass: config.loadingClass,\n metadata: metadata ?? null,\n lrsBridge,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\n\nexport interface JsAfterOptions {\n remoteUrl: string;\n localPath: string;\n timeout: number;\n loadingClass: string;\n}\n\n/**\n * Generate the async JS loader for the \"after\" slot\n * This loads at the end of <body> with remote-first, local fallback\n *\n * This script is responsible for:\n * - Loading your override JS after Rise has initialized\n * - Removing the loading class to reveal the content\n */\nexport function generateJsAfterLoader(options: JsAfterOptions): string {\n const { remoteUrl, localPath, timeout, loadingClass } = options;\n\n return `<!-- === PATCH-ADAMS: JS AFTER (async with fallback) === -->\n<script data-pa=\"js-after-loader\">\n(function() {\n 'use strict';\n var REMOTE_URL = \"${remoteUrl}\";\n var LOCAL_PATH = \"${localPath}\";\n var TIMEOUT = ${timeout};\n var LOADING_CLASS = \"${loadingClass}\";\n\n function loadJS(url, onSuccess, onError) {\n var script = document.createElement('script');\n script.src = url;\n script.async = true;\n script.setAttribute('data-pa', 'js-after');\n\n script.onload = function() {\n if (onSuccess) onSuccess();\n };\n\n script.onerror = function() {\n if (onError) onError();\n };\n\n document.body.appendChild(script);\n return script;\n }\n\n function removeLoadingClass() {\n document.documentElement.classList.remove(LOADING_CLASS);\n if (window.pa_patcher && window.pa_patcher.loaded) {\n window.pa_patcher.loaded.jsAfter = true;\n }\n console.log('[Patch-Adams] Loading complete, content revealed');\n\n // Add visual badge to indicate patching is active\n var badge = document.createElement('div');\n badge.setAttribute('data-pa', 'badge');\n badge.style.cssText = 'position:fixed;bottom:10px;right:10px;background:#4CAF50;color:white;padding:4px 8px;border-radius:4px;font-size:11px;font-family:sans-serif;z-index:999999;opacity:0.8;cursor:pointer;';\n badge.textContent = 'PA';\n badge.title = 'Patch-Adams Active';\n badge.onclick = function() { badge.style.display = 'none'; };\n document.body.appendChild(badge);\n }\n\n function loadJSWithFallback() {\n var loaded = false;\n var timeoutId;\n\n // Try remote first\n var remoteScript = loadJS(\n REMOTE_URL,\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n console.log('[Patch-Adams] JS after loaded from remote:', REMOTE_URL);\n // Give the script a moment to execute, then remove loading class\n setTimeout(removeLoadingClass, 50);\n },\n function() {\n if (loaded) return;\n loaded = true;\n clearTimeout(timeoutId);\n loadLocalFallback();\n }\n );\n\n // Timeout fallback\n timeoutId = setTimeout(function() {\n if (loaded) return;\n loaded = true;\n console.warn('[Patch-Adams] JS after timed out, using local fallback');\n if (remoteScript.parentNode) {\n document.body.removeChild(remoteScript);\n }\n loadLocalFallback();\n }, TIMEOUT);\n }\n\n function loadLocalFallback() {\n loadJS(\n LOCAL_PATH,\n function() {\n console.log('[Patch-Adams] JS after loaded from local fallback:', LOCAL_PATH);\n setTimeout(removeLoadingClass, 50);\n },\n function() {\n console.error('[Patch-Adams] JS after failed to load from both remote and local');\n // Still remove loading class so content is visible even if JS fails\n removeLoadingClass();\n }\n );\n }\n\n // Execute after DOM is ready to ensure Rise has loaded\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function() {\n // Small delay to ensure Rise has initialized\n setTimeout(loadJSWithFallback, 100);\n });\n } else {\n // DOM is already ready\n setTimeout(loadJSWithFallback, 100);\n }\n})();\n</script>`;\n}\n\n/**\n * Build options from config\n */\nexport function buildJsAfterOptions(config: PatchAdamsConfig): JsAfterOptions {\n return {\n remoteUrl: `${config.remoteDomain}/${config.localFolders.js}/${config.jsAfter.filename}`,\n localPath: `${config.localFolders.js}/${config.jsAfter.filename}`,\n timeout: config.jsAfter.timeout,\n loadingClass: config.loadingClass,\n };\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport type { GeneratedPluginAssets } from '../plugins/types.js';\nimport {\n generateCssBeforeLoader,\n buildCssBeforeOptions,\n generateCssAfterLoader,\n buildCssAfterOptions,\n generateJsBeforeLoader,\n buildJsBeforeOptions,\n generateJsAfterLoader,\n buildJsAfterOptions,\n} from '../templates/index.js';\n\n/**\n * HTML Injector for patching Rise course index.html\n *\n * Injection points:\n * 1. CSS Before - Start of <head> (blocking)\n * 2. JS Before - Start of <head>, after CSS Before (blocking)\n * 3. CSS After - End of <head> (async with fallback)\n * 4. JS After - End of <body>, after __loadEntry() (async with fallback)\n * 5. Plugin Assets - Inline CSS/JS injected directly (always executes)\n *\n * Also adds classes and data attributes to <html> tag\n */\nexport class HtmlInjector {\n private config: PatchAdamsConfig;\n private metadata: CourseMetadata | null = null;\n private pluginAssets: GeneratedPluginAssets | null = null;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Set plugin-generated assets to inject inline into the HTML.\n * These are injected directly (not via external files) so they always execute.\n */\n setPluginAssets(assets: GeneratedPluginAssets): void {\n this.pluginAssets = assets;\n }\n\n /**\n * Set the course metadata to be injected into the HTML\n */\n setMetadata(metadata: CourseMetadata): void {\n this.metadata = metadata;\n }\n\n /**\n * @deprecated Use setMetadata instead\n * Set the course ID to be injected into the HTML\n */\n setCourseId(courseId: string): void {\n // Create minimal metadata for backwards compatibility\n if (!this.metadata) {\n this.metadata = {\n courseId,\n courseIdSource: 'fallback',\n format: 'unknown',\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n } else {\n this.metadata.courseId = courseId;\n }\n }\n\n /**\n * Inject all loaders into HTML\n */\n inject(html: string): string {\n let result = html;\n\n // Step 1: Add classes and data attributes to <html> tag\n result = this.addHtmlAttributes(result);\n\n // Step 2: Inject CSS Before at start of <head>\n if (this.config.cssBefore.enabled) {\n result = this.injectCssBefore(result);\n }\n\n // Step 3: Inject JS Before at start of <head> (after CSS Before)\n if (this.config.jsBefore.enabled) {\n result = this.injectJsBefore(result);\n }\n\n // Step 4: Inject CSS After at end of <head>\n if (this.config.cssAfter.enabled) {\n result = this.injectCssAfter(result);\n }\n\n // Step 5: Inject JS After at end of <body>\n if (this.config.jsAfter.enabled) {\n result = this.injectJsAfter(result);\n }\n\n // Step 6: Inject plugin assets inline (always executes, independent of remote/local loading)\n if (this.pluginAssets) {\n result = this.injectPluginAssets(result);\n }\n\n return result;\n }\n\n /**\n * Inject plugin assets inline into the HTML.\n * Plugin CSS goes at end of <head>, plugin JS goes at end of <body>.\n * These are injected directly so they always execute regardless of\n * whether remote or local fallback files are loaded.\n */\n private injectPluginAssets(html: string): string {\n let result = html;\n\n // Inject plugin CSS (before + after combined) at end of <head>\n const pluginCss = [\n this.pluginAssets?.cssBefore || '',\n this.pluginAssets?.cssAfter || '',\n ].filter(Boolean).join('\\n');\n\n if (pluginCss) {\n const cssBlock = `<!-- === PATCH-ADAMS: PLUGIN CSS (inline) === -->\n<style data-pa=\"plugin-css\">\n${pluginCss}\n</style>`;\n result = result.replace(/<\\/head>/i, `${cssBlock}\\n</head>`);\n console.log('[HtmlInjector] Injected plugin CSS inline');\n }\n\n // Inject plugin JS (before + after combined) at end of <body>\n const pluginJs = [\n this.pluginAssets?.jsBefore || '',\n this.pluginAssets?.jsAfter || '',\n ].filter(Boolean).join('\\n');\n\n if (pluginJs) {\n const jsBlock = `<!-- === PATCH-ADAMS: PLUGIN JS (inline) === -->\n<script data-pa=\"plugin-js\">\n${pluginJs}\n</script>`;\n result = result.replace(/<\\/body>/i, `${jsBlock}\\n</body>`);\n console.log('[HtmlInjector] Injected plugin JS inline');\n }\n\n return result;\n }\n\n /**\n * Add pa-patched and pa-loading classes to <html> tag\n * Also adds data attributes for course metadata\n */\n private addHtmlAttributes(html: string): string {\n const { htmlClass, loadingClass } = this.config;\n const classes = `${htmlClass} ${loadingClass}`;\n\n // Build data attributes from metadata\n const dataAttrs: string[] = [];\n if (this.metadata) {\n dataAttrs.push(`data-pa-course-id=\"${this.escapeAttr(this.metadata.courseId)}\"`);\n dataAttrs.push(`data-pa-format=\"${this.metadata.format}\"`);\n if (this.metadata.title) {\n dataAttrs.push(`data-pa-title=\"${this.escapeAttr(this.metadata.title)}\"`);\n }\n }\n const dataAttrString = dataAttrs.length > 0 ? ' ' + dataAttrs.join(' ') : '';\n\n // Match <html> tag with or without existing class attribute\n const htmlTagPattern = /<html([^>]*)>/i;\n const match = html.match(htmlTagPattern);\n\n if (!match) {\n return html;\n }\n\n const attributes = match[1];\n\n // Check if class attribute exists\n if (/class\\s*=\\s*[\"'][^\"']*[\"']/i.test(attributes)) {\n // Append to existing class\n return html.replace(\n htmlTagPattern,\n (_match: string, attrs: string) => {\n const newAttrs = attrs.replace(\n /class\\s*=\\s*[\"']([^\"']*)[\"']/i,\n (_fullMatch: string, existingClasses: string) => `class=\"${existingClasses} ${classes}\"`\n );\n return `<html${newAttrs}${dataAttrString}>`;\n }\n );\n } else {\n // Add class attribute\n return html.replace(htmlTagPattern, `<html${attributes} class=\"${classes}\"${dataAttrString}>`);\n }\n }\n\n /**\n * Escape a string for use in HTML attributes\n */\n private escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n /**\n * Inject CSS Before loader at start of <head>\n */\n private injectCssBefore(html: string): string {\n const options = buildCssBeforeOptions(this.config);\n const loader = generateCssBeforeLoader(options);\n\n // Insert right after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject JS Before loader at start of <head> (after CSS Before if present)\n */\n private injectJsBefore(html: string): string {\n const options = buildJsBeforeOptions(this.config, this.metadata);\n const loader = generateJsBeforeLoader(options);\n\n // Look for the CSS Before marker to insert after it\n const cssBeforeMarker = '<!-- === PATCH-ADAMS: CSS BEFORE';\n if (html.includes(cssBeforeMarker)) {\n // Find end of CSS Before block and insert after\n const cssBeforeEndPattern = /<\\/script>\\s*(?=\\n|<)/;\n const cssBeforeStart = html.indexOf(cssBeforeMarker);\n const afterCssBefore = html.substring(cssBeforeStart);\n const endMatch = afterCssBefore.match(cssBeforeEndPattern);\n\n if (endMatch && endMatch.index !== undefined) {\n const insertPos = cssBeforeStart + endMatch.index + endMatch[0].length;\n return html.slice(0, insertPos) + '\\n' + loader + html.slice(insertPos);\n }\n }\n\n // Fallback: insert after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject CSS After loader at end of <head>\n */\n private injectCssAfter(html: string): string {\n const options = buildCssAfterOptions(this.config);\n const loader = generateCssAfterLoader(options);\n\n // Insert before </head>\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject JS After loader at end of <body> (after __loadEntry)\n */\n private injectJsAfter(html: string): string {\n const options = buildJsAfterOptions(this.config);\n const loader = generateJsAfterLoader(options);\n\n // Try to find __loadEntry() call and insert after it\n const loadEntryPattern = /(<script>__loadEntry\\(\\)<\\/script>)/i;\n if (loadEntryPattern.test(html)) {\n return html.replace(loadEntryPattern, `$1\\n${loader}`);\n }\n\n // Fallback: insert before </body>\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n}\n","import type { PatchAdamsConfig } from '../config/schema.js';\nimport type { CourseMetadata } from '../fingerprint/course-metadata.js';\nimport type { GeneratedPluginAssets } from '../plugins/types.js';\nimport {\n generateCssBeforeLoader,\n buildCssBeforeOptions,\n generateCssAfterLoader,\n buildCssAfterOptions,\n generateJsBeforeLoader,\n buildJsBeforeOptions,\n generateJsAfterLoader,\n buildJsAfterOptions,\n} from '../templates/index.js';\n\n/**\n * HTML Injector for patching Articulate Storyline course HTML files\n *\n * Storyline structure:\n * - Main entry: story.html (standalone) or index_lms.html (LMS)\n * - Player scripts: html5/lib/scripts/bootstrapper.min.js (loaded at end of body)\n * - Course data: html5/data/js/*.js\n * - CSS: html5/data/css/output.min.css\n *\n * Injection points:\n * 1. CSS Before - Start of <head> (blocking)\n * 2. JS Before - Start of <head>, after CSS Before (blocking)\n * 3. CSS After - End of <head>, before </head>\n * 4. JS After - End of <body>, before </body> (after content, before bootstrapper runs)\n * 5. Plugin Assets - Inline CSS/JS injected directly (always executes)\n *\n * Also adds classes and data attributes to <html> tag\n */\nexport class StorylineHtmlInjector {\n private config: PatchAdamsConfig;\n private metadata: CourseMetadata | null = null;\n private pluginAssets: GeneratedPluginAssets | null = null;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Set plugin-generated assets to inject inline into the HTML.\n * These are injected directly (not via external files) so they always execute.\n */\n setPluginAssets(assets: GeneratedPluginAssets): void {\n this.pluginAssets = assets;\n }\n\n /**\n * Set the course metadata to be injected into the HTML\n */\n setMetadata(metadata: CourseMetadata): void {\n this.metadata = metadata;\n }\n\n /**\n * Inject all loaders into HTML\n */\n inject(html: string): string {\n let result = html;\n\n // Step 1: Add classes and data attributes to <html> tag\n result = this.addHtmlAttributes(result);\n\n // Step 2: Inject CSS Before at start of <head>\n if (this.config.cssBefore.enabled) {\n result = this.injectCssBefore(result);\n }\n\n // Step 3: Inject JS Before at start of <head> (after CSS Before)\n if (this.config.jsBefore.enabled) {\n result = this.injectJsBefore(result);\n }\n\n // Step 4: Inject CSS After at end of <head>\n if (this.config.cssAfter.enabled) {\n result = this.injectCssAfter(result);\n }\n\n // Step 5: Inject JS After at end of <body>\n if (this.config.jsAfter.enabled) {\n result = this.injectJsAfter(result);\n }\n\n // Step 6: Inject plugin assets inline (always executes, independent of remote/local loading)\n if (this.pluginAssets) {\n result = this.injectPluginAssets(result);\n }\n\n return result;\n }\n\n /**\n * Inject plugin assets inline into the HTML.\n * Plugin CSS goes at end of <head>, plugin JS goes at end of <body>.\n */\n private injectPluginAssets(html: string): string {\n let result = html;\n\n // Inject plugin CSS at end of <head>\n const pluginCss = [\n this.pluginAssets?.cssBefore || '',\n this.pluginAssets?.cssAfter || '',\n ]\n .filter(Boolean)\n .join('\\n');\n\n if (pluginCss) {\n const cssBlock = `<!-- === PATCH-ADAMS: PLUGIN CSS (inline) === -->\n<style data-pa=\"plugin-css\">\n${pluginCss}\n</style>`;\n result = result.replace(/<\\/head>/i, `${cssBlock}\\n</head>`);\n console.log('[StorylineHtmlInjector] Injected plugin CSS inline');\n }\n\n // Inject plugin JS at end of <body>\n const pluginJs = [\n this.pluginAssets?.jsBefore || '',\n this.pluginAssets?.jsAfter || '',\n ]\n .filter(Boolean)\n .join('\\n');\n\n if (pluginJs) {\n const jsBlock = `<!-- === PATCH-ADAMS: PLUGIN JS (inline) === -->\n<script data-pa=\"plugin-js\">\n${pluginJs}\n</script>`;\n result = result.replace(/<\\/body>/i, `${jsBlock}\\n</body>`);\n console.log('[StorylineHtmlInjector] Injected plugin JS inline');\n }\n\n return result;\n }\n\n /**\n * Add pa-patched and pa-loading classes to <html> tag\n * Also adds data attributes for course metadata\n */\n private addHtmlAttributes(html: string): string {\n const { htmlClass, loadingClass } = this.config;\n const classes = `${htmlClass} ${loadingClass}`;\n\n // Build data attributes from metadata\n const dataAttrs: string[] = [];\n if (this.metadata) {\n dataAttrs.push(`data-pa-course-id=\"${this.escapeAttr(this.metadata.courseId)}\"`);\n dataAttrs.push(`data-pa-format=\"${this.metadata.format}\"`);\n dataAttrs.push('data-pa-tool=\"storyline\"');\n if (this.metadata.title) {\n dataAttrs.push(`data-pa-title=\"${this.escapeAttr(this.metadata.title)}\"`);\n }\n }\n const dataAttrString = dataAttrs.length > 0 ? ' ' + dataAttrs.join(' ') : '';\n\n // Match <html> tag with or without existing class attribute\n const htmlTagPattern = /<html([^>]*)>/i;\n const match = html.match(htmlTagPattern);\n\n if (!match) {\n return html;\n }\n\n const attributes = match[1];\n\n // Check if class attribute exists\n if (/class\\s*=\\s*[\"'][^\"']*[\"']/i.test(attributes)) {\n // Append to existing class\n return html.replace(htmlTagPattern, (_match: string, attrs: string) => {\n const newAttrs = attrs.replace(\n /class\\s*=\\s*[\"']([^\"']*)[\"']/i,\n (_fullMatch: string, existingClasses: string) =>\n `class=\"${existingClasses} ${classes}\"`\n );\n return `<html${newAttrs}${dataAttrString}>`;\n });\n } else {\n // Add class attribute\n return html.replace(\n htmlTagPattern,\n `<html${attributes} class=\"${classes}\"${dataAttrString}>`\n );\n }\n }\n\n /**\n * Escape a string for use in HTML attributes\n */\n private escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n }\n\n /**\n * Inject CSS Before loader at start of <head>\n */\n private injectCssBefore(html: string): string {\n const options = buildCssBeforeOptions(this.config);\n const loader = generateCssBeforeLoader(options);\n\n // Insert right after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject JS Before loader at start of <head> (after CSS Before if present)\n */\n private injectJsBefore(html: string): string {\n const options = buildJsBeforeOptions(this.config, this.metadata);\n const loader = generateJsBeforeLoader(options);\n\n // Look for the CSS Before marker to insert after it\n const cssBeforeMarker = '<!-- === PATCH-ADAMS: CSS BEFORE';\n if (html.includes(cssBeforeMarker)) {\n // Find end of CSS Before block and insert after\n const cssBeforeEndPattern = /<\\/script>\\s*(?=\\n|<)/;\n const cssBeforeStart = html.indexOf(cssBeforeMarker);\n const afterCssBefore = html.substring(cssBeforeStart);\n const endMatch = afterCssBefore.match(cssBeforeEndPattern);\n\n if (endMatch && endMatch.index !== undefined) {\n const insertPos = cssBeforeStart + endMatch.index + endMatch[0].length;\n return html.slice(0, insertPos) + '\\n' + loader + html.slice(insertPos);\n }\n }\n\n // Fallback: insert after <head> tag\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n${loader}`);\n }\n\n /**\n * Inject CSS After loader at end of <head>\n */\n private injectCssAfter(html: string): string {\n const options = buildCssAfterOptions(this.config);\n const loader = generateCssAfterLoader(options);\n\n // Insert before </head>\n return html.replace(/<\\/head>/i, `${loader}\\n</head>`);\n }\n\n /**\n * Inject JS After loader at end of <body>\n *\n * Storyline note: The bootstrapper.min.js is loaded via <script src=\"...\">\n * AFTER </body> tag (unusual but that's how Storyline works).\n * We inject before </body> so our code runs before the bootstrapper.\n */\n private injectJsAfter(html: string): string {\n const options = buildJsAfterOptions(this.config);\n const loader = generateJsAfterLoader(options);\n\n // Insert before </body>\n return html.replace(/<\\/body>/i, `${loader}\\n</body>`);\n }\n}\n","import type AdmZip from 'adm-zip';\nimport type { PatchAdamsConfig } from '../config/schema.js';\nimport type { PackageFormat } from '../detectors/format-detector.js';\n\nexport interface ManifestPaths {\n cssBefore?: string;\n cssAfter?: string;\n jsBefore?: string;\n jsAfter?: string;\n}\n\n/**\n * Update manifest files to include injected assets\n * This ensures SCORM compliance for strict LMS systems\n */\nexport class ManifestUpdater {\n private config: PatchAdamsConfig;\n\n constructor(config: PatchAdamsConfig) {\n this.config = config;\n }\n\n /**\n * Update manifest files based on package format\n */\n update(zip: AdmZip, format: PackageFormat, paths: ManifestPaths): string[] {\n const modified: string[] = [];\n\n switch (format) {\n case 'scorm12':\n case 'scorm2004-3':\n case 'scorm2004-4':\n modified.push(...this.updateImsManifest(zip, paths));\n break;\n\n case 'cmi5':\n // cmi5 packages also typically have an imsmanifest.xml\n modified.push(...this.updateImsManifest(zip, paths));\n break;\n\n case 'xapi':\n // xAPI/tincan.xml doesn't have file manifests\n // Files are loaded directly by HTML\n break;\n\n case 'aicc':\n // AICC may have imsmanifest.xml\n const aiccManifest = zip.getEntry('imsmanifest.xml');\n if (aiccManifest) {\n modified.push(...this.updateImsManifest(zip, paths));\n }\n break;\n }\n\n return modified;\n }\n\n /**\n * Update imsmanifest.xml to include new file references\n * Uses string manipulation to preserve original XML formatting exactly\n */\n private updateImsManifest(zip: AdmZip, paths: ManifestPaths): string[] {\n const entry = zip.getEntry('imsmanifest.xml');\n if (!entry) {\n return [];\n }\n\n try {\n const xmlContent = entry.getData().toString('utf-8');\n\n // Build file entries to add\n const filesToAdd = [\n paths.cssBefore,\n paths.cssAfter,\n paths.jsBefore,\n paths.jsAfter,\n ].filter(Boolean) as string[];\n\n if (filesToAdd.length === 0) {\n return [];\n }\n\n // Generate new file entry XML strings\n const newFileEntries = filesToAdd\n .map((filePath) => ` <file href=\"${filePath}\" />`)\n .join('\\n');\n\n // Find the closing </resource> tag and insert before it\n // This preserves all original XML formatting, comments, etc.\n const resourceClosePattern = /(\\s*)<\\/resource>/i;\n const match = xmlContent.match(resourceClosePattern);\n\n if (!match) {\n console.warn('[Patch-Adams] Could not find </resource> tag in imsmanifest.xml');\n return [];\n }\n\n const updatedXml = xmlContent.replace(\n resourceClosePattern,\n `\\n${newFileEntries}\\n$1</resource>`\n );\n\n zip.updateFile('imsmanifest.xml', Buffer.from(updatedXml, 'utf-8'));\n\n return ['imsmanifest.xml'];\n } catch (error) {\n console.error('[Patch-Adams] Failed to update imsmanifest.xml:', error);\n return [];\n }\n }\n\n /**\n * Get the file paths that will be added to the package\n */\n getFilePaths(): ManifestPaths {\n const paths: ManifestPaths = {};\n\n if (this.config.cssBefore.enabled) {\n paths.cssBefore = `scormcontent/${this.config.localFolders.css}/${this.config.cssBefore.filename}`;\n }\n\n if (this.config.cssAfter.enabled) {\n paths.cssAfter = `scormcontent/${this.config.localFolders.css}/${this.config.cssAfter.filename}`;\n }\n\n if (this.config.jsBefore.enabled) {\n paths.jsBefore = `scormcontent/${this.config.localFolders.js}/${this.config.jsBefore.filename}`;\n }\n\n if (this.config.jsAfter.enabled) {\n paths.jsAfter = `scormcontent/${this.config.localFolders.js}/${this.config.jsAfter.filename}`;\n }\n\n return paths;\n }\n}\n","import type AdmZip from 'adm-zip';\n\n/**\n * Supported package formats\n */\nexport type PackageFormat =\n | 'scorm12'\n | 'scorm2004-3'\n | 'scorm2004-4'\n | 'cmi5'\n | 'xapi'\n | 'aicc'\n | 'unknown';\n\n/**\n * Detect the package format by examining manifest files\n */\nexport class FormatDetector {\n /**\n * Detect the package format from a ZIP file\n */\n detect(zip: AdmZip): PackageFormat {\n // Check for cmi5.xml first (most specific)\n if (zip.getEntry('cmi5.xml')) {\n return 'cmi5';\n }\n\n // Check for xAPI (tincan.xml)\n if (zip.getEntry('tincan.xml')) {\n return 'xapi';\n }\n\n // Check for SCORM via imsmanifest.xml\n const manifest = zip.getEntry('imsmanifest.xml');\n if (manifest) {\n const content = manifest.getData().toString('utf-8');\n return this.detectScormVersion(content);\n }\n\n // Check for AICC-specific files\n if (this.hasAiccFiles(zip)) {\n return 'aicc';\n }\n\n return 'unknown';\n }\n\n /**\n * Detect SCORM version from manifest content\n */\n private detectScormVersion(content: string): PackageFormat {\n // Check for SCORM 2004 4th Edition\n if (content.includes('4th Edition') || content.includes('CAM 1.4')) {\n return 'scorm2004-4';\n }\n\n // Check for SCORM 2004 3rd Edition\n if (\n content.includes('3rd Edition') ||\n content.includes('2004 3rd') ||\n content.includes('adlcp_v1p3')\n ) {\n return 'scorm2004-3';\n }\n\n // Check for SCORM 2004 (generic)\n if (content.includes('2004') && content.includes('adlseq')) {\n return 'scorm2004-3'; // Default to 3rd edition for generic 2004\n }\n\n // Check for SCORM 1.2\n if (\n content.includes('1.2') ||\n content.includes('adlcp_rootv1p2') ||\n content.includes('imscp_rootv1p1p2')\n ) {\n return 'scorm12';\n }\n\n // Check for AICC markers in manifest\n if (content.toLowerCase().includes('aicc')) {\n return 'aicc';\n }\n\n // Default to SCORM 1.2 if we have a manifest but can't determine version\n return 'scorm12';\n }\n\n /**\n * Check for AICC-specific files\n */\n private hasAiccFiles(zip: AdmZip): boolean {\n const aiccExtensions = ['.crs', '.au', '.des', '.cst'];\n const entries = zip.getEntries();\n\n for (const entry of entries) {\n const name = entry.entryName.toLowerCase();\n if (aiccExtensions.some((ext) => name.endsWith(ext))) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Get a human-readable format name\n */\n getFormatDisplayName(format: PackageFormat): string {\n const names: Record<PackageFormat, string> = {\n scorm12: 'SCORM 1.2',\n 'scorm2004-3': 'SCORM 2004 3rd Edition',\n 'scorm2004-4': 'SCORM 2004 4th Edition',\n cmi5: 'cmi5',\n xapi: 'xAPI (Tin Can)',\n aicc: 'AICC',\n unknown: 'Unknown',\n };\n\n return names[format];\n }\n}\n","import type AdmZip from 'adm-zip';\n\n/**\n * Supported e-learning authoring tools\n */\nexport type AuthoringTool =\n | 'rise'\n | 'storyline'\n | 'captivate'\n | 'lectora'\n | 'xyleme'\n | 'unknown';\n\n/**\n * Information about the detected authoring tool\n */\nexport interface AuthoringToolInfo {\n tool: AuthoringTool;\n displayName: string;\n /** Path to the main HTML file to patch */\n mainHtmlPath: string;\n /** Additional HTML files that should also be patched (e.g., index_lms.html for Storyline) */\n additionalHtmlPaths?: string[];\n /** Version of the authoring tool if detectable */\n version?: string;\n}\n\n/**\n * Detect the authoring tool used to create a course package.\n * This determines which HTML injection strategy to use.\n */\nexport class AuthoringToolDetector {\n /**\n * Detect the authoring tool from a ZIP file\n */\n detect(zip: AdmZip): AuthoringToolInfo {\n // Check for Rise first (most common in our use case)\n const riseResult = this.detectRise(zip);\n if (riseResult) return riseResult;\n\n // Check for Storyline\n const storylineResult = this.detectStoryline(zip);\n if (storylineResult) return storylineResult;\n\n // Check for Adobe Captivate\n const captivateResult = this.detectCaptivate(zip);\n if (captivateResult) return captivateResult;\n\n // Check for Lectora\n const lectoraResult = this.detectLectora(zip);\n if (lectoraResult) return lectoraResult;\n\n // Check for Xyleme\n const xylemeResult = this.detectXyleme(zip);\n if (xylemeResult) return xylemeResult;\n\n // Unknown - try to find any index.html\n return this.detectUnknown(zip);\n }\n\n /**\n * Detect Articulate Rise packages\n * Rise packages have: scormcontent/index.html with __loadEntry() call\n */\n private detectRise(zip: AdmZip): AuthoringToolInfo | null {\n const indexEntry = zip.getEntry('scormcontent/index.html');\n if (!indexEntry) return null;\n\n const content = indexEntry.getData().toString('utf-8');\n\n // Rise has a distinctive __loadEntry() call\n if (content.includes('__loadEntry()')) {\n // Try to extract version from comments\n const versionMatch = content.match(/Rise\\s+v?(\\d+\\.\\d+(?:\\.\\d+)?)/i);\n return {\n tool: 'rise',\n displayName: 'Articulate Rise',\n mainHtmlPath: 'scormcontent/index.html',\n version: versionMatch?.[1],\n };\n }\n\n // Also check for Rise-specific lib structure\n if (zip.getEntry('scormcontent/lib/rise/')) {\n return {\n tool: 'rise',\n displayName: 'Articulate Rise',\n mainHtmlPath: 'scormcontent/index.html',\n };\n }\n\n return null;\n }\n\n /**\n * Detect Articulate Storyline packages\n * Storyline packages have: story.html with window.globals.publishSource = 'storyline'\n * or html5/lib/scripts/bootstrapper.min.js\n *\n * IMPORTANT: Storyline has TWO entry points that need patching:\n * - story.html: for standalone viewing\n * - index_lms.html: for LMS launch (what SCORM/xAPI uses)\n */\n private detectStoryline(zip: AdmZip): AuthoringToolInfo | null {\n const storyEntry = zip.getEntry('story.html');\n const lmsEntry = zip.getEntry('index_lms.html');\n\n // Build list of HTML files to patch\n const htmlPaths: string[] = [];\n if (storyEntry) htmlPaths.push('story.html');\n if (lmsEntry) htmlPaths.push('index_lms.html');\n\n // Check for story.html (primary entry point for standalone)\n if (storyEntry) {\n const content = storyEntry.getData().toString('utf-8');\n\n // Storyline marker in window.globals\n if (\n content.includes(\"publishSource: 'storyline'\") ||\n content.includes('publishSource:\"storyline\"')\n ) {\n const versionMatch = content.match(/playerVersion:\\s*['\"](\\d+\\.\\d+\\.\\d+\\.\\d+)['\"]/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n\n // Also check HTML comment for Storyline\n if (content.includes('Created using Storyline')) {\n const versionMatch = content.match(/version:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n }\n\n // Check for index_lms.html (LMS entry point) as fallback detection\n if (lmsEntry) {\n const content = lmsEntry.getData().toString('utf-8');\n if (\n content.includes(\"publishSource: 'storyline'\") ||\n content.includes('Created using Storyline')\n ) {\n const versionMatch = content.match(/version:\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)/);\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n version: versionMatch?.[1],\n };\n }\n }\n\n // Check for Storyline's distinctive folder structure\n if (zip.getEntry('html5/lib/scripts/bootstrapper.min.js')) {\n // Found Storyline structure, determine main HTML\n if (htmlPaths.length > 0) {\n return {\n tool: 'storyline',\n displayName: 'Articulate Storyline',\n mainHtmlPath: htmlPaths[0],\n additionalHtmlPaths: htmlPaths.slice(1),\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Adobe Captivate packages\n * Captivate packages typically have: project.txt or Captivate.js\n */\n private detectCaptivate(zip: AdmZip): AuthoringToolInfo | null {\n // Check for Captivate markers\n const entries = zip.getEntries();\n for (const entry of entries) {\n const name = entry.entryName.toLowerCase();\n if (name.includes('captivate') && name.endsWith('.js')) {\n // Find main HTML\n const indexHtml = zip.getEntry('index.html');\n return {\n tool: 'captivate',\n displayName: 'Adobe Captivate',\n mainHtmlPath: indexHtml ? 'index.html' : 'index.html',\n };\n }\n }\n\n // Check index.html for Captivate markers\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n if (\n content.includes('Adobe Captivate') ||\n content.includes('cp.') ||\n content.includes('cpAPIInterface')\n ) {\n return {\n tool: 'captivate',\n displayName: 'Adobe Captivate',\n mainHtmlPath: 'index.html',\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Lectora packages\n * Lectora packages have distinctive trivantis markers\n */\n private detectLectora(zip: AdmZip): AuthoringToolInfo | null {\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n if (\n content.includes('Trivantis') ||\n content.includes('Lectora') ||\n content.includes('trivDojo')\n ) {\n return {\n tool: 'lectora',\n displayName: 'Lectora',\n mainHtmlPath: 'index.html',\n };\n }\n }\n\n return null;\n }\n\n /**\n * Detect Xyleme/Bravais packages (thin pack or full)\n * Xyleme packages have: window.Xyleme namespace, cds-import.xml, or manifest.xml\n */\n private detectXyleme(zip: AdmZip): AuthoringToolInfo | null {\n // Check for Xyleme-specific files\n const hasCdsImport = zip.getEntry('cds-import.xml') !== null;\n const hasXylemeManifest = zip.getEntry('manifest.xml') !== null;\n\n // Check index.html for Xyleme markers\n const indexEntry = zip.getEntry('index.html');\n if (indexEntry) {\n const content = indexEntry.getData().toString('utf-8');\n\n // Xyleme has distinctive window.Xyleme namespace\n if (\n content.includes('window.Xyleme') ||\n content.includes('Xyleme.environment') ||\n hasCdsImport ||\n hasXylemeManifest\n ) {\n // Try to extract version\n const versionMatch = content.match(/XPE_PUBLIC_VERSION\\s*=\\s*['\"]([^'\"]+)['\"]/);\n\n // Collect all HTML pages that need patching (Xyleme has many page HTMLs)\n const additionalHtmlPaths: string[] = [];\n const entries = zip.getEntries();\n for (const entry of entries) {\n // Xyleme pages are GUIDs like: 9ee5c376-a354-45ca-9565-6472233fc13e.html\n if (\n entry.entryName.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\\.html$/i)\n ) {\n additionalHtmlPaths.push(entry.entryName);\n }\n }\n\n return {\n tool: 'xyleme',\n displayName: 'Xyleme/Bravais',\n mainHtmlPath: 'index.html',\n additionalHtmlPaths: additionalHtmlPaths.length > 0 ? additionalHtmlPaths : undefined,\n version: versionMatch?.[1],\n };\n }\n }\n\n return null;\n }\n\n /**\n * Fallback detection for unknown authoring tools\n * Try to find any suitable HTML file to patch\n */\n private detectUnknown(zip: AdmZip): AuthoringToolInfo {\n // Priority list of HTML files to look for\n const htmlFiles = [\n 'index.html',\n 'index_lms.html',\n 'story.html',\n 'launch.html',\n 'player.html',\n 'scormcontent/index.html',\n ];\n\n for (const htmlFile of htmlFiles) {\n if (zip.getEntry(htmlFile)) {\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: htmlFile,\n };\n }\n }\n\n // Last resort: find any .html file at root or one level deep\n const entries = zip.getEntries();\n for (const entry of entries) {\n if (\n entry.entryName.endsWith('.html') &&\n !entry.entryName.includes('/') // root level\n ) {\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: entry.entryName,\n };\n }\n }\n\n // Absolutely nothing found\n return {\n tool: 'unknown',\n displayName: 'Unknown Authoring Tool',\n mainHtmlPath: 'index.html', // Will fail later if doesn't exist\n };\n }\n\n /**\n * Get a human-readable tool name\n */\n getToolDisplayName(tool: AuthoringTool): string {\n const names: Record<AuthoringTool, string> = {\n rise: 'Articulate Rise',\n storyline: 'Articulate Storyline',\n captivate: 'Adobe Captivate',\n lectora: 'Lectora',\n xyleme: 'Xyleme/Bravais',\n unknown: 'Unknown',\n };\n return names[tool];\n }\n}\n","import type AdmZip from 'adm-zip';\n\n/**\n * Comprehensive course metadata extracted from manifests\n */\nexport interface CourseMetadata {\n /** The Rise course ID (from manifest identifier) */\n courseId: string;\n /** Source of the course ID */\n courseIdSource: 'manifest' | 'tincan' | 'cmi5' | 'fallback';\n /** Package format */\n format: 'scorm12' | 'scorm2004-3' | 'scorm2004-4' | 'cmi5' | 'xapi' | 'aicc' | 'unknown';\n /** Course title */\n title: string | null;\n /** Course description */\n description: string | null;\n /** Organization/item identifier within the manifest */\n organizationId: string | null;\n /** Launch URL/entry point */\n launchUrl: string | null;\n /** Mastery score (if specified) */\n masteryScore: number | null;\n /** Duration/time limit (if specified) */\n duration: string | null;\n /** Language code */\n language: string | null;\n /** Version info from manifest */\n version: string | null;\n /** Timestamp when this metadata was extracted */\n extractedAt: string;\n /** Unique ID generated for this specific patch operation */\n patchId?: string;\n}\n\n/**\n * Extract comprehensive course metadata from a course package\n */\nexport function extractCourseMetadata(zip: AdmZip, format: string): CourseMetadata {\n const metadata: CourseMetadata = {\n courseId: 'unknown',\n courseIdSource: 'fallback',\n format: format as CourseMetadata['format'],\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n\n // Try to extract from imsmanifest.xml (SCORM)\n const manifest = zip.getEntry('imsmanifest.xml');\n if (manifest) {\n extractFromImsManifest(manifest.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract from tincan.xml (xAPI)\n const tincan = zip.getEntry('tincan.xml');\n if (tincan) {\n extractFromTincan(tincan.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract from cmi5.xml\n const cmi5 = zip.getEntry('cmi5.xml');\n if (cmi5) {\n extractFromCmi5(cmi5.getData().toString('utf-8'), metadata);\n }\n\n // Try to extract title from index.html as fallback\n if (!metadata.title) {\n const indexHtml = zip.getEntry('scormcontent/index.html');\n if (indexHtml) {\n const content = indexHtml.getData().toString('utf-8');\n const titleMatch = content.match(/<title>([^<]+)<\\/title>/i);\n if (titleMatch) {\n metadata.title = titleMatch[1].trim();\n }\n }\n }\n\n return metadata;\n}\n\n/**\n * Extract metadata from imsmanifest.xml (SCORM 1.2 and 2004)\n */\nfunction extractFromImsManifest(content: string, metadata: CourseMetadata): void {\n // Course ID from manifest identifier\n const identifierMatch = content.match(/<manifest[^>]+identifier\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (identifierMatch) {\n metadata.courseId = identifierMatch[1];\n metadata.courseIdSource = 'manifest';\n }\n\n // Version from manifest version attribute\n const versionMatch = content.match(/<manifest[^>]+version\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (versionMatch) {\n metadata.version = versionMatch[1];\n }\n\n // Title - try multiple locations\n // 1. From <title> within <organization>\n const orgTitleMatch = content.match(/<organization[^>]*>[\\s\\S]*?<title>([^<]+)<\\/title>/i);\n if (orgTitleMatch) {\n metadata.title = orgTitleMatch[1].trim();\n }\n\n // 2. From <lom:title> or <general><title>\n if (!metadata.title) {\n const lomTitleMatch = content.match(/<(?:lom:)?title>\\s*<(?:lom:)?langstring[^>]*>([^<]+)<\\/(?:lom:)?langstring>/i);\n if (lomTitleMatch) {\n metadata.title = lomTitleMatch[1].trim();\n }\n }\n\n // Description from LOM metadata\n const descMatch = content.match(/<(?:lom:)?description>\\s*<(?:lom:)?langstring[^>]*>([^<]+)<\\/(?:lom:)?langstring>/i);\n if (descMatch) {\n metadata.description = descMatch[1].trim();\n }\n\n // Organization identifier\n const orgIdMatch = content.match(/<organization[^>]+identifier\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (orgIdMatch) {\n metadata.organizationId = orgIdMatch[1];\n }\n\n // Launch URL from <resource> href\n const resourceMatch = content.match(/<resource[^>]+href\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (resourceMatch) {\n metadata.launchUrl = resourceMatch[1];\n }\n\n // Mastery score (SCORM 1.2 style)\n const masteryMatch = content.match(/<adlcp:masteryscore>([^<]+)<\\/adlcp:masteryscore>/i);\n if (masteryMatch) {\n const score = parseFloat(masteryMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score;\n }\n }\n\n // Mastery score (SCORM 2004 style - completion threshold)\n if (metadata.masteryScore === null) {\n const thresholdMatch = content.match(/<imsss:minNormalizedMeasure>([^<]+)<\\/imsss:minNormalizedMeasure>/i);\n if (thresholdMatch) {\n const score = parseFloat(thresholdMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score * 100; // Convert 0-1 to 0-100\n }\n }\n }\n\n // Duration/time limit\n const durationMatch = content.match(/<(?:adlcp:)?timelimitaction>([^<]+)<\\/(?:adlcp:)?timelimitaction>/i);\n if (durationMatch) {\n metadata.duration = durationMatch[1].trim();\n }\n\n // Language from xml:lang or lom:language\n const langMatch = content.match(/xml:lang\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (langMatch) {\n metadata.language = langMatch[1];\n }\n if (!metadata.language) {\n const lomLangMatch = content.match(/<(?:lom:)?language>([^<]+)<\\/(?:lom:)?language>/i);\n if (lomLangMatch) {\n metadata.language = lomLangMatch[1].trim();\n }\n }\n}\n\n/**\n * Extract metadata from tincan.xml (xAPI courses)\n */\nfunction extractFromTincan(content: string, metadata: CourseMetadata): void {\n // Activity ID as course ID\n const idMatch = content.match(/<activity[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (idMatch && metadata.courseIdSource === 'fallback') {\n metadata.courseId = idMatch[1];\n metadata.courseIdSource = 'tincan';\n }\n\n // Activity name/title\n const nameMatch = content.match(/<name[^>]*>([^<]+)<\\/name>/i);\n if (nameMatch && !metadata.title) {\n metadata.title = nameMatch[1].trim();\n }\n\n // Activity description\n const descMatch = content.match(/<description[^>]*>([^<]+)<\\/description>/i);\n if (descMatch && !metadata.description) {\n metadata.description = descMatch[1].trim();\n }\n\n // Launch URL\n const launchMatch = content.match(/<launch[^>]*>([^<]+)<\\/launch>/i);\n if (launchMatch) {\n metadata.launchUrl = launchMatch[1].trim();\n }\n}\n\n/**\n * Extract metadata from cmi5.xml\n */\nfunction extractFromCmi5(content: string, metadata: CourseMetadata): void {\n // Course ID\n const courseIdMatch = content.match(/<course[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (courseIdMatch && metadata.courseIdSource === 'fallback') {\n metadata.courseId = courseIdMatch[1];\n metadata.courseIdSource = 'cmi5';\n }\n\n // Course title\n const titleMatch = content.match(/<title[^>]*>([^<]+)<\\/title>/i);\n if (titleMatch && !metadata.title) {\n metadata.title = titleMatch[1].trim();\n }\n\n // Course description\n const descMatch = content.match(/<description[^>]*>([^<]+)<\\/description>/i);\n if (descMatch && !metadata.description) {\n metadata.description = descMatch[1].trim();\n }\n\n // AU launch URL\n const launchMatch = content.match(/<url[^>]*>([^<]+)<\\/url>/i);\n if (launchMatch) {\n metadata.launchUrl = launchMatch[1].trim();\n }\n\n // Mastery score from moveOn\n const moveOnMatch = content.match(/moveOn\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (moveOnMatch) {\n // cmi5 uses moveOn values like \"Passed\", \"Completed\", \"CompletedAndPassed\"\n // If PassedOrFailed is required, there's likely a mastery score\n if (moveOnMatch[1].toLowerCase().includes('passed')) {\n const masteryMatch = content.match(/masteryScore\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (masteryMatch) {\n const score = parseFloat(masteryMatch[1]);\n if (!isNaN(score)) {\n metadata.masteryScore = score;\n }\n }\n }\n }\n}\n","import type {\n PatchAdamsPlugin,\n PluginConfig,\n PluginsConfig,\n GeneratedPluginAssets,\n} from './types.js';\n\n/**\n * Plugin Registry for Patch-Adams\n *\n * Manages registration and asset generation for plugins.\n * Plugins are registered at module load time and invoked during patching\n * based on the configuration.\n */\nexport class PluginRegistry {\n private plugins: Map<string, PatchAdamsPlugin> = new Map();\n\n /**\n * Register a plugin with the registry.\n * Plugins should be registered at module initialization time.\n *\n * @param plugin - The plugin to register\n * @throws Error if a plugin with the same name is already registered\n */\n register(plugin: PatchAdamsPlugin): void {\n if (this.plugins.has(plugin.name)) {\n throw new Error(`Plugin \"${plugin.name}\" is already registered`);\n }\n this.plugins.set(plugin.name, plugin);\n console.log(`[PA-Plugins] Registered: ${plugin.name} v${plugin.version}`);\n }\n\n /**\n * Unregister a plugin by name.\n *\n * @param name - The plugin name to unregister\n * @returns true if the plugin was removed, false if it wasn't registered\n */\n unregister(name: string): boolean {\n const removed = this.plugins.delete(name);\n if (removed) {\n console.log(`[PA-Plugins] Unregistered: ${name}`);\n }\n return removed;\n }\n\n /**\n * Get a registered plugin by name.\n *\n * @param name - The plugin name\n * @returns The plugin or undefined if not found\n */\n getPlugin(name: string): PatchAdamsPlugin | undefined {\n return this.plugins.get(name);\n }\n\n /**\n * Get all registered plugins.\n *\n * @returns Array of all registered plugins\n */\n getAllPlugins(): PatchAdamsPlugin[] {\n return Array.from(this.plugins.values());\n }\n\n /**\n * Get names of all registered plugins.\n *\n * @returns Array of plugin names\n */\n getPluginNames(): string[] {\n return Array.from(this.plugins.keys());\n }\n\n /**\n * Check if a plugin is registered.\n *\n * @param name - The plugin name to check\n * @returns true if registered\n */\n hasPlugin(name: string): boolean {\n return this.plugins.has(name);\n }\n\n /**\n * Generate combined assets from all enabled plugins.\n *\n * @param pluginConfigs - Configuration for each plugin (keyed by plugin name)\n * @returns Combined CSS and JS assets from all enabled plugins\n */\n generateAssets(pluginConfigs: PluginsConfig): GeneratedPluginAssets {\n const result: GeneratedPluginAssets = {\n cssBefore: '',\n cssAfter: '',\n jsBefore: '',\n jsAfter: '',\n };\n\n for (const [pluginName, rawConfig] of Object.entries(pluginConfigs)) {\n // Skip if plugin is not enabled\n if (!rawConfig.enabled) {\n console.log(`[PA-Plugins] Skipping disabled plugin: ${pluginName}`);\n continue;\n }\n\n // Get the plugin\n const plugin = this.plugins.get(pluginName);\n if (!plugin) {\n console.warn(`[PA-Plugins] Plugin \"${pluginName}\" not found in registry, skipping`);\n continue;\n }\n\n try {\n // Validate configuration against plugin schema\n const validatedConfig = plugin.configSchema.parse(rawConfig) as PluginConfig;\n console.log(`[PA-Plugins] Generating assets for: ${pluginName}`);\n\n // Generate CSS\n if (plugin.generateCss) {\n const css = plugin.generateCss(validatedConfig);\n if (css.before) {\n result.cssBefore += `/* === Plugin: ${pluginName} === */\\n${css.before}\\n\\n`;\n }\n if (css.after) {\n result.cssAfter += `/* === Plugin: ${pluginName} === */\\n${css.after}\\n\\n`;\n }\n }\n\n // Generate JS\n if (plugin.generateJs) {\n const js = plugin.generateJs(validatedConfig);\n if (js.before) {\n result.jsBefore += `// === Plugin: ${pluginName} ===\\n${js.before}\\n\\n`;\n }\n if (js.after) {\n result.jsAfter += `// === Plugin: ${pluginName} ===\\n${js.after}\\n\\n`;\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`[PA-Plugins] Error processing plugin \"${pluginName}\": ${message}`);\n // Continue with other plugins\n }\n }\n\n return result;\n }\n\n /**\n * Clear all registered plugins.\n * Useful for testing.\n */\n clear(): void {\n this.plugins.clear();\n console.log('[PA-Plugins] Registry cleared');\n }\n}\n\n/**\n * Global plugin registry instance.\n * Use this singleton to register and access plugins.\n */\nexport const pluginRegistry = new PluginRegistry();\n","import AdmZip from 'adm-zip';\nimport archiver from 'archiver';\nimport { PassThrough } from 'stream';\nimport type { PatchAdamsConfig } from '../config/schema.js';\nimport { mergeWithDefaults } from '../config/loader.js';\nimport { HtmlInjector } from './html-injector.js';\nimport { StorylineHtmlInjector } from './storyline-html-injector.js';\nimport { ManifestUpdater } from './manifest-updater.js';\nimport { FormatDetector, type PackageFormat } from '../detectors/format-detector.js';\nimport {\n AuthoringToolDetector,\n type AuthoringTool,\n type AuthoringToolInfo,\n} from '../detectors/authoring-tool-detector.js';\nimport { extractCourseMetadata, type CourseMetadata } from '../fingerprint/course-metadata.js';\nimport { pluginRegistry, type GeneratedPluginAssets } from '../plugins/index.js';\n\n/**\n * Result of a patch operation\n */\nexport interface PatchResult {\n success: boolean;\n format: PackageFormat;\n formatDisplayName: string;\n /** Detected authoring tool */\n authoringTool: AuthoringTool;\n authoringToolDisplayName: string;\n /** Full course metadata */\n metadata: CourseMetadata;\n /** @deprecated Use metadata.courseId instead */\n courseId: string;\n /** @deprecated Use metadata.courseIdSource instead */\n courseIdSource: 'manifest' | 'tincan' | 'cmi5' | 'fallback';\n filesModified: string[];\n filesAdded: string[];\n errors: string[];\n warnings: string[];\n}\n\n/**\n * Options for patching\n */\nexport interface PatchOptions {\n /** Content for CSS before (local fallback) - overrides fetched content */\n cssBeforeContent?: string;\n /** Content for CSS after (local fallback) - overrides fetched content */\n cssAfterContent?: string;\n /** Content for JS before (local fallback) - overrides fetched content */\n jsBeforeContent?: string;\n /** Content for JS after (local fallback) - overrides fetched content */\n jsAfterContent?: string;\n /** Skip fetching remote files even if fetchFallbacks is enabled in config */\n skipFetch?: boolean;\n}\n\n/**\n * Fetched fallback content from remote server\n */\ninterface FetchedFallbacks {\n cssBefore?: string;\n cssAfter?: string;\n jsBefore?: string;\n jsAfter?: string;\n}\n\n/**\n * Default fallback file contents\n */\nconst DEFAULT_CSS_BEFORE = `/* Patch-Adams: CSS Before (blocking)\n * This file loads at the start of <head> and blocks rendering.\n * Use it to hide content and prevent flash of unstyled content.\n *\n * Example:\n * html.pa-patched.pa-loading body {\n * visibility: hidden !important;\n * }\n */\n`;\n\nconst DEFAULT_CSS_AFTER = `/* Patch-Adams: CSS After (async)\n * This file loads at the end of <head> with remote fallback.\n * Use it for style overrides that take precedence over Rise styles.\n *\n * Example:\n * html.pa-patched .blocks-text {\n * font-family: 'Your Font', sans-serif !important;\n * }\n */\n`;\n\nconst DEFAULT_JS_BEFORE = `// Patch-Adams: JS Before (blocking)\n// This file loads at the start of <head> and blocks rendering.\n// Use it for setup, API interception, and preparing globals.\n//\n// Example:\n// window.PatchAdams.customConfig = {\n// theme: 'dark',\n// analytics: true\n// };\n\nconsole.log('[Patch-Adams] JS Before loaded');\n`;\n\nconst DEFAULT_JS_AFTER = `// Patch-Adams: JS After (async)\n// This file loads at the end of <body> with remote fallback.\n// Use it for DOM manipulation after Rise has initialized.\n//\n// IMPORTANT: This script should NOT remove the loading class.\n// The loader script handles that automatically after this script loads.\n//\n// Example:\n// document.querySelectorAll('.blocks-text').forEach(function(el) {\n// // Your modifications here\n// });\n\nconsole.log('[Patch-Adams] JS After loaded');\n`;\n\n/**\n * Main Patcher class for patching e-learning course packages\n * Supports Rise, Storyline, and other authoring tools\n */\nexport class Patcher {\n private config: PatchAdamsConfig;\n private riseHtmlInjector: HtmlInjector;\n private storylineHtmlInjector: StorylineHtmlInjector;\n private manifestUpdater: ManifestUpdater;\n private formatDetector: FormatDetector;\n private authoringToolDetector: AuthoringToolDetector;\n\n constructor(config: Partial<PatchAdamsConfig>) {\n // Merge with defaults so partial configs work correctly\n this.config = mergeWithDefaults(config);\n this.riseHtmlInjector = new HtmlInjector(this.config);\n this.storylineHtmlInjector = new StorylineHtmlInjector(this.config);\n this.manifestUpdater = new ManifestUpdater(this.config);\n this.formatDetector = new FormatDetector();\n this.authoringToolDetector = new AuthoringToolDetector();\n }\n\n /**\n * Fetch a single file from a URL\n * Returns undefined if fetch fails (non-blocking)\n */\n private async fetchFile(url: string): Promise<string | undefined> {\n try {\n console.log(`[Patcher] Fetching: ${url}`);\n const response = await fetch(url);\n if (!response.ok) {\n console.warn(`[Patcher] Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n return undefined;\n }\n const content = await response.text();\n console.log(`[Patcher] Fetched ${url} (${content.length} bytes)`);\n return content;\n } catch (error) {\n console.warn(`[Patcher] Error fetching ${url}:`, error);\n return undefined;\n }\n }\n\n /**\n * Fetch all remote fallback files\n * Non-blocking: if any fetch fails, we use the default content\n */\n private async fetchRemoteFallbacks(): Promise<FetchedFallbacks> {\n const fetched: FetchedFallbacks = {};\n const { remoteDomain, localFolders, cssBefore, cssAfter, jsBefore, jsAfter } = this.config;\n\n const fetchPromises: Promise<void>[] = [];\n\n if (cssBefore.enabled) {\n const url = `${remoteDomain}/${localFolders.css}/${cssBefore.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.cssBefore = content; })\n );\n }\n\n if (cssAfter.enabled) {\n const url = `${remoteDomain}/${localFolders.css}/${cssAfter.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.cssAfter = content; })\n );\n }\n\n if (jsBefore.enabled) {\n const url = `${remoteDomain}/${localFolders.js}/${jsBefore.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.jsBefore = content; })\n );\n }\n\n if (jsAfter.enabled) {\n const url = `${remoteDomain}/${localFolders.js}/${jsAfter.filename}`;\n fetchPromises.push(\n this.fetchFile(url).then(content => { fetched.jsAfter = content; })\n );\n }\n\n // Fetch all in parallel\n await Promise.all(fetchPromises);\n\n return fetched;\n }\n\n /**\n * Patch a Rise course ZIP file\n * @param inputBuffer - Input ZIP file as Buffer\n * @param options - Additional patching options\n * @returns Patched ZIP file as Buffer and result details\n */\n async patch(\n inputBuffer: Buffer,\n options: PatchOptions = {}\n ): Promise<{ buffer: Buffer; result: PatchResult }> {\n // Create placeholder metadata for initialization\n const placeholderMetadata: CourseMetadata = {\n courseId: '',\n courseIdSource: 'fallback',\n format: 'unknown',\n title: null,\n description: null,\n organizationId: null,\n launchUrl: null,\n masteryScore: null,\n duration: null,\n language: null,\n version: null,\n extractedAt: new Date().toISOString(),\n };\n\n const result: PatchResult = {\n success: false,\n format: 'unknown',\n formatDisplayName: 'Unknown',\n authoringTool: 'unknown',\n authoringToolDisplayName: 'Unknown',\n metadata: placeholderMetadata,\n courseId: '',\n courseIdSource: 'fallback',\n filesModified: [],\n filesAdded: [],\n errors: [],\n warnings: [],\n };\n\n try {\n // 1. Open ZIP with adm-zip for reading\n const zip = new AdmZip(inputBuffer);\n\n // 2. Detect package format\n result.format = this.formatDetector.detect(zip);\n result.formatDisplayName = this.formatDetector.getFormatDisplayName(result.format);\n\n if (result.format === 'unknown') {\n result.warnings.push('Could not determine package format, proceeding anyway');\n }\n\n // 2b. Detect authoring tool\n const toolInfo = this.authoringToolDetector.detect(zip);\n result.authoringTool = toolInfo.tool;\n result.authoringToolDisplayName = toolInfo.displayName;\n\n console.log(`[Patcher] Detected authoring tool: ${toolInfo.displayName}`);\n console.log(` - Main HTML: ${toolInfo.mainHtmlPath}`);\n if (toolInfo.additionalHtmlPaths?.length) {\n console.log(` - Additional HTML: ${toolInfo.additionalHtmlPaths.join(', ')}`);\n }\n if (toolInfo.version) console.log(` - Version: ${toolInfo.version}`);\n\n // 3. Extract comprehensive course metadata\n const metadata = extractCourseMetadata(zip, result.format);\n result.metadata = metadata;\n result.courseId = metadata.courseId;\n result.courseIdSource = metadata.courseIdSource;\n\n console.log(`[Patcher] Course metadata extracted:`);\n console.log(` - Course ID: ${metadata.courseId} (source: ${metadata.courseIdSource})`);\n console.log(` - Format: ${metadata.format}`);\n if (metadata.title) console.log(` - Title: ${metadata.title}`);\n if (metadata.description) console.log(` - Description: ${metadata.description.substring(0, 50)}...`);\n if (metadata.language) console.log(` - Language: ${metadata.language}`);\n if (metadata.masteryScore !== null) console.log(` - Mastery Score: ${metadata.masteryScore}`);\n\n // 4. Set course metadata on appropriate HTML injector\n const htmlInjector = this.getHtmlInjector(toolInfo.tool);\n htmlInjector.setMetadata(metadata);\n\n // 5. Fetch remote fallback files if enabled\n let fetchedFallbacks: FetchedFallbacks = {};\n if (this.config.fetchFallbacks && !options.skipFetch) {\n console.log('[Patcher] Fetching remote files for local fallbacks...');\n fetchedFallbacks = await this.fetchRemoteFallbacks();\n const fetchedCount = Object.values(fetchedFallbacks).filter(Boolean).length;\n console.log(`[Patcher] Fetched ${fetchedCount} remote files`);\n }\n\n // 5b. Generate plugin assets and pass to HTML injector for inline injection\n const pluginAssets = this.generatePluginAssets();\n const hasPluginAssets =\n pluginAssets.cssBefore || pluginAssets.cssAfter || pluginAssets.jsBefore || pluginAssets.jsAfter;\n if (hasPluginAssets) {\n htmlInjector.setPluginAssets(pluginAssets);\n console.log('[Patcher] Plugin assets will be injected inline into HTML');\n }\n\n // 6. Find and patch HTML files (varies by authoring tool)\n // Some tools like Storyline have multiple entry points (story.html, index_lms.html)\n const htmlPathsToPatch = [toolInfo.mainHtmlPath, ...(toolInfo.additionalHtmlPaths || [])];\n\n for (const htmlPath of htmlPathsToPatch) {\n const htmlEntry = zip.getEntry(htmlPath);\n if (!htmlEntry) {\n if (htmlPath === toolInfo.mainHtmlPath) {\n // Main HTML is required\n throw new Error(\n `Could not find ${htmlPath} in package. Is this a valid ${toolInfo.displayName} export?`\n );\n }\n // Additional HTML files are optional\n console.log(`[Patcher] Optional HTML file not found: ${htmlPath}`);\n continue;\n }\n\n console.log(`[Patcher] Patching HTML file: ${htmlPath}`);\n const originalHtml = htmlEntry.getData().toString('utf-8');\n const patchedHtml = htmlInjector.inject(originalHtml);\n zip.updateFile(htmlPath, Buffer.from(patchedHtml, 'utf-8'));\n result.filesModified.push(htmlPath);\n }\n\n // 7. Add local fallback files (using fetched content or defaults)\n const addedFiles = this.addFallbackFiles(zip, options, fetchedFallbacks, toolInfo);\n result.filesAdded.push(...addedFiles);\n\n // 8. Update manifest files\n if (this.config.updateManifests) {\n // Build manifest paths from the actual files added (not hardcoded paths)\n const manifestPaths = this.buildManifestPaths(addedFiles);\n const modifiedManifests = this.manifestUpdater.update(zip, result.format, manifestPaths);\n result.filesModified.push(...modifiedManifests);\n }\n\n // 9. Write output using archiver for better ZIP compatibility\n // adm-zip's toBuffer() produces ZIPs that some parsers don't handle well\n const outputBuffer = await this.writeZipWithArchiver(zip);\n\n result.success = true;\n return { buffer: outputBuffer, result };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n result.errors.push(message);\n throw error;\n }\n }\n\n /**\n * Write ZIP using archiver for better compatibility\n * adm-zip's toBuffer() produces ZIPs that some parsers don't handle well.\n * archiver produces more standard ZIP output.\n */\n private writeZipWithArchiver(admZip: AdmZip): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const archive = archiver('zip', {\n zlib: { level: 9 }, // Maximum compression\n });\n\n const chunks: Buffer[] = [];\n const passThrough = new PassThrough();\n\n passThrough.on('data', (chunk: Buffer) => chunks.push(chunk));\n passThrough.on('end', () => resolve(Buffer.concat(chunks)));\n passThrough.on('error', reject);\n\n archive.on('error', reject);\n archive.pipe(passThrough);\n\n const entries = admZip.getEntries();\n\n for (const entry of entries) {\n const entryName = entry.entryName;\n const mtime = entry.header.time ? new Date(entry.header.time) : new Date();\n\n if (entry.isDirectory) {\n // Preserve explicit directory entries - some LMS systems require them\n archive.append('', {\n name: entryName.endsWith('/') ? entryName : entryName + '/',\n date: mtime,\n store: true,\n });\n continue;\n }\n\n const entryData = entry.getData();\n\n // Determine if we should store or compress based on original\n const store = entry.header.method === 0;\n\n archive.append(entryData, {\n name: entryName,\n date: mtime,\n store: store,\n });\n }\n\n archive.finalize();\n });\n }\n\n /**\n * Get the appropriate HTML injector for the authoring tool\n */\n private getHtmlInjector(tool: AuthoringTool): HtmlInjector | StorylineHtmlInjector {\n switch (tool) {\n case 'storyline':\n return this.storylineHtmlInjector;\n case 'rise':\n default:\n // Use Rise injector as default (works for most SCORM packages)\n return this.riseHtmlInjector;\n }\n }\n\n /**\n * Get the base folder for fallback files based on authoring tool\n */\n private getFallbackBaseFolder(toolInfo: AuthoringToolInfo): string {\n switch (toolInfo.tool) {\n case 'storyline':\n // Storyline uses html5/ folder structure\n return 'html5';\n case 'rise':\n return 'scormcontent';\n case 'xyleme':\n // Xyleme thin packs have files at root level\n return '';\n default:\n // For unknown tools, try to determine from main HTML path\n const htmlPath = toolInfo.mainHtmlPath;\n if (htmlPath.includes('/')) {\n return htmlPath.split('/')[0];\n }\n // Root level - use empty string (files at root)\n return '';\n }\n }\n\n /**\n * Build manifest paths from the actual file paths that were added\n * This ensures the manifest references the correct paths regardless of authoring tool\n */\n private buildManifestPaths(addedFiles: string[]): import('./manifest-updater.js').ManifestPaths {\n const paths: import('./manifest-updater.js').ManifestPaths = {};\n\n for (const filePath of addedFiles) {\n if (filePath.endsWith(this.config.cssBefore.filename)) {\n paths.cssBefore = filePath;\n } else if (filePath.endsWith(this.config.cssAfter.filename)) {\n paths.cssAfter = filePath;\n } else if (filePath.endsWith(this.config.jsBefore.filename)) {\n paths.jsBefore = filePath;\n } else if (filePath.endsWith(this.config.jsAfter.filename)) {\n paths.jsAfter = filePath;\n }\n }\n\n return paths;\n }\n\n /**\n * Generate assets from all enabled plugins\n */\n private generatePluginAssets(): GeneratedPluginAssets {\n const plugins = this.config.plugins || {};\n const enabledPlugins = Object.entries(plugins).filter(([_, cfg]) => cfg.enabled);\n\n if (enabledPlugins.length === 0) {\n return { cssBefore: '', cssAfter: '', jsBefore: '', jsAfter: '' };\n }\n\n console.log(`[Patcher] Generating assets for ${enabledPlugins.length} enabled plugin(s)`);\n return pluginRegistry.generateAssets(plugins);\n }\n\n /**\n * Add local fallback files to the ZIP\n * Priority: options > fetched > defaults\n * Plugin assets are appended after the main content\n */\n private addFallbackFiles(\n zip: AdmZip,\n options: PatchOptions,\n fetched: FetchedFallbacks = {},\n toolInfo?: AuthoringToolInfo\n ): string[] {\n const added: string[] = [];\n const baseFolder = toolInfo ? this.getFallbackBaseFolder(toolInfo) : 'scormcontent';\n const basePath = baseFolder ? `${baseFolder}/` : '';\n const cssFolder = `${basePath}${this.config.localFolders.css}`;\n const jsFolder = `${basePath}${this.config.localFolders.js}`;\n\n // Generate plugin assets\n const pluginAssets = this.generatePluginAssets();\n const hasPluginCssBefore = pluginAssets.cssBefore.length > 0;\n const hasPluginCssAfter = pluginAssets.cssAfter.length > 0;\n const hasPluginJsBefore = pluginAssets.jsBefore.length > 0;\n const hasPluginJsAfter = pluginAssets.jsAfter.length > 0;\n\n // CSS Before (priority: options > fetched > default) + plugins\n if (this.config.cssBefore.enabled) {\n const path = `${cssFolder}/${this.config.cssBefore.filename}`;\n let content = options.cssBeforeContent ?? fetched.cssBefore ?? DEFAULT_CSS_BEFORE;\n if (hasPluginCssBefore) {\n content += '\\n\\n/* === PLUGIN CSS (Before) === */\\n' + pluginAssets.cssBefore;\n console.log(`[Patcher] Added plugin CSS (before) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.cssBefore) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // CSS After (priority: options > fetched > default) + plugins\n if (this.config.cssAfter.enabled) {\n const path = `${cssFolder}/${this.config.cssAfter.filename}`;\n let content = options.cssAfterContent ?? fetched.cssAfter ?? DEFAULT_CSS_AFTER;\n if (hasPluginCssAfter) {\n content += '\\n\\n/* === PLUGIN CSS (After) === */\\n' + pluginAssets.cssAfter;\n console.log(`[Patcher] Added plugin CSS (after) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.cssAfter) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // JS Before (priority: options > fetched > default) + plugins\n if (this.config.jsBefore.enabled) {\n const path = `${jsFolder}/${this.config.jsBefore.filename}`;\n let content = options.jsBeforeContent ?? fetched.jsBefore ?? DEFAULT_JS_BEFORE;\n if (hasPluginJsBefore) {\n content += '\\n\\n// === PLUGIN JS (Before) ===\\n' + pluginAssets.jsBefore;\n console.log(`[Patcher] Added plugin JS (before) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.jsBefore) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n // JS After (priority: options > fetched > default) + plugins\n if (this.config.jsAfter.enabled) {\n const path = `${jsFolder}/${this.config.jsAfter.filename}`;\n let content = options.jsAfterContent ?? fetched.jsAfter ?? DEFAULT_JS_AFTER;\n if (hasPluginJsAfter) {\n content += '\\n\\n// === PLUGIN JS (After) ===\\n' + pluginAssets.jsAfter;\n console.log(`[Patcher] Added plugin JS (after) to ${path}`);\n }\n zip.addFile(path, Buffer.from(content, 'utf-8'));\n added.push(path);\n if (fetched.jsAfter) console.log(`[Patcher] Using fetched content for ${path}`);\n }\n\n return added;\n }\n\n /**\n * Get the current configuration\n */\n getConfig(): PatchAdamsConfig {\n return this.config;\n }\n\n /**\n * Convenience method to patch a buffer and return only the patched buffer.\n * Used by package-uploader integration.\n * @param buffer - Input ZIP file as Buffer\n * @returns Patched ZIP file as Buffer\n */\n async patchBuffer(buffer: Buffer): Promise<Buffer> {\n console.log(`[Patcher] patchBuffer called with ${buffer.length} bytes`);\n const { buffer: patchedBuffer, result } = await this.patch(buffer);\n console.log(`[Patcher] Patch result:`, JSON.stringify(result, null, 2));\n console.log(`[Patcher] Returning ${patchedBuffer.length} bytes`);\n return patchedBuffer;\n }\n}\n\nexport { HtmlInjector } from './html-injector.js';\nexport { StorylineHtmlInjector } from './storyline-html-injector.js';\nexport { ManifestUpdater, type ManifestPaths } from './manifest-updater.js';\n","import { createHash } from 'crypto';\nimport type AdmZip from 'adm-zip';\n\n/**\n * Course fingerprint result\n */\nexport interface CourseFingerprint {\n /** The Rise course ID extracted from the manifest (same as in Rise editor URL) */\n courseId: string;\n /** Source of the fingerprint (manifest, tincan, cmi5, fallback) */\n source: 'manifest' | 'tincan' | 'cmi5' | 'fallback';\n}\n\n/**\n * Extract the Rise course ID from a course package\n *\n * This extracts the course identifier that Rise uses internally.\n * It's the same ID you see in the Rise editor URL when editing a course.\n * This ID is stable across re-exports of the same course.\n *\n * Supports all Rise export formats:\n * - SCORM 1.2 / 2004 (from imsmanifest.xml)\n * - xAPI (from tincan.xml)\n * - cmi5 (from cmi5.xml)\n */\nexport function extractCourseFingerprint(zip: AdmZip): CourseFingerprint {\n // Try imsmanifest.xml first (SCORM 1.2 and 2004)\n const manifestResult = extractFromImsManifest(zip);\n if (manifestResult) {\n return manifestResult;\n }\n\n // Try tincan.xml for xAPI courses\n const tincanResult = extractFromTincan(zip);\n if (tincanResult) {\n return tincanResult;\n }\n\n // Try cmi5.xml\n const cmi5Result = extractFromCmi5(zip);\n if (cmi5Result) {\n return cmi5Result;\n }\n\n // Fallback: generate from index.html content hash\n return extractFallbackFingerprint(zip);\n}\n\n/**\n * Extract course ID from imsmanifest.xml (SCORM 1.2 and 2004)\n */\nfunction extractFromImsManifest(zip: AdmZip): CourseFingerprint | null {\n const entry = zip.getEntry('imsmanifest.xml');\n if (!entry) {\n return null;\n }\n\n const content = entry.getData().toString('utf-8');\n\n // Extract manifest identifier attribute\n // Pattern: <manifest identifier=\"jA6iIYVKCYYlQi-GWD65BK6K1EzvuI7PKs1OB6TL\"\n const identifierMatch = content.match(/<manifest[^>]+identifier\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (!identifierMatch) {\n return null;\n }\n\n return {\n courseId: identifierMatch[1],\n source: 'manifest',\n };\n}\n\n/**\n * Extract course ID from tincan.xml (xAPI courses)\n */\nfunction extractFromTincan(zip: AdmZip): CourseFingerprint | null {\n const entry = zip.getEntry('tincan.xml');\n if (!entry) {\n return null;\n }\n\n const content = entry.getData().toString('utf-8');\n\n // Try to extract activity ID from <activity id=\"...\">\n // Rise xAPI exports use this pattern\n const idMatch = content.match(/<activity[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (!idMatch) {\n return null;\n }\n\n return {\n courseId: idMatch[1],\n source: 'tincan',\n };\n}\n\n/**\n * Extract course ID from cmi5.xml\n */\nfunction extractFromCmi5(zip: AdmZip): CourseFingerprint | null {\n const entry = zip.getEntry('cmi5.xml');\n if (!entry) {\n return null;\n }\n\n const content = entry.getData().toString('utf-8');\n\n // Try to extract course ID from <course id=\"...\">\n const idMatch = content.match(/<course[^>]+id\\s*=\\s*[\"']([^\"']+)[\"']/i);\n if (!idMatch) {\n return null;\n }\n\n return {\n courseId: idMatch[1],\n source: 'cmi5',\n };\n}\n\n/**\n * Fallback: generate fingerprint from index.html title or content hash\n */\nfunction extractFallbackFingerprint(zip: AdmZip): CourseFingerprint {\n const entry = zip.getEntry('scormcontent/index.html');\n let courseId = 'unknown-course';\n\n if (entry) {\n const content = entry.getData().toString('utf-8');\n\n // Try to extract title\n const titleMatch = content.match(/<title>([^<]+)<\\/title>/i);\n if (titleMatch) {\n // Create a hash from the title to use as ID\n courseId = createHash('sha256')\n .update(titleMatch[1].trim())\n .digest('hex')\n .substring(0, 40);\n } else {\n // Use content hash as last resort\n courseId = createHash('sha256')\n .update(content)\n .digest('hex')\n .substring(0, 40);\n }\n }\n\n return {\n courseId,\n source: 'fallback',\n };\n}\n","import chalk from 'chalk';\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';\n\n/**\n * Simple logger utility for Patch-Adams\n */\nexport class Logger {\n private level: LogLevel;\n\n constructor(level: LogLevel = 'info') {\n this.level = level;\n }\n\n private shouldLog(level: LogLevel): boolean {\n const levels: LogLevel[] = ['debug', 'info', 'warn', 'error', 'silent'];\n return levels.indexOf(level) >= levels.indexOf(this.level);\n }\n\n debug(...args: unknown[]): void {\n if (this.shouldLog('debug')) {\n console.log(chalk.gray('[DEBUG]'), ...args);\n }\n }\n\n info(...args: unknown[]): void {\n if (this.shouldLog('info')) {\n console.log(chalk.blue('[INFO]'), ...args);\n }\n }\n\n success(...args: unknown[]): void {\n if (this.shouldLog('info')) {\n console.log(chalk.green('[SUCCESS]'), ...args);\n }\n }\n\n warn(...args: unknown[]): void {\n if (this.shouldLog('warn')) {\n console.log(chalk.yellow('[WARN]'), ...args);\n }\n }\n\n error(...args: unknown[]): void {\n if (this.shouldLog('error')) {\n console.error(chalk.red('[ERROR]'), ...args);\n }\n }\n\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n}\n"]}
|