@patch-adams/core 1.5.19 → 1.5.20

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
@@ -1600,6 +1600,101 @@ function generateLrsBridgeCode(options) {
1600
1600
  return hasValidMbox || hasValidAccount;
1601
1601
  }
1602
1602
 
1603
+ // ========================================================================
1604
+ // CROSS-FRAME ACTOR SHARING
1605
+ // PA-Patcher injects the bridge into ALL HTML files in a SCORM package.
1606
+ // Rise courses have multiple HTML files (index.html, scormcontent/index.html),
1607
+ // each getting their own bridge instance with its own LRS.actor.
1608
+ // The skin overlay (email gate) only runs in one frame and sets the actor there.
1609
+ // Without sharing, other bridge instances keep the Anonymous Learner actor.
1610
+ // Solution: persist actor to localStorage so all bridge instances can use it.
1611
+ // ========================================================================
1612
+ var ACTOR_STORAGE_KEY = 'pa_lrs_shared_actor';
1613
+
1614
+ function isAnonymousActor(actor) {
1615
+ if (!actor) return true;
1616
+ if (!actor.name) return true;
1617
+ var n = actor.name.toLowerCase();
1618
+ return n === 'anonymous learner' || n === 'unknown learner' || n === 'anonymous' || n === 'unknown';
1619
+ }
1620
+
1621
+ /**
1622
+ * Persist actor to localStorage for cross-frame sharing.
1623
+ * Only stores non-anonymous actors.
1624
+ */
1625
+ function persistActor(actor) {
1626
+ if (!actor || isAnonymousActor(actor)) return;
1627
+ try {
1628
+ localStorage.setItem(ACTOR_STORAGE_KEY, JSON.stringify(actor));
1629
+ log('Actor persisted to localStorage:', actor.name);
1630
+ } catch (e) { /* localStorage unavailable */ }
1631
+ }
1632
+
1633
+ /**
1634
+ * Load shared actor from localStorage.
1635
+ * Returns the stored actor or null.
1636
+ */
1637
+ function loadSharedActor() {
1638
+ try {
1639
+ var stored = localStorage.getItem(ACTOR_STORAGE_KEY);
1640
+ if (stored) {
1641
+ var actor = JSON.parse(stored);
1642
+ if (actor && !isAnonymousActor(actor)) {
1643
+ return actor;
1644
+ }
1645
+ }
1646
+ } catch (e) { /* localStorage unavailable or parse error */ }
1647
+ return null;
1648
+ }
1649
+
1650
+ /**
1651
+ * Get the best available actor for statement building.
1652
+ * Priority: LRS.actor (if non-anonymous) > localStorage shared actor > LRS.actor > extractActor()
1653
+ */
1654
+ function getActor() {
1655
+ // If current actor is non-anonymous, use it
1656
+ if (LRS.actor && !isAnonymousActor(LRS.actor)) {
1657
+ return LRS.actor;
1658
+ }
1659
+ // Check localStorage for actor set by another frame (e.g., skin overlay)
1660
+ var shared = loadSharedActor();
1661
+ if (shared) {
1662
+ // Update local actor so future calls are fast
1663
+ LRS.actor = shared;
1664
+ log('Actor loaded from cross-frame storage:', shared.name);
1665
+ return shared;
1666
+ }
1667
+ // Fallback to current actor or re-extract
1668
+ return LRS.actor || extractActor();
1669
+ }
1670
+
1671
+ /**
1672
+ * Set actor on the bridge with cross-frame persistence.
1673
+ * Called by skins and external code via window.pa_patcher.lrs.setActor(actor)
1674
+ */
1675
+ LRS.setActor = function(actor) {
1676
+ LRS.actor = actor;
1677
+ persistActor(actor);
1678
+ // Also try to propagate to other frames in the hierarchy
1679
+ try {
1680
+ var w = window;
1681
+ for (var i = 0; i < 10; i++) {
1682
+ try {
1683
+ if (w !== window && w.pa_patcher && w.pa_patcher.lrs) {
1684
+ w.pa_patcher.lrs.actor = actor;
1685
+ }
1686
+ } catch (e) { /* cross-origin */ }
1687
+ if (w === w.parent) break;
1688
+ w = w.parent;
1689
+ }
1690
+ } catch (e) {}
1691
+ if (window.console && window.console.info) {
1692
+ console.info('[PA-LRS] Actor set:', actor.name,
1693
+ actor.mbox ? '(' + actor.mbox + ')' : '',
1694
+ '\u2014 shared across frames');
1695
+ }
1696
+ };
1697
+
1603
1698
  /**
1604
1699
  * Async version that fetches Bravais session if needed
1605
1700
  * Also re-checks all sources in case they became available
@@ -1611,6 +1706,7 @@ function generateLrsBridgeCode(options) {
1611
1706
  // Try to enhance actor with email if missing
1612
1707
  enhanceActorWithEmail(actor, function(enhancedActor) {
1613
1708
  LRS.actor = enhancedActor;
1709
+ persistActor(enhancedActor);
1614
1710
  callback(enhancedActor);
1615
1711
  });
1616
1712
  }
@@ -2617,7 +2713,7 @@ function generateLrsBridgeCode(options) {
2617
2713
 
2618
2714
  var statement = {
2619
2715
  id: generateUUID(),
2620
- actor: LRS.actor || extractActor(),
2716
+ actor: getActor(),
2621
2717
  verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,
2622
2718
  object: courseObj,
2623
2719
  timestamp: new Date().toISOString()
@@ -3039,7 +3135,7 @@ function generateLrsBridgeCode(options) {
3039
3135
  function buildStatementXyleme(verb, activityObject, result, additionalContext) {
3040
3136
  var statement = {
3041
3137
  id: generateUUID(),
3042
- actor: LRS.actor || extractActor(),
3138
+ actor: getActor(),
3043
3139
  verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,
3044
3140
  object: activityObject,
3045
3141
  timestamp: new Date().toISOString()
@@ -4768,6 +4864,15 @@ function generateLrsBridgeCode(options) {
4768
4864
  // Extract actor (sync first for immediate use)
4769
4865
  extractActor();
4770
4866
 
4867
+ // Check localStorage for actor set by another frame (e.g., skin overlay in a different HTML file)
4868
+ if (isAnonymousActor(LRS.actor)) {
4869
+ var sharedActor = loadSharedActor();
4870
+ if (sharedActor) {
4871
+ LRS.actor = sharedActor;
4872
+ log('Loaded actor from cross-frame storage at init:', sharedActor.name);
4873
+ }
4874
+ }
4875
+
4771
4876
  log('Bridge initialized in mode:', LRS.mode);
4772
4877
  log('Actor:', LRS.actor);
4773
4878
  log('Course:', LRS.courseInfo);