@patch-adams/core 1.5.3 → 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 +221 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +221 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +221 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +221 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,219 @@ 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
|
+
// IMPORTANT: This finds and wraps the ACTUAL SCORM API that Rise uses
|
|
4001
|
+
// (window.API, parent.API, or global functions), NOT the bridge's copy.
|
|
4002
|
+
// It runs unconditionally at init, not gated by actor resolution.
|
|
4003
|
+
// ========================================================================
|
|
4004
|
+
var scormInteractions = {}; // Pending interactions keyed by index N
|
|
4005
|
+
var scormInteractionsSent = {}; // Track which interactions were already sent
|
|
4006
|
+
var scormTrackerActive = false; // Set true when SCORM tracker wraps SetValue \u2014 KC handler defers
|
|
4007
|
+
|
|
4008
|
+
function interceptScormSetValue(key, value) {
|
|
4009
|
+
if (typeof key !== 'string') return;
|
|
4010
|
+
var interactionPattern = /^cmi\\.interactions\\.(\\d+)\\.(.+)$/;
|
|
4011
|
+
var match = key.match(interactionPattern);
|
|
4012
|
+
if (!match) return;
|
|
4013
|
+
|
|
4014
|
+
var idx = match[1];
|
|
4015
|
+
var field = match[2];
|
|
4016
|
+
|
|
4017
|
+
// Initialize interaction tracking for this index
|
|
4018
|
+
if (!scormInteractions[idx]) {
|
|
4019
|
+
scormInteractions[idx] = {};
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
// Store the field value
|
|
4023
|
+
scormInteractions[idx][field] = String(value);
|
|
4024
|
+
|
|
4025
|
+
log('SCORM Interaction [' + idx + '].' + field + ' = ' + String(value).substring(0, 100));
|
|
4026
|
+
|
|
4027
|
+
// When 'result' is set, the interaction is complete \u2014 fire xAPI statement
|
|
4028
|
+
if (field === 'result' && !scormInteractionsSent[idx]) {
|
|
4029
|
+
scormInteractionsSent[idx] = true;
|
|
4030
|
+
var interaction = scormInteractions[idx];
|
|
4031
|
+
|
|
4032
|
+
// Map SCORM interaction type to readable type
|
|
4033
|
+
var typeMap = {
|
|
4034
|
+
'choice': 'multiple-choice',
|
|
4035
|
+
'true-false': 'true-false',
|
|
4036
|
+
'fill-in': 'fill-in-blank',
|
|
4037
|
+
'matching': 'matching',
|
|
4038
|
+
'performance': 'performance',
|
|
4039
|
+
'sequencing': 'sequencing',
|
|
4040
|
+
'likert': 'likert',
|
|
4041
|
+
'numeric': 'numeric'
|
|
4042
|
+
};
|
|
4043
|
+
|
|
4044
|
+
var scormType = interaction.type || 'unknown';
|
|
4045
|
+
var questionType = typeMap[scormType] || scormType;
|
|
4046
|
+
|
|
4047
|
+
// Determine correctness from SCORM result value
|
|
4048
|
+
// SCORM 1.2: 'correct', 'wrong', 'unanticipated', 'neutral'
|
|
4049
|
+
// SCORM 2004: 'correct', 'incorrect', 'unanticipated', 'neutral'
|
|
4050
|
+
var isCorrect = String(value) === 'correct';
|
|
4051
|
+
|
|
4052
|
+
// Get student response
|
|
4053
|
+
var studentResponse = interaction.student_response || '';
|
|
4054
|
+
|
|
4055
|
+
// Get correct response pattern
|
|
4056
|
+
var correctResponse = '';
|
|
4057
|
+
Object.keys(interaction).forEach(function(k) {
|
|
4058
|
+
if (k.indexOf('correct_responses') > -1 && k.indexOf('pattern') > -1) {
|
|
4059
|
+
correctResponse = interaction[k];
|
|
4060
|
+
}
|
|
4061
|
+
});
|
|
4062
|
+
|
|
4063
|
+
// Rise interaction IDs encode the question text (underscored)
|
|
4064
|
+
// e.g. "Development_Week_Qui_I_want_a_clear_picture_of_..."
|
|
4065
|
+
var interactionId = interaction.id || ('interaction-' + idx);
|
|
4066
|
+
|
|
4067
|
+
// Try to make a readable question text from the interaction ID
|
|
4068
|
+
var questionText = interactionId
|
|
4069
|
+
.replace(/_\\d+$/, '') // remove trailing _0
|
|
4070
|
+
.replace(/_/g, ' ') // underscores to spaces
|
|
4071
|
+
.substring(0, 200);
|
|
4072
|
+
|
|
4073
|
+
// Get lesson context
|
|
4074
|
+
var lessonInfo = getCachedLessonInfo();
|
|
4075
|
+
|
|
4076
|
+
// Increment question counter
|
|
4077
|
+
kcQuestionCounter++;
|
|
4078
|
+
|
|
4079
|
+
log('SCORM Interaction complete [' + idx + ']:', {
|
|
4080
|
+
questionNumber: kcQuestionCounter,
|
|
4081
|
+
questionText: questionText.substring(0, 60) + '...',
|
|
4082
|
+
type: questionType,
|
|
4083
|
+
studentResponse: studentResponse,
|
|
4084
|
+
correctResponse: correctResponse,
|
|
4085
|
+
result: String(value),
|
|
4086
|
+
correct: isCorrect
|
|
4087
|
+
});
|
|
4088
|
+
|
|
4089
|
+
// Send xAPI answered statement
|
|
4090
|
+
LRS.questionAnswered({
|
|
4091
|
+
questionId: interactionId,
|
|
4092
|
+
questionGuid: interactionId,
|
|
4093
|
+
questionNumber: kcQuestionCounter,
|
|
4094
|
+
questionText: questionText,
|
|
4095
|
+
questionType: questionType,
|
|
4096
|
+
answer: studentResponse,
|
|
4097
|
+
correctAnswer: correctResponse,
|
|
4098
|
+
correct: isCorrect,
|
|
4099
|
+
result: isCorrect ? 'correct' : 'incorrect',
|
|
4100
|
+
assessmentName: lessonInfo.name || 'Quiz',
|
|
4101
|
+
lessonName: lessonInfo.name,
|
|
4102
|
+
sectionName: lessonInfo.sectionName
|
|
4103
|
+
});
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
|
|
4107
|
+
function setupScormInteractionTracker() {
|
|
4108
|
+
if (!TRACK_QUIZZES) return;
|
|
4109
|
+
|
|
4110
|
+
// Find the ACTUAL SCORM API that Rise uses \u2014 NOT our bridge's copy.
|
|
4111
|
+
// Rise discovers the API via standard SCORM lookup (window.API, parent chain).
|
|
4112
|
+
// The Bravais CDS player's ProxyApi.injectLmsApi() sets this up.
|
|
4113
|
+
var wrapped = false;
|
|
4114
|
+
|
|
4115
|
+
// Try 1: window.API (SCORM 1.2) or window.API_1484_11 (SCORM 2004)
|
|
4116
|
+
try {
|
|
4117
|
+
if (window.API && typeof window.API.LMSSetValue === 'function') {
|
|
4118
|
+
var origSetValue = window.API.LMSSetValue;
|
|
4119
|
+
window.API.LMSSetValue = function(key, value) {
|
|
4120
|
+
var result = origSetValue.apply(window.API, arguments);
|
|
4121
|
+
interceptScormSetValue(key, value);
|
|
4122
|
+
return result;
|
|
4123
|
+
};
|
|
4124
|
+
wrapped = true;
|
|
4125
|
+
log('SCORM Interaction Tracker: Wrapped window.API.LMSSetValue');
|
|
4126
|
+
}
|
|
4127
|
+
} catch (e) { log('SCORM Tracker: Cannot access window.API:', e.message); }
|
|
4128
|
+
|
|
4129
|
+
if (!wrapped) {
|
|
4130
|
+
try {
|
|
4131
|
+
if (window.API_1484_11 && typeof window.API_1484_11.SetValue === 'function') {
|
|
4132
|
+
var origSetValue2004 = window.API_1484_11.SetValue;
|
|
4133
|
+
window.API_1484_11.SetValue = function(key, value) {
|
|
4134
|
+
var result = origSetValue2004.apply(window.API_1484_11, arguments);
|
|
4135
|
+
interceptScormSetValue(key, value);
|
|
4136
|
+
return result;
|
|
4137
|
+
};
|
|
4138
|
+
wrapped = true;
|
|
4139
|
+
log('SCORM Interaction Tracker: Wrapped window.API_1484_11.SetValue');
|
|
4140
|
+
}
|
|
4141
|
+
} catch (e) { log('SCORM Tracker: Cannot access window.API_1484_11:', e.message); }
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
// Try 2: Parent frame API
|
|
4145
|
+
if (!wrapped) {
|
|
4146
|
+
try {
|
|
4147
|
+
if (window.parent && window.parent !== window) {
|
|
4148
|
+
if (window.parent.API && typeof window.parent.API.LMSSetValue === 'function') {
|
|
4149
|
+
var origParentSetValue = window.parent.API.LMSSetValue;
|
|
4150
|
+
window.parent.API.LMSSetValue = function(key, value) {
|
|
4151
|
+
var result = origParentSetValue.apply(window.parent.API, arguments);
|
|
4152
|
+
interceptScormSetValue(key, value);
|
|
4153
|
+
return result;
|
|
4154
|
+
};
|
|
4155
|
+
wrapped = true;
|
|
4156
|
+
log('SCORM Interaction Tracker: Wrapped window.parent.API.LMSSetValue');
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
} catch (e) { log('SCORM Tracker: Cannot access parent API (cross-origin)'); }
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
// Try 3: Global LMS functions (Bravais/Xyleme mock API)
|
|
4163
|
+
if (!wrapped) {
|
|
4164
|
+
try {
|
|
4165
|
+
if (typeof window.LMSSetValue === 'function') {
|
|
4166
|
+
var origGlobalSetValue = window.LMSSetValue;
|
|
4167
|
+
window.LMSSetValue = function(key, value) {
|
|
4168
|
+
var result = origGlobalSetValue.apply(window, arguments);
|
|
4169
|
+
interceptScormSetValue(key, value);
|
|
4170
|
+
return result;
|
|
4171
|
+
};
|
|
4172
|
+
wrapped = true;
|
|
4173
|
+
log('SCORM Interaction Tracker: Wrapped window.LMSSetValue (global)');
|
|
4174
|
+
}
|
|
4175
|
+
} catch (e) { log('SCORM Tracker: Cannot wrap global LMSSetValue:', e.message); }
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4178
|
+
// Try 4: Fall back to bridge's copy (least likely to work but worth trying)
|
|
4179
|
+
if (!wrapped && LRS.scormApi) {
|
|
4180
|
+
var setValueFn = LRS.scormApiType === '2004' ? 'SetValue' : 'LMSSetValue';
|
|
4181
|
+
if (typeof LRS.scormApi[setValueFn] === 'function') {
|
|
4182
|
+
var origBridgeSetValue = LRS.scormApi[setValueFn];
|
|
4183
|
+
LRS.scormApi[setValueFn] = function(key, value) {
|
|
4184
|
+
var result = origBridgeSetValue.apply(LRS.scormApi, arguments);
|
|
4185
|
+
interceptScormSetValue(key, value);
|
|
4186
|
+
return result;
|
|
4187
|
+
};
|
|
4188
|
+
wrapped = true;
|
|
4189
|
+
log('SCORM Interaction Tracker: Wrapped LRS.scormApi.' + setValueFn + ' (bridge copy)');
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
if (wrapped) {
|
|
4194
|
+
scormTrackerActive = true;
|
|
4195
|
+
log('SCORM Interaction Tracker active \u2014 KC DOM handler will defer');
|
|
4196
|
+
} else {
|
|
4197
|
+
// API not available yet \u2014 retry in 2 seconds (Bravais proxy may not be ready)
|
|
4198
|
+
log('SCORM Interaction Tracker: No SCORM API found yet, retrying in 2s...');
|
|
4199
|
+
setTimeout(function() {
|
|
4200
|
+
if (!scormTrackerActive) {
|
|
4201
|
+
setupScormInteractionTracker();
|
|
4202
|
+
}
|
|
4203
|
+
}, 2000);
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
|
|
3990
4207
|
function setupInteractionInterceptors() {
|
|
3991
4208
|
if (!TRACK_INTERACTIONS) return;
|
|
3992
4209
|
|
|
@@ -4287,6 +4504,10 @@ function generateLrsBridgeCode(options) {
|
|
|
4287
4504
|
setupQuizInterceptors();
|
|
4288
4505
|
setupInteractionInterceptors();
|
|
4289
4506
|
|
|
4507
|
+
// Intercept SCORM cmi.interactions to capture quiz answers as xAPI statements
|
|
4508
|
+
// This wraps the ACTUAL SCORM API (window.API etc.) that Rise calls
|
|
4509
|
+
setupScormInteractionTracker();
|
|
4510
|
+
|
|
4290
4511
|
// Fetch document metadata from API to get GUIDs (async)
|
|
4291
4512
|
// Then send course launched event
|
|
4292
4513
|
var sharedLinkToken = LRS.courseInfo ? LRS.courseInfo.sharedLinkToken : null;
|