@signalwire/js 4.0.0-dev-20260515133934 → 4.0.0-dev-20260717142935

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
@@ -9015,6 +9015,143 @@ var Destroyable = class {
9015
9015
  }
9016
9016
  };
9017
9017
 
9018
+ //#endregion
9019
+ //#region src/core/constants.ts
9020
+ const INVITE_VERSION = 1e3;
9021
+ const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
9022
+ const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
9023
+ const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
9024
+ const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
9025
+ const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
9026
+ const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
9027
+ const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
9028
+ const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
9029
+ const PREFERENCES_STORAGE_KEY = "sw:preferences";
9030
+ /** Scope value that enables automatic token refresh. */
9031
+ const SAT_REFRESH_SCOPE = "sat:refresh";
9032
+ /** API endpoints for device token operations. */
9033
+ const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
9034
+ const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
9035
+ /** Default device token TTL in seconds (15 minutes). */
9036
+ const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
9037
+ /** Buffer time in milliseconds before expiry to trigger refresh. */
9038
+ const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
9039
+ /** Maximum retry attempts for device token refresh on transient failure. */
9040
+ const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
9041
+ /** Base delay in milliseconds for exponential backoff on refresh retry. */
9042
+ const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
9043
+ /** Maximum retry attempts for developer credential refresh on transient failure. */
9044
+ const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
9045
+ /** Base delay in milliseconds for exponential backoff on credential refresh retry. */
9046
+ const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
9047
+ /** Maximum delay in milliseconds for credential refresh backoff. */
9048
+ const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
9049
+ /** Buffer in milliseconds before token expiry to trigger refresh. */
9050
+ const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
9051
+ /**
9052
+ * Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
9053
+ * to resolve before treating the activation as failed and falling back to
9054
+ * the developer-provided refresh path. Prevents a wedged HTTP layer from
9055
+ * leaving the session with no active refresh mechanism.
9056
+ */
9057
+ const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
9058
+ /** JSON-RPC error code for requester validation failure (corrupted auth state). */
9059
+ const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
9060
+ /** JSON-RPC error code for invalid params (e.g., missing authentication block). */
9061
+ const RPC_ERROR_INVALID_PARAMS = -32602;
9062
+ /** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
9063
+ const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
9064
+ /** Error names browsers use for a media permission denial (user or policy). */
9065
+ const MEDIA_ACCESS_DENIAL_NAMES = [
9066
+ "NotAllowedError",
9067
+ "SecurityError",
9068
+ "PermissionDeniedError"
9069
+ ];
9070
+ /** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
9071
+ const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
9072
+ /** Number of initial samples used to build a baseline for spike detection. */
9073
+ const DEFAULT_STATS_BASELINE_SAMPLES = 10;
9074
+ /** Duration in ms with no inbound audio packets before emitting a critical issue. */
9075
+ const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
9076
+ /** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
9077
+ const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
9078
+ /** Packet loss fraction (0-1) above which a warning is emitted. */
9079
+ const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
9080
+ /** Multiplier applied to baseline jitter to detect a jitter spike. */
9081
+ const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
9082
+ /** Number of seconds of metrics history to retain. */
9083
+ const DEFAULT_STATS_HISTORY_SIZE = 30;
9084
+ /** Maximum keyframe requests allowed within a single burst window. */
9085
+ const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
9086
+ /** Duration of the keyframe burst window in milliseconds. */
9087
+ const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
9088
+ /** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
9089
+ const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
9090
+ /** Minimum time between re-INVITE attempts in milliseconds. */
9091
+ const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
9092
+ /** Maximum number of re-INVITE attempts per call. */
9093
+ const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
9094
+ /** Timeout for a single re-INVITE attempt in milliseconds. */
9095
+ const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
9096
+ /** Debounce window in ms to collapse multiple detection signals into one trigger. */
9097
+ const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
9098
+ /** Cooldown period in ms between recovery attempts. */
9099
+ const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
9100
+ /** Grace period in ms before treating ICE 'disconnected' as a failure. */
9101
+ const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
9102
+ /** Timeout for a single ICE restart attempt in milliseconds. */
9103
+ const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
9104
+ /** Maximum recovery attempts before emitting 'max_attempts_reached'. */
9105
+ const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
9106
+ /** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
9107
+ const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
9108
+ /** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
9109
+ const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
9110
+ /** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
9111
+ const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
9112
+ /** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
9113
+ const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
9114
+ /** RMS level threshold (0..1) above which the local participant is considered speaking. */
9115
+ const VAD_THRESHOLD = .03;
9116
+ /** Hold window in ms below the threshold before speaking$ flips back to false. */
9117
+ const VAD_HOLD_MS = 250;
9118
+ /** Whether to persist device selections to storage by default. */
9119
+ const DEFAULT_PERSIST_DEVICE_SELECTION = true;
9120
+ /** Whether to auto-apply device changes to active calls by default. */
9121
+ const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
9122
+ /** Storage keys for persisted device selections. */
9123
+ const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
9124
+ const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
9125
+ const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
9126
+ /** Whether to auto-mute video when the tab becomes hidden. */
9127
+ const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
9128
+ /** Whether to re-enumerate devices when the page becomes visible. */
9129
+ const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
9130
+ /** Whether to check peer connection health when the page becomes visible. */
9131
+ const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
9132
+ /** Whether automatic video degradation on low bandwidth is enabled. */
9133
+ const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
9134
+ /** Bitrate in kbps below which video is automatically disabled. */
9135
+ const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
9136
+ /** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
9137
+ const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
9138
+ /** Whether relay-only escalation is enabled as a last-resort recovery tier. */
9139
+ const DEFAULT_ENABLE_RELAY_FALLBACK = true;
9140
+ /** Whether to listen for browser online/offline/connection events. */
9141
+ const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
9142
+ /** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
9143
+ const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
9144
+ /** Default video track constraints applied when video is enabled without explicit constraints. */
9145
+ const DEFAULT_VIDEO_CONSTRAINTS = {
9146
+ width: { ideal: 1280 },
9147
+ height: { ideal: 720 },
9148
+ aspectRatio: 16 / 9
9149
+ };
9150
+ /** Whether stereo Opus is enabled by default. */
9151
+ const DEFAULT_STEREO_AUDIO = false;
9152
+ /** Max average bitrate for stereo Opus in bits per second. */
9153
+ const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
9154
+
9018
9155
  //#endregion
9019
9156
  //#region src/core/errors.ts
9020
9157
  var UnexpectedError = class extends Error {
@@ -9144,6 +9281,20 @@ var CallCreateError = class extends Error {
9144
9281
  this.name = "CallCreateError";
9145
9282
  }
9146
9283
  };
9284
+ var CallNotReadyError = class extends Error {
9285
+ constructor(callId, options) {
9286
+ super(`Call "${callId}" has no self member context yet: selfId/nodeId have not been received from the server`, options);
9287
+ this.callId = callId;
9288
+ this.name = "CallNotReadyError";
9289
+ }
9290
+ };
9291
+ var ParticipantNotReadyError = class extends Error {
9292
+ constructor(memberId, options) {
9293
+ super(`Participant "${memberId}" has no call context yet: its member state (call_id/node_id) has not been received from the server`, options);
9294
+ this.memberId = memberId;
9295
+ this.name = "ParticipantNotReadyError";
9296
+ }
9297
+ };
9147
9298
  var JSONRPCError = class extends Error {
9148
9299
  constructor(code, message, data, options, requestId) {
9149
9300
  super(message, options);
@@ -9224,6 +9375,33 @@ var MediaTrackError = class extends Error {
9224
9375
  this.name = "MediaTrackError";
9225
9376
  }
9226
9377
  };
9378
+ /** True when a `getUserMedia`/`getDisplayMedia` rejection is a permission denial. */
9379
+ function isMediaAccessDenial(originalError) {
9380
+ return originalError instanceof Error && MEDIA_ACCESS_DENIAL_NAMES.includes(originalError.name);
9381
+ }
9382
+ /**
9383
+ * Failure to acquire local media (camera, microphone, or screen capture)
9384
+ * via `getUserMedia`/`getDisplayMedia`.
9385
+ *
9386
+ * Non-fatal by default: screenshare and additional-device failures never
9387
+ * end the call, and main-connection failures degrade to receive-only when
9388
+ * possible. The wrapping site sets `fatal` to `true` only when the call
9389
+ * cannot continue (receive-only fallback disabled or no receive intent).
9390
+ */
9391
+ var MediaAccessError = class extends Error {
9392
+ constructor(operation, media, originalError, fatal = false) {
9393
+ super(`Media access ${isMediaAccessDenial(originalError) ? "denied" : "failed"} for ${operation} (${media})`, { cause: originalError instanceof Error ? originalError : void 0 });
9394
+ this.operation = operation;
9395
+ this.media = media;
9396
+ this.originalError = originalError;
9397
+ this.fatal = fatal;
9398
+ this.name = "MediaAccessError";
9399
+ }
9400
+ /** True when the underlying failure is a permission denial (user or policy). */
9401
+ get denied() {
9402
+ return isMediaAccessDenial(this.originalError);
9403
+ }
9404
+ };
9227
9405
  var DPoPInitError = class extends Error {
9228
9406
  constructor(originalError, message = "Failed to initialize DPoP key pair") {
9229
9407
  super(message, { cause: originalError instanceof Error ? originalError : void 0 });
@@ -9464,9 +9642,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
9464
9642
  defaultLogger$1 = new Logger();
9465
9643
  defaultLogger$1.getLogger = function getLogger$1(name) {
9466
9644
  if (typeof name !== "symbol" && typeof name !== "string" || name === "") throw new TypeError("You must supply a name when creating a logger.");
9467
- var logger$32 = _loggersByName[name];
9468
- if (!logger$32) logger$32 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
9469
- return logger$32;
9645
+ var logger$33 = _loggersByName[name];
9646
+ if (!logger$33) logger$33 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
9647
+ return logger$33;
9470
9648
  };
9471
9649
  var _log = typeof window !== undefinedType ? window.log : void 0;
9472
9650
  defaultLogger$1.noConflict = function() {
@@ -9502,8 +9680,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
9502
9680
  defaultLogger.setLevel(defaultLoggerLevel);
9503
9681
  let userLogger = null;
9504
9682
  /** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
9505
- const setLogger = (logger$32) => {
9506
- userLogger = logger$32;
9683
+ const setLogger = (logger$33) => {
9684
+ userLogger = logger$33;
9507
9685
  };
9508
9686
  let debugOptions = {};
9509
9687
  /** Configure debug options (e.g., `{ logWsTraffic: true }`). */
@@ -9547,8 +9725,8 @@ const wsTraffic = (options) => {
9547
9725
  loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
9548
9726
  };
9549
9727
  const getLogger = () => {
9550
- const logger$32 = getLoggerInstance();
9551
- return new Proxy(logger$32, { get(_target, prop, _receiver) {
9728
+ const logger$33 = getLoggerInstance();
9729
+ return new Proxy(logger$33, { get(_target, prop, _receiver) {
9552
9730
  if (prop === "wsTraffic") return wsTraffic;
9553
9731
  const instance = getLoggerInstance();
9554
9732
  const value = Reflect.get(instance, prop);
@@ -9600,7 +9778,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
9600
9778
 
9601
9779
  //#endregion
9602
9780
  //#region src/controllers/HTTPRequestController.ts
9603
- const logger$31 = getLogger();
9781
+ const logger$32 = getLogger();
9604
9782
  const GET_PARAMS = {
9605
9783
  method: "GET",
9606
9784
  headers: { Accept: "application/json" }
@@ -9664,7 +9842,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9664
9842
  this._responses$.next(response);
9665
9843
  return response;
9666
9844
  } catch (error) {
9667
- logger$31.error("[HTTPRequestController] Request error:", error);
9845
+ logger$32.error("[HTTPRequestController] Request error:", error);
9668
9846
  this._status$.next("error");
9669
9847
  const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
9670
9848
  this._errors$.next(err);
@@ -9691,7 +9869,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9691
9869
  const url = this.buildURL(request.url);
9692
9870
  const headers = this.buildHeaders(request.headers);
9693
9871
  const timeout$5 = request.timeout ?? this.requestTimeout;
9694
- logger$31.debug("[HTTPRequestController] Executing request:", {
9872
+ logger$32.debug("[HTTPRequestController] Executing request:", {
9695
9873
  method: request.method,
9696
9874
  url,
9697
9875
  headers: Object.keys(headers).reduce((acc, key) => {
@@ -9711,7 +9889,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9711
9889
  });
9712
9890
  clearTimeout(timeoutId);
9713
9891
  const httpResponse = await this.convertResponse(response);
9714
- logger$31.debug("[HTTPRequestController] Response received:", {
9892
+ logger$32.debug("[HTTPRequestController] Response received:", {
9715
9893
  status: response.status,
9716
9894
  statusText: response.statusText,
9717
9895
  headers: [...response.headers.entries()],
@@ -9721,7 +9899,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9721
9899
  } catch (error) {
9722
9900
  clearTimeout(timeoutId);
9723
9901
  if (error instanceof Error && error.name === "AbortError") throw new RequestTimeoutError(`Request timeout after ${timeout$5}ms`, { cause: error });
9724
- logger$31.error("[HTTPRequestController] Request failed:", error);
9902
+ logger$32.error("[HTTPRequestController] Request failed:", error);
9725
9903
  throw error;
9726
9904
  }
9727
9905
  }
@@ -9735,8 +9913,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9735
9913
  const credential = this.getCredential();
9736
9914
  if (credential.token) {
9737
9915
  headers.Authorization = `Bearer ${credential.token}`;
9738
- logger$31.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
9739
- } else logger$31.warn("[HTTPRequestController] No credentials available for authentication");
9916
+ logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
9917
+ } else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
9740
9918
  return headers;
9741
9919
  }
9742
9920
  /**
@@ -9873,130 +10051,6 @@ var DeviceHistoryManager = class {
9873
10051
  }
9874
10052
  };
9875
10053
 
9876
- //#endregion
9877
- //#region src/core/constants.ts
9878
- const INVITE_VERSION = 1e3;
9879
- const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
9880
- const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
9881
- const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
9882
- const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
9883
- const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
9884
- const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
9885
- const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
9886
- const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
9887
- const PREFERENCES_STORAGE_KEY = "sw:preferences";
9888
- /** Scope value that enables automatic token refresh. */
9889
- const SAT_REFRESH_SCOPE = "sat:refresh";
9890
- /** API endpoints for device token operations. */
9891
- const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
9892
- const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
9893
- /** Default device token TTL in seconds (15 minutes). */
9894
- const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
9895
- /** Buffer time in milliseconds before expiry to trigger refresh. */
9896
- const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
9897
- /** Maximum retry attempts for device token refresh on transient failure. */
9898
- const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
9899
- /** Base delay in milliseconds for exponential backoff on refresh retry. */
9900
- const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
9901
- /** Maximum retry attempts for developer credential refresh on transient failure. */
9902
- const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
9903
- /** Base delay in milliseconds for exponential backoff on credential refresh retry. */
9904
- const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
9905
- /** Maximum delay in milliseconds for credential refresh backoff. */
9906
- const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
9907
- /** Buffer in milliseconds before token expiry to trigger refresh. */
9908
- const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
9909
- /** JSON-RPC error code for requester validation failure (corrupted auth state). */
9910
- const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
9911
- /** JSON-RPC error code for invalid params (e.g., missing authentication block). */
9912
- const RPC_ERROR_INVALID_PARAMS = -32602;
9913
- /** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
9914
- const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
9915
- /** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
9916
- const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
9917
- /** Number of initial samples used to build a baseline for spike detection. */
9918
- const DEFAULT_STATS_BASELINE_SAMPLES = 10;
9919
- /** Duration in ms with no inbound audio packets before emitting a critical issue. */
9920
- const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
9921
- /** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
9922
- const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
9923
- /** Packet loss fraction (0-1) above which a warning is emitted. */
9924
- const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
9925
- /** Multiplier applied to baseline jitter to detect a jitter spike. */
9926
- const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
9927
- /** Number of seconds of metrics history to retain. */
9928
- const DEFAULT_STATS_HISTORY_SIZE = 30;
9929
- /** Maximum keyframe requests allowed within a single burst window. */
9930
- const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
9931
- /** Duration of the keyframe burst window in milliseconds. */
9932
- const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
9933
- /** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
9934
- const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
9935
- /** Minimum time between re-INVITE attempts in milliseconds. */
9936
- const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
9937
- /** Maximum number of re-INVITE attempts per call. */
9938
- const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
9939
- /** Timeout for a single re-INVITE attempt in milliseconds. */
9940
- const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
9941
- /** Debounce window in ms to collapse multiple detection signals into one trigger. */
9942
- const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
9943
- /** Cooldown period in ms between recovery attempts. */
9944
- const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
9945
- /** Grace period in ms before treating ICE 'disconnected' as a failure. */
9946
- const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
9947
- /** Timeout for a single ICE restart attempt in milliseconds. */
9948
- const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
9949
- /** Maximum recovery attempts before emitting 'max_attempts_reached'. */
9950
- const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
9951
- /** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
9952
- const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
9953
- /** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
9954
- const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
9955
- /** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
9956
- const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
9957
- /** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
9958
- const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
9959
- /** RMS level threshold (0..1) above which the local participant is considered speaking. */
9960
- const VAD_THRESHOLD = .03;
9961
- /** Hold window in ms below the threshold before speaking$ flips back to false. */
9962
- const VAD_HOLD_MS = 250;
9963
- /** Whether to persist device selections to storage by default. */
9964
- const DEFAULT_PERSIST_DEVICE_SELECTION = true;
9965
- /** Whether to auto-apply device changes to active calls by default. */
9966
- const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
9967
- /** Storage keys for persisted device selections. */
9968
- const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
9969
- const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
9970
- const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
9971
- /** Whether to auto-mute video when the tab becomes hidden. */
9972
- const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
9973
- /** Whether to re-enumerate devices when the page becomes visible. */
9974
- const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
9975
- /** Whether to check peer connection health when the page becomes visible. */
9976
- const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
9977
- /** Whether automatic video degradation on low bandwidth is enabled. */
9978
- const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
9979
- /** Bitrate in kbps below which video is automatically disabled. */
9980
- const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
9981
- /** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
9982
- const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
9983
- /** Whether relay-only escalation is enabled as a last-resort recovery tier. */
9984
- const DEFAULT_ENABLE_RELAY_FALLBACK = true;
9985
- /** Whether to listen for browser online/offline/connection events. */
9986
- const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
9987
- /** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
9988
- const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
9989
- /** Default video track constraints applied when video is enabled without explicit constraints. */
9990
- const DEFAULT_VIDEO_CONSTRAINTS = {
9991
- width: { ideal: 1280 },
9992
- height: { ideal: 720 },
9993
- aspectRatio: 16 / 9
9994
- };
9995
- /** Whether stereo Opus is enabled by default. */
9996
- const DEFAULT_STEREO_AUDIO = false;
9997
- /** Max average bitrate for stereo Opus in bits per second. */
9998
- const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
9999
-
10000
10054
  //#endregion
10001
10055
  //#region src/utils/time.ts
10002
10056
  function fromSecToMs(seconds) {
@@ -10008,7 +10062,7 @@ function fromMsToSec(milliseconds) {
10008
10062
 
10009
10063
  //#endregion
10010
10064
  //#region src/containers/PreferencesContainer.ts
10011
- const logger$30 = getLogger();
10065
+ const logger$31 = getLogger();
10012
10066
  var PreferencesContainer = class PreferencesContainer {
10013
10067
  static get instance() {
10014
10068
  this._instance ??= new PreferencesContainer();
@@ -10670,7 +10724,7 @@ var ClientPreferences = class {
10670
10724
  if (!this._storage) return;
10671
10725
  const data = collectStoredPreferences();
10672
10726
  this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
10673
- logger$30.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
10727
+ logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
10674
10728
  });
10675
10729
  }
10676
10730
  /** Loads preferences from storage and applies them to the container. */
@@ -10679,7 +10733,7 @@ var ClientPreferences = class {
10679
10733
  this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
10680
10734
  if (stored) applyStoredPreferences(stored);
10681
10735
  }).catch((error) => {
10682
- logger$30.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
10736
+ logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
10683
10737
  });
10684
10738
  }
10685
10739
  };
@@ -10701,7 +10755,7 @@ function toError(value) {
10701
10755
  //#endregion
10702
10756
  //#region src/controllers/NavigatorDeviceController.ts
10703
10757
  var import_cjs$29 = require_cjs();
10704
- const logger$29 = getLogger();
10758
+ const logger$30 = getLogger();
10705
10759
  /** Maps a device kind to its storage key. */
10706
10760
  const DEVICE_STORAGE_KEYS = {
10707
10761
  audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
@@ -10723,7 +10777,7 @@ var NavigatorDeviceController = class extends Destroyable {
10723
10777
  super();
10724
10778
  this.webRTCApiProvider = webRTCApiProvider;
10725
10779
  this.deviceChangeHandler = () => {
10726
- logger$29.debug("[DeviceController] Device change detected");
10780
+ logger$30.debug("[DeviceController] Device change detected");
10727
10781
  this.enumerateDevices();
10728
10782
  };
10729
10783
  this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
@@ -10788,13 +10842,13 @@ var NavigatorDeviceController = class extends Destroyable {
10788
10842
  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$)));
10789
10843
  }
10790
10844
  get selectedAudioInputDevice$() {
10791
- 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$29.debug("[DeviceController] Selected audio input device changed:", info))));
10845
+ 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))));
10792
10846
  }
10793
10847
  get selectedAudioOutputDevice$() {
10794
- 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$29.debug("[DeviceController] Selected audio output device changed:", info))));
10848
+ 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))));
10795
10849
  }
10796
10850
  get selectedVideoInputDevice$() {
10797
- 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$29.debug("[DeviceController] Selected video input device changed:", info))));
10851
+ 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))));
10798
10852
  }
10799
10853
  get selectedAudioInputDevice() {
10800
10854
  if (this._audioInputDisabled$.value) return null;
@@ -10869,7 +10923,7 @@ var NavigatorDeviceController = class extends Destroyable {
10869
10923
  if (device) this.persistDeviceSelection("audioinput", device);
10870
10924
  }
10871
10925
  selectVideoInputDevice(device) {
10872
- logger$29.debug("[DeviceController] Setting selected video input device:", device);
10926
+ logger$30.debug("[DeviceController] Setting selected video input device:", device);
10873
10927
  if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
10874
10928
  const previous = this._selectedDevicesState$.value.videoinput;
10875
10929
  if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
@@ -10926,7 +10980,7 @@ var NavigatorDeviceController = class extends Destroyable {
10926
10980
  }
10927
10981
  const fromHistory = this._deviceHistory.findInHistory(kind, devices);
10928
10982
  if (fromHistory) {
10929
- logger$29.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
10983
+ logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
10930
10984
  this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
10931
10985
  return fromHistory;
10932
10986
  }
@@ -10979,7 +11033,7 @@ var NavigatorDeviceController = class extends Destroyable {
10979
11033
  try {
10980
11034
  await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
10981
11035
  } catch (error) {
10982
- logger$29.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
11036
+ logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
10983
11037
  }
10984
11038
  }
10985
11039
  async loadPersistedDevices() {
@@ -10995,7 +11049,7 @@ var NavigatorDeviceController = class extends Destroyable {
10995
11049
  [kind]: stored
10996
11050
  };
10997
11051
  } catch (error) {
10998
- logger$29.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
11052
+ logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
10999
11053
  }
11000
11054
  }
11001
11055
  /** Clears device history, persisted selections, and re-enumerates devices. */
@@ -11013,7 +11067,7 @@ var NavigatorDeviceController = class extends Destroyable {
11013
11067
  this.disableDeviceMonitoring();
11014
11068
  this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
11015
11069
  if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, import_cjs$29.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
11016
- logger$29.debug("[DeviceController] Polling devices due to interval");
11070
+ logger$30.debug("[DeviceController] Polling devices due to interval");
11017
11071
  this.enumerateDevices();
11018
11072
  });
11019
11073
  this.enumerateDevices();
@@ -11039,13 +11093,13 @@ var NavigatorDeviceController = class extends Destroyable {
11039
11093
  videoinput: []
11040
11094
  });
11041
11095
  this._devicesState$.next(devicesByKind);
11042
- logger$29.debug("[DeviceController] Devices enumerated:", {
11096
+ logger$30.debug("[DeviceController] Devices enumerated:", {
11043
11097
  audioInputs: devicesByKind.audioinput.length,
11044
11098
  audioOutputs: devicesByKind.audiooutput.length,
11045
11099
  videoInputs: devicesByKind.videoinput.length
11046
11100
  });
11047
11101
  } catch (error) {
11048
- logger$29.error("[DeviceController] Failed to enumerate devices:", error);
11102
+ logger$30.error("[DeviceController] Failed to enumerate devices:", error);
11049
11103
  this._errors$.next(toError(error));
11050
11104
  }
11051
11105
  }
@@ -11061,7 +11115,7 @@ var NavigatorDeviceController = class extends Destroyable {
11061
11115
  stream.getTracks().forEach((t) => t.stop());
11062
11116
  return capabilities;
11063
11117
  } catch (error) {
11064
- logger$29.error("[DeviceController] Failed to get device capabilities:", error);
11118
+ logger$30.error("[DeviceController] Failed to get device capabilities:", error);
11065
11119
  this._errors$.next(toError(error));
11066
11120
  throw error;
11067
11121
  }
@@ -11312,7 +11366,7 @@ var DependencyContainer = class {
11312
11366
 
11313
11367
  //#endregion
11314
11368
  //#region src/controllers/CryptoController.ts
11315
- const logger$28 = getLogger();
11369
+ const logger$29 = getLogger();
11316
11370
  const DPOP_DB_NAME = "sw-dpop";
11317
11371
  const DPOP_DB_VERSION = 1;
11318
11372
  const DPOP_STORE_NAME = "keys";
@@ -11371,7 +11425,7 @@ async function loadKeyPairFromDB() {
11371
11425
  tx.oncomplete = () => db.close();
11372
11426
  });
11373
11427
  } catch (error) {
11374
- logger$28.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
11428
+ logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
11375
11429
  return null;
11376
11430
  }
11377
11431
  }
@@ -11391,7 +11445,7 @@ async function saveKeyPairToDB(keyPair) {
11391
11445
  };
11392
11446
  });
11393
11447
  } catch (error) {
11394
- logger$28.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
11448
+ logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
11395
11449
  }
11396
11450
  }
11397
11451
  async function deleteKeyPairFromDB() {
@@ -11410,7 +11464,7 @@ async function deleteKeyPairFromDB() {
11410
11464
  };
11411
11465
  });
11412
11466
  } catch (error) {
11413
- logger$28.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
11467
+ logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
11414
11468
  }
11415
11469
  }
11416
11470
  /**
@@ -11470,13 +11524,13 @@ var CryptoController = class {
11470
11524
  this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
11471
11525
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
11472
11526
  this._initialized = true;
11473
- logger$28.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
11527
+ logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
11474
11528
  return this._fingerprint;
11475
11529
  } catch (error) {
11476
- logger$28.warn("[DPoP] Stored key pair unusable, generating new one:", error);
11530
+ logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
11477
11531
  await deleteKeyPairFromDB();
11478
11532
  }
11479
- logger$28.debug("[DPoP] Generating RSA key pair");
11533
+ logger$29.debug("[DPoP] Generating RSA key pair");
11480
11534
  this._keyPair = await crypto.subtle.generateKey({
11481
11535
  name: "RSASSA-PKCS1-v1_5",
11482
11536
  modulusLength: 2048,
@@ -11491,7 +11545,7 @@ var CryptoController = class {
11491
11545
  this._fingerprint = await computeJwkThumbprint(this._publicJwk);
11492
11546
  this._initialized = true;
11493
11547
  await saveKeyPairToDB(this._keyPair);
11494
- logger$28.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
11548
+ logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
11495
11549
  return this._fingerprint;
11496
11550
  }
11497
11551
  /**
@@ -11557,7 +11611,7 @@ var CryptoController = class {
11557
11611
  this._fingerprint = null;
11558
11612
  this._initialized = false;
11559
11613
  deleteKeyPairFromDB();
11560
- logger$28.debug("[DPoP] Controller destroyed");
11614
+ logger$29.debug("[DPoP] Controller destroyed");
11561
11615
  }
11562
11616
  get publicJwk() {
11563
11617
  if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
@@ -11581,7 +11635,7 @@ var CryptoController = class {
11581
11635
  //#endregion
11582
11636
  //#region src/controllers/NetworkMonitor.ts
11583
11637
  var import_cjs$28 = require_cjs();
11584
- const logger$27 = getLogger();
11638
+ const logger$28 = getLogger();
11585
11639
  /**
11586
11640
  * Safely check whether we are running in a browser environment
11587
11641
  * with `window` and the relevant event targets.
@@ -11638,7 +11692,7 @@ var NetworkMonitor = class extends Destroyable {
11638
11692
  }
11639
11693
  attachListeners() {
11640
11694
  if (!hasBrowserNetworkEvents()) {
11641
- logger$27.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
11695
+ logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
11642
11696
  return;
11643
11697
  }
11644
11698
  window.addEventListener("online", this._onOnline);
@@ -11646,7 +11700,7 @@ var NetworkMonitor = class extends Destroyable {
11646
11700
  const connection = getNetworkConnection();
11647
11701
  if (connection) connection.addEventListener("change", this._onConnectionChange);
11648
11702
  this._listenersAttached = true;
11649
- logger$27.debug("NetworkMonitor: event listeners attached");
11703
+ logger$28.debug("NetworkMonitor: event listeners attached");
11650
11704
  }
11651
11705
  removeListeners() {
11652
11706
  if (!this._listenersAttached) return;
@@ -11657,10 +11711,10 @@ var NetworkMonitor = class extends Destroyable {
11657
11711
  if (connection) connection.removeEventListener("change", this._onConnectionChange);
11658
11712
  }
11659
11713
  this._listenersAttached = false;
11660
- logger$27.debug("NetworkMonitor: event listeners removed");
11714
+ logger$28.debug("NetworkMonitor: event listeners removed");
11661
11715
  }
11662
11716
  handleOnline() {
11663
- logger$27.info("NetworkMonitor: browser went online");
11717
+ logger$28.info("NetworkMonitor: browser went online");
11664
11718
  this._isOnline$.next(true);
11665
11719
  this._networkChange$.next({
11666
11720
  type: "online",
@@ -11669,7 +11723,7 @@ var NetworkMonitor = class extends Destroyable {
11669
11723
  });
11670
11724
  }
11671
11725
  handleOffline() {
11672
- logger$27.info("NetworkMonitor: browser went offline");
11726
+ logger$28.info("NetworkMonitor: browser went offline");
11673
11727
  this._isOnline$.next(false);
11674
11728
  this._networkChange$.next({
11675
11729
  type: "offline",
@@ -11678,7 +11732,7 @@ var NetworkMonitor = class extends Destroyable {
11678
11732
  }
11679
11733
  handleConnectionChange() {
11680
11734
  const networkType = getNetworkType();
11681
- logger$27.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
11735
+ logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
11682
11736
  this._networkChange$.next({
11683
11737
  type: "connection_change",
11684
11738
  timestamp: Date.now(),
@@ -11794,7 +11848,7 @@ function getNavigatorMediaDevices() {
11794
11848
  //#endregion
11795
11849
  //#region src/controllers/PreflightRunner.ts
11796
11850
  var import_cjs$27 = require_cjs();
11797
- const logger$26 = getLogger();
11851
+ const logger$27 = getLogger();
11798
11852
  const DEFAULT_MEDIA_TEST_DURATION_S = 10;
11799
11853
  const ICE_GATHERING_TIMEOUT_MS = 1e4;
11800
11854
  const SIGNALING_RTT_TIMEOUT_MS = 5e3;
@@ -11843,7 +11897,7 @@ var PreflightRunner = class extends Destroyable {
11843
11897
  if (!this._options.skipMediaTest) try {
11844
11898
  bandwidth = await this.testMediaBandwidth(destination);
11845
11899
  } catch (error) {
11846
- logger$26.warn("[PreflightRunner] Media bandwidth test failed:", error);
11900
+ logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
11847
11901
  warnings.push("Media bandwidth test failed");
11848
11902
  }
11849
11903
  return {
@@ -11855,7 +11909,7 @@ var PreflightRunner = class extends Destroyable {
11855
11909
  warnings
11856
11910
  };
11857
11911
  } catch (error) {
11858
- logger$26.error("[PreflightRunner] Preflight test failed:", error);
11912
+ logger$27.error("[PreflightRunner] Preflight test failed:", error);
11859
11913
  throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
11860
11914
  } finally {
11861
11915
  this.destroy();
@@ -11886,7 +11940,7 @@ var PreflightRunner = class extends Destroyable {
11886
11940
  if (track.kind === "video" && track.readyState === "live") videoWorking = true;
11887
11941
  }
11888
11942
  } catch (error) {
11889
- logger$26.warn("[PreflightRunner] Device test failed:", error);
11943
+ logger$27.warn("[PreflightRunner] Device test failed:", error);
11890
11944
  } finally {
11891
11945
  if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
11892
11946
  }
@@ -11944,7 +11998,7 @@ var PreflightRunner = class extends Destroyable {
11944
11998
  rttMs
11945
11999
  };
11946
12000
  } catch (error) {
11947
- logger$26.warn("[PreflightRunner] ICE connectivity test failed:", error);
12001
+ logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
11948
12002
  return {
11949
12003
  type: "failed",
11950
12004
  turnReachable: false,
@@ -11992,7 +12046,7 @@ var PreflightRunner = class extends Destroyable {
11992
12046
  //#endregion
11993
12047
  //#region src/controllers/VisibilityController.ts
11994
12048
  var import_cjs$26 = require_cjs();
11995
- const logger$25 = getLogger();
12049
+ const logger$26 = getLogger();
11996
12050
  /**
11997
12051
  * Checks whether the document visibility API is available.
11998
12052
  */
@@ -12029,8 +12083,8 @@ var VisibilityController = class extends Destroyable {
12029
12083
  this._boundHandler = this._handleVisibilityChange.bind(this);
12030
12084
  if (this._hasVisibilityApi) {
12031
12085
  document.addEventListener("visibilitychange", this._boundHandler);
12032
- logger$25.debug("VisibilityController: listening for visibilitychange events");
12033
- } else logger$25.debug("VisibilityController: document visibility API not available, defaulting to visible");
12086
+ logger$26.debug("VisibilityController: listening for visibilitychange events");
12087
+ } else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
12034
12088
  }
12035
12089
  /**
12036
12090
  * Observable of the current visibility state.
@@ -12055,7 +12109,7 @@ var VisibilityController = class extends Destroyable {
12055
12109
  destroy() {
12056
12110
  if (this._hasVisibilityApi) {
12057
12111
  document.removeEventListener("visibilitychange", this._boundHandler);
12058
- logger$25.debug("VisibilityController: removed visibilitychange listener");
12112
+ logger$26.debug("VisibilityController: removed visibilitychange listener");
12059
12113
  }
12060
12114
  super.destroy();
12061
12115
  }
@@ -12073,7 +12127,7 @@ var VisibilityController = class extends Destroyable {
12073
12127
  timestamp: Date.now()
12074
12128
  };
12075
12129
  this._visibilityChange$.next(changeEvent);
12076
- logger$25.debug("VisibilityController: visibility changed", {
12130
+ logger$26.debug("VisibilityController: visibility changed", {
12077
12131
  from: previousState,
12078
12132
  to: newState
12079
12133
  });
@@ -12314,7 +12368,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
12314
12368
 
12315
12369
  //#endregion
12316
12370
  //#region src/managers/AttachManager.ts
12317
- const logger$24 = getLogger();
12371
+ const logger$25 = getLogger();
12318
12372
  var AttachManager = class {
12319
12373
  constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
12320
12374
  this.storage = storage;
@@ -12335,7 +12389,7 @@ var AttachManager = class {
12335
12389
  try {
12336
12390
  return await this.storage.getItem(this.attachKey) ?? {};
12337
12391
  } catch (error) {
12338
- logger$24.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
12392
+ logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
12339
12393
  return {};
12340
12394
  }
12341
12395
  }
@@ -12343,7 +12397,7 @@ var AttachManager = class {
12343
12397
  try {
12344
12398
  await this.storage.setItem(this.attachKey, attached);
12345
12399
  } catch (error) {
12346
- logger$24.warn("[AttachManager] Failed to write attached calls to storage", error);
12400
+ logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
12347
12401
  }
12348
12402
  }
12349
12403
  /**
@@ -12362,7 +12416,7 @@ var AttachManager = class {
12362
12416
  }
12363
12417
  async attach(call) {
12364
12418
  if (!call.to) {
12365
- logger$24.warn("[AttachManager] Skip attach for calls with no destination");
12419
+ logger$25.warn("[AttachManager] Skip attach for calls with no destination");
12366
12420
  return;
12367
12421
  }
12368
12422
  const destination = call.to;
@@ -12415,15 +12469,15 @@ var AttachManager = class {
12415
12469
  callId,
12416
12470
  ...options
12417
12471
  });
12418
- logger$24.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
12472
+ logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
12419
12473
  succeeded = true;
12420
12474
  break;
12421
12475
  } catch (error) {
12422
- logger$24.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
12476
+ logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
12423
12477
  if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
12424
12478
  }
12425
12479
  if (!succeeded) {
12426
- logger$24.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
12480
+ logger$25.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
12427
12481
  await this.detach({
12428
12482
  id: callId,
12429
12483
  mediaDirections: attachment.mediaDirections
@@ -13360,7 +13414,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
13360
13414
  position: false,
13361
13415
  meta: false,
13362
13416
  remove: false,
13363
- audioFlags: false
13417
+ audioFlags: false,
13418
+ denoise: false,
13419
+ lowbitrate: false
13364
13420
  };
13365
13421
  /**
13366
13422
  * Default call capabilities with no permissions
@@ -13433,7 +13489,9 @@ function computeMemberCapabilities(flags, memberType) {
13433
13489
  position: hasBooleanCapability(flags, memberType, null, "position"),
13434
13490
  meta: hasBooleanCapability(flags, memberType, null, "meta"),
13435
13491
  remove: hasBooleanCapability(flags, memberType, null, "remove"),
13436
- audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
13492
+ audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
13493
+ denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
13494
+ lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
13437
13495
  };
13438
13496
  }
13439
13497
  /**
@@ -13590,7 +13648,7 @@ function toggleHandraiseMethod(is) {
13590
13648
 
13591
13649
  //#endregion
13592
13650
  //#region src/core/entities/Participant.ts
13593
- const logger$23 = getLogger();
13651
+ const logger$24 = getLogger();
13594
13652
  const initialState = {};
13595
13653
  /**
13596
13654
  * Represents a participant in a call.
@@ -13600,9 +13658,9 @@ const initialState = {};
13600
13658
  * the local participant with additional device control.
13601
13659
  */
13602
13660
  var Participant = class extends Destroyable {
13603
- constructor(id, executeMethod, deviceController) {
13661
+ constructor(id, callExecuteMethod, deviceController) {
13604
13662
  super();
13605
- this.executeMethod = executeMethod;
13663
+ this.callExecuteMethod = callExecuteMethod;
13606
13664
  this.deviceController = deviceController;
13607
13665
  this._state$ = this.createBehaviorSubject(initialState);
13608
13666
  this.id = id;
@@ -13816,26 +13874,63 @@ var Participant = class extends Destroyable {
13816
13874
  get nodeId() {
13817
13875
  return this._state$.value.node_id;
13818
13876
  }
13877
+ /** Call ID for this participant's leg, or `undefined` if not available. */
13878
+ get callId() {
13879
+ return this._state$.value.call_id;
13880
+ }
13819
13881
  /** @internal */
13820
13882
  get value() {
13821
13883
  return this._state$.value;
13822
13884
  }
13885
+ /**
13886
+ * Target triple for member RPCs, built from the participant's own state.
13887
+ * The backend locates the member's session by the target `call_id`/`node_id`,
13888
+ * so this must always be the participant's own call context — never the
13889
+ * local call's id (issue #19400).
13890
+ *
13891
+ * Reading it doubles as a readiness probe: it throws until the first full
13892
+ * member event (`member.joined`/`member.updated` or the `call.joined`
13893
+ * roster) arrives, and never regresses afterwards.
13894
+ *
13895
+ * @throws {ParticipantNotReadyError} If the member state has not been
13896
+ * received yet (e.g. a participant first seen via `member.talking`) — an
13897
+ * empty call context can never address the member, so fail fast instead of
13898
+ * sending a doomed RPC.
13899
+ */
13900
+ get target() {
13901
+ const { call_id, node_id } = this._state$.value;
13902
+ if (!call_id || !node_id) throw new ParticipantNotReadyError(this.id);
13903
+ return {
13904
+ member_id: this.id,
13905
+ call_id,
13906
+ node_id
13907
+ };
13908
+ }
13909
+ /**
13910
+ * Executes a member RPC against this participant, injecting its own
13911
+ * {@link target} as the target.
13912
+ *
13913
+ * @throws {ParticipantNotReadyError} Via {@link target}, when the
13914
+ * member state has not been received yet.
13915
+ */
13916
+ async executeMethod(method, args) {
13917
+ return this.callExecuteMethod(this.target, method, args);
13918
+ }
13823
13919
  /** Toggles the deafened state (mutes/unmutes incoming audio). */
13824
13920
  async toggleDeaf() {
13825
- const method = toggleDeafMethod(this.deaf);
13826
- await this.executeMethod(this.id, method, {});
13921
+ await this.executeMethod(toggleDeafMethod(this.deaf), {});
13827
13922
  }
13828
13923
  /** Toggles the hand-raised state. */
13829
13924
  async toggleHandraise() {
13830
- await this.executeMethod(this.id, toggleHandraiseMethod(this.handraised), {});
13925
+ await this.executeMethod(toggleHandraiseMethod(this.handraised), {});
13831
13926
  }
13832
13927
  /** Mutes the participant's audio. */
13833
13928
  async mute() {
13834
- await this.executeMethod(this.id, "call.mute", { channels: ["audio"] });
13929
+ await this.executeMethod("call.mute", { channels: ["audio"] });
13835
13930
  }
13836
13931
  /** Unmutes the participant's audio. */
13837
13932
  async unmute() {
13838
- await this.executeMethod(this.id, "call.unmute", { channels: ["audio"] });
13933
+ await this.executeMethod("call.unmute", { channels: ["audio"] });
13839
13934
  }
13840
13935
  /** Toggles the participant's audio mute state. */
13841
13936
  async toggleMute() {
@@ -13843,11 +13938,11 @@ var Participant = class extends Destroyable {
13843
13938
  }
13844
13939
  /** Mutes the participant's video. */
13845
13940
  async muteVideo() {
13846
- await this.executeMethod(this.id, "call.mute", { channels: ["video"] });
13941
+ await this.executeMethod("call.mute", { channels: ["video"] });
13847
13942
  }
13848
13943
  /** Unmutes the participant's video. */
13849
13944
  async unmuteVideo() {
13850
- await this.executeMethod(this.id, "call.unmute", { channels: ["video"] });
13945
+ await this.executeMethod("call.unmute", { channels: ["video"] });
13851
13946
  }
13852
13947
  /** Toggles the participant's video mute state. */
13853
13948
  async toggleMuteVideo() {
@@ -13855,7 +13950,7 @@ var Participant = class extends Destroyable {
13855
13950
  }
13856
13951
  /** Toggles echo cancellation on the audio input. */
13857
13952
  async toggleEchoCancellation() {
13858
- await this.executeMethod(this.id, "call.audioflags.set", {
13953
+ await this.executeMethod("call.audioflags.set", {
13859
13954
  echo_cancellation: !this.echoCancellation,
13860
13955
  auto_gain: this.autoGain,
13861
13956
  noise_suppression: this.noiseSuppression
@@ -13863,7 +13958,7 @@ var Participant = class extends Destroyable {
13863
13958
  }
13864
13959
  /** Toggles automatic gain control on the audio input. */
13865
13960
  async toggleAudioInputAutoGain() {
13866
- await this.executeMethod(this.id, "call.audioflags.set", {
13961
+ await this.executeMethod("call.audioflags.set", {
13867
13962
  echo_cancellation: this.echoCancellation,
13868
13963
  auto_gain: !this.autoGain,
13869
13964
  noise_suppression: this.noiseSuppression
@@ -13871,14 +13966,15 @@ var Participant = class extends Destroyable {
13871
13966
  }
13872
13967
  /** Toggles noise suppression on the audio input. */
13873
13968
  async toggleNoiseSuppression() {
13874
- await this.executeMethod(this.id, "call.audioflags.set", {
13969
+ await this.executeMethod("call.audioflags.set", {
13875
13970
  echo_cancellation: this.echoCancellation,
13876
13971
  auto_gain: this.autoGain,
13877
13972
  noise_suppression: !this.noiseSuppression
13878
13973
  });
13879
13974
  }
13975
+ /** Toggles low-bitrate mode for this participant's media. */
13880
13976
  async toggleLowbitrate() {
13881
- throw new UnimplementedError();
13977
+ await this.executeMethod("call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
13882
13978
  }
13883
13979
  /**
13884
13980
  * Adjusts the **conference-only** microphone energy gate / sensitivity level
@@ -13894,7 +13990,7 @@ var Participant = class extends Destroyable {
13894
13990
  * (integer, larger values are more sensitive).
13895
13991
  */
13896
13992
  async setAudioInputSensitivity(value) {
13897
- await this.executeMethod(this.id, "call.microphone.sensitivity.set", { sensitivity: value });
13993
+ await this.executeMethod("call.microphone.sensitivity.set", { sensitivity: value });
13898
13994
  }
13899
13995
  /**
13900
13996
  * Sets the **server-side** microphone volume on this participant's bridged
@@ -13907,7 +14003,7 @@ var Participant = class extends Destroyable {
13907
14003
  * @param value - Volume level (0-100).
13908
14004
  */
13909
14005
  async setAudioInputVolume(value) {
13910
- await this.executeMethod(this.id, "call.microphone.volume.set", { volume: value });
14006
+ await this.executeMethod("call.microphone.volume.set", { volume: value });
13911
14007
  }
13912
14008
  /**
13913
14009
  * Sets the **server-side** speaker volume on this participant's bridged call
@@ -13921,28 +14017,31 @@ var Participant = class extends Destroyable {
13921
14017
  * @param value - Volume level (0-100).
13922
14018
  */
13923
14019
  async setAudioOutputVolume(value) {
13924
- await this.executeMethod(this.id, "call.speaker.volume.set", { volume: value });
14020
+ await this.executeMethod("call.speaker.volume.set", { volume: value });
13925
14021
  }
13926
14022
  /**
13927
14023
  * Sets the participant's position in the video layout.
14024
+ *
14025
+ * Requires the `member.position` capability. The gateway requires a
14026
+ * `targets` array of `{ target, position }` entries (issue #19400). A
14027
+ * resolved promise does not guarantee a visible change: the backend silently
14028
+ * returns `200` (no-op) for non-conference targets.
14029
+ *
13928
14030
  * @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
13929
14031
  */
13930
14032
  async setPosition(value) {
13931
- await this.executeMethod(this.id, "call.member.position.set", { position: value });
14033
+ await this.executeMethod("call.member.position.set", { targets: [{
14034
+ target: this.target,
14035
+ position: value
14036
+ }] });
13932
14037
  }
13933
14038
  /** Removes this participant from the call. */
13934
14039
  async remove() {
13935
- const state = this._state$.value;
13936
- const target = {
13937
- member_id: this.id,
13938
- call_id: state.call_id ?? "",
13939
- node_id: state.node_id ?? ""
13940
- };
13941
- await this.executeMethod(target, "call.member.remove", {});
14040
+ await this.executeMethod("call.member.remove", { targets: [this.target] });
13942
14041
  }
13943
14042
  /** Ends the call for this participant. */
13944
14043
  async end() {
13945
- await this.executeMethod(this.id, "call.end", {});
14044
+ await this.executeMethod("call.end", {});
13946
14045
  }
13947
14046
  /**
13948
14047
  * Replaces custom metadata for this participant.
@@ -13962,7 +14061,7 @@ var Participant = class extends Destroyable {
13962
14061
  }
13963
14062
  /** Destroys the participant, releasing all subscriptions and references. */
13964
14063
  destroy() {
13965
- this.executeMethod = void 0;
14064
+ this.callExecuteMethod = void 0;
13966
14065
  super.destroy();
13967
14066
  }
13968
14067
  };
@@ -13974,8 +14073,8 @@ var Participant = class extends Destroyable {
13974
14073
  */
13975
14074
  var SelfParticipant = class extends Participant {
13976
14075
  /** @internal */
13977
- constructor(id, executeMethod, vertoManager, deviceController) {
13978
- super(id, executeMethod, deviceController);
14076
+ constructor(id, callExecuteMethod, vertoManager, deviceController) {
14077
+ super(id, callExecuteMethod, deviceController);
13979
14078
  this.vertoManager = vertoManager;
13980
14079
  this._studioAudio$ = this.createBehaviorSubject(false);
13981
14080
  this.capabilities = new SelfCapabilities();
@@ -13999,7 +14098,7 @@ var SelfParticipant = class extends Participant {
13999
14098
  async enableStudioAudio() {
14000
14099
  if (this._studioAudio$.value) return;
14001
14100
  this._studioAudio$.next(true);
14002
- await this.executeMethod(this.id, "call.audioflags.set", {
14101
+ await this.executeMethod("call.audioflags.set", {
14003
14102
  echo_cancellation: false,
14004
14103
  auto_gain: false,
14005
14104
  noise_suppression: false
@@ -14012,18 +14111,27 @@ var SelfParticipant = class extends Participant {
14012
14111
  async disableStudioAudio() {
14013
14112
  if (!this._studioAudio$.value) return;
14014
14113
  this._studioAudio$.next(false);
14015
- await this.executeMethod(this.id, "call.audioflags.set", {
14114
+ await this.executeMethod("call.audioflags.set", {
14016
14115
  echo_cancellation: true,
14017
14116
  auto_gain: true,
14018
14117
  noise_suppression: true
14019
14118
  });
14020
14119
  }
14021
- /** Starts sharing the local screen. */
14120
+ /**
14121
+ * Starts sharing the local screen.
14122
+ *
14123
+ * The call is unaffected when acquisition fails.
14124
+ *
14125
+ * @throws The raw `getDisplayMedia` error. A dismissed picker or a
14126
+ * permission denial rejects with a `NotAllowedError` `DOMException` —
14127
+ * inspect `error.name` to tell benign cancels apart from real failures.
14128
+ */
14022
14129
  async startScreenShare() {
14023
14130
  try {
14024
14131
  await this.vertoManager.addScreenMedia();
14025
14132
  } catch (error) {
14026
- logger$23.error("[Participant.startScreenShare] Screen share error:", error);
14133
+ logger$24.error("[Participant.startScreenShare] Screen share error:", error);
14134
+ throw error;
14027
14135
  }
14028
14136
  }
14029
14137
  /** Observable of the current screen share status. */
@@ -14038,12 +14146,20 @@ var SelfParticipant = class extends Participant {
14038
14146
  async stopScreenShare() {
14039
14147
  return this.vertoManager.removeScreenMedia();
14040
14148
  }
14041
- /** Adds an additional media input device to the call. */
14149
+ /**
14150
+ * Adds an additional media input device to the call.
14151
+ *
14152
+ * The call is unaffected when acquisition fails.
14153
+ *
14154
+ * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
14155
+ * permission denial) — inspect `error.name` to decide how to react.
14156
+ */
14042
14157
  async addAdditionalDevice(options) {
14043
14158
  try {
14044
14159
  await this.vertoManager.addInputDevice(options);
14045
14160
  } catch (error) {
14046
- logger$23.error("[Participant.startScreenShare] Screen share error:", error);
14161
+ logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
14162
+ throw error;
14047
14163
  }
14048
14164
  }
14049
14165
  /** Removes an additional media input device by ID. */
@@ -14105,7 +14221,7 @@ var SelfParticipant = class extends Participant {
14105
14221
  */
14106
14222
  exitStudioModeIfActive() {
14107
14223
  if (this._studioAudio$.value) {
14108
- logger$23.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
14224
+ logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
14109
14225
  this._studioAudio$.next(false);
14110
14226
  }
14111
14227
  }
@@ -14129,7 +14245,7 @@ var SelfParticipant = class extends Participant {
14129
14245
  try {
14130
14246
  await super.mute();
14131
14247
  } catch (error) {
14132
- logger$23.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
14248
+ logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
14133
14249
  } finally {
14134
14250
  this.vertoManager.muteMainAudioInputDevice();
14135
14251
  }
@@ -14139,7 +14255,7 @@ var SelfParticipant = class extends Participant {
14139
14255
  try {
14140
14256
  await super.unmute();
14141
14257
  } catch (error) {
14142
- logger$23.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
14258
+ logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
14143
14259
  } finally {
14144
14260
  await this.vertoManager.unmuteMainAudioInputDevice();
14145
14261
  }
@@ -14149,7 +14265,7 @@ var SelfParticipant = class extends Participant {
14149
14265
  try {
14150
14266
  await super.muteVideo();
14151
14267
  } catch (error) {
14152
- logger$23.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
14268
+ logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
14153
14269
  } finally {
14154
14270
  this.vertoManager.muteMainVideoInputDevice();
14155
14271
  }
@@ -14159,7 +14275,7 @@ var SelfParticipant = class extends Participant {
14159
14275
  try {
14160
14276
  await super.unmuteVideo();
14161
14277
  } catch (error) {
14162
- logger$23.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
14278
+ logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
14163
14279
  } finally {
14164
14280
  await this.vertoManager.unmuteMainVideoInputDevice();
14165
14281
  }
@@ -14364,7 +14480,7 @@ function filterAs(predicate, resultPath) {
14364
14480
  //#endregion
14365
14481
  //#region src/operators/throwOnRPCError.ts
14366
14482
  var import_cjs$21 = require_cjs();
14367
- const logger$22 = getLogger();
14483
+ const logger$23 = getLogger();
14368
14484
  /**
14369
14485
  * RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
14370
14486
  * Passes successful responses through unchanged.
@@ -14372,14 +14488,14 @@ const logger$22 = getLogger();
14372
14488
  function throwOnRPCError() {
14373
14489
  return (0, import_cjs$21.map)((response) => {
14374
14490
  if (response.error) {
14375
- logger$22.error("[throwOnRPCError] RPC error response:", {
14491
+ logger$23.error("[throwOnRPCError] RPC error response:", {
14376
14492
  code: response.error.code,
14377
14493
  message: response.error.message,
14378
14494
  data: response.error.data
14379
14495
  });
14380
14496
  throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
14381
14497
  }
14382
- logger$22.debug("[throwOnRPCError] RPC successful response:", response);
14498
+ logger$23.debug("[throwOnRPCError] RPC successful response:", response);
14383
14499
  return response;
14384
14500
  });
14385
14501
  }
@@ -14387,7 +14503,7 @@ function throwOnRPCError() {
14387
14503
  //#endregion
14388
14504
  //#region src/managers/CallEventsManager.ts
14389
14505
  var import_cjs$20 = require_cjs();
14390
- const logger$21 = getLogger();
14506
+ const logger$22 = getLogger();
14391
14507
  const initialSessionState = {};
14392
14508
  /** @internal */
14393
14509
  var CallEventsManager = class extends Destroyable {
@@ -14491,7 +14607,7 @@ var CallEventsManager = class extends Destroyable {
14491
14607
  }
14492
14608
  initSubscriptions() {
14493
14609
  this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
14494
- logger$21.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
14610
+ logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
14495
14611
  callId: callJoinedEvent.call_id,
14496
14612
  roomSessionId: callJoinedEvent.room_session_id
14497
14613
  });
@@ -14518,19 +14634,19 @@ var CallEventsManager = class extends Destroyable {
14518
14634
  if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
14519
14635
  });
14520
14636
  this.subscribeTo(this.memberUpdates$, (member) => {
14521
- logger$21.debug("[CallEventsManager] Handling member update event for member ID:", member);
14637
+ logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
14522
14638
  this.upsertParticipant(member);
14523
14639
  });
14524
14640
  this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
14525
- logger$21.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
14641
+ logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
14526
14642
  const participants = { ...this._participants$.value };
14527
14643
  if (memberLeftEvent.member.member_id in participants) {
14528
14644
  delete participants[memberLeftEvent.member.member_id];
14529
14645
  this._participants$.next(participants);
14530
- } else logger$21.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
14646
+ } else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
14531
14647
  });
14532
14648
  this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
14533
- logger$21.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
14649
+ logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
14534
14650
  const roomSession = callUpdatedEvent.room_session;
14535
14651
  this._sessionState$.next({
14536
14652
  ...this._sessionState$.value,
@@ -14545,7 +14661,7 @@ var CallEventsManager = class extends Destroyable {
14545
14661
  });
14546
14662
  });
14547
14663
  this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
14548
- logger$21.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
14664
+ logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
14549
14665
  this._sessionState$.next({
14550
14666
  ...this._sessionState$.value,
14551
14667
  layout_name: layoutChangedEvent.id,
@@ -14555,10 +14671,10 @@ var CallEventsManager = class extends Destroyable {
14555
14671
  });
14556
14672
  }
14557
14673
  updateParticipantPositions(layoutChangedEvent) {
14558
- if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$21.warn("[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.");
14674
+ 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.");
14559
14675
  layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
14560
14676
  if (!(layer.member_id in this._participants$.value)) {
14561
- logger$21.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
14677
+ logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
14562
14678
  return false;
14563
14679
  }
14564
14680
  return true;
@@ -14581,7 +14697,7 @@ var CallEventsManager = class extends Destroyable {
14581
14697
  layouts: response.result.layouts
14582
14698
  });
14583
14699
  }).catch((error) => {
14584
- logger$21.error("[CallEventsManager] Error fetching layouts:", error);
14700
+ logger$22.error("[CallEventsManager] Error fetching layouts:", error);
14585
14701
  });
14586
14702
  }
14587
14703
  updateParticipants(members) {
@@ -14597,7 +14713,7 @@ var CallEventsManager = class extends Destroyable {
14597
14713
  }
14598
14714
  const participant = this._participants$.value[member.member_id];
14599
14715
  const oldValue = participant.value;
14600
- logger$21.debug("[CallEventsManager] Updating participant:", member.member_id, {
14716
+ logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
14601
14717
  oldValue,
14602
14718
  newValue: member
14603
14719
  });
@@ -14610,17 +14726,17 @@ var CallEventsManager = class extends Destroyable {
14610
14726
  }
14611
14727
  get callJoinedEvent$() {
14612
14728
  return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, import_cjs$20.filter)(isCallJoinedPayload), (0, import_cjs$20.tap)((event) => {
14613
- logger$21.debug("[CallEventsManager] Call joined event:", event);
14729
+ logger$22.debug("[CallEventsManager] Call joined event:", event);
14614
14730
  })));
14615
14731
  }
14616
14732
  get layoutChangedEvent$() {
14617
14733
  return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(filterAs(isLayoutChangedPayload, "layout"), (0, import_cjs$20.tap)((event) => {
14618
- logger$21.debug("[CallEventsManager] Layout changed event:", event);
14734
+ logger$22.debug("[CallEventsManager] Layout changed event:", event);
14619
14735
  })));
14620
14736
  }
14621
14737
  get memberUpdates$() {
14622
14738
  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) => {
14623
- logger$21.debug("[CallEventsManager] Member update event:", event);
14739
+ logger$22.debug("[CallEventsManager] Member update event:", event);
14624
14740
  })));
14625
14741
  }
14626
14742
  destroy() {
@@ -14877,7 +14993,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
14877
14993
  //#endregion
14878
14994
  //#region src/controllers/ICEGatheringController.ts
14879
14995
  var import_cjs$19 = require_cjs();
14880
- const logger$20 = getLogger();
14996
+ const logger$21 = getLogger();
14881
14997
  var ICEGatheringController = class extends Destroyable {
14882
14998
  constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
14883
14999
  super();
@@ -14885,23 +15001,23 @@ var ICEGatheringController = class extends Destroyable {
14885
15001
  this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
14886
15002
  this.onicegatheringstatechangeHandler = () => {
14887
15003
  const { iceGatheringState } = this.peerConnection;
14888
- logger$20.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
15004
+ logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
14889
15005
  if (iceGatheringState === "gathering") this._iceCandidatesState.next({
14890
15006
  state: "gathering",
14891
15007
  validSDP: false
14892
15008
  });
14893
15009
  };
14894
15010
  this.onicecandidateHandler = (event) => {
14895
- logger$20.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
15011
+ logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
14896
15012
  this.removeTimer("iceCandidateTimer");
14897
15013
  if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
14898
15014
  if (this.peerConnection.iceGatheringState !== "complete") {
14899
- logger$20.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
15015
+ logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
14900
15016
  this.handleICECandidateTimeout();
14901
15017
  }
14902
15018
  }, this.iceCandidateTimeout);
14903
15019
  else {
14904
- logger$20.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
15020
+ logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
14905
15021
  this.removeTimer("iceGatheringTimer");
14906
15022
  this.handleICEGatheringComplete();
14907
15023
  }
@@ -14919,7 +15035,7 @@ var ICEGatheringController = class extends Destroyable {
14919
15035
  this.setupEventListeners();
14920
15036
  this.iceGatheringTimer = setTimeout(() => {
14921
15037
  if (this.peerConnection.iceGatheringState !== "complete") {
14922
- logger$20.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
15038
+ logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
14923
15039
  this.handleICEGatheringTimeout();
14924
15040
  }
14925
15041
  }, this.iceGatheringTimeout);
@@ -14946,9 +15062,9 @@ var ICEGatheringController = class extends Destroyable {
14946
15062
  this.relayOnly = value;
14947
15063
  }
14948
15064
  handleICEGatheringComplete() {
14949
- logger$20.debug("[ICEGatheringController] Handling ICE gathering complete");
14950
- logger$20.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
14951
- logger$20.debug("[ICEGatheringController] ICE gathering complete");
15065
+ logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
15066
+ logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
15067
+ logger$21.debug("[ICEGatheringController] ICE gathering complete");
14952
15068
  this._iceCandidatesState.next({
14953
15069
  state: "complete",
14954
15070
  validSDP: this.hasValidLocalDescriptionSDP
@@ -14964,21 +15080,21 @@ var ICEGatheringController = class extends Destroyable {
14964
15080
  this.removeTimer("iceGatheringTimer");
14965
15081
  const validSDP = this.hasValidLocalDescriptionSDP;
14966
15082
  if (validSDP) {
14967
- logger$20.debug("[ICEGatheringController] Local SDP is valid");
15083
+ logger$21.debug("[ICEGatheringController] Local SDP is valid");
14968
15084
  this._iceCandidatesState.next({
14969
15085
  state: "timeout",
14970
15086
  validSDP
14971
15087
  });
14972
15088
  this.stopGathering();
14973
- } else logger$20.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
15089
+ } else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
14974
15090
  }
14975
15091
  handleICECandidateTimeout() {
14976
15092
  if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
14977
- logger$20.warn("[ICEGatheringController] ICE candidate timeout");
15093
+ logger$21.warn("[ICEGatheringController] ICE candidate timeout");
14978
15094
  const validSDP = this.hasValidLocalDescriptionSDP;
14979
15095
  if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
14980
15096
  else {
14981
- logger$20.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
15097
+ logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
14982
15098
  this._iceCandidatesState.next({
14983
15099
  state: "timeout",
14984
15100
  validSDP
@@ -14987,7 +15103,7 @@ var ICEGatheringController = class extends Destroyable {
14987
15103
  }
14988
15104
  }
14989
15105
  restartICEGatheringWithRelayOnly() {
14990
- logger$20.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
15106
+ logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
14991
15107
  this.relayOnly = true;
14992
15108
  this.peerConnection.setConfiguration({
14993
15109
  ...this.peerConnection.getConfiguration(),
@@ -15002,7 +15118,7 @@ var ICEGatheringController = class extends Destroyable {
15002
15118
  }
15003
15119
  }
15004
15120
  clearAllTimers() {
15005
- logger$20.debug("[ICEGatheringController] Clearing all timers");
15121
+ logger$21.debug("[ICEGatheringController] Clearing all timers");
15006
15122
  this.removeTimer("iceGatheringTimer");
15007
15123
  this.removeTimer("iceCandidateTimer");
15008
15124
  }
@@ -15011,7 +15127,7 @@ var ICEGatheringController = class extends Destroyable {
15011
15127
  this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
15012
15128
  }
15013
15129
  destroy() {
15014
- logger$20.debug("[ICEGatheringController] Destroying ICEGatheringController");
15130
+ logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
15015
15131
  this.clearAllTimers();
15016
15132
  this.removeEventListeners();
15017
15133
  super.destroy();
@@ -15021,7 +15137,7 @@ var ICEGatheringController = class extends Destroyable {
15021
15137
  //#endregion
15022
15138
  //#region src/controllers/LocalAudioPipeline.ts
15023
15139
  var import_cjs$18 = require_cjs();
15024
- const logger$19 = getLogger();
15140
+ const logger$20 = getLogger();
15025
15141
  /**
15026
15142
  * Web Audio pipeline for the local microphone stream.
15027
15143
  *
@@ -15133,7 +15249,7 @@ var LocalAudioPipeline = class extends Destroyable {
15133
15249
  try {
15134
15250
  this._inputSource.disconnect();
15135
15251
  } catch (error) {
15136
- logger$19.debug("[LocalAudioPipeline] input disconnect warning:", error);
15252
+ logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
15137
15253
  }
15138
15254
  this._inputSource = null;
15139
15255
  }
@@ -15143,7 +15259,7 @@ var LocalAudioPipeline = class extends Destroyable {
15143
15259
  this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
15144
15260
  this._inputSource.connect(this._gainNode);
15145
15261
  if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
15146
- logger$19.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
15262
+ logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
15147
15263
  });
15148
15264
  }
15149
15265
  destroy() {
@@ -15158,7 +15274,7 @@ var LocalAudioPipeline = class extends Destroyable {
15158
15274
  this._analyser.disconnect();
15159
15275
  } catch {}
15160
15276
  this._audioContext.close().catch((error) => {
15161
- logger$19.debug("[LocalAudioPipeline] audio context close warning:", error);
15277
+ logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
15162
15278
  });
15163
15279
  super.destroy();
15164
15280
  }
@@ -15185,7 +15301,7 @@ var LocalAudioPipeline = class extends Destroyable {
15185
15301
  //#endregion
15186
15302
  //#region src/controllers/LocalStreamController.ts
15187
15303
  var import_cjs$17 = require_cjs();
15188
- const logger$18 = getLogger();
15304
+ const logger$19 = getLogger();
15189
15305
  var LocalStreamController = class extends Destroyable {
15190
15306
  constructor(options) {
15191
15307
  super();
@@ -15223,26 +15339,26 @@ var LocalStreamController = class extends Destroyable {
15223
15339
  * Build the local media stream based on the provided options.
15224
15340
  */
15225
15341
  async buildLocalStream() {
15226
- logger$18.debug("[LocalStreamController] Building local media stream.");
15342
+ logger$19.debug("[LocalStreamController] Building local media stream.");
15227
15343
  let stream;
15228
15344
  if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
15229
15345
  const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
15230
15346
  stream = new MediaStream(tracks);
15231
15347
  } else if (this.options.propose === "screenshare") {
15232
- logger$18.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
15348
+ logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
15233
15349
  stream = await this.options.getDisplayMedia({
15234
15350
  video: true,
15235
15351
  audio: Boolean(this.options.inputAudioDeviceConstraints)
15236
15352
  });
15237
- logger$18.debug("[LocalStreamController] Screen share media obtained:", stream);
15353
+ logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
15238
15354
  } else {
15239
15355
  const constraints = {
15240
15356
  audio: this.options.inputAudioDeviceConstraints,
15241
15357
  video: this.options.inputVideoDeviceConstraints
15242
15358
  };
15243
- logger$18.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
15359
+ logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
15244
15360
  stream = await this.options.getUserMedia(constraints);
15245
- logger$18.debug("[LocalStreamController] User media obtained:", stream);
15361
+ logger$19.debug("[LocalStreamController] User media obtained:", stream);
15246
15362
  }
15247
15363
  this._localStream$.next(stream);
15248
15364
  this._localAudioTracks$.next(stream.getAudioTracks());
@@ -15261,7 +15377,7 @@ var LocalStreamController = class extends Destroyable {
15261
15377
  this._localStream$.next(localStream);
15262
15378
  if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
15263
15379
  else this._localAudioTracks$.next(localStream.getAudioTracks());
15264
- logger$18.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
15380
+ logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
15265
15381
  return localStream;
15266
15382
  }
15267
15383
  /**
@@ -15273,7 +15389,7 @@ var LocalStreamController = class extends Destroyable {
15273
15389
  const stream = this._localStream$.value;
15274
15390
  const track = stream?.getTracks().find((t) => t.id === trackId);
15275
15391
  if (!track) {
15276
- logger$18.debug(`[LocalStreamController] track not found: ${trackId}`);
15392
+ logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
15277
15393
  return;
15278
15394
  }
15279
15395
  track.removeEventListener("ended", this.mediaTrackEndedHandler);
@@ -15282,7 +15398,7 @@ var LocalStreamController = class extends Destroyable {
15282
15398
  this._localStream$.next(stream);
15283
15399
  if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
15284
15400
  else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
15285
- logger$18.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
15401
+ logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
15286
15402
  return track;
15287
15403
  }
15288
15404
  /**
@@ -15317,7 +15433,7 @@ var LocalStreamController = class extends Destroyable {
15317
15433
  */
15318
15434
  stopAllTracks() {
15319
15435
  this._localStream$.value?.getTracks().forEach((track) => {
15320
- logger$18.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
15436
+ logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
15321
15437
  track.removeEventListener("ended", this.mediaTrackEndedHandler);
15322
15438
  track.stop();
15323
15439
  });
@@ -15333,7 +15449,7 @@ var LocalStreamController = class extends Destroyable {
15333
15449
 
15334
15450
  //#endregion
15335
15451
  //#region src/controllers/TransceiverController.ts
15336
- const logger$17 = getLogger();
15452
+ const logger$18 = getLogger();
15337
15453
  const getDirection = (send, recv) => {
15338
15454
  if (send && recv) return "sendrecv";
15339
15455
  else if (send && !recv) return "sendonly";
@@ -15441,7 +15557,7 @@ var TransceiverController = class extends Destroyable {
15441
15557
  sendEncodings: isAudio ? void 0 : this.sendEncodings,
15442
15558
  streams: direction === "recvonly" ? void 0 : [localStream]
15443
15559
  };
15444
- logger$17.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
15560
+ logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
15445
15561
  transceiver,
15446
15562
  transceiverParams
15447
15563
  });
@@ -15449,11 +15565,11 @@ var TransceiverController = class extends Destroyable {
15449
15565
  await transceiver.sender.replaceTrack(track);
15450
15566
  transceiver.direction = transceiverParams.direction;
15451
15567
  if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
15452
- logger$17.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
15568
+ logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
15453
15569
  transceiver.sender.setStreams(...transceiverParams.streams);
15454
15570
  }
15455
15571
  } else {
15456
- logger$17.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
15572
+ logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
15457
15573
  this.peerConnection.addTransceiver(track, transceiverParams);
15458
15574
  }
15459
15575
  }
@@ -15467,13 +15583,13 @@ var TransceiverController = class extends Destroyable {
15467
15583
  if (options.updateTransceiverDirection) transceiver.direction = "inactive";
15468
15584
  }
15469
15585
  } catch (error) {
15470
- logger$17.error("[TransceiverController] stopTrackSender error", kind, error);
15586
+ logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
15471
15587
  this.options.onError?.(new MediaTrackError("stopTrackSender", kind, error));
15472
15588
  }
15473
15589
  }
15474
15590
  async restoreTrackSender(kind) {
15475
15591
  try {
15476
- logger$17.debug("[TransceiverController] restoreTrackSender called", kind);
15592
+ logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
15477
15593
  const constraints = {};
15478
15594
  const transceivers = this.transceiverByKind(kind);
15479
15595
  for (const transceiver of transceivers) {
@@ -15483,23 +15599,23 @@ var TransceiverController = class extends Destroyable {
15483
15599
  if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
15484
15600
  }
15485
15601
  }
15486
- logger$17.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
15602
+ logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
15487
15603
  if (Object.keys(constraints).length === 0) {
15488
- logger$17.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
15604
+ logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
15489
15605
  return;
15490
15606
  }
15491
15607
  const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
15492
- logger$17.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
15608
+ logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
15493
15609
  for (const newTrack of newTracks) {
15494
15610
  this.options.localStreamController.addTrack(newTrack);
15495
15611
  const trackKind = newTrack.kind;
15496
15612
  const transceiverOfKind = this.transceiverByKind(trackKind)[0];
15497
15613
  transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
15498
- logger$17.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
15614
+ logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
15499
15615
  await transceiverOfKind.sender.replaceTrack(newTrack);
15500
15616
  }
15501
15617
  } catch (error) {
15502
- logger$17.error("[TransceiverController] restoreTrackSender error", kind, error);
15618
+ logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
15503
15619
  this.options.onError?.(new MediaTrackError("restoreTrackSender", kind, error));
15504
15620
  }
15505
15621
  }
@@ -15540,14 +15656,14 @@ var TransceiverController = class extends Destroyable {
15540
15656
  };
15541
15657
  try {
15542
15658
  await track.applyConstraints(constraintsToApply);
15543
- logger$17.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
15544
- logger$17.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
15659
+ logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
15660
+ logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
15545
15661
  } catch (error) {
15546
- logger$17.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
15662
+ logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
15547
15663
  try {
15548
15664
  await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
15549
15665
  } catch (fallbackError) {
15550
- logger$17.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
15666
+ logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
15551
15667
  this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
15552
15668
  }
15553
15669
  }
@@ -15575,7 +15691,7 @@ var TransceiverController = class extends Destroyable {
15575
15691
  if (!newTrack) throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
15576
15692
  await sender.replaceTrack(newTrack);
15577
15693
  this.options.localStreamController.addTrack(newTrack);
15578
- logger$17.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
15694
+ logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
15579
15695
  }
15580
15696
  getMediaDirections() {
15581
15697
  if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
@@ -15606,7 +15722,7 @@ var TransceiverController = class extends Destroyable {
15606
15722
  //#endregion
15607
15723
  //#region src/controllers/RTCPeerConnectionController.ts
15608
15724
  var import_cjs$16 = require_cjs();
15609
- const logger$16 = getLogger();
15725
+ const logger$17 = getLogger();
15610
15726
  var RTCPeerConnectionController = class extends Destroyable {
15611
15727
  constructor(options = {}, remoteSessionDescription, deviceController) {
15612
15728
  super();
@@ -15622,43 +15738,43 @@ var RTCPeerConnectionController = class extends Destroyable {
15622
15738
  this.oniceconnectionstatechangeHandler = () => {
15623
15739
  if (this.peerConnection) {
15624
15740
  const { iceConnectionState } = this.peerConnection;
15625
- logger$16.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
15741
+ logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
15626
15742
  this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
15627
15743
  }
15628
15744
  };
15629
15745
  this.onconnectionstatechangeHandler = () => {
15630
15746
  if (this.peerConnection) {
15631
15747
  const { connectionState } = this.peerConnection;
15632
- logger$16.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
15748
+ logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
15633
15749
  if (connectionState === "connected") this.removeConnectionTimer();
15634
15750
  this._connectionState$.next(this.peerConnection.connectionState);
15635
15751
  }
15636
15752
  };
15637
15753
  this.onsignalingstatechangeHandler = () => {
15638
- logger$16.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
15754
+ logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
15639
15755
  };
15640
15756
  this.onicegatheringstatechangeHandler = () => {
15641
15757
  if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
15642
15758
  };
15643
15759
  this.onnegotiationneededHandler = (event) => {
15644
- logger$16.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
15760
+ logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
15645
15761
  this.negotiationNeeded$.next();
15646
15762
  };
15647
15763
  this.updateSelectedInputDevice = async (kind, deviceInfo) => {
15648
15764
  try {
15649
15765
  const { localStream } = this;
15650
15766
  if (!localStream) {
15651
- logger$16.warn("[RTCPeerConnectionController] No local stream available to update input device.");
15767
+ logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
15652
15768
  return;
15653
15769
  }
15654
- logger$16.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
15770
+ logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
15655
15771
  const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
15656
15772
  if (track) {
15657
15773
  this.transceiverController?.stopTrackSender(kind);
15658
15774
  this.localStreamController.removeTrack(track.id);
15659
- logger$16.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
15775
+ logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
15660
15776
  if (!deviceInfo) {
15661
- logger$16.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
15777
+ logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
15662
15778
  return;
15663
15779
  }
15664
15780
  const streamTrack = (await this.getUserMedia({ [kind]: {
@@ -15666,16 +15782,16 @@ var RTCPeerConnectionController = class extends Destroyable {
15666
15782
  ...this.deviceController.deviceInfoToConstraints(deviceInfo)
15667
15783
  } })).getTracks().find((t) => t.kind === kind);
15668
15784
  if (streamTrack) {
15669
- logger$16.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
15785
+ logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
15670
15786
  this.localStreamController.addTrack(streamTrack);
15671
15787
  await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
15672
- logger$16.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
15788
+ logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
15673
15789
  }
15674
15790
  }
15675
- logger$16.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
15791
+ logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
15676
15792
  } catch (error) {
15677
- logger$16.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
15678
- this._errors$.next(toError(error));
15793
+ logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
15794
+ this._errors$.next(new MediaTrackError("updateSelectedInputDevice", kind, error));
15679
15795
  throw error;
15680
15796
  }
15681
15797
  };
@@ -15897,7 +16013,7 @@ var RTCPeerConnectionController = class extends Destroyable {
15897
16013
  case "main":
15898
16014
  default: return {
15899
16015
  ...options,
15900
- offerToReceiveAudio: true,
16016
+ offerToReceiveAudio: this.options.receiveAudio ?? true,
15901
16017
  offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
15902
16018
  };
15903
16019
  }
@@ -15924,15 +16040,15 @@ var RTCPeerConnectionController = class extends Destroyable {
15924
16040
  this.setupPeerConnection();
15925
16041
  this.subscribeTo(this.negotiationNeeded$.pipe((0, import_cjs$16.auditTime)(0), (0, import_cjs$16.exhaustMap)(async () => this.startNegotiation())), {
15926
16042
  next: () => {
15927
- logger$16.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
16043
+ logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
15928
16044
  },
15929
16045
  error: (error) => {
15930
- logger$16.error("[RTCPeerConnectionController] Start Negotiation error:", error);
16046
+ logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
15931
16047
  this._errors$.next(toError(error));
15932
16048
  }
15933
16049
  });
15934
16050
  this.subscribeTo((0, import_cjs$16.merge)(this.deviceController.selectedAudioInputDevice$.pipe((0, import_cjs$16.map)((deviceInfo) => ["audio", deviceInfo])), this.deviceController.selectedVideoInputDevice$.pipe((0, import_cjs$16.map)((deviceInfo) => ["video", deviceInfo]))).pipe((0, import_cjs$16.skipWhile)(() => !this.localStreamController.localStream)), async ([kind, deviceInfo]) => {
15935
- logger$16.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
16051
+ logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
15936
16052
  kind,
15937
16053
  deviceInfo
15938
16054
  });
@@ -15949,7 +16065,7 @@ var RTCPeerConnectionController = class extends Destroyable {
15949
16065
  this._initialized$.next(true);
15950
16066
  }
15951
16067
  } catch (error) {
15952
- logger$16.error("[RTCPeerConnectionController] Initialization error:", error);
16068
+ logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
15953
16069
  this._errors$.next(toError(error));
15954
16070
  this.destroy();
15955
16071
  }
@@ -15981,22 +16097,22 @@ var RTCPeerConnectionController = class extends Destroyable {
15981
16097
  }
15982
16098
  async startNegotiation() {
15983
16099
  if (this.isNegotiating) {
15984
- logger$16.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
16100
+ logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
15985
16101
  return;
15986
16102
  }
15987
16103
  this.setupEventListeners();
15988
16104
  if (this.type === "answer") {
15989
- logger$16.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
16105
+ logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
15990
16106
  return;
15991
16107
  }
15992
16108
  this._isNegotiating$.next(true);
15993
- logger$16.debug("[RTCPeerConnectionController] Starting negotiation.");
16109
+ logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
15994
16110
  try {
15995
16111
  const { offerOptions } = this;
15996
- logger$16.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
16112
+ logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
15997
16113
  await this.createOffer(offerOptions);
15998
16114
  } catch (error) {
15999
- logger$16.error("[RTCPeerConnectionController] Error during negotiation:", error);
16115
+ logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
16000
16116
  this._errors$.next(toError(error));
16001
16117
  }
16002
16118
  }
@@ -16012,14 +16128,14 @@ var RTCPeerConnectionController = class extends Destroyable {
16012
16128
  let readyToConnect = status !== "failed";
16013
16129
  try {
16014
16130
  if (status === "received" && sdp) {
16015
- logger$16.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
16131
+ logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
16016
16132
  await this._setRemoteDescription({
16017
16133
  type: "answer",
16018
16134
  sdp
16019
16135
  });
16020
16136
  }
16021
16137
  } catch (error) {
16022
- logger$16.error("[RTCPeerConnectionController] Error updating answer status:", error);
16138
+ logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
16023
16139
  this._errors$.next(toError(error));
16024
16140
  readyToConnect = false;
16025
16141
  } finally {
@@ -16038,7 +16154,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16038
16154
  await this.handleOfferReceived();
16039
16155
  break;
16040
16156
  case "failed":
16041
- logger$16.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
16157
+ logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
16042
16158
  break;
16043
16159
  case "sent":
16044
16160
  default:
@@ -16051,13 +16167,14 @@ var RTCPeerConnectionController = class extends Destroyable {
16051
16167
  */
16052
16168
  async acceptInbound(mediaOverrides) {
16053
16169
  if (mediaOverrides) {
16054
- const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
16170
+ const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
16055
16171
  this.options = {
16056
16172
  ...this.options,
16057
16173
  ...audio !== void 0 ? { audio } : {},
16058
16174
  ...video !== void 0 ? { video } : {},
16059
16175
  ...receiveAudio !== void 0 ? { receiveAudio } : {},
16060
- ...receiveVideo !== void 0 ? { receiveVideo } : {}
16176
+ ...receiveVideo !== void 0 ? { receiveVideo } : {},
16177
+ ...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
16061
16178
  };
16062
16179
  this.transceiverController?.updateOptions({
16063
16180
  receiveAudio: this.receiveAudio,
@@ -16070,7 +16187,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16070
16187
  }
16071
16188
  await this.setupLocalTracks();
16072
16189
  const { answerOptions } = this;
16073
- logger$16.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
16190
+ logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
16074
16191
  await this.createAnswer(answerOptions);
16075
16192
  }
16076
16193
  async handleOfferReceived() {
@@ -16078,7 +16195,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16078
16195
  this._isNegotiating$.next(true);
16079
16196
  await this._setRemoteDescription(this.sdpInit);
16080
16197
  const { answerOptions } = this;
16081
- logger$16.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
16198
+ logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
16082
16199
  await this.createAnswer(answerOptions);
16083
16200
  }
16084
16201
  readyToConnect() {
@@ -16086,7 +16203,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16086
16203
  this.connectionTimer = setTimeout(() => {
16087
16204
  this.removeConnectionTimer();
16088
16205
  if (this.peerConnection?.connectionState !== "connected") {
16089
- logger$16.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
16206
+ logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
16090
16207
  this.iceGatheringController.restartICEGatheringWithRelayOnly();
16091
16208
  }
16092
16209
  }, this.connectionTimeout);
@@ -16108,14 +16225,14 @@ var RTCPeerConnectionController = class extends Destroyable {
16108
16225
  const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
16109
16226
  if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
16110
16227
  result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
16111
- logger$16.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
16228
+ logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
16112
16229
  preferredAudioCodecs,
16113
16230
  preferredVideoCodecs
16114
16231
  });
16115
16232
  }
16116
16233
  if (stereo) {
16117
16234
  result = enableStereoOpus(result);
16118
- logger$16.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
16235
+ logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
16119
16236
  }
16120
16237
  return Promise.resolve(result);
16121
16238
  }
@@ -16171,25 +16288,25 @@ var RTCPeerConnectionController = class extends Destroyable {
16171
16288
  ...this.peerConnection.getConfiguration(),
16172
16289
  iceTransportPolicy: "relay"
16173
16290
  });
16174
- logger$16.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
16291
+ logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
16175
16292
  } catch (error) {
16176
- logger$16.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
16293
+ logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
16177
16294
  }
16178
16295
  this.setupEventListeners();
16179
16296
  this._isNegotiating$.next(true);
16180
- logger$16.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
16297
+ logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
16181
16298
  try {
16182
16299
  const offer = await this.peerConnection.createOffer({ iceRestart: true });
16183
16300
  await this.setLocalDescription(offer);
16184
16301
  } catch (error) {
16185
- logger$16.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
16302
+ logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
16186
16303
  this._errors$.next(toError(error));
16187
16304
  this.negotiationEnded();
16188
16305
  if (policyChanged) this.restoreIceTransportPolicy();
16189
16306
  throw error;
16190
16307
  }
16191
16308
  if (policyChanged) (0, import_cjs$16.firstValueFrom)((0, import_cjs$16.race)(this._iceGatheringState$.pipe((0, import_cjs$16.filter)((state) => state === "complete"), (0, import_cjs$16.take)(1)), (0, import_cjs$16.timer)(ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe((0, import_cjs$16.map)(() => "timeout")))).then(() => this.restoreIceTransportPolicy()).catch((error) => {
16192
- logger$16.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
16309
+ logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
16193
16310
  this.restoreIceTransportPolicy();
16194
16311
  });
16195
16312
  }
@@ -16199,9 +16316,9 @@ var RTCPeerConnectionController = class extends Destroyable {
16199
16316
  ...this.peerConnection.getConfiguration(),
16200
16317
  iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
16201
16318
  });
16202
- logger$16.debug("[RTCPeerConnectionController] ICE transport policy restored");
16319
+ logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
16203
16320
  } catch (error) {
16204
- logger$16.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
16321
+ logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
16205
16322
  }
16206
16323
  }
16207
16324
  /**
@@ -16213,13 +16330,25 @@ var RTCPeerConnectionController = class extends Destroyable {
16213
16330
  await this.setupRemoteTracks();
16214
16331
  }
16215
16332
  async setupLocalTracks() {
16216
- logger$16.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
16217
- const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
16333
+ logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
16334
+ if (this.hasNoLocalMediaToSend()) {
16335
+ if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
16336
+ logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
16337
+ this.setupReceiveOnlyTransceivers();
16338
+ return;
16339
+ }
16340
+ let localStream;
16341
+ try {
16342
+ localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
16343
+ } catch (error) {
16344
+ this.handleLocalMediaFailure(error);
16345
+ return;
16346
+ }
16218
16347
  if (this.transceiverController?.useAddStream ?? false) {
16219
- logger$16.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
16348
+ logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
16220
16349
  this.peerConnection?.addStream(localStream);
16221
16350
  if (!this.isNegotiating) {
16222
- logger$16.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
16351
+ logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
16223
16352
  this.negotiationNeeded$.next();
16224
16353
  }
16225
16354
  return;
@@ -16235,12 +16364,54 @@ var RTCPeerConnectionController = class extends Destroyable {
16235
16364
  const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
16236
16365
  await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
16237
16366
  } else {
16238
- logger$16.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
16367
+ logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
16239
16368
  this.peerConnection?.addTrack(track, localStream);
16240
16369
  }
16241
16370
  }
16242
16371
  }
16243
16372
  }
16373
+ /** True for a main connection with no local media to send. */
16374
+ hasNoLocalMediaToSend() {
16375
+ const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
16376
+ return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
16377
+ }
16378
+ /** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
16379
+ get requestedMediaKinds() {
16380
+ const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
16381
+ const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
16382
+ if (wantsAudio && wantsVideo) return "audiovideo";
16383
+ return wantsVideo ? "video" : "audio";
16384
+ }
16385
+ /**
16386
+ * Handle a local media acquisition failure with a typed, semantically
16387
+ * accurate MediaAccessError created at the acquisition site:
16388
+ * - Auxiliary connections (screenshare / additional-device) throw a
16389
+ * non-fatal error — VertoManager surfaces it and the call is unaffected.
16390
+ * - The main connection degrades to receive-only when allowed (default),
16391
+ * otherwise fails with a fatal error.
16392
+ */
16393
+ handleLocalMediaFailure(error) {
16394
+ if (this.propose === "screenshare") throw new MediaAccessError("startScreenShare", "screen", error, false);
16395
+ if (this.propose === "additional-device") throw new MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
16396
+ const canReceive = this.receiveAudio || this.receiveVideo;
16397
+ if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
16398
+ logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
16399
+ this._errors$.next(new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
16400
+ this.setupReceiveOnlyTransceivers();
16401
+ }
16402
+ /**
16403
+ * Negotiate receive-only m-lines when there are no local tracks to send.
16404
+ * Only offer-type connections add transceivers — answer-type connections
16405
+ * reuse the transceivers created from the remote offer.
16406
+ */
16407
+ setupReceiveOnlyTransceivers() {
16408
+ if (this.type !== "offer") return;
16409
+ if (this.transceiverController?.useAddTransceivers ?? false) {
16410
+ this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
16411
+ this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
16412
+ }
16413
+ if (!this.isNegotiating) this.negotiationNeeded$.next();
16414
+ }
16244
16415
  async getUserMedia(constraints) {
16245
16416
  return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
16246
16417
  }
@@ -16252,7 +16423,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16252
16423
  async setupRemoteTracks() {
16253
16424
  if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
16254
16425
  this.peerConnection.ontrack = (event) => {
16255
- logger$16.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
16426
+ logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
16256
16427
  if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
16257
16428
  else {
16258
16429
  const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
@@ -16276,8 +16447,8 @@ var RTCPeerConnectionController = class extends Destroyable {
16276
16447
  try {
16277
16448
  stream = await this.getUserMedia({ audio: constraints });
16278
16449
  } catch (error) {
16279
- logger$16.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
16280
- this._errors$.next(toError(error));
16450
+ logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
16451
+ this._errors$.next(new MediaTrackError("restoreAudioPipelineInput", "audio", error));
16281
16452
  return;
16282
16453
  }
16283
16454
  const newTrack = stream.getAudioTracks().at(0);
@@ -16298,7 +16469,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16298
16469
  try {
16299
16470
  this._localAudioPipeline = new LocalAudioPipeline();
16300
16471
  } catch (error) {
16301
- logger$16.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
16472
+ logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
16302
16473
  return null;
16303
16474
  }
16304
16475
  this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
@@ -16320,7 +16491,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16320
16491
  try {
16321
16492
  await sender.replaceTrack(this._localAudioPipeline.outputTrack);
16322
16493
  } catch (error) {
16323
- logger$16.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
16494
+ logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
16324
16495
  }
16325
16496
  }
16326
16497
  /**
@@ -16336,10 +16507,10 @@ var RTCPeerConnectionController = class extends Destroyable {
16336
16507
  try {
16337
16508
  const localStream = this.localStreamController.addTrack(track);
16338
16509
  this.peerConnection.addTrack(track, localStream);
16339
- logger$16.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
16510
+ logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
16340
16511
  } catch (error) {
16341
- logger$16.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
16342
- this._errors$.next(toError(error));
16512
+ logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
16513
+ this._errors$.next(new MediaTrackError("addLocalTrack", track.kind, error));
16343
16514
  throw error;
16344
16515
  }
16345
16516
  }
@@ -16355,16 +16526,16 @@ var RTCPeerConnectionController = class extends Destroyable {
16355
16526
  }
16356
16527
  const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
16357
16528
  if (!sender) {
16358
- logger$16.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
16529
+ logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
16359
16530
  return;
16360
16531
  }
16361
16532
  try {
16362
16533
  this.peerConnection.removeTrack(sender);
16363
16534
  this.localStreamController.removeTrack(trackId);
16364
- logger$16.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
16535
+ logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
16365
16536
  } catch (error) {
16366
- logger$16.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
16367
- this._errors$.next(toError(error));
16537
+ logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
16538
+ this._errors$.next(new MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
16368
16539
  throw error;
16369
16540
  }
16370
16541
  }
@@ -16390,7 +16561,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16390
16561
  async replaceAudioTrackWithConstraints(constraints) {
16391
16562
  const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
16392
16563
  if (!senders || senders.length === 0) {
16393
- logger$16.warn("[RTCPeerConnectionController] No live audio sender to replace");
16564
+ logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
16394
16565
  return;
16395
16566
  }
16396
16567
  for (const sender of senders) {
@@ -16408,7 +16579,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16408
16579
  const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
16409
16580
  await sender.replaceTrack(newTrack);
16410
16581
  this.localStreamController.addTrack(newTrack);
16411
- logger$16.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
16582
+ logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
16412
16583
  }
16413
16584
  }
16414
16585
  /**
@@ -16416,7 +16587,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16416
16587
  * Completes all observables to prevent memory leaks.
16417
16588
  */
16418
16589
  destroy() {
16419
- logger$16.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
16590
+ logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
16420
16591
  this.removeConnectionTimer();
16421
16592
  this._iceGatheringController?.destroy();
16422
16593
  this._localAudioPipeline?.destroy();
@@ -16442,7 +16613,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16442
16613
  }
16443
16614
  stopRemoteTracks() {
16444
16615
  this._remoteStream$.value?.getTracks().forEach((track) => {
16445
- logger$16.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
16616
+ logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
16446
16617
  track.stop();
16447
16618
  });
16448
16619
  }
@@ -16459,7 +16630,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16459
16630
  ...params,
16460
16631
  sdp: finalRemote
16461
16632
  };
16462
- logger$16.debug("[RTCPeerConnectionController] Setting remote description:", answer);
16633
+ logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
16463
16634
  return this.peerConnection.setRemoteDescription(answer);
16464
16635
  }
16465
16636
  };
@@ -16474,7 +16645,11 @@ function isVertoInviteMessage(value) {
16474
16645
  const msg = value;
16475
16646
  return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
16476
16647
  }
16477
- function isVertoByeMessage(value) {
16648
+ /**
16649
+ * Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
16650
+ * built locally and never type-guarded, so only the inbound shape is asserted.
16651
+ */
16652
+ function isVertoByeInboundMessage(value) {
16478
16653
  if (!isVertoMethodMessage(value)) return false;
16479
16654
  return value.method === "verto.bye";
16480
16655
  }
@@ -16494,11 +16669,19 @@ function isVertoMediaParamsInnerParams(value) {
16494
16669
  function isVertoPingInnerParams(value) {
16495
16670
  return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
16496
16671
  }
16672
+ /**
16673
+ * Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
16674
+ * this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
16675
+ * it against a boolean answer/reject signal.
16676
+ */
16677
+ function isVertoByeInboundParamsGuard(value) {
16678
+ return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
16679
+ }
16497
16680
 
16498
16681
  //#endregion
16499
16682
  //#region src/managers/VertoManager.ts
16500
16683
  var import_cjs$15 = require_cjs();
16501
- const logger$15 = getLogger();
16684
+ const logger$16 = getLogger();
16502
16685
  /**
16503
16686
  * Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
16504
16687
  *
@@ -16554,7 +16737,7 @@ var WebRTCVertoManager = class extends VertoManager {
16554
16737
  try {
16555
16738
  await this.executeVerto(vertoModifyMessage);
16556
16739
  } catch (error) {
16557
- logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
16740
+ logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
16558
16741
  throw error;
16559
16742
  }
16560
16743
  }
@@ -16567,7 +16750,7 @@ var WebRTCVertoManager = class extends VertoManager {
16567
16750
  try {
16568
16751
  await this.executeVerto(vertoModifyMessage);
16569
16752
  } catch (error) {
16570
- logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
16753
+ logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
16571
16754
  throw error;
16572
16755
  }
16573
16756
  }
@@ -16620,7 +16803,7 @@ var WebRTCVertoManager = class extends VertoManager {
16620
16803
  if (event.member_id) this.setSelfIdIfNull(event.member_id);
16621
16804
  });
16622
16805
  this.subscribeTo(this.vertoMedia$, (event) => {
16623
- logger$15.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
16806
+ logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
16624
16807
  const { sdp, callID } = event;
16625
16808
  this.emitMainSignalingStatus(callID, "ringing");
16626
16809
  this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
@@ -16629,7 +16812,7 @@ var WebRTCVertoManager = class extends VertoManager {
16629
16812
  });
16630
16813
  });
16631
16814
  this.subscribeTo(this.vertoAnswer$, (event) => {
16632
- logger$15.debug("[WebRTCManager] Received Verto answer event:", event);
16815
+ logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
16633
16816
  const { sdp, callID } = event;
16634
16817
  this.emitMainSignalingStatus(callID, "connecting");
16635
16818
  this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
@@ -16638,7 +16821,7 @@ var WebRTCVertoManager = class extends VertoManager {
16638
16821
  });
16639
16822
  });
16640
16823
  this.subscribeTo(this.vertoMediaParams$, (event) => {
16641
- logger$15.debug("[WebRTCManager] Received Verto mediaParams event:", event);
16824
+ logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
16642
16825
  const { mediaParams, callID } = event;
16643
16826
  const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
16644
16827
  const { audio, video } = mediaParams;
@@ -16652,7 +16835,7 @@ var WebRTCVertoManager = class extends VertoManager {
16652
16835
  timestamp: Date.now()
16653
16836
  });
16654
16837
  } catch (error) {
16655
- logger$15.warn("[WebRTCManager] Error applying server-pushed media params:", error);
16838
+ logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
16656
16839
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
16657
16840
  }
16658
16841
  })();
@@ -16674,13 +16857,13 @@ var WebRTCVertoManager = class extends VertoManager {
16674
16857
  */
16675
16858
  setNodeIdIfNull(nodeId) {
16676
16859
  if (!this._nodeId$.value && nodeId) {
16677
- logger$15.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
16860
+ logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
16678
16861
  this._nodeId$.next(nodeId);
16679
16862
  }
16680
16863
  }
16681
16864
  setSelfIdIfNull(selfId) {
16682
16865
  if (!this._selfId$.value && selfId) {
16683
- logger$15.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
16866
+ logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
16684
16867
  this._selfId$.next(selfId);
16685
16868
  }
16686
16869
  }
@@ -16689,7 +16872,7 @@ var WebRTCVertoManager = class extends VertoManager {
16689
16872
  const vertoPongMessage = VertoPong({ ...vertoPing });
16690
16873
  await this.executeVerto(vertoPongMessage);
16691
16874
  } catch (error) {
16692
- logger$15.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
16875
+ logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
16693
16876
  this.onError?.(new VertoPongError(error));
16694
16877
  }
16695
16878
  }
@@ -16699,7 +16882,7 @@ var WebRTCVertoManager = class extends VertoManager {
16699
16882
  if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
16700
16883
  if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
16701
16884
  } catch (error) {
16702
- logger$15.warn("[WebRTCManager] Error updating media constraints:", error);
16885
+ logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
16703
16886
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
16704
16887
  throw error;
16705
16888
  }
@@ -16729,20 +16912,20 @@ var WebRTCVertoManager = class extends VertoManager {
16729
16912
  try {
16730
16913
  const pc = this.mainPeerConnection.peerConnection;
16731
16914
  if (!pc) {
16732
- logger$15.warn("[WebRTCManager] No peer connection for keyframe request");
16915
+ logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
16733
16916
  return;
16734
16917
  }
16735
16918
  const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
16736
16919
  if (!videoReceiver) {
16737
- logger$15.warn("[WebRTCManager] No video receiver for keyframe request");
16920
+ logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
16738
16921
  return;
16739
16922
  }
16740
16923
  if (typeof videoReceiver.requestKeyFrame === "function") {
16741
16924
  videoReceiver.requestKeyFrame();
16742
- logger$15.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
16743
- } else logger$15.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
16925
+ logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
16926
+ } else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
16744
16927
  } catch (error) {
16745
- logger$15.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
16928
+ logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
16746
16929
  }
16747
16930
  }
16748
16931
  /**
@@ -16760,13 +16943,13 @@ var WebRTCVertoManager = class extends VertoManager {
16760
16943
  try {
16761
16944
  const controller = this.mainPeerConnection;
16762
16945
  if (!controller.peerConnection) {
16763
- logger$15.warn("[WebRTCManager] No peer connection for ICE restart");
16946
+ logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
16764
16947
  return;
16765
16948
  }
16766
16949
  await controller.triggerIceRestart(relayOnly);
16767
- logger$15.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
16950
+ logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
16768
16951
  } catch (error) {
16769
- logger$15.error("[WebRTCManager] ICE restart failed:", error);
16952
+ logger$16.error("[WebRTCManager] ICE restart failed:", error);
16770
16953
  throw error;
16771
16954
  }
16772
16955
  }
@@ -16784,13 +16967,13 @@ var WebRTCVertoManager = class extends VertoManager {
16784
16967
  const entries = Array.from(this._rtcPeerConnectionsMap.entries());
16785
16968
  for (const [id, controller] of entries) try {
16786
16969
  if (!controller.peerConnection) {
16787
- logger$15.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
16970
+ logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
16788
16971
  continue;
16789
16972
  }
16790
16973
  await controller.triggerIceRestart(relayOnly);
16791
- logger$15.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
16974
+ logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
16792
16975
  } catch (error) {
16793
- logger$15.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
16976
+ logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
16794
16977
  }
16795
16978
  }
16796
16979
  /**
@@ -16802,7 +16985,7 @@ var WebRTCVertoManager = class extends VertoManager {
16802
16985
  requestKeyframeAll() {
16803
16986
  for (const [id, controller] of this._rtcPeerConnectionsMap) {
16804
16987
  if (controller.isScreenShare) {
16805
- logger$15.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
16988
+ logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
16806
16989
  continue;
16807
16990
  }
16808
16991
  try {
@@ -16812,10 +16995,10 @@ var WebRTCVertoManager = class extends VertoManager {
16812
16995
  if (!videoReceiver) continue;
16813
16996
  if (typeof videoReceiver.requestKeyFrame === "function") {
16814
16997
  videoReceiver.requestKeyFrame();
16815
- logger$15.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
16998
+ logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
16816
16999
  }
16817
17000
  } catch (error) {
16818
- logger$15.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
17001
+ logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
16819
17002
  }
16820
17003
  }
16821
17004
  }
@@ -16832,7 +17015,7 @@ var WebRTCVertoManager = class extends VertoManager {
16832
17015
  return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaParamsInnerParams, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
16833
17016
  }
16834
17017
  get vertoBye$() {
16835
- return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
17018
+ return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeInboundMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
16836
17019
  }
16837
17020
  get vertoAttach$() {
16838
17021
  return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoAttachMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
@@ -16876,7 +17059,7 @@ var WebRTCVertoManager = class extends VertoManager {
16876
17059
  default:
16877
17060
  }
16878
17061
  } catch (error) {
16879
- logger$15.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
17062
+ logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
16880
17063
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
16881
17064
  if (vertoMethod === "verto.modify") this.onModifyFailed?.();
16882
17065
  }
@@ -16891,7 +17074,7 @@ var WebRTCVertoManager = class extends VertoManager {
16891
17074
  sdp
16892
17075
  });
16893
17076
  } catch (error) {
16894
- logger$15.warn("[WebRTCManager] Error processing modify response:", error);
17077
+ logger$16.warn("[WebRTCManager] Error processing modify response:", error);
16895
17078
  const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
16896
17079
  this.onError?.(modifyError);
16897
17080
  }
@@ -16901,7 +17084,7 @@ var WebRTCVertoManager = class extends VertoManager {
16901
17084
  const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
16902
17085
  if (!rtcPeerConnController) {
16903
17086
  const signalingError = new DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
16904
- logger$15.error("[WebRTCManager] Failed to emit signaling status:", {
17087
+ logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
16905
17088
  callId,
16906
17089
  status,
16907
17090
  signalingError
@@ -16917,7 +17100,7 @@ var WebRTCVertoManager = class extends VertoManager {
16917
17100
  this._nodeId$.next(getValueFrom(response, "result.node_id") ?? null);
16918
17101
  const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
16919
17102
  const callId = getValueFrom(response, "result.result.result.callID") ?? null;
16920
- logger$15.debug("[WebRTCManager] Verto invite response:", {
17103
+ logger$16.debug("[WebRTCManager] Verto invite response:", {
16921
17104
  callId,
16922
17105
  memberId,
16923
17106
  response
@@ -16927,14 +17110,14 @@ var WebRTCVertoManager = class extends VertoManager {
16927
17110
  if (callId) {
16928
17111
  this.webRtcCallSession.addCallId(callId);
16929
17112
  this.attachManager.attach(this.buildAttachableCall(callId));
16930
- } else logger$15.warn("[WebRTCManager] Cannot attach call, missing callId:", {
17113
+ } else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
16931
17114
  nodeId: this.nodeId,
16932
17115
  callId
16933
17116
  });
16934
- logger$15.info("[WebRTCManager] Verto invite successful");
16935
- logger$15.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
17117
+ logger$16.info("[WebRTCManager] Verto invite successful");
17118
+ logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
16936
17119
  } else {
16937
- logger$15.error("[WebRTCManager] Verto invite failed:", response);
17120
+ logger$16.error("[WebRTCManager] Verto invite failed:", response);
16938
17121
  const inviteError = response.error ? new JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
16939
17122
  this.onError?.(inviteError);
16940
17123
  }
@@ -16961,6 +17144,7 @@ var WebRTCVertoManager = class extends VertoManager {
16961
17144
  inputVideoStream: options.inputVideoStream,
16962
17145
  receiveAudio: options.receiveAudio,
16963
17146
  receiveVideo: options.receiveVideo,
17147
+ fallbackToReceiveOnly: options.fallbackToReceiveOnly,
16964
17148
  webRTCApiProvider: this.webRTCApiProvider,
16965
17149
  preferredVideoCodecs: options.preferredVideoCodecs,
16966
17150
  preferredAudioCodecs: options.preferredAudioCodecs,
@@ -16979,17 +17163,17 @@ var WebRTCVertoManager = class extends VertoManager {
16979
17163
  if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
16980
17164
  }
16981
17165
  async handleInboundAnswer(rtcPeerConnController) {
16982
- logger$15.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
17166
+ logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
16983
17167
  const vertoByeOrAccepted = await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$15.takeUntil)(this.destroyed$))).catch(() => null);
16984
17168
  if (vertoByeOrAccepted === null) {
16985
- logger$15.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
17169
+ logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
16986
17170
  return;
16987
17171
  }
16988
- if (isVertoByeMessage(vertoByeOrAccepted)) {
16989
- logger$15.info("[WebRTCManager] Inbound call ended by remote before answer.");
17172
+ if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
17173
+ logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
16990
17174
  this.callSession?.destroy();
16991
17175
  } else if (!vertoByeOrAccepted) {
16992
- logger$15.info("[WebRTCManager] Inbound call rejected by user.");
17176
+ logger$16.info("[WebRTCManager] Inbound call rejected by user.");
16993
17177
  try {
16994
17178
  await this.bye("USER_BUSY");
16995
17179
  } finally {
@@ -16997,19 +17181,19 @@ var WebRTCVertoManager = class extends VertoManager {
16997
17181
  this.callSession?.destroy();
16998
17182
  }
16999
17183
  } else {
17000
- logger$15.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
17184
+ logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
17001
17185
  const answerOptions = this.webRtcCallSession.answerMediaOptions;
17002
17186
  try {
17003
17187
  await rtcPeerConnController.acceptInbound(answerOptions);
17004
17188
  } catch (error) {
17005
- logger$15.error("[WebRTCManager] Error creating inbound answer:", error);
17189
+ logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
17006
17190
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17007
17191
  }
17008
17192
  }
17009
17193
  }
17010
17194
  setupVertoAttachHandler() {
17011
17195
  this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
17012
- logger$15.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
17196
+ logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
17013
17197
  const { callID } = vertoAttach;
17014
17198
  await this.attachManager.attach({
17015
17199
  nodeId: this.nodeId ?? void 0,
@@ -17081,17 +17265,17 @@ var WebRTCVertoManager = class extends VertoManager {
17081
17265
  };
17082
17266
  }
17083
17267
  async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
17084
- logger$15.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
17268
+ logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
17085
17269
  const vertoByeOrAccepted = await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$15.takeUntil)(this.destroyed$))).catch(() => null);
17086
17270
  if (vertoByeOrAccepted === null) {
17087
- logger$15.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
17271
+ logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
17088
17272
  return;
17089
17273
  }
17090
- if (isVertoByeMessage(vertoByeOrAccepted)) {
17091
- logger$15.info("[WebRTCManager] Call ended before answer was sent.");
17274
+ if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
17275
+ logger$16.info("[WebRTCManager] Call ended before answer was sent.");
17092
17276
  this.callSession?.destroy();
17093
17277
  } else if (!vertoByeOrAccepted) {
17094
- logger$15.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
17278
+ logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
17095
17279
  try {
17096
17280
  await this.bye("USER_BUSY");
17097
17281
  } finally {
@@ -17099,14 +17283,14 @@ var WebRTCVertoManager = class extends VertoManager {
17099
17283
  this.callSession?.destroy();
17100
17284
  }
17101
17285
  } else {
17102
- logger$15.debug("[WebRTCManager] Call accepted, sending answer");
17286
+ logger$16.debug("[WebRTCManager] Call accepted, sending answer");
17103
17287
  try {
17104
17288
  this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
17105
17289
  await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
17106
17290
  await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
17107
17291
  await this.attachManager.attach(this.buildAttachableCall());
17108
17292
  } catch (error) {
17109
- logger$15.error("[WebRTCManager] Error sending Verto answer:", error);
17293
+ logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
17110
17294
  this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17111
17295
  await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
17112
17296
  }
@@ -17187,9 +17371,11 @@ var WebRTCVertoManager = class extends VertoManager {
17187
17371
  await this.initAdditionalPeerConnection("screenshare", options);
17188
17372
  }
17189
17373
  async initAdditionalPeerConnection(propose, options) {
17374
+ const isScreenShare = propose === "screenshare";
17375
+ let firstPeerConnectionError;
17190
17376
  let rtcPeerConnController = null;
17191
17377
  try {
17192
- this._screenShareStatus$.next("starting");
17378
+ if (isScreenShare) this._screenShareStatus$.next("starting");
17193
17379
  rtcPeerConnController = new RTCPeerConnectionController({
17194
17380
  ...options,
17195
17381
  ...this.RTCPeerConnectionConfig,
@@ -17197,21 +17383,27 @@ var WebRTCVertoManager = class extends VertoManager {
17197
17383
  webRTCApiProvider: this.webRTCApiProvider
17198
17384
  }, void 0, this.deviceController);
17199
17385
  this.setupLocalDescriptionHandler(rtcPeerConnController);
17200
- if (propose === "screenshare") this._screenShareId = rtcPeerConnController.id;
17386
+ if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
17201
17387
  this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
17202
17388
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17203
17389
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
17204
- this.onError?.(error);
17390
+ firstPeerConnectionError ??= error;
17391
+ this.onError?.(error, { fatal: false });
17205
17392
  });
17206
17393
  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$)));
17207
- this._screenShareStatus$.next("started");
17208
- logger$15.info("[WebRTCManager] Screen share started successfully.");
17394
+ if (isScreenShare) this._screenShareStatus$.next("started");
17395
+ logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
17209
17396
  return rtcPeerConnController.id;
17210
17397
  } catch (error) {
17211
- logger$15.warn("[WebRTCManager] Error initializing additional peer connection:", error);
17212
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17398
+ logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
17213
17399
  if (rtcPeerConnController) rtcPeerConnController.destroy();
17214
- this._screenShareStatus$.next("none");
17400
+ if (isScreenShare) this._screenShareStatus$.next("none");
17401
+ if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
17402
+ if (error instanceof import_cjs$15.EmptyError) {
17403
+ logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
17404
+ return;
17405
+ }
17406
+ throw error instanceof Error ? error : new Error(String(error), { cause: error });
17215
17407
  }
17216
17408
  }
17217
17409
  async removeInputDevices(id) {
@@ -17227,9 +17419,9 @@ var WebRTCVertoManager = class extends VertoManager {
17227
17419
  if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
17228
17420
  }
17229
17421
  async removeScreenMedia() {
17230
- if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$15.warn("[WebRTCManager] No active screen share to stop.");
17422
+ if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
17231
17423
  if (!this._screenShareId) {
17232
- logger$15.debug("[WebRTCManager] No screen share peer connection found.");
17424
+ logger$16.debug("[WebRTCManager] No screen share peer connection found.");
17233
17425
  return;
17234
17426
  }
17235
17427
  this._screenShareStatus$.next("stopping");
@@ -17258,7 +17450,7 @@ var WebRTCVertoManager = class extends VertoManager {
17258
17450
  dialogParams: this.dialogParams(rtcPeerConnController)
17259
17451
  }));
17260
17452
  } catch (error) {
17261
- logger$15.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
17453
+ logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
17262
17454
  throw error;
17263
17455
  }
17264
17456
  }
@@ -17276,7 +17468,7 @@ var WebRTCVertoManager = class extends VertoManager {
17276
17468
  try {
17277
17469
  await this.executeVerto(vertoInfoMessage);
17278
17470
  } catch (error) {
17279
- logger$15.warn("[WebRTCManager] Error sending DTMF digits:", error);
17471
+ logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
17280
17472
  throw error;
17281
17473
  }
17282
17474
  }
@@ -17287,10 +17479,10 @@ var WebRTCVertoManager = class extends VertoManager {
17287
17479
  action: "transfer"
17288
17480
  });
17289
17481
  try {
17290
- logger$15.debug("[WebRTCManager] Transferring call with options:", options);
17482
+ logger$16.debug("[WebRTCManager] Transferring call with options:", options);
17291
17483
  await this.executeVerto(message);
17292
17484
  } catch (error) {
17293
- logger$15.error("[WebRTCManager] Error transferring call:", error);
17485
+ logger$16.error("[WebRTCManager] Error transferring call:", error);
17294
17486
  throw error;
17295
17487
  }
17296
17488
  }
@@ -17307,7 +17499,7 @@ var WebRTCVertoManager = class extends VertoManager {
17307
17499
  //#endregion
17308
17500
  //#region src/controllers/RemoteAudioMeter.ts
17309
17501
  var import_cjs$14 = require_cjs();
17310
- const logger$14 = getLogger();
17502
+ const logger$15 = getLogger();
17311
17503
  /**
17312
17504
  * Read-only audio level meter for a remote MediaStream. Attaches an
17313
17505
  * AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
@@ -17342,7 +17534,7 @@ var RemoteAudioMeter = class extends Destroyable {
17342
17534
  try {
17343
17535
  this._source.disconnect();
17344
17536
  } catch (error) {
17345
- logger$14.debug("[RemoteAudioMeter] source disconnect warning:", error);
17537
+ logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
17346
17538
  }
17347
17539
  this._source = null;
17348
17540
  this._stream = null;
@@ -17359,7 +17551,7 @@ var RemoteAudioMeter = class extends Destroyable {
17359
17551
  this._source = null;
17360
17552
  }
17361
17553
  this._audioContext.close().catch((error) => {
17362
- logger$14.debug("[RemoteAudioMeter] audio context close warning:", error);
17554
+ logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
17363
17555
  });
17364
17556
  super.destroy();
17365
17557
  }
@@ -17378,7 +17570,7 @@ var RemoteAudioMeter = class extends Destroyable {
17378
17570
  //#endregion
17379
17571
  //#region src/controllers/RTCStatsMonitor.ts
17380
17572
  var import_cjs$13 = require_cjs();
17381
- const logger$13 = getLogger();
17573
+ const logger$14 = getLogger();
17382
17574
  const DEFAULT_POLLING_INTERVAL_MS = 1e3;
17383
17575
  const DEFAULT_BASELINE_SAMPLES = 10;
17384
17576
  const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
@@ -17468,9 +17660,9 @@ var RTCStatsMonitor = class extends Destroyable {
17468
17660
  const now = Date.now();
17469
17661
  this.lastAudioPacketChangeTime = now;
17470
17662
  this.lastVideoPacketChangeTime = now;
17471
- logger$13.debug("[RTCStatsMonitor] Starting stats monitoring");
17663
+ logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
17472
17664
  this.subscribeTo((0, import_cjs$13.interval)(this.pollingIntervalMs).pipe((0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.switchMap)(() => (0, import_cjs$13.from)(this.peerConnection.getStats()).pipe((0, import_cjs$13.catchError)((err) => {
17473
- logger$13.warn("[RTCStatsMonitor] Failed to get stats:", err);
17665
+ logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
17474
17666
  return import_cjs$13.EMPTY;
17475
17667
  }))), (0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.map)((report) => this.extractSample(report))), (sample$1) => this._sample$.next(sample$1));
17476
17668
  this.subscribeTo(this._sample$.pipe((0, import_cjs$13.take)(this.baselineSampleCount), (0, import_cjs$13.toArray)(), (0, import_cjs$13.map)((samples) => ({
@@ -17478,7 +17670,7 @@ var RTCStatsMonitor = class extends Destroyable {
17478
17670
  jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
17479
17671
  ready: true
17480
17672
  }))), (baseline) => {
17481
- logger$13.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
17673
+ logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
17482
17674
  this._baseline$.next(baseline);
17483
17675
  });
17484
17676
  this.subscribeTo(this._sample$.pipe((0, import_cjs$13.scan)((acc, sample$1) => ({
@@ -17515,10 +17707,10 @@ var RTCStatsMonitor = class extends Destroyable {
17515
17707
  stop() {
17516
17708
  if (!this.running) return;
17517
17709
  this.running = false;
17518
- logger$13.debug("[RTCStatsMonitor] Stopping stats monitoring");
17710
+ logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
17519
17711
  }
17520
17712
  destroy() {
17521
- logger$13.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
17713
+ logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
17522
17714
  this.stop();
17523
17715
  super.destroy();
17524
17716
  }
@@ -17647,7 +17839,7 @@ var RTCStatsMonitor = class extends Destroyable {
17647
17839
  //#endregion
17648
17840
  //#region src/managers/CallRecoveryManager.ts
17649
17841
  var import_cjs$12 = require_cjs();
17650
- const logger$12 = getLogger();
17842
+ const logger$13 = getLogger();
17651
17843
  const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
17652
17844
  const DEFAULT_COOLDOWN_MS = 1e4;
17653
17845
  const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
@@ -17744,10 +17936,10 @@ var CallRecoveryManager = class extends Destroyable {
17744
17936
  */
17745
17937
  async requestIceRestart() {
17746
17938
  if (this._recoveryState$.value === "recovering") {
17747
- logger$12.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
17939
+ logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
17748
17940
  return;
17749
17941
  }
17750
- logger$12.info("CallRecoveryManager: manual ICE restart requested");
17942
+ logger$13.info("CallRecoveryManager: manual ICE restart requested");
17751
17943
  this.transitionTo("recovering");
17752
17944
  await this.executeIceRestart(false);
17753
17945
  this.startCooldown();
@@ -17763,7 +17955,7 @@ var CallRecoveryManager = class extends Destroyable {
17763
17955
  * WebSocket reconnect or call state recovers to 'connected'.
17764
17956
  */
17765
17957
  reset() {
17766
- logger$12.info("CallRecoveryManager: resetting counters");
17958
+ logger$13.info("CallRecoveryManager: resetting counters");
17767
17959
  this._attemptCount = 0;
17768
17960
  this._keyframeBurstCount = 0;
17769
17961
  this._keyframeBurstStart = 0;
@@ -17778,7 +17970,7 @@ var CallRecoveryManager = class extends Destroyable {
17778
17970
  */
17779
17971
  notifyModifyFailed() {
17780
17972
  if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
17781
- logger$12.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
17973
+ logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
17782
17974
  this._cooldownUntil = 0;
17783
17975
  this.transitionTo("idle");
17784
17976
  this.pushTrigger({
@@ -17802,7 +17994,7 @@ var CallRecoveryManager = class extends Destroyable {
17802
17994
  reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
17803
17995
  timestamp: Date.now()
17804
17996
  });
17805
- logger$12.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
17997
+ logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
17806
17998
  } else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
17807
17999
  this._bandwidthConstrained$.next(false);
17808
18000
  this._callbacks.enableVideo();
@@ -17811,7 +18003,7 @@ var CallRecoveryManager = class extends Destroyable {
17811
18003
  reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
17812
18004
  timestamp: Date.now()
17813
18005
  });
17814
- logger$12.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
18006
+ logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
17815
18007
  }
17816
18008
  }
17817
18009
  /**
@@ -17830,14 +18022,14 @@ var CallRecoveryManager = class extends Destroyable {
17830
18022
  handleWebSocketReconnect() {
17831
18023
  const pcState = this._callbacks.getPeerConnectionState();
17832
18024
  if (pcState === "connected" || pcState === "completed") {
17833
- logger$12.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
18025
+ logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
17834
18026
  this.emitEvent({
17835
18027
  action: "signal_reconnect",
17836
18028
  reason: "WebSocket reconnected, peer connection still connected",
17837
18029
  timestamp: Date.now()
17838
18030
  });
17839
18031
  } else {
17840
- logger$12.info("CallRecoveryManager: full reconnect — peer connection also down");
18032
+ logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
17841
18033
  this.emitEvent({
17842
18034
  action: "full_reconnect",
17843
18035
  reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
@@ -17861,7 +18053,7 @@ var CallRecoveryManager = class extends Destroyable {
17861
18053
  }), (0, import_cjs$12.debounceTime)(this._config.debounceTimeMs), (0, import_cjs$12.withLatestFrom)(this._inputs.signalingReady$), (0, import_cjs$12.filter)(([, signalingReady]) => this.passGateChecks(signalingReady)), (0, import_cjs$12.map)(([trigger]) => trigger), (0, import_cjs$12.exhaustMap)((trigger) => this.executeTieredRecovery(trigger)), (0, import_cjs$12.takeUntil)((0, import_cjs$12.merge)(this._destroyed$, this._pipelineStop$))), {
17862
18054
  next: () => {},
17863
18055
  error: (err) => {
17864
- logger$12.error("CallRecoveryManager: pipeline error", err);
18056
+ logger$13.error("CallRecoveryManager: pipeline error", err);
17865
18057
  this.transitionTo("idle");
17866
18058
  }
17867
18059
  });
@@ -17888,27 +18080,27 @@ var CallRecoveryManager = class extends Destroyable {
17888
18080
  reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
17889
18081
  timestamp: Date.now()
17890
18082
  });
17891
- logger$12.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
18083
+ logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
17892
18084
  });
17893
18085
  }
17894
18086
  passGateChecks(signalingReady) {
17895
18087
  if (this._callbacks.isNegotiating()) {
17896
- logger$12.debug("CallRecoveryManager: gate blocked — negotiation in progress");
18088
+ logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
17897
18089
  this.transitionTo("idle");
17898
18090
  return false;
17899
18091
  }
17900
18092
  if (!signalingReady) {
17901
- logger$12.debug("CallRecoveryManager: gate blocked — signaling not ready");
18093
+ logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
17902
18094
  this.transitionTo("idle");
17903
18095
  return false;
17904
18096
  }
17905
18097
  if (!this._callbacks.isCallConnected()) {
17906
- logger$12.debug("CallRecoveryManager: gate blocked — call not connected");
18098
+ logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
17907
18099
  this.transitionTo("idle");
17908
18100
  return false;
17909
18101
  }
17910
18102
  if (this.isCooldownActive()) {
17911
- logger$12.debug("CallRecoveryManager: gate blocked — cooldown active");
18103
+ logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
17912
18104
  this.transitionTo("cooldown");
17913
18105
  return false;
17914
18106
  }
@@ -17919,9 +18111,9 @@ var CallRecoveryManager = class extends Destroyable {
17919
18111
  }
17920
18112
  executeTieredRecovery(trigger) {
17921
18113
  this.transitionTo("recovering");
17922
- logger$12.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
18114
+ logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
17923
18115
  return (0, import_cjs$12.from)(this.runTiers(trigger)).pipe((0, import_cjs$12.tap)(() => this.startCooldown()), (0, import_cjs$12.catchError)((err) => {
17924
- logger$12.error("CallRecoveryManager: tiered recovery failed", err);
18116
+ logger$13.error("CallRecoveryManager: tiered recovery failed", err);
17925
18117
  this.startCooldown();
17926
18118
  return import_cjs$12.EMPTY;
17927
18119
  }));
@@ -17929,7 +18121,7 @@ var CallRecoveryManager = class extends Destroyable {
17929
18121
  async runTiers(trigger) {
17930
18122
  this.executeKeyframe(trigger.detail);
17931
18123
  if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
17932
- logger$12.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
18124
+ logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
17933
18125
  return;
17934
18126
  }
17935
18127
  if (this._attemptCount < this._config.maxAttempts) {
@@ -17946,13 +18138,13 @@ var CallRecoveryManager = class extends Destroyable {
17946
18138
  maxAttempts: this._config.maxAttempts,
17947
18139
  timestamp: Date.now()
17948
18140
  });
17949
- logger$12.warn("CallRecoveryManager: max recovery attempts reached");
18141
+ logger$13.warn("CallRecoveryManager: max recovery attempts reached");
17950
18142
  }
17951
18143
  }
17952
18144
  executeKeyframe(reason) {
17953
18145
  const now = Date.now();
17954
18146
  if (now < this._keyframeCooldownUntil) {
17955
- logger$12.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
18147
+ logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
17956
18148
  return;
17957
18149
  }
17958
18150
  if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
@@ -17961,7 +18153,7 @@ var CallRecoveryManager = class extends Destroyable {
17961
18153
  }
17962
18154
  if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
17963
18155
  this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
17964
- logger$12.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
18156
+ logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
17965
18157
  return;
17966
18158
  }
17967
18159
  this._keyframeBurstCount += 1;
@@ -17971,12 +18163,12 @@ var CallRecoveryManager = class extends Destroyable {
17971
18163
  reason,
17972
18164
  timestamp: now
17973
18165
  });
17974
- logger$12.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
18166
+ logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
17975
18167
  }
17976
18168
  async executeIceRestart(relayOnly) {
17977
18169
  this._attemptCount += 1;
17978
18170
  const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
17979
- logger$12.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
18171
+ logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
17980
18172
  this.emitEvent({
17981
18173
  action: "reinvite_started",
17982
18174
  reason: `${tier} ICE restart`,
@@ -17993,7 +18185,7 @@ var CallRecoveryManager = class extends Destroyable {
17993
18185
  maxAttempts: this._config.maxAttempts,
17994
18186
  timestamp: Date.now()
17995
18187
  });
17996
- logger$12.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
18188
+ logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
17997
18189
  this._attemptCount = 0;
17998
18190
  return true;
17999
18191
  }
@@ -18004,7 +18196,7 @@ var CallRecoveryManager = class extends Destroyable {
18004
18196
  maxAttempts: this._config.maxAttempts,
18005
18197
  timestamp: Date.now()
18006
18198
  });
18007
- logger$12.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
18199
+ logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
18008
18200
  return false;
18009
18201
  } catch {
18010
18202
  this.emitEvent({
@@ -18014,7 +18206,7 @@ var CallRecoveryManager = class extends Destroyable {
18014
18206
  maxAttempts: this._config.maxAttempts,
18015
18207
  timestamp: Date.now()
18016
18208
  });
18017
- logger$12.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
18209
+ logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
18018
18210
  return false;
18019
18211
  }
18020
18212
  }
@@ -18035,7 +18227,7 @@ var CallRecoveryManager = class extends Destroyable {
18035
18227
  transitionTo(state) {
18036
18228
  const prev = this._recoveryState$.value;
18037
18229
  if (prev !== state) {
18038
- logger$12.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
18230
+ logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
18039
18231
  this._recoveryState$.next(state);
18040
18232
  }
18041
18233
  }
@@ -18138,7 +18330,7 @@ function mosToQualityLevel(mos) {
18138
18330
  //#endregion
18139
18331
  //#region src/core/entities/Call.ts
18140
18332
  var import_cjs$11 = require_cjs();
18141
- const logger$11 = getLogger();
18333
+ const logger$12 = getLogger();
18142
18334
  /**
18143
18335
  * Ratio between the critical and warning RTT spike multipliers.
18144
18336
  * Warning threshold = baseline * warningMultiplier (default 3x)
@@ -18156,7 +18348,7 @@ const fromDestinationParams = (destination) => {
18156
18348
  });
18157
18349
  return params;
18158
18350
  } catch (error) {
18159
- logger$11.warn(`Failed to parse destination URI: ${destination}`, error);
18351
+ logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
18160
18352
  return {};
18161
18353
  }
18162
18354
  };
@@ -18290,7 +18482,7 @@ var WebRTCCall = class extends Destroyable {
18290
18482
  /** Toggles the call lock state, preventing or allowing new participants from joining. */
18291
18483
  async toggleLock() {
18292
18484
  const method = this.locked ? "call.unlock" : "call.lock";
18293
- await this.executeMethod(this.selfId ?? "", method, {});
18485
+ await this.executeMethod(this.callSelf, method, {});
18294
18486
  }
18295
18487
  /**
18296
18488
  * Toggles the hold state of the call (pauses/resumes local media transmission).
@@ -18339,14 +18531,24 @@ var WebRTCCall = class extends Destroyable {
18339
18531
  *
18340
18532
  * Constructs call context (node_id, call_id, member_id) and sends the RPC request.
18341
18533
  *
18342
- * @param target - Target member ID string, or a {@link MemberTarget} object.
18534
+ * @param target - Target {@link MemberTarget} triple, or the local member's
18535
+ * ID string for self-operations (any other string is rejected — a bare
18536
+ * member id cannot carry the remote member's own call context).
18343
18537
  * @param method - Verto method name (e.g. `'call.mute'`, `'call.member.remove'`).
18344
18538
  * @param args - Parameters for the RPC method.
18345
18539
  * @returns The RPC response.
18540
+ * @throws {CallNotReadyError} If the call has no self member context yet.
18541
+ * @throws {InvalidParams} If a string target is not the local member's ID.
18346
18542
  * @throws {JSONRPCError} If the RPC call returns an error.
18347
18543
  */
18348
18544
  async executeMethod(target, method, args) {
18349
- const params = this.buildMethodParams(target, args);
18545
+ const self = this.callSelf;
18546
+ if (typeof target === "string" && target !== self.member_id) throw new InvalidParams(`Target member ID ${target} does not match call's self member ID ${self.member_id}`);
18547
+ const params = {
18548
+ ...args,
18549
+ self,
18550
+ target: typeof target === "string" ? self : target
18551
+ };
18350
18552
  const request = buildRPCRequest({
18351
18553
  method,
18352
18554
  params
@@ -18356,29 +18558,27 @@ var WebRTCCall = class extends Destroyable {
18356
18558
  if (isJSONRPCErrorResponse(response)) throw new JSONRPCError(parseInt(response.result?.code ?? "0"), `Error response from method ${method}: ${response.result?.code} ${response.result?.message}`, void 0, void 0, request.id);
18357
18559
  return response;
18358
18560
  } catch (error) {
18359
- logger$11.error(`[Call] Error executing method ${method} with params`, params, error);
18561
+ logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
18360
18562
  throw error;
18361
18563
  }
18362
18564
  }
18363
- buildMethodParams(target, args) {
18364
- const self = {
18365
- node_id: this.nodeId ?? "",
18366
- call_id: this.id,
18367
- member_id: this.vertoManager.selfId ?? ""
18368
- };
18369
- if (typeof target === "object") return {
18370
- ...args,
18371
- self,
18372
- targets: [target]
18373
- };
18565
+ /**
18566
+ * The local leg's member triple — sent as `self` in every member RPC
18567
+ * envelope, and as the `target` of call-scoped self-operations (e.g. lock,
18568
+ * layout).
18569
+ *
18570
+ * @throws {CallNotReadyError} Before `call.joined` delivers the self member
18571
+ * context (`selfId`/`nodeId`) an RPC without it cannot be routed, so fail
18572
+ * fast instead of sending a doomed request.
18573
+ */
18574
+ get callSelf() {
18575
+ const node_id = this.nodeId;
18576
+ const member_id = this.vertoManager.selfId;
18577
+ if (!node_id || !member_id) throw new CallNotReadyError(this.id);
18374
18578
  return {
18375
- ...args,
18376
- self,
18377
- target: {
18378
- node_id: this.nodeId ?? "",
18379
- call_id: this.id,
18380
- member_id: target
18381
- }
18579
+ node_id,
18580
+ call_id: this.id,
18581
+ member_id
18382
18582
  };
18383
18583
  }
18384
18584
  /** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
@@ -18559,9 +18759,9 @@ var WebRTCCall = class extends Destroyable {
18559
18759
  */
18560
18760
  initResilienceSubsystems() {
18561
18761
  const pc = this.rtcPeerConnection;
18562
- logger$11.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
18762
+ logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
18563
18763
  if (!pc) {
18564
- logger$11.warn("[Call] No peer connection available, skipping resilience init");
18764
+ logger$12.warn("[Call] No peer connection available, skipping resilience init");
18565
18765
  return;
18566
18766
  }
18567
18767
  try {
@@ -18596,14 +18796,14 @@ var WebRTCCall = class extends Destroyable {
18596
18796
  disableVideo: () => {
18597
18797
  try {
18598
18798
  this.vertoManager.muteMainVideoInputDevice();
18599
- logger$11.debug("[Call] Recovery manager disabled video");
18799
+ logger$12.debug("[Call] Recovery manager disabled video");
18600
18800
  } catch {
18601
- logger$11.debug("[Call] Recovery manager failed to disable video");
18801
+ logger$12.debug("[Call] Recovery manager failed to disable video");
18602
18802
  }
18603
18803
  },
18604
18804
  enableVideo: () => {
18605
18805
  this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
18606
- logger$11.debug("[Call] Recovery manager failed to enable video");
18806
+ logger$12.debug("[Call] Recovery manager failed to enable video");
18607
18807
  });
18608
18808
  },
18609
18809
  isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
@@ -18653,7 +18853,7 @@ var WebRTCCall = class extends Destroyable {
18653
18853
  this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
18654
18854
  this._recoveryEvent$.next(event);
18655
18855
  if (event.action === "max_attempts_reached") {
18656
- logger$11.warn("[Call] All recovery attempts exhausted, terminating call");
18856
+ logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
18657
18857
  this.emitError({
18658
18858
  kind: "network",
18659
18859
  fatal: true,
@@ -18673,13 +18873,13 @@ var WebRTCCall = class extends Destroyable {
18673
18873
  else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
18674
18874
  });
18675
18875
  this.subscribeTo(this.clientSession.authenticated$.pipe((0, import_cjs$11.skip)(1), (0, import_cjs$11.filter)(Boolean)), () => {
18676
- logger$11.debug("[Call] WebSocket reconnected — notifying recovery manager");
18876
+ logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
18677
18877
  this._recoveryManager?.handleWebSocketReconnect();
18678
18878
  });
18679
18879
  this._statsMonitor.start();
18680
- logger$11.debug("[Call] Resilience subsystems initialized for call", this.id);
18880
+ logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
18681
18881
  } catch (error) {
18682
- logger$11.warn("[Call] Failed to initialize resilience subsystems:", error);
18882
+ logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
18683
18883
  }
18684
18884
  }
18685
18885
  /**
@@ -18762,19 +18962,19 @@ var WebRTCCall = class extends Destroyable {
18762
18962
  }
18763
18963
  isCallSessionEvent(event) {
18764
18964
  try {
18765
- logger$11.debug("[Call] Checking if event is for this call session:", event);
18965
+ logger$12.debug("[Call] Checking if event is for this call session:", event);
18766
18966
  const callId = getValueFrom(event, "params.params.callID") ?? getValueFrom(event, "params.call_id");
18767
18967
  const roomSessionId = getValueFrom(event, "params.room_session_id");
18768
- logger$11.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
18968
+ logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
18769
18969
  return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
18770
18970
  } catch (error) {
18771
- logger$11.error("[Call] Error checking if event is for this call session:", error);
18971
+ logger$12.error("[Call] Error checking if event is for this call session:", error);
18772
18972
  return false;
18773
18973
  }
18774
18974
  }
18775
18975
  get callSessionEvents$() {
18776
18976
  return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, import_cjs$11.filter)((event) => this.isCallSessionEvent(event)), (0, import_cjs$11.tap)((event) => {
18777
- logger$11.debug("[Call] Received call session event:", event);
18977
+ logger$12.debug("[Call] Received call session event:", event);
18778
18978
  }), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
18779
18979
  }
18780
18980
  /** Observable of call-updated events. */
@@ -18844,16 +19044,16 @@ var WebRTCCall = class extends Destroyable {
18844
19044
  this._customSubscriptions.set(eventType, filtered$);
18845
19045
  }, (error) => {
18846
19046
  this._customSubscriptions.delete(eventType);
18847
- logger$11.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
19047
+ logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
18848
19048
  });
18849
19049
  this._customSubscriptions.set(eventType, filtered$);
18850
19050
  return filtered$;
18851
19051
  }
18852
19052
  get webrtcMessages$() {
18853
- return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$11.debug("[Call] Event is a WebRTC message event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
19053
+ return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$12.debug("[Call] Event is a WebRTC message event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
18854
19054
  }
18855
19055
  get callEvent$() {
18856
- return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$11.debug("[Call] Event is a call event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
19056
+ return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$12.debug("[Call] Event is a call event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
18857
19057
  }
18858
19058
  get layoutEvent$() {
18859
19059
  return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(filterAs(isLayoutChangedMetadata, "params")));
@@ -18931,11 +19131,27 @@ var WebRTCCall = class extends Destroyable {
18931
19131
  return this.deferEmission(this._answered$.asObservable());
18932
19132
  }
18933
19133
  /**
18934
- * Sets the call layout and participant positions.
19134
+ * Sets the call layout and, optionally, individual participant positions.
19135
+ *
19136
+ * The gateway `call.layout.set` DTO has **no** `positions` member, so when
19137
+ * `positions` is provided this method issues a `call.member.position.set`
19138
+ * request per member (via {@link Participant.setPosition}, which keys each
19139
+ * position by that member's own call context) alongside `call.layout.set`
19140
+ * (issue #19400, Flag #6).
19141
+ *
19142
+ * **These operations are NOT atomic.** The layout is applied first, then each
19143
+ * member position sequentially, so members may briefly flash into their
19144
+ * default slots before being moved to the requested positions. Targeted
19145
+ * members are validated upfront, though: when any of them has no
19146
+ * {@link Participant.target | member call context} yet, the whole call
19147
+ * rejects before any request is sent and the layout is left unchanged.
18935
19148
  *
18936
19149
  * @param layout - Layout name (must be one of {@link layouts}).
18937
- * @param positions - Map of member IDs to {@link VideoPosition} values.
19150
+ * @param positions - Optional map of member IDs to {@link VideoPosition} values.
19151
+ * When omitted or empty, only the layout is changed.
18938
19152
  * @throws {InvalidParams} If the layout is not in the available {@link layouts}.
19153
+ * @throws {ParticipantNotReadyError} If a targeted member's call context has
19154
+ * not been received yet — thrown before any request is sent.
18939
19155
  *
18940
19156
  * @example
18941
19157
  * ```ts
@@ -18946,11 +19162,19 @@ var WebRTCCall = class extends Destroyable {
18946
19162
  */
18947
19163
  async setLayout(layout, positions) {
18948
19164
  if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
19165
+ const targets = [];
19166
+ for (const [memberId, position] of Object.entries(positions ?? {})) {
19167
+ const participant = this.participants.find((p) => p.id === memberId);
19168
+ if (!participant) {
19169
+ logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
19170
+ continue;
19171
+ }
19172
+ participant.target;
19173
+ targets.push([participant, position]);
19174
+ }
18949
19175
  const selfId = await (0, import_cjs$11.firstValueFrom)(this.selfId$.pipe((0, import_cjs$11.filter)((id) => id !== null)));
18950
- await this.executeMethod(selfId, "call.layout.set", {
18951
- layout,
18952
- positions
18953
- });
19176
+ await this.executeMethod(selfId, "call.layout.set", { layout });
19177
+ for (const [participant, position] of targets) await participant.setPosition(position);
18954
19178
  }
18955
19179
  /**
18956
19180
  * Transfers the call to another destination.
@@ -18982,7 +19206,7 @@ var WebRTCCall = class extends Destroyable {
18982
19206
  setLocalMicrophoneGain(value) {
18983
19207
  const pipeline = this.vertoManager.ensureLocalAudioPipeline();
18984
19208
  if (!pipeline) {
18985
- logger$11.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
19209
+ logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
18986
19210
  return;
18987
19211
  }
18988
19212
  const percent = Math.max(0, Math.min(200, value));
@@ -19027,7 +19251,7 @@ var WebRTCCall = class extends Destroyable {
19027
19251
  enablePushToTalk() {
19028
19252
  const pipeline = this.vertoManager.ensureLocalAudioPipeline();
19029
19253
  if (!pipeline) {
19030
- logger$11.warn("[Call] enablePushToTalk: audio pipeline unavailable");
19254
+ logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
19031
19255
  return;
19032
19256
  }
19033
19257
  pipeline.setPTTActive(false);
@@ -19124,6 +19348,7 @@ function inferCallErrorKind(error) {
19124
19348
  if (error instanceof RPCTimeoutError) return "timeout";
19125
19349
  if (error instanceof JSONRPCError) return "signaling";
19126
19350
  if (error instanceof MediaTrackError) return "media";
19351
+ if (error instanceof MediaAccessError) return "media";
19127
19352
  if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
19128
19353
  return "internal";
19129
19354
  }
@@ -19140,6 +19365,7 @@ const RECOVERABLE_RPC_CODES = new Set([
19140
19365
  function isFatalError(error) {
19141
19366
  if (error instanceof VertoPongError) return false;
19142
19367
  if (error instanceof MediaTrackError) return false;
19368
+ if (error instanceof MediaAccessError) return error.fatal;
19143
19369
  if (error instanceof RPCTimeoutError) return false;
19144
19370
  if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
19145
19371
  return true;
@@ -19165,10 +19391,10 @@ var CallFactory = class {
19165
19391
  return {
19166
19392
  vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
19167
19393
  nodeId: options.nodeId,
19168
- onError: (error) => {
19394
+ onError: (error, options$1) => {
19169
19395
  const callError = {
19170
19396
  kind: inferCallErrorKind(error),
19171
- fatal: isFatalError(error),
19397
+ fatal: options$1?.fatal ?? isFatalError(error),
19172
19398
  error,
19173
19399
  callId: callInstance.id
19174
19400
  };
@@ -19190,7 +19416,7 @@ var CallFactory = class {
19190
19416
  //#endregion
19191
19417
  //#region src/behaviors/Collection.ts
19192
19418
  var import_cjs$10 = require_cjs();
19193
- const logger$10 = getLogger();
19419
+ const logger$11 = getLogger();
19194
19420
  var Fetcher = class {
19195
19421
  constructor(endpoint, params, http) {
19196
19422
  this.endpoint = endpoint;
@@ -19214,7 +19440,7 @@ var Fetcher = class {
19214
19440
  this.hasMore = !!this.nextUrl;
19215
19441
  return result.data.filter(this.filter).map(this.mapper);
19216
19442
  }
19217
- logger$10.error("Failed to fetch entity");
19443
+ logger$11.error("Failed to fetch entity");
19218
19444
  return [];
19219
19445
  }
19220
19446
  async id(v) {
@@ -19290,7 +19516,7 @@ var EntityCollection = class extends Destroyable {
19290
19516
  this._hasMore$.next(this.fetchController.hasMore ?? false);
19291
19517
  this._loading$.next(false);
19292
19518
  } catch (error) {
19293
- logger$10.error(`Failed to fetch initial collection data`, error);
19519
+ logger$11.error(`Failed to fetch initial collection data`, error);
19294
19520
  this._hasMore$.next(this.fetchController.hasMore ?? false);
19295
19521
  this._loading$.next(false);
19296
19522
  this.onError?.(new CollectionFetchError("fetchMore", error));
@@ -19304,7 +19530,7 @@ var EntityCollection = class extends Destroyable {
19304
19530
  if (data) this.upsertData(data);
19305
19531
  return data;
19306
19532
  } catch (error) {
19307
- logger$10.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
19533
+ logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
19308
19534
  this._loading$.next(false);
19309
19535
  this.onError?.(new CollectionFetchError(`tryFetch(${String(key)})`, error));
19310
19536
  }
@@ -19553,13 +19779,13 @@ var Address = class extends Destroyable {
19553
19779
  //#endregion
19554
19780
  //#region src/core/utils.ts
19555
19781
  var import_cjs$8 = require_cjs();
19556
- const logger$9 = getLogger();
19782
+ const logger$10 = getLogger();
19557
19783
  const isRPCConnectResult = (e) => {
19558
- logger$9.debug("isRPCConnectResult check:", e);
19784
+ logger$10.debug("isRPCConnectResult check:", e);
19559
19785
  if (!e || typeof e !== "object") return false;
19560
19786
  const result = e;
19561
19787
  const is = typeof result.identity === "string" && typeof result.protocol === "string" && typeof result.authorization === "object" && typeof result.authorization.jti === "string" && typeof result.authorization.project_id === "string" && typeof result.authorization.fabric_subscriber === "object";
19562
- logger$9.debug("isRPCConnectResult check result:", is);
19788
+ logger$10.debug("isRPCConnectResult check result:", is);
19563
19789
  return is;
19564
19790
  };
19565
19791
  var PendingRPC = class PendingRPC {
@@ -19568,7 +19794,7 @@ var PendingRPC = class PendingRPC {
19568
19794
  }
19569
19795
  constructor(request, responses$, options) {
19570
19796
  this.id = v4_default();
19571
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
19797
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
19572
19798
  this.request = request;
19573
19799
  const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
19574
19800
  const signal = options?.signal;
@@ -19594,22 +19820,22 @@ var PendingRPC = class PendingRPC {
19594
19820
  isSettled = true;
19595
19821
  if (response.error) {
19596
19822
  const rpcError = new JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
19597
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
19823
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
19598
19824
  reject(rpcError);
19599
19825
  } else {
19600
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
19826
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
19601
19827
  resolve(response);
19602
19828
  }
19603
19829
  subscription.unsubscribe();
19604
19830
  },
19605
19831
  error: (error) => {
19606
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
19832
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
19607
19833
  isSettled = true;
19608
19834
  reject(error);
19609
19835
  subscription.unsubscribe();
19610
19836
  },
19611
19837
  complete: () => {
19612
- logger$9.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
19838
+ logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
19613
19839
  if (!isSettled) reject(new RPCTimeoutError(request.id, timeoutMs));
19614
19840
  subscription.unsubscribe();
19615
19841
  }
@@ -19630,7 +19856,18 @@ var PendingRPC = class PendingRPC {
19630
19856
  //#endregion
19631
19857
  //#region src/managers/ClientSessionManager.ts
19632
19858
  var import_cjs$7 = require_cjs();
19633
- const logger$8 = getLogger();
19859
+ const logger$9 = getLogger();
19860
+ /**
19861
+ * Decide whether an error emitted on `call.errors$` during dial should
19862
+ * abort the dial. A non-fatal MediaAccessError means the call degraded to
19863
+ * receive-only and still connects — everything else rejects `dial()` with
19864
+ * the real cause.
19865
+ *
19866
+ * Pure function — exported for unit testing.
19867
+ */
19868
+ function shouldAbortDial(callError) {
19869
+ return callError.fatal || !(callError.error instanceof MediaAccessError);
19870
+ }
19634
19871
  const getAddressSearchURI = (options) => {
19635
19872
  const to = options.to?.split("?")[0];
19636
19873
  const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
@@ -19728,7 +19965,7 @@ var ClientSessionManager = class extends Destroyable {
19728
19965
  try {
19729
19966
  return await this.transport.execute(request, options);
19730
19967
  } catch (error) {
19731
- logger$8.debug("[Session] Execute Error", error);
19968
+ logger$9.debug("[Session] Execute Error", error);
19732
19969
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
19733
19970
  throw error;
19734
19971
  }
@@ -19742,13 +19979,13 @@ var ClientSessionManager = class extends Destroyable {
19742
19979
  return true;
19743
19980
  }
19744
19981
  setupMessageHandlers() {
19745
- logger$8.debug("[Session] Setting up message handlers");
19982
+ logger$9.debug("[Session] Setting up message handlers");
19746
19983
  this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
19747
- logger$8.debug("[Session] Authorization state event received:", authStateEvent);
19984
+ logger$9.debug("[Session] Authorization state event received:", authStateEvent);
19748
19985
  try {
19749
19986
  await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
19750
19987
  } catch (error) {
19751
- logger$8.error("[Session] Failed to handle authorization state update:", error);
19988
+ logger$9.error("[Session] Failed to handle authorization state update:", error);
19752
19989
  this._errors$.next(new AuthStateHandlerError(error));
19753
19990
  }
19754
19991
  });
@@ -19756,29 +19993,29 @@ var ClientSessionManager = class extends Destroyable {
19756
19993
  if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
19757
19994
  });
19758
19995
  this.subscribeTo(this.transport.connectionStatus$.pipe((0, import_cjs$7.filter)((status) => status === "connected"), (0, import_cjs$7.exhaustMap)(() => {
19759
- logger$8.debug("[Session] Connection established, initiating authentication");
19996
+ logger$9.debug("[Session] Connection established, initiating authentication");
19760
19997
  return (0, import_cjs$7.from)(this.authenticate()).pipe((0, import_cjs$7.catchError)((error) => {
19761
19998
  this.handleAuthenticationError(error).catch((err) => {
19762
- logger$8.error("[Session] Error handling authentication failure:", err);
19999
+ logger$9.error("[Session] Error handling authentication failure:", err);
19763
20000
  });
19764
20001
  return import_cjs$7.EMPTY;
19765
20002
  }));
19766
20003
  })), void 0);
19767
20004
  this.subscribeTo(this.vertoInvite$, async (invite) => {
19768
- logger$8.debug("[Session] Verto invite received:", invite);
20005
+ logger$9.debug("[Session] Verto invite received:", invite);
19769
20006
  try {
19770
20007
  await this.createInboundCall(invite);
19771
20008
  } catch (error) {
19772
- logger$8.error("[Session] Error handling Verto invite:", error);
20009
+ logger$9.error("[Session] Error handling Verto invite:", error);
19773
20010
  this._errors$.next(new VertoInviteHandlerError(error));
19774
20011
  }
19775
20012
  });
19776
20013
  this.subscribeTo(this.vertoAttach$, async (attach) => {
19777
- logger$8.debug("[Session] Verto attach received:", attach);
20014
+ logger$9.debug("[Session] Verto attach received:", attach);
19778
20015
  try {
19779
20016
  await this.handleVertoAttach(attach);
19780
20017
  } catch (error) {
19781
- logger$8.error("[Session] Error handling Verto attach:", error);
20018
+ logger$9.error("[Session] Error handling Verto attach:", error);
19782
20019
  this._errors$.next(new VertoAttachHandlerError(error));
19783
20020
  }
19784
20021
  });
@@ -19788,36 +20025,36 @@ var ClientSessionManager = class extends Destroyable {
19788
20025
  const storedState = await this.storage.getItem(this.authorizationStateKey);
19789
20026
  this.authorizationState$.next(storedState ?? void 0);
19790
20027
  } catch (error) {
19791
- logger$8.error("Failed to retrieve authorization state from storage:", error);
20028
+ logger$9.error("Failed to retrieve authorization state from storage:", error);
19792
20029
  this.authorizationState$.next(void 0);
19793
20030
  }
19794
20031
  }
19795
20032
  async updateAuthorizationStateInStorage(authorizationState) {
19796
20033
  if (!authorizationState) {
19797
- logger$8.debug("[Session] Removing authorization state from storage");
20034
+ logger$9.debug("[Session] Removing authorization state from storage");
19798
20035
  try {
19799
20036
  await this.storage.removeItem(this.authorizationStateKey);
19800
20037
  this.authorizationState$.next(void 0);
19801
20038
  } catch (error) {
19802
- logger$8.error("Failed to remove authorization state from storage:", error);
20039
+ logger$9.error("Failed to remove authorization state from storage:", error);
19803
20040
  throw error;
19804
20041
  }
19805
20042
  return;
19806
20043
  }
19807
20044
  try {
19808
- logger$8.debug("[Session] Updating authorization state in storage");
20045
+ logger$9.debug("[Session] Updating authorization state in storage");
19809
20046
  await this.storage.setItem(this.authorizationStateKey, authorizationState);
19810
20047
  this.authorizationState$.next(authorizationState);
19811
20048
  } catch (error) {
19812
- logger$8.error("Failed to retrieve authorization state from storage:", error);
20049
+ logger$9.error("Failed to retrieve authorization state from storage:", error);
19813
20050
  throw error;
19814
20051
  }
19815
20052
  }
19816
20053
  get authStateEvent$() {
19817
20054
  return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, import_cjs$7.tap)((msg) => {
19818
- logger$8.debug("[Session] Received incoming message:", msg);
20055
+ logger$9.debug("[Session] Received incoming message:", msg);
19819
20056
  }), filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, import_cjs$7.tap)((event) => {
19820
- logger$8.debug("[Session] Authorization state event received:", event.authorization_state);
20057
+ logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
19821
20058
  })));
19822
20059
  }
19823
20060
  get signalingEvent$() {
@@ -19855,42 +20092,72 @@ var ClientSessionManager = class extends Destroyable {
19855
20092
  await (0, import_cjs$7.firstValueFrom)(this.authenticated$.pipe((0, import_cjs$7.takeUntil)(this.destroyed$), (0, import_cjs$7.filter)(Boolean), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)({ first: 15e3 })));
19856
20093
  }
19857
20094
  async handleAuthenticationError(error) {
19858
- logger$8.error("Authentication error:", error);
20095
+ logger$9.error("Authentication error:", error);
19859
20096
  const isRecoverableAuthError = error instanceof JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
19860
20097
  const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
19861
20098
  if (isRecoverableAuthError && hasStoredState) {
19862
- logger$8.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
20099
+ logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
19863
20100
  try {
19864
20101
  await this.cleanupStoredConnectionParams();
19865
20102
  } catch (cleanupError) {
19866
- logger$8.error("Failed to cleanup stored connection params:", cleanupError);
20103
+ logger$9.error("Failed to cleanup stored connection params:", cleanupError);
19867
20104
  } finally {
19868
20105
  this.transport.reconnect();
19869
20106
  }
19870
20107
  } else this._errors$.next(error);
19871
20108
  }
20109
+ /**
20110
+ * Clear the resume state (authorization_state + protocol) only.
20111
+ *
20112
+ * This is the stale-auth-state recovery helper used by handleAuthError:
20113
+ * the server rejected a reconnect, so the resume state is discarded and a
20114
+ * fresh connect follows. Attach records are deliberately preserved — the
20115
+ * session lives on through the reconnect and reattachCalls() needs the
20116
+ * stored call references afterwards. Do NOT add detachAll() here.
20117
+ *
20118
+ * For public teardown (disconnect/destroy), use {@link teardownSessionState}
20119
+ * instead, which clears the attach records as well.
20120
+ */
19872
20121
  async cleanupStoredConnectionParams() {
19873
20122
  await this.transport.setProtocol(void 0);
19874
20123
  await this.updateAuthorizationStateInStorage(void 0);
19875
20124
  this._authorization$.next(void 0);
19876
20125
  }
20126
+ /**
20127
+ * Public-teardown helper for disconnect()/destroy(). Clears the resume
20128
+ * state (authorization_state + protocol) AND the attach records as one
20129
+ * atomic unit.
20130
+ *
20131
+ * The two stores are coupled: the backend only honors attach records
20132
+ * within the session identified by the resume state, so ending the
20133
+ * session must clear both. Clearing one without the other strands records
20134
+ * no future session can honor (disconnect) or revives a session the
20135
+ * developer explicitly ended (destroy).
20136
+ *
20137
+ * Distinct from {@link cleanupStoredConnectionParams}, which keeps the
20138
+ * attach records for the stale-auth-state recovery path.
20139
+ */
20140
+ async teardownSessionState() {
20141
+ await this.cleanupStoredConnectionParams();
20142
+ await this.attachManager.detachAll();
20143
+ }
19877
20144
  async updateAuthState(authorization_state) {
19878
20145
  try {
19879
20146
  await this.storage.setItem(this.authorizationStateKey, authorization_state);
19880
20147
  } catch (error) {
19881
- logger$8.error("Failed to update authorization state in storage:", error);
20148
+ logger$9.error("Failed to update authorization state in storage:", error);
19882
20149
  this._errors$.next(new AuthStateHandlerError(error));
19883
20150
  }
19884
20151
  }
19885
20152
  async reauthenticate(token, dpopToken, options) {
19886
- logger$8.debug("[Session] Re-authenticating session");
20153
+ logger$9.debug("[Session] Re-authenticating session");
19887
20154
  try {
19888
20155
  let resolvedDpopToken = dpopToken;
19889
20156
  if (!resolvedDpopToken && this.dpopManager?.initialized) try {
19890
20157
  resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
19891
20158
  } catch (error) {
19892
20159
  if (this.clientBound) throw error;
19893
- logger$8.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
20160
+ logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
19894
20161
  }
19895
20162
  const request = RPCReauthenticate({
19896
20163
  project: this._authorization$.value?.project_id ?? "",
@@ -19898,24 +20165,24 @@ var ClientSessionManager = class extends Destroyable {
19898
20165
  ...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
19899
20166
  });
19900
20167
  await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(request)).pipe(throwOnRPCError(), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
19901
- logger$8.error("[Session] Re-authentication RPC failed:", err);
20168
+ logger$9.error("[Session] Re-authentication RPC failed:", err);
19902
20169
  throw err;
19903
20170
  })));
19904
20171
  if (options?.clientBound) this._wasClientBound = true;
19905
- logger$8.debug("[Session] Re-authentication successful, updating stored auth state");
20172
+ logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
19906
20173
  } catch (error) {
19907
- logger$8.error("[Session] Re-authentication failed:", error);
20174
+ logger$9.error("[Session] Re-authentication failed:", error);
19908
20175
  this._errors$.next(new AuthStateHandlerError(error));
19909
20176
  throw error;
19910
20177
  }
19911
20178
  }
19912
20179
  async authenticate() {
19913
- logger$8.debug("[Session] Starting authentication process");
20180
+ logger$9.debug("[Session] Starting authentication process");
19914
20181
  const persistedParams = await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.combineLatest)({
19915
20182
  protocol: this.transport.protocol$,
19916
20183
  authorization_state: this.authorizationState$
19917
20184
  }).pipe((0, import_cjs$7.take)(1)));
19918
- logger$8.debug("[Session] Persisted params:\n", {
20185
+ logger$9.debug("[Session] Persisted params:\n", {
19919
20186
  protocol: persistedParams.protocol,
19920
20187
  authStateLength: persistedParams.authorization_state?.length
19921
20188
  });
@@ -19923,16 +20190,16 @@ var ClientSessionManager = class extends Destroyable {
19923
20190
  const storedToken = this.getCredential().token;
19924
20191
  const isReconnect = hasReconnectState && storedToken;
19925
20192
  let dpopToken;
19926
- if (isReconnect) logger$8.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
20193
+ if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
19927
20194
  else if (this.onBeforeReconnect && this.clientBound) {
19928
- logger$8.debug("[Session] Refreshing credentials before fresh connect");
20195
+ logger$9.debug("[Session] Refreshing credentials before fresh connect");
19929
20196
  await this.onBeforeReconnect();
19930
20197
  }
19931
20198
  if ((!isReconnect || this.clientBound) && this.dpopManager?.initialized) try {
19932
20199
  dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
19933
20200
  } catch (error) {
19934
20201
  if (this.clientBound) throw error;
19935
- logger$8.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
20202
+ logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
19936
20203
  }
19937
20204
  const rpcConnectRequest = RPCConnect({
19938
20205
  authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
@@ -19949,12 +20216,12 @@ var ClientSessionManager = class extends Destroyable {
19949
20216
  } : {}
19950
20217
  });
19951
20218
  const response = await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(rpcConnectRequest)).pipe(throwOnRPCError(), (0, import_cjs$7.map)((res) => res.result), (0, import_cjs$7.filter)(isRPCConnectResult), (0, import_cjs$7.tap)(() => {
19952
- logger$8.debug("[Session] Response passed filter, processing authentication result");
20219
+ logger$9.debug("[Session] Response passed filter, processing authentication result");
19953
20220
  }), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
19954
- logger$8.error("[Session] Authentication RPC failed:", err);
20221
+ logger$9.error("[Session] Authentication RPC failed:", err);
19955
20222
  throw err;
19956
20223
  })));
19957
- logger$8.debug("[Session] Processing authentication result:", {
20224
+ logger$9.debug("[Session] Processing authentication result:", {
19958
20225
  hasProtocol: !!response.protocol,
19959
20226
  hasAuthorization: !!response.authorization,
19960
20227
  hasIceServers: !!response.ice_servers
@@ -19963,12 +20230,12 @@ var ClientSessionManager = class extends Destroyable {
19963
20230
  this._authorization$.next(response.authorization);
19964
20231
  this._iceServers$.next(response.ice_servers ?? []);
19965
20232
  this._authState$.next({ kind: "authenticated" });
19966
- logger$8.debug("[Session] Authentication completed successfully");
20233
+ logger$9.debug("[Session] Authentication completed successfully");
19967
20234
  }
19968
20235
  async disconnect() {
19969
20236
  this.transport.disconnect();
19970
20237
  this._authState$.next({ kind: "unauthenticated" });
19971
- await this.cleanupStoredConnectionParams();
20238
+ await this.teardownSessionState();
19972
20239
  }
19973
20240
  async createInboundCall(invite) {
19974
20241
  const callSession = await this.createCall({
@@ -19999,11 +20266,11 @@ var ClientSessionManager = class extends Destroyable {
19999
20266
  async handleVertoAttach(attach) {
20000
20267
  const { callID } = attach;
20001
20268
  if (callID in this._calls$.value) {
20002
- logger$8.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
20269
+ logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
20003
20270
  return;
20004
20271
  }
20005
20272
  const storedOptions = await this.attachManager.consumePendingAttachment(callID);
20006
- logger$8.debug(`[Session] Creating reattached call for callID: ${callID}`);
20273
+ logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
20007
20274
  const callSession = await this.createCall({
20008
20275
  nodeId: attach.node_id,
20009
20276
  callId: callID,
@@ -20027,14 +20294,14 @@ var ClientSessionManager = class extends Destroyable {
20027
20294
  to: destinationURI,
20028
20295
  ...options
20029
20296
  });
20030
- 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.take)(1), (0, import_cjs$7.switchMap)((callError) => (0, import_cjs$7.throwError)(() => callError.error)))));
20297
+ 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)))));
20031
20298
  this._calls$.next({
20032
20299
  [`${callSession.id}`]: callSession,
20033
20300
  ...this._calls$.value
20034
20301
  });
20035
20302
  return callSession;
20036
20303
  } catch (error) {
20037
- logger$8.error("[Session] Error creating outbound call:", error);
20304
+ logger$9.error("[Session] Error creating outbound call:", error);
20038
20305
  callSession?.destroy();
20039
20306
  const callError = new CallCreateError(error instanceof import_cjs$7.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
20040
20307
  this._errors$.next(callError);
@@ -20052,7 +20319,7 @@ var ClientSessionManager = class extends Destroyable {
20052
20319
  address = this._directory.get(addressId);
20053
20320
  if (!address) throw new DependencyError(`Address ID: ${addressId} not found`);
20054
20321
  } catch {
20055
- logger$8.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
20322
+ logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
20056
20323
  }
20057
20324
  const callSession = this.callFactory.createCall(address, { ...options });
20058
20325
  this.subscribeTo(callSession.status$.pipe((0, import_cjs$7.filter)((status) => status === "destroyed"), (0, import_cjs$7.take)(1)), () => {
@@ -20061,7 +20328,7 @@ var ClientSessionManager = class extends Destroyable {
20061
20328
  });
20062
20329
  return callSession;
20063
20330
  } catch (error) {
20064
- logger$8.error("[Session] Error creating call session:", error);
20331
+ logger$9.error("[Session] Error creating call session:", error);
20065
20332
  throw new CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
20066
20333
  }
20067
20334
  }
@@ -20110,7 +20377,7 @@ const isString = (obj) => typeof obj === "string";
20110
20377
  //#endregion
20111
20378
  //#region src/managers/ConversationsManager.ts
20112
20379
  var import_cjs$6 = require_cjs();
20113
- const logger$7 = getLogger();
20380
+ const logger$8 = getLogger();
20114
20381
  var ConversationMessagesFetcher = class extends Fetcher {
20115
20382
  constructor(groupId, http) {
20116
20383
  super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
@@ -20150,13 +20417,13 @@ var ConversationsManager = class {
20150
20417
  }
20151
20418
  throw new ConversationError("Join Failed - Unexpected response");
20152
20419
  } catch (error) {
20153
- logger$7.error("[ConversationsManager] Failed to join conversation:", error);
20420
+ logger$8.error("[ConversationsManager] Failed to join conversation:", error);
20154
20421
  throw error;
20155
20422
  }
20156
20423
  }
20157
20424
  async getConversationMessageCollection(addressId) {
20158
20425
  const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
20159
- return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$7.debug("[ConversationsManager ] Conversation Event:", event)), (0, import_cjs$6.map)((params) => ({ ...params }))), this.http, this.onError));
20426
+ return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$8.debug("[ConversationsManager ] Conversation Event:", event)), (0, import_cjs$6.map)((params) => ({ ...params }))), this.http, this.onError));
20160
20427
  }
20161
20428
  async sendText(text, destinationAddressId) {
20162
20429
  const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
@@ -20173,7 +20440,7 @@ var ConversationsManager = class {
20173
20440
  })).ok) return;
20174
20441
  throw new ConversationError("Send Text Failed - Unexpected response");
20175
20442
  } catch (error) {
20176
- logger$7.error("[ConversationsManager] Failed to send text message:", error);
20443
+ logger$8.error("[ConversationsManager] Failed to send text message:", error);
20177
20444
  throw error;
20178
20445
  }
20179
20446
  }
@@ -20182,7 +20449,7 @@ var ConversationsManager = class {
20182
20449
  //#endregion
20183
20450
  //#region src/managers/DeviceTokenManager.ts
20184
20451
  var import_cjs$5 = require_cjs();
20185
- const logger$6 = getLogger();
20452
+ const logger$7 = getLogger();
20186
20453
  /**
20187
20454
  * Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
20188
20455
  * 1. `data.expires_at` — server-provided absolute timestamp
@@ -20192,7 +20459,7 @@ const logger$6 = getLogger();
20192
20459
  function resolveExpiresAt(data) {
20193
20460
  if (data.expires_at) return data.expires_at;
20194
20461
  if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
20195
- logger$6.warn("[DeviceToken] Could not determine token expiry, using default");
20462
+ logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
20196
20463
  return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
20197
20464
  }
20198
20465
  /**
@@ -20225,11 +20492,12 @@ var DeviceTokenManager = class extends Destroyable {
20225
20492
  this.getCredential = getCredential;
20226
20493
  this._currentToken$ = this.createBehaviorSubject(null);
20227
20494
  this._refreshInProgress = false;
20495
+ this._paused = false;
20228
20496
  this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
20229
20497
  this.subscribeTo(this._currentToken$.pipe((0, import_cjs$5.filter)(Boolean), (0, import_cjs$5.switchMap)((tokenData) => {
20230
20498
  const expiresAt = resolveExpiresAt(tokenData);
20231
20499
  const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
20232
- logger$6.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
20500
+ logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
20233
20501
  return (0, import_cjs$5.timer)(refreshIn);
20234
20502
  })), () => {
20235
20503
  this.executeRefresh();
@@ -20243,7 +20511,12 @@ var DeviceTokenManager = class extends Destroyable {
20243
20511
  * Activates the Client Bound SAT flow when the user's token has
20244
20512
  * `sat:refresh` scope.
20245
20513
  *
20246
- * Steps:
20514
+ * Returns an {@link ActivationResult} indicating whether the manager
20515
+ * took ownership of refresh duties. The caller must use the `activated`
20516
+ * boolean to decide whether to keep its own refresh path armed — when
20517
+ * `activated` is `false`, the caller is responsible for refresh.
20518
+ *
20519
+ * Steps on success:
20247
20520
  * 1. Check user's `sat_claims` for `sat:refresh` scope
20248
20521
  * 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
20249
20522
  * 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
@@ -20252,32 +20525,51 @@ var DeviceTokenManager = class extends Destroyable {
20252
20525
  async activate(user, session, updateCredential) {
20253
20526
  const { satClaims } = user;
20254
20527
  if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
20255
- logger$6.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
20256
- return;
20528
+ logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
20529
+ return {
20530
+ activated: false,
20531
+ reason: "no-scope"
20532
+ };
20257
20533
  }
20258
20534
  this._session = session;
20259
20535
  this._updateCredential = updateCredential;
20260
20536
  try {
20537
+ const cached = this._currentToken$.value;
20538
+ if (cached && this.isTokenFresh(cached)) {
20539
+ logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
20540
+ const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
20541
+ await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
20542
+ updateCredential({ token: cached.token });
20543
+ return { activated: true };
20544
+ }
20261
20545
  const tokenData = await this.obtainToken();
20262
20546
  if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
20263
20547
  this._effectiveExpireIn = resolveExpireIn(tokenData);
20264
20548
  const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
20265
20549
  await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
20266
20550
  updateCredential({ token: tokenData.token });
20267
- logger$6.info("[DeviceToken] Client Bound SAT activated successfully");
20551
+ logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
20268
20552
  this._currentToken$.next(tokenData);
20553
+ return { activated: true };
20269
20554
  } catch (error) {
20270
- logger$6.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
20555
+ logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
20271
20556
  this.errorHandler(new DPoPInitError(error, "Failed to activate Client Bound SAT"));
20272
- const credential = this.getCredential();
20273
- const expiresAt = satClaims.expires_at ?? (credential.expiry_at ? credential.expiry_at / 1e3 : Date.now() / 1e3 + DEVICE_TOKEN_DEFAULT_EXPIRE_IN / 1e3);
20274
- this._currentToken$.next({
20275
- token: credential.token ?? "",
20276
- expires_at: expiresAt
20277
- });
20557
+ return {
20558
+ activated: false,
20559
+ reason: "endpoint-failed"
20560
+ };
20278
20561
  }
20279
20562
  }
20280
20563
  /**
20564
+ * Returns true when the cached token has enough headroom before expiry to
20565
+ * be safely reused on reactivation. The headroom matches the refresh
20566
+ * buffer, so a token within the refresh window is treated as stale (the
20567
+ * reactive pipeline is about to refresh it anyway).
20568
+ */
20569
+ isTokenFresh(token) {
20570
+ return resolveExpiresAt(token) * 1e3 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;
20571
+ }
20572
+ /**
20281
20573
  * Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
20282
20574
  * Returns the full {@link DeviceTokenResponse} including expiry metadata.
20283
20575
  */
@@ -20307,7 +20599,7 @@ var DeviceTokenManager = class extends Destroyable {
20307
20599
  * handled by the reactive pipeline).
20308
20600
  */
20309
20601
  async refreshToken(session, currentToken, updateCredential) {
20310
- logger$6.debug("[DeviceToken] Refreshing Client Bound SAT");
20602
+ logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
20311
20603
  const dpopProof = await this.dpopManager.createHttpProof({
20312
20604
  method: "POST",
20313
20605
  uri: DEVICE_REFRESH_ENDPOINT,
@@ -20329,7 +20621,7 @@ var DeviceTokenManager = class extends Destroyable {
20329
20621
  const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
20330
20622
  await session.reauthenticate(data.token, rpcProof);
20331
20623
  updateCredential({ token: data.token });
20332
- logger$6.info("[DeviceToken] Client Bound SAT refreshed successfully");
20624
+ logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
20333
20625
  return data;
20334
20626
  }
20335
20627
  /**
@@ -20338,18 +20630,22 @@ var DeviceTokenManager = class extends Destroyable {
20338
20630
  * On all retries exhausted, emits to `errorHandler`.
20339
20631
  */
20340
20632
  async executeRefresh() {
20633
+ if (this._paused) {
20634
+ logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
20635
+ return;
20636
+ }
20341
20637
  if (this._refreshInProgress) {
20342
- logger$6.debug("[DeviceToken] Refresh already in progress, skipping");
20638
+ logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
20343
20639
  return;
20344
20640
  }
20345
20641
  const session = this._session;
20346
20642
  const updateCredential = this._updateCredential;
20347
20643
  if (!session || !updateCredential) {
20348
- logger$6.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
20644
+ logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
20349
20645
  return;
20350
20646
  }
20351
20647
  if (!session.authenticated) {
20352
- logger$6.debug("[DeviceToken] Session not authenticated, deferring refresh");
20648
+ logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
20353
20649
  return;
20354
20650
  }
20355
20651
  this._refreshInProgress = true;
@@ -20359,7 +20655,7 @@ var DeviceTokenManager = class extends Destroyable {
20359
20655
  const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
20360
20656
  this._currentToken$.next(newTokenData);
20361
20657
  } catch (error) {
20362
- logger$6.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
20658
+ logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
20363
20659
  this.errorHandler(error instanceof TokenRefreshError ? error : new TokenRefreshError("Automatic token refresh failed", error));
20364
20660
  } finally {
20365
20661
  this._refreshInProgress = false;
@@ -20377,18 +20673,215 @@ var DeviceTokenManager = class extends Destroyable {
20377
20673
  lastError = error;
20378
20674
  if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
20379
20675
  const delay$1 = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
20380
- logger$6.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay$1}ms`);
20676
+ logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay$1}ms`);
20381
20677
  await new Promise((resolve) => setTimeout(resolve, delay$1));
20382
20678
  }
20383
20679
  }
20384
20680
  throw lastError instanceof Error ? lastError : new TokenRefreshError("All refresh retries exhausted", lastError);
20385
20681
  }
20682
+ /**
20683
+ * Stops the reactive refresh pipeline from firing. Use when the underlying
20684
+ * session is being torn down (e.g., during {@link SignalWire.disconnect})
20685
+ * so a scheduled refresh cannot fire against a destroyed session.
20686
+ *
20687
+ * The manager's state (cached token, effective TTL, subscriptions) is
20688
+ * preserved — call {@link resume} to re-enable firing after reconnect.
20689
+ */
20690
+ pause() {
20691
+ this._paused = true;
20692
+ }
20693
+ /** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
20694
+ resume() {
20695
+ this._paused = false;
20696
+ }
20386
20697
  /** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
20387
20698
  destroy() {
20388
20699
  super.destroy();
20389
20700
  }
20390
20701
  };
20391
20702
 
20703
+ //#endregion
20704
+ //#region src/managers/CredentialRefreshCoordinator.ts
20705
+ const logger$6 = getLogger();
20706
+ const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
20707
+ /**
20708
+ * Centralizes credential-refresh ownership across the two competing
20709
+ * mechanisms — developer-provided `CredentialProvider.refresh()` and the
20710
+ * Client Bound SAT path via {@link DeviceTokenManager}.
20711
+ *
20712
+ * Maintains the invariant: **at most one refresh mechanism is armed at a
20713
+ * time, and at least one is armed whenever the current credential has an
20714
+ * `expiry_at`**.
20715
+ *
20716
+ * Replaces the previous design where refresh state was distributed across
20717
+ * `SignalWire` (timer field, scheduler method, activation helper) and
20718
+ * `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
20719
+ * one component eliminates the bug class that produced issue #19074.
20720
+ *
20721
+ * Race-safety:
20722
+ * - `_activating` flag prevents overlapping `activate()` calls from racing.
20723
+ * - `_activationGeneration` lets late resolutions detect they've been
20724
+ * preempted by a newer activation (e.g., reconnect during in-flight
20725
+ * `obtainToken`).
20726
+ */
20727
+ var CredentialRefreshCoordinator = class extends Destroyable {
20728
+ constructor(dpopManager, deps) {
20729
+ super();
20730
+ this.deps = deps;
20731
+ this._activating = false;
20732
+ this._activationGeneration = 0;
20733
+ if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
20734
+ }
20735
+ /** True when the Client Bound SAT path is available (DPoP initialized). */
20736
+ get clientBoundSATAvailable() {
20737
+ return this._deviceTokenManager !== void 0;
20738
+ }
20739
+ /** True when the developer-provided refresh timer is currently armed. */
20740
+ get developerRefreshArmed() {
20741
+ return this._developerTimerId !== void 0;
20742
+ }
20743
+ /**
20744
+ * Arms the developer-provided refresh timer to fire shortly before
20745
+ * `expiresAt`. Replaces any previously scheduled developer refresh.
20746
+ *
20747
+ * Idempotent — multiple calls just reschedule. On retry exhaustion,
20748
+ * invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
20749
+ */
20750
+ scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
20751
+ if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
20752
+ const refreshInterval = attempt === 0 ? Math.max(expiresAt - Date.now() - CREDENTIAL_REFRESH_BUFFER_MS, 1e3) : Math.min(CREDENTIAL_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt) * (.5 + Math.random() * .5), CREDENTIAL_REFRESH_MAX_DELAY_MS);
20753
+ this._developerTimerId = setTimeout(async () => {
20754
+ try {
20755
+ if (!provider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
20756
+ const newCredentials = await provider.refresh();
20757
+ this.deps.store.write(newCredentials);
20758
+ this.deps.store.persist(newCredentials);
20759
+ logger$6.info("[Coordinator] Credentials refreshed successfully.");
20760
+ if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
20761
+ } catch (error) {
20762
+ const nextAttempt = attempt + 1;
20763
+ logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
20764
+ this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
20765
+ if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
20766
+ else {
20767
+ logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
20768
+ this.deps.notifier.onError(new TokenRefreshError("Credential refresh failed after max retries"));
20769
+ this.deps.notifier.onRefreshExhausted();
20770
+ }
20771
+ }
20772
+ }, refreshInterval);
20773
+ }
20774
+ /**
20775
+ * Cancels any scheduled developer-provided refresh. Idempotent.
20776
+ *
20777
+ * @internal Used by the coordinator's own activation flow. External
20778
+ * callers should use {@link suspend} for disconnect-time quiescence —
20779
+ * `suspend()` also pauses the internal Client Bound SAT pipeline.
20780
+ */
20781
+ cancelDeveloperRefresh() {
20782
+ if (this._developerTimerId !== void 0) {
20783
+ clearTimeout(this._developerTimerId);
20784
+ this._developerTimerId = void 0;
20785
+ }
20786
+ }
20787
+ /**
20788
+ * Suspends both refresh paths — cancels the developer timer and pauses
20789
+ * the internal reactive pipeline. Use when the underlying session is
20790
+ * being torn down (e.g., {@link SignalWire.disconnect}). The next
20791
+ * {@link activate} call re-enables the internal pipeline.
20792
+ *
20793
+ * The internal manager's cached token survives — see
20794
+ * {@link DeviceTokenManager.pause} — so a subsequent reconnect can
20795
+ * skip the `/devices/token` exchange entirely.
20796
+ */
20797
+ suspend() {
20798
+ this.cancelDeveloperRefresh();
20799
+ this._deviceTokenManager?.pause();
20800
+ }
20801
+ /**
20802
+ * Asks the Client Bound SAT path to take over refresh. If it accepts,
20803
+ * the developer-provided timer (if any) is cancelled. If it declines, the
20804
+ * developer timer remains armed and a `credential_refresh_fallback`
20805
+ * warning is emitted.
20806
+ *
20807
+ * **Idempotent** — re-entrant calls during an in-flight activation are
20808
+ * dropped. Use `_activationGeneration` to detect stale resolutions
20809
+ * (e.g., a reconnect-triggered activate() that races with an earlier one).
20810
+ */
20811
+ async activate(user, session) {
20812
+ if (this._activating) {
20813
+ logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
20814
+ return;
20815
+ }
20816
+ if (!this._deviceTokenManager) return;
20817
+ this._deviceTokenManager.resume();
20818
+ const generation = ++this._activationGeneration;
20819
+ this._activating = true;
20820
+ try {
20821
+ const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
20822
+ if (generation !== this._activationGeneration) {
20823
+ logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
20824
+ return;
20825
+ }
20826
+ if (result.activated) {
20827
+ this.cancelDeveloperRefresh();
20828
+ logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
20829
+ return;
20830
+ }
20831
+ logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
20832
+ this.deps.notifier.onWarning({
20833
+ code: "credential_refresh_fallback",
20834
+ source: "CredentialProvider",
20835
+ reason: result.reason,
20836
+ message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
20837
+ });
20838
+ } finally {
20839
+ this._activating = false;
20840
+ }
20841
+ }
20842
+ destroy() {
20843
+ this.cancelDeveloperRefresh();
20844
+ this._deviceTokenManager?.destroy();
20845
+ this._deviceTokenManager = void 0;
20846
+ super.destroy();
20847
+ }
20848
+ /**
20849
+ * Races the manager's `activate()` against a hard timeout. A wedged HTTP
20850
+ * layer (e.g., proxy issues) could otherwise hang the activation
20851
+ * indefinitely, leaving the session with no refresh mechanism while the
20852
+ * `_activating` guard blocks subsequent retries.
20853
+ *
20854
+ * On timeout, the manager is paused immediately. The inner `activate()`
20855
+ * may still complete in the background and emit to `_currentToken$`,
20856
+ * which would normally arm the reactive refresh pipeline; pausing
20857
+ * prevents that pipeline from firing while the developer refresh path
20858
+ * is the active mechanism — preserving the "at most one mechanism
20859
+ * armed" invariant. The next `activate()` call resumes the manager.
20860
+ */
20861
+ async withActivationTimeout(inner) {
20862
+ return new Promise((resolve) => {
20863
+ const timer$4 = setTimeout(() => {
20864
+ this._deviceTokenManager?.pause();
20865
+ resolve({
20866
+ activated: false,
20867
+ reason: "activation-timeout"
20868
+ });
20869
+ }, CREDENTIAL_ACTIVATE_TIMEOUT_MS);
20870
+ inner.then((result) => {
20871
+ clearTimeout(timer$4);
20872
+ resolve(result);
20873
+ }, (error) => {
20874
+ clearTimeout(timer$4);
20875
+ this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
20876
+ resolve({
20877
+ activated: false,
20878
+ reason: "endpoint-failed"
20879
+ });
20880
+ });
20881
+ });
20882
+ }
20883
+ };
20884
+
20392
20885
  //#endregion
20393
20886
  //#region src/managers/DiagnosticsCollector.ts
20394
20887
  const logger$5 = getLogger();
@@ -20909,6 +21402,10 @@ var TransportManager = class extends Destroyable {
20909
21402
  if (!isSignalwireRequest(message)) return true;
20910
21403
  const eventChannel = message.params.event_channel;
20911
21404
  if (!eventChannel) return true;
21405
+ if (message.params.event_type.startsWith("conversation.")) {
21406
+ logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
21407
+ return true;
21408
+ }
20912
21409
  const currentProtocol = this._currentProtocol;
20913
21410
  if (!currentProtocol) return true;
20914
21411
  if (!eventChannel.includes(currentProtocol)) {
@@ -21102,6 +21599,7 @@ var SignalWire = class extends Destroyable {
21102
21599
  this._isConnected$ = this.createBehaviorSubject(false);
21103
21600
  this._isRegistered$ = this.createBehaviorSubject(false);
21104
21601
  this._errors$ = this.createReplaySubject(1);
21602
+ this._warnings$ = this.createReplaySubject(10);
21105
21603
  this._options = {};
21106
21604
  this._deps = new DependencyContainer();
21107
21605
  this._credentialProvider = credentialProvider;
@@ -21161,6 +21659,27 @@ var SignalWire = class extends Destroyable {
21161
21659
  */
21162
21660
  async resolveCredentials() {
21163
21661
  const fingerprint = await this.initDPoP();
21662
+ this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
21663
+ http: this._deps.http,
21664
+ notifier: {
21665
+ onError: (error) => this._errors$.next(error),
21666
+ onWarning: (warning) => this._warnings$.next(warning),
21667
+ onRefreshExhausted: () => void this.disconnect()
21668
+ },
21669
+ store: {
21670
+ read: () => this._deps.credential,
21671
+ write: (credential) => {
21672
+ this._deps.credential = credential;
21673
+ },
21674
+ merge: (partial) => {
21675
+ this._deps.credential = {
21676
+ ...this._deps.credential,
21677
+ ...partial
21678
+ };
21679
+ },
21680
+ persist: (credential) => this.persistCredential(credential)
21681
+ }
21682
+ });
21164
21683
  if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
21165
21684
  for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
21166
21685
  const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
@@ -21190,7 +21709,16 @@ var SignalWire = class extends Destroyable {
21190
21709
  logger$1.error("[SignalWire] Provided credentials have expired.");
21191
21710
  throw new InvalidCredentialsError("Provided credentials have expired.");
21192
21711
  }
21193
- if (_credentials.expiry_at && credentialProvider?.refresh) this.scheduleCredentialRefresh(credentialProvider, _credentials.expiry_at);
21712
+ if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
21713
+ else if (_credentials.expiry_at && !credentialProvider?.refresh) {
21714
+ logger$1.warn(`[SignalWire] [SW-NO-REFRESH-HANDLER] Credential has expiry_at=${_credentials.expiry_at} but no refresh handler. Session will terminate at expiry unless the SAT has 'sat:refresh' scope.`);
21715
+ this._warnings$.next({
21716
+ code: "credential_no_refresh_handler",
21717
+ source: "CredentialProvider",
21718
+ message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
21719
+ expiresAt: _credentials.expiry_at
21720
+ });
21721
+ }
21194
21722
  this._deps.credential = _credentials;
21195
21723
  this.persistCredential(_credentials);
21196
21724
  if (this.isConnected && this._clientSession.authenticated && _credentials.token) try {
@@ -21201,35 +21729,6 @@ var SignalWire = class extends Destroyable {
21201
21729
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
21202
21730
  }
21203
21731
  }
21204
- /**
21205
- * Schedules credential refresh with exponential backoff retry on failure.
21206
- * On success, resets attempt counter and schedules the next refresh.
21207
- * After exhausting retries, emits TokenRefreshError and disconnects.
21208
- */
21209
- scheduleCredentialRefresh(credentialProvider, expiresAt, attempt = 0) {
21210
- if (this._refreshTimerId !== void 0) clearTimeout(this._refreshTimerId);
21211
- const refreshInterval = attempt === 0 ? Math.max(expiresAt - Date.now() - CREDENTIAL_REFRESH_BUFFER_MS, 1e3) : Math.min(CREDENTIAL_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt) * (.5 + Math.random() * .5), CREDENTIAL_REFRESH_MAX_DELAY_MS);
21212
- this._refreshTimerId = setTimeout(async () => {
21213
- try {
21214
- if (!credentialProvider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
21215
- const newCredentials = await credentialProvider.refresh();
21216
- this._deps.credential = newCredentials;
21217
- this.persistCredential(newCredentials);
21218
- logger$1.info("[SignalWire] Credentials refreshed successfully.");
21219
- if (newCredentials.expiry_at) this.scheduleCredentialRefresh(credentialProvider, newCredentials.expiry_at, 0);
21220
- } catch (error) {
21221
- const nextAttempt = attempt + 1;
21222
- logger$1.error(`[SignalWire] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
21223
- this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
21224
- if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleCredentialRefresh(credentialProvider, expiresAt, nextAttempt);
21225
- else {
21226
- logger$1.error("[SignalWire] Credential refresh exhausted all retries. Disconnecting.");
21227
- this._errors$.next(new TokenRefreshError("Credential refresh failed after max retries"));
21228
- this.disconnect();
21229
- }
21230
- }
21231
- }, refreshInterval);
21232
- }
21233
21732
  /** Persist credential to localStorage when persistSession is enabled. */
21234
21733
  persistCredential(credential) {
21235
21734
  if (!credential.token) return;
@@ -21323,6 +21822,7 @@ var SignalWire = class extends Destroyable {
21323
21822
  logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
21324
21823
  const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
21325
21824
  this._deps.credential = newCredentials;
21825
+ if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
21326
21826
  logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
21327
21827
  } catch (error) {
21328
21828
  logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
@@ -21334,33 +21834,12 @@ var SignalWire = class extends Destroyable {
21334
21834
  this._errors$.next(error);
21335
21835
  });
21336
21836
  await this._clientSession.connect();
21337
- if (this._dpopManager?.initialized) {
21338
- if (this._refreshTimerId) {
21339
- clearTimeout(this._refreshTimerId);
21340
- this._refreshTimerId = void 0;
21341
- logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
21342
- }
21343
- this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
21344
- await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
21345
- this._deps.credential = {
21346
- ...this._deps.credential,
21347
- ...cred
21348
- };
21349
- });
21350
- }
21837
+ await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
21351
21838
  this.subscribeTo(this._clientSession.authenticated$.pipe((0, import_cjs$1.skip)(1), (0, import_cjs$1.filter)(Boolean)), async () => {
21352
21839
  try {
21353
- if (this._deviceTokenManager) {
21354
- await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
21355
- this._deps.credential = {
21356
- ...this._deps.credential,
21357
- ...cred
21358
- };
21359
- });
21360
- logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
21361
- }
21840
+ await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
21362
21841
  } catch (error) {
21363
- logger$1.error("[SignalWire] Client Bound SAT re-activation failed (non-fatal):", error);
21842
+ logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
21364
21843
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
21365
21844
  }
21366
21845
  try {
@@ -21446,6 +21925,19 @@ var SignalWire = class extends Destroyable {
21446
21925
  get errors$() {
21447
21926
  return this.deferEmission(this._errors$.asObservable());
21448
21927
  }
21928
+ /**
21929
+ * Observable stream of non-fatal SDK warnings.
21930
+ *
21931
+ * Subscribe to detect SDK behaviors that affect session liveness or developer-facing
21932
+ * contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
21933
+ * refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
21934
+ * scope. Discriminated by `code`.
21935
+ *
21936
+ * Independent from {@link errors$}: existing error consumers are not notified.
21937
+ */
21938
+ get warnings$() {
21939
+ return this.deferEmission(this._warnings$.asObservable());
21940
+ }
21449
21941
  /** Platform WebRTC capabilities detected at construction time. */
21450
21942
  get platformCapabilities() {
21451
21943
  this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
@@ -21514,7 +22006,7 @@ var SignalWire = class extends Destroyable {
21514
22006
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
21515
22007
  }
21516
22008
  try {
21517
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "3.30.0" });
22009
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.2" });
21518
22010
  } catch (error) {
21519
22011
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
21520
22012
  }
@@ -21522,14 +22014,19 @@ var SignalWire = class extends Destroyable {
21522
22014
  /**
21523
22015
  * Disconnects the WebSocket and tears down the current session.
21524
22016
  *
22017
+ * Ends the session identified by the protocol and clears its persisted
22018
+ * resume state (`authorization_state` + protocol) and attach records
22019
+ * together — a later {@link connect} with the same credentials starts a
22020
+ * fresh session and cannot reattach to the ended session's calls.
22021
+ * Credentials and device preferences are preserved. To temporarily stop
22022
+ * receiving inbound calls while keeping the session alive, use
22023
+ * `unregister()` instead.
22024
+ *
21525
22025
  * The client can be reconnected by calling {@link connect} again,
21526
22026
  * which creates a fresh transport and session.
21527
22027
  */
21528
22028
  async disconnect() {
21529
- if (this._refreshTimerId) {
21530
- clearTimeout(this._refreshTimerId);
21531
- this._refreshTimerId = void 0;
21532
- }
22029
+ this._refreshCoordinator?.suspend();
21533
22030
  this._diagnosticsCollector?.record("connection", "disconnected");
21534
22031
  await this.teardownTransportAndSession();
21535
22032
  this._isConnected$.next(false);
@@ -21916,15 +22413,20 @@ var SignalWire = class extends Destroyable {
21916
22413
  prefs.preferredVideoInput = null;
21917
22414
  await this._deviceController.clearDeviceState();
21918
22415
  }
21919
- /** Destroys the client, clearing timers and releasing all resources. */
22416
+ /**
22417
+ * Destroys the client, clearing timers and releasing all resources.
22418
+ *
22419
+ * Intentionally destroying the client ends its session: the resume state
22420
+ * (`authorization_state` + protocol) and the attach records are both
22421
+ * cleared. Credentials and device preferences are preserved — use
22422
+ * {@link resetToDefaults} for a full wipe. To temporarily stop receiving
22423
+ * inbound calls while keeping the session alive, use `unregister()`.
22424
+ */
21920
22425
  destroy() {
21921
- if (this._refreshTimerId) {
21922
- clearTimeout(this._refreshTimerId);
21923
- this._refreshTimerId = void 0;
21924
- }
21925
- this._deviceTokenManager?.destroy();
22426
+ this._refreshCoordinator?.destroy();
22427
+ this._refreshCoordinator = void 0;
21926
22428
  this._dpopManager?.destroy();
21927
- if (this._attachManager) this._attachManager.detachAll();
22429
+ this._clientSession.teardownSessionState();
21928
22430
  this._transport.destroy();
21929
22431
  this._clientSession.destroy();
21930
22432
  try {
@@ -22039,7 +22541,7 @@ var StaticCredentialProvider = class {
22039
22541
  /**
22040
22542
  * Library version from package.json, injected at build time.
22041
22543
  */
22042
- const version = "3.30.0";
22544
+ const version = "4.0.0-rc.2";
22043
22545
  /**
22044
22546
  * Flag indicating the library has been loaded and is ready to use.
22045
22547
  * For UMD builds: `window.SignalWire.ready`
@@ -22061,7 +22563,7 @@ const ready = true;
22061
22563
  */
22062
22564
  const emitReadyEvent = () => {
22063
22565
  if (typeof window !== "undefined") {
22064
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "3.30.0" } });
22566
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.2" } });
22065
22567
  window.dispatchEvent(event);
22066
22568
  }
22067
22569
  };
@@ -22085,5 +22587,5 @@ emitReadyEvent();
22085
22587
  if (typeof process === "undefined") globalThis.process = { env: { NODE_ENV: "production" } };
22086
22588
 
22087
22589
  //#endregion
22088
- export { Address, CallCreateError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
22590
+ 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 };
22089
22591
  //# sourceMappingURL=browser.mjs.map