@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.
@@ -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.debug('broadcast_channel_connector_initialized', {
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
- await this.connector.stop().catch(() => undefined);
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');
@@ -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.933
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.933';
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.debug('broadcast_channel_connector_initialized', {
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
- await this.connector.stop().catch(() => undefined);
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');
@@ -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.933
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.933';
7
+ export const VERSION = '0.3.5-test.936';