@signalwire/js 4.0.0-dev-20260910191529 → 4.0.0-dev-20260911135809

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.
@@ -4192,7 +4192,7 @@ var require_merge$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
4192
4192
  var empty_1$7 = require_empty();
4193
4193
  var args_1$8 = require_args();
4194
4194
  var from_1$4 = require_from();
4195
- function merge$6() {
4195
+ function merge$7() {
4196
4196
  var args = [];
4197
4197
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
4198
4198
  var scheduler = args_1$8.popScheduler(args);
@@ -4200,7 +4200,7 @@ var require_merge$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
4200
4200
  var sources = args;
4201
4201
  return !sources.length ? empty_1$7.EMPTY : sources.length === 1 ? innerFrom_1$31.innerFrom(sources[0]) : mergeAll_1$3.mergeAll(concurrent)(from_1$4.from(sources, scheduler));
4202
4202
  }
4203
- exports.merge = merge$6;
4203
+ exports.merge = merge$7;
4204
4204
  }));
4205
4205
 
4206
4206
  //#endregion
@@ -4331,13 +4331,13 @@ var require_race$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
4331
4331
  var innerFrom_1$28 = require_innerFrom();
4332
4332
  var argsOrArgArray_1$4 = require_argsOrArgArray();
4333
4333
  var OperatorSubscriber_1$48 = require_OperatorSubscriber();
4334
- function race$5() {
4334
+ function race$4() {
4335
4335
  var sources = [];
4336
4336
  for (var _i = 0; _i < arguments.length; _i++) sources[_i] = arguments[_i];
4337
4337
  sources = argsOrArgArray_1$4.argsOrArgArray(sources);
4338
4338
  return sources.length === 1 ? innerFrom_1$28.innerFrom(sources[0]) : new Observable_1$6.Observable(raceInit(sources));
4339
4339
  }
4340
- exports.race = race$5;
4340
+ exports.race = race$4;
4341
4341
  function raceInit(sources) {
4342
4342
  return function(subscriber) {
4343
4343
  var subscriptions = [];
@@ -6082,7 +6082,7 @@ var require_merge = /* @__PURE__ */ __commonJSMin(((exports) => {
6082
6082
  var mergeAll_1$2 = require_mergeAll();
6083
6083
  var args_1$3 = require_args();
6084
6084
  var from_1$1 = require_from();
6085
- function merge$5() {
6085
+ function merge$6() {
6086
6086
  var args = [];
6087
6087
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
6088
6088
  var scheduler = args_1$3.popScheduler(args);
@@ -6091,7 +6091,7 @@ var require_merge = /* @__PURE__ */ __commonJSMin(((exports) => {
6091
6091
  mergeAll_1$2.mergeAll(concurrent)(from_1$1.from(__spreadArray$8([source], __read$8(args)), scheduler)).subscribe(subscriber);
6092
6092
  });
6093
6093
  }
6094
- exports.merge = merge$5;
6094
+ exports.merge = merge$6;
6095
6095
  }));
6096
6096
 
6097
6097
  //#endregion
@@ -6938,7 +6938,7 @@ var require_switchMap = /* @__PURE__ */ __commonJSMin(((exports) => {
6938
6938
  var innerFrom_1$6 = require_innerFrom();
6939
6939
  var lift_1$13 = require_lift();
6940
6940
  var OperatorSubscriber_1$11 = require_OperatorSubscriber();
6941
- function switchMap$7(project, resultSelector) {
6941
+ function switchMap$8(project, resultSelector) {
6942
6942
  return lift_1$13.operate(function(source, subscriber) {
6943
6943
  var innerSubscriber = null;
6944
6944
  var index = 0;
@@ -6962,7 +6962,7 @@ var require_switchMap = /* @__PURE__ */ __commonJSMin(((exports) => {
6962
6962
  }));
6963
6963
  });
6964
6964
  }
6965
- exports.switchMap = switchMap$7;
6965
+ exports.switchMap = switchMap$8;
6966
6966
  }));
6967
6967
 
6968
6968
  //#endregion
@@ -9028,6 +9028,25 @@ const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
9028
9028
  const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
9029
9029
  const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
9030
9030
  const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
9031
+ /**
9032
+ * How long call setup may wait for the network, measured from local media
9033
+ * settling until the member id arrives.
9034
+ *
9035
+ * Local media acquisition is deliberately NOT inside this: a permission prompt
9036
+ * or a device picker is human time, and a human may take as long as they like
9037
+ * without spending the server's budget. The clock starts when acquisition ends.
9038
+ *
9039
+ * Must exceed `iceGatheringTimeout + the RPC timeout`, which both run inside it.
9040
+ */
9041
+ const DEFAULT_CALL_SIGNALING_TIMEOUT_MS = 12e3;
9042
+ /**
9043
+ * How long an auxiliary leg (screen share, additional device) may take to
9044
+ * connect once its media is in hand.
9045
+ *
9046
+ * One bound for both: the picker is human time and sits outside it. What remains
9047
+ * — offer, ICE, invite, answer, DTLS — is the same work for either leg kind.
9048
+ */
9049
+ const DEFAULT_AUX_LEG_CONNECT_TIMEOUT_MS = 15e3;
9031
9050
  const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
9032
9051
  const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
9033
9052
  const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
@@ -9380,6 +9399,18 @@ var CollectionFetchError = class extends Error {
9380
9399
  this.name = "CollectionFetchError";
9381
9400
  }
9382
9401
  };
9402
+ /**
9403
+ * An auxiliary leg did not connect within its budget. Typed rather than a bare
9404
+ * RxJS `TimeoutError` so the leg and cause survive.
9405
+ */
9406
+ var AuxiliaryLegTimeoutError = class extends Error {
9407
+ constructor(leg, originalError) {
9408
+ super(`Timed out waiting for the ${leg} connection to be established`, { cause: originalError });
9409
+ this.leg = leg;
9410
+ this.originalError = originalError;
9411
+ this.name = "AuxiliaryLegTimeoutError";
9412
+ }
9413
+ };
9383
9414
  var MediaTrackError = class extends Error {
9384
9415
  constructor(operation, kind, originalError) {
9385
9416
  super(`Media track ${operation} failed for ${kind}`, { cause: originalError instanceof Error ? originalError : void 0 });
@@ -9656,9 +9687,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
9656
9687
  defaultLogger$1 = new Logger();
9657
9688
  defaultLogger$1.getLogger = function getLogger$1(name) {
9658
9689
  if (typeof name !== "symbol" && typeof name !== "string" || name === "") throw new TypeError("You must supply a name when creating a logger.");
9659
- var logger$33 = _loggersByName[name];
9660
- if (!logger$33) logger$33 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
9661
- return logger$33;
9690
+ var logger$34 = _loggersByName[name];
9691
+ if (!logger$34) logger$34 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
9692
+ return logger$34;
9662
9693
  };
9663
9694
  var _log = typeof window !== undefinedType ? window.log : void 0;
9664
9695
  defaultLogger$1.noConflict = function() {
@@ -9694,8 +9725,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
9694
9725
  defaultLogger.setLevel(defaultLoggerLevel);
9695
9726
  let userLogger = null;
9696
9727
  /** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
9697
- const setLogger = (logger$33) => {
9698
- userLogger = logger$33;
9728
+ const setLogger = (logger$34) => {
9729
+ userLogger = logger$34;
9699
9730
  };
9700
9731
  let debugOptions = {};
9701
9732
  /** Configure debug options (e.g., `{ logWsTraffic: true }`). */
@@ -9739,8 +9770,8 @@ const wsTraffic = (options) => {
9739
9770
  loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
9740
9771
  };
9741
9772
  const getLogger = () => {
9742
- const logger$33 = getLoggerInstance();
9743
- return new Proxy(logger$33, { get(_target, prop, _receiver) {
9773
+ const logger$34 = getLoggerInstance();
9774
+ return new Proxy(logger$34, { get(_target, prop, _receiver) {
9744
9775
  if (prop === "wsTraffic") return wsTraffic;
9745
9776
  const instance = getLoggerInstance();
9746
9777
  const value = Reflect.get(instance, prop);
@@ -9792,7 +9823,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
9792
9823
 
9793
9824
  //#endregion
9794
9825
  //#region src/controllers/HTTPRequestController.ts
9795
- const logger$32 = getLogger();
9826
+ const logger$33 = getLogger();
9796
9827
  const GET_PARAMS = {
9797
9828
  method: "GET",
9798
9829
  headers: { Accept: "application/json" }
@@ -9856,7 +9887,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9856
9887
  this._responses$.next(response);
9857
9888
  return response;
9858
9889
  } catch (error) {
9859
- logger$32.error("[HTTPRequestController] Request error:", error);
9890
+ logger$33.error("[HTTPRequestController] Request error:", error);
9860
9891
  this._status$.next("error");
9861
9892
  const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
9862
9893
  this._errors$.next(err);
@@ -9883,7 +9914,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9883
9914
  const url = this.buildURL(request.url);
9884
9915
  const headers = this.buildHeaders(request.headers);
9885
9916
  const timeout$5 = request.timeout ?? this.requestTimeout;
9886
- logger$32.debug("[HTTPRequestController] Executing request:", {
9917
+ logger$33.debug("[HTTPRequestController] Executing request:", {
9887
9918
  method: request.method,
9888
9919
  url,
9889
9920
  headers: Object.keys(headers).reduce((acc, key) => {
@@ -9903,7 +9934,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9903
9934
  });
9904
9935
  clearTimeout(timeoutId);
9905
9936
  const httpResponse = await this.convertResponse(response);
9906
- logger$32.debug("[HTTPRequestController] Response received:", {
9937
+ logger$33.debug("[HTTPRequestController] Response received:", {
9907
9938
  status: response.status,
9908
9939
  statusText: response.statusText,
9909
9940
  headers: [...response.headers.entries()],
@@ -9913,7 +9944,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9913
9944
  } catch (error) {
9914
9945
  clearTimeout(timeoutId);
9915
9946
  if (error instanceof Error && error.name === "AbortError") throw new RequestTimeoutError(`Request timeout after ${timeout$5}ms`, { cause: error });
9916
- logger$32.error("[HTTPRequestController] Request failed:", error);
9947
+ logger$33.error("[HTTPRequestController] Request failed:", error);
9917
9948
  throw error;
9918
9949
  }
9919
9950
  }
@@ -9927,8 +9958,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9927
9958
  const credential = this.getCredential();
9928
9959
  if (credential.token) {
9929
9960
  headers.Authorization = `Bearer ${credential.token}`;
9930
- logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
9931
- } else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
9961
+ logger$33.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
9962
+ } else logger$33.warn("[HTTPRequestController] No credentials available for authentication");
9932
9963
  return headers;
9933
9964
  }
9934
9965
  /**
@@ -10076,7 +10107,7 @@ function fromMsToSec(milliseconds) {
10076
10107
 
10077
10108
  //#endregion
10078
10109
  //#region src/containers/PreferencesContainer.ts
10079
- const logger$31 = getLogger();
10110
+ const logger$32 = getLogger();
10080
10111
  var PreferencesContainer = class PreferencesContainer {
10081
10112
  static get instance() {
10082
10113
  this._instance ??= new PreferencesContainer();
@@ -10738,7 +10769,7 @@ var ClientPreferences = class {
10738
10769
  if (!this._storage) return;
10739
10770
  const data = collectStoredPreferences();
10740
10771
  this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
10741
- logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
10772
+ logger$32.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
10742
10773
  });
10743
10774
  }
10744
10775
  /** Loads preferences from storage and applies them to the container. */
@@ -10747,7 +10778,7 @@ var ClientPreferences = class {
10747
10778
  this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
10748
10779
  if (stored) applyStoredPreferences(stored);
10749
10780
  }).catch((error) => {
10750
- logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
10781
+ logger$32.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
10751
10782
  });
10752
10783
  }
10753
10784
  };
@@ -10769,7 +10800,7 @@ function toError(value) {
10769
10800
  //#endregion
10770
10801
  //#region src/controllers/NavigatorDeviceController.ts
10771
10802
  var import_cjs$29 = require_cjs();
10772
- const logger$30 = getLogger();
10803
+ const logger$31 = getLogger();
10773
10804
  /** Maps a device kind to its storage key. */
10774
10805
  const DEVICE_STORAGE_KEYS = {
10775
10806
  audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
@@ -10791,7 +10822,7 @@ var NavigatorDeviceController = class extends Destroyable {
10791
10822
  super();
10792
10823
  this.webRTCApiProvider = webRTCApiProvider;
10793
10824
  this.deviceChangeHandler = () => {
10794
- logger$30.debug("[DeviceController] Device change detected");
10825
+ logger$31.debug("[DeviceController] Device change detected");
10795
10826
  this.enumerateDevices();
10796
10827
  };
10797
10828
  this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
@@ -10856,13 +10887,13 @@ var NavigatorDeviceController = class extends Destroyable {
10856
10887
  return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$)));
10857
10888
  }
10858
10889
  get selectedAudioInputDevice$() {
10859
- return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio input device changed:", info))));
10890
+ return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$31.debug("[DeviceController] Selected audio input device changed:", info))));
10860
10891
  }
10861
10892
  get selectedAudioOutputDevice$() {
10862
- return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio output device changed:", info))));
10893
+ return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$31.debug("[DeviceController] Selected audio output device changed:", info))));
10863
10894
  }
10864
10895
  get selectedVideoInputDevice$() {
10865
- return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected video input device changed:", info))));
10896
+ return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$31.debug("[DeviceController] Selected video input device changed:", info))));
10866
10897
  }
10867
10898
  get selectedAudioInputDevice() {
10868
10899
  if (this._audioInputDisabled$.value) return null;
@@ -10937,7 +10968,7 @@ var NavigatorDeviceController = class extends Destroyable {
10937
10968
  if (device) this.persistDeviceSelection("audioinput", device);
10938
10969
  }
10939
10970
  selectVideoInputDevice(device) {
10940
- logger$30.debug("[DeviceController] Setting selected video input device:", device);
10971
+ logger$31.debug("[DeviceController] Setting selected video input device:", device);
10941
10972
  if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
10942
10973
  const previous = this._selectedDevicesState$.value.videoinput;
10943
10974
  if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
@@ -10994,7 +11025,7 @@ var NavigatorDeviceController = class extends Destroyable {
10994
11025
  }
10995
11026
  const fromHistory = this._deviceHistory.findInHistory(kind, devices);
10996
11027
  if (fromHistory) {
10997
- logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
11028
+ logger$31.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
10998
11029
  this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
10999
11030
  return fromHistory;
11000
11031
  }
@@ -11047,7 +11078,7 @@ var NavigatorDeviceController = class extends Destroyable {
11047
11078
  try {
11048
11079
  await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
11049
11080
  } catch (error) {
11050
- logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
11081
+ logger$31.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
11051
11082
  }
11052
11083
  }
11053
11084
  async loadPersistedDevices() {
@@ -11063,7 +11094,7 @@ var NavigatorDeviceController = class extends Destroyable {
11063
11094
  [kind]: stored
11064
11095
  };
11065
11096
  } catch (error) {
11066
- logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
11097
+ logger$31.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
11067
11098
  }
11068
11099
  }
11069
11100
  /** Clears device history, persisted selections, and re-enumerates devices. */
@@ -11081,7 +11112,7 @@ var NavigatorDeviceController = class extends Destroyable {
11081
11112
  this.disableDeviceMonitoring();
11082
11113
  this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
11083
11114
  if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, import_cjs$29.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
11084
- logger$30.debug("[DeviceController] Polling devices due to interval");
11115
+ logger$31.debug("[DeviceController] Polling devices due to interval");
11085
11116
  this.enumerateDevices();
11086
11117
  });
11087
11118
  this.enumerateDevices();
@@ -11107,13 +11138,13 @@ var NavigatorDeviceController = class extends Destroyable {
11107
11138
  videoinput: []
11108
11139
  });
11109
11140
  this._devicesState$.next(devicesByKind);
11110
- logger$30.debug("[DeviceController] Devices enumerated:", {
11141
+ logger$31.debug("[DeviceController] Devices enumerated:", {
11111
11142
  audioInputs: devicesByKind.audioinput.length,
11112
11143
  audioOutputs: devicesByKind.audiooutput.length,
11113
11144
  videoInputs: devicesByKind.videoinput.length
11114
11145
  });
11115
11146
  } catch (error) {
11116
- logger$30.error("[DeviceController] Failed to enumerate devices:", error);
11147
+ logger$31.error("[DeviceController] Failed to enumerate devices:", error);
11117
11148
  this._errors$.next(toError(error));
11118
11149
  }
11119
11150
  }
@@ -11129,7 +11160,7 @@ var NavigatorDeviceController = class extends Destroyable {
11129
11160
  stream.getTracks().forEach((t) => t.stop());
11130
11161
  return capabilities;
11131
11162
  } catch (error) {
11132
- logger$30.error("[DeviceController] Failed to get device capabilities:", error);
11163
+ logger$31.error("[DeviceController] Failed to get device capabilities:", error);
11133
11164
  this._errors$.next(toError(error));
11134
11165
  throw error;
11135
11166
  }
@@ -11380,7 +11411,7 @@ var DependencyContainer = class {
11380
11411
 
11381
11412
  //#endregion
11382
11413
  //#region src/controllers/CryptoController.ts
11383
- const logger$29 = getLogger();
11414
+ const logger$30 = getLogger();
11384
11415
  const DPOP_DB_NAME = "sw-dpop";
11385
11416
  const DPOP_DB_VERSION = 1;
11386
11417
  const DPOP_STORE_NAME = "keys";
@@ -11439,7 +11470,7 @@ async function loadKeyPairFromDB() {
11439
11470
  tx.oncomplete = () => db.close();
11440
11471
  });
11441
11472
  } catch (error) {
11442
- logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
11473
+ logger$30.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
11443
11474
  return null;
11444
11475
  }
11445
11476
  }
@@ -11459,7 +11490,7 @@ async function saveKeyPairToDB(keyPair) {
11459
11490
  };
11460
11491
  });
11461
11492
  } catch (error) {
11462
- logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
11493
+ logger$30.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
11463
11494
  }
11464
11495
  }
11465
11496
  async function deleteKeyPairFromDB() {
@@ -11478,7 +11509,7 @@ async function deleteKeyPairFromDB() {
11478
11509
  };
11479
11510
  });
11480
11511
  } catch (error) {
11481
- logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
11512
+ logger$30.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
11482
11513
  }
11483
11514
  }
11484
11515
  /**
@@ -11538,13 +11569,13 @@ var CryptoController = class {
11538
11569
  this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
11539
11570
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
11540
11571
  this._initialized = true;
11541
- logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
11572
+ logger$30.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
11542
11573
  return this._fingerprint;
11543
11574
  } catch (error) {
11544
- logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
11575
+ logger$30.warn("[DPoP] Stored key pair unusable, generating new one:", error);
11545
11576
  await deleteKeyPairFromDB();
11546
11577
  }
11547
- logger$29.debug("[DPoP] Generating RSA key pair");
11578
+ logger$30.debug("[DPoP] Generating RSA key pair");
11548
11579
  this._keyPair = await crypto.subtle.generateKey({
11549
11580
  name: "RSASSA-PKCS1-v1_5",
11550
11581
  modulusLength: 2048,
@@ -11559,7 +11590,7 @@ var CryptoController = class {
11559
11590
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
11560
11591
  this._initialized = true;
11561
11592
  await saveKeyPairToDB(this._keyPair);
11562
- logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
11593
+ logger$30.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
11563
11594
  return this._fingerprint;
11564
11595
  }
11565
11596
  /**
@@ -11625,7 +11656,7 @@ var CryptoController = class {
11625
11656
  this._fingerprint = null;
11626
11657
  this._initialized = false;
11627
11658
  deleteKeyPairFromDB();
11628
- logger$29.debug("[DPoP] Controller destroyed");
11659
+ logger$30.debug("[DPoP] Controller destroyed");
11629
11660
  }
11630
11661
  get publicJwk() {
11631
11662
  if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
@@ -11649,7 +11680,7 @@ var CryptoController = class {
11649
11680
  //#endregion
11650
11681
  //#region src/controllers/NetworkMonitor.ts
11651
11682
  var import_cjs$28 = require_cjs();
11652
- const logger$28 = getLogger();
11683
+ const logger$29 = getLogger();
11653
11684
  /**
11654
11685
  * Safely check whether we are running in a browser environment
11655
11686
  * with `window` and the relevant event targets.
@@ -11706,7 +11737,7 @@ var NetworkMonitor = class extends Destroyable {
11706
11737
  }
11707
11738
  attachListeners() {
11708
11739
  if (!hasBrowserNetworkEvents()) {
11709
- logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
11740
+ logger$29.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
11710
11741
  return;
11711
11742
  }
11712
11743
  window.addEventListener("online", this._onOnline);
@@ -11714,7 +11745,7 @@ var NetworkMonitor = class extends Destroyable {
11714
11745
  const connection = getNetworkConnection();
11715
11746
  if (connection) connection.addEventListener("change", this._onConnectionChange);
11716
11747
  this._listenersAttached = true;
11717
- logger$28.debug("NetworkMonitor: event listeners attached");
11748
+ logger$29.debug("NetworkMonitor: event listeners attached");
11718
11749
  }
11719
11750
  removeListeners() {
11720
11751
  if (!this._listenersAttached) return;
@@ -11725,10 +11756,10 @@ var NetworkMonitor = class extends Destroyable {
11725
11756
  if (connection) connection.removeEventListener("change", this._onConnectionChange);
11726
11757
  }
11727
11758
  this._listenersAttached = false;
11728
- logger$28.debug("NetworkMonitor: event listeners removed");
11759
+ logger$29.debug("NetworkMonitor: event listeners removed");
11729
11760
  }
11730
11761
  handleOnline() {
11731
- logger$28.info("NetworkMonitor: browser went online");
11762
+ logger$29.info("NetworkMonitor: browser went online");
11732
11763
  this._isOnline$.next(true);
11733
11764
  this._networkChange$.next({
11734
11765
  type: "online",
@@ -11737,7 +11768,7 @@ var NetworkMonitor = class extends Destroyable {
11737
11768
  });
11738
11769
  }
11739
11770
  handleOffline() {
11740
- logger$28.info("NetworkMonitor: browser went offline");
11771
+ logger$29.info("NetworkMonitor: browser went offline");
11741
11772
  this._isOnline$.next(false);
11742
11773
  this._networkChange$.next({
11743
11774
  type: "offline",
@@ -11746,7 +11777,7 @@ var NetworkMonitor = class extends Destroyable {
11746
11777
  }
11747
11778
  handleConnectionChange() {
11748
11779
  const networkType = getNetworkType();
11749
- logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
11780
+ logger$29.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
11750
11781
  this._networkChange$.next({
11751
11782
  type: "connection_change",
11752
11783
  timestamp: Date.now(),
@@ -11862,7 +11893,7 @@ function getNavigatorMediaDevices() {
11862
11893
  //#endregion
11863
11894
  //#region src/controllers/PreflightRunner.ts
11864
11895
  var import_cjs$27 = require_cjs();
11865
- const logger$27 = getLogger();
11896
+ const logger$28 = getLogger();
11866
11897
  const DEFAULT_MEDIA_TEST_DURATION_S = 10;
11867
11898
  const ICE_GATHERING_TIMEOUT_MS = 1e4;
11868
11899
  const SIGNALING_RTT_TIMEOUT_MS = 5e3;
@@ -11911,7 +11942,7 @@ var PreflightRunner = class extends Destroyable {
11911
11942
  if (!this._options.skipMediaTest) try {
11912
11943
  bandwidth = await this.testMediaBandwidth(destination);
11913
11944
  } catch (error) {
11914
- logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
11945
+ logger$28.warn("[PreflightRunner] Media bandwidth test failed:", error);
11915
11946
  warnings.push("Media bandwidth test failed");
11916
11947
  }
11917
11948
  return {
@@ -11923,7 +11954,7 @@ var PreflightRunner = class extends Destroyable {
11923
11954
  warnings
11924
11955
  };
11925
11956
  } catch (error) {
11926
- logger$27.error("[PreflightRunner] Preflight test failed:", error);
11957
+ logger$28.error("[PreflightRunner] Preflight test failed:", error);
11927
11958
  throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
11928
11959
  } finally {
11929
11960
  this.destroy();
@@ -11954,7 +11985,7 @@ var PreflightRunner = class extends Destroyable {
11954
11985
  if (track.kind === "video" && track.readyState === "live") videoWorking = true;
11955
11986
  }
11956
11987
  } catch (error) {
11957
- logger$27.warn("[PreflightRunner] Device test failed:", error);
11988
+ logger$28.warn("[PreflightRunner] Device test failed:", error);
11958
11989
  } finally {
11959
11990
  if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
11960
11991
  }
@@ -12012,7 +12043,7 @@ var PreflightRunner = class extends Destroyable {
12012
12043
  rttMs
12013
12044
  };
12014
12045
  } catch (error) {
12015
- logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
12046
+ logger$28.warn("[PreflightRunner] ICE connectivity test failed:", error);
12016
12047
  return {
12017
12048
  type: "failed",
12018
12049
  turnReachable: false,
@@ -12060,7 +12091,7 @@ var PreflightRunner = class extends Destroyable {
12060
12091
  //#endregion
12061
12092
  //#region src/controllers/VisibilityController.ts
12062
12093
  var import_cjs$26 = require_cjs();
12063
- const logger$26 = getLogger();
12094
+ const logger$27 = getLogger();
12064
12095
  /**
12065
12096
  * Checks whether the document visibility API is available.
12066
12097
  */
@@ -12097,8 +12128,8 @@ var VisibilityController = class extends Destroyable {
12097
12128
  this._boundHandler = this._handleVisibilityChange.bind(this);
12098
12129
  if (this._hasVisibilityApi) {
12099
12130
  document.addEventListener("visibilitychange", this._boundHandler);
12100
- logger$26.debug("VisibilityController: listening for visibilitychange events");
12101
- } else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
12131
+ logger$27.debug("VisibilityController: listening for visibilitychange events");
12132
+ } else logger$27.debug("VisibilityController: document visibility API not available, defaulting to visible");
12102
12133
  }
12103
12134
  /**
12104
12135
  * Observable of the current visibility state.
@@ -12123,7 +12154,7 @@ var VisibilityController = class extends Destroyable {
12123
12154
  destroy() {
12124
12155
  if (this._hasVisibilityApi) {
12125
12156
  document.removeEventListener("visibilitychange", this._boundHandler);
12126
- logger$26.debug("VisibilityController: removed visibilitychange listener");
12157
+ logger$27.debug("VisibilityController: removed visibilitychange listener");
12127
12158
  }
12128
12159
  super.destroy();
12129
12160
  }
@@ -12141,7 +12172,7 @@ var VisibilityController = class extends Destroyable {
12141
12172
  timestamp: Date.now()
12142
12173
  };
12143
12174
  this._visibilityChange$.next(changeEvent);
12144
- logger$26.debug("VisibilityController: visibility changed", {
12175
+ logger$27.debug("VisibilityController: visibility changed", {
12145
12176
  from: previousState,
12146
12177
  to: newState
12147
12178
  });
@@ -12423,7 +12454,7 @@ function isRequesterValidationError(error) {
12423
12454
 
12424
12455
  //#endregion
12425
12456
  //#region src/managers/AttachManager.ts
12426
- const logger$25 = getLogger();
12457
+ const logger$26 = getLogger();
12427
12458
  var AttachManager = class {
12428
12459
  constructor(storage, deviceController, reconnectCallsTimeout, attachKey, credentialRecovered) {
12429
12460
  this.storage = storage;
@@ -12445,7 +12476,7 @@ var AttachManager = class {
12445
12476
  try {
12446
12477
  return await this.storage.getItem(this.attachKey) ?? {};
12447
12478
  } catch (error) {
12448
- logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
12479
+ logger$26.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
12449
12480
  return {};
12450
12481
  }
12451
12482
  }
@@ -12453,7 +12484,7 @@ var AttachManager = class {
12453
12484
  try {
12454
12485
  await this.storage.setItem(this.attachKey, attached);
12455
12486
  } catch (error) {
12456
- logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
12487
+ logger$26.warn("[AttachManager] Failed to write attached calls to storage", error);
12457
12488
  }
12458
12489
  }
12459
12490
  /**
@@ -12472,7 +12503,7 @@ var AttachManager = class {
12472
12503
  }
12473
12504
  async attach(call) {
12474
12505
  if (!call.to) {
12475
- logger$25.warn("[AttachManager] Skip attach for calls with no destination");
12506
+ logger$26.warn("[AttachManager] Skip attach for calls with no destination");
12476
12507
  return;
12477
12508
  }
12478
12509
  const destination = call.to;
@@ -12532,25 +12563,25 @@ var AttachManager = class {
12532
12563
  callId,
12533
12564
  ...options
12534
12565
  });
12535
- logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
12566
+ logger$26.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
12536
12567
  succeeded = true;
12537
12568
  break;
12538
12569
  } catch (error) {
12539
- logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
12570
+ logger$26.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
12540
12571
  if (isRequesterValidationError(error)) {
12541
12572
  refusedOnCredentials = true;
12542
- logger$25.warn(`[AttachManager] Reattach of ${callId} was refused on credentials; not retrying.`);
12573
+ logger$26.warn(`[AttachManager] Reattach of ${callId} was refused on credentials; not retrying.`);
12543
12574
  break;
12544
12575
  }
12545
12576
  if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
12546
12577
  }
12547
12578
  if (!succeeded) if (this.credentialRecovered() && !refusedOnCredentials) {
12548
- logger$25.warn(`[AttachManager] Reattach of ${callId} was denied after a verified reauthentication, removing reference`);
12579
+ logger$26.warn(`[AttachManager] Reattach of ${callId} was denied after a verified reauthentication, removing reference`);
12549
12580
  await this.detach({
12550
12581
  id: callId,
12551
12582
  mediaDirections: attachment.mediaDirections
12552
12583
  });
12553
- } else logger$25.warn(`[AttachManager] Reattach failed for call ${callId}; keeping the reference (credential refused or never proven good)`);
12584
+ } else logger$26.warn(`[AttachManager] Reattach failed for call ${callId}; keeping the reference (credential refused or never proven good)`);
12554
12585
  }
12555
12586
  }
12556
12587
  /**
@@ -12651,12 +12682,12 @@ var require_race = /* @__PURE__ */ __commonJSMin(((exports) => {
12651
12682
  exports.race = void 0;
12652
12683
  var argsOrArgArray_1 = require_argsOrArgArray();
12653
12684
  var raceWith_1$1 = require_raceWith();
12654
- function race$4() {
12685
+ function race$3() {
12655
12686
  var args = [];
12656
12687
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
12657
12688
  return raceWith_1$1.raceWith.apply(void 0, __spreadArray([], __read(argsOrArgArray_1.argsOrArgArray(args))));
12658
12689
  }
12659
- exports.race = race$4;
12690
+ exports.race = race$3;
12660
12691
  }));
12661
12692
 
12662
12693
  //#endregion
@@ -13716,7 +13747,7 @@ function toggleHandraiseMethod(is) {
13716
13747
 
13717
13748
  //#endregion
13718
13749
  //#region src/core/entities/Participant.ts
13719
- const logger$24 = getLogger();
13750
+ const logger$25 = getLogger();
13720
13751
  const initialState = {};
13721
13752
  /**
13722
13753
  * Represents a participant in a call.
@@ -14190,6 +14221,9 @@ var SelfParticipant = class extends Participant {
14190
14221
  *
14191
14222
  * The call is unaffected when acquisition fails.
14192
14223
  *
14224
+ * Known issue: stopping the share before its leg connects can leave this
14225
+ * promise pending until the call ends, so do not gate UI on it.
14226
+ *
14193
14227
  * @throws The raw `getDisplayMedia` error. A dismissed picker or a
14194
14228
  * permission denial rejects with a `NotAllowedError` `DOMException` —
14195
14229
  * inspect `error.name` to tell benign cancels apart from real failures.
@@ -14198,7 +14232,7 @@ var SelfParticipant = class extends Participant {
14198
14232
  try {
14199
14233
  await this.vertoManager.addScreenMedia();
14200
14234
  } catch (error) {
14201
- logger$24.error("[Participant.startScreenShare] Screen share error:", error);
14235
+ logger$25.error("[Participant.startScreenShare] Screen share error:", error);
14202
14236
  throw error;
14203
14237
  }
14204
14238
  }
@@ -14219,14 +14253,18 @@ var SelfParticipant = class extends Participant {
14219
14253
  *
14220
14254
  * The call is unaffected when acquisition fails.
14221
14255
  *
14256
+ * Known issue: removing the device before its leg connects can leave this
14257
+ * promise pending until the call ends, so do not gate UI on it.
14258
+ *
14222
14259
  * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
14223
- * permission denial) — inspect `error.name` to decide how to react.
14260
+ * permission denial) — inspect `error.name` to decide how to react — or
14261
+ * `AuxiliaryLegTimeoutError` if the leg does not connect in time.
14224
14262
  */
14225
14263
  async addAdditionalDevice(options) {
14226
14264
  try {
14227
14265
  await this.vertoManager.addInputDevice(options);
14228
14266
  } catch (error) {
14229
- logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
14267
+ logger$25.error("[Participant.addAdditionalDevice] Additional device error:", error);
14230
14268
  throw error;
14231
14269
  }
14232
14270
  }
@@ -14289,7 +14327,7 @@ var SelfParticipant = class extends Participant {
14289
14327
  */
14290
14328
  exitStudioModeIfActive() {
14291
14329
  if (this._studioAudio$.value) {
14292
- logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
14330
+ logger$25.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
14293
14331
  this._studioAudio$.next(false);
14294
14332
  }
14295
14333
  }
@@ -14313,7 +14351,7 @@ var SelfParticipant = class extends Participant {
14313
14351
  try {
14314
14352
  await super.mute();
14315
14353
  } catch (error) {
14316
- logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
14354
+ logger$25.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
14317
14355
  } finally {
14318
14356
  this.vertoManager.muteMainAudioInputDevice();
14319
14357
  }
@@ -14323,7 +14361,7 @@ var SelfParticipant = class extends Participant {
14323
14361
  try {
14324
14362
  await super.unmute();
14325
14363
  } catch (error) {
14326
- logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
14364
+ logger$25.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
14327
14365
  } finally {
14328
14366
  await this.vertoManager.unmuteMainAudioInputDevice();
14329
14367
  }
@@ -14333,7 +14371,7 @@ var SelfParticipant = class extends Participant {
14333
14371
  try {
14334
14372
  await super.muteVideo();
14335
14373
  } catch (error) {
14336
- logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
14374
+ logger$25.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
14337
14375
  } finally {
14338
14376
  this.vertoManager.muteMainVideoInputDevice();
14339
14377
  }
@@ -14343,7 +14381,7 @@ var SelfParticipant = class extends Participant {
14343
14381
  try {
14344
14382
  await super.unmuteVideo();
14345
14383
  } catch (error) {
14346
- logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
14384
+ logger$25.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
14347
14385
  } finally {
14348
14386
  await this.vertoManager.unmuteMainVideoInputDevice();
14349
14387
  }
@@ -14548,7 +14586,7 @@ function filterAs(predicate, resultPath) {
14548
14586
  //#endregion
14549
14587
  //#region src/operators/throwOnRPCError.ts
14550
14588
  var import_cjs$21 = require_cjs();
14551
- const logger$23 = getLogger();
14589
+ const logger$24 = getLogger();
14552
14590
  /**
14553
14591
  * RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
14554
14592
  * Passes successful responses through unchanged.
@@ -14556,14 +14594,14 @@ const logger$23 = getLogger();
14556
14594
  function throwOnRPCError() {
14557
14595
  return (0, import_cjs$21.map)((response) => {
14558
14596
  if (response.error) {
14559
- logger$23.error("[throwOnRPCError] RPC error response:", {
14597
+ logger$24.error("[throwOnRPCError] RPC error response:", {
14560
14598
  code: response.error.code,
14561
14599
  message: response.error.message,
14562
14600
  data: response.error.data
14563
14601
  });
14564
14602
  throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
14565
14603
  }
14566
- logger$23.debug("[throwOnRPCError] RPC successful response:", response);
14604
+ logger$24.debug("[throwOnRPCError] RPC successful response:", response);
14567
14605
  return response;
14568
14606
  });
14569
14607
  }
@@ -14571,7 +14609,7 @@ function throwOnRPCError() {
14571
14609
  //#endregion
14572
14610
  //#region src/managers/CallEventsManager.ts
14573
14611
  var import_cjs$20 = require_cjs();
14574
- const logger$22 = getLogger();
14612
+ const logger$23 = getLogger();
14575
14613
  const initialSessionState = {};
14576
14614
  /** @internal */
14577
14615
  var CallEventsManager = class extends Destroyable {
@@ -14675,7 +14713,7 @@ var CallEventsManager = class extends Destroyable {
14675
14713
  }
14676
14714
  initSubscriptions() {
14677
14715
  this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
14678
- logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
14716
+ logger$23.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
14679
14717
  callId: callJoinedEvent.call_id,
14680
14718
  roomSessionId: callJoinedEvent.room_session_id
14681
14719
  });
@@ -14702,19 +14740,19 @@ var CallEventsManager = class extends Destroyable {
14702
14740
  if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
14703
14741
  });
14704
14742
  this.subscribeTo(this.memberUpdates$, (member) => {
14705
- logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
14743
+ logger$23.debug("[CallEventsManager] Handling member update event for member ID:", member);
14706
14744
  this.upsertParticipant(member);
14707
14745
  });
14708
14746
  this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
14709
- logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
14747
+ logger$23.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
14710
14748
  const participants = { ...this._participants$.value };
14711
14749
  if (memberLeftEvent.member.member_id in participants) {
14712
14750
  delete participants[memberLeftEvent.member.member_id];
14713
14751
  this._participants$.next(participants);
14714
- } else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
14752
+ } else logger$23.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
14715
14753
  });
14716
14754
  this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
14717
- logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
14755
+ logger$23.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
14718
14756
  const roomSession = callUpdatedEvent.room_session;
14719
14757
  this._sessionState$.next({
14720
14758
  ...this._sessionState$.value,
@@ -14729,7 +14767,7 @@ var CallEventsManager = class extends Destroyable {
14729
14767
  });
14730
14768
  });
14731
14769
  this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
14732
- logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
14770
+ logger$23.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
14733
14771
  this._sessionState$.next({
14734
14772
  ...this._sessionState$.value,
14735
14773
  layout_name: layoutChangedEvent.id,
@@ -14739,10 +14777,10 @@ var CallEventsManager = class extends Destroyable {
14739
14777
  });
14740
14778
  }
14741
14779
  updateParticipantPositions(layoutChangedEvent) {
14742
- 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.");
14780
+ if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$23.warn("[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.");
14743
14781
  layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
14744
14782
  if (!(layer.member_id in this._participants$.value)) {
14745
- logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
14783
+ logger$23.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
14746
14784
  return false;
14747
14785
  }
14748
14786
  return true;
@@ -14762,7 +14800,7 @@ var CallEventsManager = class extends Destroyable {
14762
14800
  this.webRtcCallSession.executeMethod(this.selfId, "call.layout.list", {}).then((response) => {
14763
14801
  const layouts = response.result?.layouts;
14764
14802
  if (!layouts) {
14765
- logger$22.warn("[CallEventsManager] Layout list response carried no layouts; keeping current layouts");
14803
+ logger$23.warn("[CallEventsManager] Layout list response carried no layouts; keeping current layouts");
14766
14804
  return;
14767
14805
  }
14768
14806
  this._sessionState$.next({
@@ -14770,7 +14808,7 @@ var CallEventsManager = class extends Destroyable {
14770
14808
  layouts
14771
14809
  });
14772
14810
  }).catch((error) => {
14773
- logger$22.error("[CallEventsManager] Error fetching layouts:", error);
14811
+ logger$23.error("[CallEventsManager] Error fetching layouts:", error);
14774
14812
  });
14775
14813
  }
14776
14814
  updateParticipants(members) {
@@ -14786,7 +14824,7 @@ var CallEventsManager = class extends Destroyable {
14786
14824
  }
14787
14825
  const participant = this._participants$.value[member.member_id];
14788
14826
  const oldValue = participant.value;
14789
- logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
14827
+ logger$23.debug("[CallEventsManager] Updating participant:", member.member_id, {
14790
14828
  oldValue,
14791
14829
  newValue: member
14792
14830
  });
@@ -14799,17 +14837,17 @@ var CallEventsManager = class extends Destroyable {
14799
14837
  }
14800
14838
  get callJoinedEvent$() {
14801
14839
  return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, import_cjs$20.filter)(isCallJoinedPayload), (0, import_cjs$20.tap)((event) => {
14802
- logger$22.debug("[CallEventsManager] Call joined event:", event);
14840
+ logger$23.debug("[CallEventsManager] Call joined event:", event);
14803
14841
  })));
14804
14842
  }
14805
14843
  get layoutChangedEvent$() {
14806
14844
  return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(filterAs(isLayoutChangedPayload, "layout"), (0, import_cjs$20.tap)((event) => {
14807
- logger$22.debug("[CallEventsManager] Layout changed event:", event);
14845
+ logger$23.debug("[CallEventsManager] Layout changed event:", event);
14808
14846
  })));
14809
14847
  }
14810
14848
  get memberUpdates$() {
14811
14849
  return this.cachedObservable("memberUpdates$", () => (0, import_cjs$20.merge)(this.webRtcCallSession.memberJoined$, this.webRtcCallSession.memberUpdated$, this.webRtcCallSession.memberTalking$).pipe((0, import_cjs$20.map)((event) => event.member), (0, import_cjs$20.tap)((event) => {
14812
- logger$22.debug("[CallEventsManager] Member update event:", event);
14850
+ logger$23.debug("[CallEventsManager] Member update event:", event);
14813
14851
  })));
14814
14852
  }
14815
14853
  destroy() {
@@ -14828,6 +14866,111 @@ var CallEventsManager = class extends Destroyable {
14828
14866
  }
14829
14867
  };
14830
14868
 
14869
+ //#endregion
14870
+ //#region src/controllers/ConstraintFallbackHelper.ts
14871
+ /**
14872
+ * ConstraintFallbackHelper - Provides getUserMedia with automatic constraint
14873
+ * fallback on OverconstrainedError.
14874
+ *
14875
+ * When a specific device ID is requested, the helper tries progressively
14876
+ * looser constraints:
14877
+ * 1. `{ deviceId: { exact: deviceId } }` -- exact match
14878
+ * 2. `{ deviceId: deviceId }` -- preferred (browser may pick another)
14879
+ * 3. `{}` -- no constraint, browser default
14880
+ *
14881
+ * This prevents stale device IDs from blocking call setup.
14882
+ *
14883
+ * @see Section 5.8 and Section 11 of the Implementation Guide
14884
+ */
14885
+ const logger$22 = getLogger();
14886
+ /**
14887
+ * Attempts getUserMedia with progressively looser constraints.
14888
+ *
14889
+ * The function tries three levels of constraint specificity for the given
14890
+ * device kind. Each level is only attempted if the previous one fails with
14891
+ * an OverconstrainedError. Non-OverconstrainedError failures (e.g.,
14892
+ * NotAllowedError) are thrown immediately without fallback.
14893
+ *
14894
+ * @param mediaDevices - Anything exposing `getUserMedia` (a full
14895
+ * `WebRTCMediaDevices`, or a shim wrapping one)
14896
+ * @param constraints - The full MediaStreamConstraints to use as a base
14897
+ * @param kind - Which track kind to apply fallback to ('audio' | 'video')
14898
+ * @param deviceId - The device ID to try (if undefined, calls getUserMedia as-is)
14899
+ * @returns The stream and the fallback level that succeeded
14900
+ * @throws When all fallback levels fail, or when a non-OverconstrainedError occurs
14901
+ */
14902
+ async function getUserMediaWithFallback(mediaDevices, constraints, kind, deviceId) {
14903
+ if (!deviceId) return {
14904
+ stream: await mediaDevices.getUserMedia(constraints),
14905
+ fallbackLevel: "default"
14906
+ };
14907
+ const baseConstraints = typeof constraints[kind] === "object" ? constraints[kind] : {};
14908
+ try {
14909
+ const exactConstraints = {
14910
+ ...constraints,
14911
+ [kind]: {
14912
+ ...baseConstraints,
14913
+ deviceId: { exact: deviceId }
14914
+ }
14915
+ };
14916
+ return {
14917
+ stream: await mediaDevices.getUserMedia(exactConstraints),
14918
+ fallbackLevel: "exact"
14919
+ };
14920
+ } catch (error) {
14921
+ if (!isOverconstrainedError(error)) throw error;
14922
+ logger$22.debug(`[ConstraintFallbackHelper] Exact constraint failed for ${kind}, trying preferred`, { deviceId });
14923
+ }
14924
+ try {
14925
+ const preferredConstraints = {
14926
+ ...constraints,
14927
+ [kind]: {
14928
+ ...baseConstraints,
14929
+ deviceId
14930
+ }
14931
+ };
14932
+ return {
14933
+ stream: await mediaDevices.getUserMedia(preferredConstraints),
14934
+ fallbackLevel: "preferred"
14935
+ };
14936
+ } catch (error) {
14937
+ if (!isOverconstrainedError(error)) throw error;
14938
+ logger$22.debug(`[ConstraintFallbackHelper] Preferred constraint failed for ${kind}, trying default`, { deviceId });
14939
+ }
14940
+ try {
14941
+ const defaultConstraints = {
14942
+ ...constraints,
14943
+ [kind]: { ...baseConstraints }
14944
+ };
14945
+ if (typeof defaultConstraints[kind] === "object") {
14946
+ const { deviceId: _removed, ...rest } = defaultConstraints[kind];
14947
+ defaultConstraints[kind] = rest;
14948
+ }
14949
+ const stream = await mediaDevices.getUserMedia(defaultConstraints);
14950
+ logger$22.warn(`[ConstraintFallbackHelper] Fell back to browser default for ${kind}`, { requestedDeviceId: deviceId });
14951
+ return {
14952
+ stream,
14953
+ fallbackLevel: "default"
14954
+ };
14955
+ } catch (error) {
14956
+ logger$22.error(`[ConstraintFallbackHelper] All fallback levels exhausted for ${kind}`, {
14957
+ deviceId,
14958
+ error
14959
+ });
14960
+ throw error;
14961
+ }
14962
+ }
14963
+ /**
14964
+ * Checks whether an error is an OverconstrainedError.
14965
+ *
14966
+ * Browsers may throw either a native OverconstrainedError or a DOMException
14967
+ * with a specific name.
14968
+ */
14969
+ function isOverconstrainedError(error) {
14970
+ if (error instanceof Error) return error.name === "OverconstrainedError" || error.name === "ConstraintNotSatisfiedError";
14971
+ return false;
14972
+ }
14973
+
14831
14974
  //#endregion
14832
14975
  //#region src/helpers/SDPHelper.ts
14833
14976
  /**
@@ -15386,6 +15529,7 @@ var LocalStreamController = class extends Destroyable {
15386
15529
  this._localAudioTracks$ = this.createBehaviorSubject([]);
15387
15530
  this._localVideoTracks$ = this.createBehaviorSubject([]);
15388
15531
  this._mediaTrackEnded$ = this.createSubject();
15532
+ this._trackOrigins = /* @__PURE__ */ new WeakMap();
15389
15533
  }
15390
15534
  get localStream$() {
15391
15535
  return this._localStream$.asObservable().pipe((0, import_cjs$17.takeUntil)(this.destroyed$));
@@ -15408,6 +15552,22 @@ var LocalStreamController = class extends Destroyable {
15408
15552
  get localVideoTracks() {
15409
15553
  return this._localVideoTracks$.value;
15410
15554
  }
15555
+ tagTracks(tracks, origin) {
15556
+ for (const track of tracks) this._trackOrigins.set(track, origin);
15557
+ }
15558
+ setTrackOrigin(track, origin) {
15559
+ this._trackOrigins.set(track, origin);
15560
+ }
15561
+ getTrackOrigin(track) {
15562
+ return this._trackOrigins.get(track);
15563
+ }
15564
+ /**
15565
+ * Fail-safe: an unrecorded track reads as not-a-device-capture, so a missed
15566
+ * tagging site leaves media alone rather than destroying it.
15567
+ */
15568
+ isDeviceCapture(track) {
15569
+ return this._trackOrigins.get(track) === "device";
15570
+ }
15411
15571
  /**
15412
15572
  * Build the local media stream based on the provided options.
15413
15573
  */
@@ -15417,6 +15577,7 @@ var LocalStreamController = class extends Destroyable {
15417
15577
  if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
15418
15578
  const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
15419
15579
  stream = new MediaStream(tracks);
15580
+ this.tagTracks(tracks, "application");
15420
15581
  } else if (this.options.propose === "screenshare") {
15421
15582
  logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
15422
15583
  stream = await this.options.getDisplayMedia({
@@ -15424,6 +15585,7 @@ var LocalStreamController = class extends Destroyable {
15424
15585
  audio: Boolean(this.options.inputAudioDeviceConstraints)
15425
15586
  });
15426
15587
  logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
15588
+ this.tagTracks(stream.getTracks(), "display");
15427
15589
  } else {
15428
15590
  const constraints = {
15429
15591
  audio: this.options.inputAudioDeviceConstraints,
@@ -15432,6 +15594,7 @@ var LocalStreamController = class extends Destroyable {
15432
15594
  logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
15433
15595
  stream = await this.options.getUserMedia(constraints);
15434
15596
  logger$19.debug("[LocalStreamController] User media obtained:", stream);
15597
+ this.tagTracks(stream.getTracks(), "device");
15435
15598
  }
15436
15599
  this._localStream$.next(stream);
15437
15600
  this._localAudioTracks$.next(stream.getAudioTracks());
@@ -15441,10 +15604,13 @@ var LocalStreamController = class extends Destroyable {
15441
15604
  /**
15442
15605
  * Add a local media track to the local stream.
15443
15606
  * @param track - The MediaStreamTrack to add
15607
+ * @param origin - Defaults to `'device'`; every internal caller passes a
15608
+ * fresh `getUserMedia` capture.
15444
15609
  * @returns The MediaStream (either existing or newly created)
15445
15610
  */
15446
- addTrack(track) {
15611
+ addTrack(track, origin = "device") {
15447
15612
  const localStream = this._localStream$.value ?? new MediaStream();
15613
+ this._trackOrigins.set(track, origin);
15448
15614
  track.addEventListener("ended", this.mediaTrackEndedHandler);
15449
15615
  localStream.addTrack(track);
15450
15616
  this._localStream$.next(localStream);
@@ -15714,15 +15880,27 @@ var TransceiverController = class extends Destroyable {
15714
15880
  for (let i = 0; i < Number(msStreamsNumber); i++) this.peerConnection.addTransceiver("video", { direction: "recvonly" });
15715
15881
  }
15716
15882
  }
15883
+ /**
15884
+ * @returns whether every live sender of the kind took the constraints. A
15885
+ * skipped non-device sender, an exhausted fallback, and having no live sender
15886
+ * at all all report `false` — `mediaParamsUpdated.applied` is built from this,
15887
+ * and an application told `true` cannot tell a working push from a no-op.
15888
+ */
15717
15889
  async updateSendersConstraints(kind, constraints) {
15718
15890
  if (!constraints) {
15719
15891
  this.stopTrackSender(kind);
15720
- return Promise.resolve();
15892
+ return false;
15721
15893
  }
15722
15894
  const senders = this.peerConnection.getSenders().filter((sender) => sender.track?.kind === kind && sender.track.readyState === "live");
15895
+ let applied = senders.length > 0;
15723
15896
  for (const sender of senders) {
15724
15897
  const { track } = sender;
15725
15898
  if (track) {
15899
+ if (!this.options.localStreamController.isDeviceCapture(track)) {
15900
+ logger$18.debug(`[TransceiverController] Skipping ${kind} constraints for a non-device track (origin: ${this.options.localStreamController.getTrackOrigin(track) ?? "unrecorded"}), track ${track.id}`);
15901
+ applied = false;
15902
+ continue;
15903
+ }
15726
15904
  const constraintsToApply = {
15727
15905
  ...track.getConstraints(),
15728
15906
  ...constraints
@@ -15738,33 +15916,39 @@ var TransceiverController = class extends Destroyable {
15738
15916
  } catch (fallbackError) {
15739
15917
  logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
15740
15918
  this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
15919
+ applied = false;
15741
15920
  }
15742
15921
  }
15743
15922
  }
15744
15923
  }
15924
+ return applied;
15745
15925
  }
15746
15926
  /**
15747
- * Fallback when applyConstraints fails: stop the current track, acquire a new
15748
- * one via getUserMedia with the merged constraints (preserving the current
15749
- * deviceId), replace the sender track, and update the localStream.
15927
+ * Fallback when applyConstraints fails, which on iOS Safari it silently does.
15750
15928
  *
15751
- * This is critical for iOS Safari where applyConstraints on audio tracks
15752
- * silently fails or throws.
15929
+ * Order matters: acquiring before stopping means a failed acquisition leaves
15930
+ * the existing media playing. The deviceId goes through the fallback ladder
15931
+ * rather than pinned `{ exact }`, so a stale id degrades instead of failing.
15753
15932
  */
15754
15933
  async replaceTrackFallback(sender, oldTrack, kind, mergedConstraints) {
15755
15934
  const { deviceId } = oldTrack.getSettings();
15756
- const constraintsWithDevice = {
15757
- ...mergedConstraints,
15758
- ...deviceId ? { deviceId: { exact: deviceId } } : {}
15759
- };
15760
- const trackId = oldTrack.id;
15935
+ const { stream, fallbackLevel } = await getUserMediaWithFallback({ getUserMedia: this.options.getUserMedia }, { [kind]: mergedConstraints }, kind, deviceId);
15936
+ const newTrack = stream.getTracks().find((t) => t.kind === kind);
15937
+ if (!newTrack) {
15938
+ stream.getTracks().forEach((t) => t.stop());
15939
+ throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
15940
+ }
15941
+ try {
15942
+ await sender.replaceTrack(newTrack);
15943
+ } catch (error) {
15944
+ stream.getTracks().forEach((t) => t.stop());
15945
+ throw error;
15946
+ }
15947
+ const oldTrackId = oldTrack.id;
15948
+ this.options.localStreamController.removeTrack(oldTrackId);
15761
15949
  oldTrack.stop();
15762
- this.options.localStreamController.removeTrack(trackId);
15763
- const newTrack = (await this.options.getUserMedia({ [kind]: constraintsWithDevice })).getTracks().find((t) => t.kind === kind);
15764
- if (!newTrack) throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
15765
- await sender.replaceTrack(newTrack);
15766
15950
  this.options.localStreamController.addTrack(newTrack);
15767
- logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
15951
+ logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind} (deviceId fallback level: ${fallbackLevel}). New track: ${newTrack.id}`);
15768
15952
  }
15769
15953
  getMediaDirections() {
15770
15954
  if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
@@ -15876,6 +16060,7 @@ var RTCPeerConnectionController = class extends Destroyable {
15876
16060
  this._iceGatheringState$ = this.createReplaySubject(1);
15877
16061
  this._errors$ = this.createReplaySubject(1);
15878
16062
  this._iceCandidates$ = this.createReplaySubject(1);
16063
+ this._localMediaSettled$ = this.createReplaySubject(1);
15879
16064
  this._initialized$ = this.createReplaySubject(1);
15880
16065
  this._remoteDescription$ = this.createReplaySubject(1);
15881
16066
  this._remoteStream$ = this.createBehaviorSubject(null);
@@ -15979,6 +16164,10 @@ var RTCPeerConnectionController = class extends Destroyable {
15979
16164
  get remoteDescription$() {
15980
16165
  return this.cachedObservable("remoteDescription$", () => this._remoteDescription$.asObservable().pipe((0, import_cjs$16.takeUntil)(this.destroyed$)));
15981
16166
  }
16167
+ /** Emits once local media is settled — acquired, or knowingly receive-only. */
16168
+ get localMediaSettled$() {
16169
+ return this.cachedObservable("localMediaSettled$", () => this._localMediaSettled$.asObservable().pipe((0, import_cjs$16.takeUntil)(this.destroyed$)));
16170
+ }
15982
16171
  get localStream$() {
15983
16172
  return this.cachedObservable("localStream$", () => this.localStreamController.localStream$.pipe((0, import_cjs$16.takeUntil)(this.destroyed$)));
15984
16173
  }
@@ -16134,7 +16323,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16134
16323
  this._isNegotiating$.next(true);
16135
16324
  await this._setRemoteDescription(this.sdpInit);
16136
16325
  } else {
16137
- await this.setupTrackHandling();
16326
+ if (!await this.setupTrackHandling()) return;
16138
16327
  this._initialized$.next(true);
16139
16328
  }
16140
16329
  } catch (error) {
@@ -16258,7 +16447,10 @@ var RTCPeerConnectionController = class extends Destroyable {
16258
16447
  inputVideoDeviceConstraints: this.inputVideoDeviceConstraints
16259
16448
  });
16260
16449
  }
16261
- await this.setupLocalTracks();
16450
+ if (!await this.setupLocalTracks()) {
16451
+ logger$17.debug("[RTCPeerConnectionController] Inbound answer abandoned; the connection went away.");
16452
+ return;
16453
+ }
16262
16454
  const { answerOptions } = this;
16263
16455
  logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
16264
16456
  await this.createAnswer(answerOptions);
@@ -16396,35 +16588,52 @@ var RTCPeerConnectionController = class extends Destroyable {
16396
16588
  }
16397
16589
  /**
16398
16590
  * Setup track handling for remote tracks.
16591
+ *
16592
+ * @returns `false` when the connection went away while local media was being
16593
+ * acquired — see {@link setupLocalTracks}.
16399
16594
  */
16400
16595
  async setupTrackHandling() {
16401
16596
  if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
16402
- await this.setupLocalTracks();
16597
+ if (!await this.setupLocalTracks()) return false;
16403
16598
  await this.setupRemoteTracks();
16599
+ return true;
16404
16600
  }
16601
+ /**
16602
+ * @returns `false` when the connection was torn down while getUserMedia was
16603
+ * in flight. The acquisition is not cancellable, so the caller must stop
16604
+ * rather than go on to touch a peer connection that is closed or gone.
16605
+ */
16405
16606
  async setupLocalTracks() {
16406
16607
  logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
16407
16608
  if (this.hasNoLocalMediaToSend()) {
16408
16609
  if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
16409
16610
  logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
16410
16611
  this.setupReceiveOnlyTransceivers();
16411
- return;
16612
+ this._localMediaSettled$.next();
16613
+ return true;
16412
16614
  }
16413
16615
  let localStream;
16414
16616
  try {
16415
16617
  localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
16416
16618
  } catch (error) {
16417
16619
  this.handleLocalMediaFailure(error);
16418
- return;
16620
+ this._localMediaSettled$.next();
16621
+ return true;
16419
16622
  }
16623
+ if (!this.peerConnection || this.peerConnection.signalingState === "closed") {
16624
+ logger$17.debug("[RTCPeerConnectionController] Local media arrived after teardown; releasing it.");
16625
+ localStream.getTracks().forEach((track) => track.stop());
16626
+ return false;
16627
+ }
16628
+ this._localMediaSettled$.next();
16420
16629
  if (this.transceiverController?.useAddStream ?? false) {
16421
16630
  logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
16422
- this.peerConnection?.addStream(localStream);
16631
+ this.peerConnection.addStream(localStream);
16423
16632
  if (!this.isNegotiating) {
16424
16633
  logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
16425
16634
  this.negotiationNeeded$.next();
16426
16635
  }
16427
- return;
16636
+ return true;
16428
16637
  }
16429
16638
  for (const kind of ["audio", "video"]) {
16430
16639
  const tracks = (kind === "audio" ? localStream.getAudioTracks() : localStream.getVideoTracks()).map((track, index) => ({
@@ -16438,10 +16647,11 @@ var RTCPeerConnectionController = class extends Destroyable {
16438
16647
  await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
16439
16648
  } else {
16440
16649
  logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
16441
- this.peerConnection?.addTrack(track, localStream);
16650
+ this.peerConnection.addTrack(track, localStream);
16442
16651
  }
16443
16652
  }
16444
16653
  }
16654
+ return true;
16445
16655
  }
16446
16656
  /** True for a main connection with no local media to send. */
16447
16657
  hasNoLocalMediaToSend() {
@@ -16562,6 +16772,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16562
16772
  const sender = (this.transceiverController?.audioTransceivers.at(0))?.sender ?? this.peerConnection.getSenders().find((s) => s.track?.kind === "audio");
16563
16773
  if (!sender || !raw) return;
16564
16774
  try {
16775
+ this.localStreamController.setTrackOrigin(this._localAudioPipeline.outputTrack, "processed");
16565
16776
  await sender.replaceTrack(this._localAudioPipeline.outputTrack);
16566
16777
  } catch (error) {
16567
16778
  logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
@@ -16622,37 +16833,62 @@ var RTCPeerConnectionController = class extends Destroyable {
16622
16833
  for (const existingTrack of existingTracks) this.removeLocalTrack(existingTrack.id);
16623
16834
  this.addLocalTrack(track);
16624
16835
  }
16836
+ /**
16837
+ * @returns whether the constraints reached the media the leg is sending.
16838
+ *
16839
+ * With the pipeline engaged the audio sender carries the processed
16840
+ * destination track, so the sender scan would find nothing it may touch and
16841
+ * every audio constraint API would silently no-op. The constraints belong to
16842
+ * the pipeline's device source, which is the capture that sender ultimately
16843
+ * carries.
16844
+ */
16625
16845
  async updateSendersConstraints(kind, constraints) {
16626
- await this.transceiverController?.updateSendersConstraints(kind, constraints);
16846
+ if (kind === "audio" && constraints && this._localAudioPipeline) return this.applyPipelineSourceConstraints(constraints);
16847
+ return await this.transceiverController?.updateSendersConstraints(kind, constraints) ?? false;
16627
16848
  }
16628
16849
  /**
16629
- * Replace the current audio track with a new one using the given constraints.
16630
- * Used for server-pushed audio constraint changes where applyConstraints
16631
- * fails on iOS Safari. Stops the current track, acquires a new one via
16632
- * getUserMedia, and replaces the sender track.
16850
+ * Mirror of the sender path for a piped audio leg: same merge, same fallback
16851
+ * ladder, same device-capture invariant — but the swap target is the pipeline
16852
+ * input, so the sender keeps emitting the pipeline's output track and its
16853
+ * identity survives the change.
16633
16854
  */
16634
- async replaceAudioTrackWithConstraints(constraints) {
16635
- const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
16636
- if (!senders || senders.length === 0) {
16637
- logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
16638
- return;
16855
+ async applyPipelineSourceConstraints(constraints) {
16856
+ const pipeline = this._localAudioPipeline;
16857
+ const source = this.localStreamController.localAudioTracks.at(0);
16858
+ if (!pipeline || !source) {
16859
+ logger$17.debug("[RTCPeerConnectionController] No pipeline input to constrain.");
16860
+ return false;
16639
16861
  }
16640
- for (const sender of senders) {
16641
- const oldTrack = sender.track;
16642
- if (!oldTrack) continue;
16643
- const { deviceId } = oldTrack.getSettings();
16644
- const mergedConstraints = {
16645
- ...oldTrack.getConstraints(),
16646
- ...constraints,
16647
- ...deviceId ? { deviceId: { exact: deviceId } } : {}
16648
- };
16649
- const trackId = oldTrack.id;
16650
- oldTrack.stop();
16651
- this.localStreamController.removeTrack(trackId);
16652
- const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
16653
- await sender.replaceTrack(newTrack);
16862
+ if (!this.localStreamController.isDeviceCapture(source)) {
16863
+ logger$17.debug(`[RTCPeerConnectionController] Skipping audio constraints for a non-device pipeline input (origin: ${this.localStreamController.getTrackOrigin(source) ?? "unrecorded"}).`);
16864
+ return false;
16865
+ }
16866
+ const merged = {
16867
+ ...source.getConstraints(),
16868
+ ...constraints
16869
+ };
16870
+ try {
16871
+ await source.applyConstraints(merged);
16872
+ logger$17.debug("[RTCPeerConnectionController] Pipeline input constraints updated:", merged);
16873
+ return true;
16874
+ } catch (error) {
16875
+ logger$17.warn("[RTCPeerConnectionController] applyConstraints failed on the pipeline input, re-acquiring:", error);
16876
+ }
16877
+ try {
16878
+ const { stream } = await getUserMediaWithFallback({ getUserMedia: async (c) => this.getUserMedia(c) }, { audio: merged }, "audio", source.getSettings().deviceId);
16879
+ const newTrack = stream.getAudioTracks().at(0);
16880
+ if (!newTrack) {
16881
+ stream.getTracks().forEach((track) => track.stop());
16882
+ throw new Error("getUserMedia returned no audio track");
16883
+ }
16884
+ this.localStreamController.removeTrack(source.id);
16654
16885
  this.localStreamController.addTrack(newTrack);
16655
- logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
16886
+ pipeline.setInputTrack(newTrack);
16887
+ return true;
16888
+ } catch (error) {
16889
+ logger$17.warn("[RTCPeerConnectionController] Failed to re-acquire the pipeline input for constraints:", error);
16890
+ this._errors$.next(new MediaTrackError("updateSendersConstraints", "audio", error));
16891
+ return false;
16656
16892
  }
16657
16893
  }
16658
16894
  /**
@@ -16836,7 +17072,7 @@ var WebRTCVertoManager = class extends VertoManager {
16836
17072
  this._signalingStatus$ = this.createReplaySubject(1);
16837
17073
  this._screenShareStatus$ = this.createBehaviorSubject("none");
16838
17074
  this._rtcPeerConnectionsMap = /* @__PURE__ */ new Map();
16839
- this._screenShareTimeoutMs = 5e4;
17075
+ this._legErrors$ = this.createSubject();
16840
17076
  this._nodeId$ = this.createBehaviorSubject(options.nodeId ?? null);
16841
17077
  this.onError = options.onError;
16842
17078
  this.onModifyFailed = options.onModifyFailed;
@@ -16884,6 +17120,10 @@ var WebRTCVertoManager = class extends VertoManager {
16884
17120
  get selfId$() {
16885
17121
  return this._selfId$.asObservable();
16886
17122
  }
17123
+ /** Separates the media phase of call creation from the signalling phase. */
17124
+ get localMediaSettled$() {
17125
+ return this.mainPeerConnection.localMediaSettled$;
17126
+ }
16887
17127
  get localStream() {
16888
17128
  return this._rtcPeerConnectionsMap.get(this.webRtcCallSession.id)?.localStream ?? null;
16889
17129
  }
@@ -16940,20 +17180,11 @@ var WebRTCVertoManager = class extends VertoManager {
16940
17180
  const { mediaParams, callID } = event;
16941
17181
  const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
16942
17182
  const { audio, video } = mediaParams;
16943
- (async () => {
16944
- try {
16945
- if (audio && rtcPeerConnController) await rtcPeerConnController.replaceAudioTrackWithConstraints(audio);
16946
- if (video) await rtcPeerConnController?.updateSendersConstraints("video", video);
16947
- this.webRtcCallSession.emitMediaParamsUpdated({
16948
- audio,
16949
- video,
16950
- timestamp: Date.now()
16951
- });
16952
- } catch (error) {
16953
- logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
16954
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
16955
- }
16956
- })();
17183
+ if (!rtcPeerConnController) {
17184
+ logger$16.warn(`[WebRTCManager] Ignoring server-pushed media params for unknown leg ${callID}`);
17185
+ return;
17186
+ }
17187
+ this.applyServerMediaParams(rtcPeerConnController, audio, video);
16957
17188
  });
16958
17189
  this.subscribeTo(this.vertoPing$, (vertoPing) => {
16959
17190
  this.attachManager.attach(this.buildAttachableCall());
@@ -16961,6 +17192,75 @@ var WebRTCVertoManager = class extends VertoManager {
16961
17192
  });
16962
17193
  }
16963
17194
  /**
17195
+ * An auxiliary-leg failure must never destroy the call; main-leg and
17196
+ * call-level errors keep `CallFactory.isFatalError`'s classification.
17197
+ *
17198
+ * Every site holding a peer connection reports through here, so the invariant
17199
+ * is structural rather than per-call-site — which is how cloud-product#20523
17200
+ * happened, with only one of fourteen sites passing `{ fatal: false }`.
17201
+ *
17202
+ * `override` composes rather than replaces: a caller may force non-fatal for a
17203
+ * reason of its own (a `verto.info` frame is best-effort whichever leg carries
17204
+ * it), and an auxiliary leg stays non-fatal regardless.
17205
+ */
17206
+ reportLegError(error, rtcPeerConnController, override) {
17207
+ const leg = rtcPeerConnController?.propose;
17208
+ const legId = rtcPeerConnController?.id;
17209
+ const auxiliary = Boolean(rtcPeerConnController) && !rtcPeerConnController?.isMainDevice;
17210
+ this.onError?.(error, {
17211
+ ...override?.fatal === false || auxiliary ? { fatal: false } : {},
17212
+ ...leg ? { leg } : {},
17213
+ ...legId ? { legId } : {}
17214
+ });
17215
+ if (legId) this._legErrors$.next({
17216
+ legId,
17217
+ error
17218
+ });
17219
+ }
17220
+ /**
17221
+ * Errors reported for one leg, as a stream that fails with them.
17222
+ *
17223
+ * Signaling failures are reported, never thrown — so nothing that waits on a
17224
+ * leg's progress would otherwise learn of a rejected invite. Merging this in
17225
+ * lets the wait end with the reason the server gave.
17226
+ */
17227
+ legError$(legId) {
17228
+ return this._legErrors$.pipe((0, import_cjs$15.filter)((report) => report.legId === legId), (0, import_cjs$15.map)((report) => {
17229
+ throw report.error;
17230
+ }));
17231
+ }
17232
+ /**
17233
+ * Audio and video are applied independently so a failure in one cannot
17234
+ * suppress the other, and `mediaParamsUpdated` is emitted whatever happens —
17235
+ * an application should not be starved of the params by a constraint failure.
17236
+ */
17237
+ async applyServerMediaParams(rtcPeerConnController, audio, video) {
17238
+ const failures = [];
17239
+ let applied = true;
17240
+ if (audio) try {
17241
+ applied = await rtcPeerConnController.updateSendersConstraints("audio", audio) && applied;
17242
+ } catch (error) {
17243
+ applied = false;
17244
+ failures.push(toError(error));
17245
+ }
17246
+ if (video) try {
17247
+ applied = await rtcPeerConnController.updateSendersConstraints("video", video) && applied;
17248
+ } catch (error) {
17249
+ applied = false;
17250
+ failures.push(toError(error));
17251
+ }
17252
+ this.webRtcCallSession.emitMediaParamsUpdated({
17253
+ audio,
17254
+ video,
17255
+ timestamp: Date.now(),
17256
+ applied
17257
+ });
17258
+ for (const failure of failures) {
17259
+ logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", failure);
17260
+ this.reportLegError(failure, rtcPeerConnController, { fatal: false });
17261
+ }
17262
+ }
17263
+ /**
16964
17264
  * Set node_id/selfId only when the current value is null.
16965
17265
  *
16966
17266
  * During reattach, `call.joined` and `verto.answer` events can deliver
@@ -16998,7 +17298,7 @@ var WebRTCVertoManager = class extends VertoManager {
16998
17298
  if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
16999
17299
  } catch (error) {
17000
17300
  logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
17001
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17301
+ this.reportLegError(toError(error), this.mainPeerConnection);
17002
17302
  throw error;
17003
17303
  }
17004
17304
  }
@@ -17166,23 +17466,24 @@ var WebRTCVertoManager = class extends VertoManager {
17166
17466
  return response;
17167
17467
  }
17168
17468
  async executeVerto(message, optionals = {}) {
17169
- const webrtcVertoMessage = WebrtcVerto({
17469
+ const params = {
17170
17470
  callID: optionals.callID ?? this.webRtcCallSession.id,
17171
17471
  node_id: optionals.node_id ?? this._nodeId$.value ?? "",
17172
17472
  message,
17173
17473
  subscribe: optionals.subscribe
17174
- });
17474
+ };
17475
+ const webrtcVertoMessage = WebrtcVerto(params);
17175
17476
  const response = await this.webRtcCallSession.execute(webrtcVertoMessage);
17176
17477
  const nonFatal = message.method === "verto.info" ? { fatal: false } : void 0;
17177
17478
  if (response.error) {
17178
17479
  const error = new JSONRPCError(response.error.code, response.error.message, response.error.data);
17179
- this.onError?.(error, nonFatal);
17480
+ this.reportLegError(error, this._rtcPeerConnectionsMap.get(params.callID), nonFatal);
17180
17481
  return response;
17181
17482
  }
17182
17483
  const innerResult = getValueFrom(response, "result.result");
17183
17484
  if (innerResult?.error) {
17184
17485
  const error = new JSONRPCError(innerResult.error.code, innerResult.error.message, innerResult.error.data);
17185
- this.onError?.(error, nonFatal);
17486
+ this.reportLegError(error, this._rtcPeerConnectionsMap.get(params.callID), nonFatal);
17186
17487
  return response;
17187
17488
  }
17188
17489
  return response;
@@ -17203,7 +17504,7 @@ var WebRTCVertoManager = class extends VertoManager {
17203
17504
  }
17204
17505
  } catch (error) {
17205
17506
  logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
17206
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17507
+ this.reportLegError(toError(error), rtcPeerConnController);
17207
17508
  if (vertoMethod === "verto.modify") this.onModifyFailed?.();
17208
17509
  }
17209
17510
  }
@@ -17219,7 +17520,7 @@ var WebRTCVertoManager = class extends VertoManager {
17219
17520
  } catch (error) {
17220
17521
  logger$16.warn("[WebRTCManager] Error processing modify response:", error);
17221
17522
  const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
17222
- this.onError?.(modifyError);
17523
+ this.reportLegError(modifyError, rtcPeerConnController);
17223
17524
  }
17224
17525
  }
17225
17526
  }
@@ -17232,7 +17533,7 @@ var WebRTCVertoManager = class extends VertoManager {
17232
17533
  status,
17233
17534
  signalingError
17234
17535
  });
17235
- this.onError?.(signalingError);
17536
+ this.reportLegError(signalingError, null, { fatal: false });
17236
17537
  return;
17237
17538
  }
17238
17539
  if (rtcPeerConnController.isMainDevice) this._signalingStatus$.next(status);
@@ -17262,7 +17563,7 @@ var WebRTCVertoManager = class extends VertoManager {
17262
17563
  } else {
17263
17564
  logger$16.error("[WebRTCManager] Verto invite failed:", response);
17264
17565
  const inviteError = response.error ? new JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
17265
- this.onError?.(inviteError);
17566
+ this.reportLegError(inviteError, rtcPeerConnController);
17266
17567
  }
17267
17568
  }
17268
17569
  get RTCPeerConnectionConfig() {
@@ -17301,7 +17602,7 @@ var WebRTCVertoManager = class extends VertoManager {
17301
17602
  this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
17302
17603
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17303
17604
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
17304
- this.onError?.(error);
17605
+ this.reportLegError(error, rtcPeerConnController);
17305
17606
  });
17306
17607
  if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
17307
17608
  }
@@ -17330,7 +17631,7 @@ var WebRTCVertoManager = class extends VertoManager {
17330
17631
  await rtcPeerConnController.acceptInbound(answerOptions);
17331
17632
  } catch (error) {
17332
17633
  logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
17333
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17634
+ this.reportLegError(toError(error), rtcPeerConnController);
17334
17635
  }
17335
17636
  }
17336
17637
  }
@@ -17434,7 +17735,7 @@ var WebRTCVertoManager = class extends VertoManager {
17434
17735
  await this.attachManager.attach(this.buildAttachableCall());
17435
17736
  } catch (error) {
17436
17737
  logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
17437
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17738
+ this.reportLegError(toError(error), rtcPeerConnectionController);
17438
17739
  await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
17439
17740
  }
17440
17741
  }
@@ -17506,7 +17807,7 @@ var WebRTCVertoManager = class extends VertoManager {
17506
17807
  await this.mainPeerConnection.restoreTrackSender(deviceKind);
17507
17808
  } else {
17508
17809
  const error = new InvalidParams("No valid device to be added");
17509
- this.onError?.(error);
17810
+ this.reportLegError(error, this.mainPeerConnection);
17510
17811
  throw error;
17511
17812
  }
17512
17813
  }
@@ -17531,21 +17832,30 @@ var WebRTCVertoManager = class extends VertoManager {
17531
17832
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17532
17833
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
17533
17834
  firstPeerConnectionError ??= error;
17534
- this.onError?.(error, { fatal: false });
17835
+ this.reportLegError(error, rtcPeerConnController);
17535
17836
  });
17536
- await (0, import_cjs$15.firstValueFrom)(rtcPeerConnController.connectionState$.pipe((0, import_cjs$15.filter)((state) => state === "connected"), (0, import_cjs$15.take)(1), (0, import_cjs$15.timeout)(this._screenShareTimeoutMs), (0, import_cjs$15.takeUntil)(this.destroyed$)));
17837
+ const pc = rtcPeerConnController;
17838
+ await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.merge)(pc.localMediaSettled$.pipe((0, import_cjs$15.take)(1), (0, import_cjs$15.switchMap)(() => pc.connectionState$.pipe((0, import_cjs$15.filter)((state) => state === "connected"), (0, import_cjs$15.take)(1), (0, import_cjs$15.timeout)(DEFAULT_AUX_LEG_CONNECT_TIMEOUT_MS)))), this.legError$(pc.id)).pipe((0, import_cjs$15.takeUntil)(this.destroyed$)));
17537
17839
  if (isScreenShare) this._screenShareStatus$.next("started");
17538
17840
  logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
17539
17841
  return rtcPeerConnController.id;
17540
17842
  } catch (error) {
17541
17843
  logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
17542
- if (rtcPeerConnController) rtcPeerConnController.destroy();
17543
- if (isScreenShare) this._screenShareStatus$.next("none");
17844
+ if (rtcPeerConnController) {
17845
+ rtcPeerConnController.destroy();
17846
+ this._rtcPeerConnectionsMap.delete(rtcPeerConnController.id);
17847
+ this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17848
+ }
17849
+ if (isScreenShare) {
17850
+ this._screenShareStatus$.next("none");
17851
+ this._screenShareId = void 0;
17852
+ }
17544
17853
  if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
17545
17854
  if (error instanceof import_cjs$15.EmptyError) {
17546
17855
  logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
17547
17856
  return;
17548
17857
  }
17858
+ if (error instanceof import_cjs$15.TimeoutError) throw new AuxiliaryLegTimeoutError(propose, error);
17549
17859
  throw error instanceof Error ? error : new Error(String(error), { cause: error });
17550
17860
  }
17551
17861
  }
@@ -17591,7 +17901,7 @@ var WebRTCVertoManager = class extends VertoManager {
17591
17901
  await this.executeVerto(VertoBye({
17592
17902
  ...causeParams,
17593
17903
  dialogParams: this.dialogParams(rtcPeerConnController)
17594
- }));
17904
+ }), { callID: rtcPeerConnController.id });
17595
17905
  } catch (error) {
17596
17906
  logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
17597
17907
  throw error;
@@ -18983,13 +19293,13 @@ var WebRTCCall = class extends Destroyable {
18983
19293
  get bandwidthConstrained$() {
18984
19294
  return this.deferEmission(this._bandwidthConstrained$.asObservable());
18985
19295
  }
18986
- /** Observable that emits when server-pushed media params are applied. */
19296
+ /** Observable that emits when the server pushes media params. */
18987
19297
  get mediaParamsUpdated$() {
18988
19298
  return this.deferEmission(this._mediaParamsUpdated$.asObservable());
18989
19299
  }
18990
19300
  /**
18991
19301
  * @internal Emit a media params update event.
18992
- * Called by the VertoManager when server-pushed media params are applied.
19302
+ * Called by the VertoManager when the server pushes media params.
18993
19303
  */
18994
19304
  emitMediaParamsUpdated(event) {
18995
19305
  this._mediaParamsUpdated$.next(event);
@@ -19197,6 +19507,10 @@ var WebRTCCall = class extends Destroyable {
19197
19507
  get selfId$() {
19198
19508
  return this.vertoManager.selfId$;
19199
19509
  }
19510
+ /** @internal Lets call creation bound the media and signalling phases apart. */
19511
+ get localMediaSettled$() {
19512
+ return this.vertoManager.localMediaSettled$;
19513
+ }
19200
19514
  /** Local participant's member ID, or `null` if not joined. */
19201
19515
  get selfId() {
19202
19516
  return this.vertoManager.selfId;
@@ -19592,6 +19906,9 @@ var WebRTCCall = class extends Destroyable {
19592
19906
  /**
19593
19907
  * Infers the semantic error category from a raw Error thrown by VertoManager
19594
19908
  * or an RTCPeerConnection layer.
19909
+ *
19910
+ * Pure function — exported for unit testing.
19911
+ * @internal
19595
19912
  */
19596
19913
  function inferCallErrorKind(error) {
19597
19914
  if (error instanceof RPCTimeoutError) return "timeout";
@@ -19610,7 +19927,14 @@ const RECOVERABLE_RPC_CODES = new Set([
19610
19927
  RPC_ERROR_AUTHENTICATION_FAILED,
19611
19928
  RPC_ERROR_INVALID_PARAMS
19612
19929
  ]);
19613
- /** Determines whether an error should be fatal (destroy the call). */
19930
+ /**
19931
+ * A *fallback*: callers knowing which leg failed pass an explicit `fatal` and
19932
+ * never reach here, so the default-fatal branch only sees call- and main-leg
19933
+ * errors. Auxiliary legs go through `WebRTCVertoManager.reportLegError`.
19934
+ *
19935
+ * Pure function — exported for unit testing.
19936
+ * @internal
19937
+ */
19614
19938
  function isFatalError(error) {
19615
19939
  if (error instanceof VertoPongError) return false;
19616
19940
  if (error instanceof MediaTrackError) return false;
@@ -19645,7 +19969,9 @@ var CallFactory = class {
19645
19969
  kind: inferCallErrorKind(error),
19646
19970
  fatal: options$1?.fatal ?? isFatalError(error),
19647
19971
  error,
19648
- callId: callInstance.id
19972
+ callId: callInstance.id,
19973
+ ...options$1?.leg ? { leg: options$1.leg } : {},
19974
+ ...options$1?.legId ? { legId: options$1.legId } : {}
19649
19975
  };
19650
19976
  callInstance.emitError(callError);
19651
19977
  },
@@ -20117,6 +20443,30 @@ const logger$9 = getLogger();
20117
20443
  function shouldAbortDial(callError) {
20118
20444
  return callError.fatal || !(callError.error instanceof MediaAccessError);
20119
20445
  }
20446
+ /**
20447
+ * Wait for a dialed call to be ready, or for the failure that stops it.
20448
+ *
20449
+ * Local media acquisition is deliberately unbounded: a permission prompt or a
20450
+ * device picker is human time, and `getUserMedia` cannot be cancelled anyway.
20451
+ * The clock starts only once acquisition settles, so a slow human never spends
20452
+ * the server's budget.
20453
+ *
20454
+ * `merge` rather than `race`, because the two legs settle asymmetrically. A
20455
+ * fatal acquisition failure reports the error and then destroys the call in the
20456
+ * same synchronous step; `errors$` defers delivery by a microtask while
20457
+ * `localMediaSettled$` completes immediately. Under `race` that bare completion
20458
+ * ended the wait first and `dial()` rejected with an RxJS `EmptyError`, burying
20459
+ * the `NotAllowedError` applications are told to inspect. Under `merge` the
20460
+ * completed leg is simply spent, and the queued error — enqueued before the
20461
+ * completion, so delivered before it — arrives to reject the wait. A dial
20462
+ * abandoned with no error at all still ends both legs, and the resulting
20463
+ * `EmptyError` remains the benign-cancel signal.
20464
+ *
20465
+ * Exported for unit testing.
20466
+ */
20467
+ async function awaitDialReady(session, signalingTimeoutMs) {
20468
+ return (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.merge)(session.localMediaSettled$.pipe((0, import_cjs$7.take)(1), (0, import_cjs$7.switchMap)(() => session.selfId$.pipe((0, import_cjs$7.filter)((id) => Boolean(id)), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)(signalingTimeoutMs)))), session.errors$.pipe((0, import_cjs$7.filter)(shouldAbortDial), (0, import_cjs$7.take)(1), (0, import_cjs$7.switchMap)((callError) => (0, import_cjs$7.throwError)(() => callError.error)))));
20469
+ }
20120
20470
  const getAddressSearchURI = (options) => {
20121
20471
  const to = options.to?.split("?")[0];
20122
20472
  const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
@@ -20133,7 +20483,6 @@ var ClientSessionManager = class extends Destroyable {
20133
20483
  this.authorizationStateKey = authorizationStateKey;
20134
20484
  this.attachManager = attachManager;
20135
20485
  this.dpopManager = dpopManager;
20136
- this.callCreateTimeout = 6e3;
20137
20486
  this.agent = `signalwire-js/4.0.0`;
20138
20487
  this.eventAcks = true;
20139
20488
  this.authorizationState$ = this.createReplaySubject(1);
@@ -20561,7 +20910,7 @@ var ClientSessionManager = class extends Destroyable {
20561
20910
  to: destinationURI,
20562
20911
  ...options
20563
20912
  });
20564
- await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.race)(callSession.selfId$.pipe((0, import_cjs$7.filter)((id) => Boolean(id)), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)(this.callCreateTimeout)), callSession.errors$.pipe((0, import_cjs$7.filter)(shouldAbortDial), (0, import_cjs$7.take)(1), (0, import_cjs$7.switchMap)((callError) => (0, import_cjs$7.throwError)(() => callError.error)))));
20913
+ await awaitDialReady(callSession, DEFAULT_CALL_SIGNALING_TIMEOUT_MS);
20565
20914
  this._calls$.next({
20566
20915
  [`${callSession.id}`]: callSession,
20567
20916
  ...this._calls$.value
@@ -22739,6 +23088,11 @@ var SignalWire = class extends Destroyable {
22739
23088
  * Returns a {@link Call} in `'ringing'` state. Subscribe to {@link Call.status$}
22740
23089
  * to track progression through `'connected'` → `'disconnected'`.
22741
23090
  *
23091
+ * Local media acquisition is deliberately unbounded: an unanswered permission
23092
+ * prompt leaves this promise pending indefinitely, so apply your own bound if
23093
+ * your UI needs one. The 12 s signaling budget starts only once acquisition
23094
+ * settles.
23095
+ *
22742
23096
  * @param destination - Address URI string (e.g. `'/public/my-room'`) or {@link Address} instance.
22743
23097
  * @param options - Media and dial options (audio/video, device constraints). Overrides defaults.
22744
23098
  * @returns The created {@link Call} instance.
@@ -23219,6 +23573,7 @@ if (typeof process === "undefined") globalThis.process = { env: { NODE_ENV: "pro
23219
23573
 
23220
23574
  //#endregion
23221
23575
  exports.Address = Address;
23576
+ exports.AuxiliaryLegTimeoutError = AuxiliaryLegTimeoutError;
23222
23577
  exports.CallCreateError = CallCreateError;
23223
23578
  exports.CallNotReadyError = CallNotReadyError;
23224
23579
  exports.ClientPreferences = ClientPreferences;