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

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 });
@@ -9410,6 +9441,22 @@ var MediaAccessError = class extends Error {
9410
9441
  return isMediaAccessDenial(this.originalError);
9411
9442
  }
9412
9443
  };
9444
+ /**
9445
+ * Thrown by `startScreenShare()` when the call is already sharing a screen.
9446
+ *
9447
+ * A call carries at most one screen share. Accepting a second one would
9448
+ * overwrite the only reference the SDK holds to the first, leaving it
9449
+ * capturing and sending with no way to stop it — so the second request is
9450
+ * rejected and the live share is left untouched. Call `stopScreenShare()`
9451
+ * first to replace it.
9452
+ */
9453
+ var ScreenShareAlreadyActiveError = class extends Error {
9454
+ constructor(screenShareId, options) {
9455
+ super(`A screen share is already active on this call (${screenShareId}). Call stopScreenShare() before starting another one.`, options);
9456
+ this.screenShareId = screenShareId;
9457
+ this.name = "ScreenShareAlreadyActiveError";
9458
+ }
9459
+ };
9413
9460
  var DPoPInitError = class extends Error {
9414
9461
  constructor(originalError, message = "Failed to initialize DPoP key pair") {
9415
9462
  super(message, { cause: originalError instanceof Error ? originalError : void 0 });
@@ -9650,9 +9697,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
9650
9697
  defaultLogger$1 = new Logger();
9651
9698
  defaultLogger$1.getLogger = function getLogger$1(name) {
9652
9699
  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;
9700
+ var logger$34 = _loggersByName[name];
9701
+ if (!logger$34) logger$34 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
9702
+ return logger$34;
9656
9703
  };
9657
9704
  var _log = typeof window !== undefinedType ? window.log : void 0;
9658
9705
  defaultLogger$1.noConflict = function() {
@@ -9688,8 +9735,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
9688
9735
  defaultLogger.setLevel(defaultLoggerLevel);
9689
9736
  let userLogger = null;
9690
9737
  /** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
9691
- const setLogger = (logger$33) => {
9692
- userLogger = logger$33;
9738
+ const setLogger = (logger$34) => {
9739
+ userLogger = logger$34;
9693
9740
  };
9694
9741
  let debugOptions = {};
9695
9742
  /** Configure debug options (e.g., `{ logWsTraffic: true }`). */
@@ -9733,8 +9780,8 @@ const wsTraffic = (options) => {
9733
9780
  loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
9734
9781
  };
9735
9782
  const getLogger = () => {
9736
- const logger$33 = getLoggerInstance();
9737
- return new Proxy(logger$33, { get(_target, prop, _receiver) {
9783
+ const logger$34 = getLoggerInstance();
9784
+ return new Proxy(logger$34, { get(_target, prop, _receiver) {
9738
9785
  if (prop === "wsTraffic") return wsTraffic;
9739
9786
  const instance = getLoggerInstance();
9740
9787
  const value = Reflect.get(instance, prop);
@@ -9786,7 +9833,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
9786
9833
 
9787
9834
  //#endregion
9788
9835
  //#region src/controllers/HTTPRequestController.ts
9789
- const logger$32 = getLogger();
9836
+ const logger$33 = getLogger();
9790
9837
  const GET_PARAMS = {
9791
9838
  method: "GET",
9792
9839
  headers: { Accept: "application/json" }
@@ -9850,7 +9897,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9850
9897
  this._responses$.next(response);
9851
9898
  return response;
9852
9899
  } catch (error) {
9853
- logger$32.error("[HTTPRequestController] Request error:", error);
9900
+ logger$33.error("[HTTPRequestController] Request error:", error);
9854
9901
  this._status$.next("error");
9855
9902
  const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
9856
9903
  this._errors$.next(err);
@@ -9877,7 +9924,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9877
9924
  const url = this.buildURL(request.url);
9878
9925
  const headers = this.buildHeaders(request.headers);
9879
9926
  const timeout$5 = request.timeout ?? this.requestTimeout;
9880
- logger$32.debug("[HTTPRequestController] Executing request:", {
9927
+ logger$33.debug("[HTTPRequestController] Executing request:", {
9881
9928
  method: request.method,
9882
9929
  url,
9883
9930
  headers: Object.keys(headers).reduce((acc, key) => {
@@ -9897,7 +9944,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9897
9944
  });
9898
9945
  clearTimeout(timeoutId);
9899
9946
  const httpResponse = await this.convertResponse(response);
9900
- logger$32.debug("[HTTPRequestController] Response received:", {
9947
+ logger$33.debug("[HTTPRequestController] Response received:", {
9901
9948
  status: response.status,
9902
9949
  statusText: response.statusText,
9903
9950
  headers: [...response.headers.entries()],
@@ -9907,7 +9954,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9907
9954
  } catch (error) {
9908
9955
  clearTimeout(timeoutId);
9909
9956
  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);
9957
+ logger$33.error("[HTTPRequestController] Request failed:", error);
9911
9958
  throw error;
9912
9959
  }
9913
9960
  }
@@ -9921,8 +9968,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9921
9968
  const credential = this.getCredential();
9922
9969
  if (credential.token) {
9923
9970
  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");
9971
+ logger$33.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
9972
+ } else logger$33.warn("[HTTPRequestController] No credentials available for authentication");
9926
9973
  return headers;
9927
9974
  }
9928
9975
  /**
@@ -10070,7 +10117,7 @@ function fromMsToSec(milliseconds) {
10070
10117
 
10071
10118
  //#endregion
10072
10119
  //#region src/containers/PreferencesContainer.ts
10073
- const logger$31 = getLogger();
10120
+ const logger$32 = getLogger();
10074
10121
  var PreferencesContainer = class PreferencesContainer {
10075
10122
  static get instance() {
10076
10123
  this._instance ??= new PreferencesContainer();
@@ -10732,7 +10779,7 @@ var ClientPreferences = class {
10732
10779
  if (!this._storage) return;
10733
10780
  const data = collectStoredPreferences();
10734
10781
  this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
10735
- logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
10782
+ logger$32.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
10736
10783
  });
10737
10784
  }
10738
10785
  /** Loads preferences from storage and applies them to the container. */
@@ -10741,7 +10788,7 @@ var ClientPreferences = class {
10741
10788
  this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
10742
10789
  if (stored) applyStoredPreferences(stored);
10743
10790
  }).catch((error) => {
10744
- logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
10791
+ logger$32.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
10745
10792
  });
10746
10793
  }
10747
10794
  };
@@ -10763,7 +10810,7 @@ function toError(value) {
10763
10810
  //#endregion
10764
10811
  //#region src/controllers/NavigatorDeviceController.ts
10765
10812
  var import_cjs$29 = require_cjs();
10766
- const logger$30 = getLogger();
10813
+ const logger$31 = getLogger();
10767
10814
  /** Maps a device kind to its storage key. */
10768
10815
  const DEVICE_STORAGE_KEYS = {
10769
10816
  audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
@@ -10785,7 +10832,7 @@ var NavigatorDeviceController = class extends Destroyable {
10785
10832
  super();
10786
10833
  this.webRTCApiProvider = webRTCApiProvider;
10787
10834
  this.deviceChangeHandler = () => {
10788
- logger$30.debug("[DeviceController] Device change detected");
10835
+ logger$31.debug("[DeviceController] Device change detected");
10789
10836
  this.enumerateDevices();
10790
10837
  };
10791
10838
  this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
@@ -10850,13 +10897,13 @@ var NavigatorDeviceController = class extends Destroyable {
10850
10897
  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
10898
  }
10852
10899
  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))));
10900
+ 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
10901
  }
10855
10902
  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))));
10903
+ 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
10904
  }
10858
10905
  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))));
10906
+ 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
10907
  }
10861
10908
  get selectedAudioInputDevice() {
10862
10909
  if (this._audioInputDisabled$.value) return null;
@@ -10931,7 +10978,7 @@ var NavigatorDeviceController = class extends Destroyable {
10931
10978
  if (device) this.persistDeviceSelection("audioinput", device);
10932
10979
  }
10933
10980
  selectVideoInputDevice(device) {
10934
- logger$30.debug("[DeviceController] Setting selected video input device:", device);
10981
+ logger$31.debug("[DeviceController] Setting selected video input device:", device);
10935
10982
  if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
10936
10983
  const previous = this._selectedDevicesState$.value.videoinput;
10937
10984
  if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
@@ -10988,7 +11035,7 @@ var NavigatorDeviceController = class extends Destroyable {
10988
11035
  }
10989
11036
  const fromHistory = this._deviceHistory.findInHistory(kind, devices);
10990
11037
  if (fromHistory) {
10991
- logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
11038
+ logger$31.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
10992
11039
  this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
10993
11040
  return fromHistory;
10994
11041
  }
@@ -11041,7 +11088,7 @@ var NavigatorDeviceController = class extends Destroyable {
11041
11088
  try {
11042
11089
  await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
11043
11090
  } catch (error) {
11044
- logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
11091
+ logger$31.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
11045
11092
  }
11046
11093
  }
11047
11094
  async loadPersistedDevices() {
@@ -11057,7 +11104,7 @@ var NavigatorDeviceController = class extends Destroyable {
11057
11104
  [kind]: stored
11058
11105
  };
11059
11106
  } catch (error) {
11060
- logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
11107
+ logger$31.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
11061
11108
  }
11062
11109
  }
11063
11110
  /** Clears device history, persisted selections, and re-enumerates devices. */
@@ -11075,7 +11122,7 @@ var NavigatorDeviceController = class extends Destroyable {
11075
11122
  this.disableDeviceMonitoring();
11076
11123
  this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
11077
11124
  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");
11125
+ logger$31.debug("[DeviceController] Polling devices due to interval");
11079
11126
  this.enumerateDevices();
11080
11127
  });
11081
11128
  this.enumerateDevices();
@@ -11101,13 +11148,13 @@ var NavigatorDeviceController = class extends Destroyable {
11101
11148
  videoinput: []
11102
11149
  });
11103
11150
  this._devicesState$.next(devicesByKind);
11104
- logger$30.debug("[DeviceController] Devices enumerated:", {
11151
+ logger$31.debug("[DeviceController] Devices enumerated:", {
11105
11152
  audioInputs: devicesByKind.audioinput.length,
11106
11153
  audioOutputs: devicesByKind.audiooutput.length,
11107
11154
  videoInputs: devicesByKind.videoinput.length
11108
11155
  });
11109
11156
  } catch (error) {
11110
- logger$30.error("[DeviceController] Failed to enumerate devices:", error);
11157
+ logger$31.error("[DeviceController] Failed to enumerate devices:", error);
11111
11158
  this._errors$.next(toError(error));
11112
11159
  }
11113
11160
  }
@@ -11123,7 +11170,7 @@ var NavigatorDeviceController = class extends Destroyable {
11123
11170
  stream.getTracks().forEach((t) => t.stop());
11124
11171
  return capabilities;
11125
11172
  } catch (error) {
11126
- logger$30.error("[DeviceController] Failed to get device capabilities:", error);
11173
+ logger$31.error("[DeviceController] Failed to get device capabilities:", error);
11127
11174
  this._errors$.next(toError(error));
11128
11175
  throw error;
11129
11176
  }
@@ -11374,7 +11421,7 @@ var DependencyContainer = class {
11374
11421
 
11375
11422
  //#endregion
11376
11423
  //#region src/controllers/CryptoController.ts
11377
- const logger$29 = getLogger();
11424
+ const logger$30 = getLogger();
11378
11425
  const DPOP_DB_NAME = "sw-dpop";
11379
11426
  const DPOP_DB_VERSION = 1;
11380
11427
  const DPOP_STORE_NAME = "keys";
@@ -11433,7 +11480,7 @@ async function loadKeyPairFromDB() {
11433
11480
  tx.oncomplete = () => db.close();
11434
11481
  });
11435
11482
  } catch (error) {
11436
- logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
11483
+ logger$30.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
11437
11484
  return null;
11438
11485
  }
11439
11486
  }
@@ -11453,7 +11500,7 @@ async function saveKeyPairToDB(keyPair) {
11453
11500
  };
11454
11501
  });
11455
11502
  } catch (error) {
11456
- logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
11503
+ logger$30.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
11457
11504
  }
11458
11505
  }
11459
11506
  async function deleteKeyPairFromDB() {
@@ -11472,7 +11519,7 @@ async function deleteKeyPairFromDB() {
11472
11519
  };
11473
11520
  });
11474
11521
  } catch (error) {
11475
- logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
11522
+ logger$30.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
11476
11523
  }
11477
11524
  }
11478
11525
  /**
@@ -11532,13 +11579,13 @@ var CryptoController = class {
11532
11579
  this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
11533
11580
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
11534
11581
  this._initialized = true;
11535
- logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
11582
+ logger$30.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
11536
11583
  return this._fingerprint;
11537
11584
  } catch (error) {
11538
- logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
11585
+ logger$30.warn("[DPoP] Stored key pair unusable, generating new one:", error);
11539
11586
  await deleteKeyPairFromDB();
11540
11587
  }
11541
- logger$29.debug("[DPoP] Generating RSA key pair");
11588
+ logger$30.debug("[DPoP] Generating RSA key pair");
11542
11589
  this._keyPair = await crypto.subtle.generateKey({
11543
11590
  name: "RSASSA-PKCS1-v1_5",
11544
11591
  modulusLength: 2048,
@@ -11553,7 +11600,7 @@ var CryptoController = class {
11553
11600
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
11554
11601
  this._initialized = true;
11555
11602
  await saveKeyPairToDB(this._keyPair);
11556
- logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
11603
+ logger$30.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
11557
11604
  return this._fingerprint;
11558
11605
  }
11559
11606
  /**
@@ -11619,7 +11666,7 @@ var CryptoController = class {
11619
11666
  this._fingerprint = null;
11620
11667
  this._initialized = false;
11621
11668
  deleteKeyPairFromDB();
11622
- logger$29.debug("[DPoP] Controller destroyed");
11669
+ logger$30.debug("[DPoP] Controller destroyed");
11623
11670
  }
11624
11671
  get publicJwk() {
11625
11672
  if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
@@ -11643,7 +11690,7 @@ var CryptoController = class {
11643
11690
  //#endregion
11644
11691
  //#region src/controllers/NetworkMonitor.ts
11645
11692
  var import_cjs$28 = require_cjs();
11646
- const logger$28 = getLogger();
11693
+ const logger$29 = getLogger();
11647
11694
  /**
11648
11695
  * Safely check whether we are running in a browser environment
11649
11696
  * with `window` and the relevant event targets.
@@ -11700,7 +11747,7 @@ var NetworkMonitor = class extends Destroyable {
11700
11747
  }
11701
11748
  attachListeners() {
11702
11749
  if (!hasBrowserNetworkEvents()) {
11703
- logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
11750
+ logger$29.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
11704
11751
  return;
11705
11752
  }
11706
11753
  window.addEventListener("online", this._onOnline);
@@ -11708,7 +11755,7 @@ var NetworkMonitor = class extends Destroyable {
11708
11755
  const connection = getNetworkConnection();
11709
11756
  if (connection) connection.addEventListener("change", this._onConnectionChange);
11710
11757
  this._listenersAttached = true;
11711
- logger$28.debug("NetworkMonitor: event listeners attached");
11758
+ logger$29.debug("NetworkMonitor: event listeners attached");
11712
11759
  }
11713
11760
  removeListeners() {
11714
11761
  if (!this._listenersAttached) return;
@@ -11719,10 +11766,10 @@ var NetworkMonitor = class extends Destroyable {
11719
11766
  if (connection) connection.removeEventListener("change", this._onConnectionChange);
11720
11767
  }
11721
11768
  this._listenersAttached = false;
11722
- logger$28.debug("NetworkMonitor: event listeners removed");
11769
+ logger$29.debug("NetworkMonitor: event listeners removed");
11723
11770
  }
11724
11771
  handleOnline() {
11725
- logger$28.info("NetworkMonitor: browser went online");
11772
+ logger$29.info("NetworkMonitor: browser went online");
11726
11773
  this._isOnline$.next(true);
11727
11774
  this._networkChange$.next({
11728
11775
  type: "online",
@@ -11731,7 +11778,7 @@ var NetworkMonitor = class extends Destroyable {
11731
11778
  });
11732
11779
  }
11733
11780
  handleOffline() {
11734
- logger$28.info("NetworkMonitor: browser went offline");
11781
+ logger$29.info("NetworkMonitor: browser went offline");
11735
11782
  this._isOnline$.next(false);
11736
11783
  this._networkChange$.next({
11737
11784
  type: "offline",
@@ -11740,7 +11787,7 @@ var NetworkMonitor = class extends Destroyable {
11740
11787
  }
11741
11788
  handleConnectionChange() {
11742
11789
  const networkType = getNetworkType();
11743
- logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
11790
+ logger$29.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
11744
11791
  this._networkChange$.next({
11745
11792
  type: "connection_change",
11746
11793
  timestamp: Date.now(),
@@ -11856,7 +11903,7 @@ function getNavigatorMediaDevices() {
11856
11903
  //#endregion
11857
11904
  //#region src/controllers/PreflightRunner.ts
11858
11905
  var import_cjs$27 = require_cjs();
11859
- const logger$27 = getLogger();
11906
+ const logger$28 = getLogger();
11860
11907
  const DEFAULT_MEDIA_TEST_DURATION_S = 10;
11861
11908
  const ICE_GATHERING_TIMEOUT_MS = 1e4;
11862
11909
  const SIGNALING_RTT_TIMEOUT_MS = 5e3;
@@ -11905,7 +11952,7 @@ var PreflightRunner = class extends Destroyable {
11905
11952
  if (!this._options.skipMediaTest) try {
11906
11953
  bandwidth = await this.testMediaBandwidth(destination);
11907
11954
  } catch (error) {
11908
- logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
11955
+ logger$28.warn("[PreflightRunner] Media bandwidth test failed:", error);
11909
11956
  warnings.push("Media bandwidth test failed");
11910
11957
  }
11911
11958
  return {
@@ -11917,7 +11964,7 @@ var PreflightRunner = class extends Destroyable {
11917
11964
  warnings
11918
11965
  };
11919
11966
  } catch (error) {
11920
- logger$27.error("[PreflightRunner] Preflight test failed:", error);
11967
+ logger$28.error("[PreflightRunner] Preflight test failed:", error);
11921
11968
  throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
11922
11969
  } finally {
11923
11970
  this.destroy();
@@ -11948,7 +11995,7 @@ var PreflightRunner = class extends Destroyable {
11948
11995
  if (track.kind === "video" && track.readyState === "live") videoWorking = true;
11949
11996
  }
11950
11997
  } catch (error) {
11951
- logger$27.warn("[PreflightRunner] Device test failed:", error);
11998
+ logger$28.warn("[PreflightRunner] Device test failed:", error);
11952
11999
  } finally {
11953
12000
  if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
11954
12001
  }
@@ -12006,7 +12053,7 @@ var PreflightRunner = class extends Destroyable {
12006
12053
  rttMs
12007
12054
  };
12008
12055
  } catch (error) {
12009
- logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
12056
+ logger$28.warn("[PreflightRunner] ICE connectivity test failed:", error);
12010
12057
  return {
12011
12058
  type: "failed",
12012
12059
  turnReachable: false,
@@ -12054,7 +12101,7 @@ var PreflightRunner = class extends Destroyable {
12054
12101
  //#endregion
12055
12102
  //#region src/controllers/VisibilityController.ts
12056
12103
  var import_cjs$26 = require_cjs();
12057
- const logger$26 = getLogger();
12104
+ const logger$27 = getLogger();
12058
12105
  /**
12059
12106
  * Checks whether the document visibility API is available.
12060
12107
  */
@@ -12091,8 +12138,8 @@ var VisibilityController = class extends Destroyable {
12091
12138
  this._boundHandler = this._handleVisibilityChange.bind(this);
12092
12139
  if (this._hasVisibilityApi) {
12093
12140
  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");
12141
+ logger$27.debug("VisibilityController: listening for visibilitychange events");
12142
+ } else logger$27.debug("VisibilityController: document visibility API not available, defaulting to visible");
12096
12143
  }
12097
12144
  /**
12098
12145
  * Observable of the current visibility state.
@@ -12117,7 +12164,7 @@ var VisibilityController = class extends Destroyable {
12117
12164
  destroy() {
12118
12165
  if (this._hasVisibilityApi) {
12119
12166
  document.removeEventListener("visibilitychange", this._boundHandler);
12120
- logger$26.debug("VisibilityController: removed visibilitychange listener");
12167
+ logger$27.debug("VisibilityController: removed visibilitychange listener");
12121
12168
  }
12122
12169
  super.destroy();
12123
12170
  }
@@ -12135,7 +12182,7 @@ var VisibilityController = class extends Destroyable {
12135
12182
  timestamp: Date.now()
12136
12183
  };
12137
12184
  this._visibilityChange$.next(changeEvent);
12138
- logger$26.debug("VisibilityController: visibility changed", {
12185
+ logger$27.debug("VisibilityController: visibility changed", {
12139
12186
  from: previousState,
12140
12187
  to: newState
12141
12188
  });
@@ -12417,7 +12464,7 @@ function isRequesterValidationError(error) {
12417
12464
 
12418
12465
  //#endregion
12419
12466
  //#region src/managers/AttachManager.ts
12420
- const logger$25 = getLogger();
12467
+ const logger$26 = getLogger();
12421
12468
  var AttachManager = class {
12422
12469
  constructor(storage, deviceController, reconnectCallsTimeout, attachKey, credentialRecovered) {
12423
12470
  this.storage = storage;
@@ -12439,7 +12486,7 @@ var AttachManager = class {
12439
12486
  try {
12440
12487
  return await this.storage.getItem(this.attachKey) ?? {};
12441
12488
  } catch (error) {
12442
- logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
12489
+ logger$26.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
12443
12490
  return {};
12444
12491
  }
12445
12492
  }
@@ -12447,7 +12494,7 @@ var AttachManager = class {
12447
12494
  try {
12448
12495
  await this.storage.setItem(this.attachKey, attached);
12449
12496
  } catch (error) {
12450
- logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
12497
+ logger$26.warn("[AttachManager] Failed to write attached calls to storage", error);
12451
12498
  }
12452
12499
  }
12453
12500
  /**
@@ -12466,7 +12513,7 @@ var AttachManager = class {
12466
12513
  }
12467
12514
  async attach(call) {
12468
12515
  if (!call.to) {
12469
- logger$25.warn("[AttachManager] Skip attach for calls with no destination");
12516
+ logger$26.warn("[AttachManager] Skip attach for calls with no destination");
12470
12517
  return;
12471
12518
  }
12472
12519
  const destination = call.to;
@@ -12526,25 +12573,25 @@ var AttachManager = class {
12526
12573
  callId,
12527
12574
  ...options
12528
12575
  });
12529
- logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
12576
+ logger$26.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
12530
12577
  succeeded = true;
12531
12578
  break;
12532
12579
  } catch (error) {
12533
- logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
12580
+ logger$26.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
12534
12581
  if (isRequesterValidationError(error)) {
12535
12582
  refusedOnCredentials = true;
12536
- logger$25.warn(`[AttachManager] Reattach of ${callId} was refused on credentials; not retrying.`);
12583
+ logger$26.warn(`[AttachManager] Reattach of ${callId} was refused on credentials; not retrying.`);
12537
12584
  break;
12538
12585
  }
12539
12586
  if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
12540
12587
  }
12541
12588
  if (!succeeded) if (this.credentialRecovered() && !refusedOnCredentials) {
12542
- logger$25.warn(`[AttachManager] Reattach of ${callId} was denied after a verified reauthentication, removing reference`);
12589
+ logger$26.warn(`[AttachManager] Reattach of ${callId} was denied after a verified reauthentication, removing reference`);
12543
12590
  await this.detach({
12544
12591
  id: callId,
12545
12592
  mediaDirections: attachment.mediaDirections
12546
12593
  });
12547
- } else logger$25.warn(`[AttachManager] Reattach failed for call ${callId}; keeping the reference (credential refused or never proven good)`);
12594
+ } else logger$26.warn(`[AttachManager] Reattach failed for call ${callId}; keeping the reference (credential refused or never proven good)`);
12548
12595
  }
12549
12596
  }
12550
12597
  /**
@@ -12645,12 +12692,12 @@ var require_race = /* @__PURE__ */ __commonJSMin(((exports) => {
12645
12692
  exports.race = void 0;
12646
12693
  var argsOrArgArray_1 = require_argsOrArgArray();
12647
12694
  var raceWith_1$1 = require_raceWith();
12648
- function race$4() {
12695
+ function race$3() {
12649
12696
  var args = [];
12650
12697
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
12651
12698
  return raceWith_1$1.raceWith.apply(void 0, __spreadArray([], __read(argsOrArgArray_1.argsOrArgArray(args))));
12652
12699
  }
12653
- exports.race = race$4;
12700
+ exports.race = race$3;
12654
12701
  }));
12655
12702
 
12656
12703
  //#endregion
@@ -13710,7 +13757,7 @@ function toggleHandraiseMethod(is) {
13710
13757
 
13711
13758
  //#endregion
13712
13759
  //#region src/core/entities/Participant.ts
13713
- const logger$24 = getLogger();
13760
+ const logger$25 = getLogger();
13714
13761
  const initialState = {};
13715
13762
  /**
13716
13763
  * Represents a participant in a call.
@@ -14182,8 +14229,16 @@ var SelfParticipant = class extends Participant {
14182
14229
  /**
14183
14230
  * Starts sharing the local screen.
14184
14231
  *
14232
+ * A call carries at most one screen share. Read `screenShareStatus` before
14233
+ * calling and treat `'starting'`/`'stopping'` as busy.
14234
+ *
14185
14235
  * The call is unaffected when acquisition fails.
14186
14236
  *
14237
+ * Known issue: stopping the share before its leg connects can leave this
14238
+ * promise pending until the call ends, so do not gate UI on it.
14239
+ *
14240
+ * @throws {ScreenShareAlreadyActiveError} When this call is already
14241
+ * sharing a screen. Call {@link stopScreenShare} before starting another.
14187
14242
  * @throws The raw `getDisplayMedia` error. A dismissed picker or a
14188
14243
  * permission denial rejects with a `NotAllowedError` `DOMException` —
14189
14244
  * inspect `error.name` to tell benign cancels apart from real failures.
@@ -14192,7 +14247,7 @@ var SelfParticipant = class extends Participant {
14192
14247
  try {
14193
14248
  await this.vertoManager.addScreenMedia();
14194
14249
  } catch (error) {
14195
- logger$24.error("[Participant.startScreenShare] Screen share error:", error);
14250
+ logger$25.error("[Participant.startScreenShare] Screen share error:", error);
14196
14251
  throw error;
14197
14252
  }
14198
14253
  }
@@ -14213,14 +14268,18 @@ var SelfParticipant = class extends Participant {
14213
14268
  *
14214
14269
  * The call is unaffected when acquisition fails.
14215
14270
  *
14271
+ * Known issue: removing the device before its leg connects can leave this
14272
+ * promise pending until the call ends, so do not gate UI on it.
14273
+ *
14216
14274
  * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
14217
- * permission denial) — inspect `error.name` to decide how to react.
14275
+ * permission denial) — inspect `error.name` to decide how to react — or
14276
+ * `AuxiliaryLegTimeoutError` if the leg does not connect in time.
14218
14277
  */
14219
14278
  async addAdditionalDevice(options) {
14220
14279
  try {
14221
14280
  await this.vertoManager.addInputDevice(options);
14222
14281
  } catch (error) {
14223
- logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
14282
+ logger$25.error("[Participant.addAdditionalDevice] Additional device error:", error);
14224
14283
  throw error;
14225
14284
  }
14226
14285
  }
@@ -14283,7 +14342,7 @@ var SelfParticipant = class extends Participant {
14283
14342
  */
14284
14343
  exitStudioModeIfActive() {
14285
14344
  if (this._studioAudio$.value) {
14286
- logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
14345
+ logger$25.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
14287
14346
  this._studioAudio$.next(false);
14288
14347
  }
14289
14348
  }
@@ -14307,7 +14366,7 @@ var SelfParticipant = class extends Participant {
14307
14366
  try {
14308
14367
  await super.mute();
14309
14368
  } catch (error) {
14310
- logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
14369
+ logger$25.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
14311
14370
  } finally {
14312
14371
  this.vertoManager.muteMainAudioInputDevice();
14313
14372
  }
@@ -14317,7 +14376,7 @@ var SelfParticipant = class extends Participant {
14317
14376
  try {
14318
14377
  await super.unmute();
14319
14378
  } catch (error) {
14320
- logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
14379
+ logger$25.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
14321
14380
  } finally {
14322
14381
  await this.vertoManager.unmuteMainAudioInputDevice();
14323
14382
  }
@@ -14327,7 +14386,7 @@ var SelfParticipant = class extends Participant {
14327
14386
  try {
14328
14387
  await super.muteVideo();
14329
14388
  } catch (error) {
14330
- logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
14389
+ logger$25.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
14331
14390
  } finally {
14332
14391
  this.vertoManager.muteMainVideoInputDevice();
14333
14392
  }
@@ -14337,7 +14396,7 @@ var SelfParticipant = class extends Participant {
14337
14396
  try {
14338
14397
  await super.unmuteVideo();
14339
14398
  } catch (error) {
14340
- logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
14399
+ logger$25.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
14341
14400
  } finally {
14342
14401
  await this.vertoManager.unmuteMainVideoInputDevice();
14343
14402
  }
@@ -14542,7 +14601,7 @@ function filterAs(predicate, resultPath) {
14542
14601
  //#endregion
14543
14602
  //#region src/operators/throwOnRPCError.ts
14544
14603
  var import_cjs$21 = require_cjs();
14545
- const logger$23 = getLogger();
14604
+ const logger$24 = getLogger();
14546
14605
  /**
14547
14606
  * RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
14548
14607
  * Passes successful responses through unchanged.
@@ -14550,14 +14609,14 @@ const logger$23 = getLogger();
14550
14609
  function throwOnRPCError() {
14551
14610
  return (0, import_cjs$21.map)((response) => {
14552
14611
  if (response.error) {
14553
- logger$23.error("[throwOnRPCError] RPC error response:", {
14612
+ logger$24.error("[throwOnRPCError] RPC error response:", {
14554
14613
  code: response.error.code,
14555
14614
  message: response.error.message,
14556
14615
  data: response.error.data
14557
14616
  });
14558
14617
  throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
14559
14618
  }
14560
- logger$23.debug("[throwOnRPCError] RPC successful response:", response);
14619
+ logger$24.debug("[throwOnRPCError] RPC successful response:", response);
14561
14620
  return response;
14562
14621
  });
14563
14622
  }
@@ -14565,7 +14624,7 @@ function throwOnRPCError() {
14565
14624
  //#endregion
14566
14625
  //#region src/managers/CallEventsManager.ts
14567
14626
  var import_cjs$20 = require_cjs();
14568
- const logger$22 = getLogger();
14627
+ const logger$23 = getLogger();
14569
14628
  const initialSessionState = {};
14570
14629
  /** @internal */
14571
14630
  var CallEventsManager = class extends Destroyable {
@@ -14669,7 +14728,7 @@ var CallEventsManager = class extends Destroyable {
14669
14728
  }
14670
14729
  initSubscriptions() {
14671
14730
  this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
14672
- logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
14731
+ logger$23.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
14673
14732
  callId: callJoinedEvent.call_id,
14674
14733
  roomSessionId: callJoinedEvent.room_session_id
14675
14734
  });
@@ -14696,19 +14755,19 @@ var CallEventsManager = class extends Destroyable {
14696
14755
  if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
14697
14756
  });
14698
14757
  this.subscribeTo(this.memberUpdates$, (member) => {
14699
- logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
14758
+ logger$23.debug("[CallEventsManager] Handling member update event for member ID:", member);
14700
14759
  this.upsertParticipant(member);
14701
14760
  });
14702
14761
  this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
14703
- logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
14762
+ logger$23.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
14704
14763
  const participants = { ...this._participants$.value };
14705
14764
  if (memberLeftEvent.member.member_id in participants) {
14706
14765
  delete participants[memberLeftEvent.member.member_id];
14707
14766
  this._participants$.next(participants);
14708
- } else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
14767
+ } else logger$23.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
14709
14768
  });
14710
14769
  this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
14711
- logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
14770
+ logger$23.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
14712
14771
  const roomSession = callUpdatedEvent.room_session;
14713
14772
  this._sessionState$.next({
14714
14773
  ...this._sessionState$.value,
@@ -14723,7 +14782,7 @@ var CallEventsManager = class extends Destroyable {
14723
14782
  });
14724
14783
  });
14725
14784
  this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
14726
- logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
14785
+ logger$23.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
14727
14786
  this._sessionState$.next({
14728
14787
  ...this._sessionState$.value,
14729
14788
  layout_name: layoutChangedEvent.id,
@@ -14733,10 +14792,10 @@ var CallEventsManager = class extends Destroyable {
14733
14792
  });
14734
14793
  }
14735
14794
  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.");
14795
+ 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
14796
  layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
14738
14797
  if (!(layer.member_id in this._participants$.value)) {
14739
- logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
14798
+ logger$23.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
14740
14799
  return false;
14741
14800
  }
14742
14801
  return true;
@@ -14756,7 +14815,7 @@ var CallEventsManager = class extends Destroyable {
14756
14815
  this.webRtcCallSession.executeMethod(this.selfId, "call.layout.list", {}).then((response) => {
14757
14816
  const layouts = response.result?.layouts;
14758
14817
  if (!layouts) {
14759
- logger$22.warn("[CallEventsManager] Layout list response carried no layouts; keeping current layouts");
14818
+ logger$23.warn("[CallEventsManager] Layout list response carried no layouts; keeping current layouts");
14760
14819
  return;
14761
14820
  }
14762
14821
  this._sessionState$.next({
@@ -14764,7 +14823,7 @@ var CallEventsManager = class extends Destroyable {
14764
14823
  layouts
14765
14824
  });
14766
14825
  }).catch((error) => {
14767
- logger$22.error("[CallEventsManager] Error fetching layouts:", error);
14826
+ logger$23.error("[CallEventsManager] Error fetching layouts:", error);
14768
14827
  });
14769
14828
  }
14770
14829
  updateParticipants(members) {
@@ -14780,7 +14839,7 @@ var CallEventsManager = class extends Destroyable {
14780
14839
  }
14781
14840
  const participant = this._participants$.value[member.member_id];
14782
14841
  const oldValue = participant.value;
14783
- logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
14842
+ logger$23.debug("[CallEventsManager] Updating participant:", member.member_id, {
14784
14843
  oldValue,
14785
14844
  newValue: member
14786
14845
  });
@@ -14793,17 +14852,17 @@ var CallEventsManager = class extends Destroyable {
14793
14852
  }
14794
14853
  get callJoinedEvent$() {
14795
14854
  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);
14855
+ logger$23.debug("[CallEventsManager] Call joined event:", event);
14797
14856
  })));
14798
14857
  }
14799
14858
  get layoutChangedEvent$() {
14800
14859
  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);
14860
+ logger$23.debug("[CallEventsManager] Layout changed event:", event);
14802
14861
  })));
14803
14862
  }
14804
14863
  get memberUpdates$() {
14805
14864
  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);
14865
+ logger$23.debug("[CallEventsManager] Member update event:", event);
14807
14866
  })));
14808
14867
  }
14809
14868
  destroy() {
@@ -14822,6 +14881,111 @@ var CallEventsManager = class extends Destroyable {
14822
14881
  }
14823
14882
  };
14824
14883
 
14884
+ //#endregion
14885
+ //#region src/controllers/ConstraintFallbackHelper.ts
14886
+ /**
14887
+ * ConstraintFallbackHelper - Provides getUserMedia with automatic constraint
14888
+ * fallback on OverconstrainedError.
14889
+ *
14890
+ * When a specific device ID is requested, the helper tries progressively
14891
+ * looser constraints:
14892
+ * 1. `{ deviceId: { exact: deviceId } }` -- exact match
14893
+ * 2. `{ deviceId: deviceId }` -- preferred (browser may pick another)
14894
+ * 3. `{}` -- no constraint, browser default
14895
+ *
14896
+ * This prevents stale device IDs from blocking call setup.
14897
+ *
14898
+ * @see Section 5.8 and Section 11 of the Implementation Guide
14899
+ */
14900
+ const logger$22 = getLogger();
14901
+ /**
14902
+ * Attempts getUserMedia with progressively looser constraints.
14903
+ *
14904
+ * The function tries three levels of constraint specificity for the given
14905
+ * device kind. Each level is only attempted if the previous one fails with
14906
+ * an OverconstrainedError. Non-OverconstrainedError failures (e.g.,
14907
+ * NotAllowedError) are thrown immediately without fallback.
14908
+ *
14909
+ * @param mediaDevices - Anything exposing `getUserMedia` (a full
14910
+ * `WebRTCMediaDevices`, or a shim wrapping one)
14911
+ * @param constraints - The full MediaStreamConstraints to use as a base
14912
+ * @param kind - Which track kind to apply fallback to ('audio' | 'video')
14913
+ * @param deviceId - The device ID to try (if undefined, calls getUserMedia as-is)
14914
+ * @returns The stream and the fallback level that succeeded
14915
+ * @throws When all fallback levels fail, or when a non-OverconstrainedError occurs
14916
+ */
14917
+ async function getUserMediaWithFallback(mediaDevices, constraints, kind, deviceId) {
14918
+ if (!deviceId) return {
14919
+ stream: await mediaDevices.getUserMedia(constraints),
14920
+ fallbackLevel: "default"
14921
+ };
14922
+ const baseConstraints = typeof constraints[kind] === "object" ? constraints[kind] : {};
14923
+ try {
14924
+ const exactConstraints = {
14925
+ ...constraints,
14926
+ [kind]: {
14927
+ ...baseConstraints,
14928
+ deviceId: { exact: deviceId }
14929
+ }
14930
+ };
14931
+ return {
14932
+ stream: await mediaDevices.getUserMedia(exactConstraints),
14933
+ fallbackLevel: "exact"
14934
+ };
14935
+ } catch (error) {
14936
+ if (!isOverconstrainedError(error)) throw error;
14937
+ logger$22.debug(`[ConstraintFallbackHelper] Exact constraint failed for ${kind}, trying preferred`, { deviceId });
14938
+ }
14939
+ try {
14940
+ const preferredConstraints = {
14941
+ ...constraints,
14942
+ [kind]: {
14943
+ ...baseConstraints,
14944
+ deviceId
14945
+ }
14946
+ };
14947
+ return {
14948
+ stream: await mediaDevices.getUserMedia(preferredConstraints),
14949
+ fallbackLevel: "preferred"
14950
+ };
14951
+ } catch (error) {
14952
+ if (!isOverconstrainedError(error)) throw error;
14953
+ logger$22.debug(`[ConstraintFallbackHelper] Preferred constraint failed for ${kind}, trying default`, { deviceId });
14954
+ }
14955
+ try {
14956
+ const defaultConstraints = {
14957
+ ...constraints,
14958
+ [kind]: { ...baseConstraints }
14959
+ };
14960
+ if (typeof defaultConstraints[kind] === "object") {
14961
+ const { deviceId: _removed, ...rest } = defaultConstraints[kind];
14962
+ defaultConstraints[kind] = rest;
14963
+ }
14964
+ const stream = await mediaDevices.getUserMedia(defaultConstraints);
14965
+ logger$22.warn(`[ConstraintFallbackHelper] Fell back to browser default for ${kind}`, { requestedDeviceId: deviceId });
14966
+ return {
14967
+ stream,
14968
+ fallbackLevel: "default"
14969
+ };
14970
+ } catch (error) {
14971
+ logger$22.error(`[ConstraintFallbackHelper] All fallback levels exhausted for ${kind}`, {
14972
+ deviceId,
14973
+ error
14974
+ });
14975
+ throw error;
14976
+ }
14977
+ }
14978
+ /**
14979
+ * Checks whether an error is an OverconstrainedError.
14980
+ *
14981
+ * Browsers may throw either a native OverconstrainedError or a DOMException
14982
+ * with a specific name.
14983
+ */
14984
+ function isOverconstrainedError(error) {
14985
+ if (error instanceof Error) return error.name === "OverconstrainedError" || error.name === "ConstraintNotSatisfiedError";
14986
+ return false;
14987
+ }
14988
+
14825
14989
  //#endregion
14826
14990
  //#region src/helpers/SDPHelper.ts
14827
14991
  /**
@@ -15380,6 +15544,7 @@ var LocalStreamController = class extends Destroyable {
15380
15544
  this._localAudioTracks$ = this.createBehaviorSubject([]);
15381
15545
  this._localVideoTracks$ = this.createBehaviorSubject([]);
15382
15546
  this._mediaTrackEnded$ = this.createSubject();
15547
+ this._trackOrigins = /* @__PURE__ */ new WeakMap();
15383
15548
  }
15384
15549
  get localStream$() {
15385
15550
  return this._localStream$.asObservable().pipe((0, import_cjs$17.takeUntil)(this.destroyed$));
@@ -15402,6 +15567,22 @@ var LocalStreamController = class extends Destroyable {
15402
15567
  get localVideoTracks() {
15403
15568
  return this._localVideoTracks$.value;
15404
15569
  }
15570
+ tagTracks(tracks, origin) {
15571
+ for (const track of tracks) this._trackOrigins.set(track, origin);
15572
+ }
15573
+ setTrackOrigin(track, origin) {
15574
+ this._trackOrigins.set(track, origin);
15575
+ }
15576
+ getTrackOrigin(track) {
15577
+ return this._trackOrigins.get(track);
15578
+ }
15579
+ /**
15580
+ * Fail-safe: an unrecorded track reads as not-a-device-capture, so a missed
15581
+ * tagging site leaves media alone rather than destroying it.
15582
+ */
15583
+ isDeviceCapture(track) {
15584
+ return this._trackOrigins.get(track) === "device";
15585
+ }
15405
15586
  /**
15406
15587
  * Build the local media stream based on the provided options.
15407
15588
  */
@@ -15411,6 +15592,7 @@ var LocalStreamController = class extends Destroyable {
15411
15592
  if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
15412
15593
  const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
15413
15594
  stream = new MediaStream(tracks);
15595
+ this.tagTracks(tracks, "application");
15414
15596
  } else if (this.options.propose === "screenshare") {
15415
15597
  logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
15416
15598
  stream = await this.options.getDisplayMedia({
@@ -15418,6 +15600,7 @@ var LocalStreamController = class extends Destroyable {
15418
15600
  audio: Boolean(this.options.inputAudioDeviceConstraints)
15419
15601
  });
15420
15602
  logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
15603
+ this.tagTracks(stream.getTracks(), "display");
15421
15604
  } else {
15422
15605
  const constraints = {
15423
15606
  audio: this.options.inputAudioDeviceConstraints,
@@ -15426,6 +15609,7 @@ var LocalStreamController = class extends Destroyable {
15426
15609
  logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
15427
15610
  stream = await this.options.getUserMedia(constraints);
15428
15611
  logger$19.debug("[LocalStreamController] User media obtained:", stream);
15612
+ this.tagTracks(stream.getTracks(), "device");
15429
15613
  }
15430
15614
  this._localStream$.next(stream);
15431
15615
  this._localAudioTracks$.next(stream.getAudioTracks());
@@ -15435,10 +15619,13 @@ var LocalStreamController = class extends Destroyable {
15435
15619
  /**
15436
15620
  * Add a local media track to the local stream.
15437
15621
  * @param track - The MediaStreamTrack to add
15622
+ * @param origin - Defaults to `'device'`; every internal caller passes a
15623
+ * fresh `getUserMedia` capture.
15438
15624
  * @returns The MediaStream (either existing or newly created)
15439
15625
  */
15440
- addTrack(track) {
15626
+ addTrack(track, origin = "device") {
15441
15627
  const localStream = this._localStream$.value ?? new MediaStream();
15628
+ this._trackOrigins.set(track, origin);
15442
15629
  track.addEventListener("ended", this.mediaTrackEndedHandler);
15443
15630
  localStream.addTrack(track);
15444
15631
  this._localStream$.next(localStream);
@@ -15708,15 +15895,27 @@ var TransceiverController = class extends Destroyable {
15708
15895
  for (let i = 0; i < Number(msStreamsNumber); i++) this.peerConnection.addTransceiver("video", { direction: "recvonly" });
15709
15896
  }
15710
15897
  }
15898
+ /**
15899
+ * @returns whether every live sender of the kind took the constraints. A
15900
+ * skipped non-device sender, an exhausted fallback, and having no live sender
15901
+ * at all all report `false` — `mediaParamsUpdated.applied` is built from this,
15902
+ * and an application told `true` cannot tell a working push from a no-op.
15903
+ */
15711
15904
  async updateSendersConstraints(kind, constraints) {
15712
15905
  if (!constraints) {
15713
15906
  this.stopTrackSender(kind);
15714
- return Promise.resolve();
15907
+ return false;
15715
15908
  }
15716
15909
  const senders = this.peerConnection.getSenders().filter((sender) => sender.track?.kind === kind && sender.track.readyState === "live");
15910
+ let applied = senders.length > 0;
15717
15911
  for (const sender of senders) {
15718
15912
  const { track } = sender;
15719
15913
  if (track) {
15914
+ if (!this.options.localStreamController.isDeviceCapture(track)) {
15915
+ logger$18.debug(`[TransceiverController] Skipping ${kind} constraints for a non-device track (origin: ${this.options.localStreamController.getTrackOrigin(track) ?? "unrecorded"}), track ${track.id}`);
15916
+ applied = false;
15917
+ continue;
15918
+ }
15720
15919
  const constraintsToApply = {
15721
15920
  ...track.getConstraints(),
15722
15921
  ...constraints
@@ -15732,33 +15931,39 @@ var TransceiverController = class extends Destroyable {
15732
15931
  } catch (fallbackError) {
15733
15932
  logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
15734
15933
  this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
15934
+ applied = false;
15735
15935
  }
15736
15936
  }
15737
15937
  }
15738
15938
  }
15939
+ return applied;
15739
15940
  }
15740
15941
  /**
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.
15942
+ * Fallback when applyConstraints fails, which on iOS Safari it silently does.
15744
15943
  *
15745
- * This is critical for iOS Safari where applyConstraints on audio tracks
15746
- * silently fails or throws.
15944
+ * Order matters: acquiring before stopping means a failed acquisition leaves
15945
+ * the existing media playing. The deviceId goes through the fallback ladder
15946
+ * rather than pinned `{ exact }`, so a stale id degrades instead of failing.
15747
15947
  */
15748
15948
  async replaceTrackFallback(sender, oldTrack, kind, mergedConstraints) {
15749
15949
  const { deviceId } = oldTrack.getSettings();
15750
- const constraintsWithDevice = {
15751
- ...mergedConstraints,
15752
- ...deviceId ? { deviceId: { exact: deviceId } } : {}
15753
- };
15754
- const trackId = oldTrack.id;
15950
+ const { stream, fallbackLevel } = await getUserMediaWithFallback({ getUserMedia: this.options.getUserMedia }, { [kind]: mergedConstraints }, kind, deviceId);
15951
+ const newTrack = stream.getTracks().find((t) => t.kind === kind);
15952
+ if (!newTrack) {
15953
+ stream.getTracks().forEach((t) => t.stop());
15954
+ throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
15955
+ }
15956
+ try {
15957
+ await sender.replaceTrack(newTrack);
15958
+ } catch (error) {
15959
+ stream.getTracks().forEach((t) => t.stop());
15960
+ throw error;
15961
+ }
15962
+ const oldTrackId = oldTrack.id;
15963
+ this.options.localStreamController.removeTrack(oldTrackId);
15755
15964
  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
15965
  this.options.localStreamController.addTrack(newTrack);
15761
- logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
15966
+ logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind} (deviceId fallback level: ${fallbackLevel}). New track: ${newTrack.id}`);
15762
15967
  }
15763
15968
  getMediaDirections() {
15764
15969
  if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
@@ -15870,6 +16075,7 @@ var RTCPeerConnectionController = class extends Destroyable {
15870
16075
  this._iceGatheringState$ = this.createReplaySubject(1);
15871
16076
  this._errors$ = this.createReplaySubject(1);
15872
16077
  this._iceCandidates$ = this.createReplaySubject(1);
16078
+ this._localMediaSettled$ = this.createReplaySubject(1);
15873
16079
  this._initialized$ = this.createReplaySubject(1);
15874
16080
  this._remoteDescription$ = this.createReplaySubject(1);
15875
16081
  this._remoteStream$ = this.createBehaviorSubject(null);
@@ -15973,6 +16179,10 @@ var RTCPeerConnectionController = class extends Destroyable {
15973
16179
  get remoteDescription$() {
15974
16180
  return this.cachedObservable("remoteDescription$", () => this._remoteDescription$.asObservable().pipe((0, import_cjs$16.takeUntil)(this.destroyed$)));
15975
16181
  }
16182
+ /** Emits once local media is settled — acquired, or knowingly receive-only. */
16183
+ get localMediaSettled$() {
16184
+ return this.cachedObservable("localMediaSettled$", () => this._localMediaSettled$.asObservable().pipe((0, import_cjs$16.takeUntil)(this.destroyed$)));
16185
+ }
15976
16186
  get localStream$() {
15977
16187
  return this.cachedObservable("localStream$", () => this.localStreamController.localStream$.pipe((0, import_cjs$16.takeUntil)(this.destroyed$)));
15978
16188
  }
@@ -16128,7 +16338,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16128
16338
  this._isNegotiating$.next(true);
16129
16339
  await this._setRemoteDescription(this.sdpInit);
16130
16340
  } else {
16131
- await this.setupTrackHandling();
16341
+ if (!await this.setupTrackHandling()) return;
16132
16342
  this._initialized$.next(true);
16133
16343
  }
16134
16344
  } catch (error) {
@@ -16252,7 +16462,10 @@ var RTCPeerConnectionController = class extends Destroyable {
16252
16462
  inputVideoDeviceConstraints: this.inputVideoDeviceConstraints
16253
16463
  });
16254
16464
  }
16255
- await this.setupLocalTracks();
16465
+ if (!await this.setupLocalTracks()) {
16466
+ logger$17.debug("[RTCPeerConnectionController] Inbound answer abandoned; the connection went away.");
16467
+ return;
16468
+ }
16256
16469
  const { answerOptions } = this;
16257
16470
  logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
16258
16471
  await this.createAnswer(answerOptions);
@@ -16390,35 +16603,52 @@ var RTCPeerConnectionController = class extends Destroyable {
16390
16603
  }
16391
16604
  /**
16392
16605
  * Setup track handling for remote tracks.
16606
+ *
16607
+ * @returns `false` when the connection went away while local media was being
16608
+ * acquired — see {@link setupLocalTracks}.
16393
16609
  */
16394
16610
  async setupTrackHandling() {
16395
16611
  if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
16396
- await this.setupLocalTracks();
16612
+ if (!await this.setupLocalTracks()) return false;
16397
16613
  await this.setupRemoteTracks();
16614
+ return true;
16398
16615
  }
16616
+ /**
16617
+ * @returns `false` when the connection was torn down while getUserMedia was
16618
+ * in flight. The acquisition is not cancellable, so the caller must stop
16619
+ * rather than go on to touch a peer connection that is closed or gone.
16620
+ */
16399
16621
  async setupLocalTracks() {
16400
16622
  logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
16401
16623
  if (this.hasNoLocalMediaToSend()) {
16402
16624
  if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
16403
16625
  logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
16404
16626
  this.setupReceiveOnlyTransceivers();
16405
- return;
16627
+ this._localMediaSettled$.next();
16628
+ return true;
16406
16629
  }
16407
16630
  let localStream;
16408
16631
  try {
16409
16632
  localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
16410
16633
  } catch (error) {
16411
16634
  this.handleLocalMediaFailure(error);
16412
- return;
16635
+ this._localMediaSettled$.next();
16636
+ return true;
16637
+ }
16638
+ if (!this.peerConnection || this.peerConnection.signalingState === "closed") {
16639
+ logger$17.debug("[RTCPeerConnectionController] Local media arrived after teardown; releasing it.");
16640
+ localStream.getTracks().forEach((track) => track.stop());
16641
+ return false;
16413
16642
  }
16643
+ this._localMediaSettled$.next();
16414
16644
  if (this.transceiverController?.useAddStream ?? false) {
16415
16645
  logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
16416
- this.peerConnection?.addStream(localStream);
16646
+ this.peerConnection.addStream(localStream);
16417
16647
  if (!this.isNegotiating) {
16418
16648
  logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
16419
16649
  this.negotiationNeeded$.next();
16420
16650
  }
16421
- return;
16651
+ return true;
16422
16652
  }
16423
16653
  for (const kind of ["audio", "video"]) {
16424
16654
  const tracks = (kind === "audio" ? localStream.getAudioTracks() : localStream.getVideoTracks()).map((track, index) => ({
@@ -16432,10 +16662,11 @@ var RTCPeerConnectionController = class extends Destroyable {
16432
16662
  await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
16433
16663
  } else {
16434
16664
  logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
16435
- this.peerConnection?.addTrack(track, localStream);
16665
+ this.peerConnection.addTrack(track, localStream);
16436
16666
  }
16437
16667
  }
16438
16668
  }
16669
+ return true;
16439
16670
  }
16440
16671
  /** True for a main connection with no local media to send. */
16441
16672
  hasNoLocalMediaToSend() {
@@ -16556,6 +16787,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16556
16787
  const sender = (this.transceiverController?.audioTransceivers.at(0))?.sender ?? this.peerConnection.getSenders().find((s) => s.track?.kind === "audio");
16557
16788
  if (!sender || !raw) return;
16558
16789
  try {
16790
+ this.localStreamController.setTrackOrigin(this._localAudioPipeline.outputTrack, "processed");
16559
16791
  await sender.replaceTrack(this._localAudioPipeline.outputTrack);
16560
16792
  } catch (error) {
16561
16793
  logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
@@ -16616,37 +16848,62 @@ var RTCPeerConnectionController = class extends Destroyable {
16616
16848
  for (const existingTrack of existingTracks) this.removeLocalTrack(existingTrack.id);
16617
16849
  this.addLocalTrack(track);
16618
16850
  }
16851
+ /**
16852
+ * @returns whether the constraints reached the media the leg is sending.
16853
+ *
16854
+ * With the pipeline engaged the audio sender carries the processed
16855
+ * destination track, so the sender scan would find nothing it may touch and
16856
+ * every audio constraint API would silently no-op. The constraints belong to
16857
+ * the pipeline's device source, which is the capture that sender ultimately
16858
+ * carries.
16859
+ */
16619
16860
  async updateSendersConstraints(kind, constraints) {
16620
- await this.transceiverController?.updateSendersConstraints(kind, constraints);
16861
+ if (kind === "audio" && constraints && this._localAudioPipeline) return this.applyPipelineSourceConstraints(constraints);
16862
+ return await this.transceiverController?.updateSendersConstraints(kind, constraints) ?? false;
16621
16863
  }
16622
16864
  /**
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.
16865
+ * Mirror of the sender path for a piped audio leg: same merge, same fallback
16866
+ * ladder, same device-capture invariant — but the swap target is the pipeline
16867
+ * input, so the sender keeps emitting the pipeline's output track and its
16868
+ * identity survives the change.
16627
16869
  */
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;
16870
+ async applyPipelineSourceConstraints(constraints) {
16871
+ const pipeline = this._localAudioPipeline;
16872
+ const source = this.localStreamController.localAudioTracks.at(0);
16873
+ if (!pipeline || !source) {
16874
+ logger$17.debug("[RTCPeerConnectionController] No pipeline input to constrain.");
16875
+ return false;
16633
16876
  }
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);
16877
+ if (!this.localStreamController.isDeviceCapture(source)) {
16878
+ logger$17.debug(`[RTCPeerConnectionController] Skipping audio constraints for a non-device pipeline input (origin: ${this.localStreamController.getTrackOrigin(source) ?? "unrecorded"}).`);
16879
+ return false;
16880
+ }
16881
+ const merged = {
16882
+ ...source.getConstraints(),
16883
+ ...constraints
16884
+ };
16885
+ try {
16886
+ await source.applyConstraints(merged);
16887
+ logger$17.debug("[RTCPeerConnectionController] Pipeline input constraints updated:", merged);
16888
+ return true;
16889
+ } catch (error) {
16890
+ logger$17.warn("[RTCPeerConnectionController] applyConstraints failed on the pipeline input, re-acquiring:", error);
16891
+ }
16892
+ try {
16893
+ const { stream } = await getUserMediaWithFallback({ getUserMedia: async (c) => this.getUserMedia(c) }, { audio: merged }, "audio", source.getSettings().deviceId);
16894
+ const newTrack = stream.getAudioTracks().at(0);
16895
+ if (!newTrack) {
16896
+ stream.getTracks().forEach((track) => track.stop());
16897
+ throw new Error("getUserMedia returned no audio track");
16898
+ }
16899
+ this.localStreamController.removeTrack(source.id);
16648
16900
  this.localStreamController.addTrack(newTrack);
16649
- logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
16901
+ pipeline.setInputTrack(newTrack);
16902
+ return true;
16903
+ } catch (error) {
16904
+ logger$17.warn("[RTCPeerConnectionController] Failed to re-acquire the pipeline input for constraints:", error);
16905
+ this._errors$.next(new MediaTrackError("updateSendersConstraints", "audio", error));
16906
+ return false;
16650
16907
  }
16651
16908
  }
16652
16909
  /**
@@ -16830,7 +17087,7 @@ var WebRTCVertoManager = class extends VertoManager {
16830
17087
  this._signalingStatus$ = this.createReplaySubject(1);
16831
17088
  this._screenShareStatus$ = this.createBehaviorSubject("none");
16832
17089
  this._rtcPeerConnectionsMap = /* @__PURE__ */ new Map();
16833
- this._screenShareTimeoutMs = 5e4;
17090
+ this._legErrors$ = this.createSubject();
16834
17091
  this._nodeId$ = this.createBehaviorSubject(options.nodeId ?? null);
16835
17092
  this.onError = options.onError;
16836
17093
  this.onModifyFailed = options.onModifyFailed;
@@ -16878,6 +17135,10 @@ var WebRTCVertoManager = class extends VertoManager {
16878
17135
  get selfId$() {
16879
17136
  return this._selfId$.asObservable();
16880
17137
  }
17138
+ /** Separates the media phase of call creation from the signalling phase. */
17139
+ get localMediaSettled$() {
17140
+ return this.mainPeerConnection.localMediaSettled$;
17141
+ }
16881
17142
  get localStream() {
16882
17143
  return this._rtcPeerConnectionsMap.get(this.webRtcCallSession.id)?.localStream ?? null;
16883
17144
  }
@@ -16934,20 +17195,11 @@ var WebRTCVertoManager = class extends VertoManager {
16934
17195
  const { mediaParams, callID } = event;
16935
17196
  const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
16936
17197
  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
- })();
17198
+ if (!rtcPeerConnController) {
17199
+ logger$16.warn(`[WebRTCManager] Ignoring server-pushed media params for unknown leg ${callID}`);
17200
+ return;
17201
+ }
17202
+ this.applyServerMediaParams(rtcPeerConnController, audio, video);
16951
17203
  });
16952
17204
  this.subscribeTo(this.vertoPing$, (vertoPing) => {
16953
17205
  this.attachManager.attach(this.buildAttachableCall());
@@ -16955,6 +17207,75 @@ var WebRTCVertoManager = class extends VertoManager {
16955
17207
  });
16956
17208
  }
16957
17209
  /**
17210
+ * An auxiliary-leg failure must never destroy the call; main-leg and
17211
+ * call-level errors keep `CallFactory.isFatalError`'s classification.
17212
+ *
17213
+ * Every site holding a peer connection reports through here, so the invariant
17214
+ * is structural rather than per-call-site — which is how cloud-product#20523
17215
+ * happened, with only one of fourteen sites passing `{ fatal: false }`.
17216
+ *
17217
+ * `override` composes rather than replaces: a caller may force non-fatal for a
17218
+ * reason of its own (a `verto.info` frame is best-effort whichever leg carries
17219
+ * it), and an auxiliary leg stays non-fatal regardless.
17220
+ */
17221
+ reportLegError(error, rtcPeerConnController, override) {
17222
+ const leg = rtcPeerConnController?.propose;
17223
+ const legId = rtcPeerConnController?.id;
17224
+ const auxiliary = Boolean(rtcPeerConnController) && !rtcPeerConnController?.isMainDevice;
17225
+ this.onError?.(error, {
17226
+ ...override?.fatal === false || auxiliary ? { fatal: false } : {},
17227
+ ...leg ? { leg } : {},
17228
+ ...legId ? { legId } : {}
17229
+ });
17230
+ if (legId) this._legErrors$.next({
17231
+ legId,
17232
+ error
17233
+ });
17234
+ }
17235
+ /**
17236
+ * Errors reported for one leg, as a stream that fails with them.
17237
+ *
17238
+ * Signaling failures are reported, never thrown — so nothing that waits on a
17239
+ * leg's progress would otherwise learn of a rejected invite. Merging this in
17240
+ * lets the wait end with the reason the server gave.
17241
+ */
17242
+ legError$(legId) {
17243
+ return this._legErrors$.pipe((0, import_cjs$15.filter)((report) => report.legId === legId), (0, import_cjs$15.map)((report) => {
17244
+ throw report.error;
17245
+ }));
17246
+ }
17247
+ /**
17248
+ * Audio and video are applied independently so a failure in one cannot
17249
+ * suppress the other, and `mediaParamsUpdated` is emitted whatever happens —
17250
+ * an application should not be starved of the params by a constraint failure.
17251
+ */
17252
+ async applyServerMediaParams(rtcPeerConnController, audio, video) {
17253
+ const failures = [];
17254
+ let applied = true;
17255
+ if (audio) try {
17256
+ applied = await rtcPeerConnController.updateSendersConstraints("audio", audio) && applied;
17257
+ } catch (error) {
17258
+ applied = false;
17259
+ failures.push(toError(error));
17260
+ }
17261
+ if (video) try {
17262
+ applied = await rtcPeerConnController.updateSendersConstraints("video", video) && applied;
17263
+ } catch (error) {
17264
+ applied = false;
17265
+ failures.push(toError(error));
17266
+ }
17267
+ this.webRtcCallSession.emitMediaParamsUpdated({
17268
+ audio,
17269
+ video,
17270
+ timestamp: Date.now(),
17271
+ applied
17272
+ });
17273
+ for (const failure of failures) {
17274
+ logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", failure);
17275
+ this.reportLegError(failure, rtcPeerConnController, { fatal: false });
17276
+ }
17277
+ }
17278
+ /**
16958
17279
  * Set node_id/selfId only when the current value is null.
16959
17280
  *
16960
17281
  * During reattach, `call.joined` and `verto.answer` events can deliver
@@ -16992,7 +17313,7 @@ var WebRTCVertoManager = class extends VertoManager {
16992
17313
  if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
16993
17314
  } catch (error) {
16994
17315
  logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
16995
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17316
+ this.reportLegError(toError(error), this.mainPeerConnection);
16996
17317
  throw error;
16997
17318
  }
16998
17319
  }
@@ -17160,23 +17481,24 @@ var WebRTCVertoManager = class extends VertoManager {
17160
17481
  return response;
17161
17482
  }
17162
17483
  async executeVerto(message, optionals = {}) {
17163
- const webrtcVertoMessage = WebrtcVerto({
17484
+ const params = {
17164
17485
  callID: optionals.callID ?? this.webRtcCallSession.id,
17165
17486
  node_id: optionals.node_id ?? this._nodeId$.value ?? "",
17166
17487
  message,
17167
17488
  subscribe: optionals.subscribe
17168
- });
17489
+ };
17490
+ const webrtcVertoMessage = WebrtcVerto(params);
17169
17491
  const response = await this.webRtcCallSession.execute(webrtcVertoMessage);
17170
17492
  const nonFatal = message.method === "verto.info" ? { fatal: false } : void 0;
17171
17493
  if (response.error) {
17172
17494
  const error = new JSONRPCError(response.error.code, response.error.message, response.error.data);
17173
- this.onError?.(error, nonFatal);
17495
+ this.reportLegError(error, this._rtcPeerConnectionsMap.get(params.callID), nonFatal);
17174
17496
  return response;
17175
17497
  }
17176
17498
  const innerResult = getValueFrom(response, "result.result");
17177
17499
  if (innerResult?.error) {
17178
17500
  const error = new JSONRPCError(innerResult.error.code, innerResult.error.message, innerResult.error.data);
17179
- this.onError?.(error, nonFatal);
17501
+ this.reportLegError(error, this._rtcPeerConnectionsMap.get(params.callID), nonFatal);
17180
17502
  return response;
17181
17503
  }
17182
17504
  return response;
@@ -17197,7 +17519,7 @@ var WebRTCVertoManager = class extends VertoManager {
17197
17519
  }
17198
17520
  } catch (error) {
17199
17521
  logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
17200
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17522
+ this.reportLegError(toError(error), rtcPeerConnController);
17201
17523
  if (vertoMethod === "verto.modify") this.onModifyFailed?.();
17202
17524
  }
17203
17525
  }
@@ -17213,7 +17535,7 @@ var WebRTCVertoManager = class extends VertoManager {
17213
17535
  } catch (error) {
17214
17536
  logger$16.warn("[WebRTCManager] Error processing modify response:", error);
17215
17537
  const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
17216
- this.onError?.(modifyError);
17538
+ this.reportLegError(modifyError, rtcPeerConnController);
17217
17539
  }
17218
17540
  }
17219
17541
  }
@@ -17226,7 +17548,7 @@ var WebRTCVertoManager = class extends VertoManager {
17226
17548
  status,
17227
17549
  signalingError
17228
17550
  });
17229
- this.onError?.(signalingError);
17551
+ this.reportLegError(signalingError, null, { fatal: false });
17230
17552
  return;
17231
17553
  }
17232
17554
  if (rtcPeerConnController.isMainDevice) this._signalingStatus$.next(status);
@@ -17256,7 +17578,7 @@ var WebRTCVertoManager = class extends VertoManager {
17256
17578
  } else {
17257
17579
  logger$16.error("[WebRTCManager] Verto invite failed:", response);
17258
17580
  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);
17581
+ this.reportLegError(inviteError, rtcPeerConnController);
17260
17582
  }
17261
17583
  }
17262
17584
  get RTCPeerConnectionConfig() {
@@ -17295,7 +17617,7 @@ var WebRTCVertoManager = class extends VertoManager {
17295
17617
  this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
17296
17618
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17297
17619
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
17298
- this.onError?.(error);
17620
+ this.reportLegError(error, rtcPeerConnController);
17299
17621
  });
17300
17622
  if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
17301
17623
  }
@@ -17324,7 +17646,7 @@ var WebRTCVertoManager = class extends VertoManager {
17324
17646
  await rtcPeerConnController.acceptInbound(answerOptions);
17325
17647
  } catch (error) {
17326
17648
  logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
17327
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17649
+ this.reportLegError(toError(error), rtcPeerConnController);
17328
17650
  }
17329
17651
  }
17330
17652
  }
@@ -17428,7 +17750,7 @@ var WebRTCVertoManager = class extends VertoManager {
17428
17750
  await this.attachManager.attach(this.buildAttachableCall());
17429
17751
  } catch (error) {
17430
17752
  logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
17431
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17753
+ this.reportLegError(toError(error), rtcPeerConnectionController);
17432
17754
  await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
17433
17755
  }
17434
17756
  }
@@ -17500,7 +17822,7 @@ var WebRTCVertoManager = class extends VertoManager {
17500
17822
  await this.mainPeerConnection.restoreTrackSender(deviceKind);
17501
17823
  } else {
17502
17824
  const error = new InvalidParams("No valid device to be added");
17503
- this.onError?.(error);
17825
+ this.reportLegError(error, this.mainPeerConnection);
17504
17826
  throw error;
17505
17827
  }
17506
17828
  }
@@ -17509,6 +17831,7 @@ var WebRTCVertoManager = class extends VertoManager {
17509
17831
  }
17510
17832
  async initAdditionalPeerConnection(propose, options) {
17511
17833
  const isScreenShare = propose === "screenshare";
17834
+ if (isScreenShare && this._screenShareId && this._rtcPeerConnectionsMap.has(this._screenShareId)) throw new ScreenShareAlreadyActiveError(this._screenShareId);
17512
17835
  let firstPeerConnectionError;
17513
17836
  let rtcPeerConnController = null;
17514
17837
  try {
@@ -17525,21 +17848,30 @@ var WebRTCVertoManager = class extends VertoManager {
17525
17848
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17526
17849
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
17527
17850
  firstPeerConnectionError ??= error;
17528
- this.onError?.(error, { fatal: false });
17851
+ this.reportLegError(error, rtcPeerConnController);
17529
17852
  });
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$)));
17853
+ const pc = rtcPeerConnController;
17854
+ 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
17855
  if (isScreenShare) this._screenShareStatus$.next("started");
17532
17856
  logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
17533
17857
  return rtcPeerConnController.id;
17534
17858
  } catch (error) {
17535
17859
  logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
17536
- if (rtcPeerConnController) rtcPeerConnController.destroy();
17537
- if (isScreenShare) this._screenShareStatus$.next("none");
17860
+ if (rtcPeerConnController) {
17861
+ rtcPeerConnController.destroy();
17862
+ this._rtcPeerConnectionsMap.delete(rtcPeerConnController.id);
17863
+ this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17864
+ }
17865
+ if (isScreenShare) {
17866
+ this._screenShareId = void 0;
17867
+ this._screenShareStatus$.next("none");
17868
+ }
17538
17869
  if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
17539
17870
  if (error instanceof import_cjs$15.EmptyError) {
17540
17871
  logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
17541
17872
  return;
17542
17873
  }
17874
+ if (error instanceof import_cjs$15.TimeoutError) throw new AuxiliaryLegTimeoutError(propose, error);
17543
17875
  throw error instanceof Error ? error : new Error(String(error), { cause: error });
17544
17876
  }
17545
17877
  }
@@ -17556,7 +17888,10 @@ var WebRTCVertoManager = class extends VertoManager {
17556
17888
  if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
17557
17889
  }
17558
17890
  async removeScreenMedia() {
17559
- if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
17891
+ if (!["starting", "started"].includes(this._screenShareStatus$.value)) {
17892
+ logger$16.warn("[WebRTCManager] No active screen share to stop.");
17893
+ return;
17894
+ }
17560
17895
  if (!this._screenShareId) {
17561
17896
  logger$16.debug("[WebRTCManager] No screen share peer connection found.");
17562
17897
  return;
@@ -17585,7 +17920,7 @@ var WebRTCVertoManager = class extends VertoManager {
17585
17920
  await this.executeVerto(VertoBye({
17586
17921
  ...causeParams,
17587
17922
  dialogParams: this.dialogParams(rtcPeerConnController)
17588
- }));
17923
+ }), { callID: rtcPeerConnController.id });
17589
17924
  } catch (error) {
17590
17925
  logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
17591
17926
  throw error;
@@ -18977,13 +19312,13 @@ var WebRTCCall = class extends Destroyable {
18977
19312
  get bandwidthConstrained$() {
18978
19313
  return this.deferEmission(this._bandwidthConstrained$.asObservable());
18979
19314
  }
18980
- /** Observable that emits when server-pushed media params are applied. */
19315
+ /** Observable that emits when the server pushes media params. */
18981
19316
  get mediaParamsUpdated$() {
18982
19317
  return this.deferEmission(this._mediaParamsUpdated$.asObservable());
18983
19318
  }
18984
19319
  /**
18985
19320
  * @internal Emit a media params update event.
18986
- * Called by the VertoManager when server-pushed media params are applied.
19321
+ * Called by the VertoManager when the server pushes media params.
18987
19322
  */
18988
19323
  emitMediaParamsUpdated(event) {
18989
19324
  this._mediaParamsUpdated$.next(event);
@@ -19191,6 +19526,10 @@ var WebRTCCall = class extends Destroyable {
19191
19526
  get selfId$() {
19192
19527
  return this.vertoManager.selfId$;
19193
19528
  }
19529
+ /** @internal Lets call creation bound the media and signalling phases apart. */
19530
+ get localMediaSettled$() {
19531
+ return this.vertoManager.localMediaSettled$;
19532
+ }
19194
19533
  /** Local participant's member ID, or `null` if not joined. */
19195
19534
  get selfId() {
19196
19535
  return this.vertoManager.selfId;
@@ -19586,6 +19925,9 @@ var WebRTCCall = class extends Destroyable {
19586
19925
  /**
19587
19926
  * Infers the semantic error category from a raw Error thrown by VertoManager
19588
19927
  * or an RTCPeerConnection layer.
19928
+ *
19929
+ * Pure function — exported for unit testing.
19930
+ * @internal
19589
19931
  */
19590
19932
  function inferCallErrorKind(error) {
19591
19933
  if (error instanceof RPCTimeoutError) return "timeout";
@@ -19604,7 +19946,14 @@ const RECOVERABLE_RPC_CODES = new Set([
19604
19946
  RPC_ERROR_AUTHENTICATION_FAILED,
19605
19947
  RPC_ERROR_INVALID_PARAMS
19606
19948
  ]);
19607
- /** Determines whether an error should be fatal (destroy the call). */
19949
+ /**
19950
+ * A *fallback*: callers knowing which leg failed pass an explicit `fatal` and
19951
+ * never reach here, so the default-fatal branch only sees call- and main-leg
19952
+ * errors. Auxiliary legs go through `WebRTCVertoManager.reportLegError`.
19953
+ *
19954
+ * Pure function — exported for unit testing.
19955
+ * @internal
19956
+ */
19608
19957
  function isFatalError(error) {
19609
19958
  if (error instanceof VertoPongError) return false;
19610
19959
  if (error instanceof MediaTrackError) return false;
@@ -19639,7 +19988,9 @@ var CallFactory = class {
19639
19988
  kind: inferCallErrorKind(error),
19640
19989
  fatal: options$1?.fatal ?? isFatalError(error),
19641
19990
  error,
19642
- callId: callInstance.id
19991
+ callId: callInstance.id,
19992
+ ...options$1?.leg ? { leg: options$1.leg } : {},
19993
+ ...options$1?.legId ? { legId: options$1.legId } : {}
19643
19994
  };
19644
19995
  callInstance.emitError(callError);
19645
19996
  },
@@ -20111,6 +20462,30 @@ const logger$9 = getLogger();
20111
20462
  function shouldAbortDial(callError) {
20112
20463
  return callError.fatal || !(callError.error instanceof MediaAccessError);
20113
20464
  }
20465
+ /**
20466
+ * Wait for a dialed call to be ready, or for the failure that stops it.
20467
+ *
20468
+ * Local media acquisition is deliberately unbounded: a permission prompt or a
20469
+ * device picker is human time, and `getUserMedia` cannot be cancelled anyway.
20470
+ * The clock starts only once acquisition settles, so a slow human never spends
20471
+ * the server's budget.
20472
+ *
20473
+ * `merge` rather than `race`, because the two legs settle asymmetrically. A
20474
+ * fatal acquisition failure reports the error and then destroys the call in the
20475
+ * same synchronous step; `errors$` defers delivery by a microtask while
20476
+ * `localMediaSettled$` completes immediately. Under `race` that bare completion
20477
+ * ended the wait first and `dial()` rejected with an RxJS `EmptyError`, burying
20478
+ * the `NotAllowedError` applications are told to inspect. Under `merge` the
20479
+ * completed leg is simply spent, and the queued error — enqueued before the
20480
+ * completion, so delivered before it — arrives to reject the wait. A dial
20481
+ * abandoned with no error at all still ends both legs, and the resulting
20482
+ * `EmptyError` remains the benign-cancel signal.
20483
+ *
20484
+ * Exported for unit testing.
20485
+ */
20486
+ async function awaitDialReady(session, signalingTimeoutMs) {
20487
+ 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)))));
20488
+ }
20114
20489
  const getAddressSearchURI = (options) => {
20115
20490
  const to = options.to?.split("?")[0];
20116
20491
  const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
@@ -20127,7 +20502,6 @@ var ClientSessionManager = class extends Destroyable {
20127
20502
  this.authorizationStateKey = authorizationStateKey;
20128
20503
  this.attachManager = attachManager;
20129
20504
  this.dpopManager = dpopManager;
20130
- this.callCreateTimeout = 6e3;
20131
20505
  this.agent = `signalwire-js/4.0.0`;
20132
20506
  this.eventAcks = true;
20133
20507
  this.authorizationState$ = this.createReplaySubject(1);
@@ -20555,7 +20929,7 @@ var ClientSessionManager = class extends Destroyable {
20555
20929
  to: destinationURI,
20556
20930
  ...options
20557
20931
  });
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)))));
20932
+ await awaitDialReady(callSession, DEFAULT_CALL_SIGNALING_TIMEOUT_MS);
20559
20933
  this._calls$.next({
20560
20934
  [`${callSession.id}`]: callSession,
20561
20935
  ...this._calls$.value
@@ -22733,6 +23107,11 @@ var SignalWire = class extends Destroyable {
22733
23107
  * Returns a {@link Call} in `'ringing'` state. Subscribe to {@link Call.status$}
22734
23108
  * to track progression through `'connected'` → `'disconnected'`.
22735
23109
  *
23110
+ * Local media acquisition is deliberately unbounded: an unanswered permission
23111
+ * prompt leaves this promise pending indefinitely, so apply your own bound if
23112
+ * your UI needs one. The 12 s signaling budget starts only once acquisition
23113
+ * settles.
23114
+ *
22736
23115
  * @param destination - Address URI string (e.g. `'/public/my-room'`) or {@link Address} instance.
22737
23116
  * @param options - Media and dial options (audio/video, device constraints). Overrides defaults.
22738
23117
  * @returns The created {@link Call} instance.
@@ -23212,5 +23591,5 @@ emitReadyEvent();
23212
23591
  if (typeof process === "undefined") globalThis.process = { env: { NODE_ENV: "production" } };
23213
23592
 
23214
23593
  //#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 };
23594
+ export { Address, AuxiliaryLegTimeoutError, CallCreateError, CallNotReadyError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaAccessError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, ParticipantNotReadyError, PreflightError, RecoveryError, ScreenShareAlreadyActiveError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
23216
23595
  //# sourceMappingURL=browser.mjs.map