@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.
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 {
@@ -9224,6 +9361,33 @@ var MediaTrackError = class extends Error {
9224
9361
  this.name = "MediaTrackError";
9225
9362
  }
9226
9363
  };
9364
+ /** True when a `getUserMedia`/`getDisplayMedia` rejection is a permission denial. */
9365
+ function isMediaAccessDenial(originalError) {
9366
+ return originalError instanceof Error && MEDIA_ACCESS_DENIAL_NAMES.includes(originalError.name);
9367
+ }
9368
+ /**
9369
+ * Failure to acquire local media (camera, microphone, or screen capture)
9370
+ * via `getUserMedia`/`getDisplayMedia`.
9371
+ *
9372
+ * Non-fatal by default: screenshare and additional-device failures never
9373
+ * end the call, and main-connection failures degrade to receive-only when
9374
+ * possible. The wrapping site sets `fatal` to `true` only when the call
9375
+ * cannot continue (receive-only fallback disabled or no receive intent).
9376
+ */
9377
+ var MediaAccessError = class extends Error {
9378
+ constructor(operation, media, originalError, fatal = false) {
9379
+ super(`Media access ${isMediaAccessDenial(originalError) ? "denied" : "failed"} for ${operation} (${media})`, { cause: originalError instanceof Error ? originalError : void 0 });
9380
+ this.operation = operation;
9381
+ this.media = media;
9382
+ this.originalError = originalError;
9383
+ this.fatal = fatal;
9384
+ this.name = "MediaAccessError";
9385
+ }
9386
+ /** True when the underlying failure is a permission denial (user or policy). */
9387
+ get denied() {
9388
+ return isMediaAccessDenial(this.originalError);
9389
+ }
9390
+ };
9227
9391
  var DPoPInitError = class extends Error {
9228
9392
  constructor(originalError, message = "Failed to initialize DPoP key pair") {
9229
9393
  super(message, { cause: originalError instanceof Error ? originalError : void 0 });
@@ -9873,137 +10037,6 @@ var DeviceHistoryManager = class {
9873
10037
  }
9874
10038
  };
9875
10039
 
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
- /**
9910
- * Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
9911
- * to resolve before treating the activation as failed and falling back to
9912
- * the developer-provided refresh path. Prevents a wedged HTTP layer from
9913
- * leaving the session with no active refresh mechanism.
9914
- */
9915
- const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
9916
- /** JSON-RPC error code for requester validation failure (corrupted auth state). */
9917
- const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
9918
- /** JSON-RPC error code for invalid params (e.g., missing authentication block). */
9919
- const RPC_ERROR_INVALID_PARAMS = -32602;
9920
- /** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
9921
- const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
9922
- /** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
9923
- const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
9924
- /** Number of initial samples used to build a baseline for spike detection. */
9925
- const DEFAULT_STATS_BASELINE_SAMPLES = 10;
9926
- /** Duration in ms with no inbound audio packets before emitting a critical issue. */
9927
- const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
9928
- /** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
9929
- const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
9930
- /** Packet loss fraction (0-1) above which a warning is emitted. */
9931
- const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
9932
- /** Multiplier applied to baseline jitter to detect a jitter spike. */
9933
- const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
9934
- /** Number of seconds of metrics history to retain. */
9935
- const DEFAULT_STATS_HISTORY_SIZE = 30;
9936
- /** Maximum keyframe requests allowed within a single burst window. */
9937
- const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
9938
- /** Duration of the keyframe burst window in milliseconds. */
9939
- const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
9940
- /** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
9941
- const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
9942
- /** Minimum time between re-INVITE attempts in milliseconds. */
9943
- const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
9944
- /** Maximum number of re-INVITE attempts per call. */
9945
- const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
9946
- /** Timeout for a single re-INVITE attempt in milliseconds. */
9947
- const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
9948
- /** Debounce window in ms to collapse multiple detection signals into one trigger. */
9949
- const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
9950
- /** Cooldown period in ms between recovery attempts. */
9951
- const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
9952
- /** Grace period in ms before treating ICE 'disconnected' as a failure. */
9953
- const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
9954
- /** Timeout for a single ICE restart attempt in milliseconds. */
9955
- const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
9956
- /** Maximum recovery attempts before emitting 'max_attempts_reached'. */
9957
- const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
9958
- /** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
9959
- const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
9960
- /** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
9961
- const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
9962
- /** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
9963
- const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
9964
- /** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
9965
- const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
9966
- /** RMS level threshold (0..1) above which the local participant is considered speaking. */
9967
- const VAD_THRESHOLD = .03;
9968
- /** Hold window in ms below the threshold before speaking$ flips back to false. */
9969
- const VAD_HOLD_MS = 250;
9970
- /** Whether to persist device selections to storage by default. */
9971
- const DEFAULT_PERSIST_DEVICE_SELECTION = true;
9972
- /** Whether to auto-apply device changes to active calls by default. */
9973
- const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
9974
- /** Storage keys for persisted device selections. */
9975
- const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
9976
- const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
9977
- const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
9978
- /** Whether to auto-mute video when the tab becomes hidden. */
9979
- const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
9980
- /** Whether to re-enumerate devices when the page becomes visible. */
9981
- const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
9982
- /** Whether to check peer connection health when the page becomes visible. */
9983
- const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
9984
- /** Whether automatic video degradation on low bandwidth is enabled. */
9985
- const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
9986
- /** Bitrate in kbps below which video is automatically disabled. */
9987
- const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
9988
- /** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
9989
- const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
9990
- /** Whether relay-only escalation is enabled as a last-resort recovery tier. */
9991
- const DEFAULT_ENABLE_RELAY_FALLBACK = true;
9992
- /** Whether to listen for browser online/offline/connection events. */
9993
- const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
9994
- /** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
9995
- const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
9996
- /** Default video track constraints applied when video is enabled without explicit constraints. */
9997
- const DEFAULT_VIDEO_CONSTRAINTS = {
9998
- width: { ideal: 1280 },
9999
- height: { ideal: 720 },
10000
- aspectRatio: 16 / 9
10001
- };
10002
- /** Whether stereo Opus is enabled by default. */
10003
- const DEFAULT_STEREO_AUDIO = false;
10004
- /** Max average bitrate for stereo Opus in bits per second. */
10005
- const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
10006
-
10007
10040
  //#endregion
10008
10041
  //#region src/utils/time.ts
10009
10042
  function fromSecToMs(seconds) {
@@ -14051,12 +14084,21 @@ var SelfParticipant = class extends Participant {
14051
14084
  noise_suppression: true
14052
14085
  });
14053
14086
  }
14054
- /** Starts sharing the local screen. */
14087
+ /**
14088
+ * Starts sharing the local screen.
14089
+ *
14090
+ * The call is unaffected when acquisition fails.
14091
+ *
14092
+ * @throws The raw `getDisplayMedia` error. A dismissed picker or a
14093
+ * permission denial rejects with a `NotAllowedError` `DOMException` —
14094
+ * inspect `error.name` to tell benign cancels apart from real failures.
14095
+ */
14055
14096
  async startScreenShare() {
14056
14097
  try {
14057
14098
  await this.vertoManager.addScreenMedia();
14058
14099
  } catch (error) {
14059
14100
  logger$24.error("[Participant.startScreenShare] Screen share error:", error);
14101
+ throw error;
14060
14102
  }
14061
14103
  }
14062
14104
  /** Observable of the current screen share status. */
@@ -14071,12 +14113,20 @@ var SelfParticipant = class extends Participant {
14071
14113
  async stopScreenShare() {
14072
14114
  return this.vertoManager.removeScreenMedia();
14073
14115
  }
14074
- /** Adds an additional media input device to the call. */
14116
+ /**
14117
+ * Adds an additional media input device to the call.
14118
+ *
14119
+ * The call is unaffected when acquisition fails.
14120
+ *
14121
+ * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
14122
+ * permission denial) — inspect `error.name` to decide how to react.
14123
+ */
14075
14124
  async addAdditionalDevice(options) {
14076
14125
  try {
14077
14126
  await this.vertoManager.addInputDevice(options);
14078
14127
  } catch (error) {
14079
- logger$24.error("[Participant.startScreenShare] Screen share error:", error);
14128
+ logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
14129
+ throw error;
14080
14130
  }
14081
14131
  }
14082
14132
  /** Removes an additional media input device by ID. */
@@ -15708,7 +15758,7 @@ var RTCPeerConnectionController = class extends Destroyable {
15708
15758
  logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
15709
15759
  } catch (error) {
15710
15760
  logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
15711
- this._errors$.next(toError(error));
15761
+ this._errors$.next(new MediaTrackError("updateSelectedInputDevice", kind, error));
15712
15762
  throw error;
15713
15763
  }
15714
15764
  };
@@ -15930,7 +15980,7 @@ var RTCPeerConnectionController = class extends Destroyable {
15930
15980
  case "main":
15931
15981
  default: return {
15932
15982
  ...options,
15933
- offerToReceiveAudio: true,
15983
+ offerToReceiveAudio: this.options.receiveAudio ?? true,
15934
15984
  offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
15935
15985
  };
15936
15986
  }
@@ -16084,13 +16134,14 @@ var RTCPeerConnectionController = class extends Destroyable {
16084
16134
  */
16085
16135
  async acceptInbound(mediaOverrides) {
16086
16136
  if (mediaOverrides) {
16087
- const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
16137
+ const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
16088
16138
  this.options = {
16089
16139
  ...this.options,
16090
16140
  ...audio !== void 0 ? { audio } : {},
16091
16141
  ...video !== void 0 ? { video } : {},
16092
16142
  ...receiveAudio !== void 0 ? { receiveAudio } : {},
16093
- ...receiveVideo !== void 0 ? { receiveVideo } : {}
16143
+ ...receiveVideo !== void 0 ? { receiveVideo } : {},
16144
+ ...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
16094
16145
  };
16095
16146
  this.transceiverController?.updateOptions({
16096
16147
  receiveAudio: this.receiveAudio,
@@ -16247,7 +16298,19 @@ var RTCPeerConnectionController = class extends Destroyable {
16247
16298
  }
16248
16299
  async setupLocalTracks() {
16249
16300
  logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
16250
- const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
16301
+ if (this.hasNoLocalMediaToSend()) {
16302
+ if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
16303
+ logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
16304
+ this.setupReceiveOnlyTransceivers();
16305
+ return;
16306
+ }
16307
+ let localStream;
16308
+ try {
16309
+ localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
16310
+ } catch (error) {
16311
+ this.handleLocalMediaFailure(error);
16312
+ return;
16313
+ }
16251
16314
  if (this.transceiverController?.useAddStream ?? false) {
16252
16315
  logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
16253
16316
  this.peerConnection?.addStream(localStream);
@@ -16274,6 +16337,48 @@ var RTCPeerConnectionController = class extends Destroyable {
16274
16337
  }
16275
16338
  }
16276
16339
  }
16340
+ /** True for a main connection with no local media to send. */
16341
+ hasNoLocalMediaToSend() {
16342
+ const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
16343
+ return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
16344
+ }
16345
+ /** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
16346
+ get requestedMediaKinds() {
16347
+ const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
16348
+ const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
16349
+ if (wantsAudio && wantsVideo) return "audiovideo";
16350
+ return wantsVideo ? "video" : "audio";
16351
+ }
16352
+ /**
16353
+ * Handle a local media acquisition failure with a typed, semantically
16354
+ * accurate MediaAccessError created at the acquisition site:
16355
+ * - Auxiliary connections (screenshare / additional-device) throw a
16356
+ * non-fatal error — VertoManager surfaces it and the call is unaffected.
16357
+ * - The main connection degrades to receive-only when allowed (default),
16358
+ * otherwise fails with a fatal error.
16359
+ */
16360
+ handleLocalMediaFailure(error) {
16361
+ if (this.propose === "screenshare") throw new MediaAccessError("startScreenShare", "screen", error, false);
16362
+ if (this.propose === "additional-device") throw new MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
16363
+ const canReceive = this.receiveAudio || this.receiveVideo;
16364
+ if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
16365
+ logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
16366
+ this._errors$.next(new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
16367
+ this.setupReceiveOnlyTransceivers();
16368
+ }
16369
+ /**
16370
+ * Negotiate receive-only m-lines when there are no local tracks to send.
16371
+ * Only offer-type connections add transceivers — answer-type connections
16372
+ * reuse the transceivers created from the remote offer.
16373
+ */
16374
+ setupReceiveOnlyTransceivers() {
16375
+ if (this.type !== "offer") return;
16376
+ if (this.transceiverController?.useAddTransceivers ?? false) {
16377
+ this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
16378
+ this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
16379
+ }
16380
+ if (!this.isNegotiating) this.negotiationNeeded$.next();
16381
+ }
16277
16382
  async getUserMedia(constraints) {
16278
16383
  return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
16279
16384
  }
@@ -16310,7 +16415,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16310
16415
  stream = await this.getUserMedia({ audio: constraints });
16311
16416
  } catch (error) {
16312
16417
  logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
16313
- this._errors$.next(toError(error));
16418
+ this._errors$.next(new MediaTrackError("restoreAudioPipelineInput", "audio", error));
16314
16419
  return;
16315
16420
  }
16316
16421
  const newTrack = stream.getAudioTracks().at(0);
@@ -16372,7 +16477,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16372
16477
  logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
16373
16478
  } catch (error) {
16374
16479
  logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
16375
- this._errors$.next(toError(error));
16480
+ this._errors$.next(new MediaTrackError("addLocalTrack", track.kind, error));
16376
16481
  throw error;
16377
16482
  }
16378
16483
  }
@@ -16397,7 +16502,7 @@ var RTCPeerConnectionController = class extends Destroyable {
16397
16502
  logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
16398
16503
  } catch (error) {
16399
16504
  logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
16400
- this._errors$.next(toError(error));
16505
+ this._errors$.next(new MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
16401
16506
  throw error;
16402
16507
  }
16403
16508
  }
@@ -17006,6 +17111,7 @@ var WebRTCVertoManager = class extends VertoManager {
17006
17111
  inputVideoStream: options.inputVideoStream,
17007
17112
  receiveAudio: options.receiveAudio,
17008
17113
  receiveVideo: options.receiveVideo,
17114
+ fallbackToReceiveOnly: options.fallbackToReceiveOnly,
17009
17115
  webRTCApiProvider: this.webRTCApiProvider,
17010
17116
  preferredVideoCodecs: options.preferredVideoCodecs,
17011
17117
  preferredAudioCodecs: options.preferredAudioCodecs,
@@ -17232,9 +17338,11 @@ var WebRTCVertoManager = class extends VertoManager {
17232
17338
  await this.initAdditionalPeerConnection("screenshare", options);
17233
17339
  }
17234
17340
  async initAdditionalPeerConnection(propose, options) {
17341
+ const isScreenShare = propose === "screenshare";
17342
+ let firstPeerConnectionError;
17235
17343
  let rtcPeerConnController = null;
17236
17344
  try {
17237
- this._screenShareStatus$.next("starting");
17345
+ if (isScreenShare) this._screenShareStatus$.next("starting");
17238
17346
  rtcPeerConnController = new RTCPeerConnectionController({
17239
17347
  ...options,
17240
17348
  ...this.RTCPeerConnectionConfig,
@@ -17242,21 +17350,27 @@ var WebRTCVertoManager = class extends VertoManager {
17242
17350
  webRTCApiProvider: this.webRTCApiProvider
17243
17351
  }, void 0, this.deviceController);
17244
17352
  this.setupLocalDescriptionHandler(rtcPeerConnController);
17245
- if (propose === "screenshare") this._screenShareId = rtcPeerConnController.id;
17353
+ if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
17246
17354
  this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
17247
17355
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
17248
17356
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
17249
- this.onError?.(error);
17357
+ firstPeerConnectionError ??= error;
17358
+ this.onError?.(error, { fatal: false });
17250
17359
  });
17251
17360
  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$)));
17252
- this._screenShareStatus$.next("started");
17253
- logger$16.info("[WebRTCManager] Screen share started successfully.");
17361
+ if (isScreenShare) this._screenShareStatus$.next("started");
17362
+ logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
17254
17363
  return rtcPeerConnController.id;
17255
17364
  } catch (error) {
17256
17365
  logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
17257
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
17258
17366
  if (rtcPeerConnController) rtcPeerConnController.destroy();
17259
- this._screenShareStatus$.next("none");
17367
+ if (isScreenShare) this._screenShareStatus$.next("none");
17368
+ if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
17369
+ if (error instanceof import_cjs$15.EmptyError) {
17370
+ logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
17371
+ return;
17372
+ }
17373
+ throw error instanceof Error ? error : new Error(String(error), { cause: error });
17260
17374
  }
17261
17375
  }
17262
17376
  async removeInputDevices(id) {
@@ -19197,6 +19311,7 @@ function inferCallErrorKind(error) {
19197
19311
  if (error instanceof RPCTimeoutError) return "timeout";
19198
19312
  if (error instanceof JSONRPCError) return "signaling";
19199
19313
  if (error instanceof MediaTrackError) return "media";
19314
+ if (error instanceof MediaAccessError) return "media";
19200
19315
  if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
19201
19316
  return "internal";
19202
19317
  }
@@ -19213,6 +19328,7 @@ const RECOVERABLE_RPC_CODES = new Set([
19213
19328
  function isFatalError(error) {
19214
19329
  if (error instanceof VertoPongError) return false;
19215
19330
  if (error instanceof MediaTrackError) return false;
19331
+ if (error instanceof MediaAccessError) return error.fatal;
19216
19332
  if (error instanceof RPCTimeoutError) return false;
19217
19333
  if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
19218
19334
  return true;
@@ -19238,10 +19354,10 @@ var CallFactory = class {
19238
19354
  return {
19239
19355
  vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
19240
19356
  nodeId: options.nodeId,
19241
- onError: (error) => {
19357
+ onError: (error, options$1) => {
19242
19358
  const callError = {
19243
19359
  kind: inferCallErrorKind(error),
19244
- fatal: isFatalError(error),
19360
+ fatal: options$1?.fatal ?? isFatalError(error),
19245
19361
  error,
19246
19362
  callId: callInstance.id
19247
19363
  };
@@ -19704,6 +19820,17 @@ var PendingRPC = class PendingRPC {
19704
19820
  //#region src/managers/ClientSessionManager.ts
19705
19821
  var import_cjs$7 = require_cjs();
19706
19822
  const logger$9 = getLogger();
19823
+ /**
19824
+ * Decide whether an error emitted on `call.errors$` during dial should
19825
+ * abort the dial. A non-fatal MediaAccessError means the call degraded to
19826
+ * receive-only and still connects — everything else rejects `dial()` with
19827
+ * the real cause.
19828
+ *
19829
+ * Pure function — exported for unit testing.
19830
+ */
19831
+ function shouldAbortDial(callError) {
19832
+ return callError.fatal || !(callError.error instanceof MediaAccessError);
19833
+ }
19707
19834
  const getAddressSearchURI = (options) => {
19708
19835
  const to = options.to?.split("?")[0];
19709
19836
  const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
@@ -20130,7 +20257,7 @@ var ClientSessionManager = class extends Destroyable {
20130
20257
  to: destinationURI,
20131
20258
  ...options
20132
20259
  });
20133
- 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)))));
20260
+ 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)))));
20134
20261
  this._calls$.next({
20135
20262
  [`${callSession.id}`]: callSession,
20136
20263
  ...this._calls$.value
@@ -21842,7 +21969,7 @@ var SignalWire = class extends Destroyable {
21842
21969
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
21843
21970
  }
21844
21971
  try {
21845
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.1" });
21972
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.2" });
21846
21973
  } catch (error) {
21847
21974
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
21848
21975
  }
@@ -22377,7 +22504,7 @@ var StaticCredentialProvider = class {
22377
22504
  /**
22378
22505
  * Library version from package.json, injected at build time.
22379
22506
  */
22380
- const version = "4.0.0-rc.1";
22507
+ const version = "4.0.0-rc.2";
22381
22508
  /**
22382
22509
  * Flag indicating the library has been loaded and is ready to use.
22383
22510
  * For UMD builds: `window.SignalWire.ready`
@@ -22399,7 +22526,7 @@ const ready = true;
22399
22526
  */
22400
22527
  const emitReadyEvent = () => {
22401
22528
  if (typeof window !== "undefined") {
22402
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.1" } });
22529
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.2" } });
22403
22530
  window.dispatchEvent(event);
22404
22531
  }
22405
22532
  };
@@ -22423,5 +22550,5 @@ emitReadyEvent();
22423
22550
  if (typeof process === "undefined") globalThis.process = { env: { NODE_ENV: "production" } };
22424
22551
 
22425
22552
  //#endregion
22426
- 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 };
22553
+ export { Address, CallCreateError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaAccessError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
22427
22554
  //# sourceMappingURL=browser.mjs.map