@signalwire/js 4.0.0-beta.12 → 4.0.0-beta.13

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.d.mts CHANGED
@@ -134,8 +134,25 @@ interface AuthenticateContext {
134
134
  * - Setting `expiry_at` when the credential has a known expiration so the SDK can schedule refresh.
135
135
  * - Handling errors and never leaking sensitive data through error messages.
136
136
  *
137
- * The SDK owns the credential lifecycle: it calls `authenticate` once during initialization
138
- * and, if `refresh` is provided and `expiry_at` is set, schedules automatic refresh before expiry.
137
+ * ## Refresh precedence
138
+ *
139
+ * The SDK selects exactly one refresh mechanism per session, evaluated at connect
140
+ * time (and re-evaluated on reconnect):
141
+ *
142
+ * | `refresh` provided | SAT carries `sat:refresh` scope | Active mechanism |
143
+ * | ------------------ | -------------------------------- | ------------------------------------ |
144
+ * | yes | yes | Client Bound SAT (DPoP, internal) |
145
+ * | yes | no | Developer-provided `refresh()` |
146
+ * | no | yes | Client Bound SAT (DPoP, internal) |
147
+ * | no | no | None — session ends at `expiry_at` |
148
+ *
149
+ * When the SDK falls back to the developer-provided `refresh()` because the SAT
150
+ * lacked `sat:refresh` scope, a `credential_refresh_fallback` event is emitted on
151
+ * `SignalWire.warnings$` so application code can observe the transition.
152
+ *
153
+ * Mint a SAT via `POST /api/fabric/subscribers/tokens` with `fingerprint` and
154
+ * `scope: ["sat:refresh"]` (both currently optional on that endpoint) to enable
155
+ * the Client Bound SAT path; otherwise provide `refresh()` here.
139
156
  */
140
157
  interface CredentialProvider {
141
158
  /**
@@ -146,6 +163,8 @@ interface CredentialProvider {
146
163
  * - Reject (throw) on failure — this will cause client initialization to fail.
147
164
  * - When `context.fingerprint` is provided, forward it to the server-side token
148
165
  * endpoint with `scope: "sat:refresh"` to enable automatic token refresh.
166
+ * Ignoring `context.fingerprint` causes the SDK to fall back to `refresh()`
167
+ * (if provided) or end the session at expiry.
149
168
  *
150
169
  * SDK behavior:
151
170
  * - Awaits this method before establishing the WebSocket connection.
@@ -160,14 +179,12 @@ interface CredentialProvider {
160
179
  * - Reject (throw) if refresh is not possible — the SDK will stop the refresh schedule.
161
180
  *
162
181
  * SDK behavior:
163
- * - Only called when `expiry_at` was set on the previous credential.
182
+ * - Only called when `expiry_at` was set on the previous credential AND the
183
+ * SAT does not carry `sat:refresh` scope (otherwise the SDK refreshes
184
+ * internally via Client Bound SAT). See the precedence table above.
164
185
  * - Scheduled automatically before expiry; implementors do not need to manage timing.
165
186
  * - On rejection, the refresh schedule stops and the session continues with the
166
187
  * current credentials until they expire.
167
- * - When not provided and the SAT includes a `sat:refresh` scope, the SDK
168
- * automatically refreshes via Client Bound SAT (DPoP) without developer intervention.
169
- * - When not provided and no refresh scope is present, the SDK uses the initial
170
- * credentials for the entire session lifetime.
171
188
  */
172
189
  refresh?: () => Promise<SDKCredential>;
173
190
  }
@@ -325,6 +342,12 @@ interface MediaOptions {
325
342
  receiveAudio?: boolean;
326
343
  /** Whether to receive remote video. */
327
344
  receiveVideo?: boolean;
345
+ /**
346
+ * When local media can't be acquired (permission denied or device
347
+ * unavailable), continue the call in receive-only mode instead of failing.
348
+ * Defaults to `true`. Ignored when the call is not set to receive any media.
349
+ */
350
+ fallbackToReceiveOnly?: boolean;
328
351
  }
329
352
  //#endregion
330
353
  //#region src/containers/PreferencesContainer.d.ts
@@ -795,7 +818,7 @@ interface Member {
795
818
  member_id: string;
796
819
  call_id: string;
797
820
  name: string;
798
- type: 'member' | 'screen';
821
+ type: 'member' | 'screen' | 'device' | (string & {});
799
822
  parent_id?: string;
800
823
  requested_position?: string;
801
824
  handraised: boolean;
@@ -803,17 +826,17 @@ interface Member {
803
826
  audio_muted: boolean;
804
827
  video_muted: boolean;
805
828
  deaf: boolean;
806
- input_volume: number;
807
- output_volume: number;
808
- input_sensitivity: number;
829
+ input_volume?: number;
830
+ output_volume?: number;
831
+ input_sensitivity?: number;
809
832
  echo_cancellation: boolean;
810
833
  auto_gain: boolean;
811
834
  noise_suppression: boolean;
812
835
  lowbitrate: boolean;
813
836
  denoise: boolean;
814
- talking: boolean;
815
- isAudience: boolean;
816
- meta: Record<string, unknown>;
837
+ talking?: boolean;
838
+ isAudience?: boolean;
839
+ meta?: Record<string, unknown>;
817
840
  subscriber_id: string;
818
841
  address_id: string;
819
842
  updated?: string[];
@@ -935,6 +958,15 @@ interface RoomUpdatedPayload {
935
958
  room_id: string;
936
959
  room_session_id: string;
937
960
  }
961
+ /**
962
+ * Describes the peer (remote) call referenced from a call.state event.
963
+ *
964
+ * Mirrors the backend (relay.c) `peer` shape.
965
+ */
966
+ interface CallStateRelatedCall {
967
+ call_id?: string;
968
+ node_id?: string;
969
+ }
938
970
  interface CallStatePayload {
939
971
  call_id: string;
940
972
  node_id: string;
@@ -942,10 +974,19 @@ interface CallStatePayload {
942
974
  call_state: SignalingCallStates;
943
975
  direction: CallDirection;
944
976
  device: CallDevice;
945
- start_time: number;
946
- answer_time: number;
947
- end_time: number;
977
+ /**
978
+ * Epoch timestamps for the call lifecycle. Optional because pre-answer
979
+ * states (e.g. `created`, `ringing`) do not have an `answer_time`/`end_time`
980
+ * and the backend reports them as `0` or omits them.
981
+ */
982
+ start_time?: number;
983
+ answer_time?: number;
984
+ end_time?: number;
948
985
  room_session_id: string;
986
+ /** The peer (remote) call this call is connected to, if any. */
987
+ peer?: CallStateRelatedCall;
988
+ /** Application-defined tag associated with the call. */
989
+ tag?: string;
949
990
  }
950
991
  interface CallPlayPayload {
951
992
  control_id: string;
@@ -1132,6 +1173,14 @@ declare class CallCreateError extends Error {
1132
1173
  direction: 'inbound' | 'outbound';
1133
1174
  constructor(message: string, error?: unknown, direction?: 'inbound' | 'outbound', options?: ErrorOptions);
1134
1175
  }
1176
+ declare class CallNotReadyError extends Error {
1177
+ callId: string;
1178
+ constructor(callId: string, options?: ErrorOptions);
1179
+ }
1180
+ declare class ParticipantNotReadyError extends Error {
1181
+ memberId: string;
1182
+ constructor(memberId: string, options?: ErrorOptions);
1183
+ }
1135
1184
  declare class VertoPongError extends Error {
1136
1185
  originalError: unknown;
1137
1186
  constructor(originalError: unknown);
@@ -1151,6 +1200,32 @@ declare class MediaTrackError extends Error {
1151
1200
  originalError: unknown;
1152
1201
  constructor(operation: string, kind: string, originalError: unknown);
1153
1202
  }
1203
+ /**
1204
+ * Failure to acquire local media (camera, microphone, or screen capture)
1205
+ * via `getUserMedia`/`getDisplayMedia`.
1206
+ *
1207
+ * Non-fatal by default: screenshare and additional-device failures never
1208
+ * end the call, and main-connection failures degrade to receive-only when
1209
+ * possible. The wrapping site sets `fatal` to `true` only when the call
1210
+ * cannot continue (receive-only fallback disabled or no receive intent).
1211
+ */
1212
+ declare class MediaAccessError extends Error {
1213
+ /** The SDK operation that failed, e.g. `'acquireLocalMedia'`, `'startScreenShare'`, `'addInputDevice'`. */
1214
+ operation: string;
1215
+ /** The media being acquired: `'audio' | 'video' | 'audiovideo' | 'screen'`. */
1216
+ media: string;
1217
+ /** The raw `getUserMedia`/`getDisplayMedia` error (typically a `DOMException`). */
1218
+ originalError: unknown;
1219
+ /** Whether this failure terminates the call. */
1220
+ readonly fatal: boolean;
1221
+ constructor(/** The SDK operation that failed, e.g. `'acquireLocalMedia'`, `'startScreenShare'`, `'addInputDevice'`. */
1222
+ operation: string, /** The media being acquired: `'audio' | 'video' | 'audiovideo' | 'screen'`. */
1223
+ media: string, /** The raw `getUserMedia`/`getDisplayMedia` error (typically a `DOMException`). */
1224
+ originalError: unknown, /** Whether this failure terminates the call. */
1225
+ fatal?: boolean);
1226
+ /** True when the underlying failure is a permission denial (user or policy). */
1227
+ get denied(): boolean;
1228
+ }
1154
1229
  declare class DPoPInitError extends Error {
1155
1230
  originalError: unknown;
1156
1231
  constructor(originalError: unknown, message?: string);
@@ -1491,6 +1566,8 @@ interface MemberCapabilities {
1491
1566
  readonly meta: boolean;
1492
1567
  readonly remove: boolean;
1493
1568
  readonly audioFlags: boolean;
1569
+ readonly denoise: boolean;
1570
+ readonly lowbitrate: boolean;
1494
1571
  }
1495
1572
  /**
1496
1573
  * Call-level capabilities state
@@ -1720,12 +1797,12 @@ type ParticipantState = Member & {
1720
1797
  * the local participant with additional device control.
1721
1798
  */
1722
1799
  declare class Participant extends Destroyable implements CallParticipant {
1723
- protected executeMethod: ExecuteMethod;
1800
+ private callExecuteMethod;
1724
1801
  protected deviceController: DeviceController;
1725
1802
  /** Unique member ID of this participant. */
1726
1803
  readonly id: string;
1727
1804
  private _state$;
1728
- constructor(id: string, executeMethod: ExecuteMethod, deviceController: DeviceController);
1805
+ constructor(id: string, callExecuteMethod: ExecuteMethod, deviceController: DeviceController);
1729
1806
  /** @internal */
1730
1807
  upnext(data: Partial<ParticipantState>): void;
1731
1808
  /** Observable of the participant's display name. */
@@ -1844,8 +1921,34 @@ declare class Participant extends Destroyable implements CallParticipant {
1844
1921
  get addressId(): string | undefined;
1845
1922
  /** Server node ID for this participant, or `undefined` if not available. */
1846
1923
  get nodeId(): string | undefined;
1924
+ /** Call ID for this participant's leg, or `undefined` if not available. */
1925
+ get callId(): string | undefined;
1847
1926
  /** @internal */
1848
1927
  get value(): Partial<Member>;
1928
+ /**
1929
+ * Target triple for member RPCs, built from the participant's own state.
1930
+ * The backend locates the member's session by the target `call_id`/`node_id`,
1931
+ * so this must always be the participant's own call context — never the
1932
+ * local call's id (issue #19400).
1933
+ *
1934
+ * Reading it doubles as a readiness probe: it throws until the first full
1935
+ * member event (`member.joined`/`member.updated` or the `call.joined`
1936
+ * roster) arrives, and never regresses afterwards.
1937
+ *
1938
+ * @throws {ParticipantNotReadyError} If the member state has not been
1939
+ * received yet (e.g. a participant first seen via `member.talking`) — an
1940
+ * empty call context can never address the member, so fail fast instead of
1941
+ * sending a doomed RPC.
1942
+ */
1943
+ get target(): MemberTarget;
1944
+ /**
1945
+ * Executes a member RPC against this participant, injecting its own
1946
+ * {@link target} as the target.
1947
+ *
1948
+ * @throws {ParticipantNotReadyError} Via {@link target}, when the
1949
+ * member state has not been received yet.
1950
+ */
1951
+ protected executeMethod(method: string, args: Record<string, unknown>): Promise<JSONRPCResponse>;
1849
1952
  /** Toggles the deafened state (mutes/unmutes incoming audio). */
1850
1953
  toggleDeaf(): Promise<void>;
1851
1954
  /** Toggles the hand-raised state. */
@@ -1868,6 +1971,7 @@ declare class Participant extends Destroyable implements CallParticipant {
1868
1971
  toggleAudioInputAutoGain(): Promise<void>;
1869
1972
  /** Toggles noise suppression on the audio input. */
1870
1973
  toggleNoiseSuppression(): Promise<void>;
1974
+ /** Toggles low-bitrate mode for this participant's media. */
1871
1975
  toggleLowbitrate(): Promise<void>;
1872
1976
  /**
1873
1977
  * Adjusts the **conference-only** microphone energy gate / sensitivity level
@@ -1908,6 +2012,12 @@ declare class Participant extends Destroyable implements CallParticipant {
1908
2012
  setAudioOutputVolume(value: number): Promise<void>;
1909
2013
  /**
1910
2014
  * Sets the participant's position in the video layout.
2015
+ *
2016
+ * Requires the `member.position` capability. The gateway requires a
2017
+ * `targets` array of `{ target, position }` entries (issue #19400). A
2018
+ * resolved promise does not guarantee a visible change: the backend silently
2019
+ * returns `200` (no-op) for non-conference targets.
2020
+ *
1911
2021
  * @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
1912
2022
  */
1913
2023
  setPosition(value: VideoPosition): Promise<void>;
@@ -1950,7 +2060,7 @@ declare class SelfParticipant extends Participant implements CallSelfParticipant
1950
2060
  */
1951
2061
  private _studioAudio$;
1952
2062
  /** @internal */
1953
- constructor(id: string, executeMethod: ExecuteMethod, vertoManager: VertoManager, deviceController: DeviceController);
2063
+ constructor(id: string, callExecuteMethod: ExecuteMethod, vertoManager: VertoManager, deviceController: DeviceController);
1954
2064
  destroy(): void;
1955
2065
  /** Observable indicating whether studio audio (raw/unprocessed audio) mode is enabled. */
1956
2066
  get studioAudio$(): Observable<boolean>;
@@ -1966,7 +2076,15 @@ declare class SelfParticipant extends Participant implements CallSelfParticipant
1966
2076
  * Sets echoCancellation, noiseSuppression, and autoGainControl to true.
1967
2077
  */
1968
2078
  disableStudioAudio(): Promise<void>;
1969
- /** Starts sharing the local screen. */
2079
+ /**
2080
+ * Starts sharing the local screen.
2081
+ *
2082
+ * The call is unaffected when acquisition fails.
2083
+ *
2084
+ * @throws The raw `getDisplayMedia` error. A dismissed picker or a
2085
+ * permission denial rejects with a `NotAllowedError` `DOMException` —
2086
+ * inspect `error.name` to tell benign cancels apart from real failures.
2087
+ */
1970
2088
  startScreenShare(): Promise<void>;
1971
2089
  /** Observable of the current screen share status. */
1972
2090
  get screenShareStatus$(): Observable<ScreenShareStatus>;
@@ -1974,7 +2092,14 @@ declare class SelfParticipant extends Participant implements CallSelfParticipant
1974
2092
  get screenShareStatus(): ScreenShareStatus;
1975
2093
  /** Stops the current screen share. */
1976
2094
  stopScreenShare(): Promise<void>;
1977
- /** Adds an additional media input device to the call. */
2095
+ /**
2096
+ * Adds an additional media input device to the call.
2097
+ *
2098
+ * The call is unaffected when acquisition fails.
2099
+ *
2100
+ * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
2101
+ * permission denial) — inspect `error.name` to decide how to react.
2102
+ */
1978
2103
  addAdditionalDevice(options: MediaOptions): Promise<void>;
1979
2104
  /** Removes an additional media input device by ID. */
1980
2105
  removeAdditionalDevice(id: string): Promise<void>;
@@ -2081,6 +2206,10 @@ interface CallParticipant {
2081
2206
  readonly userId: string | undefined;
2082
2207
  readonly addressId: string | undefined;
2083
2208
  readonly nodeId: string | undefined;
2209
+ readonly callId: string | undefined;
2210
+ /** The member's own RPC target triple. Throws `ParticipantNotReadyError`
2211
+ * until the member's call context has been received. */
2212
+ readonly target: MemberTarget;
2084
2213
  readonly isTalking: boolean;
2085
2214
  readonly position: LayoutLayer | undefined;
2086
2215
  readonly isAudience: boolean;
@@ -3095,7 +3224,34 @@ declare class ClientSessionManager extends Destroyable implements SessionState {
3095
3224
  private get authentication();
3096
3225
  connect(): Promise<void>;
3097
3226
  private handleAuthenticationError;
3227
+ /**
3228
+ * Clear the resume state (authorization_state + protocol) only.
3229
+ *
3230
+ * This is the stale-auth-state recovery helper used by handleAuthError:
3231
+ * the server rejected a reconnect, so the resume state is discarded and a
3232
+ * fresh connect follows. Attach records are deliberately preserved — the
3233
+ * session lives on through the reconnect and reattachCalls() needs the
3234
+ * stored call references afterwards. Do NOT add detachAll() here.
3235
+ *
3236
+ * For public teardown (disconnect/destroy), use {@link teardownSessionState}
3237
+ * instead, which clears the attach records as well.
3238
+ */
3098
3239
  cleanupStoredConnectionParams(): Promise<void>;
3240
+ /**
3241
+ * Public-teardown helper for disconnect()/destroy(). Clears the resume
3242
+ * state (authorization_state + protocol) AND the attach records as one
3243
+ * atomic unit.
3244
+ *
3245
+ * The two stores are coupled: the backend only honors attach records
3246
+ * within the session identified by the resume state, so ending the
3247
+ * session must clear both. Clearing one without the other strands records
3248
+ * no future session can honor (disconnect) or revives a session the
3249
+ * developer explicitly ended (destroy).
3250
+ *
3251
+ * Distinct from {@link cleanupStoredConnectionParams}, which keeps the
3252
+ * attach records for the stale-auth-state recovery path.
3253
+ */
3254
+ teardownSessionState(): Promise<void>;
3099
3255
  protected updateAuthState(authorization_state: string): Promise<void>;
3100
3256
  reauthenticate(token: string, dpopToken?: string, options?: {
3101
3257
  clientBound?: boolean;
@@ -3122,6 +3278,12 @@ declare class ClientSessionWrapper implements SessionState {
3122
3278
  constructor(clientSessionManager: ClientSessionManager);
3123
3279
  get authenticated$(): Observable<boolean>;
3124
3280
  get authenticated(): boolean;
3281
+ /**
3282
+ * Whether the session is using a Client Bound SAT (DPoP). Sticky — set
3283
+ * when the binding is established or restored from a resumed session's
3284
+ * server authorization.
3285
+ */
3286
+ get clientBound(): boolean;
3125
3287
  get signalingEvent$(): Observable<(Omit<{
3126
3288
  event_type: "webrtc.message";
3127
3289
  event_channel: EventChannel;
@@ -3280,6 +3442,60 @@ declare class ClientSessionWrapper implements SessionState {
3280
3442
  get calls(): Call[];
3281
3443
  }
3282
3444
  //#endregion
3445
+ //#region src/core/types/warnings.types.d.ts
3446
+ /**
3447
+ * Non-fatal warning emitted via {@link SignalWire.warnings$ | client.warnings$}.
3448
+ *
3449
+ * Use to detect SDK behaviors that affect session liveness or developer-facing
3450
+ * contracts but do not warrant disconnection. Discriminated by `code`.
3451
+ *
3452
+ * Existing consumers of `errors$` are NOT notified — `warnings$` is a separate
3453
+ * channel so application code can react to warnings without triggering
3454
+ * error-handling code paths (e.g., disconnect cascades, user-facing toasts).
3455
+ */
3456
+ type SDKWarning = CredentialRefreshFallbackWarning | CredentialNoRefreshHandlerWarning;
3457
+ /**
3458
+ * Diagnostic detail for {@link CredentialRefreshFallbackWarning}. Stable
3459
+ * values, but treat unknown strings as "fell back for an unspecified cause" —
3460
+ * do not branch on this value for control flow. New values may be added in
3461
+ * future releases.
3462
+ */
3463
+ type CredentialRefreshFallbackReason = 'no-scope' | 'no-dpop-support' | 'endpoint-failed' | 'activation-timeout' | (string & {});
3464
+ /**
3465
+ * Emitted when the SDK falls back to the developer-provided
3466
+ * {@link CredentialProvider.refresh} because the Client Bound SAT path
3467
+ * could not take over.
3468
+ *
3469
+ * Common causes:
3470
+ * - The minted SAT lacks `sat:refresh` scope (`reason: 'no-scope'`).
3471
+ * - The `/devices/token` exchange failed transiently (`reason: 'endpoint-failed'`).
3472
+ *
3473
+ * Subscribe to this warning to detect:
3474
+ * - SDKs running with plain SATs that rely on developer-managed refresh
3475
+ * - Deployments expected to use bound tokens that silently downgraded to bearer
3476
+ * (a security-relevant signal for fleet observability)
3477
+ */
3478
+ interface CredentialRefreshFallbackWarning {
3479
+ code: 'credential_refresh_fallback';
3480
+ source: 'CredentialProvider';
3481
+ reason: CredentialRefreshFallbackReason;
3482
+ message: string;
3483
+ }
3484
+ /**
3485
+ * Emitted when a credential has an `expiry_at` but the provider supplies no
3486
+ * `refresh()` handler. The session will terminate at expiry with no fallback.
3487
+ *
3488
+ * Implementors who want long-lived sessions must provide a `refresh()` handler
3489
+ * or mint tokens with the `sat:refresh` scope (Client Bound SAT path).
3490
+ */
3491
+ interface CredentialNoRefreshHandlerWarning {
3492
+ code: 'credential_no_refresh_handler';
3493
+ source: 'CredentialProvider';
3494
+ message: string;
3495
+ /** Token expiry timestamp (epoch milliseconds). */
3496
+ expiresAt: number;
3497
+ }
3498
+ //#endregion
3283
3499
  //#region src/utils/logger.d.ts
3284
3500
  /** Log level names supported by the SDK. */
3285
3501
  type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent';
@@ -3353,8 +3569,12 @@ interface SignalWireOptions {
3353
3569
  * When `false` (default), session data lives in `sessionStorage` and is
3354
3570
  * lost on reload.
3355
3571
  *
3356
- * Call {@link SignalWire.destroy | destroy()} to clear all persisted state
3357
- * (explicit logout).
3572
+ * Both {@link SignalWire.disconnect | disconnect()} and
3573
+ * {@link SignalWire.destroy | destroy()} end the session and clear the
3574
+ * persisted resume state and attach records; credentials and device
3575
+ * preferences survive. Use `resetToDefaults()` for a full wipe, or
3576
+ * `unregister()` to temporarily stop receiving inbound calls while keeping
3577
+ * the session alive.
3358
3578
  */
3359
3579
  persistSession?: boolean;
3360
3580
  /** Custom storage implementation for persistence. */
@@ -3423,10 +3643,10 @@ declare class SignalWire extends Destroyable implements DeviceController {
3423
3643
  private _isConnected$;
3424
3644
  private _isRegistered$;
3425
3645
  private _errors$;
3646
+ private _warnings$;
3426
3647
  private _options;
3427
- private _refreshTimerId?;
3428
3648
  private _dpopManager?;
3429
- private _deviceTokenManager?;
3649
+ private _refreshCoordinator?;
3430
3650
  private _credentialProvider?;
3431
3651
  private _deps;
3432
3652
  private _networkMonitor?;
@@ -3455,11 +3675,58 @@ declare class SignalWire extends Destroyable implements DeviceController {
3455
3675
  private resolveCredentials;
3456
3676
  private validateCredentials;
3457
3677
  /**
3458
- * Schedules credential refresh with exponential backoff retry on failure.
3459
- * On success, resets attempt counter and schedules the next refresh.
3460
- * After exhausting retries, emits TokenRefreshError and disconnects.
3678
+ * Reauthenticate the currently-open session with a freshly obtained
3679
+ * credential so the new token takes effect on the live socket immediately —
3680
+ * not just on the next reconnect. No-op when the session is not
3681
+ * connected/authenticated or the credential carries no token (e.g. an
3682
+ * authorization-state-only refresh). Non-fatal: reauth failures surface on
3683
+ * `errors$` without aborting the refresh that triggered this.
3684
+ */
3685
+ private reauthenticateLiveSession;
3686
+ /**
3687
+ * One-shot recovery for a live session that failed with a recoverable auth
3688
+ * error (`-32002`/`-32003`) because its token went stale — e.g. a refresh
3689
+ * timer that was throttled while the tab was backgrounded.
3690
+ *
3691
+ * Two steps, first that succeeds wins:
3692
+ * 1. Reauthenticate with the in-memory token (a fresh DPoP proof is
3693
+ * generated automatically). Heals a session whose token was already
3694
+ * refreshed by the coordinator but never applied to the open socket,
3695
+ * and a client-bound session whose server-side auth merely drifted.
3696
+ * 2. Re-mint via the developer provider's `refresh()` and reauthenticate.
3697
+ * Skipped for client-bound sessions (the DeviceTokenManager owns their
3698
+ * refresh; re-minting a base SAT here would drop the DPoP binding).
3699
+ *
3700
+ * @param allowRemint - Whether step 2 (provider re-mint) may run. Callers
3701
+ * pass `false` for non-auth failures so a transient network error never
3702
+ * escalates to a token re-mint; step 1 (in-memory reauth) always runs.
3703
+ * @returns `true` if the session was reauthenticated, `false` otherwise.
3704
+ */
3705
+ private recoverStaleCredential;
3706
+ /**
3707
+ * Re-mint a credential via `provider.refresh()`, routed through the
3708
+ * coordinator's shared in-flight guard so concurrent re-mint paths (a
3709
+ * scheduled/resume refresh, -32003 recovery, and reconnect) never fire a
3710
+ * second `provider.refresh()` in parallel — which rotating one-time-use
3711
+ * refresh tokens reject. Falls back to a direct call only if the coordinator
3712
+ * has not been constructed yet.
3713
+ */
3714
+ private remintCredential;
3715
+ /**
3716
+ * Re-mint credentials before a fresh (re)connect (`onBeforeReconnect` hook).
3717
+ * The session invokes this only when it is client-bound OR the in-memory
3718
+ * token is expired. The re-mint mechanism depends on the binding:
3719
+ * - Client-bound: `authenticate()` with the DPoP fingerprint to obtain a
3720
+ * fresh base SAT the upcoming reconnect can re-bind (the
3721
+ * DeviceTokenManager re-activates afterwards).
3722
+ * - Unbound: the developer's non-interactive `refresh()` handler.
3723
+ * `authenticate()` is deliberately NOT used here — it may be interactive
3724
+ * (a login prompt) and must not fire on a background reconnect.
3725
+ *
3726
+ * Rejects on failure so the session aborts the reconnect rather than
3727
+ * replaying a stale token.
3461
3728
  */
3462
- private scheduleCredentialRefresh;
3729
+ private refreshCredentialForReconnect;
3463
3730
  /** Persist credential to localStorage when persistSession is enabled. */
3464
3731
  private persistCredential;
3465
3732
  private init;
@@ -3546,6 +3813,17 @@ declare class SignalWire extends Destroyable implements DeviceController {
3546
3813
  get ready$(): Observable<boolean>;
3547
3814
  /** Observable stream of errors from transport, authentication, and devices. */
3548
3815
  get errors$(): Observable<Error>;
3816
+ /**
3817
+ * Observable stream of non-fatal SDK warnings.
3818
+ *
3819
+ * Subscribe to detect SDK behaviors that affect session liveness or developer-facing
3820
+ * contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
3821
+ * refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
3822
+ * scope. Discriminated by `code`.
3823
+ *
3824
+ * Independent from {@link errors$}: existing error consumers are not notified.
3825
+ */
3826
+ get warnings$(): Observable<SDKWarning>;
3549
3827
  /** Platform WebRTC capabilities detected at construction time. */
3550
3828
  get platformCapabilities(): PlatformCapabilities;
3551
3829
  /** Observable that emits when the SDK auto-switches a device. */
@@ -3563,6 +3841,14 @@ declare class SignalWire extends Destroyable implements DeviceController {
3563
3841
  /**
3564
3842
  * Disconnects the WebSocket and tears down the current session.
3565
3843
  *
3844
+ * Ends the session identified by the protocol and clears its persisted
3845
+ * resume state (`authorization_state` + protocol) and attach records
3846
+ * together — a later {@link connect} with the same credentials starts a
3847
+ * fresh session and cannot reattach to the ended session's calls.
3848
+ * Credentials and device preferences are preserved. To temporarily stop
3849
+ * receiving inbound calls while keeping the session alive, use
3850
+ * `unregister()` instead.
3851
+ *
3566
3852
  * The client can be reconnected by calling {@link connect} again,
3567
3853
  * which creates a fresh transport and session.
3568
3854
  */
@@ -3744,7 +4030,15 @@ declare class SignalWire extends Destroyable implements DeviceController {
3744
4030
  * attached call IDs, and all SDK storage keys, then re-enumerates devices.
3745
4031
  */
3746
4032
  resetToDefaults(): Promise<void>;
3747
- /** Destroys the client, clearing timers and releasing all resources. */
4033
+ /**
4034
+ * Destroys the client, clearing timers and releasing all resources.
4035
+ *
4036
+ * Intentionally destroying the client ends its session: the resume state
4037
+ * (`authorization_state` + protocol) and the attach records are both
4038
+ * cleared. Credentials and device preferences are preserved — use
4039
+ * {@link resetToDefaults} for a full wipe. To temporarily stop receiving
4040
+ * inbound calls while keeping the session alive, use `unregister()`.
4041
+ */
3748
4042
  destroy(): void;
3749
4043
  }
3750
4044
  //#endregion
@@ -3977,6 +4271,7 @@ declare class RTCPeerConnectionController extends Destroyable {
3977
4271
  stopTrackSender(kind: 'audio' | 'video' | 'both', options?: {
3978
4272
  updateTransceiverDirection: boolean;
3979
4273
  }): void;
4274
+ private stopRawAudioInputForPipeline;
3980
4275
  get isNegotiating$(): Observable<boolean>;
3981
4276
  get isNegotiating(): boolean;
3982
4277
  updateMediaDevicesOptions(options: MediaOptions): void;
@@ -4076,10 +4371,30 @@ declare class RTCPeerConnectionController extends Destroyable {
4076
4371
  */
4077
4372
  private setupTrackHandling;
4078
4373
  private setupLocalTracks;
4374
+ /** True for a main connection with no local media to send. */
4375
+ private hasNoLocalMediaToSend;
4376
+ /** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
4377
+ private get requestedMediaKinds();
4378
+ /**
4379
+ * Handle a local media acquisition failure with a typed, semantically
4380
+ * accurate MediaAccessError created at the acquisition site:
4381
+ * - Auxiliary connections (screenshare / additional-device) throw a
4382
+ * non-fatal error — VertoManager surfaces it and the call is unaffected.
4383
+ * - The main connection degrades to receive-only when allowed (default),
4384
+ * otherwise fails with a fatal error.
4385
+ */
4386
+ private handleLocalMediaFailure;
4387
+ /**
4388
+ * Negotiate receive-only m-lines when there are no local tracks to send.
4389
+ * Only offer-type connections add transceivers — answer-type connections
4390
+ * reuse the transceivers created from the remote offer.
4391
+ */
4392
+ private setupReceiveOnlyTransceivers;
4079
4393
  private getUserMedia;
4080
4394
  private getDisplayMedia;
4081
4395
  private setupRemoteTracks;
4082
4396
  restoreTrackSender(kind: 'audio' | 'video' | 'both'): Promise<void>;
4397
+ private restoreRawAudioInputForPipeline;
4083
4398
  /**
4084
4399
  * Return the lazily-created {@link LocalAudioPipeline}, constructing it on
4085
4400
  * first access. On creation the current audio sender's track is routed
@@ -4379,14 +4694,27 @@ declare class WebRTCCall extends Destroyable implements CallManager {
4379
4694
  *
4380
4695
  * Constructs call context (node_id, call_id, member_id) and sends the RPC request.
4381
4696
  *
4382
- * @param target - Target member ID string, or a {@link MemberTarget} object.
4697
+ * @param target - Target {@link MemberTarget} triple, or the local member's
4698
+ * ID string for self-operations (any other string is rejected — a bare
4699
+ * member id cannot carry the remote member's own call context).
4383
4700
  * @param method - Verto method name (e.g. `'call.mute'`, `'call.member.remove'`).
4384
4701
  * @param args - Parameters for the RPC method.
4385
4702
  * @returns The RPC response.
4703
+ * @throws {CallNotReadyError} If the call has no self member context yet.
4704
+ * @throws {InvalidParams} If a string target is not the local member's ID.
4386
4705
  * @throws {JSONRPCError} If the RPC call returns an error.
4387
4706
  */
4388
4707
  executeMethod<T extends JSONRPCResponse = JSONRPCResponse>(target: string | MemberTarget, method: string, args: Record<string, unknown>): Promise<T>;
4389
- private buildMethodParams;
4708
+ /**
4709
+ * The local leg's member triple — sent as `self` in every member RPC
4710
+ * envelope, and as the `target` of call-scoped self-operations (e.g. lock,
4711
+ * layout).
4712
+ *
4713
+ * @throws {CallNotReadyError} Before `call.joined` delivers the self member
4714
+ * context (`selfId`/`nodeId`) — an RPC without it cannot be routed, so fail
4715
+ * fast instead of sending a doomed request.
4716
+ */
4717
+ private get callSelf();
4390
4718
  /** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
4391
4719
  get status$(): Observable<CallStatus>;
4392
4720
  /** Observable of the participants list, emits on join/leave/update. */
@@ -4620,11 +4948,27 @@ declare class WebRTCCall extends Destroyable implements CallManager {
4620
4948
  /** Observable that emits `true` when answered, `false` when rejected. */
4621
4949
  get answered$(): Observable<boolean>;
4622
4950
  /**
4623
- * Sets the call layout and participant positions.
4951
+ * Sets the call layout and, optionally, individual participant positions.
4952
+ *
4953
+ * The gateway `call.layout.set` DTO has **no** `positions` member, so when
4954
+ * `positions` is provided this method issues a `call.member.position.set`
4955
+ * request per member (via {@link Participant.setPosition}, which keys each
4956
+ * position by that member's own call context) alongside `call.layout.set`
4957
+ * (issue #19400, Flag #6).
4958
+ *
4959
+ * **These operations are NOT atomic.** The layout is applied first, then each
4960
+ * member position sequentially, so members may briefly flash into their
4961
+ * default slots before being moved to the requested positions. Targeted
4962
+ * members are validated upfront, though: when any of them has no
4963
+ * {@link Participant.target | member call context} yet, the whole call
4964
+ * rejects before any request is sent and the layout is left unchanged.
4624
4965
  *
4625
4966
  * @param layout - Layout name (must be one of {@link layouts}).
4626
- * @param positions - Map of member IDs to {@link VideoPosition} values.
4967
+ * @param positions - Optional map of member IDs to {@link VideoPosition} values.
4968
+ * When omitted or empty, only the layout is changed.
4627
4969
  * @throws {InvalidParams} If the layout is not in the available {@link layouts}.
4970
+ * @throws {ParticipantNotReadyError} If a targeted member's call context has
4971
+ * not been received yet — thrown before any request is sent.
4628
4972
  *
4629
4973
  * @example
4630
4974
  * ```ts
@@ -4633,7 +4977,7 @@ declare class WebRTCCall extends Destroyable implements CallManager {
4633
4977
  * });
4634
4978
  * ```
4635
4979
  */
4636
- setLayout(layout: string, positions: Record<string, VideoPosition>): Promise<void>;
4980
+ setLayout(layout: string, positions?: Record<string, VideoPosition>): Promise<void>;
4637
4981
  /**
4638
4982
  * Transfers the call to another destination.
4639
4983
  *
@@ -4737,5 +5081,5 @@ declare const version: string;
4737
5081
  */
4738
5082
  declare const ready: boolean;
4739
5083
  //#endregion
4740
- export { Address, type AddressHistory, type AudioConstraintsEvent, type AuthenticateContext, type Call, type CallAddress, type CallCapabilitiesState, CallCreateError, type CallDiagnosticSummary, type CallDirection, type CallError, type CallErrorKind, type NetworkIssue as CallNetworkIssue, type NetworkIssue, type NetworkMetrics as CallNetworkMetrics, type NetworkMetrics, type CallOptions, type CallParticipant, type CallSelfParticipant, type CallState, type CallStatus, type Capability, ClientPreferences, CollectionFetchError, type ConstraintFallbackEvent, type CredentialProvider, DPoPInitError, type DebugOptions, type DeviceController, type DeviceRecoveryEvent, DeviceTokenError, type DiagnosticEvent, type DialOptions, type Directory, EmbedTokenCredentialProvider, type ExecuteMethod, InvalidCredentialsError, type JSONRPCErrorResponse, type JSONRPCRequest, type JSONRPCResponse, type JSONRPCSuccessResponse, type LayoutLayer, type LogLevel, type MediaDirection, type MediaDirections, type MediaOptions, type MediaParamsEvent, MediaTrackError, type MemberCapabilities, MessageParseError, type NodeSocketAdapter, type OnOffCapability, OverconstrainedFallbackError, Participant, type PendingRPCOptions, type PermissionResult, type PlatformCapabilities, PreflightError, type PreflightOptions, type PreflightResult, type QualityLevel, RecoveryError, type RecoveryEvent, type RecoveryState, type ResilienceCallStatus, type SATClaims, type SDKCredential, type SDKLogger, type ScreenShareStatus, type SelectDeviceOptions, SelfCapabilities, SelfParticipant, type SessionDiagnostics, type SessionState, SignalWire, type SignalWireOptions, StaticCredentialProvider, type Storage, type StoredDevicePreference, type TextMessage, TokenRefreshError, type TransferOptions, UnexpectedError, User, type UserPresence, VertoPongError, type VideoPosition, type WebRTCApiProvider, WebRTCCall, type WebRTCMediaDevices, type WebSocketAdapter, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
5084
+ export { Address, type AddressHistory, type AudioConstraintsEvent, type AuthenticateContext, type Call, type CallAddress, type CallCapabilitiesState, CallCreateError, type CallDiagnosticSummary, type CallDirection, type CallError, type CallErrorKind, type NetworkIssue as CallNetworkIssue, type NetworkIssue, type NetworkMetrics as CallNetworkMetrics, type NetworkMetrics, CallNotReadyError, type CallOptions, type CallParticipant, type CallSelfParticipant, type CallState, type CallStatus, type Capability, ClientPreferences, CollectionFetchError, type ConstraintFallbackEvent, type CredentialNoRefreshHandlerWarning, type CredentialProvider, type CredentialRefreshFallbackReason, type CredentialRefreshFallbackWarning, DPoPInitError, type DebugOptions, type DeviceController, type DeviceRecoveryEvent, DeviceTokenError, type DiagnosticEvent, type DialOptions, type Directory, EmbedTokenCredentialProvider, type ExecuteMethod, InvalidCredentialsError, type JSONRPCErrorResponse, type JSONRPCRequest, type JSONRPCResponse, type JSONRPCSuccessResponse, type LayoutLayer, type LogLevel, MediaAccessError, type MediaDirection, type MediaDirections, type MediaOptions, type MediaParamsEvent, MediaTrackError, type MemberCapabilities, MessageParseError, type NodeSocketAdapter, type OnOffCapability, OverconstrainedFallbackError, Participant, ParticipantNotReadyError, type PendingRPCOptions, type PermissionResult, type PlatformCapabilities, PreflightError, type PreflightOptions, type PreflightResult, type QualityLevel, RecoveryError, type RecoveryEvent, type RecoveryState, type ResilienceCallStatus, type SATClaims, type SDKCredential, type SDKLogger, type SDKWarning, type ScreenShareStatus, type SelectDeviceOptions, SelfCapabilities, SelfParticipant, type SessionDiagnostics, type SessionState, SignalWire, type SignalWireOptions, StaticCredentialProvider, type Storage, type StoredDevicePreference, type TextMessage, TokenRefreshError, type TransferOptions, UnexpectedError, User, type UserPresence, VertoPongError, type VideoPosition, type WebRTCApiProvider, WebRTCCall, type WebRTCMediaDevices, type WebSocketAdapter, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
4741
5085
  //# sourceMappingURL=index.d.mts.map