@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
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/dist/cjs/version.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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;
|