@signalwire/js 4.0.0-rc.1 → 4.0.0-rc.2

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.
@@ -9021,6 +9021,143 @@ var Destroyable = class {
9021
9021
  }
9022
9022
  };
9023
9023
 
9024
+ //#endregion
9025
+ //#region src/core/constants.ts
9026
+ const INVITE_VERSION = 1e3;
9027
+ const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
9028
+ const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
9029
+ const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
9030
+ const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
9031
+ const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
9032
+ const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
9033
+ const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
9034
+ const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
9035
+ const PREFERENCES_STORAGE_KEY = "sw:preferences";
9036
+ /** Scope value that enables automatic token refresh. */
9037
+ const SAT_REFRESH_SCOPE = "sat:refresh";
9038
+ /** API endpoints for device token operations. */
9039
+ const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
9040
+ const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
9041
+ /** Default device token TTL in seconds (15 minutes). */
9042
+ const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
9043
+ /** Buffer time in milliseconds before expiry to trigger refresh. */
9044
+ const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
9045
+ /** Maximum retry attempts for device token refresh on transient failure. */
9046
+ const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
9047
+ /** Base delay in milliseconds for exponential backoff on refresh retry. */
9048
+ const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
9049
+ /** Maximum retry attempts for developer credential refresh on transient failure. */
9050
+ const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
9051
+ /** Base delay in milliseconds for exponential backoff on credential refresh retry. */
9052
+ const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
9053
+ /** Maximum delay in milliseconds for credential refresh backoff. */
9054
+ const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
9055
+ /** Buffer in milliseconds before token expiry to trigger refresh. */
9056
+ const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
9057
+ /**
9058
+ * Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
9059
+ * to resolve before treating the activation as failed and falling back to
9060
+ * the developer-provided refresh path. Prevents a wedged HTTP layer from
9061
+ * leaving the session with no active refresh mechanism.
9062
+ */
9063
+ const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
9064
+ /** JSON-RPC error code for requester validation failure (corrupted auth state). */
9065
+ const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
9066
+ /** JSON-RPC error code for invalid params (e.g., missing authentication block). */
9067
+ const RPC_ERROR_INVALID_PARAMS = -32602;
9068
+ /** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
9069
+ const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
9070
+ /** Error names browsers use for a media permission denial (user or policy). */
9071
+ const MEDIA_ACCESS_DENIAL_NAMES = [
9072
+ "NotAllowedError",
9073
+ "SecurityError",
9074
+ "PermissionDeniedError"
9075
+ ];
9076
+ /** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
9077
+ const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
9078
+ /** Number of initial samples used to build a baseline for spike detection. */
9079
+ const DEFAULT_STATS_BASELINE_SAMPLES = 10;
9080
+ /** Duration in ms with no inbound audio packets before emitting a critical issue. */
9081
+ const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
9082
+ /** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
9083
+ const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
9084
+ /** Packet loss fraction (0-1) above which a warning is emitted. */
9085
+ const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
9086
+ /** Multiplier applied to baseline jitter to detect a jitter spike. */
9087
+ const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
9088
+ /** Number of seconds of metrics history to retain. */
9089
+ const DEFAULT_STATS_HISTORY_SIZE = 30;
9090
+ /** Maximum keyframe requests allowed within a single burst window. */
9091
+ const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
9092
+ /** Duration of the keyframe burst window in milliseconds. */
9093
+ const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
9094
+ /** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
9095
+ const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
9096
+ /** Minimum time between re-INVITE attempts in milliseconds. */
9097
+ const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
9098
+ /** Maximum number of re-INVITE attempts per call. */
9099
+ const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
9100
+ /** Timeout for a single re-INVITE attempt in milliseconds. */
9101
+ const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
9102
+ /** Debounce window in ms to collapse multiple detection signals into one trigger. */
9103
+ const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
9104
+ /** Cooldown period in ms between recovery attempts. */
9105
+ const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
9106
+ /** Grace period in ms before treating ICE 'disconnected' as a failure. */
9107
+ const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
9108
+ /** Timeout for a single ICE restart attempt in milliseconds. */
9109
+ const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
9110
+ /** Maximum recovery attempts before emitting 'max_attempts_reached'. */
9111
+ const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
9112
+ /** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
9113
+ const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
9114
+ /** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
9115
+ const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
9116
+ /** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
9117
+ const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
9118
+ /** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
9119
+ const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
9120
+ /** RMS level threshold (0..1) above which the local participant is considered speaking. */
9121
+ const VAD_THRESHOLD = .03;
9122
+ /** Hold window in ms below the threshold before speaking$ flips back to false. */
9123
+ const VAD_HOLD_MS = 250;
9124
+ /** Whether to persist device selections to storage by default. */
9125
+ const DEFAULT_PERSIST_DEVICE_SELECTION = true;
9126
+ /** Whether to auto-apply device changes to active calls by default. */
9127
+ const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
9128
+ /** Storage keys for persisted device selections. */
9129
+ const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
9130
+ const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
9131
+ const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
9132
+ /** Whether to auto-mute video when the tab becomes hidden. */
9133
+ const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
9134
+ /** Whether to re-enumerate devices when the page becomes visible. */
9135
+ const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
9136
+ /** Whether to check peer connection health when the page becomes visible. */
9137
+ const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
9138
+ /** Whether automatic video degradation on low bandwidth is enabled. */
9139
+ const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
9140
+ /** Bitrate in kbps below which video is automatically disabled. */
9141
+ const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
9142
+ /** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
9143
+ const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
9144
+ /** Whether relay-only escalation is enabled as a last-resort recovery tier. */
9145
+ const DEFAULT_ENABLE_RELAY_FALLBACK = true;
9146
+ /** Whether to listen for browser online/offline/connection events. */
9147
+ const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
9148
+ /** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
9149
+ const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
9150
+ /** Default video track constraints applied when video is enabled without explicit constraints. */
9151
+ const DEFAULT_VIDEO_CONSTRAINTS = {
9152
+ width: { ideal: 1280 },
9153
+ height: { ideal: 720 },
9154
+ aspectRatio: 16 / 9
9155
+ };
9156
+ /** Whether stereo Opus is enabled by default. */
9157
+ const DEFAULT_STEREO_AUDIO = false;
9158
+ /** Max average bitrate for stereo Opus in bits per second. */
9159
+ const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
9160
+
9024
9161
  //#endregion
9025
9162
  //#region src/core/errors.ts
9026
9163
  var UnexpectedError = class extends Error {
@@ -9230,6 +9367,33 @@ var MediaTrackError = class extends Error {
9230
9367
  this.name = "MediaTrackError";
9231
9368
  }
9232
9369
  };
9370
+ /** True when a `getUserMedia`/`getDisplayMedia` rejection is a permission denial. */
9371
+ function isMediaAccessDenial(originalError) {
9372
+ return originalError instanceof Error && MEDIA_ACCESS_DENIAL_NAMES.includes(originalError.name);
9373
+ }
9374
+ /**
9375
+ * Failure to acquire local media (camera, microphone, or screen capture)
9376
+ * via `getUserMedia`/`getDisplayMedia`.
9377
+ *
9378
+ * Non-fatal by default: screenshare and additional-device failures never
9379
+ * end the call, and main-connection failures degrade to receive-only when
9380
+ * possible. The wrapping site sets `fatal` to `true` only when the call
9381
+ * cannot continue (receive-only fallback disabled or no receive intent).
9382
+ */
9383
+ var MediaAccessError = class extends Error {
9384
+ constructor(operation, media, originalError, fatal = false) {
9385
+ super(`Media access ${isMediaAccessDenial(originalError) ? "denied" : "failed"} for ${operation} (${media})`, { cause: originalError instanceof Error ? originalError : void 0 });
9386
+ this.operation = operation;
9387
+ this.media = media;
9388
+ this.originalError = originalError;
9389
+ this.fatal = fatal;
9390
+ this.name = "MediaAccessError";
9391
+ }
9392
+ /** True when the underlying failure is a permission denial (user or policy). */
9393
+ get denied() {
9394
+ return isMediaAccessDenial(this.originalError);
9395
+ }
9396
+ };
9233
9397
  var DPoPInitError = class extends Error {
9234
9398
  constructor(originalError, message = "Failed to initialize DPoP key pair") {
9235
9399
  super(message, { cause: originalError instanceof Error ? originalError : void 0 });
@@ -9879,137 +10043,6 @@ var DeviceHistoryManager = class {
9879
10043
  }
9880
10044
  };
9881
10045
 
9882
- //#endregion
9883
- //#region src/core/constants.ts
9884
- const INVITE_VERSION = 1e3;
9885
- const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
9886
- const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
9887
- const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
9888
- const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
9889
- const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
9890
- const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
9891
- const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
9892
- const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
9893
- const PREFERENCES_STORAGE_KEY = "sw:preferences";
9894
- /** Scope value that enables automatic token refresh. */
9895
- const SAT_REFRESH_SCOPE = "sat:refresh";
9896
- /** API endpoints for device token operations. */
9897
- const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
9898
- const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
9899
- /** Default device token TTL in seconds (15 minutes). */
9900
- const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
9901
- /** Buffer time in milliseconds before expiry to trigger refresh. */
9902
- const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
9903
- /** Maximum retry attempts for device token refresh on transient failure. */
9904
- const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
9905
- /** Base delay in milliseconds for exponential backoff on refresh retry. */
9906
- const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
9907
- /** Maximum retry attempts for developer credential refresh on transient failure. */
9908
- const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
9909
- /** Base delay in milliseconds for exponential backoff on credential refresh retry. */
9910
- const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
9911
- /** Maximum delay in milliseconds for credential refresh backoff. */
9912
- const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
9913
- /** Buffer in milliseconds before token expiry to trigger refresh. */
9914
- const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
9915
- /**
9916
- * Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
9917
- * to resolve before treating the activation as failed and falling back to
9918
- * the developer-provided refresh path. Prevents a wedged HTTP layer from
9919
- * leaving the session with no active refresh mechanism.
9920
- */
9921
- const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
9922
- /** JSON-RPC error code for requester validation failure (corrupted auth state). */
9923
- const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
9924
- /** JSON-RPC error code for invalid params (e.g., missing authentication block). */
9925
- const RPC_ERROR_INVALID_PARAMS = -32602;
9926
- /** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
9927
- const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
9928
- /** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
9929
- const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
9930
- /** Number of initial samples used to build a baseline for spike detection. */
9931
- const DEFAULT_STATS_BASELINE_SAMPLES = 10;
9932
- /** Duration in ms with no inbound audio packets before emitting a critical issue. */
9933
- const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
9934
- /** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
9935
- const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
9936
- /** Packet loss fraction (0-1) above which a warning is emitted. */
9937
- const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
9938
- /** Multiplier applied to baseline jitter to detect a jitter spike. */
9939
- const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
9940
- /** Number of seconds of metrics history to retain. */
9941
- const DEFAULT_STATS_HISTORY_SIZE = 30;
9942
- /** Maximum keyframe requests allowed within a single burst window. */
9943
- const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
9944
- /** Duration of the keyframe burst window in milliseconds. */
9945
- const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
9946
- /** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
9947
- const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
9948
- /** Minimum time between re-INVITE attempts in milliseconds. */
9949
- const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
9950
- /** Maximum number of re-INVITE attempts per call. */
9951
- const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
9952
- /** Timeout for a single re-INVITE attempt in milliseconds. */
9953
- const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
9954
- /** Debounce window in ms to collapse multiple detection signals into one trigger. */
9955
- const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
9956
- /** Cooldown period in ms between recovery attempts. */
9957
- const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
9958
- /** Grace period in ms before treating ICE 'disconnected' as a failure. */
9959
- const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
9960
- /** Timeout for a single ICE restart attempt in milliseconds. */
9961
- const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
9962
- /** Maximum recovery attempts before emitting 'max_attempts_reached'. */
9963
- const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
9964
- /** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
9965
- const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
9966
- /** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
9967
- const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
9968
- /** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
9969
- const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
9970
- /** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
9971
- const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
9972
- /** RMS level threshold (0..1) above which the local participant is considered speaking. */
9973
- const VAD_THRESHOLD = .03;
9974
- /** Hold window in ms below the threshold before speaking$ flips back to false. */
9975
- const VAD_HOLD_MS = 250;
9976
- /** Whether to persist device selections to storage by default. */
9977
- const DEFAULT_PERSIST_DEVICE_SELECTION = true;
9978
- /** Whether to auto-apply device changes to active calls by default. */
9979
- const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
9980
- /** Storage keys for persisted device selections. */
9981
- const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
9982
- const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
9983
- const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
9984
- /** Whether to auto-mute video when the tab becomes hidden. */
9985
- const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
9986
- /** Whether to re-enumerate devices when the page becomes visible. */
9987
- const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
9988
- /** Whether to check peer connection health when the page becomes visible. */
9989
- const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
9990
- /** Whether automatic video degradation on low bandwidth is enabled. */
9991
- const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
9992
- /** Bitrate in kbps below which video is automatically disabled. */
9993
- const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
9994
- /** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
9995
- const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
9996
- /** Whether relay-only escalation is enabled as a last-resort recovery tier. */
9997
- const DEFAULT_ENABLE_RELAY_FALLBACK = true;
9998
- /** Whether to listen for browser online/offline/connection events. */
9999
- const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
10000
- /** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
10001
- const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
10002
- /** Default video track constraints applied when video is enabled without explicit constraints. */
10003
- const DEFAULT_VIDEO_CONSTRAINTS = {
10004
- width: { ideal: 1280 },
10005
- height: { ideal: 720 },
10006
- aspectRatio: 16 / 9
10007
- };
10008
- /** Whether stereo Opus is enabled by default. */
10009
- const DEFAULT_STEREO_AUDIO = false;
10010
- /** Max average bitrate for stereo Opus in bits per second. */
10011
- const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
10012
-
10013
10046
  //#endregion
10014
10047
  //#region src/utils/time.ts
10015
10048
  function fromSecToMs(seconds) {
@@ -14057,12 +14090,21 @@ var SelfParticipant = class extends Participant {
14057
14090
  noise_suppression: true
14058
14091
  });
14059
14092
  }
14060
- /** Starts sharing the local screen. */
14093
+ /**
14094
+ * Starts sharing the local screen.
14095
+ *
14096
+ * The call is unaffected when acquisition fails.
14097
+ *
14098
+ * @throws The raw `getDisplayMedia` error. A dismissed picker or a
14099
+ * permission denial rejects with a `NotAllowedError` `DOMException` —
14100
+ * inspect `error.name` to tell benign cancels apart from real failures.
14101
+ */
14061
14102
  async startScreenShare() {
14062
14103
  try {
14063
14104
  await this.vertoManager.addScreenMedia();
14064
14105
  } catch (error) {
14065
14106
  logger$24.error("[Participant.startScreenShare] Screen share error:", error);
14107
+ throw error;
14066
14108
  }
14067
14109
  }
14068
14110
  /** Observable of the current screen share status. */
@@ -14077,12 +14119,20 @@ var SelfParticipant = class extends Participant {
14077
14119
  async stopScreenShare() {
14078
14120
  return this.vertoManager.removeScreenMedia();
14079
14121
  }
14080
- /** Adds an additional media input device to the call. */
14122
+ /**
14123
+ * Adds an additional media input device to the call.
14124
+ *
14125
+ * The call is unaffected when acquisition fails.
14126
+ *
14127
+ * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
14128
+ * permission denial) — inspect `error.name` to decide how to react.
14129
+ */
14081
14130
  async addAdditionalDevice(options) {
14082
14131
  try {
14083
14132
  await this.vertoManager.addInputDevice(options);
14084
14133
  } catch (error) {
14085
- logger$24.error("[Participant.startScreenShare] Screen share error:", error);
14134
+ logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
14135
+ throw error;
14086
14136
  }
14087
14137
  }
14088
14138
  /** Removes an additional media input device by ID. */
@@ -15714,7 +15764,7 @@ var RTCPeerConnectionController = class extends Destroyable {
15714
15764
  logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
15715
15765
  } catch (error) {
15716
15766
  logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
15717
- this._errors$.next(toError(error));
15767
+ this._errors$.next(new MediaTrackError("updateSelectedInputDevice", kind, error));
15718
15768
  throw error;
15719
15769
  }
15720
15770
  };
@@ -15936,7 +15986,7 @@ var RTCPeerConnectionController = class extends Destroyable {
15936
15986
  case "main":
15937
15987
  default: return {
15938
15988
  ...options,
15939
- offerToReceiveAudio: true,
15989
+ offerToReceiveAudio: this.options.receiveAudio ?? true,
15940
15990
  offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
15941
15991
  };
15942
15992
  }
@@ -16090,13 +16140,14 @@ var RTCPeerConnectionController = class extends Destroyable {
16090
16140
  */
16091
16141
  async acceptInbound(mediaOverrides) {
16092
16142
  if (mediaOverrides) {
16093
- const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
16143
+ const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
16094
16144
  this.options = {
16095
16145
  ...this.options,
16096
16146
  ...audio !== void 0 ? { audio } : {},
16097
16147
  ...video !== void 0 ? { video } : {},
16098
16148
  ...receiveAudio !== void 0 ? { receiveAudio } : {},
16099
- ...receiveVideo !== void 0 ? { receiveVideo } : {}
16149
+ ...receiveVideo !== void 0 ? { receiveVideo } : {},
16150
+ ...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
16100
16151
  };
16101
16152
  this.transceiverController?.updateOptions({
16102
16153
  receiveAudio: this.receiveAudio,
@@ -16253,7 +16304,19 @@ var RTCPeerConnectionController = class extends Destroyable {
16253
16304
  }
16254
16305
  async setupLocalTracks() {
16255
16306
  logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
16256
- const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
16307
+ if (this.hasNoLocalMediaToSend()) {
16308
+ if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
16309
+ logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
16310
+ this.setupReceiveOnlyTransceivers();
16311
+ return;
16312
+ }
16313
+ let localStream;
16314
+ try {
16315
+ localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
16316
+ } catch (error) {
16317
+ this.handleLocalMediaFailure(error);
16318
+ return;
16319
+ }
16257
16320
  if (this.transceiverController?.useAddStream ?? false) {
16258
16321
  logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
16259
16322
  this.peerConnection?.addStream(localStream);
@@ -16280,6 +16343,48 @@ var RTCPeerConnectionController = class extends Destroyable {
16280
16343
  }
16281
16344
  }
16282
16345
  }
16346
+ /** True for a main connection with no local media to send. */
16347
+ hasNoLocalMediaToSend() {
16348
+ const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
16349
+ return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
16350
+ }
16351
+ /** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
16352
+ get requestedMediaKinds() {
16353
+ const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
16354
+ const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
16355
+ if (wantsAudio && wantsVideo) return "audiovideo";
16356
+ return wantsVideo ? "video" : "audio";
16357
+ }
16358
+ /**
16359
+ * Handle a local media acquisition failure with a typed, semantically
16360
+ * accurate MediaAccessError created at the acquisition site:
16361
+ * - Auxiliary connections (screenshare / additional-device) throw a
16362
+ * non-fatal error — VertoManager surfaces it and the call is unaffected.
16363
+ * - The main connection degrades to receive-only when allowed (default),
16364
+ * otherwise fails with a fatal error.
16365
+ */
16366
+ handleLocalMediaFailure(error) {
16367
+ if (this.propose === "screenshare") throw new MediaAccessError("startScreenShare", "screen", error, false);
16368
+ if (this.propose === "additional-device") throw new MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
16369
+ const canReceive = this.receiveAudio || this.receiveVideo;
16370
+ if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
16371
+ logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
16372
+ this._errors$.next(new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
16373
+ this.setupReceiveOnlyTransceivers();
16374
+ }
16375
+ /**
16376
+ * Negotiate receive-only m-lines when there are no local tracks to send.
16377
+ * Only offer-type connections add transceivers — answer-type connections
16378
+ * reuse the transceivers created from the remote offer.
16379
+ */
16380
+ setupReceiveOnlyTransceivers() {
16381
+ if (this.type !== "offer") return;
16382
+ if (this.transceiverController?.useAddTransceivers ?? false) {
16383
+ this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
16384
+ this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
16385
+ }
16386
+ if (!this.isNegotiating) this.negotiationNeeded$.next();
16387
+ }
16283
16388
  async getUserMedia(constraints) {
16284
16389
  return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
16285
16390
  }
@@ -16316,7 +16421,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16316
16421
  stream = await this.getUserMedia({ audio: constraints });
16317
16422
  } catch (error) {
16318
16423
  logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
16319
- this._errors$.next(toError(error));
16424
+ this._errors$.next(new MediaTrackError("restoreAudioPipelineInput", "audio", error));
16320
16425
  return;
16321
16426
  }
16322
16427
  const newTrack = stream.getAudioTracks().at(0);
@@ -16378,7 +16483,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16378
16483
  logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
16379
16484
  } catch (error) {
16380
16485
  logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
16381
- this._errors$.next(toError(error));
16486
+ this._errors$.next(new MediaTrackError("addLocalTrack", track.kind, error));
16382
16487
  throw error;
16383
16488
  }
16384
16489
  }
@@ -16403,7 +16508,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16403
16508
  logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
16404
16509
  } catch (error) {
16405
16510
  logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
16406
- this._errors$.next(toError(error));
16511
+ this._errors$.next(new MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
16407
16512
  throw error;
16408
16513
  }
16409
16514
  }
@@ -17012,6 +17117,7 @@ var WebRTCVertoManager = class extends VertoManager {
17012
17117
  inputVideoStream: options.inputVideoStream,
17013
17118
  receiveAudio: options.receiveAudio,
17014
17119
  receiveVideo: options.receiveVideo,
17120
+ fallbackToReceiveOnly: options.fallbackToReceiveOnly,
17015
17121
  webRTCApiProvider: this.webRTCApiProvider,
17016
17122
  preferredVideoCodecs: options.preferredVideoCodecs,
17017
17123
  preferredAudioCodecs: options.preferredAudioCodecs,
@@ -17238,9 +17344,11 @@ var WebRTCVertoManager = class extends VertoManager {
17238
17344
  await this.initAdditionalPeerConnection("screenshare", options);
17239
17345
  }
17240
17346
  async initAdditionalPeerConnection(propose, options) {
17347
+ const isScreenShare = propose === "screenshare";
17348
+ let firstPeerConnectionError;
17241
17349
  let rtcPeerConnController = null;
17242
17350
  try {
17243
- this._screenShareStatus$.next("starting");
17351
+ if (isScreenShare) this._screenShareStatus$.next("starting");
17244
17352
  rtcPeerConnController = new RTCPeerConnectionController({
17245
17353
  ...options,
17246
17354
  ...this.RTCPeerConnectionConfig,
@@ -17248,21 +17356,27 @@ var WebRTCVertoManager = class extends VertoManager {
17248
17356
  webRTCApiProvider: this.webRTCApiProvider
17249
17357
  }, void 0, this.deviceController);
17250
17358
  this.setupLocalDescriptionHandler(rtcPeerConnController);
17251
- if (propose === "screenshare") this._screenShareId = rtcPeerConnController.id;
17359
+ if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
17252
17360
  this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
17253
17361
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17254
17362
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
17255
- this.onError?.(error);
17363
+ firstPeerConnectionError ??= error;
17364
+ this.onError?.(error, { fatal: false });
17256
17365
  });
17257
17366
  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$)));
17258
- this._screenShareStatus$.next("started");
17259
- logger$16.info("[WebRTCManager] Screen share started successfully.");
17367
+ if (isScreenShare) this._screenShareStatus$.next("started");
17368
+ logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
17260
17369
  return rtcPeerConnController.id;
17261
17370
  } catch (error) {
17262
17371
  logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
17263
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17264
17372
  if (rtcPeerConnController) rtcPeerConnController.destroy();
17265
- this._screenShareStatus$.next("none");
17373
+ if (isScreenShare) this._screenShareStatus$.next("none");
17374
+ if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
17375
+ if (error instanceof import_cjs$15.EmptyError) {
17376
+ logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
17377
+ return;
17378
+ }
17379
+ throw error instanceof Error ? error : new Error(String(error), { cause: error });
17266
17380
  }
17267
17381
  }
17268
17382
  async removeInputDevices(id) {
@@ -19203,6 +19317,7 @@ function inferCallErrorKind(error) {
19203
19317
  if (error instanceof RPCTimeoutError) return "timeout";
19204
19318
  if (error instanceof JSONRPCError) return "signaling";
19205
19319
  if (error instanceof MediaTrackError) return "media";
19320
+ if (error instanceof MediaAccessError) return "media";
19206
19321
  if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
19207
19322
  return "internal";
19208
19323
  }
@@ -19219,6 +19334,7 @@ const RECOVERABLE_RPC_CODES = new Set([
19219
19334
  function isFatalError(error) {
19220
19335
  if (error instanceof VertoPongError) return false;
19221
19336
  if (error instanceof MediaTrackError) return false;
19337
+ if (error instanceof MediaAccessError) return error.fatal;
19222
19338
  if (error instanceof RPCTimeoutError) return false;
19223
19339
  if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
19224
19340
  return true;
@@ -19244,10 +19360,10 @@ var CallFactory = class {
19244
19360
  return {
19245
19361
  vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
19246
19362
  nodeId: options.nodeId,
19247
- onError: (error) => {
19363
+ onError: (error, options$1) => {
19248
19364
  const callError = {
19249
19365
  kind: inferCallErrorKind(error),
19250
- fatal: isFatalError(error),
19366
+ fatal: options$1?.fatal ?? isFatalError(error),
19251
19367
  error,
19252
19368
  callId: callInstance.id
19253
19369
  };
@@ -19710,6 +19826,17 @@ var PendingRPC = class PendingRPC {
19710
19826
  //#region src/managers/ClientSessionManager.ts
19711
19827
  var import_cjs$7 = require_cjs();
19712
19828
  const logger$9 = getLogger();
19829
+ /**
19830
+ * Decide whether an error emitted on `call.errors$` during dial should
19831
+ * abort the dial. A non-fatal MediaAccessError means the call degraded to
19832
+ * receive-only and still connects — everything else rejects `dial()` with
19833
+ * the real cause.
19834
+ *
19835
+ * Pure function — exported for unit testing.
19836
+ */
19837
+ function shouldAbortDial(callError) {
19838
+ return callError.fatal || !(callError.error instanceof MediaAccessError);
19839
+ }
19713
19840
  const getAddressSearchURI = (options) => {
19714
19841
  const to = options.to?.split("?")[0];
19715
19842
  const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
@@ -20136,7 +20263,7 @@ var ClientSessionManager = class extends Destroyable {
20136
20263
  to: destinationURI,
20137
20264
  ...options
20138
20265
  });
20139
- 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)))));
20266
+ 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)))));
20140
20267
  this._calls$.next({
20141
20268
  [`${callSession.id}`]: callSession,
20142
20269
  ...this._calls$.value
@@ -21848,7 +21975,7 @@ var SignalWire = class extends Destroyable {
21848
21975
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
21849
21976
  }
21850
21977
  try {
21851
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.1" });
21978
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.2" });
21852
21979
  } catch (error) {
21853
21980
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
21854
21981
  }
@@ -22383,7 +22510,7 @@ var StaticCredentialProvider = class {
22383
22510
  /**
22384
22511
  * Library version from package.json, injected at build time.
22385
22512
  */
22386
- const version = "4.0.0-rc.1";
22513
+ const version = "4.0.0-rc.2";
22387
22514
  /**
22388
22515
  * Flag indicating the library has been loaded and is ready to use.
22389
22516
  * For UMD builds: `window.SignalWire.ready`
@@ -22405,7 +22532,7 @@ const ready = true;
22405
22532
  */
22406
22533
  const emitReadyEvent = () => {
22407
22534
  if (typeof window !== "undefined") {
22408
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.1" } });
22535
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.2" } });
22409
22536
  window.dispatchEvent(event);
22410
22537
  }
22411
22538
  };
@@ -22437,6 +22564,7 @@ exports.DPoPInitError = DPoPInitError;
22437
22564
  exports.DeviceTokenError = DeviceTokenError;
22438
22565
  exports.EmbedTokenCredentialProvider = EmbedTokenCredentialProvider;
22439
22566
  exports.InvalidCredentialsError = InvalidCredentialsError;
22567
+ exports.MediaAccessError = MediaAccessError;
22440
22568
  exports.MediaTrackError = MediaTrackError;
22441
22569
  exports.MessageParseError = MessageParseError;
22442
22570
  exports.OverconstrainedFallbackError = OverconstrainedFallbackError;