@patch-adams/core 1.5.11 → 1.5.14

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
@@ -50,7 +50,9 @@ var LrsBridgeConfigSchema = zod.z.object({
50
50
  /** Document GUID for xAPI statement aggregation - baked into package at wrap time */
51
51
  documentGuid: zod.z.string().optional(),
52
52
  /** Version GUID for xAPI statement aggregation - unique per export */
53
- versionGuid: zod.z.string().optional()
53
+ versionGuid: zod.z.string().optional(),
54
+ /** Original SCORM package filename - baked in at wrap time for LRS searchability */
55
+ packageName: zod.z.string().optional()
54
56
  });
55
57
  var BlockingAssetConfigSchema = zod.z.object({
56
58
  /** Filename for the asset */
@@ -399,7 +401,8 @@ var DEFAULT_LRS_OPTIONS = {
399
401
  lrsAuth: "",
400
402
  lrsProxyEndpoint: "",
401
403
  documentGuid: "",
402
- versionGuid: ""
404
+ versionGuid: "",
405
+ packageName: ""
403
406
  };
404
407
  function generateLrsBridgeCode(options) {
405
408
  if (!options.enabled) {
@@ -415,6 +418,7 @@ function generateLrsBridgeCode(options) {
415
418
  const lrsProxyEndpoint = options.lrsProxyEndpoint || "";
416
419
  const documentGuid = options.documentGuid || "";
417
420
  const versionGuid = options.versionGuid || "";
421
+ const packageName = options.packageName || "";
418
422
  return `
419
423
  // ==========================================================================
420
424
  // PATCH-ADAMS LRS BRIDGE v2.2.0
@@ -447,6 +451,7 @@ function generateLrsBridgeCode(options) {
447
451
  var LRS_PROXY_ENDPOINT = '${lrsProxyEndpoint}';
448
452
  var DOCUMENT_GUID = '${documentGuid}';
449
453
  var VERSION_GUID = '${versionGuid}';
454
+ var PACKAGE_NAME = '${packageName}';
450
455
 
451
456
  // Bravais LRS endpoint pattern: https://lrs-{tenant}.bravais.com/XAPI/statements
452
457
  // Example: core-acme.bravais.com -> lrs-acme.bravais.com
@@ -2130,7 +2135,9 @@ function generateLrsBridgeCode(options) {
2130
2135
  sharedLinkName: null,
2131
2136
  // Tenant info
2132
2137
  homepage: COURSE_HOMEPAGE || window.location.origin,
2133
- tenantHomepage: null // https://{tenant}.bravais.com format
2138
+ tenantHomepage: null, // https://{tenant}.bravais.com format
2139
+ // Package info
2140
+ packageName: null // Original SCORM zip filename
2134
2141
  };
2135
2142
 
2136
2143
  // 0. Use baked-in GUIDs from PA-Patcher config (highest priority - set at wrap time)
@@ -2143,6 +2150,10 @@ function generateLrsBridgeCode(options) {
2143
2150
  info.versionGuid = VERSION_GUID;
2144
2151
  log('Version GUID from baked-in config:', VERSION_GUID);
2145
2152
  }
2153
+ if (PACKAGE_NAME) {
2154
+ info.packageName = PACKAGE_NAME;
2155
+ log('Package name from baked-in config:', PACKAGE_NAME);
2156
+ }
2146
2157
 
2147
2158
  // 1. Extract shared link token and document ID from URL
2148
2159
  info.sharedLinkToken = extractSharedLinkToken();
@@ -2573,6 +2584,11 @@ function generateLrsBridgeCode(options) {
2573
2584
  // Resource type
2574
2585
  obj.definition.extensions['resourceType'] = LRS.courseInfo.resourceType || 'Course';
2575
2586
 
2587
+ // Package name (original SCORM zip filename)
2588
+ if (LRS.courseInfo.packageName) {
2589
+ obj.definition.extensions['packageName'] = LRS.courseInfo.packageName;
2590
+ }
2591
+
2576
2592
  return obj;
2577
2593
  }
2578
2594
 
@@ -3406,11 +3422,11 @@ function generateLrsBridgeCode(options) {
3406
3422
  // Send a course completion statement \u2014 called by skin or auto-detected via SCORM
3407
3423
  LRS.sendCompletionStatement = function(data) {
3408
3424
  data = data || {};
3409
- if (scormCompletionSent) {
3410
- log('Completion statement already sent (SCORM auto-detected). Skipping duplicate.');
3411
- return;
3425
+ // Cancel any pending SCORM auto-detection to avoid double-fire for same attempt
3426
+ if (scormCompletionDebounce) {
3427
+ clearTimeout(scormCompletionDebounce);
3428
+ scormCompletionDebounce = null;
3412
3429
  }
3413
- scormCompletionSent = true;
3414
3430
 
3415
3431
  var status = data.status || 'completed';
3416
3432
  var courseTitle = data.courseTitle || (LRS.courseInfo && LRS.courseInfo.title ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course');
@@ -3440,11 +3456,58 @@ function generateLrsBridgeCode(options) {
3440
3456
  };
3441
3457
  if (data.employeeName) activityDetails.employeeName = data.employeeName;
3442
3458
 
3443
- log('Sending completion statement:', verbKey, 'score:', result.score, 'title:', courseTitle);
3444
- var statement = buildStatement(verbKey, ACTIVITY_TYPES.course, activityDetails, result, null);
3459
+ // 1. Always send "completed" statement
3460
+ log('Sending completed statement, score:', result.score, 'title:', courseTitle);
3461
+ var completedStatement = buildStatement('completed', ACTIVITY_TYPES.course, activityDetails, result, null);
3462
+ sendStatement(completedStatement);
3463
+
3464
+ // 2. Send "passed" or "failed" statement (assessment outcome)
3465
+ if (status === 'passed' || status === 'failed') {
3466
+ log('Sending', status, 'statement');
3467
+ var outcomeStatement = buildStatement(status, ACTIVITY_TYPES.course, activityDetails, result, null);
3468
+ sendStatement(outcomeStatement);
3469
+ }
3470
+ };
3471
+
3472
+ // Send a "terminated" statement \u2014 called by skin close button or auto on page unload
3473
+ var exitStatementSent = false;
3474
+ LRS.sendExitStatement = function(data) {
3475
+ if (exitStatementSent) return;
3476
+ exitStatementSent = true;
3477
+ data = data || {};
3478
+
3479
+ var courseTitle = (LRS.courseInfo && LRS.courseInfo.title) ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course';
3480
+ var duration = null;
3481
+ if (LRS.launchTime) {
3482
+ var ms = new Date().getTime() - new Date(LRS.launchTime).getTime();
3483
+ var secs = Math.floor(ms / 1000);
3484
+ var mins = Math.floor(secs / 60);
3485
+ var hrs = Math.floor(mins / 60);
3486
+ duration = 'PT' + (hrs > 0 ? hrs + 'H' : '') + (mins % 60) + 'M' + (secs % 60) + 'S';
3487
+ }
3488
+
3489
+ var activityDetails = {
3490
+ courseTitle: courseTitle,
3491
+ exitSource: data.source || 'page-unload',
3492
+ employeeEmail: LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined,
3493
+ employeeId: LRS.employeeId || undefined,
3494
+ sessionDuration: duration,
3495
+ statementsSent: LRS.stats.statementsSent
3496
+ };
3497
+
3498
+ var result = duration ? { duration: duration } : null;
3499
+ log('Sending exit statement, duration:', duration);
3500
+ var statement = buildStatement('terminated', ACTIVITY_TYPES.course, activityDetails, result, null);
3445
3501
  sendStatement(statement);
3446
3502
  };
3447
3503
 
3504
+ // Auto-send exit statement on page unload
3505
+ window.addEventListener('beforeunload', function() {
3506
+ if (!exitStatementSent && LRS.actor) {
3507
+ LRS.sendExitStatement({ source: 'page-unload' });
3508
+ }
3509
+ });
3510
+
3448
3511
  /**
3449
3512
  * Re-extract actor from SCORM after LMSInitialize has been called.
3450
3513
  * Call this from the SCORM wrapper after scormInit() succeeds,
@@ -3551,6 +3614,23 @@ function generateLrsBridgeCode(options) {
3551
3614
  LRS.interacted({ type: 'choice-branch', id: data.id, name: 'Choice Branch Discarded' });
3552
3615
  };
3553
3616
 
3617
+ // ========================================================================
3618
+ // PUBLIC API \u2014 expose core internals for skin scripts
3619
+ // Usage: window.pa_patcher.lrs.api.sendStatement(stmt)
3620
+ // ========================================================================
3621
+ LRS.api = {
3622
+ sendStatement: sendStatement,
3623
+ buildStatement: buildStatement,
3624
+ buildCourseActivityObject: buildCourseActivityObject,
3625
+ buildXylemeContext: buildXylemeContext,
3626
+ generateUUID: generateUUID,
3627
+ decodeEntities: decodeEntities,
3628
+ extractActor: extractActor,
3629
+ getCachedLessonInfo: getCachedLessonInfo,
3630
+ VERBS: VERBS,
3631
+ ACTIVITY_TYPES: ACTIVITY_TYPES
3632
+ };
3633
+
3554
3634
  // ========================================================================
3555
3635
  // 8. RISE EVENT INTERCEPTORS
3556
3636
  // ========================================================================
@@ -4116,7 +4196,7 @@ function generateLrsBridgeCode(options) {
4116
4196
  var scormInteractionsSent = {}; // Track which interactions were already sent
4117
4197
  var scormTrackerActive = false; // Set true when SCORM tracker wraps SetValue \u2014 KC handler defers
4118
4198
  var scormCourseData = {}; // Accumulates course-level SCORM data (score, status)
4119
- var scormCompletionSent = false; // Prevent duplicate completion statements
4199
+ var scormCompletionDebounce = null; // Debounce timer \u2014 prevents duplicate fires within same completion event
4120
4200
 
4121
4201
  function interceptScormSetValue(key, value) {
4122
4202
  if (typeof key !== 'string') return;
@@ -4136,12 +4216,15 @@ function generateLrsBridgeCode(options) {
4136
4216
  if (key === 'cmi.success_status') { scormCourseData.successStatus = String(value).toLowerCase(); }
4137
4217
 
4138
4218
  // Fire course completion statement when status indicates pass/fail/complete
4139
- if ((key === 'cmi.core.lesson_status' || key === 'cmi.success_status' || key === 'cmi.completion_status') && !scormCompletionSent) {
4219
+ // Uses debounce (not one-time flag) so retries/new attempts are captured
4220
+ if (key === 'cmi.core.lesson_status' || key === 'cmi.success_status' || key === 'cmi.completion_status') {
4140
4221
  var status = String(value).toLowerCase();
4141
4222
  if (status === 'passed' || status === 'failed' || status === 'completed') {
4142
- scormCompletionSent = true;
4143
- // Slight delay to let score values arrive (Rise often sets score just before status)
4144
- setTimeout(function() { sendCourseCompletionStatement(status); }, 200);
4223
+ if (scormCompletionDebounce) clearTimeout(scormCompletionDebounce);
4224
+ scormCompletionDebounce = setTimeout(function() {
4225
+ scormCompletionDebounce = null;
4226
+ sendCourseCompletionStatement(status);
4227
+ }, 500);
4145
4228
  }
4146
4229
  }
4147
4230
 
@@ -4252,11 +4335,6 @@ function generateLrsBridgeCode(options) {
4252
4335
 
4253
4336
  var courseTitle = (LRS.courseInfo && LRS.courseInfo.title) ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course';
4254
4337
 
4255
- // Determine verb
4256
- var verbKey = 'completed';
4257
- if (status === 'passed') verbKey = 'passed';
4258
- if (status === 'failed') verbKey = 'failed';
4259
-
4260
4338
  // Build result with score
4261
4339
  var result = { completion: true };
4262
4340
 
@@ -4282,10 +4360,17 @@ function generateLrsBridgeCode(options) {
4282
4360
  employeeId: LRS.employeeId || undefined
4283
4361
  };
4284
4362
 
4285
- log('Sending course completion:', verbKey, 'score:', result.score, 'title:', courseTitle);
4363
+ // 1. Send "completed" statement (course was finished)
4364
+ log('Sending completed statement, score:', result.score, 'title:', courseTitle);
4365
+ var completedStatement = buildStatement('completed', ACTIVITY_TYPES.course, activityDetails, result, null);
4366
+ sendStatement(completedStatement);
4286
4367
 
4287
- var statement = buildStatement(verbKey, ACTIVITY_TYPES.course, activityDetails, result, null);
4288
- sendStatement(statement);
4368
+ // 2. Send "passed" or "failed" statement (assessment outcome)
4369
+ if (status === 'passed' || status === 'failed') {
4370
+ log('Sending', status, 'statement');
4371
+ var outcomeStatement = buildStatement(status, ACTIVITY_TYPES.course, activityDetails, result, null);
4372
+ sendStatement(outcomeStatement);
4373
+ }
4289
4374
 
4290
4375
  logGroupEnd();
4291
4376
  }
@@ -4969,7 +5054,8 @@ function buildJsBeforeOptions(config, metadata) {
4969
5054
  lrsProxyEndpoint: lrsBridgeConfig.lrsProxyEndpoint,
4970
5055
  employeeApiEndpoint: lrsBridgeConfig.employeeApiEndpoint,
4971
5056
  documentGuid: lrsBridgeConfig.documentGuid,
4972
- versionGuid: lrsBridgeConfig.versionGuid
5057
+ versionGuid: lrsBridgeConfig.versionGuid,
5058
+ packageName: lrsBridgeConfig.packageName
4973
5059
  };
4974
5060
  return {
4975
5061
  remoteUrl: `${config.remoteDomain}/${config.localFolders.js}/${config.jsBefore.filename}`,
@@ -6659,6 +6745,11 @@ var Patcher = class {
6659
6745
  if (effectiveSkin) {
6660
6746
  console.log(`[Patcher] Skin: ${effectiveSkin}`);
6661
6747
  }
6748
+ if (options.packageName) {
6749
+ this.config.lrsBridge = this.config.lrsBridge ?? {};
6750
+ this.config.lrsBridge.packageName = options.packageName;
6751
+ console.log(`[Patcher] Package name: ${options.packageName}`);
6752
+ }
6662
6753
  const htmlInjector = this.getHtmlInjector(toolInfo.tool);
6663
6754
  htmlInjector.setMetadata(metadata);
6664
6755
  let fetchedFallbacks = {};