@patch-adams/core 1.5.3 → 1.5.4

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/index.cjs CHANGED
@@ -3831,7 +3831,11 @@ function generateLrsBridgeCode(options) {
3831
3831
  if (!TRACK_QUIZZES) return;
3832
3832
 
3833
3833
  // Intercept submit button clicks on Knowledge Check blocks
3834
+ // NOTE: If the SCORM tracker is active, it handles quiz tracking via
3835
+ // cmi.interactions \u2014 skip DOM scraping to avoid duplicate statements
3834
3836
  document.addEventListener('click', function(e) {
3837
+ if (scormTrackerActive) return; // SCORM tracker handles this
3838
+
3835
3839
  var submitBtn = e.target.closest('.quiz-card__button');
3836
3840
  if (!submitBtn) return;
3837
3841
 
@@ -3987,6 +3991,142 @@ function generateLrsBridgeCode(options) {
3987
3991
  return answerText;
3988
3992
  }
3989
3993
 
3994
+ // ========================================================================
3995
+ // SCORM INTERACTION TRACKER
3996
+ // Intercepts cmi.interactions.N.* SetValue calls to capture quiz answers
3997
+ // directly from the SCORM data model \u2014 works regardless of Rise UI format
3998
+ // (Knowledge Check blocks, quiz lessons, etc.)
3999
+ // ========================================================================
4000
+ var scormInteractions = {}; // Pending interactions keyed by index N
4001
+ var scormInteractionsSent = {}; // Track which interactions were already sent
4002
+ var scormTrackerActive = false; // Set true when SCORM tracker wraps SetValue \u2014 KC handler defers
4003
+
4004
+ function setupScormInteractionTracker() {
4005
+ if (!TRACK_QUIZZES) return;
4006
+ if (!LRS.scormApi) {
4007
+ log('SCORM Interaction Tracker: No SCORM API found, skipping');
4008
+ return;
4009
+ }
4010
+
4011
+ var api = LRS.scormApi;
4012
+ var apiType = LRS.scormApiType;
4013
+
4014
+ // Determine which SetValue function to wrap
4015
+ var setValueFn = apiType === '2004' ? 'SetValue' : 'LMSSetValue';
4016
+ var originalSetValue = api[setValueFn];
4017
+
4018
+ if (typeof originalSetValue !== 'function') {
4019
+ log('SCORM Interaction Tracker: No ' + setValueFn + ' function found');
4020
+ return;
4021
+ }
4022
+
4023
+ // SCORM 1.2 uses cmi.interactions.N.*, SCORM 2004 uses cmi.interactions.N.*
4024
+ var interactionPattern = /^cmi\\.interactions\\.(\\d+)\\.(.+)$/;
4025
+
4026
+ api[setValueFn] = function(key, value) {
4027
+ // Always call the original first
4028
+ var result = originalSetValue.apply(api, arguments);
4029
+
4030
+ // Check if this is an interaction data element
4031
+ if (typeof key === 'string') {
4032
+ var match = key.match(interactionPattern);
4033
+ if (match) {
4034
+ var idx = match[1];
4035
+ var field = match[2];
4036
+
4037
+ // Initialize interaction tracking for this index
4038
+ if (!scormInteractions[idx]) {
4039
+ scormInteractions[idx] = {};
4040
+ }
4041
+
4042
+ // Store the field value
4043
+ scormInteractions[idx][field] = value;
4044
+
4045
+ log('SCORM Interaction [' + idx + '].' + field + ' = ' + (value ? value.substring(0, 80) : value));
4046
+
4047
+ // When 'result' is set, the interaction is complete \u2014 fire xAPI statement
4048
+ if (field === 'result' && !scormInteractionsSent[idx]) {
4049
+ scormInteractionsSent[idx] = true;
4050
+ var interaction = scormInteractions[idx];
4051
+
4052
+ // Map SCORM interaction type to readable type
4053
+ var typeMap = {
4054
+ 'choice': 'multiple-choice',
4055
+ 'true-false': 'true-false',
4056
+ 'fill-in': 'fill-in-blank',
4057
+ 'matching': 'matching',
4058
+ 'performance': 'performance',
4059
+ 'sequencing': 'sequencing',
4060
+ 'likert': 'likert',
4061
+ 'numeric': 'numeric'
4062
+ };
4063
+
4064
+ var scormType = interaction.type || 'unknown';
4065
+ var questionType = typeMap[scormType] || scormType;
4066
+
4067
+ // Determine correctness from SCORM result value
4068
+ // SCORM 1.2: 'correct', 'wrong', 'unanticipated', 'neutral'
4069
+ // SCORM 2004: 'correct', 'incorrect', 'unanticipated', 'neutral'
4070
+ var isCorrect = value === 'correct';
4071
+
4072
+ // Get student response \u2014 for 'choice' type, this might be indices like '1,3'
4073
+ var studentResponse = interaction.student_response || interaction['student_response'] || '';
4074
+
4075
+ // Get correct response pattern
4076
+ var correctResponse = '';
4077
+ // SCORM 1.2: correct_responses.0.pattern
4078
+ // Check stored fields for correct_responses
4079
+ Object.keys(interaction).forEach(function(k) {
4080
+ if (k.indexOf('correct_responses') > -1 && k.indexOf('pattern') > -1) {
4081
+ correctResponse = interaction[k];
4082
+ }
4083
+ });
4084
+
4085
+ // Get interaction ID (Rise sets this to a unique question identifier)
4086
+ var interactionId = interaction.id || ('interaction-' + idx);
4087
+
4088
+ // Get lesson context
4089
+ var lessonInfo = getCachedLessonInfo();
4090
+
4091
+ // Increment question counter
4092
+ kcQuestionCounter++;
4093
+
4094
+ log('SCORM Interaction complete [' + idx + ']:', {
4095
+ id: interactionId,
4096
+ type: questionType,
4097
+ response: studentResponse,
4098
+ correctResponse: correctResponse,
4099
+ result: value,
4100
+ correct: isCorrect,
4101
+ questionNumber: kcQuestionCounter
4102
+ });
4103
+
4104
+ // Send xAPI answered statement
4105
+ LRS.questionAnswered({
4106
+ questionId: interactionId,
4107
+ questionGuid: interactionId,
4108
+ questionNumber: kcQuestionCounter,
4109
+ questionText: 'Question ' + kcQuestionCounter + ' (' + interactionId + ')',
4110
+ questionType: questionType,
4111
+ answer: studentResponse,
4112
+ correctAnswer: correctResponse,
4113
+ correct: isCorrect,
4114
+ result: isCorrect ? 'correct' : 'incorrect',
4115
+ assessmentName: lessonInfo.name || 'Quiz',
4116
+ lessonName: lessonInfo.name,
4117
+ sectionName: lessonInfo.sectionName
4118
+ });
4119
+ }
4120
+ }
4121
+ }
4122
+
4123
+ return result;
4124
+ };
4125
+
4126
+ scormTrackerActive = true;
4127
+ log('SCORM Interaction Tracker: Wrapped ' + setValueFn + ' (apiType=' + apiType + ') \u2014 KC DOM handler will defer to SCORM tracker');
4128
+ }
4129
+
3990
4130
  function setupInteractionInterceptors() {
3991
4131
  if (!TRACK_INTERACTIONS) return;
3992
4132
 
@@ -4205,6 +4345,9 @@ function generateLrsBridgeCode(options) {
4205
4345
  LRS.scormApiFound = true;
4206
4346
  LRS.scormApiType = result.type;
4207
4347
 
4348
+ // Wrap SCORM SetValue to intercept cmi.interactions for quiz tracking
4349
+ setupScormInteractionTracker();
4350
+
4208
4351
  // Read learner_id for diagnostics
4209
4352
  var scormLearnerId = 'n/a', scormLearnerName = 'n/a';
4210
4353
  try {