@naylence/runtime 0.3.5-test.953 → 0.3.5-test.955

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 (42) hide show
  1. package/dist/browser/index.cjs +133 -530
  2. package/dist/browser/index.mjs +133 -530
  3. package/dist/cjs/naylence/fame/channel/flow-controller.js +38 -1
  4. package/dist/cjs/naylence/fame/connector/broadcast-channel-connector-factory.js +2 -14
  5. package/dist/cjs/naylence/fame/connector/broadcast-channel-connector.browser.js +51 -109
  6. package/dist/cjs/naylence/fame/connector/broadcast-channel-listener.js +10 -76
  7. package/dist/cjs/naylence/fame/connector/inpage-connector-factory.js +0 -12
  8. package/dist/cjs/naylence/fame/connector/inpage-connector.js +1 -105
  9. package/dist/cjs/naylence/fame/connector/inpage-listener.js +2 -49
  10. package/dist/cjs/naylence/fame/grants/broadcast-channel-connection-grant.js +12 -23
  11. package/dist/cjs/naylence/fame/grants/inpage-connection-grant.js +0 -28
  12. package/dist/cjs/naylence/fame/node/admission/default-node-attach-client.js +0 -14
  13. package/dist/cjs/naylence/fame/node/upstream-session-manager.js +0 -6
  14. package/dist/cjs/version.js +2 -2
  15. package/dist/esm/naylence/fame/channel/flow-controller.js +38 -1
  16. package/dist/esm/naylence/fame/connector/broadcast-channel-connector-factory.js +2 -14
  17. package/dist/esm/naylence/fame/connector/broadcast-channel-connector.browser.js +51 -109
  18. package/dist/esm/naylence/fame/connector/broadcast-channel-listener.js +10 -76
  19. package/dist/esm/naylence/fame/connector/inpage-connector-factory.js +0 -12
  20. package/dist/esm/naylence/fame/connector/inpage-connector.js +1 -105
  21. package/dist/esm/naylence/fame/connector/inpage-listener.js +2 -49
  22. package/dist/esm/naylence/fame/grants/broadcast-channel-connection-grant.js +12 -23
  23. package/dist/esm/naylence/fame/grants/inpage-connection-grant.js +0 -28
  24. package/dist/esm/naylence/fame/node/admission/default-node-attach-client.js +0 -14
  25. package/dist/esm/naylence/fame/node/upstream-session-manager.js +0 -6
  26. package/dist/esm/version.js +2 -2
  27. package/dist/node/index.cjs +133 -530
  28. package/dist/node/index.mjs +133 -530
  29. package/dist/node/node.cjs +133 -546
  30. package/dist/node/node.mjs +133 -546
  31. package/dist/types/naylence/fame/connector/broadcast-channel-connector-factory.d.ts +0 -2
  32. package/dist/types/naylence/fame/connector/broadcast-channel-connector.browser.d.ts +4 -9
  33. package/dist/types/naylence/fame/connector/inpage-connector-factory.d.ts +0 -2
  34. package/dist/types/naylence/fame/connector/inpage-connector.d.ts +0 -9
  35. package/dist/types/naylence/fame/connector/inpage-listener.d.ts +0 -1
  36. package/dist/types/naylence/fame/grants/broadcast-channel-connection-grant.d.ts +3 -6
  37. package/dist/types/naylence/fame/grants/inpage-connection-grant.d.ts +0 -8
  38. package/dist/types/version.d.ts +1 -1
  39. package/package.json +1 -1
  40. package/dist/cjs/naylence/fame/connector/transport-frame.js +0 -100
  41. package/dist/esm/naylence/fame/connector/transport-frame.js +0 -93
  42. package/dist/types/naylence/fame/connector/transport-frame.d.ts +0 -56
@@ -65,7 +65,6 @@ class InPageListener extends transport_listener_js_1.TransportListener {
65
65
  this._busHandler = null;
66
66
  this._senderRegistry = new Map();
67
67
  this._systemToSender = new Map();
68
- this._flowIdToSender = new Map();
69
68
  this._pendingAttachments = new Map();
70
69
  ensureBrowserEnvironment();
71
70
  const channelCandidate = options?.channelName;
@@ -132,7 +131,6 @@ class InPageListener extends transport_listener_js_1.TransportListener {
132
131
  this._unregisterBusListener();
133
132
  this._senderRegistry.clear();
134
133
  this._systemToSender.clear();
135
- this._flowIdToSender.clear();
136
134
  this._pendingAttachments.clear();
137
135
  logger.debug('inpage_listener_stopped', {
138
136
  channel: this._channelName,
@@ -186,25 +184,10 @@ class InPageListener extends transport_listener_js_1.TransportListener {
186
184
  await this._handleAttachFrame(senderId, envelope);
187
185
  return;
188
186
  }
189
- // Try to find connector by sender ID first
190
- let entry = this._senderRegistry.get(senderId);
191
- // If not found and we have a flowId, try to route based on flow
192
- if (!entry && envelope.flowId) {
193
- const originalSenderId = this._flowIdToSender.get(envelope.flowId);
194
- if (originalSenderId) {
195
- entry = this._senderRegistry.get(originalSenderId);
196
- logger.debug('inpage_listener_routed_by_flow_id', {
197
- sender_id: senderId,
198
- original_sender_id: originalSenderId,
199
- flow_id: envelope.flowId,
200
- frame_type: envelope.frame?.type ?? 'unknown',
201
- });
202
- }
203
- }
187
+ const entry = this._senderRegistry.get(senderId);
204
188
  if (!entry) {
205
189
  logger.debug('inpage_listener_no_connector_for_sender', {
206
190
  sender_id: senderId,
207
- flow_id: envelope.flowId,
208
191
  frame_type: envelope.frame?.type ?? 'unknown',
209
192
  });
210
193
  return;
@@ -281,15 +264,6 @@ class InPageListener extends transport_listener_js_1.TransportListener {
281
264
  }
282
265
  this._senderRegistry.set(senderId, entry);
283
266
  this._systemToSender.set(entry.systemId, senderId);
284
- // Track the flowId if present so we can route responses back
285
- if (envelope.flowId) {
286
- this._flowIdToSender.set(envelope.flowId, senderId);
287
- logger.debug('inpage_listener_registered_flow_id', {
288
- sender_id: senderId,
289
- system_id: entry.systemId,
290
- flow_id: envelope.flowId,
291
- });
292
- }
293
267
  await this._deliverEnvelope(entry, envelope);
294
268
  }
295
269
  async _createConnectorForAttach(params) {
@@ -337,7 +311,7 @@ class InPageListener extends transport_listener_js_1.TransportListener {
337
311
  origin_type: originType,
338
312
  connector_type: connector.constructor?.name ?? 'unknown',
339
313
  });
340
- return { connector, systemId, originType, senderId: params.senderId };
314
+ return { connector, systemId, originType };
341
315
  }
342
316
  catch (error) {
343
317
  logger.error('inpage_listener_connector_creation_failed', {
@@ -391,12 +365,6 @@ class InPageListener extends transport_listener_js_1.TransportListener {
391
365
  if (this._systemToSender.get(systemId) === senderId) {
392
366
  this._systemToSender.delete(systemId);
393
367
  }
394
- // Clean up flowId mappings for this sender
395
- for (const [flowId, sid] of this._flowIdToSender.entries()) {
396
- if (sid === senderId) {
397
- this._flowIdToSender.delete(flowId);
398
- }
399
- }
400
368
  })
401
369
  .catch((error) => {
402
370
  logger.debug('inpage_listener_wait_until_closed_failed', {
@@ -408,24 +376,9 @@ class InPageListener extends transport_listener_js_1.TransportListener {
408
376
  if (this._systemToSender.get(systemId) === senderId) {
409
377
  this._systemToSender.delete(systemId);
410
378
  }
411
- // Clean up flowId mappings for this sender
412
- for (const [flowId, sid] of this._flowIdToSender.entries()) {
413
- if (sid === senderId) {
414
- this._flowIdToSender.delete(flowId);
415
- }
416
- }
417
379
  });
418
380
  }
419
381
  async _deliverEnvelope(entry, envelope) {
420
- // Track flowId for routing responses back
421
- if (envelope.flowId && !this._flowIdToSender.has(envelope.flowId)) {
422
- this._flowIdToSender.set(envelope.flowId, entry.senderId);
423
- logger.debug('inpage_listener_registered_flow_id_on_delivery', {
424
- sender_id: entry.senderId,
425
- system_id: entry.systemId,
426
- flow_id: envelope.flowId,
427
- });
428
- }
429
382
  const message = this._buildChannelMessage({
430
383
  envelope,
431
384
  connector: entry.connector,
@@ -25,12 +25,9 @@ function isBroadcastChannelConnectionGrant(candidate) {
25
25
  record.inboxCapacity <= 0)) {
26
26
  return false;
27
27
  }
28
- if (record.localNodeId !== undefined &&
29
- (typeof record.localNodeId !== 'string' || record.localNodeId.length === 0)) {
30
- return false;
31
- }
32
- if (record.remoteNodeId !== undefined &&
33
- (typeof record.remoteNodeId !== 'string' || record.remoteNodeId.length === 0)) {
28
+ if (record.initialWindow !== undefined &&
29
+ (!Number.isFinite(record.initialWindow) ||
30
+ record.initialWindow <= 0)) {
34
31
  return false;
35
32
  }
36
33
  return true;
@@ -66,19 +63,14 @@ function normalizeBroadcastChannelConnectionGrant(candidate) {
66
63
  }
67
64
  result.inboxCapacity = Math.floor(inboxValue);
68
65
  }
69
- const localNodeIdValue = candidate.localNodeId ?? candidate['local_node_id'];
70
- if (localNodeIdValue !== undefined) {
71
- if (typeof localNodeIdValue !== 'string' || localNodeIdValue.trim().length === 0) {
72
- throw new TypeError('BroadcastChannelConnectionGrant "localNodeId" must be a non-empty string when provided');
66
+ const windowValue = candidate.initialWindow ?? candidate['initial_window'];
67
+ if (windowValue !== undefined) {
68
+ if (typeof windowValue !== 'number' ||
69
+ !Number.isFinite(windowValue) ||
70
+ windowValue <= 0) {
71
+ throw new TypeError('BroadcastChannelConnectionGrant "initialWindow" must be a positive number when provided');
73
72
  }
74
- result.localNodeId = localNodeIdValue.trim();
75
- }
76
- const remoteNodeIdValue = candidate.remoteNodeId ?? candidate['remote_node_id'];
77
- if (remoteNodeIdValue !== undefined) {
78
- if (typeof remoteNodeIdValue !== 'string' || remoteNodeIdValue.trim().length === 0) {
79
- throw new TypeError('BroadcastChannelConnectionGrant "remoteNodeId" must be a non-empty string when provided');
80
- }
81
- result.remoteNodeId = remoteNodeIdValue.trim();
73
+ result.initialWindow = Math.floor(windowValue);
82
74
  }
83
75
  return result;
84
76
  }
@@ -93,11 +85,8 @@ function broadcastChannelGrantToConnectorConfig(grant) {
93
85
  if (normalized.inboxCapacity !== undefined) {
94
86
  config.inboxCapacity = normalized.inboxCapacity;
95
87
  }
96
- if (normalized.localNodeId) {
97
- config.localNodeId = normalized.localNodeId;
98
- }
99
- if (normalized.remoteNodeId) {
100
- config.remoteNodeId = normalized.remoteNodeId;
88
+ if (normalized.initialWindow !== undefined) {
89
+ config.initialWindow = normalized.initialWindow;
101
90
  }
102
91
  return config;
103
92
  }
@@ -25,14 +25,6 @@ function isInPageConnectionGrant(candidate) {
25
25
  record.inboxCapacity <= 0)) {
26
26
  return false;
27
27
  }
28
- if (record.localNodeId !== undefined &&
29
- (typeof record.localNodeId !== 'string' || record.localNodeId.length === 0)) {
30
- return false;
31
- }
32
- if (record.remoteNodeId !== undefined &&
33
- (typeof record.remoteNodeId !== 'string' || record.remoteNodeId.length === 0)) {
34
- return false;
35
- }
36
28
  return true;
37
29
  }
38
30
  function normalizeInPageConnectionGrant(candidate) {
@@ -66,20 +58,6 @@ function normalizeInPageConnectionGrant(candidate) {
66
58
  }
67
59
  result.inboxCapacity = Math.floor(inboxValue);
68
60
  }
69
- const localNodeIdValue = candidate.localNodeId ?? candidate['local_node_id'];
70
- if (localNodeIdValue !== undefined) {
71
- if (typeof localNodeIdValue !== 'string' || localNodeIdValue.trim().length === 0) {
72
- throw new TypeError('InPageConnectionGrant "localNodeId" must be a non-empty string when provided');
73
- }
74
- result.localNodeId = localNodeIdValue.trim();
75
- }
76
- const remoteNodeIdValue = candidate.remoteNodeId ?? candidate['remote_node_id'];
77
- if (remoteNodeIdValue !== undefined) {
78
- if (typeof remoteNodeIdValue !== 'string' || remoteNodeIdValue.trim().length === 0) {
79
- throw new TypeError('InPageConnectionGrant "remoteNodeId" must be a non-empty string when provided');
80
- }
81
- result.remoteNodeId = remoteNodeIdValue.trim();
82
- }
83
61
  return result;
84
62
  }
85
63
  function inPageGrantToConnectorConfig(grant) {
@@ -93,11 +71,5 @@ function inPageGrantToConnectorConfig(grant) {
93
71
  if (normalized.inboxCapacity !== undefined) {
94
72
  config.inboxCapacity = normalized.inboxCapacity;
95
73
  }
96
- if (normalized.localNodeId) {
97
- config.localNodeId = normalized.localNodeId;
98
- }
99
- if (normalized.remoteNodeId) {
100
- config.remoteNodeId = normalized.remoteNodeId;
101
- }
102
74
  return config;
103
75
  }
@@ -166,20 +166,6 @@ class DefaultNodeAttachClient {
166
166
  if (!targetSystemId) {
167
167
  throw new Error('Target system ID must be set in NodeAttachAckFrame on success');
168
168
  }
169
- // Update connector's remote node ID if it supports it (e.g., BroadcastChannelConnector, InPageConnector)
170
- // This allows upstream connectors to switch from wildcard '*' to specific node addressing
171
- const updatableConnector = connector;
172
- if (typeof updatableConnector.updateRemoteNodeId === 'function') {
173
- try {
174
- updatableConnector.updateRemoteNodeId(targetSystemId);
175
- }
176
- catch (error) {
177
- logger.debug('connector_remote_node_id_update_failed', {
178
- target_system_id: targetSystemId,
179
- error: error instanceof Error ? error.message : String(error),
180
- });
181
- }
182
- }
183
169
  try {
184
170
  if (this.replicaStickinessManager) {
185
171
  this.replicaStickinessManager.accept(ackFrame.stickiness ?? null);
@@ -299,12 +299,6 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
299
299
  cryptoProvider.prepareForAttach(welcome.frame.systemId, welcome.frame.assignedPath, welcome.frame.acceptedLogicals ?? []);
300
300
  }
301
301
  await this.onWelcome(welcome.frame);
302
- // Inject node ID into grant for transport frame multiplexing
303
- // This ensures localNodeId matches the node's systemId for proper frame filtering
304
- grant.localNodeId = welcome.frame.systemId;
305
- if (welcome.frame.targetSystemId) {
306
- grant.remoteNodeId = welcome.frame.targetSystemId;
307
- }
308
302
  const connector = await connector_factory_js_1.ConnectorFactory.createConnector(grant, {
309
303
  systemId: welcome.frame.systemId,
310
304
  });
@@ -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.953
3
+ // Generated from package.json version: 0.3.5-test.955
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.953';
10
+ exports.VERSION = '0.3.5-test.955';
@@ -22,6 +22,8 @@
22
22
  * flow at any time.
23
23
  */
24
24
  import { FlowFlags } from '@naylence/core';
25
+ import { getLogger } from '../util/logging.js';
26
+ const logger = getLogger('naylence.fame.flow.flow_controller');
25
27
  /**
26
28
  * Simple condition variable implementation for TypeScript/Node.js
27
29
  * Similar to Python's asyncio.Condition
@@ -147,8 +149,17 @@ export class FlowController {
147
149
  // clamp into [0, initialWindow]
148
150
  const newBalance = Math.max(0, Math.min(this.initialWindow, prev + delta));
149
151
  this.credits.set(flowId, newBalance);
152
+ const crossedZero = prev <= 0 && newBalance > 0;
153
+ logger.debug('flow_controller_add_credits', {
154
+ flow_id: flowId,
155
+ delta,
156
+ prev_balance: prev,
157
+ new_balance: newBalance,
158
+ initial_window: this.initialWindow,
159
+ crossed_zero: crossedZero,
160
+ });
150
161
  // wake waiters only if we crossed the zero boundary
151
- if (prev <= 0 && newBalance > 0) {
162
+ if (crossedZero) {
152
163
  this.wakeWaiters(flowId);
153
164
  }
154
165
  return newBalance;
@@ -159,11 +170,27 @@ export class FlowController {
159
170
  async acquire(flowId) {
160
171
  this.ensureFlow(flowId);
161
172
  const condition = this.conditions.get(flowId);
173
+ logger.debug('flow_controller_acquire_attempt', {
174
+ flow_id: flowId,
175
+ current_balance: this.credits.get(flowId),
176
+ });
162
177
  while (this.credits.get(flowId) <= 0) {
178
+ logger.debug('flow_controller_waiting_for_credit', {
179
+ flow_id: flowId,
180
+ });
163
181
  await condition.wait();
182
+ logger.debug('flow_controller_woke_with_credit', {
183
+ flow_id: flowId,
184
+ balance_after_wake: this.credits.get(flowId),
185
+ });
164
186
  }
165
187
  const current = this.credits.get(flowId);
166
188
  this.credits.set(flowId, current - 1);
189
+ logger.debug('flow_controller_credit_consumed', {
190
+ flow_id: flowId,
191
+ prev_balance: current,
192
+ remaining_balance: current - 1,
193
+ });
167
194
  }
168
195
  /**
169
196
  * Consume *credits* immediately (non-blocking).
@@ -183,6 +210,12 @@ export class FlowController {
183
210
  const current = this.credits.get(flowId);
184
211
  const remaining = Math.max(current - credits, 0);
185
212
  this.credits.set(flowId, remaining);
213
+ logger.debug('flow_controller_consume', {
214
+ flow_id: flowId,
215
+ requested: credits,
216
+ prev_balance: current,
217
+ remaining_balance: remaining,
218
+ });
186
219
  return remaining;
187
220
  }
188
221
  /**
@@ -203,6 +236,10 @@ export class FlowController {
203
236
  this.windowIds.delete(flowId);
204
237
  this.credits.set(flowId, this.initialWindow);
205
238
  this.wakeWaiters(flowId);
239
+ logger.debug('flow_controller_flow_reset', {
240
+ flow_id: flowId,
241
+ reset_balance: this.initialWindow,
242
+ });
206
243
  }
207
244
  /**
208
245
  * Return `[windowId, flags]` for the next outbound envelope.
@@ -37,8 +37,7 @@ export class BroadcastChannelConnectorFactory extends ConnectorFactory {
37
37
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
38
38
  channelName: connectorConfig.channelName,
39
39
  inboxCapacity: connectorConfig.inboxCapacity,
40
- localNodeId: connectorConfig.localNodeId,
41
- remoteNodeId: connectorConfig.remoteNodeId,
40
+ initialWindow: connectorConfig.initialWindow,
42
41
  };
43
42
  }
44
43
  const config = {
@@ -63,6 +62,7 @@ export class BroadcastChannelConnectorFactory extends ConnectorFactory {
63
62
  purpose: 'connection',
64
63
  channelName: normalizedConfig.channelName,
65
64
  inboxCapacity: normalizedConfig.inboxCapacity,
65
+ initialWindow: normalizedConfig.initialWindow,
66
66
  });
67
67
  return grant;
68
68
  }
@@ -88,8 +88,6 @@ export class BroadcastChannelConnectorFactory extends ConnectorFactory {
88
88
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
89
89
  channelName,
90
90
  inboxCapacity,
91
- localNodeId: normalized.localNodeId,
92
- remoteNodeId: normalized.remoteNodeId,
93
91
  };
94
92
  const connector = new BroadcastChannelConnector(connectorConfig, baseConfig);
95
93
  if (options.authorization) {
@@ -151,16 +149,6 @@ export class BroadcastChannelConnectorFactory extends ConnectorFactory {
151
149
  if (candidate.authorizationContext !== undefined) {
152
150
  normalized.authorizationContext = candidate.authorizationContext;
153
151
  }
154
- // Handle localNodeId
155
- const localNodeId = candidate.localNodeId ?? candidate['local_node_id'];
156
- if (typeof localNodeId === 'string' && localNodeId.trim().length > 0) {
157
- normalized.localNodeId = localNodeId.trim();
158
- }
159
- // Handle remoteNodeId
160
- const remoteNodeId = candidate.remoteNodeId ?? candidate['remote_node_id'];
161
- if (typeof remoteNodeId === 'string' && remoteNodeId.trim().length > 0) {
162
- normalized.remoteNodeId = remoteNodeId.trim();
163
- }
164
152
  normalized.channelName = normalized.channelName ?? DEFAULT_CHANNEL;
165
153
  normalized.inboxCapacity =
166
154
  normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY;
@@ -3,7 +3,6 @@ import { FameTransportClose } from '../errors/errors.js';
3
3
  import { getLogger } from '../util/logging.js';
4
4
  import { BoundedAsyncQueue, QueueFullError, } from '../util/bounded-async-queue.js';
5
5
  import { ConnectorState } from '@naylence/core';
6
- import { wrapTransportFrame, unwrapTransportFrame, serializeTransportFrame, } from './transport-frame.js';
7
6
  const logger = getLogger('naylence.fame.connector.broadcast_channel_connector');
8
7
  export const BROADCAST_CHANNEL_CONNECTOR_TYPE = 'broadcast-channel-connector';
9
8
  const DEFAULT_CHANNEL = 'naylence-fabric';
@@ -67,22 +66,12 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
67
66
  ? Math.floor(config.inboxCapacity)
68
67
  : DEFAULT_INBOX_CAPACITY;
69
68
  this.inbox = new BoundedAsyncQueue(preferredCapacity);
69
+ this.inboxCapacity = preferredCapacity;
70
70
  this.connectorId = BroadcastChannelConnector.generateConnectorId();
71
71
  this.channel = new BroadcastChannel(this.channelName);
72
- // Set local and remote node IDs (defaults to connector ID for backwards compatibility)
73
- this.localNodeId =
74
- typeof config.localNodeId === 'string' && config.localNodeId.trim().length > 0
75
- ? config.localNodeId.trim()
76
- : this.connectorId;
77
- this.remoteNodeId =
78
- typeof config.remoteNodeId === 'string' && config.remoteNodeId.trim().length > 0
79
- ? config.remoteNodeId.trim()
80
- : '*'; // Accept from any remote if not specified
81
72
  logger.debug('broadcast_channel_connector_created', {
82
73
  channel: this.channelName,
83
74
  connector_id: this.connectorId,
84
- local_node_id: this.localNodeId,
85
- remote_node_id: this.remoteNodeId,
86
75
  inbox_capacity: preferredCapacity,
87
76
  timestamp: new Date().toISOString(),
88
77
  });
@@ -115,67 +104,6 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
115
104
  if (busMessage.senderId === this.connectorId) {
116
105
  return;
117
106
  }
118
- // Try to unwrap as transport frame
119
- const frame = unwrapTransportFrame(busMessage.payload);
120
- if (frame) {
121
- // Apply connector's filtering policy: strict dst check, src accepts wildcard
122
- const srcMatches = this.remoteNodeId === '*' || frame.src === this.remoteNodeId;
123
- const dstMatches = frame.dst === this.localNodeId;
124
- if (dstMatches && srcMatches) {
125
- // Successfully received and filtered transport frame
126
- logger.debug('broadcast_channel_transport_frame_received', {
127
- channel: this.channelName,
128
- sender_id: busMessage.senderId,
129
- connector_id: this.connectorId,
130
- local_node_id: this.localNodeId,
131
- remote_node_id: this.remoteNodeId,
132
- frame_src: frame.src,
133
- frame_dst: frame.dst,
134
- payload_length: frame.payload.byteLength,
135
- });
136
- const unwrapped = frame.payload;
137
- if (this._shouldSkipDuplicateAck(busMessage.senderId, unwrapped)) {
138
- return;
139
- }
140
- try {
141
- if (typeof this.inbox.tryEnqueue === 'function') {
142
- const accepted = this.inbox.tryEnqueue(unwrapped);
143
- if (accepted) {
144
- return;
145
- }
146
- }
147
- this.inbox.enqueue(unwrapped);
148
- }
149
- catch (error) {
150
- if (error instanceof QueueFullError) {
151
- logger.warning('broadcast_channel_receive_queue_full', {
152
- channel: this.channelName,
153
- });
154
- }
155
- else {
156
- logger.error('broadcast_channel_receive_error', {
157
- channel: this.channelName,
158
- error: error instanceof Error ? error.message : String(error),
159
- });
160
- }
161
- }
162
- return;
163
- }
164
- else {
165
- // Frame filtered out by addressing rules
166
- logger.debug('broadcast_channel_transport_frame_filtered', {
167
- channel: this.channelName,
168
- connector_id: this.connectorId,
169
- local_node_id: this.localNodeId,
170
- remote_node_id: this.remoteNodeId,
171
- frame_src: frame.src,
172
- frame_dst: frame.dst,
173
- reason: !dstMatches ? 'wrong_destination' : 'wrong_source',
174
- });
175
- return;
176
- }
177
- }
178
- // Fall back to legacy format (no transport frame)
179
107
  const payload = BroadcastChannelConnector.coercePayload(busMessage.payload);
180
108
  if (!payload) {
181
109
  logger.debug('broadcast_channel_payload_rejected', {
@@ -198,15 +126,27 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
198
126
  if (typeof this.inbox.tryEnqueue === 'function') {
199
127
  const accepted = this.inbox.tryEnqueue(payload);
200
128
  if (accepted) {
129
+ this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
130
+ source: 'listener',
131
+ enqueue_strategy: 'try',
132
+ payload_length: payload.byteLength,
133
+ });
201
134
  return;
202
135
  }
203
136
  }
204
137
  this.inbox.enqueue(payload);
138
+ this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
139
+ source: 'listener',
140
+ enqueue_strategy: 'enqueue',
141
+ payload_length: payload.byteLength,
142
+ });
205
143
  }
206
144
  catch (error) {
207
145
  if (error instanceof QueueFullError) {
208
146
  logger.warning('broadcast_channel_receive_queue_full', {
209
147
  channel: this.channelName,
148
+ inbox_capacity: this.inboxCapacity,
149
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
210
150
  });
211
151
  }
212
152
  else {
@@ -290,15 +230,25 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
290
230
  if (typeof this.inbox.tryEnqueue === 'function') {
291
231
  const accepted = this.inbox.tryEnqueue(item);
292
232
  if (accepted) {
233
+ this.logInboxSnapshot('broadcast_channel_push_enqueued', {
234
+ enqueue_strategy: 'try',
235
+ item_type: this._describeInboxItem(item),
236
+ });
293
237
  return;
294
238
  }
295
239
  }
296
240
  this.inbox.enqueue(item);
241
+ this.logInboxSnapshot('broadcast_channel_push_enqueued', {
242
+ enqueue_strategy: 'enqueue',
243
+ item_type: this._describeInboxItem(item),
244
+ });
297
245
  }
298
246
  catch (error) {
299
247
  if (error instanceof QueueFullError) {
300
248
  logger.warning('broadcast_channel_push_queue_full', {
301
249
  channel: this.channelName,
250
+ inbox_capacity: this.inboxCapacity,
251
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
302
252
  });
303
253
  throw error;
304
254
  }
@@ -314,30 +264,18 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
314
264
  logger.debug('broadcast_channel_message_sending', {
315
265
  channel: this.channelName,
316
266
  sender_id: this.connectorId,
317
- local_node_id: this.localNodeId,
318
- remote_node_id: this.remoteNodeId,
319
267
  });
320
- // Only use transport framing if both localNodeId and remoteNodeId are explicitly set
321
- // (not using default values). This ensures backwards compatibility.
322
- const useTransportFrame = this.localNodeId !== this.connectorId ||
323
- this.remoteNodeId !== '*';
324
- let payload;
325
- if (useTransportFrame) {
326
- // Wrap payload in transport frame
327
- const frame = wrapTransportFrame(data, this.localNodeId, this.remoteNodeId);
328
- payload = serializeTransportFrame(frame);
329
- }
330
- else {
331
- // Legacy format: send raw payload
332
- payload = data;
333
- }
334
268
  this.channel.postMessage({
335
269
  senderId: this.connectorId,
336
- payload,
270
+ payload: data,
337
271
  });
338
272
  }
339
273
  async _transportReceive() {
340
- return await this.inbox.dequeue();
274
+ const item = await this.inbox.dequeue();
275
+ this.logInboxSnapshot('broadcast_channel_inbox_dequeued', {
276
+ item_type: this._describeInboxItem(item),
277
+ });
278
+ return item;
341
279
  }
342
280
  async _transportClose(code, reason) {
343
281
  logger.debug('broadcast_channel_transport_closing', {
@@ -391,6 +329,28 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
391
329
  }
392
330
  return rawOrEnvelope;
393
331
  }
332
+ _describeInboxItem(item) {
333
+ if (item instanceof Uint8Array) {
334
+ return 'bytes';
335
+ }
336
+ if (item.envelope) {
337
+ return 'channel_message';
338
+ }
339
+ if (item.frame) {
340
+ return 'envelope';
341
+ }
342
+ return 'unknown';
343
+ }
344
+ logInboxSnapshot(event, extra = {}) {
345
+ logger.debug(event, {
346
+ channel: this.channelName,
347
+ connector_id: this.connectorId,
348
+ connector_state: this.state,
349
+ inbox_capacity: this.inboxCapacity,
350
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
351
+ ...extra,
352
+ });
353
+ }
394
354
  _shouldSkipDuplicateAck(senderId, payload) {
395
355
  const dedupKey = this._extractAckDedupKey(payload);
396
356
  if (!dedupKey) {
@@ -489,24 +449,6 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
489
449
  });
490
450
  }
491
451
  }
492
- /**
493
- * Update the remote node ID after learning it from NodeAttachAck
494
- * This allows upstream connectors to switch from wildcard to specific addressing
495
- */
496
- updateRemoteNodeId(newRemoteNodeId) {
497
- if (typeof newRemoteNodeId !== 'string' || newRemoteNodeId.trim().length === 0) {
498
- throw new Error('Invalid remote node ID');
499
- }
500
- const oldValue = this.remoteNodeId;
501
- this.remoteNodeId = newRemoteNodeId.trim();
502
- logger.debug('broadcast_channel_connector_remote_node_id_updated', {
503
- channel: this.channelName,
504
- connector_id: this.connectorId,
505
- local_node_id: this.localNodeId,
506
- old_remote_node_id: oldValue,
507
- new_remote_node_id: this.remoteNodeId,
508
- });
509
- }
510
452
  _trimSeenAcks(now) {
511
453
  while (this.seenAckOrder.length > 0) {
512
454
  const candidate = this.seenAckOrder[0];