@naylence/runtime 0.3.5-test.942 → 0.3.5-test.944

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 (36) hide show
  1. package/dist/browser/index.cjs +380 -8
  2. package/dist/browser/index.mjs +380 -8
  3. package/dist/cjs/naylence/fame/connector/broadcast-channel-connector-factory.js +12 -0
  4. package/dist/cjs/naylence/fame/connector/broadcast-channel-connector.browser.js +69 -1
  5. package/dist/cjs/naylence/fame/connector/broadcast-channel-listener.js +35 -0
  6. package/dist/cjs/naylence/fame/connector/inpage-connector-factory.js +12 -0
  7. package/dist/cjs/naylence/fame/connector/inpage-connector.js +66 -1
  8. package/dist/cjs/naylence/fame/connector/inpage-listener.js +49 -2
  9. package/dist/cjs/naylence/fame/connector/transport-frame.js +101 -0
  10. package/dist/cjs/naylence/fame/grants/broadcast-channel-connection-grant.js +28 -0
  11. package/dist/cjs/naylence/fame/grants/inpage-connection-grant.js +28 -0
  12. package/dist/cjs/version.js +2 -2
  13. package/dist/esm/naylence/fame/connector/broadcast-channel-connector-factory.js +12 -0
  14. package/dist/esm/naylence/fame/connector/broadcast-channel-connector.browser.js +69 -1
  15. package/dist/esm/naylence/fame/connector/broadcast-channel-listener.js +35 -0
  16. package/dist/esm/naylence/fame/connector/inpage-connector-factory.js +12 -0
  17. package/dist/esm/naylence/fame/connector/inpage-connector.js +66 -1
  18. package/dist/esm/naylence/fame/connector/inpage-listener.js +49 -2
  19. package/dist/esm/naylence/fame/connector/transport-frame.js +94 -0
  20. package/dist/esm/naylence/fame/grants/broadcast-channel-connection-grant.js +28 -0
  21. package/dist/esm/naylence/fame/grants/inpage-connection-grant.js +28 -0
  22. package/dist/esm/version.js +2 -2
  23. package/dist/node/index.cjs +380 -8
  24. package/dist/node/index.mjs +380 -8
  25. package/dist/node/node.cjs +396 -8
  26. package/dist/node/node.mjs +396 -8
  27. package/dist/types/naylence/fame/connector/broadcast-channel-connector-factory.d.ts +2 -0
  28. package/dist/types/naylence/fame/connector/broadcast-channel-connector.browser.d.ts +4 -0
  29. package/dist/types/naylence/fame/connector/inpage-connector-factory.d.ts +2 -0
  30. package/dist/types/naylence/fame/connector/inpage-connector.d.ts +4 -0
  31. package/dist/types/naylence/fame/connector/inpage-listener.d.ts +1 -0
  32. package/dist/types/naylence/fame/connector/transport-frame.d.ts +58 -0
  33. package/dist/types/naylence/fame/grants/broadcast-channel-connection-grant.d.ts +6 -0
  34. package/dist/types/naylence/fame/grants/inpage-connection-grant.d.ts +8 -0
  35. package/dist/types/version.d.ts +1 -1
  36. package/package.json +1 -1
@@ -6,6 +6,7 @@ const errors_js_1 = require("../errors/errors.js");
6
6
  const logging_js_1 = require("../util/logging.js");
7
7
  const bounded_async_queue_js_1 = require("../util/bounded-async-queue.js");
8
8
  const core_1 = require("@naylence/core");
9
+ const transport_frame_js_1 = require("./transport-frame.js");
9
10
  const logger = (0, logging_js_1.getLogger)('naylence.fame.connector.broadcast_channel_connector');
10
11
  exports.BROADCAST_CHANNEL_CONNECTOR_TYPE = 'broadcast-channel-connector';
11
12
  const DEFAULT_CHANNEL = 'naylence-fabric';
@@ -71,9 +72,20 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
71
72
  this.inbox = new bounded_async_queue_js_1.BoundedAsyncQueue(preferredCapacity);
72
73
  this.connectorId = BroadcastChannelConnector.generateConnectorId();
73
74
  this.channel = new BroadcastChannel(this.channelName);
75
+ // Set local and remote node IDs (defaults to connector ID for backwards compatibility)
76
+ this.localNodeId =
77
+ typeof config.localNodeId === 'string' && config.localNodeId.trim().length > 0
78
+ ? config.localNodeId.trim()
79
+ : this.connectorId;
80
+ this.remoteNodeId =
81
+ typeof config.remoteNodeId === 'string' && config.remoteNodeId.trim().length > 0
82
+ ? config.remoteNodeId.trim()
83
+ : '*'; // Accept from any remote if not specified
74
84
  logger.debug('broadcast_channel_connector_created', {
75
85
  channel: this.channelName,
76
86
  connector_id: this.connectorId,
87
+ local_node_id: this.localNodeId,
88
+ remote_node_id: this.remoteNodeId,
77
89
  inbox_capacity: preferredCapacity,
78
90
  timestamp: new Date().toISOString(),
79
91
  });
@@ -106,6 +118,46 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
106
118
  if (busMessage.senderId === this.connectorId) {
107
119
  return;
108
120
  }
121
+ // Try to unwrap as transport frame
122
+ const unwrapped = (0, transport_frame_js_1.unwrapTransportFrame)(busMessage.payload, this.localNodeId, this.remoteNodeId === '*' ? busMessage.senderId : this.remoteNodeId);
123
+ if (unwrapped) {
124
+ // Successfully unwrapped transport frame
125
+ logger.debug('broadcast_channel_transport_frame_received', {
126
+ channel: this.channelName,
127
+ sender_id: busMessage.senderId,
128
+ connector_id: this.connectorId,
129
+ local_node_id: this.localNodeId,
130
+ remote_node_id: this.remoteNodeId,
131
+ payload_length: unwrapped.byteLength,
132
+ });
133
+ if (this._shouldSkipDuplicateAck(busMessage.senderId, unwrapped)) {
134
+ return;
135
+ }
136
+ try {
137
+ if (typeof this.inbox.tryEnqueue === 'function') {
138
+ const accepted = this.inbox.tryEnqueue(unwrapped);
139
+ if (accepted) {
140
+ return;
141
+ }
142
+ }
143
+ this.inbox.enqueue(unwrapped);
144
+ }
145
+ catch (error) {
146
+ if (error instanceof bounded_async_queue_js_1.QueueFullError) {
147
+ logger.warning('broadcast_channel_receive_queue_full', {
148
+ channel: this.channelName,
149
+ });
150
+ }
151
+ else {
152
+ logger.error('broadcast_channel_receive_error', {
153
+ channel: this.channelName,
154
+ error: error instanceof Error ? error.message : String(error),
155
+ });
156
+ }
157
+ }
158
+ return;
159
+ }
160
+ // Fall back to legacy format (no transport frame)
109
161
  const payload = BroadcastChannelConnector.coercePayload(busMessage.payload);
110
162
  if (!payload) {
111
163
  logger.debug('broadcast_channel_payload_rejected', {
@@ -244,10 +296,26 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
244
296
  logger.debug('broadcast_channel_message_sending', {
245
297
  channel: this.channelName,
246
298
  sender_id: this.connectorId,
299
+ local_node_id: this.localNodeId,
300
+ remote_node_id: this.remoteNodeId,
247
301
  });
302
+ // Only use transport framing if both localNodeId and remoteNodeId are explicitly set
303
+ // (not using default values). This ensures backwards compatibility.
304
+ const useTransportFrame = this.localNodeId !== this.connectorId ||
305
+ this.remoteNodeId !== '*';
306
+ let payload;
307
+ if (useTransportFrame) {
308
+ // Wrap payload in transport frame
309
+ const frame = (0, transport_frame_js_1.wrapTransportFrame)(data, this.localNodeId, this.remoteNodeId);
310
+ payload = (0, transport_frame_js_1.serializeTransportFrame)(frame);
311
+ }
312
+ else {
313
+ // Legacy format: send raw payload
314
+ payload = data;
315
+ }
248
316
  this.channel.postMessage({
249
317
  senderId: this.connectorId,
250
- payload: data,
318
+ payload,
251
319
  });
252
320
  }
253
321
  async _transportReceive() {
@@ -344,6 +344,26 @@ class BroadcastChannelListener extends transport_listener_js_1.TransportListener
344
344
  inboxCapacity: this._inboxCapacity,
345
345
  };
346
346
  }
347
+ // Automatically configure transport frame multiplexing:
348
+ // Set remoteNodeId to the incoming senderId to target responses back to the specific client/agent
349
+ const broadcastConfig = connectorConfig;
350
+ if (!broadcastConfig.remoteNodeId) {
351
+ broadcastConfig.remoteNodeId = params.senderId;
352
+ logger.debug('broadcast_channel_listener_auto_configured_remote_node_id', {
353
+ sender_id: params.senderId,
354
+ system_id: systemId,
355
+ remote_node_id: params.senderId,
356
+ local_node_id: broadcastConfig.localNodeId ?? '<not set>',
357
+ });
358
+ }
359
+ else {
360
+ logger.debug('broadcast_channel_listener_using_provided_remote_node_id', {
361
+ sender_id: params.senderId,
362
+ system_id: systemId,
363
+ remote_node_id: broadcastConfig.remoteNodeId,
364
+ local_node_id: broadcastConfig.localNodeId ?? '<not set>',
365
+ });
366
+ }
347
367
  try {
348
368
  const connector = await routingNode.createOriginConnector({
349
369
  originType,
@@ -413,6 +433,21 @@ class BroadcastChannelListener extends transport_listener_js_1.TransportListener
413
433
  inboxCandidate > 0) {
414
434
  config.inboxCapacity = Math.floor(inboxCandidate);
415
435
  }
436
+ // Extract transport frame multiplexing node IDs
437
+ const localNodeIdCandidate = candidate.localNodeId ?? candidate['local_node_id'];
438
+ if (typeof localNodeIdCandidate === 'string' && localNodeIdCandidate.trim().length > 0) {
439
+ config.localNodeId = localNodeIdCandidate.trim();
440
+ logger.debug('broadcast_channel_listener_extracted_local_node_id', {
441
+ local_node_id: config.localNodeId,
442
+ });
443
+ }
444
+ const remoteNodeIdCandidate = candidate.remoteNodeId ?? candidate['remote_node_id'];
445
+ if (typeof remoteNodeIdCandidate === 'string' && remoteNodeIdCandidate.trim().length > 0) {
446
+ config.remoteNodeId = remoteNodeIdCandidate.trim();
447
+ logger.debug('broadcast_channel_listener_extracted_remote_node_id', {
448
+ remote_node_id: config.remoteNodeId,
449
+ });
450
+ }
416
451
  return config;
417
452
  }
418
453
  _monitorConnectorLifecycle(senderId, systemId, connector) {
@@ -84,6 +84,8 @@ class InPageConnectorFactory extends connector_factory_js_1.ConnectorFactory {
84
84
  type: inpage_connector_js_1.INPAGE_CONNECTOR_TYPE,
85
85
  channelName,
86
86
  inboxCapacity,
87
+ localNodeId: normalized.localNodeId,
88
+ remoteNodeId: normalized.remoteNodeId,
87
89
  };
88
90
  const connector = new inpage_connector_js_1.InPageConnector(connectorConfig, baseConfig);
89
91
  if (options.authorization) {
@@ -152,6 +154,16 @@ class InPageConnectorFactory extends connector_factory_js_1.ConnectorFactory {
152
154
  if (candidate.authorizationContext !== undefined) {
153
155
  normalized.authorizationContext = candidate.authorizationContext;
154
156
  }
157
+ // Handle localNodeId
158
+ const localNodeId = candidate.localNodeId ?? candidate['local_node_id'];
159
+ if (typeof localNodeId === 'string' && localNodeId.trim().length > 0) {
160
+ normalized.localNodeId = localNodeId.trim();
161
+ }
162
+ // Handle remoteNodeId
163
+ const remoteNodeId = candidate.remoteNodeId ?? candidate['remote_node_id'];
164
+ if (typeof remoteNodeId === 'string' && remoteNodeId.trim().length > 0) {
165
+ normalized.remoteNodeId = remoteNodeId.trim();
166
+ }
155
167
  normalized.channelName = normalized.channelName ?? DEFAULT_CHANNEL;
156
168
  normalized.inboxCapacity =
157
169
  normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY;
@@ -10,6 +10,7 @@ const errors_js_1 = require("../errors/errors.js");
10
10
  const logging_js_1 = require("../util/logging.js");
11
11
  const bounded_async_queue_js_1 = require("../util/bounded-async-queue.js");
12
12
  const core_1 = require("@naylence/core");
13
+ const transport_frame_js_1 = require("./transport-frame.js");
13
14
  const logger = (0, logging_js_1.getLogger)('naylence.fame.connector.inpage_connector');
14
15
  exports.INPAGE_CONNECTOR_TYPE = 'inpage-connector';
15
16
  const DEFAULT_CHANNEL = 'naylence-fabric';
@@ -80,9 +81,20 @@ class InPageConnector extends base_async_connector_js_1.BaseAsyncConnector {
80
81
  : DEFAULT_INBOX_CAPACITY;
81
82
  this.inbox = new bounded_async_queue_js_1.BoundedAsyncQueue(preferredCapacity);
82
83
  this.connectorId = InPageConnector.generateConnectorId();
84
+ // Set local and remote node IDs (defaults to connector ID for backwards compatibility)
85
+ this.localNodeId =
86
+ typeof config.localNodeId === 'string' && config.localNodeId.trim().length > 0
87
+ ? config.localNodeId.trim()
88
+ : this.connectorId;
89
+ this.remoteNodeId =
90
+ typeof config.remoteNodeId === 'string' && config.remoteNodeId.trim().length > 0
91
+ ? config.remoteNodeId.trim()
92
+ : '*'; // Accept from any remote if not specified
83
93
  logger.debug('inpage_connector_initialized', {
84
94
  channel: this.channelName,
85
95
  connector_id: this.connectorId,
96
+ local_node_id: this.localNodeId,
97
+ remote_node_id: this.remoteNodeId,
86
98
  });
87
99
  this.onMsg = (event) => {
88
100
  const messageEvent = event;
@@ -116,6 +128,43 @@ class InPageConnector extends base_async_connector_js_1.BaseAsyncConnector {
116
128
  if (busMessage.senderId === this.connectorId) {
117
129
  return;
118
130
  }
131
+ // Try to unwrap as transport frame
132
+ const unwrapped = (0, transport_frame_js_1.unwrapTransportFrame)(busMessage.payload, this.localNodeId, this.remoteNodeId === '*' ? busMessage.senderId : this.remoteNodeId);
133
+ if (unwrapped) {
134
+ // Successfully unwrapped transport frame
135
+ logger.debug('inpage_transport_frame_received', {
136
+ channel: this.channelName,
137
+ sender_id: busMessage.senderId,
138
+ connector_id: this.connectorId,
139
+ local_node_id: this.localNodeId,
140
+ remote_node_id: this.remoteNodeId,
141
+ payload_length: unwrapped.byteLength,
142
+ });
143
+ try {
144
+ if (typeof this.inbox.tryEnqueue === 'function') {
145
+ const accepted = this.inbox.tryEnqueue(unwrapped);
146
+ if (accepted) {
147
+ return;
148
+ }
149
+ }
150
+ this.inbox.enqueue(unwrapped);
151
+ }
152
+ catch (error) {
153
+ if (error instanceof bounded_async_queue_js_1.QueueFullError) {
154
+ logger.warning('inpage_receive_queue_full', {
155
+ channel: this.channelName,
156
+ });
157
+ }
158
+ else {
159
+ logger.error('inpage_receive_error', {
160
+ channel: this.channelName,
161
+ error: error instanceof Error ? error.message : String(error),
162
+ });
163
+ }
164
+ }
165
+ return;
166
+ }
167
+ // Fall back to legacy format (no transport frame)
119
168
  const payload = InPageConnector.coercePayload(busMessage.payload);
120
169
  if (!payload) {
121
170
  logger.debug('inpage_payload_rejected', {
@@ -274,11 +323,27 @@ class InPageConnector extends base_async_connector_js_1.BaseAsyncConnector {
274
323
  logger.debug('inpage_message_sending', {
275
324
  channel: this.channelName,
276
325
  sender_id: this.connectorId,
326
+ local_node_id: this.localNodeId,
327
+ remote_node_id: this.remoteNodeId,
277
328
  });
329
+ // Only use transport framing if both localNodeId and remoteNodeId are explicitly set
330
+ // (not using default values). This ensures backwards compatibility.
331
+ const useTransportFrame = this.localNodeId !== this.connectorId ||
332
+ this.remoteNodeId !== '*';
333
+ let payload;
334
+ if (useTransportFrame) {
335
+ // Wrap payload in transport frame
336
+ const frame = (0, transport_frame_js_1.wrapTransportFrame)(data, this.localNodeId, this.remoteNodeId);
337
+ payload = (0, transport_frame_js_1.serializeTransportFrame)(frame);
338
+ }
339
+ else {
340
+ // Legacy format: send raw payload
341
+ payload = data;
342
+ }
278
343
  const event = new MessageEvent(this.channelName, {
279
344
  data: {
280
345
  senderId: this.connectorId,
281
- payload: data,
346
+ payload,
282
347
  },
283
348
  });
284
349
  getSharedBus().dispatchEvent(event);
@@ -65,6 +65,7 @@ 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();
68
69
  this._pendingAttachments = new Map();
69
70
  ensureBrowserEnvironment();
70
71
  const channelCandidate = options?.channelName;
@@ -131,6 +132,7 @@ class InPageListener extends transport_listener_js_1.TransportListener {
131
132
  this._unregisterBusListener();
132
133
  this._senderRegistry.clear();
133
134
  this._systemToSender.clear();
135
+ this._flowIdToSender.clear();
134
136
  this._pendingAttachments.clear();
135
137
  logger.debug('inpage_listener_stopped', {
136
138
  channel: this._channelName,
@@ -184,10 +186,25 @@ class InPageListener extends transport_listener_js_1.TransportListener {
184
186
  await this._handleAttachFrame(senderId, envelope);
185
187
  return;
186
188
  }
187
- const entry = this._senderRegistry.get(senderId);
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
+ }
188
204
  if (!entry) {
189
205
  logger.debug('inpage_listener_no_connector_for_sender', {
190
206
  sender_id: senderId,
207
+ flow_id: envelope.flowId,
191
208
  frame_type: envelope.frame?.type ?? 'unknown',
192
209
  });
193
210
  return;
@@ -264,6 +281,15 @@ class InPageListener extends transport_listener_js_1.TransportListener {
264
281
  }
265
282
  this._senderRegistry.set(senderId, entry);
266
283
  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
+ }
267
293
  await this._deliverEnvelope(entry, envelope);
268
294
  }
269
295
  async _createConnectorForAttach(params) {
@@ -311,7 +337,7 @@ class InPageListener extends transport_listener_js_1.TransportListener {
311
337
  origin_type: originType,
312
338
  connector_type: connector.constructor?.name ?? 'unknown',
313
339
  });
314
- return { connector, systemId, originType };
340
+ return { connector, systemId, originType, senderId: params.senderId };
315
341
  }
316
342
  catch (error) {
317
343
  logger.error('inpage_listener_connector_creation_failed', {
@@ -365,6 +391,12 @@ class InPageListener extends transport_listener_js_1.TransportListener {
365
391
  if (this._systemToSender.get(systemId) === senderId) {
366
392
  this._systemToSender.delete(systemId);
367
393
  }
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
+ }
368
400
  })
369
401
  .catch((error) => {
370
402
  logger.debug('inpage_listener_wait_until_closed_failed', {
@@ -376,9 +408,24 @@ class InPageListener extends transport_listener_js_1.TransportListener {
376
408
  if (this._systemToSender.get(systemId) === senderId) {
377
409
  this._systemToSender.delete(systemId);
378
410
  }
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
+ }
379
417
  });
380
418
  }
381
419
  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
+ }
382
429
  const message = this._buildChannelMessage({
383
430
  envelope,
384
431
  connector: entry.connector,
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ /**
3
+ * Transport frame layer for multiplexing logical links on physical channels.
4
+ *
5
+ * This lightweight framing layer wraps raw FAME payloads to enable multiple
6
+ * logical connections over a single physical channel (BroadcastChannel or InPage bus).
7
+ *
8
+ * The transport frame does NOT modify FAME envelopes - it only wraps the raw
9
+ * Uint8Array payload at the connector level.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TRANSPORT_FRAME_VERSION = void 0;
13
+ exports.wrapTransportFrame = wrapTransportFrame;
14
+ exports.serializeTransportFrame = serializeTransportFrame;
15
+ exports.unwrapTransportFrame = unwrapTransportFrame;
16
+ exports.isTransportFrame = isTransportFrame;
17
+ /**
18
+ * Transport frame version for future compatibility
19
+ */
20
+ exports.TRANSPORT_FRAME_VERSION = 1;
21
+ /**
22
+ * Wrap a raw payload in a transport frame
23
+ *
24
+ * @param payload - Raw FAME envelope bytes
25
+ * @param srcNodeId - Local node ID (this connector)
26
+ * @param dstNodeId - Remote node ID (target connector)
27
+ * @returns Transport frame ready for transmission
28
+ */
29
+ function wrapTransportFrame(payload, srcNodeId, dstNodeId) {
30
+ return {
31
+ v: exports.TRANSPORT_FRAME_VERSION,
32
+ src: srcNodeId,
33
+ dst: dstNodeId,
34
+ payload,
35
+ };
36
+ }
37
+ /**
38
+ * Serialize a transport frame for transmission over the bus
39
+ *
40
+ * @param frame - Transport frame to serialize
41
+ * @returns Serialized frame data ready for postMessage/dispatchEvent
42
+ */
43
+ function serializeTransportFrame(frame) {
44
+ // Convert Uint8Array to regular array for JSON serialization
45
+ const serializable = {
46
+ v: frame.v,
47
+ src: frame.src,
48
+ dst: frame.dst,
49
+ payload: Array.from(frame.payload),
50
+ };
51
+ return serializable;
52
+ }
53
+ /**
54
+ * Unwrap a transport frame, validating source and destination
55
+ *
56
+ * @param raw - Raw data from the bus
57
+ * @param localNodeId - This connector's node ID
58
+ * @param remoteNodeId - Expected remote node ID
59
+ * @returns Unwrapped payload if frame is valid and addressed to us, null otherwise
60
+ */
61
+ function unwrapTransportFrame(raw, localNodeId, remoteNodeId) {
62
+ // Validate basic structure
63
+ if (!raw || typeof raw !== 'object') {
64
+ return null;
65
+ }
66
+ const frame = raw;
67
+ // Check version
68
+ if (frame.v !== exports.TRANSPORT_FRAME_VERSION) {
69
+ return null;
70
+ }
71
+ // Check src and dst
72
+ if (typeof frame.src !== 'string' || typeof frame.dst !== 'string') {
73
+ return null;
74
+ }
75
+ // Only accept frames addressed to us from the expected remote
76
+ if (frame.dst !== localNodeId || frame.src !== remoteNodeId) {
77
+ return null;
78
+ }
79
+ // Extract payload
80
+ if (!frame.payload || !Array.isArray(frame.payload)) {
81
+ return null;
82
+ }
83
+ // Convert array back to Uint8Array
84
+ return Uint8Array.from(frame.payload);
85
+ }
86
+ /**
87
+ * Check if raw data looks like a transport frame
88
+ *
89
+ * @param raw - Raw data from the bus
90
+ * @returns True if this appears to be a transport frame
91
+ */
92
+ function isTransportFrame(raw) {
93
+ if (!raw || typeof raw !== 'object') {
94
+ return false;
95
+ }
96
+ const frame = raw;
97
+ return (typeof frame.v === 'number' &&
98
+ typeof frame.src === 'string' &&
99
+ typeof frame.dst === 'string' &&
100
+ Array.isArray(frame.payload));
101
+ }
@@ -25,6 +25,14 @@ 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)) {
34
+ return false;
35
+ }
28
36
  return true;
29
37
  }
30
38
  function normalizeBroadcastChannelConnectionGrant(candidate) {
@@ -58,6 +66,20 @@ function normalizeBroadcastChannelConnectionGrant(candidate) {
58
66
  }
59
67
  result.inboxCapacity = Math.floor(inboxValue);
60
68
  }
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');
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('BroadcastChannelConnectionGrant "remoteNodeId" must be a non-empty string when provided');
80
+ }
81
+ result.remoteNodeId = remoteNodeIdValue.trim();
82
+ }
61
83
  return result;
62
84
  }
63
85
  function broadcastChannelGrantToConnectorConfig(grant) {
@@ -71,5 +93,11 @@ function broadcastChannelGrantToConnectorConfig(grant) {
71
93
  if (normalized.inboxCapacity !== undefined) {
72
94
  config.inboxCapacity = normalized.inboxCapacity;
73
95
  }
96
+ if (normalized.localNodeId) {
97
+ config.localNodeId = normalized.localNodeId;
98
+ }
99
+ if (normalized.remoteNodeId) {
100
+ config.remoteNodeId = normalized.remoteNodeId;
101
+ }
74
102
  return config;
75
103
  }
@@ -25,6 +25,14 @@ 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
+ }
28
36
  return true;
29
37
  }
30
38
  function normalizeInPageConnectionGrant(candidate) {
@@ -58,6 +66,20 @@ function normalizeInPageConnectionGrant(candidate) {
58
66
  }
59
67
  result.inboxCapacity = Math.floor(inboxValue);
60
68
  }
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
+ }
61
83
  return result;
62
84
  }
63
85
  function inPageGrantToConnectorConfig(grant) {
@@ -71,5 +93,11 @@ function inPageGrantToConnectorConfig(grant) {
71
93
  if (normalized.inboxCapacity !== undefined) {
72
94
  config.inboxCapacity = normalized.inboxCapacity;
73
95
  }
96
+ if (normalized.localNodeId) {
97
+ config.localNodeId = normalized.localNodeId;
98
+ }
99
+ if (normalized.remoteNodeId) {
100
+ config.remoteNodeId = normalized.remoteNodeId;
101
+ }
74
102
  return config;
75
103
  }
@@ -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.942
3
+ // Generated from package.json version: 0.3.5-test.944
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.942';
10
+ exports.VERSION = '0.3.5-test.944';
@@ -86,6 +86,8 @@ export class BroadcastChannelConnectorFactory extends ConnectorFactory {
86
86
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
87
87
  channelName,
88
88
  inboxCapacity,
89
+ localNodeId: normalized.localNodeId,
90
+ remoteNodeId: normalized.remoteNodeId,
89
91
  };
90
92
  const connector = new BroadcastChannelConnector(connectorConfig, baseConfig);
91
93
  if (options.authorization) {
@@ -147,6 +149,16 @@ export class BroadcastChannelConnectorFactory extends ConnectorFactory {
147
149
  if (candidate.authorizationContext !== undefined) {
148
150
  normalized.authorizationContext = candidate.authorizationContext;
149
151
  }
152
+ // Handle localNodeId
153
+ const localNodeId = candidate.localNodeId ?? candidate['local_node_id'];
154
+ if (typeof localNodeId === 'string' && localNodeId.trim().length > 0) {
155
+ normalized.localNodeId = localNodeId.trim();
156
+ }
157
+ // Handle remoteNodeId
158
+ const remoteNodeId = candidate.remoteNodeId ?? candidate['remote_node_id'];
159
+ if (typeof remoteNodeId === 'string' && remoteNodeId.trim().length > 0) {
160
+ normalized.remoteNodeId = remoteNodeId.trim();
161
+ }
150
162
  normalized.channelName = normalized.channelName ?? DEFAULT_CHANNEL;
151
163
  normalized.inboxCapacity =
152
164
  normalized.inboxCapacity ?? DEFAULT_INBOX_CAPACITY;