@signalwire/js 4.0.0-dev-20260908172838 → 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.
package/dist/browser.mjs CHANGED
@@ -4186,7 +4186,7 @@ var require_merge$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
4186
4186
  var empty_1$7 = require_empty();
4187
4187
  var args_1$8 = require_args();
4188
4188
  var from_1$4 = require_from();
4189
- function merge$6() {
4189
+ function merge$7() {
4190
4190
  var args = [];
4191
4191
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
4192
4192
  var scheduler = args_1$8.popScheduler(args);
@@ -4194,7 +4194,7 @@ var require_merge$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
4194
4194
  var sources = args;
4195
4195
  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));
4196
4196
  }
4197
- exports.merge = merge$6;
4197
+ exports.merge = merge$7;
4198
4198
  }));
4199
4199
 
4200
4200
  //#endregion
@@ -4325,13 +4325,13 @@ var require_race$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
4325
4325
  var innerFrom_1$28 = require_innerFrom();
4326
4326
  var argsOrArgArray_1$4 = require_argsOrArgArray();
4327
4327
  var OperatorSubscriber_1$48 = require_OperatorSubscriber();
4328
- function race$5() {
4328
+ function race$4() {
4329
4329
  var sources = [];
4330
4330
  for (var _i = 0; _i < arguments.length; _i++) sources[_i] = arguments[_i];
4331
4331
  sources = argsOrArgArray_1$4.argsOrArgArray(sources);
4332
4332
  return sources.length === 1 ? innerFrom_1$28.innerFrom(sources[0]) : new Observable_1$6.Observable(raceInit(sources));
4333
4333
  }
4334
- exports.race = race$5;
4334
+ exports.race = race$4;
4335
4335
  function raceInit(sources) {
4336
4336
  return function(subscriber) {
4337
4337
  var subscriptions = [];
@@ -6076,7 +6076,7 @@ var require_merge = /* @__PURE__ */ __commonJSMin(((exports) => {
6076
6076
  var mergeAll_1$2 = require_mergeAll();
6077
6077
  var args_1$3 = require_args();
6078
6078
  var from_1$1 = require_from();
6079
- function merge$5() {
6079
+ function merge$6() {
6080
6080
  var args = [];
6081
6081
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
6082
6082
  var scheduler = args_1$3.popScheduler(args);
@@ -6085,7 +6085,7 @@ var require_merge = /* @__PURE__ */ __commonJSMin(((exports) => {
6085
6085
  mergeAll_1$2.mergeAll(concurrent)(from_1$1.from(__spreadArray$8([source], __read$8(args)), scheduler)).subscribe(subscriber);
6086
6086
  });
6087
6087
  }
6088
- exports.merge = merge$5;
6088
+ exports.merge = merge$6;
6089
6089
  }));
6090
6090
 
6091
6091
  //#endregion
@@ -6932,7 +6932,7 @@ var require_switchMap = /* @__PURE__ */ __commonJSMin(((exports) => {
6932
6932
  var innerFrom_1$6 = require_innerFrom();
6933
6933
  var lift_1$13 = require_lift();
6934
6934
  var OperatorSubscriber_1$11 = require_OperatorSubscriber();
6935
- function switchMap$7(project, resultSelector) {
6935
+ function switchMap$8(project, resultSelector) {
6936
6936
  return lift_1$13.operate(function(source, subscriber) {
6937
6937
  var innerSubscriber = null;
6938
6938
  var index = 0;
@@ -6956,7 +6956,7 @@ var require_switchMap = /* @__PURE__ */ __commonJSMin(((exports) => {
6956
6956
  }));
6957
6957
  });
6958
6958
  }
6959
- exports.switchMap = switchMap$7;
6959
+ exports.switchMap = switchMap$8;
6960
6960
  }));
6961
6961
 
6962
6962
  //#endregion
@@ -9022,6 +9022,25 @@ const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
9022
9022
  const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
9023
9023
  const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
9024
9024
  const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
9025
+ /**
9026
+ * How long call setup may wait for the network, measured from local media
9027
+ * settling until the member id arrives.
9028
+ *
9029
+ * Local media acquisition is deliberately NOT inside this: a permission prompt
9030
+ * or a device picker is human time, and a human may take as long as they like
9031
+ * without spending the server's budget. The clock starts when acquisition ends.
9032
+ *
9033
+ * Must exceed `iceGatheringTimeout + the RPC timeout`, which both run inside it.
9034
+ */
9035
+ const DEFAULT_CALL_SIGNALING_TIMEOUT_MS = 12e3;
9036
+ /**
9037
+ * How long an auxiliary leg (screen share, additional device) may take to
9038
+ * connect once its media is in hand.
9039
+ *
9040
+ * One bound for both: the picker is human time and sits outside it. What remains
9041
+ * — offer, ICE, invite, answer, DTLS — is the same work for either leg kind.
9042
+ */
9043
+ const DEFAULT_AUX_LEG_CONNECT_TIMEOUT_MS = 15e3;
9025
9044
  const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
9026
9045
  const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
9027
9046
  const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
@@ -9374,6 +9393,18 @@ var CollectionFetchError = class extends Error {
9374
9393
  this.name = "CollectionFetchError";
9375
9394
  }
9376
9395
  };
9396
+ /**
9397
+ * An auxiliary leg did not connect within its budget. Typed rather than a bare
9398
+ * RxJS `TimeoutError` so the leg and cause survive.
9399
+ */
9400
+ var AuxiliaryLegTimeoutError = class extends Error {
9401
+ constructor(leg, originalError) {
9402
+ super(`Timed out waiting for the ${leg} connection to be established`, { cause: originalError });
9403
+ this.leg = leg;
9404
+ this.originalError = originalError;
9405
+ this.name = "AuxiliaryLegTimeoutError";
9406
+ }
9407
+ };
9377
9408
  var MediaTrackError = class extends Error {
9378
9409
  constructor(operation, kind, originalError) {
9379
9410
  super(`Media track ${operation} failed for ${kind}`, { cause: originalError instanceof Error ? originalError : void 0 });
@@ -9650,9 +9681,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
9650
9681
  defaultLogger$1 = new Logger();
9651
9682
  defaultLogger$1.getLogger = function getLogger$1(name) {
9652
9683
  if (typeof name !== "symbol" && typeof name !== "string" || name === "") throw new TypeError("You must supply a name when creating a logger.");
9653
- var logger$33 = _loggersByName[name];
9654
- if (!logger$33) logger$33 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
9655
- return logger$33;
9684
+ var logger$34 = _loggersByName[name];
9685
+ if (!logger$34) logger$34 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
9686
+ return logger$34;
9656
9687
  };
9657
9688
  var _log = typeof window !== undefinedType ? window.log : void 0;
9658
9689
  defaultLogger$1.noConflict = function() {
@@ -9688,8 +9719,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
9688
9719
  defaultLogger.setLevel(defaultLoggerLevel);
9689
9720
  let userLogger = null;
9690
9721
  /** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
9691
- const setLogger = (logger$33) => {
9692
- userLogger = logger$33;
9722
+ const setLogger = (logger$34) => {
9723
+ userLogger = logger$34;
9693
9724
  };
9694
9725
  let debugOptions = {};
9695
9726
  /** Configure debug options (e.g., `{ logWsTraffic: true }`). */
@@ -9733,8 +9764,8 @@ const wsTraffic = (options) => {
9733
9764
  loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
9734
9765
  };
9735
9766
  const getLogger = () => {
9736
- const logger$33 = getLoggerInstance();
9737
- return new Proxy(logger$33, { get(_target, prop, _receiver) {
9767
+ const logger$34 = getLoggerInstance();
9768
+ return new Proxy(logger$34, { get(_target, prop, _receiver) {
9738
9769
  if (prop === "wsTraffic") return wsTraffic;
9739
9770
  const instance = getLoggerInstance();
9740
9771
  const value = Reflect.get(instance, prop);
@@ -9786,7 +9817,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
9786
9817
 
9787
9818
  //#endregion
9788
9819
  //#region src/controllers/HTTPRequestController.ts
9789
- const logger$32 = getLogger();
9820
+ const logger$33 = getLogger();
9790
9821
  const GET_PARAMS = {
9791
9822
  method: "GET",
9792
9823
  headers: { Accept: "application/json" }
@@ -9850,7 +9881,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9850
9881
  this._responses$.next(response);
9851
9882
  return response;
9852
9883
  } catch (error) {
9853
- logger$32.error("[HTTPRequestController] Request error:", error);
9884
+ logger$33.error("[HTTPRequestController] Request error:", error);
9854
9885
  this._status$.next("error");
9855
9886
  const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
9856
9887
  this._errors$.next(err);
@@ -9877,7 +9908,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9877
9908
  const url = this.buildURL(request.url);
9878
9909
  const headers = this.buildHeaders(request.headers);
9879
9910
  const timeout$5 = request.timeout ?? this.requestTimeout;
9880
- logger$32.debug("[HTTPRequestController] Executing request:", {
9911
+ logger$33.debug("[HTTPRequestController] Executing request:", {
9881
9912
  method: request.method,
9882
9913
  url,
9883
9914
  headers: Object.keys(headers).reduce((acc, key) => {
@@ -9897,7 +9928,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9897
9928
  });
9898
9929
  clearTimeout(timeoutId);
9899
9930
  const httpResponse = await this.convertResponse(response);
9900
- logger$32.debug("[HTTPRequestController] Response received:", {
9931
+ logger$33.debug("[HTTPRequestController] Response received:", {
9901
9932
  status: response.status,
9902
9933
  statusText: response.statusText,
9903
9934
  headers: [...response.headers.entries()],
@@ -9907,7 +9938,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9907
9938
  } catch (error) {
9908
9939
  clearTimeout(timeoutId);
9909
9940
  if (error instanceof Error && error.name === "AbortError") throw new RequestTimeoutError(`Request timeout after ${timeout$5}ms`, { cause: error });
9910
- logger$32.error("[HTTPRequestController] Request failed:", error);
9941
+ logger$33.error("[HTTPRequestController] Request failed:", error);
9911
9942
  throw error;
9912
9943
  }
9913
9944
  }
@@ -9921,8 +9952,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9921
9952
  const credential = this.getCredential();
9922
9953
  if (credential.token) {
9923
9954
  headers.Authorization = `Bearer ${credential.token}`;
9924
- logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
9925
- } else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
9955
+ logger$33.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
9956
+ } else logger$33.warn("[HTTPRequestController] No credentials available for authentication");
9926
9957
  return headers;
9927
9958
  }
9928
9959
  /**
@@ -10070,7 +10101,7 @@ function fromMsToSec(milliseconds) {
10070
10101
 
10071
10102
  //#endregion
10072
10103
  //#region src/containers/PreferencesContainer.ts
10073
- const logger$31 = getLogger();
10104
+ const logger$32 = getLogger();
10074
10105
  var PreferencesContainer = class PreferencesContainer {
10075
10106
  static get instance() {
10076
10107
  this._instance ??= new PreferencesContainer();
@@ -10732,7 +10763,7 @@ var ClientPreferences = class {
10732
10763
  if (!this._storage) return;
10733
10764
  const data = collectStoredPreferences();
10734
10765
  this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
10735
- logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
10766
+ logger$32.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
10736
10767
  });
10737
10768
  }
10738
10769
  /** Loads preferences from storage and applies them to the container. */
@@ -10741,7 +10772,7 @@ var ClientPreferences = class {
10741
10772
  this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
10742
10773
  if (stored) applyStoredPreferences(stored);
10743
10774
  }).catch((error) => {
10744
- logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
10775
+ logger$32.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
10745
10776
  });
10746
10777
  }
10747
10778
  };
@@ -10763,7 +10794,7 @@ function toError(value) {
10763
10794
  //#endregion
10764
10795
  //#region src/controllers/NavigatorDeviceController.ts
10765
10796
  var import_cjs$29 = require_cjs();
10766
- const logger$30 = getLogger();
10797
+ const logger$31 = getLogger();
10767
10798
  /** Maps a device kind to its storage key. */
10768
10799
  const DEVICE_STORAGE_KEYS = {
10769
10800
  audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
@@ -10785,7 +10816,7 @@ var NavigatorDeviceController = class extends Destroyable {
10785
10816
  super();
10786
10817
  this.webRTCApiProvider = webRTCApiProvider;
10787
10818
  this.deviceChangeHandler = () => {
10788
- logger$30.debug("[DeviceController] Device change detected");
10819
+ logger$31.debug("[DeviceController] Device change detected");
10789
10820
  this.enumerateDevices();
10790
10821
  };
10791
10822
  this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
@@ -10850,13 +10881,13 @@ var NavigatorDeviceController = class extends Destroyable {
10850
10881
  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$)));
10851
10882
  }
10852
10883
  get selectedAudioInputDevice$() {
10853
- 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))));
10884
+ 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))));
10854
10885
  }
10855
10886
  get selectedAudioOutputDevice$() {
10856
- 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))));
10887
+ 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))));
10857
10888
  }
10858
10889
  get selectedVideoInputDevice$() {
10859
- 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))));
10890
+ 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))));
10860
10891
  }
10861
10892
  get selectedAudioInputDevice() {
10862
10893
  if (this._audioInputDisabled$.value) return null;
@@ -10931,7 +10962,7 @@ var NavigatorDeviceController = class extends Destroyable {
10931
10962
  if (device) this.persistDeviceSelection("audioinput", device);
10932
10963
  }
10933
10964
  selectVideoInputDevice(device) {
10934
- logger$30.debug("[DeviceController] Setting selected video input device:", device);
10965
+ logger$31.debug("[DeviceController] Setting selected video input device:", device);
10935
10966
  if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
10936
10967
  const previous = this._selectedDevicesState$.value.videoinput;
10937
10968
  if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
@@ -10988,7 +11019,7 @@ var NavigatorDeviceController = class extends Destroyable {
10988
11019
  }
10989
11020
  const fromHistory = this._deviceHistory.findInHistory(kind, devices);
10990
11021
  if (fromHistory) {
10991
- logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
11022
+ logger$31.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
10992
11023
  this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
10993
11024
  return fromHistory;
10994
11025
  }
@@ -11041,7 +11072,7 @@ var NavigatorDeviceController = class extends Destroyable {
11041
11072
  try {
11042
11073
  await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
11043
11074
  } catch (error) {
11044
- logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
11075
+ logger$31.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
11045
11076
  }
11046
11077
  }
11047
11078
  async loadPersistedDevices() {
@@ -11057,7 +11088,7 @@ var NavigatorDeviceController = class extends Destroyable {
11057
11088
  [kind]: stored
11058
11089
  };
11059
11090
  } catch (error) {
11060
- logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
11091
+ logger$31.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
11061
11092
  }
11062
11093
  }
11063
11094
  /** Clears device history, persisted selections, and re-enumerates devices. */
@@ -11075,7 +11106,7 @@ var NavigatorDeviceController = class extends Destroyable {
11075
11106
  this.disableDeviceMonitoring();
11076
11107
  this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
11077
11108
  if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, import_cjs$29.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
11078
- logger$30.debug("[DeviceController] Polling devices due to interval");
11109
+ logger$31.debug("[DeviceController] Polling devices due to interval");
11079
11110
  this.enumerateDevices();
11080
11111
  });
11081
11112
  this.enumerateDevices();
@@ -11101,13 +11132,13 @@ var NavigatorDeviceController = class extends Destroyable {
11101
11132
  videoinput: []
11102
11133
  });
11103
11134
  this._devicesState$.next(devicesByKind);
11104
- logger$30.debug("[DeviceController] Devices enumerated:", {
11135
+ logger$31.debug("[DeviceController] Devices enumerated:", {
11105
11136
  audioInputs: devicesByKind.audioinput.length,
11106
11137
  audioOutputs: devicesByKind.audiooutput.length,
11107
11138
  videoInputs: devicesByKind.videoinput.length
11108
11139
  });
11109
11140
  } catch (error) {
11110
- logger$30.error("[DeviceController] Failed to enumerate devices:", error);
11141
+ logger$31.error("[DeviceController] Failed to enumerate devices:", error);
11111
11142
  this._errors$.next(toError(error));
11112
11143
  }
11113
11144
  }
@@ -11123,7 +11154,7 @@ var NavigatorDeviceController = class extends Destroyable {
11123
11154
  stream.getTracks().forEach((t) => t.stop());
11124
11155
  return capabilities;
11125
11156
  } catch (error) {
11126
- logger$30.error("[DeviceController] Failed to get device capabilities:", error);
11157
+ logger$31.error("[DeviceController] Failed to get device capabilities:", error);
11127
11158
  this._errors$.next(toError(error));
11128
11159
  throw error;
11129
11160
  }
@@ -11374,7 +11405,7 @@ var DependencyContainer = class {
11374
11405
 
11375
11406
  //#endregion
11376
11407
  //#region src/controllers/CryptoController.ts
11377
- const logger$29 = getLogger();
11408
+ const logger$30 = getLogger();
11378
11409
  const DPOP_DB_NAME = "sw-dpop";
11379
11410
  const DPOP_DB_VERSION = 1;
11380
11411
  const DPOP_STORE_NAME = "keys";
@@ -11433,7 +11464,7 @@ async function loadKeyPairFromDB() {
11433
11464
  tx.oncomplete = () => db.close();
11434
11465
  });
11435
11466
  } catch (error) {
11436
- logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
11467
+ logger$30.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
11437
11468
  return null;
11438
11469
  }
11439
11470
  }
@@ -11453,7 +11484,7 @@ async function saveKeyPairToDB(keyPair) {
11453
11484
  };
11454
11485
  });
11455
11486
  } catch (error) {
11456
- logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
11487
+ logger$30.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
11457
11488
  }
11458
11489
  }
11459
11490
  async function deleteKeyPairFromDB() {
@@ -11472,7 +11503,7 @@ async function deleteKeyPairFromDB() {
11472
11503
  };
11473
11504
  });
11474
11505
  } catch (error) {
11475
- logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
11506
+ logger$30.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
11476
11507
  }
11477
11508
  }
11478
11509
  /**
@@ -11532,13 +11563,13 @@ var CryptoController = class {
11532
11563
  this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
11533
11564
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
11534
11565
  this._initialized = true;
11535
- logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
11566
+ logger$30.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
11536
11567
  return this._fingerprint;
11537
11568
  } catch (error) {
11538
- logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
11569
+ logger$30.warn("[DPoP] Stored key pair unusable, generating new one:", error);
11539
11570
  await deleteKeyPairFromDB();
11540
11571
  }
11541
- logger$29.debug("[DPoP] Generating RSA key pair");
11572
+ logger$30.debug("[DPoP] Generating RSA key pair");
11542
11573
  this._keyPair = await crypto.subtle.generateKey({
11543
11574
  name: "RSASSA-PKCS1-v1_5",
11544
11575
  modulusLength: 2048,
@@ -11553,7 +11584,7 @@ var CryptoController = class {
11553
11584
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
11554
11585
  this._initialized = true;
11555
11586
  await saveKeyPairToDB(this._keyPair);
11556
- logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
11587
+ logger$30.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
11557
11588
  return this._fingerprint;
11558
11589
  }
11559
11590
  /**
@@ -11619,7 +11650,7 @@ var CryptoController = class {
11619
11650
  this._fingerprint = null;
11620
11651
  this._initialized = false;
11621
11652
  deleteKeyPairFromDB();
11622
- logger$29.debug("[DPoP] Controller destroyed");
11653
+ logger$30.debug("[DPoP] Controller destroyed");
11623
11654
  }
11624
11655
  get publicJwk() {
11625
11656
  if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
@@ -11643,7 +11674,7 @@ var CryptoController = class {
11643
11674
  //#endregion
11644
11675
  //#region src/controllers/NetworkMonitor.ts
11645
11676
  var import_cjs$28 = require_cjs();
11646
- const logger$28 = getLogger();
11677
+ const logger$29 = getLogger();
11647
11678
  /**
11648
11679
  * Safely check whether we are running in a browser environment
11649
11680
  * with `window` and the relevant event targets.
@@ -11700,7 +11731,7 @@ var NetworkMonitor = class extends Destroyable {
11700
11731
  }
11701
11732
  attachListeners() {
11702
11733
  if (!hasBrowserNetworkEvents()) {
11703
- logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
11734
+ logger$29.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
11704
11735
  return;
11705
11736
  }
11706
11737
  window.addEventListener("online", this._onOnline);
@@ -11708,7 +11739,7 @@ var NetworkMonitor = class extends Destroyable {
11708
11739
  const connection = getNetworkConnection();
11709
11740
  if (connection) connection.addEventListener("change", this._onConnectionChange);
11710
11741
  this._listenersAttached = true;
11711
- logger$28.debug("NetworkMonitor: event listeners attached");
11742
+ logger$29.debug("NetworkMonitor: event listeners attached");
11712
11743
  }
11713
11744
  removeListeners() {
11714
11745
  if (!this._listenersAttached) return;
@@ -11719,10 +11750,10 @@ var NetworkMonitor = class extends Destroyable {
11719
11750
  if (connection) connection.removeEventListener("change", this._onConnectionChange);
11720
11751
  }
11721
11752
  this._listenersAttached = false;
11722
- logger$28.debug("NetworkMonitor: event listeners removed");
11753
+ logger$29.debug("NetworkMonitor: event listeners removed");
11723
11754
  }
11724
11755
  handleOnline() {
11725
- logger$28.info("NetworkMonitor: browser went online");
11756
+ logger$29.info("NetworkMonitor: browser went online");
11726
11757
  this._isOnline$.next(true);
11727
11758
  this._networkChange$.next({
11728
11759
  type: "online",
@@ -11731,7 +11762,7 @@ var NetworkMonitor = class extends Destroyable {
11731
11762
  });
11732
11763
  }
11733
11764
  handleOffline() {
11734
- logger$28.info("NetworkMonitor: browser went offline");
11765
+ logger$29.info("NetworkMonitor: browser went offline");
11735
11766
  this._isOnline$.next(false);
11736
11767
  this._networkChange$.next({
11737
11768
  type: "offline",
@@ -11740,7 +11771,7 @@ var NetworkMonitor = class extends Destroyable {
11740
11771
  }
11741
11772
  handleConnectionChange() {
11742
11773
  const networkType = getNetworkType();
11743
- logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
11774
+ logger$29.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
11744
11775
  this._networkChange$.next({
11745
11776
  type: "connection_change",
11746
11777
  timestamp: Date.now(),
@@ -11856,7 +11887,7 @@ function getNavigatorMediaDevices() {
11856
11887
  //#endregion
11857
11888
  //#region src/controllers/PreflightRunner.ts
11858
11889
  var import_cjs$27 = require_cjs();
11859
- const logger$27 = getLogger();
11890
+ const logger$28 = getLogger();
11860
11891
  const DEFAULT_MEDIA_TEST_DURATION_S = 10;
11861
11892
  const ICE_GATHERING_TIMEOUT_MS = 1e4;
11862
11893
  const SIGNALING_RTT_TIMEOUT_MS = 5e3;
@@ -11905,7 +11936,7 @@ var PreflightRunner = class extends Destroyable {
11905
11936
  if (!this._options.skipMediaTest) try {
11906
11937
  bandwidth = await this.testMediaBandwidth(destination);
11907
11938
  } catch (error) {
11908
- logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
11939
+ logger$28.warn("[PreflightRunner] Media bandwidth test failed:", error);
11909
11940
  warnings.push("Media bandwidth test failed");
11910
11941
  }
11911
11942
  return {
@@ -11917,7 +11948,7 @@ var PreflightRunner = class extends Destroyable {
11917
11948
  warnings
11918
11949
  };
11919
11950
  } catch (error) {
11920
- logger$27.error("[PreflightRunner] Preflight test failed:", error);
11951
+ logger$28.error("[PreflightRunner] Preflight test failed:", error);
11921
11952
  throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
11922
11953
  } finally {
11923
11954
  this.destroy();
@@ -11948,7 +11979,7 @@ var PreflightRunner = class extends Destroyable {
11948
11979
  if (track.kind === "video" && track.readyState === "live") videoWorking = true;
11949
11980
  }
11950
11981
  } catch (error) {
11951
- logger$27.warn("[PreflightRunner] Device test failed:", error);
11982
+ logger$28.warn("[PreflightRunner] Device test failed:", error);
11952
11983
  } finally {
11953
11984
  if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
11954
11985
  }
@@ -12006,7 +12037,7 @@ var PreflightRunner = class extends Destroyable {
12006
12037
  rttMs
12007
12038
  };
12008
12039
  } catch (error) {
12009
- logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
12040
+ logger$28.warn("[PreflightRunner] ICE connectivity test failed:", error);
12010
12041
  return {
12011
12042
  type: "failed",
12012
12043
  turnReachable: false,
@@ -12054,7 +12085,7 @@ var PreflightRunner = class extends Destroyable {
12054
12085
  //#endregion
12055
12086
  //#region src/controllers/VisibilityController.ts
12056
12087
  var import_cjs$26 = require_cjs();
12057
- const logger$26 = getLogger();
12088
+ const logger$27 = getLogger();
12058
12089
  /**
12059
12090
  * Checks whether the document visibility API is available.
12060
12091
  */
@@ -12091,8 +12122,8 @@ var VisibilityController = class extends Destroyable {
12091
12122
  this._boundHandler = this._handleVisibilityChange.bind(this);
12092
12123
  if (this._hasVisibilityApi) {
12093
12124
  document.addEventListener("visibilitychange", this._boundHandler);
12094
- logger$26.debug("VisibilityController: listening for visibilitychange events");
12095
- } else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
12125
+ logger$27.debug("VisibilityController: listening for visibilitychange events");
12126
+ } else logger$27.debug("VisibilityController: document visibility API not available, defaulting to visible");
12096
12127
  }
12097
12128
  /**
12098
12129
  * Observable of the current visibility state.
@@ -12117,7 +12148,7 @@ var VisibilityController = class extends Destroyable {
12117
12148
  destroy() {
12118
12149
  if (this._hasVisibilityApi) {
12119
12150
  document.removeEventListener("visibilitychange", this._boundHandler);
12120
- logger$26.debug("VisibilityController: removed visibilitychange listener");
12151
+ logger$27.debug("VisibilityController: removed visibilitychange listener");
12121
12152
  }
12122
12153
  super.destroy();
12123
12154
  }
@@ -12135,7 +12166,7 @@ var VisibilityController = class extends Destroyable {
12135
12166
  timestamp: Date.now()
12136
12167
  };
12137
12168
  this._visibilityChange$.next(changeEvent);
12138
- logger$26.debug("VisibilityController: visibility changed", {
12169
+ logger$27.debug("VisibilityController: visibility changed", {
12139
12170
  from: previousState,
12140
12171
  to: newState
12141
12172
  });
@@ -12417,7 +12448,7 @@ function isRequesterValidationError(error) {
12417
12448
 
12418
12449
  //#endregion
12419
12450
  //#region src/managers/AttachManager.ts
12420
- const logger$25 = getLogger();
12451
+ const logger$26 = getLogger();
12421
12452
  var AttachManager = class {
12422
12453
  constructor(storage, deviceController, reconnectCallsTimeout, attachKey, credentialRecovered) {
12423
12454
  this.storage = storage;
@@ -12439,7 +12470,7 @@ var AttachManager = class {
12439
12470
  try {
12440
12471
  return await this.storage.getItem(this.attachKey) ?? {};
12441
12472
  } catch (error) {
12442
- logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
12473
+ logger$26.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
12443
12474
  return {};
12444
12475
  }
12445
12476
  }
@@ -12447,7 +12478,7 @@ var AttachManager = class {
12447
12478
  try {
12448
12479
  await this.storage.setItem(this.attachKey, attached);
12449
12480
  } catch (error) {
12450
- logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
12481
+ logger$26.warn("[AttachManager] Failed to write attached calls to storage", error);
12451
12482
  }
12452
12483
  }
12453
12484
  /**
@@ -12466,7 +12497,7 @@ var AttachManager = class {
12466
12497
  }
12467
12498
  async attach(call) {
12468
12499
  if (!call.to) {
12469
- logger$25.warn("[AttachManager] Skip attach for calls with no destination");
12500
+ logger$26.warn("[AttachManager] Skip attach for calls with no destination");
12470
12501
  return;
12471
12502
  }
12472
12503
  const destination = call.to;
@@ -12526,25 +12557,25 @@ var AttachManager = class {
12526
12557
  callId,
12527
12558
  ...options
12528
12559
  });
12529
- logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
12560
+ logger$26.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
12530
12561
  succeeded = true;
12531
12562
  break;
12532
12563
  } catch (error) {
12533
- logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
12564
+ logger$26.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
12534
12565
  if (isRequesterValidationError(error)) {
12535
12566
  refusedOnCredentials = true;
12536
- logger$25.warn(`[AttachManager] Reattach of ${callId} was refused on credentials; not retrying.`);
12567
+ logger$26.warn(`[AttachManager] Reattach of ${callId} was refused on credentials; not retrying.`);
12537
12568
  break;
12538
12569
  }
12539
12570
  if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
12540
12571
  }
12541
12572
  if (!succeeded) if (this.credentialRecovered() && !refusedOnCredentials) {
12542
- logger$25.warn(`[AttachManager] Reattach of ${callId} was denied after a verified reauthentication, removing reference`);
12573
+ logger$26.warn(`[AttachManager] Reattach of ${callId} was denied after a verified reauthentication, removing reference`);
12543
12574
  await this.detach({
12544
12575
  id: callId,
12545
12576
  mediaDirections: attachment.mediaDirections
12546
12577
  });
12547
- } else logger$25.warn(`[AttachManager] Reattach failed for call ${callId}; keeping the reference (credential refused or never proven good)`);
12578
+ } else logger$26.warn(`[AttachManager] Reattach failed for call ${callId}; keeping the reference (credential refused or never proven good)`);
12548
12579
  }
12549
12580
  }
12550
12581
  /**
@@ -12645,12 +12676,12 @@ var require_race = /* @__PURE__ */ __commonJSMin(((exports) => {
12645
12676
  exports.race = void 0;
12646
12677
  var argsOrArgArray_1 = require_argsOrArgArray();
12647
12678
  var raceWith_1$1 = require_raceWith();
12648
- function race$4() {
12679
+ function race$3() {
12649
12680
  var args = [];
12650
12681
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
12651
12682
  return raceWith_1$1.raceWith.apply(void 0, __spreadArray([], __read(argsOrArgArray_1.argsOrArgArray(args))));
12652
12683
  }
12653
- exports.race = race$4;
12684
+ exports.race = race$3;
12654
12685
  }));
12655
12686
 
12656
12687
  //#endregion
@@ -13710,7 +13741,7 @@ function toggleHandraiseMethod(is) {
13710
13741
 
13711
13742
  //#endregion
13712
13743
  //#region src/core/entities/Participant.ts
13713
- const logger$24 = getLogger();
13744
+ const logger$25 = getLogger();
13714
13745
  const initialState = {};
13715
13746
  /**
13716
13747
  * Represents a participant in a call.
@@ -14184,6 +14215,9 @@ var SelfParticipant = class extends Participant {
14184
14215
  *
14185
14216
  * The call is unaffected when acquisition fails.
14186
14217
  *
14218
+ * Known issue: stopping the share before its leg connects can leave this
14219
+ * promise pending until the call ends, so do not gate UI on it.
14220
+ *
14187
14221
  * @throws The raw `getDisplayMedia` error. A dismissed picker or a
14188
14222
  * permission denial rejects with a `NotAllowedError` `DOMException` —
14189
14223
  * inspect `error.name` to tell benign cancels apart from real failures.
@@ -14192,7 +14226,7 @@ var SelfParticipant = class extends Participant {
14192
14226
  try {
14193
14227
  await this.vertoManager.addScreenMedia();
14194
14228
  } catch (error) {
14195
- logger$24.error("[Participant.startScreenShare] Screen share error:", error);
14229
+ logger$25.error("[Participant.startScreenShare] Screen share error:", error);
14196
14230
  throw error;
14197
14231
  }
14198
14232
  }
@@ -14213,14 +14247,18 @@ var SelfParticipant = class extends Participant {
14213
14247
  *
14214
14248
  * The call is unaffected when acquisition fails.
14215
14249
  *
14250
+ * Known issue: removing the device before its leg connects can leave this
14251
+ * promise pending until the call ends, so do not gate UI on it.
14252
+ *
14216
14253
  * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
14217
- * permission denial) — inspect `error.name` to decide how to react.
14254
+ * permission denial) — inspect `error.name` to decide how to react — or
14255
+ * `AuxiliaryLegTimeoutError` if the leg does not connect in time.
14218
14256
  */
14219
14257
  async addAdditionalDevice(options) {
14220
14258
  try {
14221
14259
  await this.vertoManager.addInputDevice(options);
14222
14260
  } catch (error) {
14223
- logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
14261
+ logger$25.error("[Participant.addAdditionalDevice] Additional device error:", error);
14224
14262
  throw error;
14225
14263
  }
14226
14264
  }
@@ -14283,7 +14321,7 @@ var SelfParticipant = class extends Participant {
14283
14321
  */
14284
14322
  exitStudioModeIfActive() {
14285
14323
  if (this._studioAudio$.value) {
14286
- logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
14324
+ logger$25.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
14287
14325
  this._studioAudio$.next(false);
14288
14326
  }
14289
14327
  }
@@ -14307,7 +14345,7 @@ var SelfParticipant = class extends Participant {
14307
14345
  try {
14308
14346
  await super.mute();
14309
14347
  } catch (error) {
14310
- logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
14348
+ logger$25.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
14311
14349
  } finally {
14312
14350
  this.vertoManager.muteMainAudioInputDevice();
14313
14351
  }
@@ -14317,7 +14355,7 @@ var SelfParticipant = class extends Participant {
14317
14355
  try {
14318
14356
  await super.unmute();
14319
14357
  } catch (error) {
14320
- logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
14358
+ logger$25.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
14321
14359
  } finally {
14322
14360
  await this.vertoManager.unmuteMainAudioInputDevice();
14323
14361
  }
@@ -14327,7 +14365,7 @@ var SelfParticipant = class extends Participant {
14327
14365
  try {
14328
14366
  await super.muteVideo();
14329
14367
  } catch (error) {
14330
- logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
14368
+ logger$25.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
14331
14369
  } finally {
14332
14370
  this.vertoManager.muteMainVideoInputDevice();
14333
14371
  }
@@ -14337,7 +14375,7 @@ var SelfParticipant = class extends Participant {
14337
14375
  try {
14338
14376
  await super.unmuteVideo();
14339
14377
  } catch (error) {
14340
- logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
14378
+ logger$25.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
14341
14379
  } finally {
14342
14380
  await this.vertoManager.unmuteMainVideoInputDevice();
14343
14381
  }
@@ -14542,7 +14580,7 @@ function filterAs(predicate, resultPath) {
14542
14580
  //#endregion
14543
14581
  //#region src/operators/throwOnRPCError.ts
14544
14582
  var import_cjs$21 = require_cjs();
14545
- const logger$23 = getLogger();
14583
+ const logger$24 = getLogger();
14546
14584
  /**
14547
14585
  * RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
14548
14586
  * Passes successful responses through unchanged.
@@ -14550,14 +14588,14 @@ const logger$23 = getLogger();
14550
14588
  function throwOnRPCError() {
14551
14589
  return (0, import_cjs$21.map)((response) => {
14552
14590
  if (response.error) {
14553
- logger$23.error("[throwOnRPCError] RPC error response:", {
14591
+ logger$24.error("[throwOnRPCError] RPC error response:", {
14554
14592
  code: response.error.code,
14555
14593
  message: response.error.message,
14556
14594
  data: response.error.data
14557
14595
  });
14558
14596
  throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
14559
14597
  }
14560
- logger$23.debug("[throwOnRPCError] RPC successful response:", response);
14598
+ logger$24.debug("[throwOnRPCError] RPC successful response:", response);
14561
14599
  return response;
14562
14600
  });
14563
14601
  }
@@ -14565,7 +14603,7 @@ function throwOnRPCError() {
14565
14603
  //#endregion
14566
14604
  //#region src/managers/CallEventsManager.ts
14567
14605
  var import_cjs$20 = require_cjs();
14568
- const logger$22 = getLogger();
14606
+ const logger$23 = getLogger();
14569
14607
  const initialSessionState = {};
14570
14608
  /** @internal */
14571
14609
  var CallEventsManager = class extends Destroyable {
@@ -14669,7 +14707,7 @@ var CallEventsManager = class extends Destroyable {
14669
14707
  }
14670
14708
  initSubscriptions() {
14671
14709
  this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
14672
- logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
14710
+ logger$23.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
14673
14711
  callId: callJoinedEvent.call_id,
14674
14712
  roomSessionId: callJoinedEvent.room_session_id
14675
14713
  });
@@ -14696,19 +14734,19 @@ var CallEventsManager = class extends Destroyable {
14696
14734
  if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
14697
14735
  });
14698
14736
  this.subscribeTo(this.memberUpdates$, (member) => {
14699
- logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
14737
+ logger$23.debug("[CallEventsManager] Handling member update event for member ID:", member);
14700
14738
  this.upsertParticipant(member);
14701
14739
  });
14702
14740
  this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
14703
- logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
14741
+ logger$23.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
14704
14742
  const participants = { ...this._participants$.value };
14705
14743
  if (memberLeftEvent.member.member_id in participants) {
14706
14744
  delete participants[memberLeftEvent.member.member_id];
14707
14745
  this._participants$.next(participants);
14708
- } else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
14746
+ } else logger$23.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
14709
14747
  });
14710
14748
  this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
14711
- logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
14749
+ logger$23.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
14712
14750
  const roomSession = callUpdatedEvent.room_session;
14713
14751
  this._sessionState$.next({
14714
14752
  ...this._sessionState$.value,
@@ -14723,7 +14761,7 @@ var CallEventsManager = class extends Destroyable {
14723
14761
  });
14724
14762
  });
14725
14763
  this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
14726
- logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
14764
+ logger$23.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
14727
14765
  this._sessionState$.next({
14728
14766
  ...this._sessionState$.value,
14729
14767
  layout_name: layoutChangedEvent.id,
@@ -14733,10 +14771,10 @@ var CallEventsManager = class extends Destroyable {
14733
14771
  });
14734
14772
  }
14735
14773
  updateParticipantPositions(layoutChangedEvent) {
14736
- 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.");
14774
+ 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.");
14737
14775
  layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
14738
14776
  if (!(layer.member_id in this._participants$.value)) {
14739
- logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
14777
+ logger$23.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
14740
14778
  return false;
14741
14779
  }
14742
14780
  return true;
@@ -14756,7 +14794,7 @@ var CallEventsManager = class extends Destroyable {
14756
14794
  this.webRtcCallSession.executeMethod(this.selfId, "call.layout.list", {}).then((response) => {
14757
14795
  const layouts = response.result?.layouts;
14758
14796
  if (!layouts) {
14759
- logger$22.warn("[CallEventsManager] Layout list response carried no layouts; keeping current layouts");
14797
+ logger$23.warn("[CallEventsManager] Layout list response carried no layouts; keeping current layouts");
14760
14798
  return;
14761
14799
  }
14762
14800
  this._sessionState$.next({
@@ -14764,7 +14802,7 @@ var CallEventsManager = class extends Destroyable {
14764
14802
  layouts
14765
14803
  });
14766
14804
  }).catch((error) => {
14767
- logger$22.error("[CallEventsManager] Error fetching layouts:", error);
14805
+ logger$23.error("[CallEventsManager] Error fetching layouts:", error);
14768
14806
  });
14769
14807
  }
14770
14808
  updateParticipants(members) {
@@ -14780,7 +14818,7 @@ var CallEventsManager = class extends Destroyable {
14780
14818
  }
14781
14819
  const participant = this._participants$.value[member.member_id];
14782
14820
  const oldValue = participant.value;
14783
- logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
14821
+ logger$23.debug("[CallEventsManager] Updating participant:", member.member_id, {
14784
14822
  oldValue,
14785
14823
  newValue: member
14786
14824
  });
@@ -14793,17 +14831,17 @@ var CallEventsManager = class extends Destroyable {
14793
14831
  }
14794
14832
  get callJoinedEvent$() {
14795
14833
  return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, import_cjs$20.filter)(isCallJoinedPayload), (0, import_cjs$20.tap)((event) => {
14796
- logger$22.debug("[CallEventsManager] Call joined event:", event);
14834
+ logger$23.debug("[CallEventsManager] Call joined event:", event);
14797
14835
  })));
14798
14836
  }
14799
14837
  get layoutChangedEvent$() {
14800
14838
  return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(filterAs(isLayoutChangedPayload, "layout"), (0, import_cjs$20.tap)((event) => {
14801
- logger$22.debug("[CallEventsManager] Layout changed event:", event);
14839
+ logger$23.debug("[CallEventsManager] Layout changed event:", event);
14802
14840
  })));
14803
14841
  }
14804
14842
  get memberUpdates$() {
14805
14843
  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) => {
14806
- logger$22.debug("[CallEventsManager] Member update event:", event);
14844
+ logger$23.debug("[CallEventsManager] Member update event:", event);
14807
14845
  })));
14808
14846
  }
14809
14847
  destroy() {
@@ -14822,6 +14860,111 @@ var CallEventsManager = class extends Destroyable {
14822
14860
  }
14823
14861
  };
14824
14862
 
14863
+ //#endregion
14864
+ //#region src/controllers/ConstraintFallbackHelper.ts
14865
+ /**
14866
+ * ConstraintFallbackHelper - Provides getUserMedia with automatic constraint
14867
+ * fallback on OverconstrainedError.
14868
+ *
14869
+ * When a specific device ID is requested, the helper tries progressively
14870
+ * looser constraints:
14871
+ * 1. `{ deviceId: { exact: deviceId } }` -- exact match
14872
+ * 2. `{ deviceId: deviceId }` -- preferred (browser may pick another)
14873
+ * 3. `{}` -- no constraint, browser default
14874
+ *
14875
+ * This prevents stale device IDs from blocking call setup.
14876
+ *
14877
+ * @see Section 5.8 and Section 11 of the Implementation Guide
14878
+ */
14879
+ const logger$22 = getLogger();
14880
+ /**
14881
+ * Attempts getUserMedia with progressively looser constraints.
14882
+ *
14883
+ * The function tries three levels of constraint specificity for the given
14884
+ * device kind. Each level is only attempted if the previous one fails with
14885
+ * an OverconstrainedError. Non-OverconstrainedError failures (e.g.,
14886
+ * NotAllowedError) are thrown immediately without fallback.
14887
+ *
14888
+ * @param mediaDevices - Anything exposing `getUserMedia` (a full
14889
+ * `WebRTCMediaDevices`, or a shim wrapping one)
14890
+ * @param constraints - The full MediaStreamConstraints to use as a base
14891
+ * @param kind - Which track kind to apply fallback to ('audio' | 'video')
14892
+ * @param deviceId - The device ID to try (if undefined, calls getUserMedia as-is)
14893
+ * @returns The stream and the fallback level that succeeded
14894
+ * @throws When all fallback levels fail, or when a non-OverconstrainedError occurs
14895
+ */
14896
+ async function getUserMediaWithFallback(mediaDevices, constraints, kind, deviceId) {
14897
+ if (!deviceId) return {
14898
+ stream: await mediaDevices.getUserMedia(constraints),
14899
+ fallbackLevel: "default"
14900
+ };
14901
+ const baseConstraints = typeof constraints[kind] === "object" ? constraints[kind] : {};
14902
+ try {
14903
+ const exactConstraints = {
14904
+ ...constraints,
14905
+ [kind]: {
14906
+ ...baseConstraints,
14907
+ deviceId: { exact: deviceId }
14908
+ }
14909
+ };
14910
+ return {
14911
+ stream: await mediaDevices.getUserMedia(exactConstraints),
14912
+ fallbackLevel: "exact"
14913
+ };
14914
+ } catch (error) {
14915
+ if (!isOverconstrainedError(error)) throw error;
14916
+ logger$22.debug(`[ConstraintFallbackHelper] Exact constraint failed for ${kind}, trying preferred`, { deviceId });
14917
+ }
14918
+ try {
14919
+ const preferredConstraints = {
14920
+ ...constraints,
14921
+ [kind]: {
14922
+ ...baseConstraints,
14923
+ deviceId
14924
+ }
14925
+ };
14926
+ return {
14927
+ stream: await mediaDevices.getUserMedia(preferredConstraints),
14928
+ fallbackLevel: "preferred"
14929
+ };
14930
+ } catch (error) {
14931
+ if (!isOverconstrainedError(error)) throw error;
14932
+ logger$22.debug(`[ConstraintFallbackHelper] Preferred constraint failed for ${kind}, trying default`, { deviceId });
14933
+ }
14934
+ try {
14935
+ const defaultConstraints = {
14936
+ ...constraints,
14937
+ [kind]: { ...baseConstraints }
14938
+ };
14939
+ if (typeof defaultConstraints[kind] === "object") {
14940
+ const { deviceId: _removed, ...rest } = defaultConstraints[kind];
14941
+ defaultConstraints[kind] = rest;
14942
+ }
14943
+ const stream = await mediaDevices.getUserMedia(defaultConstraints);
14944
+ logger$22.warn(`[ConstraintFallbackHelper] Fell back to browser default for ${kind}`, { requestedDeviceId: deviceId });
14945
+ return {
14946
+ stream,
14947
+ fallbackLevel: "default"
14948
+ };
14949
+ } catch (error) {
14950
+ logger$22.error(`[ConstraintFallbackHelper] All fallback levels exhausted for ${kind}`, {
14951
+ deviceId,
14952
+ error
14953
+ });
14954
+ throw error;
14955
+ }
14956
+ }
14957
+ /**
14958
+ * Checks whether an error is an OverconstrainedError.
14959
+ *
14960
+ * Browsers may throw either a native OverconstrainedError or a DOMException
14961
+ * with a specific name.
14962
+ */
14963
+ function isOverconstrainedError(error) {
14964
+ if (error instanceof Error) return error.name === "OverconstrainedError" || error.name === "ConstraintNotSatisfiedError";
14965
+ return false;
14966
+ }
14967
+
14825
14968
  //#endregion
14826
14969
  //#region src/helpers/SDPHelper.ts
14827
14970
  /**
@@ -15380,6 +15523,7 @@ var LocalStreamController = class extends Destroyable {
15380
15523
  this._localAudioTracks$ = this.createBehaviorSubject([]);
15381
15524
  this._localVideoTracks$ = this.createBehaviorSubject([]);
15382
15525
  this._mediaTrackEnded$ = this.createSubject();
15526
+ this._trackOrigins = /* @__PURE__ */ new WeakMap();
15383
15527
  }
15384
15528
  get localStream$() {
15385
15529
  return this._localStream$.asObservable().pipe((0, import_cjs$17.takeUntil)(this.destroyed$));
@@ -15402,6 +15546,22 @@ var LocalStreamController = class extends Destroyable {
15402
15546
  get localVideoTracks() {
15403
15547
  return this._localVideoTracks$.value;
15404
15548
  }
15549
+ tagTracks(tracks, origin) {
15550
+ for (const track of tracks) this._trackOrigins.set(track, origin);
15551
+ }
15552
+ setTrackOrigin(track, origin) {
15553
+ this._trackOrigins.set(track, origin);
15554
+ }
15555
+ getTrackOrigin(track) {
15556
+ return this._trackOrigins.get(track);
15557
+ }
15558
+ /**
15559
+ * Fail-safe: an unrecorded track reads as not-a-device-capture, so a missed
15560
+ * tagging site leaves media alone rather than destroying it.
15561
+ */
15562
+ isDeviceCapture(track) {
15563
+ return this._trackOrigins.get(track) === "device";
15564
+ }
15405
15565
  /**
15406
15566
  * Build the local media stream based on the provided options.
15407
15567
  */
@@ -15411,6 +15571,7 @@ var LocalStreamController = class extends Destroyable {
15411
15571
  if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
15412
15572
  const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
15413
15573
  stream = new MediaStream(tracks);
15574
+ this.tagTracks(tracks, "application");
15414
15575
  } else if (this.options.propose === "screenshare") {
15415
15576
  logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
15416
15577
  stream = await this.options.getDisplayMedia({
@@ -15418,6 +15579,7 @@ var LocalStreamController = class extends Destroyable {
15418
15579
  audio: Boolean(this.options.inputAudioDeviceConstraints)
15419
15580
  });
15420
15581
  logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
15582
+ this.tagTracks(stream.getTracks(), "display");
15421
15583
  } else {
15422
15584
  const constraints = {
15423
15585
  audio: this.options.inputAudioDeviceConstraints,
@@ -15426,6 +15588,7 @@ var LocalStreamController = class extends Destroyable {
15426
15588
  logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
15427
15589
  stream = await this.options.getUserMedia(constraints);
15428
15590
  logger$19.debug("[LocalStreamController] User media obtained:", stream);
15591
+ this.tagTracks(stream.getTracks(), "device");
15429
15592
  }
15430
15593
  this._localStream$.next(stream);
15431
15594
  this._localAudioTracks$.next(stream.getAudioTracks());
@@ -15435,10 +15598,13 @@ var LocalStreamController = class extends Destroyable {
15435
15598
  /**
15436
15599
  * Add a local media track to the local stream.
15437
15600
  * @param track - The MediaStreamTrack to add
15601
+ * @param origin - Defaults to `'device'`; every internal caller passes a
15602
+ * fresh `getUserMedia` capture.
15438
15603
  * @returns The MediaStream (either existing or newly created)
15439
15604
  */
15440
- addTrack(track) {
15605
+ addTrack(track, origin = "device") {
15441
15606
  const localStream = this._localStream$.value ?? new MediaStream();
15607
+ this._trackOrigins.set(track, origin);
15442
15608
  track.addEventListener("ended", this.mediaTrackEndedHandler);
15443
15609
  localStream.addTrack(track);
15444
15610
  this._localStream$.next(localStream);
@@ -15708,15 +15874,27 @@ var TransceiverController = class extends Destroyable {
15708
15874
  for (let i = 0; i < Number(msStreamsNumber); i++) this.peerConnection.addTransceiver("video", { direction: "recvonly" });
15709
15875
  }
15710
15876
  }
15877
+ /**
15878
+ * @returns whether every live sender of the kind took the constraints. A
15879
+ * skipped non-device sender, an exhausted fallback, and having no live sender
15880
+ * at all all report `false` — `mediaParamsUpdated.applied` is built from this,
15881
+ * and an application told `true` cannot tell a working push from a no-op.
15882
+ */
15711
15883
  async updateSendersConstraints(kind, constraints) {
15712
15884
  if (!constraints) {
15713
15885
  this.stopTrackSender(kind);
15714
- return Promise.resolve();
15886
+ return false;
15715
15887
  }
15716
15888
  const senders = this.peerConnection.getSenders().filter((sender) => sender.track?.kind === kind && sender.track.readyState === "live");
15889
+ let applied = senders.length > 0;
15717
15890
  for (const sender of senders) {
15718
15891
  const { track } = sender;
15719
15892
  if (track) {
15893
+ if (!this.options.localStreamController.isDeviceCapture(track)) {
15894
+ logger$18.debug(`[TransceiverController] Skipping ${kind} constraints for a non-device track (origin: ${this.options.localStreamController.getTrackOrigin(track) ?? "unrecorded"}), track ${track.id}`);
15895
+ applied = false;
15896
+ continue;
15897
+ }
15720
15898
  const constraintsToApply = {
15721
15899
  ...track.getConstraints(),
15722
15900
  ...constraints
@@ -15732,33 +15910,39 @@ var TransceiverController = class extends Destroyable {
15732
15910
  } catch (fallbackError) {
15733
15911
  logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
15734
15912
  this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
15913
+ applied = false;
15735
15914
  }
15736
15915
  }
15737
15916
  }
15738
15917
  }
15918
+ return applied;
15739
15919
  }
15740
15920
  /**
15741
- * Fallback when applyConstraints fails: stop the current track, acquire a new
15742
- * one via getUserMedia with the merged constraints (preserving the current
15743
- * deviceId), replace the sender track, and update the localStream.
15921
+ * Fallback when applyConstraints fails, which on iOS Safari it silently does.
15744
15922
  *
15745
- * This is critical for iOS Safari where applyConstraints on audio tracks
15746
- * silently fails or throws.
15923
+ * Order matters: acquiring before stopping means a failed acquisition leaves
15924
+ * the existing media playing. The deviceId goes through the fallback ladder
15925
+ * rather than pinned `{ exact }`, so a stale id degrades instead of failing.
15747
15926
  */
15748
15927
  async replaceTrackFallback(sender, oldTrack, kind, mergedConstraints) {
15749
15928
  const { deviceId } = oldTrack.getSettings();
15750
- const constraintsWithDevice = {
15751
- ...mergedConstraints,
15752
- ...deviceId ? { deviceId: { exact: deviceId } } : {}
15753
- };
15754
- const trackId = oldTrack.id;
15929
+ const { stream, fallbackLevel } = await getUserMediaWithFallback({ getUserMedia: this.options.getUserMedia }, { [kind]: mergedConstraints }, kind, deviceId);
15930
+ const newTrack = stream.getTracks().find((t) => t.kind === kind);
15931
+ if (!newTrack) {
15932
+ stream.getTracks().forEach((t) => t.stop());
15933
+ throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
15934
+ }
15935
+ try {
15936
+ await sender.replaceTrack(newTrack);
15937
+ } catch (error) {
15938
+ stream.getTracks().forEach((t) => t.stop());
15939
+ throw error;
15940
+ }
15941
+ const oldTrackId = oldTrack.id;
15942
+ this.options.localStreamController.removeTrack(oldTrackId);
15755
15943
  oldTrack.stop();
15756
- this.options.localStreamController.removeTrack(trackId);
15757
- const newTrack = (await this.options.getUserMedia({ [kind]: constraintsWithDevice })).getTracks().find((t) => t.kind === kind);
15758
- if (!newTrack) throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
15759
- await sender.replaceTrack(newTrack);
15760
15944
  this.options.localStreamController.addTrack(newTrack);
15761
- logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
15945
+ logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind} (deviceId fallback level: ${fallbackLevel}). New track: ${newTrack.id}`);
15762
15946
  }
15763
15947
  getMediaDirections() {
15764
15948
  if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
@@ -15870,6 +16054,7 @@ var RTCPeerConnectionController = class extends Destroyable {
15870
16054
  this._iceGatheringState$ = this.createReplaySubject(1);
15871
16055
  this._errors$ = this.createReplaySubject(1);
15872
16056
  this._iceCandidates$ = this.createReplaySubject(1);
16057
+ this._localMediaSettled$ = this.createReplaySubject(1);
15873
16058
  this._initialized$ = this.createReplaySubject(1);
15874
16059
  this._remoteDescription$ = this.createReplaySubject(1);
15875
16060
  this._remoteStream$ = this.createBehaviorSubject(null);
@@ -15973,6 +16158,10 @@ var RTCPeerConnectionController = class extends Destroyable {
15973
16158
  get remoteDescription$() {
15974
16159
  return this.cachedObservable("remoteDescription$", () => this._remoteDescription$.asObservable().pipe((0, import_cjs$16.takeUntil)(this.destroyed$)));
15975
16160
  }
16161
+ /** Emits once local media is settled — acquired, or knowingly receive-only. */
16162
+ get localMediaSettled$() {
16163
+ return this.cachedObservable("localMediaSettled$", () => this._localMediaSettled$.asObservable().pipe((0, import_cjs$16.takeUntil)(this.destroyed$)));
16164
+ }
15976
16165
  get localStream$() {
15977
16166
  return this.cachedObservable("localStream$", () => this.localStreamController.localStream$.pipe((0, import_cjs$16.takeUntil)(this.destroyed$)));
15978
16167
  }
@@ -16128,7 +16317,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16128
16317
  this._isNegotiating$.next(true);
16129
16318
  await this._setRemoteDescription(this.sdpInit);
16130
16319
  } else {
16131
- await this.setupTrackHandling();
16320
+ if (!await this.setupTrackHandling()) return;
16132
16321
  this._initialized$.next(true);
16133
16322
  }
16134
16323
  } catch (error) {
@@ -16252,7 +16441,10 @@ var RTCPeerConnectionController = class extends Destroyable {
16252
16441
  inputVideoDeviceConstraints: this.inputVideoDeviceConstraints
16253
16442
  });
16254
16443
  }
16255
- await this.setupLocalTracks();
16444
+ if (!await this.setupLocalTracks()) {
16445
+ logger$17.debug("[RTCPeerConnectionController] Inbound answer abandoned; the connection went away.");
16446
+ return;
16447
+ }
16256
16448
  const { answerOptions } = this;
16257
16449
  logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
16258
16450
  await this.createAnswer(answerOptions);
@@ -16390,35 +16582,52 @@ var RTCPeerConnectionController = class extends Destroyable {
16390
16582
  }
16391
16583
  /**
16392
16584
  * Setup track handling for remote tracks.
16585
+ *
16586
+ * @returns `false` when the connection went away while local media was being
16587
+ * acquired — see {@link setupLocalTracks}.
16393
16588
  */
16394
16589
  async setupTrackHandling() {
16395
16590
  if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
16396
- await this.setupLocalTracks();
16591
+ if (!await this.setupLocalTracks()) return false;
16397
16592
  await this.setupRemoteTracks();
16593
+ return true;
16398
16594
  }
16595
+ /**
16596
+ * @returns `false` when the connection was torn down while getUserMedia was
16597
+ * in flight. The acquisition is not cancellable, so the caller must stop
16598
+ * rather than go on to touch a peer connection that is closed or gone.
16599
+ */
16399
16600
  async setupLocalTracks() {
16400
16601
  logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
16401
16602
  if (this.hasNoLocalMediaToSend()) {
16402
16603
  if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
16403
16604
  logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
16404
16605
  this.setupReceiveOnlyTransceivers();
16405
- return;
16606
+ this._localMediaSettled$.next();
16607
+ return true;
16406
16608
  }
16407
16609
  let localStream;
16408
16610
  try {
16409
16611
  localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
16410
16612
  } catch (error) {
16411
16613
  this.handleLocalMediaFailure(error);
16412
- return;
16614
+ this._localMediaSettled$.next();
16615
+ return true;
16413
16616
  }
16617
+ if (!this.peerConnection || this.peerConnection.signalingState === "closed") {
16618
+ logger$17.debug("[RTCPeerConnectionController] Local media arrived after teardown; releasing it.");
16619
+ localStream.getTracks().forEach((track) => track.stop());
16620
+ return false;
16621
+ }
16622
+ this._localMediaSettled$.next();
16414
16623
  if (this.transceiverController?.useAddStream ?? false) {
16415
16624
  logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
16416
- this.peerConnection?.addStream(localStream);
16625
+ this.peerConnection.addStream(localStream);
16417
16626
  if (!this.isNegotiating) {
16418
16627
  logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
16419
16628
  this.negotiationNeeded$.next();
16420
16629
  }
16421
- return;
16630
+ return true;
16422
16631
  }
16423
16632
  for (const kind of ["audio", "video"]) {
16424
16633
  const tracks = (kind === "audio" ? localStream.getAudioTracks() : localStream.getVideoTracks()).map((track, index) => ({
@@ -16432,10 +16641,11 @@ var RTCPeerConnectionController = class extends Destroyable {
16432
16641
  await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
16433
16642
  } else {
16434
16643
  logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
16435
- this.peerConnection?.addTrack(track, localStream);
16644
+ this.peerConnection.addTrack(track, localStream);
16436
16645
  }
16437
16646
  }
16438
16647
  }
16648
+ return true;
16439
16649
  }
16440
16650
  /** True for a main connection with no local media to send. */
16441
16651
  hasNoLocalMediaToSend() {
@@ -16556,6 +16766,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16556
16766
  const sender = (this.transceiverController?.audioTransceivers.at(0))?.sender ?? this.peerConnection.getSenders().find((s) => s.track?.kind === "audio");
16557
16767
  if (!sender || !raw) return;
16558
16768
  try {
16769
+ this.localStreamController.setTrackOrigin(this._localAudioPipeline.outputTrack, "processed");
16559
16770
  await sender.replaceTrack(this._localAudioPipeline.outputTrack);
16560
16771
  } catch (error) {
16561
16772
  logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
@@ -16616,37 +16827,62 @@ var RTCPeerConnectionController = class extends Destroyable {
16616
16827
  for (const existingTrack of existingTracks) this.removeLocalTrack(existingTrack.id);
16617
16828
  this.addLocalTrack(track);
16618
16829
  }
16830
+ /**
16831
+ * @returns whether the constraints reached the media the leg is sending.
16832
+ *
16833
+ * With the pipeline engaged the audio sender carries the processed
16834
+ * destination track, so the sender scan would find nothing it may touch and
16835
+ * every audio constraint API would silently no-op. The constraints belong to
16836
+ * the pipeline's device source, which is the capture that sender ultimately
16837
+ * carries.
16838
+ */
16619
16839
  async updateSendersConstraints(kind, constraints) {
16620
- await this.transceiverController?.updateSendersConstraints(kind, constraints);
16840
+ if (kind === "audio" && constraints && this._localAudioPipeline) return this.applyPipelineSourceConstraints(constraints);
16841
+ return await this.transceiverController?.updateSendersConstraints(kind, constraints) ?? false;
16621
16842
  }
16622
16843
  /**
16623
- * Replace the current audio track with a new one using the given constraints.
16624
- * Used for server-pushed audio constraint changes where applyConstraints
16625
- * fails on iOS Safari. Stops the current track, acquires a new one via
16626
- * getUserMedia, and replaces the sender track.
16844
+ * Mirror of the sender path for a piped audio leg: same merge, same fallback
16845
+ * ladder, same device-capture invariant — but the swap target is the pipeline
16846
+ * input, so the sender keeps emitting the pipeline's output track and its
16847
+ * identity survives the change.
16627
16848
  */
16628
- async replaceAudioTrackWithConstraints(constraints) {
16629
- const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
16630
- if (!senders || senders.length === 0) {
16631
- logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
16632
- return;
16849
+ async applyPipelineSourceConstraints(constraints) {
16850
+ const pipeline = this._localAudioPipeline;
16851
+ const source = this.localStreamController.localAudioTracks.at(0);
16852
+ if (!pipeline || !source) {
16853
+ logger$17.debug("[RTCPeerConnectionController] No pipeline input to constrain.");
16854
+ return false;
16633
16855
  }
16634
- for (const sender of senders) {
16635
- const oldTrack = sender.track;
16636
- if (!oldTrack) continue;
16637
- const { deviceId } = oldTrack.getSettings();
16638
- const mergedConstraints = {
16639
- ...oldTrack.getConstraints(),
16640
- ...constraints,
16641
- ...deviceId ? { deviceId: { exact: deviceId } } : {}
16642
- };
16643
- const trackId = oldTrack.id;
16644
- oldTrack.stop();
16645
- this.localStreamController.removeTrack(trackId);
16646
- const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
16647
- await sender.replaceTrack(newTrack);
16856
+ if (!this.localStreamController.isDeviceCapture(source)) {
16857
+ logger$17.debug(`[RTCPeerConnectionController] Skipping audio constraints for a non-device pipeline input (origin: ${this.localStreamController.getTrackOrigin(source) ?? "unrecorded"}).`);
16858
+ return false;
16859
+ }
16860
+ const merged = {
16861
+ ...source.getConstraints(),
16862
+ ...constraints
16863
+ };
16864
+ try {
16865
+ await source.applyConstraints(merged);
16866
+ logger$17.debug("[RTCPeerConnectionController] Pipeline input constraints updated:", merged);
16867
+ return true;
16868
+ } catch (error) {
16869
+ logger$17.warn("[RTCPeerConnectionController] applyConstraints failed on the pipeline input, re-acquiring:", error);
16870
+ }
16871
+ try {
16872
+ const { stream } = await getUserMediaWithFallback({ getUserMedia: async (c) => this.getUserMedia(c) }, { audio: merged }, "audio", source.getSettings().deviceId);
16873
+ const newTrack = stream.getAudioTracks().at(0);
16874
+ if (!newTrack) {
16875
+ stream.getTracks().forEach((track) => track.stop());
16876
+ throw new Error("getUserMedia returned no audio track");
16877
+ }
16878
+ this.localStreamController.removeTrack(source.id);
16648
16879
  this.localStreamController.addTrack(newTrack);
16649
- logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
16880
+ pipeline.setInputTrack(newTrack);
16881
+ return true;
16882
+ } catch (error) {
16883
+ logger$17.warn("[RTCPeerConnectionController] Failed to re-acquire the pipeline input for constraints:", error);
16884
+ this._errors$.next(new MediaTrackError("updateSendersConstraints", "audio", error));
16885
+ return false;
16650
16886
  }
16651
16887
  }
16652
16888
  /**
@@ -16830,7 +17066,7 @@ var WebRTCVertoManager = class extends VertoManager {
16830
17066
  this._signalingStatus$ = this.createReplaySubject(1);
16831
17067
  this._screenShareStatus$ = this.createBehaviorSubject("none");
16832
17068
  this._rtcPeerConnectionsMap = /* @__PURE__ */ new Map();
16833
- this._screenShareTimeoutMs = 5e4;
17069
+ this._legErrors$ = this.createSubject();
16834
17070
  this._nodeId$ = this.createBehaviorSubject(options.nodeId ?? null);
16835
17071
  this.onError = options.onError;
16836
17072
  this.onModifyFailed = options.onModifyFailed;
@@ -16878,6 +17114,10 @@ var WebRTCVertoManager = class extends VertoManager {
16878
17114
  get selfId$() {
16879
17115
  return this._selfId$.asObservable();
16880
17116
  }
17117
+ /** Separates the media phase of call creation from the signalling phase. */
17118
+ get localMediaSettled$() {
17119
+ return this.mainPeerConnection.localMediaSettled$;
17120
+ }
16881
17121
  get localStream() {
16882
17122
  return this._rtcPeerConnectionsMap.get(this.webRtcCallSession.id)?.localStream ?? null;
16883
17123
  }
@@ -16934,20 +17174,11 @@ var WebRTCVertoManager = class extends VertoManager {
16934
17174
  const { mediaParams, callID } = event;
16935
17175
  const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
16936
17176
  const { audio, video } = mediaParams;
16937
- (async () => {
16938
- try {
16939
- if (audio && rtcPeerConnController) await rtcPeerConnController.replaceAudioTrackWithConstraints(audio);
16940
- if (video) await rtcPeerConnController?.updateSendersConstraints("video", video);
16941
- this.webRtcCallSession.emitMediaParamsUpdated({
16942
- audio,
16943
- video,
16944
- timestamp: Date.now()
16945
- });
16946
- } catch (error) {
16947
- logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
16948
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
16949
- }
16950
- })();
17177
+ if (!rtcPeerConnController) {
17178
+ logger$16.warn(`[WebRTCManager] Ignoring server-pushed media params for unknown leg ${callID}`);
17179
+ return;
17180
+ }
17181
+ this.applyServerMediaParams(rtcPeerConnController, audio, video);
16951
17182
  });
16952
17183
  this.subscribeTo(this.vertoPing$, (vertoPing) => {
16953
17184
  this.attachManager.attach(this.buildAttachableCall());
@@ -16955,6 +17186,75 @@ var WebRTCVertoManager = class extends VertoManager {
16955
17186
  });
16956
17187
  }
16957
17188
  /**
17189
+ * An auxiliary-leg failure must never destroy the call; main-leg and
17190
+ * call-level errors keep `CallFactory.isFatalError`'s classification.
17191
+ *
17192
+ * Every site holding a peer connection reports through here, so the invariant
17193
+ * is structural rather than per-call-site — which is how cloud-product#20523
17194
+ * happened, with only one of fourteen sites passing `{ fatal: false }`.
17195
+ *
17196
+ * `override` composes rather than replaces: a caller may force non-fatal for a
17197
+ * reason of its own (a `verto.info` frame is best-effort whichever leg carries
17198
+ * it), and an auxiliary leg stays non-fatal regardless.
17199
+ */
17200
+ reportLegError(error, rtcPeerConnController, override) {
17201
+ const leg = rtcPeerConnController?.propose;
17202
+ const legId = rtcPeerConnController?.id;
17203
+ const auxiliary = Boolean(rtcPeerConnController) && !rtcPeerConnController?.isMainDevice;
17204
+ this.onError?.(error, {
17205
+ ...override?.fatal === false || auxiliary ? { fatal: false } : {},
17206
+ ...leg ? { leg } : {},
17207
+ ...legId ? { legId } : {}
17208
+ });
17209
+ if (legId) this._legErrors$.next({
17210
+ legId,
17211
+ error
17212
+ });
17213
+ }
17214
+ /**
17215
+ * Errors reported for one leg, as a stream that fails with them.
17216
+ *
17217
+ * Signaling failures are reported, never thrown — so nothing that waits on a
17218
+ * leg's progress would otherwise learn of a rejected invite. Merging this in
17219
+ * lets the wait end with the reason the server gave.
17220
+ */
17221
+ legError$(legId) {
17222
+ return this._legErrors$.pipe((0, import_cjs$15.filter)((report) => report.legId === legId), (0, import_cjs$15.map)((report) => {
17223
+ throw report.error;
17224
+ }));
17225
+ }
17226
+ /**
17227
+ * Audio and video are applied independently so a failure in one cannot
17228
+ * suppress the other, and `mediaParamsUpdated` is emitted whatever happens —
17229
+ * an application should not be starved of the params by a constraint failure.
17230
+ */
17231
+ async applyServerMediaParams(rtcPeerConnController, audio, video) {
17232
+ const failures = [];
17233
+ let applied = true;
17234
+ if (audio) try {
17235
+ applied = await rtcPeerConnController.updateSendersConstraints("audio", audio) && applied;
17236
+ } catch (error) {
17237
+ applied = false;
17238
+ failures.push(toError(error));
17239
+ }
17240
+ if (video) try {
17241
+ applied = await rtcPeerConnController.updateSendersConstraints("video", video) && applied;
17242
+ } catch (error) {
17243
+ applied = false;
17244
+ failures.push(toError(error));
17245
+ }
17246
+ this.webRtcCallSession.emitMediaParamsUpdated({
17247
+ audio,
17248
+ video,
17249
+ timestamp: Date.now(),
17250
+ applied
17251
+ });
17252
+ for (const failure of failures) {
17253
+ logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", failure);
17254
+ this.reportLegError(failure, rtcPeerConnController, { fatal: false });
17255
+ }
17256
+ }
17257
+ /**
16958
17258
  * Set node_id/selfId only when the current value is null.
16959
17259
  *
16960
17260
  * During reattach, `call.joined` and `verto.answer` events can deliver
@@ -16992,7 +17292,7 @@ var WebRTCVertoManager = class extends VertoManager {
16992
17292
  if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
16993
17293
  } catch (error) {
16994
17294
  logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
16995
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17295
+ this.reportLegError(toError(error), this.mainPeerConnection);
16996
17296
  throw error;
16997
17297
  }
16998
17298
  }
@@ -17160,23 +17460,24 @@ var WebRTCVertoManager = class extends VertoManager {
17160
17460
  return response;
17161
17461
  }
17162
17462
  async executeVerto(message, optionals = {}) {
17163
- const webrtcVertoMessage = WebrtcVerto({
17463
+ const params = {
17164
17464
  callID: optionals.callID ?? this.webRtcCallSession.id,
17165
17465
  node_id: optionals.node_id ?? this._nodeId$.value ?? "",
17166
17466
  message,
17167
17467
  subscribe: optionals.subscribe
17168
- });
17468
+ };
17469
+ const webrtcVertoMessage = WebrtcVerto(params);
17169
17470
  const response = await this.webRtcCallSession.execute(webrtcVertoMessage);
17170
17471
  const nonFatal = message.method === "verto.info" ? { fatal: false } : void 0;
17171
17472
  if (response.error) {
17172
17473
  const error = new JSONRPCError(response.error.code, response.error.message, response.error.data);
17173
- this.onError?.(error, nonFatal);
17474
+ this.reportLegError(error, this._rtcPeerConnectionsMap.get(params.callID), nonFatal);
17174
17475
  return response;
17175
17476
  }
17176
17477
  const innerResult = getValueFrom(response, "result.result");
17177
17478
  if (innerResult?.error) {
17178
17479
  const error = new JSONRPCError(innerResult.error.code, innerResult.error.message, innerResult.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
  return response;
@@ -17197,7 +17498,7 @@ var WebRTCVertoManager = class extends VertoManager {
17197
17498
  }
17198
17499
  } catch (error) {
17199
17500
  logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
17200
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17501
+ this.reportLegError(toError(error), rtcPeerConnController);
17201
17502
  if (vertoMethod === "verto.modify") this.onModifyFailed?.();
17202
17503
  }
17203
17504
  }
@@ -17213,7 +17514,7 @@ var WebRTCVertoManager = class extends VertoManager {
17213
17514
  } catch (error) {
17214
17515
  logger$16.warn("[WebRTCManager] Error processing modify response:", error);
17215
17516
  const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
17216
- this.onError?.(modifyError);
17517
+ this.reportLegError(modifyError, rtcPeerConnController);
17217
17518
  }
17218
17519
  }
17219
17520
  }
@@ -17226,7 +17527,7 @@ var WebRTCVertoManager = class extends VertoManager {
17226
17527
  status,
17227
17528
  signalingError
17228
17529
  });
17229
- this.onError?.(signalingError);
17530
+ this.reportLegError(signalingError, null, { fatal: false });
17230
17531
  return;
17231
17532
  }
17232
17533
  if (rtcPeerConnController.isMainDevice) this._signalingStatus$.next(status);
@@ -17256,7 +17557,7 @@ var WebRTCVertoManager = class extends VertoManager {
17256
17557
  } else {
17257
17558
  logger$16.error("[WebRTCManager] Verto invite failed:", response);
17258
17559
  const inviteError = response.error ? new JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
17259
- this.onError?.(inviteError);
17560
+ this.reportLegError(inviteError, rtcPeerConnController);
17260
17561
  }
17261
17562
  }
17262
17563
  get RTCPeerConnectionConfig() {
@@ -17295,7 +17596,7 @@ var WebRTCVertoManager = class extends VertoManager {
17295
17596
  this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
17296
17597
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17297
17598
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
17298
- this.onError?.(error);
17599
+ this.reportLegError(error, rtcPeerConnController);
17299
17600
  });
17300
17601
  if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
17301
17602
  }
@@ -17324,7 +17625,7 @@ var WebRTCVertoManager = class extends VertoManager {
17324
17625
  await rtcPeerConnController.acceptInbound(answerOptions);
17325
17626
  } catch (error) {
17326
17627
  logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
17327
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17628
+ this.reportLegError(toError(error), rtcPeerConnController);
17328
17629
  }
17329
17630
  }
17330
17631
  }
@@ -17428,7 +17729,7 @@ var WebRTCVertoManager = class extends VertoManager {
17428
17729
  await this.attachManager.attach(this.buildAttachableCall());
17429
17730
  } catch (error) {
17430
17731
  logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
17431
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17732
+ this.reportLegError(toError(error), rtcPeerConnectionController);
17432
17733
  await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
17433
17734
  }
17434
17735
  }
@@ -17500,7 +17801,7 @@ var WebRTCVertoManager = class extends VertoManager {
17500
17801
  await this.mainPeerConnection.restoreTrackSender(deviceKind);
17501
17802
  } else {
17502
17803
  const error = new InvalidParams("No valid device to be added");
17503
- this.onError?.(error);
17804
+ this.reportLegError(error, this.mainPeerConnection);
17504
17805
  throw error;
17505
17806
  }
17506
17807
  }
@@ -17525,21 +17826,30 @@ var WebRTCVertoManager = class extends VertoManager {
17525
17826
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17526
17827
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
17527
17828
  firstPeerConnectionError ??= error;
17528
- this.onError?.(error, { fatal: false });
17829
+ this.reportLegError(error, rtcPeerConnController);
17529
17830
  });
17530
- 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$)));
17831
+ const pc = rtcPeerConnController;
17832
+ 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$)));
17531
17833
  if (isScreenShare) this._screenShareStatus$.next("started");
17532
17834
  logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
17533
17835
  return rtcPeerConnController.id;
17534
17836
  } catch (error) {
17535
17837
  logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
17536
- if (rtcPeerConnController) rtcPeerConnController.destroy();
17537
- if (isScreenShare) this._screenShareStatus$.next("none");
17838
+ if (rtcPeerConnController) {
17839
+ rtcPeerConnController.destroy();
17840
+ this._rtcPeerConnectionsMap.delete(rtcPeerConnController.id);
17841
+ this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17842
+ }
17843
+ if (isScreenShare) {
17844
+ this._screenShareStatus$.next("none");
17845
+ this._screenShareId = void 0;
17846
+ }
17538
17847
  if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
17539
17848
  if (error instanceof import_cjs$15.EmptyError) {
17540
17849
  logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
17541
17850
  return;
17542
17851
  }
17852
+ if (error instanceof import_cjs$15.TimeoutError) throw new AuxiliaryLegTimeoutError(propose, error);
17543
17853
  throw error instanceof Error ? error : new Error(String(error), { cause: error });
17544
17854
  }
17545
17855
  }
@@ -17585,7 +17895,7 @@ var WebRTCVertoManager = class extends VertoManager {
17585
17895
  await this.executeVerto(VertoBye({
17586
17896
  ...causeParams,
17587
17897
  dialogParams: this.dialogParams(rtcPeerConnController)
17588
- }));
17898
+ }), { callID: rtcPeerConnController.id });
17589
17899
  } catch (error) {
17590
17900
  logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
17591
17901
  throw error;
@@ -18977,13 +19287,13 @@ var WebRTCCall = class extends Destroyable {
18977
19287
  get bandwidthConstrained$() {
18978
19288
  return this.deferEmission(this._bandwidthConstrained$.asObservable());
18979
19289
  }
18980
- /** Observable that emits when server-pushed media params are applied. */
19290
+ /** Observable that emits when the server pushes media params. */
18981
19291
  get mediaParamsUpdated$() {
18982
19292
  return this.deferEmission(this._mediaParamsUpdated$.asObservable());
18983
19293
  }
18984
19294
  /**
18985
19295
  * @internal Emit a media params update event.
18986
- * Called by the VertoManager when server-pushed media params are applied.
19296
+ * Called by the VertoManager when the server pushes media params.
18987
19297
  */
18988
19298
  emitMediaParamsUpdated(event) {
18989
19299
  this._mediaParamsUpdated$.next(event);
@@ -19191,6 +19501,10 @@ var WebRTCCall = class extends Destroyable {
19191
19501
  get selfId$() {
19192
19502
  return this.vertoManager.selfId$;
19193
19503
  }
19504
+ /** @internal Lets call creation bound the media and signalling phases apart. */
19505
+ get localMediaSettled$() {
19506
+ return this.vertoManager.localMediaSettled$;
19507
+ }
19194
19508
  /** Local participant's member ID, or `null` if not joined. */
19195
19509
  get selfId() {
19196
19510
  return this.vertoManager.selfId;
@@ -19586,6 +19900,9 @@ var WebRTCCall = class extends Destroyable {
19586
19900
  /**
19587
19901
  * Infers the semantic error category from a raw Error thrown by VertoManager
19588
19902
  * or an RTCPeerConnection layer.
19903
+ *
19904
+ * Pure function — exported for unit testing.
19905
+ * @internal
19589
19906
  */
19590
19907
  function inferCallErrorKind(error) {
19591
19908
  if (error instanceof RPCTimeoutError) return "timeout";
@@ -19604,7 +19921,14 @@ const RECOVERABLE_RPC_CODES = new Set([
19604
19921
  RPC_ERROR_AUTHENTICATION_FAILED,
19605
19922
  RPC_ERROR_INVALID_PARAMS
19606
19923
  ]);
19607
- /** Determines whether an error should be fatal (destroy the call). */
19924
+ /**
19925
+ * A *fallback*: callers knowing which leg failed pass an explicit `fatal` and
19926
+ * never reach here, so the default-fatal branch only sees call- and main-leg
19927
+ * errors. Auxiliary legs go through `WebRTCVertoManager.reportLegError`.
19928
+ *
19929
+ * Pure function — exported for unit testing.
19930
+ * @internal
19931
+ */
19608
19932
  function isFatalError(error) {
19609
19933
  if (error instanceof VertoPongError) return false;
19610
19934
  if (error instanceof MediaTrackError) return false;
@@ -19639,7 +19963,9 @@ var CallFactory = class {
19639
19963
  kind: inferCallErrorKind(error),
19640
19964
  fatal: options$1?.fatal ?? isFatalError(error),
19641
19965
  error,
19642
- callId: callInstance.id
19966
+ callId: callInstance.id,
19967
+ ...options$1?.leg ? { leg: options$1.leg } : {},
19968
+ ...options$1?.legId ? { legId: options$1.legId } : {}
19643
19969
  };
19644
19970
  callInstance.emitError(callError);
19645
19971
  },
@@ -20111,6 +20437,30 @@ const logger$9 = getLogger();
20111
20437
  function shouldAbortDial(callError) {
20112
20438
  return callError.fatal || !(callError.error instanceof MediaAccessError);
20113
20439
  }
20440
+ /**
20441
+ * Wait for a dialed call to be ready, or for the failure that stops it.
20442
+ *
20443
+ * Local media acquisition is deliberately unbounded: a permission prompt or a
20444
+ * device picker is human time, and `getUserMedia` cannot be cancelled anyway.
20445
+ * The clock starts only once acquisition settles, so a slow human never spends
20446
+ * the server's budget.
20447
+ *
20448
+ * `merge` rather than `race`, because the two legs settle asymmetrically. A
20449
+ * fatal acquisition failure reports the error and then destroys the call in the
20450
+ * same synchronous step; `errors$` defers delivery by a microtask while
20451
+ * `localMediaSettled$` completes immediately. Under `race` that bare completion
20452
+ * ended the wait first and `dial()` rejected with an RxJS `EmptyError`, burying
20453
+ * the `NotAllowedError` applications are told to inspect. Under `merge` the
20454
+ * completed leg is simply spent, and the queued error — enqueued before the
20455
+ * completion, so delivered before it — arrives to reject the wait. A dial
20456
+ * abandoned with no error at all still ends both legs, and the resulting
20457
+ * `EmptyError` remains the benign-cancel signal.
20458
+ *
20459
+ * Exported for unit testing.
20460
+ */
20461
+ async function awaitDialReady(session, signalingTimeoutMs) {
20462
+ 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)))));
20463
+ }
20114
20464
  const getAddressSearchURI = (options) => {
20115
20465
  const to = options.to?.split("?")[0];
20116
20466
  const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
@@ -20127,7 +20477,6 @@ var ClientSessionManager = class extends Destroyable {
20127
20477
  this.authorizationStateKey = authorizationStateKey;
20128
20478
  this.attachManager = attachManager;
20129
20479
  this.dpopManager = dpopManager;
20130
- this.callCreateTimeout = 6e3;
20131
20480
  this.agent = `signalwire-js/4.0.0`;
20132
20481
  this.eventAcks = true;
20133
20482
  this.authorizationState$ = this.createReplaySubject(1);
@@ -20555,7 +20904,7 @@ var ClientSessionManager = class extends Destroyable {
20555
20904
  to: destinationURI,
20556
20905
  ...options
20557
20906
  });
20558
- 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)))));
20907
+ await awaitDialReady(callSession, DEFAULT_CALL_SIGNALING_TIMEOUT_MS);
20559
20908
  this._calls$.next({
20560
20909
  [`${callSession.id}`]: callSession,
20561
20910
  ...this._calls$.value
@@ -22733,6 +23082,11 @@ var SignalWire = class extends Destroyable {
22733
23082
  * Returns a {@link Call} in `'ringing'` state. Subscribe to {@link Call.status$}
22734
23083
  * to track progression through `'connected'` → `'disconnected'`.
22735
23084
  *
23085
+ * Local media acquisition is deliberately unbounded: an unanswered permission
23086
+ * prompt leaves this promise pending indefinitely, so apply your own bound if
23087
+ * your UI needs one. The 12 s signaling budget starts only once acquisition
23088
+ * settles.
23089
+ *
22736
23090
  * @param destination - Address URI string (e.g. `'/public/my-room'`) or {@link Address} instance.
22737
23091
  * @param options - Media and dial options (audio/video, device constraints). Overrides defaults.
22738
23092
  * @returns The created {@link Call} instance.
@@ -23212,5 +23566,5 @@ emitReadyEvent();
23212
23566
  if (typeof process === "undefined") globalThis.process = { env: { NODE_ENV: "production" } };
23213
23567
 
23214
23568
  //#endregion
23215
- export { Address, CallCreateError, CallNotReadyError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaAccessError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, ParticipantNotReadyError, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
23569
+ export { Address, AuxiliaryLegTimeoutError, CallCreateError, CallNotReadyError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaAccessError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, ParticipantNotReadyError, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
23216
23570
  //# sourceMappingURL=browser.mjs.map