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