@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.js CHANGED
@@ -1591,6 +1591,101 @@ function generateLrsBridgeCode(options) {
1591
1591
  return hasValidMbox || hasValidAccount;
1592
1592
  }
1593
1593
 
1594
+ // ========================================================================
1595
+ // CROSS-FRAME ACTOR SHARING
1596
+ // PA-Patcher injects the bridge into ALL HTML files in a SCORM package.
1597
+ // Rise courses have multiple HTML files (index.html, scormcontent/index.html),
1598
+ // each getting their own bridge instance with its own LRS.actor.
1599
+ // The skin overlay (email gate) only runs in one frame and sets the actor there.
1600
+ // Without sharing, other bridge instances keep the Anonymous Learner actor.
1601
+ // Solution: persist actor to localStorage so all bridge instances can use it.
1602
+ // ========================================================================
1603
+ var ACTOR_STORAGE_KEY = 'pa_lrs_shared_actor';
1604
+
1605
+ function isAnonymousActor(actor) {
1606
+ if (!actor) return true;
1607
+ if (!actor.name) return true;
1608
+ var n = actor.name.toLowerCase();
1609
+ return n === 'anonymous learner' || n === 'unknown learner' || n === 'anonymous' || n === 'unknown';
1610
+ }
1611
+
1612
+ /**
1613
+ * Persist actor to localStorage for cross-frame sharing.
1614
+ * Only stores non-anonymous actors.
1615
+ */
1616
+ function persistActor(actor) {
1617
+ if (!actor || isAnonymousActor(actor)) return;
1618
+ try {
1619
+ localStorage.setItem(ACTOR_STORAGE_KEY, JSON.stringify(actor));
1620
+ log('Actor persisted to localStorage:', actor.name);
1621
+ } catch (e) { /* localStorage unavailable */ }
1622
+ }
1623
+
1624
+ /**
1625
+ * Load shared actor from localStorage.
1626
+ * Returns the stored actor or null.
1627
+ */
1628
+ function loadSharedActor() {
1629
+ try {
1630
+ var stored = localStorage.getItem(ACTOR_STORAGE_KEY);
1631
+ if (stored) {
1632
+ var actor = JSON.parse(stored);
1633
+ if (actor && !isAnonymousActor(actor)) {
1634
+ return actor;
1635
+ }
1636
+ }
1637
+ } catch (e) { /* localStorage unavailable or parse error */ }
1638
+ return null;
1639
+ }
1640
+
1641
+ /**
1642
+ * Get the best available actor for statement building.
1643
+ * Priority: LRS.actor (if non-anonymous) > localStorage shared actor > LRS.actor > extractActor()
1644
+ */
1645
+ function getActor() {
1646
+ // If current actor is non-anonymous, use it
1647
+ if (LRS.actor && !isAnonymousActor(LRS.actor)) {
1648
+ return LRS.actor;
1649
+ }
1650
+ // Check localStorage for actor set by another frame (e.g., skin overlay)
1651
+ var shared = loadSharedActor();
1652
+ if (shared) {
1653
+ // Update local actor so future calls are fast
1654
+ LRS.actor = shared;
1655
+ log('Actor loaded from cross-frame storage:', shared.name);
1656
+ return shared;
1657
+ }
1658
+ // Fallback to current actor or re-extract
1659
+ return LRS.actor || extractActor();
1660
+ }
1661
+
1662
+ /**
1663
+ * Set actor on the bridge with cross-frame persistence.
1664
+ * Called by skins and external code via window.pa_patcher.lrs.setActor(actor)
1665
+ */
1666
+ LRS.setActor = function(actor) {
1667
+ LRS.actor = actor;
1668
+ persistActor(actor);
1669
+ // Also try to propagate to other frames in the hierarchy
1670
+ try {
1671
+ var w = window;
1672
+ for (var i = 0; i < 10; i++) {
1673
+ try {
1674
+ if (w !== window && w.pa_patcher && w.pa_patcher.lrs) {
1675
+ w.pa_patcher.lrs.actor = actor;
1676
+ }
1677
+ } catch (e) { /* cross-origin */ }
1678
+ if (w === w.parent) break;
1679
+ w = w.parent;
1680
+ }
1681
+ } catch (e) {}
1682
+ if (window.console && window.console.info) {
1683
+ console.info('[PA-LRS] Actor set:', actor.name,
1684
+ actor.mbox ? '(' + actor.mbox + ')' : '',
1685
+ '\u2014 shared across frames');
1686
+ }
1687
+ };
1688
+
1594
1689
  /**
1595
1690
  * Async version that fetches Bravais session if needed
1596
1691
  * Also re-checks all sources in case they became available
@@ -1602,6 +1697,7 @@ function generateLrsBridgeCode(options) {
1602
1697
  // Try to enhance actor with email if missing
1603
1698
  enhanceActorWithEmail(actor, function(enhancedActor) {
1604
1699
  LRS.actor = enhancedActor;
1700
+ persistActor(enhancedActor);
1605
1701
  callback(enhancedActor);
1606
1702
  });
1607
1703
  }
@@ -2608,7 +2704,7 @@ function generateLrsBridgeCode(options) {
2608
2704
 
2609
2705
  var statement = {
2610
2706
  id: generateUUID(),
2611
- actor: LRS.actor || extractActor(),
2707
+ actor: getActor(),
2612
2708
  verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,
2613
2709
  object: courseObj,
2614
2710
  timestamp: new Date().toISOString()
@@ -3030,7 +3126,7 @@ function generateLrsBridgeCode(options) {
3030
3126
  function buildStatementXyleme(verb, activityObject, result, additionalContext) {
3031
3127
  var statement = {
3032
3128
  id: generateUUID(),
3033
- actor: LRS.actor || extractActor(),
3129
+ actor: getActor(),
3034
3130
  verb: typeof verb === 'string' ? (VERBS[verb] || { id: verb, display: { 'en-US': verb } }) : verb,
3035
3131
  object: activityObject,
3036
3132
  timestamp: new Date().toISOString()
@@ -4759,6 +4855,15 @@ function generateLrsBridgeCode(options) {
4759
4855
  // Extract actor (sync first for immediate use)
4760
4856
  extractActor();
4761
4857
 
4858
+ // Check localStorage for actor set by another frame (e.g., skin overlay in a different HTML file)
4859
+ if (isAnonymousActor(LRS.actor)) {
4860
+ var sharedActor = loadSharedActor();
4861
+ if (sharedActor) {
4862
+ LRS.actor = sharedActor;
4863
+ log('Loaded actor from cross-frame storage at init:', sharedActor.name);
4864
+ }
4865
+ }
4866
+
4762
4867
  log('Bridge initialized in mode:', LRS.mode);
4763
4868
  log('Actor:', LRS.actor);
4764
4869
  log('Course:', LRS.courseInfo);