@naylence/runtime 0.3.5-test.961 → 0.3.5-test.963

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 (24) hide show
  1. package/dist/browser/index.cjs +290 -33
  2. package/dist/browser/index.mjs +290 -33
  3. package/dist/cjs/naylence/fame/connector/broadcast-channel-connector-factory.js +36 -0
  4. package/dist/cjs/naylence/fame/connector/broadcast-channel-connector.browser.js +131 -6
  5. package/dist/cjs/naylence/fame/connector/broadcast-channel-listener.js +91 -25
  6. package/dist/cjs/naylence/fame/node/upstream-session-manager.js +30 -0
  7. package/dist/cjs/version.js +2 -2
  8. package/dist/esm/naylence/fame/connector/broadcast-channel-connector-factory.js +36 -0
  9. package/dist/esm/naylence/fame/connector/broadcast-channel-connector.browser.js +131 -6
  10. package/dist/esm/naylence/fame/connector/broadcast-channel-listener.js +91 -25
  11. package/dist/esm/naylence/fame/node/upstream-session-manager.js +30 -0
  12. package/dist/esm/version.js +2 -2
  13. package/dist/node/index.cjs +290 -33
  14. package/dist/node/index.mjs +290 -33
  15. package/dist/node/node.cjs +290 -33
  16. package/dist/node/node.mjs +290 -33
  17. package/dist/types/naylence/fame/connector/broadcast-channel-connector-factory.d.ts +6 -0
  18. package/dist/types/naylence/fame/connector/broadcast-channel-connector.browser.d.ts +10 -0
  19. package/dist/types/naylence/fame/connector/broadcast-channel-connector.node.d.ts +1 -6
  20. package/dist/types/naylence/fame/connector/broadcast-channel-listener.d.ts +3 -0
  21. package/dist/types/naylence/fame/grants/broadcast-channel-connection-grant.d.ts +1 -1
  22. package/dist/types/naylence/fame/node/upstream-session-manager.d.ts +2 -0
  23. package/dist/types/version.d.ts +1 -1
  24. package/package.json +1 -1
@@ -13,12 +13,12 @@ import fastify from 'fastify';
13
13
  import websocketPlugin from '@fastify/websocket';
14
14
 
15
15
  // This file is auto-generated during build - do not edit manually
16
- // Generated from package.json version: 0.3.5-test.961
16
+ // Generated from package.json version: 0.3.5-test.963
17
17
  /**
18
18
  * The package version, injected at build time.
19
19
  * @internal
20
20
  */
21
- const VERSION = '0.3.5-test.961';
21
+ const VERSION = '0.3.5-test.963';
22
22
 
23
23
  /**
24
24
  * Fame protocol specific error classes with WebSocket close codes and proper inheritance.
@@ -9861,6 +9861,26 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
9861
9861
  }
9862
9862
  return null;
9863
9863
  }
9864
+ static normalizeNodeId(value) {
9865
+ if (typeof value !== 'string') {
9866
+ return null;
9867
+ }
9868
+ const trimmed = value.trim();
9869
+ return trimmed.length > 0 ? trimmed : null;
9870
+ }
9871
+ static normalizeTargetNodeId(value) {
9872
+ if (typeof value !== 'string') {
9873
+ return undefined;
9874
+ }
9875
+ const trimmed = value.trim();
9876
+ if (trimmed.length === 0) {
9877
+ return undefined;
9878
+ }
9879
+ if (trimmed === '*') {
9880
+ return '*';
9881
+ }
9882
+ return trimmed;
9883
+ }
9864
9884
  constructor(config, baseConfig = {}) {
9865
9885
  ensureBroadcastEnvironment();
9866
9886
  super(baseConfig);
@@ -9883,10 +9903,18 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
9883
9903
  this.inbox = new BoundedAsyncQueue(preferredCapacity);
9884
9904
  this.inboxCapacity = preferredCapacity;
9885
9905
  this.connectorId = BroadcastChannelConnector.generateConnectorId();
9906
+ const normalizedLocalNodeId = BroadcastChannelConnector.normalizeNodeId(config.localNodeId);
9907
+ if (!normalizedLocalNodeId) {
9908
+ throw new Error('BroadcastChannelConnector requires a non-empty localNodeId');
9909
+ }
9910
+ this.localNodeId = normalizedLocalNodeId;
9911
+ this.targetNodeId = BroadcastChannelConnector.normalizeTargetNodeId(config.initialTargetNodeId);
9886
9912
  this.channel = new BroadcastChannel(this.channelName);
9887
9913
  logger$_.debug('broadcast_channel_connector_created', {
9888
9914
  channel: this.channelName,
9889
9915
  connector_id: this.connectorId,
9916
+ local_node_id: this.localNodeId,
9917
+ target_node_id: this.targetNodeId ?? null,
9890
9918
  inbox_capacity: preferredCapacity,
9891
9919
  timestamp: new Date().toISOString(),
9892
9920
  });
@@ -9908,15 +9936,32 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
9908
9936
  ? message.constructor?.name ?? typeof message
9909
9937
  : typeof message,
9910
9938
  has_sender_id: Boolean(message?.senderId),
9939
+ has_sender_node_id: Boolean(message?.senderNodeId),
9911
9940
  });
9912
9941
  if (!message || typeof message !== 'object') {
9913
9942
  return;
9914
9943
  }
9915
9944
  const busMessage = message;
9916
- if (typeof busMessage.senderId !== 'string' || busMessage.senderId.length === 0) {
9945
+ const senderNodeId = BroadcastChannelConnector.normalizeNodeId(busMessage.senderNodeId);
9946
+ if (!senderNodeId) {
9947
+ logger$_.debug('broadcast_channel_message_rejected', {
9948
+ channel: this.channelName,
9949
+ connector_id: this.connectorId,
9950
+ reason: 'missing_sender_node_id',
9951
+ });
9917
9952
  return;
9918
9953
  }
9919
- if (busMessage.senderId === this.connectorId) {
9954
+ if (senderNodeId === this.localNodeId) {
9955
+ logger$_.debug('broadcast_channel_message_rejected', {
9956
+ channel: this.channelName,
9957
+ connector_id: this.connectorId,
9958
+ reason: 'self_echo',
9959
+ sender_node_id: senderNodeId,
9960
+ });
9961
+ return;
9962
+ }
9963
+ const incomingTargetNodeId = BroadcastChannelConnector.normalizeTargetNodeId(busMessage.targetNodeId);
9964
+ if (!this._shouldAcceptMessageFromBus(senderNodeId, incomingTargetNodeId)) {
9920
9965
  return;
9921
9966
  }
9922
9967
  const payload = BroadcastChannelConnector.coercePayload(busMessage.payload);
@@ -9930,11 +9975,13 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
9930
9975
  }
9931
9976
  logger$_.debug('broadcast_channel_message_received', {
9932
9977
  channel: this.channelName,
9933
- sender_id: busMessage.senderId,
9978
+ sender_id: message?.senderId,
9979
+ sender_node_id: senderNodeId,
9980
+ target_node_id: incomingTargetNodeId ?? null,
9934
9981
  connector_id: this.connectorId,
9935
9982
  payload_length: payload.byteLength,
9936
9983
  });
9937
- if (this._shouldSkipDuplicateAck(busMessage.senderId, payload)) {
9984
+ if (this._shouldSkipDuplicateAck(senderNodeId, payload)) {
9938
9985
  return;
9939
9986
  }
9940
9987
  try {
@@ -10078,12 +10125,17 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
10078
10125
  }
10079
10126
  async _transportSendBytes(data) {
10080
10127
  ensureBroadcastEnvironment();
10128
+ const targetNodeId = this.targetNodeId ?? '*';
10081
10129
  logger$_.debug('broadcast_channel_message_sending', {
10082
10130
  channel: this.channelName,
10083
10131
  sender_id: this.connectorId,
10132
+ sender_node_id: this.localNodeId,
10133
+ target_node_id: targetNodeId,
10084
10134
  });
10085
10135
  this.channel.postMessage({
10086
10136
  senderId: this.connectorId,
10137
+ senderNodeId: this.localNodeId,
10138
+ targetNodeId,
10087
10139
  payload: data,
10088
10140
  });
10089
10141
  }
@@ -10146,6 +10198,51 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
10146
10198
  }
10147
10199
  return rawOrEnvelope;
10148
10200
  }
10201
+ _isWildcardTarget() {
10202
+ return this.targetNodeId === '*' || typeof this.targetNodeId === 'undefined';
10203
+ }
10204
+ _shouldAcceptMessageFromBus(senderNodeId, targetNodeId) {
10205
+ if (this._isWildcardTarget()) {
10206
+ if (targetNodeId && targetNodeId !== '*') {
10207
+ logger$_.debug('broadcast_channel_message_rejected', {
10208
+ channel: this.channelName,
10209
+ connector_id: this.connectorId,
10210
+ reason: 'wildcard_target_mismatch',
10211
+ sender_node_id: senderNodeId,
10212
+ target_node_id: targetNodeId,
10213
+ local_node_id: this.localNodeId,
10214
+ });
10215
+ return false;
10216
+ }
10217
+ return true;
10218
+ }
10219
+ const expectedSender = this.targetNodeId;
10220
+ if (expectedSender && expectedSender !== '*' && senderNodeId !== expectedSender) {
10221
+ logger$_.debug('broadcast_channel_message_rejected', {
10222
+ channel: this.channelName,
10223
+ connector_id: this.connectorId,
10224
+ reason: 'unexpected_sender',
10225
+ expected_sender_node_id: expectedSender,
10226
+ sender_node_id: senderNodeId,
10227
+ local_node_id: this.localNodeId,
10228
+ });
10229
+ return false;
10230
+ }
10231
+ if (targetNodeId &&
10232
+ targetNodeId !== '*' &&
10233
+ targetNodeId !== this.localNodeId) {
10234
+ logger$_.debug('broadcast_channel_message_rejected', {
10235
+ channel: this.channelName,
10236
+ connector_id: this.connectorId,
10237
+ reason: 'unexpected_target',
10238
+ sender_node_id: senderNodeId,
10239
+ target_node_id: targetNodeId,
10240
+ local_node_id: this.localNodeId,
10241
+ });
10242
+ return false;
10243
+ }
10244
+ return true;
10245
+ }
10149
10246
  _describeInboxItem(item) {
10150
10247
  if (item instanceof Uint8Array) {
10151
10248
  return 'bytes';
@@ -10176,7 +10273,7 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
10176
10273
  const normalizedSenderId = typeof senderId === 'string' && senderId.length > 0
10177
10274
  ? senderId
10178
10275
  : undefined;
10179
- if (normalizedSenderId && normalizedSenderId !== this.connectorId) {
10276
+ if (normalizedSenderId && normalizedSenderId !== this.localNodeId) {
10180
10277
  logger$_.debug('broadcast_channel_duplicate_ack_bypass_non_self', {
10181
10278
  channel: this.channelName,
10182
10279
  connector_id: this.connectorId,
@@ -10216,7 +10313,7 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
10216
10313
  return false;
10217
10314
  }
10218
10315
  const senderId = this._extractSenderIdFromInboxItem(item);
10219
- if (senderId && senderId !== this.connectorId) {
10316
+ if (senderId && senderId !== this.localNodeId) {
10220
10317
  logger$_.debug('broadcast_channel_duplicate_ack_bypass_non_self', {
10221
10318
  channel: this.channelName,
10222
10319
  connector_id: this.connectorId,
@@ -10312,6 +10409,34 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
10312
10409
  });
10313
10410
  }
10314
10411
  }
10412
+ setTargetNodeId(nodeId) {
10413
+ const normalized = BroadcastChannelConnector.normalizeNodeId(nodeId);
10414
+ if (!normalized) {
10415
+ throw new Error('BroadcastChannelConnector target node id must be a non-empty string');
10416
+ }
10417
+ if (normalized === '*') {
10418
+ this.setWildcardTarget();
10419
+ return;
10420
+ }
10421
+ this.targetNodeId = normalized;
10422
+ logger$_.debug('broadcast_channel_target_updated', {
10423
+ channel: this.channelName,
10424
+ connector_id: this.connectorId,
10425
+ local_node_id: this.localNodeId,
10426
+ target_node_id: this.targetNodeId,
10427
+ target_mode: 'direct',
10428
+ });
10429
+ }
10430
+ setWildcardTarget() {
10431
+ this.targetNodeId = '*';
10432
+ logger$_.debug('broadcast_channel_target_updated', {
10433
+ channel: this.channelName,
10434
+ connector_id: this.connectorId,
10435
+ local_node_id: this.localNodeId,
10436
+ target_node_id: this.targetNodeId,
10437
+ target_mode: 'wildcard',
10438
+ });
10439
+ }
10315
10440
  _trimSeenAcks(now) {
10316
10441
  while (this.seenAckOrder.length > 0) {
10317
10442
  const candidate = this.seenAckOrder[0];
@@ -10783,6 +10908,20 @@ class UpstreamSessionManager extends TaskSpawner {
10783
10908
  waitEvent(event, signal) {
10784
10909
  return signal ? event.wait({ signal }) : event.wait();
10785
10910
  }
10911
+ _getLocalNodeId() {
10912
+ const normalized = this._normalizeNodeId(this.node.id);
10913
+ if (!normalized) {
10914
+ throw new Error('UpstreamSessionManager requires node with a stable identifier');
10915
+ }
10916
+ return normalized;
10917
+ }
10918
+ _normalizeNodeId(value) {
10919
+ if (typeof value !== 'string') {
10920
+ return null;
10921
+ }
10922
+ const trimmed = value.trim();
10923
+ return trimmed.length > 0 ? trimmed : null;
10924
+ }
10786
10925
  async connectCycle() {
10787
10926
  if (!this.admissionClient) {
10788
10927
  throw new FameConnectError('Admission client is required to attach upstream');
@@ -10804,6 +10943,8 @@ class UpstreamSessionManager extends TaskSpawner {
10804
10943
  await this.onWelcome(welcome.frame);
10805
10944
  const connector = await ConnectorFactory.createConnector(grant, {
10806
10945
  systemId: welcome.frame.systemId,
10946
+ localNodeId: this._getLocalNodeId(),
10947
+ initialTargetNodeId: '*',
10807
10948
  });
10808
10949
  await connector.start(this.wrappedHandler);
10809
10950
  this.connector = connector;
@@ -10829,6 +10970,20 @@ class UpstreamSessionManager extends TaskSpawner {
10829
10970
  }
10830
10971
  const attachInfo = await this.attachClient.attach(this.node, this.outboundOriginType, connector, welcome.frame, this.wrappedHandler, this.getKeys() ?? undefined, callbackGrants);
10831
10972
  this.targetSystemId = attachInfo.targetSystemId ?? null;
10973
+ if (this.targetSystemId) {
10974
+ const targetAware = connector;
10975
+ if (typeof targetAware.setTargetNodeId === 'function') {
10976
+ try {
10977
+ targetAware.setTargetNodeId(this.targetSystemId);
10978
+ }
10979
+ catch (error) {
10980
+ logger$Z.warning('broadcast_channel_target_apply_failed', {
10981
+ error: error instanceof Error ? error.message : String(error),
10982
+ target_node_id: this.targetSystemId,
10983
+ });
10984
+ }
10985
+ }
10986
+ }
10832
10987
  await this.onAttach(attachInfo, connector);
10833
10988
  // Close the admission client immediately after attach completes
10834
10989
  // This releases HTTP keep-alive connections (Node.js fetch/undici requires explicit cleanup)
@@ -28686,8 +28841,16 @@ class BroadcastChannelConnectorFactory extends ConnectorFactory {
28686
28841
  }
28687
28842
  const normalized = this._normalizeConfig(config);
28688
28843
  const options = (factoryArgs[0] ?? {});
28844
+ const normalizedLocalNodeFromConfig = this._normalizeNodeId(normalized.localNodeId);
28845
+ const localNodeId = this._normalizeNodeId(options.localNodeId) ?? normalizedLocalNodeFromConfig;
28846
+ if (!localNodeId) {
28847
+ throw new Error('BroadcastChannelConnectorFactory requires a localNodeId from config or create() options');
28848
+ }
28689
28849
  const channelName = normalized.channelName ?? DEFAULT_CHANNEL$5;
28690
28850
  const inboxCapacity = normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY$5;
28851
+ const targetFromOptions = this._normalizeTargetNodeId(options.initialTargetNodeId);
28852
+ const targetFromConfig = this._normalizeTargetNodeId(normalized.initialTargetNodeId);
28853
+ const resolvedTarget = targetFromOptions ?? targetFromConfig ?? '*';
28691
28854
  const baseConfig = {
28692
28855
  drainTimeout: normalized.drainTimeout,
28693
28856
  flowControl: normalized.flowControl,
@@ -28702,6 +28865,8 @@ class BroadcastChannelConnectorFactory extends ConnectorFactory {
28702
28865
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
28703
28866
  channelName,
28704
28867
  inboxCapacity,
28868
+ localNodeId,
28869
+ initialTargetNodeId: resolvedTarget,
28705
28870
  };
28706
28871
  const connector = new BroadcastChannelConnector(connectorConfig, baseConfig);
28707
28872
  if (options.authorization) {
@@ -28725,11 +28890,21 @@ class BroadcastChannelConnectorFactory extends ConnectorFactory {
28725
28890
  normalized.channelName = channel.trim();
28726
28891
  }
28727
28892
  const capacity = candidate.inboxCapacity ?? candidate['inbox_capacity'];
28893
+ const initialTargetNodeId = candidate.initialTargetNodeId ?? candidate['initial_target_node_id'];
28894
+ const normalizedTarget = this._normalizeTargetNodeId(initialTargetNodeId);
28895
+ if (normalizedTarget) {
28896
+ normalized.initialTargetNodeId = normalizedTarget;
28897
+ }
28728
28898
  if (typeof capacity === 'number' &&
28729
28899
  Number.isFinite(capacity) &&
28730
28900
  capacity > 0) {
28731
28901
  normalized.inboxCapacity = Math.floor(capacity);
28732
28902
  }
28903
+ const localNodeId = candidate.localNodeId ?? candidate['local_node_id'];
28904
+ const normalizedLocalNodeId = this._normalizeNodeId(localNodeId);
28905
+ if (normalizedLocalNodeId) {
28906
+ normalized.localNodeId = normalizedLocalNodeId;
28907
+ }
28733
28908
  if (typeof candidate.flowControl === 'boolean') {
28734
28909
  normalized.flowControl = candidate.flowControl;
28735
28910
  }
@@ -28768,6 +28943,22 @@ class BroadcastChannelConnectorFactory extends ConnectorFactory {
28768
28943
  normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY$5;
28769
28944
  return normalized;
28770
28945
  }
28946
+ _normalizeNodeId(value) {
28947
+ if (typeof value !== 'string') {
28948
+ return null;
28949
+ }
28950
+ const trimmed = value.trim();
28951
+ return trimmed.length > 0 ? trimmed : null;
28952
+ }
28953
+ _normalizeTargetNodeId(value) {
28954
+ if (value === undefined || value === null) {
28955
+ return undefined;
28956
+ }
28957
+ if (value === '*') {
28958
+ return '*';
28959
+ }
28960
+ return this._normalizeNodeId(value) ?? undefined;
28961
+ }
28771
28962
  }
28772
28963
 
28773
28964
  var broadcastChannelConnectorFactory = /*#__PURE__*/Object.freeze({
@@ -36345,7 +36536,7 @@ class BroadcastChannelListener extends TransportListener {
36345
36536
  node: routingNode,
36346
36537
  });
36347
36538
  const selection = defaultGrantSelectionPolicy.selectCallbackGrant(selectionContext);
36348
- connectorConfig = this._grantToConnectorConfig(selection.grant);
36539
+ connectorConfig = this._grantToConnectorConfig(selection.grant, systemId);
36349
36540
  }
36350
36541
  catch (error) {
36351
36542
  logger$a.debug('broadcast_channel_listener_grant_selection_failed', {
@@ -36354,13 +36545,20 @@ class BroadcastChannelListener extends TransportListener {
36354
36545
  error: error instanceof Error ? error.message : String(error),
36355
36546
  });
36356
36547
  connectorConfig =
36357
- this._extractBroadcastConnectorConfig(frame) ??
36358
- {
36548
+ this._extractBroadcastConnectorConfig(frame, systemId) ??
36549
+ this._buildConnectorConfigForSystem(systemId, {
36359
36550
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
36360
36551
  channelName: this._channelName,
36361
36552
  inboxCapacity: this._inboxCapacity,
36362
36553
  passive: true,
36363
- };
36554
+ });
36555
+ }
36556
+ if (!connectorConfig) {
36557
+ logger$a.error('broadcast_channel_listener_missing_connector_config', {
36558
+ sender_id: params.senderId,
36559
+ system_id: systemId,
36560
+ });
36561
+ return null;
36364
36562
  }
36365
36563
  try {
36366
36564
  const connector = await routingNode.createOriginConnector({
@@ -36386,7 +36584,7 @@ class BroadcastChannelListener extends TransportListener {
36386
36584
  return null;
36387
36585
  }
36388
36586
  }
36389
- _extractBroadcastConnectorConfig(frame) {
36587
+ _extractBroadcastConnectorConfig(frame, systemId) {
36390
36588
  const rawGrants = frame.callbackGrants;
36391
36589
  if (!Array.isArray(rawGrants)) {
36392
36590
  return null;
@@ -36397,7 +36595,10 @@ class BroadcastChannelListener extends TransportListener {
36397
36595
  (grant.type === BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE ||
36398
36596
  grant.type === BROADCAST_CHANNEL_CONNECTOR_TYPE)) {
36399
36597
  try {
36400
- return this._grantToConnectorConfig(grant);
36598
+ if (grant.type === BROADCAST_CHANNEL_CONNECTOR_TYPE) {
36599
+ return this._buildConnectorConfigForSystem(systemId, grant);
36600
+ }
36601
+ return this._buildConnectorConfigForSystem(systemId, broadcastChannelGrantToConnectorConfig(grant));
36401
36602
  }
36402
36603
  catch (error) {
36403
36604
  logger$a.debug('broadcast_channel_listener_grant_normalization_failed', {
@@ -36408,31 +36609,87 @@ class BroadcastChannelListener extends TransportListener {
36408
36609
  }
36409
36610
  return null;
36410
36611
  }
36411
- _grantToConnectorConfig(grant) {
36412
- if (grant.type !== BROADCAST_CHANNEL_CONNECTOR_TYPE) {
36413
- if (grant.type === BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE) {
36414
- return broadcastChannelGrantToConnectorConfig(grant);
36612
+ _grantToConnectorConfig(grant, systemId) {
36613
+ if (grant.type === BROADCAST_CHANNEL_CONNECTOR_TYPE) {
36614
+ return this._buildConnectorConfigForSystem(systemId, grant);
36615
+ }
36616
+ if (grant.type === BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE) {
36617
+ return this._buildConnectorConfigForSystem(systemId, broadcastChannelGrantToConnectorConfig(grant));
36618
+ }
36619
+ if ('toConnectorConfig' in grant &&
36620
+ typeof grant.toConnectorConfig ===
36621
+ 'function') {
36622
+ const normalized = grant.toConnectorConfig();
36623
+ if (normalized.type !== BROADCAST_CHANNEL_CONNECTOR_TYPE) {
36624
+ throw new Error(`Unsupported grant connector type: ${normalized.type}`);
36415
36625
  }
36416
- throw new Error(`Unsupported grant type: ${grant.type}`);
36626
+ return this._buildConnectorConfigForSystem(systemId, normalized);
36417
36627
  }
36418
- const candidate = grant;
36419
- const config = {
36628
+ throw new Error(`Unsupported grant type: ${grant.type}`);
36629
+ }
36630
+ _buildConnectorConfigForSystem(systemId, baseConfig) {
36631
+ const localNodeId = this._requireLocalNodeId();
36632
+ const targetSystemId = this._normalizeNodeId(systemId);
36633
+ if (!targetSystemId) {
36634
+ throw new Error('BroadcastChannelListener requires a valid system id');
36635
+ }
36636
+ const candidate = baseConfig ?? null;
36637
+ const channelCandidate = candidate && 'channelName' in candidate
36638
+ ? candidate.channelName
36639
+ : undefined;
36640
+ const inboxCandidate = candidate && 'inboxCapacity' in candidate
36641
+ ? candidate.inboxCapacity
36642
+ : undefined;
36643
+ const initialWindowCandidate = candidate && 'initialWindow' in candidate
36644
+ ? candidate.initialWindow
36645
+ : undefined;
36646
+ const passiveCandidate = candidate && 'passive' in candidate
36647
+ ? candidate.passive
36648
+ : undefined;
36649
+ const targetCandidate = candidate && 'initialTargetNodeId' in candidate
36650
+ ? candidate.initialTargetNodeId
36651
+ : undefined;
36652
+ const channelName = typeof channelCandidate === 'string' && channelCandidate.trim().length > 0
36653
+ ? channelCandidate.trim()
36654
+ : this._channelName;
36655
+ const inboxCapacity = typeof inboxCandidate === 'number' &&
36656
+ Number.isFinite(inboxCandidate) &&
36657
+ inboxCandidate > 0
36658
+ ? Math.floor(inboxCandidate)
36659
+ : this._inboxCapacity;
36660
+ const initialWindow = typeof initialWindowCandidate === 'number' &&
36661
+ Number.isFinite(initialWindowCandidate) &&
36662
+ initialWindowCandidate > 0
36663
+ ? Math.floor(initialWindowCandidate)
36664
+ : undefined;
36665
+ const initialTargetNodeId = this._normalizeNodeId(targetCandidate) ?? targetSystemId;
36666
+ return {
36420
36667
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
36421
- channelName: this._channelName,
36422
- inboxCapacity: this._inboxCapacity,
36423
- passive: true,
36668
+ channelName,
36669
+ inboxCapacity,
36670
+ passive: typeof passiveCandidate === 'boolean' ? passiveCandidate : true,
36671
+ initialWindow,
36672
+ localNodeId,
36673
+ initialTargetNodeId,
36424
36674
  };
36425
- const channelCandidate = candidate.channelName ?? candidate['channel_name'];
36426
- if (typeof channelCandidate === 'string' && channelCandidate.trim().length > 0) {
36427
- config.channelName = channelCandidate.trim();
36675
+ }
36676
+ _requireLocalNodeId() {
36677
+ if (!this._routingNode) {
36678
+ throw new Error('BroadcastChannelListener requires routing node context');
36428
36679
  }
36429
- const inboxCandidate = candidate.inboxCapacity ?? candidate['inbox_capacity'];
36430
- if (typeof inboxCandidate === 'number' &&
36431
- Number.isFinite(inboxCandidate) &&
36432
- inboxCandidate > 0) {
36433
- config.inboxCapacity = Math.floor(inboxCandidate);
36680
+ const normalized = this._normalizeNodeId(this._routingNode.sid) ??
36681
+ this._normalizeNodeId(this._routingNode.id);
36682
+ if (!normalized) {
36683
+ throw new Error('BroadcastChannelListener requires routing node with a stable identifier');
36434
36684
  }
36435
- return config;
36685
+ return normalized;
36686
+ }
36687
+ _normalizeNodeId(value) {
36688
+ if (typeof value !== 'string') {
36689
+ return null;
36690
+ }
36691
+ const trimmed = value.trim();
36692
+ return trimmed.length > 0 ? trimmed : null;
36436
36693
  }
36437
36694
  _monitorConnectorLifecycle(senderId, systemId, connector) {
36438
36695
  const maybeClosable = connector;