@patch-adams/core 1.5.4 → 1.5.5

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 CHANGED
@@ -4329,135 +4329,212 @@ function generateLrsBridgeCode(options) {
4329
4329
  // Intercepts cmi.interactions.N.* SetValue calls to capture quiz answers
4330
4330
  // directly from the SCORM data model \u2014 works regardless of Rise UI format
4331
4331
  // (Knowledge Check blocks, quiz lessons, etc.)
4332
+ //
4333
+ // IMPORTANT: This finds and wraps the ACTUAL SCORM API that Rise uses
4334
+ // (window.API, parent.API, or global functions), NOT the bridge's copy.
4335
+ // It runs unconditionally at init, not gated by actor resolution.
4332
4336
  // ========================================================================
4333
4337
  var scormInteractions = {}; // Pending interactions keyed by index N
4334
4338
  var scormInteractionsSent = {}; // Track which interactions were already sent
4335
4339
  var scormTrackerActive = false; // Set true when SCORM tracker wraps SetValue \u2014 KC handler defers
4336
4340
 
4337
- function setupScormInteractionTracker() {
4338
- if (!TRACK_QUIZZES) return;
4339
- if (!LRS.scormApi) {
4340
- log('SCORM Interaction Tracker: No SCORM API found, skipping');
4341
- return;
4342
- }
4343
-
4344
- var api = LRS.scormApi;
4345
- var apiType = LRS.scormApiType;
4341
+ function interceptScormSetValue(key, value) {
4342
+ if (typeof key !== 'string') return;
4343
+ var interactionPattern = /^cmi\\.interactions\\.(\\d+)\\.(.+)$/;
4344
+ var match = key.match(interactionPattern);
4345
+ if (!match) return;
4346
4346
 
4347
- // Determine which SetValue function to wrap
4348
- var setValueFn = apiType === '2004' ? 'SetValue' : 'LMSSetValue';
4349
- var originalSetValue = api[setValueFn];
4347
+ var idx = match[1];
4348
+ var field = match[2];
4350
4349
 
4351
- if (typeof originalSetValue !== 'function') {
4352
- log('SCORM Interaction Tracker: No ' + setValueFn + ' function found');
4353
- return;
4350
+ // Initialize interaction tracking for this index
4351
+ if (!scormInteractions[idx]) {
4352
+ scormInteractions[idx] = {};
4354
4353
  }
4355
4354
 
4356
- // SCORM 1.2 uses cmi.interactions.N.*, SCORM 2004 uses cmi.interactions.N.*
4357
- var interactionPattern = /^cmi\\.interactions\\.(\\d+)\\.(.+)$/;
4355
+ // Store the field value
4356
+ scormInteractions[idx][field] = String(value);
4358
4357
 
4359
- api[setValueFn] = function(key, value) {
4360
- // Always call the original first
4361
- var result = originalSetValue.apply(api, arguments);
4358
+ log('SCORM Interaction [' + idx + '].' + field + ' = ' + String(value).substring(0, 100));
4362
4359
 
4363
- // Check if this is an interaction data element
4364
- if (typeof key === 'string') {
4365
- var match = key.match(interactionPattern);
4366
- if (match) {
4367
- var idx = match[1];
4368
- var field = match[2];
4360
+ // When 'result' is set, the interaction is complete \u2014 fire xAPI statement
4361
+ if (field === 'result' && !scormInteractionsSent[idx]) {
4362
+ scormInteractionsSent[idx] = true;
4363
+ var interaction = scormInteractions[idx];
4369
4364
 
4370
- // Initialize interaction tracking for this index
4371
- if (!scormInteractions[idx]) {
4372
- scormInteractions[idx] = {};
4373
- }
4365
+ // Map SCORM interaction type to readable type
4366
+ var typeMap = {
4367
+ 'choice': 'multiple-choice',
4368
+ 'true-false': 'true-false',
4369
+ 'fill-in': 'fill-in-blank',
4370
+ 'matching': 'matching',
4371
+ 'performance': 'performance',
4372
+ 'sequencing': 'sequencing',
4373
+ 'likert': 'likert',
4374
+ 'numeric': 'numeric'
4375
+ };
4374
4376
 
4375
- // Store the field value
4376
- scormInteractions[idx][field] = value;
4377
-
4378
- log('SCORM Interaction [' + idx + '].' + field + ' = ' + (value ? value.substring(0, 80) : value));
4379
-
4380
- // When 'result' is set, the interaction is complete \u2014 fire xAPI statement
4381
- if (field === 'result' && !scormInteractionsSent[idx]) {
4382
- scormInteractionsSent[idx] = true;
4383
- var interaction = scormInteractions[idx];
4384
-
4385
- // Map SCORM interaction type to readable type
4386
- var typeMap = {
4387
- 'choice': 'multiple-choice',
4388
- 'true-false': 'true-false',
4389
- 'fill-in': 'fill-in-blank',
4390
- 'matching': 'matching',
4391
- 'performance': 'performance',
4392
- 'sequencing': 'sequencing',
4393
- 'likert': 'likert',
4394
- 'numeric': 'numeric'
4395
- };
4377
+ var scormType = interaction.type || 'unknown';
4378
+ var questionType = typeMap[scormType] || scormType;
4396
4379
 
4397
- var scormType = interaction.type || 'unknown';
4398
- var questionType = typeMap[scormType] || scormType;
4380
+ // Determine correctness from SCORM result value
4381
+ // SCORM 1.2: 'correct', 'wrong', 'unanticipated', 'neutral'
4382
+ // SCORM 2004: 'correct', 'incorrect', 'unanticipated', 'neutral'
4383
+ var isCorrect = String(value) === 'correct';
4399
4384
 
4400
- // Determine correctness from SCORM result value
4401
- // SCORM 1.2: 'correct', 'wrong', 'unanticipated', 'neutral'
4402
- // SCORM 2004: 'correct', 'incorrect', 'unanticipated', 'neutral'
4403
- var isCorrect = value === 'correct';
4385
+ // Get student response
4386
+ var studentResponse = interaction.student_response || '';
4404
4387
 
4405
- // Get student response \u2014 for 'choice' type, this might be indices like '1,3'
4406
- var studentResponse = interaction.student_response || interaction['student_response'] || '';
4388
+ // Get correct response pattern
4389
+ var correctResponse = '';
4390
+ Object.keys(interaction).forEach(function(k) {
4391
+ if (k.indexOf('correct_responses') > -1 && k.indexOf('pattern') > -1) {
4392
+ correctResponse = interaction[k];
4393
+ }
4394
+ });
4407
4395
 
4408
- // Get correct response pattern
4409
- var correctResponse = '';
4410
- // SCORM 1.2: correct_responses.0.pattern
4411
- // Check stored fields for correct_responses
4412
- Object.keys(interaction).forEach(function(k) {
4413
- if (k.indexOf('correct_responses') > -1 && k.indexOf('pattern') > -1) {
4414
- correctResponse = interaction[k];
4415
- }
4416
- });
4396
+ // Rise interaction IDs encode the question text (underscored)
4397
+ // e.g. "Development_Week_Qui_I_want_a_clear_picture_of_..."
4398
+ var interactionId = interaction.id || ('interaction-' + idx);
4399
+
4400
+ // Try to make a readable question text from the interaction ID
4401
+ var questionText = interactionId
4402
+ .replace(/_\\d+$/, '') // remove trailing _0
4403
+ .replace(/_/g, ' ') // underscores to spaces
4404
+ .substring(0, 200);
4405
+
4406
+ // Get lesson context
4407
+ var lessonInfo = getCachedLessonInfo();
4408
+
4409
+ // Increment question counter
4410
+ kcQuestionCounter++;
4411
+
4412
+ log('SCORM Interaction complete [' + idx + ']:', {
4413
+ questionNumber: kcQuestionCounter,
4414
+ questionText: questionText.substring(0, 60) + '...',
4415
+ type: questionType,
4416
+ studentResponse: studentResponse,
4417
+ correctResponse: correctResponse,
4418
+ result: String(value),
4419
+ correct: isCorrect
4420
+ });
4417
4421
 
4418
- // Get interaction ID (Rise sets this to a unique question identifier)
4419
- var interactionId = interaction.id || ('interaction-' + idx);
4422
+ // Send xAPI answered statement
4423
+ LRS.questionAnswered({
4424
+ questionId: interactionId,
4425
+ questionGuid: interactionId,
4426
+ questionNumber: kcQuestionCounter,
4427
+ questionText: questionText,
4428
+ questionType: questionType,
4429
+ answer: studentResponse,
4430
+ correctAnswer: correctResponse,
4431
+ correct: isCorrect,
4432
+ result: isCorrect ? 'correct' : 'incorrect',
4433
+ assessmentName: lessonInfo.name || 'Quiz',
4434
+ lessonName: lessonInfo.name,
4435
+ sectionName: lessonInfo.sectionName
4436
+ });
4437
+ }
4438
+ }
4420
4439
 
4421
- // Get lesson context
4422
- var lessonInfo = getCachedLessonInfo();
4440
+ function setupScormInteractionTracker() {
4441
+ if (!TRACK_QUIZZES) return;
4423
4442
 
4424
- // Increment question counter
4425
- kcQuestionCounter++;
4443
+ // Find the ACTUAL SCORM API that Rise uses \u2014 NOT our bridge's copy.
4444
+ // Rise discovers the API via standard SCORM lookup (window.API, parent chain).
4445
+ // The Bravais CDS player's ProxyApi.injectLmsApi() sets this up.
4446
+ var wrapped = false;
4426
4447
 
4427
- log('SCORM Interaction complete [' + idx + ']:', {
4428
- id: interactionId,
4429
- type: questionType,
4430
- response: studentResponse,
4431
- correctResponse: correctResponse,
4432
- result: value,
4433
- correct: isCorrect,
4434
- questionNumber: kcQuestionCounter
4435
- });
4448
+ // Try 1: window.API (SCORM 1.2) or window.API_1484_11 (SCORM 2004)
4449
+ try {
4450
+ if (window.API && typeof window.API.LMSSetValue === 'function') {
4451
+ var origSetValue = window.API.LMSSetValue;
4452
+ window.API.LMSSetValue = function(key, value) {
4453
+ var result = origSetValue.apply(window.API, arguments);
4454
+ interceptScormSetValue(key, value);
4455
+ return result;
4456
+ };
4457
+ wrapped = true;
4458
+ log('SCORM Interaction Tracker: Wrapped window.API.LMSSetValue');
4459
+ }
4460
+ } catch (e) { log('SCORM Tracker: Cannot access window.API:', e.message); }
4436
4461
 
4437
- // Send xAPI answered statement
4438
- LRS.questionAnswered({
4439
- questionId: interactionId,
4440
- questionGuid: interactionId,
4441
- questionNumber: kcQuestionCounter,
4442
- questionText: 'Question ' + kcQuestionCounter + ' (' + interactionId + ')',
4443
- questionType: questionType,
4444
- answer: studentResponse,
4445
- correctAnswer: correctResponse,
4446
- correct: isCorrect,
4447
- result: isCorrect ? 'correct' : 'incorrect',
4448
- assessmentName: lessonInfo.name || 'Quiz',
4449
- lessonName: lessonInfo.name,
4450
- sectionName: lessonInfo.sectionName
4451
- });
4462
+ if (!wrapped) {
4463
+ try {
4464
+ if (window.API_1484_11 && typeof window.API_1484_11.SetValue === 'function') {
4465
+ var origSetValue2004 = window.API_1484_11.SetValue;
4466
+ window.API_1484_11.SetValue = function(key, value) {
4467
+ var result = origSetValue2004.apply(window.API_1484_11, arguments);
4468
+ interceptScormSetValue(key, value);
4469
+ return result;
4470
+ };
4471
+ wrapped = true;
4472
+ log('SCORM Interaction Tracker: Wrapped window.API_1484_11.SetValue');
4473
+ }
4474
+ } catch (e) { log('SCORM Tracker: Cannot access window.API_1484_11:', e.message); }
4475
+ }
4476
+
4477
+ // Try 2: Parent frame API
4478
+ if (!wrapped) {
4479
+ try {
4480
+ if (window.parent && window.parent !== window) {
4481
+ if (window.parent.API && typeof window.parent.API.LMSSetValue === 'function') {
4482
+ var origParentSetValue = window.parent.API.LMSSetValue;
4483
+ window.parent.API.LMSSetValue = function(key, value) {
4484
+ var result = origParentSetValue.apply(window.parent.API, arguments);
4485
+ interceptScormSetValue(key, value);
4486
+ return result;
4487
+ };
4488
+ wrapped = true;
4489
+ log('SCORM Interaction Tracker: Wrapped window.parent.API.LMSSetValue');
4452
4490
  }
4453
4491
  }
4454
- }
4492
+ } catch (e) { log('SCORM Tracker: Cannot access parent API (cross-origin)'); }
4493
+ }
4455
4494
 
4456
- return result;
4457
- };
4495
+ // Try 3: Global LMS functions (Bravais/Xyleme mock API)
4496
+ if (!wrapped) {
4497
+ try {
4498
+ if (typeof window.LMSSetValue === 'function') {
4499
+ var origGlobalSetValue = window.LMSSetValue;
4500
+ window.LMSSetValue = function(key, value) {
4501
+ var result = origGlobalSetValue.apply(window, arguments);
4502
+ interceptScormSetValue(key, value);
4503
+ return result;
4504
+ };
4505
+ wrapped = true;
4506
+ log('SCORM Interaction Tracker: Wrapped window.LMSSetValue (global)');
4507
+ }
4508
+ } catch (e) { log('SCORM Tracker: Cannot wrap global LMSSetValue:', e.message); }
4509
+ }
4510
+
4511
+ // Try 4: Fall back to bridge's copy (least likely to work but worth trying)
4512
+ if (!wrapped && LRS.scormApi) {
4513
+ var setValueFn = LRS.scormApiType === '2004' ? 'SetValue' : 'LMSSetValue';
4514
+ if (typeof LRS.scormApi[setValueFn] === 'function') {
4515
+ var origBridgeSetValue = LRS.scormApi[setValueFn];
4516
+ LRS.scormApi[setValueFn] = function(key, value) {
4517
+ var result = origBridgeSetValue.apply(LRS.scormApi, arguments);
4518
+ interceptScormSetValue(key, value);
4519
+ return result;
4520
+ };
4521
+ wrapped = true;
4522
+ log('SCORM Interaction Tracker: Wrapped LRS.scormApi.' + setValueFn + ' (bridge copy)');
4523
+ }
4524
+ }
4458
4525
 
4459
- scormTrackerActive = true;
4460
- log('SCORM Interaction Tracker: Wrapped ' + setValueFn + ' (apiType=' + apiType + ') \u2014 KC DOM handler will defer to SCORM tracker');
4526
+ if (wrapped) {
4527
+ scormTrackerActive = true;
4528
+ log('SCORM Interaction Tracker active \u2014 KC DOM handler will defer');
4529
+ } else {
4530
+ // API not available yet \u2014 retry in 2 seconds (Bravais proxy may not be ready)
4531
+ log('SCORM Interaction Tracker: No SCORM API found yet, retrying in 2s...');
4532
+ setTimeout(function() {
4533
+ if (!scormTrackerActive) {
4534
+ setupScormInteractionTracker();
4535
+ }
4536
+ }, 2000);
4537
+ }
4461
4538
  }
4462
4539
 
4463
4540
  function setupInteractionInterceptors() {
@@ -4678,9 +4755,6 @@ function generateLrsBridgeCode(options) {
4678
4755
  LRS.scormApiFound = true;
4679
4756
  LRS.scormApiType = result.type;
4680
4757
 
4681
- // Wrap SCORM SetValue to intercept cmi.interactions for quiz tracking
4682
- setupScormInteractionTracker();
4683
-
4684
4758
  // Read learner_id for diagnostics
4685
4759
  var scormLearnerId = 'n/a', scormLearnerName = 'n/a';
4686
4760
  try {
@@ -4763,6 +4837,10 @@ function generateLrsBridgeCode(options) {
4763
4837
  setupQuizInterceptors();
4764
4838
  setupInteractionInterceptors();
4765
4839
 
4840
+ // Intercept SCORM cmi.interactions to capture quiz answers as xAPI statements
4841
+ // This wraps the ACTUAL SCORM API (window.API etc.) that Rise calls
4842
+ setupScormInteractionTracker();
4843
+
4766
4844
  // Fetch document metadata from API to get GUIDs (async)
4767
4845
  // Then send course launched event
4768
4846
  var sharedLinkToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;