@naylence/runtime 0.3.5-test.933 → 0.3.5-test.936
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 +178 -4
- package/dist/browser/index.mjs +178 -4
- package/dist/cjs/naylence/fame/connector/base-async-connector.js +92 -0
- package/dist/cjs/naylence/fame/connector/broadcast-channel-connector.browser.js +59 -1
- package/dist/cjs/naylence/fame/node/upstream-session-manager.js +26 -1
- package/dist/cjs/version.js +2 -2
- package/dist/esm/naylence/fame/connector/base-async-connector.js +92 -0
- package/dist/esm/naylence/fame/connector/broadcast-channel-connector.browser.js +59 -1
- package/dist/esm/naylence/fame/node/upstream-session-manager.js +27 -2
- package/dist/esm/version.js +2 -2
- package/dist/node/index.cjs +178 -4
- package/dist/node/index.mjs +178 -4
- package/dist/node/node.cjs +178 -4
- package/dist/node/node.mjs +178 -4
- package/dist/types/naylence/fame/connector/base-async-connector.d.ts +8 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +2 -2
|
@@ -148,6 +148,10 @@ class BaseAsyncConnector extends task_spawner_js_1.TaskSpawner {
|
|
|
148
148
|
* Stop the connector gracefully
|
|
149
149
|
*/
|
|
150
150
|
async stop() {
|
|
151
|
+
logger.debug('stopping_connector', {
|
|
152
|
+
current_state: this._state,
|
|
153
|
+
connector_id: this._connectorFlowId,
|
|
154
|
+
});
|
|
151
155
|
if (!core_1.ConnectorStateUtils.canStop(this._state)) {
|
|
152
156
|
logger.debug('connector_stop_already_stopped', {
|
|
153
157
|
current_state: this._state,
|
|
@@ -160,6 +164,52 @@ class BaseAsyncConnector extends task_spawner_js_1.TaskSpawner {
|
|
|
160
164
|
if (this._lastError) {
|
|
161
165
|
throw this._lastError;
|
|
162
166
|
}
|
|
167
|
+
logger.debug('connector_stopped', {
|
|
168
|
+
current_state: this._state,
|
|
169
|
+
connector_id: this._connectorFlowId,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Pause the connector (suspends heartbeat and housekeeping, but keeps connection alive)
|
|
174
|
+
*/
|
|
175
|
+
async pause() {
|
|
176
|
+
logger.debug('pausing_connector', {
|
|
177
|
+
current_state: this._state,
|
|
178
|
+
connector_id: this._connectorFlowId,
|
|
179
|
+
});
|
|
180
|
+
if (this._state !== core_1.ConnectorState.STARTED) {
|
|
181
|
+
logger.debug('connector_pause_invalid_state', {
|
|
182
|
+
current_state: this._state,
|
|
183
|
+
connector_id: this._connectorFlowId,
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
this._setState(core_1.ConnectorState.PAUSED);
|
|
188
|
+
logger.debug('connector_paused', {
|
|
189
|
+
current_state: this._state,
|
|
190
|
+
connector_id: this._connectorFlowId,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Resume the connector from paused state
|
|
195
|
+
*/
|
|
196
|
+
async resume() {
|
|
197
|
+
logger.debug('resuming_connector', {
|
|
198
|
+
current_state: this._state,
|
|
199
|
+
connector_id: this._connectorFlowId,
|
|
200
|
+
});
|
|
201
|
+
if (this._state !== core_1.ConnectorState.PAUSED) {
|
|
202
|
+
logger.debug('connector_resume_invalid_state', {
|
|
203
|
+
current_state: this._state,
|
|
204
|
+
connector_id: this._connectorFlowId,
|
|
205
|
+
});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
this._setState(core_1.ConnectorState.STARTED);
|
|
209
|
+
logger.debug('connector_resumed', {
|
|
210
|
+
current_state: this._state,
|
|
211
|
+
connector_id: this._connectorFlowId,
|
|
212
|
+
});
|
|
163
213
|
}
|
|
164
214
|
/**
|
|
165
215
|
* Close the connector with optional code and reason
|
|
@@ -490,8 +540,21 @@ class BaseAsyncConnector extends task_spawner_js_1.TaskSpawner {
|
|
|
490
540
|
*/
|
|
491
541
|
async _shutdown(code, reason, gracePeriod, exc) {
|
|
492
542
|
if (this._closed) {
|
|
543
|
+
logger.debug('shutdown_already_closed', {
|
|
544
|
+
connector_id: this._connectorFlowId,
|
|
545
|
+
current_state: this._state,
|
|
546
|
+
});
|
|
493
547
|
return;
|
|
494
548
|
}
|
|
549
|
+
logger.info('connector_shutdown_starting', {
|
|
550
|
+
connector_id: this._connectorFlowId,
|
|
551
|
+
connector_type: this.constructor.name,
|
|
552
|
+
code,
|
|
553
|
+
reason,
|
|
554
|
+
current_state: this._state,
|
|
555
|
+
has_error: !!exc,
|
|
556
|
+
timestamp: new Date().toISOString(),
|
|
557
|
+
});
|
|
495
558
|
this._closed = true;
|
|
496
559
|
this._closeCode = code;
|
|
497
560
|
this._closeReason = reason;
|
|
@@ -513,16 +576,39 @@ class BaseAsyncConnector extends task_spawner_js_1.TaskSpawner {
|
|
|
513
576
|
this._sendPromiseResolve = undefined;
|
|
514
577
|
}
|
|
515
578
|
// Close transport
|
|
579
|
+
logger.info('connector_closing_transport', {
|
|
580
|
+
connector_id: this._connectorFlowId,
|
|
581
|
+
connector_type: this.constructor.name,
|
|
582
|
+
timestamp: new Date().toISOString(),
|
|
583
|
+
});
|
|
516
584
|
await this._transportClose(code, reason);
|
|
585
|
+
logger.info('connector_transport_closed', {
|
|
586
|
+
connector_id: this._connectorFlowId,
|
|
587
|
+
connector_type: this.constructor.name,
|
|
588
|
+
timestamp: new Date().toISOString(),
|
|
589
|
+
});
|
|
517
590
|
// Shutdown spawned tasks
|
|
591
|
+
logger.info('connector_shutting_down_tasks', {
|
|
592
|
+
connector_id: this._connectorFlowId,
|
|
593
|
+
connector_type: this.constructor.name,
|
|
594
|
+
grace_period_ms: effectiveGracePeriod * 1000,
|
|
595
|
+
join_timeout_ms: this._shutdownJoinTimeout,
|
|
596
|
+
timestamp: new Date().toISOString(),
|
|
597
|
+
});
|
|
518
598
|
try {
|
|
519
599
|
await this.shutdownTasks({
|
|
520
600
|
gracePeriod: effectiveGracePeriod * 1000, // Convert to milliseconds
|
|
521
601
|
joinTimeout: this._shutdownJoinTimeout,
|
|
522
602
|
});
|
|
603
|
+
logger.info('connector_tasks_shutdown_complete', {
|
|
604
|
+
connector_id: this._connectorFlowId,
|
|
605
|
+
connector_type: this.constructor.name,
|
|
606
|
+
timestamp: new Date().toISOString(),
|
|
607
|
+
});
|
|
523
608
|
}
|
|
524
609
|
catch (error) {
|
|
525
610
|
logger.warning('task_shutdown_error', {
|
|
611
|
+
connector_id: this._connectorFlowId,
|
|
526
612
|
error: error instanceof Error ? error.message : String(error),
|
|
527
613
|
});
|
|
528
614
|
}
|
|
@@ -534,6 +620,12 @@ class BaseAsyncConnector extends task_spawner_js_1.TaskSpawner {
|
|
|
534
620
|
if (this._closeResolver) {
|
|
535
621
|
this._closeResolver();
|
|
536
622
|
}
|
|
623
|
+
logger.info('connector_shutdown_complete', {
|
|
624
|
+
connector_id: this._connectorFlowId,
|
|
625
|
+
connector_type: this.constructor.name,
|
|
626
|
+
final_state: this._state,
|
|
627
|
+
timestamp: new Date().toISOString(),
|
|
628
|
+
});
|
|
537
629
|
}
|
|
538
630
|
/**
|
|
539
631
|
* Close the underlying transport
|
|
@@ -5,6 +5,7 @@ const base_async_connector_js_1 = require("./base-async-connector.js");
|
|
|
5
5
|
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
|
+
const core_1 = require("@naylence/core");
|
|
8
9
|
const logger = (0, logging_js_1.getLogger)('naylence.fame.connector.broadcast_channel_connector');
|
|
9
10
|
exports.BROADCAST_CHANNEL_CONNECTOR_TYPE = 'broadcast-channel-connector';
|
|
10
11
|
const DEFAULT_CHANNEL = 'naylence-fabric';
|
|
@@ -70,12 +71,22 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
|
|
|
70
71
|
this.inbox = new bounded_async_queue_js_1.BoundedAsyncQueue(preferredCapacity);
|
|
71
72
|
this.connectorId = BroadcastChannelConnector.generateConnectorId();
|
|
72
73
|
this.channel = new BroadcastChannel(this.channelName);
|
|
73
|
-
logger.
|
|
74
|
+
logger.info('broadcast_channel_connector_created', {
|
|
74
75
|
channel: this.channelName,
|
|
75
76
|
connector_id: this.connectorId,
|
|
76
77
|
inbox_capacity: preferredCapacity,
|
|
78
|
+
timestamp: new Date().toISOString(),
|
|
77
79
|
});
|
|
78
80
|
this.onMsg = (event) => {
|
|
81
|
+
// Guard: Don't process if listener was unregistered
|
|
82
|
+
if (!this.listenerRegistered) {
|
|
83
|
+
logger.warning('broadcast_channel_message_after_unregister', {
|
|
84
|
+
channel: this.channelName,
|
|
85
|
+
connector_id: this.connectorId,
|
|
86
|
+
timestamp: new Date().toISOString(),
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
79
90
|
const message = event.data;
|
|
80
91
|
logger.debug('broadcast_channel_raw_event', {
|
|
81
92
|
channel: this.channelName,
|
|
@@ -147,6 +158,25 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
|
|
|
147
158
|
visibility: isHidden ? 'hidden' : 'visible',
|
|
148
159
|
timestamp: new Date().toISOString(),
|
|
149
160
|
});
|
|
161
|
+
// Pause/resume connector based on visibility
|
|
162
|
+
if (isHidden && this.state === core_1.ConnectorState.STARTED) {
|
|
163
|
+
this.pause().catch((err) => {
|
|
164
|
+
logger.warning('broadcast_channel_pause_failed', {
|
|
165
|
+
channel: this.channelName,
|
|
166
|
+
connector_id: this.connectorId,
|
|
167
|
+
error: err instanceof Error ? err.message : String(err),
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
else if (!isHidden && this.state === core_1.ConnectorState.PAUSED) {
|
|
172
|
+
this.resume().catch((err) => {
|
|
173
|
+
logger.warning('broadcast_channel_resume_failed', {
|
|
174
|
+
channel: this.channelName,
|
|
175
|
+
connector_id: this.connectorId,
|
|
176
|
+
error: err instanceof Error ? err.message : String(err),
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
150
180
|
};
|
|
151
181
|
if (typeof document !== 'undefined') {
|
|
152
182
|
document.addEventListener('visibilitychange', this.visibilityChangeHandler);
|
|
@@ -202,16 +232,44 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
|
|
|
202
232
|
return await this.inbox.dequeue();
|
|
203
233
|
}
|
|
204
234
|
async _transportClose(code, reason) {
|
|
235
|
+
logger.info('broadcast_channel_transport_closing', {
|
|
236
|
+
channel: this.channelName,
|
|
237
|
+
connector_id: this.connectorId,
|
|
238
|
+
code,
|
|
239
|
+
reason,
|
|
240
|
+
listener_registered: this.listenerRegistered,
|
|
241
|
+
timestamp: new Date().toISOString(),
|
|
242
|
+
});
|
|
205
243
|
if (this.listenerRegistered) {
|
|
244
|
+
logger.info('broadcast_channel_removing_listener', {
|
|
245
|
+
channel: this.channelName,
|
|
246
|
+
connector_id: this.connectorId,
|
|
247
|
+
timestamp: new Date().toISOString(),
|
|
248
|
+
});
|
|
206
249
|
this.channel.removeEventListener('message', this.onMsg);
|
|
207
250
|
this.listenerRegistered = false;
|
|
251
|
+
logger.info('broadcast_channel_listener_removed', {
|
|
252
|
+
channel: this.channelName,
|
|
253
|
+
connector_id: this.connectorId,
|
|
254
|
+
timestamp: new Date().toISOString(),
|
|
255
|
+
});
|
|
208
256
|
}
|
|
209
257
|
if (this.visibilityChangeListenerRegistered && this.visibilityChangeHandler && typeof document !== 'undefined') {
|
|
210
258
|
document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
|
|
211
259
|
this.visibilityChangeListenerRegistered = false;
|
|
212
260
|
this.visibilityChangeHandler = undefined;
|
|
213
261
|
}
|
|
262
|
+
logger.info('broadcast_channel_closing', {
|
|
263
|
+
channel: this.channelName,
|
|
264
|
+
connector_id: this.connectorId,
|
|
265
|
+
timestamp: new Date().toISOString(),
|
|
266
|
+
});
|
|
214
267
|
this.channel.close();
|
|
268
|
+
logger.info('broadcast_channel_closed', {
|
|
269
|
+
channel: this.channelName,
|
|
270
|
+
connector_id: this.connectorId,
|
|
271
|
+
timestamp: new Date().toISOString(),
|
|
272
|
+
});
|
|
215
273
|
const closeCode = typeof code === 'number' ? code : 1000;
|
|
216
274
|
const closeReason = typeof reason === 'string' && reason.length > 0 ? reason : 'closed';
|
|
217
275
|
const shutdownError = new errors_js_1.FameTransportClose(closeReason, closeCode);
|
|
@@ -387,7 +387,22 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
|
|
|
387
387
|
this.currentStopSubtasks = null;
|
|
388
388
|
await Promise.allSettled(tasks.map((task) => task.promise));
|
|
389
389
|
if (this.connector) {
|
|
390
|
-
|
|
390
|
+
logger.info('upstream_stopping_old_connector', {
|
|
391
|
+
connect_epoch: this.connectEpoch,
|
|
392
|
+
target_system_id: this.targetSystemId,
|
|
393
|
+
timestamp: new Date().toISOString(),
|
|
394
|
+
});
|
|
395
|
+
await this.connector.stop().catch((err) => {
|
|
396
|
+
logger.warning('upstream_connector_stop_error', {
|
|
397
|
+
connect_epoch: this.connectEpoch,
|
|
398
|
+
error: err instanceof Error ? err.message : String(err),
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
logger.info('upstream_old_connector_stopped', {
|
|
402
|
+
connect_epoch: this.connectEpoch,
|
|
403
|
+
target_system_id: this.targetSystemId,
|
|
404
|
+
timestamp: new Date().toISOString(),
|
|
405
|
+
});
|
|
391
406
|
this.connector = null;
|
|
392
407
|
}
|
|
393
408
|
}
|
|
@@ -521,6 +536,15 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
|
|
|
521
536
|
if (stopEvt.isSet() || signal?.aborted) {
|
|
522
537
|
break;
|
|
523
538
|
}
|
|
539
|
+
// Skip heartbeat if connector is paused (e.g., tab is hidden)
|
|
540
|
+
// Keep ack time current so we don't timeout immediately after resuming
|
|
541
|
+
if (connector.state === core_1.ConnectorState.PAUSED) {
|
|
542
|
+
logger.debug('skipping_heartbeat_connector_paused', {
|
|
543
|
+
connector_state: connector.state,
|
|
544
|
+
});
|
|
545
|
+
this.lastHeartbeatAckTime = Date.now();
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
524
548
|
const envelope = await this.makeHeartbeatEnvelope();
|
|
525
549
|
logger.debug('sending_heartbeat', {
|
|
526
550
|
hb_corr_id: envelope.corrId,
|
|
@@ -542,6 +566,7 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
|
|
|
542
566
|
throw error;
|
|
543
567
|
}
|
|
544
568
|
await this.node.dispatchEvent('onHeartbeatSent', this.node, envelope);
|
|
569
|
+
// Don't check heartbeat timeout when paused
|
|
545
570
|
if (this.lastHeartbeatAckTime !== null &&
|
|
546
571
|
Date.now() - this.lastHeartbeatAckTime > graceMs) {
|
|
547
572
|
throw new errors_js_1.FameConnectError('missed heartbeat acknowledgement');
|
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.936
|
|
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.936';
|
|
@@ -144,6 +144,10 @@ export class BaseAsyncConnector extends TaskSpawner {
|
|
|
144
144
|
* Stop the connector gracefully
|
|
145
145
|
*/
|
|
146
146
|
async stop() {
|
|
147
|
+
logger.debug('stopping_connector', {
|
|
148
|
+
current_state: this._state,
|
|
149
|
+
connector_id: this._connectorFlowId,
|
|
150
|
+
});
|
|
147
151
|
if (!ConnectorStateUtils.canStop(this._state)) {
|
|
148
152
|
logger.debug('connector_stop_already_stopped', {
|
|
149
153
|
current_state: this._state,
|
|
@@ -156,6 +160,52 @@ export class BaseAsyncConnector extends TaskSpawner {
|
|
|
156
160
|
if (this._lastError) {
|
|
157
161
|
throw this._lastError;
|
|
158
162
|
}
|
|
163
|
+
logger.debug('connector_stopped', {
|
|
164
|
+
current_state: this._state,
|
|
165
|
+
connector_id: this._connectorFlowId,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Pause the connector (suspends heartbeat and housekeeping, but keeps connection alive)
|
|
170
|
+
*/
|
|
171
|
+
async pause() {
|
|
172
|
+
logger.debug('pausing_connector', {
|
|
173
|
+
current_state: this._state,
|
|
174
|
+
connector_id: this._connectorFlowId,
|
|
175
|
+
});
|
|
176
|
+
if (this._state !== ConnectorState.STARTED) {
|
|
177
|
+
logger.debug('connector_pause_invalid_state', {
|
|
178
|
+
current_state: this._state,
|
|
179
|
+
connector_id: this._connectorFlowId,
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this._setState(ConnectorState.PAUSED);
|
|
184
|
+
logger.debug('connector_paused', {
|
|
185
|
+
current_state: this._state,
|
|
186
|
+
connector_id: this._connectorFlowId,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Resume the connector from paused state
|
|
191
|
+
*/
|
|
192
|
+
async resume() {
|
|
193
|
+
logger.debug('resuming_connector', {
|
|
194
|
+
current_state: this._state,
|
|
195
|
+
connector_id: this._connectorFlowId,
|
|
196
|
+
});
|
|
197
|
+
if (this._state !== ConnectorState.PAUSED) {
|
|
198
|
+
logger.debug('connector_resume_invalid_state', {
|
|
199
|
+
current_state: this._state,
|
|
200
|
+
connector_id: this._connectorFlowId,
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
this._setState(ConnectorState.STARTED);
|
|
205
|
+
logger.debug('connector_resumed', {
|
|
206
|
+
current_state: this._state,
|
|
207
|
+
connector_id: this._connectorFlowId,
|
|
208
|
+
});
|
|
159
209
|
}
|
|
160
210
|
/**
|
|
161
211
|
* Close the connector with optional code and reason
|
|
@@ -486,8 +536,21 @@ export class BaseAsyncConnector extends TaskSpawner {
|
|
|
486
536
|
*/
|
|
487
537
|
async _shutdown(code, reason, gracePeriod, exc) {
|
|
488
538
|
if (this._closed) {
|
|
539
|
+
logger.debug('shutdown_already_closed', {
|
|
540
|
+
connector_id: this._connectorFlowId,
|
|
541
|
+
current_state: this._state,
|
|
542
|
+
});
|
|
489
543
|
return;
|
|
490
544
|
}
|
|
545
|
+
logger.info('connector_shutdown_starting', {
|
|
546
|
+
connector_id: this._connectorFlowId,
|
|
547
|
+
connector_type: this.constructor.name,
|
|
548
|
+
code,
|
|
549
|
+
reason,
|
|
550
|
+
current_state: this._state,
|
|
551
|
+
has_error: !!exc,
|
|
552
|
+
timestamp: new Date().toISOString(),
|
|
553
|
+
});
|
|
491
554
|
this._closed = true;
|
|
492
555
|
this._closeCode = code;
|
|
493
556
|
this._closeReason = reason;
|
|
@@ -509,16 +572,39 @@ export class BaseAsyncConnector extends TaskSpawner {
|
|
|
509
572
|
this._sendPromiseResolve = undefined;
|
|
510
573
|
}
|
|
511
574
|
// Close transport
|
|
575
|
+
logger.info('connector_closing_transport', {
|
|
576
|
+
connector_id: this._connectorFlowId,
|
|
577
|
+
connector_type: this.constructor.name,
|
|
578
|
+
timestamp: new Date().toISOString(),
|
|
579
|
+
});
|
|
512
580
|
await this._transportClose(code, reason);
|
|
581
|
+
logger.info('connector_transport_closed', {
|
|
582
|
+
connector_id: this._connectorFlowId,
|
|
583
|
+
connector_type: this.constructor.name,
|
|
584
|
+
timestamp: new Date().toISOString(),
|
|
585
|
+
});
|
|
513
586
|
// Shutdown spawned tasks
|
|
587
|
+
logger.info('connector_shutting_down_tasks', {
|
|
588
|
+
connector_id: this._connectorFlowId,
|
|
589
|
+
connector_type: this.constructor.name,
|
|
590
|
+
grace_period_ms: effectiveGracePeriod * 1000,
|
|
591
|
+
join_timeout_ms: this._shutdownJoinTimeout,
|
|
592
|
+
timestamp: new Date().toISOString(),
|
|
593
|
+
});
|
|
514
594
|
try {
|
|
515
595
|
await this.shutdownTasks({
|
|
516
596
|
gracePeriod: effectiveGracePeriod * 1000, // Convert to milliseconds
|
|
517
597
|
joinTimeout: this._shutdownJoinTimeout,
|
|
518
598
|
});
|
|
599
|
+
logger.info('connector_tasks_shutdown_complete', {
|
|
600
|
+
connector_id: this._connectorFlowId,
|
|
601
|
+
connector_type: this.constructor.name,
|
|
602
|
+
timestamp: new Date().toISOString(),
|
|
603
|
+
});
|
|
519
604
|
}
|
|
520
605
|
catch (error) {
|
|
521
606
|
logger.warning('task_shutdown_error', {
|
|
607
|
+
connector_id: this._connectorFlowId,
|
|
522
608
|
error: error instanceof Error ? error.message : String(error),
|
|
523
609
|
});
|
|
524
610
|
}
|
|
@@ -530,6 +616,12 @@ export class BaseAsyncConnector extends TaskSpawner {
|
|
|
530
616
|
if (this._closeResolver) {
|
|
531
617
|
this._closeResolver();
|
|
532
618
|
}
|
|
619
|
+
logger.info('connector_shutdown_complete', {
|
|
620
|
+
connector_id: this._connectorFlowId,
|
|
621
|
+
connector_type: this.constructor.name,
|
|
622
|
+
final_state: this._state,
|
|
623
|
+
timestamp: new Date().toISOString(),
|
|
624
|
+
});
|
|
533
625
|
}
|
|
534
626
|
/**
|
|
535
627
|
* Close the underlying transport
|
|
@@ -2,6 +2,7 @@ import { BaseAsyncConnector, } from './base-async-connector.js';
|
|
|
2
2
|
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
|
+
import { ConnectorState } from '@naylence/core';
|
|
5
6
|
const logger = getLogger('naylence.fame.connector.broadcast_channel_connector');
|
|
6
7
|
export const BROADCAST_CHANNEL_CONNECTOR_TYPE = 'broadcast-channel-connector';
|
|
7
8
|
const DEFAULT_CHANNEL = 'naylence-fabric';
|
|
@@ -67,12 +68,22 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
|
|
|
67
68
|
this.inbox = new BoundedAsyncQueue(preferredCapacity);
|
|
68
69
|
this.connectorId = BroadcastChannelConnector.generateConnectorId();
|
|
69
70
|
this.channel = new BroadcastChannel(this.channelName);
|
|
70
|
-
logger.
|
|
71
|
+
logger.info('broadcast_channel_connector_created', {
|
|
71
72
|
channel: this.channelName,
|
|
72
73
|
connector_id: this.connectorId,
|
|
73
74
|
inbox_capacity: preferredCapacity,
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
74
76
|
});
|
|
75
77
|
this.onMsg = (event) => {
|
|
78
|
+
// Guard: Don't process if listener was unregistered
|
|
79
|
+
if (!this.listenerRegistered) {
|
|
80
|
+
logger.warning('broadcast_channel_message_after_unregister', {
|
|
81
|
+
channel: this.channelName,
|
|
82
|
+
connector_id: this.connectorId,
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
76
87
|
const message = event.data;
|
|
77
88
|
logger.debug('broadcast_channel_raw_event', {
|
|
78
89
|
channel: this.channelName,
|
|
@@ -144,6 +155,25 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
|
|
|
144
155
|
visibility: isHidden ? 'hidden' : 'visible',
|
|
145
156
|
timestamp: new Date().toISOString(),
|
|
146
157
|
});
|
|
158
|
+
// Pause/resume connector based on visibility
|
|
159
|
+
if (isHidden && this.state === ConnectorState.STARTED) {
|
|
160
|
+
this.pause().catch((err) => {
|
|
161
|
+
logger.warning('broadcast_channel_pause_failed', {
|
|
162
|
+
channel: this.channelName,
|
|
163
|
+
connector_id: this.connectorId,
|
|
164
|
+
error: err instanceof Error ? err.message : String(err),
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
else if (!isHidden && this.state === ConnectorState.PAUSED) {
|
|
169
|
+
this.resume().catch((err) => {
|
|
170
|
+
logger.warning('broadcast_channel_resume_failed', {
|
|
171
|
+
channel: this.channelName,
|
|
172
|
+
connector_id: this.connectorId,
|
|
173
|
+
error: err instanceof Error ? err.message : String(err),
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
147
177
|
};
|
|
148
178
|
if (typeof document !== 'undefined') {
|
|
149
179
|
document.addEventListener('visibilitychange', this.visibilityChangeHandler);
|
|
@@ -199,16 +229,44 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
|
|
|
199
229
|
return await this.inbox.dequeue();
|
|
200
230
|
}
|
|
201
231
|
async _transportClose(code, reason) {
|
|
232
|
+
logger.info('broadcast_channel_transport_closing', {
|
|
233
|
+
channel: this.channelName,
|
|
234
|
+
connector_id: this.connectorId,
|
|
235
|
+
code,
|
|
236
|
+
reason,
|
|
237
|
+
listener_registered: this.listenerRegistered,
|
|
238
|
+
timestamp: new Date().toISOString(),
|
|
239
|
+
});
|
|
202
240
|
if (this.listenerRegistered) {
|
|
241
|
+
logger.info('broadcast_channel_removing_listener', {
|
|
242
|
+
channel: this.channelName,
|
|
243
|
+
connector_id: this.connectorId,
|
|
244
|
+
timestamp: new Date().toISOString(),
|
|
245
|
+
});
|
|
203
246
|
this.channel.removeEventListener('message', this.onMsg);
|
|
204
247
|
this.listenerRegistered = false;
|
|
248
|
+
logger.info('broadcast_channel_listener_removed', {
|
|
249
|
+
channel: this.channelName,
|
|
250
|
+
connector_id: this.connectorId,
|
|
251
|
+
timestamp: new Date().toISOString(),
|
|
252
|
+
});
|
|
205
253
|
}
|
|
206
254
|
if (this.visibilityChangeListenerRegistered && this.visibilityChangeHandler && typeof document !== 'undefined') {
|
|
207
255
|
document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
|
|
208
256
|
this.visibilityChangeListenerRegistered = false;
|
|
209
257
|
this.visibilityChangeHandler = undefined;
|
|
210
258
|
}
|
|
259
|
+
logger.info('broadcast_channel_closing', {
|
|
260
|
+
channel: this.channelName,
|
|
261
|
+
connector_id: this.connectorId,
|
|
262
|
+
timestamp: new Date().toISOString(),
|
|
263
|
+
});
|
|
211
264
|
this.channel.close();
|
|
265
|
+
logger.info('broadcast_channel_closed', {
|
|
266
|
+
channel: this.channelName,
|
|
267
|
+
connector_id: this.connectorId,
|
|
268
|
+
timestamp: new Date().toISOString(),
|
|
269
|
+
});
|
|
212
270
|
const closeCode = typeof code === 'number' ? code : 1000;
|
|
213
271
|
const closeReason = typeof reason === 'string' && reason.length > 0 ? reason : 'closed';
|
|
214
272
|
const shutdownError = new FameTransportClose(closeReason, closeCode);
|
|
@@ -2,7 +2,7 @@ import { ConnectorFactory } from '../connector/connector-factory.js';
|
|
|
2
2
|
import { TaskSpawner } from '../util/task-spawner.js';
|
|
3
3
|
import { AsyncEvent } from '../util/async-event.js';
|
|
4
4
|
import { getLogger } from '../util/logging.js';
|
|
5
|
-
import { DeliveryOriginType, FameFabric, generateId, } from '@naylence/core';
|
|
5
|
+
import { ConnectorState, DeliveryOriginType, FameFabric, generateId, } from '@naylence/core';
|
|
6
6
|
import { FameConnectError, FameMessageTooLarge, FameTransportClose, } from '../errors/errors.js';
|
|
7
7
|
import { TaskCancelledError } from '../util/task-types.js';
|
|
8
8
|
import { FameResponseType } from '@naylence/core';
|
|
@@ -384,7 +384,22 @@ export class UpstreamSessionManager extends TaskSpawner {
|
|
|
384
384
|
this.currentStopSubtasks = null;
|
|
385
385
|
await Promise.allSettled(tasks.map((task) => task.promise));
|
|
386
386
|
if (this.connector) {
|
|
387
|
-
|
|
387
|
+
logger.info('upstream_stopping_old_connector', {
|
|
388
|
+
connect_epoch: this.connectEpoch,
|
|
389
|
+
target_system_id: this.targetSystemId,
|
|
390
|
+
timestamp: new Date().toISOString(),
|
|
391
|
+
});
|
|
392
|
+
await this.connector.stop().catch((err) => {
|
|
393
|
+
logger.warning('upstream_connector_stop_error', {
|
|
394
|
+
connect_epoch: this.connectEpoch,
|
|
395
|
+
error: err instanceof Error ? err.message : String(err),
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
logger.info('upstream_old_connector_stopped', {
|
|
399
|
+
connect_epoch: this.connectEpoch,
|
|
400
|
+
target_system_id: this.targetSystemId,
|
|
401
|
+
timestamp: new Date().toISOString(),
|
|
402
|
+
});
|
|
388
403
|
this.connector = null;
|
|
389
404
|
}
|
|
390
405
|
}
|
|
@@ -518,6 +533,15 @@ export class UpstreamSessionManager extends TaskSpawner {
|
|
|
518
533
|
if (stopEvt.isSet() || signal?.aborted) {
|
|
519
534
|
break;
|
|
520
535
|
}
|
|
536
|
+
// Skip heartbeat if connector is paused (e.g., tab is hidden)
|
|
537
|
+
// Keep ack time current so we don't timeout immediately after resuming
|
|
538
|
+
if (connector.state === ConnectorState.PAUSED) {
|
|
539
|
+
logger.debug('skipping_heartbeat_connector_paused', {
|
|
540
|
+
connector_state: connector.state,
|
|
541
|
+
});
|
|
542
|
+
this.lastHeartbeatAckTime = Date.now();
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
521
545
|
const envelope = await this.makeHeartbeatEnvelope();
|
|
522
546
|
logger.debug('sending_heartbeat', {
|
|
523
547
|
hb_corr_id: envelope.corrId,
|
|
@@ -539,6 +563,7 @@ export class UpstreamSessionManager extends TaskSpawner {
|
|
|
539
563
|
throw error;
|
|
540
564
|
}
|
|
541
565
|
await this.node.dispatchEvent('onHeartbeatSent', this.node, envelope);
|
|
566
|
+
// Don't check heartbeat timeout when paused
|
|
542
567
|
if (this.lastHeartbeatAckTime !== null &&
|
|
543
568
|
Date.now() - this.lastHeartbeatAckTime > graceMs) {
|
|
544
569
|
throw new FameConnectError('missed heartbeat acknowledgement');
|
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.936
|
|
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.936';
|