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