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

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.cts CHANGED
@@ -324,6 +324,17 @@ interface MediaDirections {
324
324
  /** Video direction */
325
325
  video: MediaDirection;
326
326
  }
327
+ /** Options for starting a screen share. */
328
+ interface ScreenShareOptions {
329
+ /**
330
+ * Request the shared surface's audio. Defaults to `false`.
331
+ *
332
+ * Whether audio can actually be captured depends on the browser, the OS and
333
+ * the surface the user picks — Chrome offers it for tabs and windows, and a
334
+ * share the user grants without audio yields a video-only stream.
335
+ */
336
+ audio?: boolean;
337
+ }
327
338
  /** Options controlling which media tracks to send and receive. */
328
339
  interface MediaOptions {
329
340
  /** Enable audio input. Defaults to `true` when not specified. */
@@ -1166,6 +1177,14 @@ interface CallError {
1166
1177
  readonly error: Error;
1167
1178
  /** ID of the call that produced this error. */
1168
1179
  readonly callId: string;
1180
+ /**
1181
+ * Which peer connection failed. Auxiliary legs are never fatal, so a consumer
1182
+ * can surface "screen share failed, call continues". Absent for call- and
1183
+ * session-level errors.
1184
+ */
1185
+ readonly leg?: RTCPeerConnectionPropose;
1186
+ /** `callId` is always the call's id, never the leg's. Use this for the leg. */
1187
+ readonly legId?: string;
1169
1188
  }
1170
1189
  declare class CallCreateError extends Error {
1171
1190
  message: string;
@@ -1173,6 +1192,14 @@ declare class CallCreateError extends Error {
1173
1192
  direction: 'inbound' | 'outbound';
1174
1193
  constructor(message: string, error?: unknown, direction?: 'inbound' | 'outbound', options?: ErrorOptions);
1175
1194
  }
1195
+ declare class CallNotReadyError extends Error {
1196
+ callId: string;
1197
+ constructor(callId: string, options?: ErrorOptions);
1198
+ }
1199
+ declare class ParticipantNotReadyError extends Error {
1200
+ memberId: string;
1201
+ constructor(memberId: string, options?: ErrorOptions);
1202
+ }
1176
1203
  declare class VertoPongError extends Error {
1177
1204
  originalError: unknown;
1178
1205
  constructor(originalError: unknown);
@@ -1186,6 +1213,26 @@ declare class CollectionFetchError extends Error {
1186
1213
  originalError: unknown;
1187
1214
  constructor(operation: string, originalError: unknown);
1188
1215
  }
1216
+ /**
1217
+ * An auxiliary leg did not connect within its budget. Typed rather than a bare
1218
+ * RxJS `TimeoutError` so the leg and cause survive.
1219
+ */
1220
+ declare class AuxiliaryLegTimeoutError extends Error {
1221
+ readonly leg: RTCPeerConnectionPropose;
1222
+ readonly originalError?: Error | undefined;
1223
+ constructor(leg: RTCPeerConnectionPropose, originalError?: Error | undefined);
1224
+ }
1225
+ /**
1226
+ * An auxiliary leg was removed before it finished connecting.
1227
+ *
1228
+ * Typed rather than a bare resolve so a caller awaiting the start can tell a
1229
+ * cancel apart from a share that actually came up — the public methods return
1230
+ * `void`, so the promise is the only signal they have.
1231
+ */
1232
+ declare class AuxiliaryLegCancelledError extends Error {
1233
+ readonly leg: RTCPeerConnectionPropose;
1234
+ constructor(leg: RTCPeerConnectionPropose);
1235
+ }
1189
1236
  declare class MediaTrackError extends Error {
1190
1237
  operation: string;
1191
1238
  kind: string;
@@ -1218,6 +1265,21 @@ declare class MediaAccessError extends Error {
1218
1265
  /** True when the underlying failure is a permission denial (user or policy). */
1219
1266
  get denied(): boolean;
1220
1267
  }
1268
+ /**
1269
+ * Thrown by `startScreenShare()` when the call is already sharing a screen.
1270
+ *
1271
+ * A call carries at most one screen share. Accepting a second one would
1272
+ * overwrite the only reference the SDK holds to the first, leaving it
1273
+ * capturing and sending with no way to stop it — so the second request is
1274
+ * rejected and the live share is left untouched. Call `stopScreenShare()`
1275
+ * first to replace it.
1276
+ */
1277
+ declare class ScreenShareAlreadyActiveError extends Error {
1278
+ /** Id of the screen share leg that is already active. */
1279
+ readonly screenShareId: string;
1280
+ constructor(/** Id of the screen share leg that is already active. */
1281
+ screenShareId: string, options?: ErrorOptions);
1282
+ }
1221
1283
  declare class DPoPInitError extends Error {
1222
1284
  originalError: unknown;
1223
1285
  constructor(originalError: unknown, message?: string);
@@ -1436,7 +1498,7 @@ interface AudioConstraintsEvent {
1436
1498
  /** Timestamp when the event occurred (epoch ms). */
1437
1499
  readonly timestamp: number;
1438
1500
  }
1439
- /** Event emitted when server-pushed media params are applied. */
1501
+ /** Event emitted when the server pushes media params. */
1440
1502
  interface MediaParamsEvent {
1441
1503
  /** Audio constraints pushed by the server, if any. */
1442
1504
  readonly audio?: MediaTrackConstraints;
@@ -1444,6 +1506,15 @@ interface MediaParamsEvent {
1444
1506
  readonly video?: MediaTrackConstraints;
1445
1507
  /** Timestamp when the event occurred (epoch ms). */
1446
1508
  readonly timestamp: number;
1509
+ /**
1510
+ * `false` when the constraints did not reach every sender of a pushed kind:
1511
+ * the sender carries media the SDK did not capture, the browser refused the
1512
+ * constraints and re-acquisition failed, or the leg sends no media of that
1513
+ * kind at all. The event is emitted whether or not they were applied, but a
1514
+ * push naming a leg this call does not hold emits nothing at all — the event
1515
+ * carries no leg identity to report it against.
1516
+ */
1517
+ readonly applied: boolean;
1447
1518
  }
1448
1519
  /** Structured diagnostic bundle for a session. */
1449
1520
  interface SessionDiagnostics {
@@ -1757,7 +1828,7 @@ interface DeviceController {
1757
1828
  interface VertoManager {
1758
1829
  readonly screenShareStatus$: Observable<ScreenShareStatus>;
1759
1830
  readonly screenShareStatus: ScreenShareStatus;
1760
- addScreenMedia(): Promise<void>;
1831
+ addScreenMedia(options?: ScreenShareOptions): Promise<void>;
1761
1832
  removeScreenMedia(): Promise<void>;
1762
1833
  addInputDevice(options?: MediaOptions): Promise<string | undefined>;
1763
1834
  removeInputDevices(id: string): Promise<void>;
@@ -1765,7 +1836,7 @@ interface VertoManager {
1765
1836
  updateMediaConstraints(options?: {
1766
1837
  audio?: MediaTrackConstraints;
1767
1838
  video?: MediaTrackConstraints;
1768
- }): Promise<void>;
1839
+ }): Promise<boolean>;
1769
1840
  muteMainAudioInputDevice(): void;
1770
1841
  unmuteMainAudioInputDevice(): Promise<void>;
1771
1842
  muteMainVideoInputDevice(): void;
@@ -1789,12 +1860,12 @@ type ParticipantState = Member & {
1789
1860
  * the local participant with additional device control.
1790
1861
  */
1791
1862
  declare class Participant extends Destroyable implements CallParticipant {
1792
- protected executeMethod: ExecuteMethod;
1863
+ private callExecuteMethod;
1793
1864
  protected deviceController: DeviceController;
1794
1865
  /** Unique member ID of this participant. */
1795
1866
  readonly id: string;
1796
1867
  private _state$;
1797
- constructor(id: string, executeMethod: ExecuteMethod, deviceController: DeviceController);
1868
+ constructor(id: string, callExecuteMethod: ExecuteMethod, deviceController: DeviceController);
1798
1869
  /** @internal */
1799
1870
  upnext(data: Partial<ParticipantState>): void;
1800
1871
  /** Observable of the participant's display name. */
@@ -1917,6 +1988,30 @@ declare class Participant extends Destroyable implements CallParticipant {
1917
1988
  get callId(): string | undefined;
1918
1989
  /** @internal */
1919
1990
  get value(): Partial<Member>;
1991
+ /**
1992
+ * Target triple for member RPCs, built from the participant's own state.
1993
+ * The backend locates the member's session by the target `call_id`/`node_id`,
1994
+ * so this must always be the participant's own call context — never the
1995
+ * local call's id (issue #19400).
1996
+ *
1997
+ * Reading it doubles as a readiness probe: it throws until the first full
1998
+ * member event (`member.joined`/`member.updated` or the `call.joined`
1999
+ * roster) arrives, and never regresses afterwards.
2000
+ *
2001
+ * @throws {ParticipantNotReadyError} If the member state has not been
2002
+ * received yet (e.g. a participant first seen via `member.talking`) — an
2003
+ * empty call context can never address the member, so fail fast instead of
2004
+ * sending a doomed RPC.
2005
+ */
2006
+ get target(): MemberTarget;
2007
+ /**
2008
+ * Executes a member RPC against this participant, injecting its own
2009
+ * {@link target} as the target.
2010
+ *
2011
+ * @throws {ParticipantNotReadyError} Via {@link target}, when the
2012
+ * member state has not been received yet.
2013
+ */
2014
+ protected executeMethod(method: string, args: Record<string, unknown>): Promise<JSONRPCResponse>;
1920
2015
  /** Toggles the deafened state (mutes/unmutes incoming audio). */
1921
2016
  toggleDeaf(): Promise<void>;
1922
2017
  /** Toggles the hand-raised state. */
@@ -1981,12 +2076,10 @@ declare class Participant extends Destroyable implements CallParticipant {
1981
2076
  /**
1982
2077
  * Sets the participant's position in the video layout.
1983
2078
  *
1984
- * Requires the `member.position` capability. The gateway keys positions by the
1985
- * **target member's own** `call_id`/`node_id` (see issue #19400 and the legacy
1986
- * `setPositions` implementation), so this sends the participant's own call
1987
- * context — matching {@link Participant.remove}. A resolved promise does not
1988
- * guarantee a visible change: the backend silently returns `200` (no-op) for
1989
- * non-conference targets.
2079
+ * Requires the `member.position` capability. The gateway requires a
2080
+ * `targets` array of `{ target, position }` entries (issue #19400). A
2081
+ * resolved promise does not guarantee a visible change: the backend silently
2082
+ * returns `200` (no-op) for non-conference targets.
1990
2083
  *
1991
2084
  * @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
1992
2085
  */
@@ -2030,7 +2123,7 @@ declare class SelfParticipant extends Participant implements CallSelfParticipant
2030
2123
  */
2031
2124
  private _studioAudio$;
2032
2125
  /** @internal */
2033
- constructor(id: string, executeMethod: ExecuteMethod, vertoManager: VertoManager, deviceController: DeviceController);
2126
+ constructor(id: string, callExecuteMethod: ExecuteMethod, vertoManager: VertoManager, deviceController: DeviceController);
2034
2127
  destroy(): void;
2035
2128
  /** Observable indicating whether studio audio (raw/unprocessed audio) mode is enabled. */
2036
2129
  get studioAudio$(): Observable<boolean>;
@@ -2049,13 +2142,22 @@ declare class SelfParticipant extends Participant implements CallSelfParticipant
2049
2142
  /**
2050
2143
  * Starts sharing the local screen.
2051
2144
  *
2145
+ * A call carries at most one screen share. Read `screenShareStatus` before
2146
+ * calling and treat `'starting'`/`'stopping'` as busy.
2147
+ *
2052
2148
  * The call is unaffected when acquisition fails.
2053
2149
  *
2150
+ * @param options - Pass `{ audio: true }` to also request the shared
2151
+ * surface's audio. Defaults to video only.
2152
+ * @throws {ScreenShareAlreadyActiveError} When this call is already
2153
+ * sharing a screen. Call {@link stopScreenShare} before starting another.
2154
+ * @throws {AuxiliaryLegCancelledError} When {@link stopScreenShare} removes
2155
+ * the share before its leg finishes connecting.
2054
2156
  * @throws The raw `getDisplayMedia` error. A dismissed picker or a
2055
2157
  * permission denial rejects with a `NotAllowedError` `DOMException` —
2056
2158
  * inspect `error.name` to tell benign cancels apart from real failures.
2057
2159
  */
2058
- startScreenShare(): Promise<void>;
2160
+ startScreenShare(options?: ScreenShareOptions): Promise<void>;
2059
2161
  /** Observable of the current screen share status. */
2060
2162
  get screenShareStatus$(): Observable<ScreenShareStatus>;
2061
2163
  /** Current screen share status. */
@@ -2067,8 +2169,11 @@ declare class SelfParticipant extends Participant implements CallSelfParticipant
2067
2169
  *
2068
2170
  * The call is unaffected when acquisition fails.
2069
2171
  *
2172
+ * @throws {AuxiliaryLegCancelledError} When {@link removeAdditionalDevice}
2173
+ * removes the device before its leg finishes connecting.
2070
2174
  * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
2071
- * permission denial) — inspect `error.name` to decide how to react.
2175
+ * permission denial) — inspect `error.name` to decide how to react — or
2176
+ * `AuxiliaryLegTimeoutError` if the leg does not connect in time.
2072
2177
  */
2073
2178
  addAdditionalDevice(options: MediaOptions): Promise<void>;
2074
2179
  /** Removes an additional media input device by ID. */
@@ -2093,17 +2198,26 @@ declare class SelfParticipant extends Participant implements CallSelfParticipant
2093
2198
  addInputDevices(options?: MediaOptions): Promise<void>;
2094
2199
  /** Selects the audio input device for future calls. Optionally saves as a preference. */
2095
2200
  selectAudioInputDevice(device: MediaDeviceInfo, options?: SelectDeviceOptions): void;
2096
- /** Updates the audio input track constraints for the active call. */
2097
- setAudioInputDeviceConstraints(constraints: MediaTrackConstraints): Promise<void>;
2098
- /** Updates both audio and video input track constraints for the active call. */
2201
+ /**
2202
+ * Updates the audio input track constraints for the active call.
2203
+ * @returns whether the constraints reached the media the call is sending.
2204
+ */
2205
+ setAudioInputDeviceConstraints(constraints: MediaTrackConstraints): Promise<boolean>;
2206
+ /**
2207
+ * Updates both audio and video input track constraints for the active call.
2208
+ * @returns whether both kinds took the constraints.
2209
+ */
2099
2210
  setInputDevicesConstraints(constraints: {
2100
2211
  audio: MediaTrackConstraints;
2101
2212
  video: MediaTrackConstraints;
2102
- }): Promise<void>;
2213
+ }): Promise<boolean>;
2103
2214
  /** Selects the video input device for future calls. Optionally saves as a preference. */
2104
2215
  selectVideoInputDevice(device: MediaDeviceInfo, options?: SelectDeviceOptions): void;
2105
- /** Updates the video input track constraints for the active call. */
2106
- setVideoInputDeviceConstraints(constraints: MediaTrackConstraints): Promise<void>;
2216
+ /**
2217
+ * Updates the video input track constraints for the active call.
2218
+ * @returns whether the constraints reached the media the call is sending.
2219
+ */
2220
+ setVideoInputDeviceConstraints(constraints: MediaTrackConstraints): Promise<boolean>;
2107
2221
  /** Selects the audio output device. Optionally saves as a preference. */
2108
2222
  selectAudioOutputDevice(device: MediaDeviceInfo, options?: SelectDeviceOptions): void;
2109
2223
  /**
@@ -2177,6 +2291,9 @@ interface CallParticipant {
2177
2291
  readonly addressId: string | undefined;
2178
2292
  readonly nodeId: string | undefined;
2179
2293
  readonly callId: string | undefined;
2294
+ /** The member's own RPC target triple. Throws `ParticipantNotReadyError`
2295
+ * until the member's call context has been received. */
2296
+ readonly target: MemberTarget;
2180
2297
  readonly isTalking: boolean;
2181
2298
  readonly position: LayoutLayer | undefined;
2182
2299
  readonly isAudience: boolean;
@@ -2212,7 +2329,7 @@ interface CallSelfParticipant extends CallParticipant {
2212
2329
  readonly studioAudio: boolean;
2213
2330
  enableStudioAudio(): Promise<void>;
2214
2331
  disableStudioAudio(): Promise<void>;
2215
- startScreenShare(): Promise<void>;
2332
+ startScreenShare(options?: ScreenShareOptions): Promise<void>;
2216
2333
  stopScreenShare(): Promise<void>;
2217
2334
  selectAudioInputDevice(device: MediaDeviceInfo, options?: SelectDeviceOptions): void;
2218
2335
  selectVideoInputDevice(device: MediaDeviceInfo, options?: SelectDeviceOptions): void;
@@ -2228,12 +2345,12 @@ interface CallSelfParticipant extends CallParticipant {
2228
2345
  stream?: MediaStream;
2229
2346
  }): Promise<void>;
2230
2347
  addInputDevices(options?: MediaOptions): Promise<void>;
2231
- setAudioInputDeviceConstraints(constraints: MediaTrackConstraints): Promise<void>;
2232
- setVideoInputDeviceConstraints(constraints: MediaTrackConstraints): Promise<void>;
2348
+ setAudioInputDeviceConstraints(constraints: MediaTrackConstraints): Promise<boolean>;
2349
+ setVideoInputDeviceConstraints(constraints: MediaTrackConstraints): Promise<boolean>;
2233
2350
  setInputDevicesConstraints(constraints: {
2234
2351
  audio: MediaTrackConstraints;
2235
2352
  video: MediaTrackConstraints;
2236
- }): Promise<void>;
2353
+ }): Promise<boolean>;
2237
2354
  }
2238
2355
  /**
2239
2356
  * Minimal interface for a collection with pagination
@@ -2321,7 +2438,8 @@ interface Call extends CallState {
2321
2438
  readonly capabilities: Capability[];
2322
2439
  readonly mediaDirections$: Observable<MediaDirections>;
2323
2440
  readonly mediaDirections: MediaDirections;
2324
- readonly self$: Observable<CallSelfParticipant | null>;
2441
+ /** Withholds emission until self exists, so it never emits `null` — unlike {@link Call.self}. */
2442
+ readonly self$: Observable<CallSelfParticipant>;
2325
2443
  readonly self: CallSelfParticipant | null;
2326
2444
  readonly to?: string;
2327
2445
  readonly toName?: string;
@@ -2375,6 +2493,17 @@ interface Call extends CallState {
2375
2493
  answer(options?: MediaOptions): void;
2376
2494
  reject(): void;
2377
2495
  sendDigits(digits: string): Promise<void>;
2496
+ readonly localAudioLevel$: Observable<number>;
2497
+ readonly localSpeaking$: Observable<boolean>;
2498
+ readonly remoteAudioLevel$: Observable<number>;
2499
+ readonly localMicrophoneGain$: Observable<number>;
2500
+ setLocalMicrophoneGain(value: number): void;
2501
+ enablePushToTalk(): void;
2502
+ disablePushToTalk(): void;
2503
+ setPushToTalkActive(active: boolean): void;
2504
+ setEchoCancellation(enabled: boolean): Promise<boolean>;
2505
+ setNoiseSuppression(enabled: boolean): Promise<boolean>;
2506
+ setAutoGainControl(enabled: boolean): Promise<boolean>;
2378
2507
  executeMethod<T extends JSONRPCResponse = JSONRPCResponse>(target: string, method: string, args: Record<string, unknown>): Promise<T>;
2379
2508
  execute<T extends JSONRPCResponse = JSONRPCResponse>(request: JSONRPCRequest, options?: PendingRPCOptions): Promise<T>;
2380
2509
  }
@@ -2652,9 +2781,27 @@ declare class AttachManager {
2652
2781
  private readonly deviceController;
2653
2782
  private readonly reconnectCallsTimeout;
2654
2783
  private attachKey;
2784
+ /**
2785
+ * Whether a credential recovery has been verified on this client — the
2786
+ * session reauthenticated with a fresh token AND the operation that
2787
+ * reauthentication was meant to unblock then succeeded. Gates attach-record
2788
+ * discard together with the failure kind: a record is dropped only when
2789
+ * this is true AND the reattach refusal was NOT a credential refusal
2790
+ * (-32003). See {@link reattachCalls}.
2791
+ */
2792
+ private readonly credentialRecovered;
2655
2793
  private session;
2656
2794
  private writeQueue;
2657
- constructor(storage: StorageManager, deviceController: DeviceController, reconnectCallsTimeout: number, attachKey: string);
2795
+ constructor(storage: StorageManager, deviceController: DeviceController, reconnectCallsTimeout: number, attachKey: string,
2796
+ /**
2797
+ * Whether a credential recovery has been verified on this client — the
2798
+ * session reauthenticated with a fresh token AND the operation that
2799
+ * reauthentication was meant to unblock then succeeded. Gates attach-record
2800
+ * discard together with the failure kind: a record is dropped only when
2801
+ * this is true AND the reattach refusal was NOT a credential refusal
2802
+ * (-32003). See {@link reattachCalls}.
2803
+ */
2804
+ credentialRecovered: () => boolean);
2658
2805
  detachAll(): Promise<void>;
2659
2806
  setSession(session: OutboundCallProvider): void;
2660
2807
  private readAttached;
@@ -2667,6 +2814,19 @@ declare class AttachManager {
2667
2814
  */
2668
2815
  private mutate;
2669
2816
  attach(call: AttachableCall): Promise<void>;
2817
+ /**
2818
+ * Keep an already-stored call's reference alive and current — the periodic
2819
+ * refresh the `verto.ping` keepalive drives.
2820
+ *
2821
+ * Only ever updates: a call with no record is one nothing wants reattached,
2822
+ * and re-creating it here would undo a `detach`. That matters because a ping
2823
+ * can land in the window between `bye()` detaching and the call being torn
2824
+ * down, and a record revived there survives the hangup — so the next page
2825
+ * load dials a call nobody is on. The existence check and the write share
2826
+ * one {@link mutate} turn, so a concurrent detach cannot slip between them.
2827
+ */
2828
+ refresh(call: AttachableCall): Promise<void>;
2829
+ private buildAttachment;
2670
2830
  detach(call: AttachableCall): Promise<void>;
2671
2831
  flush(): Promise<void>;
2672
2832
  /**
@@ -2681,8 +2841,14 @@ declare class AttachManager {
2681
2841
  * rejecting. Once that fix is deployed, this will work for both
2682
2842
  * page reloads and WebSocket reconnects.
2683
2843
  *
2684
- * Failed reattach attempts are handled gracefully — the stale call
2685
- * reference is cleaned up from storage.
2844
+ * A failed reattach does NOT generally cost the stored reference. It is
2845
+ * discarded only when the server denied the reattach on a session whose
2846
+ * credential it had already accepted — a verified reauthentication followed
2847
+ * by a refusal is the server saying the call is gone, and that is the one
2848
+ * refusal worth acting on. Until then the credential may be what is being
2849
+ * refused, and the record is the only way a later reload can try again;
2850
+ * keeping it costs nothing, since `detachExpired` reaps it once it is older
2851
+ * than `reconnectCallsTimeout`.
2686
2852
  */
2687
2853
  reattachCalls(): Promise<void>;
2688
2854
  /**
@@ -2712,6 +2878,11 @@ declare class TransportManager extends Destroyable {
2712
2878
  protocol$: rxjs0.ReplaySubject<string | undefined>;
2713
2879
  private isConnecting;
2714
2880
  private isConnected;
2881
+ private socketStatus;
2882
+ private sessionAuthenticated;
2883
+ private heldRequests;
2884
+ private serverPingTimer?;
2885
+ private serverPingWatch;
2715
2886
  private ackEvent;
2716
2887
  private replySignalwirePing;
2717
2888
  /**
@@ -2733,12 +2904,22 @@ declare class TransportManager extends Destroyable {
2733
2904
  setProtocol(protocol: string | undefined): Promise<void>;
2734
2905
  get incomingEvent$(): Observable<JSONRPCRequest | JSONRPCResponse>;
2735
2906
  get connectionStatus$(): Observable<string>;
2907
+ get connectionStatus(): string;
2736
2908
  connect(): Promise<void>;
2737
2909
  reconnect(): void;
2738
2910
  execute<T extends JSONRPCResponse = JSONRPCResponse>(request: JSONRPCRequest, options?: PendingRPCOptions): Promise<T>;
2739
2911
  send(message: unknown): void;
2912
+ /**
2913
+ * Called by the session when signalwire.connect succeeds on the current
2914
+ * socket. Sends the requests held since the socket opened, in order.
2915
+ */
2916
+ setAuthenticated(): void;
2740
2917
  disconnect(): void;
2741
2918
  destroy(): void;
2919
+ private armServerPingWatchdog;
2920
+ /** Bumps the watch so a probe still in flight no longer acts on its result. */
2921
+ private clearServerPingWatchdog;
2922
+ private probeServer;
2742
2923
  private _loadProtocolFromStorage;
2743
2924
  private _updateProtocolInStorage;
2744
2925
  private _init;
@@ -2839,56 +3020,6 @@ interface NetworkChangeEvent {
2839
3020
  networkType?: string;
2840
3021
  }
2841
3022
  //#endregion
2842
- //#region src/core/entities/Directory.d.ts
2843
- /**
2844
- * Directory interface for managing addresses
2845
- *
2846
- * This is the public API contract for address directory functionality.
2847
- * It provides access to addresses, loading capabilities, and search functionality.
2848
- *
2849
- * @public
2850
- */
2851
- interface Directory extends AddressProvider<Address> {
2852
- /**
2853
- * Observable stream of all addresses in the directory
2854
- * Emits a new array whenever addresses are added, removed, or updated
2855
- */
2856
- readonly addresses$: Observable<Address[]>;
2857
- /**
2858
- * Current snapshot of all addresses in the directory
2859
- */
2860
- readonly addresses: Address[];
2861
- /**
2862
- * Observable indicating whether more addresses can be loaded from the server
2863
- */
2864
- readonly hasMore$: Observable<boolean>;
2865
- /**
2866
- * Observable indicating the current loading state
2867
- * Emits `true` when loading, `false` when idle
2868
- */
2869
- readonly loading$: Observable<boolean>;
2870
- readonly loading: boolean;
2871
- /**
2872
- * Load more addresses from the server
2873
- * Only loads if `hasMore` is true
2874
- */
2875
- loadMore(): void;
2876
- /**
2877
- * Get a specific address by ID
2878
- *
2879
- * @param addressId - The address ID to retrieve
2880
- * @returns The address instance, or undefined if not found
2881
- */
2882
- get(addressId: string): Address | undefined;
2883
- /**
2884
- * Find an address ID by searching for a name
2885
- *
2886
- * @param uri - The address name to search for
2887
- * @returns Promise resolving to the address ID, or undefined if not found
2888
- */
2889
- findAddressIdByURI(uri: string): Promise<string | undefined>;
2890
- }
2891
- //#endregion
2892
3023
  //#region src/interfaces/ClientSession.d.ts
2893
3024
  /**
2894
3025
  * Minimal interface for what Call needs from session management
@@ -2918,2045 +3049,2406 @@ interface ClientSession {
2918
3049
  * indicates a re-authentication after the initial connect).
2919
3050
  */
2920
3051
  readonly authenticated$: Observable<boolean>;
3052
+ /**
3053
+ * Control transport for every call in this session: `'routed'` (default) sends
3054
+ * call.* verbs on the client's session channel; `'in-dialog'` carries them on
3055
+ * each call's own signaling channel via `verto.info`. Set once as a client
3056
+ * config ({@link SignalWireOptions.callControl}) — it is session-wide, so a
3057
+ * reattached call reads it here rather than restoring it from persisted state.
3058
+ * Read by {@link Call.executeMethod}.
3059
+ */
3060
+ readonly callControl: 'routed' | 'in-dialog';
2921
3061
  }
2922
3062
  //#endregion
2923
- //#region src/interfaces/SessionState.d.ts
3063
+ //#region src/controllers/LocalAudioPipeline.d.ts
2924
3064
  /**
2925
- * Extended session interface that adds call management and authentication
2926
- * state on top of the narrow ClientSession contract.
3065
+ * Options for {@link LocalAudioPipeline}.
3066
+ */
3067
+ interface LocalAudioPipelineOptions {
3068
+ /** Factory for AudioContext — override for tests. Defaults to `new AudioContext()`. */
3069
+ audioContextFactory?: () => AudioContext;
3070
+ /** Initial gain (0..2, where 1 is unity). Defaults to 1. */
3071
+ initialGain?: number;
3072
+ /** RMS level [0..1] above which speaking$ emits true. Defaults to {@link VAD_THRESHOLD}. */
3073
+ speakingThreshold?: number;
3074
+ /**
3075
+ * Milliseconds of silence below the threshold before speaking$ flips back to
3076
+ * false. Prevents flicker on normal speech gaps. Defaults to {@link VAD_HOLD_MS}.
3077
+ */
3078
+ speakingHoldMs?: number;
3079
+ /** Polling interval for level$. Defaults to {@link AUDIO_LEVEL_POLL_INTERVAL_MS}. */
3080
+ pollIntervalMs?: number;
3081
+ }
3082
+ /**
3083
+ * Web Audio pipeline for the local microphone stream.
2927
3084
  *
2928
- * Accessible via `client.session`. Call and CallFactory continue to depend
2929
- * only on the narrow ClientSession interface.
3085
+ * Wraps the raw mic `MediaStreamTrack` in a graph of:
3086
+ *
3087
+ * ```
3088
+ * MediaStreamAudioSourceNode → GainNode → AnalyserNode → MediaStreamAudioDestinationNode
3089
+ * ```
3090
+ *
3091
+ * The {@link outputTrack} from the destination node is what callers should
3092
+ * attach to the `RTCRtpSender` in place of the raw mic track. The same
3093
+ * destination track is reused across input changes (device switch, mute /
3094
+ * unmute track replacement) so the sender reference stays stable — only the
3095
+ * source end of the graph is rebuilt.
3096
+ *
3097
+ * The pipeline owns a single {@link AudioContext}. Callers must invoke
3098
+ * {@link destroy} to release it when the call ends.
2930
3099
  */
2931
- interface SessionState extends ClientSession {
3100
+ declare class LocalAudioPipeline extends Destroyable {
3101
+ private readonly _audioContext;
3102
+ private readonly _gainNode;
3103
+ private readonly _analyser;
3104
+ private readonly _destination;
3105
+ private readonly _analyserBuffer;
3106
+ private readonly _speakingThreshold;
3107
+ private readonly _speakingHoldMs;
3108
+ private readonly _pollIntervalMs;
3109
+ private _inputSource;
3110
+ private _inputStream;
3111
+ private _lastSpokeAt;
3112
+ private _gain$;
3113
+ /** 1 when audio should pass through, 0 when silenced by PTT. */
3114
+ private _pttMultiplier;
3115
+ constructor(options?: LocalAudioPipelineOptions);
3116
+ /** Observable of the current gain value (0..2). */
3117
+ get gain$(): Observable<number>;
3118
+ /** Current gain value (0..2). */
3119
+ get gain(): number;
2932
3120
  /**
2933
- * Observable stream of currently active inbound calls.
2934
- * Filters `calls$` to only include calls with `direction === 'inbound'`.
3121
+ * Processed output track to attach to the RTCRtpSender. Stable reference
3122
+ * across input changes, so `sender.replaceTrack(pipeline.outputTrack)` only
3123
+ * needs to be called once.
2935
3124
  */
2936
- readonly incomingCalls$: Observable<Call[]>;
3125
+ get outputTrack(): MediaStreamTrack;
2937
3126
  /**
2938
- * Current snapshot of active inbound calls.
3127
+ * Root-mean-square audio level of the input signal, 0..1. Emits on a fixed
3128
+ * interval (~30fps by default).
2939
3129
  */
2940
- readonly incomingCalls: Call[];
3130
+ get level$(): Observable<number>;
2941
3131
  /**
2942
- * Observable stream of all currently active calls (both inbound and outbound).
3132
+ * Boolean VAD derived from {@link level$}. True while level ≥ threshold or
3133
+ * during the hold window after the last frame that crossed the threshold.
2943
3134
  */
2944
- readonly calls$: Observable<Call[]>;
3135
+ get speaking$(): Observable<boolean>;
2945
3136
  /**
2946
- * Current snapshot of all active calls.
3137
+ * Set gain multiplier applied to the input signal. 0 = silence,
3138
+ * 1 = unity, 2 = 2x. Values are clamped to [0, 2]. The effective gain on
3139
+ * the graph also respects the current PTT state.
2947
3140
  */
2948
- readonly calls: Call[];
3141
+ setGain(value: number): void;
2949
3142
  /**
2950
- * Observable that emits `true` once the session has been authenticated,
2951
- * and `false` after disconnect.
3143
+ * Silence the graph when `active = false`, otherwise restore the configured
3144
+ * gain. Use this from a PTT handler: released → `false`, held → `true`.
3145
+ * Orthogonal to {@link setGain} — once PTT returns to active, the last
3146
+ * configured gain reappears.
2952
3147
  */
2953
- readonly authenticated$: Observable<boolean>;
3148
+ setPTTActive(active: boolean): void;
3149
+ private applyEffectiveGain;
2954
3150
  /**
2955
- * Current authentication state.
2956
- * Returns `true` if the session is currently authenticated.
3151
+ * Wire a new raw mic track as the pipeline's input. Replaces any previous
3152
+ * input source and reconnects the graph so {@link outputTrack} continues
3153
+ * to emit the processed audio. Pass `null` to disconnect the input (the
3154
+ * output track stays alive but emits silence).
3155
+ *
3156
+ * Also resumes the underlying AudioContext on attach — Chrome creates it
3157
+ * in a suspended state and the graph won't process (the destination
3158
+ * track emits silence) until resume() succeeds.
2957
3159
  */
2958
- readonly authenticated: boolean;
3160
+ setInputTrack(track: MediaStreamTrack | null): void;
3161
+ destroy(): void;
3162
+ private computeLevel;
3163
+ private evaluateSpeaking;
2959
3164
  }
2960
3165
  //#endregion
2961
- //#region src/managers/ClientSessionManager.d.ts
2962
- /**
2963
- * Discriminated union for session authentication state.
2964
- * clientBound is tracked separately via _wasClientBound (sticky flag)
2965
- * to avoid dual sources of truth.
2966
- */
2967
- type SessionAuthState = {
2968
- kind: 'unauthenticated';
2969
- } | {
2970
- kind: 'authenticated';
2971
- };
2972
- declare class ClientSessionManager extends Destroyable implements SessionState {
2973
- private readonly getCredential;
2974
- private readonly transport;
2975
- private readonly storage;
2976
- private readonly authorizationStateKey;
2977
- private readonly attachManager;
2978
- private readonly dpopManager?;
2979
- private callFactory;
2980
- private callCreateTimeout;
2981
- private readonly agent;
2982
- private readonly eventAcks;
2983
- initialized$: Observable<boolean>;
2984
- private authorizationState$;
2985
- private connectVersion;
3166
+ //#region src/controllers/RTCPeerConnectionController.d.ts
3167
+ interface RTCPeerConnectionControllerOptions extends MediaOptions {
3168
+ callId?: string;
3169
+ rtcConfiguration?: RTCConfiguration;
3170
+ simulcast?: boolean;
3171
+ sfu?: boolean;
3172
+ msStreamsNumber?: number;
3173
+ propose: RTCPeerConnectionPropose;
3174
+ iceServers?: RTCIceServer[];
3175
+ disableUdpIceServers?: boolean;
3176
+ relayOnly?: boolean;
3177
+ iceCandidateTimeout?: number;
3178
+ iceGatheringTimeout?: number;
3179
+ webRTCApiProvider?: WebRTCApiProvider;
3180
+ /** Per-call preferred video codecs (overrides global preferences). */
3181
+ preferredVideoCodecs?: string[];
3182
+ /** Per-call preferred audio codecs (overrides global preferences). */
3183
+ preferredAudioCodecs?: string[];
3184
+ /** Per-call stereo Opus setting (overrides global preferences). */
3185
+ stereo?: boolean;
2986
3186
  /**
2987
- * Optional hook called before a fresh connect on reconnect.
2988
- * Used by SignalWire to refresh expired credentials before re-authenticating.
2989
- * @internal
3187
+ * Request the shared surface's audio on a `'screenshare'` connection. Kept
3188
+ * apart from `audio`, which selects a microphone the share must not inherit.
2990
3189
  */
2991
- onBeforeReconnect?: () => Promise<void>;
2992
- private _authorization$;
3190
+ screenShareAudio?: boolean;
3191
+ }
3192
+ type RTCPeerConnectionControllerOptionsPartial = Partial<RTCPeerConnectionControllerOptions>;
3193
+ interface UpdateSDPStatusParams {
3194
+ status: 'received' | 'sent' | 'failed';
3195
+ sdp?: string;
3196
+ }
3197
+ declare class RTCPeerConnectionController extends Destroyable {
3198
+ protected options: RTCPeerConnectionControllerOptionsPartial;
3199
+ readonly id: string;
3200
+ firstSDPExchangeCompleted: boolean;
3201
+ sdpInit?: RTCSessionDescriptionInit;
3202
+ private negotiationNeeded$;
3203
+ private deviceController;
3204
+ private localStreamController;
3205
+ private transceiverController?;
3206
+ readonly localDescription$: Observable<RTCSessionDescription | null>;
3207
+ peerConnection?: RTCPeerConnection;
3208
+ private initPromise?;
3209
+ private connectionTimeout;
3210
+ private connectionTimer?;
3211
+ private oniceconnectionstatechangeHandler;
3212
+ private onconnectionstatechangeHandler;
3213
+ private onsignalingstatechangeHandler;
3214
+ private onicegatheringstatechangeHandler;
3215
+ private onnegotiationneededHandler;
3216
+ private updateSelectedInputDevice;
3217
+ private _isNegotiating$;
3218
+ private _iceGatheringController?;
3219
+ private _memberId;
3220
+ private _nodeId;
3221
+ private _type;
3222
+ private _iceConnectionState$;
3223
+ private _connectionState$;
3224
+ private _signalingState$;
3225
+ private _iceGatheringState$;
2993
3226
  private _errors$;
2994
- private _directory?;
2995
- private _authState$;
2996
- /** Sticky flag — once true, stays true for the session lifetime. */
2997
- private _wasClientBound;
2998
- private _userInfo$;
2999
- private _calls$;
3000
- private _iceServers$;
3001
- constructor(getCredential: () => SDKCredential, transport: TransportManager, storage: StorageManager, authorizationStateKey: string, deviceController: DeviceController, attachManager: AttachManager, webRTCApiProvider: WebRTCApiProvider, dpopManager?: CryptoController | undefined, networkChange$?: Observable<NetworkChangeEvent>);
3002
- get incomingCalls$(): Observable<Call[]>;
3003
- get incomingCalls(): Call[];
3004
- get userInfo$(): Observable<Address | null>;
3005
- get userInfo(): Address | null;
3006
- get calls$(): Observable<Call[]>;
3007
- get calls(): Call[];
3008
- get iceServers(): RTCIceServer[] | undefined;
3009
- get authorization$(): Observable<Authorization | undefined>;
3010
- get authorization(): Authorization | undefined;
3011
- get errors$(): Observable<Error>;
3012
- get authenticated$(): Observable<boolean>;
3013
- get authenticated(): boolean;
3227
+ private _iceCandidates$;
3014
3228
  /**
3015
- * Whether this session is client-bound (using a Client Bound SAT).
3016
- * When client-bound, DPoP proof creation failures are treated as
3017
- * authentication errors rather than silently degraded.
3018
- * @internal
3229
+ * Emits once local media is settled: acquired, intentionally receive-only, or
3230
+ * failed and degraded. Separates the media and signalling phases of call
3231
+ * creation. Not `localStream$` — the receive-only paths never build a stream,
3232
+ * so that would hang exactly the calls with nothing to acquire.
3019
3233
  */
3020
- get clientBound(): boolean;
3021
- /** @internal Current auth state for debugging/testing. */
3022
- get authState(): SessionAuthState;
3234
+ private _localMediaSettled$;
3235
+ private _initialized$;
3236
+ private _remoteDescription$;
3237
+ private _remoteStream$;
3238
+ private _remoteOfferMediaDirections;
3239
+ private _localAudioPipeline;
3240
+ constructor(options?: RTCPeerConnectionControllerOptionsPartial, remoteSessionDescription?: string, deviceController?: DeviceController);
3241
+ private get iceGatheringController();
3242
+ private get shouldEmitLocalDescription();
3243
+ private removeConnectionTimer;
3244
+ setMemberId(memberId: string | null): void;
3245
+ get memberId(): string | null;
3246
+ /** The node this leg's invite landed on — auxiliary legs are placed independently. */
3247
+ setNodeId(nodeId: string | null): void;
3248
+ get nodeId(): string | null;
3249
+ stopTrackSender(kind: 'audio' | 'video' | 'both', options?: {
3250
+ updateTransceiverDirection: boolean;
3251
+ }): void;
3252
+ private stopRawAudioInputForPipeline;
3253
+ get isNegotiating$(): Observable<boolean>;
3254
+ get isNegotiating(): boolean;
3255
+ updateMediaDevicesOptions(options: MediaOptions): void;
3256
+ get iceGatheringState$(): Observable<RTCIceGatheringState>;
3257
+ get mediaTrackEnded$(): Observable<MediaStreamTrack>;
3258
+ get errors$(): Observable<Error>;
3259
+ get iceCandidates$(): Observable<RTCIceCandidate[]>;
3260
+ get initialized$(): Observable<boolean>;
3261
+ get remoteDescription$(): Observable<RTCSessionDescription | null>;
3262
+ /** Emits once local media is settled — acquired, or knowingly receive-only. */
3263
+ get localMediaSettled$(): Observable<void>;
3264
+ get localStream$(): Observable<MediaStream | null>;
3265
+ get remoteStream$(): Observable<MediaStream | null>;
3266
+ get localAudioTracks$(): Observable<MediaStreamTrack[]>;
3267
+ get localVideoTracks$(): Observable<MediaStreamTrack[]>;
3268
+ get iceConnectionState$(): Observable<RTCIceConnectionState>;
3269
+ get connectionState$(): Observable<RTCPeerConnectionState>;
3270
+ get signalingState$(): Observable<RTCSignalingState>;
3271
+ get type(): RTCPeerConnectionType;
3272
+ get propose(): RTCPeerConnectionPropose;
3273
+ get connectionState(): RTCPeerConnectionState | undefined;
3274
+ get isAdditionalDevice(): boolean;
3275
+ get isMainDevice(): boolean;
3276
+ get isScreenShare(): boolean;
3277
+ protected get iceServers(): RTCIceServer[];
3278
+ private get rtcConfiguration();
3279
+ get receiveVideo(): boolean;
3280
+ get receiveAudio(): boolean;
3281
+ get localStream(): MediaStream | null;
3282
+ get remoteStream(): MediaStream | null;
3283
+ private get inputAudioDeviceConstraints();
3284
+ private get inputVideoDeviceConstraints();
3285
+ private get WebRTCPeerConnectionConstructor();
3286
+ private get offerOptions();
3287
+ private get answerOptions();
3023
3288
  /**
3024
- * Set the directory instance
3025
- * Called by SignalWire after directory is created
3026
- * @internal
3289
+ * Initialize the RTCPeerConnection and setup event listeners.
3290
+ * Called automatically when localDescription$ is subscribed to (deferred pattern).
3291
+ * Uses Promise memoization to ensure initialization only happens once,
3292
+ * even if called concurrently.
3027
3293
  */
3028
- setDirectory(directory: Directory): void;
3029
- execute<T extends JSONRPCResponse = JSONRPCResponse>(request: JSONRPCRequest, options?: PendingRPCOptions): Promise<T>;
3030
- send(message: JSONSerializable): void;
3031
3294
  private init;
3032
- private setupMessageHandlers;
3033
- private loadAuthorizationStateFromStorage;
3034
- private updateAuthorizationStateInStorage;
3035
- private get authStateEvent$();
3036
- get signalingEvent$(): Observable<(Omit<{
3037
- event_type: "webrtc.message";
3038
- event_channel: EventChannel;
3039
- timestamp: number;
3040
- project_id?: string;
3041
- node_id?: string;
3042
- is_author?: boolean;
3043
- params: WebrtcMessagePayload;
3044
- }, "event_channel" | "project_id" | "node_id"> & {
3045
- event_channel: string;
3046
- project_id: string;
3047
- node_id: string;
3048
- }) | {
3049
- event_type: "signalwire.authorization.state";
3050
- params: SignalwireAuthorizationStatePayload;
3051
- } | (Omit<{
3052
- event_type: "call.joined";
3053
- event_channel: EventChannel;
3054
- timestamp: number;
3055
- project_id?: string;
3056
- node_id?: string;
3057
- is_author?: boolean;
3058
- params: CallJoinedPayload;
3059
- }, "event_channel"> & {
3060
- event_channel: string;
3061
- }) | (Omit<{
3062
- event_type: "call.left";
3063
- event_channel: EventChannel;
3064
- timestamp: number;
3065
- project_id?: string;
3066
- node_id?: string;
3067
- is_author?: boolean;
3068
- params: CallLeftPayload;
3069
- }, "event_channel"> & {
3070
- event_channel: string;
3071
- }) | (Omit<{
3072
- event_type: "call.updated";
3073
- event_channel: EventChannel;
3074
- timestamp: number;
3075
- project_id?: string;
3076
- node_id?: string;
3077
- is_author?: boolean;
3078
- params: CallUpdatedPayload;
3079
- }, "event_channel"> & {
3080
- event_channel: string;
3081
- }) | (Omit<{
3082
- event_type: "call.state";
3083
- event_channel: EventChannel;
3084
- timestamp: number;
3085
- project_id?: string;
3086
- node_id?: string;
3087
- is_author?: boolean;
3088
- params: CallStatePayload;
3089
- }, "event_channel"> & {
3090
- event_channel: string;
3091
- }) | (Omit<{
3092
- event_type: "call.play";
3093
- event_channel: EventChannel;
3094
- timestamp: number;
3095
- project_id?: string;
3096
- node_id?: string;
3097
- is_author?: boolean;
3098
- params: CallPlayPayload;
3099
- }, "event_channel"> & {
3100
- event_channel: string;
3101
- }) | (Omit<{
3102
- event_type: "call.connect";
3103
- event_channel: EventChannel;
3104
- timestamp: number;
3105
- project_id?: string;
3106
- node_id?: string;
3107
- is_author?: boolean;
3108
- params: CallConnectPayload;
3109
- }, "event_channel"> & {
3110
- event_channel: string;
3111
- }) | (Omit<{
3112
- event_type: "room.updated";
3113
- event_channel: EventChannel;
3114
- timestamp: number;
3115
- project_id?: string;
3116
- node_id?: string;
3117
- is_author?: boolean;
3118
- params: RoomUpdatedPayload;
3119
- }, "event_channel"> & {
3120
- event_channel: string;
3121
- }) | Omit<{
3122
- event_type: "member.updated";
3123
- event_channel: EventChannel;
3124
- timestamp: number;
3125
- project_id?: string;
3126
- node_id?: string;
3127
- is_author?: boolean;
3128
- params: MemberUpdatedPayload;
3129
- }, never> | Omit<{
3130
- event_type: "member.joined";
3131
- event_channel: EventChannel;
3132
- timestamp: number;
3133
- project_id?: string;
3134
- node_id?: string;
3135
- is_author?: boolean;
3136
- params: MemberJoinedPayload;
3137
- }, never> | Omit<{
3138
- event_type: "member.left";
3139
- event_channel: EventChannel;
3140
- timestamp: number;
3141
- project_id?: string;
3142
- node_id?: string;
3143
- is_author?: boolean;
3144
- params: MemberLeftPayload;
3145
- }, never> | Omit<{
3146
- event_type: "member.talking";
3147
- event_channel: EventChannel;
3148
- timestamp: number;
3149
- project_id?: string;
3150
- node_id?: string;
3151
- is_author?: boolean;
3152
- params: MemberTalkingPayload;
3153
- }, never> | Omit<{
3154
- event_type: "layout.changed";
3155
- event_channel: EventChannel;
3156
- timestamp: number;
3157
- project_id?: string;
3158
- node_id?: string;
3159
- is_author?: boolean;
3160
- params: LayoutChangedPayload;
3161
- }, never> | (Omit<{
3162
- event_type: "conversation.message";
3163
- event_channel: EventChannel;
3164
- timestamp: number;
3165
- project_id?: string;
3166
- node_id?: string;
3167
- is_author?: boolean;
3168
- params: ConversationMessagePayload;
3169
- }, "event_channel" | "timestamp" | "is_author"> & {
3170
- event_channel: string;
3171
- timestamp: string;
3172
- is_author: boolean;
3173
- }) | (Omit<{
3174
- event_type: "conversation.message.updated";
3175
- event_channel: EventChannel;
3176
- timestamp: number;
3177
- project_id?: string;
3178
- node_id?: string;
3179
- is_author?: boolean;
3180
- params: ConversationMessagePayload;
3181
- }, "event_channel" | "timestamp" | "is_author"> & {
3182
- event_channel: string;
3183
- timestamp: string;
3184
- is_author: boolean;
3185
- })>;
3186
- private get vertoInvite$();
3187
- private get vertoAttach$();
3188
- private get contexts();
3189
- private get eventing();
3190
- private get topics();
3191
- private get authentication();
3192
- connect(): Promise<void>;
3193
- private handleAuthenticationError;
3194
3295
  /**
3195
- * Clear the resume state (authorization_state + protocol) only.
3296
+ * Internal initialization implementation.
3297
+ * Should only be called via init() to ensure single execution.
3298
+ */
3299
+ private doInit;
3300
+ private setupPeerConnection;
3301
+ private startNegotiation;
3302
+ /**
3303
+ * Create an SDP offer and set it as local description.
3304
+ */
3305
+ private createOffer;
3306
+ updateAnswerStatus({
3307
+ status,
3308
+ sdp
3309
+ }: UpdateSDPStatusParams): Promise<void>;
3310
+ updateOfferStatus({
3311
+ status,
3312
+ sdp
3313
+ }: UpdateSDPStatusParams): Promise<void>;
3314
+ /**
3315
+ * Accept an inbound call by creating the SDP answer.
3316
+ * Optionally override media options before the answer is generated.
3317
+ * Must be called after initialization for inbound (answer-type) connections.
3318
+ */
3319
+ acceptInbound(mediaOverrides?: MediaOptions): Promise<void>;
3320
+ private handleOfferReceived;
3321
+ private readyToConnect;
3322
+ private setRemoteDescriptionBefore;
3323
+ protected setLocalDescription(params: RTCSessionDescriptionInit): Promise<void>;
3324
+ setLocalDescriptionBefore(sdp?: string): Promise<string>;
3325
+ /**
3326
+ * Create an SDP answer and set it as local description.
3327
+ */
3328
+ private createAnswer;
3329
+ /**
3330
+ * Setup event listeners on RTCPeerConnection for state changes.
3331
+ */
3332
+ private setupEventListeners;
3333
+ private negotiationEnded;
3334
+ /**
3335
+ * Trigger an ICE restart through the existing negotiation pipeline.
3196
3336
  *
3197
- * This is the stale-auth-state recovery helper used by handleAuthError:
3198
- * the server rejected a reconnect, so the resume state is discarded and a
3199
- * fresh connect follows. Attach records are deliberately preserved — the
3200
- * session lives on through the reconnect and reattachCalls() needs the
3201
- * stored call references afterwards. Do NOT add detachAll() here.
3337
+ * This creates an offer with iceRestart: true and goes through the full
3338
+ * SDP pipeline (setLocalDescription → ICE gathering → localDescription$ emission).
3339
+ * The caller should NOT send the SDP manually — the existing
3340
+ * setupLocalDescriptionHandler in VertoManager will pick up the emission
3341
+ * from localDescription$ and send it as a verto.modify.
3342
+ *
3343
+ * Unlike calling pc.createOffer/setLocalDescription directly, this method:
3344
+ * - Sets _isNegotiating$ so ICEGatheringController arms its timers
3345
+ * - Waits for ICE gathering to complete before localDescription$ emits
3346
+ * - Goes through setLocalDescriptionBefore() for any SDP munging
3347
+ */
3348
+ triggerIceRestart(relayOnly?: boolean): Promise<void>;
3349
+ private restoreIceTransportPolicy;
3350
+ /**
3351
+ * Setup track handling for remote tracks.
3352
+ *
3353
+ * @returns `false` when the connection went away while local media was being
3354
+ * acquired — see {@link setupLocalTracks}.
3355
+ */
3356
+ private setupTrackHandling;
3357
+ /**
3358
+ * @returns `false` when the connection was torn down while getUserMedia was
3359
+ * in flight. The acquisition is not cancellable, so the caller must stop
3360
+ * rather than go on to touch a peer connection that is closed or gone.
3361
+ */
3362
+ private setupLocalTracks;
3363
+ /** True for a main connection with no local media to send. */
3364
+ private hasNoLocalMediaToSend;
3365
+ /** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
3366
+ private get requestedMediaKinds();
3367
+ /**
3368
+ * Handle a local media acquisition failure with a typed, semantically
3369
+ * accurate MediaAccessError created at the acquisition site:
3370
+ * - Auxiliary connections (screenshare / additional-device) throw a
3371
+ * non-fatal error — VertoManager surfaces it and the call is unaffected.
3372
+ * - The main connection degrades to receive-only when allowed (default),
3373
+ * otherwise fails with a fatal error.
3374
+ */
3375
+ private handleLocalMediaFailure;
3376
+ /**
3377
+ * Negotiate receive-only m-lines when there are no local tracks to send.
3378
+ * Only offer-type connections add transceivers — answer-type connections
3379
+ * reuse the transceivers created from the remote offer.
3380
+ */
3381
+ private setupReceiveOnlyTransceivers;
3382
+ private getUserMedia;
3383
+ private getDisplayMedia;
3384
+ private setupRemoteTracks;
3385
+ restoreTrackSender(kind: 'audio' | 'video' | 'both'): Promise<void>;
3386
+ private restoreRawAudioInputForPipeline;
3387
+ /**
3388
+ * Capture the newly selected device, leaving the current capture running.
3202
3389
  *
3203
- * For public teardown (disconnect/destroy), use {@link teardownSessionState}
3204
- * instead, which clears the attach records as well.
3390
+ * A rejection must leave the current track sending, so nothing is released
3391
+ * until the replacement is in hand. The one exception is hardware that admits
3392
+ * a single opener — a phone's front and back cameras, typically — which
3393
+ * rejects the second capture until the first is closed.
3205
3394
  */
3206
- cleanupStoredConnectionParams(): Promise<void>;
3395
+ private acquireInputTrack;
3396
+ private captureTrack;
3397
+ /** Best-effort return to the device that was released for an exclusive retry. */
3398
+ private restorePreviousInputTrack;
3399
+ private attachInputTrack;
3207
3400
  /**
3208
- * Public-teardown helper for disconnect()/destroy(). Clears the resume
3209
- * state (authorization_state + protocol) AND the attach records as one
3210
- * atomic unit.
3211
- *
3212
- * The two stores are coupled: the backend only honors attach records
3213
- * within the session identified by the resume state, so ending the
3214
- * session must clear both. Clearing one without the other strands records
3215
- * no future session can honor (disconnect) or revives a session the
3216
- * developer explicitly ended (destroy).
3217
- *
3218
- * Distinct from {@link cleanupStoredConnectionParams}, which keeps the
3219
- * attach records for the stale-auth-state recovery path.
3401
+ * Return the lazily-created {@link LocalAudioPipeline}, constructing it on
3402
+ * first access. On creation the current audio sender's track is routed
3403
+ * through the pipeline (input → gain → analyser → destination) and the
3404
+ * sender is switched to emit the processed track. Returns `null` when no
3405
+ * audio sender exists yet (pre-negotiation).
3220
3406
  */
3221
- teardownSessionState(): Promise<void>;
3222
- protected updateAuthState(authorization_state: string): Promise<void>;
3223
- reauthenticate(token: string, dpopToken?: string, options?: {
3224
- clientBound?: boolean;
3225
- }): Promise<void>;
3226
- private authenticate;
3227
- disconnect(): Promise<void>;
3228
- private createInboundCall;
3407
+ ensureLocalAudioPipeline(): LocalAudioPipeline | null;
3408
+ /** The active LocalAudioPipeline, or null if it hasn't been created yet. */
3409
+ get localAudioPipeline(): LocalAudioPipeline | null;
3410
+ private applyLocalAudioPipelineToSender;
3229
3411
  /**
3230
- * Handle a server-pushed verto.attach event at the session level.
3412
+ * Add a local media track to the peer connection.
3413
+ * @param track - The MediaStreamTrack to add
3414
+ */
3415
+ addLocalTrack(track: MediaStreamTrack): void;
3416
+ /**
3417
+ * Remove a local media track from the peer connection.
3418
+ * @param trackId - The ID of the track to remove
3419
+ */
3420
+ removeLocalTrack(trackId: string): void;
3421
+ /**
3422
+ * Replace all existing media tracks with a new media track.
3423
+ * Convenience method for single-track scenarios.
3424
+ * @param track - The MediaStreamTrack to set
3425
+ */
3426
+ setLocalTrack(track: MediaStreamTrack): void;
3427
+ /**
3428
+ * @returns whether the constraints reached the media the leg is sending.
3231
3429
  *
3232
- * On page reload the server detects the reconnected session and pushes
3233
- * verto.attach for any active calls. If a call object already exists
3234
- * (network blip, no reload), the per-call handler in VertoManager deals
3235
- * with it. This method only creates a new call object when no existing
3236
- * one matches the callID.
3430
+ * With the pipeline engaged the audio sender carries the processed
3431
+ * destination track, so the sender scan would find nothing it may touch and
3432
+ * every audio constraint API would silently no-op. The constraints belong to
3433
+ * the pipeline's device source, which is the capture that sender ultimately
3434
+ * carries.
3435
+ */
3436
+ updateSendersConstraints(kind: 'audio' | 'video', constraints?: MediaTrackConstraints): Promise<boolean>;
3437
+ /**
3438
+ * Mirror of the sender path for a piped audio leg: same merge, same fallback
3439
+ * ladder, same device-capture invariant — but the swap target is the pipeline
3440
+ * input, so the sender keeps emitting the pipeline's output track and its
3441
+ * identity survives the change.
3442
+ */
3443
+ private applyPipelineSourceConstraints;
3444
+ /**
3445
+ * Clean up resources and close the peer connection.
3446
+ * Completes all observables to prevent memory leaks.
3237
3447
  */
3238
- private handleVertoAttach;
3239
- createOutboundCall(destination: string | Address, options?: CallOptions): Promise<Call>;
3240
- private createCall;
3241
3448
  destroy(): void;
3449
+ private removeAllListeners;
3450
+ private stopRemoteTracks;
3451
+ get mediaDirections(): {
3452
+ audio: RTCRtpTransceiverDirection;
3453
+ video: RTCRtpTransceiverDirection;
3454
+ };
3455
+ protected _setRemoteDescription(params: RTCSessionDescriptionInit): Promise<void>;
3242
3456
  }
3243
- declare class ClientSessionWrapper implements SessionState {
3244
- private clientSessionManager;
3245
- constructor(clientSessionManager: ClientSessionManager);
3246
- get authenticated$(): Observable<boolean>;
3247
- get authenticated(): boolean;
3248
- get signalingEvent$(): Observable<(Omit<{
3249
- event_type: "webrtc.message";
3250
- event_channel: EventChannel;
3251
- timestamp: number;
3252
- project_id?: string;
3253
- node_id?: string;
3254
- is_author?: boolean;
3255
- params: WebrtcMessagePayload;
3256
- }, "event_channel" | "project_id" | "node_id"> & {
3257
- event_channel: string;
3258
- project_id: string;
3259
- node_id: string;
3260
- }) | {
3261
- event_type: "signalwire.authorization.state";
3262
- params: SignalwireAuthorizationStatePayload;
3263
- } | (Omit<{
3264
- event_type: "call.joined";
3265
- event_channel: EventChannel;
3266
- timestamp: number;
3267
- project_id?: string;
3268
- node_id?: string;
3269
- is_author?: boolean;
3270
- params: CallJoinedPayload;
3271
- }, "event_channel"> & {
3272
- event_channel: string;
3273
- }) | (Omit<{
3274
- event_type: "call.left";
3275
- event_channel: EventChannel;
3276
- timestamp: number;
3277
- project_id?: string;
3278
- node_id?: string;
3279
- is_author?: boolean;
3280
- params: CallLeftPayload;
3281
- }, "event_channel"> & {
3282
- event_channel: string;
3283
- }) | (Omit<{
3284
- event_type: "call.updated";
3285
- event_channel: EventChannel;
3286
- timestamp: number;
3287
- project_id?: string;
3288
- node_id?: string;
3289
- is_author?: boolean;
3290
- params: CallUpdatedPayload;
3291
- }, "event_channel"> & {
3292
- event_channel: string;
3293
- }) | (Omit<{
3294
- event_type: "call.state";
3295
- event_channel: EventChannel;
3296
- timestamp: number;
3297
- project_id?: string;
3298
- node_id?: string;
3299
- is_author?: boolean;
3300
- params: CallStatePayload;
3301
- }, "event_channel"> & {
3302
- event_channel: string;
3303
- }) | (Omit<{
3304
- event_type: "call.play";
3305
- event_channel: EventChannel;
3306
- timestamp: number;
3307
- project_id?: string;
3308
- node_id?: string;
3309
- is_author?: boolean;
3310
- params: CallPlayPayload;
3311
- }, "event_channel"> & {
3312
- event_channel: string;
3313
- }) | (Omit<{
3314
- event_type: "call.connect";
3315
- event_channel: EventChannel;
3316
- timestamp: number;
3317
- project_id?: string;
3318
- node_id?: string;
3319
- is_author?: boolean;
3320
- params: CallConnectPayload;
3321
- }, "event_channel"> & {
3322
- event_channel: string;
3323
- }) | (Omit<{
3324
- event_type: "room.updated";
3325
- event_channel: EventChannel;
3326
- timestamp: number;
3327
- project_id?: string;
3328
- node_id?: string;
3329
- is_author?: boolean;
3330
- params: RoomUpdatedPayload;
3331
- }, "event_channel"> & {
3332
- event_channel: string;
3333
- }) | Omit<{
3334
- event_type: "member.updated";
3335
- event_channel: EventChannel;
3336
- timestamp: number;
3337
- project_id?: string;
3338
- node_id?: string;
3339
- is_author?: boolean;
3340
- params: MemberUpdatedPayload;
3341
- }, never> | Omit<{
3342
- event_type: "member.joined";
3343
- event_channel: EventChannel;
3344
- timestamp: number;
3345
- project_id?: string;
3346
- node_id?: string;
3347
- is_author?: boolean;
3348
- params: MemberJoinedPayload;
3349
- }, never> | Omit<{
3350
- event_type: "member.left";
3351
- event_channel: EventChannel;
3352
- timestamp: number;
3353
- project_id?: string;
3354
- node_id?: string;
3355
- is_author?: boolean;
3356
- params: MemberLeftPayload;
3357
- }, never> | Omit<{
3358
- event_type: "member.talking";
3359
- event_channel: EventChannel;
3360
- timestamp: number;
3361
- project_id?: string;
3362
- node_id?: string;
3363
- is_author?: boolean;
3364
- params: MemberTalkingPayload;
3365
- }, never> | Omit<{
3366
- event_type: "layout.changed";
3367
- event_channel: EventChannel;
3368
- timestamp: number;
3369
- project_id?: string;
3370
- node_id?: string;
3371
- is_author?: boolean;
3372
- params: LayoutChangedPayload;
3373
- }, never> | (Omit<{
3374
- event_type: "conversation.message";
3375
- event_channel: EventChannel;
3376
- timestamp: number;
3377
- project_id?: string;
3378
- node_id?: string;
3379
- is_author?: boolean;
3380
- params: ConversationMessagePayload;
3381
- }, "event_channel" | "timestamp" | "is_author"> & {
3382
- event_channel: string;
3383
- timestamp: string;
3384
- is_author: boolean;
3385
- }) | (Omit<{
3386
- event_type: "conversation.message.updated";
3387
- event_channel: EventChannel;
3388
- timestamp: number;
3389
- project_id?: string;
3390
- node_id?: string;
3391
- is_author?: boolean;
3392
- params: ConversationMessagePayload;
3393
- }, "event_channel" | "timestamp" | "is_author"> & {
3394
- event_channel: string;
3395
- timestamp: string;
3396
- is_author: boolean;
3397
- })>;
3398
- get iceServers(): RTCIceServer[] | undefined;
3399
- execute<T extends JSONRPCResponse = JSONRPCResponse>(request: JSONRPCRequest, options?: PendingRPCOptions): Promise<T>;
3400
- get incomingCalls$(): Observable<Call[]>;
3401
- get incomingCalls(): Call[];
3402
- get calls$(): Observable<Call[]>;
3403
- get calls(): Call[];
3404
- }
3405
- //#endregion
3406
- //#region src/core/types/warnings.types.d.ts
3407
- /**
3408
- * Non-fatal warning emitted via {@link SignalWire.warnings$ | client.warnings$}.
3409
- *
3410
- * Use to detect SDK behaviors that affect session liveness or developer-facing
3411
- * contracts but do not warrant disconnection. Discriminated by `code`.
3412
- *
3413
- * Existing consumers of `errors$` are NOT notified — `warnings$` is a separate
3414
- * channel so application code can react to warnings without triggering
3415
- * error-handling code paths (e.g., disconnect cascades, user-facing toasts).
3416
- */
3417
- type SDKWarning = CredentialRefreshFallbackWarning | CredentialNoRefreshHandlerWarning;
3418
- /**
3419
- * Diagnostic detail for {@link CredentialRefreshFallbackWarning}. Stable
3420
- * values, but treat unknown strings as "fell back for an unspecified cause" —
3421
- * do not branch on this value for control flow. New values may be added in
3422
- * future releases.
3423
- */
3424
- type CredentialRefreshFallbackReason = 'no-scope' | 'no-dpop-support' | 'endpoint-failed' | 'activation-timeout' | (string & {});
3457
+ //#endregion
3458
+ //#region src/interfaces/WebRTCVerto.d.ts
3425
3459
  /**
3426
- * Emitted when the SDK falls back to the developer-provided
3427
- * {@link CredentialProvider.refresh} because the Client Bound SAT path
3428
- * could not take over.
3429
- *
3430
- * Common causes:
3431
- * - The minted SAT lacks `sat:refresh` scope (`reason: 'no-scope'`).
3432
- * - The `/devices/token` exchange failed transiently (`reason: 'endpoint-failed'`).
3433
- *
3434
- * Subscribe to this warning to detect:
3435
- * - SDKs running with plain SATs that rely on developer-managed refresh
3436
- * - Deployments expected to use bound tokens that silently downgraded to bearer
3437
- * (a security-relevant signal for fleet observability)
3460
+ * Extended interface for WebRTC Verto Manager
3461
+ * Includes WebRTC-specific state and peer connection management
3438
3462
  */
3439
- interface CredentialRefreshFallbackWarning {
3440
- code: 'credential_refresh_fallback';
3441
- source: 'CredentialProvider';
3442
- reason: CredentialRefreshFallbackReason;
3443
- message: string;
3463
+ interface WebRTCVerto extends VertoManager {
3464
+ readonly selfId$: Observable<string | null>;
3465
+ /** Separates the media phase of call creation from the signalling phase. */
3466
+ readonly localMediaSettled$: Observable<void>;
3467
+ readonly selfId: string | null;
3468
+ readonly nodeId$: Observable<string | null>;
3469
+ readonly nodeId: string | null;
3470
+ readonly localStream$: Observable<MediaStream>;
3471
+ readonly localStream: MediaStream | null;
3472
+ readonly remoteStream$: Observable<MediaStream>;
3473
+ readonly remoteStream: MediaStream | null;
3474
+ readonly mediaDirections$: Observable<MediaDirections>;
3475
+ readonly mediaDirections: MediaDirections;
3476
+ readonly signalingStatus$: Observable<SignalingStatus>;
3477
+ readonly mainPeerConnection: RTCPeerConnectionController;
3478
+ bye(cause?: string): Promise<void>;
3479
+ sendDigits(dtmf: string): Promise<void>;
3480
+ /**
3481
+ * Send a member-control op in-dialog via verto.info (no self/target member
3482
+ * tuple). The payload rides in the verto.info `params.command` body — a
3483
+ * sibling of `dialogParams`, same level as `dtmf` — matched to this call's
3484
+ * channel by `dialogParams.callID`, so control lands on the call's own channel
3485
+ * without relying on the {node_id,call_id,member_id} addressing the routed
3486
+ * transport uses.
3487
+ */
3488
+ sendCallControl(method: string, params: Record<string, unknown>): Promise<unknown>;
3489
+ hold(): Promise<void>;
3490
+ unhold(): Promise<void>;
3491
+ destroy(): void;
3492
+ transfer(options: TransferOptions): Promise<void>;
3493
+ /** Request a video keyframe via verto.modify. */
3494
+ requestKeyframe?: () => void;
3495
+ /** Request an ICE restart via verto.modify with iceRestart offer. */
3496
+ requestIceRestart?: (relayOnly?: boolean) => Promise<void>;
3497
+ /** Request an ICE restart on all active peer connections (multi-leg). */
3498
+ requestIceRestartAll?: (relayOnly?: boolean) => Promise<void>;
3499
+ /** Request keyframes on all video-receiving legs (skips send-only screen share). */
3500
+ requestKeyframeAll?: () => void;
3501
+ /** Lazily create (or return) the local audio pipeline for the main peer connection. */
3502
+ ensureLocalAudioPipeline(): LocalAudioPipeline | null;
3503
+ /** Current local audio pipeline, or null if it has not been created yet. */
3504
+ readonly localAudioPipeline: LocalAudioPipeline | null;
3505
+ }
3506
+ //#endregion
3507
+ //#region src/managers/CallEventsManager.d.ts
3508
+ interface WebRTCCallEventManagerOptions {}
3509
+ /** @internal */
3510
+ declare class CallEventsManager extends Destroyable {
3511
+ protected webRtcCallSession: CallManager;
3512
+ protected options: WebRTCCallEventManagerOptions;
3513
+ private selfId?;
3514
+ private originCallId?;
3515
+ private callIds;
3516
+ private roomSessionIds;
3517
+ private _participants$;
3518
+ private _self$;
3519
+ private _sessionState$;
3520
+ constructor(webRtcCallSession: CallManager, options?: WebRTCCallEventManagerOptions);
3521
+ get participants$(): Observable<CallParticipant[]>;
3522
+ get participants(): CallParticipant[];
3523
+ get self$(): Observable<CallSelfParticipant>;
3524
+ isRoomSessionIdValid(roomSessionId: string): boolean;
3525
+ addCallId(callId: string): void;
3526
+ isCallIdValid(callId: string): boolean;
3527
+ get recording$(): Observable<boolean>;
3528
+ get recordings$(): Observable<Record<string, unknown>[]>;
3529
+ get streaming$(): Observable<boolean>;
3530
+ get streams$(): Observable<Record<string, unknown>[]>;
3531
+ get playbacks$(): Observable<Record<string, unknown>[]>;
3532
+ get raiseHandPriority$(): Observable<boolean>;
3533
+ get locked$(): Observable<boolean>;
3534
+ get meta$(): Observable<Record<string, unknown>>;
3535
+ get capabilities$(): Observable<Capability[]>;
3536
+ get layout$(): Observable<string>;
3537
+ get layouts$(): Observable<string[]>;
3538
+ get layoutLayers$(): Observable<LayoutLayer[]>;
3539
+ get self(): CallSelfParticipant | null;
3540
+ get layoutLayers(): LayoutLayer[];
3541
+ get recording(): boolean;
3542
+ get streaming(): boolean;
3543
+ get raiseHandPriority(): boolean;
3544
+ get locked(): boolean;
3545
+ get meta(): Record<string, unknown>;
3546
+ get layout(): string | undefined;
3547
+ get layouts(): string[];
3548
+ get capabilities(): Capability[];
3549
+ isSessionEvent(id: string): boolean;
3550
+ protected initSubscriptions(): void;
3551
+ private updateParticipantPositions;
3552
+ updateLayouts(): void;
3553
+ private updateParticipants;
3554
+ private upsertParticipant;
3555
+ private get callJoinedEvent$();
3556
+ private get layoutChangedEvent$();
3557
+ private get memberUpdates$();
3558
+ destroy(): void;
3559
+ }
3560
+ //#endregion
3561
+ //#region src/managers/CallRecoveryManager.d.ts
3562
+ type RecoveryState$1 = 'idle' | 'debouncing' | 'recovering' | 'cooldown';
3563
+ interface RecoveryEvent$1 {
3564
+ action: 'keyframe_requested' | 'reinvite_started' | 'reinvite_succeeded' | 'reinvite_failed' | 'reinvite_timeout' | 'max_attempts_reached' | 'signal_reconnect' | 'full_reconnect' | 'video_disabled' | 'video_restored';
3565
+ reason: string;
3566
+ attempt?: number;
3567
+ maxAttempts?: number;
3568
+ timestamp: number;
3444
3569
  }
3570
+ //#endregion
3571
+ //#region src/utils/qualityScore.d.ts
3445
3572
  /**
3446
- * Emitted when a credential has an `expiry_at` but the provider supplies no
3447
- * `refresh()` handler. The session will terminate at expiry with no fallback.
3573
+ * MOS (Mean Opinion Score) quality computation based on the simplified
3574
+ * ITU-T G.107 E-model.
3448
3575
  *
3449
- * Implementors who want long-lived sessions must provide a `refresh()` handler
3450
- * or mint tokens with the `sat:refresh` scope (Client Bound SAT path).
3576
+ * Provides a single 1-5 number that applications can use for a
3577
+ * green / yellow / red quality indicator without understanding raw
3578
+ * jitter and packet-loss values.
3451
3579
  */
3452
- interface CredentialNoRefreshHandlerWarning {
3453
- code: 'credential_no_refresh_handler';
3454
- source: 'CredentialProvider';
3455
- message: string;
3456
- /** Token expiry timestamp (epoch milliseconds). */
3457
- expiresAt: number;
3458
- }
3580
+ type QualityLevel$1 = 'excellent' | 'good' | 'fair' | 'poor' | 'critical';
3459
3581
  //#endregion
3460
- //#region src/utils/logger.d.ts
3461
- /** Log level names supported by the SDK. */
3462
- type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent';
3582
+ //#region src/core/entities/Call.d.ts
3463
3583
  /**
3464
- * Logger interface that consumers can implement to replace the built-in logger.
3465
- * All methods accept variadic arguments matching the browser console API.
3584
+ * Manager instances returned by initialization callback
3466
3585
  */
3467
- interface SDKLogger {
3468
- error(...args: unknown[]): void;
3469
- warn(...args: unknown[]): void;
3470
- info(...args: unknown[]): void;
3471
- debug(...args: unknown[]): void;
3472
- trace(...args: unknown[]): void;
3473
- }
3474
- /** Options for WebSocket traffic logging. */
3475
- interface WsTrafficOptions {
3476
- type: 'send' | 'recv' | 'http';
3477
- /** Parsed object or raw string — will be JSON.stringify'd for display if an object. */
3478
- payload: unknown;
3586
+ interface CallManagers {
3587
+ vertoManager: WebRTCVerto;
3588
+ callEventsManager: CallEventsManager;
3479
3589
  }
3480
3590
  /**
3481
- * Options for WebSocket traffic logging using raw strings.
3482
- * The string is only parsed when logging is enabled, avoiding
3483
- * unnecessary JSON.parse on every message.
3591
+ * Initialization callback that creates managers for a Call instance
3592
+ * @param call - The WebRTCCall instance being initialized
3593
+ * @returns Manager instances for the call
3484
3594
  */
3485
- interface WsTrafficRawOptions {
3486
- type: 'send' | 'recv';
3487
- raw: string;
3488
- }
3489
- /** Debug options that control verbose SDK logging. */
3490
- interface DebugOptions {
3491
- /** Log all WebSocket send/recv traffic to the console. */
3492
- logWsTraffic?: boolean;
3493
- }
3494
- /** Extended logger with SDK-internal helpers (wsTraffic). */
3495
- interface InternalSDKLogger extends SDKLogger {
3496
- wsTraffic: (options: WsTrafficOptions | WsTrafficRawOptions) => void;
3595
+ type ManagerInitializer = (call: WebRTCCall) => CallManagers;
3596
+ /**
3597
+ * Required initialization configuration for Call constructor.
3598
+ * Calls must be created via {@link CallFactory} which provides these dependencies.
3599
+ */
3600
+ interface CallInitialization {
3601
+ /**
3602
+ * Callback function that creates and wires manager instances
3603
+ */
3604
+ initializeManagers: ManagerInitializer;
3605
+ /**
3606
+ * Device controller for media device access
3607
+ */
3608
+ deviceController: DeviceController;
3609
+ /**
3610
+ * Network change events for feeding recovery pipeline
3611
+ */
3612
+ networkChange$?: Observable<NetworkChangeEvent>;
3497
3613
  }
3498
- /** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
3499
- declare const setLogger: (logger: SDKLogger | null) => void;
3500
- /** Configure debug options (e.g., `{ logWsTraffic: true }`). */
3501
- declare const setDebugOptions: (options: DebugOptions | null) => void;
3502
3614
  /**
3503
- * Set the log level for the built-in logger.
3504
- * Has no effect when a custom logger is set via `setLogger()`.
3615
+ * Concrete WebRTC call implementation.
3616
+ *
3617
+ * Manages the full lifecycle of a call including signaling, media streams,
3618
+ * participants, layout, and event routing. Created via {@link SignalWire.dial}
3619
+ * or received as an inbound call.
3505
3620
  */
3506
- declare const setLogLevel: (level: LogLevel) => void;
3507
- declare const getLogger: () => InternalSDKLogger;
3508
- //#endregion
3509
- //#region src/clients/SignalWire.d.ts
3510
- /** Options for constructing a {@link SignalWire}. */
3511
- interface SignalWireOptions {
3512
- /** Skip automatic WebSocket connection on construction. */
3513
- skipConnection?: boolean;
3514
- /** Skip automatic user registration on construction. */
3515
- skipRegister?: boolean;
3516
- /** Skip monitoring media device changes. */
3517
- skipDeviceMonitoring?: boolean;
3518
- /** Whether to reconnect to previously attached calls. */
3519
- reconnectAttachedCalls?: boolean;
3520
- /** Whether to save preferences. */
3521
- savePreferences?: boolean;
3621
+ declare class WebRTCCall extends Destroyable implements CallManager, Call {
3622
+ clientSession: ClientSession;
3623
+ options: CallOptions;
3624
+ address?: Address | undefined;
3625
+ /** Unique identifier for this call. */
3626
+ readonly id: string;
3627
+ /** Destination URI this call was placed to. */
3628
+ to?: string;
3629
+ private vertoManager;
3630
+ private callEventsManager;
3631
+ private participantFactory;
3632
+ private _errors$;
3633
+ private _status$;
3634
+ private _lastMergedStatus;
3635
+ private _answered$;
3636
+ private _answerMediaOptions?;
3637
+ private _holdState;
3638
+ private _userVariables$;
3639
+ private _statsMonitor?;
3640
+ private _recoveryManager?;
3641
+ private _networkChange$?;
3642
+ private _networkIssues$;
3643
+ private _networkMetrics$;
3644
+ private _isNetworkHealthy$;
3645
+ private _qualityScore$;
3646
+ private _qualityLevel$;
3647
+ private _recoveryState$;
3648
+ private _recoveryEvent$;
3649
+ private _bandwidthConstrained$;
3650
+ private _mediaParamsUpdated$;
3651
+ private _customSubscriptions;
3652
+ private _pushToTalkEnabled;
3653
+ private _remoteAudioMeter;
3654
+ constructor(clientSession: ClientSession, options: CallOptions, initialization: CallInitialization, address?: Address | undefined);
3655
+ /** Observable stream of errors from media, signaling, and peer connection layers. */
3656
+ get errors$(): Observable<CallError>;
3522
3657
  /**
3523
- * Persist the session across page reloads.
3658
+ * @internal Push an error to the call's error stream.
3659
+ * Fatal errors automatically transition the call to `'failed'` and destroy it.
3660
+ */
3661
+ emitError(callError: CallError): void;
3662
+ /** Notify the recovery manager that a verto.modify signaling exchange failed. */
3663
+ notifyModifyFailed(): void;
3664
+ /** Whether this call is `'inbound'` or `'outbound'`. */
3665
+ get direction(): CallDirection;
3666
+ /** Observable of the address associated with this call. */
3667
+ get address$(): Observable<Address | undefined>;
3668
+ /** Display name of the caller. */
3669
+ get fromName(): string | undefined;
3670
+ /** Address URI of the caller. */
3671
+ get from(): string | undefined;
3672
+ /** Display name of the callee. */
3673
+ get toName(): string | undefined;
3674
+ /** Toggles whether incoming video is received. @throws {UnimplementedError} Not yet implemented. */
3675
+ toggleIncomingVideo(): Promise<void>;
3676
+ /** Toggles whether incoming audio is received. @throws {UnimplementedError} Not yet implemented. */
3677
+ toggleIncomingAudio(): Promise<void>;
3678
+ /** @internal Registers an additional call ID for event routing. */
3679
+ addCallId(callId: string): void;
3680
+ /** List of capabilities available in the current call. */
3681
+ get capabilities(): Capability[];
3682
+ /** Current snapshot of all participants in the call. */
3683
+ get participants(): CallParticipant[];
3684
+ /** The local participant, or `null` if not yet joined. */
3685
+ get self(): CallSelfParticipant | null;
3686
+ /** Toggles the call lock state, preventing or allowing new participants from joining. */
3687
+ toggleLock(): Promise<void>;
3688
+ /**
3689
+ * Toggles the hold state of the call (pauses/resumes local media transmission).
3524
3690
  *
3525
- * When `true`, credential, authorization state, and protocol are stored in
3526
- * `localStorage` (survives reload). The DPoP key pair is persisted in
3527
- * IndexedDB. On reload, the SDK restores the session from cache
3528
- * without calling `credentialProvider.authenticate()`.
3691
+ * Distinct from {@link Participant.toggleMute} which mutes individual tracks.
3692
+ */
3693
+ toggleHold(): Promise<void>;
3694
+ /** @throws {UnimplementedError} Not yet implemented. Status tracked via {@link recording$}. */
3695
+ startRecording(): Promise<void>;
3696
+ /** @throws {UnimplementedError} Not yet implemented. Status tracked via {@link streaming$}. */
3697
+ startStreaming(): Promise<void>;
3698
+ /**
3699
+ * Replaces the call's custom metadata.
3700
+ * @param _meta - Metadata object to set.
3701
+ * @throws {UnimplementedError} Not yet implemented.
3702
+ */
3703
+ setMeta(_meta: Record<string, unknown>): Promise<void>;
3704
+ /**
3705
+ * Merges values into the call's custom metadata (unlike {@link setMeta} which replaces).
3706
+ * @param _meta - Metadata to merge.
3707
+ * @throws {UnimplementedError} Not yet implemented.
3708
+ */
3709
+ updateMeta(_meta: Record<string, unknown>): Promise<void>;
3710
+ /** Observable of layout layer positions for all participants. */
3711
+ get layoutLayers$(): Observable<LayoutLayer[]>;
3712
+ /** Current snapshot of layout layers. */
3713
+ get layoutLayers(): LayoutLayer[];
3714
+ /**
3715
+ * Executes a Verto RPC method targeting a specific participant.
3529
3716
  *
3530
- * When `false` (default), session data lives in `sessionStorage` and is
3531
- * lost on reload.
3717
+ * Constructs call context (node_id, call_id, member_id) and sends the RPC request.
3532
3718
  *
3533
- * Both {@link SignalWire.disconnect | disconnect()} and
3534
- * {@link SignalWire.destroy | destroy()} end the session and clear the
3535
- * persisted resume state and attach records; credentials and device
3536
- * preferences survive. Use `resetToDefaults()` for a full wipe, or
3537
- * `unregister()` to temporarily stop receiving inbound calls while keeping
3538
- * the session alive.
3719
+ * @param target - Target {@link MemberTarget} triple, or the local member's
3720
+ * ID string for self-operations (any other string is rejected — a bare
3721
+ * member id cannot carry the remote member's own call context).
3722
+ * @param method - Verto method name (e.g. `'call.mute'`, `'call.member.remove'`).
3723
+ * @param args - Parameters for the RPC method.
3724
+ * @returns The RPC response.
3725
+ * @throws {CallNotReadyError} If the call has no self member context yet.
3726
+ * @throws {InvalidParams} If a string target is not the local member's ID.
3727
+ * @throws {JSONRPCError} If the RPC call returns an error.
3539
3728
  */
3540
- persistSession?: boolean;
3541
- /** Custom storage implementation for persistence. */
3542
- storageImplementation?: Storage;
3543
- /** Custom WebSocket constructor */
3544
- webSocketConstructor?: WebSocketAdapter | NodeSocketAdapter;
3545
- /** Custom WebRTC API provider */
3546
- webRTCApiProvider?: WebRTCApiProvider;
3729
+ executeMethod<T extends JSONRPCResponse = JSONRPCResponse>(target: string | MemberTarget, method: string, args: Record<string, unknown>): Promise<T>;
3547
3730
  /**
3548
- * Custom logger implementation. Must implement the {@link SDKLogger} interface.
3549
- * Pass `null` to restore the built-in logger.
3731
+ * `executeMethod` for a call opened with `callControl: 'in-dialog'`.
3550
3732
  *
3551
- * **Note:** Logger configuration is global — setting it on one instance affects all instances.
3733
+ * Translates the routed transport's calling convention into the in-dialog one. No
3734
+ * `self` tuple is sent, but a `target` is — the same {call_id, member_id} the routed
3735
+ * transport puts in `target` (minus node_id), for self-ops and cross-member ops alike.
3736
+ *
3737
+ * Target shapes are per-verb and irregular, so they are centralised here rather
3738
+ * than left to callers: most verbs take a singular `target`, `call.member.remove`
3739
+ * takes a plural `targets` array, and `call.member.position.set` takes a flat
3740
+ * `targets` of `{call_id, position}` — the one verb keyed on call_id rather than
3741
+ * member_id, so the member triple `Participant.setPosition` built is unwrapped.
3552
3742
  */
3553
- logger?: SDKLogger | null;
3743
+ private executeMethodInDialog;
3554
3744
  /**
3555
- * Log level for the built-in logger.
3556
- * Default: `'warn'`. Set to `'debug'` for verbose SDK output.
3557
- * Has no effect when a custom `logger` is provided.
3745
+ * Sends a `call.*` control verb **in-dialog** via `verto.info`, as an alternative
3746
+ * to the routed {@link executeMethod} transport.
3558
3747
  *
3559
- * **Note:** Logger configuration is global — setting it on one instance affects all instances.
3748
+ * Why both exist: `executeMethod` addresses the member with an explicit
3749
+ * `{node_id, call_id, member_id}` tuple, which does not resolve for every conference,
3750
+ * so the op can fail. An in-dialog frame carries the verb on the member's own
3751
+ * signaling channel instead, so control works without the client needing to know how
3752
+ * the conference is hosted.
3753
+ *
3754
+ * The trade-off is reach: the in-dialog transport is only accepted for calls that
3755
+ * join a conference over SWML (e.g. an SWML `join_conference`); use the routed
3756
+ * default otherwise.
3757
+ *
3758
+ * `params` are sent verbatim — nothing is built for you, which includes the target.
3759
+ * **A self-directed op still needs one**, or it is refused; name yourself explicitly:
3760
+ *
3761
+ * ```ts
3762
+ * const { call_id, member_id } = call.self.target;
3763
+ * await call.sendCommand('call.mute', { channels: ['audio'], target: { call_id, member_id } });
3764
+ * ```
3765
+ *
3766
+ * Never include `node_id` — only the two ids. The shapes are per-verb: most take a
3767
+ * singular `target`, `call.member.remove` takes a plural `targets` array, and
3768
+ * `call.member.position.set` takes a flat `targets: [{call_id, position}]` (the one
3769
+ * verb keyed on `call_id` rather than `member_id`). Verbs that act on the call as a
3770
+ * whole, or that the SDK does not wrap at all, take no target.
3771
+ *
3772
+ * For the typed alternative that handles all of this, create the client with
3773
+ * `callControl: 'in-dialog'` and use the ordinary `Call`/`Participant` methods.
3774
+ *
3775
+ * @internal Not part of the supported surface while the in-dialog transport is still
3776
+ * rolling out. `WebRTCCall` is exported from the package entry, so without this tag
3777
+ * TypeDoc publishes the method — and the example above — as public API.
3778
+ *
3779
+ * @param method - A `call.*` method name (e.g. `'call.mute'`).
3780
+ * @param params - Method parameters, sent verbatim.
3781
+ * @returns The method's own reply, unwrapped from the `verto.info` envelope.
3782
+ * @throws {JSONRPCError} If the control op fails.
3560
3783
  */
3561
- logLevel?: LogLevel;
3562
- /** Debug options for verbose SDK diagnostics (e.g., `{ logWsTraffic: true }`). */
3563
- debug?: DebugOptions;
3564
- }
3565
- /** Options for {@link SignalWire.dial}. Extends {@link MediaOptions} with dial-specific settings. */
3566
- interface DialOptions extends MediaOptions {
3567
- /** Preferred video codecs for this call (overrides global preferences). */
3568
- preferredVideoCodecs?: string[];
3569
- /** Preferred audio codecs for this call (overrides global preferences). */
3570
- preferredAudioCodecs?: string[];
3571
- /** Enable stereo Opus for this call (overrides global preferences). */
3572
- stereo?: boolean;
3573
- /** Optional node ID for routing the call */
3574
- nodeId?: string;
3784
+ sendCommand<T extends JSONRPCResponse = JSONRPCResponse>(method: string, params?: Record<string, unknown>): Promise<T>;
3575
3785
  /**
3576
- * Custom variables sent with the Verto invite. Merged with
3577
- * `client.preferences.userVariables` and any query-string variables on the
3578
- * destination URI; values here take precedence.
3786
+ * The local leg's member triple — sent as `self` in every member RPC
3787
+ * envelope, and as the `target` of call-scoped self-operations (e.g. lock,
3788
+ * layout).
3789
+ *
3790
+ * @throws {CallNotReadyError} Before `call.joined` delivers the self member
3791
+ * context (`selfId`/`nodeId`) — an RPC without it cannot be routed, so fail
3792
+ * fast instead of sending a doomed request.
3579
3793
  */
3580
- userVariables?: Record<string, unknown>;
3581
- }
3582
- /**
3583
- * Main entry point for the SignalWire Browser SDK.
3584
- *
3585
- * Manages authentication, WebSocket transport, call creation, and media devices.
3586
- *
3587
- * @example
3588
- * ```ts
3589
- * const client = new SignalWire(credentialProvider);
3590
- * client.isConnected$.subscribe(connected => console.log('Connected:', connected));
3591
- * const call = await client.dial('/public/my-room');
3592
- * ```
3593
- */
3594
- declare class SignalWire extends Destroyable implements DeviceController {
3595
- /** Global SDK preferences (timeouts, ICE config, media defaults). */
3596
- preferences: ClientPreferences;
3597
- private _user$;
3598
- private _directory$;
3599
- private _transport;
3600
- private _clientSession;
3601
- private _publicSession;
3602
- private _deviceController;
3603
- private _attachManager?;
3604
- private _isConnected$;
3605
- private _isRegistered$;
3606
- private _errors$;
3607
- private _warnings$;
3608
- private _options;
3609
- private _dpopManager?;
3610
- private _refreshCoordinator?;
3611
- private _credentialProvider?;
3612
- private _deps;
3613
- private _networkMonitor?;
3614
- private _visibilityController?;
3615
- private _diagnosticsCollector?;
3616
- private _platformCapabilities?;
3794
+ private get callSelf();
3795
+ /** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
3796
+ get status$(): Observable<CallStatus>;
3797
+ /** Observable of the participants list, emits on join/leave/update. */
3798
+ get participants$(): Observable<CallParticipant[]>;
3799
+ /** Observable of the local (self) participant. */
3800
+ get self$(): Observable<CallSelfParticipant>;
3801
+ /** Observable indicating whether the call is being recorded. */
3802
+ get recording$(): Observable<boolean>;
3803
+ /** Observable indicating whether the call is being streamed. */
3804
+ get streaming$(): Observable<boolean>;
3805
+ /** Observable indicating whether raise-hand priority is active. */
3806
+ get raiseHandPriority$(): Observable<boolean>;
3807
+ /** Observable indicating whether the call room is locked. */
3808
+ get locked$(): Observable<boolean>;
3809
+ /** Observable of custom metadata associated with the call. */
3810
+ get meta$(): Observable<Record<string, unknown>>;
3811
+ /** Observable of the call's capability flags. */
3812
+ get capabilities$(): Observable<Capability[]>;
3813
+ /** Observable of the current layout name. */
3814
+ get layout$(): Observable<string>;
3815
+ /** Current call status. */
3816
+ get status(): CallStatus;
3817
+ /** Whether the call is currently being recorded. */
3818
+ get recording(): boolean;
3819
+ /** Whether the call is currently being streamed. */
3820
+ get streaming(): boolean;
3821
+ /** Whether raise-hand priority is active. */
3822
+ get raiseHandPriority(): boolean;
3823
+ /** Whether the call room is locked. */
3824
+ get locked(): boolean;
3825
+ /** Current custom metadata of the call. */
3826
+ get meta(): Record<string, unknown>;
3827
+ /** Current layout name, or `undefined` if not set. */
3828
+ get layout(): string | undefined;
3829
+ /** Observable of available layout names. */
3830
+ get layouts$(): Observable<string[]>;
3831
+ /** Current snapshot of available layout names. */
3832
+ get layouts(): string[];
3833
+ /** Observable of the local media stream (camera/microphone). */
3834
+ get localStream$(): Observable<MediaStream>;
3835
+ /** Current local media stream, or `null` if not available. */
3836
+ get localStream(): MediaStream | null;
3837
+ /** Observable of the remote media stream from the far end. */
3838
+ get remoteStream$(): Observable<MediaStream>;
3839
+ /** Current remote media stream, or `null` if not available. */
3840
+ get remoteStream(): MediaStream | null;
3841
+ /** Observable of custom user variables associated with the call. */
3842
+ get userVariables$(): Observable<Record<string, unknown>>;
3843
+ /** a copy of the current custom user variables of the call. */
3844
+ get userVariables(): Record<string, unknown>;
3845
+ /** Merge current custom user variables of the call. */
3846
+ set userVariables(variables: Record<string, unknown>);
3847
+ /** Observable of current network health issues (empty array = healthy). */
3848
+ get networkIssues$(): Observable<NetworkIssue[]>;
3849
+ /** Current snapshot of network issues. */
3850
+ get networkIssues(): NetworkIssue[];
3851
+ /** Simple boolean health indicator derived from stats monitor. */
3852
+ get isNetworkHealthy$(): Observable<boolean>;
3853
+ /** Whether the network is currently healthy. */
3854
+ get isNetworkHealthy(): boolean;
3855
+ /** Rolling history of raw network metrics (RTT, jitter, packet loss, bitrate). */
3856
+ get networkMetrics$(): Observable<NetworkMetrics[]>;
3857
+ /** Current snapshot of the metrics rolling window. */
3858
+ get networkMetrics(): NetworkMetrics[];
3859
+ /** Observable of MOS quality score (1-5) computed from stats metrics. */
3860
+ get qualityScore$(): Observable<number>;
3861
+ /** Observable of simplified quality level (excellent/good/fair/poor/critical). */
3862
+ get qualityLevel$(): Observable<QualityLevel$1>;
3863
+ /** Observable of the recovery pipeline state machine. */
3864
+ get recoveryState$(): Observable<RecoveryState$1>;
3865
+ /** Observable of recovery events (keyframe requested, ICE restart, etc.). */
3866
+ get recoveryEvent$(): Observable<RecoveryEvent$1>;
3867
+ /** Observable indicating whether the call is bandwidth-constrained. */
3868
+ get bandwidthConstrained$(): Observable<boolean>;
3869
+ /** Observable that emits when the server pushes media params. */
3870
+ get mediaParamsUpdated$(): Observable<MediaParamsEvent>;
3617
3871
  /**
3618
- * Creates a new SignalWire client and begins connecting.
3619
- *
3620
- * @param credentialProvider - Provider that supplies authentication credentials.
3621
- * @param options - Configuration options (connection, device monitoring, preferences).
3872
+ * @internal Emit a media params update event.
3873
+ * Called by the VertoManager when the server pushes media params.
3622
3874
  */
3623
- constructor(credentialProvider: CredentialProvider | undefined, options?: SignalWireOptions);
3875
+ emitMediaParamsUpdated(event: MediaParamsEvent): void;
3876
+ /** Request a video keyframe via RTCP PLI/FIR. */
3877
+ requestKeyframe(): void;
3878
+ /** Force an ICE restart / re-INVITE. */
3879
+ requestIceRestart(): Promise<void>;
3624
3880
  /**
3625
- * Initializes DPoP if not already set up. Returns the fingerprint on success.
3881
+ * @internal Initialize resilience subsystems when the call reaches 'connected'.
3882
+ * Called from within the status subscription to wire stats and recovery.
3883
+ */
3884
+ private initResilienceSubsystems;
3885
+ /**
3886
+ * Wait for the underlying RTCPeerConnection to reach 'connected' after
3887
+ * triggering an ICE restart. Resolves true on success, false on failure
3888
+ * or if the state doesn't transition within the configured timeout.
3889
+ *
3890
+ * Polls connectionState directly because the recovery manager already
3891
+ * wraps this call in its own withTimeout(); a separate listener-based
3892
+ * implementation would race the outer timeout in subtle ways.
3626
3893
  */
3627
- private initDPoP;
3894
+ private waitForPeerConnectionConnected;
3628
3895
  /**
3629
- * Resolves credentials using cache-first strategy when persistSession is enabled.
3630
- *
3631
- * 1. If persistSession → check localStorage for cached credential
3632
- * 2. If cached and not expired → use it (skip provider.authenticate())
3633
- * 3. If no cache or expired → call provider.authenticate()
3634
- * 4. If no provider AND no cache → throw
3896
+ * @internal Stop and destroy resilience subsystems (on disconnect/destroy).
3897
+ * Clears references so they can be re-created on reconnect.
3635
3898
  */
3636
- private resolveCredentials;
3637
- private validateCredentials;
3638
- /** Persist credential to localStorage when persistSession is enabled. */
3639
- private persistCredential;
3640
- private init;
3641
- private handleAttachments;
3899
+ private stopResilienceSubsystems;
3900
+ /** @internal */
3901
+ createParticipant(memberId: string, selfId?: string | null): Participant | SelfParticipant;
3902
+ /** Observable of the current audio/video send/receive directions. */
3903
+ get mediaDirections$(): Observable<MediaDirections>;
3904
+ /** Current audio/video send/receive directions. */
3905
+ get mediaDirections(): MediaDirections;
3906
+ protected get participantsId$(): Observable<string[]>;
3642
3907
  /**
3643
- * Establishes the WebSocket connection and authenticates the session.
3644
- *
3645
- * ## Reconnection behavior
3646
- *
3647
- * After a successful connection the underlying {@link WebSocketController}
3648
- * automatically attempts to reconnect whenever the socket closes
3649
- * unexpectedly (e.g. network change, server restart). Reconnection uses an
3650
- * **exponential back-off** strategy:
3908
+ * Executes a raw JSON-RPC request on the client session.
3651
3909
  *
3652
- * - First retry after `reconnectDelayMin` (default **0.1 s**).
3653
- * - Each subsequent retry doubles the delay up to `reconnectDelayMax`
3654
- * (default **3 s**).
3655
- * - The delay resets to `reconnectDelayMin` once a connection succeeds.
3656
- * - A per-attempt `connectionTimeout` (default **10 s**) aborts the
3657
- * attempt and schedules the next retry if the server does not respond.
3910
+ * Lower-level than {@link executeMethod} — allows full control over the RPC request structure.
3658
3911
  *
3659
- * Calling {@link disconnect} stops the reconnection loop entirely.
3912
+ * @param request - Complete JSON-RPC request object.
3913
+ * @param options - Optional RPC execution options (timeout, etc.).
3914
+ * @returns The RPC response.
3915
+ * @throws {JSONRPCError} If the RPC call returns an error response.
3916
+ */
3917
+ execute<T extends JSONRPCResponse = JSONRPCResponse>(request: JSONRPCRequest, options?: PendingRPCOptions): Promise<T>;
3918
+ /** Observable of the local participant's member ID. */
3919
+ get selfId$(): Observable<string | null>;
3920
+ /** @internal Lets call creation bound the media and signalling phases apart. */
3921
+ get localMediaSettled$(): Observable<void>;
3922
+ /** Local participant's member ID, or `null` if not joined. */
3923
+ get selfId(): string | null;
3924
+ /** Observable of the server node ID handling this call. */
3925
+ get nodeId$(): Observable<string | null>;
3926
+ /** Server node ID handling this call, or `null`. */
3927
+ get nodeId(): string | null;
3928
+ private isCallSessionEvent;
3929
+ private get callSessionEvents$();
3930
+ /** Observable of call-updated events. */
3931
+ get callUpdated$(): Observable<CallUpdatedPayload>;
3932
+ /** Observable of member-joined events, emitted when a remote participant joins the call. */
3933
+ get memberJoined$(): Observable<MemberJoinedPayload>;
3934
+ /** Observable of member-left events, emitted when a participant leaves the call. */
3935
+ get memberLeft$(): Observable<MemberLeftPayload>;
3936
+ /** Observable of member-updated events (mute, volume, etc.). */
3937
+ get memberUpdated$(): Observable<MemberUpdatedPayload>;
3938
+ /** Observable of member-talking events (speech start/stop). */
3939
+ get memberTalking$(): Observable<MemberTalkingPayload>;
3940
+ /** Observable of call state-change events. */
3941
+ get callStates$(): Observable<CallStatePayload>;
3942
+ /** Observable of layout-changed events. */
3943
+ get layoutUpdates$(): Observable<LayoutChangedPayload>;
3944
+ /** Underlying `RTCPeerConnection`, for advanced use cases. */
3945
+ get rtcPeerConnection(): RTCPeerConnection | undefined;
3946
+ /** Observable of raw signaling events as plain objects. */
3947
+ get signalingEvent$(): Observable<Record<string, unknown>>;
3948
+ /**
3949
+ * Subscribe to a custom signaling event type on this call.
3660
3950
  *
3661
- * ## Message handling during temporary disconnections
3951
+ * Returns a cached observable that filters `callSessionEvents$` for events
3952
+ * whose `event_type` matches the given string. The observable completes
3953
+ * when the call is destroyed.
3662
3954
  *
3663
- * While the socket is not in the `connected` state, **outgoing messages
3664
- * are queued** in an internal buffer. Once the connection is
3665
- * re-established the queue is flushed in order so no outgoing RPC call is
3666
- * lost.
3955
+ * Unlike `signalingEvent$` (which only emits known call-level event types),
3956
+ * this method also matches custom/user-defined event types.
3667
3957
  *
3668
- * **Incoming** server-to-client messages that arrive while the socket is
3669
- * down are *not* buffered by the SDK — they are expected to be
3670
- * re-delivered by the server after the session is re-authenticated.
3671
- * Active RPC calls that were awaiting a response will time out
3672
- * (default **5 s**) and reject with an `RPCTimeoutError`; callers should
3673
- * handle this and retry if appropriate.
3958
+ * The SDK does not validate event type strings --- the server decides
3959
+ * whether a given type is valid.
3674
3960
  *
3675
- * The connection status can be observed via the `status$` observable on
3676
- * the transport layer, which emits `'connecting'`, `'connected'`,
3677
- * `'reconnecting'`, `'disconnecting'`, or `'disconnected'`.
3678
- */
3679
- connect(): Promise<void>;
3680
- /**
3681
- * Observable that emits the {@link User} profile once fetched,
3682
- * or `undefined` before authentication completes.
3961
+ * @param eventType - The event type to subscribe to (e.g. `'my.custom.event'`).
3962
+ * @returns An observable that emits matching signaling events.
3683
3963
  *
3684
3964
  * @example
3685
3965
  * ```ts
3686
- * client.user$.subscribe(u => {
3687
- * if (u) console.log('Logged in as', u.email);
3966
+ * call.subscribe('my.custom.event').subscribe(event => {
3967
+ * console.log('Custom event:', event);
3688
3968
  * });
3689
3969
  * ```
3690
3970
  */
3691
- get user$(): Observable<User | undefined>;
3692
- /** Current user snapshot, or `undefined` if not yet authenticated. */
3693
- get user(): User | undefined;
3971
+ subscribe(eventType: string): Observable<Record<string, unknown>>;
3972
+ get webrtcMessages$(): Observable<WebrtcMessagePayload>;
3973
+ get callEvent$(): Observable<WebrtcMessagePayload | CallJoinedPayload | CallLeftPayload | CallUpdatedPayload | CallStatePayload | CallPlayPayload | CallConnectPayload | RoomUpdatedPayload | MemberUpdatedPayload | MemberJoinedPayload | MemberLeftPayload | MemberTalkingPayload | LayoutChangedPayload | ConversationMessagePayload>;
3974
+ get layoutEvent$(): Observable<LayoutChangedPayload>;
3694
3975
  /**
3695
- * Observable that emits the {@link Directory} instance once the client is connected,
3696
- * or `undefined` while disconnected. Subscribe to this to safely wait for the directory
3697
- * to become available without risking an error.
3976
+ * Hangs up the call and releases all resources.
3977
+ *
3978
+ * Sends a Verto `bye` to the server, transitions status to `'disconnecting'`,
3979
+ * then destroys the call. After this, the call instance is no longer usable.
3698
3980
  *
3699
3981
  * @example
3700
3982
  * ```ts
3701
- * client.directory$.subscribe(dir => {
3702
- * if (dir) dir.addresses$.subscribe(console.log);
3703
- * });
3983
+ * await call.hangup();
3704
3984
  * ```
3705
3985
  */
3706
- get directory$(): Observable<Directory | undefined>;
3707
- /**
3708
- * Current directory snapshot, or `undefined` if the client is not yet connected.
3709
- * Prefer {@link directory$} when you need to react to the directory becoming available.
3710
- */
3711
- get directory(): Directory | undefined;
3712
- /** Observable that emits when the user registration state changes. */
3713
- get isRegistered$(): Observable<boolean>;
3714
- /** Whether the user is currently registered. */
3715
- get isRegistered(): boolean;
3716
- /** Whether the client is currently connected. */
3717
- get isConnected(): boolean;
3718
- /** Observable that emits when the connection state changes. */
3719
- get isConnected$(): Observable<boolean>;
3720
- /** Observable that emits `true` when the client is both connected and authenticated. */
3721
- get ready$(): Observable<boolean>;
3722
- /** Observable stream of errors from transport, authentication, and devices. */
3723
- get errors$(): Observable<Error>;
3986
+ hangup(): Promise<void>;
3724
3987
  /**
3725
- * Observable stream of non-fatal SDK warnings.
3988
+ * Sends DTMF digits on the call.
3726
3989
  *
3727
- * Subscribe to detect SDK behaviors that affect session liveness or developer-facing
3728
- * contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
3729
- * refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
3730
- * scope. Discriminated by `code`.
3990
+ * @param dtmf - The digit string to send (e.g. `'1234#'`).
3731
3991
  *
3732
- * Independent from {@link errors$}: existing error consumers are not notified.
3733
- */
3734
- get warnings$(): Observable<SDKWarning>;
3735
- /** Platform WebRTC capabilities detected at construction time. */
3736
- get platformCapabilities(): PlatformCapabilities;
3737
- /** Observable that emits when the SDK auto-switches a device. */
3738
- get deviceRecovered$(): Observable<DeviceRecoveryEvent>;
3739
- /**
3740
- * Export a structured diagnostic bundle for support/debugging.
3741
- * Includes connection events, call summaries, and device changes.
3742
- */
3743
- exportDiagnostics(): SessionDiagnostics;
3744
- /**
3745
- * Initialize resilience subsystems. Non-fatal: any failure is logged and
3746
- * the SDK continues working without the failing subsystem.
3992
+ * @example
3993
+ * ```ts
3994
+ * await call.sendDigits('1234#');
3995
+ * ```
3747
3996
  */
3748
- private initResilienceSubsystems;
3997
+ sendDigits(dtmf: string): Promise<void>;
3998
+ /** Observable of WebRTC-specific signaling messages. */
3999
+ /** Observable of call-level signaling events. */
4000
+ /** Observable of layout-changed signaling events. */
3749
4001
  /**
3750
- * Disconnects the WebSocket and tears down the current session.
3751
- *
3752
- * Ends the session identified by the protocol and clears its persisted
3753
- * resume state (`authorization_state` + protocol) and attach records
3754
- * together — a later {@link connect} with the same credentials starts a
3755
- * fresh session and cannot reattach to the ended session's calls.
3756
- * Credentials and device preferences are preserved. To temporarily stop
3757
- * receiving inbound calls while keeping the session alive, use
3758
- * `unregister()` instead.
4002
+ * Accepts an inbound call, optionally overriding media options for the answer.
3759
4003
  *
3760
- * The client can be reconnected by calling {@link connect} again,
3761
- * which creates a fresh transport and session.
3762
- */
3763
- disconnect(): Promise<void>;
3764
- /**
3765
- * Tear down the current transport / session / attach manager. Safe to call
3766
- * when nothing has been initialized yet (e.g. first connect()).
3767
- */
3768
- private teardownTransportAndSession;
3769
- private waitAuthentication;
3770
- /**
3771
- * Registers the user as online to receive inbound calls and events.
4004
+ * @param options - Optional media constraints for the answer (audio/video).
3772
4005
  *
3773
- * Waits for authentication to complete before sending the registration.
3774
- * If the initial attempt fails, reauthentication is attempted automatically.
4006
+ * @example
4007
+ * ```ts
4008
+ * // Accept with defaults
4009
+ * call.answer();
3775
4010
  *
3776
- * @throws {InvalidCredentialsError} If registration and reauthentication both fail.
4011
+ * // Accept audio-only
4012
+ * call.answer({ audio: true, video: false });
4013
+ * ```
4014
+ * @see {@link reject} to decline the call instead.
4015
+ * @see {@link answered$} to observe the acceptance state.
3777
4016
  */
3778
- register(): Promise<void>;
4017
+ answer(options?: MediaOptions): void;
4018
+ /** Media options provided when answering. Used internally by the VertoManager. */
4019
+ get answerMediaOptions(): MediaOptions | undefined;
3779
4020
  /**
3780
- * Unregisters the user, going offline for inbound calls.
4021
+ * Rejects an inbound call, preventing media negotiation.
3781
4022
  *
3782
- * The WebSocket connection remains open; use {@link disconnect} to fully close it.
4023
+ * @see {@link answer} to accept the call instead.
4024
+ * @see {@link answered$} to observe the rejection state.
3783
4025
  */
3784
- unregister(): Promise<void>;
4026
+ reject(): void;
4027
+ /** Observable that emits `true` when answered, `false` when rejected. */
4028
+ get answered$(): Observable<boolean>;
3785
4029
  /**
3786
- * Places an outbound call to the given destination.
3787
- *
3788
- * Waits for authentication before dialing. Media options are merged from
3789
- * saved preferences, destination query parameters (e.g. `?channel=video`),
3790
- * and the provided `options` (highest priority).
3791
- *
3792
- * Returns a {@link Call} in `'ringing'` state. Subscribe to {@link Call.status$}
3793
- * to track progression through `'connected'` → `'disconnected'`.
3794
- *
3795
- * @param destination - Address URI string (e.g. `'/public/my-room'`) or {@link Address} instance.
3796
- * @param options - Media and dial options (audio/video, device constraints). Overrides defaults.
3797
- * @returns The created {@link Call} instance.
3798
- * @throws {Error} If authentication is not complete or call creation fails.
4030
+ * Sets the call layout and, optionally, individual participant positions.
3799
4031
  *
3800
- * @example
3801
- * ```ts
3802
- * const call = await client.dial('/public/conference', {
3803
- * audio: true,
3804
- * video: true,
3805
- * });
3806
- * call.status$.subscribe(status => console.log('Call:', status));
3807
- * ```
3808
- */
3809
- dial(destination: string | Address, options?: DialOptions): Promise<Call>;
3810
- /**
3811
- * Runs a multi-phase connectivity test against the given destination.
4032
+ * The gateway `call.layout.set` DTO has **no** `positions` member, so when
4033
+ * `positions` is provided this method issues a `call.member.position.set`
4034
+ * request per member (via {@link Participant.setPosition}, which keys each
4035
+ * position by that member's own call context) alongside `call.layout.set`
4036
+ * (issue #19400, Flag #6).
3812
4037
  *
3813
- * The test checks:
3814
- * 1. **Signaling** -- WebSocket connected, RTT measurement
3815
- * 2. **Devices** -- getUserMedia succeeds with selected (or specified) devices
3816
- * 3. **ICE/TURN** -- gathers ICE candidates to verify STUN/TURN reachability
3817
- * 4. **Media/bandwidth** (unless `skipMediaTest`) -- dials the destination,
3818
- * collects getStats() for `duration` seconds, computes bandwidth estimates
4038
+ * **These operations are NOT atomic.** The layout is applied first, then each
4039
+ * member position sequentially, so members may briefly flash into their
4040
+ * default slots before being moved to the requested positions. Targeted
4041
+ * members are validated upfront, though: when any of them has no
4042
+ * {@link Participant.target | member call context} yet, the whole call
4043
+ * rejects before any request is sent and the layout is left unchanged.
3819
4044
  *
3820
- * @param destination - A destination to dial for the media test (e.g. `'/private/network-test'`).
3821
- * @param options - Preflight options (duration, skipMediaTest, device overrides).
3822
- * @returns A {@link PreflightResult} describing connectivity health.
4045
+ * @param layout - Layout name (must be one of {@link layouts}).
4046
+ * @param positions - Optional map of member IDs to {@link VideoPosition} values.
4047
+ * When omitted or empty, only the layout is changed.
4048
+ * @throws {InvalidParams} If the layout is not in the available {@link layouts}.
4049
+ * @throws {ParticipantNotReadyError} If a targeted member's call context has
4050
+ * not been received yet — thrown before any request is sent.
3823
4051
  *
3824
4052
  * @example
3825
4053
  * ```ts
3826
- * const result = await client.preflight('/private/network-test', { duration: 5 });
3827
- * if (!result.ok) console.warn('Connectivity issues:', result.warnings);
4054
+ * await call.setLayout('grid-responsive', {
4055
+ * [participantId]: 'reserved-0',
4056
+ * });
3828
4057
  * ```
3829
4058
  */
3830
- preflight(destination: string, options?: PreflightOptions): Promise<PreflightResult>;
3831
- /** The underlying client session for advanced RPC operations. */
3832
- get session(): ClientSessionWrapper;
3833
- /** Observable list of available audio input (microphone) devices. */
3834
- get audioInputDevices$(): Observable<MediaDeviceInfo[]>;
3835
- /** Current snapshot of available audio input devices. */
3836
- get audioInputDevices(): MediaDeviceInfo[];
3837
- /** Observable list of available audio output (speaker) devices. */
3838
- get audioOutputDevices$(): Observable<MediaDeviceInfo[]>;
3839
- /** Current snapshot of available audio output devices. */
3840
- get audioOutputDevices(): MediaDeviceInfo[];
3841
- /** Observable list of available video input (camera) devices. */
3842
- get videoInputDevices$(): Observable<MediaDeviceInfo[]>;
3843
- /** Current snapshot of available video input devices. */
3844
- get videoInputDevices(): MediaDeviceInfo[];
3845
- /** Observable of the currently selected audio input device. */
3846
- get selectedAudioInputDevice$(): Observable<MediaDeviceInfo | null>;
3847
- /** Observable of the currently selected audio output device. */
3848
- get selectedAudioOutputDevice$(): Observable<MediaDeviceInfo | null>;
3849
- /** Observable of the currently selected video input device. */
3850
- get selectedVideoInputDevice$(): Observable<MediaDeviceInfo | null>;
3851
- /** Currently selected audio input device, or `null` if none. */
3852
- get selectedAudioInputDevice(): MediaDeviceInfo | null;
3853
- /** Currently selected audio output device, or `null` if none. */
3854
- get selectedAudioOutputDevice(): MediaDeviceInfo | null;
3855
- /** Currently selected video input device, or `null` if none. */
3856
- get selectedVideoInputDevice(): MediaDeviceInfo | null;
3857
- /** Media track constraints for the selected audio input device. Returns `false` when disabled. */
3858
- get selectedAudioInputDeviceConstraints(): MediaTrackConstraints | boolean;
3859
- /** Media track constraints for the selected video input device. Returns `false` when disabled. */
3860
- get selectedVideoInputDeviceConstraints(): MediaTrackConstraints | boolean;
3861
- /** Converts a `MediaDeviceInfo` to track constraints suitable for `getUserMedia`. */
3862
- deviceInfoToConstraints(deviceInfo: MediaDeviceInfo | null): MediaTrackConstraints;
3863
- /** Sets the preferred audio input device. */
3864
- selectAudioInputDevice(device: MediaDeviceInfo | null): void;
3865
- /** Sets the preferred video input device. */
3866
- selectVideoInputDevice(device: MediaDeviceInfo | null): void;
3867
- /** Sets the preferred audio output device. */
3868
- selectAudioOutputDevice(device: MediaDeviceInfo | null): void;
4059
+ setLayout(layout: string, positions?: Record<string, VideoPosition>): Promise<void>;
3869
4060
  /**
3870
- * Apply the currently selected audio output device to an HTMLMediaElement
3871
- * (e.g. the `<audio>` or `<video>` element the consumer attached the
3872
- * remote stream to). Uses `HTMLMediaElement.setSinkId` under the hood.
3873
- * Returns a `Promise<boolean>`: `true` if the sink was applied,
3874
- * `false` if the browser doesn't support `setSinkId` or no device is
3875
- * selected.
4061
+ * Transfers the call to another destination.
3876
4062
  *
3877
- * @example
3878
- * ```ts
3879
- * audioEl.srcObject = call.remoteStream;
3880
- * await client.applySelectedAudioOutputDevice(audioEl);
3881
- * ```
4063
+ * @param options - Transfer configuration including the target destination.
4064
+ * @see {@link status$} to observe the transfer progress.
3882
4065
  */
3883
- applySelectedAudioOutputDevice(element: HTMLMediaElement): Promise<boolean>;
3884
- /** Starts monitoring for media device changes (connect/disconnect). */
3885
- enableDeviceMonitoring(): void;
3886
- /** Stops monitoring for media device changes. */
3887
- disableDeviceMonitoring(): void;
4066
+ transfer(options: TransferOptions): Promise<void>;
3888
4067
  /**
3889
- * Returns the capabilities of a media device.
3890
- * @param deviceInfo - The device to query.
3891
- * @returns The device capabilities, or `null` if unavailable.
4068
+ * Set the local microphone gain as a percentage applied before transmission.
4069
+ *
4070
+ * - `0` = silent
4071
+ * - `100` = unity (no change, default)
4072
+ * - `200` = 2× digital boost (max; expect clipping / noise amplification)
4073
+ *
4074
+ * Values are clamped to [0, 200]. Engages the local audio pipeline on
4075
+ * first use (one-time cost).
4076
+ *
4077
+ * Note: this is a **digital** multiplier applied in a Web Audio GainNode
4078
+ * between your mic track and the RTCRtpSender — it does not change the
4079
+ * physical mic's hardware sensitivity. Browsers' autoGainControl can
4080
+ * fight the setting; call {@link setAutoGainControl}(false) for
4081
+ * predictable behaviour.
4082
+ *
4083
+ * @param value - Gain percentage (0..200; 100 = unity).
3892
4084
  */
3893
- getDeviceCapabilities(deviceInfo: MediaDeviceInfo): Promise<MediaTrackCapabilities | null>;
4085
+ setLocalMicrophoneGain(value: number): void;
4086
+ /** Observable of the current local microphone gain (0..200, where 100 = unity). */
4087
+ get localMicrophoneGain$(): Observable<number>;
3894
4088
  /**
3895
- * Checks whether a device is still available and usable.
3896
- * @param deviceInfo - The device to validate, or `null`.
3897
- * @returns `true` if the device is valid and available. Returns `false` for `null`, audio output devices, or unavailable devices.
4089
+ * Observable of the RMS audio level of the local microphone, 0..1.
4090
+ * Emits at ~30fps while a mic track is active. Engages the local audio
4091
+ * pipeline on first subscription.
3898
4092
  */
3899
- isValidDevice(deviceInfo: MediaDeviceInfo | null): Promise<boolean>;
3900
- /** Injects a storage manager into the device controller for persistence. */
3901
- setStorageManager(storageManager: StorageManager): void;
3902
- /** Clears all device state and re-enumerates. */
3903
- clearDeviceState(): Promise<void>;
3904
- /** Forces a device re-enumeration. */
3905
- enumerateDevices(): Promise<void>;
3906
- /** Disables audio input (receive-only mode). No audio track will be acquired. */
3907
- disableAudioInput(): void;
3908
- /** Re-enables audio input, restoring the last selection or auto-selecting. */
3909
- enableAudioInput(): void;
3910
- /** Disables video input (receive-only mode). No video track will be acquired. */
3911
- disableVideoInput(): void;
3912
- /** Re-enables video input, restoring the last selection or auto-selecting. */
3913
- enableVideoInput(): void;
3914
- /** Observable that emits `true` when video input is disabled (receive-only). */
3915
- get videoInputDisabled$(): Observable<boolean>;
3916
- /** Observable that emits `true` when audio input is disabled (receive-only). */
3917
- get audioInputDisabled$(): Observable<boolean>;
3918
- /** Whether video input is currently disabled. */
3919
- get videoInputDisabled(): boolean;
3920
- /** Whether audio input is currently disabled. */
3921
- get audioInputDisabled(): boolean;
4093
+ get localAudioLevel$(): Observable<number>;
3922
4094
  /**
3923
- * Triggers the browser's media permission dialog and captures the user's device selections.
3924
- *
3925
- * @param options - Which permissions to request.
3926
- * @param options.audio - Whether to request audio permission.
3927
- * @param options.video - Whether to request video permission.
3928
- * @returns The permission result with selected devices.
4095
+ * Observable that is `true` while the local participant is speaking
4096
+ * (RMS level above the VAD threshold, with hold time to avoid flicker).
3929
4097
  */
3930
- requestMediaPermissions(options?: {
3931
- audio?: boolean;
3932
- video?: boolean;
3933
- }): Promise<PermissionResult>;
4098
+ get localSpeaking$(): Observable<boolean>;
3934
4099
  /**
3935
- * Clears all SDK-persisted state and resets to defaults.
4100
+ * Enable push-to-talk: while {@link setPushToTalkActive} has been called
4101
+ * with `false`, the microphone gain is forced to 0; calling
4102
+ * {@link setPushToTalkActive} with `true` restores the configured gain.
4103
+ * Use this instead of mute/unmute for instant talk/silence transitions
4104
+ * because it doesn't rebuild the track.
3936
4105
  *
3937
- * This clears device preferences, device history, authorization state,
3938
- * attached call IDs, and all SDK storage keys, then re-enumerates devices.
4106
+ * This method installs the pipeline but does not attach any keyboard
4107
+ * listener — consumers bind the key themselves and call
4108
+ * {@link setPushToTalkActive} on keydown/keyup.
3939
4109
  */
3940
- resetToDefaults(): Promise<void>;
4110
+ enablePushToTalk(): void;
4111
+ /** Disable push-to-talk; mic gain returns to the configured value. */
4112
+ disablePushToTalk(): void;
3941
4113
  /**
3942
- * Destroys the client, clearing timers and releasing all resources.
4114
+ * While push-to-talk is enabled, sets the talk state. `true` = transmitting,
4115
+ * `false` = silent. No-op if push-to-talk has not been enabled.
4116
+ */
4117
+ setPushToTalkActive(active: boolean): void;
4118
+ /**
4119
+ * Toggle echo cancellation on the local mic at runtime. Applied via
4120
+ * `track.applyConstraints`; browsers that don't honour runtime constraints
4121
+ * (notably iOS Safari) fall back to re-acquiring the track with the new
4122
+ * constraint set and plumbing the replacement through the local audio
4123
+ * pipeline if one is active.
3943
4124
  *
3944
- * Intentionally destroying the client ends its session: the resume state
3945
- * (`authorization_state` + protocol) and the attach records are both
3946
- * cleared. Credentials and device preferences are preserved — use
3947
- * {@link resetToDefaults} for a full wipe. To temporarily stop receiving
3948
- * inbound calls while keeping the session alive, use `unregister()`.
4125
+ * @returns whether the constraint reached the microphone. `false` is an
4126
+ * outcome rather than an error — a leg sending media the SDK did not capture
4127
+ * is left alone — so a UI that reflects the toggle must read it. Any failure
4128
+ * behind a `false` is also reported on {@link errors$}.
3949
4129
  */
3950
- destroy(): void;
3951
- }
3952
- //#endregion
3953
- //#region src/utils/embeddableCall.d.ts
3954
- /** Options for {@link embeddableCall}. */
3955
- interface EmbeddableCallOptions {
3956
- /** Destination URI to call. */
3957
- to: string;
3958
- /** Embed token for authentication. */
3959
- embedToken: string;
3960
- /** SignalWire host URL. */
3961
- host: string;
3962
- }
3963
- /**
3964
- * Creates a call using an embed token for simple, embeddable integrations.
3965
- *
3966
- * Handles client creation, authentication, and dialing in a single call.
3967
- *
3968
- * @param options - Embed token, host, and destination.
3969
- * @returns The created {@link Call} instance.
3970
- */
3971
- declare function embeddableCall(options: EmbeddableCallOptions): Promise<Call>;
3972
- //#endregion
3973
- //#region src/dependencies/StaticCredentialProvider.d.ts
3974
- /**
3975
- * Credential provider that returns a fixed set of credentials.
3976
- *
3977
- * Use when the token is already available (e.g. from a backend endpoint).
3978
- *
3979
- * @example
3980
- * ```ts
3981
- * const provider = new StaticCredentialProvider({ token: 'my-sat-token' });
3982
- * const client = new SignalWire(provider);
3983
- * ```
3984
- */
3985
- declare class StaticCredentialProvider implements CredentialProvider {
3986
- private credentials;
3987
- constructor(credentials: SDKCredential);
3988
- /** Returns the static credentials. */
3989
- authenticate(): Promise<SDKCredential>;
3990
- }
3991
- //#endregion
3992
- //#region src/dependencies/EmbedTokenCredentialProvider.d.ts
3993
- /** Credential provider that exchanges an embed token for a SAT via the host's token endpoint. */
3994
- declare class EmbedTokenCredentialProvider implements CredentialProvider {
3995
- private host;
3996
- private embedToken;
3997
- constructor(host: string, embedToken: string);
3998
- private fetchSAT;
3999
- authenticate(): Promise<{
4000
- token: string;
4001
- expiry_at: number;
4002
- }>;
4003
- refresh(): Promise<{
4004
- token: string;
4005
- expiry_at: number;
4006
- }>;
4007
- }
4008
- //#endregion
4009
- //#region src/controllers/LocalAudioPipeline.d.ts
4010
- /**
4011
- * Options for {@link LocalAudioPipeline}.
4012
- */
4013
- interface LocalAudioPipelineOptions {
4014
- /** Factory for AudioContext — override for tests. Defaults to `new AudioContext()`. */
4015
- audioContextFactory?: () => AudioContext;
4016
- /** Initial gain (0..2, where 1 is unity). Defaults to 1. */
4017
- initialGain?: number;
4018
- /** RMS level [0..1] above which speaking$ emits true. Defaults to {@link VAD_THRESHOLD}. */
4019
- speakingThreshold?: number;
4130
+ setEchoCancellation(enabled: boolean): Promise<boolean>;
4131
+ /**
4132
+ * Toggle browser noise suppression on the local mic at runtime.
4133
+ * @returns whether the constraint reached the microphone.
4134
+ */
4135
+ setNoiseSuppression(enabled: boolean): Promise<boolean>;
4136
+ /**
4137
+ * Toggle browser automatic gain control on the local mic at runtime.
4138
+ * @returns whether the constraint reached the microphone.
4139
+ */
4140
+ setAutoGainControl(enabled: boolean): Promise<boolean>;
4141
+ /**
4142
+ * Observable of the aggregate remote audio level, 0..1 RMS. The server
4143
+ * delivers a single mixed audio stream for all remote participants — this
4144
+ * meter reports that mix. Per-participant audio is not available client-side.
4145
+ *
4146
+ * Engages a shared AudioContext on first subscription (cheap — one
4147
+ * AnalyserNode, no GainNode, no destination) so it does not affect the
4148
+ * caller's audio element playback.
4149
+ */
4150
+ get remoteAudioLevel$(): Observable<number>;
4151
+ /** Destroys the call, releasing all resources and subscriptions. */
4152
+ destroy(): void;
4020
4153
  /**
4021
- * Milliseconds of silence below the threshold before speaking$ flips back to
4022
- * false. Prevents flicker on normal speech gaps. Defaults to {@link VAD_HOLD_MS}.
4154
+ * @internal Send a verto.subscribe message to add an event type to the
4155
+ * server's subscription list for this call. Returns the underlying RPC
4156
+ * promise so callers can decide whether to cache the observable on success
4157
+ * or retry on failure.
4023
4158
  */
4024
- speakingHoldMs?: number;
4025
- /** Polling interval for level$. Defaults to {@link AUDIO_LEVEL_POLL_INTERVAL_MS}. */
4026
- pollIntervalMs?: number;
4159
+ private _sendVertoSubscribe;
4027
4160
  }
4161
+ //#endregion
4162
+ //#region src/core/entities/Directory.d.ts
4028
4163
  /**
4029
- * Web Audio pipeline for the local microphone stream.
4030
- *
4031
- * Wraps the raw mic `MediaStreamTrack` in a graph of:
4032
- *
4033
- * ```
4034
- * MediaStreamAudioSourceNode → GainNode → AnalyserNode → MediaStreamAudioDestinationNode
4035
- * ```
4164
+ * Directory interface for managing addresses
4036
4165
  *
4037
- * The {@link outputTrack} from the destination node is what callers should
4038
- * attach to the `RTCRtpSender` in place of the raw mic track. The same
4039
- * destination track is reused across input changes (device switch, mute /
4040
- * unmute track replacement) so the sender reference stays stable — only the
4041
- * source end of the graph is rebuilt.
4166
+ * This is the public API contract for address directory functionality.
4167
+ * It provides access to addresses, loading capabilities, and search functionality.
4042
4168
  *
4043
- * The pipeline owns a single {@link AudioContext}. Callers must invoke
4044
- * {@link destroy} to release it when the call ends.
4169
+ * @public
4045
4170
  */
4046
- declare class LocalAudioPipeline extends Destroyable {
4047
- private readonly _audioContext;
4048
- private readonly _gainNode;
4049
- private readonly _analyser;
4050
- private readonly _destination;
4051
- private readonly _analyserBuffer;
4052
- private readonly _speakingThreshold;
4053
- private readonly _speakingHoldMs;
4054
- private readonly _pollIntervalMs;
4055
- private _inputSource;
4056
- private _inputStream;
4057
- private _lastSpokeAt;
4058
- private _gain$;
4059
- /** 1 when audio should pass through, 0 when silenced by PTT. */
4060
- private _pttMultiplier;
4061
- constructor(options?: LocalAudioPipelineOptions);
4062
- /** Observable of the current gain value (0..2). */
4063
- get gain$(): Observable<number>;
4064
- /** Current gain value (0..2). */
4065
- get gain(): number;
4171
+ interface Directory extends AddressProvider<Address> {
4066
4172
  /**
4067
- * Processed output track to attach to the RTCRtpSender. Stable reference
4068
- * across input changes, so `sender.replaceTrack(pipeline.outputTrack)` only
4069
- * needs to be called once.
4173
+ * Observable stream of all addresses in the directory
4174
+ * Emits a new array whenever addresses are added, removed, or updated
4070
4175
  */
4071
- get outputTrack(): MediaStreamTrack;
4176
+ readonly addresses$: Observable<Address[]>;
4072
4177
  /**
4073
- * Root-mean-square audio level of the input signal, 0..1. Emits on a fixed
4074
- * interval (~30fps by default).
4178
+ * Current snapshot of all addresses in the directory
4075
4179
  */
4076
- get level$(): Observable<number>;
4180
+ readonly addresses: Address[];
4077
4181
  /**
4078
- * Boolean VAD derived from {@link level$}. True while level ≥ threshold or
4079
- * during the hold window after the last frame that crossed the threshold.
4182
+ * Observable indicating whether more addresses can be loaded from the server
4080
4183
  */
4081
- get speaking$(): Observable<boolean>;
4184
+ readonly hasMore$: Observable<boolean>;
4082
4185
  /**
4083
- * Set gain multiplier applied to the input signal. 0 = silence,
4084
- * 1 = unity, 2 = 2x. Values are clamped to [0, 2]. The effective gain on
4085
- * the graph also respects the current PTT state.
4186
+ * Observable indicating the current loading state
4187
+ * Emits `true` when loading, `false` when idle
4086
4188
  */
4087
- setGain(value: number): void;
4189
+ readonly loading$: Observable<boolean>;
4190
+ readonly loading: boolean;
4088
4191
  /**
4089
- * Silence the graph when `active = false`, otherwise restore the configured
4090
- * gain. Use this from a PTT handler: released → `false`, held → `true`.
4091
- * Orthogonal to {@link setGain} — once PTT returns to active, the last
4092
- * configured gain reappears.
4192
+ * Load more addresses from the server
4193
+ * Only loads if `hasMore` is true
4093
4194
  */
4094
- setPTTActive(active: boolean): void;
4095
- private applyEffectiveGain;
4195
+ loadMore(): void;
4096
4196
  /**
4097
- * Wire a new raw mic track as the pipeline's input. Replaces any previous
4098
- * input source and reconnects the graph so {@link outputTrack} continues
4099
- * to emit the processed audio. Pass `null` to disconnect the input (the
4100
- * output track stays alive but emits silence).
4197
+ * Get a specific address by ID
4101
4198
  *
4102
- * Also resumes the underlying AudioContext on attach — Chrome creates it
4103
- * in a suspended state and the graph won't process (the destination
4104
- * track emits silence) until resume() succeeds.
4105
- */
4106
- setInputTrack(track: MediaStreamTrack | null): void;
4107
- destroy(): void;
4108
- private computeLevel;
4109
- private evaluateSpeaking;
4110
- }
4111
- //#endregion
4112
- //#region src/controllers/RTCPeerConnectionController.d.ts
4113
- interface RTCPeerConnectionControllerOptions extends MediaOptions {
4114
- callId?: string;
4115
- rtcConfiguration?: RTCConfiguration;
4116
- simulcast?: boolean;
4117
- sfu?: boolean;
4118
- msStreamsNumber?: number;
4119
- propose: RTCPeerConnectionPropose;
4120
- iceServers?: RTCIceServer[];
4121
- disableUdpIceServers?: boolean;
4122
- relayOnly?: boolean;
4123
- iceCandidateTimeout?: number;
4124
- iceGatheringTimeout?: number;
4125
- webRTCApiProvider?: WebRTCApiProvider;
4126
- /** Per-call preferred video codecs (overrides global preferences). */
4127
- preferredVideoCodecs?: string[];
4128
- /** Per-call preferred audio codecs (overrides global preferences). */
4129
- preferredAudioCodecs?: string[];
4130
- /** Per-call stereo Opus setting (overrides global preferences). */
4131
- stereo?: boolean;
4132
- }
4133
- type RTCPeerConnectionControllerOptionsPartial = Partial<RTCPeerConnectionControllerOptions>;
4134
- interface UpdateSDPStatusParams {
4135
- status: 'received' | 'sent' | 'failed';
4136
- sdp?: string;
4137
- }
4138
- declare class RTCPeerConnectionController extends Destroyable {
4139
- protected options: RTCPeerConnectionControllerOptionsPartial;
4140
- readonly id: string;
4141
- firstSDPExchangeCompleted: boolean;
4142
- sdpInit?: RTCSessionDescriptionInit;
4143
- private negotiationNeeded$;
4144
- private deviceController;
4145
- private localStreamController;
4146
- private transceiverController?;
4147
- readonly localDescription$: Observable<RTCSessionDescription | null>;
4148
- peerConnection?: RTCPeerConnection;
4149
- private initPromise?;
4150
- private connectionTimeout;
4151
- private connectionTimer?;
4152
- private oniceconnectionstatechangeHandler;
4153
- private onconnectionstatechangeHandler;
4154
- private onsignalingstatechangeHandler;
4155
- private onicegatheringstatechangeHandler;
4156
- private onnegotiationneededHandler;
4157
- private updateSelectedInputDevice;
4158
- private _isNegotiating$;
4159
- private _iceGatheringController?;
4160
- private _memberId;
4161
- private _type;
4162
- private _iceConnectionState$;
4163
- private _connectionState$;
4164
- private _signalingState$;
4165
- private _iceGatheringState$;
4166
- private _errors$;
4167
- private _iceCandidates$;
4168
- private _initialized$;
4169
- private _remoteDescription$;
4170
- private _remoteStream$;
4171
- private _remoteOfferMediaDirections;
4172
- private _localAudioPipeline;
4173
- constructor(options?: RTCPeerConnectionControllerOptionsPartial, remoteSessionDescription?: string, deviceController?: DeviceController);
4174
- private get iceGatheringController();
4175
- private get shouldEmitLocalDescription();
4176
- private removeConnectionTimer;
4177
- setMemberId(memberId: string | null): void;
4178
- get memberId(): string | null;
4179
- stopTrackSender(kind: 'audio' | 'video' | 'both', options?: {
4180
- updateTransceiverDirection: boolean;
4181
- }): void;
4182
- private stopRawAudioInputForPipeline;
4183
- get isNegotiating$(): Observable<boolean>;
4184
- get isNegotiating(): boolean;
4185
- updateMediaDevicesOptions(options: MediaOptions): void;
4186
- get iceGatheringState$(): Observable<RTCIceGatheringState>;
4187
- get mediaTrackEnded$(): Observable<MediaStreamTrack>;
4188
- get errors$(): Observable<Error>;
4189
- get iceCandidates$(): Observable<RTCIceCandidate[]>;
4190
- get initialized$(): Observable<boolean>;
4191
- get remoteDescription$(): Observable<RTCSessionDescription | null>;
4192
- get localStream$(): Observable<MediaStream | null>;
4193
- get remoteStream$(): Observable<MediaStream | null>;
4194
- get localAudioTracks$(): Observable<MediaStreamTrack[]>;
4195
- get localVideoTracks$(): Observable<MediaStreamTrack[]>;
4196
- get iceConnectionState$(): Observable<RTCIceConnectionState>;
4197
- get connectionState$(): Observable<RTCPeerConnectionState>;
4198
- get signalingState$(): Observable<RTCSignalingState>;
4199
- get type(): RTCPeerConnectionType;
4200
- get propose(): RTCPeerConnectionPropose;
4201
- get isAdditionalDevice(): boolean;
4202
- get isMainDevice(): boolean;
4203
- get isScreenShare(): boolean;
4204
- protected get iceServers(): RTCIceServer[];
4205
- private get rtcConfiguration();
4206
- get receiveVideo(): boolean;
4207
- get receiveAudio(): boolean;
4208
- get localStream(): MediaStream | null;
4209
- get remoteStream(): MediaStream | null;
4210
- private get inputAudioDeviceConstraints();
4211
- private get inputVideoDeviceConstraints();
4212
- private get WebRTCPeerConnectionConstructor();
4213
- private get offerOptions();
4214
- private get answerOptions();
4215
- /**
4216
- * Initialize the RTCPeerConnection and setup event listeners.
4217
- * Called automatically when localDescription$ is subscribed to (deferred pattern).
4218
- * Uses Promise memoization to ensure initialization only happens once,
4219
- * even if called concurrently.
4199
+ * @param addressId - The address ID to retrieve
4200
+ * @returns The address instance, or undefined if not found
4220
4201
  */
4221
- private init;
4202
+ get(addressId: string): Address | undefined;
4222
4203
  /**
4223
- * Internal initialization implementation.
4224
- * Should only be called via init() to ensure single execution.
4204
+ * Find an address ID by searching for a name
4205
+ *
4206
+ * @param uri - The address name to search for
4207
+ * @returns Promise resolving to the address ID, or undefined if not found
4225
4208
  */
4226
- private doInit;
4227
- private setupPeerConnection;
4228
- private startNegotiation;
4209
+ findAddressIdByURI(uri: string): Promise<string | undefined>;
4210
+ }
4211
+ //#endregion
4212
+ //#region src/interfaces/SessionState.d.ts
4213
+ /**
4214
+ * Extended session interface that adds call management and authentication
4215
+ * state on top of the narrow ClientSession contract.
4216
+ *
4217
+ * Accessible via `client.session`. Call and CallFactory continue to depend
4218
+ * only on the narrow ClientSession interface.
4219
+ */
4220
+ interface SessionState extends ClientSession {
4229
4221
  /**
4230
- * Create an SDP offer and set it as local description.
4222
+ * Observable stream of currently active inbound calls.
4223
+ * Filters `calls$` to only include calls with `direction === 'inbound'`.
4231
4224
  */
4232
- private createOffer;
4233
- updateAnswerStatus({
4234
- status,
4235
- sdp
4236
- }: UpdateSDPStatusParams): Promise<void>;
4237
- updateOfferStatus({
4238
- status,
4239
- sdp
4240
- }: UpdateSDPStatusParams): Promise<void>;
4241
- /**
4242
- * Accept an inbound call by creating the SDP answer.
4243
- * Optionally override media options before the answer is generated.
4244
- * Must be called after initialization for inbound (answer-type) connections.
4225
+ readonly incomingCalls$: Observable<Call[]>;
4226
+ /**
4227
+ * Current snapshot of active inbound calls.
4245
4228
  */
4246
- acceptInbound(mediaOverrides?: MediaOptions): Promise<void>;
4247
- private handleOfferReceived;
4248
- private readyToConnect;
4249
- private setRemoteDescriptionBefore;
4250
- protected setLocalDescription(params: RTCSessionDescriptionInit): Promise<void>;
4251
- setLocalDescriptionBefore(sdp?: string): Promise<string>;
4229
+ readonly incomingCalls: Call[];
4252
4230
  /**
4253
- * Create an SDP answer and set it as local description.
4231
+ * Observable stream of all currently active calls (both inbound and outbound).
4254
4232
  */
4255
- private createAnswer;
4233
+ readonly calls$: Observable<Call[]>;
4256
4234
  /**
4257
- * Setup event listeners on RTCPeerConnection for state changes.
4235
+ * Current snapshot of all active calls.
4258
4236
  */
4259
- private setupEventListeners;
4260
- private negotiationEnded;
4237
+ readonly calls: Call[];
4261
4238
  /**
4262
- * Trigger an ICE restart through the existing negotiation pipeline.
4263
- *
4264
- * This creates an offer with iceRestart: true and goes through the full
4265
- * SDP pipeline (setLocalDescription → ICE gathering → localDescription$ emission).
4266
- * The caller should NOT send the SDP manually — the existing
4267
- * setupLocalDescriptionHandler in VertoManager will pick up the emission
4268
- * from localDescription$ and send it as a verto.modify.
4269
- *
4270
- * Unlike calling pc.createOffer/setLocalDescription directly, this method:
4271
- * - Sets _isNegotiating$ so ICEGatheringController arms its timers
4272
- * - Waits for ICE gathering to complete before localDescription$ emits
4273
- * - Goes through setLocalDescriptionBefore() for any SDP munging
4239
+ * Observable that emits `true` once the session has been authenticated,
4240
+ * and `false` after disconnect.
4274
4241
  */
4275
- triggerIceRestart(relayOnly?: boolean): Promise<void>;
4276
- private restoreIceTransportPolicy;
4242
+ readonly authenticated$: Observable<boolean>;
4277
4243
  /**
4278
- * Setup track handling for remote tracks.
4244
+ * Current authentication state.
4245
+ * Returns `true` if the session is currently authenticated.
4279
4246
  */
4280
- private setupTrackHandling;
4281
- private setupLocalTracks;
4282
- /** True for a main connection with no local media to send. */
4283
- private hasNoLocalMediaToSend;
4284
- /** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
4285
- private get requestedMediaKinds();
4247
+ readonly authenticated: boolean;
4248
+ }
4249
+ //#endregion
4250
+ //#region src/managers/ClientSessionManager.d.ts
4251
+ /**
4252
+ * Discriminated union for session authentication state.
4253
+ * clientBound is tracked separately via _wasClientBound (sticky flag)
4254
+ * to avoid dual sources of truth.
4255
+ */
4256
+ type SessionAuthState = {
4257
+ kind: 'unauthenticated';
4258
+ } | {
4259
+ kind: 'authenticated';
4260
+ };
4261
+ declare class ClientSessionManager extends Destroyable implements SessionState {
4262
+ private readonly getCredential;
4263
+ private readonly transport;
4264
+ private readonly storage;
4265
+ private readonly authorizationStateKey;
4266
+ private readonly attachManager;
4267
+ private readonly dpopManager?;
4268
+ private callFactory;
4269
+ private readonly agent;
4270
+ private readonly eventAcks;
4271
+ initialized$: Observable<boolean>;
4272
+ private authorizationState$;
4273
+ private connectVersion;
4286
4274
  /**
4287
- * Handle a local media acquisition failure with a typed, semantically
4288
- * accurate MediaAccessError created at the acquisition site:
4289
- * - Auxiliary connections (screenshare / additional-device) throw a
4290
- * non-fatal error — VertoManager surfaces it and the call is unaffected.
4291
- * - The main connection degrades to receive-only when allowed (default),
4292
- * otherwise fails with a fatal error.
4275
+ * Optional hook called before a fresh connect on reconnect.
4276
+ * Used by SignalWire to refresh expired credentials before re-authenticating.
4277
+ * @internal
4293
4278
  */
4294
- private handleLocalMediaFailure;
4279
+ onBeforeReconnect?: () => Promise<void>;
4295
4280
  /**
4296
- * Negotiate receive-only m-lines when there are no local tracks to send.
4297
- * Only offer-type connections add transceivers — answer-type connections
4298
- * reuse the transceivers created from the remote offer.
4281
+ * Session-wide call control transport (see {@link ClientSession.callControl}).
4282
+ * Set from {@link SignalWireOptions.callControl} by SignalWire after construction;
4283
+ * defaults to `'routed'`. Read by every Call, so it needs no per-call persistence.
4299
4284
  */
4300
- private setupReceiveOnlyTransceivers;
4301
- private getUserMedia;
4302
- private getDisplayMedia;
4303
- private setupRemoteTracks;
4304
- restoreTrackSender(kind: 'audio' | 'video' | 'both'): Promise<void>;
4305
- private restoreRawAudioInputForPipeline;
4285
+ callControl: 'routed' | 'in-dialog';
4286
+ private _authorization$;
4287
+ private _errors$;
4288
+ private _directory?;
4289
+ private _authState$;
4290
+ /** Sticky flag — once true, stays true for the session lifetime. */
4291
+ private _wasClientBound;
4292
+ private _userInfo$;
4293
+ private _calls$;
4294
+ private _iceServers$;
4295
+ constructor(getCredential: () => SDKCredential, transport: TransportManager, storage: StorageManager, authorizationStateKey: string, deviceController: DeviceController, attachManager: AttachManager, webRTCApiProvider: WebRTCApiProvider, dpopManager?: CryptoController | undefined, networkChange$?: Observable<NetworkChangeEvent>);
4296
+ get incomingCalls$(): Observable<Call[]>;
4297
+ get incomingCalls(): Call[];
4298
+ get userInfo$(): Observable<Address | null>;
4299
+ get userInfo(): Address | null;
4300
+ get calls$(): Observable<Call[]>;
4301
+ get calls(): Call[];
4302
+ get iceServers(): RTCIceServer[] | undefined;
4303
+ get authorization$(): Observable<Authorization | undefined>;
4304
+ get authorization(): Authorization | undefined;
4305
+ get errors$(): Observable<Error>;
4306
+ get authenticated$(): Observable<boolean>;
4307
+ get authenticated(): boolean;
4306
4308
  /**
4307
- * Return the lazily-created {@link LocalAudioPipeline}, constructing it on
4308
- * first access. On creation the current audio sender's track is routed
4309
- * through the pipeline (input → gain → analyser → destination) and the
4310
- * sender is switched to emit the processed track. Returns `null` when no
4311
- * audio sender exists yet (pre-negotiation).
4309
+ * Whether this session is client-bound (using a Client Bound SAT).
4310
+ * When client-bound, DPoP proof creation failures are treated as
4311
+ * authentication errors rather than silently degraded.
4312
+ * @internal
4312
4313
  */
4313
- ensureLocalAudioPipeline(): LocalAudioPipeline | null;
4314
- /** The active LocalAudioPipeline, or null if it hasn't been created yet. */
4315
- get localAudioPipeline(): LocalAudioPipeline | null;
4316
- private applyLocalAudioPipelineToSender;
4314
+ get clientBound(): boolean;
4315
+ /** @internal Current auth state for debugging/testing. */
4316
+ get authState(): SessionAuthState;
4317
4317
  /**
4318
- * Add a local media track to the peer connection.
4319
- * @param track - The MediaStreamTrack to add
4318
+ * Set the directory instance
4319
+ * Called by SignalWire after directory is created
4320
+ * @internal
4320
4321
  */
4321
- addLocalTrack(track: MediaStreamTrack): void;
4322
+ setDirectory(directory: Directory): void;
4323
+ execute<T extends JSONRPCResponse = JSONRPCResponse>(request: JSONRPCRequest, options?: PendingRPCOptions): Promise<T>;
4324
+ send(message: JSONSerializable): void;
4325
+ private init;
4326
+ private setupMessageHandlers;
4327
+ private loadAuthorizationStateFromStorage;
4328
+ private updateAuthorizationStateInStorage;
4329
+ private get authStateEvent$();
4330
+ get signalingEvent$(): Observable<(Omit<{
4331
+ event_type: "webrtc.message";
4332
+ event_channel: EventChannel;
4333
+ timestamp: number;
4334
+ project_id?: string;
4335
+ node_id?: string;
4336
+ is_author?: boolean;
4337
+ params: WebrtcMessagePayload;
4338
+ }, "event_channel" | "project_id" | "node_id"> & {
4339
+ event_channel: string;
4340
+ project_id: string;
4341
+ node_id: string;
4342
+ }) | {
4343
+ event_type: "signalwire.authorization.state";
4344
+ params: SignalwireAuthorizationStatePayload;
4345
+ } | (Omit<{
4346
+ event_type: "call.joined";
4347
+ event_channel: EventChannel;
4348
+ timestamp: number;
4349
+ project_id?: string;
4350
+ node_id?: string;
4351
+ is_author?: boolean;
4352
+ params: CallJoinedPayload;
4353
+ }, "event_channel"> & {
4354
+ event_channel: string;
4355
+ }) | (Omit<{
4356
+ event_type: "call.left";
4357
+ event_channel: EventChannel;
4358
+ timestamp: number;
4359
+ project_id?: string;
4360
+ node_id?: string;
4361
+ is_author?: boolean;
4362
+ params: CallLeftPayload;
4363
+ }, "event_channel"> & {
4364
+ event_channel: string;
4365
+ }) | (Omit<{
4366
+ event_type: "call.updated";
4367
+ event_channel: EventChannel;
4368
+ timestamp: number;
4369
+ project_id?: string;
4370
+ node_id?: string;
4371
+ is_author?: boolean;
4372
+ params: CallUpdatedPayload;
4373
+ }, "event_channel"> & {
4374
+ event_channel: string;
4375
+ }) | (Omit<{
4376
+ event_type: "call.state";
4377
+ event_channel: EventChannel;
4378
+ timestamp: number;
4379
+ project_id?: string;
4380
+ node_id?: string;
4381
+ is_author?: boolean;
4382
+ params: CallStatePayload;
4383
+ }, "event_channel"> & {
4384
+ event_channel: string;
4385
+ }) | (Omit<{
4386
+ event_type: "call.play";
4387
+ event_channel: EventChannel;
4388
+ timestamp: number;
4389
+ project_id?: string;
4390
+ node_id?: string;
4391
+ is_author?: boolean;
4392
+ params: CallPlayPayload;
4393
+ }, "event_channel"> & {
4394
+ event_channel: string;
4395
+ }) | (Omit<{
4396
+ event_type: "call.connect";
4397
+ event_channel: EventChannel;
4398
+ timestamp: number;
4399
+ project_id?: string;
4400
+ node_id?: string;
4401
+ is_author?: boolean;
4402
+ params: CallConnectPayload;
4403
+ }, "event_channel"> & {
4404
+ event_channel: string;
4405
+ }) | (Omit<{
4406
+ event_type: "room.updated";
4407
+ event_channel: EventChannel;
4408
+ timestamp: number;
4409
+ project_id?: string;
4410
+ node_id?: string;
4411
+ is_author?: boolean;
4412
+ params: RoomUpdatedPayload;
4413
+ }, "event_channel"> & {
4414
+ event_channel: string;
4415
+ }) | Omit<{
4416
+ event_type: "member.updated";
4417
+ event_channel: EventChannel;
4418
+ timestamp: number;
4419
+ project_id?: string;
4420
+ node_id?: string;
4421
+ is_author?: boolean;
4422
+ params: MemberUpdatedPayload;
4423
+ }, never> | Omit<{
4424
+ event_type: "member.joined";
4425
+ event_channel: EventChannel;
4426
+ timestamp: number;
4427
+ project_id?: string;
4428
+ node_id?: string;
4429
+ is_author?: boolean;
4430
+ params: MemberJoinedPayload;
4431
+ }, never> | Omit<{
4432
+ event_type: "member.left";
4433
+ event_channel: EventChannel;
4434
+ timestamp: number;
4435
+ project_id?: string;
4436
+ node_id?: string;
4437
+ is_author?: boolean;
4438
+ params: MemberLeftPayload;
4439
+ }, never> | Omit<{
4440
+ event_type: "member.talking";
4441
+ event_channel: EventChannel;
4442
+ timestamp: number;
4443
+ project_id?: string;
4444
+ node_id?: string;
4445
+ is_author?: boolean;
4446
+ params: MemberTalkingPayload;
4447
+ }, never> | Omit<{
4448
+ event_type: "layout.changed";
4449
+ event_channel: EventChannel;
4450
+ timestamp: number;
4451
+ project_id?: string;
4452
+ node_id?: string;
4453
+ is_author?: boolean;
4454
+ params: LayoutChangedPayload;
4455
+ }, never> | (Omit<{
4456
+ event_type: "conversation.message";
4457
+ event_channel: EventChannel;
4458
+ timestamp: number;
4459
+ project_id?: string;
4460
+ node_id?: string;
4461
+ is_author?: boolean;
4462
+ params: ConversationMessagePayload;
4463
+ }, "event_channel" | "timestamp" | "is_author"> & {
4464
+ event_channel: string;
4465
+ timestamp: string;
4466
+ is_author: boolean;
4467
+ }) | (Omit<{
4468
+ event_type: "conversation.message.updated";
4469
+ event_channel: EventChannel;
4470
+ timestamp: number;
4471
+ project_id?: string;
4472
+ node_id?: string;
4473
+ is_author?: boolean;
4474
+ params: ConversationMessagePayload;
4475
+ }, "event_channel" | "timestamp" | "is_author"> & {
4476
+ event_channel: string;
4477
+ timestamp: string;
4478
+ is_author: boolean;
4479
+ })>;
4480
+ private get vertoInvite$();
4481
+ private get vertoAttach$();
4482
+ private get contexts();
4483
+ private get eventing();
4484
+ private get topics();
4485
+ private get authentication();
4486
+ connect(): Promise<void>;
4487
+ private handleAuthenticationError;
4322
4488
  /**
4323
- * Remove a local media track from the peer connection.
4324
- * @param trackId - The ID of the track to remove
4489
+ * Clear the resume state (authorization_state + protocol) and ask the
4490
+ * transport to reconnect. The `connected` event re-triggers
4491
+ * `authenticate()`, which now has no stored state and so performs a fresh
4492
+ * connect.
4493
+ *
4494
+ * This is the stale-auth-state recovery helper used by handleAuthError:
4495
+ * the server rejected a reconnect, so the resume state is discarded and a
4496
+ * fresh connect follows. Attach records are deliberately preserved — the
4497
+ * session lives on through the reconnect and reattachCalls() needs the
4498
+ * stored call references afterwards. Do NOT add detachAll() here.
4499
+ *
4500
+ * Connect-time recovery only. A *request* refused on an already
4501
+ * authenticated session is never healed here: dropping the resume state
4502
+ * destroys the association between the socket and the previous session,
4503
+ * which is what reattach depends on. That path mints a fresh credential and
4504
+ * reauthenticates instead (see `SignalWire.recoverAndRetry`).
4505
+ *
4506
+ * For public teardown (disconnect/destroy), use {@link teardownSessionState}
4507
+ * instead, which clears the attach records as well.
4325
4508
  */
4326
- removeLocalTrack(trackId: string): void;
4509
+ private discardResumeStateAndReconnect;
4510
+ cleanupStoredConnectionParams(): Promise<void>;
4327
4511
  /**
4328
- * Replace all existing media tracks with a new media track.
4329
- * Convenience method for single-track scenarios.
4330
- * @param track - The MediaStreamTrack to set
4512
+ * Public-teardown helper for disconnect()/destroy(). Clears the resume
4513
+ * state (authorization_state + protocol) AND the attach records as one
4514
+ * atomic unit.
4515
+ *
4516
+ * The two stores are coupled: the backend only honors attach records
4517
+ * within the session identified by the resume state, so ending the
4518
+ * session must clear both. Clearing one without the other strands records
4519
+ * no future session can honor (disconnect) or revives a session the
4520
+ * developer explicitly ended (destroy).
4521
+ *
4522
+ * Distinct from {@link cleanupStoredConnectionParams}, which keeps the
4523
+ * attach records for the stale-auth-state recovery path.
4331
4524
  */
4332
- setLocalTrack(track: MediaStreamTrack): void;
4333
- updateSendersConstraints(kind: 'audio' | 'video', constraints?: MediaTrackConstraints): Promise<void>;
4525
+ teardownSessionState(): Promise<void>;
4526
+ protected updateAuthState(authorization_state: string): Promise<void>;
4527
+ reauthenticate(token: string, dpopToken?: string, options?: {
4528
+ clientBound?: boolean;
4529
+ }): Promise<void>;
4530
+ private authenticate;
4531
+ disconnect(): Promise<void>;
4532
+ private createInboundCall;
4334
4533
  /**
4335
- * Replace the current audio track with a new one using the given constraints.
4336
- * Used for server-pushed audio constraint changes where applyConstraints
4337
- * fails on iOS Safari. Stops the current track, acquires a new one via
4338
- * getUserMedia, and replaces the sender track.
4534
+ * Handle a server-pushed verto.attach event at the session level.
4535
+ *
4536
+ * On page reload the server detects the reconnected session and pushes
4537
+ * verto.attach for any active calls. If a call object already exists
4538
+ * (network blip, no reload), the per-call handler in VertoManager deals
4539
+ * with it. This method only creates a new call object when no existing
4540
+ * one matches the callID.
4339
4541
  */
4340
- replaceAudioTrackWithConstraints(constraints: MediaTrackConstraints): Promise<void>;
4542
+ private handleVertoAttach;
4543
+ createOutboundCall(destination: string | Address, options?: CallOptions): Promise<Call>;
4544
+ private createCall;
4545
+ destroy(): void;
4546
+ }
4547
+ declare class ClientSessionWrapper implements SessionState {
4548
+ private clientSessionManager;
4549
+ constructor(clientSessionManager: ClientSessionManager);
4550
+ get authenticated$(): Observable<boolean>;
4551
+ get authenticated(): boolean;
4341
4552
  /**
4342
- * Clean up resources and close the peer connection.
4343
- * Completes all observables to prevent memory leaks.
4553
+ * Whether the session is using a Client Bound SAT (DPoP). Sticky — set
4554
+ * when the binding is established or restored from a resumed session's
4555
+ * server authorization.
4344
4556
  */
4345
- destroy(): void;
4346
- private removeAllListeners;
4347
- private stopRemoteTracks;
4348
- get mediaDirections(): {
4349
- audio: RTCRtpTransceiverDirection;
4350
- video: RTCRtpTransceiverDirection;
4351
- };
4352
- protected _setRemoteDescription(params: RTCSessionDescriptionInit): Promise<void>;
4557
+ get clientBound(): boolean;
4558
+ get signalingEvent$(): Observable<(Omit<{
4559
+ event_type: "webrtc.message";
4560
+ event_channel: EventChannel;
4561
+ timestamp: number;
4562
+ project_id?: string;
4563
+ node_id?: string;
4564
+ is_author?: boolean;
4565
+ params: WebrtcMessagePayload;
4566
+ }, "event_channel" | "project_id" | "node_id"> & {
4567
+ event_channel: string;
4568
+ project_id: string;
4569
+ node_id: string;
4570
+ }) | {
4571
+ event_type: "signalwire.authorization.state";
4572
+ params: SignalwireAuthorizationStatePayload;
4573
+ } | (Omit<{
4574
+ event_type: "call.joined";
4575
+ event_channel: EventChannel;
4576
+ timestamp: number;
4577
+ project_id?: string;
4578
+ node_id?: string;
4579
+ is_author?: boolean;
4580
+ params: CallJoinedPayload;
4581
+ }, "event_channel"> & {
4582
+ event_channel: string;
4583
+ }) | (Omit<{
4584
+ event_type: "call.left";
4585
+ event_channel: EventChannel;
4586
+ timestamp: number;
4587
+ project_id?: string;
4588
+ node_id?: string;
4589
+ is_author?: boolean;
4590
+ params: CallLeftPayload;
4591
+ }, "event_channel"> & {
4592
+ event_channel: string;
4593
+ }) | (Omit<{
4594
+ event_type: "call.updated";
4595
+ event_channel: EventChannel;
4596
+ timestamp: number;
4597
+ project_id?: string;
4598
+ node_id?: string;
4599
+ is_author?: boolean;
4600
+ params: CallUpdatedPayload;
4601
+ }, "event_channel"> & {
4602
+ event_channel: string;
4603
+ }) | (Omit<{
4604
+ event_type: "call.state";
4605
+ event_channel: EventChannel;
4606
+ timestamp: number;
4607
+ project_id?: string;
4608
+ node_id?: string;
4609
+ is_author?: boolean;
4610
+ params: CallStatePayload;
4611
+ }, "event_channel"> & {
4612
+ event_channel: string;
4613
+ }) | (Omit<{
4614
+ event_type: "call.play";
4615
+ event_channel: EventChannel;
4616
+ timestamp: number;
4617
+ project_id?: string;
4618
+ node_id?: string;
4619
+ is_author?: boolean;
4620
+ params: CallPlayPayload;
4621
+ }, "event_channel"> & {
4622
+ event_channel: string;
4623
+ }) | (Omit<{
4624
+ event_type: "call.connect";
4625
+ event_channel: EventChannel;
4626
+ timestamp: number;
4627
+ project_id?: string;
4628
+ node_id?: string;
4629
+ is_author?: boolean;
4630
+ params: CallConnectPayload;
4631
+ }, "event_channel"> & {
4632
+ event_channel: string;
4633
+ }) | (Omit<{
4634
+ event_type: "room.updated";
4635
+ event_channel: EventChannel;
4636
+ timestamp: number;
4637
+ project_id?: string;
4638
+ node_id?: string;
4639
+ is_author?: boolean;
4640
+ params: RoomUpdatedPayload;
4641
+ }, "event_channel"> & {
4642
+ event_channel: string;
4643
+ }) | Omit<{
4644
+ event_type: "member.updated";
4645
+ event_channel: EventChannel;
4646
+ timestamp: number;
4647
+ project_id?: string;
4648
+ node_id?: string;
4649
+ is_author?: boolean;
4650
+ params: MemberUpdatedPayload;
4651
+ }, never> | Omit<{
4652
+ event_type: "member.joined";
4653
+ event_channel: EventChannel;
4654
+ timestamp: number;
4655
+ project_id?: string;
4656
+ node_id?: string;
4657
+ is_author?: boolean;
4658
+ params: MemberJoinedPayload;
4659
+ }, never> | Omit<{
4660
+ event_type: "member.left";
4661
+ event_channel: EventChannel;
4662
+ timestamp: number;
4663
+ project_id?: string;
4664
+ node_id?: string;
4665
+ is_author?: boolean;
4666
+ params: MemberLeftPayload;
4667
+ }, never> | Omit<{
4668
+ event_type: "member.talking";
4669
+ event_channel: EventChannel;
4670
+ timestamp: number;
4671
+ project_id?: string;
4672
+ node_id?: string;
4673
+ is_author?: boolean;
4674
+ params: MemberTalkingPayload;
4675
+ }, never> | Omit<{
4676
+ event_type: "layout.changed";
4677
+ event_channel: EventChannel;
4678
+ timestamp: number;
4679
+ project_id?: string;
4680
+ node_id?: string;
4681
+ is_author?: boolean;
4682
+ params: LayoutChangedPayload;
4683
+ }, never> | (Omit<{
4684
+ event_type: "conversation.message";
4685
+ event_channel: EventChannel;
4686
+ timestamp: number;
4687
+ project_id?: string;
4688
+ node_id?: string;
4689
+ is_author?: boolean;
4690
+ params: ConversationMessagePayload;
4691
+ }, "event_channel" | "timestamp" | "is_author"> & {
4692
+ event_channel: string;
4693
+ timestamp: string;
4694
+ is_author: boolean;
4695
+ }) | (Omit<{
4696
+ event_type: "conversation.message.updated";
4697
+ event_channel: EventChannel;
4698
+ timestamp: number;
4699
+ project_id?: string;
4700
+ node_id?: string;
4701
+ is_author?: boolean;
4702
+ params: ConversationMessagePayload;
4703
+ }, "event_channel" | "timestamp" | "is_author"> & {
4704
+ event_channel: string;
4705
+ timestamp: string;
4706
+ is_author: boolean;
4707
+ })>;
4708
+ get iceServers(): RTCIceServer[] | undefined;
4709
+ get callControl(): 'routed' | 'in-dialog';
4710
+ execute<T extends JSONRPCResponse = JSONRPCResponse>(request: JSONRPCRequest, options?: PendingRPCOptions): Promise<T>;
4711
+ get incomingCalls$(): Observable<Call[]>;
4712
+ get incomingCalls(): Call[];
4713
+ get calls$(): Observable<Call[]>;
4714
+ get calls(): Call[];
4353
4715
  }
4354
4716
  //#endregion
4355
- //#region src/interfaces/WebRTCVerto.d.ts
4717
+ //#region src/core/types/warnings.types.d.ts
4356
4718
  /**
4357
- * Extended interface for WebRTC Verto Manager
4358
- * Includes WebRTC-specific state and peer connection management
4719
+ * Non-fatal warning emitted via {@link SignalWire.warnings$ | client.warnings$}.
4720
+ *
4721
+ * Use to detect SDK behaviors that affect session liveness or developer-facing
4722
+ * contracts but do not warrant disconnection. Discriminated by `code`.
4723
+ *
4724
+ * Existing consumers of `errors$` are NOT notified — `warnings$` is a separate
4725
+ * channel so application code can react to warnings without triggering
4726
+ * error-handling code paths (e.g., disconnect cascades, user-facing toasts).
4359
4727
  */
4360
- interface WebRTCVerto extends VertoManager {
4361
- readonly selfId$: Observable<string | null>;
4362
- readonly selfId: string | null;
4363
- readonly nodeId$: Observable<string | null>;
4364
- readonly nodeId: string | null;
4365
- readonly localStream$: Observable<MediaStream>;
4366
- readonly localStream: MediaStream | null;
4367
- readonly remoteStream$: Observable<MediaStream>;
4368
- readonly remoteStream: MediaStream | null;
4369
- readonly mediaDirections$: Observable<MediaDirections>;
4370
- readonly mediaDirections: MediaDirections;
4371
- readonly signalingStatus$: Observable<SignalingStatus>;
4372
- readonly mainPeerConnection: RTCPeerConnectionController;
4373
- bye(cause?: string): Promise<void>;
4374
- sendDigits(dtmf: string): Promise<void>;
4375
- hold(): Promise<void>;
4376
- unhold(): Promise<void>;
4377
- destroy(): void;
4378
- transfer(options: TransferOptions): Promise<void>;
4379
- /** Request a video keyframe via verto.modify. */
4380
- requestKeyframe?: () => void;
4381
- /** Request an ICE restart via verto.modify with iceRestart offer. */
4382
- requestIceRestart?: (relayOnly?: boolean) => Promise<void>;
4383
- /** Request an ICE restart on all active peer connections (multi-leg). */
4384
- requestIceRestartAll?: (relayOnly?: boolean) => Promise<void>;
4385
- /** Request keyframes on all video-receiving legs (skips send-only screen share). */
4386
- requestKeyframeAll?: () => void;
4387
- /** Lazily create (or return) the local audio pipeline for the main peer connection. */
4388
- ensureLocalAudioPipeline(): LocalAudioPipeline | null;
4389
- /** Current local audio pipeline, or null if it has not been created yet. */
4390
- readonly localAudioPipeline: LocalAudioPipeline | null;
4391
- }
4392
- //#endregion
4393
- //#region src/managers/CallEventsManager.d.ts
4394
- interface WebRTCCallEventManagerOptions {}
4395
- /** @internal */
4396
- declare class CallEventsManager extends Destroyable {
4397
- protected webRtcCallSession: CallManager;
4398
- protected options: WebRTCCallEventManagerOptions;
4399
- private selfId?;
4400
- private originCallId?;
4401
- private callIds;
4402
- private roomSessionIds;
4403
- private _participants$;
4404
- private _self$;
4405
- private _sessionState$;
4406
- constructor(webRtcCallSession: CallManager, options?: WebRTCCallEventManagerOptions);
4407
- get participants$(): Observable<CallParticipant[]>;
4408
- get participants(): CallParticipant[];
4409
- get self$(): Observable<CallSelfParticipant>;
4410
- isRoomSessionIdValid(roomSessionId: string): boolean;
4411
- addCallId(callId: string): void;
4412
- isCallIdValid(callId: string): boolean;
4413
- get recording$(): Observable<boolean>;
4414
- get recordings$(): Observable<Record<string, unknown>[]>;
4415
- get streaming$(): Observable<boolean>;
4416
- get streams$(): Observable<Record<string, unknown>[]>;
4417
- get playbacks$(): Observable<Record<string, unknown>[]>;
4418
- get raiseHandPriority$(): Observable<boolean>;
4419
- get locked$(): Observable<boolean>;
4420
- get meta$(): Observable<Record<string, unknown>>;
4421
- get capabilities$(): Observable<Capability[]>;
4422
- get layout$(): Observable<string>;
4423
- get layouts$(): Observable<string[]>;
4424
- get layoutLayers$(): Observable<LayoutLayer[]>;
4425
- get self(): CallSelfParticipant | null;
4426
- get layoutLayers(): LayoutLayer[];
4427
- get recording(): boolean;
4428
- get streaming(): boolean;
4429
- get raiseHandPriority(): boolean;
4430
- get locked(): boolean;
4431
- get meta(): Record<string, unknown>;
4432
- get layout(): string | undefined;
4433
- get layouts(): string[];
4434
- get capabilities(): Capability[];
4435
- isSessionEvent(id: string): boolean;
4436
- protected initSubscriptions(): void;
4437
- private updateParticipantPositions;
4438
- updateLayouts(): void;
4439
- private updateParticipants;
4440
- private upsertParticipant;
4441
- private get callJoinedEvent$();
4442
- private get layoutChangedEvent$();
4443
- private get memberUpdates$();
4444
- destroy(): void;
4445
- }
4446
- //#endregion
4447
- //#region src/managers/CallRecoveryManager.d.ts
4448
- type RecoveryState$1 = 'idle' | 'debouncing' | 'recovering' | 'cooldown';
4449
- interface RecoveryEvent$1 {
4450
- action: 'keyframe_requested' | 'reinvite_started' | 'reinvite_succeeded' | 'reinvite_failed' | 'reinvite_timeout' | 'max_attempts_reached' | 'signal_reconnect' | 'full_reconnect' | 'video_disabled' | 'video_restored';
4451
- reason: string;
4452
- attempt?: number;
4453
- maxAttempts?: number;
4454
- timestamp: number;
4728
+ type SDKWarning = CredentialRefreshFallbackWarning | CredentialNoRefreshHandlerWarning;
4729
+ /**
4730
+ * Diagnostic detail for {@link CredentialRefreshFallbackWarning}. Stable
4731
+ * values, but treat unknown strings as "fell back for an unspecified cause" —
4732
+ * do not branch on this value for control flow. New values may be added in
4733
+ * future releases.
4734
+ */
4735
+ type CredentialRefreshFallbackReason = 'no-scope' | 'no-dpop-support' | 'endpoint-failed' | 'activation-timeout' | (string & {});
4736
+ /**
4737
+ * Emitted when the SDK falls back to the developer-provided
4738
+ * {@link CredentialProvider.refresh} because the Client Bound SAT path
4739
+ * could not take over.
4740
+ *
4741
+ * Common causes:
4742
+ * - The minted SAT lacks `sat:refresh` scope (`reason: 'no-scope'`).
4743
+ * - The `/devices/token` exchange failed transiently (`reason: 'endpoint-failed'`).
4744
+ *
4745
+ * Subscribe to this warning to detect:
4746
+ * - SDKs running with plain SATs that rely on developer-managed refresh
4747
+ * - Deployments expected to use bound tokens that silently downgraded to bearer
4748
+ * (a security-relevant signal for fleet observability)
4749
+ */
4750
+ interface CredentialRefreshFallbackWarning {
4751
+ code: 'credential_refresh_fallback';
4752
+ source: 'CredentialProvider';
4753
+ reason: CredentialRefreshFallbackReason;
4754
+ message: string;
4455
4755
  }
4456
- //#endregion
4457
- //#region src/utils/qualityScore.d.ts
4458
4756
  /**
4459
- * MOS (Mean Opinion Score) quality computation based on the simplified
4460
- * ITU-T G.107 E-model.
4757
+ * Emitted when a credential has an `expiry_at` but the provider supplies no
4758
+ * `refresh()` handler. The session will terminate at expiry with no fallback.
4461
4759
  *
4462
- * Provides a single 1-5 number that applications can use for a
4463
- * green / yellow / red quality indicator without understanding raw
4464
- * jitter and packet-loss values.
4760
+ * Implementors who want long-lived sessions must provide a `refresh()` handler
4761
+ * or mint tokens with the `sat:refresh` scope (Client Bound SAT path).
4465
4762
  */
4466
- type QualityLevel$1 = 'excellent' | 'good' | 'fair' | 'poor' | 'critical';
4763
+ interface CredentialNoRefreshHandlerWarning {
4764
+ code: 'credential_no_refresh_handler';
4765
+ source: 'CredentialProvider';
4766
+ message: string;
4767
+ /** Token expiry timestamp (epoch milliseconds). */
4768
+ expiresAt: number;
4769
+ }
4467
4770
  //#endregion
4468
- //#region src/core/entities/Call.d.ts
4771
+ //#region src/utils/logger.d.ts
4772
+ /** Log level names supported by the SDK. */
4773
+ type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent';
4469
4774
  /**
4470
- * Manager instances returned by initialization callback
4775
+ * Logger interface that consumers can implement to replace the built-in logger.
4776
+ * All methods accept variadic arguments matching the browser console API.
4471
4777
  */
4472
- interface CallManagers {
4473
- vertoManager: WebRTCVerto;
4474
- callEventsManager: CallEventsManager;
4778
+ interface SDKLogger {
4779
+ error(...args: unknown[]): void;
4780
+ warn(...args: unknown[]): void;
4781
+ info(...args: unknown[]): void;
4782
+ debug(...args: unknown[]): void;
4783
+ trace(...args: unknown[]): void;
4784
+ }
4785
+ /** Options for WebSocket traffic logging. */
4786
+ interface WsTrafficOptions {
4787
+ type: 'send' | 'recv' | 'http';
4788
+ /** Parsed object or raw string — will be JSON.stringify'd for display if an object. */
4789
+ payload: unknown;
4475
4790
  }
4476
4791
  /**
4477
- * Initialization callback that creates managers for a Call instance
4478
- * @param call - The WebRTCCall instance being initialized
4479
- * @returns Manager instances for the call
4792
+ * Options for WebSocket traffic logging using raw strings.
4793
+ * The string is only parsed when logging is enabled, avoiding
4794
+ * unnecessary JSON.parse on every message.
4480
4795
  */
4481
- type ManagerInitializer = (call: WebRTCCall) => CallManagers;
4796
+ interface WsTrafficRawOptions {
4797
+ type: 'send' | 'recv';
4798
+ raw: string;
4799
+ }
4800
+ /** Debug options that control verbose SDK logging. */
4801
+ interface DebugOptions {
4802
+ /** Log all WebSocket send/recv traffic to the console. */
4803
+ logWsTraffic?: boolean;
4804
+ }
4805
+ /** Extended logger with SDK-internal helpers (wsTraffic). */
4806
+ interface InternalSDKLogger extends SDKLogger {
4807
+ wsTraffic: (options: WsTrafficOptions | WsTrafficRawOptions) => void;
4808
+ }
4809
+ /** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
4810
+ declare const setLogger: (logger: SDKLogger | null) => void;
4811
+ /** Configure debug options (e.g., `{ logWsTraffic: true }`). */
4812
+ declare const setDebugOptions: (options: DebugOptions | null) => void;
4482
4813
  /**
4483
- * Required initialization configuration for Call constructor.
4484
- * Calls must be created via {@link CallFactory} which provides these dependencies.
4814
+ * Set the log level for the built-in logger.
4815
+ * Has no effect when a custom logger is set via `setLogger()`.
4485
4816
  */
4486
- interface CallInitialization {
4817
+ declare const setLogLevel: (level: LogLevel) => void;
4818
+ declare const getLogger: () => InternalSDKLogger;
4819
+ //#endregion
4820
+ //#region src/clients/SignalWire.d.ts
4821
+ /** Options for constructing a {@link SignalWire}. */
4822
+ interface SignalWireOptions {
4823
+ /** Skip automatic WebSocket connection on construction. */
4824
+ skipConnection?: boolean;
4825
+ /** Skip automatic user registration on construction. */
4826
+ skipRegister?: boolean;
4827
+ /** Skip monitoring media device changes. */
4828
+ skipDeviceMonitoring?: boolean;
4829
+ /** Whether to reconnect to previously attached calls. */
4830
+ reconnectAttachedCalls?: boolean;
4831
+ /** Whether to save preferences. */
4832
+ savePreferences?: boolean;
4833
+ /**
4834
+ * Persist the session across page reloads.
4835
+ *
4836
+ * When `true`, credential, authorization state, and protocol are stored in
4837
+ * `localStorage` (survives reload). The DPoP key pair is persisted in
4838
+ * IndexedDB. On reload, the SDK restores the session from cache
4839
+ * without calling `credentialProvider.authenticate()`.
4840
+ *
4841
+ * When `false` (default), session data lives in `sessionStorage` and is
4842
+ * lost on reload.
4843
+ *
4844
+ * Both {@link SignalWire.disconnect | disconnect()} and
4845
+ * {@link SignalWire.destroy | destroy()} end the session and clear the
4846
+ * persisted resume state and attach records; credentials and device
4847
+ * preferences survive. Use `resetToDefaults()` for a full wipe, or
4848
+ * `unregister()` to temporarily stop receiving inbound calls while keeping
4849
+ * the session alive.
4850
+ */
4851
+ persistSession?: boolean;
4852
+ /** Custom storage implementation for persistence. */
4853
+ storageImplementation?: Storage;
4854
+ /** Custom WebSocket constructor */
4855
+ webSocketConstructor?: WebSocketAdapter | NodeSocketAdapter;
4856
+ /** Custom WebRTC API provider */
4857
+ webRTCApiProvider?: WebRTCApiProvider;
4487
4858
  /**
4488
- * Callback function that creates and wires manager instances
4859
+ * Custom logger implementation. Must implement the {@link SDKLogger} interface.
4860
+ * Pass `null` to restore the built-in logger.
4861
+ *
4862
+ * **Note:** Logger configuration is global — setting it on one instance affects all instances.
4489
4863
  */
4490
- initializeManagers: ManagerInitializer;
4864
+ logger?: SDKLogger | null;
4491
4865
  /**
4492
- * Device controller for media device access
4866
+ * Log level for the built-in logger.
4867
+ * Default: `'warn'`. Set to `'debug'` for verbose SDK output.
4868
+ * Has no effect when a custom `logger` is provided.
4869
+ *
4870
+ * **Note:** Logger configuration is global — setting it on one instance affects all instances.
4493
4871
  */
4494
- deviceController: DeviceController;
4872
+ logLevel?: LogLevel;
4873
+ /** Debug options for verbose SDK diagnostics (e.g., `{ logWsTraffic: true }`). */
4874
+ debug?: DebugOptions;
4495
4875
  /**
4496
- * Network change events for feeding recovery pipeline
4876
+ * Control transport for `call.*` verbs across ALL calls in this session:
4877
+ * - `'routed'` (default) sends them on the client's session channel.
4878
+ * - `'in-dialog'` carries them on each call's own signaling channel via `verto.info`,
4879
+ * so control works without the client needing to know how the conference is hosted.
4880
+ *
4881
+ * `'in-dialog'` is opt-in because it does not reach everywhere `'routed'` does: use it
4882
+ * only for calls that join a conference over SWML (e.g. an SWML `join_conference`).
4883
+ *
4884
+ * It is a client-wide setting rather than per-`dial()` because the SDK issues some
4885
+ * control RPCs itself (fetching the layout list on join, for example), and those must
4886
+ * travel the same way as the app's own, or reads and writes land on different transports.
4887
+ *
4888
+ * @experimental A rollout switch, not a long-term part of the API. Expected to
4889
+ * disappear once `'in-dialog'` becomes the only transport, so it carries no semver
4890
+ * promise and application code should not depend on it.
4497
4891
  */
4498
- networkChange$?: Observable<NetworkChangeEvent>;
4892
+ callControl?: 'routed' | 'in-dialog';
4893
+ }
4894
+ /** Options for {@link SignalWire.dial}. Extends {@link MediaOptions} with dial-specific settings. */
4895
+ interface DialOptions extends MediaOptions {
4896
+ /** Preferred video codecs for this call (overrides global preferences). */
4897
+ preferredVideoCodecs?: string[];
4898
+ /** Preferred audio codecs for this call (overrides global preferences). */
4899
+ preferredAudioCodecs?: string[];
4900
+ /** Enable stereo Opus for this call (overrides global preferences). */
4901
+ stereo?: boolean;
4902
+ /** Optional node ID for routing the call */
4903
+ nodeId?: string;
4904
+ /**
4905
+ * Custom variables sent with the Verto invite. Merged with
4906
+ * `client.preferences.userVariables` and any query-string variables on the
4907
+ * destination URI; values here take precedence.
4908
+ */
4909
+ userVariables?: Record<string, unknown>;
4499
4910
  }
4500
4911
  /**
4501
- * Concrete WebRTC call implementation.
4912
+ * Main entry point for the SignalWire Browser SDK.
4502
4913
  *
4503
- * Manages the full lifecycle of a call including signaling, media streams,
4504
- * participants, layout, and event routing. Created via {@link SignalWire.dial}
4505
- * or received as an inbound call.
4914
+ * Manages authentication, WebSocket transport, call creation, and media devices.
4915
+ *
4916
+ * @example
4917
+ * ```ts
4918
+ * const client = new SignalWire(credentialProvider);
4919
+ * client.isConnected$.subscribe(connected => console.log('Connected:', connected));
4920
+ * const call = await client.dial('/public/my-room');
4921
+ * ```
4506
4922
  */
4507
- declare class WebRTCCall extends Destroyable implements CallManager {
4508
- clientSession: ClientSession;
4509
- options: CallOptions;
4510
- address?: Address | undefined;
4511
- /** Unique identifier for this call. */
4512
- readonly id: string;
4513
- /** Destination URI this call was placed to. */
4514
- to?: string;
4515
- private vertoManager;
4516
- private callEventsManager;
4517
- private participantFactory;
4518
- private _errors$;
4519
- private _status$;
4520
- private _lastMergedStatus;
4521
- private _answered$;
4522
- private _answerMediaOptions?;
4523
- private _holdState;
4524
- private _userVariables$;
4525
- private _statsMonitor?;
4526
- private _recoveryManager?;
4527
- private _networkChange$?;
4528
- private _networkIssues$;
4529
- private _networkMetrics$;
4530
- private _isNetworkHealthy$;
4531
- private _qualityScore$;
4532
- private _qualityLevel$;
4533
- private _recoveryState$;
4534
- private _recoveryEvent$;
4535
- private _bandwidthConstrained$;
4536
- private _mediaParamsUpdated$;
4537
- private _customSubscriptions;
4538
- private _pushToTalkEnabled;
4539
- private _remoteAudioMeter;
4540
- constructor(clientSession: ClientSession, options: CallOptions, initialization: CallInitialization, address?: Address | undefined);
4541
- /** Observable stream of errors from media, signaling, and peer connection layers. */
4542
- get errors$(): Observable<CallError>;
4923
+ declare class SignalWire extends Destroyable implements DeviceController {
4924
+ /** Global SDK preferences (timeouts, ICE config, media defaults). */
4925
+ preferences: ClientPreferences;
4926
+ private _user$;
4927
+ private _directory$;
4928
+ private _transport;
4929
+ private _clientSession;
4930
+ private _publicSession;
4931
+ private _deviceController;
4932
+ private _attachManager?;
4543
4933
  /**
4544
- * @internal Push an error to the call's error stream.
4545
- * Fatal errors automatically transition the call to `'failed'` and destroy it.
4934
+ * Set once a credential recovery has been *verified* — reauthenticated and
4935
+ * then proven by the operation it was meant to unblock. Read by the attach
4936
+ * path, which may only discard an attach record when a reattach is refused
4937
+ * on a credential the server has already accepted.
4546
4938
  */
4547
- emitError(callError: CallError): void;
4548
- /** Notify the recovery manager that a verto.modify signaling exchange failed. */
4549
- notifyModifyFailed(): void;
4550
- /** Whether this call is `'inbound'` or `'outbound'`. */
4551
- get direction(): CallDirection;
4552
- /** Observable of the address associated with this call. */
4553
- get address$(): Observable<Address | undefined>;
4554
- /** Display name of the caller. */
4555
- get fromName(): string | undefined;
4556
- /** Address URI of the caller. */
4557
- get from(): string | undefined;
4558
- /** Display name of the callee. */
4559
- get toName(): string | undefined;
4560
- /** Toggles whether incoming video is received. @throws {UnimplementedError} Not yet implemented. */
4561
- toggleIncomingVideo(): Promise<void>;
4562
- /** Toggles whether incoming audio is received. @throws {UnimplementedError} Not yet implemented. */
4563
- toggleIncomingAudio(): Promise<void>;
4564
- /** @internal Registers an additional call ID for event routing. */
4565
- addCallId(callId: string): void;
4566
- /** List of capabilities available in the current call. */
4567
- get capabilities(): Capability[];
4568
- /** Current snapshot of all participants in the call. */
4569
- get participants(): CallParticipant[];
4570
- /** The local participant, or `null` if not yet joined. */
4571
- get self(): CallSelfParticipant | null;
4572
- /** Toggles the call lock state, preventing or allowing new participants from joining. */
4573
- toggleLock(): Promise<void>;
4939
+ private _credentialRecovered;
4940
+ private _isConnected$;
4941
+ private _isRegistered$;
4942
+ private _errors$;
4943
+ private _warnings$;
4944
+ private _options;
4945
+ private _dpopManager?;
4946
+ private _refreshCoordinator?;
4947
+ /** The refresh path's own HTTP controller — see resolveCredentials. */
4948
+ private _refreshHttp?;
4949
+ /** Host `_refreshHttp` was built against, so it can be rebuilt when the token's `ch` changes. */
4950
+ private _refreshHttpHost?;
4951
+ private _credentialProvider?;
4952
+ private _deps;
4953
+ private _networkMonitor?;
4954
+ private _visibilityController?;
4955
+ private _diagnosticsCollector?;
4956
+ private _platformCapabilities?;
4574
4957
  /**
4575
- * Toggles the hold state of the call (pauses/resumes local media transmission).
4958
+ * Creates a new SignalWire client and begins connecting.
4576
4959
  *
4577
- * Distinct from {@link Participant.toggleMute} which mutes individual tracks.
4960
+ * @param credentialProvider - Provider that supplies authentication credentials.
4961
+ * @param options - Configuration options (connection, device monitoring, preferences).
4578
4962
  */
4579
- toggleHold(): Promise<void>;
4580
- /** @throws {UnimplementedError} Not yet implemented. Status tracked via {@link recording$}. */
4581
- startRecording(): Promise<void>;
4582
- /** @throws {UnimplementedError} Not yet implemented. Status tracked via {@link streaming$}. */
4583
- startStreaming(): Promise<void>;
4963
+ constructor(credentialProvider: CredentialProvider | undefined, options?: SignalWireOptions);
4584
4964
  /**
4585
- * Replaces the call's custom metadata.
4586
- * @param _meta - Metadata object to set.
4587
- * @throws {UnimplementedError} Not yet implemented.
4965
+ * Build the refresh path's own HTTP controller, against whatever host is current.
4966
+ *
4967
+ * Called on first use rather than up front, so `apiHost` already reflects the
4968
+ * token's `ch` claim. Same credential source as the container's controller — only
4969
+ * the instance, and therefore its observable streams, is separate.
4588
4970
  */
4589
- setMeta(_meta: Record<string, unknown>): Promise<void>;
4971
+ private createRefreshHttpController;
4590
4972
  /**
4591
- * Merges values into the call's custom metadata (unlike {@link setMeta} which replaces).
4592
- * @param _meta - Metadata to merge.
4593
- * @throws {UnimplementedError} Not yet implemented.
4973
+ * Initializes DPoP if not already set up. Returns the fingerprint on success.
4594
4974
  */
4595
- updateMeta(_meta: Record<string, unknown>): Promise<void>;
4596
- /** Observable of layout layer positions for all participants. */
4597
- get layoutLayers$(): Observable<LayoutLayer[]>;
4598
- /** Current snapshot of layout layers. */
4599
- get layoutLayers(): LayoutLayer[];
4975
+ private initDPoP;
4600
4976
  /**
4601
- * Executes a Verto RPC method targeting a specific participant.
4602
- *
4603
- * Constructs call context (node_id, call_id, member_id) and sends the RPC request.
4977
+ * Resolves credentials using cache-first strategy when persistSession is enabled.
4604
4978
  *
4605
- * @param target - Target member ID string, or a {@link MemberTarget} object.
4606
- * @param method - Verto method name (e.g. `'call.mute'`, `'call.member.remove'`).
4607
- * @param args - Parameters for the RPC method.
4608
- * @returns The RPC response.
4609
- * @throws {JSONRPCError} If the RPC call returns an error.
4979
+ * 1. If persistSession → check localStorage for cached credential
4980
+ * 2. If cached and not expired → use it (skip provider.authenticate())
4981
+ * 3. If no cache or expired → call provider.authenticate()
4982
+ * 4. If no provider AND no cache → throw
4610
4983
  */
4611
- executeMethod<T extends JSONRPCResponse = JSONRPCResponse>(target: string | MemberTarget, method: string, args: Record<string, unknown>): Promise<T>;
4612
- private buildMethodParams;
4613
- /** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
4614
- get status$(): Observable<CallStatus>;
4615
- /** Observable of the participants list, emits on join/leave/update. */
4616
- get participants$(): Observable<CallParticipant[]>;
4617
- /** Observable of the local (self) participant. */
4618
- get self$(): Observable<CallSelfParticipant>;
4619
- /** Observable indicating whether the call is being recorded. */
4620
- get recording$(): Observable<boolean>;
4621
- /** Observable indicating whether the call is being streamed. */
4622
- get streaming$(): Observable<boolean>;
4623
- /** Observable indicating whether raise-hand priority is active. */
4624
- get raiseHandPriority$(): Observable<boolean>;
4625
- /** Observable indicating whether the call room is locked. */
4626
- get locked$(): Observable<boolean>;
4627
- /** Observable of custom metadata associated with the call. */
4628
- get meta$(): Observable<Record<string, unknown>>;
4629
- /** Observable of the call's capability flags. */
4630
- get capabilities$(): Observable<Capability[]>;
4631
- /** Observable of the current layout name. */
4632
- get layout$(): Observable<string>;
4633
- /** Current call status. */
4634
- get status(): CallStatus;
4635
- /** Whether the call is currently being recorded. */
4636
- get recording(): boolean;
4637
- /** Whether the call is currently being streamed. */
4638
- get streaming(): boolean;
4639
- /** Whether raise-hand priority is active. */
4640
- get raiseHandPriority(): boolean;
4641
- /** Whether the call room is locked. */
4642
- get locked(): boolean;
4643
- /** Current custom metadata of the call. */
4644
- get meta(): Record<string, unknown>;
4645
- /** Current layout name, or `undefined` if not set. */
4646
- get layout(): string | undefined;
4647
- /** Observable of available layout names. */
4648
- get layouts$(): Observable<string[]>;
4649
- /** Current snapshot of available layout names. */
4650
- get layouts(): string[];
4651
- /** Observable of the local media stream (camera/microphone). */
4652
- get localStream$(): Observable<MediaStream>;
4653
- /** Current local media stream, or `null` if not available. */
4654
- get localStream(): MediaStream | null;
4655
- /** Observable of the remote media stream from the far end. */
4656
- get remoteStream$(): Observable<MediaStream>;
4657
- /** Current remote media stream, or `null` if not available. */
4658
- get remoteStream(): MediaStream | null;
4659
- /** Observable of custom user variables associated with the call. */
4660
- get userVariables$(): Observable<Record<string, unknown>>;
4661
- /** a copy of the current custom user variables of the call. */
4662
- get userVariables(): Record<string, unknown>;
4663
- /** Merge current custom user variables of the call. */
4664
- set userVariables(variables: Record<string, unknown>);
4665
- /** Observable of current network health issues (empty array = healthy). */
4666
- get networkIssues$(): Observable<NetworkIssue[]>;
4667
- /** Current snapshot of network issues. */
4668
- get networkIssues(): NetworkIssue[];
4669
- /** Simple boolean health indicator derived from stats monitor. */
4670
- get isNetworkHealthy$(): Observable<boolean>;
4671
- /** Whether the network is currently healthy. */
4672
- get isNetworkHealthy(): boolean;
4673
- /** Rolling history of raw network metrics (RTT, jitter, packet loss, bitrate). */
4674
- get networkMetrics$(): Observable<NetworkMetrics[]>;
4675
- /** Current snapshot of the metrics rolling window. */
4676
- get networkMetrics(): NetworkMetrics[];
4677
- /** Observable of MOS quality score (1-5) computed from stats metrics. */
4678
- get qualityScore$(): Observable<number>;
4679
- /** Observable of simplified quality level (excellent/good/fair/poor/critical). */
4680
- get qualityLevel$(): Observable<QualityLevel$1>;
4681
- /** Observable of the recovery pipeline state machine. */
4682
- get recoveryState$(): Observable<RecoveryState$1>;
4683
- /** Observable of recovery events (keyframe requested, ICE restart, etc.). */
4684
- get recoveryEvent$(): Observable<RecoveryEvent$1>;
4685
- /** Observable indicating whether the call is bandwidth-constrained. */
4686
- get bandwidthConstrained$(): Observable<boolean>;
4687
- /** Observable that emits when server-pushed media params are applied. */
4688
- get mediaParamsUpdated$(): Observable<MediaParamsEvent>;
4984
+ private resolveCredentials;
4985
+ private validateCredentials;
4689
4986
  /**
4690
- * @internal Emit a media params update event.
4691
- * Called by the VertoManager when server-pushed media params are applied.
4987
+ * Reauthenticate the currently-open session with a freshly obtained
4988
+ * credential so the new token takes effect on the live socket immediately —
4989
+ * not just on the next reconnect. No-op when the session is not
4990
+ * connected/authenticated or the credential carries no token (e.g. an
4991
+ * authorization-state-only refresh). Non-fatal: reauth failures surface on
4992
+ * `errors$` without aborting the refresh that triggered this.
4692
4993
  */
4693
- emitMediaParamsUpdated(event: MediaParamsEvent): void;
4694
- /** Request a video keyframe via RTCP PLI/FIR. */
4695
- requestKeyframe(): void;
4696
- /** Force an ICE restart / re-INVITE. */
4697
- requestIceRestart(): Promise<void>;
4994
+ private reauthenticateLiveSession;
4698
4995
  /**
4699
- * @internal Initialize resilience subsystems when the call reaches 'connected'.
4700
- * Called from within the status subscription to wire stats and recovery.
4996
+ * Recover a session the server is refusing: mint a fresh credential,
4997
+ * reauthenticate the live session with it, and retry the operation.
4998
+ *
4999
+ * The connection is deliberately kept. A reload authenticates the new socket
5000
+ * against the persisted `authorization_state`, and that handshake is what
5001
+ * associates the socket with the previous session — the association reattach
5002
+ * depends on. `signalwire.reauthenticate` swaps the credential *on that same
5003
+ * session*, so recovery never touches the resume state. Discarding it would
5004
+ * heal the credential by destroying the very thing the caller is trying to
5005
+ * get back to.
5006
+ *
5007
+ * The operation is still the verdict, never the RPC. Reauthenticating with
5008
+ * the in-memory token is accepted by a resume even while requests stay
5009
+ * refused, because the persisted `authorization_state` short-circuits token
5010
+ * validation — and `signalwire.reauthenticate` with a *freshly minted* token
5011
+ * has also been observed accepted while `subscriber.online` keeps being
5012
+ * refused (staging run 33826974634). Both look like success and are not.
5013
+ *
5014
+ * @returns the operation's value, or the reason recovery could not deliver
5015
+ * one. `error` is undefined when there was no way to mint at all.
4701
5016
  */
4702
- private initResilienceSubsystems;
5017
+ private recoverAndRetry;
4703
5018
  /**
4704
- * Wait for the underlying RTCPeerConnection to reach 'connected' after
4705
- * triggering an ICE restart. Resolves true on success, false on failure
4706
- * or if the state doesn't transition within the configured timeout.
5019
+ * Re-mint a credential and adopt it only if the live session accepts it.
4707
5020
  *
4708
- * Polls connectionState directly because the recovery manager already
4709
- * wraps this call in its own withTimeout(); a separate listener-based
4710
- * implementation would race the outer timeout in subtle ways.
5021
+ * The mechanism follows the binding: a client-bound session re-mints a bound
5022
+ * base SAT through `authenticate()` with the DPoP fingerprint, because the
5023
+ * developer refresh handler would hand back an unbound token and silently
5024
+ * degrade the session. An unbound session uses the refresh handler. Rotation
5025
+ * cost is not a reason to skip this — the only reason is having no mechanism.
5026
+ *
5027
+ * @returns whether the session is now running on a freshly accepted credential.
5028
+ */
5029
+ private remintAndReauthenticate;
5030
+ /**
5031
+ * Re-mint a credential via `provider.refresh()`, routed through the
5032
+ * coordinator's shared in-flight guard so concurrent re-mint paths (a
5033
+ * scheduled/resume refresh, -32003 recovery, and reconnect) never fire a
5034
+ * second `provider.refresh()` in parallel — which rotating one-time-use
5035
+ * refresh tokens reject. Falls back to a direct call only if the coordinator
5036
+ * has not been constructed yet.
5037
+ */
5038
+ private remintCredential;
5039
+ /**
5040
+ * Re-mint credentials before a fresh (re)connect (`onBeforeReconnect` hook).
5041
+ * The session invokes this only when it is client-bound OR the in-memory
5042
+ * token is expired. The re-mint mechanism depends on the binding:
5043
+ * - Client-bound: `authenticate()` with the DPoP fingerprint to obtain a
5044
+ * fresh base SAT the upcoming reconnect can re-bind (the
5045
+ * DeviceTokenManager re-activates afterwards).
5046
+ * - Unbound: the developer's non-interactive `refresh()` handler.
5047
+ * `authenticate()` is deliberately NOT used here — it may be interactive
5048
+ * (a login prompt) and must not fire on a background reconnect.
5049
+ *
5050
+ * Rejects on failure so the session aborts the reconnect rather than
5051
+ * replaying a stale token.
4711
5052
  */
4712
- private waitForPeerConnectionConnected;
5053
+ private refreshCredentialForReconnect;
5054
+ /** Persist credential to localStorage when persistSession is enabled. */
5055
+ private persistCredential;
4713
5056
  /**
4714
- * @internal Stop and destroy resilience subsystems (on disconnect/destroy).
4715
- * Clears references so they can be re-created on reconnect.
5057
+ * Persist whether the session is client-bound, mirroring the credential's
5058
+ * storage scopes so it survives a reload. The preflight recovery reads it
5059
+ * before any session exists to decide whether to re-bind via `authenticate()`
5060
+ * or refresh an unbound token; the marker tracks the latest binding, so an
5061
+ * unbound reconnect clears a stale marker from an earlier client-bound login.
4716
5062
  */
4717
- private stopResilienceSubsystems;
4718
- /** @internal */
4719
- createParticipant(memberId: string, selfId?: string | null): Participant | SelfParticipant;
4720
- /** Observable of the current audio/video send/receive directions. */
4721
- get mediaDirections$(): Observable<MediaDirections>;
4722
- /** Current audio/video send/receive directions. */
4723
- get mediaDirections(): MediaDirections;
4724
- protected get participantsId$(): Observable<string[]>;
5063
+ private persistClientBoundMarker;
5064
+ /** Read the persisted client-bound marker (see {@link persistClientBoundMarker}). */
5065
+ private wasClientBound;
5066
+ private init;
5067
+ private handleAttachments;
4725
5068
  /**
4726
- * Executes a raw JSON-RPC request on the client session.
5069
+ * Fetch the authenticated user profile, recovering a stale credential.
4727
5070
  *
4728
- * Lower-level than {@link executeMethod} — allows full control over the RPC request structure.
4729
- *
4730
- * @param request - Complete JSON-RPC request object.
4731
- * @param options - Optional RPC execution options (timeout, etc.).
4732
- * @returns The RPC response.
4733
- * @throws {JSONRPCError} If the RPC call returns an error response.
4734
- */
4735
- execute<T extends JSONRPCResponse = JSONRPCResponse>(request: JSONRPCRequest, options?: PendingRPCOptions): Promise<T>;
4736
- /** Observable of the local participant's member ID. */
4737
- get selfId$(): Observable<string | null>;
4738
- /** Local participant's member ID, or `null` if not joined. */
4739
- get selfId(): string | null;
4740
- /** Observable of the server node ID handling this call. */
4741
- get nodeId$(): Observable<string | null>;
4742
- /** Server node ID handling this call, or `null`. */
4743
- get nodeId(): string | null;
4744
- private isCallSessionEvent;
4745
- private get callSessionEvents$();
4746
- /** Observable of call-updated events. */
4747
- get callUpdated$(): Observable<CallUpdatedPayload>;
4748
- /** Observable of member-joined events, emitted when a remote participant joins the call. */
4749
- get memberJoined$(): Observable<MemberJoinedPayload>;
4750
- /** Observable of member-left events, emitted when a participant leaves the call. */
4751
- get memberLeft$(): Observable<MemberLeftPayload>;
4752
- /** Observable of member-updated events (mute, volume, etc.). */
4753
- get memberUpdated$(): Observable<MemberUpdatedPayload>;
4754
- /** Observable of member-talking events (speech start/stop). */
4755
- get memberTalking$(): Observable<MemberTalkingPayload>;
4756
- /** Observable of call state-change events. */
4757
- get callStates$(): Observable<CallStatePayload>;
4758
- /** Observable of layout-changed events. */
4759
- get layoutUpdates$(): Observable<LayoutChangedPayload>;
4760
- /** Underlying `RTCPeerConnection`, for advanced use cases. */
4761
- get rtcPeerConnection(): RTCPeerConnection | undefined;
4762
- /** Observable of raw signaling events as plain objects. */
4763
- get signalingEvent$(): Observable<Record<string, unknown>>;
5071
+ * On a reload the persisted credential can be expired. Unlike the WS resume —
5072
+ * which the server accepts against the persisted `authorization_state` even
5073
+ * with an expired token — this REST preflight has no such short-circuit and is
5074
+ * refused (401). There is no session yet to reauthenticate, so recovery
5075
+ * re-mints the credential through the provider ({@link refreshCredentialForReconnect})
5076
+ * and retries with a FRESH {@link User}: Fetchable memoizes its result
5077
+ * (shareReplay), so reusing the instance would replay the 401 instead of
5078
+ * re-fetching with the new token. Without the user id the transport/session —
5079
+ * and the reattach a reload is trying to preserve — cannot even be addressed.
5080
+ */
5081
+ private fetchUserOrRecover;
4764
5082
  /**
4765
- * Subscribe to a custom signaling event type on this call.
5083
+ * Establishes the WebSocket connection and authenticates the session.
4766
5084
  *
4767
- * Returns a cached observable that filters `callSessionEvents$` for events
4768
- * whose `event_type` matches the given string. The observable completes
4769
- * when the call is destroyed.
5085
+ * ## Reconnection behavior
4770
5086
  *
4771
- * Unlike `signalingEvent$` (which only emits known call-level event types),
4772
- * this method also matches custom/user-defined event types.
5087
+ * After a successful connection the underlying {@link WebSocketController}
5088
+ * automatically attempts to reconnect whenever the socket closes
5089
+ * unexpectedly (e.g. network change, server restart). Reconnection uses an
5090
+ * **exponential back-off** strategy:
4773
5091
  *
4774
- * The SDK does not validate event type strings --- the server decides
4775
- * whether a given type is valid.
5092
+ * - First retry after `reconnectDelayMin` (default **0.1 s**).
5093
+ * - Each subsequent retry doubles the delay up to `reconnectDelayMax`
5094
+ * (default **3 s**).
5095
+ * - The delay resets to `reconnectDelayMin` once a connection succeeds.
5096
+ * - A per-attempt `connectionTimeout` (default **10 s**) aborts the
5097
+ * attempt and schedules the next retry if the server does not respond.
4776
5098
  *
4777
- * @param eventType - The event type to subscribe to (e.g. `'my.custom.event'`).
4778
- * @returns An observable that emits matching signaling events.
5099
+ * Calling {@link disconnect} stops the reconnection loop entirely.
5100
+ *
5101
+ * ## Message handling during temporary disconnections
5102
+ *
5103
+ * While the socket is not in the `connected` state, **outgoing messages
5104
+ * are queued** in an internal buffer. Once the connection is
5105
+ * re-established the queue is flushed in order so no outgoing RPC call is
5106
+ * lost.
5107
+ *
5108
+ * **Incoming** server-to-client messages that arrive while the socket is
5109
+ * down are *not* buffered by the SDK — they are expected to be
5110
+ * re-delivered by the server after the session is re-authenticated.
5111
+ * Active RPC calls that were awaiting a response will time out
5112
+ * (default **5 s**) and reject with an `RPCTimeoutError`; callers should
5113
+ * handle this and retry if appropriate.
5114
+ *
5115
+ * The connection status can be observed via the `status$` observable on
5116
+ * the transport layer, which emits `'connecting'`, `'connected'`,
5117
+ * `'reconnecting'`, `'disconnecting'`, or `'disconnected'`.
5118
+ */
5119
+ connect(): Promise<void>;
5120
+ /**
5121
+ * Observable that emits the {@link User} profile once fetched,
5122
+ * or `undefined` before authentication completes.
4779
5123
  *
4780
5124
  * @example
4781
5125
  * ```ts
4782
- * call.subscribe('my.custom.event').subscribe(event => {
4783
- * console.log('Custom event:', event);
5126
+ * client.user$.subscribe(u => {
5127
+ * if (u) console.log('Logged in as', u.email);
4784
5128
  * });
4785
5129
  * ```
4786
5130
  */
4787
- subscribe(eventType: string): Observable<Record<string, unknown>>;
4788
- get webrtcMessages$(): Observable<WebrtcMessagePayload>;
4789
- get callEvent$(): Observable<WebrtcMessagePayload | CallJoinedPayload | CallLeftPayload | CallUpdatedPayload | CallStatePayload | CallPlayPayload | CallConnectPayload | RoomUpdatedPayload | MemberUpdatedPayload | MemberJoinedPayload | MemberLeftPayload | MemberTalkingPayload | LayoutChangedPayload | ConversationMessagePayload>;
4790
- get layoutEvent$(): Observable<LayoutChangedPayload>;
5131
+ get user$(): Observable<User | undefined>;
5132
+ /** Current user snapshot, or `undefined` if not yet authenticated. */
5133
+ get user(): User | undefined;
4791
5134
  /**
4792
- * Hangs up the call and releases all resources.
4793
- *
4794
- * Sends a Verto `bye` to the server, transitions status to `'disconnecting'`,
4795
- * then destroys the call. After this, the call instance is no longer usable.
5135
+ * Observable that emits the {@link Directory} instance once the client is connected,
5136
+ * or `undefined` while disconnected. Subscribe to this to safely wait for the directory
5137
+ * to become available without risking an error.
4796
5138
  *
4797
5139
  * @example
4798
5140
  * ```ts
4799
- * await call.hangup();
5141
+ * client.directory$.subscribe(dir => {
5142
+ * if (dir) dir.addresses$.subscribe(console.log);
5143
+ * });
4800
5144
  * ```
4801
5145
  */
4802
- hangup(): Promise<void>;
5146
+ get directory$(): Observable<Directory | undefined>;
4803
5147
  /**
4804
- * Sends DTMF digits on the call.
5148
+ * Current directory snapshot, or `undefined` if the client is not yet connected.
5149
+ * Prefer {@link directory$} when you need to react to the directory becoming available.
5150
+ */
5151
+ get directory(): Directory | undefined;
5152
+ /** Observable that emits when the user registration state changes. */
5153
+ get isRegistered$(): Observable<boolean>;
5154
+ /** Whether the user is currently registered. */
5155
+ get isRegistered(): boolean;
5156
+ /** Whether the client is currently connected. */
5157
+ get isConnected(): boolean;
5158
+ /** Observable that emits when the connection state changes. */
5159
+ get isConnected$(): Observable<boolean>;
5160
+ /** Observable that emits `true` when the client is both connected and authenticated. */
5161
+ get ready$(): Observable<boolean>;
5162
+ /** Observable stream of errors from transport, authentication, and devices. */
5163
+ get errors$(): Observable<Error>;
5164
+ /**
5165
+ * Observable stream of non-fatal SDK warnings.
4805
5166
  *
4806
- * @param dtmf - The digit string to send (e.g. `'1234#'`).
5167
+ * Subscribe to detect SDK behaviors that affect session liveness or developer-facing
5168
+ * contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
5169
+ * refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
5170
+ * scope. Discriminated by `code`.
4807
5171
  *
4808
- * @example
4809
- * ```ts
4810
- * await call.sendDigits('1234#');
4811
- * ```
5172
+ * Independent from {@link errors$}: existing error consumers are not notified.
4812
5173
  */
4813
- sendDigits(dtmf: string): Promise<void>;
4814
- /** Observable of WebRTC-specific signaling messages. */
4815
- /** Observable of call-level signaling events. */
4816
- /** Observable of layout-changed signaling events. */
5174
+ get warnings$(): Observable<SDKWarning>;
5175
+ /** Platform WebRTC capabilities detected at construction time. */
5176
+ get platformCapabilities(): PlatformCapabilities;
5177
+ /** Observable that emits when the SDK auto-switches a device. */
5178
+ get deviceRecovered$(): Observable<DeviceRecoveryEvent>;
4817
5179
  /**
4818
- * Accepts an inbound call, optionally overriding media options for the answer.
5180
+ * Export a structured diagnostic bundle for support/debugging.
5181
+ * Includes connection events, call summaries, and device changes.
5182
+ */
5183
+ exportDiagnostics(): SessionDiagnostics;
5184
+ /**
5185
+ * Initialize resilience subsystems. Non-fatal: any failure is logged and
5186
+ * the SDK continues working without the failing subsystem.
5187
+ */
5188
+ private initResilienceSubsystems;
5189
+ /**
5190
+ * Disconnects the WebSocket and tears down the current session.
4819
5191
  *
4820
- * @param options - Optional media constraints for the answer (audio/video).
5192
+ * Ends the session identified by the protocol and clears its persisted
5193
+ * resume state (`authorization_state` + protocol) and attach records
5194
+ * together — a later {@link connect} with the same credentials starts a
5195
+ * fresh session and cannot reattach to the ended session's calls.
5196
+ * Credentials and device preferences are preserved. To temporarily stop
5197
+ * receiving inbound calls while keeping the session alive, use
5198
+ * `unregister()` instead.
4821
5199
  *
4822
- * @example
4823
- * ```ts
4824
- * // Accept with defaults
4825
- * call.answer();
5200
+ * The client can be reconnected by calling {@link connect} again,
5201
+ * which creates a fresh transport and session.
5202
+ */
5203
+ disconnect(): Promise<void>;
5204
+ /**
5205
+ * Tear down the current transport / session / attach manager. Safe to call
5206
+ * when nothing has been initialized yet (e.g. first connect()).
5207
+ */
5208
+ private teardownTransportAndSession;
5209
+ private waitAuthentication;
5210
+ /**
5211
+ * Registers the user as online to receive inbound calls and events.
4826
5212
  *
4827
- * // Accept audio-only
4828
- * call.answer({ audio: true, video: false });
4829
- * ```
4830
- * @see {@link reject} to decline the call instead.
4831
- * @see {@link answered$} to observe the acceptance state.
5213
+ * Waits for authentication to complete before sending the registration.
5214
+ * If the initial attempt fails, reauthentication is attempted automatically.
5215
+ *
5216
+ * @throws {InvalidCredentialsError} If registration and reauthentication both fail.
4832
5217
  */
4833
- answer(options?: MediaOptions): void;
4834
- /** Media options provided when answering. Used internally by the VertoManager. */
4835
- get answerMediaOptions(): MediaOptions | undefined;
5218
+ register(): Promise<void>;
4836
5219
  /**
4837
- * Rejects an inbound call, preventing media negotiation.
5220
+ * Unregisters the user, going offline for inbound calls.
4838
5221
  *
4839
- * @see {@link answer} to accept the call instead.
4840
- * @see {@link answered$} to observe the rejection state.
5222
+ * The WebSocket connection remains open; use {@link disconnect} to fully close it.
4841
5223
  */
4842
- reject(): void;
4843
- /** Observable that emits `true` when answered, `false` when rejected. */
4844
- get answered$(): Observable<boolean>;
5224
+ unregister(): Promise<void>;
4845
5225
  /**
4846
- * Sets the call layout and, optionally, individual participant positions.
5226
+ * Places an outbound call to the given destination.
4847
5227
  *
4848
- * The gateway `call.layout.set` DTO has **no** `positions` member, so when
4849
- * `positions` is provided this method issues a `call.member.position.set`
4850
- * request per member (via {@link Participant.setPosition}, which keys each
4851
- * position by that member's own call context) alongside `call.layout.set`
4852
- * (issue #19400, Flag #6).
5228
+ * Waits for authentication before dialing. Media options are merged from
5229
+ * saved preferences, destination query parameters (e.g. `?channel=video`),
5230
+ * and the provided `options` (highest priority).
4853
5231
  *
4854
- * **These operations are NOT atomic.** The layout is applied first, then each
4855
- * member position sequentially, so members may briefly flash into their
4856
- * default slots before being moved to the requested positions.
5232
+ * Returns a {@link Call} in `'ringing'` state. Subscribe to {@link Call.status$}
5233
+ * to track progression through `'connected'` → `'disconnected'`.
4857
5234
  *
4858
- * @param layout - Layout name (must be one of {@link layouts}).
4859
- * @param positions - Optional map of member IDs to {@link VideoPosition} values.
4860
- * When omitted or empty, only the layout is changed.
4861
- * @throws {InvalidParams} If the layout is not in the available {@link layouts}.
5235
+ * Local media acquisition is deliberately unbounded: an unanswered permission
5236
+ * prompt leaves this promise pending indefinitely, so apply your own bound if
5237
+ * your UI needs one. The 12 s signaling budget starts only once acquisition
5238
+ * settles.
5239
+ *
5240
+ * @param destination - Address URI string (e.g. `'/public/my-room'`) or {@link Address} instance.
5241
+ * @param options - Media and dial options (audio/video, device constraints). Overrides defaults.
5242
+ * @returns The created {@link Call} instance.
5243
+ * @throws {Error} If authentication is not complete or call creation fails.
4862
5244
  *
4863
5245
  * @example
4864
5246
  * ```ts
4865
- * await call.setLayout('grid-responsive', {
4866
- * [participantId]: 'reserved-0',
5247
+ * const call = await client.dial('/public/conference', {
5248
+ * audio: true,
5249
+ * video: true,
4867
5250
  * });
5251
+ * call.status$.subscribe(status => console.log('Call:', status));
4868
5252
  * ```
4869
5253
  */
4870
- setLayout(layout: string, positions?: Record<string, VideoPosition>): Promise<void>;
4871
- /**
4872
- * Transfers the call to another destination.
4873
- *
4874
- * @param options - Transfer configuration including the target destination.
4875
- * @see {@link status$} to observe the transfer progress.
4876
- */
4877
- transfer(options: TransferOptions): Promise<void>;
5254
+ dial(destination: string | Address, options?: DialOptions): Promise<Call>;
4878
5255
  /**
4879
- * Set the local microphone gain as a percentage applied before transmission.
4880
- *
4881
- * - `0` = silent
4882
- * - `100` = unity (no change, default)
4883
- * - `200` = 2× digital boost (max; expect clipping / noise amplification)
5256
+ * Runs a multi-phase connectivity test against the given destination.
4884
5257
  *
4885
- * Values are clamped to [0, 200]. Engages the local audio pipeline on
4886
- * first use (one-time cost).
5258
+ * The test checks:
5259
+ * 1. **Signaling** -- WebSocket connected, RTT measurement
5260
+ * 2. **Devices** -- getUserMedia succeeds with selected (or specified) devices
5261
+ * 3. **ICE/TURN** -- gathers ICE candidates to verify STUN/TURN reachability
5262
+ * 4. **Media/bandwidth** (unless `skipMediaTest`) -- dials the destination,
5263
+ * collects getStats() for `duration` seconds, computes bandwidth estimates
4887
5264
  *
4888
- * Note: this is a **digital** multiplier applied in a Web Audio GainNode
4889
- * between your mic track and the RTCRtpSender — it does not change the
4890
- * physical mic's hardware sensitivity. Browsers' autoGainControl can
4891
- * fight the setting; call {@link setAutoGainControl}(false) for
4892
- * predictable behaviour.
5265
+ * @param destination - A destination to dial for the media test (e.g. `'/private/network-test'`).
5266
+ * @param options - Preflight options (duration, skipMediaTest, device overrides).
5267
+ * @returns A {@link PreflightResult} describing connectivity health.
4893
5268
  *
4894
- * @param value - Gain percentage (0..200; 100 = unity).
5269
+ * @example
5270
+ * ```ts
5271
+ * const result = await client.preflight('/private/network-test', { duration: 5 });
5272
+ * if (!result.ok) console.warn('Connectivity issues:', result.warnings);
5273
+ * ```
4895
5274
  */
4896
- setLocalMicrophoneGain(value: number): void;
4897
- /** Observable of the current local microphone gain (0..200, where 100 = unity). */
4898
- get localMicrophoneGain$(): Observable<number>;
5275
+ preflight(destination: string, options?: PreflightOptions): Promise<PreflightResult>;
5276
+ /** The underlying client session for advanced RPC operations. */
5277
+ get session(): ClientSessionWrapper;
5278
+ /** Observable list of available audio input (microphone) devices. */
5279
+ get audioInputDevices$(): Observable<MediaDeviceInfo[]>;
5280
+ /** Current snapshot of available audio input devices. */
5281
+ get audioInputDevices(): MediaDeviceInfo[];
5282
+ /** Observable list of available audio output (speaker) devices. */
5283
+ get audioOutputDevices$(): Observable<MediaDeviceInfo[]>;
5284
+ /** Current snapshot of available audio output devices. */
5285
+ get audioOutputDevices(): MediaDeviceInfo[];
5286
+ /** Observable list of available video input (camera) devices. */
5287
+ get videoInputDevices$(): Observable<MediaDeviceInfo[]>;
5288
+ /** Current snapshot of available video input devices. */
5289
+ get videoInputDevices(): MediaDeviceInfo[];
5290
+ /** Observable of the currently selected audio input device. */
5291
+ get selectedAudioInputDevice$(): Observable<MediaDeviceInfo | null>;
5292
+ /** Observable of the currently selected audio output device. */
5293
+ get selectedAudioOutputDevice$(): Observable<MediaDeviceInfo | null>;
5294
+ /** Observable of the currently selected video input device. */
5295
+ get selectedVideoInputDevice$(): Observable<MediaDeviceInfo | null>;
5296
+ /** Currently selected audio input device, or `null` if none. */
5297
+ get selectedAudioInputDevice(): MediaDeviceInfo | null;
5298
+ /** Currently selected audio output device, or `null` if none. */
5299
+ get selectedAudioOutputDevice(): MediaDeviceInfo | null;
5300
+ /** Currently selected video input device, or `null` if none. */
5301
+ get selectedVideoInputDevice(): MediaDeviceInfo | null;
5302
+ /** Media track constraints for the selected audio input device. Returns `false` when disabled. */
5303
+ get selectedAudioInputDeviceConstraints(): MediaTrackConstraints | boolean;
5304
+ /** Media track constraints for the selected video input device. Returns `false` when disabled. */
5305
+ get selectedVideoInputDeviceConstraints(): MediaTrackConstraints | boolean;
5306
+ /** Converts a `MediaDeviceInfo` to track constraints suitable for `getUserMedia`. */
5307
+ deviceInfoToConstraints(deviceInfo: MediaDeviceInfo | null): MediaTrackConstraints;
5308
+ /** Sets the preferred audio input device. */
5309
+ selectAudioInputDevice(device: MediaDeviceInfo | null): void;
5310
+ /** Sets the preferred video input device. */
5311
+ selectVideoInputDevice(device: MediaDeviceInfo | null): void;
5312
+ /** Sets the preferred audio output device. */
5313
+ selectAudioOutputDevice(device: MediaDeviceInfo | null): void;
4899
5314
  /**
4900
- * Observable of the RMS audio level of the local microphone, 0..1.
4901
- * Emits at ~30fps while a mic track is active. Engages the local audio
4902
- * pipeline on first subscription.
5315
+ * Apply the currently selected audio output device to an HTMLMediaElement
5316
+ * (e.g. the `<audio>` or `<video>` element the consumer attached the
5317
+ * remote stream to). Uses `HTMLMediaElement.setSinkId` under the hood.
5318
+ * Returns a `Promise<boolean>`: `true` if the sink was applied,
5319
+ * `false` if the browser doesn't support `setSinkId` or no device is
5320
+ * selected.
5321
+ *
5322
+ * @example
5323
+ * ```ts
5324
+ * audioEl.srcObject = call.remoteStream;
5325
+ * await client.applySelectedAudioOutputDevice(audioEl);
5326
+ * ```
4903
5327
  */
4904
- get localAudioLevel$(): Observable<number>;
5328
+ applySelectedAudioOutputDevice(element: HTMLMediaElement): Promise<boolean>;
5329
+ /** Starts monitoring for media device changes (connect/disconnect). */
5330
+ enableDeviceMonitoring(): void;
5331
+ /** Stops monitoring for media device changes. */
5332
+ disableDeviceMonitoring(): void;
4905
5333
  /**
4906
- * Observable that is `true` while the local participant is speaking
4907
- * (RMS level above the VAD threshold, with hold time to avoid flicker).
5334
+ * Returns the capabilities of a media device.
5335
+ * @param deviceInfo - The device to query.
5336
+ * @returns The device capabilities, or `null` if unavailable.
4908
5337
  */
4909
- get localSpeaking$(): Observable<boolean>;
5338
+ getDeviceCapabilities(deviceInfo: MediaDeviceInfo): Promise<MediaTrackCapabilities | null>;
4910
5339
  /**
4911
- * Enable push-to-talk: while {@link setPushToTalkActive} has been called
4912
- * with `false`, the microphone gain is forced to 0; calling
4913
- * {@link setPushToTalkActive} with `true` restores the configured gain.
4914
- * Use this instead of mute/unmute for instant talk/silence transitions
4915
- * because it doesn't rebuild the track.
4916
- *
4917
- * This method installs the pipeline but does not attach any keyboard
4918
- * listener — consumers bind the key themselves and call
4919
- * {@link setPushToTalkActive} on keydown/keyup.
5340
+ * Checks whether a device is still available and usable.
5341
+ * @param deviceInfo - The device to validate, or `null`.
5342
+ * @returns `true` if the device is valid and available. Returns `false` for `null`, audio output devices, or unavailable devices.
4920
5343
  */
4921
- enablePushToTalk(): void;
4922
- /** Disable push-to-talk; mic gain returns to the configured value. */
4923
- disablePushToTalk(): void;
5344
+ isValidDevice(deviceInfo: MediaDeviceInfo | null): Promise<boolean>;
5345
+ /** Injects a storage manager into the device controller for persistence. */
5346
+ setStorageManager(storageManager: StorageManager): void;
5347
+ /** Clears all device state and re-enumerates. */
5348
+ clearDeviceState(): Promise<void>;
5349
+ /** Forces a device re-enumeration. */
5350
+ enumerateDevices(): Promise<void>;
5351
+ /** Disables audio input (receive-only mode). No audio track will be acquired. */
5352
+ disableAudioInput(): void;
5353
+ /** Re-enables audio input, restoring the last selection or auto-selecting. */
5354
+ enableAudioInput(): void;
5355
+ /** Disables video input (receive-only mode). No video track will be acquired. */
5356
+ disableVideoInput(): void;
5357
+ /** Re-enables video input, restoring the last selection or auto-selecting. */
5358
+ enableVideoInput(): void;
5359
+ /** Observable that emits `true` when video input is disabled (receive-only). */
5360
+ get videoInputDisabled$(): Observable<boolean>;
5361
+ /** Observable that emits `true` when audio input is disabled (receive-only). */
5362
+ get audioInputDisabled$(): Observable<boolean>;
5363
+ /** Whether video input is currently disabled. */
5364
+ get videoInputDisabled(): boolean;
5365
+ /** Whether audio input is currently disabled. */
5366
+ get audioInputDisabled(): boolean;
4924
5367
  /**
4925
- * While push-to-talk is enabled, sets the talk state. `true` = transmitting,
4926
- * `false` = silent. No-op if push-to-talk has not been enabled.
5368
+ * Triggers the browser's media permission dialog and captures the user's device selections.
5369
+ *
5370
+ * @param options - Which permissions to request.
5371
+ * @param options.audio - Whether to request audio permission.
5372
+ * @param options.video - Whether to request video permission.
5373
+ * @returns The permission result with selected devices.
4927
5374
  */
4928
- setPushToTalkActive(active: boolean): void;
5375
+ requestMediaPermissions(options?: {
5376
+ audio?: boolean;
5377
+ video?: boolean;
5378
+ }): Promise<PermissionResult>;
4929
5379
  /**
4930
- * Toggle echo cancellation on the local mic at runtime. Applied via
4931
- * `track.applyConstraints`; browsers that don't honour runtime constraints
4932
- * (notably iOS Safari) fall back to re-acquiring the track with the new
4933
- * constraint set and plumbing the replacement through the local audio
4934
- * pipeline if one is active.
5380
+ * Clears all SDK-persisted state and resets to defaults.
5381
+ *
5382
+ * This clears device preferences, device history, authorization state,
5383
+ * attached call IDs, and all SDK storage keys, then re-enumerates devices.
4935
5384
  */
4936
- setEchoCancellation(enabled: boolean): Promise<void>;
4937
- /** Toggle browser noise suppression on the local mic at runtime. */
4938
- setNoiseSuppression(enabled: boolean): Promise<void>;
4939
- /** Toggle browser automatic gain control on the local mic at runtime. */
4940
- setAutoGainControl(enabled: boolean): Promise<void>;
5385
+ resetToDefaults(): Promise<void>;
4941
5386
  /**
4942
- * Observable of the aggregate remote audio level, 0..1 RMS. The server
4943
- * delivers a single mixed audio stream for all remote participants — this
4944
- * meter reports that mix. Per-participant audio is not available client-side.
5387
+ * Destroys the client, clearing timers and releasing all resources.
4945
5388
  *
4946
- * Engages a shared AudioContext on first subscription (cheap — one
4947
- * AnalyserNode, no GainNode, no destination) so it does not affect the
4948
- * caller's audio element playback.
5389
+ * Intentionally destroying the client ends its session: the resume state
5390
+ * (`authorization_state` + protocol) and the attach records are both
5391
+ * cleared. Credentials and device preferences are preserved — use
5392
+ * {@link resetToDefaults} for a full wipe. To temporarily stop receiving
5393
+ * inbound calls while keeping the session alive, use `unregister()`.
4949
5394
  */
4950
- get remoteAudioLevel$(): Observable<number>;
4951
- /** Destroys the call, releasing all resources and subscriptions. */
4952
5395
  destroy(): void;
4953
- /**
4954
- * @internal Send a verto.subscribe message to add an event type to the
4955
- * server's subscription list for this call. Returns the underlying RPC
4956
- * promise so callers can decide whether to cache the observable on success
4957
- * or retry on failure.
4958
- */
4959
- private _sendVertoSubscribe;
5396
+ }
5397
+ //#endregion
5398
+ //#region src/utils/embeddableCall.d.ts
5399
+ /** Options for {@link embeddableCall}. */
5400
+ interface EmbeddableCallOptions {
5401
+ /** Destination URI to call. */
5402
+ to: string;
5403
+ /** Embed token for authentication. */
5404
+ embedToken: string;
5405
+ /** SignalWire host URL. */
5406
+ host: string;
5407
+ }
5408
+ /**
5409
+ * Creates a call using an embed token for simple, embeddable integrations.
5410
+ *
5411
+ * Handles client creation, authentication, and dialing in a single call.
5412
+ *
5413
+ * @param options - Embed token, host, and destination.
5414
+ * @returns The created {@link Call} instance.
5415
+ */
5416
+ declare function embeddableCall(options: EmbeddableCallOptions): Promise<Call>;
5417
+ //#endregion
5418
+ //#region src/dependencies/StaticCredentialProvider.d.ts
5419
+ /**
5420
+ * Credential provider that returns a fixed set of credentials.
5421
+ *
5422
+ * Use when the token is already available (e.g. from a backend endpoint).
5423
+ *
5424
+ * @example
5425
+ * ```ts
5426
+ * const provider = new StaticCredentialProvider({ token: 'my-sat-token' });
5427
+ * const client = new SignalWire(provider);
5428
+ * ```
5429
+ */
5430
+ declare class StaticCredentialProvider implements CredentialProvider {
5431
+ private credentials;
5432
+ constructor(credentials: SDKCredential);
5433
+ /** Returns the static credentials. */
5434
+ authenticate(): Promise<SDKCredential>;
5435
+ }
5436
+ //#endregion
5437
+ //#region src/dependencies/EmbedTokenCredentialProvider.d.ts
5438
+ /** Credential provider that exchanges an embed token for a SAT via the host's token endpoint. */
5439
+ declare class EmbedTokenCredentialProvider implements CredentialProvider {
5440
+ private host;
5441
+ private embedToken;
5442
+ constructor(host: string, embedToken: string);
5443
+ private fetchSAT;
5444
+ authenticate(): Promise<{
5445
+ token: string;
5446
+ expiry_at: number;
5447
+ }>;
5448
+ refresh(): Promise<{
5449
+ token: string;
5450
+ expiry_at: number;
5451
+ }>;
4960
5452
  }
4961
5453
  //#endregion
4962
5454
  //#region src/index.d.ts
@@ -4971,5 +5463,5 @@ declare const version: string;
4971
5463
  */
4972
5464
  declare const ready: boolean;
4973
5465
  //#endregion
4974
- 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 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, 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 };
5466
+ export { Address, type AddressHistory, type AudioConstraintsEvent, type AuthenticateContext, AuxiliaryLegCancelledError, AuxiliaryLegTimeoutError, 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, ScreenShareAlreadyActiveError, type ScreenShareOptions, 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 };
4975
5467
  //# sourceMappingURL=index.d.cts.map