@patch-adams/core 1.5.9 → 1.5.11

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.js CHANGED
@@ -3378,6 +3378,64 @@ function generateLrsBridgeCode(options) {
3378
3378
  sendStatement(statement);
3379
3379
  };
3380
3380
 
3381
+ // Send a "launched" statement \u2014 called by skin after email gate / actor setup
3382
+ LRS.sendLaunchStatement = function(data) {
3383
+ data = data || {};
3384
+ var activityDetails = {
3385
+ launchSource: data.source || 'email-gate',
3386
+ employeeEmail: data.email || (LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined),
3387
+ employeeId: data.employeeId || LRS.employeeId || undefined
3388
+ };
3389
+
3390
+ if (data.employeeName) activityDetails.employeeName = data.employeeName;
3391
+
3392
+ var statement = buildStatement('launched', ACTIVITY_TYPES.course, activityDetails, null, null);
3393
+ log('Sending launch statement for:', activityDetails.employeeEmail || '(unknown)');
3394
+ sendStatement(statement);
3395
+ };
3396
+
3397
+ // Send a course completion statement \u2014 called by skin or auto-detected via SCORM
3398
+ LRS.sendCompletionStatement = function(data) {
3399
+ data = data || {};
3400
+ if (scormCompletionSent) {
3401
+ log('Completion statement already sent (SCORM auto-detected). Skipping duplicate.');
3402
+ return;
3403
+ }
3404
+ scormCompletionSent = true;
3405
+
3406
+ var status = data.status || 'completed';
3407
+ var courseTitle = data.courseTitle || (LRS.courseInfo && LRS.courseInfo.title ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course');
3408
+
3409
+ var verbKey = 'completed';
3410
+ if (status === 'passed') verbKey = 'passed';
3411
+ if (status === 'failed') verbKey = 'failed';
3412
+
3413
+ var result = { completion: true, success: (status === 'passed') };
3414
+
3415
+ if (typeof data.score === 'number') {
3416
+ var max = data.scoreMax || 100;
3417
+ var min = data.scoreMin || 0;
3418
+ result.score = {
3419
+ raw: data.score,
3420
+ max: max,
3421
+ min: min,
3422
+ scaled: max > min ? (data.score - min) / (max - min) : 0
3423
+ };
3424
+ }
3425
+
3426
+ var activityDetails = {
3427
+ courseTitle: courseTitle,
3428
+ completionStatus: status,
3429
+ employeeEmail: data.email || (LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined),
3430
+ employeeId: data.employeeId || LRS.employeeId || undefined
3431
+ };
3432
+ if (data.employeeName) activityDetails.employeeName = data.employeeName;
3433
+
3434
+ log('Sending completion statement:', verbKey, 'score:', result.score, 'title:', courseTitle);
3435
+ var statement = buildStatement(verbKey, ACTIVITY_TYPES.course, activityDetails, result, null);
3436
+ sendStatement(statement);
3437
+ };
3438
+
3381
3439
  /**
3382
3440
  * Re-extract actor from SCORM after LMSInitialize has been called.
3383
3441
  * Call this from the SCORM wrapper after scormInit() succeeds,
@@ -4048,9 +4106,37 @@ function generateLrsBridgeCode(options) {
4048
4106
  var scormInteractions = {}; // Pending interactions keyed by index N
4049
4107
  var scormInteractionsSent = {}; // Track which interactions were already sent
4050
4108
  var scormTrackerActive = false; // Set true when SCORM tracker wraps SetValue \u2014 KC handler defers
4109
+ var scormCourseData = {}; // Accumulates course-level SCORM data (score, status)
4110
+ var scormCompletionSent = false; // Prevent duplicate completion statements
4051
4111
 
4052
4112
  function interceptScormSetValue(key, value) {
4053
4113
  if (typeof key !== 'string') return;
4114
+
4115
+ // ---- Course-level SCORM data (score, status, completion) ----
4116
+ // SCORM 1.2
4117
+ if (key === 'cmi.core.score.raw') { scormCourseData.scoreRaw = parseFloat(value); }
4118
+ if (key === 'cmi.core.score.max') { scormCourseData.scoreMax = parseFloat(value); }
4119
+ if (key === 'cmi.core.score.min') { scormCourseData.scoreMin = parseFloat(value); }
4120
+ if (key === 'cmi.core.lesson_status') { scormCourseData.status = String(value).toLowerCase(); }
4121
+ // SCORM 2004
4122
+ if (key === 'cmi.score.raw') { scormCourseData.scoreRaw = parseFloat(value); }
4123
+ if (key === 'cmi.score.max') { scormCourseData.scoreMax = parseFloat(value); }
4124
+ if (key === 'cmi.score.min') { scormCourseData.scoreMin = parseFloat(value); }
4125
+ if (key === 'cmi.score.scaled') { scormCourseData.scoreScaled = parseFloat(value); }
4126
+ if (key === 'cmi.completion_status') { scormCourseData.completionStatus = String(value).toLowerCase(); }
4127
+ if (key === 'cmi.success_status') { scormCourseData.successStatus = String(value).toLowerCase(); }
4128
+
4129
+ // Fire course completion statement when status indicates pass/fail/complete
4130
+ if ((key === 'cmi.core.lesson_status' || key === 'cmi.success_status' || key === 'cmi.completion_status') && !scormCompletionSent) {
4131
+ var status = String(value).toLowerCase();
4132
+ if (status === 'passed' || status === 'failed' || status === 'completed') {
4133
+ scormCompletionSent = true;
4134
+ // Slight delay to let score values arrive (Rise often sets score just before status)
4135
+ setTimeout(function() { sendCourseCompletionStatement(status); }, 200);
4136
+ }
4137
+ }
4138
+
4139
+ // ---- Interaction-level tracking (existing) ----
4054
4140
  var interactionPattern = /^cmi\\.interactions\\.(\\d+)\\.(.+)$/;
4055
4141
  var match = key.match(interactionPattern);
4056
4142
  if (!match) return;
@@ -4150,6 +4236,51 @@ function generateLrsBridgeCode(options) {
4150
4236
  }
4151
4237
  }
4152
4238
 
4239
+ function sendCourseCompletionStatement(status) {
4240
+ logGroup('Course Completion');
4241
+ log('Status:', status);
4242
+ log('SCORM course data:', scormCourseData);
4243
+
4244
+ var courseTitle = (LRS.courseInfo && LRS.courseInfo.title) ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course';
4245
+
4246
+ // Determine verb
4247
+ var verbKey = 'completed';
4248
+ if (status === 'passed') verbKey = 'passed';
4249
+ if (status === 'failed') verbKey = 'failed';
4250
+
4251
+ // Build result with score
4252
+ var result = { completion: true };
4253
+
4254
+ var raw = scormCourseData.scoreRaw;
4255
+ var max = scormCourseData.scoreMax || 100;
4256
+ var min = scormCourseData.scoreMin || 0;
4257
+
4258
+ if (typeof raw === 'number' && !isNaN(raw)) {
4259
+ result.score = {
4260
+ raw: raw,
4261
+ max: max,
4262
+ min: min,
4263
+ scaled: scormCourseData.scoreScaled != null ? scormCourseData.scoreScaled : (max > min ? (raw - min) / (max - min) : 0)
4264
+ };
4265
+ }
4266
+
4267
+ result.success = (status === 'passed');
4268
+
4269
+ var activityDetails = {
4270
+ courseTitle: courseTitle,
4271
+ completionStatus: status,
4272
+ employeeEmail: LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined,
4273
+ employeeId: LRS.employeeId || undefined
4274
+ };
4275
+
4276
+ log('Sending course completion:', verbKey, 'score:', result.score, 'title:', courseTitle);
4277
+
4278
+ var statement = buildStatement(verbKey, ACTIVITY_TYPES.course, activityDetails, result, null);
4279
+ sendStatement(statement);
4280
+
4281
+ logGroupEnd();
4282
+ }
4283
+
4153
4284
  function setupScormInteractionTracker() {
4154
4285
  if (!TRACK_QUIZZES) return;
4155
4286