@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
@@ -49,6 +49,26 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
49
49
  }
50
50
  return null;
51
51
  }
52
+ static normalizeNodeId(value) {
53
+ if (typeof value !== 'string') {
54
+ return null;
55
+ }
56
+ const trimmed = value.trim();
57
+ return trimmed.length > 0 ? trimmed : null;
58
+ }
59
+ static normalizeTargetNodeId(value) {
60
+ if (typeof value !== 'string') {
61
+ return undefined;
62
+ }
63
+ const trimmed = value.trim();
64
+ if (trimmed.length === 0) {
65
+ return undefined;
66
+ }
67
+ if (trimmed === '*') {
68
+ return '*';
69
+ }
70
+ return trimmed;
71
+ }
52
72
  constructor(config, baseConfig = {}) {
53
73
  ensureBroadcastEnvironment();
54
74
  super(baseConfig);
@@ -71,10 +91,18 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
71
91
  this.inbox = new bounded_async_queue_js_1.BoundedAsyncQueue(preferredCapacity);
72
92
  this.inboxCapacity = preferredCapacity;
73
93
  this.connectorId = BroadcastChannelConnector.generateConnectorId();
94
+ const normalizedLocalNodeId = BroadcastChannelConnector.normalizeNodeId(config.localNodeId);
95
+ if (!normalizedLocalNodeId) {
96
+ throw new Error('BroadcastChannelConnector requires a non-empty localNodeId');
97
+ }
98
+ this.localNodeId = normalizedLocalNodeId;
99
+ this.targetNodeId = BroadcastChannelConnector.normalizeTargetNodeId(config.initialTargetNodeId);
74
100
  this.channel = new BroadcastChannel(this.channelName);
75
101
  logger.debug('broadcast_channel_connector_created', {
76
102
  channel: this.channelName,
77
103
  connector_id: this.connectorId,
104
+ local_node_id: this.localNodeId,
105
+ target_node_id: this.targetNodeId ?? null,
78
106
  inbox_capacity: preferredCapacity,
79
107
  timestamp: new Date().toISOString(),
80
108
  });
@@ -96,15 +124,32 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
96
124
  ? message.constructor?.name ?? typeof message
97
125
  : typeof message,
98
126
  has_sender_id: Boolean(message?.senderId),
127
+ has_sender_node_id: Boolean(message?.senderNodeId),
99
128
  });
100
129
  if (!message || typeof message !== 'object') {
101
130
  return;
102
131
  }
103
132
  const busMessage = message;
104
- if (typeof busMessage.senderId !== 'string' || busMessage.senderId.length === 0) {
133
+ const senderNodeId = BroadcastChannelConnector.normalizeNodeId(busMessage.senderNodeId);
134
+ if (!senderNodeId) {
135
+ logger.debug('broadcast_channel_message_rejected', {
136
+ channel: this.channelName,
137
+ connector_id: this.connectorId,
138
+ reason: 'missing_sender_node_id',
139
+ });
140
+ return;
141
+ }
142
+ if (senderNodeId === this.localNodeId) {
143
+ logger.debug('broadcast_channel_message_rejected', {
144
+ channel: this.channelName,
145
+ connector_id: this.connectorId,
146
+ reason: 'self_echo',
147
+ sender_node_id: senderNodeId,
148
+ });
105
149
  return;
106
150
  }
107
- if (busMessage.senderId === this.connectorId) {
151
+ const incomingTargetNodeId = BroadcastChannelConnector.normalizeTargetNodeId(busMessage.targetNodeId);
152
+ if (!this._shouldAcceptMessageFromBus(senderNodeId, incomingTargetNodeId)) {
108
153
  return;
109
154
  }
110
155
  const payload = BroadcastChannelConnector.coercePayload(busMessage.payload);
@@ -118,11 +163,13 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
118
163
  }
119
164
  logger.debug('broadcast_channel_message_received', {
120
165
  channel: this.channelName,
121
- sender_id: busMessage.senderId,
166
+ sender_id: message?.senderId,
167
+ sender_node_id: senderNodeId,
168
+ target_node_id: incomingTargetNodeId ?? null,
122
169
  connector_id: this.connectorId,
123
170
  payload_length: payload.byteLength,
124
171
  });
125
- if (this._shouldSkipDuplicateAck(busMessage.senderId, payload)) {
172
+ if (this._shouldSkipDuplicateAck(senderNodeId, payload)) {
126
173
  return;
127
174
  }
128
175
  try {
@@ -266,12 +313,17 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
266
313
  }
267
314
  async _transportSendBytes(data) {
268
315
  ensureBroadcastEnvironment();
316
+ const targetNodeId = this.targetNodeId ?? '*';
269
317
  logger.debug('broadcast_channel_message_sending', {
270
318
  channel: this.channelName,
271
319
  sender_id: this.connectorId,
320
+ sender_node_id: this.localNodeId,
321
+ target_node_id: targetNodeId,
272
322
  });
273
323
  this.channel.postMessage({
274
324
  senderId: this.connectorId,
325
+ senderNodeId: this.localNodeId,
326
+ targetNodeId,
275
327
  payload: data,
276
328
  });
277
329
  }
@@ -334,6 +386,51 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
334
386
  }
335
387
  return rawOrEnvelope;
336
388
  }
389
+ _isWildcardTarget() {
390
+ return this.targetNodeId === '*' || typeof this.targetNodeId === 'undefined';
391
+ }
392
+ _shouldAcceptMessageFromBus(senderNodeId, targetNodeId) {
393
+ if (this._isWildcardTarget()) {
394
+ if (targetNodeId && targetNodeId !== '*') {
395
+ logger.debug('broadcast_channel_message_rejected', {
396
+ channel: this.channelName,
397
+ connector_id: this.connectorId,
398
+ reason: 'wildcard_target_mismatch',
399
+ sender_node_id: senderNodeId,
400
+ target_node_id: targetNodeId,
401
+ local_node_id: this.localNodeId,
402
+ });
403
+ return false;
404
+ }
405
+ return true;
406
+ }
407
+ const expectedSender = this.targetNodeId;
408
+ if (expectedSender && expectedSender !== '*' && senderNodeId !== expectedSender) {
409
+ logger.debug('broadcast_channel_message_rejected', {
410
+ channel: this.channelName,
411
+ connector_id: this.connectorId,
412
+ reason: 'unexpected_sender',
413
+ expected_sender_node_id: expectedSender,
414
+ sender_node_id: senderNodeId,
415
+ local_node_id: this.localNodeId,
416
+ });
417
+ return false;
418
+ }
419
+ if (targetNodeId &&
420
+ targetNodeId !== '*' &&
421
+ targetNodeId !== this.localNodeId) {
422
+ logger.debug('broadcast_channel_message_rejected', {
423
+ channel: this.channelName,
424
+ connector_id: this.connectorId,
425
+ reason: 'unexpected_target',
426
+ sender_node_id: senderNodeId,
427
+ target_node_id: targetNodeId,
428
+ local_node_id: this.localNodeId,
429
+ });
430
+ return false;
431
+ }
432
+ return true;
433
+ }
337
434
  _describeInboxItem(item) {
338
435
  if (item instanceof Uint8Array) {
339
436
  return 'bytes';
@@ -364,7 +461,7 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
364
461
  const normalizedSenderId = typeof senderId === 'string' && senderId.length > 0
365
462
  ? senderId
366
463
  : undefined;
367
- if (normalizedSenderId && normalizedSenderId !== this.connectorId) {
464
+ if (normalizedSenderId && normalizedSenderId !== this.localNodeId) {
368
465
  logger.debug('broadcast_channel_duplicate_ack_bypass_non_self', {
369
466
  channel: this.channelName,
370
467
  connector_id: this.connectorId,
@@ -404,7 +501,7 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
404
501
  return false;
405
502
  }
406
503
  const senderId = this._extractSenderIdFromInboxItem(item);
407
- if (senderId && senderId !== this.connectorId) {
504
+ if (senderId && senderId !== this.localNodeId) {
408
505
  logger.debug('broadcast_channel_duplicate_ack_bypass_non_self', {
409
506
  channel: this.channelName,
410
507
  connector_id: this.connectorId,
@@ -500,6 +597,34 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
500
597
  });
501
598
  }
502
599
  }
600
+ setTargetNodeId(nodeId) {
601
+ const normalized = BroadcastChannelConnector.normalizeNodeId(nodeId);
602
+ if (!normalized) {
603
+ throw new Error('BroadcastChannelConnector target node id must be a non-empty string');
604
+ }
605
+ if (normalized === '*') {
606
+ this.setWildcardTarget();
607
+ return;
608
+ }
609
+ this.targetNodeId = normalized;
610
+ logger.debug('broadcast_channel_target_updated', {
611
+ channel: this.channelName,
612
+ connector_id: this.connectorId,
613
+ local_node_id: this.localNodeId,
614
+ target_node_id: this.targetNodeId,
615
+ target_mode: 'direct',
616
+ });
617
+ }
618
+ setWildcardTarget() {
619
+ this.targetNodeId = '*';
620
+ logger.debug('broadcast_channel_target_updated', {
621
+ channel: this.channelName,
622
+ connector_id: this.connectorId,
623
+ local_node_id: this.localNodeId,
624
+ target_node_id: this.targetNodeId,
625
+ target_mode: 'wildcard',
626
+ });
627
+ }
503
628
  _trimSeenAcks(now) {
504
629
  while (this.seenAckOrder.length > 0) {
505
630
  const candidate = this.seenAckOrder[0];
@@ -328,7 +328,7 @@ class BroadcastChannelListener extends transport_listener_js_1.TransportListener
328
328
  node: routingNode,
329
329
  });
330
330
  const selection = grant_selection_policy_js_1.defaultGrantSelectionPolicy.selectCallbackGrant(selectionContext);
331
- connectorConfig = this._grantToConnectorConfig(selection.grant);
331
+ connectorConfig = this._grantToConnectorConfig(selection.grant, systemId);
332
332
  }
333
333
  catch (error) {
334
334
  logger.debug('broadcast_channel_listener_grant_selection_failed', {
@@ -337,13 +337,20 @@ class BroadcastChannelListener extends transport_listener_js_1.TransportListener
337
337
  error: error instanceof Error ? error.message : String(error),
338
338
  });
339
339
  connectorConfig =
340
- this._extractBroadcastConnectorConfig(frame) ??
341
- {
340
+ this._extractBroadcastConnectorConfig(frame, systemId) ??
341
+ this._buildConnectorConfigForSystem(systemId, {
342
342
  type: broadcast_channel_connector_js_1.BROADCAST_CHANNEL_CONNECTOR_TYPE,
343
343
  channelName: this._channelName,
344
344
  inboxCapacity: this._inboxCapacity,
345
345
  passive: true,
346
- };
346
+ });
347
+ }
348
+ if (!connectorConfig) {
349
+ logger.error('broadcast_channel_listener_missing_connector_config', {
350
+ sender_id: params.senderId,
351
+ system_id: systemId,
352
+ });
353
+ return null;
347
354
  }
348
355
  try {
349
356
  const connector = await routingNode.createOriginConnector({
@@ -369,7 +376,7 @@ class BroadcastChannelListener extends transport_listener_js_1.TransportListener
369
376
  return null;
370
377
  }
371
378
  }
372
- _extractBroadcastConnectorConfig(frame) {
379
+ _extractBroadcastConnectorConfig(frame, systemId) {
373
380
  const rawGrants = frame.callbackGrants;
374
381
  if (!Array.isArray(rawGrants)) {
375
382
  return null;
@@ -380,7 +387,10 @@ class BroadcastChannelListener extends transport_listener_js_1.TransportListener
380
387
  (grant.type === broadcast_channel_connection_grant_js_1.BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE ||
381
388
  grant.type === broadcast_channel_connector_js_1.BROADCAST_CHANNEL_CONNECTOR_TYPE)) {
382
389
  try {
383
- return this._grantToConnectorConfig(grant);
390
+ if (grant.type === broadcast_channel_connector_js_1.BROADCAST_CHANNEL_CONNECTOR_TYPE) {
391
+ return this._buildConnectorConfigForSystem(systemId, grant);
392
+ }
393
+ return this._buildConnectorConfigForSystem(systemId, (0, broadcast_channel_connection_grant_js_1.broadcastChannelGrantToConnectorConfig)(grant));
384
394
  }
385
395
  catch (error) {
386
396
  logger.debug('broadcast_channel_listener_grant_normalization_failed', {
@@ -391,31 +401,87 @@ class BroadcastChannelListener extends transport_listener_js_1.TransportListener
391
401
  }
392
402
  return null;
393
403
  }
394
- _grantToConnectorConfig(grant) {
395
- if (grant.type !== broadcast_channel_connector_js_1.BROADCAST_CHANNEL_CONNECTOR_TYPE) {
396
- if (grant.type === broadcast_channel_connection_grant_js_1.BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE) {
397
- return (0, broadcast_channel_connection_grant_js_1.broadcastChannelGrantToConnectorConfig)(grant);
404
+ _grantToConnectorConfig(grant, systemId) {
405
+ if (grant.type === broadcast_channel_connector_js_1.BROADCAST_CHANNEL_CONNECTOR_TYPE) {
406
+ return this._buildConnectorConfigForSystem(systemId, grant);
407
+ }
408
+ if (grant.type === broadcast_channel_connection_grant_js_1.BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE) {
409
+ return this._buildConnectorConfigForSystem(systemId, (0, broadcast_channel_connection_grant_js_1.broadcastChannelGrantToConnectorConfig)(grant));
410
+ }
411
+ if ('toConnectorConfig' in grant &&
412
+ typeof grant.toConnectorConfig ===
413
+ 'function') {
414
+ const normalized = grant.toConnectorConfig();
415
+ if (normalized.type !== broadcast_channel_connector_js_1.BROADCAST_CHANNEL_CONNECTOR_TYPE) {
416
+ throw new Error(`Unsupported grant connector type: ${normalized.type}`);
398
417
  }
399
- throw new Error(`Unsupported grant type: ${grant.type}`);
418
+ return this._buildConnectorConfigForSystem(systemId, normalized);
400
419
  }
401
- const candidate = grant;
402
- const config = {
420
+ throw new Error(`Unsupported grant type: ${grant.type}`);
421
+ }
422
+ _buildConnectorConfigForSystem(systemId, baseConfig) {
423
+ const localNodeId = this._requireLocalNodeId();
424
+ const targetSystemId = this._normalizeNodeId(systemId);
425
+ if (!targetSystemId) {
426
+ throw new Error('BroadcastChannelListener requires a valid system id');
427
+ }
428
+ const candidate = baseConfig ?? null;
429
+ const channelCandidate = candidate && 'channelName' in candidate
430
+ ? candidate.channelName
431
+ : undefined;
432
+ const inboxCandidate = candidate && 'inboxCapacity' in candidate
433
+ ? candidate.inboxCapacity
434
+ : undefined;
435
+ const initialWindowCandidate = candidate && 'initialWindow' in candidate
436
+ ? candidate.initialWindow
437
+ : undefined;
438
+ const passiveCandidate = candidate && 'passive' in candidate
439
+ ? candidate.passive
440
+ : undefined;
441
+ const targetCandidate = candidate && 'initialTargetNodeId' in candidate
442
+ ? candidate.initialTargetNodeId
443
+ : undefined;
444
+ const channelName = typeof channelCandidate === 'string' && channelCandidate.trim().length > 0
445
+ ? channelCandidate.trim()
446
+ : this._channelName;
447
+ const inboxCapacity = typeof inboxCandidate === 'number' &&
448
+ Number.isFinite(inboxCandidate) &&
449
+ inboxCandidate > 0
450
+ ? Math.floor(inboxCandidate)
451
+ : this._inboxCapacity;
452
+ const initialWindow = typeof initialWindowCandidate === 'number' &&
453
+ Number.isFinite(initialWindowCandidate) &&
454
+ initialWindowCandidate > 0
455
+ ? Math.floor(initialWindowCandidate)
456
+ : undefined;
457
+ const initialTargetNodeId = this._normalizeNodeId(targetCandidate) ?? targetSystemId;
458
+ return {
403
459
  type: broadcast_channel_connector_js_1.BROADCAST_CHANNEL_CONNECTOR_TYPE,
404
- channelName: this._channelName,
405
- inboxCapacity: this._inboxCapacity,
406
- passive: true,
460
+ channelName,
461
+ inboxCapacity,
462
+ passive: typeof passiveCandidate === 'boolean' ? passiveCandidate : true,
463
+ initialWindow,
464
+ localNodeId,
465
+ initialTargetNodeId,
407
466
  };
408
- const channelCandidate = candidate.channelName ?? candidate['channel_name'];
409
- if (typeof channelCandidate === 'string' && channelCandidate.trim().length > 0) {
410
- config.channelName = channelCandidate.trim();
467
+ }
468
+ _requireLocalNodeId() {
469
+ if (!this._routingNode) {
470
+ throw new Error('BroadcastChannelListener requires routing node context');
411
471
  }
412
- const inboxCandidate = candidate.inboxCapacity ?? candidate['inbox_capacity'];
413
- if (typeof inboxCandidate === 'number' &&
414
- Number.isFinite(inboxCandidate) &&
415
- inboxCandidate > 0) {
416
- config.inboxCapacity = Math.floor(inboxCandidate);
472
+ const normalized = this._normalizeNodeId(this._routingNode.sid) ??
473
+ this._normalizeNodeId(this._routingNode.id);
474
+ if (!normalized) {
475
+ throw new Error('BroadcastChannelListener requires routing node with a stable identifier');
476
+ }
477
+ return normalized;
478
+ }
479
+ _normalizeNodeId(value) {
480
+ if (typeof value !== 'string') {
481
+ return null;
417
482
  }
418
- return config;
483
+ const trimmed = value.trim();
484
+ return trimmed.length > 0 ? trimmed : null;
419
485
  }
420
486
  _monitorConnectorLifecycle(senderId, systemId, connector) {
421
487
  const maybeClosable = connector;
@@ -280,6 +280,20 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
280
280
  waitEvent(event, signal) {
281
281
  return signal ? event.wait({ signal }) : event.wait();
282
282
  }
283
+ _getLocalNodeId() {
284
+ const normalized = this._normalizeNodeId(this.node.id);
285
+ if (!normalized) {
286
+ throw new Error('UpstreamSessionManager requires node with a stable identifier');
287
+ }
288
+ return normalized;
289
+ }
290
+ _normalizeNodeId(value) {
291
+ if (typeof value !== 'string') {
292
+ return null;
293
+ }
294
+ const trimmed = value.trim();
295
+ return trimmed.length > 0 ? trimmed : null;
296
+ }
283
297
  async connectCycle() {
284
298
  if (!this.admissionClient) {
285
299
  throw new errors_js_1.FameConnectError('Admission client is required to attach upstream');
@@ -301,6 +315,8 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
301
315
  await this.onWelcome(welcome.frame);
302
316
  const connector = await connector_factory_js_1.ConnectorFactory.createConnector(grant, {
303
317
  systemId: welcome.frame.systemId,
318
+ localNodeId: this._getLocalNodeId(),
319
+ initialTargetNodeId: '*',
304
320
  });
305
321
  await connector.start(this.wrappedHandler);
306
322
  this.connector = connector;
@@ -326,6 +342,20 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
326
342
  }
327
343
  const attachInfo = await this.attachClient.attach(this.node, this.outboundOriginType, connector, welcome.frame, this.wrappedHandler, this.getKeys() ?? undefined, callbackGrants);
328
344
  this.targetSystemId = attachInfo.targetSystemId ?? null;
345
+ if (this.targetSystemId) {
346
+ const targetAware = connector;
347
+ if (typeof targetAware.setTargetNodeId === 'function') {
348
+ try {
349
+ targetAware.setTargetNodeId(this.targetSystemId);
350
+ }
351
+ catch (error) {
352
+ logger.warning('broadcast_channel_target_apply_failed', {
353
+ error: error instanceof Error ? error.message : String(error),
354
+ target_node_id: this.targetSystemId,
355
+ });
356
+ }
357
+ }
358
+ }
329
359
  await this.onAttach(attachInfo, connector);
330
360
  // Close the admission client immediately after attach completes
331
361
  // This releases HTTP keep-alive connections (Node.js fetch/undici requires explicit cleanup)
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  // This file is auto-generated during build - do not edit manually
3
- // Generated from package.json version: 0.3.5-test.961
3
+ // Generated from package.json version: 0.3.5-test.963
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.VERSION = void 0;
6
6
  /**
7
7
  * The package version, injected at build time.
8
8
  * @internal
9
9
  */
10
- exports.VERSION = '0.3.5-test.961';
10
+ exports.VERSION = '0.3.5-test.963';
@@ -72,8 +72,16 @@ export class BroadcastChannelConnectorFactory extends ConnectorFactory {
72
72
  }
73
73
  const normalized = this._normalizeConfig(config);
74
74
  const options = (factoryArgs[0] ?? {});
75
+ const normalizedLocalNodeFromConfig = this._normalizeNodeId(normalized.localNodeId);
76
+ const localNodeId = this._normalizeNodeId(options.localNodeId) ?? normalizedLocalNodeFromConfig;
77
+ if (!localNodeId) {
78
+ throw new Error('BroadcastChannelConnectorFactory requires a localNodeId from config or create() options');
79
+ }
75
80
  const channelName = normalized.channelName ?? DEFAULT_CHANNEL;
76
81
  const inboxCapacity = normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY;
82
+ const targetFromOptions = this._normalizeTargetNodeId(options.initialTargetNodeId);
83
+ const targetFromConfig = this._normalizeTargetNodeId(normalized.initialTargetNodeId);
84
+ const resolvedTarget = targetFromOptions ?? targetFromConfig ?? '*';
77
85
  const baseConfig = {
78
86
  drainTimeout: normalized.drainTimeout,
79
87
  flowControl: normalized.flowControl,
@@ -88,6 +96,8 @@ export class BroadcastChannelConnectorFactory extends ConnectorFactory {
88
96
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
89
97
  channelName,
90
98
  inboxCapacity,
99
+ localNodeId,
100
+ initialTargetNodeId: resolvedTarget,
91
101
  };
92
102
  const connector = new BroadcastChannelConnector(connectorConfig, baseConfig);
93
103
  if (options.authorization) {
@@ -111,11 +121,21 @@ export class BroadcastChannelConnectorFactory extends ConnectorFactory {
111
121
  normalized.channelName = channel.trim();
112
122
  }
113
123
  const capacity = candidate.inboxCapacity ?? candidate['inbox_capacity'];
124
+ const initialTargetNodeId = candidate.initialTargetNodeId ?? candidate['initial_target_node_id'];
125
+ const normalizedTarget = this._normalizeTargetNodeId(initialTargetNodeId);
126
+ if (normalizedTarget) {
127
+ normalized.initialTargetNodeId = normalizedTarget;
128
+ }
114
129
  if (typeof capacity === 'number' &&
115
130
  Number.isFinite(capacity) &&
116
131
  capacity > 0) {
117
132
  normalized.inboxCapacity = Math.floor(capacity);
118
133
  }
134
+ const localNodeId = candidate.localNodeId ?? candidate['local_node_id'];
135
+ const normalizedLocalNodeId = this._normalizeNodeId(localNodeId);
136
+ if (normalizedLocalNodeId) {
137
+ normalized.localNodeId = normalizedLocalNodeId;
138
+ }
119
139
  if (typeof candidate.flowControl === 'boolean') {
120
140
  normalized.flowControl = candidate.flowControl;
121
141
  }
@@ -154,5 +174,21 @@ export class BroadcastChannelConnectorFactory extends ConnectorFactory {
154
174
  normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY;
155
175
  return normalized;
156
176
  }
177
+ _normalizeNodeId(value) {
178
+ if (typeof value !== 'string') {
179
+ return null;
180
+ }
181
+ const trimmed = value.trim();
182
+ return trimmed.length > 0 ? trimmed : null;
183
+ }
184
+ _normalizeTargetNodeId(value) {
185
+ if (value === undefined || value === null) {
186
+ return undefined;
187
+ }
188
+ if (value === '*') {
189
+ return '*';
190
+ }
191
+ return this._normalizeNodeId(value) ?? undefined;
192
+ }
157
193
  }
158
194
  export default BroadcastChannelConnectorFactory;