@signalwire/js 4.0.0-beta.12 → 4.0.0-beta.13

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/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_operators = require('./operators-D6a2J1KA.cjs');
1
+ const require_operators = require('./operators-Bn4Ij3VB.cjs');
2
2
  let jwt_decode = require("jwt-decode");
3
3
  let rxjs = require("rxjs");
4
4
  let uuid = require("uuid");
@@ -131,7 +131,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
131
131
 
132
132
  //#endregion
133
133
  //#region src/controllers/HTTPRequestController.ts
134
- const logger$30 = require_operators.getLogger();
134
+ const logger$31 = require_operators.getLogger();
135
135
  const GET_PARAMS = {
136
136
  method: "GET",
137
137
  headers: { Accept: "application/json" }
@@ -195,7 +195,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
195
195
  this._responses$.next(response);
196
196
  return response;
197
197
  } catch (error) {
198
- logger$30.error("[HTTPRequestController] Request error:", error);
198
+ logger$31.error("[HTTPRequestController] Request error:", error);
199
199
  this._status$.next("error");
200
200
  const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
201
201
  this._errors$.next(err);
@@ -222,7 +222,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
222
222
  const url = this.buildURL(request.url);
223
223
  const headers = this.buildHeaders(request.headers);
224
224
  const timeout$4 = request.timeout ?? this.requestTimeout;
225
- logger$30.debug("[HTTPRequestController] Executing request:", {
225
+ logger$31.debug("[HTTPRequestController] Executing request:", {
226
226
  method: request.method,
227
227
  url,
228
228
  headers: Object.keys(headers).reduce((acc, key) => {
@@ -242,7 +242,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
242
242
  });
243
243
  clearTimeout(timeoutId);
244
244
  const httpResponse = await this.convertResponse(response);
245
- logger$30.debug("[HTTPRequestController] Response received:", {
245
+ logger$31.debug("[HTTPRequestController] Response received:", {
246
246
  status: response.status,
247
247
  statusText: response.statusText,
248
248
  headers: [...response.headers.entries()],
@@ -252,7 +252,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
252
252
  } catch (error) {
253
253
  clearTimeout(timeoutId);
254
254
  if (error instanceof Error && error.name === "AbortError") throw new require_operators.RequestTimeoutError(`Request timeout after ${timeout$4}ms`, { cause: error });
255
- logger$30.error("[HTTPRequestController] Request failed:", error);
255
+ logger$31.error("[HTTPRequestController] Request failed:", error);
256
256
  throw error;
257
257
  }
258
258
  }
@@ -266,8 +266,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
266
266
  const credential = this.getCredential();
267
267
  if (credential.token) {
268
268
  headers.Authorization = `Bearer ${credential.token}`;
269
- logger$30.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
270
- } else logger$30.warn("[HTTPRequestController] No credentials available for authentication");
269
+ logger$31.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
270
+ } else logger$31.warn("[HTTPRequestController] No credentials available for authentication");
271
271
  return headers;
272
272
  }
273
273
  /**
@@ -404,130 +404,6 @@ var DeviceHistoryManager = class {
404
404
  }
405
405
  };
406
406
 
407
- //#endregion
408
- //#region src/core/constants.ts
409
- const INVITE_VERSION = 1e3;
410
- const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
411
- const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
412
- const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
413
- const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
414
- const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
415
- const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
416
- const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
417
- const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
418
- const PREFERENCES_STORAGE_KEY = "sw:preferences";
419
- /** Scope value that enables automatic token refresh. */
420
- const SAT_REFRESH_SCOPE = "sat:refresh";
421
- /** API endpoints for device token operations. */
422
- const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
423
- const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
424
- /** Default device token TTL in seconds (15 minutes). */
425
- const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
426
- /** Buffer time in milliseconds before expiry to trigger refresh. */
427
- const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
428
- /** Maximum retry attempts for device token refresh on transient failure. */
429
- const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
430
- /** Base delay in milliseconds for exponential backoff on refresh retry. */
431
- const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
432
- /** Maximum retry attempts for developer credential refresh on transient failure. */
433
- const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
434
- /** Base delay in milliseconds for exponential backoff on credential refresh retry. */
435
- const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
436
- /** Maximum delay in milliseconds for credential refresh backoff. */
437
- const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
438
- /** Buffer in milliseconds before token expiry to trigger refresh. */
439
- const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
440
- /** JSON-RPC error code for requester validation failure (corrupted auth state). */
441
- const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
442
- /** JSON-RPC error code for invalid params (e.g., missing authentication block). */
443
- const RPC_ERROR_INVALID_PARAMS = -32602;
444
- /** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
445
- const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
446
- /** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
447
- const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
448
- /** Number of initial samples used to build a baseline for spike detection. */
449
- const DEFAULT_STATS_BASELINE_SAMPLES = 10;
450
- /** Duration in ms with no inbound audio packets before emitting a critical issue. */
451
- const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
452
- /** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
453
- const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
454
- /** Packet loss fraction (0-1) above which a warning is emitted. */
455
- const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
456
- /** Multiplier applied to baseline jitter to detect a jitter spike. */
457
- const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
458
- /** Number of seconds of metrics history to retain. */
459
- const DEFAULT_STATS_HISTORY_SIZE = 30;
460
- /** Maximum keyframe requests allowed within a single burst window. */
461
- const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
462
- /** Duration of the keyframe burst window in milliseconds. */
463
- const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
464
- /** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
465
- const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
466
- /** Minimum time between re-INVITE attempts in milliseconds. */
467
- const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
468
- /** Maximum number of re-INVITE attempts per call. */
469
- const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
470
- /** Timeout for a single re-INVITE attempt in milliseconds. */
471
- const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
472
- /** Debounce window in ms to collapse multiple detection signals into one trigger. */
473
- const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
474
- /** Cooldown period in ms between recovery attempts. */
475
- const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
476
- /** Grace period in ms before treating ICE 'disconnected' as a failure. */
477
- const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
478
- /** Timeout for a single ICE restart attempt in milliseconds. */
479
- const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
480
- /** Maximum recovery attempts before emitting 'max_attempts_reached'. */
481
- const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
482
- /** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
483
- const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
484
- /** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
485
- const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
486
- /** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
487
- const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
488
- /** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
489
- const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
490
- /** RMS level threshold (0..1) above which the local participant is considered speaking. */
491
- const VAD_THRESHOLD = .03;
492
- /** Hold window in ms below the threshold before speaking$ flips back to false. */
493
- const VAD_HOLD_MS = 250;
494
- /** Whether to persist device selections to storage by default. */
495
- const DEFAULT_PERSIST_DEVICE_SELECTION = true;
496
- /** Whether to auto-apply device changes to active calls by default. */
497
- const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
498
- /** Storage keys for persisted device selections. */
499
- const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
500
- const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
501
- const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
502
- /** Whether to auto-mute video when the tab becomes hidden. */
503
- const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
504
- /** Whether to re-enumerate devices when the page becomes visible. */
505
- const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
506
- /** Whether to check peer connection health when the page becomes visible. */
507
- const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
508
- /** Whether automatic video degradation on low bandwidth is enabled. */
509
- const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
510
- /** Bitrate in kbps below which video is automatically disabled. */
511
- const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
512
- /** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
513
- const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
514
- /** Whether relay-only escalation is enabled as a last-resort recovery tier. */
515
- const DEFAULT_ENABLE_RELAY_FALLBACK = true;
516
- /** Whether to listen for browser online/offline/connection events. */
517
- const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
518
- /** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
519
- const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
520
- /** Default video track constraints applied when video is enabled without explicit constraints. */
521
- const DEFAULT_VIDEO_CONSTRAINTS = {
522
- width: { ideal: 1280 },
523
- height: { ideal: 720 },
524
- aspectRatio: 16 / 9
525
- };
526
- /** Whether stereo Opus is enabled by default. */
527
- const DEFAULT_STEREO_AUDIO = false;
528
- /** Max average bitrate for stereo Opus in bits per second. */
529
- const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
530
-
531
407
  //#endregion
532
408
  //#region src/utils/time.ts
533
409
  function fromSecToMs(seconds) {
@@ -539,23 +415,23 @@ function fromMsToSec(milliseconds) {
539
415
 
540
416
  //#endregion
541
417
  //#region src/containers/PreferencesContainer.ts
542
- const logger$29 = require_operators.getLogger();
418
+ const logger$30 = require_operators.getLogger();
543
419
  var PreferencesContainer = class PreferencesContainer {
544
420
  static get instance() {
545
421
  this._instance ??= new PreferencesContainer();
546
422
  return this._instance;
547
423
  }
548
424
  constructor() {
549
- this.deviceDebounceTime = DEFAULT_DEVICE_DEBOUNCE_TIME_MS;
550
- this.devicePollingInterval = DEFAULT_DEVICE_POLLING_INTERVAL_MS;
551
- this.reconnectCallsTimeout = DEFAULT_RECONNECT_CALLS_TIMEOUT_MS;
552
- this.connectionTimeout = DEFAULT_CONNECTION_TIMEOUT_MS;
553
- this.reconnectDelayMin = DEFAULT_RECONNECT_DELAY_MIN_MS;
554
- this.reconnectDelayMax = DEFAULT_RECONNECT_DELAY_MAX_MS;
425
+ this.deviceDebounceTime = require_operators.DEFAULT_DEVICE_DEBOUNCE_TIME_MS;
426
+ this.devicePollingInterval = require_operators.DEFAULT_DEVICE_POLLING_INTERVAL_MS;
427
+ this.reconnectCallsTimeout = require_operators.DEFAULT_RECONNECT_CALLS_TIMEOUT_MS;
428
+ this.connectionTimeout = require_operators.DEFAULT_CONNECTION_TIMEOUT_MS;
429
+ this.reconnectDelayMin = require_operators.DEFAULT_RECONNECT_DELAY_MIN_MS;
430
+ this.reconnectDelayMax = require_operators.DEFAULT_RECONNECT_DELAY_MAX_MS;
555
431
  this.disableUdpIceServers = false;
556
432
  this.relayOnly = false;
557
- this.iceCandidateTimeout = DEFAULT_ICE_CANDIDATE_TIMEOUT_MS;
558
- this.iceGatheringTimeout = DEFAULT_ICE_GATHERING_TIMEOUT_MS;
433
+ this.iceCandidateTimeout = require_operators.DEFAULT_ICE_CANDIDATE_TIMEOUT_MS;
434
+ this.iceGatheringTimeout = require_operators.DEFAULT_ICE_GATHERING_TIMEOUT_MS;
559
435
  this.defaultSignalWireOptions = {
560
436
  skipConnection: false,
561
437
  skipRegister: false,
@@ -582,38 +458,38 @@ var PreferencesContainer = class PreferencesContainer {
582
458
  "call.joined"
583
459
  ];
584
460
  this.userVariables = {};
585
- this.statsPollingInterval = DEFAULT_STATS_POLLING_INTERVAL_MS;
586
- this.statsBaselineSamples = DEFAULT_STATS_BASELINE_SAMPLES;
587
- this.statsNoPacketThreshold = DEFAULT_STATS_NO_PACKET_THRESHOLD_MS;
588
- this.statsRttSpikeMultiplier = DEFAULT_STATS_RTT_SPIKE_MULTIPLIER;
589
- this.statsPacketLossThreshold = DEFAULT_STATS_PACKET_LOSS_THRESHOLD;
590
- this.statsJitterSpikeMultiplier = DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER;
591
- this.statsHistorySize = DEFAULT_STATS_HISTORY_SIZE;
592
- this.keyframeMaxBurst = DEFAULT_KEYFRAME_MAX_BURST$1;
593
- this.keyframeBurstWindow = DEFAULT_KEYFRAME_BURST_WINDOW_MS$1;
594
- this.keyframeCooldown = DEFAULT_KEYFRAME_COOLDOWN_MS$1;
595
- this.reinviteDebounceTime = DEFAULT_REINVITE_DEBOUNCE_TIME_MS;
596
- this.reinviteMaxAttempts = DEFAULT_REINVITE_MAX_ATTEMPTS;
597
- this.reinviteTimeout = DEFAULT_REINVITE_TIMEOUT_MS;
598
- this.recoveryDebounceTime = DEFAULT_RECOVERY_DEBOUNCE_TIME_MS;
599
- this.recoveryCooldown = DEFAULT_RECOVERY_COOLDOWN_MS;
600
- this.iceDisconnectedGracePeriod = DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS;
601
- this.iceRestartTimeout = DEFAULT_ICE_RESTART_TIMEOUT_MS$1;
602
- this.maxRecoveryAttempts = DEFAULT_MAX_RECOVERY_ATTEMPTS;
603
- this.enableRelayFallback = DEFAULT_ENABLE_RELAY_FALLBACK;
604
- this.enableNetworkChangeDetection = DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION;
605
- this.enableServerHangupInterception = DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION;
606
- this.persistDeviceSelection = DEFAULT_PERSIST_DEVICE_SELECTION;
607
- this.syncDevicesToActiveCalls = DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS;
608
- this.autoMuteVideoOnHidden = DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN;
609
- this.refreshDevicesOnVisible = DEFAULT_REFRESH_DEVICES_ON_VISIBLE;
610
- this.checkConnectionOnVisible = DEFAULT_CHECK_CONNECTION_ON_VISIBLE;
461
+ this.statsPollingInterval = require_operators.DEFAULT_STATS_POLLING_INTERVAL_MS;
462
+ this.statsBaselineSamples = require_operators.DEFAULT_STATS_BASELINE_SAMPLES;
463
+ this.statsNoPacketThreshold = require_operators.DEFAULT_STATS_NO_PACKET_THRESHOLD_MS;
464
+ this.statsRttSpikeMultiplier = require_operators.DEFAULT_STATS_RTT_SPIKE_MULTIPLIER;
465
+ this.statsPacketLossThreshold = require_operators.DEFAULT_STATS_PACKET_LOSS_THRESHOLD;
466
+ this.statsJitterSpikeMultiplier = require_operators.DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER;
467
+ this.statsHistorySize = require_operators.DEFAULT_STATS_HISTORY_SIZE;
468
+ this.keyframeMaxBurst = require_operators.DEFAULT_KEYFRAME_MAX_BURST;
469
+ this.keyframeBurstWindow = require_operators.DEFAULT_KEYFRAME_BURST_WINDOW_MS;
470
+ this.keyframeCooldown = require_operators.DEFAULT_KEYFRAME_COOLDOWN_MS;
471
+ this.reinviteDebounceTime = require_operators.DEFAULT_REINVITE_DEBOUNCE_TIME_MS;
472
+ this.reinviteMaxAttempts = require_operators.DEFAULT_REINVITE_MAX_ATTEMPTS;
473
+ this.reinviteTimeout = require_operators.DEFAULT_REINVITE_TIMEOUT_MS;
474
+ this.recoveryDebounceTime = require_operators.DEFAULT_RECOVERY_DEBOUNCE_TIME_MS;
475
+ this.recoveryCooldown = require_operators.DEFAULT_RECOVERY_COOLDOWN_MS;
476
+ this.iceDisconnectedGracePeriod = require_operators.DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS;
477
+ this.iceRestartTimeout = require_operators.DEFAULT_ICE_RESTART_TIMEOUT_MS;
478
+ this.maxRecoveryAttempts = require_operators.DEFAULT_MAX_RECOVERY_ATTEMPTS;
479
+ this.enableRelayFallback = require_operators.DEFAULT_ENABLE_RELAY_FALLBACK;
480
+ this.enableNetworkChangeDetection = require_operators.DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION;
481
+ this.enableServerHangupInterception = require_operators.DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION;
482
+ this.persistDeviceSelection = require_operators.DEFAULT_PERSIST_DEVICE_SELECTION;
483
+ this.syncDevicesToActiveCalls = require_operators.DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS;
484
+ this.autoMuteVideoOnHidden = require_operators.DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN;
485
+ this.refreshDevicesOnVisible = require_operators.DEFAULT_REFRESH_DEVICES_ON_VISIBLE;
486
+ this.checkConnectionOnVisible = require_operators.DEFAULT_CHECK_CONNECTION_ON_VISIBLE;
611
487
  this.defaultAudioConstraints = void 0;
612
488
  this.defaultVideoConstraints = void 0;
613
- this.stereoAudio = DEFAULT_STEREO_AUDIO;
614
- this.enableAutoDegradation = DEFAULT_ENABLE_AUTO_DEGRADATION;
615
- this.degradationBitrateThreshold = DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS;
616
- this.degradationRecoveryThreshold = DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS;
489
+ this.stereoAudio = require_operators.DEFAULT_STEREO_AUDIO;
490
+ this.enableAutoDegradation = require_operators.DEFAULT_ENABLE_AUTO_DEGRADATION;
491
+ this.degradationBitrateThreshold = require_operators.DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS;
492
+ this.degradationRecoveryThreshold = require_operators.DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS;
617
493
  this.preferredVideoCodecs = [];
618
494
  this.preferredAudioCodecs = [];
619
495
  }
@@ -1200,17 +1076,17 @@ var ClientPreferences = class {
1200
1076
  _saveToStorage() {
1201
1077
  if (!this._storage) return;
1202
1078
  const data = collectStoredPreferences();
1203
- this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
1204
- logger$29.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
1079
+ this._storage.setItem(require_operators.PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
1080
+ logger$30.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
1205
1081
  });
1206
1082
  }
1207
1083
  /** Loads preferences from storage and applies them to the container. */
1208
1084
  _loadFromStorage() {
1209
1085
  if (!this._storage) return;
1210
- this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
1086
+ this._storage.getItem(require_operators.PREFERENCES_STORAGE_KEY, "local").then((stored) => {
1211
1087
  if (stored) applyStoredPreferences(stored);
1212
1088
  }).catch((error) => {
1213
- logger$29.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
1089
+ logger$30.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
1214
1090
  });
1215
1091
  }
1216
1092
  };
@@ -1231,12 +1107,12 @@ function toError(value) {
1231
1107
 
1232
1108
  //#endregion
1233
1109
  //#region src/controllers/NavigatorDeviceController.ts
1234
- const logger$28 = require_operators.getLogger();
1110
+ const logger$29 = require_operators.getLogger();
1235
1111
  /** Maps a device kind to its storage key. */
1236
1112
  const DEVICE_STORAGE_KEYS = {
1237
- audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
1238
- audiooutput: DEVICE_STORAGE_KEY_AUDIO_OUTPUT,
1239
- videoinput: DEVICE_STORAGE_KEY_VIDEO_INPUT
1113
+ audioinput: require_operators.DEVICE_STORAGE_KEY_AUDIO_INPUT,
1114
+ audiooutput: require_operators.DEVICE_STORAGE_KEY_AUDIO_OUTPUT,
1115
+ videoinput: require_operators.DEVICE_STORAGE_KEY_VIDEO_INPUT
1240
1116
  };
1241
1117
  const initialDevicesState = {
1242
1118
  audioinput: [],
@@ -1253,7 +1129,7 @@ var NavigatorDeviceController = class extends Destroyable {
1253
1129
  super();
1254
1130
  this.webRTCApiProvider = webRTCApiProvider;
1255
1131
  this.deviceChangeHandler = () => {
1256
- logger$28.debug("[DeviceController] Device change detected");
1132
+ logger$29.debug("[DeviceController] Device change detected");
1257
1133
  this.enumerateDevices();
1258
1134
  };
1259
1135
  this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
@@ -1318,13 +1194,13 @@ var NavigatorDeviceController = class extends Destroyable {
1318
1194
  return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$)));
1319
1195
  }
1320
1196
  get selectedAudioInputDevice$() {
1321
- return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audioinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$28.debug("[DeviceController] Selected audio input device changed:", info))));
1197
+ return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audioinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$29.debug("[DeviceController] Selected audio input device changed:", info))));
1322
1198
  }
1323
1199
  get selectedAudioOutputDevice$() {
1324
- return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audiooutput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$28.debug("[DeviceController] Selected audio output device changed:", info))));
1200
+ return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audiooutput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$29.debug("[DeviceController] Selected audio output device changed:", info))));
1325
1201
  }
1326
1202
  get selectedVideoInputDevice$() {
1327
- return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$28.debug("[DeviceController] Selected video input device changed:", info))));
1203
+ return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$29.debug("[DeviceController] Selected video input device changed:", info))));
1328
1204
  }
1329
1205
  get selectedAudioInputDevice() {
1330
1206
  if (this._audioInputDisabled$.value) return null;
@@ -1399,7 +1275,7 @@ var NavigatorDeviceController = class extends Destroyable {
1399
1275
  if (device) this.persistDeviceSelection("audioinput", device);
1400
1276
  }
1401
1277
  selectVideoInputDevice(device) {
1402
- logger$28.debug("[DeviceController] Setting selected video input device:", device);
1278
+ logger$29.debug("[DeviceController] Setting selected video input device:", device);
1403
1279
  if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
1404
1280
  const previous = this._selectedDevicesState$.value.videoinput;
1405
1281
  if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
@@ -1456,7 +1332,7 @@ var NavigatorDeviceController = class extends Destroyable {
1456
1332
  }
1457
1333
  const fromHistory = this._deviceHistory.findInHistory(kind, devices);
1458
1334
  if (fromHistory) {
1459
- logger$28.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
1335
+ logger$29.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
1460
1336
  this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
1461
1337
  return fromHistory;
1462
1338
  }
@@ -1509,7 +1385,7 @@ var NavigatorDeviceController = class extends Destroyable {
1509
1385
  try {
1510
1386
  await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
1511
1387
  } catch (error) {
1512
- logger$28.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
1388
+ logger$29.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
1513
1389
  }
1514
1390
  }
1515
1391
  async loadPersistedDevices() {
@@ -1525,7 +1401,7 @@ var NavigatorDeviceController = class extends Destroyable {
1525
1401
  [kind]: stored
1526
1402
  };
1527
1403
  } catch (error) {
1528
- logger$28.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
1404
+ logger$29.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
1529
1405
  }
1530
1406
  }
1531
1407
  /** Clears device history, persisted selections, and re-enumerates devices. */
@@ -1543,7 +1419,7 @@ var NavigatorDeviceController = class extends Destroyable {
1543
1419
  this.disableDeviceMonitoring();
1544
1420
  this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
1545
1421
  if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, rxjs.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
1546
- logger$28.debug("[DeviceController] Polling devices due to interval");
1422
+ logger$29.debug("[DeviceController] Polling devices due to interval");
1547
1423
  this.enumerateDevices();
1548
1424
  });
1549
1425
  this.enumerateDevices();
@@ -1569,13 +1445,13 @@ var NavigatorDeviceController = class extends Destroyable {
1569
1445
  videoinput: []
1570
1446
  });
1571
1447
  this._devicesState$.next(devicesByKind);
1572
- logger$28.debug("[DeviceController] Devices enumerated:", {
1448
+ logger$29.debug("[DeviceController] Devices enumerated:", {
1573
1449
  audioInputs: devicesByKind.audioinput.length,
1574
1450
  audioOutputs: devicesByKind.audiooutput.length,
1575
1451
  videoInputs: devicesByKind.videoinput.length
1576
1452
  });
1577
1453
  } catch (error) {
1578
- logger$28.error("[DeviceController] Failed to enumerate devices:", error);
1454
+ logger$29.error("[DeviceController] Failed to enumerate devices:", error);
1579
1455
  this._errors$.next(toError(error));
1580
1456
  }
1581
1457
  }
@@ -1591,7 +1467,7 @@ var NavigatorDeviceController = class extends Destroyable {
1591
1467
  stream.getTracks().forEach((t) => t.stop());
1592
1468
  return capabilities;
1593
1469
  } catch (error) {
1594
- logger$28.error("[DeviceController] Failed to get device capabilities:", error);
1470
+ logger$29.error("[DeviceController] Failed to get device capabilities:", error);
1595
1471
  this._errors$.next(toError(error));
1596
1472
  throw error;
1597
1473
  }
@@ -1842,7 +1718,7 @@ var DependencyContainer = class {
1842
1718
 
1843
1719
  //#endregion
1844
1720
  //#region src/controllers/CryptoController.ts
1845
- const logger$27 = require_operators.getLogger();
1721
+ const logger$28 = require_operators.getLogger();
1846
1722
  const DPOP_DB_NAME = "sw-dpop";
1847
1723
  const DPOP_DB_VERSION = 1;
1848
1724
  const DPOP_STORE_NAME = "keys";
@@ -1901,7 +1777,7 @@ async function loadKeyPairFromDB() {
1901
1777
  tx.oncomplete = () => db.close();
1902
1778
  });
1903
1779
  } catch (error) {
1904
- logger$27.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
1780
+ logger$28.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
1905
1781
  return null;
1906
1782
  }
1907
1783
  }
@@ -1921,7 +1797,7 @@ async function saveKeyPairToDB(keyPair) {
1921
1797
  };
1922
1798
  });
1923
1799
  } catch (error) {
1924
- logger$27.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
1800
+ logger$28.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
1925
1801
  }
1926
1802
  }
1927
1803
  async function deleteKeyPairFromDB() {
@@ -1940,7 +1816,7 @@ async function deleteKeyPairFromDB() {
1940
1816
  };
1941
1817
  });
1942
1818
  } catch (error) {
1943
- logger$27.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
1819
+ logger$28.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
1944
1820
  }
1945
1821
  }
1946
1822
  /**
@@ -2000,13 +1876,13 @@ var CryptoController = class {
2000
1876
  this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
2001
1877
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
2002
1878
  this._initialized = true;
2003
- logger$27.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
1879
+ logger$28.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
2004
1880
  return this._fingerprint;
2005
1881
  } catch (error) {
2006
- logger$27.warn("[DPoP] Stored key pair unusable, generating new one:", error);
1882
+ logger$28.warn("[DPoP] Stored key pair unusable, generating new one:", error);
2007
1883
  await deleteKeyPairFromDB();
2008
1884
  }
2009
- logger$27.debug("[DPoP] Generating RSA key pair");
1885
+ logger$28.debug("[DPoP] Generating RSA key pair");
2010
1886
  this._keyPair = await crypto.subtle.generateKey({
2011
1887
  name: "RSASSA-PKCS1-v1_5",
2012
1888
  modulusLength: 2048,
@@ -2021,7 +1897,7 @@ var CryptoController = class {
2021
1897
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
2022
1898
  this._initialized = true;
2023
1899
  await saveKeyPairToDB(this._keyPair);
2024
- logger$27.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
1900
+ logger$28.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
2025
1901
  return this._fingerprint;
2026
1902
  }
2027
1903
  /**
@@ -2087,7 +1963,7 @@ var CryptoController = class {
2087
1963
  this._fingerprint = null;
2088
1964
  this._initialized = false;
2089
1965
  deleteKeyPairFromDB();
2090
- logger$27.debug("[DPoP] Controller destroyed");
1966
+ logger$28.debug("[DPoP] Controller destroyed");
2091
1967
  }
2092
1968
  get publicJwk() {
2093
1969
  if (!this._publicJwk) throw new require_operators.DPoPInitError("CryptoController not initialized. Call init() first.");
@@ -2110,7 +1986,7 @@ var CryptoController = class {
2110
1986
 
2111
1987
  //#endregion
2112
1988
  //#region src/controllers/NetworkMonitor.ts
2113
- const logger$26 = require_operators.getLogger();
1989
+ const logger$27 = require_operators.getLogger();
2114
1990
  /**
2115
1991
  * Safely check whether we are running in a browser environment
2116
1992
  * with `window` and the relevant event targets.
@@ -2167,7 +2043,7 @@ var NetworkMonitor = class extends Destroyable {
2167
2043
  }
2168
2044
  attachListeners() {
2169
2045
  if (!hasBrowserNetworkEvents()) {
2170
- logger$26.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
2046
+ logger$27.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
2171
2047
  return;
2172
2048
  }
2173
2049
  window.addEventListener("online", this._onOnline);
@@ -2175,7 +2051,7 @@ var NetworkMonitor = class extends Destroyable {
2175
2051
  const connection = getNetworkConnection();
2176
2052
  if (connection) connection.addEventListener("change", this._onConnectionChange);
2177
2053
  this._listenersAttached = true;
2178
- logger$26.debug("NetworkMonitor: event listeners attached");
2054
+ logger$27.debug("NetworkMonitor: event listeners attached");
2179
2055
  }
2180
2056
  removeListeners() {
2181
2057
  if (!this._listenersAttached) return;
@@ -2186,10 +2062,10 @@ var NetworkMonitor = class extends Destroyable {
2186
2062
  if (connection) connection.removeEventListener("change", this._onConnectionChange);
2187
2063
  }
2188
2064
  this._listenersAttached = false;
2189
- logger$26.debug("NetworkMonitor: event listeners removed");
2065
+ logger$27.debug("NetworkMonitor: event listeners removed");
2190
2066
  }
2191
2067
  handleOnline() {
2192
- logger$26.info("NetworkMonitor: browser went online");
2068
+ logger$27.info("NetworkMonitor: browser went online");
2193
2069
  this._isOnline$.next(true);
2194
2070
  this._networkChange$.next({
2195
2071
  type: "online",
@@ -2198,7 +2074,7 @@ var NetworkMonitor = class extends Destroyable {
2198
2074
  });
2199
2075
  }
2200
2076
  handleOffline() {
2201
- logger$26.info("NetworkMonitor: browser went offline");
2077
+ logger$27.info("NetworkMonitor: browser went offline");
2202
2078
  this._isOnline$.next(false);
2203
2079
  this._networkChange$.next({
2204
2080
  type: "offline",
@@ -2207,7 +2083,7 @@ var NetworkMonitor = class extends Destroyable {
2207
2083
  }
2208
2084
  handleConnectionChange() {
2209
2085
  const networkType = getNetworkType();
2210
- logger$26.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
2086
+ logger$27.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
2211
2087
  this._networkChange$.next({
2212
2088
  type: "connection_change",
2213
2089
  timestamp: Date.now(),
@@ -2322,7 +2198,7 @@ function getNavigatorMediaDevices() {
2322
2198
 
2323
2199
  //#endregion
2324
2200
  //#region src/controllers/PreflightRunner.ts
2325
- const logger$25 = require_operators.getLogger();
2201
+ const logger$26 = require_operators.getLogger();
2326
2202
  const DEFAULT_MEDIA_TEST_DURATION_S = 10;
2327
2203
  const ICE_GATHERING_TIMEOUT_MS = 1e4;
2328
2204
  const SIGNALING_RTT_TIMEOUT_MS = 5e3;
@@ -2371,7 +2247,7 @@ var PreflightRunner = class extends Destroyable {
2371
2247
  if (!this._options.skipMediaTest) try {
2372
2248
  bandwidth = await this.testMediaBandwidth(destination);
2373
2249
  } catch (error) {
2374
- logger$25.warn("[PreflightRunner] Media bandwidth test failed:", error);
2250
+ logger$26.warn("[PreflightRunner] Media bandwidth test failed:", error);
2375
2251
  warnings.push("Media bandwidth test failed");
2376
2252
  }
2377
2253
  return {
@@ -2383,7 +2259,7 @@ var PreflightRunner = class extends Destroyable {
2383
2259
  warnings
2384
2260
  };
2385
2261
  } catch (error) {
2386
- logger$25.error("[PreflightRunner] Preflight test failed:", error);
2262
+ logger$26.error("[PreflightRunner] Preflight test failed:", error);
2387
2263
  throw new require_operators.PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
2388
2264
  } finally {
2389
2265
  this.destroy();
@@ -2414,7 +2290,7 @@ var PreflightRunner = class extends Destroyable {
2414
2290
  if (track.kind === "video" && track.readyState === "live") videoWorking = true;
2415
2291
  }
2416
2292
  } catch (error) {
2417
- logger$25.warn("[PreflightRunner] Device test failed:", error);
2293
+ logger$26.warn("[PreflightRunner] Device test failed:", error);
2418
2294
  } finally {
2419
2295
  if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
2420
2296
  }
@@ -2472,7 +2348,7 @@ var PreflightRunner = class extends Destroyable {
2472
2348
  rttMs
2473
2349
  };
2474
2350
  } catch (error) {
2475
- logger$25.warn("[PreflightRunner] ICE connectivity test failed:", error);
2351
+ logger$26.warn("[PreflightRunner] ICE connectivity test failed:", error);
2476
2352
  return {
2477
2353
  type: "failed",
2478
2354
  turnReachable: false,
@@ -2519,7 +2395,7 @@ var PreflightRunner = class extends Destroyable {
2519
2395
 
2520
2396
  //#endregion
2521
2397
  //#region src/controllers/VisibilityController.ts
2522
- const logger$24 = require_operators.getLogger();
2398
+ const logger$25 = require_operators.getLogger();
2523
2399
  /**
2524
2400
  * Checks whether the document visibility API is available.
2525
2401
  */
@@ -2556,8 +2432,8 @@ var VisibilityController = class extends Destroyable {
2556
2432
  this._boundHandler = this._handleVisibilityChange.bind(this);
2557
2433
  if (this._hasVisibilityApi) {
2558
2434
  document.addEventListener("visibilitychange", this._boundHandler);
2559
- logger$24.debug("VisibilityController: listening for visibilitychange events");
2560
- } else logger$24.debug("VisibilityController: document visibility API not available, defaulting to visible");
2435
+ logger$25.debug("VisibilityController: listening for visibilitychange events");
2436
+ } else logger$25.debug("VisibilityController: document visibility API not available, defaulting to visible");
2561
2437
  }
2562
2438
  /**
2563
2439
  * Observable of the current visibility state.
@@ -2582,7 +2458,7 @@ var VisibilityController = class extends Destroyable {
2582
2458
  destroy() {
2583
2459
  if (this._hasVisibilityApi) {
2584
2460
  document.removeEventListener("visibilitychange", this._boundHandler);
2585
- logger$24.debug("VisibilityController: removed visibilitychange listener");
2461
+ logger$25.debug("VisibilityController: removed visibilitychange listener");
2586
2462
  }
2587
2463
  super.destroy();
2588
2464
  }
@@ -2600,7 +2476,7 @@ var VisibilityController = class extends Destroyable {
2600
2476
  timestamp: Date.now()
2601
2477
  };
2602
2478
  this._visibilityChange$.next(changeEvent);
2603
- logger$24.debug("VisibilityController: visibility changed", {
2479
+ logger$25.debug("VisibilityController: visibility changed", {
2604
2480
  from: previousState,
2605
2481
  to: newState
2606
2482
  });
@@ -2803,7 +2679,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
2803
2679
 
2804
2680
  //#endregion
2805
2681
  //#region src/managers/AttachManager.ts
2806
- const logger$23 = require_operators.getLogger();
2682
+ const logger$24 = require_operators.getLogger();
2807
2683
  var AttachManager = class {
2808
2684
  constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
2809
2685
  this.storage = storage;
@@ -2824,7 +2700,7 @@ var AttachManager = class {
2824
2700
  try {
2825
2701
  return await this.storage.getItem(this.attachKey) ?? {};
2826
2702
  } catch (error) {
2827
- logger$23.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
2703
+ logger$24.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
2828
2704
  return {};
2829
2705
  }
2830
2706
  }
@@ -2832,7 +2708,7 @@ var AttachManager = class {
2832
2708
  try {
2833
2709
  await this.storage.setItem(this.attachKey, attached);
2834
2710
  } catch (error) {
2835
- logger$23.warn("[AttachManager] Failed to write attached calls to storage", error);
2711
+ logger$24.warn("[AttachManager] Failed to write attached calls to storage", error);
2836
2712
  }
2837
2713
  }
2838
2714
  /**
@@ -2851,7 +2727,7 @@ var AttachManager = class {
2851
2727
  }
2852
2728
  async attach(call) {
2853
2729
  if (!call.to) {
2854
- logger$23.warn("[AttachManager] Skip attach for calls with no destination");
2730
+ logger$24.warn("[AttachManager] Skip attach for calls with no destination");
2855
2731
  return;
2856
2732
  }
2857
2733
  const destination = call.to;
@@ -2904,15 +2780,15 @@ var AttachManager = class {
2904
2780
  callId,
2905
2781
  ...options
2906
2782
  });
2907
- logger$23.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
2783
+ logger$24.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
2908
2784
  succeeded = true;
2909
2785
  break;
2910
2786
  } catch (error) {
2911
- logger$23.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
2787
+ logger$24.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
2912
2788
  if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
2913
2789
  }
2914
2790
  if (!succeeded) {
2915
- logger$23.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
2791
+ logger$24.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
2916
2792
  await this.detach({
2917
2793
  id: callId,
2918
2794
  mediaDirections: attachment.mediaDirections
@@ -2998,7 +2874,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
2998
2874
  position: false,
2999
2875
  meta: false,
3000
2876
  remove: false,
3001
- audioFlags: false
2877
+ audioFlags: false,
2878
+ denoise: false,
2879
+ lowbitrate: false
3002
2880
  };
3003
2881
  /**
3004
2882
  * Default call capabilities with no permissions
@@ -3071,7 +2949,9 @@ function computeMemberCapabilities(flags, memberType) {
3071
2949
  position: hasBooleanCapability(flags, memberType, null, "position"),
3072
2950
  meta: hasBooleanCapability(flags, memberType, null, "meta"),
3073
2951
  remove: hasBooleanCapability(flags, memberType, null, "remove"),
3074
- audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
2952
+ audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
2953
+ denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
2954
+ lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
3075
2955
  };
3076
2956
  }
3077
2957
  /**
@@ -3226,7 +3106,7 @@ function toggleHandraiseMethod(is) {
3226
3106
 
3227
3107
  //#endregion
3228
3108
  //#region src/core/entities/Participant.ts
3229
- const logger$22 = require_operators.getLogger();
3109
+ const logger$23 = require_operators.getLogger();
3230
3110
  const initialState = {};
3231
3111
  /**
3232
3112
  * Represents a participant in a call.
@@ -3236,9 +3116,9 @@ const initialState = {};
3236
3116
  * the local participant with additional device control.
3237
3117
  */
3238
3118
  var Participant = class extends Destroyable {
3239
- constructor(id, executeMethod, deviceController) {
3119
+ constructor(id, callExecuteMethod, deviceController) {
3240
3120
  super();
3241
- this.executeMethod = executeMethod;
3121
+ this.callExecuteMethod = callExecuteMethod;
3242
3122
  this.deviceController = deviceController;
3243
3123
  this._state$ = this.createBehaviorSubject(initialState);
3244
3124
  this.id = id;
@@ -3452,26 +3332,63 @@ var Participant = class extends Destroyable {
3452
3332
  get nodeId() {
3453
3333
  return this._state$.value.node_id;
3454
3334
  }
3335
+ /** Call ID for this participant's leg, or `undefined` if not available. */
3336
+ get callId() {
3337
+ return this._state$.value.call_id;
3338
+ }
3455
3339
  /** @internal */
3456
3340
  get value() {
3457
3341
  return this._state$.value;
3458
3342
  }
3343
+ /**
3344
+ * Target triple for member RPCs, built from the participant's own state.
3345
+ * The backend locates the member's session by the target `call_id`/`node_id`,
3346
+ * so this must always be the participant's own call context — never the
3347
+ * local call's id (issue #19400).
3348
+ *
3349
+ * Reading it doubles as a readiness probe: it throws until the first full
3350
+ * member event (`member.joined`/`member.updated` or the `call.joined`
3351
+ * roster) arrives, and never regresses afterwards.
3352
+ *
3353
+ * @throws {ParticipantNotReadyError} If the member state has not been
3354
+ * received yet (e.g. a participant first seen via `member.talking`) — an
3355
+ * empty call context can never address the member, so fail fast instead of
3356
+ * sending a doomed RPC.
3357
+ */
3358
+ get target() {
3359
+ const { call_id, node_id } = this._state$.value;
3360
+ if (!call_id || !node_id) throw new require_operators.ParticipantNotReadyError(this.id);
3361
+ return {
3362
+ member_id: this.id,
3363
+ call_id,
3364
+ node_id
3365
+ };
3366
+ }
3367
+ /**
3368
+ * Executes a member RPC against this participant, injecting its own
3369
+ * {@link target} as the target.
3370
+ *
3371
+ * @throws {ParticipantNotReadyError} Via {@link target}, when the
3372
+ * member state has not been received yet.
3373
+ */
3374
+ async executeMethod(method, args) {
3375
+ return this.callExecuteMethod(this.target, method, args);
3376
+ }
3459
3377
  /** Toggles the deafened state (mutes/unmutes incoming audio). */
3460
3378
  async toggleDeaf() {
3461
- const method = toggleDeafMethod(this.deaf);
3462
- await this.executeMethod(this.id, method, {});
3379
+ await this.executeMethod(toggleDeafMethod(this.deaf), {});
3463
3380
  }
3464
3381
  /** Toggles the hand-raised state. */
3465
3382
  async toggleHandraise() {
3466
- await this.executeMethod(this.id, toggleHandraiseMethod(this.handraised), {});
3383
+ await this.executeMethod(toggleHandraiseMethod(this.handraised), {});
3467
3384
  }
3468
3385
  /** Mutes the participant's audio. */
3469
3386
  async mute() {
3470
- await this.executeMethod(this.id, "call.mute", { channels: ["audio"] });
3387
+ await this.executeMethod("call.mute", { channels: ["audio"] });
3471
3388
  }
3472
3389
  /** Unmutes the participant's audio. */
3473
3390
  async unmute() {
3474
- await this.executeMethod(this.id, "call.unmute", { channels: ["audio"] });
3391
+ await this.executeMethod("call.unmute", { channels: ["audio"] });
3475
3392
  }
3476
3393
  /** Toggles the participant's audio mute state. */
3477
3394
  async toggleMute() {
@@ -3479,11 +3396,11 @@ var Participant = class extends Destroyable {
3479
3396
  }
3480
3397
  /** Mutes the participant's video. */
3481
3398
  async muteVideo() {
3482
- await this.executeMethod(this.id, "call.mute", { channels: ["video"] });
3399
+ await this.executeMethod("call.mute", { channels: ["video"] });
3483
3400
  }
3484
3401
  /** Unmutes the participant's video. */
3485
3402
  async unmuteVideo() {
3486
- await this.executeMethod(this.id, "call.unmute", { channels: ["video"] });
3403
+ await this.executeMethod("call.unmute", { channels: ["video"] });
3487
3404
  }
3488
3405
  /** Toggles the participant's video mute state. */
3489
3406
  async toggleMuteVideo() {
@@ -3491,7 +3408,7 @@ var Participant = class extends Destroyable {
3491
3408
  }
3492
3409
  /** Toggles echo cancellation on the audio input. */
3493
3410
  async toggleEchoCancellation() {
3494
- await this.executeMethod(this.id, "call.audioflags.set", {
3411
+ await this.executeMethod("call.audioflags.set", {
3495
3412
  echo_cancellation: !this.echoCancellation,
3496
3413
  auto_gain: this.autoGain,
3497
3414
  noise_suppression: this.noiseSuppression
@@ -3499,7 +3416,7 @@ var Participant = class extends Destroyable {
3499
3416
  }
3500
3417
  /** Toggles automatic gain control on the audio input. */
3501
3418
  async toggleAudioInputAutoGain() {
3502
- await this.executeMethod(this.id, "call.audioflags.set", {
3419
+ await this.executeMethod("call.audioflags.set", {
3503
3420
  echo_cancellation: this.echoCancellation,
3504
3421
  auto_gain: !this.autoGain,
3505
3422
  noise_suppression: this.noiseSuppression
@@ -3507,14 +3424,15 @@ var Participant = class extends Destroyable {
3507
3424
  }
3508
3425
  /** Toggles noise suppression on the audio input. */
3509
3426
  async toggleNoiseSuppression() {
3510
- await this.executeMethod(this.id, "call.audioflags.set", {
3427
+ await this.executeMethod("call.audioflags.set", {
3511
3428
  echo_cancellation: this.echoCancellation,
3512
3429
  auto_gain: this.autoGain,
3513
3430
  noise_suppression: !this.noiseSuppression
3514
3431
  });
3515
3432
  }
3433
+ /** Toggles low-bitrate mode for this participant's media. */
3516
3434
  async toggleLowbitrate() {
3517
- throw new require_operators.UnimplementedError();
3435
+ await this.executeMethod("call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
3518
3436
  }
3519
3437
  /**
3520
3438
  * Adjusts the **conference-only** microphone energy gate / sensitivity level
@@ -3530,7 +3448,7 @@ var Participant = class extends Destroyable {
3530
3448
  * (integer, larger values are more sensitive).
3531
3449
  */
3532
3450
  async setAudioInputSensitivity(value) {
3533
- await this.executeMethod(this.id, "call.microphone.sensitivity.set", { sensitivity: value });
3451
+ await this.executeMethod("call.microphone.sensitivity.set", { sensitivity: value });
3534
3452
  }
3535
3453
  /**
3536
3454
  * Sets the **server-side** microphone volume on this participant's bridged
@@ -3543,7 +3461,7 @@ var Participant = class extends Destroyable {
3543
3461
  * @param value - Volume level (0-100).
3544
3462
  */
3545
3463
  async setAudioInputVolume(value) {
3546
- await this.executeMethod(this.id, "call.microphone.volume.set", { volume: value });
3464
+ await this.executeMethod("call.microphone.volume.set", { volume: value });
3547
3465
  }
3548
3466
  /**
3549
3467
  * Sets the **server-side** speaker volume on this participant's bridged call
@@ -3557,28 +3475,31 @@ var Participant = class extends Destroyable {
3557
3475
  * @param value - Volume level (0-100).
3558
3476
  */
3559
3477
  async setAudioOutputVolume(value) {
3560
- await this.executeMethod(this.id, "call.speaker.volume.set", { volume: value });
3478
+ await this.executeMethod("call.speaker.volume.set", { volume: value });
3561
3479
  }
3562
3480
  /**
3563
3481
  * Sets the participant's position in the video layout.
3482
+ *
3483
+ * Requires the `member.position` capability. The gateway requires a
3484
+ * `targets` array of `{ target, position }` entries (issue #19400). A
3485
+ * resolved promise does not guarantee a visible change: the backend silently
3486
+ * returns `200` (no-op) for non-conference targets.
3487
+ *
3564
3488
  * @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
3565
3489
  */
3566
3490
  async setPosition(value) {
3567
- await this.executeMethod(this.id, "call.member.position.set", { position: value });
3491
+ await this.executeMethod("call.member.position.set", { targets: [{
3492
+ target: this.target,
3493
+ position: value
3494
+ }] });
3568
3495
  }
3569
3496
  /** Removes this participant from the call. */
3570
3497
  async remove() {
3571
- const state = this._state$.value;
3572
- const target = {
3573
- member_id: this.id,
3574
- call_id: state.call_id ?? "",
3575
- node_id: state.node_id ?? ""
3576
- };
3577
- await this.executeMethod(target, "call.member.remove", {});
3498
+ await this.executeMethod("call.member.remove", { targets: [this.target] });
3578
3499
  }
3579
3500
  /** Ends the call for this participant. */
3580
3501
  async end() {
3581
- await this.executeMethod(this.id, "call.end", {});
3502
+ await this.executeMethod("call.end", {});
3582
3503
  }
3583
3504
  /**
3584
3505
  * Replaces custom metadata for this participant.
@@ -3598,7 +3519,7 @@ var Participant = class extends Destroyable {
3598
3519
  }
3599
3520
  /** Destroys the participant, releasing all subscriptions and references. */
3600
3521
  destroy() {
3601
- this.executeMethod = void 0;
3522
+ this.callExecuteMethod = void 0;
3602
3523
  super.destroy();
3603
3524
  }
3604
3525
  };
@@ -3610,8 +3531,8 @@ var Participant = class extends Destroyable {
3610
3531
  */
3611
3532
  var SelfParticipant = class extends Participant {
3612
3533
  /** @internal */
3613
- constructor(id, executeMethod, vertoManager, deviceController) {
3614
- super(id, executeMethod, deviceController);
3534
+ constructor(id, callExecuteMethod, vertoManager, deviceController) {
3535
+ super(id, callExecuteMethod, deviceController);
3615
3536
  this.vertoManager = vertoManager;
3616
3537
  this._studioAudio$ = this.createBehaviorSubject(false);
3617
3538
  this.capabilities = new SelfCapabilities();
@@ -3635,7 +3556,7 @@ var SelfParticipant = class extends Participant {
3635
3556
  async enableStudioAudio() {
3636
3557
  if (this._studioAudio$.value) return;
3637
3558
  this._studioAudio$.next(true);
3638
- await this.executeMethod(this.id, "call.audioflags.set", {
3559
+ await this.executeMethod("call.audioflags.set", {
3639
3560
  echo_cancellation: false,
3640
3561
  auto_gain: false,
3641
3562
  noise_suppression: false
@@ -3648,18 +3569,27 @@ var SelfParticipant = class extends Participant {
3648
3569
  async disableStudioAudio() {
3649
3570
  if (!this._studioAudio$.value) return;
3650
3571
  this._studioAudio$.next(false);
3651
- await this.executeMethod(this.id, "call.audioflags.set", {
3572
+ await this.executeMethod("call.audioflags.set", {
3652
3573
  echo_cancellation: true,
3653
3574
  auto_gain: true,
3654
3575
  noise_suppression: true
3655
3576
  });
3656
3577
  }
3657
- /** Starts sharing the local screen. */
3578
+ /**
3579
+ * Starts sharing the local screen.
3580
+ *
3581
+ * The call is unaffected when acquisition fails.
3582
+ *
3583
+ * @throws The raw `getDisplayMedia` error. A dismissed picker or a
3584
+ * permission denial rejects with a `NotAllowedError` `DOMException` —
3585
+ * inspect `error.name` to tell benign cancels apart from real failures.
3586
+ */
3658
3587
  async startScreenShare() {
3659
3588
  try {
3660
3589
  await this.vertoManager.addScreenMedia();
3661
3590
  } catch (error) {
3662
- logger$22.error("[Participant.startScreenShare] Screen share error:", error);
3591
+ logger$23.error("[Participant.startScreenShare] Screen share error:", error);
3592
+ throw error;
3663
3593
  }
3664
3594
  }
3665
3595
  /** Observable of the current screen share status. */
@@ -3674,12 +3604,20 @@ var SelfParticipant = class extends Participant {
3674
3604
  async stopScreenShare() {
3675
3605
  return this.vertoManager.removeScreenMedia();
3676
3606
  }
3677
- /** Adds an additional media input device to the call. */
3607
+ /**
3608
+ * Adds an additional media input device to the call.
3609
+ *
3610
+ * The call is unaffected when acquisition fails.
3611
+ *
3612
+ * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
3613
+ * permission denial) — inspect `error.name` to decide how to react.
3614
+ */
3678
3615
  async addAdditionalDevice(options) {
3679
3616
  try {
3680
3617
  await this.vertoManager.addInputDevice(options);
3681
3618
  } catch (error) {
3682
- logger$22.error("[Participant.startScreenShare] Screen share error:", error);
3619
+ logger$23.error("[Participant.addAdditionalDevice] Additional device error:", error);
3620
+ throw error;
3683
3621
  }
3684
3622
  }
3685
3623
  /** Removes an additional media input device by ID. */
@@ -3741,7 +3679,7 @@ var SelfParticipant = class extends Participant {
3741
3679
  */
3742
3680
  exitStudioModeIfActive() {
3743
3681
  if (this._studioAudio$.value) {
3744
- logger$22.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
3682
+ logger$23.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
3745
3683
  this._studioAudio$.next(false);
3746
3684
  }
3747
3685
  }
@@ -3765,7 +3703,7 @@ var SelfParticipant = class extends Participant {
3765
3703
  try {
3766
3704
  await super.mute();
3767
3705
  } catch (error) {
3768
- logger$22.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
3706
+ logger$23.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
3769
3707
  } finally {
3770
3708
  this.vertoManager.muteMainAudioInputDevice();
3771
3709
  }
@@ -3775,7 +3713,7 @@ var SelfParticipant = class extends Participant {
3775
3713
  try {
3776
3714
  await super.unmute();
3777
3715
  } catch (error) {
3778
- logger$22.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
3716
+ logger$23.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
3779
3717
  } finally {
3780
3718
  await this.vertoManager.unmuteMainAudioInputDevice();
3781
3719
  }
@@ -3785,7 +3723,7 @@ var SelfParticipant = class extends Participant {
3785
3723
  try {
3786
3724
  await super.muteVideo();
3787
3725
  } catch (error) {
3788
- logger$22.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
3726
+ logger$23.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
3789
3727
  } finally {
3790
3728
  this.vertoManager.muteMainVideoInputDevice();
3791
3729
  }
@@ -3795,7 +3733,7 @@ var SelfParticipant = class extends Participant {
3795
3733
  try {
3796
3734
  await super.unmuteVideo();
3797
3735
  } catch (error) {
3798
- logger$22.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
3736
+ logger$23.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
3799
3737
  } finally {
3800
3738
  await this.vertoManager.unmuteMainVideoInputDevice();
3801
3739
  }
@@ -3889,7 +3827,7 @@ function isLayoutChangedPayload(value) {
3889
3827
 
3890
3828
  //#endregion
3891
3829
  //#region src/managers/CallEventsManager.ts
3892
- const logger$21 = require_operators.getLogger();
3830
+ const logger$22 = require_operators.getLogger();
3893
3831
  const initialSessionState = {};
3894
3832
  /** @internal */
3895
3833
  var CallEventsManager = class extends Destroyable {
@@ -3993,7 +3931,7 @@ var CallEventsManager = class extends Destroyable {
3993
3931
  }
3994
3932
  initSubscriptions() {
3995
3933
  this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
3996
- logger$21.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
3934
+ logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
3997
3935
  callId: callJoinedEvent.call_id,
3998
3936
  roomSessionId: callJoinedEvent.room_session_id
3999
3937
  });
@@ -4020,19 +3958,19 @@ var CallEventsManager = class extends Destroyable {
4020
3958
  if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
4021
3959
  });
4022
3960
  this.subscribeTo(this.memberUpdates$, (member) => {
4023
- logger$21.debug("[CallEventsManager] Handling member update event for member ID:", member);
3961
+ logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
4024
3962
  this.upsertParticipant(member);
4025
3963
  });
4026
3964
  this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
4027
- logger$21.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
3965
+ logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
4028
3966
  const participants = { ...this._participants$.value };
4029
3967
  if (memberLeftEvent.member.member_id in participants) {
4030
3968
  delete participants[memberLeftEvent.member.member_id];
4031
3969
  this._participants$.next(participants);
4032
- } else logger$21.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
3970
+ } else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
4033
3971
  });
4034
3972
  this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
4035
- logger$21.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
3973
+ logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
4036
3974
  const roomSession = callUpdatedEvent.room_session;
4037
3975
  this._sessionState$.next({
4038
3976
  ...this._sessionState$.value,
@@ -4047,7 +3985,7 @@ var CallEventsManager = class extends Destroyable {
4047
3985
  });
4048
3986
  });
4049
3987
  this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
4050
- logger$21.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
3988
+ logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
4051
3989
  this._sessionState$.next({
4052
3990
  ...this._sessionState$.value,
4053
3991
  layout_name: layoutChangedEvent.id,
@@ -4057,10 +3995,10 @@ var CallEventsManager = class extends Destroyable {
4057
3995
  });
4058
3996
  }
4059
3997
  updateParticipantPositions(layoutChangedEvent) {
4060
- if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$21.warn("[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.");
3998
+ if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$22.warn("[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.");
4061
3999
  layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
4062
4000
  if (!(layer.member_id in this._participants$.value)) {
4063
- logger$21.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
4001
+ logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
4064
4002
  return false;
4065
4003
  }
4066
4004
  return true;
@@ -4083,7 +4021,7 @@ var CallEventsManager = class extends Destroyable {
4083
4021
  layouts: response.result.layouts
4084
4022
  });
4085
4023
  }).catch((error) => {
4086
- logger$21.error("[CallEventsManager] Error fetching layouts:", error);
4024
+ logger$22.error("[CallEventsManager] Error fetching layouts:", error);
4087
4025
  });
4088
4026
  }
4089
4027
  updateParticipants(members) {
@@ -4099,7 +4037,7 @@ var CallEventsManager = class extends Destroyable {
4099
4037
  }
4100
4038
  const participant = this._participants$.value[member.member_id];
4101
4039
  const oldValue = participant.value;
4102
- logger$21.debug("[CallEventsManager] Updating participant:", member.member_id, {
4040
+ logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
4103
4041
  oldValue,
4104
4042
  newValue: member
4105
4043
  });
@@ -4112,17 +4050,17 @@ var CallEventsManager = class extends Destroyable {
4112
4050
  }
4113
4051
  get callJoinedEvent$() {
4114
4052
  return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, rxjs.filter)(isCallJoinedPayload), (0, rxjs.tap)((event) => {
4115
- logger$21.debug("[CallEventsManager] Call joined event:", event);
4053
+ logger$22.debug("[CallEventsManager] Call joined event:", event);
4116
4054
  })));
4117
4055
  }
4118
4056
  get layoutChangedEvent$() {
4119
4057
  return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(require_operators.filterAs(isLayoutChangedPayload, "layout"), (0, rxjs.tap)((event) => {
4120
- logger$21.debug("[CallEventsManager] Layout changed event:", event);
4058
+ logger$22.debug("[CallEventsManager] Layout changed event:", event);
4121
4059
  })));
4122
4060
  }
4123
4061
  get memberUpdates$() {
4124
4062
  return this.cachedObservable("memberUpdates$", () => (0, rxjs.merge)(this.webRtcCallSession.memberJoined$, this.webRtcCallSession.memberUpdated$, this.webRtcCallSession.memberTalking$).pipe((0, rxjs.map)((event) => event.member), (0, rxjs.tap)((event) => {
4125
- logger$21.debug("[CallEventsManager] Member update event:", event);
4063
+ logger$22.debug("[CallEventsManager] Member update event:", event);
4126
4064
  })));
4127
4065
  }
4128
4066
  destroy() {
@@ -4282,7 +4220,7 @@ function reorderCodecs(sdp, preferredVideo = [], preferredAudio = []) {
4282
4220
  * // a=fmtp:111 minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1;maxaveragebitrate=510000
4283
4221
  * ```
4284
4222
  */
4285
- function enableStereoOpus(sdp, maxBitrate = DEFAULT_STEREO_MAX_AVERAGE_BITRATE) {
4223
+ function enableStereoOpus(sdp, maxBitrate = require_operators.DEFAULT_STEREO_MAX_AVERAGE_BITRATE) {
4286
4224
  if (!sdp) return sdp;
4287
4225
  const opusPayloadType = findOpusPayloadType(sdp);
4288
4226
  if (opusPayloadType === null) return sdp;
@@ -4378,7 +4316,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
4378
4316
 
4379
4317
  //#endregion
4380
4318
  //#region src/controllers/ICEGatheringController.ts
4381
- const logger$20 = require_operators.getLogger();
4319
+ const logger$21 = require_operators.getLogger();
4382
4320
  var ICEGatheringController = class extends Destroyable {
4383
4321
  constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
4384
4322
  super();
@@ -4386,23 +4324,23 @@ var ICEGatheringController = class extends Destroyable {
4386
4324
  this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
4387
4325
  this.onicegatheringstatechangeHandler = () => {
4388
4326
  const { iceGatheringState } = this.peerConnection;
4389
- logger$20.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
4327
+ logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
4390
4328
  if (iceGatheringState === "gathering") this._iceCandidatesState.next({
4391
4329
  state: "gathering",
4392
4330
  validSDP: false
4393
4331
  });
4394
4332
  };
4395
4333
  this.onicecandidateHandler = (event) => {
4396
- logger$20.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
4334
+ logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
4397
4335
  this.removeTimer("iceCandidateTimer");
4398
4336
  if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
4399
4337
  if (this.peerConnection.iceGatheringState !== "complete") {
4400
- logger$20.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
4338
+ logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
4401
4339
  this.handleICECandidateTimeout();
4402
4340
  }
4403
4341
  }, this.iceCandidateTimeout);
4404
4342
  else {
4405
- logger$20.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
4343
+ logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
4406
4344
  this.removeTimer("iceGatheringTimer");
4407
4345
  this.handleICEGatheringComplete();
4408
4346
  }
@@ -4411,8 +4349,8 @@ var ICEGatheringController = class extends Destroyable {
4411
4349
  state: "new",
4412
4350
  validSDP: false
4413
4351
  });
4414
- this.iceCandidateTimeout = options.iceCandidateTimeout ?? DEFAULT_ICE_CANDIDATE_TIMEOUT_MS;
4415
- this.iceGatheringTimeout = options.iceGatheringTimeout ?? DEFAULT_ICE_GATHERING_TIMEOUT_MS;
4352
+ this.iceCandidateTimeout = options.iceCandidateTimeout ?? require_operators.DEFAULT_ICE_CANDIDATE_TIMEOUT_MS;
4353
+ this.iceGatheringTimeout = options.iceGatheringTimeout ?? require_operators.DEFAULT_ICE_GATHERING_TIMEOUT_MS;
4416
4354
  this.relayOnly = options.relayOnly ?? false;
4417
4355
  this.setupEventListeners();
4418
4356
  this.subscribeTo(this.peerConnectionControllerNegotiating$.pipe((0, rxjs.filter)((isNegotiating) => isNegotiating)), (isNegotiating) => {
@@ -4420,7 +4358,7 @@ var ICEGatheringController = class extends Destroyable {
4420
4358
  this.setupEventListeners();
4421
4359
  this.iceGatheringTimer = setTimeout(() => {
4422
4360
  if (this.peerConnection.iceGatheringState !== "complete") {
4423
- logger$20.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
4361
+ logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
4424
4362
  this.handleICEGatheringTimeout();
4425
4363
  }
4426
4364
  }, this.iceGatheringTimeout);
@@ -4447,9 +4385,9 @@ var ICEGatheringController = class extends Destroyable {
4447
4385
  this.relayOnly = value;
4448
4386
  }
4449
4387
  handleICEGatheringComplete() {
4450
- logger$20.debug("[ICEGatheringController] Handling ICE gathering complete");
4451
- logger$20.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
4452
- logger$20.debug("[ICEGatheringController] ICE gathering complete");
4388
+ logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
4389
+ logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
4390
+ logger$21.debug("[ICEGatheringController] ICE gathering complete");
4453
4391
  this._iceCandidatesState.next({
4454
4392
  state: "complete",
4455
4393
  validSDP: this.hasValidLocalDescriptionSDP
@@ -4465,21 +4403,21 @@ var ICEGatheringController = class extends Destroyable {
4465
4403
  this.removeTimer("iceGatheringTimer");
4466
4404
  const validSDP = this.hasValidLocalDescriptionSDP;
4467
4405
  if (validSDP) {
4468
- logger$20.debug("[ICEGatheringController] Local SDP is valid");
4406
+ logger$21.debug("[ICEGatheringController] Local SDP is valid");
4469
4407
  this._iceCandidatesState.next({
4470
4408
  state: "timeout",
4471
4409
  validSDP
4472
4410
  });
4473
4411
  this.stopGathering();
4474
- } else logger$20.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
4412
+ } else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
4475
4413
  }
4476
4414
  handleICECandidateTimeout() {
4477
4415
  if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
4478
- logger$20.warn("[ICEGatheringController] ICE candidate timeout");
4416
+ logger$21.warn("[ICEGatheringController] ICE candidate timeout");
4479
4417
  const validSDP = this.hasValidLocalDescriptionSDP;
4480
4418
  if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
4481
4419
  else {
4482
- logger$20.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
4420
+ logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
4483
4421
  this._iceCandidatesState.next({
4484
4422
  state: "timeout",
4485
4423
  validSDP
@@ -4488,7 +4426,7 @@ var ICEGatheringController = class extends Destroyable {
4488
4426
  }
4489
4427
  }
4490
4428
  restartICEGatheringWithRelayOnly() {
4491
- logger$20.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
4429
+ logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
4492
4430
  this.relayOnly = true;
4493
4431
  this.peerConnection.setConfiguration({
4494
4432
  ...this.peerConnection.getConfiguration(),
@@ -4503,7 +4441,7 @@ var ICEGatheringController = class extends Destroyable {
4503
4441
  }
4504
4442
  }
4505
4443
  clearAllTimers() {
4506
- logger$20.debug("[ICEGatheringController] Clearing all timers");
4444
+ logger$21.debug("[ICEGatheringController] Clearing all timers");
4507
4445
  this.removeTimer("iceGatheringTimer");
4508
4446
  this.removeTimer("iceCandidateTimer");
4509
4447
  }
@@ -4512,7 +4450,7 @@ var ICEGatheringController = class extends Destroyable {
4512
4450
  this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
4513
4451
  }
4514
4452
  destroy() {
4515
- logger$20.debug("[ICEGatheringController] Destroying ICEGatheringController");
4453
+ logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
4516
4454
  this.clearAllTimers();
4517
4455
  this.removeEventListeners();
4518
4456
  super.destroy();
@@ -4521,7 +4459,7 @@ var ICEGatheringController = class extends Destroyable {
4521
4459
 
4522
4460
  //#endregion
4523
4461
  //#region src/controllers/LocalAudioPipeline.ts
4524
- const logger$19 = require_operators.getLogger();
4462
+ const logger$20 = require_operators.getLogger();
4525
4463
  /**
4526
4464
  * Web Audio pipeline for the local microphone stream.
4527
4465
  *
@@ -4557,9 +4495,9 @@ var LocalAudioPipeline = class extends Destroyable {
4557
4495
  this._destination = this._audioContext.createMediaStreamDestination();
4558
4496
  this._gainNode.connect(this._analyser);
4559
4497
  this._analyser.connect(this._destination);
4560
- this._speakingThreshold = options.speakingThreshold ?? VAD_THRESHOLD;
4561
- this._speakingHoldMs = options.speakingHoldMs ?? VAD_HOLD_MS;
4562
- this._pollIntervalMs = options.pollIntervalMs ?? AUDIO_LEVEL_POLL_INTERVAL_MS;
4498
+ this._speakingThreshold = options.speakingThreshold ?? require_operators.VAD_THRESHOLD;
4499
+ this._speakingHoldMs = options.speakingHoldMs ?? require_operators.VAD_HOLD_MS;
4500
+ this._pollIntervalMs = options.pollIntervalMs ?? require_operators.AUDIO_LEVEL_POLL_INTERVAL_MS;
4563
4501
  const initial = options.initialGain ?? 1;
4564
4502
  this._gain$.next(initial);
4565
4503
  this.applyEffectiveGain();
@@ -4633,7 +4571,7 @@ var LocalAudioPipeline = class extends Destroyable {
4633
4571
  try {
4634
4572
  this._inputSource.disconnect();
4635
4573
  } catch (error) {
4636
- logger$19.debug("[LocalAudioPipeline] input disconnect warning:", error);
4574
+ logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
4637
4575
  }
4638
4576
  this._inputSource = null;
4639
4577
  }
@@ -4643,7 +4581,7 @@ var LocalAudioPipeline = class extends Destroyable {
4643
4581
  this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
4644
4582
  this._inputSource.connect(this._gainNode);
4645
4583
  if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
4646
- logger$19.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
4584
+ logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
4647
4585
  });
4648
4586
  }
4649
4587
  destroy() {
@@ -4658,7 +4596,7 @@ var LocalAudioPipeline = class extends Destroyable {
4658
4596
  this._analyser.disconnect();
4659
4597
  } catch {}
4660
4598
  this._audioContext.close().catch((error) => {
4661
- logger$19.debug("[LocalAudioPipeline] audio context close warning:", error);
4599
+ logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
4662
4600
  });
4663
4601
  super.destroy();
4664
4602
  }
@@ -4684,7 +4622,7 @@ var LocalAudioPipeline = class extends Destroyable {
4684
4622
 
4685
4623
  //#endregion
4686
4624
  //#region src/controllers/LocalStreamController.ts
4687
- const logger$18 = require_operators.getLogger();
4625
+ const logger$19 = require_operators.getLogger();
4688
4626
  var LocalStreamController = class extends Destroyable {
4689
4627
  constructor(options) {
4690
4628
  super();
@@ -4722,28 +4660,30 @@ var LocalStreamController = class extends Destroyable {
4722
4660
  * Build the local media stream based on the provided options.
4723
4661
  */
4724
4662
  async buildLocalStream() {
4725
- logger$18.debug("[LocalStreamController] Building local media stream.");
4663
+ logger$19.debug("[LocalStreamController] Building local media stream.");
4726
4664
  let stream;
4727
4665
  if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
4728
4666
  const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
4729
4667
  stream = new MediaStream(tracks);
4730
4668
  } else if (this.options.propose === "screenshare") {
4731
- logger$18.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
4669
+ logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
4732
4670
  stream = await this.options.getDisplayMedia({
4733
4671
  video: true,
4734
4672
  audio: Boolean(this.options.inputAudioDeviceConstraints)
4735
4673
  });
4736
- logger$18.debug("[LocalStreamController] Screen share media obtained:", stream);
4674
+ logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
4737
4675
  } else {
4738
4676
  const constraints = {
4739
4677
  audio: this.options.inputAudioDeviceConstraints,
4740
4678
  video: this.options.inputVideoDeviceConstraints
4741
4679
  };
4742
- logger$18.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
4680
+ logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
4743
4681
  stream = await this.options.getUserMedia(constraints);
4744
- logger$18.debug("[LocalStreamController] User media obtained:", stream);
4682
+ logger$19.debug("[LocalStreamController] User media obtained:", stream);
4745
4683
  }
4746
4684
  this._localStream$.next(stream);
4685
+ this._localAudioTracks$.next(stream.getAudioTracks());
4686
+ this._localVideoTracks$.next(stream.getVideoTracks());
4747
4687
  return stream;
4748
4688
  }
4749
4689
  /**
@@ -4758,7 +4698,7 @@ var LocalStreamController = class extends Destroyable {
4758
4698
  this._localStream$.next(localStream);
4759
4699
  if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
4760
4700
  else this._localAudioTracks$.next(localStream.getAudioTracks());
4761
- logger$18.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
4701
+ logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
4762
4702
  return localStream;
4763
4703
  }
4764
4704
  /**
@@ -4770,7 +4710,7 @@ var LocalStreamController = class extends Destroyable {
4770
4710
  const stream = this._localStream$.value;
4771
4711
  const track = stream?.getTracks().find((t) => t.id === trackId);
4772
4712
  if (!track) {
4773
- logger$18.debug(`[LocalStreamController] track not found: ${trackId}`);
4713
+ logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
4774
4714
  return;
4775
4715
  }
4776
4716
  track.removeEventListener("ended", this.mediaTrackEndedHandler);
@@ -4779,7 +4719,7 @@ var LocalStreamController = class extends Destroyable {
4779
4719
  this._localStream$.next(stream);
4780
4720
  if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
4781
4721
  else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
4782
- logger$18.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
4722
+ logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
4783
4723
  return track;
4784
4724
  }
4785
4725
  /**
@@ -4814,7 +4754,7 @@ var LocalStreamController = class extends Destroyable {
4814
4754
  */
4815
4755
  stopAllTracks() {
4816
4756
  this._localStream$.value?.getTracks().forEach((track) => {
4817
- logger$18.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
4757
+ logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
4818
4758
  track.removeEventListener("ended", this.mediaTrackEndedHandler);
4819
4759
  track.stop();
4820
4760
  });
@@ -4830,7 +4770,7 @@ var LocalStreamController = class extends Destroyable {
4830
4770
 
4831
4771
  //#endregion
4832
4772
  //#region src/controllers/TransceiverController.ts
4833
- const logger$17 = require_operators.getLogger();
4773
+ const logger$18 = require_operators.getLogger();
4834
4774
  const getDirection = (send, recv) => {
4835
4775
  if (send && recv) return "sendrecv";
4836
4776
  else if (send && !recv) return "sendonly";
@@ -4911,6 +4851,12 @@ var TransceiverController = class extends Destroyable {
4911
4851
  scaleResolutionDownBy: Number(rid) * 6 || 1
4912
4852
  }));
4913
4853
  }
4854
+ /**
4855
+ * Resolve the current MediaTrackConstraints for an input kind, normalising
4856
+ * boolean shorthand to an empty object. Public so the surrounding
4857
+ * RTCPeerConnectionController can drive its own pipeline-aware getUserMedia
4858
+ * call with the same effective constraints the transceiver would have used.
4859
+ */
4914
4860
  getConstraintsFor(kind) {
4915
4861
  const constraints = kind === "audio" ? this.inputAudioDeviceConstraints : this.inputVideoDeviceConstraints;
4916
4862
  return typeof constraints === "boolean" ? {} : constraints;
@@ -4932,7 +4878,7 @@ var TransceiverController = class extends Destroyable {
4932
4878
  sendEncodings: isAudio ? void 0 : this.sendEncodings,
4933
4879
  streams: direction === "recvonly" ? void 0 : [localStream]
4934
4880
  };
4935
- logger$17.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
4881
+ logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
4936
4882
  transceiver,
4937
4883
  transceiverParams
4938
4884
  });
@@ -4940,11 +4886,11 @@ var TransceiverController = class extends Destroyable {
4940
4886
  await transceiver.sender.replaceTrack(track);
4941
4887
  transceiver.direction = transceiverParams.direction;
4942
4888
  if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
4943
- logger$17.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
4889
+ logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
4944
4890
  transceiver.sender.setStreams(...transceiverParams.streams);
4945
4891
  }
4946
4892
  } else {
4947
- logger$17.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
4893
+ logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
4948
4894
  this.peerConnection.addTransceiver(track, transceiverParams);
4949
4895
  }
4950
4896
  }
@@ -4958,13 +4904,13 @@ var TransceiverController = class extends Destroyable {
4958
4904
  if (options.updateTransceiverDirection) transceiver.direction = "inactive";
4959
4905
  }
4960
4906
  } catch (error) {
4961
- logger$17.error("[TransceiverController] stopTrackSender error", kind, error);
4907
+ logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
4962
4908
  this.options.onError?.(new require_operators.MediaTrackError("stopTrackSender", kind, error));
4963
4909
  }
4964
4910
  }
4965
4911
  async restoreTrackSender(kind) {
4966
4912
  try {
4967
- logger$17.debug("[TransceiverController] restoreTrackSender called", kind);
4913
+ logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
4968
4914
  const constraints = {};
4969
4915
  const transceivers = this.transceiverByKind(kind);
4970
4916
  for (const transceiver of transceivers) {
@@ -4974,23 +4920,23 @@ var TransceiverController = class extends Destroyable {
4974
4920
  if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
4975
4921
  }
4976
4922
  }
4977
- logger$17.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
4923
+ logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
4978
4924
  if (Object.keys(constraints).length === 0) {
4979
- logger$17.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
4925
+ logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
4980
4926
  return;
4981
4927
  }
4982
4928
  const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
4983
- logger$17.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
4929
+ logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
4984
4930
  for (const newTrack of newTracks) {
4985
4931
  this.options.localStreamController.addTrack(newTrack);
4986
4932
  const trackKind = newTrack.kind;
4987
4933
  const transceiverOfKind = this.transceiverByKind(trackKind)[0];
4988
4934
  transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
4989
- logger$17.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
4935
+ logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
4990
4936
  await transceiverOfKind.sender.replaceTrack(newTrack);
4991
4937
  }
4992
4938
  } catch (error) {
4993
- logger$17.error("[TransceiverController] restoreTrackSender error", kind, error);
4939
+ logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
4994
4940
  this.options.onError?.(new require_operators.MediaTrackError("restoreTrackSender", kind, error));
4995
4941
  }
4996
4942
  }
@@ -5031,14 +4977,14 @@ var TransceiverController = class extends Destroyable {
5031
4977
  };
5032
4978
  try {
5033
4979
  await track.applyConstraints(constraintsToApply);
5034
- logger$17.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
5035
- logger$17.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
4980
+ logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
4981
+ logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
5036
4982
  } catch (error) {
5037
- logger$17.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
4983
+ logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
5038
4984
  try {
5039
4985
  await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
5040
4986
  } catch (fallbackError) {
5041
- logger$17.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
4987
+ logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
5042
4988
  this.options.onError?.(new require_operators.MediaTrackError("updateSendersConstraints", kind, fallbackError));
5043
4989
  }
5044
4990
  }
@@ -5066,7 +5012,7 @@ var TransceiverController = class extends Destroyable {
5066
5012
  if (!newTrack) throw new require_operators.MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
5067
5013
  await sender.replaceTrack(newTrack);
5068
5014
  this.options.localStreamController.addTrack(newTrack);
5069
- logger$17.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
5015
+ logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
5070
5016
  }
5071
5017
  getMediaDirections() {
5072
5018
  if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
@@ -5096,7 +5042,7 @@ var TransceiverController = class extends Destroyable {
5096
5042
 
5097
5043
  //#endregion
5098
5044
  //#region src/controllers/RTCPeerConnectionController.ts
5099
- const logger$16 = require_operators.getLogger();
5045
+ const logger$17 = require_operators.getLogger();
5100
5046
  var RTCPeerConnectionController = class extends Destroyable {
5101
5047
  constructor(options = {}, remoteSessionDescription, deviceController) {
5102
5048
  super();
@@ -5112,43 +5058,43 @@ var RTCPeerConnectionController = class extends Destroyable {
5112
5058
  this.oniceconnectionstatechangeHandler = () => {
5113
5059
  if (this.peerConnection) {
5114
5060
  const { iceConnectionState } = this.peerConnection;
5115
- logger$16.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
5061
+ logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
5116
5062
  this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
5117
5063
  }
5118
5064
  };
5119
5065
  this.onconnectionstatechangeHandler = () => {
5120
5066
  if (this.peerConnection) {
5121
5067
  const { connectionState } = this.peerConnection;
5122
- logger$16.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
5068
+ logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
5123
5069
  if (connectionState === "connected") this.removeConnectionTimer();
5124
5070
  this._connectionState$.next(this.peerConnection.connectionState);
5125
5071
  }
5126
5072
  };
5127
5073
  this.onsignalingstatechangeHandler = () => {
5128
- logger$16.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
5074
+ logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
5129
5075
  };
5130
5076
  this.onicegatheringstatechangeHandler = () => {
5131
5077
  if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
5132
5078
  };
5133
5079
  this.onnegotiationneededHandler = (event) => {
5134
- logger$16.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
5080
+ logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
5135
5081
  this.negotiationNeeded$.next();
5136
5082
  };
5137
5083
  this.updateSelectedInputDevice = async (kind, deviceInfo) => {
5138
5084
  try {
5139
5085
  const { localStream } = this;
5140
5086
  if (!localStream) {
5141
- logger$16.warn("[RTCPeerConnectionController] No local stream available to update input device.");
5087
+ logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
5142
5088
  return;
5143
5089
  }
5144
- logger$16.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
5090
+ logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
5145
5091
  const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
5146
5092
  if (track) {
5147
5093
  this.transceiverController?.stopTrackSender(kind);
5148
5094
  this.localStreamController.removeTrack(track.id);
5149
- logger$16.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
5095
+ logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
5150
5096
  if (!deviceInfo) {
5151
- logger$16.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
5097
+ logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
5152
5098
  return;
5153
5099
  }
5154
5100
  const streamTrack = (await this.getUserMedia({ [kind]: {
@@ -5156,16 +5102,16 @@ var RTCPeerConnectionController = class extends Destroyable {
5156
5102
  ...this.deviceController.deviceInfoToConstraints(deviceInfo)
5157
5103
  } })).getTracks().find((t) => t.kind === kind);
5158
5104
  if (streamTrack) {
5159
- logger$16.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
5105
+ logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
5160
5106
  this.localStreamController.addTrack(streamTrack);
5161
5107
  await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
5162
- logger$16.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
5108
+ logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
5163
5109
  }
5164
5110
  }
5165
- logger$16.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
5111
+ logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
5166
5112
  } catch (error) {
5167
- logger$16.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
5168
- this._errors$.next(toError(error));
5113
+ logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
5114
+ this._errors$.next(new require_operators.MediaTrackError("updateSelectedInputDevice", kind, error));
5169
5115
  throw error;
5170
5116
  }
5171
5117
  };
@@ -5236,7 +5182,19 @@ var RTCPeerConnectionController = class extends Destroyable {
5236
5182
  return this._memberId;
5237
5183
  }
5238
5184
  stopTrackSender(kind, options = { updateTransceiverDirection: false }) {
5239
- this.transceiverController?.stopTrackSender(kind, options);
5185
+ const audioCovered = kind === "audio" || kind === "both";
5186
+ if (audioCovered && this._localAudioPipeline) this.stopRawAudioInputForPipeline();
5187
+ if (!audioCovered) this.transceiverController?.stopTrackSender(kind, options);
5188
+ else if (kind === "both") this.transceiverController?.stopTrackSender("video", options);
5189
+ else if (!this._localAudioPipeline) this.transceiverController?.stopTrackSender(kind, options);
5190
+ }
5191
+ stopRawAudioInputForPipeline() {
5192
+ const rawTracks = this.localStreamController.localAudioTracks;
5193
+ for (const track of rawTracks) if (track.readyState === "live") {
5194
+ track.stop();
5195
+ this.localStreamController.removeTrack(track.id);
5196
+ }
5197
+ this._localAudioPipeline?.setInputTrack(null);
5240
5198
  }
5241
5199
  get isNegotiating$() {
5242
5200
  return this._isNegotiating$.asObservable();
@@ -5375,7 +5333,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5375
5333
  case "main":
5376
5334
  default: return {
5377
5335
  ...options,
5378
- offerToReceiveAudio: true,
5336
+ offerToReceiveAudio: this.options.receiveAudio ?? true,
5379
5337
  offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
5380
5338
  };
5381
5339
  }
@@ -5402,15 +5360,15 @@ var RTCPeerConnectionController = class extends Destroyable {
5402
5360
  this.setupPeerConnection();
5403
5361
  this.subscribeTo(this.negotiationNeeded$.pipe((0, rxjs.auditTime)(0), (0, rxjs.exhaustMap)(async () => this.startNegotiation())), {
5404
5362
  next: () => {
5405
- logger$16.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
5363
+ logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
5406
5364
  },
5407
5365
  error: (error) => {
5408
- logger$16.error("[RTCPeerConnectionController] Start Negotiation error:", error);
5366
+ logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
5409
5367
  this._errors$.next(toError(error));
5410
5368
  }
5411
5369
  });
5412
5370
  this.subscribeTo((0, rxjs.merge)(this.deviceController.selectedAudioInputDevice$.pipe((0, rxjs.map)((deviceInfo) => ["audio", deviceInfo])), this.deviceController.selectedVideoInputDevice$.pipe((0, rxjs.map)((deviceInfo) => ["video", deviceInfo]))).pipe((0, rxjs.skipWhile)(() => !this.localStreamController.localStream)), async ([kind, deviceInfo]) => {
5413
- logger$16.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
5371
+ logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
5414
5372
  kind,
5415
5373
  deviceInfo
5416
5374
  });
@@ -5427,7 +5385,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5427
5385
  this._initialized$.next(true);
5428
5386
  }
5429
5387
  } catch (error) {
5430
- logger$16.error("[RTCPeerConnectionController] Initialization error:", error);
5388
+ logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
5431
5389
  this._errors$.next(toError(error));
5432
5390
  this.destroy();
5433
5391
  }
@@ -5459,22 +5417,22 @@ var RTCPeerConnectionController = class extends Destroyable {
5459
5417
  }
5460
5418
  async startNegotiation() {
5461
5419
  if (this.isNegotiating) {
5462
- logger$16.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
5420
+ logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
5463
5421
  return;
5464
5422
  }
5465
5423
  this.setupEventListeners();
5466
5424
  if (this.type === "answer") {
5467
- logger$16.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
5425
+ logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
5468
5426
  return;
5469
5427
  }
5470
5428
  this._isNegotiating$.next(true);
5471
- logger$16.debug("[RTCPeerConnectionController] Starting negotiation.");
5429
+ logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
5472
5430
  try {
5473
5431
  const { offerOptions } = this;
5474
- logger$16.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
5432
+ logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
5475
5433
  await this.createOffer(offerOptions);
5476
5434
  } catch (error) {
5477
- logger$16.error("[RTCPeerConnectionController] Error during negotiation:", error);
5435
+ logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
5478
5436
  this._errors$.next(toError(error));
5479
5437
  }
5480
5438
  }
@@ -5490,14 +5448,14 @@ var RTCPeerConnectionController = class extends Destroyable {
5490
5448
  let readyToConnect = status !== "failed";
5491
5449
  try {
5492
5450
  if (status === "received" && sdp) {
5493
- logger$16.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
5451
+ logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
5494
5452
  await this._setRemoteDescription({
5495
5453
  type: "answer",
5496
5454
  sdp
5497
5455
  });
5498
5456
  }
5499
5457
  } catch (error) {
5500
- logger$16.error("[RTCPeerConnectionController] Error updating answer status:", error);
5458
+ logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
5501
5459
  this._errors$.next(toError(error));
5502
5460
  readyToConnect = false;
5503
5461
  } finally {
@@ -5516,7 +5474,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5516
5474
  await this.handleOfferReceived();
5517
5475
  break;
5518
5476
  case "failed":
5519
- logger$16.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
5477
+ logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
5520
5478
  break;
5521
5479
  case "sent":
5522
5480
  default:
@@ -5529,13 +5487,14 @@ var RTCPeerConnectionController = class extends Destroyable {
5529
5487
  */
5530
5488
  async acceptInbound(mediaOverrides) {
5531
5489
  if (mediaOverrides) {
5532
- const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
5490
+ const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
5533
5491
  this.options = {
5534
5492
  ...this.options,
5535
5493
  ...audio !== void 0 ? { audio } : {},
5536
5494
  ...video !== void 0 ? { video } : {},
5537
5495
  ...receiveAudio !== void 0 ? { receiveAudio } : {},
5538
- ...receiveVideo !== void 0 ? { receiveVideo } : {}
5496
+ ...receiveVideo !== void 0 ? { receiveVideo } : {},
5497
+ ...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
5539
5498
  };
5540
5499
  this.transceiverController?.updateOptions({
5541
5500
  receiveAudio: this.receiveAudio,
@@ -5548,7 +5507,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5548
5507
  }
5549
5508
  await this.setupLocalTracks();
5550
5509
  const { answerOptions } = this;
5551
- logger$16.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
5510
+ logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
5552
5511
  await this.createAnswer(answerOptions);
5553
5512
  }
5554
5513
  async handleOfferReceived() {
@@ -5556,7 +5515,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5556
5515
  this._isNegotiating$.next(true);
5557
5516
  await this._setRemoteDescription(this.sdpInit);
5558
5517
  const { answerOptions } = this;
5559
- logger$16.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
5518
+ logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
5560
5519
  await this.createAnswer(answerOptions);
5561
5520
  }
5562
5521
  readyToConnect() {
@@ -5564,7 +5523,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5564
5523
  this.connectionTimer = setTimeout(() => {
5565
5524
  this.removeConnectionTimer();
5566
5525
  if (this.peerConnection?.connectionState !== "connected") {
5567
- logger$16.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
5526
+ logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
5568
5527
  this.iceGatheringController.restartICEGatheringWithRelayOnly();
5569
5528
  }
5570
5529
  }, this.connectionTimeout);
@@ -5586,14 +5545,14 @@ var RTCPeerConnectionController = class extends Destroyable {
5586
5545
  const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
5587
5546
  if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
5588
5547
  result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
5589
- logger$16.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
5548
+ logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
5590
5549
  preferredAudioCodecs,
5591
5550
  preferredVideoCodecs
5592
5551
  });
5593
5552
  }
5594
5553
  if (stereo) {
5595
5554
  result = enableStereoOpus(result);
5596
- logger$16.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
5555
+ logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
5597
5556
  }
5598
5557
  return Promise.resolve(result);
5599
5558
  }
@@ -5649,25 +5608,25 @@ var RTCPeerConnectionController = class extends Destroyable {
5649
5608
  ...this.peerConnection.getConfiguration(),
5650
5609
  iceTransportPolicy: "relay"
5651
5610
  });
5652
- logger$16.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
5611
+ logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
5653
5612
  } catch (error) {
5654
- logger$16.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
5613
+ logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
5655
5614
  }
5656
5615
  this.setupEventListeners();
5657
5616
  this._isNegotiating$.next(true);
5658
- logger$16.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
5617
+ logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
5659
5618
  try {
5660
5619
  const offer = await this.peerConnection.createOffer({ iceRestart: true });
5661
5620
  await this.setLocalDescription(offer);
5662
5621
  } catch (error) {
5663
- logger$16.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
5622
+ logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
5664
5623
  this._errors$.next(toError(error));
5665
5624
  this.negotiationEnded();
5666
5625
  if (policyChanged) this.restoreIceTransportPolicy();
5667
5626
  throw error;
5668
5627
  }
5669
- if (policyChanged) (0, rxjs.firstValueFrom)((0, rxjs.race)(this._iceGatheringState$.pipe((0, rxjs.filter)((state) => state === "complete"), (0, rxjs.take)(1)), (0, rxjs.timer)(ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe((0, rxjs.map)(() => "timeout")))).then(() => this.restoreIceTransportPolicy()).catch((error) => {
5670
- logger$16.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
5628
+ if (policyChanged) (0, rxjs.firstValueFrom)((0, rxjs.race)(this._iceGatheringState$.pipe((0, rxjs.filter)((state) => state === "complete"), (0, rxjs.take)(1)), (0, rxjs.timer)(require_operators.ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe((0, rxjs.map)(() => "timeout")))).then(() => this.restoreIceTransportPolicy()).catch((error) => {
5629
+ logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
5671
5630
  this.restoreIceTransportPolicy();
5672
5631
  });
5673
5632
  }
@@ -5677,9 +5636,9 @@ var RTCPeerConnectionController = class extends Destroyable {
5677
5636
  ...this.peerConnection.getConfiguration(),
5678
5637
  iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
5679
5638
  });
5680
- logger$16.debug("[RTCPeerConnectionController] ICE transport policy restored");
5639
+ logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
5681
5640
  } catch (error) {
5682
- logger$16.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
5641
+ logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
5683
5642
  }
5684
5643
  }
5685
5644
  /**
@@ -5691,13 +5650,25 @@ var RTCPeerConnectionController = class extends Destroyable {
5691
5650
  await this.setupRemoteTracks();
5692
5651
  }
5693
5652
  async setupLocalTracks() {
5694
- logger$16.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
5695
- const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
5653
+ logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
5654
+ if (this.hasNoLocalMediaToSend()) {
5655
+ if (!this.receiveAudio && !this.receiveVideo) throw new require_operators.InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
5656
+ logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
5657
+ this.setupReceiveOnlyTransceivers();
5658
+ return;
5659
+ }
5660
+ let localStream;
5661
+ try {
5662
+ localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
5663
+ } catch (error) {
5664
+ this.handleLocalMediaFailure(error);
5665
+ return;
5666
+ }
5696
5667
  if (this.transceiverController?.useAddStream ?? false) {
5697
- logger$16.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
5668
+ logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
5698
5669
  this.peerConnection?.addStream(localStream);
5699
5670
  if (!this.isNegotiating) {
5700
- logger$16.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
5671
+ logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
5701
5672
  this.negotiationNeeded$.next();
5702
5673
  }
5703
5674
  return;
@@ -5713,12 +5684,54 @@ var RTCPeerConnectionController = class extends Destroyable {
5713
5684
  const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
5714
5685
  await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
5715
5686
  } else {
5716
- logger$16.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
5687
+ logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
5717
5688
  this.peerConnection?.addTrack(track, localStream);
5718
5689
  }
5719
5690
  }
5720
5691
  }
5721
5692
  }
5693
+ /** True for a main connection with no local media to send. */
5694
+ hasNoLocalMediaToSend() {
5695
+ const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
5696
+ return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
5697
+ }
5698
+ /** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
5699
+ get requestedMediaKinds() {
5700
+ const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
5701
+ const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
5702
+ if (wantsAudio && wantsVideo) return "audiovideo";
5703
+ return wantsVideo ? "video" : "audio";
5704
+ }
5705
+ /**
5706
+ * Handle a local media acquisition failure with a typed, semantically
5707
+ * accurate MediaAccessError created at the acquisition site:
5708
+ * - Auxiliary connections (screenshare / additional-device) throw a
5709
+ * non-fatal error — VertoManager surfaces it and the call is unaffected.
5710
+ * - The main connection degrades to receive-only when allowed (default),
5711
+ * otherwise fails with a fatal error.
5712
+ */
5713
+ handleLocalMediaFailure(error) {
5714
+ if (this.propose === "screenshare") throw new require_operators.MediaAccessError("startScreenShare", "screen", error, false);
5715
+ if (this.propose === "additional-device") throw new require_operators.MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
5716
+ const canReceive = this.receiveAudio || this.receiveVideo;
5717
+ if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new require_operators.MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
5718
+ logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
5719
+ this._errors$.next(new require_operators.MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
5720
+ this.setupReceiveOnlyTransceivers();
5721
+ }
5722
+ /**
5723
+ * Negotiate receive-only m-lines when there are no local tracks to send.
5724
+ * Only offer-type connections add transceivers — answer-type connections
5725
+ * reuse the transceivers created from the remote offer.
5726
+ */
5727
+ setupReceiveOnlyTransceivers() {
5728
+ if (this.type !== "offer") return;
5729
+ if (this.transceiverController?.useAddTransceivers ?? false) {
5730
+ this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
5731
+ this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
5732
+ }
5733
+ if (!this.isNegotiating) this.negotiationNeeded$.next();
5734
+ }
5722
5735
  async getUserMedia(constraints) {
5723
5736
  return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
5724
5737
  }
@@ -5730,7 +5743,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5730
5743
  async setupRemoteTracks() {
5731
5744
  if (!this.peerConnection) throw new require_operators.DependencyError("RTCPeerConnection is not initialized");
5732
5745
  this.peerConnection.ontrack = (event) => {
5733
- logger$16.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
5746
+ logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
5734
5747
  if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
5735
5748
  else {
5736
5749
  const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
@@ -5741,8 +5754,27 @@ var RTCPeerConnectionController = class extends Destroyable {
5741
5754
  await this.transceiverController?.setupRemoteTransceivers(this.type);
5742
5755
  }
5743
5756
  async restoreTrackSender(kind) {
5744
- await this.transceiverController?.restoreTrackSender(kind);
5745
- if (kind !== "video" && this._localAudioPipeline) await this.applyLocalAudioPipelineToSender();
5757
+ const audioCovered = kind === "audio" || kind === "both";
5758
+ if (audioCovered && this._localAudioPipeline) await this.restoreRawAudioInputForPipeline();
5759
+ if (!audioCovered) await this.transceiverController?.restoreTrackSender(kind);
5760
+ else if (kind === "both") await this.transceiverController?.restoreTrackSender("video");
5761
+ else if (!this._localAudioPipeline) await this.transceiverController?.restoreTrackSender(kind);
5762
+ }
5763
+ async restoreRawAudioInputForPipeline() {
5764
+ if (!this._localAudioPipeline) return;
5765
+ const constraints = this.transceiverController?.getConstraintsFor("audio") ?? {};
5766
+ let stream;
5767
+ try {
5768
+ stream = await this.getUserMedia({ audio: constraints });
5769
+ } catch (error) {
5770
+ logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
5771
+ this._errors$.next(new require_operators.MediaTrackError("restoreAudioPipelineInput", "audio", error));
5772
+ return;
5773
+ }
5774
+ const newTrack = stream.getAudioTracks().at(0);
5775
+ if (!newTrack) return;
5776
+ this.localStreamController.addTrack(newTrack);
5777
+ this._localAudioPipeline.setInputTrack(newTrack);
5746
5778
  }
5747
5779
  /**
5748
5780
  * Return the lazily-created {@link LocalAudioPipeline}, constructing it on
@@ -5757,7 +5789,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5757
5789
  try {
5758
5790
  this._localAudioPipeline = new LocalAudioPipeline();
5759
5791
  } catch (error) {
5760
- logger$16.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
5792
+ logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
5761
5793
  return null;
5762
5794
  }
5763
5795
  this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
@@ -5779,7 +5811,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5779
5811
  try {
5780
5812
  await sender.replaceTrack(this._localAudioPipeline.outputTrack);
5781
5813
  } catch (error) {
5782
- logger$16.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
5814
+ logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
5783
5815
  }
5784
5816
  }
5785
5817
  /**
@@ -5795,10 +5827,10 @@ var RTCPeerConnectionController = class extends Destroyable {
5795
5827
  try {
5796
5828
  const localStream = this.localStreamController.addTrack(track);
5797
5829
  this.peerConnection.addTrack(track, localStream);
5798
- logger$16.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
5830
+ logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
5799
5831
  } catch (error) {
5800
- logger$16.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
5801
- this._errors$.next(toError(error));
5832
+ logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
5833
+ this._errors$.next(new require_operators.MediaTrackError("addLocalTrack", track.kind, error));
5802
5834
  throw error;
5803
5835
  }
5804
5836
  }
@@ -5814,16 +5846,16 @@ var RTCPeerConnectionController = class extends Destroyable {
5814
5846
  }
5815
5847
  const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
5816
5848
  if (!sender) {
5817
- logger$16.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
5849
+ logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
5818
5850
  return;
5819
5851
  }
5820
5852
  try {
5821
5853
  this.peerConnection.removeTrack(sender);
5822
5854
  this.localStreamController.removeTrack(trackId);
5823
- logger$16.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
5855
+ logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
5824
5856
  } catch (error) {
5825
- logger$16.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
5826
- this._errors$.next(toError(error));
5857
+ logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
5858
+ this._errors$.next(new require_operators.MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
5827
5859
  throw error;
5828
5860
  }
5829
5861
  }
@@ -5849,7 +5881,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5849
5881
  async replaceAudioTrackWithConstraints(constraints) {
5850
5882
  const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
5851
5883
  if (!senders || senders.length === 0) {
5852
- logger$16.warn("[RTCPeerConnectionController] No live audio sender to replace");
5884
+ logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
5853
5885
  return;
5854
5886
  }
5855
5887
  for (const sender of senders) {
@@ -5867,7 +5899,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5867
5899
  const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
5868
5900
  await sender.replaceTrack(newTrack);
5869
5901
  this.localStreamController.addTrack(newTrack);
5870
- logger$16.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
5902
+ logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
5871
5903
  }
5872
5904
  }
5873
5905
  /**
@@ -5875,7 +5907,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5875
5907
  * Completes all observables to prevent memory leaks.
5876
5908
  */
5877
5909
  destroy() {
5878
- logger$16.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
5910
+ logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
5879
5911
  this.removeConnectionTimer();
5880
5912
  this._iceGatheringController?.destroy();
5881
5913
  this._localAudioPipeline?.destroy();
@@ -5901,7 +5933,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5901
5933
  }
5902
5934
  stopRemoteTracks() {
5903
5935
  this._remoteStream$.value?.getTracks().forEach((track) => {
5904
- logger$16.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
5936
+ logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
5905
5937
  track.stop();
5906
5938
  });
5907
5939
  }
@@ -5918,7 +5950,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5918
5950
  ...params,
5919
5951
  sdp: finalRemote
5920
5952
  };
5921
- logger$16.debug("[RTCPeerConnectionController] Setting remote description:", answer);
5953
+ logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
5922
5954
  return this.peerConnection.setRemoteDescription(answer);
5923
5955
  }
5924
5956
  };
@@ -5933,7 +5965,11 @@ function isVertoInviteMessage(value) {
5933
5965
  const msg = value;
5934
5966
  return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
5935
5967
  }
5936
- function isVertoByeMessage(value) {
5968
+ /**
5969
+ * Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
5970
+ * built locally and never type-guarded, so only the inbound shape is asserted.
5971
+ */
5972
+ function isVertoByeInboundMessage(value) {
5937
5973
  if (!isVertoMethodMessage(value)) return false;
5938
5974
  return value.method === "verto.bye";
5939
5975
  }
@@ -5953,10 +5989,18 @@ function isVertoMediaParamsInnerParams(value) {
5953
5989
  function isVertoPingInnerParams(value) {
5954
5990
  return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
5955
5991
  }
5992
+ /**
5993
+ * Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
5994
+ * this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
5995
+ * it against a boolean answer/reject signal.
5996
+ */
5997
+ function isVertoByeInboundParamsGuard(value) {
5998
+ return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
5999
+ }
5956
6000
 
5957
6001
  //#endregion
5958
6002
  //#region src/managers/VertoManager.ts
5959
- const logger$15 = require_operators.getLogger();
6003
+ const logger$16 = require_operators.getLogger();
5960
6004
  /**
5961
6005
  * Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
5962
6006
  *
@@ -6012,7 +6056,7 @@ var WebRTCVertoManager = class extends VertoManager {
6012
6056
  try {
6013
6057
  await this.executeVerto(vertoModifyMessage);
6014
6058
  } catch (error) {
6015
- logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
6059
+ logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
6016
6060
  throw error;
6017
6061
  }
6018
6062
  }
@@ -6025,7 +6069,7 @@ var WebRTCVertoManager = class extends VertoManager {
6025
6069
  try {
6026
6070
  await this.executeVerto(vertoModifyMessage);
6027
6071
  } catch (error) {
6028
- logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
6072
+ logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
6029
6073
  throw error;
6030
6074
  }
6031
6075
  }
@@ -6078,25 +6122,25 @@ var WebRTCVertoManager = class extends VertoManager {
6078
6122
  if (event.member_id) this.setSelfIdIfNull(event.member_id);
6079
6123
  });
6080
6124
  this.subscribeTo(this.vertoMedia$, (event) => {
6081
- logger$15.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
6082
- this._signalingStatus$.next("ringing");
6125
+ logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
6083
6126
  const { sdp, callID } = event;
6127
+ this.emitMainSignalingStatus(callID, "ringing");
6084
6128
  this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
6085
6129
  status: "received",
6086
6130
  sdp
6087
6131
  });
6088
6132
  });
6089
6133
  this.subscribeTo(this.vertoAnswer$, (event) => {
6090
- logger$15.debug("[WebRTCManager] Received Verto answer event:", event);
6091
- this._signalingStatus$.next("connecting");
6134
+ logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
6092
6135
  const { sdp, callID } = event;
6136
+ this.emitMainSignalingStatus(callID, "connecting");
6093
6137
  this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
6094
6138
  status: "received",
6095
6139
  sdp
6096
6140
  });
6097
6141
  });
6098
6142
  this.subscribeTo(this.vertoMediaParams$, (event) => {
6099
- logger$15.debug("[WebRTCManager] Received Verto mediaParams event:", event);
6143
+ logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
6100
6144
  const { mediaParams, callID } = event;
6101
6145
  const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
6102
6146
  const { audio, video } = mediaParams;
@@ -6110,7 +6154,7 @@ var WebRTCVertoManager = class extends VertoManager {
6110
6154
  timestamp: Date.now()
6111
6155
  });
6112
6156
  } catch (error) {
6113
- logger$15.warn("[WebRTCManager] Error applying server-pushed media params:", error);
6157
+ logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
6114
6158
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6115
6159
  }
6116
6160
  })();
@@ -6132,13 +6176,13 @@ var WebRTCVertoManager = class extends VertoManager {
6132
6176
  */
6133
6177
  setNodeIdIfNull(nodeId) {
6134
6178
  if (!this._nodeId$.value && nodeId) {
6135
- logger$15.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
6179
+ logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
6136
6180
  this._nodeId$.next(nodeId);
6137
6181
  }
6138
6182
  }
6139
6183
  setSelfIdIfNull(selfId) {
6140
6184
  if (!this._selfId$.value && selfId) {
6141
- logger$15.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
6185
+ logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
6142
6186
  this._selfId$.next(selfId);
6143
6187
  }
6144
6188
  }
@@ -6147,7 +6191,7 @@ var WebRTCVertoManager = class extends VertoManager {
6147
6191
  const vertoPongMessage = VertoPong({ ...vertoPing });
6148
6192
  await this.executeVerto(vertoPongMessage);
6149
6193
  } catch (error) {
6150
- logger$15.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
6194
+ logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
6151
6195
  this.onError?.(new require_operators.VertoPongError(error));
6152
6196
  }
6153
6197
  }
@@ -6157,7 +6201,7 @@ var WebRTCVertoManager = class extends VertoManager {
6157
6201
  if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
6158
6202
  if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
6159
6203
  } catch (error) {
6160
- logger$15.warn("[WebRTCManager] Error updating media constraints:", error);
6204
+ logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
6161
6205
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6162
6206
  throw error;
6163
6207
  }
@@ -6187,20 +6231,20 @@ var WebRTCVertoManager = class extends VertoManager {
6187
6231
  try {
6188
6232
  const pc = this.mainPeerConnection.peerConnection;
6189
6233
  if (!pc) {
6190
- logger$15.warn("[WebRTCManager] No peer connection for keyframe request");
6234
+ logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
6191
6235
  return;
6192
6236
  }
6193
6237
  const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
6194
6238
  if (!videoReceiver) {
6195
- logger$15.warn("[WebRTCManager] No video receiver for keyframe request");
6239
+ logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
6196
6240
  return;
6197
6241
  }
6198
6242
  if (typeof videoReceiver.requestKeyFrame === "function") {
6199
6243
  videoReceiver.requestKeyFrame();
6200
- logger$15.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
6201
- } else logger$15.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
6244
+ logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
6245
+ } else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
6202
6246
  } catch (error) {
6203
- logger$15.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
6247
+ logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
6204
6248
  }
6205
6249
  }
6206
6250
  /**
@@ -6218,13 +6262,13 @@ var WebRTCVertoManager = class extends VertoManager {
6218
6262
  try {
6219
6263
  const controller = this.mainPeerConnection;
6220
6264
  if (!controller.peerConnection) {
6221
- logger$15.warn("[WebRTCManager] No peer connection for ICE restart");
6265
+ logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
6222
6266
  return;
6223
6267
  }
6224
6268
  await controller.triggerIceRestart(relayOnly);
6225
- logger$15.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
6269
+ logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
6226
6270
  } catch (error) {
6227
- logger$15.error("[WebRTCManager] ICE restart failed:", error);
6271
+ logger$16.error("[WebRTCManager] ICE restart failed:", error);
6228
6272
  throw error;
6229
6273
  }
6230
6274
  }
@@ -6242,13 +6286,13 @@ var WebRTCVertoManager = class extends VertoManager {
6242
6286
  const entries = Array.from(this._rtcPeerConnectionsMap.entries());
6243
6287
  for (const [id, controller] of entries) try {
6244
6288
  if (!controller.peerConnection) {
6245
- logger$15.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
6289
+ logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
6246
6290
  continue;
6247
6291
  }
6248
6292
  await controller.triggerIceRestart(relayOnly);
6249
- logger$15.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
6293
+ logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
6250
6294
  } catch (error) {
6251
- logger$15.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
6295
+ logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
6252
6296
  }
6253
6297
  }
6254
6298
  /**
@@ -6260,7 +6304,7 @@ var WebRTCVertoManager = class extends VertoManager {
6260
6304
  requestKeyframeAll() {
6261
6305
  for (const [id, controller] of this._rtcPeerConnectionsMap) {
6262
6306
  if (controller.isScreenShare) {
6263
- logger$15.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
6307
+ logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
6264
6308
  continue;
6265
6309
  }
6266
6310
  try {
@@ -6270,10 +6314,10 @@ var WebRTCVertoManager = class extends VertoManager {
6270
6314
  if (!videoReceiver) continue;
6271
6315
  if (typeof videoReceiver.requestKeyFrame === "function") {
6272
6316
  videoReceiver.requestKeyFrame();
6273
- logger$15.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
6317
+ logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
6274
6318
  }
6275
6319
  } catch (error) {
6276
- logger$15.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
6320
+ logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
6277
6321
  }
6278
6322
  }
6279
6323
  }
@@ -6290,7 +6334,7 @@ var WebRTCVertoManager = class extends VertoManager {
6290
6334
  return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoMediaParamsInnerParams, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
6291
6335
  }
6292
6336
  get vertoBye$() {
6293
- return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoByeMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
6337
+ return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoByeInboundMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
6294
6338
  }
6295
6339
  get vertoAttach$() {
6296
6340
  return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoAttachMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
@@ -6334,7 +6378,7 @@ var WebRTCVertoManager = class extends VertoManager {
6334
6378
  default:
6335
6379
  }
6336
6380
  } catch (error) {
6337
- logger$15.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
6381
+ logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
6338
6382
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6339
6383
  if (vertoMethod === "verto.modify") this.onModifyFailed?.();
6340
6384
  }
@@ -6349,19 +6393,33 @@ var WebRTCVertoManager = class extends VertoManager {
6349
6393
  sdp
6350
6394
  });
6351
6395
  } catch (error) {
6352
- logger$15.warn("[WebRTCManager] Error processing modify response:", error);
6396
+ logger$16.warn("[WebRTCManager] Error processing modify response:", error);
6353
6397
  const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
6354
6398
  this.onError?.(modifyError);
6355
6399
  }
6356
6400
  }
6357
6401
  }
6402
+ emitMainSignalingStatus(callId, status) {
6403
+ const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
6404
+ if (!rtcPeerConnController) {
6405
+ const signalingError = new require_operators.DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
6406
+ logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
6407
+ callId,
6408
+ status,
6409
+ signalingError
6410
+ });
6411
+ this.onError?.(signalingError);
6412
+ return;
6413
+ }
6414
+ if (rtcPeerConnController.isMainDevice) this._signalingStatus$.next(status);
6415
+ }
6358
6416
  processInviteResponse(response, rtcPeerConnController) {
6359
6417
  if (!response.error && require_operators.getValueFrom(response, "result.result.result.message") === "CALL CREATED") {
6360
- this._signalingStatus$.next("trying");
6418
+ this.emitMainSignalingStatus(rtcPeerConnController.id, "trying");
6361
6419
  this._nodeId$.next(require_operators.getValueFrom(response, "result.node_id") ?? null);
6362
6420
  const memberId = require_operators.getValueFrom(response, "result.result.result.memberID") ?? null;
6363
6421
  const callId = require_operators.getValueFrom(response, "result.result.result.callID") ?? null;
6364
- logger$15.debug("[WebRTCManager] Verto invite response:", {
6422
+ logger$16.debug("[WebRTCManager] Verto invite response:", {
6365
6423
  callId,
6366
6424
  memberId,
6367
6425
  response
@@ -6371,14 +6429,14 @@ var WebRTCVertoManager = class extends VertoManager {
6371
6429
  if (callId) {
6372
6430
  this.webRtcCallSession.addCallId(callId);
6373
6431
  this.attachManager.attach(this.buildAttachableCall(callId));
6374
- } else logger$15.warn("[WebRTCManager] Cannot attach call, missing callId:", {
6432
+ } else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
6375
6433
  nodeId: this.nodeId,
6376
6434
  callId
6377
6435
  });
6378
- logger$15.info("[WebRTCManager] Verto invite successful");
6379
- logger$15.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
6436
+ logger$16.info("[WebRTCManager] Verto invite successful");
6437
+ logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
6380
6438
  } else {
6381
- logger$15.error("[WebRTCManager] Verto invite failed:", response);
6439
+ logger$16.error("[WebRTCManager] Verto invite failed:", response);
6382
6440
  const inviteError = response.error ? new require_operators.JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
6383
6441
  this.onError?.(inviteError);
6384
6442
  }
@@ -6405,6 +6463,7 @@ var WebRTCVertoManager = class extends VertoManager {
6405
6463
  inputVideoStream: options.inputVideoStream,
6406
6464
  receiveAudio: options.receiveAudio,
6407
6465
  receiveVideo: options.receiveVideo,
6466
+ fallbackToReceiveOnly: options.fallbackToReceiveOnly,
6408
6467
  webRTCApiProvider: this.webRTCApiProvider,
6409
6468
  preferredVideoCodecs: options.preferredVideoCodecs,
6410
6469
  preferredAudioCodecs: options.preferredAudioCodecs,
@@ -6423,17 +6482,17 @@ var WebRTCVertoManager = class extends VertoManager {
6423
6482
  if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
6424
6483
  }
6425
6484
  async handleInboundAnswer(rtcPeerConnController) {
6426
- logger$15.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
6485
+ logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
6427
6486
  const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
6428
6487
  if (vertoByeOrAccepted === null) {
6429
- logger$15.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
6488
+ logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
6430
6489
  return;
6431
6490
  }
6432
- if (isVertoByeMessage(vertoByeOrAccepted)) {
6433
- logger$15.info("[WebRTCManager] Inbound call ended by remote before answer.");
6491
+ if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
6492
+ logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
6434
6493
  this.callSession?.destroy();
6435
6494
  } else if (!vertoByeOrAccepted) {
6436
- logger$15.info("[WebRTCManager] Inbound call rejected by user.");
6495
+ logger$16.info("[WebRTCManager] Inbound call rejected by user.");
6437
6496
  try {
6438
6497
  await this.bye("USER_BUSY");
6439
6498
  } finally {
@@ -6441,19 +6500,19 @@ var WebRTCVertoManager = class extends VertoManager {
6441
6500
  this.callSession?.destroy();
6442
6501
  }
6443
6502
  } else {
6444
- logger$15.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
6503
+ logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
6445
6504
  const answerOptions = this.webRtcCallSession.answerMediaOptions;
6446
6505
  try {
6447
6506
  await rtcPeerConnController.acceptInbound(answerOptions);
6448
6507
  } catch (error) {
6449
- logger$15.error("[WebRTCManager] Error creating inbound answer:", error);
6508
+ logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
6450
6509
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6451
6510
  }
6452
6511
  }
6453
6512
  }
6454
6513
  setupVertoAttachHandler() {
6455
6514
  this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
6456
- logger$15.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
6515
+ logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
6457
6516
  const { callID } = vertoAttach;
6458
6517
  await this.attachManager.attach({
6459
6518
  nodeId: this.nodeId ?? void 0,
@@ -6525,17 +6584,17 @@ var WebRTCVertoManager = class extends VertoManager {
6525
6584
  };
6526
6585
  }
6527
6586
  async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
6528
- logger$15.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
6587
+ logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
6529
6588
  const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
6530
6589
  if (vertoByeOrAccepted === null) {
6531
- logger$15.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
6590
+ logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
6532
6591
  return;
6533
6592
  }
6534
- if (isVertoByeMessage(vertoByeOrAccepted)) {
6535
- logger$15.info("[WebRTCManager] Call ended before answer was sent.");
6593
+ if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
6594
+ logger$16.info("[WebRTCManager] Call ended before answer was sent.");
6536
6595
  this.callSession?.destroy();
6537
6596
  } else if (!vertoByeOrAccepted) {
6538
- logger$15.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
6597
+ logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
6539
6598
  try {
6540
6599
  await this.bye("USER_BUSY");
6541
6600
  } finally {
@@ -6543,14 +6602,14 @@ var WebRTCVertoManager = class extends VertoManager {
6543
6602
  this.callSession?.destroy();
6544
6603
  }
6545
6604
  } else {
6546
- logger$15.debug("[WebRTCManager] Call accepted, sending answer");
6605
+ logger$16.debug("[WebRTCManager] Call accepted, sending answer");
6547
6606
  try {
6548
- this._signalingStatus$.next("connecting");
6607
+ this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
6549
6608
  await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
6550
6609
  await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
6551
6610
  await this.attachManager.attach(this.buildAttachableCall());
6552
6611
  } catch (error) {
6553
- logger$15.error("[WebRTCManager] Error sending Verto answer:", error);
6612
+ logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
6554
6613
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6555
6614
  await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
6556
6615
  }
@@ -6576,7 +6635,7 @@ var WebRTCVertoManager = class extends VertoManager {
6576
6635
  screenShare: rtcPeerConnectionController.isScreenShare,
6577
6636
  additionalDevice: rtcPeerConnectionController.isAdditionalDevice,
6578
6637
  pingSupported: true,
6579
- version: INVITE_VERSION
6638
+ version: require_operators.INVITE_VERSION
6580
6639
  };
6581
6640
  }
6582
6641
  muteMainAudioInputDevice() {
@@ -6631,9 +6690,11 @@ var WebRTCVertoManager = class extends VertoManager {
6631
6690
  await this.initAdditionalPeerConnection("screenshare", options);
6632
6691
  }
6633
6692
  async initAdditionalPeerConnection(propose, options) {
6693
+ const isScreenShare = propose === "screenshare";
6694
+ let firstPeerConnectionError;
6634
6695
  let rtcPeerConnController = null;
6635
6696
  try {
6636
- this._screenShareStatus$.next("starting");
6697
+ if (isScreenShare) this._screenShareStatus$.next("starting");
6637
6698
  rtcPeerConnController = new RTCPeerConnectionController({
6638
6699
  ...options,
6639
6700
  ...this.RTCPeerConnectionConfig,
@@ -6641,21 +6702,27 @@ var WebRTCVertoManager = class extends VertoManager {
6641
6702
  webRTCApiProvider: this.webRTCApiProvider
6642
6703
  }, void 0, this.deviceController);
6643
6704
  this.setupLocalDescriptionHandler(rtcPeerConnController);
6644
- if (propose === "screenshare") this._screenShareId = rtcPeerConnController.id;
6705
+ if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
6645
6706
  this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
6646
6707
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
6647
6708
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
6648
- this.onError?.(error);
6709
+ firstPeerConnectionError ??= error;
6710
+ this.onError?.(error, { fatal: false });
6649
6711
  });
6650
6712
  await (0, rxjs.firstValueFrom)(rtcPeerConnController.connectionState$.pipe((0, rxjs.filter)((state) => state === "connected"), (0, rxjs.take)(1), (0, rxjs.timeout)(this._screenShareTimeoutMs), (0, rxjs.takeUntil)(this.destroyed$)));
6651
- this._screenShareStatus$.next("started");
6652
- logger$15.info("[WebRTCManager] Screen share started successfully.");
6713
+ if (isScreenShare) this._screenShareStatus$.next("started");
6714
+ logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
6653
6715
  return rtcPeerConnController.id;
6654
6716
  } catch (error) {
6655
- logger$15.warn("[WebRTCManager] Error initializing additional peer connection:", error);
6656
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6717
+ logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
6657
6718
  if (rtcPeerConnController) rtcPeerConnController.destroy();
6658
- this._screenShareStatus$.next("none");
6719
+ if (isScreenShare) this._screenShareStatus$.next("none");
6720
+ if (firstPeerConnectionError) throw firstPeerConnectionError instanceof require_operators.MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
6721
+ if (error instanceof rxjs.EmptyError) {
6722
+ logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
6723
+ return;
6724
+ }
6725
+ throw error instanceof Error ? error : new Error(String(error), { cause: error });
6659
6726
  }
6660
6727
  }
6661
6728
  async removeInputDevices(id) {
@@ -6671,9 +6738,9 @@ var WebRTCVertoManager = class extends VertoManager {
6671
6738
  if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
6672
6739
  }
6673
6740
  async removeScreenMedia() {
6674
- if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$15.warn("[WebRTCManager] No active screen share to stop.");
6741
+ if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
6675
6742
  if (!this._screenShareId) {
6676
- logger$15.debug("[WebRTCManager] No screen share peer connection found.");
6743
+ logger$16.debug("[WebRTCManager] No screen share peer connection found.");
6677
6744
  return;
6678
6745
  }
6679
6746
  this._screenShareStatus$.next("stopping");
@@ -6702,7 +6769,7 @@ var WebRTCVertoManager = class extends VertoManager {
6702
6769
  dialogParams: this.dialogParams(rtcPeerConnController)
6703
6770
  }));
6704
6771
  } catch (error) {
6705
- logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
6772
+ logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
6706
6773
  throw error;
6707
6774
  }
6708
6775
  }
@@ -6720,7 +6787,7 @@ var WebRTCVertoManager = class extends VertoManager {
6720
6787
  try {
6721
6788
  await this.executeVerto(vertoInfoMessage);
6722
6789
  } catch (error) {
6723
- logger$15.warn("[WebRTCManager] Error sending DTMF digits:", error);
6790
+ logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
6724
6791
  throw error;
6725
6792
  }
6726
6793
  }
@@ -6731,10 +6798,10 @@ var WebRTCVertoManager = class extends VertoManager {
6731
6798
  action: "transfer"
6732
6799
  });
6733
6800
  try {
6734
- logger$15.debug("[WebRTCManager] Transferring call with options:", options);
6801
+ logger$16.debug("[WebRTCManager] Transferring call with options:", options);
6735
6802
  await this.executeVerto(message);
6736
6803
  } catch (error) {
6737
- logger$15.error("[WebRTCManager] Error transferring call:", error);
6804
+ logger$16.error("[WebRTCManager] Error transferring call:", error);
6738
6805
  throw error;
6739
6806
  }
6740
6807
  }
@@ -6750,7 +6817,7 @@ var WebRTCVertoManager = class extends VertoManager {
6750
6817
 
6751
6818
  //#endregion
6752
6819
  //#region src/controllers/RemoteAudioMeter.ts
6753
- const logger$14 = require_operators.getLogger();
6820
+ const logger$15 = require_operators.getLogger();
6754
6821
  /**
6755
6822
  * Read-only audio level meter for a remote MediaStream. Attaches an
6756
6823
  * AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
@@ -6770,7 +6837,7 @@ var RemoteAudioMeter = class extends Destroyable {
6770
6837
  this._analyser.fftSize = 2048;
6771
6838
  this._analyser.smoothingTimeConstant = .3;
6772
6839
  this._analyserBuffer = new Uint8Array(new ArrayBuffer(this._analyser.fftSize));
6773
- this._pollIntervalMs = options.pollIntervalMs ?? AUDIO_LEVEL_POLL_INTERVAL_MS;
6840
+ this._pollIntervalMs = options.pollIntervalMs ?? require_operators.AUDIO_LEVEL_POLL_INTERVAL_MS;
6774
6841
  }
6775
6842
  /** RMS level of the remote audio, 0..1. 0 when no stream is attached. */
6776
6843
  get level$() {
@@ -6785,7 +6852,7 @@ var RemoteAudioMeter = class extends Destroyable {
6785
6852
  try {
6786
6853
  this._source.disconnect();
6787
6854
  } catch (error) {
6788
- logger$14.debug("[RemoteAudioMeter] source disconnect warning:", error);
6855
+ logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
6789
6856
  }
6790
6857
  this._source = null;
6791
6858
  this._stream = null;
@@ -6802,7 +6869,7 @@ var RemoteAudioMeter = class extends Destroyable {
6802
6869
  this._source = null;
6803
6870
  }
6804
6871
  this._audioContext.close().catch((error) => {
6805
- logger$14.debug("[RemoteAudioMeter] audio context close warning:", error);
6872
+ logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
6806
6873
  });
6807
6874
  super.destroy();
6808
6875
  }
@@ -6826,7 +6893,7 @@ var RemoteAudioMeter = class extends Destroyable {
6826
6893
  *
6827
6894
  * Extends Destroyable for automatic RxJS cleanup.
6828
6895
  */
6829
- const logger$13 = require_operators.getLogger();
6896
+ const logger$14 = require_operators.getLogger();
6830
6897
  const DEFAULT_POLLING_INTERVAL_MS = 1e3;
6831
6898
  const DEFAULT_BASELINE_SAMPLES = 10;
6832
6899
  const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
@@ -6916,9 +6983,9 @@ var RTCStatsMonitor = class extends Destroyable {
6916
6983
  const now = Date.now();
6917
6984
  this.lastAudioPacketChangeTime = now;
6918
6985
  this.lastVideoPacketChangeTime = now;
6919
- logger$13.debug("[RTCStatsMonitor] Starting stats monitoring");
6986
+ logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
6920
6987
  this.subscribeTo((0, rxjs.interval)(this.pollingIntervalMs).pipe((0, rxjs.filter)(() => this.running), (0, rxjs.switchMap)(() => (0, rxjs.from)(this.peerConnection.getStats()).pipe((0, rxjs.catchError)((err) => {
6921
- logger$13.warn("[RTCStatsMonitor] Failed to get stats:", err);
6988
+ logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
6922
6989
  return rxjs.EMPTY;
6923
6990
  }))), (0, rxjs.filter)(() => this.running), (0, rxjs.map)((report) => this.extractSample(report))), (sample) => this._sample$.next(sample));
6924
6991
  this.subscribeTo(this._sample$.pipe((0, rxjs.take)(this.baselineSampleCount), (0, rxjs.toArray)(), (0, rxjs.map)((samples) => ({
@@ -6926,7 +6993,7 @@ var RTCStatsMonitor = class extends Destroyable {
6926
6993
  jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
6927
6994
  ready: true
6928
6995
  }))), (baseline) => {
6929
- logger$13.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
6996
+ logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
6930
6997
  this._baseline$.next(baseline);
6931
6998
  });
6932
6999
  this.subscribeTo(this._sample$.pipe((0, rxjs.scan)((acc, sample) => ({
@@ -6963,10 +7030,10 @@ var RTCStatsMonitor = class extends Destroyable {
6963
7030
  stop() {
6964
7031
  if (!this.running) return;
6965
7032
  this.running = false;
6966
- logger$13.debug("[RTCStatsMonitor] Stopping stats monitoring");
7033
+ logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
6967
7034
  }
6968
7035
  destroy() {
6969
- logger$13.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
7036
+ logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
6970
7037
  this.stop();
6971
7038
  super.destroy();
6972
7039
  }
@@ -7094,7 +7161,7 @@ var RTCStatsMonitor = class extends Destroyable {
7094
7161
 
7095
7162
  //#endregion
7096
7163
  //#region src/managers/CallRecoveryManager.ts
7097
- const logger$12 = require_operators.getLogger();
7164
+ const logger$13 = require_operators.getLogger();
7098
7165
  const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
7099
7166
  const DEFAULT_COOLDOWN_MS = 1e4;
7100
7167
  const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
@@ -7191,10 +7258,10 @@ var CallRecoveryManager = class extends Destroyable {
7191
7258
  */
7192
7259
  async requestIceRestart() {
7193
7260
  if (this._recoveryState$.value === "recovering") {
7194
- logger$12.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
7261
+ logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
7195
7262
  return;
7196
7263
  }
7197
- logger$12.info("CallRecoveryManager: manual ICE restart requested");
7264
+ logger$13.info("CallRecoveryManager: manual ICE restart requested");
7198
7265
  this.transitionTo("recovering");
7199
7266
  await this.executeIceRestart(false);
7200
7267
  this.startCooldown();
@@ -7210,7 +7277,7 @@ var CallRecoveryManager = class extends Destroyable {
7210
7277
  * WebSocket reconnect or call state recovers to 'connected'.
7211
7278
  */
7212
7279
  reset() {
7213
- logger$12.info("CallRecoveryManager: resetting counters");
7280
+ logger$13.info("CallRecoveryManager: resetting counters");
7214
7281
  this._attemptCount = 0;
7215
7282
  this._keyframeBurstCount = 0;
7216
7283
  this._keyframeBurstStart = 0;
@@ -7225,7 +7292,7 @@ var CallRecoveryManager = class extends Destroyable {
7225
7292
  */
7226
7293
  notifyModifyFailed() {
7227
7294
  if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
7228
- logger$12.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
7295
+ logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
7229
7296
  this._cooldownUntil = 0;
7230
7297
  this.transitionTo("idle");
7231
7298
  this.pushTrigger({
@@ -7249,7 +7316,7 @@ var CallRecoveryManager = class extends Destroyable {
7249
7316
  reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
7250
7317
  timestamp: Date.now()
7251
7318
  });
7252
- logger$12.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
7319
+ logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
7253
7320
  } else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
7254
7321
  this._bandwidthConstrained$.next(false);
7255
7322
  this._callbacks.enableVideo();
@@ -7258,7 +7325,7 @@ var CallRecoveryManager = class extends Destroyable {
7258
7325
  reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
7259
7326
  timestamp: Date.now()
7260
7327
  });
7261
- logger$12.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
7328
+ logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
7262
7329
  }
7263
7330
  }
7264
7331
  /**
@@ -7277,14 +7344,14 @@ var CallRecoveryManager = class extends Destroyable {
7277
7344
  handleWebSocketReconnect() {
7278
7345
  const pcState = this._callbacks.getPeerConnectionState();
7279
7346
  if (pcState === "connected" || pcState === "completed") {
7280
- logger$12.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
7347
+ logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
7281
7348
  this.emitEvent({
7282
7349
  action: "signal_reconnect",
7283
7350
  reason: "WebSocket reconnected, peer connection still connected",
7284
7351
  timestamp: Date.now()
7285
7352
  });
7286
7353
  } else {
7287
- logger$12.info("CallRecoveryManager: full reconnect — peer connection also down");
7354
+ logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
7288
7355
  this.emitEvent({
7289
7356
  action: "full_reconnect",
7290
7357
  reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
@@ -7308,7 +7375,7 @@ var CallRecoveryManager = class extends Destroyable {
7308
7375
  }), (0, rxjs.debounceTime)(this._config.debounceTimeMs), (0, rxjs.withLatestFrom)(this._inputs.signalingReady$), (0, rxjs.filter)(([, signalingReady]) => this.passGateChecks(signalingReady)), (0, rxjs.map)(([trigger]) => trigger), (0, rxjs.exhaustMap)((trigger) => this.executeTieredRecovery(trigger)), (0, rxjs.takeUntil)((0, rxjs.merge)(this._destroyed$, this._pipelineStop$))), {
7309
7376
  next: () => {},
7310
7377
  error: (err) => {
7311
- logger$12.error("CallRecoveryManager: pipeline error", err);
7378
+ logger$13.error("CallRecoveryManager: pipeline error", err);
7312
7379
  this.transitionTo("idle");
7313
7380
  }
7314
7381
  });
@@ -7335,27 +7402,27 @@ var CallRecoveryManager = class extends Destroyable {
7335
7402
  reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
7336
7403
  timestamp: Date.now()
7337
7404
  });
7338
- logger$12.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
7405
+ logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
7339
7406
  });
7340
7407
  }
7341
7408
  passGateChecks(signalingReady) {
7342
7409
  if (this._callbacks.isNegotiating()) {
7343
- logger$12.debug("CallRecoveryManager: gate blocked — negotiation in progress");
7410
+ logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
7344
7411
  this.transitionTo("idle");
7345
7412
  return false;
7346
7413
  }
7347
7414
  if (!signalingReady) {
7348
- logger$12.debug("CallRecoveryManager: gate blocked — signaling not ready");
7415
+ logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
7349
7416
  this.transitionTo("idle");
7350
7417
  return false;
7351
7418
  }
7352
7419
  if (!this._callbacks.isCallConnected()) {
7353
- logger$12.debug("CallRecoveryManager: gate blocked — call not connected");
7420
+ logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
7354
7421
  this.transitionTo("idle");
7355
7422
  return false;
7356
7423
  }
7357
7424
  if (this.isCooldownActive()) {
7358
- logger$12.debug("CallRecoveryManager: gate blocked — cooldown active");
7425
+ logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
7359
7426
  this.transitionTo("cooldown");
7360
7427
  return false;
7361
7428
  }
@@ -7366,9 +7433,9 @@ var CallRecoveryManager = class extends Destroyable {
7366
7433
  }
7367
7434
  executeTieredRecovery(trigger) {
7368
7435
  this.transitionTo("recovering");
7369
- logger$12.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
7436
+ logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
7370
7437
  return (0, rxjs.from)(this.runTiers(trigger)).pipe((0, rxjs.tap)(() => this.startCooldown()), (0, rxjs.catchError)((err) => {
7371
- logger$12.error("CallRecoveryManager: tiered recovery failed", err);
7438
+ logger$13.error("CallRecoveryManager: tiered recovery failed", err);
7372
7439
  this.startCooldown();
7373
7440
  return rxjs.EMPTY;
7374
7441
  }));
@@ -7376,7 +7443,7 @@ var CallRecoveryManager = class extends Destroyable {
7376
7443
  async runTiers(trigger) {
7377
7444
  this.executeKeyframe(trigger.detail);
7378
7445
  if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
7379
- logger$12.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
7446
+ logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
7380
7447
  return;
7381
7448
  }
7382
7449
  if (this._attemptCount < this._config.maxAttempts) {
@@ -7393,13 +7460,13 @@ var CallRecoveryManager = class extends Destroyable {
7393
7460
  maxAttempts: this._config.maxAttempts,
7394
7461
  timestamp: Date.now()
7395
7462
  });
7396
- logger$12.warn("CallRecoveryManager: max recovery attempts reached");
7463
+ logger$13.warn("CallRecoveryManager: max recovery attempts reached");
7397
7464
  }
7398
7465
  }
7399
7466
  executeKeyframe(reason) {
7400
7467
  const now = Date.now();
7401
7468
  if (now < this._keyframeCooldownUntil) {
7402
- logger$12.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
7469
+ logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
7403
7470
  return;
7404
7471
  }
7405
7472
  if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
@@ -7408,7 +7475,7 @@ var CallRecoveryManager = class extends Destroyable {
7408
7475
  }
7409
7476
  if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
7410
7477
  this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
7411
- logger$12.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
7478
+ logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
7412
7479
  return;
7413
7480
  }
7414
7481
  this._keyframeBurstCount += 1;
@@ -7418,12 +7485,12 @@ var CallRecoveryManager = class extends Destroyable {
7418
7485
  reason,
7419
7486
  timestamp: now
7420
7487
  });
7421
- logger$12.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
7488
+ logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
7422
7489
  }
7423
7490
  async executeIceRestart(relayOnly) {
7424
7491
  this._attemptCount += 1;
7425
7492
  const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
7426
- logger$12.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
7493
+ logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
7427
7494
  this.emitEvent({
7428
7495
  action: "reinvite_started",
7429
7496
  reason: `${tier} ICE restart`,
@@ -7440,7 +7507,7 @@ var CallRecoveryManager = class extends Destroyable {
7440
7507
  maxAttempts: this._config.maxAttempts,
7441
7508
  timestamp: Date.now()
7442
7509
  });
7443
- logger$12.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
7510
+ logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
7444
7511
  this._attemptCount = 0;
7445
7512
  return true;
7446
7513
  }
@@ -7451,7 +7518,7 @@ var CallRecoveryManager = class extends Destroyable {
7451
7518
  maxAttempts: this._config.maxAttempts,
7452
7519
  timestamp: Date.now()
7453
7520
  });
7454
- logger$12.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
7521
+ logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
7455
7522
  return false;
7456
7523
  } catch {
7457
7524
  this.emitEvent({
@@ -7461,7 +7528,7 @@ var CallRecoveryManager = class extends Destroyable {
7461
7528
  maxAttempts: this._config.maxAttempts,
7462
7529
  timestamp: Date.now()
7463
7530
  });
7464
- logger$12.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
7531
+ logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
7465
7532
  return false;
7466
7533
  }
7467
7534
  }
@@ -7482,7 +7549,7 @@ var CallRecoveryManager = class extends Destroyable {
7482
7549
  transitionTo(state) {
7483
7550
  const prev = this._recoveryState$.value;
7484
7551
  if (prev !== state) {
7485
- logger$12.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
7552
+ logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
7486
7553
  this._recoveryState$.next(state);
7487
7554
  }
7488
7555
  }
@@ -7584,7 +7651,7 @@ function mosToQualityLevel(mos) {
7584
7651
 
7585
7652
  //#endregion
7586
7653
  //#region src/core/entities/Call.ts
7587
- const logger$11 = require_operators.getLogger();
7654
+ const logger$12 = require_operators.getLogger();
7588
7655
  /**
7589
7656
  * Ratio between the critical and warning RTT spike multipliers.
7590
7657
  * Warning threshold = baseline * warningMultiplier (default 3x)
@@ -7602,7 +7669,7 @@ const fromDestinationParams = (destination) => {
7602
7669
  });
7603
7670
  return params;
7604
7671
  } catch (error) {
7605
- logger$11.warn(`Failed to parse destination URI: ${destination}`, error);
7672
+ logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
7606
7673
  return {};
7607
7674
  }
7608
7675
  };
@@ -7736,7 +7803,7 @@ var WebRTCCall = class extends Destroyable {
7736
7803
  /** Toggles the call lock state, preventing or allowing new participants from joining. */
7737
7804
  async toggleLock() {
7738
7805
  const method = this.locked ? "call.unlock" : "call.lock";
7739
- await this.executeMethod(this.selfId ?? "", method, {});
7806
+ await this.executeMethod(this.callSelf, method, {});
7740
7807
  }
7741
7808
  /**
7742
7809
  * Toggles the hold state of the call (pauses/resumes local media transmission).
@@ -7785,14 +7852,24 @@ var WebRTCCall = class extends Destroyable {
7785
7852
  *
7786
7853
  * Constructs call context (node_id, call_id, member_id) and sends the RPC request.
7787
7854
  *
7788
- * @param target - Target member ID string, or a {@link MemberTarget} object.
7855
+ * @param target - Target {@link MemberTarget} triple, or the local member's
7856
+ * ID string for self-operations (any other string is rejected — a bare
7857
+ * member id cannot carry the remote member's own call context).
7789
7858
  * @param method - Verto method name (e.g. `'call.mute'`, `'call.member.remove'`).
7790
7859
  * @param args - Parameters for the RPC method.
7791
7860
  * @returns The RPC response.
7861
+ * @throws {CallNotReadyError} If the call has no self member context yet.
7862
+ * @throws {InvalidParams} If a string target is not the local member's ID.
7792
7863
  * @throws {JSONRPCError} If the RPC call returns an error.
7793
7864
  */
7794
7865
  async executeMethod(target, method, args) {
7795
- const params = this.buildMethodParams(target, args);
7866
+ const self = this.callSelf;
7867
+ if (typeof target === "string" && target !== self.member_id) throw new require_operators.InvalidParams(`Target member ID ${target} does not match call's self member ID ${self.member_id}`);
7868
+ const params = {
7869
+ ...args,
7870
+ self,
7871
+ target: typeof target === "string" ? self : target
7872
+ };
7796
7873
  const request = buildRPCRequest({
7797
7874
  method,
7798
7875
  params
@@ -7802,29 +7879,27 @@ var WebRTCCall = class extends Destroyable {
7802
7879
  if (isJSONRPCErrorResponse(response)) throw new require_operators.JSONRPCError(parseInt(response.result?.code ?? "0"), `Error response from method ${method}: ${response.result?.code} ${response.result?.message}`, void 0, void 0, request.id);
7803
7880
  return response;
7804
7881
  } catch (error) {
7805
- logger$11.error(`[Call] Error executing method ${method} with params`, params, error);
7882
+ logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
7806
7883
  throw error;
7807
7884
  }
7808
7885
  }
7809
- buildMethodParams(target, args) {
7810
- const self = {
7811
- node_id: this.nodeId ?? "",
7812
- call_id: this.id,
7813
- member_id: this.vertoManager.selfId ?? ""
7814
- };
7815
- if (typeof target === "object") return {
7816
- ...args,
7817
- self,
7818
- targets: [target]
7819
- };
7886
+ /**
7887
+ * The local leg's member triple — sent as `self` in every member RPC
7888
+ * envelope, and as the `target` of call-scoped self-operations (e.g. lock,
7889
+ * layout).
7890
+ *
7891
+ * @throws {CallNotReadyError} Before `call.joined` delivers the self member
7892
+ * context (`selfId`/`nodeId`) an RPC without it cannot be routed, so fail
7893
+ * fast instead of sending a doomed request.
7894
+ */
7895
+ get callSelf() {
7896
+ const node_id = this.nodeId;
7897
+ const member_id = this.vertoManager.selfId;
7898
+ if (!node_id || !member_id) throw new require_operators.CallNotReadyError(this.id);
7820
7899
  return {
7821
- ...args,
7822
- self,
7823
- target: {
7824
- node_id: this.nodeId ?? "",
7825
- call_id: this.id,
7826
- member_id: target
7827
- }
7900
+ node_id,
7901
+ call_id: this.id,
7902
+ member_id
7828
7903
  };
7829
7904
  }
7830
7905
  /** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
@@ -8005,9 +8080,9 @@ var WebRTCCall = class extends Destroyable {
8005
8080
  */
8006
8081
  initResilienceSubsystems() {
8007
8082
  const pc = this.rtcPeerConnection;
8008
- logger$11.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
8083
+ logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
8009
8084
  if (!pc) {
8010
- logger$11.warn("[Call] No peer connection available, skipping resilience init");
8085
+ logger$12.warn("[Call] No peer connection available, skipping resilience init");
8011
8086
  return;
8012
8087
  }
8013
8088
  try {
@@ -8042,14 +8117,14 @@ var WebRTCCall = class extends Destroyable {
8042
8117
  disableVideo: () => {
8043
8118
  try {
8044
8119
  this.vertoManager.muteMainVideoInputDevice();
8045
- logger$11.debug("[Call] Recovery manager disabled video");
8120
+ logger$12.debug("[Call] Recovery manager disabled video");
8046
8121
  } catch {
8047
- logger$11.debug("[Call] Recovery manager failed to disable video");
8122
+ logger$12.debug("[Call] Recovery manager failed to disable video");
8048
8123
  }
8049
8124
  },
8050
8125
  enableVideo: () => {
8051
8126
  this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
8052
- logger$11.debug("[Call] Recovery manager failed to enable video");
8127
+ logger$12.debug("[Call] Recovery manager failed to enable video");
8053
8128
  });
8054
8129
  },
8055
8130
  isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
@@ -8099,7 +8174,7 @@ var WebRTCCall = class extends Destroyable {
8099
8174
  this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
8100
8175
  this._recoveryEvent$.next(event);
8101
8176
  if (event.action === "max_attempts_reached") {
8102
- logger$11.warn("[Call] All recovery attempts exhausted, terminating call");
8177
+ logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
8103
8178
  this.emitError({
8104
8179
  kind: "network",
8105
8180
  fatal: true,
@@ -8119,13 +8194,13 @@ var WebRTCCall = class extends Destroyable {
8119
8194
  else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
8120
8195
  });
8121
8196
  this.subscribeTo(this.clientSession.authenticated$.pipe((0, rxjs.skip)(1), (0, rxjs.filter)(Boolean)), () => {
8122
- logger$11.debug("[Call] WebSocket reconnected — notifying recovery manager");
8197
+ logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
8123
8198
  this._recoveryManager?.handleWebSocketReconnect();
8124
8199
  });
8125
8200
  this._statsMonitor.start();
8126
- logger$11.debug("[Call] Resilience subsystems initialized for call", this.id);
8201
+ logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
8127
8202
  } catch (error) {
8128
- logger$11.warn("[Call] Failed to initialize resilience subsystems:", error);
8203
+ logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
8129
8204
  }
8130
8205
  }
8131
8206
  /**
@@ -8140,13 +8215,13 @@ var WebRTCCall = class extends Destroyable {
8140
8215
  async waitForPeerConnectionConnected() {
8141
8216
  const pc = this.rtcPeerConnection;
8142
8217
  if (!pc) return false;
8143
- const deadline = Date.now() + PEER_CONNECTION_RECOVERY_WAIT_MS;
8218
+ const deadline = Date.now() + require_operators.PEER_CONNECTION_RECOVERY_WAIT_MS;
8144
8219
  for (;;) {
8145
8220
  const state = pc.connectionState;
8146
8221
  if (state === "connected") return true;
8147
8222
  if (state === "failed" || state === "closed") return false;
8148
8223
  if (Date.now() >= deadline) return false;
8149
- await new Promise((resolve) => setTimeout(resolve, PEER_CONNECTION_RECOVERY_POLL_MS));
8224
+ await new Promise((resolve) => setTimeout(resolve, require_operators.PEER_CONNECTION_RECOVERY_POLL_MS));
8150
8225
  }
8151
8226
  }
8152
8227
  /**
@@ -8208,19 +8283,19 @@ var WebRTCCall = class extends Destroyable {
8208
8283
  }
8209
8284
  isCallSessionEvent(event) {
8210
8285
  try {
8211
- logger$11.debug("[Call] Checking if event is for this call session:", event);
8286
+ logger$12.debug("[Call] Checking if event is for this call session:", event);
8212
8287
  const callId = require_operators.getValueFrom(event, "params.params.callID") ?? require_operators.getValueFrom(event, "params.call_id");
8213
8288
  const roomSessionId = require_operators.getValueFrom(event, "params.room_session_id");
8214
- logger$11.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
8289
+ logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
8215
8290
  return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
8216
8291
  } catch (error) {
8217
- logger$11.error("[Call] Error checking if event is for this call session:", error);
8292
+ logger$12.error("[Call] Error checking if event is for this call session:", error);
8218
8293
  return false;
8219
8294
  }
8220
8295
  }
8221
8296
  get callSessionEvents$() {
8222
8297
  return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, rxjs.filter)((event) => this.isCallSessionEvent(event)), (0, rxjs.tap)((event) => {
8223
- logger$11.debug("[Call] Received call session event:", event);
8298
+ logger$12.debug("[Call] Received call session event:", event);
8224
8299
  }), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
8225
8300
  }
8226
8301
  /** Observable of call-updated events. */
@@ -8290,16 +8365,16 @@ var WebRTCCall = class extends Destroyable {
8290
8365
  this._customSubscriptions.set(eventType, filtered$);
8291
8366
  }, (error) => {
8292
8367
  this._customSubscriptions.delete(eventType);
8293
- logger$11.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
8368
+ logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
8294
8369
  });
8295
8370
  this._customSubscriptions.set(eventType, filtered$);
8296
8371
  return filtered$;
8297
8372
  }
8298
8373
  get webrtcMessages$() {
8299
- return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isWebrtcMessageMetadata, "params"), (0, rxjs.tap)((event) => logger$11.debug("[Call] Event is a WebRTC message event:", event)), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
8374
+ return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isWebrtcMessageMetadata, "params"), (0, rxjs.tap)((event) => logger$12.debug("[Call] Event is a WebRTC message event:", event)), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
8300
8375
  }
8301
8376
  get callEvent$() {
8302
- return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isSignalwireCallMetadata, "params"), (0, rxjs.tap)((event) => logger$11.debug("[Call] Event is a call event:", event)), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
8377
+ return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isSignalwireCallMetadata, "params"), (0, rxjs.tap)((event) => logger$12.debug("[Call] Event is a call event:", event)), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
8303
8378
  }
8304
8379
  get layoutEvent$() {
8305
8380
  return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(require_operators.filterAs(isLayoutChangedMetadata, "params")));
@@ -8377,11 +8452,27 @@ var WebRTCCall = class extends Destroyable {
8377
8452
  return this.deferEmission(this._answered$.asObservable());
8378
8453
  }
8379
8454
  /**
8380
- * Sets the call layout and participant positions.
8455
+ * Sets the call layout and, optionally, individual participant positions.
8456
+ *
8457
+ * The gateway `call.layout.set` DTO has **no** `positions` member, so when
8458
+ * `positions` is provided this method issues a `call.member.position.set`
8459
+ * request per member (via {@link Participant.setPosition}, which keys each
8460
+ * position by that member's own call context) alongside `call.layout.set`
8461
+ * (issue #19400, Flag #6).
8462
+ *
8463
+ * **These operations are NOT atomic.** The layout is applied first, then each
8464
+ * member position sequentially, so members may briefly flash into their
8465
+ * default slots before being moved to the requested positions. Targeted
8466
+ * members are validated upfront, though: when any of them has no
8467
+ * {@link Participant.target | member call context} yet, the whole call
8468
+ * rejects before any request is sent and the layout is left unchanged.
8381
8469
  *
8382
8470
  * @param layout - Layout name (must be one of {@link layouts}).
8383
- * @param positions - Map of member IDs to {@link VideoPosition} values.
8471
+ * @param positions - Optional map of member IDs to {@link VideoPosition} values.
8472
+ * When omitted or empty, only the layout is changed.
8384
8473
  * @throws {InvalidParams} If the layout is not in the available {@link layouts}.
8474
+ * @throws {ParticipantNotReadyError} If a targeted member's call context has
8475
+ * not been received yet — thrown before any request is sent.
8385
8476
  *
8386
8477
  * @example
8387
8478
  * ```ts
@@ -8392,11 +8483,19 @@ var WebRTCCall = class extends Destroyable {
8392
8483
  */
8393
8484
  async setLayout(layout, positions) {
8394
8485
  if (!this.layouts.includes(layout)) throw new require_operators.InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
8486
+ const targets = [];
8487
+ for (const [memberId, position] of Object.entries(positions ?? {})) {
8488
+ const participant = this.participants.find((p) => p.id === memberId);
8489
+ if (!participant) {
8490
+ logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
8491
+ continue;
8492
+ }
8493
+ participant.target;
8494
+ targets.push([participant, position]);
8495
+ }
8395
8496
  const selfId = await (0, rxjs.firstValueFrom)(this.selfId$.pipe((0, rxjs.filter)((id) => id !== null)));
8396
- await this.executeMethod(selfId, "call.layout.set", {
8397
- layout,
8398
- positions
8399
- });
8497
+ await this.executeMethod(selfId, "call.layout.set", { layout });
8498
+ for (const [participant, position] of targets) await participant.setPosition(position);
8400
8499
  }
8401
8500
  /**
8402
8501
  * Transfers the call to another destination.
@@ -8428,7 +8527,7 @@ var WebRTCCall = class extends Destroyable {
8428
8527
  setLocalMicrophoneGain(value) {
8429
8528
  const pipeline = this.vertoManager.ensureLocalAudioPipeline();
8430
8529
  if (!pipeline) {
8431
- logger$11.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
8530
+ logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
8432
8531
  return;
8433
8532
  }
8434
8533
  const percent = Math.max(0, Math.min(200, value));
@@ -8473,7 +8572,7 @@ var WebRTCCall = class extends Destroyable {
8473
8572
  enablePushToTalk() {
8474
8573
  const pipeline = this.vertoManager.ensureLocalAudioPipeline();
8475
8574
  if (!pipeline) {
8476
- logger$11.warn("[Call] enablePushToTalk: audio pipeline unavailable");
8575
+ logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
8477
8576
  return;
8478
8577
  }
8479
8578
  pipeline.setPTTActive(false);
@@ -8570,6 +8669,7 @@ function inferCallErrorKind(error) {
8570
8669
  if (error instanceof require_operators.RPCTimeoutError) return "timeout";
8571
8670
  if (error instanceof require_operators.JSONRPCError) return "signaling";
8572
8671
  if (error instanceof require_operators.MediaTrackError) return "media";
8672
+ if (error instanceof require_operators.MediaAccessError) return "media";
8573
8673
  if (error instanceof require_operators.WebSocketConnectionError || error instanceof require_operators.TransportConnectionError) return "network";
8574
8674
  return "internal";
8575
8675
  }
@@ -8578,14 +8678,15 @@ function inferCallErrorKind(error) {
8578
8678
  * destroy the call, because the session will reauthenticate and any pending
8579
8679
  * RPC can then be retried. */
8580
8680
  const RECOVERABLE_RPC_CODES = new Set([
8581
- RPC_ERROR_REQUESTER_VALIDATION_FAILED,
8582
- RPC_ERROR_AUTHENTICATION_FAILED,
8583
- RPC_ERROR_INVALID_PARAMS
8681
+ require_operators.RPC_ERROR_REQUESTER_VALIDATION_FAILED,
8682
+ require_operators.RPC_ERROR_AUTHENTICATION_FAILED,
8683
+ require_operators.RPC_ERROR_INVALID_PARAMS
8584
8684
  ]);
8585
8685
  /** Determines whether an error should be fatal (destroy the call). */
8586
8686
  function isFatalError(error) {
8587
8687
  if (error instanceof require_operators.VertoPongError) return false;
8588
8688
  if (error instanceof require_operators.MediaTrackError) return false;
8689
+ if (error instanceof require_operators.MediaAccessError) return error.fatal;
8589
8690
  if (error instanceof require_operators.RPCTimeoutError) return false;
8590
8691
  if (error instanceof require_operators.JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
8591
8692
  return true;
@@ -8611,10 +8712,10 @@ var CallFactory = class {
8611
8712
  return {
8612
8713
  vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
8613
8714
  nodeId: options.nodeId,
8614
- onError: (error) => {
8715
+ onError: (error, options$1) => {
8615
8716
  const callError = {
8616
8717
  kind: inferCallErrorKind(error),
8617
- fatal: isFatalError(error),
8718
+ fatal: options$1?.fatal ?? isFatalError(error),
8618
8719
  error,
8619
8720
  callId: callInstance.id
8620
8721
  };
@@ -8635,7 +8736,7 @@ var CallFactory = class {
8635
8736
 
8636
8737
  //#endregion
8637
8738
  //#region src/behaviors/Collection.ts
8638
- const logger$10 = require_operators.getLogger();
8739
+ const logger$11 = require_operators.getLogger();
8639
8740
  var Fetcher = class {
8640
8741
  constructor(endpoint, params, http) {
8641
8742
  this.endpoint = endpoint;
@@ -8659,7 +8760,7 @@ var Fetcher = class {
8659
8760
  this.hasMore = !!this.nextUrl;
8660
8761
  return result.data.filter(this.filter).map(this.mapper);
8661
8762
  }
8662
- logger$10.error("Failed to fetch entity");
8763
+ logger$11.error("Failed to fetch entity");
8663
8764
  return [];
8664
8765
  }
8665
8766
  async id(v) {
@@ -8735,7 +8836,7 @@ var EntityCollection = class extends Destroyable {
8735
8836
  this._hasMore$.next(this.fetchController.hasMore ?? false);
8736
8837
  this._loading$.next(false);
8737
8838
  } catch (error) {
8738
- logger$10.error(`Failed to fetch initial collection data`, error);
8839
+ logger$11.error(`Failed to fetch initial collection data`, error);
8739
8840
  this._hasMore$.next(this.fetchController.hasMore ?? false);
8740
8841
  this._loading$.next(false);
8741
8842
  this.onError?.(new require_operators.CollectionFetchError("fetchMore", error));
@@ -8749,7 +8850,7 @@ var EntityCollection = class extends Destroyable {
8749
8850
  if (data) this.upsertData(data);
8750
8851
  return data;
8751
8852
  } catch (error) {
8752
- logger$10.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
8853
+ logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
8753
8854
  this._loading$.next(false);
8754
8855
  this.onError?.(new require_operators.CollectionFetchError(`tryFetch(${String(key)})`, error));
8755
8856
  }
@@ -8996,13 +9097,13 @@ var Address = class extends Destroyable {
8996
9097
 
8997
9098
  //#endregion
8998
9099
  //#region src/core/utils.ts
8999
- const logger$9 = require_operators.getLogger();
9100
+ const logger$10 = require_operators.getLogger();
9000
9101
  const isRPCConnectResult = (e) => {
9001
- logger$9.debug("isRPCConnectResult check:", e);
9102
+ logger$10.debug("isRPCConnectResult check:", e);
9002
9103
  if (!e || typeof e !== "object") return false;
9003
9104
  const result = e;
9004
9105
  const is = typeof result.identity === "string" && typeof result.protocol === "string" && typeof result.authorization === "object" && typeof result.authorization.jti === "string" && typeof result.authorization.project_id === "string" && typeof result.authorization.fabric_subscriber === "object";
9005
- logger$9.debug("isRPCConnectResult check result:", is);
9106
+ logger$10.debug("isRPCConnectResult check result:", is);
9006
9107
  return is;
9007
9108
  };
9008
9109
  var PendingRPC = class PendingRPC {
@@ -9011,7 +9112,7 @@ var PendingRPC = class PendingRPC {
9011
9112
  }
9012
9113
  constructor(request, responses$, options) {
9013
9114
  this.id = (0, uuid.v4)();
9014
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
9115
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
9015
9116
  this.request = request;
9016
9117
  const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
9017
9118
  const signal = options?.signal;
@@ -9037,22 +9138,22 @@ var PendingRPC = class PendingRPC {
9037
9138
  isSettled = true;
9038
9139
  if (response.error) {
9039
9140
  const rpcError = new require_operators.JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
9040
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
9141
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
9041
9142
  reject(rpcError);
9042
9143
  } else {
9043
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
9144
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
9044
9145
  resolve(response);
9045
9146
  }
9046
9147
  subscription.unsubscribe();
9047
9148
  },
9048
9149
  error: (error) => {
9049
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
9150
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
9050
9151
  isSettled = true;
9051
9152
  reject(error);
9052
9153
  subscription.unsubscribe();
9053
9154
  },
9054
9155
  complete: () => {
9055
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
9156
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
9056
9157
  if (!isSettled) reject(new require_operators.RPCTimeoutError(request.id, timeoutMs));
9057
9158
  subscription.unsubscribe();
9058
9159
  }
@@ -9072,7 +9173,18 @@ var PendingRPC = class PendingRPC {
9072
9173
 
9073
9174
  //#endregion
9074
9175
  //#region src/managers/ClientSessionManager.ts
9075
- const logger$8 = require_operators.getLogger();
9176
+ const logger$9 = require_operators.getLogger();
9177
+ /**
9178
+ * Decide whether an error emitted on `call.errors$` during dial should
9179
+ * abort the dial. A non-fatal MediaAccessError means the call degraded to
9180
+ * receive-only and still connects — everything else rejects `dial()` with
9181
+ * the real cause.
9182
+ *
9183
+ * Pure function — exported for unit testing.
9184
+ */
9185
+ function shouldAbortDial(callError) {
9186
+ return callError.fatal || !(callError.error instanceof require_operators.MediaAccessError);
9187
+ }
9076
9188
  const getAddressSearchURI = (options) => {
9077
9189
  const to = options.to?.split("?")[0];
9078
9190
  const from$8 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
@@ -9170,7 +9282,7 @@ var ClientSessionManager = class extends Destroyable {
9170
9282
  try {
9171
9283
  return await this.transport.execute(request, options);
9172
9284
  } catch (error) {
9173
- logger$8.debug("[Session] Execute Error", error);
9285
+ logger$9.debug("[Session] Execute Error", error);
9174
9286
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
9175
9287
  throw error;
9176
9288
  }
@@ -9184,13 +9296,13 @@ var ClientSessionManager = class extends Destroyable {
9184
9296
  return true;
9185
9297
  }
9186
9298
  setupMessageHandlers() {
9187
- logger$8.debug("[Session] Setting up message handlers");
9299
+ logger$9.debug("[Session] Setting up message handlers");
9188
9300
  this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
9189
- logger$8.debug("[Session] Authorization state event received:", authStateEvent);
9301
+ logger$9.debug("[Session] Authorization state event received:", authStateEvent);
9190
9302
  try {
9191
9303
  await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
9192
9304
  } catch (error) {
9193
- logger$8.error("[Session] Failed to handle authorization state update:", error);
9305
+ logger$9.error("[Session] Failed to handle authorization state update:", error);
9194
9306
  this._errors$.next(new require_operators.AuthStateHandlerError(error));
9195
9307
  }
9196
9308
  });
@@ -9198,29 +9310,29 @@ var ClientSessionManager = class extends Destroyable {
9198
9310
  if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
9199
9311
  });
9200
9312
  this.subscribeTo(this.transport.connectionStatus$.pipe((0, rxjs.filter)((status) => status === "connected"), (0, rxjs.exhaustMap)(() => {
9201
- logger$8.debug("[Session] Connection established, initiating authentication");
9313
+ logger$9.debug("[Session] Connection established, initiating authentication");
9202
9314
  return (0, rxjs.from)(this.authenticate()).pipe((0, rxjs.catchError)((error) => {
9203
9315
  this.handleAuthenticationError(error).catch((err) => {
9204
- logger$8.error("[Session] Error handling authentication failure:", err);
9316
+ logger$9.error("[Session] Error handling authentication failure:", err);
9205
9317
  });
9206
9318
  return rxjs.EMPTY;
9207
9319
  }));
9208
9320
  })), void 0);
9209
9321
  this.subscribeTo(this.vertoInvite$, async (invite) => {
9210
- logger$8.debug("[Session] Verto invite received:", invite);
9322
+ logger$9.debug("[Session] Verto invite received:", invite);
9211
9323
  try {
9212
9324
  await this.createInboundCall(invite);
9213
9325
  } catch (error) {
9214
- logger$8.error("[Session] Error handling Verto invite:", error);
9326
+ logger$9.error("[Session] Error handling Verto invite:", error);
9215
9327
  this._errors$.next(new require_operators.VertoInviteHandlerError(error));
9216
9328
  }
9217
9329
  });
9218
9330
  this.subscribeTo(this.vertoAttach$, async (attach) => {
9219
- logger$8.debug("[Session] Verto attach received:", attach);
9331
+ logger$9.debug("[Session] Verto attach received:", attach);
9220
9332
  try {
9221
9333
  await this.handleVertoAttach(attach);
9222
9334
  } catch (error) {
9223
- logger$8.error("[Session] Error handling Verto attach:", error);
9335
+ logger$9.error("[Session] Error handling Verto attach:", error);
9224
9336
  this._errors$.next(new require_operators.VertoAttachHandlerError(error));
9225
9337
  }
9226
9338
  });
@@ -9230,36 +9342,36 @@ var ClientSessionManager = class extends Destroyable {
9230
9342
  const storedState = await this.storage.getItem(this.authorizationStateKey);
9231
9343
  this.authorizationState$.next(storedState ?? void 0);
9232
9344
  } catch (error) {
9233
- logger$8.error("Failed to retrieve authorization state from storage:", error);
9345
+ logger$9.error("Failed to retrieve authorization state from storage:", error);
9234
9346
  this.authorizationState$.next(void 0);
9235
9347
  }
9236
9348
  }
9237
9349
  async updateAuthorizationStateInStorage(authorizationState) {
9238
9350
  if (!authorizationState) {
9239
- logger$8.debug("[Session] Removing authorization state from storage");
9351
+ logger$9.debug("[Session] Removing authorization state from storage");
9240
9352
  try {
9241
9353
  await this.storage.removeItem(this.authorizationStateKey);
9242
9354
  this.authorizationState$.next(void 0);
9243
9355
  } catch (error) {
9244
- logger$8.error("Failed to remove authorization state from storage:", error);
9356
+ logger$9.error("Failed to remove authorization state from storage:", error);
9245
9357
  throw error;
9246
9358
  }
9247
9359
  return;
9248
9360
  }
9249
9361
  try {
9250
- logger$8.debug("[Session] Updating authorization state in storage");
9362
+ logger$9.debug("[Session] Updating authorization state in storage");
9251
9363
  await this.storage.setItem(this.authorizationStateKey, authorizationState);
9252
9364
  this.authorizationState$.next(authorizationState);
9253
9365
  } catch (error) {
9254
- logger$8.error("Failed to retrieve authorization state from storage:", error);
9366
+ logger$9.error("Failed to retrieve authorization state from storage:", error);
9255
9367
  throw error;
9256
9368
  }
9257
9369
  }
9258
9370
  get authStateEvent$() {
9259
9371
  return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, rxjs.tap)((msg) => {
9260
- logger$8.debug("[Session] Received incoming message:", msg);
9372
+ logger$9.debug("[Session] Received incoming message:", msg);
9261
9373
  }), require_operators.filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, rxjs.tap)((event) => {
9262
- logger$8.debug("[Session] Authorization state event received:", event.authorization_state);
9374
+ logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
9263
9375
  })));
9264
9376
  }
9265
9377
  get signalingEvent$() {
@@ -9297,42 +9409,72 @@ var ClientSessionManager = class extends Destroyable {
9297
9409
  await (0, rxjs.firstValueFrom)(this.authenticated$.pipe((0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.filter)(Boolean), (0, rxjs.take)(1), (0, rxjs.timeout)({ first: 15e3 })));
9298
9410
  }
9299
9411
  async handleAuthenticationError(error) {
9300
- logger$8.error("Authentication error:", error);
9301
- const isRecoverableAuthError = error instanceof require_operators.JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
9412
+ logger$9.error("Authentication error:", error);
9413
+ const isRecoverableAuthError$1 = error instanceof require_operators.JSONRPCError && (error.code === require_operators.RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === require_operators.RPC_ERROR_INVALID_PARAMS || error.code === require_operators.RPC_ERROR_AUTHENTICATION_FAILED);
9302
9414
  const hasStoredState = await (0, rxjs.firstValueFrom)(this.authorizationState$.pipe((0, rxjs.take)(1))) !== void 0;
9303
- if (isRecoverableAuthError && hasStoredState) {
9304
- logger$8.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
9415
+ if (isRecoverableAuthError$1 && hasStoredState) {
9416
+ logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
9305
9417
  try {
9306
9418
  await this.cleanupStoredConnectionParams();
9307
9419
  } catch (cleanupError) {
9308
- logger$8.error("Failed to cleanup stored connection params:", cleanupError);
9420
+ logger$9.error("Failed to cleanup stored connection params:", cleanupError);
9309
9421
  } finally {
9310
9422
  this.transport.reconnect();
9311
9423
  }
9312
9424
  } else this._errors$.next(error);
9313
9425
  }
9426
+ /**
9427
+ * Clear the resume state (authorization_state + protocol) only.
9428
+ *
9429
+ * This is the stale-auth-state recovery helper used by handleAuthError:
9430
+ * the server rejected a reconnect, so the resume state is discarded and a
9431
+ * fresh connect follows. Attach records are deliberately preserved — the
9432
+ * session lives on through the reconnect and reattachCalls() needs the
9433
+ * stored call references afterwards. Do NOT add detachAll() here.
9434
+ *
9435
+ * For public teardown (disconnect/destroy), use {@link teardownSessionState}
9436
+ * instead, which clears the attach records as well.
9437
+ */
9314
9438
  async cleanupStoredConnectionParams() {
9315
9439
  await this.transport.setProtocol(void 0);
9316
9440
  await this.updateAuthorizationStateInStorage(void 0);
9317
9441
  this._authorization$.next(void 0);
9318
9442
  }
9443
+ /**
9444
+ * Public-teardown helper for disconnect()/destroy(). Clears the resume
9445
+ * state (authorization_state + protocol) AND the attach records as one
9446
+ * atomic unit.
9447
+ *
9448
+ * The two stores are coupled: the backend only honors attach records
9449
+ * within the session identified by the resume state, so ending the
9450
+ * session must clear both. Clearing one without the other strands records
9451
+ * no future session can honor (disconnect) or revives a session the
9452
+ * developer explicitly ended (destroy).
9453
+ *
9454
+ * Distinct from {@link cleanupStoredConnectionParams}, which keeps the
9455
+ * attach records for the stale-auth-state recovery path.
9456
+ */
9457
+ async teardownSessionState() {
9458
+ await this.cleanupStoredConnectionParams();
9459
+ await this.attachManager.detachAll();
9460
+ }
9319
9461
  async updateAuthState(authorization_state) {
9320
9462
  try {
9321
9463
  await this.storage.setItem(this.authorizationStateKey, authorization_state);
9322
9464
  } catch (error) {
9323
- logger$8.error("Failed to update authorization state in storage:", error);
9465
+ logger$9.error("Failed to update authorization state in storage:", error);
9324
9466
  this._errors$.next(new require_operators.AuthStateHandlerError(error));
9325
9467
  }
9326
9468
  }
9327
9469
  async reauthenticate(token, dpopToken, options) {
9328
- logger$8.debug("[Session] Re-authenticating session");
9470
+ logger$9.debug("[Session] Re-authenticating session");
9329
9471
  try {
9330
9472
  let resolvedDpopToken = dpopToken;
9331
9473
  if (!resolvedDpopToken && this.dpopManager?.initialized) try {
9332
9474
  resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
9333
9475
  } catch (error) {
9334
9476
  if (this.clientBound) throw error;
9335
- logger$8.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
9477
+ logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
9336
9478
  }
9337
9479
  const request = RPCReauthenticate({
9338
9480
  project: this._authorization$.value?.project_id ?? "",
@@ -9340,24 +9482,24 @@ var ClientSessionManager = class extends Destroyable {
9340
9482
  ...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
9341
9483
  });
9342
9484
  await (0, rxjs.lastValueFrom)((0, rxjs.from)(this.transport.execute(request)).pipe(require_operators.throwOnRPCError(), (0, rxjs.take)(1), (0, rxjs.catchError)((err) => {
9343
- logger$8.error("[Session] Re-authentication RPC failed:", err);
9485
+ logger$9.error("[Session] Re-authentication RPC failed:", err);
9344
9486
  throw err;
9345
9487
  })));
9346
9488
  if (options?.clientBound) this._wasClientBound = true;
9347
- logger$8.debug("[Session] Re-authentication successful, updating stored auth state");
9489
+ logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
9348
9490
  } catch (error) {
9349
- logger$8.error("[Session] Re-authentication failed:", error);
9491
+ logger$9.error("[Session] Re-authentication failed:", error);
9350
9492
  this._errors$.next(new require_operators.AuthStateHandlerError(error));
9351
9493
  throw error;
9352
9494
  }
9353
9495
  }
9354
9496
  async authenticate() {
9355
- logger$8.debug("[Session] Starting authentication process");
9497
+ logger$9.debug("[Session] Starting authentication process");
9356
9498
  const persistedParams = await (0, rxjs.firstValueFrom)((0, rxjs.combineLatest)({
9357
9499
  protocol: this.transport.protocol$,
9358
9500
  authorization_state: this.authorizationState$
9359
9501
  }).pipe((0, rxjs.take)(1)));
9360
- logger$8.debug("[Session] Persisted params:\n", {
9502
+ logger$9.debug("[Session] Persisted params:\n", {
9361
9503
  protocol: persistedParams.protocol,
9362
9504
  authStateLength: persistedParams.authorization_state?.length
9363
9505
  });
@@ -9365,16 +9507,20 @@ var ClientSessionManager = class extends Destroyable {
9365
9507
  const storedToken = this.getCredential().token;
9366
9508
  const isReconnect = hasReconnectState && storedToken;
9367
9509
  let dpopToken;
9368
- if (isReconnect) logger$8.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
9369
- else if (this.onBeforeReconnect && this.clientBound) {
9370
- logger$8.debug("[Session] Refreshing credentials before fresh connect");
9371
- await this.onBeforeReconnect();
9510
+ if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
9511
+ else {
9512
+ const credential = this.getCredential();
9513
+ const credentialExpired = credential.expiry_at !== void 0 && credential.expiry_at <= Date.now() + require_operators.CREDENTIAL_EXPIRY_SKEW_MS;
9514
+ if (this.onBeforeReconnect && (this.clientBound || credentialExpired)) {
9515
+ logger$9.debug("[Session] Refreshing credentials before fresh connect");
9516
+ await this.onBeforeReconnect();
9517
+ }
9372
9518
  }
9373
- if ((!isReconnect || this.clientBound) && this.dpopManager?.initialized) try {
9519
+ if (this.dpopManager?.initialized) try {
9374
9520
  dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
9375
9521
  } catch (error) {
9376
9522
  if (this.clientBound) throw error;
9377
- logger$8.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
9523
+ logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
9378
9524
  }
9379
9525
  const rpcConnectRequest = RPCConnect({
9380
9526
  authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
@@ -9391,26 +9537,27 @@ var ClientSessionManager = class extends Destroyable {
9391
9537
  } : {}
9392
9538
  });
9393
9539
  const response = await (0, rxjs.lastValueFrom)((0, rxjs.from)(this.transport.execute(rpcConnectRequest)).pipe(require_operators.throwOnRPCError(), (0, rxjs.map)((res) => res.result), (0, rxjs.filter)(isRPCConnectResult), (0, rxjs.tap)(() => {
9394
- logger$8.debug("[Session] Response passed filter, processing authentication result");
9540
+ logger$9.debug("[Session] Response passed filter, processing authentication result");
9395
9541
  }), (0, rxjs.take)(1), (0, rxjs.catchError)((err) => {
9396
- logger$8.error("[Session] Authentication RPC failed:", err);
9542
+ logger$9.error("[Session] Authentication RPC failed:", err);
9397
9543
  throw err;
9398
9544
  })));
9399
- logger$8.debug("[Session] Processing authentication result:", {
9545
+ logger$9.debug("[Session] Processing authentication result:", {
9400
9546
  hasProtocol: !!response.protocol,
9401
9547
  hasAuthorization: !!response.authorization,
9402
9548
  hasIceServers: !!response.ice_servers
9403
9549
  });
9404
9550
  if (response.protocol) await this.transport.setProtocol(response.protocol);
9405
9551
  this._authorization$.next(response.authorization);
9552
+ if (response.authorization.cnf?.jkt) this._wasClientBound = true;
9406
9553
  this._iceServers$.next(response.ice_servers ?? []);
9407
9554
  this._authState$.next({ kind: "authenticated" });
9408
- logger$8.debug("[Session] Authentication completed successfully");
9555
+ logger$9.debug("[Session] Authentication completed successfully");
9409
9556
  }
9410
9557
  async disconnect() {
9411
9558
  this.transport.disconnect();
9412
9559
  this._authState$.next({ kind: "unauthenticated" });
9413
- await this.cleanupStoredConnectionParams();
9560
+ await this.teardownSessionState();
9414
9561
  }
9415
9562
  async createInboundCall(invite) {
9416
9563
  const callSession = await this.createCall({
@@ -9441,11 +9588,11 @@ var ClientSessionManager = class extends Destroyable {
9441
9588
  async handleVertoAttach(attach) {
9442
9589
  const { callID } = attach;
9443
9590
  if (callID in this._calls$.value) {
9444
- logger$8.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
9591
+ logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
9445
9592
  return;
9446
9593
  }
9447
9594
  const storedOptions = await this.attachManager.consumePendingAttachment(callID);
9448
- logger$8.debug(`[Session] Creating reattached call for callID: ${callID}`);
9595
+ logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
9449
9596
  const callSession = await this.createCall({
9450
9597
  nodeId: attach.node_id,
9451
9598
  callId: callID,
@@ -9469,14 +9616,14 @@ var ClientSessionManager = class extends Destroyable {
9469
9616
  to: destinationURI,
9470
9617
  ...options
9471
9618
  });
9472
- await (0, rxjs.firstValueFrom)((0, rxjs.race)(callSession.selfId$.pipe((0, rxjs.filter)((id) => Boolean(id)), (0, rxjs.take)(1), (0, rxjs.timeout)(this.callCreateTimeout)), callSession.errors$.pipe((0, rxjs.take)(1), (0, rxjs.switchMap)((callError) => (0, rxjs.throwError)(() => callError.error)))));
9619
+ await (0, rxjs.firstValueFrom)((0, rxjs.race)(callSession.selfId$.pipe((0, rxjs.filter)((id) => Boolean(id)), (0, rxjs.take)(1), (0, rxjs.timeout)(this.callCreateTimeout)), callSession.errors$.pipe((0, rxjs.filter)(shouldAbortDial), (0, rxjs.take)(1), (0, rxjs.switchMap)((callError) => (0, rxjs.throwError)(() => callError.error)))));
9473
9620
  this._calls$.next({
9474
9621
  [`${callSession.id}`]: callSession,
9475
9622
  ...this._calls$.value
9476
9623
  });
9477
9624
  return callSession;
9478
9625
  } catch (error) {
9479
- logger$8.error("[Session] Error creating outbound call:", error);
9626
+ logger$9.error("[Session] Error creating outbound call:", error);
9480
9627
  callSession?.destroy();
9481
9628
  const callError = new require_operators.CallCreateError(error instanceof rxjs.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
9482
9629
  this._errors$.next(callError);
@@ -9494,7 +9641,7 @@ var ClientSessionManager = class extends Destroyable {
9494
9641
  address = this._directory.get(addressId);
9495
9642
  if (!address) throw new require_operators.DependencyError(`Address ID: ${addressId} not found`);
9496
9643
  } catch {
9497
- logger$8.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
9644
+ logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
9498
9645
  }
9499
9646
  const callSession = this.callFactory.createCall(address, { ...options });
9500
9647
  this.subscribeTo(callSession.status$.pipe((0, rxjs.filter)((status) => status === "destroyed"), (0, rxjs.take)(1)), () => {
@@ -9503,7 +9650,7 @@ var ClientSessionManager = class extends Destroyable {
9503
9650
  });
9504
9651
  return callSession;
9505
9652
  } catch (error) {
9506
- logger$8.error("[Session] Error creating call session:", error);
9653
+ logger$9.error("[Session] Error creating call session:", error);
9507
9654
  throw new require_operators.CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
9508
9655
  }
9509
9656
  }
@@ -9522,6 +9669,14 @@ var ClientSessionWrapper = class {
9522
9669
  get authenticated() {
9523
9670
  return this.clientSessionManager.authenticated;
9524
9671
  }
9672
+ /**
9673
+ * Whether the session is using a Client Bound SAT (DPoP). Sticky — set
9674
+ * when the binding is established or restored from a resumed session's
9675
+ * server authorization.
9676
+ */
9677
+ get clientBound() {
9678
+ return this.clientSessionManager.clientBound;
9679
+ }
9525
9680
  get signalingEvent$() {
9526
9681
  return this.clientSessionManager.signalingEvent$;
9527
9682
  }
@@ -9551,7 +9706,7 @@ const isString = (obj) => typeof obj === "string";
9551
9706
 
9552
9707
  //#endregion
9553
9708
  //#region src/managers/ConversationsManager.ts
9554
- const logger$7 = require_operators.getLogger();
9709
+ const logger$8 = require_operators.getLogger();
9555
9710
  var ConversationMessagesFetcher = class extends Fetcher {
9556
9711
  constructor(groupId, http) {
9557
9712
  super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
@@ -9591,13 +9746,13 @@ var ConversationsManager = class {
9591
9746
  }
9592
9747
  throw new require_operators.ConversationError("Join Failed - Unexpected response");
9593
9748
  } catch (error) {
9594
- logger$7.error("[ConversationsManager] Failed to join conversation:", error);
9749
+ logger$8.error("[ConversationsManager] Failed to join conversation:", error);
9595
9750
  throw error;
9596
9751
  }
9597
9752
  }
9598
9753
  async getConversationMessageCollection(addressId) {
9599
9754
  const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
9600
- return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(require_operators.filterAs(isConversationMessageMetadata, "params"), (0, rxjs.tap)((event) => logger$7.debug("[ConversationsManager ] Conversation Event:", event)), (0, rxjs.map)((params) => ({ ...params }))), this.http, this.onError));
9755
+ return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(require_operators.filterAs(isConversationMessageMetadata, "params"), (0, rxjs.tap)((event) => logger$8.debug("[ConversationsManager ] Conversation Event:", event)), (0, rxjs.map)((params) => ({ ...params }))), this.http, this.onError));
9601
9756
  }
9602
9757
  async sendText(text, destinationAddressId) {
9603
9758
  const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
@@ -9614,7 +9769,7 @@ var ConversationsManager = class {
9614
9769
  })).ok) return;
9615
9770
  throw new require_operators.ConversationError("Send Text Failed - Unexpected response");
9616
9771
  } catch (error) {
9617
- logger$7.error("[ConversationsManager] Failed to send text message:", error);
9772
+ logger$8.error("[ConversationsManager] Failed to send text message:", error);
9618
9773
  throw error;
9619
9774
  }
9620
9775
  }
@@ -9622,7 +9777,7 @@ var ConversationsManager = class {
9622
9777
 
9623
9778
  //#endregion
9624
9779
  //#region src/managers/DeviceTokenManager.ts
9625
- const logger$6 = require_operators.getLogger();
9780
+ const logger$7 = require_operators.getLogger();
9626
9781
  /**
9627
9782
  * Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
9628
9783
  * 1. `data.expires_at` — server-provided absolute timestamp
@@ -9632,8 +9787,8 @@ const logger$6 = require_operators.getLogger();
9632
9787
  function resolveExpiresAt(data) {
9633
9788
  if (data.expires_at) return data.expires_at;
9634
9789
  if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
9635
- logger$6.warn("[DeviceToken] Could not determine token expiry, using default");
9636
- return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
9790
+ logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
9791
+ return Math.floor(Date.now() / 1e3) + require_operators.DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
9637
9792
  }
9638
9793
  /**
9639
9794
  * Resolves the token TTL in seconds from a fresh response.
@@ -9646,7 +9801,7 @@ function resolveExpiresAt(data) {
9646
9801
  function resolveExpireIn(data) {
9647
9802
  if (data.expires_in) return data.expires_in;
9648
9803
  if (data.expires_at) return Math.max(data.expires_at - Math.floor(Date.now() / 1e3), 1);
9649
- return DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
9804
+ return require_operators.DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
9650
9805
  }
9651
9806
  /**
9652
9807
  * Manages the Client Bound SAT lifecycle: activation, token exchange,
@@ -9665,11 +9820,12 @@ var DeviceTokenManager = class extends Destroyable {
9665
9820
  this.getCredential = getCredential;
9666
9821
  this._currentToken$ = this.createBehaviorSubject(null);
9667
9822
  this._refreshInProgress = false;
9668
- this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
9823
+ this._paused = false;
9824
+ this._effectiveExpireIn = require_operators.DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
9669
9825
  this.subscribeTo(this._currentToken$.pipe((0, rxjs.filter)(Boolean), (0, rxjs.switchMap)((tokenData) => {
9670
9826
  const expiresAt = resolveExpiresAt(tokenData);
9671
- const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
9672
- logger$6.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
9827
+ const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - require_operators.DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
9828
+ logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
9673
9829
  return (0, rxjs.timer)(refreshIn);
9674
9830
  })), () => {
9675
9831
  this.executeRefresh();
@@ -9683,7 +9839,12 @@ var DeviceTokenManager = class extends Destroyable {
9683
9839
  * Activates the Client Bound SAT flow when the user's token has
9684
9840
  * `sat:refresh` scope.
9685
9841
  *
9686
- * Steps:
9842
+ * Returns an {@link ActivationResult} indicating whether the manager
9843
+ * took ownership of refresh duties. The caller must use the `activated`
9844
+ * boolean to decide whether to keep its own refresh path armed — when
9845
+ * `activated` is `false`, the caller is responsible for refresh.
9846
+ *
9847
+ * Steps on success:
9687
9848
  * 1. Check user's `sat_claims` for `sat:refresh` scope
9688
9849
  * 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
9689
9850
  * 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
@@ -9691,47 +9852,81 @@ var DeviceTokenManager = class extends Destroyable {
9691
9852
  */
9692
9853
  async activate(user, session, updateCredential) {
9693
9854
  const { satClaims } = user;
9694
- if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
9695
- logger$6.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
9696
- return;
9855
+ if (!satClaims?.scope?.includes(require_operators.SAT_REFRESH_SCOPE)) {
9856
+ logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
9857
+ return {
9858
+ activated: false,
9859
+ reason: "no-scope"
9860
+ };
9697
9861
  }
9698
9862
  this._session = session;
9699
9863
  this._updateCredential = updateCredential;
9700
9864
  try {
9865
+ const cached = this._currentToken$.value;
9866
+ if (cached && this.isTokenFresh(cached)) {
9867
+ logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
9868
+ const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
9869
+ await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
9870
+ updateCredential({ token: cached.token });
9871
+ return { activated: true };
9872
+ }
9701
9873
  const tokenData = await this.obtainToken();
9702
9874
  if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
9703
9875
  this._effectiveExpireIn = resolveExpireIn(tokenData);
9704
9876
  const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
9705
9877
  await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
9706
9878
  updateCredential({ token: tokenData.token });
9707
- logger$6.info("[DeviceToken] Client Bound SAT activated successfully");
9708
- this._currentToken$.next(tokenData);
9879
+ logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
9880
+ this.emitCurrentToken(tokenData);
9881
+ return { activated: true };
9709
9882
  } catch (error) {
9710
- logger$6.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
9883
+ logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
9711
9884
  this.errorHandler(new require_operators.DPoPInitError(error, "Failed to activate Client Bound SAT"));
9712
- const credential = this.getCredential();
9713
- const expiresAt = satClaims.expires_at ?? (credential.expiry_at ? credential.expiry_at / 1e3 : Date.now() / 1e3 + DEVICE_TOKEN_DEFAULT_EXPIRE_IN / 1e3);
9714
- this._currentToken$.next({
9715
- token: credential.token ?? "",
9716
- expires_at: expiresAt
9717
- });
9885
+ return {
9886
+ activated: false,
9887
+ reason: "endpoint-failed"
9888
+ };
9718
9889
  }
9719
9890
  }
9720
9891
  /**
9892
+ * Emit a freshly received token to the reactive pipeline, stamping an
9893
+ * absolute `expires_at` when the response carried only `expires_in`.
9894
+ * Resolving the expiry at RECEIVE time (not at read time) is what lets
9895
+ * {@link refreshNowIfDue} detect due-ness on resume: a bare `expires_in`
9896
+ * re-resolved later would always compute a full TTL from "now" and never
9897
+ * cross the refresh buffer.
9898
+ */
9899
+ emitCurrentToken(token) {
9900
+ const stamped = token.expires_at ? token : {
9901
+ ...token,
9902
+ expires_at: resolveExpiresAt(token)
9903
+ };
9904
+ this._currentToken$.next(stamped);
9905
+ }
9906
+ /**
9907
+ * Returns true when the cached token has enough headroom before expiry to
9908
+ * be safely reused on reactivation. The headroom matches the refresh
9909
+ * buffer, so a token within the refresh window is treated as stale (the
9910
+ * reactive pipeline is about to refresh it anyway).
9911
+ */
9912
+ isTokenFresh(token) {
9913
+ return resolveExpiresAt(token) * 1e3 - Date.now() > require_operators.DEVICE_TOKEN_REFRESH_BUFFER_MS;
9914
+ }
9915
+ /**
9721
9916
  * Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
9722
9917
  * Returns the full {@link DeviceTokenResponse} including expiry metadata.
9723
9918
  */
9724
9919
  async obtainToken() {
9725
9920
  const dpopProof = await this.dpopManager.createHttpProof({
9726
9921
  method: "POST",
9727
- uri: DEVICE_TOKEN_ENDPOINT
9922
+ uri: require_operators.DEVICE_TOKEN_ENDPOINT
9728
9923
  });
9729
9924
  const response = await this.http.request({
9730
- url: DEVICE_TOKEN_ENDPOINT,
9925
+ url: require_operators.DEVICE_TOKEN_ENDPOINT,
9731
9926
  ...POST_PARAMS,
9732
9927
  body: JSON.stringify({
9733
9928
  dpop_token: dpopProof,
9734
- expire_in: DEVICE_TOKEN_DEFAULT_EXPIRE_IN
9929
+ expire_in: require_operators.DEVICE_TOKEN_DEFAULT_EXPIRE_IN
9735
9930
  })
9736
9931
  });
9737
9932
  if (!response.ok || !response.body) throw new require_operators.DeviceTokenError(`Failed to obtain device token: ${response.status} ${response.statusText}`);
@@ -9747,14 +9942,14 @@ var DeviceTokenManager = class extends Destroyable {
9747
9942
  * handled by the reactive pipeline).
9748
9943
  */
9749
9944
  async refreshToken(session, currentToken, updateCredential) {
9750
- logger$6.debug("[DeviceToken] Refreshing Client Bound SAT");
9945
+ logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
9751
9946
  const dpopProof = await this.dpopManager.createHttpProof({
9752
9947
  method: "POST",
9753
- uri: DEVICE_REFRESH_ENDPOINT,
9948
+ uri: require_operators.DEVICE_REFRESH_ENDPOINT,
9754
9949
  accessToken: currentToken
9755
9950
  });
9756
9951
  const response = await this.http.request({
9757
- url: DEVICE_REFRESH_ENDPOINT,
9952
+ url: require_operators.DEVICE_REFRESH_ENDPOINT,
9758
9953
  ...POST_PARAMS,
9759
9954
  body: JSON.stringify({
9760
9955
  dpop_token: dpopProof,
@@ -9769,7 +9964,7 @@ var DeviceTokenManager = class extends Destroyable {
9769
9964
  const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
9770
9965
  await session.reauthenticate(data.token, rpcProof);
9771
9966
  updateCredential({ token: data.token });
9772
- logger$6.info("[DeviceToken] Client Bound SAT refreshed successfully");
9967
+ logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
9773
9968
  return data;
9774
9969
  }
9775
9970
  /**
@@ -9778,18 +9973,22 @@ var DeviceTokenManager = class extends Destroyable {
9778
9973
  * On all retries exhausted, emits to `errorHandler`.
9779
9974
  */
9780
9975
  async executeRefresh() {
9976
+ if (this._paused) {
9977
+ logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
9978
+ return;
9979
+ }
9781
9980
  if (this._refreshInProgress) {
9782
- logger$6.debug("[DeviceToken] Refresh already in progress, skipping");
9981
+ logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
9783
9982
  return;
9784
9983
  }
9785
9984
  const session = this._session;
9786
9985
  const updateCredential = this._updateCredential;
9787
9986
  if (!session || !updateCredential) {
9788
- logger$6.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
9987
+ logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
9789
9988
  return;
9790
9989
  }
9791
9990
  if (!session.authenticated) {
9792
- logger$6.debug("[DeviceToken] Session not authenticated, deferring refresh");
9991
+ logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
9793
9992
  return;
9794
9993
  }
9795
9994
  this._refreshInProgress = true;
@@ -9797,9 +9996,9 @@ var DeviceTokenManager = class extends Destroyable {
9797
9996
  const currentToken = this.getCredential().token;
9798
9997
  if (!currentToken) throw new require_operators.TokenRefreshError("No current token available for refresh");
9799
9998
  const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
9800
- this._currentToken$.next(newTokenData);
9999
+ this.emitCurrentToken(newTokenData);
9801
10000
  } catch (error) {
9802
- logger$6.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
10001
+ logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
9803
10002
  this.errorHandler(error instanceof require_operators.TokenRefreshError ? error : new require_operators.TokenRefreshError("Automatic token refresh failed", error));
9804
10003
  } finally {
9805
10004
  this._refreshInProgress = false;
@@ -9811,24 +10010,330 @@ var DeviceTokenManager = class extends Destroyable {
9811
10010
  */
9812
10011
  async retryRefresh(session, currentToken, updateCredential) {
9813
10012
  let lastError;
9814
- for (let attempt = 0; attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES; attempt++) try {
10013
+ for (let attempt = 0; attempt < require_operators.DEVICE_TOKEN_REFRESH_MAX_RETRIES; attempt++) try {
9815
10014
  return await this.refreshToken(session, currentToken, updateCredential);
9816
10015
  } catch (error) {
9817
10016
  lastError = error;
9818
- if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
9819
- const delay = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
9820
- logger$6.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay}ms`);
10017
+ if (attempt < require_operators.DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
10018
+ const delay = require_operators.DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
10019
+ logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay}ms`);
9821
10020
  await new Promise((resolve) => setTimeout(resolve, delay));
9822
10021
  }
9823
10022
  }
9824
10023
  throw lastError instanceof Error ? lastError : new require_operators.TokenRefreshError("All refresh retries exhausted", lastError);
9825
10024
  }
10025
+ /**
10026
+ * Force an immediate refresh when the cached Client Bound SAT is already
10027
+ * past its refresh window. Called on resume from suspension where
10028
+ * background-tab throttling can delay the reactive timer past the buffer.
10029
+ * A no-op when no token is cached or it still has headroom; the normal
10030
+ * {@link executeRefresh} guards (paused / in-progress / unauthenticated)
10031
+ * still apply.
10032
+ */
10033
+ refreshNowIfDue() {
10034
+ const token = this._currentToken$.value;
10035
+ if (!token) return;
10036
+ if (resolveExpiresAt(token) * 1e3 - Date.now() <= require_operators.DEVICE_TOKEN_REFRESH_BUFFER_MS) {
10037
+ logger$7.debug("[DeviceToken] Resume: cached SAT past refresh window; refreshing now");
10038
+ this.executeRefresh();
10039
+ }
10040
+ }
10041
+ /**
10042
+ * Stops the reactive refresh pipeline from firing. Use when the underlying
10043
+ * session is being torn down (e.g., during {@link SignalWire.disconnect})
10044
+ * so a scheduled refresh cannot fire against a destroyed session.
10045
+ *
10046
+ * The manager's state (cached token, effective TTL, subscriptions) is
10047
+ * preserved — call {@link resume} to re-enable firing after reconnect.
10048
+ */
10049
+ pause() {
10050
+ this._paused = true;
10051
+ }
10052
+ /** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
10053
+ resume() {
10054
+ this._paused = false;
10055
+ }
9826
10056
  /** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
9827
10057
  destroy() {
9828
10058
  super.destroy();
9829
10059
  }
9830
10060
  };
9831
10061
 
10062
+ //#endregion
10063
+ //#region src/managers/CredentialRefreshCoordinator.ts
10064
+ const logger$6 = require_operators.getLogger();
10065
+ const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
10066
+ /**
10067
+ * Centralizes credential-refresh ownership across the two competing
10068
+ * mechanisms — developer-provided `CredentialProvider.refresh()` and the
10069
+ * Client Bound SAT path via {@link DeviceTokenManager}.
10070
+ *
10071
+ * Maintains the invariant: **at most one refresh mechanism is armed at a
10072
+ * time, and at least one is armed whenever the current credential has an
10073
+ * `expiry_at`**.
10074
+ *
10075
+ * Replaces the previous design where refresh state was distributed across
10076
+ * `SignalWire` (timer field, scheduler method, activation helper) and
10077
+ * `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
10078
+ * one component eliminates the bug class that produced issue #19074.
10079
+ *
10080
+ * Race-safety:
10081
+ * - `_activating` flag prevents overlapping `activate()` calls from racing.
10082
+ * - `_activationGeneration` lets late resolutions detect they've been
10083
+ * preempted by a newer activation (e.g., reconnect during in-flight
10084
+ * `obtainToken`).
10085
+ */
10086
+ var CredentialRefreshCoordinator = class extends Destroyable {
10087
+ constructor(dpopManager, deps) {
10088
+ super();
10089
+ this.deps = deps;
10090
+ this._activating = false;
10091
+ this._activationGeneration = 0;
10092
+ this._developerRefreshInProgress = false;
10093
+ if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
10094
+ }
10095
+ /** True when the Client Bound SAT path is available (DPoP initialized). */
10096
+ get clientBoundSATAvailable() {
10097
+ return this._deviceTokenManager !== void 0;
10098
+ }
10099
+ /** True when the developer-provided refresh timer is currently armed. */
10100
+ get developerRefreshArmed() {
10101
+ return this._developerTimerId !== void 0;
10102
+ }
10103
+ /**
10104
+ * Arms the developer-provided refresh timer to fire shortly before
10105
+ * `expiresAt`. Replaces any previously scheduled developer refresh.
10106
+ *
10107
+ * Idempotent — multiple calls just reschedule. On retry exhaustion,
10108
+ * invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
10109
+ */
10110
+ scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
10111
+ this._activeProvider = provider;
10112
+ if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
10113
+ const refreshInterval = attempt === 0 ? Math.max(expiresAt - Date.now() - require_operators.CREDENTIAL_REFRESH_BUFFER_MS, 1e3) : Math.min(require_operators.CREDENTIAL_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt) * (.5 + Math.random() * .5), require_operators.CREDENTIAL_REFRESH_MAX_DELAY_MS);
10114
+ this._developerTimerId = setTimeout(() => {
10115
+ this._developerTimerId = void 0;
10116
+ this.executeDeveloperRefresh(provider, expiresAt, attempt);
10117
+ }, refreshInterval);
10118
+ }
10119
+ /**
10120
+ * Runs the developer-provided refresh once: mints a new credential, stores
10121
+ * and persists it, reauthenticates the live session (via the notifier), and
10122
+ * reschedules against the new expiry. On failure retries with backoff up to
10123
+ * {@link CREDENTIAL_REFRESH_MAX_RETRIES}, then signals exhaustion.
10124
+ *
10125
+ * Shared by the scheduled timer tick and {@link forceRefreshIfDue}. The
10126
+ * `_developerRefreshInProgress` guard prevents the two from overlapping.
10127
+ */
10128
+ async executeDeveloperRefresh(provider, expiresAt, attempt) {
10129
+ if (this._developerRefreshInProgress) {
10130
+ logger$6.debug("[Coordinator] Developer refresh already in progress; skipping");
10131
+ return;
10132
+ }
10133
+ this._developerRefreshInProgress = true;
10134
+ try {
10135
+ const newCredentials = await this.refreshCredential(provider);
10136
+ this.deps.store.write(newCredentials);
10137
+ this.deps.store.persist(newCredentials);
10138
+ try {
10139
+ await this.deps.notifier.onCredentialRefreshed(newCredentials);
10140
+ } catch (reauthError) {
10141
+ logger$6.warn("[Coordinator] onCredentialRefreshed rejected (non-fatal):", reauthError);
10142
+ }
10143
+ logger$6.info("[Coordinator] Credentials refreshed successfully.");
10144
+ if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
10145
+ } catch (error) {
10146
+ const nextAttempt = attempt + 1;
10147
+ logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${require_operators.CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
10148
+ this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
10149
+ if (nextAttempt < require_operators.CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
10150
+ else {
10151
+ logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
10152
+ this.deps.notifier.onError(new require_operators.TokenRefreshError("Credential refresh failed after max retries"));
10153
+ this.deps.notifier.onRefreshExhausted();
10154
+ }
10155
+ } finally {
10156
+ this._developerRefreshInProgress = false;
10157
+ }
10158
+ }
10159
+ /**
10160
+ * Force an immediate refresh when the current credential is already past its
10161
+ * scheduled refresh window. Called on resume from suspension, where
10162
+ * background-tab timer throttling can delay the armed refresh well past
10163
+ * expiry, leaving the live session stale.
10164
+ *
10165
+ * Routes to whichever mechanism is armed: the developer timer if armed,
10166
+ * otherwise the Client Bound SAT pipeline. A no-op when nothing is due.
10167
+ */
10168
+ forceRefreshIfDue() {
10169
+ if (this._developerTimerId !== void 0 && this._activeProvider) {
10170
+ const expiry = this.deps.store.read().expiry_at;
10171
+ if (expiry !== void 0 && Date.now() >= expiry - require_operators.CREDENTIAL_REFRESH_BUFFER_MS) {
10172
+ logger$6.debug("[Coordinator] Resume: credential past refresh window; forcing refresh");
10173
+ clearTimeout(this._developerTimerId);
10174
+ this._developerTimerId = void 0;
10175
+ this.executeDeveloperRefresh(this._activeProvider, expiry, 0);
10176
+ }
10177
+ return;
10178
+ }
10179
+ this._deviceTokenManager?.refreshNowIfDue();
10180
+ }
10181
+ /**
10182
+ * Sync the credential's expiry from the server-provided authorization (the
10183
+ * `signalwire.connect` result). SATs are opaque JWE, so
10184
+ * `fabric_subscriber.expires_at` is the authoritative expiry of the token
10185
+ * the session actually connected with — the provider-reported `expiry_at`
10186
+ * is only a hint (and may be wrong or absent). Corrects the stored
10187
+ * credential and re-arms the developer refresh timer against the real
10188
+ * deadline when the provider supports `refresh()`.
10189
+ */
10190
+ syncExpiryFromAuthorization(authorization, provider) {
10191
+ const expiresAtSec = authorization?.fabric_subscriber?.expires_at;
10192
+ if (!expiresAtSec) return;
10193
+ const expiryAt = expiresAtSec * 1e3;
10194
+ const credential = this.deps.store.read();
10195
+ if (credential.expiry_at === expiryAt) return;
10196
+ logger$6.debug(`[Coordinator] Correcting credential expiry from server authorization: ${new Date(expiryAt).toISOString()}`);
10197
+ const updated = {
10198
+ ...credential,
10199
+ expiry_at: expiryAt
10200
+ };
10201
+ this.deps.store.write(updated);
10202
+ this.deps.store.persist(updated);
10203
+ if (provider?.refresh) this.scheduleDeveloperRefresh(provider, expiryAt);
10204
+ }
10205
+ /**
10206
+ * Invoke `provider.refresh()` deduped against any concurrent developer
10207
+ * refresh. Concurrent callers — the scheduled tick, a resume-forced refresh,
10208
+ * and the orchestrator's -32003 recovery / reconnect re-mint — share one
10209
+ * in-flight promise, so a provider backed by one-time-use rotating refresh
10210
+ * tokens is never invoked twice in parallel.
10211
+ *
10212
+ * The caller owns applying the returned credential (store write, session
10213
+ * reauth, rescheduling); this method only serializes the network call.
10214
+ */
10215
+ async refreshCredential(provider) {
10216
+ if (this._refreshInFlight) return this._refreshInFlight;
10217
+ if (!provider.refresh) throw new require_operators.InvalidCredentialsError("Credential provider does not support refresh");
10218
+ const run = provider.refresh();
10219
+ this._refreshInFlight = run;
10220
+ const clear = () => {
10221
+ if (this._refreshInFlight === run) this._refreshInFlight = void 0;
10222
+ };
10223
+ run.then(clear, clear);
10224
+ return run;
10225
+ }
10226
+ /**
10227
+ * Cancels any scheduled developer-provided refresh. Idempotent.
10228
+ *
10229
+ * @internal Used by the coordinator's own activation flow. External
10230
+ * callers should use {@link suspend} for disconnect-time quiescence —
10231
+ * `suspend()` also pauses the internal Client Bound SAT pipeline.
10232
+ */
10233
+ cancelDeveloperRefresh() {
10234
+ if (this._developerTimerId !== void 0) {
10235
+ clearTimeout(this._developerTimerId);
10236
+ this._developerTimerId = void 0;
10237
+ }
10238
+ }
10239
+ /**
10240
+ * Suspends both refresh paths — cancels the developer timer and pauses
10241
+ * the internal reactive pipeline. Use when the underlying session is
10242
+ * being torn down (e.g., {@link SignalWire.disconnect}). The next
10243
+ * {@link activate} call re-enables the internal pipeline.
10244
+ *
10245
+ * The internal manager's cached token survives — see
10246
+ * {@link DeviceTokenManager.pause} — so a subsequent reconnect can
10247
+ * skip the `/devices/token` exchange entirely.
10248
+ */
10249
+ suspend() {
10250
+ this.cancelDeveloperRefresh();
10251
+ this._deviceTokenManager?.pause();
10252
+ }
10253
+ /**
10254
+ * Asks the Client Bound SAT path to take over refresh. If it accepts,
10255
+ * the developer-provided timer (if any) is cancelled. If it declines, the
10256
+ * developer timer remains armed and a `credential_refresh_fallback`
10257
+ * warning is emitted.
10258
+ *
10259
+ * **Idempotent** — re-entrant calls during an in-flight activation are
10260
+ * dropped. Use `_activationGeneration` to detect stale resolutions
10261
+ * (e.g., a reconnect-triggered activate() that races with an earlier one).
10262
+ */
10263
+ async activate(user, session) {
10264
+ if (this._activating) {
10265
+ logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
10266
+ return;
10267
+ }
10268
+ if (!this._deviceTokenManager) return;
10269
+ this._deviceTokenManager.resume();
10270
+ const generation = ++this._activationGeneration;
10271
+ this._activating = true;
10272
+ try {
10273
+ const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
10274
+ if (generation !== this._activationGeneration) {
10275
+ logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
10276
+ return;
10277
+ }
10278
+ if (result.activated) {
10279
+ this.cancelDeveloperRefresh();
10280
+ logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
10281
+ return;
10282
+ }
10283
+ logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
10284
+ this.deps.notifier.onWarning({
10285
+ code: "credential_refresh_fallback",
10286
+ source: "CredentialProvider",
10287
+ reason: result.reason,
10288
+ message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
10289
+ });
10290
+ } finally {
10291
+ this._activating = false;
10292
+ }
10293
+ }
10294
+ destroy() {
10295
+ this.cancelDeveloperRefresh();
10296
+ this._deviceTokenManager?.destroy();
10297
+ this._deviceTokenManager = void 0;
10298
+ super.destroy();
10299
+ }
10300
+ /**
10301
+ * Races the manager's `activate()` against a hard timeout. A wedged HTTP
10302
+ * layer (e.g., proxy issues) could otherwise hang the activation
10303
+ * indefinitely, leaving the session with no refresh mechanism while the
10304
+ * `_activating` guard blocks subsequent retries.
10305
+ *
10306
+ * On timeout, the manager is paused immediately. The inner `activate()`
10307
+ * may still complete in the background and emit to `_currentToken$`,
10308
+ * which would normally arm the reactive refresh pipeline; pausing
10309
+ * prevents that pipeline from firing while the developer refresh path
10310
+ * is the active mechanism — preserving the "at most one mechanism
10311
+ * armed" invariant. The next `activate()` call resumes the manager.
10312
+ */
10313
+ async withActivationTimeout(inner) {
10314
+ return new Promise((resolve) => {
10315
+ const timer$3 = setTimeout(() => {
10316
+ this._deviceTokenManager?.pause();
10317
+ resolve({
10318
+ activated: false,
10319
+ reason: "activation-timeout"
10320
+ });
10321
+ }, require_operators.CREDENTIAL_ACTIVATE_TIMEOUT_MS);
10322
+ inner.then((result) => {
10323
+ clearTimeout(timer$3);
10324
+ resolve(result);
10325
+ }, (error) => {
10326
+ clearTimeout(timer$3);
10327
+ this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
10328
+ resolve({
10329
+ activated: false,
10330
+ reason: "endpoint-failed"
10331
+ });
10332
+ });
10333
+ });
10334
+ }
10335
+ };
10336
+
9832
10337
  //#endregion
9833
10338
  //#region src/managers/DiagnosticsCollector.ts
9834
10339
  const logger$5 = require_operators.getLogger();
@@ -10346,6 +10851,10 @@ var TransportManager = class extends Destroyable {
10346
10851
  if (!isSignalwireRequest(message)) return true;
10347
10852
  const eventChannel = message.params.event_channel;
10348
10853
  if (!eventChannel) return true;
10854
+ if (message.params.event_type.startsWith("conversation.")) {
10855
+ logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
10856
+ return true;
10857
+ }
10349
10858
  const currentProtocol = this._currentProtocol;
10350
10859
  if (!currentProtocol) return true;
10351
10860
  if (!eventChannel.includes(currentProtocol)) {
@@ -10489,6 +10998,33 @@ var TransportManager = class extends Destroyable {
10489
10998
  }
10490
10999
  };
10491
11000
 
11001
+ //#endregion
11002
+ //#region src/utils/authRecovery.ts
11003
+ /**
11004
+ * Walk an error's `error`/`cause` chain looking for a {@link JSONRPCError}.
11005
+ * Errors thrown by call creation are wrapped (e.g. `CallCreateError`), so the
11006
+ * underlying signaling error is nested. Bounded by a visited set to guard
11007
+ * against cyclic causes.
11008
+ */
11009
+ function findJSONRPCError(error) {
11010
+ const seen = /* @__PURE__ */ new Set();
11011
+ let current = error;
11012
+ while (current instanceof Error && !seen.has(current)) {
11013
+ seen.add(current);
11014
+ if (current instanceof require_operators.JSONRPCError) return current;
11015
+ current = current.error ?? current.cause;
11016
+ }
11017
+ }
11018
+ /**
11019
+ * Whether an error is a session-recoverable authentication failure
11020
+ * (`-32002` authentication failed or `-32003` requester validation failed)
11021
+ * that a credential re-mint + retry can heal.
11022
+ */
11023
+ function isRecoverableAuthError(error) {
11024
+ const rpcError = findJSONRPCError(error);
11025
+ return rpcError !== void 0 && (rpcError.code === require_operators.RPC_ERROR_REQUESTER_VALIDATION_FAILED || rpcError.code === require_operators.RPC_ERROR_AUTHENTICATION_FAILED);
11026
+ }
11027
+
10492
11028
  //#endregion
10493
11029
  //#region src/clients/SignalWire.ts
10494
11030
  const logger$1 = require_operators.getLogger();
@@ -10538,6 +11074,7 @@ var SignalWire = class extends Destroyable {
10538
11074
  this._isConnected$ = this.createBehaviorSubject(false);
10539
11075
  this._isRegistered$ = this.createBehaviorSubject(false);
10540
11076
  this._errors$ = this.createReplaySubject(1);
11077
+ this._warnings$ = this.createReplaySubject(10);
10541
11078
  this._options = {};
10542
11079
  this._deps = new DependencyContainer();
10543
11080
  this._credentialProvider = credentialProvider;
@@ -10597,6 +11134,29 @@ var SignalWire = class extends Destroyable {
10597
11134
  */
10598
11135
  async resolveCredentials() {
10599
11136
  const fingerprint = await this.initDPoP();
11137
+ this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
11138
+ http: this._deps.http,
11139
+ notifier: {
11140
+ onError: (error) => this._errors$.next(error),
11141
+ onWarning: (warning) => this._warnings$.next(warning),
11142
+ onRefreshExhausted: () => void this.disconnect(),
11143
+ onCredentialRefreshed: async (credential) => this.reauthenticateLiveSession(credential)
11144
+ },
11145
+ store: {
11146
+ read: () => this._deps.credential,
11147
+ write: (credential) => {
11148
+ this._deps.credential = credential;
11149
+ },
11150
+ merge: (partial) => {
11151
+ this._deps.credential = {
11152
+ ...this._deps.credential,
11153
+ ...partial
11154
+ };
11155
+ this.persistCredential(this._deps.credential);
11156
+ },
11157
+ persist: (credential) => this.persistCredential(credential)
11158
+ }
11159
+ });
10600
11160
  if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
10601
11161
  for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
10602
11162
  const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
@@ -10626,11 +11186,32 @@ var SignalWire = class extends Destroyable {
10626
11186
  logger$1.error("[SignalWire] Provided credentials have expired.");
10627
11187
  throw new require_operators.InvalidCredentialsError("Provided credentials have expired.");
10628
11188
  }
10629
- if (_credentials.expiry_at && credentialProvider?.refresh) this.scheduleCredentialRefresh(credentialProvider, _credentials.expiry_at);
11189
+ if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
11190
+ else if (_credentials.expiry_at && !credentialProvider?.refresh) {
11191
+ logger$1.warn(`[SignalWire] [SW-NO-REFRESH-HANDLER] Credential has expiry_at=${_credentials.expiry_at} but no refresh handler. Session will terminate at expiry unless the SAT has 'sat:refresh' scope.`);
11192
+ this._warnings$.next({
11193
+ code: "credential_no_refresh_handler",
11194
+ source: "CredentialProvider",
11195
+ message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
11196
+ expiresAt: _credentials.expiry_at
11197
+ });
11198
+ }
10630
11199
  this._deps.credential = _credentials;
10631
11200
  this.persistCredential(_credentials);
10632
- if (this.isConnected && this._clientSession.authenticated && _credentials.token) try {
10633
- await this._clientSession.reauthenticate(_credentials.token);
11201
+ await this.reauthenticateLiveSession(_credentials);
11202
+ }
11203
+ /**
11204
+ * Reauthenticate the currently-open session with a freshly obtained
11205
+ * credential so the new token takes effect on the live socket immediately —
11206
+ * not just on the next reconnect. No-op when the session is not
11207
+ * connected/authenticated or the credential carries no token (e.g. an
11208
+ * authorization-state-only refresh). Non-fatal: reauth failures surface on
11209
+ * `errors$` without aborting the refresh that triggered this.
11210
+ */
11211
+ async reauthenticateLiveSession(credential) {
11212
+ if (!this.isConnected || !this._clientSession.authenticated || !credential.token) return;
11213
+ try {
11214
+ await this._clientSession.reauthenticate(credential.token);
10634
11215
  logger$1.info("[SignalWire] Session refreshed with new credentials.");
10635
11216
  } catch (error) {
10636
11217
  logger$1.error("[SignalWire] Failed to refresh session with new credentials:", error);
@@ -10638,33 +11219,101 @@ var SignalWire = class extends Destroyable {
10638
11219
  }
10639
11220
  }
10640
11221
  /**
10641
- * Schedules credential refresh with exponential backoff retry on failure.
10642
- * On success, resets attempt counter and schedules the next refresh.
10643
- * After exhausting retries, emits TokenRefreshError and disconnects.
11222
+ * One-shot recovery for a live session that failed with a recoverable auth
11223
+ * error (`-32002`/`-32003`) because its token went stale e.g. a refresh
11224
+ * timer that was throttled while the tab was backgrounded.
11225
+ *
11226
+ * Two steps, first that succeeds wins:
11227
+ * 1. Reauthenticate with the in-memory token (a fresh DPoP proof is
11228
+ * generated automatically). Heals a session whose token was already
11229
+ * refreshed by the coordinator but never applied to the open socket,
11230
+ * and a client-bound session whose server-side auth merely drifted.
11231
+ * 2. Re-mint via the developer provider's `refresh()` and reauthenticate.
11232
+ * Skipped for client-bound sessions (the DeviceTokenManager owns their
11233
+ * refresh; re-minting a base SAT here would drop the DPoP binding).
11234
+ *
11235
+ * @param allowRemint - Whether step 2 (provider re-mint) may run. Callers
11236
+ * pass `false` for non-auth failures so a transient network error never
11237
+ * escalates to a token re-mint; step 1 (in-memory reauth) always runs.
11238
+ * @returns `true` if the session was reauthenticated, `false` otherwise.
11239
+ */
11240
+ async recoverStaleCredential(allowRemint = true) {
11241
+ const currentToken = this._deps.credential.token;
11242
+ if (currentToken) try {
11243
+ await this._clientSession.reauthenticate(currentToken);
11244
+ return true;
11245
+ } catch (error) {
11246
+ logger$1.debug("[SignalWire] In-memory reauth failed during recovery:", error);
11247
+ }
11248
+ if (!allowRemint || this._clientSession.clientBound || !this._credentialProvider?.refresh) return false;
11249
+ try {
11250
+ const newCredentials = await this.remintCredential(this._credentialProvider);
11251
+ if (!newCredentials.token) return false;
11252
+ this._deps.credential = newCredentials;
11253
+ this.persistCredential(newCredentials);
11254
+ await this._clientSession.reauthenticate(newCredentials.token);
11255
+ if (newCredentials.expiry_at) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
11256
+ return true;
11257
+ } catch (error) {
11258
+ logger$1.error("[SignalWire] Credential recovery failed:", error);
11259
+ return false;
11260
+ }
11261
+ }
11262
+ /**
11263
+ * Re-mint a credential via `provider.refresh()`, routed through the
11264
+ * coordinator's shared in-flight guard so concurrent re-mint paths (a
11265
+ * scheduled/resume refresh, -32003 recovery, and reconnect) never fire a
11266
+ * second `provider.refresh()` in parallel — which rotating one-time-use
11267
+ * refresh tokens reject. Falls back to a direct call only if the coordinator
11268
+ * has not been constructed yet.
10644
11269
  */
10645
- scheduleCredentialRefresh(credentialProvider, expiresAt, attempt = 0) {
10646
- if (this._refreshTimerId !== void 0) clearTimeout(this._refreshTimerId);
10647
- const refreshInterval = attempt === 0 ? Math.max(expiresAt - Date.now() - CREDENTIAL_REFRESH_BUFFER_MS, 1e3) : Math.min(CREDENTIAL_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt) * (.5 + Math.random() * .5), CREDENTIAL_REFRESH_MAX_DELAY_MS);
10648
- this._refreshTimerId = setTimeout(async () => {
10649
- try {
10650
- if (!credentialProvider.refresh) throw new require_operators.InvalidCredentialsError("Credential provider does not support refresh");
10651
- const newCredentials = await credentialProvider.refresh();
10652
- this._deps.credential = newCredentials;
10653
- this.persistCredential(newCredentials);
10654
- logger$1.info("[SignalWire] Credentials refreshed successfully.");
10655
- if (newCredentials.expiry_at) this.scheduleCredentialRefresh(credentialProvider, newCredentials.expiry_at, 0);
10656
- } catch (error) {
10657
- const nextAttempt = attempt + 1;
10658
- logger$1.error(`[SignalWire] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
10659
- this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
10660
- if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleCredentialRefresh(credentialProvider, expiresAt, nextAttempt);
10661
- else {
10662
- logger$1.error("[SignalWire] Credential refresh exhausted all retries. Disconnecting.");
10663
- this._errors$.next(new require_operators.TokenRefreshError("Credential refresh failed after max retries"));
10664
- this.disconnect();
10665
- }
11270
+ async remintCredential(provider) {
11271
+ if (this._refreshCoordinator) return this._refreshCoordinator.refreshCredential(provider);
11272
+ if (!provider.refresh) throw new require_operators.InvalidCredentialsError("Credential provider does not support refresh");
11273
+ return provider.refresh();
11274
+ }
11275
+ /**
11276
+ * Re-mint credentials before a fresh (re)connect (`onBeforeReconnect` hook).
11277
+ * The session invokes this only when it is client-bound OR the in-memory
11278
+ * token is expired. The re-mint mechanism depends on the binding:
11279
+ * - Client-bound: `authenticate()` with the DPoP fingerprint to obtain a
11280
+ * fresh base SAT the upcoming reconnect can re-bind (the
11281
+ * DeviceTokenManager re-activates afterwards).
11282
+ * - Unbound: the developer's non-interactive `refresh()` handler.
11283
+ * `authenticate()` is deliberately NOT used here — it may be interactive
11284
+ * (a login prompt) and must not fire on a background reconnect.
11285
+ *
11286
+ * Rejects on failure so the session aborts the reconnect rather than
11287
+ * replaying a stale token.
11288
+ */
11289
+ async refreshCredentialForReconnect() {
11290
+ if (!this._credentialProvider) return;
11291
+ try {
11292
+ let newCredentials;
11293
+ if (this._clientSession.clientBound) {
11294
+ const fingerprint = this._dpopManager?.initialized ? this._dpopManager.fingerprint : void 0;
11295
+ logger$1.debug("[SignalWire] Re-minting client-bound base SAT before reconnect");
11296
+ newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
11297
+ } else if (this._credentialProvider.refresh) {
11298
+ logger$1.debug("[SignalWire] Refreshing unbound credential before reconnect");
11299
+ newCredentials = await this.remintCredential(this._credentialProvider);
11300
+ } else {
11301
+ logger$1.warn("[SignalWire] [SW-NO-REFRESH-HANDLER] Token expired on reconnect but no refresh handler; reconnecting with the existing token.");
11302
+ return;
10666
11303
  }
10667
- }, refreshInterval);
11304
+ if (!newCredentials.token) {
11305
+ logger$1.warn("[SignalWire] Re-minted credential has no token; keeping the existing credential for reconnect.");
11306
+ return;
11307
+ }
11308
+ this._deps.credential = newCredentials;
11309
+ this.persistCredential(newCredentials);
11310
+ if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
11311
+ logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
11312
+ } catch (error) {
11313
+ logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
11314
+ this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
11315
+ throw error;
11316
+ }
10668
11317
  }
10669
11318
  /** Persist credential to localStorage when persistSession is enabled. */
10670
11319
  persistCredential(credential) {
@@ -10752,51 +11401,20 @@ var SignalWire = class extends Destroyable {
10752
11401
  this._attachManager = new AttachManager(this._deps.storage, this._deps.deviceController, PreferencesContainer.instance.reconnectCallsTimeout, this._deps.attachedCallsKey);
10753
11402
  this._clientSession = new ClientSessionManager(() => this._deps.credential, this._transport, this._deps.storage, this._deps.authorizationStateKey, this._deps.deviceController, this._attachManager, this._deps.webRTCApiProvider, this._dpopManager, this._networkMonitor?.networkChange$);
10754
11403
  this._publicSession = new ClientSessionWrapper(this._clientSession);
10755
- this._clientSession.onBeforeReconnect = async () => {
10756
- if (!this._credentialProvider) return;
10757
- try {
10758
- const fingerprint = this._dpopManager?.initialized ? this._dpopManager.fingerprint : void 0;
10759
- logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
10760
- const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
10761
- this._deps.credential = newCredentials;
10762
- logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
10763
- } catch (error) {
10764
- logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
10765
- this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
10766
- throw error;
10767
- }
10768
- };
11404
+ this._clientSession.onBeforeReconnect = async () => this.refreshCredentialForReconnect();
10769
11405
  this.subscribeTo(this._clientSession.errors$, (error) => {
10770
11406
  this._errors$.next(error);
10771
11407
  });
11408
+ this.subscribeTo(this._clientSession.authorization$, (authorization) => {
11409
+ this._refreshCoordinator?.syncExpiryFromAuthorization(authorization, this._credentialProvider);
11410
+ });
10772
11411
  await this._clientSession.connect();
10773
- if (this._dpopManager?.initialized) {
10774
- if (this._refreshTimerId) {
10775
- clearTimeout(this._refreshTimerId);
10776
- this._refreshTimerId = void 0;
10777
- logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
10778
- }
10779
- this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
10780
- await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
10781
- this._deps.credential = {
10782
- ...this._deps.credential,
10783
- ...cred
10784
- };
10785
- });
10786
- }
11412
+ await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
10787
11413
  this.subscribeTo(this._clientSession.authenticated$.pipe((0, rxjs.skip)(1), (0, rxjs.filter)(Boolean)), async () => {
10788
11414
  try {
10789
- if (this._deviceTokenManager) {
10790
- await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
10791
- this._deps.credential = {
10792
- ...this._deps.credential,
10793
- ...cred
10794
- };
10795
- });
10796
- logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
10797
- }
11415
+ await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
10798
11416
  } catch (error) {
10799
- logger$1.error("[SignalWire] Client Bound SAT re-activation failed (non-fatal):", error);
11417
+ logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
10800
11418
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
10801
11419
  }
10802
11420
  try {
@@ -10882,6 +11500,19 @@ var SignalWire = class extends Destroyable {
10882
11500
  get errors$() {
10883
11501
  return this.deferEmission(this._errors$.asObservable());
10884
11502
  }
11503
+ /**
11504
+ * Observable stream of non-fatal SDK warnings.
11505
+ *
11506
+ * Subscribe to detect SDK behaviors that affect session liveness or developer-facing
11507
+ * contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
11508
+ * refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
11509
+ * scope. Discriminated by `code`.
11510
+ *
11511
+ * Independent from {@link errors$}: existing error consumers are not notified.
11512
+ */
11513
+ get warnings$() {
11514
+ return this.deferEmission(this._warnings$.asObservable());
11515
+ }
10885
11516
  /** Platform WebRTC capabilities detected at construction time. */
10886
11517
  get platformCapabilities() {
10887
11518
  this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
@@ -10939,6 +11570,13 @@ var SignalWire = class extends Destroyable {
10939
11570
  }
10940
11571
  try {
10941
11572
  this._visibilityController = new VisibilityController();
11573
+ this.subscribeTo(this._visibilityController.visibilityChange$.pipe((0, rxjs.filter)((event) => event.to === "visible")), () => {
11574
+ try {
11575
+ this._refreshCoordinator?.forceRefreshIfDue();
11576
+ } catch (error) {
11577
+ logger$1.warn("[SignalWire] Resume credential revalidation failed (non-fatal):", error);
11578
+ }
11579
+ });
10942
11580
  this.subscribeTo(this._visibilityController.visibilityChange$.pipe((0, rxjs.filter)((event) => event.to === "visible" && PreferencesContainer.instance.refreshDevicesOnVisible)), () => {
10943
11581
  logger$1.debug("[SignalWire] Page visible, re-enumerating devices");
10944
11582
  try {
@@ -10950,7 +11588,7 @@ var SignalWire = class extends Destroyable {
10950
11588
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
10951
11589
  }
10952
11590
  try {
10953
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "3.30.0" });
11591
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.2" });
10954
11592
  } catch (error) {
10955
11593
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
10956
11594
  }
@@ -10958,14 +11596,19 @@ var SignalWire = class extends Destroyable {
10958
11596
  /**
10959
11597
  * Disconnects the WebSocket and tears down the current session.
10960
11598
  *
11599
+ * Ends the session identified by the protocol and clears its persisted
11600
+ * resume state (`authorization_state` + protocol) and attach records
11601
+ * together — a later {@link connect} with the same credentials starts a
11602
+ * fresh session and cannot reattach to the ended session's calls.
11603
+ * Credentials and device preferences are preserved. To temporarily stop
11604
+ * receiving inbound calls while keeping the session alive, use
11605
+ * `unregister()` instead.
11606
+ *
10961
11607
  * The client can be reconnected by calling {@link connect} again,
10962
11608
  * which creates a fresh transport and session.
10963
11609
  */
10964
11610
  async disconnect() {
10965
- if (this._refreshTimerId) {
10966
- clearTimeout(this._refreshTimerId);
10967
- this._refreshTimerId = void 0;
10968
- }
11611
+ this._refreshCoordinator?.suspend();
10969
11612
  this._diagnosticsCollector?.record("connection", "disconnected");
10970
11613
  await this.teardownTransportAndSession();
10971
11614
  this._isConnected$.next(false);
@@ -11016,21 +11659,23 @@ var SignalWire = class extends Destroyable {
11016
11659
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
11017
11660
  throw error;
11018
11661
  }
11019
- logger$1.debug("[SignalWire] Failed to register user, trying reauthentication...");
11020
- try {
11021
- await this._clientSession.reauthenticate(this._deps.credential.token);
11022
- logger$1.debug("[SignalWire] Reauthentication successful, retrying register()");
11662
+ logger$1.debug("[SignalWire] Failed to register user, attempting credential recovery...");
11663
+ let failureCause = error;
11664
+ if (await this.recoverStaleCredential(isRecoverableAuthError(error))) try {
11665
+ logger$1.debug("[SignalWire] Recovery succeeded, retrying register()");
11023
11666
  await this._transport.execute(RPCExecute({
11024
11667
  method: "subscriber.online",
11025
11668
  params: {}
11026
11669
  }));
11027
11670
  this._isRegistered$.next(true);
11028
- } catch (reauthError) {
11029
- logger$1.error("[SignalWire] Reauthentication failed during register():", reauthError);
11030
- const registerError = new require_operators.InvalidCredentialsError("Failed to register user, and reauthentication attempt also failed. Please check your credentials.", { cause: reauthError instanceof Error ? reauthError : new Error(String(reauthError), { cause: reauthError }) });
11031
- this._errors$.next(registerError);
11032
- throw registerError;
11671
+ return;
11672
+ } catch (retryError) {
11673
+ logger$1.error("[SignalWire] Register retry after recovery failed:", retryError);
11674
+ failureCause = retryError;
11033
11675
  }
11676
+ const registerError = new require_operators.InvalidCredentialsError("Failed to register user, and credential recovery also failed. Please check your credentials.", { cause: failureCause instanceof Error ? failureCause : new Error(String(failureCause), { cause: failureCause }) });
11677
+ this._errors$.next(registerError);
11678
+ throw registerError;
11034
11679
  }
11035
11680
  }
11036
11681
  /**
@@ -11083,7 +11728,15 @@ var SignalWire = class extends Destroyable {
11083
11728
  };
11084
11729
  await this.waitAuthentication();
11085
11730
  logger$1.debug("[SignalWire] Dialing with options:", computed_options);
11086
- return this._clientSession.createOutboundCall(destination, computed_options);
11731
+ try {
11732
+ return await this._clientSession.createOutboundCall(destination, computed_options);
11733
+ } catch (error) {
11734
+ if (!isRecoverableAuthError(error)) throw error;
11735
+ logger$1.debug("[SignalWire] Dial hit a recoverable auth error; recovering credential and retrying once");
11736
+ if (!await this.recoverStaleCredential()) throw error;
11737
+ await this.waitAuthentication();
11738
+ return this._clientSession.createOutboundCall(destination, computed_options);
11739
+ }
11087
11740
  }
11088
11741
  /**
11089
11742
  * Runs a multi-phase connectivity test against the given destination.
@@ -11352,17 +12005,23 @@ var SignalWire = class extends Destroyable {
11352
12005
  prefs.preferredVideoInput = null;
11353
12006
  await this._deviceController.clearDeviceState();
11354
12007
  }
11355
- /** Destroys the client, clearing timers and releasing all resources. */
12008
+ /**
12009
+ * Destroys the client, clearing timers and releasing all resources.
12010
+ *
12011
+ * Intentionally destroying the client ends its session: the resume state
12012
+ * (`authorization_state` + protocol) and the attach records are both
12013
+ * cleared. Credentials and device preferences are preserved — use
12014
+ * {@link resetToDefaults} for a full wipe. To temporarily stop receiving
12015
+ * inbound calls while keeping the session alive, use `unregister()`.
12016
+ */
11356
12017
  destroy() {
11357
- if (this._refreshTimerId) {
11358
- clearTimeout(this._refreshTimerId);
11359
- this._refreshTimerId = void 0;
11360
- }
11361
- this._deviceTokenManager?.destroy();
12018
+ this._refreshCoordinator?.destroy();
12019
+ this._refreshCoordinator = void 0;
11362
12020
  this._dpopManager?.destroy();
11363
- if (this._attachManager) this._attachManager.detachAll();
11364
- this._transport.destroy();
11365
- this._clientSession.destroy();
12021
+ const session = this._clientSession;
12022
+ session?.teardownSessionState();
12023
+ this._transport?.destroy();
12024
+ session?.destroy();
11366
12025
  try {
11367
12026
  this._networkMonitor?.destroy();
11368
12027
  } catch {}
@@ -11474,7 +12133,7 @@ var StaticCredentialProvider = class {
11474
12133
  /**
11475
12134
  * Library version from package.json, injected at build time.
11476
12135
  */
11477
- const version = "3.30.0";
12136
+ const version = "4.0.0-rc.2";
11478
12137
  /**
11479
12138
  * Flag indicating the library has been loaded and is ready to use.
11480
12139
  * For UMD builds: `window.SignalWire.ready`
@@ -11496,7 +12155,7 @@ const ready = true;
11496
12155
  */
11497
12156
  const emitReadyEvent = () => {
11498
12157
  if (typeof window !== "undefined") {
11499
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "3.30.0" } });
12158
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.2" } });
11500
12159
  window.dispatchEvent(event);
11501
12160
  }
11502
12161
  };
@@ -11505,16 +12164,19 @@ emitReadyEvent();
11505
12164
  //#endregion
11506
12165
  exports.Address = Address;
11507
12166
  exports.CallCreateError = require_operators.CallCreateError;
12167
+ exports.CallNotReadyError = require_operators.CallNotReadyError;
11508
12168
  exports.ClientPreferences = ClientPreferences;
11509
12169
  exports.CollectionFetchError = require_operators.CollectionFetchError;
11510
12170
  exports.DPoPInitError = require_operators.DPoPInitError;
11511
12171
  exports.DeviceTokenError = require_operators.DeviceTokenError;
11512
12172
  exports.EmbedTokenCredentialProvider = EmbedTokenCredentialProvider;
11513
12173
  exports.InvalidCredentialsError = require_operators.InvalidCredentialsError;
12174
+ exports.MediaAccessError = require_operators.MediaAccessError;
11514
12175
  exports.MediaTrackError = require_operators.MediaTrackError;
11515
12176
  exports.MessageParseError = require_operators.MessageParseError;
11516
12177
  exports.OverconstrainedFallbackError = require_operators.OverconstrainedFallbackError;
11517
12178
  exports.Participant = Participant;
12179
+ exports.ParticipantNotReadyError = require_operators.ParticipantNotReadyError;
11518
12180
  exports.PreflightError = require_operators.PreflightError;
11519
12181
  exports.RecoveryError = require_operators.RecoveryError;
11520
12182
  exports.SelfCapabilities = SelfCapabilities;