@signalwire/js 4.0.0-dev-20260515133934 → 4.0.0-rc.1

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
@@ -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
  /**
@@ -437,6 +437,13 @@ const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
437
437
  const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
438
438
  /** Buffer in milliseconds before token expiry to trigger refresh. */
439
439
  const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
440
+ /**
441
+ * Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
442
+ * to resolve before treating the activation as failed and falling back to
443
+ * the developer-provided refresh path. Prevents a wedged HTTP layer from
444
+ * leaving the session with no active refresh mechanism.
445
+ */
446
+ const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
440
447
  /** JSON-RPC error code for requester validation failure (corrupted auth state). */
441
448
  const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
442
449
  /** JSON-RPC error code for invalid params (e.g., missing authentication block). */
@@ -539,7 +546,7 @@ function fromMsToSec(milliseconds) {
539
546
 
540
547
  //#endregion
541
548
  //#region src/containers/PreferencesContainer.ts
542
- const logger$29 = require_operators.getLogger();
549
+ const logger$30 = require_operators.getLogger();
543
550
  var PreferencesContainer = class PreferencesContainer {
544
551
  static get instance() {
545
552
  this._instance ??= new PreferencesContainer();
@@ -1201,7 +1208,7 @@ var ClientPreferences = class {
1201
1208
  if (!this._storage) return;
1202
1209
  const data = collectStoredPreferences();
1203
1210
  this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
1204
- logger$29.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
1211
+ logger$30.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
1205
1212
  });
1206
1213
  }
1207
1214
  /** Loads preferences from storage and applies them to the container. */
@@ -1210,7 +1217,7 @@ var ClientPreferences = class {
1210
1217
  this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
1211
1218
  if (stored) applyStoredPreferences(stored);
1212
1219
  }).catch((error) => {
1213
- logger$29.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
1220
+ logger$30.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
1214
1221
  });
1215
1222
  }
1216
1223
  };
@@ -1231,7 +1238,7 @@ function toError(value) {
1231
1238
 
1232
1239
  //#endregion
1233
1240
  //#region src/controllers/NavigatorDeviceController.ts
1234
- const logger$28 = require_operators.getLogger();
1241
+ const logger$29 = require_operators.getLogger();
1235
1242
  /** Maps a device kind to its storage key. */
1236
1243
  const DEVICE_STORAGE_KEYS = {
1237
1244
  audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
@@ -1253,7 +1260,7 @@ var NavigatorDeviceController = class extends Destroyable {
1253
1260
  super();
1254
1261
  this.webRTCApiProvider = webRTCApiProvider;
1255
1262
  this.deviceChangeHandler = () => {
1256
- logger$28.debug("[DeviceController] Device change detected");
1263
+ logger$29.debug("[DeviceController] Device change detected");
1257
1264
  this.enumerateDevices();
1258
1265
  };
1259
1266
  this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
@@ -1318,13 +1325,13 @@ var NavigatorDeviceController = class extends Destroyable {
1318
1325
  return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$)));
1319
1326
  }
1320
1327
  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))));
1328
+ 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
1329
  }
1323
1330
  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))));
1331
+ 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
1332
  }
1326
1333
  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))));
1334
+ 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
1335
  }
1329
1336
  get selectedAudioInputDevice() {
1330
1337
  if (this._audioInputDisabled$.value) return null;
@@ -1399,7 +1406,7 @@ var NavigatorDeviceController = class extends Destroyable {
1399
1406
  if (device) this.persistDeviceSelection("audioinput", device);
1400
1407
  }
1401
1408
  selectVideoInputDevice(device) {
1402
- logger$28.debug("[DeviceController] Setting selected video input device:", device);
1409
+ logger$29.debug("[DeviceController] Setting selected video input device:", device);
1403
1410
  if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
1404
1411
  const previous = this._selectedDevicesState$.value.videoinput;
1405
1412
  if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
@@ -1456,7 +1463,7 @@ var NavigatorDeviceController = class extends Destroyable {
1456
1463
  }
1457
1464
  const fromHistory = this._deviceHistory.findInHistory(kind, devices);
1458
1465
  if (fromHistory) {
1459
- logger$28.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
1466
+ logger$29.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
1460
1467
  this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
1461
1468
  return fromHistory;
1462
1469
  }
@@ -1509,7 +1516,7 @@ var NavigatorDeviceController = class extends Destroyable {
1509
1516
  try {
1510
1517
  await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
1511
1518
  } catch (error) {
1512
- logger$28.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
1519
+ logger$29.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
1513
1520
  }
1514
1521
  }
1515
1522
  async loadPersistedDevices() {
@@ -1525,7 +1532,7 @@ var NavigatorDeviceController = class extends Destroyable {
1525
1532
  [kind]: stored
1526
1533
  };
1527
1534
  } catch (error) {
1528
- logger$28.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
1535
+ logger$29.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
1529
1536
  }
1530
1537
  }
1531
1538
  /** Clears device history, persisted selections, and re-enumerates devices. */
@@ -1543,7 +1550,7 @@ var NavigatorDeviceController = class extends Destroyable {
1543
1550
  this.disableDeviceMonitoring();
1544
1551
  this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
1545
1552
  if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, rxjs.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
1546
- logger$28.debug("[DeviceController] Polling devices due to interval");
1553
+ logger$29.debug("[DeviceController] Polling devices due to interval");
1547
1554
  this.enumerateDevices();
1548
1555
  });
1549
1556
  this.enumerateDevices();
@@ -1569,13 +1576,13 @@ var NavigatorDeviceController = class extends Destroyable {
1569
1576
  videoinput: []
1570
1577
  });
1571
1578
  this._devicesState$.next(devicesByKind);
1572
- logger$28.debug("[DeviceController] Devices enumerated:", {
1579
+ logger$29.debug("[DeviceController] Devices enumerated:", {
1573
1580
  audioInputs: devicesByKind.audioinput.length,
1574
1581
  audioOutputs: devicesByKind.audiooutput.length,
1575
1582
  videoInputs: devicesByKind.videoinput.length
1576
1583
  });
1577
1584
  } catch (error) {
1578
- logger$28.error("[DeviceController] Failed to enumerate devices:", error);
1585
+ logger$29.error("[DeviceController] Failed to enumerate devices:", error);
1579
1586
  this._errors$.next(toError(error));
1580
1587
  }
1581
1588
  }
@@ -1591,7 +1598,7 @@ var NavigatorDeviceController = class extends Destroyable {
1591
1598
  stream.getTracks().forEach((t) => t.stop());
1592
1599
  return capabilities;
1593
1600
  } catch (error) {
1594
- logger$28.error("[DeviceController] Failed to get device capabilities:", error);
1601
+ logger$29.error("[DeviceController] Failed to get device capabilities:", error);
1595
1602
  this._errors$.next(toError(error));
1596
1603
  throw error;
1597
1604
  }
@@ -1842,7 +1849,7 @@ var DependencyContainer = class {
1842
1849
 
1843
1850
  //#endregion
1844
1851
  //#region src/controllers/CryptoController.ts
1845
- const logger$27 = require_operators.getLogger();
1852
+ const logger$28 = require_operators.getLogger();
1846
1853
  const DPOP_DB_NAME = "sw-dpop";
1847
1854
  const DPOP_DB_VERSION = 1;
1848
1855
  const DPOP_STORE_NAME = "keys";
@@ -1901,7 +1908,7 @@ async function loadKeyPairFromDB() {
1901
1908
  tx.oncomplete = () => db.close();
1902
1909
  });
1903
1910
  } catch (error) {
1904
- logger$27.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
1911
+ logger$28.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
1905
1912
  return null;
1906
1913
  }
1907
1914
  }
@@ -1921,7 +1928,7 @@ async function saveKeyPairToDB(keyPair) {
1921
1928
  };
1922
1929
  });
1923
1930
  } catch (error) {
1924
- logger$27.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
1931
+ logger$28.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
1925
1932
  }
1926
1933
  }
1927
1934
  async function deleteKeyPairFromDB() {
@@ -1940,7 +1947,7 @@ async function deleteKeyPairFromDB() {
1940
1947
  };
1941
1948
  });
1942
1949
  } catch (error) {
1943
- logger$27.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
1950
+ logger$28.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
1944
1951
  }
1945
1952
  }
1946
1953
  /**
@@ -2000,13 +2007,13 @@ var CryptoController = class {
2000
2007
  this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
2001
2008
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
2002
2009
  this._initialized = true;
2003
- logger$27.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
2010
+ logger$28.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
2004
2011
  return this._fingerprint;
2005
2012
  } catch (error) {
2006
- logger$27.warn("[DPoP] Stored key pair unusable, generating new one:", error);
2013
+ logger$28.warn("[DPoP] Stored key pair unusable, generating new one:", error);
2007
2014
  await deleteKeyPairFromDB();
2008
2015
  }
2009
- logger$27.debug("[DPoP] Generating RSA key pair");
2016
+ logger$28.debug("[DPoP] Generating RSA key pair");
2010
2017
  this._keyPair = await crypto.subtle.generateKey({
2011
2018
  name: "RSASSA-PKCS1-v1_5",
2012
2019
  modulusLength: 2048,
@@ -2021,7 +2028,7 @@ var CryptoController = class {
2021
2028
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
2022
2029
  this._initialized = true;
2023
2030
  await saveKeyPairToDB(this._keyPair);
2024
- logger$27.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
2031
+ logger$28.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
2025
2032
  return this._fingerprint;
2026
2033
  }
2027
2034
  /**
@@ -2087,7 +2094,7 @@ var CryptoController = class {
2087
2094
  this._fingerprint = null;
2088
2095
  this._initialized = false;
2089
2096
  deleteKeyPairFromDB();
2090
- logger$27.debug("[DPoP] Controller destroyed");
2097
+ logger$28.debug("[DPoP] Controller destroyed");
2091
2098
  }
2092
2099
  get publicJwk() {
2093
2100
  if (!this._publicJwk) throw new require_operators.DPoPInitError("CryptoController not initialized. Call init() first.");
@@ -2110,7 +2117,7 @@ var CryptoController = class {
2110
2117
 
2111
2118
  //#endregion
2112
2119
  //#region src/controllers/NetworkMonitor.ts
2113
- const logger$26 = require_operators.getLogger();
2120
+ const logger$27 = require_operators.getLogger();
2114
2121
  /**
2115
2122
  * Safely check whether we are running in a browser environment
2116
2123
  * with `window` and the relevant event targets.
@@ -2167,7 +2174,7 @@ var NetworkMonitor = class extends Destroyable {
2167
2174
  }
2168
2175
  attachListeners() {
2169
2176
  if (!hasBrowserNetworkEvents()) {
2170
- logger$26.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
2177
+ logger$27.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
2171
2178
  return;
2172
2179
  }
2173
2180
  window.addEventListener("online", this._onOnline);
@@ -2175,7 +2182,7 @@ var NetworkMonitor = class extends Destroyable {
2175
2182
  const connection = getNetworkConnection();
2176
2183
  if (connection) connection.addEventListener("change", this._onConnectionChange);
2177
2184
  this._listenersAttached = true;
2178
- logger$26.debug("NetworkMonitor: event listeners attached");
2185
+ logger$27.debug("NetworkMonitor: event listeners attached");
2179
2186
  }
2180
2187
  removeListeners() {
2181
2188
  if (!this._listenersAttached) return;
@@ -2186,10 +2193,10 @@ var NetworkMonitor = class extends Destroyable {
2186
2193
  if (connection) connection.removeEventListener("change", this._onConnectionChange);
2187
2194
  }
2188
2195
  this._listenersAttached = false;
2189
- logger$26.debug("NetworkMonitor: event listeners removed");
2196
+ logger$27.debug("NetworkMonitor: event listeners removed");
2190
2197
  }
2191
2198
  handleOnline() {
2192
- logger$26.info("NetworkMonitor: browser went online");
2199
+ logger$27.info("NetworkMonitor: browser went online");
2193
2200
  this._isOnline$.next(true);
2194
2201
  this._networkChange$.next({
2195
2202
  type: "online",
@@ -2198,7 +2205,7 @@ var NetworkMonitor = class extends Destroyable {
2198
2205
  });
2199
2206
  }
2200
2207
  handleOffline() {
2201
- logger$26.info("NetworkMonitor: browser went offline");
2208
+ logger$27.info("NetworkMonitor: browser went offline");
2202
2209
  this._isOnline$.next(false);
2203
2210
  this._networkChange$.next({
2204
2211
  type: "offline",
@@ -2207,7 +2214,7 @@ var NetworkMonitor = class extends Destroyable {
2207
2214
  }
2208
2215
  handleConnectionChange() {
2209
2216
  const networkType = getNetworkType();
2210
- logger$26.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
2217
+ logger$27.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
2211
2218
  this._networkChange$.next({
2212
2219
  type: "connection_change",
2213
2220
  timestamp: Date.now(),
@@ -2322,7 +2329,7 @@ function getNavigatorMediaDevices() {
2322
2329
 
2323
2330
  //#endregion
2324
2331
  //#region src/controllers/PreflightRunner.ts
2325
- const logger$25 = require_operators.getLogger();
2332
+ const logger$26 = require_operators.getLogger();
2326
2333
  const DEFAULT_MEDIA_TEST_DURATION_S = 10;
2327
2334
  const ICE_GATHERING_TIMEOUT_MS = 1e4;
2328
2335
  const SIGNALING_RTT_TIMEOUT_MS = 5e3;
@@ -2371,7 +2378,7 @@ var PreflightRunner = class extends Destroyable {
2371
2378
  if (!this._options.skipMediaTest) try {
2372
2379
  bandwidth = await this.testMediaBandwidth(destination);
2373
2380
  } catch (error) {
2374
- logger$25.warn("[PreflightRunner] Media bandwidth test failed:", error);
2381
+ logger$26.warn("[PreflightRunner] Media bandwidth test failed:", error);
2375
2382
  warnings.push("Media bandwidth test failed");
2376
2383
  }
2377
2384
  return {
@@ -2383,7 +2390,7 @@ var PreflightRunner = class extends Destroyable {
2383
2390
  warnings
2384
2391
  };
2385
2392
  } catch (error) {
2386
- logger$25.error("[PreflightRunner] Preflight test failed:", error);
2393
+ logger$26.error("[PreflightRunner] Preflight test failed:", error);
2387
2394
  throw new require_operators.PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
2388
2395
  } finally {
2389
2396
  this.destroy();
@@ -2414,7 +2421,7 @@ var PreflightRunner = class extends Destroyable {
2414
2421
  if (track.kind === "video" && track.readyState === "live") videoWorking = true;
2415
2422
  }
2416
2423
  } catch (error) {
2417
- logger$25.warn("[PreflightRunner] Device test failed:", error);
2424
+ logger$26.warn("[PreflightRunner] Device test failed:", error);
2418
2425
  } finally {
2419
2426
  if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
2420
2427
  }
@@ -2472,7 +2479,7 @@ var PreflightRunner = class extends Destroyable {
2472
2479
  rttMs
2473
2480
  };
2474
2481
  } catch (error) {
2475
- logger$25.warn("[PreflightRunner] ICE connectivity test failed:", error);
2482
+ logger$26.warn("[PreflightRunner] ICE connectivity test failed:", error);
2476
2483
  return {
2477
2484
  type: "failed",
2478
2485
  turnReachable: false,
@@ -2519,7 +2526,7 @@ var PreflightRunner = class extends Destroyable {
2519
2526
 
2520
2527
  //#endregion
2521
2528
  //#region src/controllers/VisibilityController.ts
2522
- const logger$24 = require_operators.getLogger();
2529
+ const logger$25 = require_operators.getLogger();
2523
2530
  /**
2524
2531
  * Checks whether the document visibility API is available.
2525
2532
  */
@@ -2556,8 +2563,8 @@ var VisibilityController = class extends Destroyable {
2556
2563
  this._boundHandler = this._handleVisibilityChange.bind(this);
2557
2564
  if (this._hasVisibilityApi) {
2558
2565
  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");
2566
+ logger$25.debug("VisibilityController: listening for visibilitychange events");
2567
+ } else logger$25.debug("VisibilityController: document visibility API not available, defaulting to visible");
2561
2568
  }
2562
2569
  /**
2563
2570
  * Observable of the current visibility state.
@@ -2582,7 +2589,7 @@ var VisibilityController = class extends Destroyable {
2582
2589
  destroy() {
2583
2590
  if (this._hasVisibilityApi) {
2584
2591
  document.removeEventListener("visibilitychange", this._boundHandler);
2585
- logger$24.debug("VisibilityController: removed visibilitychange listener");
2592
+ logger$25.debug("VisibilityController: removed visibilitychange listener");
2586
2593
  }
2587
2594
  super.destroy();
2588
2595
  }
@@ -2600,7 +2607,7 @@ var VisibilityController = class extends Destroyable {
2600
2607
  timestamp: Date.now()
2601
2608
  };
2602
2609
  this._visibilityChange$.next(changeEvent);
2603
- logger$24.debug("VisibilityController: visibility changed", {
2610
+ logger$25.debug("VisibilityController: visibility changed", {
2604
2611
  from: previousState,
2605
2612
  to: newState
2606
2613
  });
@@ -2803,7 +2810,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
2803
2810
 
2804
2811
  //#endregion
2805
2812
  //#region src/managers/AttachManager.ts
2806
- const logger$23 = require_operators.getLogger();
2813
+ const logger$24 = require_operators.getLogger();
2807
2814
  var AttachManager = class {
2808
2815
  constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
2809
2816
  this.storage = storage;
@@ -2824,7 +2831,7 @@ var AttachManager = class {
2824
2831
  try {
2825
2832
  return await this.storage.getItem(this.attachKey) ?? {};
2826
2833
  } catch (error) {
2827
- logger$23.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
2834
+ logger$24.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
2828
2835
  return {};
2829
2836
  }
2830
2837
  }
@@ -2832,7 +2839,7 @@ var AttachManager = class {
2832
2839
  try {
2833
2840
  await this.storage.setItem(this.attachKey, attached);
2834
2841
  } catch (error) {
2835
- logger$23.warn("[AttachManager] Failed to write attached calls to storage", error);
2842
+ logger$24.warn("[AttachManager] Failed to write attached calls to storage", error);
2836
2843
  }
2837
2844
  }
2838
2845
  /**
@@ -2851,7 +2858,7 @@ var AttachManager = class {
2851
2858
  }
2852
2859
  async attach(call) {
2853
2860
  if (!call.to) {
2854
- logger$23.warn("[AttachManager] Skip attach for calls with no destination");
2861
+ logger$24.warn("[AttachManager] Skip attach for calls with no destination");
2855
2862
  return;
2856
2863
  }
2857
2864
  const destination = call.to;
@@ -2904,15 +2911,15 @@ var AttachManager = class {
2904
2911
  callId,
2905
2912
  ...options
2906
2913
  });
2907
- logger$23.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
2914
+ logger$24.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
2908
2915
  succeeded = true;
2909
2916
  break;
2910
2917
  } catch (error) {
2911
- logger$23.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
2918
+ logger$24.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
2912
2919
  if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
2913
2920
  }
2914
2921
  if (!succeeded) {
2915
- logger$23.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
2922
+ logger$24.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
2916
2923
  await this.detach({
2917
2924
  id: callId,
2918
2925
  mediaDirections: attachment.mediaDirections
@@ -2998,7 +3005,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
2998
3005
  position: false,
2999
3006
  meta: false,
3000
3007
  remove: false,
3001
- audioFlags: false
3008
+ audioFlags: false,
3009
+ denoise: false,
3010
+ lowbitrate: false
3002
3011
  };
3003
3012
  /**
3004
3013
  * Default call capabilities with no permissions
@@ -3071,7 +3080,9 @@ function computeMemberCapabilities(flags, memberType) {
3071
3080
  position: hasBooleanCapability(flags, memberType, null, "position"),
3072
3081
  meta: hasBooleanCapability(flags, memberType, null, "meta"),
3073
3082
  remove: hasBooleanCapability(flags, memberType, null, "remove"),
3074
- audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
3083
+ audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
3084
+ denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
3085
+ lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
3075
3086
  };
3076
3087
  }
3077
3088
  /**
@@ -3226,7 +3237,7 @@ function toggleHandraiseMethod(is) {
3226
3237
 
3227
3238
  //#endregion
3228
3239
  //#region src/core/entities/Participant.ts
3229
- const logger$22 = require_operators.getLogger();
3240
+ const logger$23 = require_operators.getLogger();
3230
3241
  const initialState = {};
3231
3242
  /**
3232
3243
  * Represents a participant in a call.
@@ -3452,6 +3463,10 @@ var Participant = class extends Destroyable {
3452
3463
  get nodeId() {
3453
3464
  return this._state$.value.node_id;
3454
3465
  }
3466
+ /** Call ID for this participant's leg, or `undefined` if not available. */
3467
+ get callId() {
3468
+ return this._state$.value.call_id;
3469
+ }
3455
3470
  /** @internal */
3456
3471
  get value() {
3457
3472
  return this._state$.value;
@@ -3513,8 +3528,9 @@ var Participant = class extends Destroyable {
3513
3528
  noise_suppression: !this.noiseSuppression
3514
3529
  });
3515
3530
  }
3531
+ /** Toggles low-bitrate mode for this participant's media. */
3516
3532
  async toggleLowbitrate() {
3517
- throw new require_operators.UnimplementedError();
3533
+ await this.executeMethod(this.id, "call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
3518
3534
  }
3519
3535
  /**
3520
3536
  * Adjusts the **conference-only** microphone energy gate / sensitivity level
@@ -3561,10 +3577,27 @@ var Participant = class extends Destroyable {
3561
3577
  }
3562
3578
  /**
3563
3579
  * Sets the participant's position in the video layout.
3580
+ *
3581
+ * Requires the `member.position` capability. The gateway keys positions by the
3582
+ * **target member's own** `call_id`/`node_id` (see issue #19400 and the legacy
3583
+ * `setPositions` implementation), so this sends the participant's own call
3584
+ * context — matching {@link Participant.remove}. A resolved promise does not
3585
+ * guarantee a visible change: the backend silently returns `200` (no-op) for
3586
+ * non-conference targets.
3587
+ *
3564
3588
  * @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
3565
3589
  */
3566
3590
  async setPosition(value) {
3567
- await this.executeMethod(this.id, "call.member.position.set", { position: value });
3591
+ const state = this._state$.value;
3592
+ const target = {
3593
+ member_id: this.id,
3594
+ call_id: state.call_id ?? "",
3595
+ node_id: state.node_id ?? ""
3596
+ };
3597
+ await this.executeMethod(target, "call.member.position.set", { targets: [{
3598
+ target,
3599
+ position: value
3600
+ }] });
3568
3601
  }
3569
3602
  /** Removes this participant from the call. */
3570
3603
  async remove() {
@@ -3659,7 +3692,7 @@ var SelfParticipant = class extends Participant {
3659
3692
  try {
3660
3693
  await this.vertoManager.addScreenMedia();
3661
3694
  } catch (error) {
3662
- logger$22.error("[Participant.startScreenShare] Screen share error:", error);
3695
+ logger$23.error("[Participant.startScreenShare] Screen share error:", error);
3663
3696
  }
3664
3697
  }
3665
3698
  /** Observable of the current screen share status. */
@@ -3679,7 +3712,7 @@ var SelfParticipant = class extends Participant {
3679
3712
  try {
3680
3713
  await this.vertoManager.addInputDevice(options);
3681
3714
  } catch (error) {
3682
- logger$22.error("[Participant.startScreenShare] Screen share error:", error);
3715
+ logger$23.error("[Participant.startScreenShare] Screen share error:", error);
3683
3716
  }
3684
3717
  }
3685
3718
  /** Removes an additional media input device by ID. */
@@ -3741,7 +3774,7 @@ var SelfParticipant = class extends Participant {
3741
3774
  */
3742
3775
  exitStudioModeIfActive() {
3743
3776
  if (this._studioAudio$.value) {
3744
- logger$22.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
3777
+ logger$23.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
3745
3778
  this._studioAudio$.next(false);
3746
3779
  }
3747
3780
  }
@@ -3765,7 +3798,7 @@ var SelfParticipant = class extends Participant {
3765
3798
  try {
3766
3799
  await super.mute();
3767
3800
  } catch (error) {
3768
- logger$22.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
3801
+ logger$23.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
3769
3802
  } finally {
3770
3803
  this.vertoManager.muteMainAudioInputDevice();
3771
3804
  }
@@ -3775,7 +3808,7 @@ var SelfParticipant = class extends Participant {
3775
3808
  try {
3776
3809
  await super.unmute();
3777
3810
  } catch (error) {
3778
- logger$22.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
3811
+ logger$23.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
3779
3812
  } finally {
3780
3813
  await this.vertoManager.unmuteMainAudioInputDevice();
3781
3814
  }
@@ -3785,7 +3818,7 @@ var SelfParticipant = class extends Participant {
3785
3818
  try {
3786
3819
  await super.muteVideo();
3787
3820
  } catch (error) {
3788
- logger$22.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
3821
+ logger$23.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
3789
3822
  } finally {
3790
3823
  this.vertoManager.muteMainVideoInputDevice();
3791
3824
  }
@@ -3795,7 +3828,7 @@ var SelfParticipant = class extends Participant {
3795
3828
  try {
3796
3829
  await super.unmuteVideo();
3797
3830
  } catch (error) {
3798
- logger$22.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
3831
+ logger$23.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
3799
3832
  } finally {
3800
3833
  await this.vertoManager.unmuteMainVideoInputDevice();
3801
3834
  }
@@ -3889,7 +3922,7 @@ function isLayoutChangedPayload(value) {
3889
3922
 
3890
3923
  //#endregion
3891
3924
  //#region src/managers/CallEventsManager.ts
3892
- const logger$21 = require_operators.getLogger();
3925
+ const logger$22 = require_operators.getLogger();
3893
3926
  const initialSessionState = {};
3894
3927
  /** @internal */
3895
3928
  var CallEventsManager = class extends Destroyable {
@@ -3993,7 +4026,7 @@ var CallEventsManager = class extends Destroyable {
3993
4026
  }
3994
4027
  initSubscriptions() {
3995
4028
  this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
3996
- logger$21.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
4029
+ logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
3997
4030
  callId: callJoinedEvent.call_id,
3998
4031
  roomSessionId: callJoinedEvent.room_session_id
3999
4032
  });
@@ -4020,19 +4053,19 @@ var CallEventsManager = class extends Destroyable {
4020
4053
  if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
4021
4054
  });
4022
4055
  this.subscribeTo(this.memberUpdates$, (member) => {
4023
- logger$21.debug("[CallEventsManager] Handling member update event for member ID:", member);
4056
+ logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
4024
4057
  this.upsertParticipant(member);
4025
4058
  });
4026
4059
  this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
4027
- logger$21.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
4060
+ logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
4028
4061
  const participants = { ...this._participants$.value };
4029
4062
  if (memberLeftEvent.member.member_id in participants) {
4030
4063
  delete participants[memberLeftEvent.member.member_id];
4031
4064
  this._participants$.next(participants);
4032
- } else logger$21.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
4065
+ } else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
4033
4066
  });
4034
4067
  this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
4035
- logger$21.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
4068
+ logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
4036
4069
  const roomSession = callUpdatedEvent.room_session;
4037
4070
  this._sessionState$.next({
4038
4071
  ...this._sessionState$.value,
@@ -4047,7 +4080,7 @@ var CallEventsManager = class extends Destroyable {
4047
4080
  });
4048
4081
  });
4049
4082
  this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
4050
- logger$21.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
4083
+ logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
4051
4084
  this._sessionState$.next({
4052
4085
  ...this._sessionState$.value,
4053
4086
  layout_name: layoutChangedEvent.id,
@@ -4057,10 +4090,10 @@ var CallEventsManager = class extends Destroyable {
4057
4090
  });
4058
4091
  }
4059
4092
  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.");
4093
+ 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
4094
  layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
4062
4095
  if (!(layer.member_id in this._participants$.value)) {
4063
- logger$21.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
4096
+ logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
4064
4097
  return false;
4065
4098
  }
4066
4099
  return true;
@@ -4083,7 +4116,7 @@ var CallEventsManager = class extends Destroyable {
4083
4116
  layouts: response.result.layouts
4084
4117
  });
4085
4118
  }).catch((error) => {
4086
- logger$21.error("[CallEventsManager] Error fetching layouts:", error);
4119
+ logger$22.error("[CallEventsManager] Error fetching layouts:", error);
4087
4120
  });
4088
4121
  }
4089
4122
  updateParticipants(members) {
@@ -4099,7 +4132,7 @@ var CallEventsManager = class extends Destroyable {
4099
4132
  }
4100
4133
  const participant = this._participants$.value[member.member_id];
4101
4134
  const oldValue = participant.value;
4102
- logger$21.debug("[CallEventsManager] Updating participant:", member.member_id, {
4135
+ logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
4103
4136
  oldValue,
4104
4137
  newValue: member
4105
4138
  });
@@ -4112,17 +4145,17 @@ var CallEventsManager = class extends Destroyable {
4112
4145
  }
4113
4146
  get callJoinedEvent$() {
4114
4147
  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);
4148
+ logger$22.debug("[CallEventsManager] Call joined event:", event);
4116
4149
  })));
4117
4150
  }
4118
4151
  get layoutChangedEvent$() {
4119
4152
  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);
4153
+ logger$22.debug("[CallEventsManager] Layout changed event:", event);
4121
4154
  })));
4122
4155
  }
4123
4156
  get memberUpdates$() {
4124
4157
  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);
4158
+ logger$22.debug("[CallEventsManager] Member update event:", event);
4126
4159
  })));
4127
4160
  }
4128
4161
  destroy() {
@@ -4378,7 +4411,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
4378
4411
 
4379
4412
  //#endregion
4380
4413
  //#region src/controllers/ICEGatheringController.ts
4381
- const logger$20 = require_operators.getLogger();
4414
+ const logger$21 = require_operators.getLogger();
4382
4415
  var ICEGatheringController = class extends Destroyable {
4383
4416
  constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
4384
4417
  super();
@@ -4386,23 +4419,23 @@ var ICEGatheringController = class extends Destroyable {
4386
4419
  this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
4387
4420
  this.onicegatheringstatechangeHandler = () => {
4388
4421
  const { iceGatheringState } = this.peerConnection;
4389
- logger$20.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
4422
+ logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
4390
4423
  if (iceGatheringState === "gathering") this._iceCandidatesState.next({
4391
4424
  state: "gathering",
4392
4425
  validSDP: false
4393
4426
  });
4394
4427
  };
4395
4428
  this.onicecandidateHandler = (event) => {
4396
- logger$20.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
4429
+ logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
4397
4430
  this.removeTimer("iceCandidateTimer");
4398
4431
  if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
4399
4432
  if (this.peerConnection.iceGatheringState !== "complete") {
4400
- logger$20.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
4433
+ logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
4401
4434
  this.handleICECandidateTimeout();
4402
4435
  }
4403
4436
  }, this.iceCandidateTimeout);
4404
4437
  else {
4405
- logger$20.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
4438
+ logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
4406
4439
  this.removeTimer("iceGatheringTimer");
4407
4440
  this.handleICEGatheringComplete();
4408
4441
  }
@@ -4420,7 +4453,7 @@ var ICEGatheringController = class extends Destroyable {
4420
4453
  this.setupEventListeners();
4421
4454
  this.iceGatheringTimer = setTimeout(() => {
4422
4455
  if (this.peerConnection.iceGatheringState !== "complete") {
4423
- logger$20.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
4456
+ logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
4424
4457
  this.handleICEGatheringTimeout();
4425
4458
  }
4426
4459
  }, this.iceGatheringTimeout);
@@ -4447,9 +4480,9 @@ var ICEGatheringController = class extends Destroyable {
4447
4480
  this.relayOnly = value;
4448
4481
  }
4449
4482
  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");
4483
+ logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
4484
+ logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
4485
+ logger$21.debug("[ICEGatheringController] ICE gathering complete");
4453
4486
  this._iceCandidatesState.next({
4454
4487
  state: "complete",
4455
4488
  validSDP: this.hasValidLocalDescriptionSDP
@@ -4465,21 +4498,21 @@ var ICEGatheringController = class extends Destroyable {
4465
4498
  this.removeTimer("iceGatheringTimer");
4466
4499
  const validSDP = this.hasValidLocalDescriptionSDP;
4467
4500
  if (validSDP) {
4468
- logger$20.debug("[ICEGatheringController] Local SDP is valid");
4501
+ logger$21.debug("[ICEGatheringController] Local SDP is valid");
4469
4502
  this._iceCandidatesState.next({
4470
4503
  state: "timeout",
4471
4504
  validSDP
4472
4505
  });
4473
4506
  this.stopGathering();
4474
- } else logger$20.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
4507
+ } else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
4475
4508
  }
4476
4509
  handleICECandidateTimeout() {
4477
4510
  if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
4478
- logger$20.warn("[ICEGatheringController] ICE candidate timeout");
4511
+ logger$21.warn("[ICEGatheringController] ICE candidate timeout");
4479
4512
  const validSDP = this.hasValidLocalDescriptionSDP;
4480
4513
  if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
4481
4514
  else {
4482
- logger$20.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
4515
+ logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
4483
4516
  this._iceCandidatesState.next({
4484
4517
  state: "timeout",
4485
4518
  validSDP
@@ -4488,7 +4521,7 @@ var ICEGatheringController = class extends Destroyable {
4488
4521
  }
4489
4522
  }
4490
4523
  restartICEGatheringWithRelayOnly() {
4491
- logger$20.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
4524
+ logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
4492
4525
  this.relayOnly = true;
4493
4526
  this.peerConnection.setConfiguration({
4494
4527
  ...this.peerConnection.getConfiguration(),
@@ -4503,7 +4536,7 @@ var ICEGatheringController = class extends Destroyable {
4503
4536
  }
4504
4537
  }
4505
4538
  clearAllTimers() {
4506
- logger$20.debug("[ICEGatheringController] Clearing all timers");
4539
+ logger$21.debug("[ICEGatheringController] Clearing all timers");
4507
4540
  this.removeTimer("iceGatheringTimer");
4508
4541
  this.removeTimer("iceCandidateTimer");
4509
4542
  }
@@ -4512,7 +4545,7 @@ var ICEGatheringController = class extends Destroyable {
4512
4545
  this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
4513
4546
  }
4514
4547
  destroy() {
4515
- logger$20.debug("[ICEGatheringController] Destroying ICEGatheringController");
4548
+ logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
4516
4549
  this.clearAllTimers();
4517
4550
  this.removeEventListeners();
4518
4551
  super.destroy();
@@ -4521,7 +4554,7 @@ var ICEGatheringController = class extends Destroyable {
4521
4554
 
4522
4555
  //#endregion
4523
4556
  //#region src/controllers/LocalAudioPipeline.ts
4524
- const logger$19 = require_operators.getLogger();
4557
+ const logger$20 = require_operators.getLogger();
4525
4558
  /**
4526
4559
  * Web Audio pipeline for the local microphone stream.
4527
4560
  *
@@ -4633,7 +4666,7 @@ var LocalAudioPipeline = class extends Destroyable {
4633
4666
  try {
4634
4667
  this._inputSource.disconnect();
4635
4668
  } catch (error) {
4636
- logger$19.debug("[LocalAudioPipeline] input disconnect warning:", error);
4669
+ logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
4637
4670
  }
4638
4671
  this._inputSource = null;
4639
4672
  }
@@ -4643,7 +4676,7 @@ var LocalAudioPipeline = class extends Destroyable {
4643
4676
  this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
4644
4677
  this._inputSource.connect(this._gainNode);
4645
4678
  if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
4646
- logger$19.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
4679
+ logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
4647
4680
  });
4648
4681
  }
4649
4682
  destroy() {
@@ -4658,7 +4691,7 @@ var LocalAudioPipeline = class extends Destroyable {
4658
4691
  this._analyser.disconnect();
4659
4692
  } catch {}
4660
4693
  this._audioContext.close().catch((error) => {
4661
- logger$19.debug("[LocalAudioPipeline] audio context close warning:", error);
4694
+ logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
4662
4695
  });
4663
4696
  super.destroy();
4664
4697
  }
@@ -4684,7 +4717,7 @@ var LocalAudioPipeline = class extends Destroyable {
4684
4717
 
4685
4718
  //#endregion
4686
4719
  //#region src/controllers/LocalStreamController.ts
4687
- const logger$18 = require_operators.getLogger();
4720
+ const logger$19 = require_operators.getLogger();
4688
4721
  var LocalStreamController = class extends Destroyable {
4689
4722
  constructor(options) {
4690
4723
  super();
@@ -4722,26 +4755,26 @@ var LocalStreamController = class extends Destroyable {
4722
4755
  * Build the local media stream based on the provided options.
4723
4756
  */
4724
4757
  async buildLocalStream() {
4725
- logger$18.debug("[LocalStreamController] Building local media stream.");
4758
+ logger$19.debug("[LocalStreamController] Building local media stream.");
4726
4759
  let stream;
4727
4760
  if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
4728
4761
  const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
4729
4762
  stream = new MediaStream(tracks);
4730
4763
  } else if (this.options.propose === "screenshare") {
4731
- logger$18.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
4764
+ logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
4732
4765
  stream = await this.options.getDisplayMedia({
4733
4766
  video: true,
4734
4767
  audio: Boolean(this.options.inputAudioDeviceConstraints)
4735
4768
  });
4736
- logger$18.debug("[LocalStreamController] Screen share media obtained:", stream);
4769
+ logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
4737
4770
  } else {
4738
4771
  const constraints = {
4739
4772
  audio: this.options.inputAudioDeviceConstraints,
4740
4773
  video: this.options.inputVideoDeviceConstraints
4741
4774
  };
4742
- logger$18.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
4775
+ logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
4743
4776
  stream = await this.options.getUserMedia(constraints);
4744
- logger$18.debug("[LocalStreamController] User media obtained:", stream);
4777
+ logger$19.debug("[LocalStreamController] User media obtained:", stream);
4745
4778
  }
4746
4779
  this._localStream$.next(stream);
4747
4780
  this._localAudioTracks$.next(stream.getAudioTracks());
@@ -4760,7 +4793,7 @@ var LocalStreamController = class extends Destroyable {
4760
4793
  this._localStream$.next(localStream);
4761
4794
  if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
4762
4795
  else this._localAudioTracks$.next(localStream.getAudioTracks());
4763
- logger$18.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
4796
+ logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
4764
4797
  return localStream;
4765
4798
  }
4766
4799
  /**
@@ -4772,7 +4805,7 @@ var LocalStreamController = class extends Destroyable {
4772
4805
  const stream = this._localStream$.value;
4773
4806
  const track = stream?.getTracks().find((t) => t.id === trackId);
4774
4807
  if (!track) {
4775
- logger$18.debug(`[LocalStreamController] track not found: ${trackId}`);
4808
+ logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
4776
4809
  return;
4777
4810
  }
4778
4811
  track.removeEventListener("ended", this.mediaTrackEndedHandler);
@@ -4781,7 +4814,7 @@ var LocalStreamController = class extends Destroyable {
4781
4814
  this._localStream$.next(stream);
4782
4815
  if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
4783
4816
  else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
4784
- logger$18.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
4817
+ logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
4785
4818
  return track;
4786
4819
  }
4787
4820
  /**
@@ -4816,7 +4849,7 @@ var LocalStreamController = class extends Destroyable {
4816
4849
  */
4817
4850
  stopAllTracks() {
4818
4851
  this._localStream$.value?.getTracks().forEach((track) => {
4819
- logger$18.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
4852
+ logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
4820
4853
  track.removeEventListener("ended", this.mediaTrackEndedHandler);
4821
4854
  track.stop();
4822
4855
  });
@@ -4832,7 +4865,7 @@ var LocalStreamController = class extends Destroyable {
4832
4865
 
4833
4866
  //#endregion
4834
4867
  //#region src/controllers/TransceiverController.ts
4835
- const logger$17 = require_operators.getLogger();
4868
+ const logger$18 = require_operators.getLogger();
4836
4869
  const getDirection = (send, recv) => {
4837
4870
  if (send && recv) return "sendrecv";
4838
4871
  else if (send && !recv) return "sendonly";
@@ -4940,7 +4973,7 @@ var TransceiverController = class extends Destroyable {
4940
4973
  sendEncodings: isAudio ? void 0 : this.sendEncodings,
4941
4974
  streams: direction === "recvonly" ? void 0 : [localStream]
4942
4975
  };
4943
- logger$17.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
4976
+ logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
4944
4977
  transceiver,
4945
4978
  transceiverParams
4946
4979
  });
@@ -4948,11 +4981,11 @@ var TransceiverController = class extends Destroyable {
4948
4981
  await transceiver.sender.replaceTrack(track);
4949
4982
  transceiver.direction = transceiverParams.direction;
4950
4983
  if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
4951
- logger$17.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
4984
+ logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
4952
4985
  transceiver.sender.setStreams(...transceiverParams.streams);
4953
4986
  }
4954
4987
  } else {
4955
- logger$17.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
4988
+ logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
4956
4989
  this.peerConnection.addTransceiver(track, transceiverParams);
4957
4990
  }
4958
4991
  }
@@ -4966,13 +4999,13 @@ var TransceiverController = class extends Destroyable {
4966
4999
  if (options.updateTransceiverDirection) transceiver.direction = "inactive";
4967
5000
  }
4968
5001
  } catch (error) {
4969
- logger$17.error("[TransceiverController] stopTrackSender error", kind, error);
5002
+ logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
4970
5003
  this.options.onError?.(new require_operators.MediaTrackError("stopTrackSender", kind, error));
4971
5004
  }
4972
5005
  }
4973
5006
  async restoreTrackSender(kind) {
4974
5007
  try {
4975
- logger$17.debug("[TransceiverController] restoreTrackSender called", kind);
5008
+ logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
4976
5009
  const constraints = {};
4977
5010
  const transceivers = this.transceiverByKind(kind);
4978
5011
  for (const transceiver of transceivers) {
@@ -4982,23 +5015,23 @@ var TransceiverController = class extends Destroyable {
4982
5015
  if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
4983
5016
  }
4984
5017
  }
4985
- logger$17.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
5018
+ logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
4986
5019
  if (Object.keys(constraints).length === 0) {
4987
- logger$17.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
5020
+ logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
4988
5021
  return;
4989
5022
  }
4990
5023
  const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
4991
- logger$17.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
5024
+ logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
4992
5025
  for (const newTrack of newTracks) {
4993
5026
  this.options.localStreamController.addTrack(newTrack);
4994
5027
  const trackKind = newTrack.kind;
4995
5028
  const transceiverOfKind = this.transceiverByKind(trackKind)[0];
4996
5029
  transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
4997
- logger$17.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
5030
+ logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
4998
5031
  await transceiverOfKind.sender.replaceTrack(newTrack);
4999
5032
  }
5000
5033
  } catch (error) {
5001
- logger$17.error("[TransceiverController] restoreTrackSender error", kind, error);
5034
+ logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
5002
5035
  this.options.onError?.(new require_operators.MediaTrackError("restoreTrackSender", kind, error));
5003
5036
  }
5004
5037
  }
@@ -5039,14 +5072,14 @@ var TransceiverController = class extends Destroyable {
5039
5072
  };
5040
5073
  try {
5041
5074
  await track.applyConstraints(constraintsToApply);
5042
- logger$17.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
5043
- logger$17.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
5075
+ logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
5076
+ logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
5044
5077
  } catch (error) {
5045
- logger$17.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
5078
+ logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
5046
5079
  try {
5047
5080
  await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
5048
5081
  } catch (fallbackError) {
5049
- logger$17.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
5082
+ logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
5050
5083
  this.options.onError?.(new require_operators.MediaTrackError("updateSendersConstraints", kind, fallbackError));
5051
5084
  }
5052
5085
  }
@@ -5074,7 +5107,7 @@ var TransceiverController = class extends Destroyable {
5074
5107
  if (!newTrack) throw new require_operators.MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
5075
5108
  await sender.replaceTrack(newTrack);
5076
5109
  this.options.localStreamController.addTrack(newTrack);
5077
- logger$17.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
5110
+ logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
5078
5111
  }
5079
5112
  getMediaDirections() {
5080
5113
  if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
@@ -5104,7 +5137,7 @@ var TransceiverController = class extends Destroyable {
5104
5137
 
5105
5138
  //#endregion
5106
5139
  //#region src/controllers/RTCPeerConnectionController.ts
5107
- const logger$16 = require_operators.getLogger();
5140
+ const logger$17 = require_operators.getLogger();
5108
5141
  var RTCPeerConnectionController = class extends Destroyable {
5109
5142
  constructor(options = {}, remoteSessionDescription, deviceController) {
5110
5143
  super();
@@ -5120,43 +5153,43 @@ var RTCPeerConnectionController = class extends Destroyable {
5120
5153
  this.oniceconnectionstatechangeHandler = () => {
5121
5154
  if (this.peerConnection) {
5122
5155
  const { iceConnectionState } = this.peerConnection;
5123
- logger$16.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
5156
+ logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
5124
5157
  this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
5125
5158
  }
5126
5159
  };
5127
5160
  this.onconnectionstatechangeHandler = () => {
5128
5161
  if (this.peerConnection) {
5129
5162
  const { connectionState } = this.peerConnection;
5130
- logger$16.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
5163
+ logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
5131
5164
  if (connectionState === "connected") this.removeConnectionTimer();
5132
5165
  this._connectionState$.next(this.peerConnection.connectionState);
5133
5166
  }
5134
5167
  };
5135
5168
  this.onsignalingstatechangeHandler = () => {
5136
- logger$16.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
5169
+ logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
5137
5170
  };
5138
5171
  this.onicegatheringstatechangeHandler = () => {
5139
5172
  if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
5140
5173
  };
5141
5174
  this.onnegotiationneededHandler = (event) => {
5142
- logger$16.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
5175
+ logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
5143
5176
  this.negotiationNeeded$.next();
5144
5177
  };
5145
5178
  this.updateSelectedInputDevice = async (kind, deviceInfo) => {
5146
5179
  try {
5147
5180
  const { localStream } = this;
5148
5181
  if (!localStream) {
5149
- logger$16.warn("[RTCPeerConnectionController] No local stream available to update input device.");
5182
+ logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
5150
5183
  return;
5151
5184
  }
5152
- logger$16.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
5185
+ logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
5153
5186
  const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
5154
5187
  if (track) {
5155
5188
  this.transceiverController?.stopTrackSender(kind);
5156
5189
  this.localStreamController.removeTrack(track.id);
5157
- logger$16.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
5190
+ logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
5158
5191
  if (!deviceInfo) {
5159
- logger$16.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
5192
+ logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
5160
5193
  return;
5161
5194
  }
5162
5195
  const streamTrack = (await this.getUserMedia({ [kind]: {
@@ -5164,15 +5197,15 @@ var RTCPeerConnectionController = class extends Destroyable {
5164
5197
  ...this.deviceController.deviceInfoToConstraints(deviceInfo)
5165
5198
  } })).getTracks().find((t) => t.kind === kind);
5166
5199
  if (streamTrack) {
5167
- logger$16.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
5200
+ logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
5168
5201
  this.localStreamController.addTrack(streamTrack);
5169
5202
  await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
5170
- logger$16.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
5203
+ logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
5171
5204
  }
5172
5205
  }
5173
- logger$16.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
5206
+ logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
5174
5207
  } catch (error) {
5175
- logger$16.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
5208
+ logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
5176
5209
  this._errors$.next(toError(error));
5177
5210
  throw error;
5178
5211
  }
@@ -5422,15 +5455,15 @@ var RTCPeerConnectionController = class extends Destroyable {
5422
5455
  this.setupPeerConnection();
5423
5456
  this.subscribeTo(this.negotiationNeeded$.pipe((0, rxjs.auditTime)(0), (0, rxjs.exhaustMap)(async () => this.startNegotiation())), {
5424
5457
  next: () => {
5425
- logger$16.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
5458
+ logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
5426
5459
  },
5427
5460
  error: (error) => {
5428
- logger$16.error("[RTCPeerConnectionController] Start Negotiation error:", error);
5461
+ logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
5429
5462
  this._errors$.next(toError(error));
5430
5463
  }
5431
5464
  });
5432
5465
  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]) => {
5433
- logger$16.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
5466
+ logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
5434
5467
  kind,
5435
5468
  deviceInfo
5436
5469
  });
@@ -5447,7 +5480,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5447
5480
  this._initialized$.next(true);
5448
5481
  }
5449
5482
  } catch (error) {
5450
- logger$16.error("[RTCPeerConnectionController] Initialization error:", error);
5483
+ logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
5451
5484
  this._errors$.next(toError(error));
5452
5485
  this.destroy();
5453
5486
  }
@@ -5479,22 +5512,22 @@ var RTCPeerConnectionController = class extends Destroyable {
5479
5512
  }
5480
5513
  async startNegotiation() {
5481
5514
  if (this.isNegotiating) {
5482
- logger$16.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
5515
+ logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
5483
5516
  return;
5484
5517
  }
5485
5518
  this.setupEventListeners();
5486
5519
  if (this.type === "answer") {
5487
- logger$16.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
5520
+ logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
5488
5521
  return;
5489
5522
  }
5490
5523
  this._isNegotiating$.next(true);
5491
- logger$16.debug("[RTCPeerConnectionController] Starting negotiation.");
5524
+ logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
5492
5525
  try {
5493
5526
  const { offerOptions } = this;
5494
- logger$16.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
5527
+ logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
5495
5528
  await this.createOffer(offerOptions);
5496
5529
  } catch (error) {
5497
- logger$16.error("[RTCPeerConnectionController] Error during negotiation:", error);
5530
+ logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
5498
5531
  this._errors$.next(toError(error));
5499
5532
  }
5500
5533
  }
@@ -5510,14 +5543,14 @@ var RTCPeerConnectionController = class extends Destroyable {
5510
5543
  let readyToConnect = status !== "failed";
5511
5544
  try {
5512
5545
  if (status === "received" && sdp) {
5513
- logger$16.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
5546
+ logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
5514
5547
  await this._setRemoteDescription({
5515
5548
  type: "answer",
5516
5549
  sdp
5517
5550
  });
5518
5551
  }
5519
5552
  } catch (error) {
5520
- logger$16.error("[RTCPeerConnectionController] Error updating answer status:", error);
5553
+ logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
5521
5554
  this._errors$.next(toError(error));
5522
5555
  readyToConnect = false;
5523
5556
  } finally {
@@ -5536,7 +5569,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5536
5569
  await this.handleOfferReceived();
5537
5570
  break;
5538
5571
  case "failed":
5539
- logger$16.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
5572
+ logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
5540
5573
  break;
5541
5574
  case "sent":
5542
5575
  default:
@@ -5568,7 +5601,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5568
5601
  }
5569
5602
  await this.setupLocalTracks();
5570
5603
  const { answerOptions } = this;
5571
- logger$16.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
5604
+ logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
5572
5605
  await this.createAnswer(answerOptions);
5573
5606
  }
5574
5607
  async handleOfferReceived() {
@@ -5576,7 +5609,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5576
5609
  this._isNegotiating$.next(true);
5577
5610
  await this._setRemoteDescription(this.sdpInit);
5578
5611
  const { answerOptions } = this;
5579
- logger$16.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
5612
+ logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
5580
5613
  await this.createAnswer(answerOptions);
5581
5614
  }
5582
5615
  readyToConnect() {
@@ -5584,7 +5617,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5584
5617
  this.connectionTimer = setTimeout(() => {
5585
5618
  this.removeConnectionTimer();
5586
5619
  if (this.peerConnection?.connectionState !== "connected") {
5587
- logger$16.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
5620
+ logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
5588
5621
  this.iceGatheringController.restartICEGatheringWithRelayOnly();
5589
5622
  }
5590
5623
  }, this.connectionTimeout);
@@ -5606,14 +5639,14 @@ var RTCPeerConnectionController = class extends Destroyable {
5606
5639
  const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
5607
5640
  if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
5608
5641
  result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
5609
- logger$16.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
5642
+ logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
5610
5643
  preferredAudioCodecs,
5611
5644
  preferredVideoCodecs
5612
5645
  });
5613
5646
  }
5614
5647
  if (stereo) {
5615
5648
  result = enableStereoOpus(result);
5616
- logger$16.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
5649
+ logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
5617
5650
  }
5618
5651
  return Promise.resolve(result);
5619
5652
  }
@@ -5669,25 +5702,25 @@ var RTCPeerConnectionController = class extends Destroyable {
5669
5702
  ...this.peerConnection.getConfiguration(),
5670
5703
  iceTransportPolicy: "relay"
5671
5704
  });
5672
- logger$16.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
5705
+ logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
5673
5706
  } catch (error) {
5674
- logger$16.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
5707
+ logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
5675
5708
  }
5676
5709
  this.setupEventListeners();
5677
5710
  this._isNegotiating$.next(true);
5678
- logger$16.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
5711
+ logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
5679
5712
  try {
5680
5713
  const offer = await this.peerConnection.createOffer({ iceRestart: true });
5681
5714
  await this.setLocalDescription(offer);
5682
5715
  } catch (error) {
5683
- logger$16.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
5716
+ logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
5684
5717
  this._errors$.next(toError(error));
5685
5718
  this.negotiationEnded();
5686
5719
  if (policyChanged) this.restoreIceTransportPolicy();
5687
5720
  throw error;
5688
5721
  }
5689
5722
  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) => {
5690
- logger$16.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
5723
+ logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
5691
5724
  this.restoreIceTransportPolicy();
5692
5725
  });
5693
5726
  }
@@ -5697,9 +5730,9 @@ var RTCPeerConnectionController = class extends Destroyable {
5697
5730
  ...this.peerConnection.getConfiguration(),
5698
5731
  iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
5699
5732
  });
5700
- logger$16.debug("[RTCPeerConnectionController] ICE transport policy restored");
5733
+ logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
5701
5734
  } catch (error) {
5702
- logger$16.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
5735
+ logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
5703
5736
  }
5704
5737
  }
5705
5738
  /**
@@ -5711,13 +5744,13 @@ var RTCPeerConnectionController = class extends Destroyable {
5711
5744
  await this.setupRemoteTracks();
5712
5745
  }
5713
5746
  async setupLocalTracks() {
5714
- logger$16.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
5747
+ logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
5715
5748
  const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
5716
5749
  if (this.transceiverController?.useAddStream ?? false) {
5717
- logger$16.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
5750
+ logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
5718
5751
  this.peerConnection?.addStream(localStream);
5719
5752
  if (!this.isNegotiating) {
5720
- logger$16.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
5753
+ logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
5721
5754
  this.negotiationNeeded$.next();
5722
5755
  }
5723
5756
  return;
@@ -5733,7 +5766,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5733
5766
  const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
5734
5767
  await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
5735
5768
  } else {
5736
- logger$16.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
5769
+ logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
5737
5770
  this.peerConnection?.addTrack(track, localStream);
5738
5771
  }
5739
5772
  }
@@ -5750,7 +5783,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5750
5783
  async setupRemoteTracks() {
5751
5784
  if (!this.peerConnection) throw new require_operators.DependencyError("RTCPeerConnection is not initialized");
5752
5785
  this.peerConnection.ontrack = (event) => {
5753
- logger$16.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
5786
+ logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
5754
5787
  if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
5755
5788
  else {
5756
5789
  const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
@@ -5774,7 +5807,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5774
5807
  try {
5775
5808
  stream = await this.getUserMedia({ audio: constraints });
5776
5809
  } catch (error) {
5777
- logger$16.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
5810
+ logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
5778
5811
  this._errors$.next(toError(error));
5779
5812
  return;
5780
5813
  }
@@ -5796,7 +5829,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5796
5829
  try {
5797
5830
  this._localAudioPipeline = new LocalAudioPipeline();
5798
5831
  } catch (error) {
5799
- logger$16.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
5832
+ logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
5800
5833
  return null;
5801
5834
  }
5802
5835
  this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
@@ -5818,7 +5851,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5818
5851
  try {
5819
5852
  await sender.replaceTrack(this._localAudioPipeline.outputTrack);
5820
5853
  } catch (error) {
5821
- logger$16.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
5854
+ logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
5822
5855
  }
5823
5856
  }
5824
5857
  /**
@@ -5834,9 +5867,9 @@ var RTCPeerConnectionController = class extends Destroyable {
5834
5867
  try {
5835
5868
  const localStream = this.localStreamController.addTrack(track);
5836
5869
  this.peerConnection.addTrack(track, localStream);
5837
- logger$16.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
5870
+ logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
5838
5871
  } catch (error) {
5839
- logger$16.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
5872
+ logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
5840
5873
  this._errors$.next(toError(error));
5841
5874
  throw error;
5842
5875
  }
@@ -5853,15 +5886,15 @@ var RTCPeerConnectionController = class extends Destroyable {
5853
5886
  }
5854
5887
  const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
5855
5888
  if (!sender) {
5856
- logger$16.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
5889
+ logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
5857
5890
  return;
5858
5891
  }
5859
5892
  try {
5860
5893
  this.peerConnection.removeTrack(sender);
5861
5894
  this.localStreamController.removeTrack(trackId);
5862
- logger$16.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
5895
+ logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
5863
5896
  } catch (error) {
5864
- logger$16.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
5897
+ logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
5865
5898
  this._errors$.next(toError(error));
5866
5899
  throw error;
5867
5900
  }
@@ -5888,7 +5921,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5888
5921
  async replaceAudioTrackWithConstraints(constraints) {
5889
5922
  const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
5890
5923
  if (!senders || senders.length === 0) {
5891
- logger$16.warn("[RTCPeerConnectionController] No live audio sender to replace");
5924
+ logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
5892
5925
  return;
5893
5926
  }
5894
5927
  for (const sender of senders) {
@@ -5906,7 +5939,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5906
5939
  const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
5907
5940
  await sender.replaceTrack(newTrack);
5908
5941
  this.localStreamController.addTrack(newTrack);
5909
- logger$16.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
5942
+ logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
5910
5943
  }
5911
5944
  }
5912
5945
  /**
@@ -5914,7 +5947,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5914
5947
  * Completes all observables to prevent memory leaks.
5915
5948
  */
5916
5949
  destroy() {
5917
- logger$16.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
5950
+ logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
5918
5951
  this.removeConnectionTimer();
5919
5952
  this._iceGatheringController?.destroy();
5920
5953
  this._localAudioPipeline?.destroy();
@@ -5940,7 +5973,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5940
5973
  }
5941
5974
  stopRemoteTracks() {
5942
5975
  this._remoteStream$.value?.getTracks().forEach((track) => {
5943
- logger$16.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
5976
+ logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
5944
5977
  track.stop();
5945
5978
  });
5946
5979
  }
@@ -5957,7 +5990,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5957
5990
  ...params,
5958
5991
  sdp: finalRemote
5959
5992
  };
5960
- logger$16.debug("[RTCPeerConnectionController] Setting remote description:", answer);
5993
+ logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
5961
5994
  return this.peerConnection.setRemoteDescription(answer);
5962
5995
  }
5963
5996
  };
@@ -5972,7 +6005,11 @@ function isVertoInviteMessage(value) {
5972
6005
  const msg = value;
5973
6006
  return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
5974
6007
  }
5975
- function isVertoByeMessage(value) {
6008
+ /**
6009
+ * Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
6010
+ * built locally and never type-guarded, so only the inbound shape is asserted.
6011
+ */
6012
+ function isVertoByeInboundMessage(value) {
5976
6013
  if (!isVertoMethodMessage(value)) return false;
5977
6014
  return value.method === "verto.bye";
5978
6015
  }
@@ -5992,10 +6029,18 @@ function isVertoMediaParamsInnerParams(value) {
5992
6029
  function isVertoPingInnerParams(value) {
5993
6030
  return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
5994
6031
  }
6032
+ /**
6033
+ * Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
6034
+ * this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
6035
+ * it against a boolean answer/reject signal.
6036
+ */
6037
+ function isVertoByeInboundParamsGuard(value) {
6038
+ return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
6039
+ }
5995
6040
 
5996
6041
  //#endregion
5997
6042
  //#region src/managers/VertoManager.ts
5998
- const logger$15 = require_operators.getLogger();
6043
+ const logger$16 = require_operators.getLogger();
5999
6044
  /**
6000
6045
  * Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
6001
6046
  *
@@ -6051,7 +6096,7 @@ var WebRTCVertoManager = class extends VertoManager {
6051
6096
  try {
6052
6097
  await this.executeVerto(vertoModifyMessage);
6053
6098
  } catch (error) {
6054
- logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
6099
+ logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
6055
6100
  throw error;
6056
6101
  }
6057
6102
  }
@@ -6064,7 +6109,7 @@ var WebRTCVertoManager = class extends VertoManager {
6064
6109
  try {
6065
6110
  await this.executeVerto(vertoModifyMessage);
6066
6111
  } catch (error) {
6067
- logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
6112
+ logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
6068
6113
  throw error;
6069
6114
  }
6070
6115
  }
@@ -6117,7 +6162,7 @@ var WebRTCVertoManager = class extends VertoManager {
6117
6162
  if (event.member_id) this.setSelfIdIfNull(event.member_id);
6118
6163
  });
6119
6164
  this.subscribeTo(this.vertoMedia$, (event) => {
6120
- logger$15.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
6165
+ logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
6121
6166
  const { sdp, callID } = event;
6122
6167
  this.emitMainSignalingStatus(callID, "ringing");
6123
6168
  this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
@@ -6126,7 +6171,7 @@ var WebRTCVertoManager = class extends VertoManager {
6126
6171
  });
6127
6172
  });
6128
6173
  this.subscribeTo(this.vertoAnswer$, (event) => {
6129
- logger$15.debug("[WebRTCManager] Received Verto answer event:", event);
6174
+ logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
6130
6175
  const { sdp, callID } = event;
6131
6176
  this.emitMainSignalingStatus(callID, "connecting");
6132
6177
  this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
@@ -6135,7 +6180,7 @@ var WebRTCVertoManager = class extends VertoManager {
6135
6180
  });
6136
6181
  });
6137
6182
  this.subscribeTo(this.vertoMediaParams$, (event) => {
6138
- logger$15.debug("[WebRTCManager] Received Verto mediaParams event:", event);
6183
+ logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
6139
6184
  const { mediaParams, callID } = event;
6140
6185
  const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
6141
6186
  const { audio, video } = mediaParams;
@@ -6149,7 +6194,7 @@ var WebRTCVertoManager = class extends VertoManager {
6149
6194
  timestamp: Date.now()
6150
6195
  });
6151
6196
  } catch (error) {
6152
- logger$15.warn("[WebRTCManager] Error applying server-pushed media params:", error);
6197
+ logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
6153
6198
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6154
6199
  }
6155
6200
  })();
@@ -6171,13 +6216,13 @@ var WebRTCVertoManager = class extends VertoManager {
6171
6216
  */
6172
6217
  setNodeIdIfNull(nodeId) {
6173
6218
  if (!this._nodeId$.value && nodeId) {
6174
- logger$15.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
6219
+ logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
6175
6220
  this._nodeId$.next(nodeId);
6176
6221
  }
6177
6222
  }
6178
6223
  setSelfIdIfNull(selfId) {
6179
6224
  if (!this._selfId$.value && selfId) {
6180
- logger$15.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
6225
+ logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
6181
6226
  this._selfId$.next(selfId);
6182
6227
  }
6183
6228
  }
@@ -6186,7 +6231,7 @@ var WebRTCVertoManager = class extends VertoManager {
6186
6231
  const vertoPongMessage = VertoPong({ ...vertoPing });
6187
6232
  await this.executeVerto(vertoPongMessage);
6188
6233
  } catch (error) {
6189
- logger$15.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
6234
+ logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
6190
6235
  this.onError?.(new require_operators.VertoPongError(error));
6191
6236
  }
6192
6237
  }
@@ -6196,7 +6241,7 @@ var WebRTCVertoManager = class extends VertoManager {
6196
6241
  if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
6197
6242
  if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
6198
6243
  } catch (error) {
6199
- logger$15.warn("[WebRTCManager] Error updating media constraints:", error);
6244
+ logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
6200
6245
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6201
6246
  throw error;
6202
6247
  }
@@ -6226,20 +6271,20 @@ var WebRTCVertoManager = class extends VertoManager {
6226
6271
  try {
6227
6272
  const pc = this.mainPeerConnection.peerConnection;
6228
6273
  if (!pc) {
6229
- logger$15.warn("[WebRTCManager] No peer connection for keyframe request");
6274
+ logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
6230
6275
  return;
6231
6276
  }
6232
6277
  const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
6233
6278
  if (!videoReceiver) {
6234
- logger$15.warn("[WebRTCManager] No video receiver for keyframe request");
6279
+ logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
6235
6280
  return;
6236
6281
  }
6237
6282
  if (typeof videoReceiver.requestKeyFrame === "function") {
6238
6283
  videoReceiver.requestKeyFrame();
6239
- logger$15.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
6240
- } else logger$15.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
6284
+ logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
6285
+ } else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
6241
6286
  } catch (error) {
6242
- logger$15.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
6287
+ logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
6243
6288
  }
6244
6289
  }
6245
6290
  /**
@@ -6257,13 +6302,13 @@ var WebRTCVertoManager = class extends VertoManager {
6257
6302
  try {
6258
6303
  const controller = this.mainPeerConnection;
6259
6304
  if (!controller.peerConnection) {
6260
- logger$15.warn("[WebRTCManager] No peer connection for ICE restart");
6305
+ logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
6261
6306
  return;
6262
6307
  }
6263
6308
  await controller.triggerIceRestart(relayOnly);
6264
- logger$15.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
6309
+ logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
6265
6310
  } catch (error) {
6266
- logger$15.error("[WebRTCManager] ICE restart failed:", error);
6311
+ logger$16.error("[WebRTCManager] ICE restart failed:", error);
6267
6312
  throw error;
6268
6313
  }
6269
6314
  }
@@ -6281,13 +6326,13 @@ var WebRTCVertoManager = class extends VertoManager {
6281
6326
  const entries = Array.from(this._rtcPeerConnectionsMap.entries());
6282
6327
  for (const [id, controller] of entries) try {
6283
6328
  if (!controller.peerConnection) {
6284
- logger$15.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
6329
+ logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
6285
6330
  continue;
6286
6331
  }
6287
6332
  await controller.triggerIceRestart(relayOnly);
6288
- logger$15.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
6333
+ logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
6289
6334
  } catch (error) {
6290
- logger$15.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
6335
+ logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
6291
6336
  }
6292
6337
  }
6293
6338
  /**
@@ -6299,7 +6344,7 @@ var WebRTCVertoManager = class extends VertoManager {
6299
6344
  requestKeyframeAll() {
6300
6345
  for (const [id, controller] of this._rtcPeerConnectionsMap) {
6301
6346
  if (controller.isScreenShare) {
6302
- logger$15.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
6347
+ logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
6303
6348
  continue;
6304
6349
  }
6305
6350
  try {
@@ -6309,10 +6354,10 @@ var WebRTCVertoManager = class extends VertoManager {
6309
6354
  if (!videoReceiver) continue;
6310
6355
  if (typeof videoReceiver.requestKeyFrame === "function") {
6311
6356
  videoReceiver.requestKeyFrame();
6312
- logger$15.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
6357
+ logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
6313
6358
  }
6314
6359
  } catch (error) {
6315
- logger$15.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
6360
+ logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
6316
6361
  }
6317
6362
  }
6318
6363
  }
@@ -6329,7 +6374,7 @@ var WebRTCVertoManager = class extends VertoManager {
6329
6374
  return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoMediaParamsInnerParams, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
6330
6375
  }
6331
6376
  get vertoBye$() {
6332
- return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoByeMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
6377
+ return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoByeInboundMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
6333
6378
  }
6334
6379
  get vertoAttach$() {
6335
6380
  return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoAttachMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
@@ -6373,7 +6418,7 @@ var WebRTCVertoManager = class extends VertoManager {
6373
6418
  default:
6374
6419
  }
6375
6420
  } catch (error) {
6376
- logger$15.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
6421
+ logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
6377
6422
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6378
6423
  if (vertoMethod === "verto.modify") this.onModifyFailed?.();
6379
6424
  }
@@ -6388,7 +6433,7 @@ var WebRTCVertoManager = class extends VertoManager {
6388
6433
  sdp
6389
6434
  });
6390
6435
  } catch (error) {
6391
- logger$15.warn("[WebRTCManager] Error processing modify response:", error);
6436
+ logger$16.warn("[WebRTCManager] Error processing modify response:", error);
6392
6437
  const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
6393
6438
  this.onError?.(modifyError);
6394
6439
  }
@@ -6398,7 +6443,7 @@ var WebRTCVertoManager = class extends VertoManager {
6398
6443
  const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
6399
6444
  if (!rtcPeerConnController) {
6400
6445
  const signalingError = new require_operators.DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
6401
- logger$15.error("[WebRTCManager] Failed to emit signaling status:", {
6446
+ logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
6402
6447
  callId,
6403
6448
  status,
6404
6449
  signalingError
@@ -6414,7 +6459,7 @@ var WebRTCVertoManager = class extends VertoManager {
6414
6459
  this._nodeId$.next(require_operators.getValueFrom(response, "result.node_id") ?? null);
6415
6460
  const memberId = require_operators.getValueFrom(response, "result.result.result.memberID") ?? null;
6416
6461
  const callId = require_operators.getValueFrom(response, "result.result.result.callID") ?? null;
6417
- logger$15.debug("[WebRTCManager] Verto invite response:", {
6462
+ logger$16.debug("[WebRTCManager] Verto invite response:", {
6418
6463
  callId,
6419
6464
  memberId,
6420
6465
  response
@@ -6424,14 +6469,14 @@ var WebRTCVertoManager = class extends VertoManager {
6424
6469
  if (callId) {
6425
6470
  this.webRtcCallSession.addCallId(callId);
6426
6471
  this.attachManager.attach(this.buildAttachableCall(callId));
6427
- } else logger$15.warn("[WebRTCManager] Cannot attach call, missing callId:", {
6472
+ } else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
6428
6473
  nodeId: this.nodeId,
6429
6474
  callId
6430
6475
  });
6431
- logger$15.info("[WebRTCManager] Verto invite successful");
6432
- logger$15.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
6476
+ logger$16.info("[WebRTCManager] Verto invite successful");
6477
+ logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
6433
6478
  } else {
6434
- logger$15.error("[WebRTCManager] Verto invite failed:", response);
6479
+ logger$16.error("[WebRTCManager] Verto invite failed:", response);
6435
6480
  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");
6436
6481
  this.onError?.(inviteError);
6437
6482
  }
@@ -6476,17 +6521,17 @@ var WebRTCVertoManager = class extends VertoManager {
6476
6521
  if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
6477
6522
  }
6478
6523
  async handleInboundAnswer(rtcPeerConnController) {
6479
- logger$15.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
6524
+ logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
6480
6525
  const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
6481
6526
  if (vertoByeOrAccepted === null) {
6482
- logger$15.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
6527
+ logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
6483
6528
  return;
6484
6529
  }
6485
- if (isVertoByeMessage(vertoByeOrAccepted)) {
6486
- logger$15.info("[WebRTCManager] Inbound call ended by remote before answer.");
6530
+ if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
6531
+ logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
6487
6532
  this.callSession?.destroy();
6488
6533
  } else if (!vertoByeOrAccepted) {
6489
- logger$15.info("[WebRTCManager] Inbound call rejected by user.");
6534
+ logger$16.info("[WebRTCManager] Inbound call rejected by user.");
6490
6535
  try {
6491
6536
  await this.bye("USER_BUSY");
6492
6537
  } finally {
@@ -6494,19 +6539,19 @@ var WebRTCVertoManager = class extends VertoManager {
6494
6539
  this.callSession?.destroy();
6495
6540
  }
6496
6541
  } else {
6497
- logger$15.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
6542
+ logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
6498
6543
  const answerOptions = this.webRtcCallSession.answerMediaOptions;
6499
6544
  try {
6500
6545
  await rtcPeerConnController.acceptInbound(answerOptions);
6501
6546
  } catch (error) {
6502
- logger$15.error("[WebRTCManager] Error creating inbound answer:", error);
6547
+ logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
6503
6548
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6504
6549
  }
6505
6550
  }
6506
6551
  }
6507
6552
  setupVertoAttachHandler() {
6508
6553
  this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
6509
- logger$15.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
6554
+ logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
6510
6555
  const { callID } = vertoAttach;
6511
6556
  await this.attachManager.attach({
6512
6557
  nodeId: this.nodeId ?? void 0,
@@ -6578,17 +6623,17 @@ var WebRTCVertoManager = class extends VertoManager {
6578
6623
  };
6579
6624
  }
6580
6625
  async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
6581
- logger$15.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
6626
+ logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
6582
6627
  const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
6583
6628
  if (vertoByeOrAccepted === null) {
6584
- logger$15.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
6629
+ logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
6585
6630
  return;
6586
6631
  }
6587
- if (isVertoByeMessage(vertoByeOrAccepted)) {
6588
- logger$15.info("[WebRTCManager] Call ended before answer was sent.");
6632
+ if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
6633
+ logger$16.info("[WebRTCManager] Call ended before answer was sent.");
6589
6634
  this.callSession?.destroy();
6590
6635
  } else if (!vertoByeOrAccepted) {
6591
- logger$15.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
6636
+ logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
6592
6637
  try {
6593
6638
  await this.bye("USER_BUSY");
6594
6639
  } finally {
@@ -6596,14 +6641,14 @@ var WebRTCVertoManager = class extends VertoManager {
6596
6641
  this.callSession?.destroy();
6597
6642
  }
6598
6643
  } else {
6599
- logger$15.debug("[WebRTCManager] Call accepted, sending answer");
6644
+ logger$16.debug("[WebRTCManager] Call accepted, sending answer");
6600
6645
  try {
6601
6646
  this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
6602
6647
  await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
6603
6648
  await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
6604
6649
  await this.attachManager.attach(this.buildAttachableCall());
6605
6650
  } catch (error) {
6606
- logger$15.error("[WebRTCManager] Error sending Verto answer:", error);
6651
+ logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
6607
6652
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6608
6653
  await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
6609
6654
  }
@@ -6702,10 +6747,10 @@ var WebRTCVertoManager = class extends VertoManager {
6702
6747
  });
6703
6748
  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$)));
6704
6749
  this._screenShareStatus$.next("started");
6705
- logger$15.info("[WebRTCManager] Screen share started successfully.");
6750
+ logger$16.info("[WebRTCManager] Screen share started successfully.");
6706
6751
  return rtcPeerConnController.id;
6707
6752
  } catch (error) {
6708
- logger$15.warn("[WebRTCManager] Error initializing additional peer connection:", error);
6753
+ logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
6709
6754
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6710
6755
  if (rtcPeerConnController) rtcPeerConnController.destroy();
6711
6756
  this._screenShareStatus$.next("none");
@@ -6724,9 +6769,9 @@ var WebRTCVertoManager = class extends VertoManager {
6724
6769
  if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
6725
6770
  }
6726
6771
  async removeScreenMedia() {
6727
- if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$15.warn("[WebRTCManager] No active screen share to stop.");
6772
+ if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
6728
6773
  if (!this._screenShareId) {
6729
- logger$15.debug("[WebRTCManager] No screen share peer connection found.");
6774
+ logger$16.debug("[WebRTCManager] No screen share peer connection found.");
6730
6775
  return;
6731
6776
  }
6732
6777
  this._screenShareStatus$.next("stopping");
@@ -6755,7 +6800,7 @@ var WebRTCVertoManager = class extends VertoManager {
6755
6800
  dialogParams: this.dialogParams(rtcPeerConnController)
6756
6801
  }));
6757
6802
  } catch (error) {
6758
- logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
6803
+ logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
6759
6804
  throw error;
6760
6805
  }
6761
6806
  }
@@ -6773,7 +6818,7 @@ var WebRTCVertoManager = class extends VertoManager {
6773
6818
  try {
6774
6819
  await this.executeVerto(vertoInfoMessage);
6775
6820
  } catch (error) {
6776
- logger$15.warn("[WebRTCManager] Error sending DTMF digits:", error);
6821
+ logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
6777
6822
  throw error;
6778
6823
  }
6779
6824
  }
@@ -6784,10 +6829,10 @@ var WebRTCVertoManager = class extends VertoManager {
6784
6829
  action: "transfer"
6785
6830
  });
6786
6831
  try {
6787
- logger$15.debug("[WebRTCManager] Transferring call with options:", options);
6832
+ logger$16.debug("[WebRTCManager] Transferring call with options:", options);
6788
6833
  await this.executeVerto(message);
6789
6834
  } catch (error) {
6790
- logger$15.error("[WebRTCManager] Error transferring call:", error);
6835
+ logger$16.error("[WebRTCManager] Error transferring call:", error);
6791
6836
  throw error;
6792
6837
  }
6793
6838
  }
@@ -6803,7 +6848,7 @@ var WebRTCVertoManager = class extends VertoManager {
6803
6848
 
6804
6849
  //#endregion
6805
6850
  //#region src/controllers/RemoteAudioMeter.ts
6806
- const logger$14 = require_operators.getLogger();
6851
+ const logger$15 = require_operators.getLogger();
6807
6852
  /**
6808
6853
  * Read-only audio level meter for a remote MediaStream. Attaches an
6809
6854
  * AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
@@ -6838,7 +6883,7 @@ var RemoteAudioMeter = class extends Destroyable {
6838
6883
  try {
6839
6884
  this._source.disconnect();
6840
6885
  } catch (error) {
6841
- logger$14.debug("[RemoteAudioMeter] source disconnect warning:", error);
6886
+ logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
6842
6887
  }
6843
6888
  this._source = null;
6844
6889
  this._stream = null;
@@ -6855,7 +6900,7 @@ var RemoteAudioMeter = class extends Destroyable {
6855
6900
  this._source = null;
6856
6901
  }
6857
6902
  this._audioContext.close().catch((error) => {
6858
- logger$14.debug("[RemoteAudioMeter] audio context close warning:", error);
6903
+ logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
6859
6904
  });
6860
6905
  super.destroy();
6861
6906
  }
@@ -6879,7 +6924,7 @@ var RemoteAudioMeter = class extends Destroyable {
6879
6924
  *
6880
6925
  * Extends Destroyable for automatic RxJS cleanup.
6881
6926
  */
6882
- const logger$13 = require_operators.getLogger();
6927
+ const logger$14 = require_operators.getLogger();
6883
6928
  const DEFAULT_POLLING_INTERVAL_MS = 1e3;
6884
6929
  const DEFAULT_BASELINE_SAMPLES = 10;
6885
6930
  const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
@@ -6969,9 +7014,9 @@ var RTCStatsMonitor = class extends Destroyable {
6969
7014
  const now = Date.now();
6970
7015
  this.lastAudioPacketChangeTime = now;
6971
7016
  this.lastVideoPacketChangeTime = now;
6972
- logger$13.debug("[RTCStatsMonitor] Starting stats monitoring");
7017
+ logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
6973
7018
  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) => {
6974
- logger$13.warn("[RTCStatsMonitor] Failed to get stats:", err);
7019
+ logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
6975
7020
  return rxjs.EMPTY;
6976
7021
  }))), (0, rxjs.filter)(() => this.running), (0, rxjs.map)((report) => this.extractSample(report))), (sample) => this._sample$.next(sample));
6977
7022
  this.subscribeTo(this._sample$.pipe((0, rxjs.take)(this.baselineSampleCount), (0, rxjs.toArray)(), (0, rxjs.map)((samples) => ({
@@ -6979,7 +7024,7 @@ var RTCStatsMonitor = class extends Destroyable {
6979
7024
  jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
6980
7025
  ready: true
6981
7026
  }))), (baseline) => {
6982
- logger$13.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
7027
+ logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
6983
7028
  this._baseline$.next(baseline);
6984
7029
  });
6985
7030
  this.subscribeTo(this._sample$.pipe((0, rxjs.scan)((acc, sample) => ({
@@ -7016,10 +7061,10 @@ var RTCStatsMonitor = class extends Destroyable {
7016
7061
  stop() {
7017
7062
  if (!this.running) return;
7018
7063
  this.running = false;
7019
- logger$13.debug("[RTCStatsMonitor] Stopping stats monitoring");
7064
+ logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
7020
7065
  }
7021
7066
  destroy() {
7022
- logger$13.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
7067
+ logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
7023
7068
  this.stop();
7024
7069
  super.destroy();
7025
7070
  }
@@ -7147,7 +7192,7 @@ var RTCStatsMonitor = class extends Destroyable {
7147
7192
 
7148
7193
  //#endregion
7149
7194
  //#region src/managers/CallRecoveryManager.ts
7150
- const logger$12 = require_operators.getLogger();
7195
+ const logger$13 = require_operators.getLogger();
7151
7196
  const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
7152
7197
  const DEFAULT_COOLDOWN_MS = 1e4;
7153
7198
  const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
@@ -7244,10 +7289,10 @@ var CallRecoveryManager = class extends Destroyable {
7244
7289
  */
7245
7290
  async requestIceRestart() {
7246
7291
  if (this._recoveryState$.value === "recovering") {
7247
- logger$12.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
7292
+ logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
7248
7293
  return;
7249
7294
  }
7250
- logger$12.info("CallRecoveryManager: manual ICE restart requested");
7295
+ logger$13.info("CallRecoveryManager: manual ICE restart requested");
7251
7296
  this.transitionTo("recovering");
7252
7297
  await this.executeIceRestart(false);
7253
7298
  this.startCooldown();
@@ -7263,7 +7308,7 @@ var CallRecoveryManager = class extends Destroyable {
7263
7308
  * WebSocket reconnect or call state recovers to 'connected'.
7264
7309
  */
7265
7310
  reset() {
7266
- logger$12.info("CallRecoveryManager: resetting counters");
7311
+ logger$13.info("CallRecoveryManager: resetting counters");
7267
7312
  this._attemptCount = 0;
7268
7313
  this._keyframeBurstCount = 0;
7269
7314
  this._keyframeBurstStart = 0;
@@ -7278,7 +7323,7 @@ var CallRecoveryManager = class extends Destroyable {
7278
7323
  */
7279
7324
  notifyModifyFailed() {
7280
7325
  if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
7281
- logger$12.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
7326
+ logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
7282
7327
  this._cooldownUntil = 0;
7283
7328
  this.transitionTo("idle");
7284
7329
  this.pushTrigger({
@@ -7302,7 +7347,7 @@ var CallRecoveryManager = class extends Destroyable {
7302
7347
  reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
7303
7348
  timestamp: Date.now()
7304
7349
  });
7305
- logger$12.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
7350
+ logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
7306
7351
  } else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
7307
7352
  this._bandwidthConstrained$.next(false);
7308
7353
  this._callbacks.enableVideo();
@@ -7311,7 +7356,7 @@ var CallRecoveryManager = class extends Destroyable {
7311
7356
  reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
7312
7357
  timestamp: Date.now()
7313
7358
  });
7314
- logger$12.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
7359
+ logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
7315
7360
  }
7316
7361
  }
7317
7362
  /**
@@ -7330,14 +7375,14 @@ var CallRecoveryManager = class extends Destroyable {
7330
7375
  handleWebSocketReconnect() {
7331
7376
  const pcState = this._callbacks.getPeerConnectionState();
7332
7377
  if (pcState === "connected" || pcState === "completed") {
7333
- logger$12.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
7378
+ logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
7334
7379
  this.emitEvent({
7335
7380
  action: "signal_reconnect",
7336
7381
  reason: "WebSocket reconnected, peer connection still connected",
7337
7382
  timestamp: Date.now()
7338
7383
  });
7339
7384
  } else {
7340
- logger$12.info("CallRecoveryManager: full reconnect — peer connection also down");
7385
+ logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
7341
7386
  this.emitEvent({
7342
7387
  action: "full_reconnect",
7343
7388
  reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
@@ -7361,7 +7406,7 @@ var CallRecoveryManager = class extends Destroyable {
7361
7406
  }), (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$))), {
7362
7407
  next: () => {},
7363
7408
  error: (err) => {
7364
- logger$12.error("CallRecoveryManager: pipeline error", err);
7409
+ logger$13.error("CallRecoveryManager: pipeline error", err);
7365
7410
  this.transitionTo("idle");
7366
7411
  }
7367
7412
  });
@@ -7388,27 +7433,27 @@ var CallRecoveryManager = class extends Destroyable {
7388
7433
  reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
7389
7434
  timestamp: Date.now()
7390
7435
  });
7391
- logger$12.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
7436
+ logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
7392
7437
  });
7393
7438
  }
7394
7439
  passGateChecks(signalingReady) {
7395
7440
  if (this._callbacks.isNegotiating()) {
7396
- logger$12.debug("CallRecoveryManager: gate blocked — negotiation in progress");
7441
+ logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
7397
7442
  this.transitionTo("idle");
7398
7443
  return false;
7399
7444
  }
7400
7445
  if (!signalingReady) {
7401
- logger$12.debug("CallRecoveryManager: gate blocked — signaling not ready");
7446
+ logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
7402
7447
  this.transitionTo("idle");
7403
7448
  return false;
7404
7449
  }
7405
7450
  if (!this._callbacks.isCallConnected()) {
7406
- logger$12.debug("CallRecoveryManager: gate blocked — call not connected");
7451
+ logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
7407
7452
  this.transitionTo("idle");
7408
7453
  return false;
7409
7454
  }
7410
7455
  if (this.isCooldownActive()) {
7411
- logger$12.debug("CallRecoveryManager: gate blocked — cooldown active");
7456
+ logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
7412
7457
  this.transitionTo("cooldown");
7413
7458
  return false;
7414
7459
  }
@@ -7419,9 +7464,9 @@ var CallRecoveryManager = class extends Destroyable {
7419
7464
  }
7420
7465
  executeTieredRecovery(trigger) {
7421
7466
  this.transitionTo("recovering");
7422
- logger$12.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
7467
+ logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
7423
7468
  return (0, rxjs.from)(this.runTiers(trigger)).pipe((0, rxjs.tap)(() => this.startCooldown()), (0, rxjs.catchError)((err) => {
7424
- logger$12.error("CallRecoveryManager: tiered recovery failed", err);
7469
+ logger$13.error("CallRecoveryManager: tiered recovery failed", err);
7425
7470
  this.startCooldown();
7426
7471
  return rxjs.EMPTY;
7427
7472
  }));
@@ -7429,7 +7474,7 @@ var CallRecoveryManager = class extends Destroyable {
7429
7474
  async runTiers(trigger) {
7430
7475
  this.executeKeyframe(trigger.detail);
7431
7476
  if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
7432
- logger$12.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
7477
+ logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
7433
7478
  return;
7434
7479
  }
7435
7480
  if (this._attemptCount < this._config.maxAttempts) {
@@ -7446,13 +7491,13 @@ var CallRecoveryManager = class extends Destroyable {
7446
7491
  maxAttempts: this._config.maxAttempts,
7447
7492
  timestamp: Date.now()
7448
7493
  });
7449
- logger$12.warn("CallRecoveryManager: max recovery attempts reached");
7494
+ logger$13.warn("CallRecoveryManager: max recovery attempts reached");
7450
7495
  }
7451
7496
  }
7452
7497
  executeKeyframe(reason) {
7453
7498
  const now = Date.now();
7454
7499
  if (now < this._keyframeCooldownUntil) {
7455
- logger$12.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
7500
+ logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
7456
7501
  return;
7457
7502
  }
7458
7503
  if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
@@ -7461,7 +7506,7 @@ var CallRecoveryManager = class extends Destroyable {
7461
7506
  }
7462
7507
  if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
7463
7508
  this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
7464
- logger$12.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
7509
+ logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
7465
7510
  return;
7466
7511
  }
7467
7512
  this._keyframeBurstCount += 1;
@@ -7471,12 +7516,12 @@ var CallRecoveryManager = class extends Destroyable {
7471
7516
  reason,
7472
7517
  timestamp: now
7473
7518
  });
7474
- logger$12.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
7519
+ logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
7475
7520
  }
7476
7521
  async executeIceRestart(relayOnly) {
7477
7522
  this._attemptCount += 1;
7478
7523
  const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
7479
- logger$12.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
7524
+ logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
7480
7525
  this.emitEvent({
7481
7526
  action: "reinvite_started",
7482
7527
  reason: `${tier} ICE restart`,
@@ -7493,7 +7538,7 @@ var CallRecoveryManager = class extends Destroyable {
7493
7538
  maxAttempts: this._config.maxAttempts,
7494
7539
  timestamp: Date.now()
7495
7540
  });
7496
- logger$12.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
7541
+ logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
7497
7542
  this._attemptCount = 0;
7498
7543
  return true;
7499
7544
  }
@@ -7504,7 +7549,7 @@ var CallRecoveryManager = class extends Destroyable {
7504
7549
  maxAttempts: this._config.maxAttempts,
7505
7550
  timestamp: Date.now()
7506
7551
  });
7507
- logger$12.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
7552
+ logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
7508
7553
  return false;
7509
7554
  } catch {
7510
7555
  this.emitEvent({
@@ -7514,7 +7559,7 @@ var CallRecoveryManager = class extends Destroyable {
7514
7559
  maxAttempts: this._config.maxAttempts,
7515
7560
  timestamp: Date.now()
7516
7561
  });
7517
- logger$12.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
7562
+ logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
7518
7563
  return false;
7519
7564
  }
7520
7565
  }
@@ -7535,7 +7580,7 @@ var CallRecoveryManager = class extends Destroyable {
7535
7580
  transitionTo(state) {
7536
7581
  const prev = this._recoveryState$.value;
7537
7582
  if (prev !== state) {
7538
- logger$12.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
7583
+ logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
7539
7584
  this._recoveryState$.next(state);
7540
7585
  }
7541
7586
  }
@@ -7637,7 +7682,13 @@ function mosToQualityLevel(mos) {
7637
7682
 
7638
7683
  //#endregion
7639
7684
  //#region src/core/entities/Call.ts
7640
- const logger$11 = require_operators.getLogger();
7685
+ const logger$12 = require_operators.getLogger();
7686
+ /**
7687
+ * Verto method for setting member layout positions. Its gateway DTO requires a
7688
+ * `targets` array whose entries are `{ target, position }` (NOT bare targets),
7689
+ * so {@link WebRTCCall.buildMethodParams} special-cases it. See issue #19400.
7690
+ */
7691
+ const POSITION_SET_METHOD = "call.member.position.set";
7641
7692
  /**
7642
7693
  * Ratio between the critical and warning RTT spike multipliers.
7643
7694
  * Warning threshold = baseline * warningMultiplier (default 3x)
@@ -7655,7 +7706,7 @@ const fromDestinationParams = (destination) => {
7655
7706
  });
7656
7707
  return params;
7657
7708
  } catch (error) {
7658
- logger$11.warn(`Failed to parse destination URI: ${destination}`, error);
7709
+ logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
7659
7710
  return {};
7660
7711
  }
7661
7712
  };
@@ -7845,7 +7896,7 @@ var WebRTCCall = class extends Destroyable {
7845
7896
  * @throws {JSONRPCError} If the RPC call returns an error.
7846
7897
  */
7847
7898
  async executeMethod(target, method, args) {
7848
- const params = this.buildMethodParams(target, args);
7899
+ const params = this.buildMethodParams(target, args, method);
7849
7900
  const request = buildRPCRequest({
7850
7901
  method,
7851
7902
  params
@@ -7855,16 +7906,20 @@ var WebRTCCall = class extends Destroyable {
7855
7906
  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);
7856
7907
  return response;
7857
7908
  } catch (error) {
7858
- logger$11.error(`[Call] Error executing method ${method} with params`, params, error);
7909
+ logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
7859
7910
  throw error;
7860
7911
  }
7861
7912
  }
7862
- buildMethodParams(target, args) {
7913
+ buildMethodParams(target, args, method) {
7863
7914
  const self = {
7864
7915
  node_id: this.nodeId ?? "",
7865
7916
  call_id: this.id,
7866
7917
  member_id: this.vertoManager.selfId ?? ""
7867
7918
  };
7919
+ if (method === POSITION_SET_METHOD) return {
7920
+ ...args,
7921
+ self
7922
+ };
7868
7923
  if (typeof target === "object") return {
7869
7924
  ...args,
7870
7925
  self,
@@ -8058,9 +8113,9 @@ var WebRTCCall = class extends Destroyable {
8058
8113
  */
8059
8114
  initResilienceSubsystems() {
8060
8115
  const pc = this.rtcPeerConnection;
8061
- logger$11.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
8116
+ logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
8062
8117
  if (!pc) {
8063
- logger$11.warn("[Call] No peer connection available, skipping resilience init");
8118
+ logger$12.warn("[Call] No peer connection available, skipping resilience init");
8064
8119
  return;
8065
8120
  }
8066
8121
  try {
@@ -8095,14 +8150,14 @@ var WebRTCCall = class extends Destroyable {
8095
8150
  disableVideo: () => {
8096
8151
  try {
8097
8152
  this.vertoManager.muteMainVideoInputDevice();
8098
- logger$11.debug("[Call] Recovery manager disabled video");
8153
+ logger$12.debug("[Call] Recovery manager disabled video");
8099
8154
  } catch {
8100
- logger$11.debug("[Call] Recovery manager failed to disable video");
8155
+ logger$12.debug("[Call] Recovery manager failed to disable video");
8101
8156
  }
8102
8157
  },
8103
8158
  enableVideo: () => {
8104
8159
  this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
8105
- logger$11.debug("[Call] Recovery manager failed to enable video");
8160
+ logger$12.debug("[Call] Recovery manager failed to enable video");
8106
8161
  });
8107
8162
  },
8108
8163
  isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
@@ -8152,7 +8207,7 @@ var WebRTCCall = class extends Destroyable {
8152
8207
  this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
8153
8208
  this._recoveryEvent$.next(event);
8154
8209
  if (event.action === "max_attempts_reached") {
8155
- logger$11.warn("[Call] All recovery attempts exhausted, terminating call");
8210
+ logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
8156
8211
  this.emitError({
8157
8212
  kind: "network",
8158
8213
  fatal: true,
@@ -8172,13 +8227,13 @@ var WebRTCCall = class extends Destroyable {
8172
8227
  else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
8173
8228
  });
8174
8229
  this.subscribeTo(this.clientSession.authenticated$.pipe((0, rxjs.skip)(1), (0, rxjs.filter)(Boolean)), () => {
8175
- logger$11.debug("[Call] WebSocket reconnected — notifying recovery manager");
8230
+ logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
8176
8231
  this._recoveryManager?.handleWebSocketReconnect();
8177
8232
  });
8178
8233
  this._statsMonitor.start();
8179
- logger$11.debug("[Call] Resilience subsystems initialized for call", this.id);
8234
+ logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
8180
8235
  } catch (error) {
8181
- logger$11.warn("[Call] Failed to initialize resilience subsystems:", error);
8236
+ logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
8182
8237
  }
8183
8238
  }
8184
8239
  /**
@@ -8261,19 +8316,19 @@ var WebRTCCall = class extends Destroyable {
8261
8316
  }
8262
8317
  isCallSessionEvent(event) {
8263
8318
  try {
8264
- logger$11.debug("[Call] Checking if event is for this call session:", event);
8319
+ logger$12.debug("[Call] Checking if event is for this call session:", event);
8265
8320
  const callId = require_operators.getValueFrom(event, "params.params.callID") ?? require_operators.getValueFrom(event, "params.call_id");
8266
8321
  const roomSessionId = require_operators.getValueFrom(event, "params.room_session_id");
8267
- logger$11.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
8322
+ logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
8268
8323
  return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
8269
8324
  } catch (error) {
8270
- logger$11.error("[Call] Error checking if event is for this call session:", error);
8325
+ logger$12.error("[Call] Error checking if event is for this call session:", error);
8271
8326
  return false;
8272
8327
  }
8273
8328
  }
8274
8329
  get callSessionEvents$() {
8275
8330
  return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, rxjs.filter)((event) => this.isCallSessionEvent(event)), (0, rxjs.tap)((event) => {
8276
- logger$11.debug("[Call] Received call session event:", event);
8331
+ logger$12.debug("[Call] Received call session event:", event);
8277
8332
  }), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
8278
8333
  }
8279
8334
  /** Observable of call-updated events. */
@@ -8343,16 +8398,16 @@ var WebRTCCall = class extends Destroyable {
8343
8398
  this._customSubscriptions.set(eventType, filtered$);
8344
8399
  }, (error) => {
8345
8400
  this._customSubscriptions.delete(eventType);
8346
- logger$11.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
8401
+ logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
8347
8402
  });
8348
8403
  this._customSubscriptions.set(eventType, filtered$);
8349
8404
  return filtered$;
8350
8405
  }
8351
8406
  get webrtcMessages$() {
8352
- 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)()));
8407
+ 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)()));
8353
8408
  }
8354
8409
  get callEvent$() {
8355
- 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)()));
8410
+ 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)()));
8356
8411
  }
8357
8412
  get layoutEvent$() {
8358
8413
  return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(require_operators.filterAs(isLayoutChangedMetadata, "params")));
@@ -8430,10 +8485,21 @@ var WebRTCCall = class extends Destroyable {
8430
8485
  return this.deferEmission(this._answered$.asObservable());
8431
8486
  }
8432
8487
  /**
8433
- * Sets the call layout and participant positions.
8488
+ * Sets the call layout and, optionally, individual participant positions.
8489
+ *
8490
+ * The gateway `call.layout.set` DTO has **no** `positions` member, so when
8491
+ * `positions` is provided this method issues a `call.member.position.set`
8492
+ * request per member (via {@link Participant.setPosition}, which keys each
8493
+ * position by that member's own call context) alongside `call.layout.set`
8494
+ * (issue #19400, Flag #6).
8495
+ *
8496
+ * **These operations are NOT atomic.** The layout is applied first, then each
8497
+ * member position sequentially, so members may briefly flash into their
8498
+ * default slots before being moved to the requested positions.
8434
8499
  *
8435
8500
  * @param layout - Layout name (must be one of {@link layouts}).
8436
- * @param positions - Map of member IDs to {@link VideoPosition} values.
8501
+ * @param positions - Optional map of member IDs to {@link VideoPosition} values.
8502
+ * When omitted or empty, only the layout is changed.
8437
8503
  * @throws {InvalidParams} If the layout is not in the available {@link layouts}.
8438
8504
  *
8439
8505
  * @example
@@ -8446,10 +8512,17 @@ var WebRTCCall = class extends Destroyable {
8446
8512
  async setLayout(layout, positions) {
8447
8513
  if (!this.layouts.includes(layout)) throw new require_operators.InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
8448
8514
  const selfId = await (0, rxjs.firstValueFrom)(this.selfId$.pipe((0, rxjs.filter)((id) => id !== null)));
8449
- await this.executeMethod(selfId, "call.layout.set", {
8450
- layout,
8451
- positions
8452
- });
8515
+ await this.executeMethod(selfId, "call.layout.set", { layout });
8516
+ const positionEntries = Object.entries(positions ?? {});
8517
+ if (positionEntries.length === 0) return;
8518
+ for (const [memberId, position] of positionEntries) {
8519
+ const participant = this.participants.find((p) => p.id === memberId);
8520
+ if (!participant) {
8521
+ logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
8522
+ continue;
8523
+ }
8524
+ await participant.setPosition(position);
8525
+ }
8453
8526
  }
8454
8527
  /**
8455
8528
  * Transfers the call to another destination.
@@ -8481,7 +8554,7 @@ var WebRTCCall = class extends Destroyable {
8481
8554
  setLocalMicrophoneGain(value) {
8482
8555
  const pipeline = this.vertoManager.ensureLocalAudioPipeline();
8483
8556
  if (!pipeline) {
8484
- logger$11.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
8557
+ logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
8485
8558
  return;
8486
8559
  }
8487
8560
  const percent = Math.max(0, Math.min(200, value));
@@ -8526,7 +8599,7 @@ var WebRTCCall = class extends Destroyable {
8526
8599
  enablePushToTalk() {
8527
8600
  const pipeline = this.vertoManager.ensureLocalAudioPipeline();
8528
8601
  if (!pipeline) {
8529
- logger$11.warn("[Call] enablePushToTalk: audio pipeline unavailable");
8602
+ logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
8530
8603
  return;
8531
8604
  }
8532
8605
  pipeline.setPTTActive(false);
@@ -8688,7 +8761,7 @@ var CallFactory = class {
8688
8761
 
8689
8762
  //#endregion
8690
8763
  //#region src/behaviors/Collection.ts
8691
- const logger$10 = require_operators.getLogger();
8764
+ const logger$11 = require_operators.getLogger();
8692
8765
  var Fetcher = class {
8693
8766
  constructor(endpoint, params, http) {
8694
8767
  this.endpoint = endpoint;
@@ -8712,7 +8785,7 @@ var Fetcher = class {
8712
8785
  this.hasMore = !!this.nextUrl;
8713
8786
  return result.data.filter(this.filter).map(this.mapper);
8714
8787
  }
8715
- logger$10.error("Failed to fetch entity");
8788
+ logger$11.error("Failed to fetch entity");
8716
8789
  return [];
8717
8790
  }
8718
8791
  async id(v) {
@@ -8788,7 +8861,7 @@ var EntityCollection = class extends Destroyable {
8788
8861
  this._hasMore$.next(this.fetchController.hasMore ?? false);
8789
8862
  this._loading$.next(false);
8790
8863
  } catch (error) {
8791
- logger$10.error(`Failed to fetch initial collection data`, error);
8864
+ logger$11.error(`Failed to fetch initial collection data`, error);
8792
8865
  this._hasMore$.next(this.fetchController.hasMore ?? false);
8793
8866
  this._loading$.next(false);
8794
8867
  this.onError?.(new require_operators.CollectionFetchError("fetchMore", error));
@@ -8802,7 +8875,7 @@ var EntityCollection = class extends Destroyable {
8802
8875
  if (data) this.upsertData(data);
8803
8876
  return data;
8804
8877
  } catch (error) {
8805
- logger$10.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
8878
+ logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
8806
8879
  this._loading$.next(false);
8807
8880
  this.onError?.(new require_operators.CollectionFetchError(`tryFetch(${String(key)})`, error));
8808
8881
  }
@@ -9049,13 +9122,13 @@ var Address = class extends Destroyable {
9049
9122
 
9050
9123
  //#endregion
9051
9124
  //#region src/core/utils.ts
9052
- const logger$9 = require_operators.getLogger();
9125
+ const logger$10 = require_operators.getLogger();
9053
9126
  const isRPCConnectResult = (e) => {
9054
- logger$9.debug("isRPCConnectResult check:", e);
9127
+ logger$10.debug("isRPCConnectResult check:", e);
9055
9128
  if (!e || typeof e !== "object") return false;
9056
9129
  const result = e;
9057
9130
  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";
9058
- logger$9.debug("isRPCConnectResult check result:", is);
9131
+ logger$10.debug("isRPCConnectResult check result:", is);
9059
9132
  return is;
9060
9133
  };
9061
9134
  var PendingRPC = class PendingRPC {
@@ -9064,7 +9137,7 @@ var PendingRPC = class PendingRPC {
9064
9137
  }
9065
9138
  constructor(request, responses$, options) {
9066
9139
  this.id = (0, uuid.v4)();
9067
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
9140
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
9068
9141
  this.request = request;
9069
9142
  const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
9070
9143
  const signal = options?.signal;
@@ -9090,22 +9163,22 @@ var PendingRPC = class PendingRPC {
9090
9163
  isSettled = true;
9091
9164
  if (response.error) {
9092
9165
  const rpcError = new require_operators.JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
9093
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
9166
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
9094
9167
  reject(rpcError);
9095
9168
  } else {
9096
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
9169
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
9097
9170
  resolve(response);
9098
9171
  }
9099
9172
  subscription.unsubscribe();
9100
9173
  },
9101
9174
  error: (error) => {
9102
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
9175
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
9103
9176
  isSettled = true;
9104
9177
  reject(error);
9105
9178
  subscription.unsubscribe();
9106
9179
  },
9107
9180
  complete: () => {
9108
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
9181
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
9109
9182
  if (!isSettled) reject(new require_operators.RPCTimeoutError(request.id, timeoutMs));
9110
9183
  subscription.unsubscribe();
9111
9184
  }
@@ -9125,7 +9198,7 @@ var PendingRPC = class PendingRPC {
9125
9198
 
9126
9199
  //#endregion
9127
9200
  //#region src/managers/ClientSessionManager.ts
9128
- const logger$8 = require_operators.getLogger();
9201
+ const logger$9 = require_operators.getLogger();
9129
9202
  const getAddressSearchURI = (options) => {
9130
9203
  const to = options.to?.split("?")[0];
9131
9204
  const from$8 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
@@ -9223,7 +9296,7 @@ var ClientSessionManager = class extends Destroyable {
9223
9296
  try {
9224
9297
  return await this.transport.execute(request, options);
9225
9298
  } catch (error) {
9226
- logger$8.debug("[Session] Execute Error", error);
9299
+ logger$9.debug("[Session] Execute Error", error);
9227
9300
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
9228
9301
  throw error;
9229
9302
  }
@@ -9237,13 +9310,13 @@ var ClientSessionManager = class extends Destroyable {
9237
9310
  return true;
9238
9311
  }
9239
9312
  setupMessageHandlers() {
9240
- logger$8.debug("[Session] Setting up message handlers");
9313
+ logger$9.debug("[Session] Setting up message handlers");
9241
9314
  this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
9242
- logger$8.debug("[Session] Authorization state event received:", authStateEvent);
9315
+ logger$9.debug("[Session] Authorization state event received:", authStateEvent);
9243
9316
  try {
9244
9317
  await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
9245
9318
  } catch (error) {
9246
- logger$8.error("[Session] Failed to handle authorization state update:", error);
9319
+ logger$9.error("[Session] Failed to handle authorization state update:", error);
9247
9320
  this._errors$.next(new require_operators.AuthStateHandlerError(error));
9248
9321
  }
9249
9322
  });
@@ -9251,29 +9324,29 @@ var ClientSessionManager = class extends Destroyable {
9251
9324
  if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
9252
9325
  });
9253
9326
  this.subscribeTo(this.transport.connectionStatus$.pipe((0, rxjs.filter)((status) => status === "connected"), (0, rxjs.exhaustMap)(() => {
9254
- logger$8.debug("[Session] Connection established, initiating authentication");
9327
+ logger$9.debug("[Session] Connection established, initiating authentication");
9255
9328
  return (0, rxjs.from)(this.authenticate()).pipe((0, rxjs.catchError)((error) => {
9256
9329
  this.handleAuthenticationError(error).catch((err) => {
9257
- logger$8.error("[Session] Error handling authentication failure:", err);
9330
+ logger$9.error("[Session] Error handling authentication failure:", err);
9258
9331
  });
9259
9332
  return rxjs.EMPTY;
9260
9333
  }));
9261
9334
  })), void 0);
9262
9335
  this.subscribeTo(this.vertoInvite$, async (invite) => {
9263
- logger$8.debug("[Session] Verto invite received:", invite);
9336
+ logger$9.debug("[Session] Verto invite received:", invite);
9264
9337
  try {
9265
9338
  await this.createInboundCall(invite);
9266
9339
  } catch (error) {
9267
- logger$8.error("[Session] Error handling Verto invite:", error);
9340
+ logger$9.error("[Session] Error handling Verto invite:", error);
9268
9341
  this._errors$.next(new require_operators.VertoInviteHandlerError(error));
9269
9342
  }
9270
9343
  });
9271
9344
  this.subscribeTo(this.vertoAttach$, async (attach) => {
9272
- logger$8.debug("[Session] Verto attach received:", attach);
9345
+ logger$9.debug("[Session] Verto attach received:", attach);
9273
9346
  try {
9274
9347
  await this.handleVertoAttach(attach);
9275
9348
  } catch (error) {
9276
- logger$8.error("[Session] Error handling Verto attach:", error);
9349
+ logger$9.error("[Session] Error handling Verto attach:", error);
9277
9350
  this._errors$.next(new require_operators.VertoAttachHandlerError(error));
9278
9351
  }
9279
9352
  });
@@ -9283,36 +9356,36 @@ var ClientSessionManager = class extends Destroyable {
9283
9356
  const storedState = await this.storage.getItem(this.authorizationStateKey);
9284
9357
  this.authorizationState$.next(storedState ?? void 0);
9285
9358
  } catch (error) {
9286
- logger$8.error("Failed to retrieve authorization state from storage:", error);
9359
+ logger$9.error("Failed to retrieve authorization state from storage:", error);
9287
9360
  this.authorizationState$.next(void 0);
9288
9361
  }
9289
9362
  }
9290
9363
  async updateAuthorizationStateInStorage(authorizationState) {
9291
9364
  if (!authorizationState) {
9292
- logger$8.debug("[Session] Removing authorization state from storage");
9365
+ logger$9.debug("[Session] Removing authorization state from storage");
9293
9366
  try {
9294
9367
  await this.storage.removeItem(this.authorizationStateKey);
9295
9368
  this.authorizationState$.next(void 0);
9296
9369
  } catch (error) {
9297
- logger$8.error("Failed to remove authorization state from storage:", error);
9370
+ logger$9.error("Failed to remove authorization state from storage:", error);
9298
9371
  throw error;
9299
9372
  }
9300
9373
  return;
9301
9374
  }
9302
9375
  try {
9303
- logger$8.debug("[Session] Updating authorization state in storage");
9376
+ logger$9.debug("[Session] Updating authorization state in storage");
9304
9377
  await this.storage.setItem(this.authorizationStateKey, authorizationState);
9305
9378
  this.authorizationState$.next(authorizationState);
9306
9379
  } catch (error) {
9307
- logger$8.error("Failed to retrieve authorization state from storage:", error);
9380
+ logger$9.error("Failed to retrieve authorization state from storage:", error);
9308
9381
  throw error;
9309
9382
  }
9310
9383
  }
9311
9384
  get authStateEvent$() {
9312
9385
  return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, rxjs.tap)((msg) => {
9313
- logger$8.debug("[Session] Received incoming message:", msg);
9386
+ logger$9.debug("[Session] Received incoming message:", msg);
9314
9387
  }), require_operators.filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, rxjs.tap)((event) => {
9315
- logger$8.debug("[Session] Authorization state event received:", event.authorization_state);
9388
+ logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
9316
9389
  })));
9317
9390
  }
9318
9391
  get signalingEvent$() {
@@ -9350,42 +9423,72 @@ var ClientSessionManager = class extends Destroyable {
9350
9423
  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 })));
9351
9424
  }
9352
9425
  async handleAuthenticationError(error) {
9353
- logger$8.error("Authentication error:", error);
9426
+ logger$9.error("Authentication error:", error);
9354
9427
  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);
9355
9428
  const hasStoredState = await (0, rxjs.firstValueFrom)(this.authorizationState$.pipe((0, rxjs.take)(1))) !== void 0;
9356
9429
  if (isRecoverableAuthError && hasStoredState) {
9357
- logger$8.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
9430
+ logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
9358
9431
  try {
9359
9432
  await this.cleanupStoredConnectionParams();
9360
9433
  } catch (cleanupError) {
9361
- logger$8.error("Failed to cleanup stored connection params:", cleanupError);
9434
+ logger$9.error("Failed to cleanup stored connection params:", cleanupError);
9362
9435
  } finally {
9363
9436
  this.transport.reconnect();
9364
9437
  }
9365
9438
  } else this._errors$.next(error);
9366
9439
  }
9440
+ /**
9441
+ * Clear the resume state (authorization_state + protocol) only.
9442
+ *
9443
+ * This is the stale-auth-state recovery helper used by handleAuthError:
9444
+ * the server rejected a reconnect, so the resume state is discarded and a
9445
+ * fresh connect follows. Attach records are deliberately preserved — the
9446
+ * session lives on through the reconnect and reattachCalls() needs the
9447
+ * stored call references afterwards. Do NOT add detachAll() here.
9448
+ *
9449
+ * For public teardown (disconnect/destroy), use {@link teardownSessionState}
9450
+ * instead, which clears the attach records as well.
9451
+ */
9367
9452
  async cleanupStoredConnectionParams() {
9368
9453
  await this.transport.setProtocol(void 0);
9369
9454
  await this.updateAuthorizationStateInStorage(void 0);
9370
9455
  this._authorization$.next(void 0);
9371
9456
  }
9457
+ /**
9458
+ * Public-teardown helper for disconnect()/destroy(). Clears the resume
9459
+ * state (authorization_state + protocol) AND the attach records as one
9460
+ * atomic unit.
9461
+ *
9462
+ * The two stores are coupled: the backend only honors attach records
9463
+ * within the session identified by the resume state, so ending the
9464
+ * session must clear both. Clearing one without the other strands records
9465
+ * no future session can honor (disconnect) or revives a session the
9466
+ * developer explicitly ended (destroy).
9467
+ *
9468
+ * Distinct from {@link cleanupStoredConnectionParams}, which keeps the
9469
+ * attach records for the stale-auth-state recovery path.
9470
+ */
9471
+ async teardownSessionState() {
9472
+ await this.cleanupStoredConnectionParams();
9473
+ await this.attachManager.detachAll();
9474
+ }
9372
9475
  async updateAuthState(authorization_state) {
9373
9476
  try {
9374
9477
  await this.storage.setItem(this.authorizationStateKey, authorization_state);
9375
9478
  } catch (error) {
9376
- logger$8.error("Failed to update authorization state in storage:", error);
9479
+ logger$9.error("Failed to update authorization state in storage:", error);
9377
9480
  this._errors$.next(new require_operators.AuthStateHandlerError(error));
9378
9481
  }
9379
9482
  }
9380
9483
  async reauthenticate(token, dpopToken, options) {
9381
- logger$8.debug("[Session] Re-authenticating session");
9484
+ logger$9.debug("[Session] Re-authenticating session");
9382
9485
  try {
9383
9486
  let resolvedDpopToken = dpopToken;
9384
9487
  if (!resolvedDpopToken && this.dpopManager?.initialized) try {
9385
9488
  resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
9386
9489
  } catch (error) {
9387
9490
  if (this.clientBound) throw error;
9388
- logger$8.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
9491
+ logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
9389
9492
  }
9390
9493
  const request = RPCReauthenticate({
9391
9494
  project: this._authorization$.value?.project_id ?? "",
@@ -9393,24 +9496,24 @@ var ClientSessionManager = class extends Destroyable {
9393
9496
  ...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
9394
9497
  });
9395
9498
  await (0, rxjs.lastValueFrom)((0, rxjs.from)(this.transport.execute(request)).pipe(require_operators.throwOnRPCError(), (0, rxjs.take)(1), (0, rxjs.catchError)((err) => {
9396
- logger$8.error("[Session] Re-authentication RPC failed:", err);
9499
+ logger$9.error("[Session] Re-authentication RPC failed:", err);
9397
9500
  throw err;
9398
9501
  })));
9399
9502
  if (options?.clientBound) this._wasClientBound = true;
9400
- logger$8.debug("[Session] Re-authentication successful, updating stored auth state");
9503
+ logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
9401
9504
  } catch (error) {
9402
- logger$8.error("[Session] Re-authentication failed:", error);
9505
+ logger$9.error("[Session] Re-authentication failed:", error);
9403
9506
  this._errors$.next(new require_operators.AuthStateHandlerError(error));
9404
9507
  throw error;
9405
9508
  }
9406
9509
  }
9407
9510
  async authenticate() {
9408
- logger$8.debug("[Session] Starting authentication process");
9511
+ logger$9.debug("[Session] Starting authentication process");
9409
9512
  const persistedParams = await (0, rxjs.firstValueFrom)((0, rxjs.combineLatest)({
9410
9513
  protocol: this.transport.protocol$,
9411
9514
  authorization_state: this.authorizationState$
9412
9515
  }).pipe((0, rxjs.take)(1)));
9413
- logger$8.debug("[Session] Persisted params:\n", {
9516
+ logger$9.debug("[Session] Persisted params:\n", {
9414
9517
  protocol: persistedParams.protocol,
9415
9518
  authStateLength: persistedParams.authorization_state?.length
9416
9519
  });
@@ -9418,16 +9521,16 @@ var ClientSessionManager = class extends Destroyable {
9418
9521
  const storedToken = this.getCredential().token;
9419
9522
  const isReconnect = hasReconnectState && storedToken;
9420
9523
  let dpopToken;
9421
- if (isReconnect) logger$8.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
9524
+ if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
9422
9525
  else if (this.onBeforeReconnect && this.clientBound) {
9423
- logger$8.debug("[Session] Refreshing credentials before fresh connect");
9526
+ logger$9.debug("[Session] Refreshing credentials before fresh connect");
9424
9527
  await this.onBeforeReconnect();
9425
9528
  }
9426
9529
  if ((!isReconnect || this.clientBound) && this.dpopManager?.initialized) try {
9427
9530
  dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
9428
9531
  } catch (error) {
9429
9532
  if (this.clientBound) throw error;
9430
- logger$8.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
9533
+ logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
9431
9534
  }
9432
9535
  const rpcConnectRequest = RPCConnect({
9433
9536
  authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
@@ -9444,12 +9547,12 @@ var ClientSessionManager = class extends Destroyable {
9444
9547
  } : {}
9445
9548
  });
9446
9549
  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)(() => {
9447
- logger$8.debug("[Session] Response passed filter, processing authentication result");
9550
+ logger$9.debug("[Session] Response passed filter, processing authentication result");
9448
9551
  }), (0, rxjs.take)(1), (0, rxjs.catchError)((err) => {
9449
- logger$8.error("[Session] Authentication RPC failed:", err);
9552
+ logger$9.error("[Session] Authentication RPC failed:", err);
9450
9553
  throw err;
9451
9554
  })));
9452
- logger$8.debug("[Session] Processing authentication result:", {
9555
+ logger$9.debug("[Session] Processing authentication result:", {
9453
9556
  hasProtocol: !!response.protocol,
9454
9557
  hasAuthorization: !!response.authorization,
9455
9558
  hasIceServers: !!response.ice_servers
@@ -9458,12 +9561,12 @@ var ClientSessionManager = class extends Destroyable {
9458
9561
  this._authorization$.next(response.authorization);
9459
9562
  this._iceServers$.next(response.ice_servers ?? []);
9460
9563
  this._authState$.next({ kind: "authenticated" });
9461
- logger$8.debug("[Session] Authentication completed successfully");
9564
+ logger$9.debug("[Session] Authentication completed successfully");
9462
9565
  }
9463
9566
  async disconnect() {
9464
9567
  this.transport.disconnect();
9465
9568
  this._authState$.next({ kind: "unauthenticated" });
9466
- await this.cleanupStoredConnectionParams();
9569
+ await this.teardownSessionState();
9467
9570
  }
9468
9571
  async createInboundCall(invite) {
9469
9572
  const callSession = await this.createCall({
@@ -9494,11 +9597,11 @@ var ClientSessionManager = class extends Destroyable {
9494
9597
  async handleVertoAttach(attach) {
9495
9598
  const { callID } = attach;
9496
9599
  if (callID in this._calls$.value) {
9497
- logger$8.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
9600
+ logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
9498
9601
  return;
9499
9602
  }
9500
9603
  const storedOptions = await this.attachManager.consumePendingAttachment(callID);
9501
- logger$8.debug(`[Session] Creating reattached call for callID: ${callID}`);
9604
+ logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
9502
9605
  const callSession = await this.createCall({
9503
9606
  nodeId: attach.node_id,
9504
9607
  callId: callID,
@@ -9529,7 +9632,7 @@ var ClientSessionManager = class extends Destroyable {
9529
9632
  });
9530
9633
  return callSession;
9531
9634
  } catch (error) {
9532
- logger$8.error("[Session] Error creating outbound call:", error);
9635
+ logger$9.error("[Session] Error creating outbound call:", error);
9533
9636
  callSession?.destroy();
9534
9637
  const callError = new require_operators.CallCreateError(error instanceof rxjs.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
9535
9638
  this._errors$.next(callError);
@@ -9547,7 +9650,7 @@ var ClientSessionManager = class extends Destroyable {
9547
9650
  address = this._directory.get(addressId);
9548
9651
  if (!address) throw new require_operators.DependencyError(`Address ID: ${addressId} not found`);
9549
9652
  } catch {
9550
- logger$8.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
9653
+ logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
9551
9654
  }
9552
9655
  const callSession = this.callFactory.createCall(address, { ...options });
9553
9656
  this.subscribeTo(callSession.status$.pipe((0, rxjs.filter)((status) => status === "destroyed"), (0, rxjs.take)(1)), () => {
@@ -9556,7 +9659,7 @@ var ClientSessionManager = class extends Destroyable {
9556
9659
  });
9557
9660
  return callSession;
9558
9661
  } catch (error) {
9559
- logger$8.error("[Session] Error creating call session:", error);
9662
+ logger$9.error("[Session] Error creating call session:", error);
9560
9663
  throw new require_operators.CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
9561
9664
  }
9562
9665
  }
@@ -9604,7 +9707,7 @@ const isString = (obj) => typeof obj === "string";
9604
9707
 
9605
9708
  //#endregion
9606
9709
  //#region src/managers/ConversationsManager.ts
9607
- const logger$7 = require_operators.getLogger();
9710
+ const logger$8 = require_operators.getLogger();
9608
9711
  var ConversationMessagesFetcher = class extends Fetcher {
9609
9712
  constructor(groupId, http) {
9610
9713
  super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
@@ -9644,13 +9747,13 @@ var ConversationsManager = class {
9644
9747
  }
9645
9748
  throw new require_operators.ConversationError("Join Failed - Unexpected response");
9646
9749
  } catch (error) {
9647
- logger$7.error("[ConversationsManager] Failed to join conversation:", error);
9750
+ logger$8.error("[ConversationsManager] Failed to join conversation:", error);
9648
9751
  throw error;
9649
9752
  }
9650
9753
  }
9651
9754
  async getConversationMessageCollection(addressId) {
9652
9755
  const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
9653
- 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));
9756
+ 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));
9654
9757
  }
9655
9758
  async sendText(text, destinationAddressId) {
9656
9759
  const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
@@ -9667,7 +9770,7 @@ var ConversationsManager = class {
9667
9770
  })).ok) return;
9668
9771
  throw new require_operators.ConversationError("Send Text Failed - Unexpected response");
9669
9772
  } catch (error) {
9670
- logger$7.error("[ConversationsManager] Failed to send text message:", error);
9773
+ logger$8.error("[ConversationsManager] Failed to send text message:", error);
9671
9774
  throw error;
9672
9775
  }
9673
9776
  }
@@ -9675,7 +9778,7 @@ var ConversationsManager = class {
9675
9778
 
9676
9779
  //#endregion
9677
9780
  //#region src/managers/DeviceTokenManager.ts
9678
- const logger$6 = require_operators.getLogger();
9781
+ const logger$7 = require_operators.getLogger();
9679
9782
  /**
9680
9783
  * Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
9681
9784
  * 1. `data.expires_at` — server-provided absolute timestamp
@@ -9685,7 +9788,7 @@ const logger$6 = require_operators.getLogger();
9685
9788
  function resolveExpiresAt(data) {
9686
9789
  if (data.expires_at) return data.expires_at;
9687
9790
  if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
9688
- logger$6.warn("[DeviceToken] Could not determine token expiry, using default");
9791
+ logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
9689
9792
  return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
9690
9793
  }
9691
9794
  /**
@@ -9718,11 +9821,12 @@ var DeviceTokenManager = class extends Destroyable {
9718
9821
  this.getCredential = getCredential;
9719
9822
  this._currentToken$ = this.createBehaviorSubject(null);
9720
9823
  this._refreshInProgress = false;
9824
+ this._paused = false;
9721
9825
  this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
9722
9826
  this.subscribeTo(this._currentToken$.pipe((0, rxjs.filter)(Boolean), (0, rxjs.switchMap)((tokenData) => {
9723
9827
  const expiresAt = resolveExpiresAt(tokenData);
9724
9828
  const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
9725
- logger$6.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
9829
+ logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
9726
9830
  return (0, rxjs.timer)(refreshIn);
9727
9831
  })), () => {
9728
9832
  this.executeRefresh();
@@ -9736,7 +9840,12 @@ var DeviceTokenManager = class extends Destroyable {
9736
9840
  * Activates the Client Bound SAT flow when the user's token has
9737
9841
  * `sat:refresh` scope.
9738
9842
  *
9739
- * Steps:
9843
+ * Returns an {@link ActivationResult} indicating whether the manager
9844
+ * took ownership of refresh duties. The caller must use the `activated`
9845
+ * boolean to decide whether to keep its own refresh path armed — when
9846
+ * `activated` is `false`, the caller is responsible for refresh.
9847
+ *
9848
+ * Steps on success:
9740
9849
  * 1. Check user's `sat_claims` for `sat:refresh` scope
9741
9850
  * 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
9742
9851
  * 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
@@ -9745,32 +9854,51 @@ var DeviceTokenManager = class extends Destroyable {
9745
9854
  async activate(user, session, updateCredential) {
9746
9855
  const { satClaims } = user;
9747
9856
  if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
9748
- logger$6.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
9749
- return;
9857
+ logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
9858
+ return {
9859
+ activated: false,
9860
+ reason: "no-scope"
9861
+ };
9750
9862
  }
9751
9863
  this._session = session;
9752
9864
  this._updateCredential = updateCredential;
9753
9865
  try {
9866
+ const cached = this._currentToken$.value;
9867
+ if (cached && this.isTokenFresh(cached)) {
9868
+ logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
9869
+ const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
9870
+ await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
9871
+ updateCredential({ token: cached.token });
9872
+ return { activated: true };
9873
+ }
9754
9874
  const tokenData = await this.obtainToken();
9755
9875
  if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
9756
9876
  this._effectiveExpireIn = resolveExpireIn(tokenData);
9757
9877
  const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
9758
9878
  await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
9759
9879
  updateCredential({ token: tokenData.token });
9760
- logger$6.info("[DeviceToken] Client Bound SAT activated successfully");
9880
+ logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
9761
9881
  this._currentToken$.next(tokenData);
9882
+ return { activated: true };
9762
9883
  } catch (error) {
9763
- logger$6.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
9884
+ logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
9764
9885
  this.errorHandler(new require_operators.DPoPInitError(error, "Failed to activate Client Bound SAT"));
9765
- const credential = this.getCredential();
9766
- const expiresAt = satClaims.expires_at ?? (credential.expiry_at ? credential.expiry_at / 1e3 : Date.now() / 1e3 + DEVICE_TOKEN_DEFAULT_EXPIRE_IN / 1e3);
9767
- this._currentToken$.next({
9768
- token: credential.token ?? "",
9769
- expires_at: expiresAt
9770
- });
9886
+ return {
9887
+ activated: false,
9888
+ reason: "endpoint-failed"
9889
+ };
9771
9890
  }
9772
9891
  }
9773
9892
  /**
9893
+ * Returns true when the cached token has enough headroom before expiry to
9894
+ * be safely reused on reactivation. The headroom matches the refresh
9895
+ * buffer, so a token within the refresh window is treated as stale (the
9896
+ * reactive pipeline is about to refresh it anyway).
9897
+ */
9898
+ isTokenFresh(token) {
9899
+ return resolveExpiresAt(token) * 1e3 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;
9900
+ }
9901
+ /**
9774
9902
  * Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
9775
9903
  * Returns the full {@link DeviceTokenResponse} including expiry metadata.
9776
9904
  */
@@ -9800,7 +9928,7 @@ var DeviceTokenManager = class extends Destroyable {
9800
9928
  * handled by the reactive pipeline).
9801
9929
  */
9802
9930
  async refreshToken(session, currentToken, updateCredential) {
9803
- logger$6.debug("[DeviceToken] Refreshing Client Bound SAT");
9931
+ logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
9804
9932
  const dpopProof = await this.dpopManager.createHttpProof({
9805
9933
  method: "POST",
9806
9934
  uri: DEVICE_REFRESH_ENDPOINT,
@@ -9822,7 +9950,7 @@ var DeviceTokenManager = class extends Destroyable {
9822
9950
  const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
9823
9951
  await session.reauthenticate(data.token, rpcProof);
9824
9952
  updateCredential({ token: data.token });
9825
- logger$6.info("[DeviceToken] Client Bound SAT refreshed successfully");
9953
+ logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
9826
9954
  return data;
9827
9955
  }
9828
9956
  /**
@@ -9831,18 +9959,22 @@ var DeviceTokenManager = class extends Destroyable {
9831
9959
  * On all retries exhausted, emits to `errorHandler`.
9832
9960
  */
9833
9961
  async executeRefresh() {
9962
+ if (this._paused) {
9963
+ logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
9964
+ return;
9965
+ }
9834
9966
  if (this._refreshInProgress) {
9835
- logger$6.debug("[DeviceToken] Refresh already in progress, skipping");
9967
+ logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
9836
9968
  return;
9837
9969
  }
9838
9970
  const session = this._session;
9839
9971
  const updateCredential = this._updateCredential;
9840
9972
  if (!session || !updateCredential) {
9841
- logger$6.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
9973
+ logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
9842
9974
  return;
9843
9975
  }
9844
9976
  if (!session.authenticated) {
9845
- logger$6.debug("[DeviceToken] Session not authenticated, deferring refresh");
9977
+ logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
9846
9978
  return;
9847
9979
  }
9848
9980
  this._refreshInProgress = true;
@@ -9852,7 +9984,7 @@ var DeviceTokenManager = class extends Destroyable {
9852
9984
  const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
9853
9985
  this._currentToken$.next(newTokenData);
9854
9986
  } catch (error) {
9855
- logger$6.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
9987
+ logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
9856
9988
  this.errorHandler(error instanceof require_operators.TokenRefreshError ? error : new require_operators.TokenRefreshError("Automatic token refresh failed", error));
9857
9989
  } finally {
9858
9990
  this._refreshInProgress = false;
@@ -9870,18 +10002,215 @@ var DeviceTokenManager = class extends Destroyable {
9870
10002
  lastError = error;
9871
10003
  if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
9872
10004
  const delay = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
9873
- logger$6.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay}ms`);
10005
+ logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay}ms`);
9874
10006
  await new Promise((resolve) => setTimeout(resolve, delay));
9875
10007
  }
9876
10008
  }
9877
10009
  throw lastError instanceof Error ? lastError : new require_operators.TokenRefreshError("All refresh retries exhausted", lastError);
9878
10010
  }
10011
+ /**
10012
+ * Stops the reactive refresh pipeline from firing. Use when the underlying
10013
+ * session is being torn down (e.g., during {@link SignalWire.disconnect})
10014
+ * so a scheduled refresh cannot fire against a destroyed session.
10015
+ *
10016
+ * The manager's state (cached token, effective TTL, subscriptions) is
10017
+ * preserved — call {@link resume} to re-enable firing after reconnect.
10018
+ */
10019
+ pause() {
10020
+ this._paused = true;
10021
+ }
10022
+ /** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
10023
+ resume() {
10024
+ this._paused = false;
10025
+ }
9879
10026
  /** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
9880
10027
  destroy() {
9881
10028
  super.destroy();
9882
10029
  }
9883
10030
  };
9884
10031
 
10032
+ //#endregion
10033
+ //#region src/managers/CredentialRefreshCoordinator.ts
10034
+ const logger$6 = require_operators.getLogger();
10035
+ const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
10036
+ /**
10037
+ * Centralizes credential-refresh ownership across the two competing
10038
+ * mechanisms — developer-provided `CredentialProvider.refresh()` and the
10039
+ * Client Bound SAT path via {@link DeviceTokenManager}.
10040
+ *
10041
+ * Maintains the invariant: **at most one refresh mechanism is armed at a
10042
+ * time, and at least one is armed whenever the current credential has an
10043
+ * `expiry_at`**.
10044
+ *
10045
+ * Replaces the previous design where refresh state was distributed across
10046
+ * `SignalWire` (timer field, scheduler method, activation helper) and
10047
+ * `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
10048
+ * one component eliminates the bug class that produced issue #19074.
10049
+ *
10050
+ * Race-safety:
10051
+ * - `_activating` flag prevents overlapping `activate()` calls from racing.
10052
+ * - `_activationGeneration` lets late resolutions detect they've been
10053
+ * preempted by a newer activation (e.g., reconnect during in-flight
10054
+ * `obtainToken`).
10055
+ */
10056
+ var CredentialRefreshCoordinator = class extends Destroyable {
10057
+ constructor(dpopManager, deps) {
10058
+ super();
10059
+ this.deps = deps;
10060
+ this._activating = false;
10061
+ this._activationGeneration = 0;
10062
+ if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
10063
+ }
10064
+ /** True when the Client Bound SAT path is available (DPoP initialized). */
10065
+ get clientBoundSATAvailable() {
10066
+ return this._deviceTokenManager !== void 0;
10067
+ }
10068
+ /** True when the developer-provided refresh timer is currently armed. */
10069
+ get developerRefreshArmed() {
10070
+ return this._developerTimerId !== void 0;
10071
+ }
10072
+ /**
10073
+ * Arms the developer-provided refresh timer to fire shortly before
10074
+ * `expiresAt`. Replaces any previously scheduled developer refresh.
10075
+ *
10076
+ * Idempotent — multiple calls just reschedule. On retry exhaustion,
10077
+ * invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
10078
+ */
10079
+ scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
10080
+ if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
10081
+ 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);
10082
+ this._developerTimerId = setTimeout(async () => {
10083
+ try {
10084
+ if (!provider.refresh) throw new require_operators.InvalidCredentialsError("Credential provider does not support refresh");
10085
+ const newCredentials = await provider.refresh();
10086
+ this.deps.store.write(newCredentials);
10087
+ this.deps.store.persist(newCredentials);
10088
+ logger$6.info("[Coordinator] Credentials refreshed successfully.");
10089
+ if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
10090
+ } catch (error) {
10091
+ const nextAttempt = attempt + 1;
10092
+ logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
10093
+ this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
10094
+ if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
10095
+ else {
10096
+ logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
10097
+ this.deps.notifier.onError(new require_operators.TokenRefreshError("Credential refresh failed after max retries"));
10098
+ this.deps.notifier.onRefreshExhausted();
10099
+ }
10100
+ }
10101
+ }, refreshInterval);
10102
+ }
10103
+ /**
10104
+ * Cancels any scheduled developer-provided refresh. Idempotent.
10105
+ *
10106
+ * @internal Used by the coordinator's own activation flow. External
10107
+ * callers should use {@link suspend} for disconnect-time quiescence —
10108
+ * `suspend()` also pauses the internal Client Bound SAT pipeline.
10109
+ */
10110
+ cancelDeveloperRefresh() {
10111
+ if (this._developerTimerId !== void 0) {
10112
+ clearTimeout(this._developerTimerId);
10113
+ this._developerTimerId = void 0;
10114
+ }
10115
+ }
10116
+ /**
10117
+ * Suspends both refresh paths — cancels the developer timer and pauses
10118
+ * the internal reactive pipeline. Use when the underlying session is
10119
+ * being torn down (e.g., {@link SignalWire.disconnect}). The next
10120
+ * {@link activate} call re-enables the internal pipeline.
10121
+ *
10122
+ * The internal manager's cached token survives — see
10123
+ * {@link DeviceTokenManager.pause} — so a subsequent reconnect can
10124
+ * skip the `/devices/token` exchange entirely.
10125
+ */
10126
+ suspend() {
10127
+ this.cancelDeveloperRefresh();
10128
+ this._deviceTokenManager?.pause();
10129
+ }
10130
+ /**
10131
+ * Asks the Client Bound SAT path to take over refresh. If it accepts,
10132
+ * the developer-provided timer (if any) is cancelled. If it declines, the
10133
+ * developer timer remains armed and a `credential_refresh_fallback`
10134
+ * warning is emitted.
10135
+ *
10136
+ * **Idempotent** — re-entrant calls during an in-flight activation are
10137
+ * dropped. Use `_activationGeneration` to detect stale resolutions
10138
+ * (e.g., a reconnect-triggered activate() that races with an earlier one).
10139
+ */
10140
+ async activate(user, session) {
10141
+ if (this._activating) {
10142
+ logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
10143
+ return;
10144
+ }
10145
+ if (!this._deviceTokenManager) return;
10146
+ this._deviceTokenManager.resume();
10147
+ const generation = ++this._activationGeneration;
10148
+ this._activating = true;
10149
+ try {
10150
+ const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
10151
+ if (generation !== this._activationGeneration) {
10152
+ logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
10153
+ return;
10154
+ }
10155
+ if (result.activated) {
10156
+ this.cancelDeveloperRefresh();
10157
+ logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
10158
+ return;
10159
+ }
10160
+ logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
10161
+ this.deps.notifier.onWarning({
10162
+ code: "credential_refresh_fallback",
10163
+ source: "CredentialProvider",
10164
+ reason: result.reason,
10165
+ message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
10166
+ });
10167
+ } finally {
10168
+ this._activating = false;
10169
+ }
10170
+ }
10171
+ destroy() {
10172
+ this.cancelDeveloperRefresh();
10173
+ this._deviceTokenManager?.destroy();
10174
+ this._deviceTokenManager = void 0;
10175
+ super.destroy();
10176
+ }
10177
+ /**
10178
+ * Races the manager's `activate()` against a hard timeout. A wedged HTTP
10179
+ * layer (e.g., proxy issues) could otherwise hang the activation
10180
+ * indefinitely, leaving the session with no refresh mechanism while the
10181
+ * `_activating` guard blocks subsequent retries.
10182
+ *
10183
+ * On timeout, the manager is paused immediately. The inner `activate()`
10184
+ * may still complete in the background and emit to `_currentToken$`,
10185
+ * which would normally arm the reactive refresh pipeline; pausing
10186
+ * prevents that pipeline from firing while the developer refresh path
10187
+ * is the active mechanism — preserving the "at most one mechanism
10188
+ * armed" invariant. The next `activate()` call resumes the manager.
10189
+ */
10190
+ async withActivationTimeout(inner) {
10191
+ return new Promise((resolve) => {
10192
+ const timer$3 = setTimeout(() => {
10193
+ this._deviceTokenManager?.pause();
10194
+ resolve({
10195
+ activated: false,
10196
+ reason: "activation-timeout"
10197
+ });
10198
+ }, CREDENTIAL_ACTIVATE_TIMEOUT_MS);
10199
+ inner.then((result) => {
10200
+ clearTimeout(timer$3);
10201
+ resolve(result);
10202
+ }, (error) => {
10203
+ clearTimeout(timer$3);
10204
+ this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
10205
+ resolve({
10206
+ activated: false,
10207
+ reason: "endpoint-failed"
10208
+ });
10209
+ });
10210
+ });
10211
+ }
10212
+ };
10213
+
9885
10214
  //#endregion
9886
10215
  //#region src/managers/DiagnosticsCollector.ts
9887
10216
  const logger$5 = require_operators.getLogger();
@@ -10399,6 +10728,10 @@ var TransportManager = class extends Destroyable {
10399
10728
  if (!isSignalwireRequest(message)) return true;
10400
10729
  const eventChannel = message.params.event_channel;
10401
10730
  if (!eventChannel) return true;
10731
+ if (message.params.event_type.startsWith("conversation.")) {
10732
+ logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
10733
+ return true;
10734
+ }
10402
10735
  const currentProtocol = this._currentProtocol;
10403
10736
  if (!currentProtocol) return true;
10404
10737
  if (!eventChannel.includes(currentProtocol)) {
@@ -10591,6 +10924,7 @@ var SignalWire = class extends Destroyable {
10591
10924
  this._isConnected$ = this.createBehaviorSubject(false);
10592
10925
  this._isRegistered$ = this.createBehaviorSubject(false);
10593
10926
  this._errors$ = this.createReplaySubject(1);
10927
+ this._warnings$ = this.createReplaySubject(10);
10594
10928
  this._options = {};
10595
10929
  this._deps = new DependencyContainer();
10596
10930
  this._credentialProvider = credentialProvider;
@@ -10650,6 +10984,27 @@ var SignalWire = class extends Destroyable {
10650
10984
  */
10651
10985
  async resolveCredentials() {
10652
10986
  const fingerprint = await this.initDPoP();
10987
+ this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
10988
+ http: this._deps.http,
10989
+ notifier: {
10990
+ onError: (error) => this._errors$.next(error),
10991
+ onWarning: (warning) => this._warnings$.next(warning),
10992
+ onRefreshExhausted: () => void this.disconnect()
10993
+ },
10994
+ store: {
10995
+ read: () => this._deps.credential,
10996
+ write: (credential) => {
10997
+ this._deps.credential = credential;
10998
+ },
10999
+ merge: (partial) => {
11000
+ this._deps.credential = {
11001
+ ...this._deps.credential,
11002
+ ...partial
11003
+ };
11004
+ },
11005
+ persist: (credential) => this.persistCredential(credential)
11006
+ }
11007
+ });
10653
11008
  if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
10654
11009
  for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
10655
11010
  const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
@@ -10679,7 +11034,16 @@ var SignalWire = class extends Destroyable {
10679
11034
  logger$1.error("[SignalWire] Provided credentials have expired.");
10680
11035
  throw new require_operators.InvalidCredentialsError("Provided credentials have expired.");
10681
11036
  }
10682
- if (_credentials.expiry_at && credentialProvider?.refresh) this.scheduleCredentialRefresh(credentialProvider, _credentials.expiry_at);
11037
+ if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
11038
+ else if (_credentials.expiry_at && !credentialProvider?.refresh) {
11039
+ 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.`);
11040
+ this._warnings$.next({
11041
+ code: "credential_no_refresh_handler",
11042
+ source: "CredentialProvider",
11043
+ message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
11044
+ expiresAt: _credentials.expiry_at
11045
+ });
11046
+ }
10683
11047
  this._deps.credential = _credentials;
10684
11048
  this.persistCredential(_credentials);
10685
11049
  if (this.isConnected && this._clientSession.authenticated && _credentials.token) try {
@@ -10690,35 +11054,6 @@ var SignalWire = class extends Destroyable {
10690
11054
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
10691
11055
  }
10692
11056
  }
10693
- /**
10694
- * Schedules credential refresh with exponential backoff retry on failure.
10695
- * On success, resets attempt counter and schedules the next refresh.
10696
- * After exhausting retries, emits TokenRefreshError and disconnects.
10697
- */
10698
- scheduleCredentialRefresh(credentialProvider, expiresAt, attempt = 0) {
10699
- if (this._refreshTimerId !== void 0) clearTimeout(this._refreshTimerId);
10700
- 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);
10701
- this._refreshTimerId = setTimeout(async () => {
10702
- try {
10703
- if (!credentialProvider.refresh) throw new require_operators.InvalidCredentialsError("Credential provider does not support refresh");
10704
- const newCredentials = await credentialProvider.refresh();
10705
- this._deps.credential = newCredentials;
10706
- this.persistCredential(newCredentials);
10707
- logger$1.info("[SignalWire] Credentials refreshed successfully.");
10708
- if (newCredentials.expiry_at) this.scheduleCredentialRefresh(credentialProvider, newCredentials.expiry_at, 0);
10709
- } catch (error) {
10710
- const nextAttempt = attempt + 1;
10711
- logger$1.error(`[SignalWire] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
10712
- this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
10713
- if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleCredentialRefresh(credentialProvider, expiresAt, nextAttempt);
10714
- else {
10715
- logger$1.error("[SignalWire] Credential refresh exhausted all retries. Disconnecting.");
10716
- this._errors$.next(new require_operators.TokenRefreshError("Credential refresh failed after max retries"));
10717
- this.disconnect();
10718
- }
10719
- }
10720
- }, refreshInterval);
10721
- }
10722
11057
  /** Persist credential to localStorage when persistSession is enabled. */
10723
11058
  persistCredential(credential) {
10724
11059
  if (!credential.token) return;
@@ -10812,6 +11147,7 @@ var SignalWire = class extends Destroyable {
10812
11147
  logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
10813
11148
  const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
10814
11149
  this._deps.credential = newCredentials;
11150
+ if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
10815
11151
  logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
10816
11152
  } catch (error) {
10817
11153
  logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
@@ -10823,33 +11159,12 @@ var SignalWire = class extends Destroyable {
10823
11159
  this._errors$.next(error);
10824
11160
  });
10825
11161
  await this._clientSession.connect();
10826
- if (this._dpopManager?.initialized) {
10827
- if (this._refreshTimerId) {
10828
- clearTimeout(this._refreshTimerId);
10829
- this._refreshTimerId = void 0;
10830
- logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
10831
- }
10832
- this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
10833
- await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
10834
- this._deps.credential = {
10835
- ...this._deps.credential,
10836
- ...cred
10837
- };
10838
- });
10839
- }
11162
+ await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
10840
11163
  this.subscribeTo(this._clientSession.authenticated$.pipe((0, rxjs.skip)(1), (0, rxjs.filter)(Boolean)), async () => {
10841
11164
  try {
10842
- if (this._deviceTokenManager) {
10843
- await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
10844
- this._deps.credential = {
10845
- ...this._deps.credential,
10846
- ...cred
10847
- };
10848
- });
10849
- logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
10850
- }
11165
+ await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
10851
11166
  } catch (error) {
10852
- logger$1.error("[SignalWire] Client Bound SAT re-activation failed (non-fatal):", error);
11167
+ logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
10853
11168
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
10854
11169
  }
10855
11170
  try {
@@ -10935,6 +11250,19 @@ var SignalWire = class extends Destroyable {
10935
11250
  get errors$() {
10936
11251
  return this.deferEmission(this._errors$.asObservable());
10937
11252
  }
11253
+ /**
11254
+ * Observable stream of non-fatal SDK warnings.
11255
+ *
11256
+ * Subscribe to detect SDK behaviors that affect session liveness or developer-facing
11257
+ * contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
11258
+ * refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
11259
+ * scope. Discriminated by `code`.
11260
+ *
11261
+ * Independent from {@link errors$}: existing error consumers are not notified.
11262
+ */
11263
+ get warnings$() {
11264
+ return this.deferEmission(this._warnings$.asObservable());
11265
+ }
10938
11266
  /** Platform WebRTC capabilities detected at construction time. */
10939
11267
  get platformCapabilities() {
10940
11268
  this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
@@ -11003,7 +11331,7 @@ var SignalWire = class extends Destroyable {
11003
11331
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
11004
11332
  }
11005
11333
  try {
11006
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "3.30.0" });
11334
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.1" });
11007
11335
  } catch (error) {
11008
11336
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
11009
11337
  }
@@ -11011,14 +11339,19 @@ var SignalWire = class extends Destroyable {
11011
11339
  /**
11012
11340
  * Disconnects the WebSocket and tears down the current session.
11013
11341
  *
11342
+ * Ends the session identified by the protocol and clears its persisted
11343
+ * resume state (`authorization_state` + protocol) and attach records
11344
+ * together — a later {@link connect} with the same credentials starts a
11345
+ * fresh session and cannot reattach to the ended session's calls.
11346
+ * Credentials and device preferences are preserved. To temporarily stop
11347
+ * receiving inbound calls while keeping the session alive, use
11348
+ * `unregister()` instead.
11349
+ *
11014
11350
  * The client can be reconnected by calling {@link connect} again,
11015
11351
  * which creates a fresh transport and session.
11016
11352
  */
11017
11353
  async disconnect() {
11018
- if (this._refreshTimerId) {
11019
- clearTimeout(this._refreshTimerId);
11020
- this._refreshTimerId = void 0;
11021
- }
11354
+ this._refreshCoordinator?.suspend();
11022
11355
  this._diagnosticsCollector?.record("connection", "disconnected");
11023
11356
  await this.teardownTransportAndSession();
11024
11357
  this._isConnected$.next(false);
@@ -11405,15 +11738,20 @@ var SignalWire = class extends Destroyable {
11405
11738
  prefs.preferredVideoInput = null;
11406
11739
  await this._deviceController.clearDeviceState();
11407
11740
  }
11408
- /** Destroys the client, clearing timers and releasing all resources. */
11741
+ /**
11742
+ * Destroys the client, clearing timers and releasing all resources.
11743
+ *
11744
+ * Intentionally destroying the client ends its session: the resume state
11745
+ * (`authorization_state` + protocol) and the attach records are both
11746
+ * cleared. Credentials and device preferences are preserved — use
11747
+ * {@link resetToDefaults} for a full wipe. To temporarily stop receiving
11748
+ * inbound calls while keeping the session alive, use `unregister()`.
11749
+ */
11409
11750
  destroy() {
11410
- if (this._refreshTimerId) {
11411
- clearTimeout(this._refreshTimerId);
11412
- this._refreshTimerId = void 0;
11413
- }
11414
- this._deviceTokenManager?.destroy();
11751
+ this._refreshCoordinator?.destroy();
11752
+ this._refreshCoordinator = void 0;
11415
11753
  this._dpopManager?.destroy();
11416
- if (this._attachManager) this._attachManager.detachAll();
11754
+ this._clientSession.teardownSessionState();
11417
11755
  this._transport.destroy();
11418
11756
  this._clientSession.destroy();
11419
11757
  try {
@@ -11527,7 +11865,7 @@ var StaticCredentialProvider = class {
11527
11865
  /**
11528
11866
  * Library version from package.json, injected at build time.
11529
11867
  */
11530
- const version = "3.30.0";
11868
+ const version = "4.0.0-rc.1";
11531
11869
  /**
11532
11870
  * Flag indicating the library has been loaded and is ready to use.
11533
11871
  * For UMD builds: `window.SignalWire.ready`
@@ -11549,7 +11887,7 @@ const ready = true;
11549
11887
  */
11550
11888
  const emitReadyEvent = () => {
11551
11889
  if (typeof window !== "undefined") {
11552
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "3.30.0" } });
11890
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.1" } });
11553
11891
  window.dispatchEvent(event);
11554
11892
  }
11555
11893
  };