@signalwire/js 4.0.0-rc.0 → 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/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { A as StorageNotAvailableError, B as VertoPongError, C as OverconstrainedFallbackError, D as RequestError, E as RecoveryError, F as UnexpectedError, H as WebSocketTimeoutError, I as UnimplementedError, L as ValidationError, M as StorageWriteError, N as TokenRefreshError, O as RequestTimeoutError, P as TransportConnectionError, R as VertoAttachHandlerError, S as MessageParseError, T as RPCTimeoutError, V as WebSocketConnectionError, _ as DeviceTokenError, a as filterNull, b as JSONRPCError, c as setLogLevel, d as CallCreateError, f as CollectionFetchError, g as DeserializationError, h as DependencyError, i as getValueFrom, j as StorageReadError, k as SerializationError, l as setLogger, m as DPoPInitError, n as filterAs, o as getLogger, p as ConversationError, s as setDebugOptions, t as throwOnRPCError, u as AuthStateHandlerError, v as InvalidCredentialsError, w as PreflightError, x as MediaTrackError, y as InvalidParams, z as VertoInviteHandlerError } from "./operators-CX_lCCJm.mjs";
1
+ import { $ as DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS, $t as VAD_HOLD_MS, A as SerializationError, At as DEFAULT_STATS_POLLING_INTERVAL_MS, B as VertoInviteHandlerError, Bt as DEVICE_TOKEN_ENDPOINT, C as MessageParseError, Ct as DEFAULT_REINVITE_MAX_ATTEMPTS, D as RecoveryError, Dt as DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER, E as RPCTimeoutError, Et as DEFAULT_STATS_HISTORY_SIZE, F as TransportConnectionError, Ft as DEVICE_REFRESH_ENDPOINT, G as CREDENTIAL_ACTIVATE_TIMEOUT_MS, Gt as INVITE_VERSION, H as WebSocketConnectionError, Ht as DEVICE_TOKEN_REFRESH_MAX_RETRIES, I as UnexpectedError, It as DEVICE_STORAGE_KEY_AUDIO_INPUT, J as CREDENTIAL_REFRESH_MAX_RETRIES, Jt as PREFERENCES_STORAGE_KEY, K as CREDENTIAL_REFRESH_BUFFER_MS, Kt as PEER_CONNECTION_RECOVERY_POLL_MS, L as UnimplementedError, Lt as DEVICE_STORAGE_KEY_AUDIO_OUTPUT, M as StorageReadError, Mt as DEFAULT_STEREO_AUDIO, N as StorageWriteError, Nt as DEFAULT_STEREO_MAX_AVERAGE_BITRATE, O as RequestError, Ot as DEFAULT_STATS_NO_PACKET_THRESHOLD_MS, P as TokenRefreshError, Pt as DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS, Q as DEFAULT_CONNECTION_TIMEOUT_MS, Qt as SAT_REFRESH_SCOPE, R as ValidationError, Rt as DEVICE_STORAGE_KEY_VIDEO_INPUT, S as MediaTrackError, St as DEFAULT_REINVITE_DEBOUNCE_TIME_MS, T as PreflightError, Tt as DEFAULT_STATS_BASELINE_SAMPLES, U as WebSocketTimeoutError, Ut as DEVICE_TOKEN_REFRESH_RETRY_BASE_MS, V as VertoPongError, Vt as DEVICE_TOKEN_REFRESH_BUFFER_MS, W as AUDIO_LEVEL_POLL_INTERVAL_MS, Wt as ICE_GATHERING_COMPLETE_TIMEOUT_MS, X as DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN, Xt as RPC_ERROR_INVALID_PARAMS, Y as CREDENTIAL_REFRESH_RETRY_BASE_MS, Yt as RPC_ERROR_AUTHENTICATION_FAILED, Z as DEFAULT_CHECK_CONNECTION_ON_VISIBLE, Zt as RPC_ERROR_REQUESTER_VALIDATION_FAILED, _ as DeviceTokenError, _t as DEFAULT_RECONNECT_DELAY_MAX_MS, a as filterNull, at as DEFAULT_ENABLE_RELAY_FALLBACK, b as JSONRPCError, bt as DEFAULT_RECOVERY_DEBOUNCE_TIME_MS, c as setLogLevel, ct as DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS, d as CallCreateError, dt as DEFAULT_KEYFRAME_BURST_WINDOW_MS$1, en as VAD_THRESHOLD, et as DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS, f as CollectionFetchError, ft as DEFAULT_KEYFRAME_COOLDOWN_MS$1, g as DeserializationError, gt as DEFAULT_RECONNECT_CALLS_TIMEOUT_MS, h as DependencyError, ht as DEFAULT_PERSIST_DEVICE_SELECTION, i as getValueFrom, it as DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION, j as StorageNotAvailableError, jt as DEFAULT_STATS_RTT_SPIKE_MULTIPLIER, k as RequestTimeoutError, kt as DEFAULT_STATS_PACKET_LOSS_THRESHOLD, l as setLogger, lt as DEFAULT_ICE_GATHERING_TIMEOUT_MS, m as DPoPInitError, mt as DEFAULT_MAX_RECOVERY_ATTEMPTS, n as filterAs, nt as DEFAULT_DEVICE_POLLING_INTERVAL_MS, o as getLogger, ot as DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION, p as ConversationError, pt as DEFAULT_KEYFRAME_MAX_BURST$1, q as CREDENTIAL_REFRESH_MAX_DELAY_MS, qt as PEER_CONNECTION_RECOVERY_WAIT_MS, rt as DEFAULT_ENABLE_AUTO_DEGRADATION, s as setDebugOptions, st as DEFAULT_ICE_CANDIDATE_TIMEOUT_MS, t as throwOnRPCError, tt as DEFAULT_DEVICE_DEBOUNCE_TIME_MS, u as AuthStateHandlerError, ut as DEFAULT_ICE_RESTART_TIMEOUT_MS$1, v as InvalidCredentialsError, vt as DEFAULT_RECONNECT_DELAY_MIN_MS, w as OverconstrainedFallbackError, wt as DEFAULT_REINVITE_TIMEOUT_MS, x as MediaAccessError, xt as DEFAULT_REFRESH_DEVICES_ON_VISIBLE, y as InvalidParams, yt as DEFAULT_RECOVERY_COOLDOWN_MS, z as VertoAttachHandlerError, zt as DEVICE_TOKEN_DEFAULT_EXPIRE_IN } from "./operators-BlUtq-t0.mjs";
2
2
  import { jwtDecode } from "jwt-decode";
3
- import { BehaviorSubject, EMPTY, NEVER, Observable, ReplaySubject, Subject, TimeoutError, animationFrameScheduler, asapScheduler, auditTime, catchError, combineLatest, debounceTime, defer, distinctUntilChanged, exhaustMap, filter, first, firstValueFrom, from, interval, lastValueFrom, map, merge, mergeMap, observeOn, of, pipe, race, scan, share, shareReplay, skip, skipWhile, startWith, switchMap, take, takeUntil, tap, throwError, timeout, timer, toArray, withLatestFrom } from "rxjs";
3
+ import { BehaviorSubject, EMPTY, EmptyError, NEVER, Observable, ReplaySubject, Subject, TimeoutError, animationFrameScheduler, asapScheduler, auditTime, catchError, combineLatest, debounceTime, defer, distinctUntilChanged, exhaustMap, filter, first, firstValueFrom, from, interval, lastValueFrom, map, merge, mergeMap, observeOn, of, pipe, race, scan, share, shareReplay, skip, skipWhile, startWith, switchMap, take, takeUntil, tap, throwError, timeout, timer, toArray, withLatestFrom } from "rxjs";
4
4
  import { v4 } from "uuid";
5
5
  import { distinctUntilChanged as distinctUntilChanged$1, map as map$1 } from "rxjs/operators";
6
6
 
@@ -404,137 +404,6 @@ var DeviceHistoryManager = class {
404
404
  }
405
405
  };
406
406
 
407
- //#endregion
408
- //#region src/core/constants.ts
409
- const INVITE_VERSION = 1e3;
410
- const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
411
- const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
412
- const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
413
- const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
414
- const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
415
- const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
416
- const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
417
- const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
418
- const PREFERENCES_STORAGE_KEY = "sw:preferences";
419
- /** Scope value that enables automatic token refresh. */
420
- const SAT_REFRESH_SCOPE = "sat:refresh";
421
- /** API endpoints for device token operations. */
422
- const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
423
- const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
424
- /** Default device token TTL in seconds (15 minutes). */
425
- const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
426
- /** Buffer time in milliseconds before expiry to trigger refresh. */
427
- const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
428
- /** Maximum retry attempts for device token refresh on transient failure. */
429
- const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
430
- /** Base delay in milliseconds for exponential backoff on refresh retry. */
431
- const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
432
- /** Maximum retry attempts for developer credential refresh on transient failure. */
433
- const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
434
- /** Base delay in milliseconds for exponential backoff on credential refresh retry. */
435
- const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
436
- /** Maximum delay in milliseconds for credential refresh backoff. */
437
- const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
438
- /** Buffer in milliseconds before token expiry to trigger refresh. */
439
- const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
440
- /**
441
- * Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
442
- * to resolve before treating the activation as failed and falling back to
443
- * the developer-provided refresh path. Prevents a wedged HTTP layer from
444
- * leaving the session with no active refresh mechanism.
445
- */
446
- const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
447
- /** JSON-RPC error code for requester validation failure (corrupted auth state). */
448
- const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
449
- /** JSON-RPC error code for invalid params (e.g., missing authentication block). */
450
- const RPC_ERROR_INVALID_PARAMS = -32602;
451
- /** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
452
- const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
453
- /** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
454
- const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
455
- /** Number of initial samples used to build a baseline for spike detection. */
456
- const DEFAULT_STATS_BASELINE_SAMPLES = 10;
457
- /** Duration in ms with no inbound audio packets before emitting a critical issue. */
458
- const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
459
- /** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
460
- const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
461
- /** Packet loss fraction (0-1) above which a warning is emitted. */
462
- const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
463
- /** Multiplier applied to baseline jitter to detect a jitter spike. */
464
- const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
465
- /** Number of seconds of metrics history to retain. */
466
- const DEFAULT_STATS_HISTORY_SIZE = 30;
467
- /** Maximum keyframe requests allowed within a single burst window. */
468
- const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
469
- /** Duration of the keyframe burst window in milliseconds. */
470
- const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
471
- /** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
472
- const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
473
- /** Minimum time between re-INVITE attempts in milliseconds. */
474
- const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
475
- /** Maximum number of re-INVITE attempts per call. */
476
- const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
477
- /** Timeout for a single re-INVITE attempt in milliseconds. */
478
- const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
479
- /** Debounce window in ms to collapse multiple detection signals into one trigger. */
480
- const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
481
- /** Cooldown period in ms between recovery attempts. */
482
- const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
483
- /** Grace period in ms before treating ICE 'disconnected' as a failure. */
484
- const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
485
- /** Timeout for a single ICE restart attempt in milliseconds. */
486
- const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
487
- /** Maximum recovery attempts before emitting 'max_attempts_reached'. */
488
- const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
489
- /** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
490
- const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
491
- /** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
492
- const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
493
- /** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
494
- const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
495
- /** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
496
- const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
497
- /** RMS level threshold (0..1) above which the local participant is considered speaking. */
498
- const VAD_THRESHOLD = .03;
499
- /** Hold window in ms below the threshold before speaking$ flips back to false. */
500
- const VAD_HOLD_MS = 250;
501
- /** Whether to persist device selections to storage by default. */
502
- const DEFAULT_PERSIST_DEVICE_SELECTION = true;
503
- /** Whether to auto-apply device changes to active calls by default. */
504
- const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
505
- /** Storage keys for persisted device selections. */
506
- const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
507
- const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
508
- const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
509
- /** Whether to auto-mute video when the tab becomes hidden. */
510
- const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
511
- /** Whether to re-enumerate devices when the page becomes visible. */
512
- const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
513
- /** Whether to check peer connection health when the page becomes visible. */
514
- const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
515
- /** Whether automatic video degradation on low bandwidth is enabled. */
516
- const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
517
- /** Bitrate in kbps below which video is automatically disabled. */
518
- const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
519
- /** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
520
- const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
521
- /** Whether relay-only escalation is enabled as a last-resort recovery tier. */
522
- const DEFAULT_ENABLE_RELAY_FALLBACK = true;
523
- /** Whether to listen for browser online/offline/connection events. */
524
- const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
525
- /** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
526
- const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
527
- /** Default video track constraints applied when video is enabled without explicit constraints. */
528
- const DEFAULT_VIDEO_CONSTRAINTS = {
529
- width: { ideal: 1280 },
530
- height: { ideal: 720 },
531
- aspectRatio: 16 / 9
532
- };
533
- /** Whether stereo Opus is enabled by default. */
534
- const DEFAULT_STEREO_AUDIO = false;
535
- /** Max average bitrate for stereo Opus in bits per second. */
536
- const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
537
-
538
407
  //#endregion
539
408
  //#region src/utils/time.ts
540
409
  function fromSecToMs(seconds) {
@@ -3005,7 +2874,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
3005
2874
  position: false,
3006
2875
  meta: false,
3007
2876
  remove: false,
3008
- audioFlags: false
2877
+ audioFlags: false,
2878
+ denoise: false,
2879
+ lowbitrate: false
3009
2880
  };
3010
2881
  /**
3011
2882
  * Default call capabilities with no permissions
@@ -3078,7 +2949,9 @@ function computeMemberCapabilities(flags, memberType) {
3078
2949
  position: hasBooleanCapability(flags, memberType, null, "position"),
3079
2950
  meta: hasBooleanCapability(flags, memberType, null, "meta"),
3080
2951
  remove: hasBooleanCapability(flags, memberType, null, "remove"),
3081
- audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
2952
+ audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
2953
+ denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
2954
+ lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
3082
2955
  };
3083
2956
  }
3084
2957
  /**
@@ -3459,6 +3332,10 @@ var Participant = class extends Destroyable {
3459
3332
  get nodeId() {
3460
3333
  return this._state$.value.node_id;
3461
3334
  }
3335
+ /** Call ID for this participant's leg, or `undefined` if not available. */
3336
+ get callId() {
3337
+ return this._state$.value.call_id;
3338
+ }
3462
3339
  /** @internal */
3463
3340
  get value() {
3464
3341
  return this._state$.value;
@@ -3520,8 +3397,9 @@ var Participant = class extends Destroyable {
3520
3397
  noise_suppression: !this.noiseSuppression
3521
3398
  });
3522
3399
  }
3400
+ /** Toggles low-bitrate mode for this participant's media. */
3523
3401
  async toggleLowbitrate() {
3524
- throw new UnimplementedError();
3402
+ await this.executeMethod(this.id, "call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
3525
3403
  }
3526
3404
  /**
3527
3405
  * Adjusts the **conference-only** microphone energy gate / sensitivity level
@@ -3568,10 +3446,27 @@ var Participant = class extends Destroyable {
3568
3446
  }
3569
3447
  /**
3570
3448
  * Sets the participant's position in the video layout.
3449
+ *
3450
+ * Requires the `member.position` capability. The gateway keys positions by the
3451
+ * **target member's own** `call_id`/`node_id` (see issue #19400 and the legacy
3452
+ * `setPositions` implementation), so this sends the participant's own call
3453
+ * context — matching {@link Participant.remove}. A resolved promise does not
3454
+ * guarantee a visible change: the backend silently returns `200` (no-op) for
3455
+ * non-conference targets.
3456
+ *
3571
3457
  * @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
3572
3458
  */
3573
3459
  async setPosition(value) {
3574
- await this.executeMethod(this.id, "call.member.position.set", { position: value });
3460
+ const state = this._state$.value;
3461
+ const target = {
3462
+ member_id: this.id,
3463
+ call_id: state.call_id ?? "",
3464
+ node_id: state.node_id ?? ""
3465
+ };
3466
+ await this.executeMethod(target, "call.member.position.set", { targets: [{
3467
+ target,
3468
+ position: value
3469
+ }] });
3575
3470
  }
3576
3471
  /** Removes this participant from the call. */
3577
3472
  async remove() {
@@ -3661,12 +3556,21 @@ var SelfParticipant = class extends Participant {
3661
3556
  noise_suppression: true
3662
3557
  });
3663
3558
  }
3664
- /** Starts sharing the local screen. */
3559
+ /**
3560
+ * Starts sharing the local screen.
3561
+ *
3562
+ * The call is unaffected when acquisition fails.
3563
+ *
3564
+ * @throws The raw `getDisplayMedia` error. A dismissed picker or a
3565
+ * permission denial rejects with a `NotAllowedError` `DOMException` —
3566
+ * inspect `error.name` to tell benign cancels apart from real failures.
3567
+ */
3665
3568
  async startScreenShare() {
3666
3569
  try {
3667
3570
  await this.vertoManager.addScreenMedia();
3668
3571
  } catch (error) {
3669
3572
  logger$23.error("[Participant.startScreenShare] Screen share error:", error);
3573
+ throw error;
3670
3574
  }
3671
3575
  }
3672
3576
  /** Observable of the current screen share status. */
@@ -3681,12 +3585,20 @@ var SelfParticipant = class extends Participant {
3681
3585
  async stopScreenShare() {
3682
3586
  return this.vertoManager.removeScreenMedia();
3683
3587
  }
3684
- /** Adds an additional media input device to the call. */
3588
+ /**
3589
+ * Adds an additional media input device to the call.
3590
+ *
3591
+ * The call is unaffected when acquisition fails.
3592
+ *
3593
+ * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
3594
+ * permission denial) — inspect `error.name` to decide how to react.
3595
+ */
3685
3596
  async addAdditionalDevice(options) {
3686
3597
  try {
3687
3598
  await this.vertoManager.addInputDevice(options);
3688
3599
  } catch (error) {
3689
- logger$23.error("[Participant.startScreenShare] Screen share error:", error);
3600
+ logger$23.error("[Participant.addAdditionalDevice] Additional device error:", error);
3601
+ throw error;
3690
3602
  }
3691
3603
  }
3692
3604
  /** Removes an additional media input device by ID. */
@@ -5180,7 +5092,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5180
5092
  logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
5181
5093
  } catch (error) {
5182
5094
  logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
5183
- this._errors$.next(toError(error));
5095
+ this._errors$.next(new MediaTrackError("updateSelectedInputDevice", kind, error));
5184
5096
  throw error;
5185
5097
  }
5186
5098
  };
@@ -5402,7 +5314,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5402
5314
  case "main":
5403
5315
  default: return {
5404
5316
  ...options,
5405
- offerToReceiveAudio: true,
5317
+ offerToReceiveAudio: this.options.receiveAudio ?? true,
5406
5318
  offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
5407
5319
  };
5408
5320
  }
@@ -5556,13 +5468,14 @@ var RTCPeerConnectionController = class extends Destroyable {
5556
5468
  */
5557
5469
  async acceptInbound(mediaOverrides) {
5558
5470
  if (mediaOverrides) {
5559
- const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
5471
+ const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
5560
5472
  this.options = {
5561
5473
  ...this.options,
5562
5474
  ...audio !== void 0 ? { audio } : {},
5563
5475
  ...video !== void 0 ? { video } : {},
5564
5476
  ...receiveAudio !== void 0 ? { receiveAudio } : {},
5565
- ...receiveVideo !== void 0 ? { receiveVideo } : {}
5477
+ ...receiveVideo !== void 0 ? { receiveVideo } : {},
5478
+ ...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
5566
5479
  };
5567
5480
  this.transceiverController?.updateOptions({
5568
5481
  receiveAudio: this.receiveAudio,
@@ -5719,7 +5632,19 @@ var RTCPeerConnectionController = class extends Destroyable {
5719
5632
  }
5720
5633
  async setupLocalTracks() {
5721
5634
  logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
5722
- const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
5635
+ if (this.hasNoLocalMediaToSend()) {
5636
+ if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
5637
+ logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
5638
+ this.setupReceiveOnlyTransceivers();
5639
+ return;
5640
+ }
5641
+ let localStream;
5642
+ try {
5643
+ localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
5644
+ } catch (error) {
5645
+ this.handleLocalMediaFailure(error);
5646
+ return;
5647
+ }
5723
5648
  if (this.transceiverController?.useAddStream ?? false) {
5724
5649
  logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
5725
5650
  this.peerConnection?.addStream(localStream);
@@ -5746,6 +5671,48 @@ var RTCPeerConnectionController = class extends Destroyable {
5746
5671
  }
5747
5672
  }
5748
5673
  }
5674
+ /** True for a main connection with no local media to send. */
5675
+ hasNoLocalMediaToSend() {
5676
+ const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
5677
+ return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
5678
+ }
5679
+ /** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
5680
+ get requestedMediaKinds() {
5681
+ const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
5682
+ const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
5683
+ if (wantsAudio && wantsVideo) return "audiovideo";
5684
+ return wantsVideo ? "video" : "audio";
5685
+ }
5686
+ /**
5687
+ * Handle a local media acquisition failure with a typed, semantically
5688
+ * accurate MediaAccessError created at the acquisition site:
5689
+ * - Auxiliary connections (screenshare / additional-device) throw a
5690
+ * non-fatal error — VertoManager surfaces it and the call is unaffected.
5691
+ * - The main connection degrades to receive-only when allowed (default),
5692
+ * otherwise fails with a fatal error.
5693
+ */
5694
+ handleLocalMediaFailure(error) {
5695
+ if (this.propose === "screenshare") throw new MediaAccessError("startScreenShare", "screen", error, false);
5696
+ if (this.propose === "additional-device") throw new MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
5697
+ const canReceive = this.receiveAudio || this.receiveVideo;
5698
+ if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
5699
+ logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
5700
+ this._errors$.next(new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
5701
+ this.setupReceiveOnlyTransceivers();
5702
+ }
5703
+ /**
5704
+ * Negotiate receive-only m-lines when there are no local tracks to send.
5705
+ * Only offer-type connections add transceivers — answer-type connections
5706
+ * reuse the transceivers created from the remote offer.
5707
+ */
5708
+ setupReceiveOnlyTransceivers() {
5709
+ if (this.type !== "offer") return;
5710
+ if (this.transceiverController?.useAddTransceivers ?? false) {
5711
+ this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
5712
+ this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
5713
+ }
5714
+ if (!this.isNegotiating) this.negotiationNeeded$.next();
5715
+ }
5749
5716
  async getUserMedia(constraints) {
5750
5717
  return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
5751
5718
  }
@@ -5782,7 +5749,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5782
5749
  stream = await this.getUserMedia({ audio: constraints });
5783
5750
  } catch (error) {
5784
5751
  logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
5785
- this._errors$.next(toError(error));
5752
+ this._errors$.next(new MediaTrackError("restoreAudioPipelineInput", "audio", error));
5786
5753
  return;
5787
5754
  }
5788
5755
  const newTrack = stream.getAudioTracks().at(0);
@@ -5844,7 +5811,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5844
5811
  logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
5845
5812
  } catch (error) {
5846
5813
  logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
5847
- this._errors$.next(toError(error));
5814
+ this._errors$.next(new MediaTrackError("addLocalTrack", track.kind, error));
5848
5815
  throw error;
5849
5816
  }
5850
5817
  }
@@ -5869,7 +5836,7 @@ var RTCPeerConnectionController = class extends Destroyable {
5869
5836
  logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
5870
5837
  } catch (error) {
5871
5838
  logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
5872
- this._errors$.next(toError(error));
5839
+ this._errors$.next(new MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
5873
5840
  throw error;
5874
5841
  }
5875
5842
  }
@@ -5979,7 +5946,11 @@ function isVertoInviteMessage(value) {
5979
5946
  const msg = value;
5980
5947
  return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
5981
5948
  }
5982
- function isVertoByeMessage(value) {
5949
+ /**
5950
+ * Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
5951
+ * built locally and never type-guarded, so only the inbound shape is asserted.
5952
+ */
5953
+ function isVertoByeInboundMessage(value) {
5983
5954
  if (!isVertoMethodMessage(value)) return false;
5984
5955
  return value.method === "verto.bye";
5985
5956
  }
@@ -5999,6 +5970,14 @@ function isVertoMediaParamsInnerParams(value) {
5999
5970
  function isVertoPingInnerParams(value) {
6000
5971
  return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
6001
5972
  }
5973
+ /**
5974
+ * Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
5975
+ * this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
5976
+ * it against a boolean answer/reject signal.
5977
+ */
5978
+ function isVertoByeInboundParamsGuard(value) {
5979
+ return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
5980
+ }
6002
5981
 
6003
5982
  //#endregion
6004
5983
  //#region src/managers/VertoManager.ts
@@ -6336,7 +6315,7 @@ var WebRTCVertoManager = class extends VertoManager {
6336
6315
  return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaParamsInnerParams, "params"), takeUntil(this.destroyed$)));
6337
6316
  }
6338
6317
  get vertoBye$() {
6339
- return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeMessage, "params"), takeUntil(this.destroyed$)));
6318
+ return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeInboundMessage, "params"), takeUntil(this.destroyed$)));
6340
6319
  }
6341
6320
  get vertoAttach$() {
6342
6321
  return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoAttachMessage, "params"), takeUntil(this.destroyed$)));
@@ -6465,6 +6444,7 @@ var WebRTCVertoManager = class extends VertoManager {
6465
6444
  inputVideoStream: options.inputVideoStream,
6466
6445
  receiveAudio: options.receiveAudio,
6467
6446
  receiveVideo: options.receiveVideo,
6447
+ fallbackToReceiveOnly: options.fallbackToReceiveOnly,
6468
6448
  webRTCApiProvider: this.webRTCApiProvider,
6469
6449
  preferredVideoCodecs: options.preferredVideoCodecs,
6470
6450
  preferredAudioCodecs: options.preferredAudioCodecs,
@@ -6489,7 +6469,7 @@ var WebRTCVertoManager = class extends VertoManager {
6489
6469
  logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
6490
6470
  return;
6491
6471
  }
6492
- if (isVertoByeMessage(vertoByeOrAccepted)) {
6472
+ if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
6493
6473
  logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
6494
6474
  this.callSession?.destroy();
6495
6475
  } else if (!vertoByeOrAccepted) {
@@ -6591,7 +6571,7 @@ var WebRTCVertoManager = class extends VertoManager {
6591
6571
  logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
6592
6572
  return;
6593
6573
  }
6594
- if (isVertoByeMessage(vertoByeOrAccepted)) {
6574
+ if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
6595
6575
  logger$16.info("[WebRTCManager] Call ended before answer was sent.");
6596
6576
  this.callSession?.destroy();
6597
6577
  } else if (!vertoByeOrAccepted) {
@@ -6691,9 +6671,11 @@ var WebRTCVertoManager = class extends VertoManager {
6691
6671
  await this.initAdditionalPeerConnection("screenshare", options);
6692
6672
  }
6693
6673
  async initAdditionalPeerConnection(propose, options) {
6674
+ const isScreenShare = propose === "screenshare";
6675
+ let firstPeerConnectionError;
6694
6676
  let rtcPeerConnController = null;
6695
6677
  try {
6696
- this._screenShareStatus$.next("starting");
6678
+ if (isScreenShare) this._screenShareStatus$.next("starting");
6697
6679
  rtcPeerConnController = new RTCPeerConnectionController({
6698
6680
  ...options,
6699
6681
  ...this.RTCPeerConnectionConfig,
@@ -6701,21 +6683,27 @@ var WebRTCVertoManager = class extends VertoManager {
6701
6683
  webRTCApiProvider: this.webRTCApiProvider
6702
6684
  }, void 0, this.deviceController);
6703
6685
  this.setupLocalDescriptionHandler(rtcPeerConnController);
6704
- if (propose === "screenshare") this._screenShareId = rtcPeerConnController.id;
6686
+ if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
6705
6687
  this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
6706
6688
  this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
6707
6689
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
6708
- this.onError?.(error);
6690
+ firstPeerConnectionError ??= error;
6691
+ this.onError?.(error, { fatal: false });
6709
6692
  });
6710
6693
  await firstValueFrom(rtcPeerConnController.connectionState$.pipe(filter((state) => state === "connected"), take(1), timeout(this._screenShareTimeoutMs), takeUntil(this.destroyed$)));
6711
- this._screenShareStatus$.next("started");
6712
- logger$16.info("[WebRTCManager] Screen share started successfully.");
6694
+ if (isScreenShare) this._screenShareStatus$.next("started");
6695
+ logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
6713
6696
  return rtcPeerConnController.id;
6714
6697
  } catch (error) {
6715
6698
  logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
6716
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
6717
6699
  if (rtcPeerConnController) rtcPeerConnController.destroy();
6718
- this._screenShareStatus$.next("none");
6700
+ if (isScreenShare) this._screenShareStatus$.next("none");
6701
+ if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
6702
+ if (error instanceof EmptyError) {
6703
+ logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
6704
+ return;
6705
+ }
6706
+ throw error instanceof Error ? error : new Error(String(error), { cause: error });
6719
6707
  }
6720
6708
  }
6721
6709
  async removeInputDevices(id) {
@@ -7646,6 +7634,12 @@ function mosToQualityLevel(mos) {
7646
7634
  //#region src/core/entities/Call.ts
7647
7635
  const logger$12 = getLogger();
7648
7636
  /**
7637
+ * Verto method for setting member layout positions. Its gateway DTO requires a
7638
+ * `targets` array whose entries are `{ target, position }` (NOT bare targets),
7639
+ * so {@link WebRTCCall.buildMethodParams} special-cases it. See issue #19400.
7640
+ */
7641
+ const POSITION_SET_METHOD = "call.member.position.set";
7642
+ /**
7649
7643
  * Ratio between the critical and warning RTT spike multipliers.
7650
7644
  * Warning threshold = baseline * warningMultiplier (default 3x)
7651
7645
  * Critical threshold = baseline * warningMultiplier * RTT_CRITICAL_TO_WARNING_RATIO
@@ -7852,7 +7846,7 @@ var WebRTCCall = class extends Destroyable {
7852
7846
  * @throws {JSONRPCError} If the RPC call returns an error.
7853
7847
  */
7854
7848
  async executeMethod(target, method, args) {
7855
- const params = this.buildMethodParams(target, args);
7849
+ const params = this.buildMethodParams(target, args, method);
7856
7850
  const request = buildRPCRequest({
7857
7851
  method,
7858
7852
  params
@@ -7866,12 +7860,16 @@ var WebRTCCall = class extends Destroyable {
7866
7860
  throw error;
7867
7861
  }
7868
7862
  }
7869
- buildMethodParams(target, args) {
7863
+ buildMethodParams(target, args, method) {
7870
7864
  const self = {
7871
7865
  node_id: this.nodeId ?? "",
7872
7866
  call_id: this.id,
7873
7867
  member_id: this.vertoManager.selfId ?? ""
7874
7868
  };
7869
+ if (method === POSITION_SET_METHOD) return {
7870
+ ...args,
7871
+ self
7872
+ };
7875
7873
  if (typeof target === "object") return {
7876
7874
  ...args,
7877
7875
  self,
@@ -8437,10 +8435,21 @@ var WebRTCCall = class extends Destroyable {
8437
8435
  return this.deferEmission(this._answered$.asObservable());
8438
8436
  }
8439
8437
  /**
8440
- * Sets the call layout and participant positions.
8438
+ * Sets the call layout and, optionally, individual participant positions.
8439
+ *
8440
+ * The gateway `call.layout.set` DTO has **no** `positions` member, so when
8441
+ * `positions` is provided this method issues a `call.member.position.set`
8442
+ * request per member (via {@link Participant.setPosition}, which keys each
8443
+ * position by that member's own call context) alongside `call.layout.set`
8444
+ * (issue #19400, Flag #6).
8445
+ *
8446
+ * **These operations are NOT atomic.** The layout is applied first, then each
8447
+ * member position sequentially, so members may briefly flash into their
8448
+ * default slots before being moved to the requested positions.
8441
8449
  *
8442
8450
  * @param layout - Layout name (must be one of {@link layouts}).
8443
- * @param positions - Map of member IDs to {@link VideoPosition} values.
8451
+ * @param positions - Optional map of member IDs to {@link VideoPosition} values.
8452
+ * When omitted or empty, only the layout is changed.
8444
8453
  * @throws {InvalidParams} If the layout is not in the available {@link layouts}.
8445
8454
  *
8446
8455
  * @example
@@ -8453,10 +8462,17 @@ var WebRTCCall = class extends Destroyable {
8453
8462
  async setLayout(layout, positions) {
8454
8463
  if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
8455
8464
  const selfId = await firstValueFrom(this.selfId$.pipe(filter((id) => id !== null)));
8456
- await this.executeMethod(selfId, "call.layout.set", {
8457
- layout,
8458
- positions
8459
- });
8465
+ await this.executeMethod(selfId, "call.layout.set", { layout });
8466
+ const positionEntries = Object.entries(positions ?? {});
8467
+ if (positionEntries.length === 0) return;
8468
+ for (const [memberId, position] of positionEntries) {
8469
+ const participant = this.participants.find((p) => p.id === memberId);
8470
+ if (!participant) {
8471
+ logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
8472
+ continue;
8473
+ }
8474
+ await participant.setPosition(position);
8475
+ }
8460
8476
  }
8461
8477
  /**
8462
8478
  * Transfers the call to another destination.
@@ -8630,6 +8646,7 @@ function inferCallErrorKind(error) {
8630
8646
  if (error instanceof RPCTimeoutError) return "timeout";
8631
8647
  if (error instanceof JSONRPCError) return "signaling";
8632
8648
  if (error instanceof MediaTrackError) return "media";
8649
+ if (error instanceof MediaAccessError) return "media";
8633
8650
  if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
8634
8651
  return "internal";
8635
8652
  }
@@ -8646,6 +8663,7 @@ const RECOVERABLE_RPC_CODES = new Set([
8646
8663
  function isFatalError(error) {
8647
8664
  if (error instanceof VertoPongError) return false;
8648
8665
  if (error instanceof MediaTrackError) return false;
8666
+ if (error instanceof MediaAccessError) return error.fatal;
8649
8667
  if (error instanceof RPCTimeoutError) return false;
8650
8668
  if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
8651
8669
  return true;
@@ -8671,10 +8689,10 @@ var CallFactory = class {
8671
8689
  return {
8672
8690
  vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
8673
8691
  nodeId: options.nodeId,
8674
- onError: (error) => {
8692
+ onError: (error, options$1) => {
8675
8693
  const callError = {
8676
8694
  kind: inferCallErrorKind(error),
8677
- fatal: isFatalError(error),
8695
+ fatal: options$1?.fatal ?? isFatalError(error),
8678
8696
  error,
8679
8697
  callId: callInstance.id
8680
8698
  };
@@ -9133,6 +9151,17 @@ var PendingRPC = class PendingRPC {
9133
9151
  //#endregion
9134
9152
  //#region src/managers/ClientSessionManager.ts
9135
9153
  const logger$9 = getLogger();
9154
+ /**
9155
+ * Decide whether an error emitted on `call.errors$` during dial should
9156
+ * abort the dial. A non-fatal MediaAccessError means the call degraded to
9157
+ * receive-only and still connects — everything else rejects `dial()` with
9158
+ * the real cause.
9159
+ *
9160
+ * Pure function — exported for unit testing.
9161
+ */
9162
+ function shouldAbortDial(callError) {
9163
+ return callError.fatal || !(callError.error instanceof MediaAccessError);
9164
+ }
9136
9165
  const getAddressSearchURI = (options) => {
9137
9166
  const to = options.to?.split("?")[0];
9138
9167
  const from$1 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
@@ -9371,11 +9400,41 @@ var ClientSessionManager = class extends Destroyable {
9371
9400
  }
9372
9401
  } else this._errors$.next(error);
9373
9402
  }
9403
+ /**
9404
+ * Clear the resume state (authorization_state + protocol) only.
9405
+ *
9406
+ * This is the stale-auth-state recovery helper used by handleAuthError:
9407
+ * the server rejected a reconnect, so the resume state is discarded and a
9408
+ * fresh connect follows. Attach records are deliberately preserved — the
9409
+ * session lives on through the reconnect and reattachCalls() needs the
9410
+ * stored call references afterwards. Do NOT add detachAll() here.
9411
+ *
9412
+ * For public teardown (disconnect/destroy), use {@link teardownSessionState}
9413
+ * instead, which clears the attach records as well.
9414
+ */
9374
9415
  async cleanupStoredConnectionParams() {
9375
9416
  await this.transport.setProtocol(void 0);
9376
9417
  await this.updateAuthorizationStateInStorage(void 0);
9377
9418
  this._authorization$.next(void 0);
9378
9419
  }
9420
+ /**
9421
+ * Public-teardown helper for disconnect()/destroy(). Clears the resume
9422
+ * state (authorization_state + protocol) AND the attach records as one
9423
+ * atomic unit.
9424
+ *
9425
+ * The two stores are coupled: the backend only honors attach records
9426
+ * within the session identified by the resume state, so ending the
9427
+ * session must clear both. Clearing one without the other strands records
9428
+ * no future session can honor (disconnect) or revives a session the
9429
+ * developer explicitly ended (destroy).
9430
+ *
9431
+ * Distinct from {@link cleanupStoredConnectionParams}, which keeps the
9432
+ * attach records for the stale-auth-state recovery path.
9433
+ */
9434
+ async teardownSessionState() {
9435
+ await this.cleanupStoredConnectionParams();
9436
+ await this.attachManager.detachAll();
9437
+ }
9379
9438
  async updateAuthState(authorization_state) {
9380
9439
  try {
9381
9440
  await this.storage.setItem(this.authorizationStateKey, authorization_state);
@@ -9470,7 +9529,7 @@ var ClientSessionManager = class extends Destroyable {
9470
9529
  async disconnect() {
9471
9530
  this.transport.disconnect();
9472
9531
  this._authState$.next({ kind: "unauthenticated" });
9473
- await this.cleanupStoredConnectionParams();
9532
+ await this.teardownSessionState();
9474
9533
  }
9475
9534
  async createInboundCall(invite) {
9476
9535
  const callSession = await this.createCall({
@@ -9529,7 +9588,7 @@ var ClientSessionManager = class extends Destroyable {
9529
9588
  to: destinationURI,
9530
9589
  ...options
9531
9590
  });
9532
- await firstValueFrom(race(callSession.selfId$.pipe(filter((id) => Boolean(id)), take(1), timeout(this.callCreateTimeout)), callSession.errors$.pipe(take(1), switchMap((callError) => throwError(() => callError.error)))));
9591
+ await firstValueFrom(race(callSession.selfId$.pipe(filter((id) => Boolean(id)), take(1), timeout(this.callCreateTimeout)), callSession.errors$.pipe(filter(shouldAbortDial), take(1), switchMap((callError) => throwError(() => callError.error)))));
9533
9592
  this._calls$.next({
9534
9593
  [`${callSession.id}`]: callSession,
9535
9594
  ...this._calls$.value
@@ -10632,6 +10691,10 @@ var TransportManager = class extends Destroyable {
10632
10691
  if (!isSignalwireRequest(message)) return true;
10633
10692
  const eventChannel = message.params.event_channel;
10634
10693
  if (!eventChannel) return true;
10694
+ if (message.params.event_type.startsWith("conversation.")) {
10695
+ logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
10696
+ return true;
10697
+ }
10635
10698
  const currentProtocol = this._currentProtocol;
10636
10699
  if (!currentProtocol) return true;
10637
10700
  if (!eventChannel.includes(currentProtocol)) {
@@ -11231,7 +11294,7 @@ var SignalWire = class extends Destroyable {
11231
11294
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
11232
11295
  }
11233
11296
  try {
11234
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.0" });
11297
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.2" });
11235
11298
  } catch (error) {
11236
11299
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
11237
11300
  }
@@ -11239,6 +11302,14 @@ var SignalWire = class extends Destroyable {
11239
11302
  /**
11240
11303
  * Disconnects the WebSocket and tears down the current session.
11241
11304
  *
11305
+ * Ends the session identified by the protocol and clears its persisted
11306
+ * resume state (`authorization_state` + protocol) and attach records
11307
+ * together — a later {@link connect} with the same credentials starts a
11308
+ * fresh session and cannot reattach to the ended session's calls.
11309
+ * Credentials and device preferences are preserved. To temporarily stop
11310
+ * receiving inbound calls while keeping the session alive, use
11311
+ * `unregister()` instead.
11312
+ *
11242
11313
  * The client can be reconnected by calling {@link connect} again,
11243
11314
  * which creates a fresh transport and session.
11244
11315
  */
@@ -11630,12 +11701,20 @@ var SignalWire = class extends Destroyable {
11630
11701
  prefs.preferredVideoInput = null;
11631
11702
  await this._deviceController.clearDeviceState();
11632
11703
  }
11633
- /** Destroys the client, clearing timers and releasing all resources. */
11704
+ /**
11705
+ * Destroys the client, clearing timers and releasing all resources.
11706
+ *
11707
+ * Intentionally destroying the client ends its session: the resume state
11708
+ * (`authorization_state` + protocol) and the attach records are both
11709
+ * cleared. Credentials and device preferences are preserved — use
11710
+ * {@link resetToDefaults} for a full wipe. To temporarily stop receiving
11711
+ * inbound calls while keeping the session alive, use `unregister()`.
11712
+ */
11634
11713
  destroy() {
11635
11714
  this._refreshCoordinator?.destroy();
11636
11715
  this._refreshCoordinator = void 0;
11637
11716
  this._dpopManager?.destroy();
11638
- if (this._attachManager) this._attachManager.detachAll();
11717
+ this._clientSession.teardownSessionState();
11639
11718
  this._transport.destroy();
11640
11719
  this._clientSession.destroy();
11641
11720
  try {
@@ -11749,7 +11828,7 @@ var StaticCredentialProvider = class {
11749
11828
  /**
11750
11829
  * Library version from package.json, injected at build time.
11751
11830
  */
11752
- const version = "4.0.0-rc.0";
11831
+ const version = "4.0.0-rc.2";
11753
11832
  /**
11754
11833
  * Flag indicating the library has been loaded and is ready to use.
11755
11834
  * For UMD builds: `window.SignalWire.ready`
@@ -11771,12 +11850,12 @@ const ready = true;
11771
11850
  */
11772
11851
  const emitReadyEvent = () => {
11773
11852
  if (typeof window !== "undefined") {
11774
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.0" } });
11853
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.2" } });
11775
11854
  window.dispatchEvent(event);
11776
11855
  }
11777
11856
  };
11778
11857
  emitReadyEvent();
11779
11858
 
11780
11859
  //#endregion
11781
- 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 };
11860
+ 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 };
11782
11861
  //# sourceMappingURL=index.mjs.map