@naylence/runtime 0.3.5-test.934 → 0.3.5-test.937

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.
@@ -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,7 +71,7 @@ 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.info('broadcast_channel_connector_created', {
74
+ logger.debug('broadcast_channel_connector_created', {
74
75
  channel: this.channelName,
75
76
  connector_id: this.connectorId,
76
77
  inbox_capacity: preferredCapacity,
@@ -151,18 +152,37 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
151
152
  // Setup visibility change monitoring
152
153
  this.visibilityChangeHandler = () => {
153
154
  const isHidden = document.hidden;
154
- logger.info('broadcast_channel_visibility_changed', {
155
+ logger.debug('broadcast_channel_visibility_changed', {
155
156
  channel: this.channelName,
156
157
  connector_id: this.connectorId,
157
158
  visibility: isHidden ? 'hidden' : 'visible',
158
159
  timestamp: new Date().toISOString(),
159
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
+ }
160
180
  };
161
181
  if (typeof document !== 'undefined') {
162
182
  document.addEventListener('visibilitychange', this.visibilityChangeHandler);
163
183
  this.visibilityChangeListenerRegistered = true;
164
184
  // Log initial state
165
- logger.info('broadcast_channel_initial_visibility', {
185
+ logger.debug('broadcast_channel_initial_visibility', {
166
186
  channel: this.channelName,
167
187
  connector_id: this.connectorId,
168
188
  visibility: document.hidden ? 'hidden' : 'visible',
@@ -212,7 +232,7 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
212
232
  return await this.inbox.dequeue();
213
233
  }
214
234
  async _transportClose(code, reason) {
215
- logger.info('broadcast_channel_transport_closing', {
235
+ logger.debug('broadcast_channel_transport_closing', {
216
236
  channel: this.channelName,
217
237
  connector_id: this.connectorId,
218
238
  code,
@@ -221,14 +241,14 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
221
241
  timestamp: new Date().toISOString(),
222
242
  });
223
243
  if (this.listenerRegistered) {
224
- logger.info('broadcast_channel_removing_listener', {
244
+ logger.debug('broadcast_channel_removing_listener', {
225
245
  channel: this.channelName,
226
246
  connector_id: this.connectorId,
227
247
  timestamp: new Date().toISOString(),
228
248
  });
229
249
  this.channel.removeEventListener('message', this.onMsg);
230
250
  this.listenerRegistered = false;
231
- logger.info('broadcast_channel_listener_removed', {
251
+ logger.debug('broadcast_channel_listener_removed', {
232
252
  channel: this.channelName,
233
253
  connector_id: this.connectorId,
234
254
  timestamp: new Date().toISOString(),
@@ -239,13 +259,13 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
239
259
  this.visibilityChangeListenerRegistered = false;
240
260
  this.visibilityChangeHandler = undefined;
241
261
  }
242
- logger.info('broadcast_channel_closing', {
262
+ logger.debug('broadcast_channel_closing', {
243
263
  channel: this.channelName,
244
264
  connector_id: this.connectorId,
245
265
  timestamp: new Date().toISOString(),
246
266
  });
247
267
  this.channel.close();
248
- logger.info('broadcast_channel_closed', {
268
+ logger.debug('broadcast_channel_closed', {
249
269
  channel: this.channelName,
250
270
  connector_id: this.connectorId,
251
271
  timestamp: new Date().toISOString(),
@@ -336,6 +356,28 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
336
356
  }
337
357
  return undefined;
338
358
  }
359
+ /**
360
+ * Override start() to check initial visibility state
361
+ */
362
+ async start(inboundHandler) {
363
+ await super.start(inboundHandler);
364
+ // After transitioning to STARTED, check if tab is already hidden
365
+ if (typeof document !== 'undefined' && document.hidden) {
366
+ logger.debug('broadcast_channel_start_in_hidden_tab', {
367
+ channel: this.channelName,
368
+ connector_id: this.connectorId,
369
+ timestamp: new Date().toISOString(),
370
+ });
371
+ // Immediately pause if tab is hidden at start time
372
+ await this.pause().catch((err) => {
373
+ logger.warning('broadcast_channel_initial_pause_failed', {
374
+ channel: this.channelName,
375
+ connector_id: this.connectorId,
376
+ error: err instanceof Error ? err.message : String(err),
377
+ });
378
+ });
379
+ }
380
+ }
339
381
  _trimSeenAcks(now) {
340
382
  while (this.seenAckOrder.length > 0) {
341
383
  const candidate = this.seenAckOrder[0];
@@ -536,6 +536,15 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
536
536
  if (stopEvt.isSet() || signal?.aborted) {
537
537
  break;
538
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
+ }
539
548
  const envelope = await this.makeHeartbeatEnvelope();
540
549
  logger.debug('sending_heartbeat', {
541
550
  hb_corr_id: envelope.corrId,
@@ -557,6 +566,7 @@ class UpstreamSessionManager extends task_spawner_js_1.TaskSpawner {
557
566
  throw error;
558
567
  }
559
568
  await this.node.dispatchEvent('onHeartbeatSent', this.node, envelope);
569
+ // Don't check heartbeat timeout when paused
560
570
  if (this.lastHeartbeatAckTime !== null &&
561
571
  Date.now() - this.lastHeartbeatAckTime > graceMs) {
562
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.934
3
+ // Generated from package.json version: 0.3.5-test.937
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.934';
10
+ exports.VERSION = '0.3.5-test.937';
@@ -165,6 +165,48 @@ export class BaseAsyncConnector extends TaskSpawner {
165
165
  connector_id: this._connectorFlowId,
166
166
  });
167
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
+ });
209
+ }
168
210
  /**
169
211
  * Close the connector with optional code and reason
170
212
  */
@@ -500,7 +542,7 @@ export class BaseAsyncConnector extends TaskSpawner {
500
542
  });
501
543
  return;
502
544
  }
503
- logger.info('connector_shutdown_starting', {
545
+ logger.debug('connector_shutdown_starting', {
504
546
  connector_id: this._connectorFlowId,
505
547
  connector_type: this.constructor.name,
506
548
  code,
@@ -530,19 +572,19 @@ export class BaseAsyncConnector extends TaskSpawner {
530
572
  this._sendPromiseResolve = undefined;
531
573
  }
532
574
  // Close transport
533
- logger.info('connector_closing_transport', {
575
+ logger.debug('connector_closing_transport', {
534
576
  connector_id: this._connectorFlowId,
535
577
  connector_type: this.constructor.name,
536
578
  timestamp: new Date().toISOString(),
537
579
  });
538
580
  await this._transportClose(code, reason);
539
- logger.info('connector_transport_closed', {
581
+ logger.debug('connector_transport_closed', {
540
582
  connector_id: this._connectorFlowId,
541
583
  connector_type: this.constructor.name,
542
584
  timestamp: new Date().toISOString(),
543
585
  });
544
586
  // Shutdown spawned tasks
545
- logger.info('connector_shutting_down_tasks', {
587
+ logger.debug('connector_shutting_down_tasks', {
546
588
  connector_id: this._connectorFlowId,
547
589
  connector_type: this.constructor.name,
548
590
  grace_period_ms: effectiveGracePeriod * 1000,
@@ -554,7 +596,7 @@ export class BaseAsyncConnector extends TaskSpawner {
554
596
  gracePeriod: effectiveGracePeriod * 1000, // Convert to milliseconds
555
597
  joinTimeout: this._shutdownJoinTimeout,
556
598
  });
557
- logger.info('connector_tasks_shutdown_complete', {
599
+ logger.debug('connector_tasks_shutdown_complete', {
558
600
  connector_id: this._connectorFlowId,
559
601
  connector_type: this.constructor.name,
560
602
  timestamp: new Date().toISOString(),
@@ -574,7 +616,7 @@ export class BaseAsyncConnector extends TaskSpawner {
574
616
  if (this._closeResolver) {
575
617
  this._closeResolver();
576
618
  }
577
- logger.info('connector_shutdown_complete', {
619
+ logger.debug('connector_shutdown_complete', {
578
620
  connector_id: this._connectorFlowId,
579
621
  connector_type: this.constructor.name,
580
622
  final_state: this._state,
@@ -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,7 +68,7 @@ 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.info('broadcast_channel_connector_created', {
71
+ logger.debug('broadcast_channel_connector_created', {
71
72
  channel: this.channelName,
72
73
  connector_id: this.connectorId,
73
74
  inbox_capacity: preferredCapacity,
@@ -148,18 +149,37 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
148
149
  // Setup visibility change monitoring
149
150
  this.visibilityChangeHandler = () => {
150
151
  const isHidden = document.hidden;
151
- logger.info('broadcast_channel_visibility_changed', {
152
+ logger.debug('broadcast_channel_visibility_changed', {
152
153
  channel: this.channelName,
153
154
  connector_id: this.connectorId,
154
155
  visibility: isHidden ? 'hidden' : 'visible',
155
156
  timestamp: new Date().toISOString(),
156
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
+ }
157
177
  };
158
178
  if (typeof document !== 'undefined') {
159
179
  document.addEventListener('visibilitychange', this.visibilityChangeHandler);
160
180
  this.visibilityChangeListenerRegistered = true;
161
181
  // Log initial state
162
- logger.info('broadcast_channel_initial_visibility', {
182
+ logger.debug('broadcast_channel_initial_visibility', {
163
183
  channel: this.channelName,
164
184
  connector_id: this.connectorId,
165
185
  visibility: document.hidden ? 'hidden' : 'visible',
@@ -209,7 +229,7 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
209
229
  return await this.inbox.dequeue();
210
230
  }
211
231
  async _transportClose(code, reason) {
212
- logger.info('broadcast_channel_transport_closing', {
232
+ logger.debug('broadcast_channel_transport_closing', {
213
233
  channel: this.channelName,
214
234
  connector_id: this.connectorId,
215
235
  code,
@@ -218,14 +238,14 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
218
238
  timestamp: new Date().toISOString(),
219
239
  });
220
240
  if (this.listenerRegistered) {
221
- logger.info('broadcast_channel_removing_listener', {
241
+ logger.debug('broadcast_channel_removing_listener', {
222
242
  channel: this.channelName,
223
243
  connector_id: this.connectorId,
224
244
  timestamp: new Date().toISOString(),
225
245
  });
226
246
  this.channel.removeEventListener('message', this.onMsg);
227
247
  this.listenerRegistered = false;
228
- logger.info('broadcast_channel_listener_removed', {
248
+ logger.debug('broadcast_channel_listener_removed', {
229
249
  channel: this.channelName,
230
250
  connector_id: this.connectorId,
231
251
  timestamp: new Date().toISOString(),
@@ -236,13 +256,13 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
236
256
  this.visibilityChangeListenerRegistered = false;
237
257
  this.visibilityChangeHandler = undefined;
238
258
  }
239
- logger.info('broadcast_channel_closing', {
259
+ logger.debug('broadcast_channel_closing', {
240
260
  channel: this.channelName,
241
261
  connector_id: this.connectorId,
242
262
  timestamp: new Date().toISOString(),
243
263
  });
244
264
  this.channel.close();
245
- logger.info('broadcast_channel_closed', {
265
+ logger.debug('broadcast_channel_closed', {
246
266
  channel: this.channelName,
247
267
  connector_id: this.connectorId,
248
268
  timestamp: new Date().toISOString(),
@@ -333,6 +353,28 @@ export class BroadcastChannelConnector extends BaseAsyncConnector {
333
353
  }
334
354
  return undefined;
335
355
  }
356
+ /**
357
+ * Override start() to check initial visibility state
358
+ */
359
+ async start(inboundHandler) {
360
+ await super.start(inboundHandler);
361
+ // After transitioning to STARTED, check if tab is already hidden
362
+ if (typeof document !== 'undefined' && document.hidden) {
363
+ logger.debug('broadcast_channel_start_in_hidden_tab', {
364
+ channel: this.channelName,
365
+ connector_id: this.connectorId,
366
+ timestamp: new Date().toISOString(),
367
+ });
368
+ // Immediately pause if tab is hidden at start time
369
+ await this.pause().catch((err) => {
370
+ logger.warning('broadcast_channel_initial_pause_failed', {
371
+ channel: this.channelName,
372
+ connector_id: this.connectorId,
373
+ error: err instanceof Error ? err.message : String(err),
374
+ });
375
+ });
376
+ }
377
+ }
336
378
  _trimSeenAcks(now) {
337
379
  while (this.seenAckOrder.length > 0) {
338
380
  const candidate = this.seenAckOrder[0];
@@ -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';
@@ -533,6 +533,15 @@ export class UpstreamSessionManager extends TaskSpawner {
533
533
  if (stopEvt.isSet() || signal?.aborted) {
534
534
  break;
535
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
+ }
536
545
  const envelope = await this.makeHeartbeatEnvelope();
537
546
  logger.debug('sending_heartbeat', {
538
547
  hb_corr_id: envelope.corrId,
@@ -554,6 +563,7 @@ export class UpstreamSessionManager extends TaskSpawner {
554
563
  throw error;
555
564
  }
556
565
  await this.node.dispatchEvent('onHeartbeatSent', this.node, envelope);
566
+ // Don't check heartbeat timeout when paused
557
567
  if (this.lastHeartbeatAckTime !== null &&
558
568
  Date.now() - this.lastHeartbeatAckTime > graceMs) {
559
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.934
2
+ // Generated from package.json version: 0.3.5-test.937
3
3
  /**
4
4
  * The package version, injected at build time.
5
5
  * @internal
6
6
  */
7
- export const VERSION = '0.3.5-test.934';
7
+ export const VERSION = '0.3.5-test.937';