@naylence/runtime 0.3.5-test.941 → 0.3.5-test.943

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.
Files changed (33) hide show
  1. package/dist/browser/index.cjs +390 -8
  2. package/dist/browser/index.mjs +390 -8
  3. package/dist/cjs/naylence/fame/connector/broadcast-channel-connector-factory.js +12 -0
  4. package/dist/cjs/naylence/fame/connector/broadcast-channel-connector.browser.js +69 -1
  5. package/dist/cjs/naylence/fame/connector/inpage-connector-factory.js +12 -0
  6. package/dist/cjs/naylence/fame/connector/inpage-connector.js +159 -1
  7. package/dist/cjs/naylence/fame/connector/transport-frame.js +101 -0
  8. package/dist/cjs/naylence/fame/grants/broadcast-channel-connection-grant.js +28 -0
  9. package/dist/cjs/naylence/fame/grants/inpage-connection-grant.js +28 -0
  10. package/dist/cjs/naylence/fame/node/upstream-session-manager.js +2 -2
  11. package/dist/cjs/version.js +2 -2
  12. package/dist/esm/naylence/fame/connector/broadcast-channel-connector-factory.js +12 -0
  13. package/dist/esm/naylence/fame/connector/broadcast-channel-connector.browser.js +69 -1
  14. package/dist/esm/naylence/fame/connector/inpage-connector-factory.js +12 -0
  15. package/dist/esm/naylence/fame/connector/inpage-connector.js +159 -1
  16. package/dist/esm/naylence/fame/connector/transport-frame.js +94 -0
  17. package/dist/esm/naylence/fame/grants/broadcast-channel-connection-grant.js +28 -0
  18. package/dist/esm/naylence/fame/grants/inpage-connection-grant.js +28 -0
  19. package/dist/esm/naylence/fame/node/upstream-session-manager.js +2 -2
  20. package/dist/esm/version.js +2 -2
  21. package/dist/node/index.cjs +390 -8
  22. package/dist/node/index.mjs +390 -8
  23. package/dist/node/node.cjs +406 -8
  24. package/dist/node/node.mjs +406 -8
  25. package/dist/types/naylence/fame/connector/broadcast-channel-connector-factory.d.ts +2 -0
  26. package/dist/types/naylence/fame/connector/broadcast-channel-connector.browser.d.ts +4 -0
  27. package/dist/types/naylence/fame/connector/inpage-connector-factory.d.ts +2 -0
  28. package/dist/types/naylence/fame/connector/inpage-connector.d.ts +11 -1
  29. package/dist/types/naylence/fame/connector/transport-frame.d.ts +58 -0
  30. package/dist/types/naylence/fame/grants/broadcast-channel-connection-grant.d.ts +6 -0
  31. package/dist/types/naylence/fame/grants/inpage-connection-grant.d.ts +8 -0
  32. package/dist/types/version.d.ts +1 -1
  33. package/package.json +1 -1
@@ -5478,12 +5478,12 @@ for (const [name, config] of Object.entries(SQLITE_PROFILES)) {
5478
5478
  }
5479
5479
 
5480
5480
  // This file is auto-generated during build - do not edit manually
5481
- // Generated from package.json version: 0.3.5-test.941
5481
+ // Generated from package.json version: 0.3.5-test.943
5482
5482
  /**
5483
5483
  * The package version, injected at build time.
5484
5484
  * @internal
5485
5485
  */
5486
- const VERSION = '0.3.5-test.941';
5486
+ const VERSION = '0.3.5-test.943';
5487
5487
 
5488
5488
  /**
5489
5489
  * Fame errors module - Fame protocol specific error classes
@@ -11471,6 +11471,101 @@ class BoundedAsyncQueue {
11471
11471
  }
11472
11472
  }
11473
11473
 
11474
+ /**
11475
+ * Transport frame layer for multiplexing logical links on physical channels.
11476
+ *
11477
+ * This lightweight framing layer wraps raw FAME payloads to enable multiple
11478
+ * logical connections over a single physical channel (BroadcastChannel or InPage bus).
11479
+ *
11480
+ * The transport frame does NOT modify FAME envelopes - it only wraps the raw
11481
+ * Uint8Array payload at the connector level.
11482
+ */
11483
+ /**
11484
+ * Transport frame version for future compatibility
11485
+ */
11486
+ const TRANSPORT_FRAME_VERSION = 1;
11487
+ /**
11488
+ * Wrap a raw payload in a transport frame
11489
+ *
11490
+ * @param payload - Raw FAME envelope bytes
11491
+ * @param srcNodeId - Local node ID (this connector)
11492
+ * @param dstNodeId - Remote node ID (target connector)
11493
+ * @returns Transport frame ready for transmission
11494
+ */
11495
+ function wrapTransportFrame(payload, srcNodeId, dstNodeId) {
11496
+ return {
11497
+ v: TRANSPORT_FRAME_VERSION,
11498
+ src: srcNodeId,
11499
+ dst: dstNodeId,
11500
+ payload,
11501
+ };
11502
+ }
11503
+ /**
11504
+ * Serialize a transport frame for transmission over the bus
11505
+ *
11506
+ * @param frame - Transport frame to serialize
11507
+ * @returns Serialized frame data ready for postMessage/dispatchEvent
11508
+ */
11509
+ function serializeTransportFrame(frame) {
11510
+ // Convert Uint8Array to regular array for JSON serialization
11511
+ const serializable = {
11512
+ v: frame.v,
11513
+ src: frame.src,
11514
+ dst: frame.dst,
11515
+ payload: Array.from(frame.payload),
11516
+ };
11517
+ return serializable;
11518
+ }
11519
+ /**
11520
+ * Unwrap a transport frame, validating source and destination
11521
+ *
11522
+ * @param raw - Raw data from the bus
11523
+ * @param localNodeId - This connector's node ID
11524
+ * @param remoteNodeId - Expected remote node ID
11525
+ * @returns Unwrapped payload if frame is valid and addressed to us, null otherwise
11526
+ */
11527
+ function unwrapTransportFrame(raw, localNodeId, remoteNodeId) {
11528
+ // Validate basic structure
11529
+ if (!raw || typeof raw !== 'object') {
11530
+ return null;
11531
+ }
11532
+ const frame = raw;
11533
+ // Check version
11534
+ if (frame.v !== TRANSPORT_FRAME_VERSION) {
11535
+ return null;
11536
+ }
11537
+ // Check src and dst
11538
+ if (typeof frame.src !== 'string' || typeof frame.dst !== 'string') {
11539
+ return null;
11540
+ }
11541
+ // Only accept frames addressed to us from the expected remote
11542
+ if (frame.dst !== localNodeId || frame.src !== remoteNodeId) {
11543
+ return null;
11544
+ }
11545
+ // Extract payload
11546
+ if (!frame.payload || !Array.isArray(frame.payload)) {
11547
+ return null;
11548
+ }
11549
+ // Convert array back to Uint8Array
11550
+ return Uint8Array.from(frame.payload);
11551
+ }
11552
+ /**
11553
+ * Check if raw data looks like a transport frame
11554
+ *
11555
+ * @param raw - Raw data from the bus
11556
+ * @returns True if this appears to be a transport frame
11557
+ */
11558
+ function isTransportFrame(raw) {
11559
+ if (!raw || typeof raw !== 'object') {
11560
+ return false;
11561
+ }
11562
+ const frame = raw;
11563
+ return (typeof frame.v === 'number' &&
11564
+ typeof frame.src === 'string' &&
11565
+ typeof frame.dst === 'string' &&
11566
+ Array.isArray(frame.payload));
11567
+ }
11568
+
11474
11569
  const logger$10 = getLogger('naylence.fame.connector.broadcast_channel_connector');
11475
11570
  const BROADCAST_CHANNEL_CONNECTOR_TYPE$1 = 'broadcast-channel-connector';
11476
11571
  const DEFAULT_CHANNEL$7 = 'naylence-fabric';
@@ -11536,9 +11631,20 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
11536
11631
  this.inbox = new BoundedAsyncQueue(preferredCapacity);
11537
11632
  this.connectorId = BroadcastChannelConnector.generateConnectorId();
11538
11633
  this.channel = new BroadcastChannel(this.channelName);
11634
+ // Set local and remote node IDs (defaults to connector ID for backwards compatibility)
11635
+ this.localNodeId =
11636
+ typeof config.localNodeId === 'string' && config.localNodeId.trim().length > 0
11637
+ ? config.localNodeId.trim()
11638
+ : this.connectorId;
11639
+ this.remoteNodeId =
11640
+ typeof config.remoteNodeId === 'string' && config.remoteNodeId.trim().length > 0
11641
+ ? config.remoteNodeId.trim()
11642
+ : '*'; // Accept from any remote if not specified
11539
11643
  logger$10.debug('broadcast_channel_connector_created', {
11540
11644
  channel: this.channelName,
11541
11645
  connector_id: this.connectorId,
11646
+ local_node_id: this.localNodeId,
11647
+ remote_node_id: this.remoteNodeId,
11542
11648
  inbox_capacity: preferredCapacity,
11543
11649
  timestamp: new Date().toISOString(),
11544
11650
  });
@@ -11571,6 +11677,46 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
11571
11677
  if (busMessage.senderId === this.connectorId) {
11572
11678
  return;
11573
11679
  }
11680
+ // Try to unwrap as transport frame
11681
+ const unwrapped = unwrapTransportFrame(busMessage.payload, this.localNodeId, this.remoteNodeId === '*' ? busMessage.senderId : this.remoteNodeId);
11682
+ if (unwrapped) {
11683
+ // Successfully unwrapped transport frame
11684
+ logger$10.debug('broadcast_channel_transport_frame_received', {
11685
+ channel: this.channelName,
11686
+ sender_id: busMessage.senderId,
11687
+ connector_id: this.connectorId,
11688
+ local_node_id: this.localNodeId,
11689
+ remote_node_id: this.remoteNodeId,
11690
+ payload_length: unwrapped.byteLength,
11691
+ });
11692
+ if (this._shouldSkipDuplicateAck(busMessage.senderId, unwrapped)) {
11693
+ return;
11694
+ }
11695
+ try {
11696
+ if (typeof this.inbox.tryEnqueue === 'function') {
11697
+ const accepted = this.inbox.tryEnqueue(unwrapped);
11698
+ if (accepted) {
11699
+ return;
11700
+ }
11701
+ }
11702
+ this.inbox.enqueue(unwrapped);
11703
+ }
11704
+ catch (error) {
11705
+ if (error instanceof QueueFullError) {
11706
+ logger$10.warning('broadcast_channel_receive_queue_full', {
11707
+ channel: this.channelName,
11708
+ });
11709
+ }
11710
+ else {
11711
+ logger$10.error('broadcast_channel_receive_error', {
11712
+ channel: this.channelName,
11713
+ error: error instanceof Error ? error.message : String(error),
11714
+ });
11715
+ }
11716
+ }
11717
+ return;
11718
+ }
11719
+ // Fall back to legacy format (no transport frame)
11574
11720
  const payload = BroadcastChannelConnector.coercePayload(busMessage.payload);
11575
11721
  if (!payload) {
11576
11722
  logger$10.debug('broadcast_channel_payload_rejected', {
@@ -11709,10 +11855,26 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
11709
11855
  logger$10.debug('broadcast_channel_message_sending', {
11710
11856
  channel: this.channelName,
11711
11857
  sender_id: this.connectorId,
11712
- });
11858
+ local_node_id: this.localNodeId,
11859
+ remote_node_id: this.remoteNodeId,
11860
+ });
11861
+ // Only use transport framing if both localNodeId and remoteNodeId are explicitly set
11862
+ // (not using default values). This ensures backwards compatibility.
11863
+ const useTransportFrame = this.localNodeId !== this.connectorId ||
11864
+ this.remoteNodeId !== '*';
11865
+ let payload;
11866
+ if (useTransportFrame) {
11867
+ // Wrap payload in transport frame
11868
+ const frame = wrapTransportFrame(data, this.localNodeId, this.remoteNodeId);
11869
+ payload = serializeTransportFrame(frame);
11870
+ }
11871
+ else {
11872
+ // Legacy format: send raw payload
11873
+ payload = data;
11874
+ }
11713
11875
  this.channel.postMessage({
11714
11876
  senderId: this.connectorId,
11715
- payload: data,
11877
+ payload,
11716
11878
  });
11717
11879
  }
11718
11880
  async _transportReceive() {
@@ -11960,6 +12122,14 @@ function isBroadcastChannelConnectionGrant(candidate) {
11960
12122
  record.inboxCapacity <= 0)) {
11961
12123
  return false;
11962
12124
  }
12125
+ if (record.localNodeId !== undefined &&
12126
+ (typeof record.localNodeId !== 'string' || record.localNodeId.length === 0)) {
12127
+ return false;
12128
+ }
12129
+ if (record.remoteNodeId !== undefined &&
12130
+ (typeof record.remoteNodeId !== 'string' || record.remoteNodeId.length === 0)) {
12131
+ return false;
12132
+ }
11963
12133
  return true;
11964
12134
  }
11965
12135
  function normalizeBroadcastChannelConnectionGrant(candidate) {
@@ -11993,6 +12163,20 @@ function normalizeBroadcastChannelConnectionGrant(candidate) {
11993
12163
  }
11994
12164
  result.inboxCapacity = Math.floor(inboxValue);
11995
12165
  }
12166
+ const localNodeIdValue = candidate.localNodeId ?? candidate['local_node_id'];
12167
+ if (localNodeIdValue !== undefined) {
12168
+ if (typeof localNodeIdValue !== 'string' || localNodeIdValue.trim().length === 0) {
12169
+ throw new TypeError('BroadcastChannelConnectionGrant "localNodeId" must be a non-empty string when provided');
12170
+ }
12171
+ result.localNodeId = localNodeIdValue.trim();
12172
+ }
12173
+ const remoteNodeIdValue = candidate.remoteNodeId ?? candidate['remote_node_id'];
12174
+ if (remoteNodeIdValue !== undefined) {
12175
+ if (typeof remoteNodeIdValue !== 'string' || remoteNodeIdValue.trim().length === 0) {
12176
+ throw new TypeError('BroadcastChannelConnectionGrant "remoteNodeId" must be a non-empty string when provided');
12177
+ }
12178
+ result.remoteNodeId = remoteNodeIdValue.trim();
12179
+ }
11996
12180
  return result;
11997
12181
  }
11998
12182
  function broadcastChannelGrantToConnectorConfig(grant) {
@@ -12006,6 +12190,12 @@ function broadcastChannelGrantToConnectorConfig(grant) {
12006
12190
  if (normalized.inboxCapacity !== undefined) {
12007
12191
  config.inboxCapacity = normalized.inboxCapacity;
12008
12192
  }
12193
+ if (normalized.localNodeId) {
12194
+ config.localNodeId = normalized.localNodeId;
12195
+ }
12196
+ if (normalized.remoteNodeId) {
12197
+ config.remoteNodeId = normalized.remoteNodeId;
12198
+ }
12009
12199
  return config;
12010
12200
  }
12011
12201
 
@@ -12385,7 +12575,7 @@ class UpstreamSessionManager extends TaskSpawner {
12385
12575
  this.currentStopSubtasks = null;
12386
12576
  await Promise.allSettled(tasks.map((task) => task.promise));
12387
12577
  if (this.connector) {
12388
- logger$$.info('upstream_stopping_old_connector', {
12578
+ logger$$.debug('upstream_stopping_old_connector', {
12389
12579
  connect_epoch: this.connectEpoch,
12390
12580
  target_system_id: this.targetSystemId,
12391
12581
  timestamp: new Date().toISOString(),
@@ -12396,7 +12586,7 @@ class UpstreamSessionManager extends TaskSpawner {
12396
12586
  error: err instanceof Error ? err.message : String(err),
12397
12587
  });
12398
12588
  });
12399
- logger$$.info('upstream_old_connector_stopped', {
12589
+ logger$$.debug('upstream_old_connector_stopped', {
12400
12590
  connect_epoch: this.connectEpoch,
12401
12591
  target_system_id: this.targetSystemId,
12402
12592
  timestamp: new Date().toISOString(),
@@ -21426,6 +21616,7 @@ class InPageConnector extends BaseAsyncConnector {
21426
21616
  ensureBrowserEnvironment$2();
21427
21617
  super(baseConfig);
21428
21618
  this.listenerRegistered = false;
21619
+ this.visibilityChangeListenerRegistered = false;
21429
21620
  this.channelName =
21430
21621
  typeof config.channelName === 'string' && config.channelName.trim().length > 0
21431
21622
  ? config.channelName.trim()
@@ -21437,9 +21628,20 @@ class InPageConnector extends BaseAsyncConnector {
21437
21628
  : DEFAULT_INBOX_CAPACITY$6;
21438
21629
  this.inbox = new BoundedAsyncQueue(preferredCapacity);
21439
21630
  this.connectorId = InPageConnector.generateConnectorId();
21631
+ // Set local and remote node IDs (defaults to connector ID for backwards compatibility)
21632
+ this.localNodeId =
21633
+ typeof config.localNodeId === 'string' && config.localNodeId.trim().length > 0
21634
+ ? config.localNodeId.trim()
21635
+ : this.connectorId;
21636
+ this.remoteNodeId =
21637
+ typeof config.remoteNodeId === 'string' && config.remoteNodeId.trim().length > 0
21638
+ ? config.remoteNodeId.trim()
21639
+ : '*'; // Accept from any remote if not specified
21440
21640
  logger$J.debug('inpage_connector_initialized', {
21441
21641
  channel: this.channelName,
21442
21642
  connector_id: this.connectorId,
21643
+ local_node_id: this.localNodeId,
21644
+ remote_node_id: this.remoteNodeId,
21443
21645
  });
21444
21646
  this.onMsg = (event) => {
21445
21647
  const messageEvent = event;
@@ -21473,6 +21675,43 @@ class InPageConnector extends BaseAsyncConnector {
21473
21675
  if (busMessage.senderId === this.connectorId) {
21474
21676
  return;
21475
21677
  }
21678
+ // Try to unwrap as transport frame
21679
+ const unwrapped = unwrapTransportFrame(busMessage.payload, this.localNodeId, this.remoteNodeId === '*' ? busMessage.senderId : this.remoteNodeId);
21680
+ if (unwrapped) {
21681
+ // Successfully unwrapped transport frame
21682
+ logger$J.debug('inpage_transport_frame_received', {
21683
+ channel: this.channelName,
21684
+ sender_id: busMessage.senderId,
21685
+ connector_id: this.connectorId,
21686
+ local_node_id: this.localNodeId,
21687
+ remote_node_id: this.remoteNodeId,
21688
+ payload_length: unwrapped.byteLength,
21689
+ });
21690
+ try {
21691
+ if (typeof this.inbox.tryEnqueue === 'function') {
21692
+ const accepted = this.inbox.tryEnqueue(unwrapped);
21693
+ if (accepted) {
21694
+ return;
21695
+ }
21696
+ }
21697
+ this.inbox.enqueue(unwrapped);
21698
+ }
21699
+ catch (error) {
21700
+ if (error instanceof QueueFullError) {
21701
+ logger$J.warning('inpage_receive_queue_full', {
21702
+ channel: this.channelName,
21703
+ });
21704
+ }
21705
+ else {
21706
+ logger$J.error('inpage_receive_error', {
21707
+ channel: this.channelName,
21708
+ error: error instanceof Error ? error.message : String(error),
21709
+ });
21710
+ }
21711
+ }
21712
+ return;
21713
+ }
21714
+ // Fall back to legacy format (no transport frame)
21476
21715
  const payload = InPageConnector.coercePayload(busMessage.payload);
21477
21716
  if (!payload) {
21478
21717
  logger$J.debug('inpage_payload_rejected', {
@@ -21513,6 +21752,92 @@ class InPageConnector extends BaseAsyncConnector {
21513
21752
  };
21514
21753
  getSharedBus$1().addEventListener(this.channelName, this.onMsg);
21515
21754
  this.listenerRegistered = true;
21755
+ // Setup visibility change monitoring
21756
+ this.visibilityChangeHandler = () => {
21757
+ const isHidden = document.hidden;
21758
+ logger$J.debug('inpage_visibility_changed', {
21759
+ channel: this.channelName,
21760
+ connector_id: this.connectorId,
21761
+ visibility: isHidden ? 'hidden' : 'visible',
21762
+ timestamp: new Date().toISOString(),
21763
+ });
21764
+ // Pause/resume connector based on visibility
21765
+ if (isHidden && this.state === core.ConnectorState.STARTED) {
21766
+ this.pause().catch((err) => {
21767
+ logger$J.warning('inpage_pause_failed', {
21768
+ channel: this.channelName,
21769
+ connector_id: this.connectorId,
21770
+ error: err instanceof Error ? err.message : String(err),
21771
+ });
21772
+ });
21773
+ }
21774
+ else if (!isHidden && this.state === core.ConnectorState.PAUSED) {
21775
+ this.resume().catch((err) => {
21776
+ logger$J.warning('inpage_resume_failed', {
21777
+ channel: this.channelName,
21778
+ connector_id: this.connectorId,
21779
+ error: err instanceof Error ? err.message : String(err),
21780
+ });
21781
+ });
21782
+ }
21783
+ };
21784
+ if (typeof document !== 'undefined') {
21785
+ document.addEventListener('visibilitychange', this.visibilityChangeHandler);
21786
+ this.visibilityChangeListenerRegistered = true;
21787
+ // Track page lifecycle events to detect browser unload/discard
21788
+ if (typeof window !== 'undefined') {
21789
+ const lifecycleLogger = (event) => {
21790
+ logger$J.info('inpage_page_lifecycle', {
21791
+ channel: this.channelName,
21792
+ connector_id: this.connectorId,
21793
+ event_type: event.type,
21794
+ visibility_state: document.visibilityState,
21795
+ timestamp: new Date().toISOString(),
21796
+ });
21797
+ };
21798
+ window.addEventListener('beforeunload', lifecycleLogger);
21799
+ window.addEventListener('unload', lifecycleLogger);
21800
+ window.addEventListener('pagehide', lifecycleLogger);
21801
+ window.addEventListener('pageshow', lifecycleLogger);
21802
+ document.addEventListener('freeze', lifecycleLogger);
21803
+ document.addEventListener('resume', lifecycleLogger);
21804
+ }
21805
+ // Log initial state with detailed visibility info
21806
+ logger$J.debug('inpage_initial_visibility', {
21807
+ channel: this.channelName,
21808
+ connector_id: this.connectorId,
21809
+ visibility: document.hidden ? 'hidden' : 'visible',
21810
+ document_hidden: document.hidden,
21811
+ visibility_state: document.visibilityState,
21812
+ has_focus: document.hasFocus(),
21813
+ timestamp: new Date().toISOString(),
21814
+ });
21815
+ }
21816
+ }
21817
+ /**
21818
+ * Override start() to check initial visibility state
21819
+ */
21820
+ async start(inboundHandler) {
21821
+ await super.start(inboundHandler);
21822
+ // After transitioning to STARTED, check if tab is already hidden
21823
+ if (typeof document !== 'undefined' && document.hidden) {
21824
+ logger$J.debug('inpage_start_in_hidden_tab', {
21825
+ channel: this.channelName,
21826
+ connector_id: this.connectorId,
21827
+ document_hidden: document.hidden,
21828
+ visibility_state: document.visibilityState,
21829
+ has_focus: document.hasFocus(),
21830
+ timestamp: new Date().toISOString(),
21831
+ });
21832
+ // Immediately pause if tab is hidden at start time
21833
+ await this.pause().catch((err) => {
21834
+ logger$J.warning('inpage_initial_pause_failed', {
21835
+ channel: this.channelName,
21836
+ connector_id: this.connectorId,
21837
+ error: err instanceof Error ? err.message : String(err),
21838
+ });
21839
+ });
21840
+ }
21516
21841
  }
21517
21842
  // Allow listeners to feed envelopes directly into the in-page receive queue.
21518
21843
  async pushToReceive(rawOrEnvelope) {
@@ -21545,11 +21870,27 @@ class InPageConnector extends BaseAsyncConnector {
21545
21870
  logger$J.debug('inpage_message_sending', {
21546
21871
  channel: this.channelName,
21547
21872
  sender_id: this.connectorId,
21548
- });
21873
+ local_node_id: this.localNodeId,
21874
+ remote_node_id: this.remoteNodeId,
21875
+ });
21876
+ // Only use transport framing if both localNodeId and remoteNodeId are explicitly set
21877
+ // (not using default values). This ensures backwards compatibility.
21878
+ const useTransportFrame = this.localNodeId !== this.connectorId ||
21879
+ this.remoteNodeId !== '*';
21880
+ let payload;
21881
+ if (useTransportFrame) {
21882
+ // Wrap payload in transport frame
21883
+ const frame = wrapTransportFrame(data, this.localNodeId, this.remoteNodeId);
21884
+ payload = serializeTransportFrame(frame);
21885
+ }
21886
+ else {
21887
+ // Legacy format: send raw payload
21888
+ payload = data;
21889
+ }
21549
21890
  const event = new MessageEvent(this.channelName, {
21550
21891
  data: {
21551
21892
  senderId: this.connectorId,
21552
- payload: data,
21893
+ payload,
21553
21894
  },
21554
21895
  });
21555
21896
  getSharedBus$1().dispatchEvent(event);
@@ -21562,6 +21903,11 @@ class InPageConnector extends BaseAsyncConnector {
21562
21903
  getSharedBus$1().removeEventListener(this.channelName, this.onMsg);
21563
21904
  this.listenerRegistered = false;
21564
21905
  }
21906
+ if (this.visibilityChangeListenerRegistered && this.visibilityChangeHandler && typeof document !== 'undefined') {
21907
+ document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
21908
+ this.visibilityChangeListenerRegistered = false;
21909
+ this.visibilityChangeHandler = undefined;
21910
+ }
21565
21911
  const closeCode = typeof code === 'number' ? code : 1000;
21566
21912
  const closeReason = typeof reason === 'string' && reason.length > 0 ? reason : 'closed';
21567
21913
  const shutdownError = new FameTransportClose(closeReason, closeCode);
@@ -28856,6 +29202,14 @@ function isInPageConnectionGrant(candidate) {
28856
29202
  record.inboxCapacity <= 0)) {
28857
29203
  return false;
28858
29204
  }
29205
+ if (record.localNodeId !== undefined &&
29206
+ (typeof record.localNodeId !== 'string' || record.localNodeId.length === 0)) {
29207
+ return false;
29208
+ }
29209
+ if (record.remoteNodeId !== undefined &&
29210
+ (typeof record.remoteNodeId !== 'string' || record.remoteNodeId.length === 0)) {
29211
+ return false;
29212
+ }
28859
29213
  return true;
28860
29214
  }
28861
29215
  function normalizeInPageConnectionGrant(candidate) {
@@ -28889,6 +29243,20 @@ function normalizeInPageConnectionGrant(candidate) {
28889
29243
  }
28890
29244
  result.inboxCapacity = Math.floor(inboxValue);
28891
29245
  }
29246
+ const localNodeIdValue = candidate.localNodeId ?? candidate['local_node_id'];
29247
+ if (localNodeIdValue !== undefined) {
29248
+ if (typeof localNodeIdValue !== 'string' || localNodeIdValue.trim().length === 0) {
29249
+ throw new TypeError('InPageConnectionGrant "localNodeId" must be a non-empty string when provided');
29250
+ }
29251
+ result.localNodeId = localNodeIdValue.trim();
29252
+ }
29253
+ const remoteNodeIdValue = candidate.remoteNodeId ?? candidate['remote_node_id'];
29254
+ if (remoteNodeIdValue !== undefined) {
29255
+ if (typeof remoteNodeIdValue !== 'string' || remoteNodeIdValue.trim().length === 0) {
29256
+ throw new TypeError('InPageConnectionGrant "remoteNodeId" must be a non-empty string when provided');
29257
+ }
29258
+ result.remoteNodeId = remoteNodeIdValue.trim();
29259
+ }
28892
29260
  return result;
28893
29261
  }
28894
29262
  function inPageGrantToConnectorConfig(grant) {
@@ -28902,6 +29270,12 @@ function inPageGrantToConnectorConfig(grant) {
28902
29270
  if (normalized.inboxCapacity !== undefined) {
28903
29271
  config.inboxCapacity = normalized.inboxCapacity;
28904
29272
  }
29273
+ if (normalized.localNodeId) {
29274
+ config.localNodeId = normalized.localNodeId;
29275
+ }
29276
+ if (normalized.remoteNodeId) {
29277
+ config.remoteNodeId = normalized.remoteNodeId;
29278
+ }
28905
29279
  return config;
28906
29280
  }
28907
29281
 
@@ -30169,6 +30543,8 @@ class InPageConnectorFactory extends ConnectorFactory {
30169
30543
  type: INPAGE_CONNECTOR_TYPE,
30170
30544
  channelName,
30171
30545
  inboxCapacity,
30546
+ localNodeId: normalized.localNodeId,
30547
+ remoteNodeId: normalized.remoteNodeId,
30172
30548
  };
30173
30549
  const connector = new InPageConnector(connectorConfig, baseConfig);
30174
30550
  if (options.authorization) {
@@ -30237,6 +30613,16 @@ class InPageConnectorFactory extends ConnectorFactory {
30237
30613
  if (candidate.authorizationContext !== undefined) {
30238
30614
  normalized.authorizationContext = candidate.authorizationContext;
30239
30615
  }
30616
+ // Handle localNodeId
30617
+ const localNodeId = candidate.localNodeId ?? candidate['local_node_id'];
30618
+ if (typeof localNodeId === 'string' && localNodeId.trim().length > 0) {
30619
+ normalized.localNodeId = localNodeId.trim();
30620
+ }
30621
+ // Handle remoteNodeId
30622
+ const remoteNodeId = candidate.remoteNodeId ?? candidate['remote_node_id'];
30623
+ if (typeof remoteNodeId === 'string' && remoteNodeId.trim().length > 0) {
30624
+ normalized.remoteNodeId = remoteNodeId.trim();
30625
+ }
30240
30626
  normalized.channelName = normalized.channelName ?? DEFAULT_CHANNEL$3;
30241
30627
  normalized.inboxCapacity =
30242
30628
  normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY$3;
@@ -30336,6 +30722,8 @@ class BroadcastChannelConnectorFactory extends ConnectorFactory {
30336
30722
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE$1,
30337
30723
  channelName,
30338
30724
  inboxCapacity,
30725
+ localNodeId: normalized.localNodeId,
30726
+ remoteNodeId: normalized.remoteNodeId,
30339
30727
  };
30340
30728
  const connector = new BroadcastChannelConnector(connectorConfig, baseConfig);
30341
30729
  if (options.authorization) {
@@ -30397,6 +30785,16 @@ class BroadcastChannelConnectorFactory extends ConnectorFactory {
30397
30785
  if (candidate.authorizationContext !== undefined) {
30398
30786
  normalized.authorizationContext = candidate.authorizationContext;
30399
30787
  }
30788
+ // Handle localNodeId
30789
+ const localNodeId = candidate.localNodeId ?? candidate['local_node_id'];
30790
+ if (typeof localNodeId === 'string' && localNodeId.trim().length > 0) {
30791
+ normalized.localNodeId = localNodeId.trim();
30792
+ }
30793
+ // Handle remoteNodeId
30794
+ const remoteNodeId = candidate.remoteNodeId ?? candidate['remote_node_id'];
30795
+ if (typeof remoteNodeId === 'string' && remoteNodeId.trim().length > 0) {
30796
+ normalized.remoteNodeId = remoteNodeId.trim();
30797
+ }
30400
30798
  normalized.channelName = normalized.channelName ?? DEFAULT_CHANNEL$2;
30401
30799
  normalized.inboxCapacity =
30402
30800
  normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY$2;