@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.
- package/dist/browser/index.cjs +133 -530
- package/dist/browser/index.mjs +133 -530
- package/dist/cjs/naylence/fame/channel/flow-controller.js +38 -1
- package/dist/cjs/naylence/fame/connector/broadcast-channel-connector-factory.js +2 -14
- package/dist/cjs/naylence/fame/connector/broadcast-channel-connector.browser.js +51 -109
- package/dist/cjs/naylence/fame/connector/broadcast-channel-listener.js +10 -76
- package/dist/cjs/naylence/fame/connector/inpage-connector-factory.js +0 -12
- package/dist/cjs/naylence/fame/connector/inpage-connector.js +1 -105
- package/dist/cjs/naylence/fame/connector/inpage-listener.js +2 -49
- package/dist/cjs/naylence/fame/grants/broadcast-channel-connection-grant.js +12 -23
- package/dist/cjs/naylence/fame/grants/inpage-connection-grant.js +0 -28
- package/dist/cjs/naylence/fame/node/admission/default-node-attach-client.js +0 -14
- package/dist/cjs/naylence/fame/node/upstream-session-manager.js +0 -6
- package/dist/cjs/version.js +2 -2
- package/dist/esm/naylence/fame/channel/flow-controller.js +38 -1
- package/dist/esm/naylence/fame/connector/broadcast-channel-connector-factory.js +2 -14
- package/dist/esm/naylence/fame/connector/broadcast-channel-connector.browser.js +51 -109
- package/dist/esm/naylence/fame/connector/broadcast-channel-listener.js +10 -76
- package/dist/esm/naylence/fame/connector/inpage-connector-factory.js +0 -12
- package/dist/esm/naylence/fame/connector/inpage-connector.js +1 -105
- package/dist/esm/naylence/fame/connector/inpage-listener.js +2 -49
- package/dist/esm/naylence/fame/grants/broadcast-channel-connection-grant.js +12 -23
- package/dist/esm/naylence/fame/grants/inpage-connection-grant.js +0 -28
- package/dist/esm/naylence/fame/node/admission/default-node-attach-client.js +0 -14
- package/dist/esm/naylence/fame/node/upstream-session-manager.js +0 -6
- package/dist/esm/version.js +2 -2
- package/dist/node/index.cjs +133 -530
- package/dist/node/index.mjs +133 -530
- package/dist/node/node.cjs +133 -546
- package/dist/node/node.mjs +133 -546
- package/dist/types/naylence/fame/connector/broadcast-channel-connector-factory.d.ts +0 -2
- package/dist/types/naylence/fame/connector/broadcast-channel-connector.browser.d.ts +4 -9
- package/dist/types/naylence/fame/connector/inpage-connector-factory.d.ts +0 -2
- package/dist/types/naylence/fame/connector/inpage-connector.d.ts +0 -9
- package/dist/types/naylence/fame/connector/inpage-listener.d.ts +0 -1
- package/dist/types/naylence/fame/grants/broadcast-channel-connection-grant.d.ts +3 -6
- package/dist/types/naylence/fame/grants/inpage-connection-grant.d.ts +0 -8
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
- package/dist/cjs/naylence/fame/connector/transport-frame.js +0 -100
- package/dist/esm/naylence/fame/connector/transport-frame.js +0 -93
- 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
|
-
|
|
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
|
|
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.
|
|
29
|
-
(
|
|
30
|
-
|
|
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
|
|
70
|
-
if (
|
|
71
|
-
if (typeof
|
|
72
|
-
|
|
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.
|
|
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.
|
|
97
|
-
config.
|
|
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
|
});
|
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.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.
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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];
|