@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.d.cts CHANGED
@@ -38,6 +38,8 @@ declare const LrsBridgeConfigSchema: z.ZodObject<{
38
38
  documentGuid: z.ZodOptional<z.ZodString>;
39
39
  /** Version GUID for xAPI statement aggregation - unique per export */
40
40
  versionGuid: z.ZodOptional<z.ZodString>;
41
+ /** Original SCORM package filename - baked in at wrap time for LRS searchability */
42
+ packageName: z.ZodOptional<z.ZodString>;
41
43
  }, "strip", z.ZodTypeAny, {
42
44
  enabled: boolean;
43
45
  trackMedia: boolean;
@@ -55,6 +57,7 @@ declare const LrsBridgeConfigSchema: z.ZodObject<{
55
57
  employeeApiEndpoint?: string | undefined;
56
58
  documentGuid?: string | undefined;
57
59
  versionGuid?: string | undefined;
60
+ packageName?: string | undefined;
58
61
  }, {
59
62
  enabled?: boolean | undefined;
60
63
  trackMedia?: boolean | undefined;
@@ -72,6 +75,7 @@ declare const LrsBridgeConfigSchema: z.ZodObject<{
72
75
  employeeApiEndpoint?: string | undefined;
73
76
  documentGuid?: string | undefined;
74
77
  versionGuid?: string | undefined;
78
+ packageName?: string | undefined;
75
79
  }>;
76
80
  /**
77
81
  * Configuration for a blocking asset (CSS before, JS before)
@@ -245,6 +249,8 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
245
249
  documentGuid: z.ZodOptional<z.ZodString>;
246
250
  /** Version GUID for xAPI statement aggregation - unique per export */
247
251
  versionGuid: z.ZodOptional<z.ZodString>;
252
+ /** Original SCORM package filename - baked in at wrap time for LRS searchability */
253
+ packageName: z.ZodOptional<z.ZodString>;
248
254
  }, "strip", z.ZodTypeAny, {
249
255
  enabled: boolean;
250
256
  trackMedia: boolean;
@@ -262,6 +268,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
262
268
  employeeApiEndpoint?: string | undefined;
263
269
  documentGuid?: string | undefined;
264
270
  versionGuid?: string | undefined;
271
+ packageName?: string | undefined;
265
272
  }, {
266
273
  enabled?: boolean | undefined;
267
274
  trackMedia?: boolean | undefined;
@@ -279,6 +286,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
279
286
  employeeApiEndpoint?: string | undefined;
280
287
  documentGuid?: string | undefined;
281
288
  versionGuid?: string | undefined;
289
+ packageName?: string | undefined;
282
290
  }>>;
283
291
  /** Plugin configurations - each plugin is keyed by its name */
284
292
  plugins: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -338,6 +346,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
338
346
  employeeApiEndpoint?: string | undefined;
339
347
  documentGuid?: string | undefined;
340
348
  versionGuid?: string | undefined;
349
+ packageName?: string | undefined;
341
350
  };
342
351
  plugins: Record<string, z.objectOutputType<{
343
352
  /** Whether this plugin is enabled */
@@ -389,6 +398,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
389
398
  employeeApiEndpoint?: string | undefined;
390
399
  documentGuid?: string | undefined;
391
400
  versionGuid?: string | undefined;
401
+ packageName?: string | undefined;
392
402
  } | undefined;
393
403
  plugins?: Record<string, z.objectInputType<{
394
404
  /** Whether this plugin is enabled */
@@ -864,6 +874,8 @@ interface PatchOptions {
864
874
  skipFetch?: boolean;
865
875
  /** Optional skin name — per-call override for config.skin */
866
876
  skin?: string;
877
+ /** Original package filename — baked into LRS bridge for statement searchability */
878
+ packageName?: string;
867
879
  }
868
880
  /**
869
881
  * Main Patcher class for patching e-learning course packages
@@ -1037,6 +1049,8 @@ interface LrsBridgeOptions {
1037
1049
  documentGuid?: string;
1038
1050
  /** Version GUID for xAPI statement aggregation - unique per export */
1039
1051
  versionGuid?: string;
1052
+ /** Original SCORM package filename - baked in at wrap time for LRS searchability */
1053
+ packageName?: string;
1040
1054
  }
1041
1055
  declare const DEFAULT_LRS_OPTIONS: LrsBridgeOptions;
1042
1056
  /**
package/dist/index.d.ts CHANGED
@@ -38,6 +38,8 @@ declare const LrsBridgeConfigSchema: z.ZodObject<{
38
38
  documentGuid: z.ZodOptional<z.ZodString>;
39
39
  /** Version GUID for xAPI statement aggregation - unique per export */
40
40
  versionGuid: z.ZodOptional<z.ZodString>;
41
+ /** Original SCORM package filename - baked in at wrap time for LRS searchability */
42
+ packageName: z.ZodOptional<z.ZodString>;
41
43
  }, "strip", z.ZodTypeAny, {
42
44
  enabled: boolean;
43
45
  trackMedia: boolean;
@@ -55,6 +57,7 @@ declare const LrsBridgeConfigSchema: z.ZodObject<{
55
57
  employeeApiEndpoint?: string | undefined;
56
58
  documentGuid?: string | undefined;
57
59
  versionGuid?: string | undefined;
60
+ packageName?: string | undefined;
58
61
  }, {
59
62
  enabled?: boolean | undefined;
60
63
  trackMedia?: boolean | undefined;
@@ -72,6 +75,7 @@ declare const LrsBridgeConfigSchema: z.ZodObject<{
72
75
  employeeApiEndpoint?: string | undefined;
73
76
  documentGuid?: string | undefined;
74
77
  versionGuid?: string | undefined;
78
+ packageName?: string | undefined;
75
79
  }>;
76
80
  /**
77
81
  * Configuration for a blocking asset (CSS before, JS before)
@@ -245,6 +249,8 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
245
249
  documentGuid: z.ZodOptional<z.ZodString>;
246
250
  /** Version GUID for xAPI statement aggregation - unique per export */
247
251
  versionGuid: z.ZodOptional<z.ZodString>;
252
+ /** Original SCORM package filename - baked in at wrap time for LRS searchability */
253
+ packageName: z.ZodOptional<z.ZodString>;
248
254
  }, "strip", z.ZodTypeAny, {
249
255
  enabled: boolean;
250
256
  trackMedia: boolean;
@@ -262,6 +268,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
262
268
  employeeApiEndpoint?: string | undefined;
263
269
  documentGuid?: string | undefined;
264
270
  versionGuid?: string | undefined;
271
+ packageName?: string | undefined;
265
272
  }, {
266
273
  enabled?: boolean | undefined;
267
274
  trackMedia?: boolean | undefined;
@@ -279,6 +286,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
279
286
  employeeApiEndpoint?: string | undefined;
280
287
  documentGuid?: string | undefined;
281
288
  versionGuid?: string | undefined;
289
+ packageName?: string | undefined;
282
290
  }>>;
283
291
  /** Plugin configurations - each plugin is keyed by its name */
284
292
  plugins: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -338,6 +346,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
338
346
  employeeApiEndpoint?: string | undefined;
339
347
  documentGuid?: string | undefined;
340
348
  versionGuid?: string | undefined;
349
+ packageName?: string | undefined;
341
350
  };
342
351
  plugins: Record<string, z.objectOutputType<{
343
352
  /** Whether this plugin is enabled */
@@ -389,6 +398,7 @@ declare const PatchAdamsConfigSchema: z.ZodObject<{
389
398
  employeeApiEndpoint?: string | undefined;
390
399
  documentGuid?: string | undefined;
391
400
  versionGuid?: string | undefined;
401
+ packageName?: string | undefined;
392
402
  } | undefined;
393
403
  plugins?: Record<string, z.objectInputType<{
394
404
  /** Whether this plugin is enabled */
@@ -864,6 +874,8 @@ interface PatchOptions {
864
874
  skipFetch?: boolean;
865
875
  /** Optional skin name — per-call override for config.skin */
866
876
  skin?: string;
877
+ /** Original package filename — baked into LRS bridge for statement searchability */
878
+ packageName?: string;
867
879
  }
868
880
  /**
869
881
  * Main Patcher class for patching e-learning course packages
@@ -1037,6 +1049,8 @@ interface LrsBridgeOptions {
1037
1049
  documentGuid?: string;
1038
1050
  /** Version GUID for xAPI statement aggregation - unique per export */
1039
1051
  versionGuid?: string;
1052
+ /** Original SCORM package filename - baked in at wrap time for LRS searchability */
1053
+ packageName?: string;
1040
1054
  }
1041
1055
  declare const DEFAULT_LRS_OPTIONS: LrsBridgeOptions;
1042
1056
  /**
package/dist/index.js CHANGED
@@ -41,7 +41,9 @@ var LrsBridgeConfigSchema = z.object({
41
41
  /** Document GUID for xAPI statement aggregation - baked into package at wrap time */
42
42
  documentGuid: z.string().optional(),
43
43
  /** Version GUID for xAPI statement aggregation - unique per export */
44
- versionGuid: z.string().optional()
44
+ versionGuid: z.string().optional(),
45
+ /** Original SCORM package filename - baked in at wrap time for LRS searchability */
46
+ packageName: z.string().optional()
45
47
  });
46
48
  var BlockingAssetConfigSchema = z.object({
47
49
  /** Filename for the asset */
@@ -390,7 +392,8 @@ var DEFAULT_LRS_OPTIONS = {
390
392
  lrsAuth: "",
391
393
  lrsProxyEndpoint: "",
392
394
  documentGuid: "",
393
- versionGuid: ""
395
+ versionGuid: "",
396
+ packageName: ""
394
397
  };
395
398
  function generateLrsBridgeCode(options) {
396
399
  if (!options.enabled) {
@@ -406,6 +409,7 @@ function generateLrsBridgeCode(options) {
406
409
  const lrsProxyEndpoint = options.lrsProxyEndpoint || "";
407
410
  const documentGuid = options.documentGuid || "";
408
411
  const versionGuid = options.versionGuid || "";
412
+ const packageName = options.packageName || "";
409
413
  return `
410
414
  // ==========================================================================
411
415
  // PATCH-ADAMS LRS BRIDGE v2.2.0
@@ -438,6 +442,7 @@ function generateLrsBridgeCode(options) {
438
442
  var LRS_PROXY_ENDPOINT = '${lrsProxyEndpoint}';
439
443
  var DOCUMENT_GUID = '${documentGuid}';
440
444
  var VERSION_GUID = '${versionGuid}';
445
+ var PACKAGE_NAME = '${packageName}';
441
446
 
442
447
  // Bravais LRS endpoint pattern: https://lrs-{tenant}.bravais.com/XAPI/statements
443
448
  // Example: core-acme.bravais.com -> lrs-acme.bravais.com
@@ -2121,7 +2126,9 @@ function generateLrsBridgeCode(options) {
2121
2126
  sharedLinkName: null,
2122
2127
  // Tenant info
2123
2128
  homepage: COURSE_HOMEPAGE || window.location.origin,
2124
- tenantHomepage: null // https://{tenant}.bravais.com format
2129
+ tenantHomepage: null, // https://{tenant}.bravais.com format
2130
+ // Package info
2131
+ packageName: null // Original SCORM zip filename
2125
2132
  };
2126
2133
 
2127
2134
  // 0. Use baked-in GUIDs from PA-Patcher config (highest priority - set at wrap time)
@@ -2134,6 +2141,10 @@ function generateLrsBridgeCode(options) {
2134
2141
  info.versionGuid = VERSION_GUID;
2135
2142
  log('Version GUID from baked-in config:', VERSION_GUID);
2136
2143
  }
2144
+ if (PACKAGE_NAME) {
2145
+ info.packageName = PACKAGE_NAME;
2146
+ log('Package name from baked-in config:', PACKAGE_NAME);
2147
+ }
2137
2148
 
2138
2149
  // 1. Extract shared link token and document ID from URL
2139
2150
  info.sharedLinkToken = extractSharedLinkToken();
@@ -2564,6 +2575,11 @@ function generateLrsBridgeCode(options) {
2564
2575
  // Resource type
2565
2576
  obj.definition.extensions['resourceType'] = LRS.courseInfo.resourceType || 'Course';
2566
2577
 
2578
+ // Package name (original SCORM zip filename)
2579
+ if (LRS.courseInfo.packageName) {
2580
+ obj.definition.extensions['packageName'] = LRS.courseInfo.packageName;
2581
+ }
2582
+
2567
2583
  return obj;
2568
2584
  }
2569
2585
 
@@ -3397,11 +3413,11 @@ function generateLrsBridgeCode(options) {
3397
3413
  // Send a course completion statement \u2014 called by skin or auto-detected via SCORM
3398
3414
  LRS.sendCompletionStatement = function(data) {
3399
3415
  data = data || {};
3400
- if (scormCompletionSent) {
3401
- log('Completion statement already sent (SCORM auto-detected). Skipping duplicate.');
3402
- return;
3416
+ // Cancel any pending SCORM auto-detection to avoid double-fire for same attempt
3417
+ if (scormCompletionDebounce) {
3418
+ clearTimeout(scormCompletionDebounce);
3419
+ scormCompletionDebounce = null;
3403
3420
  }
3404
- scormCompletionSent = true;
3405
3421
 
3406
3422
  var status = data.status || 'completed';
3407
3423
  var courseTitle = data.courseTitle || (LRS.courseInfo && LRS.courseInfo.title ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course');
@@ -3431,11 +3447,58 @@ function generateLrsBridgeCode(options) {
3431
3447
  };
3432
3448
  if (data.employeeName) activityDetails.employeeName = data.employeeName;
3433
3449
 
3434
- log('Sending completion statement:', verbKey, 'score:', result.score, 'title:', courseTitle);
3435
- var statement = buildStatement(verbKey, ACTIVITY_TYPES.course, activityDetails, result, null);
3450
+ // 1. Always send "completed" statement
3451
+ log('Sending completed statement, score:', result.score, 'title:', courseTitle);
3452
+ var completedStatement = buildStatement('completed', ACTIVITY_TYPES.course, activityDetails, result, null);
3453
+ sendStatement(completedStatement);
3454
+
3455
+ // 2. Send "passed" or "failed" statement (assessment outcome)
3456
+ if (status === 'passed' || status === 'failed') {
3457
+ log('Sending', status, 'statement');
3458
+ var outcomeStatement = buildStatement(status, ACTIVITY_TYPES.course, activityDetails, result, null);
3459
+ sendStatement(outcomeStatement);
3460
+ }
3461
+ };
3462
+
3463
+ // Send a "terminated" statement \u2014 called by skin close button or auto on page unload
3464
+ var exitStatementSent = false;
3465
+ LRS.sendExitStatement = function(data) {
3466
+ if (exitStatementSent) return;
3467
+ exitStatementSent = true;
3468
+ data = data || {};
3469
+
3470
+ var courseTitle = (LRS.courseInfo && LRS.courseInfo.title) ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course';
3471
+ var duration = null;
3472
+ if (LRS.launchTime) {
3473
+ var ms = new Date().getTime() - new Date(LRS.launchTime).getTime();
3474
+ var secs = Math.floor(ms / 1000);
3475
+ var mins = Math.floor(secs / 60);
3476
+ var hrs = Math.floor(mins / 60);
3477
+ duration = 'PT' + (hrs > 0 ? hrs + 'H' : '') + (mins % 60) + 'M' + (secs % 60) + 'S';
3478
+ }
3479
+
3480
+ var activityDetails = {
3481
+ courseTitle: courseTitle,
3482
+ exitSource: data.source || 'page-unload',
3483
+ employeeEmail: LRS.actor && LRS.actor.mbox ? LRS.actor.mbox.replace('mailto:', '') : undefined,
3484
+ employeeId: LRS.employeeId || undefined,
3485
+ sessionDuration: duration,
3486
+ statementsSent: LRS.stats.statementsSent
3487
+ };
3488
+
3489
+ var result = duration ? { duration: duration } : null;
3490
+ log('Sending exit statement, duration:', duration);
3491
+ var statement = buildStatement('terminated', ACTIVITY_TYPES.course, activityDetails, result, null);
3436
3492
  sendStatement(statement);
3437
3493
  };
3438
3494
 
3495
+ // Auto-send exit statement on page unload
3496
+ window.addEventListener('beforeunload', function() {
3497
+ if (!exitStatementSent && LRS.actor) {
3498
+ LRS.sendExitStatement({ source: 'page-unload' });
3499
+ }
3500
+ });
3501
+
3439
3502
  /**
3440
3503
  * Re-extract actor from SCORM after LMSInitialize has been called.
3441
3504
  * Call this from the SCORM wrapper after scormInit() succeeds,
@@ -3542,6 +3605,23 @@ function generateLrsBridgeCode(options) {
3542
3605
  LRS.interacted({ type: 'choice-branch', id: data.id, name: 'Choice Branch Discarded' });
3543
3606
  };
3544
3607
 
3608
+ // ========================================================================
3609
+ // PUBLIC API \u2014 expose core internals for skin scripts
3610
+ // Usage: window.pa_patcher.lrs.api.sendStatement(stmt)
3611
+ // ========================================================================
3612
+ LRS.api = {
3613
+ sendStatement: sendStatement,
3614
+ buildStatement: buildStatement,
3615
+ buildCourseActivityObject: buildCourseActivityObject,
3616
+ buildXylemeContext: buildXylemeContext,
3617
+ generateUUID: generateUUID,
3618
+ decodeEntities: decodeEntities,
3619
+ extractActor: extractActor,
3620
+ getCachedLessonInfo: getCachedLessonInfo,
3621
+ VERBS: VERBS,
3622
+ ACTIVITY_TYPES: ACTIVITY_TYPES
3623
+ };
3624
+
3545
3625
  // ========================================================================
3546
3626
  // 8. RISE EVENT INTERCEPTORS
3547
3627
  // ========================================================================
@@ -4107,7 +4187,7 @@ function generateLrsBridgeCode(options) {
4107
4187
  var scormInteractionsSent = {}; // Track which interactions were already sent
4108
4188
  var scormTrackerActive = false; // Set true when SCORM tracker wraps SetValue \u2014 KC handler defers
4109
4189
  var scormCourseData = {}; // Accumulates course-level SCORM data (score, status)
4110
- var scormCompletionSent = false; // Prevent duplicate completion statements
4190
+ var scormCompletionDebounce = null; // Debounce timer \u2014 prevents duplicate fires within same completion event
4111
4191
 
4112
4192
  function interceptScormSetValue(key, value) {
4113
4193
  if (typeof key !== 'string') return;
@@ -4127,12 +4207,15 @@ function generateLrsBridgeCode(options) {
4127
4207
  if (key === 'cmi.success_status') { scormCourseData.successStatus = String(value).toLowerCase(); }
4128
4208
 
4129
4209
  // 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) {
4210
+ // Uses debounce (not one-time flag) so retries/new attempts are captured
4211
+ if (key === 'cmi.core.lesson_status' || key === 'cmi.success_status' || key === 'cmi.completion_status') {
4131
4212
  var status = String(value).toLowerCase();
4132
4213
  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);
4214
+ if (scormCompletionDebounce) clearTimeout(scormCompletionDebounce);
4215
+ scormCompletionDebounce = setTimeout(function() {
4216
+ scormCompletionDebounce = null;
4217
+ sendCourseCompletionStatement(status);
4218
+ }, 500);
4136
4219
  }
4137
4220
  }
4138
4221
 
@@ -4243,11 +4326,6 @@ function generateLrsBridgeCode(options) {
4243
4326
 
4244
4327
  var courseTitle = (LRS.courseInfo && LRS.courseInfo.title) ? decodeEntities(LRS.courseInfo.title) : decodeEntities(document.title) || 'Course';
4245
4328
 
4246
- // Determine verb
4247
- var verbKey = 'completed';
4248
- if (status === 'passed') verbKey = 'passed';
4249
- if (status === 'failed') verbKey = 'failed';
4250
-
4251
4329
  // Build result with score
4252
4330
  var result = { completion: true };
4253
4331
 
@@ -4273,10 +4351,17 @@ function generateLrsBridgeCode(options) {
4273
4351
  employeeId: LRS.employeeId || undefined
4274
4352
  };
4275
4353
 
4276
- log('Sending course completion:', verbKey, 'score:', result.score, 'title:', courseTitle);
4354
+ // 1. Send "completed" statement (course was finished)
4355
+ log('Sending completed statement, score:', result.score, 'title:', courseTitle);
4356
+ var completedStatement = buildStatement('completed', ACTIVITY_TYPES.course, activityDetails, result, null);
4357
+ sendStatement(completedStatement);
4277
4358
 
4278
- var statement = buildStatement(verbKey, ACTIVITY_TYPES.course, activityDetails, result, null);
4279
- sendStatement(statement);
4359
+ // 2. Send "passed" or "failed" statement (assessment outcome)
4360
+ if (status === 'passed' || status === 'failed') {
4361
+ log('Sending', status, 'statement');
4362
+ var outcomeStatement = buildStatement(status, ACTIVITY_TYPES.course, activityDetails, result, null);
4363
+ sendStatement(outcomeStatement);
4364
+ }
4280
4365
 
4281
4366
  logGroupEnd();
4282
4367
  }
@@ -4960,7 +5045,8 @@ function buildJsBeforeOptions(config, metadata) {
4960
5045
  lrsProxyEndpoint: lrsBridgeConfig.lrsProxyEndpoint,
4961
5046
  employeeApiEndpoint: lrsBridgeConfig.employeeApiEndpoint,
4962
5047
  documentGuid: lrsBridgeConfig.documentGuid,
4963
- versionGuid: lrsBridgeConfig.versionGuid
5048
+ versionGuid: lrsBridgeConfig.versionGuid,
5049
+ packageName: lrsBridgeConfig.packageName
4964
5050
  };
4965
5051
  return {
4966
5052
  remoteUrl: `${config.remoteDomain}/${config.localFolders.js}/${config.jsBefore.filename}`,
@@ -6650,6 +6736,11 @@ var Patcher = class {
6650
6736
  if (effectiveSkin) {
6651
6737
  console.log(`[Patcher] Skin: ${effectiveSkin}`);
6652
6738
  }
6739
+ if (options.packageName) {
6740
+ this.config.lrsBridge = this.config.lrsBridge ?? {};
6741
+ this.config.lrsBridge.packageName = options.packageName;
6742
+ console.log(`[Patcher] Package name: ${options.packageName}`);
6743
+ }
6653
6744
  const htmlInjector = this.getHtmlInjector(toolInfo.tool);
6654
6745
  htmlInjector.setMetadata(metadata);
6655
6746
  let fetchedFallbacks = {};