@naylence/agent-sdk 0.3.4-test.730 → 0.3.5

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.
@@ -15674,12 +15674,12 @@
15674
15674
  // --- END ENV SHIM ---
15675
15675
 
15676
15676
  // This file is auto-generated during build - do not edit manually
15677
- // Generated from package.json version: 0.3.5-test.941
15677
+ // Generated from package.json version: 0.3.6
15678
15678
  /**
15679
15679
  * The package version, injected at build time.
15680
15680
  * @internal
15681
15681
  */
15682
- const VERSION$1 = '0.3.5-test.941';
15682
+ const VERSION$1 = '0.3.6';
15683
15683
 
15684
15684
  /**
15685
15685
  * Fame protocol specific error classes with WebSocket close codes and proper inheritance.
@@ -16383,7 +16383,7 @@
16383
16383
  * Provides functionality similar to Python's asyncio TaskSpawner with proper
16384
16384
  * error handling, cancellation, and graceful shutdown capabilities.
16385
16385
  */
16386
- const logger$1b = getLogger('naylence.fame.util.task_spawner');
16386
+ const logger$1c = getLogger('naylence.fame.util.task_spawner');
16387
16387
  function firstDefined(source, keys) {
16388
16388
  for (const key of keys) {
16389
16389
  if (Object.prototype.hasOwnProperty.call(source, key)) {
@@ -16544,7 +16544,7 @@
16544
16544
  const taskId = `task-${++this._taskCounter}`;
16545
16545
  const taskName = normalizedOptions.name || `unnamed-${taskId}`;
16546
16546
  const timeout = normalizedOptions.timeout ?? this._config.defaultTimeout;
16547
- logger$1b.debug('starting_background_task', {
16547
+ logger$1c.debug('starting_background_task', {
16548
16548
  task_name: taskName,
16549
16549
  task_id: taskId,
16550
16550
  });
@@ -16561,7 +16561,7 @@
16561
16561
  task.promise
16562
16562
  .then(() => {
16563
16563
  if (!this._suppressCompletionLogging) {
16564
- logger$1b.debug('task_completed_successfully', {
16564
+ logger$1c.debug('task_completed_successfully', {
16565
16565
  task_name: taskName,
16566
16566
  task_id: taskId,
16567
16567
  duration_ms: Date.now() - task.startTime,
@@ -16615,7 +16615,7 @@
16615
16615
  error.name === 'AbortError' ||
16616
16616
  error.message === 'Task cancelled' ||
16617
16617
  error.message === 'Aborted') {
16618
- logger$1b.debug('task_cancelled', {
16618
+ logger$1c.debug('task_cancelled', {
16619
16619
  task_name: taskName,
16620
16620
  note: 'Task cancelled as requested',
16621
16621
  });
@@ -16623,7 +16623,7 @@
16623
16623
  }
16624
16624
  // Handle timeout
16625
16625
  if (error instanceof TaskTimeoutError) {
16626
- logger$1b.warning('task_timed_out', {
16626
+ logger$1c.warning('task_timed_out', {
16627
16627
  task_name: taskName,
16628
16628
  error: error.message,
16629
16629
  });
@@ -16635,7 +16635,7 @@
16635
16635
  // Handle known WebSocket shutdown race condition (similar to Python version)
16636
16636
  if (error.message.includes("await wasn't used with future") ||
16637
16637
  error.message.includes('WebSocket closed during receive')) {
16638
- logger$1b.debug('task_shutdown_race_condition_handled', {
16638
+ logger$1c.debug('task_shutdown_race_condition_handled', {
16639
16639
  task_name: taskName,
16640
16640
  note: 'Normal WebSocket close timing during shutdown - not an error',
16641
16641
  });
@@ -16645,7 +16645,7 @@
16645
16645
  if (error.name === 'FameTransportClose' ||
16646
16646
  error.message.includes('normal closure') ||
16647
16647
  error.message.includes('Connection closed')) {
16648
- logger$1b.debug('task_shutdown_completed_normally', {
16648
+ logger$1c.debug('task_shutdown_completed_normally', {
16649
16649
  task_name: taskName,
16650
16650
  note: 'Task closed normally during shutdown',
16651
16651
  });
@@ -16658,14 +16658,14 @@
16658
16658
  // Log retriable errors as warnings (they'll be retried by upstream logic)
16659
16659
  // Log non-retriable errors as errors (fatal failures)
16660
16660
  if (isRetriableError) {
16661
- logger$1b.warning('background_task_failed', {
16661
+ logger$1c.warning('background_task_failed', {
16662
16662
  task_name: taskName,
16663
16663
  error: error.message,
16664
16664
  retriable: true,
16665
16665
  });
16666
16666
  }
16667
16667
  else {
16668
- logger$1b.error('background_task_failed', {
16668
+ logger$1c.error('background_task_failed', {
16669
16669
  task_name: taskName,
16670
16670
  error: error.message,
16671
16671
  stack: error.stack,
@@ -16684,11 +16684,11 @@
16684
16684
  async shutdownTasks(options = {}) {
16685
16685
  const { gracePeriod, cancelHanging, joinTimeout } = normalizeShutdownOptions(options);
16686
16686
  if (this._tasks.size === 0) {
16687
- logger$1b.debug('shutdown_tasks_no_tasks_to_shutdown');
16687
+ logger$1c.debug('shutdown_tasks_no_tasks_to_shutdown');
16688
16688
  return;
16689
16689
  }
16690
16690
  this._suppressCompletionLogging = true;
16691
- logger$1b.debug('shutting_down_tasks', {
16691
+ logger$1c.debug('shutting_down_tasks', {
16692
16692
  task_count: this._tasks.size,
16693
16693
  task_names: Array.from(this._tasks.values()).map((t) => t.name),
16694
16694
  grace_period_ms: gracePeriod,
@@ -16703,7 +16703,7 @@
16703
16703
  if (cancelHanging) {
16704
16704
  const stillRunning = tasks.filter((task) => task.getState() === TaskState.RUNNING && !completed.has(task));
16705
16705
  if (stillRunning.length > 0) {
16706
- logger$1b.debug('tasks_did_not_complete_within_grace_period', {
16706
+ logger$1c.debug('tasks_did_not_complete_within_grace_period', {
16707
16707
  hanging_count: stillRunning.length,
16708
16708
  });
16709
16709
  // Wait for them to finish with individual timeouts
@@ -16713,7 +16713,7 @@
16713
16713
  }
16714
16714
  catch (error) {
16715
16715
  if (error instanceof TaskTimeoutError) {
16716
- logger$1b.warning('task_did_not_shutdown', {
16716
+ logger$1c.warning('task_did_not_shutdown', {
16717
16717
  task_name: task.name || task.id,
16718
16718
  join_timeout_ms: joinTimeout,
16719
16719
  });
@@ -16724,7 +16724,7 @@
16724
16724
  }
16725
16725
  else if (!(error instanceof TaskCancelledError)) {
16726
16726
  /* istanbul ignore next - unreachable defensive branch */
16727
- logger$1b.error('task_raised_during_cancellation', {
16727
+ logger$1c.error('task_raised_during_cancellation', {
16728
16728
  task_name: task.name || task.id,
16729
16729
  error: error instanceof Error ? error.message : String(error),
16730
16730
  });
@@ -17298,6 +17298,7 @@
17298
17298
  * condition/promise and ensure at most one notifier coroutine exists for a
17299
17299
  * flow at any time.
17300
17300
  */
17301
+ const logger$1b = getLogger('naylence.fame.flow.flow_controller');
17301
17302
  /**
17302
17303
  * Simple condition variable implementation for TypeScript/Node.js
17303
17304
  * Similar to Python's asyncio.Condition
@@ -17389,8 +17390,15 @@
17389
17390
  // Create a notifier promise
17390
17391
  const notifierPromise = (async () => {
17391
17392
  try {
17392
- // Use setImmediate to defer to next tick (similar to asyncio scheduling)
17393
- await new Promise((resolve) => setImmediate(resolve));
17393
+ // Use setImmediate/setTimeout to defer to next tick (similar to asyncio scheduling)
17394
+ await new Promise((resolve) => {
17395
+ if (typeof setImmediate === 'function') {
17396
+ setImmediate(resolve);
17397
+ }
17398
+ else {
17399
+ setTimeout(resolve, 0);
17400
+ }
17401
+ });
17394
17402
  condition.notifyAll();
17395
17403
  }
17396
17404
  finally {
@@ -17423,8 +17431,17 @@
17423
17431
  // clamp into [0, initialWindow]
17424
17432
  const newBalance = Math.max(0, Math.min(this.initialWindow, prev + delta));
17425
17433
  this.credits.set(flowId, newBalance);
17434
+ const crossedZero = prev <= 0 && newBalance > 0;
17435
+ logger$1b.debug('flow_controller_add_credits', {
17436
+ flow_id: flowId,
17437
+ delta,
17438
+ prev_balance: prev,
17439
+ new_balance: newBalance,
17440
+ initial_window: this.initialWindow,
17441
+ crossed_zero: crossedZero,
17442
+ });
17426
17443
  // wake waiters only if we crossed the zero boundary
17427
- if (prev <= 0 && newBalance > 0) {
17444
+ if (crossedZero) {
17428
17445
  this.wakeWaiters(flowId);
17429
17446
  }
17430
17447
  return newBalance;
@@ -17435,11 +17452,29 @@
17435
17452
  async acquire(flowId) {
17436
17453
  this.ensureFlow(flowId);
17437
17454
  const condition = this.conditions.get(flowId);
17455
+ logger$1b.debug('flow_controller_acquire_attempt', {
17456
+ flow_id: flowId,
17457
+ current_balance: this.credits.get(flowId),
17458
+ });
17438
17459
  while (this.credits.get(flowId) <= 0) {
17460
+ logger$1b.debug('flow_controller_waiting_for_credits', {
17461
+ flow_id: flowId,
17462
+ current_balance: this.credits.get(flowId),
17463
+ });
17439
17464
  await condition.wait();
17440
17465
  }
17441
- const current = this.credits.get(flowId);
17442
- this.credits.set(flowId, current - 1);
17466
+ const newBalance = this.credits.get(flowId) - 1;
17467
+ this.credits.set(flowId, newBalance);
17468
+ logger$1b.debug('flow_controller_acquire_success', {
17469
+ flow_id: flowId,
17470
+ new_balance: newBalance,
17471
+ });
17472
+ if (newBalance <= this.lowWatermark) {
17473
+ logger$1b.debug('flow_controller_acquire_below_low_watermark', {
17474
+ flow_id: flowId,
17475
+ low_watermark: this.lowWatermark,
17476
+ });
17477
+ }
17443
17478
  }
17444
17479
  /**
17445
17480
  * Consume *credits* immediately (non-blocking).
@@ -17459,6 +17494,12 @@
17459
17494
  const current = this.credits.get(flowId);
17460
17495
  const remaining = Math.max(current - credits, 0);
17461
17496
  this.credits.set(flowId, remaining);
17497
+ logger$1b.debug('flow_controller_consume', {
17498
+ flow_id: flowId,
17499
+ requested: credits,
17500
+ prev_balance: current,
17501
+ remaining_balance: remaining,
17502
+ });
17462
17503
  return remaining;
17463
17504
  }
17464
17505
  /**
@@ -17479,6 +17520,10 @@
17479
17520
  this.windowIds.delete(flowId);
17480
17521
  this.credits.set(flowId, this.initialWindow);
17481
17522
  this.wakeWaiters(flowId);
17523
+ logger$1b.debug('flow_controller_flow_reset', {
17524
+ flow_id: flowId,
17525
+ reset_balance: this.initialWindow,
17526
+ });
17482
17527
  }
17483
17528
  /**
17484
17529
  * Return `[windowId, flags]` for the next outbound envelope.
@@ -24039,10 +24084,12 @@
24039
24084
  throw new FameTransportClose('Connection closed', 1006);
24040
24085
  }
24041
24086
  // Apply flow control if enabled and not a credit update
24042
- if (this._fcEnabled &&
24043
- !(envelope.frame &&
24044
- 'flow_id' in envelope.frame &&
24045
- 'credits' in envelope.frame)) {
24087
+ const isCreditUpdateFrame = Boolean(envelope.frame &&
24088
+ (envelope.frame.type === 'CreditUpdate' ||
24089
+ envelope.frame.type === 'credit_update' ||
24090
+ ('flowId' in envelope.frame && 'credits' in envelope.frame) ||
24091
+ ('flow_id' in envelope.frame && 'credits' in envelope.frame)));
24092
+ if (this._fcEnabled && !isCreditUpdateFrame) {
24046
24093
  const flowId = envelope.flowId || this._connectorFlowId;
24047
24094
  envelope.flowId = flowId;
24048
24095
  const t0 = this._metrics ? performance.now() : 0;
@@ -24285,11 +24332,31 @@
24285
24332
  * Emit credit update if flow control needs refill
24286
24333
  */
24287
24334
  async _maybeEmitCredit(flowId, traceId) {
24288
- if (!this._flowCtrl.needsRefill(flowId)) {
24335
+ const remainingCredits = this._flowCtrl.getCredits(flowId);
24336
+ const needsRefill = this._flowCtrl.needsRefill(flowId);
24337
+ logger$$.debug('maybe_emit_credit_check', {
24338
+ connector_id: this._connectorFlowId,
24339
+ flow_id: flowId,
24340
+ trace_id: traceId ?? null,
24341
+ remaining_credits: remainingCredits,
24342
+ low_watermark: this._flowCtrl instanceof FlowController
24343
+ ? this._flowCtrl.lowWatermark
24344
+ : null,
24345
+ initial_window: this._initialWindow,
24346
+ needs_refill: needsRefill,
24347
+ });
24348
+ if (!needsRefill) {
24289
24349
  return;
24290
24350
  }
24291
24351
  const delta = this._initialWindow;
24292
24352
  this._flowCtrl.addCredits(flowId, delta);
24353
+ logger$$.debug('maybe_emit_credit_emit', {
24354
+ connector_id: this._connectorFlowId,
24355
+ flow_id: flowId,
24356
+ trace_id: traceId ?? null,
24357
+ emitted_credits: delta,
24358
+ post_emit_balance: this._flowCtrl.getCredits(flowId),
24359
+ });
24293
24360
  const ackEnv = createFameEnvelope({
24294
24361
  ...(traceId && { traceId }),
24295
24362
  flowId,
@@ -24301,7 +24368,25 @@
24301
24368
  },
24302
24369
  flags: FlowFlags.ACK,
24303
24370
  });
24304
- await this.send(ackEnv);
24371
+ try {
24372
+ await this.send(ackEnv);
24373
+ logger$$.debug('maybe_emit_credit_sent', {
24374
+ connector_id: this._connectorFlowId,
24375
+ flow_id: flowId,
24376
+ trace_id: traceId ?? null,
24377
+ credits_acknowledged: delta,
24378
+ });
24379
+ }
24380
+ catch (error) {
24381
+ logger$$.error('maybe_emit_credit_send_failed', {
24382
+ connector_id: this._connectorFlowId,
24383
+ flow_id: flowId,
24384
+ trace_id: traceId ?? null,
24385
+ credits_attempted: delta,
24386
+ error: error instanceof Error ? error.message : String(error),
24387
+ });
24388
+ throw error;
24389
+ }
24305
24390
  }
24306
24391
  // ---------------------------------------------------------------------
24307
24392
  // Shutdown Management
@@ -24517,15 +24602,30 @@
24517
24602
  }
24518
24603
  return null;
24519
24604
  }
24605
+ static normalizeNodeId(value) {
24606
+ if (typeof value !== 'string') {
24607
+ return null;
24608
+ }
24609
+ const trimmed = value.trim();
24610
+ return trimmed.length > 0 ? trimmed : null;
24611
+ }
24612
+ static normalizeTargetNodeId(value) {
24613
+ if (typeof value !== 'string') {
24614
+ return undefined;
24615
+ }
24616
+ const trimmed = value.trim();
24617
+ if (trimmed.length === 0) {
24618
+ return undefined;
24619
+ }
24620
+ if (trimmed === '*') {
24621
+ return '*';
24622
+ }
24623
+ return trimmed;
24624
+ }
24520
24625
  constructor(config, baseConfig = {}) {
24521
24626
  ensureBroadcastEnvironment();
24522
24627
  super(baseConfig);
24523
24628
  this.listenerRegistered = false;
24524
- this.seenAckKeys = new Map();
24525
- this.seenAckOrder = [];
24526
- this.ackDedupTtlMs = 30000;
24527
- this.ackDedupMaxEntries = 4096;
24528
- this.textDecoder = new TextDecoder();
24529
24629
  this.visibilityChangeListenerRegistered = false;
24530
24630
  this.channelName =
24531
24631
  typeof config.channelName === 'string' && config.channelName.trim().length > 0
@@ -24537,11 +24637,20 @@
24537
24637
  ? Math.floor(config.inboxCapacity)
24538
24638
  : DEFAULT_INBOX_CAPACITY$7;
24539
24639
  this.inbox = new BoundedAsyncQueue(preferredCapacity);
24640
+ this.inboxCapacity = preferredCapacity;
24540
24641
  this.connectorId = BroadcastChannelConnector.generateConnectorId();
24642
+ const normalizedLocalNodeId = BroadcastChannelConnector.normalizeNodeId(config.localNodeId);
24643
+ if (!normalizedLocalNodeId) {
24644
+ throw new Error('BroadcastChannelConnector requires a non-empty localNodeId');
24645
+ }
24646
+ this.localNodeId = normalizedLocalNodeId;
24647
+ this.targetNodeId = BroadcastChannelConnector.normalizeTargetNodeId(config.initialTargetNodeId);
24541
24648
  this.channel = new BroadcastChannel(this.channelName);
24542
24649
  logger$_.debug('broadcast_channel_connector_created', {
24543
24650
  channel: this.channelName,
24544
24651
  connector_id: this.connectorId,
24652
+ local_node_id: this.localNodeId,
24653
+ target_node_id: this.targetNodeId ?? null,
24545
24654
  inbox_capacity: preferredCapacity,
24546
24655
  timestamp: new Date().toISOString(),
24547
24656
  });
@@ -24563,15 +24672,32 @@
24563
24672
  ? message.constructor?.name ?? typeof message
24564
24673
  : typeof message,
24565
24674
  has_sender_id: Boolean(message?.senderId),
24675
+ has_sender_node_id: Boolean(message?.senderNodeId),
24566
24676
  });
24567
24677
  if (!message || typeof message !== 'object') {
24568
24678
  return;
24569
24679
  }
24570
24680
  const busMessage = message;
24571
- if (typeof busMessage.senderId !== 'string' || busMessage.senderId.length === 0) {
24681
+ const senderNodeId = BroadcastChannelConnector.normalizeNodeId(busMessage.senderNodeId);
24682
+ if (!senderNodeId) {
24683
+ logger$_.debug('broadcast_channel_message_rejected', {
24684
+ channel: this.channelName,
24685
+ connector_id: this.connectorId,
24686
+ reason: 'missing_sender_node_id',
24687
+ });
24688
+ return;
24689
+ }
24690
+ if (senderNodeId === this.localNodeId) {
24691
+ logger$_.debug('broadcast_channel_message_rejected', {
24692
+ channel: this.channelName,
24693
+ connector_id: this.connectorId,
24694
+ reason: 'self_echo',
24695
+ sender_node_id: senderNodeId,
24696
+ });
24572
24697
  return;
24573
24698
  }
24574
- if (busMessage.senderId === this.connectorId) {
24699
+ const incomingTargetNodeId = BroadcastChannelConnector.normalizeTargetNodeId(busMessage.targetNodeId);
24700
+ if (!this._shouldAcceptMessageFromBus(senderNodeId, incomingTargetNodeId)) {
24575
24701
  return;
24576
24702
  }
24577
24703
  const payload = BroadcastChannelConnector.coercePayload(busMessage.payload);
@@ -24585,26 +24711,37 @@
24585
24711
  }
24586
24712
  logger$_.debug('broadcast_channel_message_received', {
24587
24713
  channel: this.channelName,
24588
- sender_id: busMessage.senderId,
24714
+ sender_id: message?.senderId,
24715
+ sender_node_id: senderNodeId,
24716
+ target_node_id: incomingTargetNodeId ?? null,
24589
24717
  connector_id: this.connectorId,
24590
24718
  payload_length: payload.byteLength,
24591
24719
  });
24592
- if (this._shouldSkipDuplicateAck(busMessage.senderId, payload)) {
24593
- return;
24594
- }
24595
24720
  try {
24596
24721
  if (typeof this.inbox.tryEnqueue === 'function') {
24597
24722
  const accepted = this.inbox.tryEnqueue(payload);
24598
24723
  if (accepted) {
24724
+ this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
24725
+ source: 'listener',
24726
+ enqueue_strategy: 'try',
24727
+ payload_length: payload.byteLength,
24728
+ });
24599
24729
  return;
24600
24730
  }
24601
24731
  }
24602
24732
  this.inbox.enqueue(payload);
24733
+ this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
24734
+ source: 'listener',
24735
+ enqueue_strategy: 'enqueue',
24736
+ payload_length: payload.byteLength,
24737
+ });
24603
24738
  }
24604
24739
  catch (error) {
24605
24740
  if (error instanceof QueueFullError) {
24606
24741
  logger$_.warning('broadcast_channel_receive_queue_full', {
24607
24742
  channel: this.channelName,
24743
+ inbox_capacity: this.inboxCapacity,
24744
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
24608
24745
  });
24609
24746
  }
24610
24747
  else {
@@ -24615,8 +24752,10 @@
24615
24752
  }
24616
24753
  }
24617
24754
  };
24618
- this.channel.addEventListener('message', this.onMsg);
24619
- this.listenerRegistered = true;
24755
+ if (!config.passive) {
24756
+ this.channel.addEventListener('message', this.onMsg);
24757
+ this.listenerRegistered = true;
24758
+ }
24620
24759
  // Setup visibility change monitoring
24621
24760
  this.visibilityChangeHandler = () => {
24622
24761
  const isHidden = document.hidden;
@@ -24681,22 +24820,29 @@
24681
24820
  }
24682
24821
  async pushToReceive(rawOrEnvelope) {
24683
24822
  const item = this._normalizeInboxItem(rawOrEnvelope);
24684
- if (this._shouldSkipDuplicateAckFromInboxItem(item)) {
24685
- return;
24686
- }
24687
24823
  try {
24688
24824
  if (typeof this.inbox.tryEnqueue === 'function') {
24689
24825
  const accepted = this.inbox.tryEnqueue(item);
24690
24826
  if (accepted) {
24827
+ this.logInboxSnapshot('broadcast_channel_push_enqueued', {
24828
+ enqueue_strategy: 'try',
24829
+ item_type: this._describeInboxItem(item),
24830
+ });
24691
24831
  return;
24692
24832
  }
24693
24833
  }
24694
24834
  this.inbox.enqueue(item);
24835
+ this.logInboxSnapshot('broadcast_channel_push_enqueued', {
24836
+ enqueue_strategy: 'enqueue',
24837
+ item_type: this._describeInboxItem(item),
24838
+ });
24695
24839
  }
24696
24840
  catch (error) {
24697
24841
  if (error instanceof QueueFullError) {
24698
24842
  logger$_.warning('broadcast_channel_push_queue_full', {
24699
24843
  channel: this.channelName,
24844
+ inbox_capacity: this.inboxCapacity,
24845
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
24700
24846
  });
24701
24847
  throw error;
24702
24848
  }
@@ -24709,17 +24855,26 @@
24709
24855
  }
24710
24856
  async _transportSendBytes(data) {
24711
24857
  ensureBroadcastEnvironment();
24858
+ const targetNodeId = this.targetNodeId ?? '*';
24712
24859
  logger$_.debug('broadcast_channel_message_sending', {
24713
24860
  channel: this.channelName,
24714
24861
  sender_id: this.connectorId,
24862
+ sender_node_id: this.localNodeId,
24863
+ target_node_id: targetNodeId,
24715
24864
  });
24716
24865
  this.channel.postMessage({
24717
24866
  senderId: this.connectorId,
24867
+ senderNodeId: this.localNodeId,
24868
+ targetNodeId,
24718
24869
  payload: data,
24719
24870
  });
24720
24871
  }
24721
24872
  async _transportReceive() {
24722
- return await this.inbox.dequeue();
24873
+ const item = await this.inbox.dequeue();
24874
+ this.logInboxSnapshot('broadcast_channel_inbox_dequeued', {
24875
+ item_type: this._describeInboxItem(item),
24876
+ });
24877
+ return item;
24723
24878
  }
24724
24879
  async _transportClose(code, reason) {
24725
24880
  logger$_.debug('broadcast_channel_transport_closing', {
@@ -24764,8 +24919,6 @@
24764
24919
  const closeReason = typeof reason === 'string' && reason.length > 0 ? reason : 'closed';
24765
24920
  const shutdownError = new FameTransportClose(closeReason, closeCode);
24766
24921
  this.inbox.drain(shutdownError);
24767
- this.seenAckKeys.clear();
24768
- this.seenAckOrder.length = 0;
24769
24922
  }
24770
24923
  _normalizeInboxItem(rawOrEnvelope) {
24771
24924
  if (rawOrEnvelope instanceof Uint8Array) {
@@ -24773,78 +24926,74 @@
24773
24926
  }
24774
24927
  return rawOrEnvelope;
24775
24928
  }
24776
- _shouldSkipDuplicateAck(senderId, payload) {
24777
- const dedupKey = this._extractAckDedupKey(payload);
24778
- if (!dedupKey) {
24779
- return false;
24780
- }
24781
- const normalizedSenderId = typeof senderId === 'string' && senderId.length > 0
24782
- ? senderId
24783
- : undefined;
24784
- return this._checkDuplicateAck(dedupKey, normalizedSenderId);
24929
+ _isWildcardTarget() {
24930
+ return this.targetNodeId === '*' || typeof this.targetNodeId === 'undefined';
24785
24931
  }
24786
- _shouldSkipDuplicateAckFromInboxItem(item) {
24787
- if (item instanceof Uint8Array) {
24788
- return this._shouldSkipDuplicateAck(undefined, item);
24789
- }
24790
- const envelope = this._extractEnvelopeFromInboxItem(item);
24791
- if (!envelope) {
24792
- return false;
24793
- }
24794
- const frame = envelope.frame;
24795
- if (!frame || frame.type !== 'DeliveryAck') {
24796
- return false;
24932
+ _shouldAcceptMessageFromBus(senderNodeId, targetNodeId) {
24933
+ if (this._isWildcardTarget()) {
24934
+ if (targetNodeId &&
24935
+ targetNodeId !== '*' &&
24936
+ targetNodeId !== this.localNodeId) {
24937
+ logger$_.debug('broadcast_channel_message_rejected', {
24938
+ channel: this.channelName,
24939
+ connector_id: this.connectorId,
24940
+ reason: 'wildcard_target_mismatch',
24941
+ sender_node_id: senderNodeId,
24942
+ target_node_id: targetNodeId,
24943
+ local_node_id: this.localNodeId,
24944
+ });
24945
+ return false;
24946
+ }
24947
+ return true;
24797
24948
  }
24798
- const refId = typeof frame.refId === 'string' && frame.refId.length > 0
24799
- ? frame.refId
24800
- : null;
24801
- const dedupKey = refId ?? envelope.id ?? null;
24802
- if (!dedupKey) {
24949
+ const expectedSender = this.targetNodeId;
24950
+ if (expectedSender && expectedSender !== '*' && senderNodeId !== expectedSender) {
24951
+ logger$_.debug('broadcast_channel_message_rejected', {
24952
+ channel: this.channelName,
24953
+ connector_id: this.connectorId,
24954
+ reason: 'unexpected_sender',
24955
+ expected_sender_node_id: expectedSender,
24956
+ sender_node_id: senderNodeId,
24957
+ local_node_id: this.localNodeId,
24958
+ });
24803
24959
  return false;
24804
24960
  }
24805
- const senderId = this._extractSenderIdFromInboxItem(item);
24806
- return this._checkDuplicateAck(dedupKey, senderId);
24807
- }
24808
- _checkDuplicateAck(dedupKey, senderId) {
24809
- const now = Date.now();
24810
- const lastSeen = this.seenAckKeys.get(dedupKey);
24811
- if (lastSeen && now - lastSeen < this.ackDedupTtlMs) {
24812
- logger$_.debug('broadcast_channel_duplicate_ack_suppressed', {
24961
+ if (targetNodeId &&
24962
+ targetNodeId !== '*' &&
24963
+ targetNodeId !== this.localNodeId) {
24964
+ logger$_.debug('broadcast_channel_message_rejected', {
24813
24965
  channel: this.channelName,
24814
24966
  connector_id: this.connectorId,
24815
- sender_id: senderId ?? null,
24816
- dedup_key: dedupKey,
24967
+ reason: 'unexpected_target',
24968
+ sender_node_id: senderNodeId,
24969
+ target_node_id: targetNodeId,
24970
+ local_node_id: this.localNodeId,
24817
24971
  });
24818
- return true;
24972
+ return false;
24819
24973
  }
24820
- this.seenAckKeys.set(dedupKey, now);
24821
- this.seenAckOrder.push(dedupKey);
24822
- this._trimSeenAcks(now);
24823
- return false;
24974
+ return true;
24824
24975
  }
24825
- _extractEnvelopeFromInboxItem(item) {
24826
- if (!item || typeof item !== 'object') {
24827
- return null;
24976
+ _describeInboxItem(item) {
24977
+ if (item instanceof Uint8Array) {
24978
+ return 'bytes';
24828
24979
  }
24829
- if ('envelope' in item) {
24830
- return item.envelope;
24980
+ if (item.envelope) {
24981
+ return 'channel_message';
24831
24982
  }
24832
- if ('frame' in item) {
24833
- return item;
24983
+ if (item.frame) {
24984
+ return 'envelope';
24834
24985
  }
24835
- return null;
24986
+ return 'unknown';
24836
24987
  }
24837
- _extractSenderIdFromInboxItem(item) {
24838
- if (!item || typeof item !== 'object') {
24839
- return undefined;
24840
- }
24841
- if ('context' in item) {
24842
- const context = item.context;
24843
- if (context && typeof context.fromSystemId === 'string') {
24844
- return context.fromSystemId;
24845
- }
24846
- }
24847
- return undefined;
24988
+ logInboxSnapshot(event, extra = {}) {
24989
+ logger$_.debug(event, {
24990
+ channel: this.channelName,
24991
+ connector_id: this.connectorId,
24992
+ connector_state: this.state,
24993
+ inbox_capacity: this.inboxCapacity,
24994
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
24995
+ ...extra,
24996
+ });
24848
24997
  }
24849
24998
  /**
24850
24999
  * Override start() to check initial visibility state
@@ -24871,46 +25020,33 @@
24871
25020
  });
24872
25021
  }
24873
25022
  }
24874
- _trimSeenAcks(now) {
24875
- while (this.seenAckOrder.length > 0) {
24876
- const candidate = this.seenAckOrder[0];
24877
- const timestamp = this.seenAckKeys.get(candidate);
24878
- if (timestamp === undefined) {
24879
- this.seenAckOrder.shift();
24880
- continue;
24881
- }
24882
- if (this.seenAckKeys.size > this.ackDedupMaxEntries ||
24883
- now - timestamp > this.ackDedupTtlMs) {
24884
- this.seenAckKeys.delete(candidate);
24885
- this.seenAckOrder.shift();
24886
- continue;
24887
- }
24888
- break;
24889
- }
24890
- }
24891
- _extractAckDedupKey(payload) {
24892
- try {
24893
- const decoded = this.textDecoder.decode(payload);
24894
- const parsed = JSON.parse(decoded);
24895
- const envelopeId = typeof parsed?.id === 'string' ? parsed.id : null;
24896
- const frameType = parsed?.frame?.type;
24897
- if (typeof frameType !== 'string' || frameType !== 'DeliveryAck') {
24898
- return null;
24899
- }
24900
- const refId = parsed.frame?.refId;
24901
- if (typeof refId === 'string' && refId.length > 0) {
24902
- return refId;
24903
- }
24904
- return envelopeId;
25023
+ setTargetNodeId(nodeId) {
25024
+ const normalized = BroadcastChannelConnector.normalizeNodeId(nodeId);
25025
+ if (!normalized) {
25026
+ throw new Error('BroadcastChannelConnector target node id must be a non-empty string');
24905
25027
  }
24906
- catch (error) {
24907
- logger$_.debug('broadcast_channel_ack_dedup_parse_failed', {
24908
- channel: this.channelName,
24909
- connector_id: this.connectorId,
24910
- error: error instanceof Error ? error.message : String(error),
24911
- });
24912
- return null;
25028
+ if (normalized === '*') {
25029
+ this.setWildcardTarget();
25030
+ return;
24913
25031
  }
25032
+ this.targetNodeId = normalized;
25033
+ logger$_.debug('broadcast_channel_target_updated', {
25034
+ channel: this.channelName,
25035
+ connector_id: this.connectorId,
25036
+ local_node_id: this.localNodeId,
25037
+ target_node_id: this.targetNodeId,
25038
+ target_mode: 'direct',
25039
+ });
25040
+ }
25041
+ setWildcardTarget() {
25042
+ this.targetNodeId = '*';
25043
+ logger$_.debug('broadcast_channel_target_updated', {
25044
+ channel: this.channelName,
25045
+ connector_id: this.connectorId,
25046
+ local_node_id: this.localNodeId,
25047
+ target_node_id: this.targetNodeId,
25048
+ target_mode: 'wildcard',
25049
+ });
24914
25050
  }
24915
25051
  };
24916
25052
 
@@ -25019,6 +25155,15 @@
25019
25155
  }
25020
25156
  result.inboxCapacity = Math.floor(inboxValue);
25021
25157
  }
25158
+ const windowValue = candidate.initialWindow ?? candidate['initial_window'];
25159
+ if (windowValue !== undefined) {
25160
+ if (typeof windowValue !== 'number' ||
25161
+ !Number.isFinite(windowValue) ||
25162
+ windowValue <= 0) {
25163
+ throw new TypeError('BroadcastChannelConnectionGrant "initialWindow" must be a positive number when provided');
25164
+ }
25165
+ result.initialWindow = Math.floor(windowValue);
25166
+ }
25022
25167
  return result;
25023
25168
  }
25024
25169
  function broadcastChannelGrantToConnectorConfig(grant) {
@@ -25032,6 +25177,9 @@
25032
25177
  if (normalized.inboxCapacity !== undefined) {
25033
25178
  config.inboxCapacity = normalized.inboxCapacity;
25034
25179
  }
25180
+ if (normalized.initialWindow !== undefined) {
25181
+ config.initialWindow = normalized.initialWindow;
25182
+ }
25035
25183
  return config;
25036
25184
  }
25037
25185
 
@@ -25303,6 +25451,20 @@
25303
25451
  waitEvent(event, signal) {
25304
25452
  return signal ? event.wait({ signal }) : event.wait();
25305
25453
  }
25454
+ _getLocalNodeId() {
25455
+ const normalized = this._normalizeNodeId(this.node.id);
25456
+ if (!normalized) {
25457
+ throw new Error('UpstreamSessionManager requires node with a stable identifier');
25458
+ }
25459
+ return normalized;
25460
+ }
25461
+ _normalizeNodeId(value) {
25462
+ if (typeof value !== 'string') {
25463
+ return null;
25464
+ }
25465
+ const trimmed = value.trim();
25466
+ return trimmed.length > 0 ? trimmed : null;
25467
+ }
25306
25468
  async connectCycle() {
25307
25469
  if (!this.admissionClient) {
25308
25470
  throw new FameConnectError('Admission client is required to attach upstream');
@@ -25324,6 +25486,8 @@
25324
25486
  await this.onWelcome(welcome.frame);
25325
25487
  const connector = await ConnectorFactory.createConnector(grant, {
25326
25488
  systemId: welcome.frame.systemId,
25489
+ localNodeId: this._getLocalNodeId(),
25490
+ initialTargetNodeId: '*',
25327
25491
  });
25328
25492
  await connector.start(this.wrappedHandler);
25329
25493
  this.connector = connector;
@@ -25349,6 +25513,20 @@
25349
25513
  }
25350
25514
  const attachInfo = await this.attachClient.attach(this.node, this.outboundOriginType, connector, welcome.frame, this.wrappedHandler, this.getKeys() ?? undefined, callbackGrants);
25351
25515
  this.targetSystemId = attachInfo.targetSystemId ?? null;
25516
+ if (this.targetSystemId) {
25517
+ const targetAware = connector;
25518
+ if (typeof targetAware.setTargetNodeId === 'function') {
25519
+ try {
25520
+ targetAware.setTargetNodeId(this.targetSystemId);
25521
+ }
25522
+ catch (error) {
25523
+ logger$Z.warning('broadcast_channel_target_apply_failed', {
25524
+ error: error instanceof Error ? error.message : String(error),
25525
+ target_node_id: this.targetSystemId,
25526
+ });
25527
+ }
25528
+ }
25529
+ }
25352
25530
  await this.onAttach(attachInfo, connector);
25353
25531
  // Close the admission client immediately after attach completes
25354
25532
  // This releases HTTP keep-alive connections (Node.js fetch/undici requires explicit cleanup)
@@ -25411,7 +25589,7 @@
25411
25589
  this.currentStopSubtasks = null;
25412
25590
  await Promise.allSettled(tasks.map((task) => task.promise));
25413
25591
  if (this.connector) {
25414
- logger$Z.info('upstream_stopping_old_connector', {
25592
+ logger$Z.debug('upstream_stopping_old_connector', {
25415
25593
  connect_epoch: this.connectEpoch,
25416
25594
  target_system_id: this.targetSystemId,
25417
25595
  timestamp: new Date().toISOString(),
@@ -25422,7 +25600,7 @@
25422
25600
  error: err instanceof Error ? err.message : String(err),
25423
25601
  });
25424
25602
  });
25425
- logger$Z.info('upstream_old_connector_stopped', {
25603
+ logger$Z.debug('upstream_old_connector_stopped', {
25426
25604
  connect_epoch: this.connectEpoch,
25427
25605
  target_system_id: this.targetSystemId,
25428
25606
  timestamp: new Date().toISOString(),
@@ -25624,8 +25802,15 @@
25624
25802
  if (!envelope) {
25625
25803
  continue;
25626
25804
  }
25805
+ logger$Z.debug('upstream_pump_sending_envelope', {
25806
+ envelopeId: envelope.id,
25807
+ type: envelope.frame?.type,
25808
+ });
25627
25809
  try {
25628
25810
  await connector.send(envelope);
25811
+ logger$Z.debug('upstream_pump_sent_envelope', {
25812
+ envelopeId: envelope.id,
25813
+ });
25629
25814
  }
25630
25815
  catch (error) {
25631
25816
  if (error instanceof FameMessageTooLarge) {
@@ -34846,10 +35031,31 @@
34846
35031
  }
34847
35032
  return null;
34848
35033
  }
35034
+ static normalizeNodeId(value) {
35035
+ if (typeof value !== 'string') {
35036
+ return null;
35037
+ }
35038
+ const trimmed = value.trim();
35039
+ return trimmed.length > 0 ? trimmed : null;
35040
+ }
35041
+ static normalizeTargetNodeId(value) {
35042
+ if (typeof value !== 'string') {
35043
+ return undefined;
35044
+ }
35045
+ const trimmed = value.trim();
35046
+ if (trimmed.length === 0) {
35047
+ return undefined;
35048
+ }
35049
+ if (trimmed === '*') {
35050
+ return '*';
35051
+ }
35052
+ return trimmed;
35053
+ }
34849
35054
  constructor(config, baseConfig = {}) {
34850
35055
  ensureBrowserEnvironment$2();
34851
35056
  super(baseConfig);
34852
35057
  this.listenerRegistered = false;
35058
+ this.visibilityChangeListenerRegistered = false;
34853
35059
  this.channelName =
34854
35060
  typeof config.channelName === 'string' && config.channelName.trim().length > 0
34855
35061
  ? config.channelName.trim()
@@ -34860,41 +35066,68 @@
34860
35066
  ? Math.floor(config.inboxCapacity)
34861
35067
  : DEFAULT_INBOX_CAPACITY$6;
34862
35068
  this.inbox = new BoundedAsyncQueue(preferredCapacity);
35069
+ this.inboxCapacity = preferredCapacity;
34863
35070
  this.connectorId = InPageConnector.generateConnectorId();
35071
+ const normalizedLocalNodeId = InPageConnector.normalizeNodeId(config.localNodeId);
35072
+ if (!normalizedLocalNodeId) {
35073
+ throw new Error('InPageConnector requires a non-empty localNodeId');
35074
+ }
35075
+ this.localNodeId = normalizedLocalNodeId;
35076
+ this.targetNodeId = InPageConnector.normalizeTargetNodeId(config.initialTargetNodeId);
34864
35077
  logger$G.debug('inpage_connector_initialized', {
34865
35078
  channel: this.channelName,
34866
35079
  connector_id: this.connectorId,
35080
+ local_node_id: this.localNodeId,
35081
+ target_node_id: this.targetNodeId ?? null,
35082
+ inbox_capacity: preferredCapacity,
34867
35083
  });
34868
35084
  this.onMsg = (event) => {
35085
+ if (!this.listenerRegistered) {
35086
+ logger$G.warning('inpage_message_after_unregister', {
35087
+ channel: this.channelName,
35088
+ connector_id: this.connectorId,
35089
+ timestamp: new Date().toISOString(),
35090
+ });
35091
+ return;
35092
+ }
34869
35093
  const messageEvent = event;
34870
35094
  const message = messageEvent.data;
34871
35095
  logger$G.debug('inpage_raw_event', {
34872
35096
  channel: this.channelName,
34873
35097
  connector_id: this.connectorId,
34874
- message_type: message && typeof message === 'object' ? message.constructor?.name ?? typeof message : typeof message,
34875
- has_sender_id: Boolean(message?.senderId),
34876
- payload_type: message && typeof message === 'object'
34877
- ? message?.payload instanceof Uint8Array
34878
- ? 'Uint8Array'
34879
- : message?.payload instanceof ArrayBuffer
34880
- ? 'ArrayBuffer'
34881
- : typeof message?.payload
35098
+ message_type: message && typeof message === 'object'
35099
+ ? message.constructor?.name ?? typeof message
34882
35100
  : typeof message,
34883
- payload_constructor: message && typeof message === 'object'
34884
- ? message?.payload?.constructor?.name
34885
- : undefined,
34886
- payload_keys: message && typeof message === 'object' && message?.payload && typeof message?.payload === 'object'
34887
- ? Object.keys(message.payload).slice(0, 5)
34888
- : undefined,
35101
+ has_sender_id: Boolean(message?.senderId),
35102
+ has_sender_node_id: Boolean(message?.senderNodeId),
34889
35103
  });
34890
35104
  if (!message || typeof message !== 'object') {
34891
35105
  return;
34892
35106
  }
34893
35107
  const busMessage = message;
34894
- if (typeof busMessage.senderId !== 'string' || busMessage.senderId.length === 0) {
35108
+ const senderId = typeof busMessage.senderId === 'string' && busMessage.senderId.length > 0
35109
+ ? busMessage.senderId
35110
+ : null;
35111
+ const senderNodeId = InPageConnector.normalizeNodeId(busMessage.senderNodeId);
35112
+ if (!senderId || !senderNodeId) {
35113
+ logger$G.debug('inpage_message_rejected', {
35114
+ channel: this.channelName,
35115
+ connector_id: this.connectorId,
35116
+ reason: 'missing_sender_metadata',
35117
+ });
35118
+ return;
35119
+ }
35120
+ if (senderId === this.connectorId || senderNodeId === this.localNodeId) {
35121
+ logger$G.debug('inpage_message_rejected', {
35122
+ channel: this.channelName,
35123
+ connector_id: this.connectorId,
35124
+ reason: 'self_echo',
35125
+ sender_node_id: senderNodeId,
35126
+ });
34895
35127
  return;
34896
35128
  }
34897
- if (busMessage.senderId === this.connectorId) {
35129
+ const incomingTargetNodeId = InPageConnector.normalizeTargetNodeId(busMessage.targetNodeId);
35130
+ if (!this._shouldAcceptMessageFromBus(senderNodeId, incomingTargetNodeId)) {
34898
35131
  return;
34899
35132
  }
34900
35133
  const payload = InPageConnector.coercePayload(busMessage.payload);
@@ -34908,7 +35141,9 @@
34908
35141
  }
34909
35142
  logger$G.debug('inpage_message_received', {
34910
35143
  channel: this.channelName,
34911
- sender_id: busMessage.senderId,
35144
+ sender_id: senderId,
35145
+ sender_node_id: senderNodeId,
35146
+ target_node_id: incomingTargetNodeId ?? null,
34912
35147
  connector_id: this.connectorId,
34913
35148
  payload_length: payload.byteLength,
34914
35149
  });
@@ -34916,15 +35151,27 @@
34916
35151
  if (typeof this.inbox.tryEnqueue === 'function') {
34917
35152
  const accepted = this.inbox.tryEnqueue(payload);
34918
35153
  if (accepted) {
35154
+ this.logInboxSnapshot('inpage_inbox_enqueued', {
35155
+ source: 'listener',
35156
+ enqueue_strategy: 'try',
35157
+ payload_length: payload.byteLength,
35158
+ });
34919
35159
  return;
34920
35160
  }
34921
35161
  }
34922
35162
  this.inbox.enqueue(payload);
35163
+ this.logInboxSnapshot('inpage_inbox_enqueued', {
35164
+ source: 'listener',
35165
+ enqueue_strategy: 'enqueue',
35166
+ payload_length: payload.byteLength,
35167
+ });
34923
35168
  }
34924
35169
  catch (error) {
34925
35170
  if (error instanceof QueueFullError) {
34926
35171
  logger$G.warning('inpage_receive_queue_full', {
34927
35172
  channel: this.channelName,
35173
+ inbox_capacity: this.inboxCapacity,
35174
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
34928
35175
  });
34929
35176
  }
34930
35177
  else {
@@ -34937,6 +35184,92 @@
34937
35184
  };
34938
35185
  getSharedBus$1().addEventListener(this.channelName, this.onMsg);
34939
35186
  this.listenerRegistered = true;
35187
+ // Setup visibility change monitoring
35188
+ this.visibilityChangeHandler = () => {
35189
+ const isHidden = document.hidden;
35190
+ logger$G.debug('inpage_visibility_changed', {
35191
+ channel: this.channelName,
35192
+ connector_id: this.connectorId,
35193
+ visibility: isHidden ? 'hidden' : 'visible',
35194
+ timestamp: new Date().toISOString(),
35195
+ });
35196
+ // Pause/resume connector based on visibility
35197
+ if (isHidden && this.state === ConnectorState.STARTED) {
35198
+ this.pause().catch((err) => {
35199
+ logger$G.warning('inpage_pause_failed', {
35200
+ channel: this.channelName,
35201
+ connector_id: this.connectorId,
35202
+ error: err instanceof Error ? err.message : String(err),
35203
+ });
35204
+ });
35205
+ }
35206
+ else if (!isHidden && this.state === ConnectorState.PAUSED) {
35207
+ this.resume().catch((err) => {
35208
+ logger$G.warning('inpage_resume_failed', {
35209
+ channel: this.channelName,
35210
+ connector_id: this.connectorId,
35211
+ error: err instanceof Error ? err.message : String(err),
35212
+ });
35213
+ });
35214
+ }
35215
+ };
35216
+ if (typeof document !== 'undefined') {
35217
+ document.addEventListener('visibilitychange', this.visibilityChangeHandler);
35218
+ this.visibilityChangeListenerRegistered = true;
35219
+ // Track page lifecycle events to detect browser unload/discard
35220
+ if (typeof window !== 'undefined') {
35221
+ const lifecycleLogger = (event) => {
35222
+ logger$G.info('inpage_page_lifecycle', {
35223
+ channel: this.channelName,
35224
+ connector_id: this.connectorId,
35225
+ event_type: event.type,
35226
+ visibility_state: document.visibilityState,
35227
+ timestamp: new Date().toISOString(),
35228
+ });
35229
+ };
35230
+ window.addEventListener('beforeunload', lifecycleLogger);
35231
+ window.addEventListener('unload', lifecycleLogger);
35232
+ window.addEventListener('pagehide', lifecycleLogger);
35233
+ window.addEventListener('pageshow', lifecycleLogger);
35234
+ document.addEventListener('freeze', lifecycleLogger);
35235
+ document.addEventListener('resume', lifecycleLogger);
35236
+ }
35237
+ // Log initial state with detailed visibility info
35238
+ logger$G.debug('inpage_initial_visibility', {
35239
+ channel: this.channelName,
35240
+ connector_id: this.connectorId,
35241
+ visibility: document.hidden ? 'hidden' : 'visible',
35242
+ document_hidden: document.hidden,
35243
+ visibility_state: document.visibilityState,
35244
+ has_focus: document.hasFocus(),
35245
+ timestamp: new Date().toISOString(),
35246
+ });
35247
+ }
35248
+ }
35249
+ /**
35250
+ * Override start() to check initial visibility state
35251
+ */
35252
+ async start(inboundHandler) {
35253
+ await super.start(inboundHandler);
35254
+ // After transitioning to STARTED, check if tab is already hidden
35255
+ if (typeof document !== 'undefined' && document.hidden) {
35256
+ logger$G.debug('inpage_start_in_hidden_tab', {
35257
+ channel: this.channelName,
35258
+ connector_id: this.connectorId,
35259
+ document_hidden: document.hidden,
35260
+ visibility_state: document.visibilityState,
35261
+ has_focus: document.hasFocus(),
35262
+ timestamp: new Date().toISOString(),
35263
+ });
35264
+ // Immediately pause if tab is hidden at start time
35265
+ await this.pause().catch((err) => {
35266
+ logger$G.warning('inpage_initial_pause_failed', {
35267
+ channel: this.channelName,
35268
+ connector_id: this.connectorId,
35269
+ error: err instanceof Error ? err.message : String(err),
35270
+ });
35271
+ });
35272
+ }
34940
35273
  }
34941
35274
  // Allow listeners to feed envelopes directly into the in-page receive queue.
34942
35275
  async pushToReceive(rawOrEnvelope) {
@@ -34945,15 +35278,25 @@
34945
35278
  if (typeof this.inbox.tryEnqueue === 'function') {
34946
35279
  const accepted = this.inbox.tryEnqueue(item);
34947
35280
  if (accepted) {
35281
+ this.logInboxSnapshot('inpage_push_enqueued', {
35282
+ enqueue_strategy: 'try',
35283
+ item_type: this._describeInboxItem(item),
35284
+ });
34948
35285
  return;
34949
35286
  }
34950
35287
  }
34951
35288
  this.inbox.enqueue(item);
35289
+ this.logInboxSnapshot('inpage_push_enqueued', {
35290
+ enqueue_strategy: 'enqueue',
35291
+ item_type: this._describeInboxItem(item),
35292
+ });
34952
35293
  }
34953
35294
  catch (error) {
34954
35295
  if (error instanceof QueueFullError) {
34955
35296
  logger$G.warning('inpage_push_queue_full', {
34956
35297
  channel: this.channelName,
35298
+ inbox_capacity: this.inboxCapacity,
35299
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
34957
35300
  });
34958
35301
  throw error;
34959
35302
  }
@@ -34966,25 +35309,57 @@
34966
35309
  }
34967
35310
  async _transportSendBytes(data) {
34968
35311
  ensureBrowserEnvironment$2();
35312
+ const targetNodeId = this.targetNodeId ?? '*';
34969
35313
  logger$G.debug('inpage_message_sending', {
34970
35314
  channel: this.channelName,
34971
35315
  sender_id: this.connectorId,
35316
+ sender_node_id: this.localNodeId,
35317
+ target_node_id: targetNodeId,
34972
35318
  });
34973
35319
  const event = new MessageEvent(this.channelName, {
34974
35320
  data: {
34975
35321
  senderId: this.connectorId,
35322
+ senderNodeId: this.localNodeId,
35323
+ targetNodeId,
34976
35324
  payload: data,
34977
35325
  },
34978
35326
  });
34979
35327
  getSharedBus$1().dispatchEvent(event);
34980
35328
  }
34981
35329
  async _transportReceive() {
34982
- return await this.inbox.dequeue();
35330
+ const item = await this.inbox.dequeue();
35331
+ this.logInboxSnapshot('inpage_inbox_dequeued', {
35332
+ item_type: this._describeInboxItem(item),
35333
+ });
35334
+ return item;
34983
35335
  }
34984
35336
  async _transportClose(code, reason) {
35337
+ logger$G.debug('inpage_transport_closing', {
35338
+ channel: this.channelName,
35339
+ connector_id: this.connectorId,
35340
+ code,
35341
+ reason,
35342
+ listener_registered: this.listenerRegistered,
35343
+ timestamp: new Date().toISOString(),
35344
+ });
34985
35345
  if (this.listenerRegistered) {
35346
+ logger$G.debug('inpage_removing_listener', {
35347
+ channel: this.channelName,
35348
+ connector_id: this.connectorId,
35349
+ timestamp: new Date().toISOString(),
35350
+ });
34986
35351
  getSharedBus$1().removeEventListener(this.channelName, this.onMsg);
34987
35352
  this.listenerRegistered = false;
35353
+ logger$G.debug('inpage_listener_removed', {
35354
+ channel: this.channelName,
35355
+ connector_id: this.connectorId,
35356
+ timestamp: new Date().toISOString(),
35357
+ });
35358
+ }
35359
+ if (this.visibilityChangeListenerRegistered && this.visibilityChangeHandler && typeof document !== 'undefined') {
35360
+ document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
35361
+ this.visibilityChangeListenerRegistered = false;
35362
+ this.visibilityChangeHandler = undefined;
34988
35363
  }
34989
35364
  const closeCode = typeof code === 'number' ? code : 1000;
34990
35365
  const closeReason = typeof reason === 'string' && reason.length > 0 ? reason : 'closed';
@@ -34997,6 +35372,103 @@
34997
35372
  }
34998
35373
  return rawOrEnvelope;
34999
35374
  }
35375
+ _isWildcardTarget() {
35376
+ return this.targetNodeId === '*' || typeof this.targetNodeId === 'undefined';
35377
+ }
35378
+ _shouldAcceptMessageFromBus(senderNodeId, targetNodeId) {
35379
+ if (this._isWildcardTarget()) {
35380
+ if (targetNodeId &&
35381
+ targetNodeId !== '*' &&
35382
+ targetNodeId !== this.localNodeId) {
35383
+ logger$G.debug('inpage_message_rejected', {
35384
+ channel: this.channelName,
35385
+ connector_id: this.connectorId,
35386
+ reason: 'wildcard_target_mismatch',
35387
+ sender_node_id: senderNodeId,
35388
+ target_node_id: targetNodeId,
35389
+ local_node_id: this.localNodeId,
35390
+ });
35391
+ return false;
35392
+ }
35393
+ return true;
35394
+ }
35395
+ const expectedSender = this.targetNodeId;
35396
+ if (expectedSender && expectedSender !== '*' && senderNodeId !== expectedSender) {
35397
+ logger$G.debug('inpage_message_rejected', {
35398
+ channel: this.channelName,
35399
+ connector_id: this.connectorId,
35400
+ reason: 'unexpected_sender',
35401
+ expected_sender_node_id: expectedSender,
35402
+ sender_node_id: senderNodeId,
35403
+ local_node_id: this.localNodeId,
35404
+ });
35405
+ return false;
35406
+ }
35407
+ if (targetNodeId &&
35408
+ targetNodeId !== '*' &&
35409
+ targetNodeId !== this.localNodeId) {
35410
+ logger$G.debug('inpage_message_rejected', {
35411
+ channel: this.channelName,
35412
+ connector_id: this.connectorId,
35413
+ reason: 'unexpected_target',
35414
+ sender_node_id: senderNodeId,
35415
+ target_node_id: targetNodeId,
35416
+ local_node_id: this.localNodeId,
35417
+ });
35418
+ return false;
35419
+ }
35420
+ return true;
35421
+ }
35422
+ _describeInboxItem(item) {
35423
+ if (item instanceof Uint8Array) {
35424
+ return 'bytes';
35425
+ }
35426
+ if (item.envelope) {
35427
+ return 'channel_message';
35428
+ }
35429
+ if (item.frame) {
35430
+ return 'envelope';
35431
+ }
35432
+ return 'unknown';
35433
+ }
35434
+ logInboxSnapshot(event, extra = {}) {
35435
+ logger$G.debug(event, {
35436
+ channel: this.channelName,
35437
+ connector_id: this.connectorId,
35438
+ connector_state: this.state,
35439
+ inbox_capacity: this.inboxCapacity,
35440
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
35441
+ ...extra,
35442
+ });
35443
+ }
35444
+ setTargetNodeId(nodeId) {
35445
+ const normalized = InPageConnector.normalizeNodeId(nodeId);
35446
+ if (!normalized) {
35447
+ throw new Error('InPageConnector target node id must be a non-empty string');
35448
+ }
35449
+ if (normalized === '*') {
35450
+ this.setWildcardTarget();
35451
+ return;
35452
+ }
35453
+ this.targetNodeId = normalized;
35454
+ logger$G.debug('inpage_target_updated', {
35455
+ channel: this.channelName,
35456
+ connector_id: this.connectorId,
35457
+ local_node_id: this.localNodeId,
35458
+ target_node_id: this.targetNodeId,
35459
+ target_mode: 'direct',
35460
+ });
35461
+ }
35462
+ setWildcardTarget() {
35463
+ this.targetNodeId = '*';
35464
+ logger$G.debug('inpage_target_updated', {
35465
+ channel: this.channelName,
35466
+ connector_id: this.connectorId,
35467
+ local_node_id: this.localNodeId,
35468
+ target_node_id: this.targetNodeId,
35469
+ target_mode: 'wildcard',
35470
+ });
35471
+ }
35000
35472
  }
35001
35473
 
35002
35474
  const RPC_REGISTRY = Symbol('naylence.rpc.registry');
@@ -42424,8 +42896,16 @@
42424
42896
  }
42425
42897
  const normalized = this._normalizeConfig(config);
42426
42898
  const options = (factoryArgs[0] ?? {});
42899
+ const normalizedLocalNodeFromConfig = this._normalizeNodeId(normalized.localNodeId);
42900
+ const localNodeId = this._normalizeNodeId(options.localNodeId) ?? normalizedLocalNodeFromConfig;
42901
+ if (!localNodeId) {
42902
+ throw new Error('InPageConnectorFactory requires a localNodeId from config or create() options');
42903
+ }
42427
42904
  const channelName = normalized.channelName ?? DEFAULT_CHANNEL$5;
42428
42905
  const inboxCapacity = normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY$5;
42906
+ const targetFromOptions = this._normalizeTargetNodeId(options.initialTargetNodeId);
42907
+ const targetFromConfig = this._normalizeTargetNodeId(normalized.initialTargetNodeId);
42908
+ const resolvedTarget = targetFromOptions ?? targetFromConfig ?? '*';
42429
42909
  const baseConfig = {
42430
42910
  drainTimeout: normalized.drainTimeout,
42431
42911
  flowControl: normalized.flowControl,
@@ -42440,6 +42920,8 @@
42440
42920
  type: INPAGE_CONNECTOR_TYPE,
42441
42921
  channelName,
42442
42922
  inboxCapacity,
42923
+ localNodeId,
42924
+ initialTargetNodeId: resolvedTarget,
42443
42925
  };
42444
42926
  const connector = new InPageConnector(connectorConfig, baseConfig);
42445
42927
  if (options.authorization) {
@@ -42475,6 +42957,16 @@
42475
42957
  capacity > 0) {
42476
42958
  normalized.inboxCapacity = Math.floor(capacity);
42477
42959
  }
42960
+ const localNodeId = candidate.localNodeId ?? candidate['local_node_id'];
42961
+ const normalizedLocalNodeId = this._normalizeNodeId(localNodeId);
42962
+ if (normalizedLocalNodeId) {
42963
+ normalized.localNodeId = normalizedLocalNodeId;
42964
+ }
42965
+ const initialTargetNodeId = candidate.initialTargetNodeId ?? candidate['initial_target_node_id'];
42966
+ const normalizedTarget = this._normalizeTargetNodeId(initialTargetNodeId);
42967
+ if (normalizedTarget) {
42968
+ normalized.initialTargetNodeId = normalizedTarget;
42969
+ }
42478
42970
  if (typeof candidate.flowControl === 'boolean') {
42479
42971
  normalized.flowControl = candidate.flowControl;
42480
42972
  }
@@ -42513,6 +43005,22 @@
42513
43005
  normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY$5;
42514
43006
  return normalized;
42515
43007
  }
43008
+ _normalizeNodeId(value) {
43009
+ if (typeof value !== 'string') {
43010
+ return null;
43011
+ }
43012
+ const trimmed = value.trim();
43013
+ return trimmed.length > 0 ? trimmed : null;
43014
+ }
43015
+ _normalizeTargetNodeId(value) {
43016
+ if (value === undefined || value === null) {
43017
+ return undefined;
43018
+ }
43019
+ if (value === '*') {
43020
+ return '*';
43021
+ }
43022
+ return this._normalizeNodeId(value) ?? undefined;
43023
+ }
42516
43024
  }
42517
43025
 
42518
43026
  var inpageConnectorFactory = /*#__PURE__*/Object.freeze({
@@ -42558,6 +43066,7 @@
42558
43066
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
42559
43067
  channelName: connectorConfig.channelName,
42560
43068
  inboxCapacity: connectorConfig.inboxCapacity,
43069
+ initialWindow: connectorConfig.initialWindow,
42561
43070
  };
42562
43071
  }
42563
43072
  const config = {
@@ -42582,6 +43091,7 @@
42582
43091
  purpose: 'connection',
42583
43092
  channelName: normalizedConfig.channelName,
42584
43093
  inboxCapacity: normalizedConfig.inboxCapacity,
43094
+ initialWindow: normalizedConfig.initialWindow,
42585
43095
  });
42586
43096
  return grant;
42587
43097
  }
@@ -42591,8 +43101,16 @@
42591
43101
  }
42592
43102
  const normalized = this._normalizeConfig(config);
42593
43103
  const options = (factoryArgs[0] ?? {});
43104
+ const normalizedLocalNodeFromConfig = this._normalizeNodeId(normalized.localNodeId);
43105
+ const localNodeId = this._normalizeNodeId(options.localNodeId) ?? normalizedLocalNodeFromConfig;
43106
+ if (!localNodeId) {
43107
+ throw new Error('BroadcastChannelConnectorFactory requires a localNodeId from config or create() options');
43108
+ }
42594
43109
  const channelName = normalized.channelName ?? DEFAULT_CHANNEL$4;
42595
43110
  const inboxCapacity = normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY$4;
43111
+ const targetFromOptions = this._normalizeTargetNodeId(options.initialTargetNodeId);
43112
+ const targetFromConfig = this._normalizeTargetNodeId(normalized.initialTargetNodeId);
43113
+ const resolvedTarget = targetFromOptions ?? targetFromConfig ?? '*';
42596
43114
  const baseConfig = {
42597
43115
  drainTimeout: normalized.drainTimeout,
42598
43116
  flowControl: normalized.flowControl,
@@ -42607,6 +43125,8 @@
42607
43125
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
42608
43126
  channelName,
42609
43127
  inboxCapacity,
43128
+ localNodeId,
43129
+ initialTargetNodeId: resolvedTarget,
42610
43130
  };
42611
43131
  const connector = new BroadcastChannelConnector(connectorConfig, baseConfig);
42612
43132
  if (options.authorization) {
@@ -42630,11 +43150,21 @@
42630
43150
  normalized.channelName = channel.trim();
42631
43151
  }
42632
43152
  const capacity = candidate.inboxCapacity ?? candidate['inbox_capacity'];
43153
+ const initialTargetNodeId = candidate.initialTargetNodeId ?? candidate['initial_target_node_id'];
43154
+ const normalizedTarget = this._normalizeTargetNodeId(initialTargetNodeId);
43155
+ if (normalizedTarget) {
43156
+ normalized.initialTargetNodeId = normalizedTarget;
43157
+ }
42633
43158
  if (typeof capacity === 'number' &&
42634
43159
  Number.isFinite(capacity) &&
42635
43160
  capacity > 0) {
42636
43161
  normalized.inboxCapacity = Math.floor(capacity);
42637
43162
  }
43163
+ const localNodeId = candidate.localNodeId ?? candidate['local_node_id'];
43164
+ const normalizedLocalNodeId = this._normalizeNodeId(localNodeId);
43165
+ if (normalizedLocalNodeId) {
43166
+ normalized.localNodeId = normalizedLocalNodeId;
43167
+ }
42638
43168
  if (typeof candidate.flowControl === 'boolean') {
42639
43169
  normalized.flowControl = candidate.flowControl;
42640
43170
  }
@@ -42673,6 +43203,22 @@
42673
43203
  normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY$4;
42674
43204
  return normalized;
42675
43205
  }
43206
+ _normalizeNodeId(value) {
43207
+ if (typeof value !== 'string') {
43208
+ return null;
43209
+ }
43210
+ const trimmed = value.trim();
43211
+ return trimmed.length > 0 ? trimmed : null;
43212
+ }
43213
+ _normalizeTargetNodeId(value) {
43214
+ if (value === undefined || value === null) {
43215
+ return undefined;
43216
+ }
43217
+ if (value === '*') {
43218
+ return '*';
43219
+ }
43220
+ return this._normalizeNodeId(value) ?? undefined;
43221
+ }
42676
43222
  }
42677
43223
 
42678
43224
  var broadcastChannelConnectorFactory = /*#__PURE__*/Object.freeze({
@@ -43253,7 +43799,7 @@
43253
43799
  node: routingNode,
43254
43800
  });
43255
43801
  const selection = defaultGrantSelectionPolicy.selectCallbackGrant(selectionContext);
43256
- connectorConfig = this._grantToConnectorConfig(selection.grant);
43802
+ connectorConfig = this._buildConnectorConfigForSystem(systemId, this._grantToConnectorConfig(selection.grant));
43257
43803
  }
43258
43804
  catch (error) {
43259
43805
  logger$p.debug('inpage_listener_grant_selection_failed', {
@@ -43261,13 +43807,13 @@
43261
43807
  system_id: systemId,
43262
43808
  error: error instanceof Error ? error.message : String(error),
43263
43809
  });
43264
- connectorConfig =
43265
- this._extractInPageConnectorConfig(frame) ??
43266
- {
43267
- type: INPAGE_CONNECTOR_TYPE,
43268
- channelName: this._channelName,
43269
- inboxCapacity: this._inboxCapacity,
43270
- };
43810
+ const fallbackConfig = this._extractInPageConnectorConfig(frame) ??
43811
+ {
43812
+ type: INPAGE_CONNECTOR_TYPE,
43813
+ channelName: this._channelName,
43814
+ inboxCapacity: this._inboxCapacity,
43815
+ };
43816
+ connectorConfig = this._buildConnectorConfigForSystem(systemId, fallbackConfig);
43271
43817
  }
43272
43818
  try {
43273
43819
  const connector = await routingNode.createOriginConnector({
@@ -43386,6 +43932,65 @@
43386
43932
  typeof frame === 'object' &&
43387
43933
  frame.type === 'NodeAttach');
43388
43934
  }
43935
+ _buildConnectorConfigForSystem(systemId, baseConfig) {
43936
+ const localNodeId = this._requireLocalNodeId();
43937
+ const targetSystemId = this._normalizeNodeId(systemId);
43938
+ if (!targetSystemId) {
43939
+ throw new Error('InPageListener requires a valid system id for connector creation');
43940
+ }
43941
+ const candidate = baseConfig ?? null;
43942
+ const channelCandidate = candidate && 'channelName' in candidate
43943
+ ? candidate.channelName
43944
+ : undefined;
43945
+ const inboxCandidate = candidate && 'inboxCapacity' in candidate
43946
+ ? candidate.inboxCapacity
43947
+ : undefined;
43948
+ const targetCandidate = candidate && 'initialTargetNodeId' in candidate
43949
+ ? candidate.initialTargetNodeId
43950
+ : undefined;
43951
+ const channelName = typeof channelCandidate === 'string' && channelCandidate.trim().length > 0
43952
+ ? channelCandidate.trim()
43953
+ : this._channelName;
43954
+ const inboxCapacity = typeof inboxCandidate === 'number' &&
43955
+ Number.isFinite(inboxCandidate) &&
43956
+ inboxCandidate > 0
43957
+ ? Math.floor(inboxCandidate)
43958
+ : this._inboxCapacity;
43959
+ const normalizedTarget = this._normalizeTargetNodeId(targetCandidate);
43960
+ return {
43961
+ type: INPAGE_CONNECTOR_TYPE,
43962
+ channelName,
43963
+ inboxCapacity,
43964
+ localNodeId,
43965
+ initialTargetNodeId: normalizedTarget ?? targetSystemId,
43966
+ };
43967
+ }
43968
+ _requireLocalNodeId() {
43969
+ if (!this._routingNode) {
43970
+ throw new Error('InPageListener requires routing node context');
43971
+ }
43972
+ const normalized = this._normalizeNodeId(this._routingNode.id);
43973
+ if (!normalized) {
43974
+ throw new Error('InPageListener requires routing node with a stable identifier');
43975
+ }
43976
+ return normalized;
43977
+ }
43978
+ _normalizeNodeId(value) {
43979
+ if (typeof value !== 'string') {
43980
+ return null;
43981
+ }
43982
+ const trimmed = value.trim();
43983
+ return trimmed.length > 0 ? trimmed : null;
43984
+ }
43985
+ _normalizeTargetNodeId(value) {
43986
+ if (value === undefined || value === null) {
43987
+ return undefined;
43988
+ }
43989
+ if (value === '*') {
43990
+ return '*';
43991
+ }
43992
+ return this._normalizeNodeId(value) ?? undefined;
43993
+ }
43389
43994
  }
43390
43995
  function getInPageListenerInstance() {
43391
43996
  return _lastInPageListenerInstance;
@@ -43776,7 +44381,7 @@
43776
44381
  node: routingNode,
43777
44382
  });
43778
44383
  const selection = defaultGrantSelectionPolicy.selectCallbackGrant(selectionContext);
43779
- connectorConfig = this._grantToConnectorConfig(selection.grant);
44384
+ connectorConfig = this._grantToConnectorConfig(selection.grant, systemId);
43780
44385
  }
43781
44386
  catch (error) {
43782
44387
  logger$o.debug('broadcast_channel_listener_grant_selection_failed', {
@@ -43785,12 +44390,20 @@
43785
44390
  error: error instanceof Error ? error.message : String(error),
43786
44391
  });
43787
44392
  connectorConfig =
43788
- this._extractBroadcastConnectorConfig(frame) ??
43789
- {
44393
+ this._extractBroadcastConnectorConfig(frame, systemId) ??
44394
+ this._buildConnectorConfigForSystem(systemId, {
43790
44395
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
43791
44396
  channelName: this._channelName,
43792
44397
  inboxCapacity: this._inboxCapacity,
43793
- };
44398
+ passive: true,
44399
+ });
44400
+ }
44401
+ if (!connectorConfig) {
44402
+ logger$o.error('broadcast_channel_listener_missing_connector_config', {
44403
+ sender_id: params.senderId,
44404
+ system_id: systemId,
44405
+ });
44406
+ return null;
43794
44407
  }
43795
44408
  try {
43796
44409
  const connector = await routingNode.createOriginConnector({
@@ -43816,7 +44429,7 @@
43816
44429
  return null;
43817
44430
  }
43818
44431
  }
43819
- _extractBroadcastConnectorConfig(frame) {
44432
+ _extractBroadcastConnectorConfig(frame, systemId) {
43820
44433
  const rawGrants = frame.callbackGrants;
43821
44434
  if (!Array.isArray(rawGrants)) {
43822
44435
  return null;
@@ -43827,7 +44440,10 @@
43827
44440
  (grant.type === BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE ||
43828
44441
  grant.type === BROADCAST_CHANNEL_CONNECTOR_TYPE)) {
43829
44442
  try {
43830
- return this._grantToConnectorConfig(grant);
44443
+ if (grant.type === BROADCAST_CHANNEL_CONNECTOR_TYPE) {
44444
+ return this._buildConnectorConfigForSystem(systemId, grant);
44445
+ }
44446
+ return this._buildConnectorConfigForSystem(systemId, broadcastChannelGrantToConnectorConfig(grant));
43831
44447
  }
43832
44448
  catch (error) {
43833
44449
  logger$o.debug('broadcast_channel_listener_grant_normalization_failed', {
@@ -43838,30 +44454,86 @@
43838
44454
  }
43839
44455
  return null;
43840
44456
  }
43841
- _grantToConnectorConfig(grant) {
43842
- if (grant.type !== BROADCAST_CHANNEL_CONNECTOR_TYPE) {
43843
- if (grant.type === BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE) {
43844
- return broadcastChannelGrantToConnectorConfig(grant);
44457
+ _grantToConnectorConfig(grant, systemId) {
44458
+ if (grant.type === BROADCAST_CHANNEL_CONNECTOR_TYPE) {
44459
+ return this._buildConnectorConfigForSystem(systemId, grant);
44460
+ }
44461
+ if (grant.type === BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE) {
44462
+ return this._buildConnectorConfigForSystem(systemId, broadcastChannelGrantToConnectorConfig(grant));
44463
+ }
44464
+ if ('toConnectorConfig' in grant &&
44465
+ typeof grant.toConnectorConfig ===
44466
+ 'function') {
44467
+ const normalized = grant.toConnectorConfig();
44468
+ if (normalized.type !== BROADCAST_CHANNEL_CONNECTOR_TYPE) {
44469
+ throw new Error(`Unsupported grant connector type: ${normalized.type}`);
43845
44470
  }
43846
- throw new Error(`Unsupported grant type: ${grant.type}`);
44471
+ return this._buildConnectorConfigForSystem(systemId, normalized);
43847
44472
  }
43848
- const candidate = grant;
43849
- const config = {
44473
+ throw new Error(`Unsupported grant type: ${grant.type}`);
44474
+ }
44475
+ _buildConnectorConfigForSystem(systemId, baseConfig) {
44476
+ const localNodeId = this._requireLocalNodeId();
44477
+ const targetSystemId = this._normalizeNodeId(systemId);
44478
+ if (!targetSystemId) {
44479
+ throw new Error('BroadcastChannelListener requires a valid system id');
44480
+ }
44481
+ const candidate = baseConfig ?? null;
44482
+ const channelCandidate = candidate && 'channelName' in candidate
44483
+ ? candidate.channelName
44484
+ : undefined;
44485
+ const inboxCandidate = candidate && 'inboxCapacity' in candidate
44486
+ ? candidate.inboxCapacity
44487
+ : undefined;
44488
+ const initialWindowCandidate = candidate && 'initialWindow' in candidate
44489
+ ? candidate.initialWindow
44490
+ : undefined;
44491
+ const passiveCandidate = candidate && 'passive' in candidate
44492
+ ? candidate.passive
44493
+ : undefined;
44494
+ const targetCandidate = candidate && 'initialTargetNodeId' in candidate
44495
+ ? candidate.initialTargetNodeId
44496
+ : undefined;
44497
+ const channelName = typeof channelCandidate === 'string' && channelCandidate.trim().length > 0
44498
+ ? channelCandidate.trim()
44499
+ : this._channelName;
44500
+ const inboxCapacity = typeof inboxCandidate === 'number' &&
44501
+ Number.isFinite(inboxCandidate) &&
44502
+ inboxCandidate > 0
44503
+ ? Math.floor(inboxCandidate)
44504
+ : this._inboxCapacity;
44505
+ const initialWindow = typeof initialWindowCandidate === 'number' &&
44506
+ Number.isFinite(initialWindowCandidate) &&
44507
+ initialWindowCandidate > 0
44508
+ ? Math.floor(initialWindowCandidate)
44509
+ : undefined;
44510
+ const initialTargetNodeId = this._normalizeNodeId(targetCandidate) ?? targetSystemId;
44511
+ return {
43850
44512
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
43851
- channelName: this._channelName,
43852
- inboxCapacity: this._inboxCapacity,
44513
+ channelName,
44514
+ inboxCapacity,
44515
+ passive: typeof passiveCandidate === 'boolean' ? passiveCandidate : true,
44516
+ initialWindow,
44517
+ localNodeId,
44518
+ initialTargetNodeId,
43853
44519
  };
43854
- const channelCandidate = candidate.channelName ?? candidate['channel_name'];
43855
- if (typeof channelCandidate === 'string' && channelCandidate.trim().length > 0) {
43856
- config.channelName = channelCandidate.trim();
44520
+ }
44521
+ _requireLocalNodeId() {
44522
+ if (!this._routingNode) {
44523
+ throw new Error('BroadcastChannelListener requires routing node context');
43857
44524
  }
43858
- const inboxCandidate = candidate.inboxCapacity ?? candidate['inbox_capacity'];
43859
- if (typeof inboxCandidate === 'number' &&
43860
- Number.isFinite(inboxCandidate) &&
43861
- inboxCandidate > 0) {
43862
- config.inboxCapacity = Math.floor(inboxCandidate);
44525
+ const normalized = this._normalizeNodeId(this._routingNode.id);
44526
+ if (!normalized) {
44527
+ throw new Error('BroadcastChannelListener requires routing node with a stable identifier');
43863
44528
  }
43864
- return config;
44529
+ return normalized;
44530
+ }
44531
+ _normalizeNodeId(value) {
44532
+ if (typeof value !== 'string') {
44533
+ return null;
44534
+ }
44535
+ const trimmed = value.trim();
44536
+ return trimmed.length > 0 ? trimmed : null;
43865
44537
  }
43866
44538
  _monitorConnectorLifecycle(senderId, systemId, connector) {
43867
44539
  const maybeClosable = connector;