@naylence/runtime 0.3.5-test.960 → 0.3.5-test.962

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.
@@ -46,6 +46,26 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
46
46
  }
47
47
  return null;
48
48
  }
49
+ static normalizeNodeId(value) {
50
+ if (typeof value !== 'string') {
51
+ return null;
52
+ }
53
+ const trimmed = value.trim();
54
+ return trimmed.length > 0 ? trimmed : null;
55
+ }
56
+ static normalizeTargetNodeId(value) {
57
+ if (typeof value !== 'string') {
58
+ return undefined;
59
+ }
60
+ const trimmed = value.trim();
61
+ if (trimmed.length === 0) {
62
+ return undefined;
63
+ }
64
+ if (trimmed === '*') {
65
+ return '*';
66
+ }
67
+ return trimmed;
68
+ }
49
69
  constructor(config, baseConfig = {}) {
50
70
  ensureBroadcastEnvironment();
51
71
  super(baseConfig);
@@ -68,10 +88,18 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
68
88
  this.inbox = new BoundedAsyncQueue(preferredCapacity);
69
89
  this.inboxCapacity = preferredCapacity;
70
90
  this.connectorId = BroadcastChannelConnector.generateConnectorId();
91
+ const normalizedLocalNodeId = BroadcastChannelConnector.normalizeNodeId(config.localNodeId);
92
+ if (!normalizedLocalNodeId) {
93
+ throw new Error('BroadcastChannelConnector requires a non-empty localNodeId');
94
+ }
95
+ this.localNodeId = normalizedLocalNodeId;
96
+ this.targetNodeId = BroadcastChannelConnector.normalizeTargetNodeId(config.initialTargetNodeId);
71
97
  this.channel = new BroadcastChannel(this.channelName);
72
98
  logger.debug('broadcast_channel_connector_created', {
73
99
  channel: this.channelName,
74
100
  connector_id: this.connectorId,
101
+ local_node_id: this.localNodeId,
102
+ target_node_id: this.targetNodeId ?? null,
75
103
  inbox_capacity: preferredCapacity,
76
104
  timestamp: new Date().toISOString(),
77
105
  });
@@ -93,15 +121,32 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
93
121
  ? message.constructor?.name ?? typeof message
94
122
  : typeof message,
95
123
  has_sender_id: Boolean(message?.senderId),
124
+ has_sender_node_id: Boolean(message?.senderNodeId),
96
125
  });
97
126
  if (!message || typeof message !== 'object') {
98
127
  return;
99
128
  }
100
129
  const busMessage = message;
101
- if (typeof busMessage.senderId !== 'string' || busMessage.senderId.length === 0) {
130
+ const senderNodeId = BroadcastChannelConnector.normalizeNodeId(busMessage.senderNodeId);
131
+ if (!senderNodeId) {
132
+ logger.debug('broadcast_channel_message_rejected', {
133
+ channel: this.channelName,
134
+ connector_id: this.connectorId,
135
+ reason: 'missing_sender_node_id',
136
+ });
137
+ return;
138
+ }
139
+ if (senderNodeId === this.localNodeId) {
140
+ logger.debug('broadcast_channel_message_rejected', {
141
+ channel: this.channelName,
142
+ connector_id: this.connectorId,
143
+ reason: 'self_echo',
144
+ sender_node_id: senderNodeId,
145
+ });
102
146
  return;
103
147
  }
104
- if (busMessage.senderId === this.connectorId) {
148
+ const incomingTargetNodeId = BroadcastChannelConnector.normalizeTargetNodeId(busMessage.targetNodeId);
149
+ if (!this._shouldAcceptMessageFromBus(senderNodeId, incomingTargetNodeId)) {
105
150
  return;
106
151
  }
107
152
  const payload = BroadcastChannelConnector.coercePayload(busMessage.payload);
@@ -115,11 +160,13 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
115
160
  }
116
161
  logger.debug('broadcast_channel_message_received', {
117
162
  channel: this.channelName,
118
- sender_id: busMessage.senderId,
163
+ sender_id: message?.senderId,
164
+ sender_node_id: senderNodeId,
165
+ target_node_id: incomingTargetNodeId ?? null,
119
166
  connector_id: this.connectorId,
120
167
  payload_length: payload.byteLength,
121
168
  });
122
- if (this._shouldSkipDuplicateAck(busMessage.senderId, payload)) {
169
+ if (this._shouldSkipDuplicateAck(senderNodeId, payload)) {
123
170
  return;
124
171
  }
125
172
  try {
@@ -263,12 +310,17 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
263
310
  }
264
311
  async _transportSendBytes(data) {
265
312
  ensureBroadcastEnvironment();
313
+ const targetNodeId = this.targetNodeId ?? '*';
266
314
  logger.debug('broadcast_channel_message_sending', {
267
315
  channel: this.channelName,
268
316
  sender_id: this.connectorId,
317
+ sender_node_id: this.localNodeId,
318
+ target_node_id: targetNodeId,
269
319
  });
270
320
  this.channel.postMessage({
271
321
  senderId: this.connectorId,
322
+ senderNodeId: this.localNodeId,
323
+ targetNodeId,
272
324
  payload: data,
273
325
  });
274
326
  }
@@ -331,6 +383,51 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
331
383
  }
332
384
  return rawOrEnvelope;
333
385
  }
386
+ _isWildcardTarget() {
387
+ return this.targetNodeId === '*' || typeof this.targetNodeId === 'undefined';
388
+ }
389
+ _shouldAcceptMessageFromBus(senderNodeId, targetNodeId) {
390
+ if (this._isWildcardTarget()) {
391
+ if (targetNodeId && targetNodeId !== '*') {
392
+ logger.debug('broadcast_channel_message_rejected', {
393
+ channel: this.channelName,
394
+ connector_id: this.connectorId,
395
+ reason: 'wildcard_target_mismatch',
396
+ sender_node_id: senderNodeId,
397
+ target_node_id: targetNodeId,
398
+ local_node_id: this.localNodeId,
399
+ });
400
+ return false;
401
+ }
402
+ return true;
403
+ }
404
+ const expectedSender = this.targetNodeId;
405
+ if (expectedSender && expectedSender !== '*' && senderNodeId !== expectedSender) {
406
+ logger.debug('broadcast_channel_message_rejected', {
407
+ channel: this.channelName,
408
+ connector_id: this.connectorId,
409
+ reason: 'unexpected_sender',
410
+ expected_sender_node_id: expectedSender,
411
+ sender_node_id: senderNodeId,
412
+ local_node_id: this.localNodeId,
413
+ });
414
+ return false;
415
+ }
416
+ if (targetNodeId &&
417
+ targetNodeId !== '*' &&
418
+ targetNodeId !== this.localNodeId) {
419
+ logger.debug('broadcast_channel_message_rejected', {
420
+ channel: this.channelName,
421
+ connector_id: this.connectorId,
422
+ reason: 'unexpected_target',
423
+ sender_node_id: senderNodeId,
424
+ target_node_id: targetNodeId,
425
+ local_node_id: this.localNodeId,
426
+ });
427
+ return false;
428
+ }
429
+ return true;
430
+ }
334
431
  _describeInboxItem(item) {
335
432
  if (item instanceof Uint8Array) {
336
433
  return 'bytes';
@@ -361,6 +458,16 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
361
458
  const normalizedSenderId = typeof senderId === 'string' && senderId.length > 0
362
459
  ? senderId
363
460
  : undefined;
461
+ if (normalizedSenderId && normalizedSenderId !== this.localNodeId) {
462
+ logger.debug('broadcast_channel_duplicate_ack_bypass_non_self', {
463
+ channel: this.channelName,
464
+ connector_id: this.connectorId,
465
+ sender_id: normalizedSenderId,
466
+ dedup_key: dedupKey,
467
+ source: 'listener',
468
+ });
469
+ return false;
470
+ }
364
471
  logger.debug('broadcast_channel_duplicate_ack_check', {
365
472
  channel: this.channelName,
366
473
  connector_id: this.connectorId,
@@ -391,6 +498,16 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
391
498
  return false;
392
499
  }
393
500
  const senderId = this._extractSenderIdFromInboxItem(item);
501
+ if (senderId && senderId !== this.localNodeId) {
502
+ logger.debug('broadcast_channel_duplicate_ack_bypass_non_self', {
503
+ channel: this.channelName,
504
+ connector_id: this.connectorId,
505
+ sender_id: senderId,
506
+ dedup_key: dedupKey,
507
+ source: 'inbox_item',
508
+ });
509
+ return false;
510
+ }
394
511
  logger.debug('broadcast_channel_duplicate_ack_check', {
395
512
  channel: this.channelName,
396
513
  connector_id: this.connectorId,
@@ -477,6 +594,34 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
477
594
  });
478
595
  }
479
596
  }
597
+ setTargetNodeId(nodeId) {
598
+ const normalized = BroadcastChannelConnector.normalizeNodeId(nodeId);
599
+ if (!normalized) {
600
+ throw new Error('BroadcastChannelConnector target node id must be a non-empty string');
601
+ }
602
+ if (normalized === '*') {
603
+ this.setWildcardTarget();
604
+ return;
605
+ }
606
+ this.targetNodeId = normalized;
607
+ logger.debug('broadcast_channel_target_updated', {
608
+ channel: this.channelName,
609
+ connector_id: this.connectorId,
610
+ local_node_id: this.localNodeId,
611
+ target_node_id: this.targetNodeId,
612
+ target_mode: 'direct',
613
+ });
614
+ }
615
+ setWildcardTarget() {
616
+ this.targetNodeId = '*';
617
+ logger.debug('broadcast_channel_target_updated', {
618
+ channel: this.channelName,
619
+ connector_id: this.connectorId,
620
+ local_node_id: this.localNodeId,
621
+ target_node_id: this.targetNodeId,
622
+ target_mode: 'wildcard',
623
+ });
624
+ }
480
625
  _trimSeenAcks(now) {
481
626
  while (this.seenAckOrder.length > 0) {
482
627
  const candidate = this.seenAckOrder[0];
@@ -324,7 +324,7 @@ export class BroadcastChannelListener extends TransportListener {
324
324
  node: routingNode,
325
325
  });
326
326
  const selection = defaultGrantSelectionPolicy.selectCallbackGrant(selectionContext);
327
- connectorConfig = this._grantToConnectorConfig(selection.grant);
327
+ connectorConfig = this._grantToConnectorConfig(selection.grant, systemId);
328
328
  }
329
329
  catch (error) {
330
330
  logger.debug('broadcast_channel_listener_grant_selection_failed', {
@@ -333,13 +333,20 @@ export class BroadcastChannelListener extends TransportListener {
333
333
  error: error instanceof Error ? error.message : String(error),
334
334
  });
335
335
  connectorConfig =
336
- this._extractBroadcastConnectorConfig(frame) ??
337
- {
336
+ this._extractBroadcastConnectorConfig(frame, systemId) ??
337
+ this._buildConnectorConfigForSystem(systemId, {
338
338
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
339
339
  channelName: this._channelName,
340
340
  inboxCapacity: this._inboxCapacity,
341
341
  passive: true,
342
- };
342
+ });
343
+ }
344
+ if (!connectorConfig) {
345
+ logger.error('broadcast_channel_listener_missing_connector_config', {
346
+ sender_id: params.senderId,
347
+ system_id: systemId,
348
+ });
349
+ return null;
343
350
  }
344
351
  try {
345
352
  const connector = await routingNode.createOriginConnector({
@@ -365,7 +372,7 @@ export class BroadcastChannelListener extends TransportListener {
365
372
  return null;
366
373
  }
367
374
  }
368
- _extractBroadcastConnectorConfig(frame) {
375
+ _extractBroadcastConnectorConfig(frame, systemId) {
369
376
  const rawGrants = frame.callbackGrants;
370
377
  if (!Array.isArray(rawGrants)) {
371
378
  return null;
@@ -376,7 +383,10 @@ export class BroadcastChannelListener extends TransportListener {
376
383
  (grant.type === BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE ||
377
384
  grant.type === BROADCAST_CHANNEL_CONNECTOR_TYPE)) {
378
385
  try {
379
- return this._grantToConnectorConfig(grant);
386
+ if (grant.type === BROADCAST_CHANNEL_CONNECTOR_TYPE) {
387
+ return this._buildConnectorConfigForSystem(systemId, grant);
388
+ }
389
+ return this._buildConnectorConfigForSystem(systemId, broadcastChannelGrantToConnectorConfig(grant));
380
390
  }
381
391
  catch (error) {
382
392
  logger.debug('broadcast_channel_listener_grant_normalization_failed', {
@@ -387,31 +397,87 @@ export class BroadcastChannelListener extends TransportListener {
387
397
  }
388
398
  return null;
389
399
  }
390
- _grantToConnectorConfig(grant) {
391
- if (grant.type !== BROADCAST_CHANNEL_CONNECTOR_TYPE) {
392
- if (grant.type === BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE) {
393
- return broadcastChannelGrantToConnectorConfig(grant);
400
+ _grantToConnectorConfig(grant, systemId) {
401
+ if (grant.type === BROADCAST_CHANNEL_CONNECTOR_TYPE) {
402
+ return this._buildConnectorConfigForSystem(systemId, grant);
403
+ }
404
+ if (grant.type === BROADCAST_CHANNEL_CONNECTION_GRANT_TYPE) {
405
+ return this._buildConnectorConfigForSystem(systemId, broadcastChannelGrantToConnectorConfig(grant));
406
+ }
407
+ if ('toConnectorConfig' in grant &&
408
+ typeof grant.toConnectorConfig ===
409
+ 'function') {
410
+ const normalized = grant.toConnectorConfig();
411
+ if (normalized.type !== BROADCAST_CHANNEL_CONNECTOR_TYPE) {
412
+ throw new Error(`Unsupported grant connector type: ${normalized.type}`);
394
413
  }
395
- throw new Error(`Unsupported grant type: ${grant.type}`);
414
+ return this._buildConnectorConfigForSystem(systemId, normalized);
396
415
  }
397
- const candidate = grant;
398
- const config = {
416
+ throw new Error(`Unsupported grant type: ${grant.type}`);
417
+ }
418
+ _buildConnectorConfigForSystem(systemId, baseConfig) {
419
+ const localNodeId = this._requireLocalNodeId();
420
+ const targetSystemId = this._normalizeNodeId(systemId);
421
+ if (!targetSystemId) {
422
+ throw new Error('BroadcastChannelListener requires a valid system id');
423
+ }
424
+ const candidate = baseConfig ?? null;
425
+ const channelCandidate = candidate && 'channelName' in candidate
426
+ ? candidate.channelName
427
+ : undefined;
428
+ const inboxCandidate = candidate && 'inboxCapacity' in candidate
429
+ ? candidate.inboxCapacity
430
+ : undefined;
431
+ const initialWindowCandidate = candidate && 'initialWindow' in candidate
432
+ ? candidate.initialWindow
433
+ : undefined;
434
+ const passiveCandidate = candidate && 'passive' in candidate
435
+ ? candidate.passive
436
+ : undefined;
437
+ const targetCandidate = candidate && 'initialTargetNodeId' in candidate
438
+ ? candidate.initialTargetNodeId
439
+ : undefined;
440
+ const channelName = typeof channelCandidate === 'string' && channelCandidate.trim().length > 0
441
+ ? channelCandidate.trim()
442
+ : this._channelName;
443
+ const inboxCapacity = typeof inboxCandidate === 'number' &&
444
+ Number.isFinite(inboxCandidate) &&
445
+ inboxCandidate > 0
446
+ ? Math.floor(inboxCandidate)
447
+ : this._inboxCapacity;
448
+ const initialWindow = typeof initialWindowCandidate === 'number' &&
449
+ Number.isFinite(initialWindowCandidate) &&
450
+ initialWindowCandidate > 0
451
+ ? Math.floor(initialWindowCandidate)
452
+ : undefined;
453
+ const initialTargetNodeId = this._normalizeNodeId(targetCandidate) ?? targetSystemId;
454
+ return {
399
455
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
400
- channelName: this._channelName,
401
- inboxCapacity: this._inboxCapacity,
402
- passive: true,
456
+ channelName,
457
+ inboxCapacity,
458
+ passive: typeof passiveCandidate === 'boolean' ? passiveCandidate : true,
459
+ initialWindow,
460
+ localNodeId,
461
+ initialTargetNodeId,
403
462
  };
404
- const channelCandidate = candidate.channelName ?? candidate['channel_name'];
405
- if (typeof channelCandidate === 'string' && channelCandidate.trim().length > 0) {
406
- config.channelName = channelCandidate.trim();
463
+ }
464
+ _requireLocalNodeId() {
465
+ if (!this._routingNode) {
466
+ throw new Error('BroadcastChannelListener requires routing node context');
407
467
  }
408
- const inboxCandidate = candidate.inboxCapacity ?? candidate['inbox_capacity'];
409
- if (typeof inboxCandidate === 'number' &&
410
- Number.isFinite(inboxCandidate) &&
411
- inboxCandidate > 0) {
412
- config.inboxCapacity = Math.floor(inboxCandidate);
468
+ const normalized = this._normalizeNodeId(this._routingNode.sid) ??
469
+ this._normalizeNodeId(this._routingNode.id);
470
+ if (!normalized) {
471
+ throw new Error('BroadcastChannelListener requires routing node with a stable identifier');
472
+ }
473
+ return normalized;
474
+ }
475
+ _normalizeNodeId(value) {
476
+ if (typeof value !== 'string') {
477
+ return null;
413
478
  }
414
- return config;
479
+ const trimmed = value.trim();
480
+ return trimmed.length > 0 ? trimmed : null;
415
481
  }
416
482
  _monitorConnectorLifecycle(senderId, systemId, connector) {
417
483
  const maybeClosable = connector;
@@ -1,7 +1,7 @@
1
1
  // This file is auto-generated during build - do not edit manually
2
- // Generated from package.json version: 0.3.5-test.960
2
+ // Generated from package.json version: 0.3.5-test.962
3
3
  /**
4
4
  * The package version, injected at build time.
5
5
  * @internal
6
6
  */
7
- export const VERSION = '0.3.5-test.960';
7
+ export const VERSION = '0.3.5-test.962';