{"version":3,"file":"index.cjs","names":["Subject","asapScheduler","ReplaySubject","BehaviorSubject","ValidationError","result: Awaited<T>","logger","getLogger","baseURL: string","getCredential: () => SDKCredential","UnexpectedError","timeout","RequestTimeoutError","headers: HTTPHeaders","logger","getLogger","DEFAULT_DEVICE_DEBOUNCE_TIME_MS","DEFAULT_DEVICE_POLLING_INTERVAL_MS","DEFAULT_RECONNECT_CALLS_TIMEOUT_MS","DEFAULT_CONNECTION_TIMEOUT_MS","DEFAULT_RECONNECT_DELAY_MIN_MS","DEFAULT_RECONNECT_DELAY_MAX_MS","DEFAULT_ICE_CANDIDATE_TIMEOUT_MS","DEFAULT_ICE_GATHERING_TIMEOUT_MS","DEFAULT_STATS_POLLING_INTERVAL_MS","DEFAULT_STATS_BASELINE_SAMPLES","DEFAULT_STATS_NO_PACKET_THRESHOLD_MS","DEFAULT_STATS_RTT_SPIKE_MULTIPLIER","DEFAULT_STATS_PACKET_LOSS_THRESHOLD","DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER","DEFAULT_STATS_HISTORY_SIZE","DEFAULT_KEYFRAME_MAX_BURST","DEFAULT_KEYFRAME_BURST_WINDOW_MS","DEFAULT_KEYFRAME_COOLDOWN_MS","DEFAULT_REINVITE_DEBOUNCE_TIME_MS","DEFAULT_REINVITE_MAX_ATTEMPTS","DEFAULT_REINVITE_TIMEOUT_MS","DEFAULT_RECOVERY_DEBOUNCE_TIME_MS","DEFAULT_RECOVERY_COOLDOWN_MS","DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS","DEFAULT_ICE_RESTART_TIMEOUT_MS","DEFAULT_MAX_RECOVERY_ATTEMPTS","DEFAULT_ENABLE_RELAY_FALLBACK","DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION","DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION","DEFAULT_PERSIST_DEVICE_SELECTION","DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS","DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN","DEFAULT_REFRESH_DEVICES_ON_VISIBLE","DEFAULT_CHECK_CONNECTION_ON_VISIBLE","DEFAULT_STEREO_AUDIO","DEFAULT_ENABLE_AUTO_DEGRADATION","DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS","DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS","STORED_NUMBER_KEYS: (keyof StoredPreferences & keyof Preferences)[]","STORED_BOOLEAN_KEYS: (keyof StoredPreferences & keyof Preferences)[]","PREFERENCES_STORAGE_KEY","logger","getLogger","DEVICE_STORAGE_KEYS: Record<DeviceKind, string>","DEVICE_STORAGE_KEY_AUDIO_INPUT","DEVICE_STORAGE_KEY_AUDIO_OUTPUT","DEVICE_STORAGE_KEY_VIDEO_INPUT","initialDevicesState: DevicesState","initialSelectedDevicesState: SelectedDevicesState","webRTCApiProvider: WebRTCApiProvider","device","stored: StoredDevicePreference","devicesByKind: DevicesState","StorageNotAvailableError","keysToRemove: string[]","storageImpl: Storage","SerializationError","StorageWriteError","item: string | null","StorageReadError","DeserializationError","DependencyError","logger","getLogger","DPoPInitError","payload: Record<string, unknown>","logger","getLogger","codecs: string[]","capabilities: PlatformCapabilities","logger","getLogger","deviceController: DeviceController","iceServers: RTCIceServer[]","isConnected: boolean","transportRttMs: number","dialFn: DialFn","warnings: string[]","bandwidth: PreflightResult['bandwidth']","PreflightError","audioStream: MediaStream | undefined","constraints: MediaStreamConstraints","pc: RTCPeerConnection | undefined","timer","type: IceTestResult['type']","call: Call | undefined","logger","getLogger","changeEvent: VisibilityChangeEvent","fromPath: string","http: HTTPRequestController","SDK_TO_VERTO_FIELD_MAP: Record<string, string>","VertoByeCauseCodes: Record<VertoByeCause, string>","current: unknown","JSONRPCError","RPC_ERROR_REQUESTER_VALIDATION_FAILED","RPC_ERROR_AUTHENTICATION_FAILED","logger","getLogger","storage: StorageManager","deviceController: DeviceController","reconnectCallsTimeout: number","attachKey: string","credentialRecovered: () => boolean","timeout","DEFAULT_ON_OFF: OnOffCapability","DEFAULT_MEMBER_CAPABILITIES: MemberCapabilities","DEFAULT_CALL_CAPABILITIES: CallCapabilitiesState","logger","getLogger","initialState: Partial<ParticipantState>","callExecuteMethod: ExecuteMethod","deviceController: DeviceController","ParticipantNotReadyError","UnimplementedError","vertoManager: VertoManager","AuxiliaryLegCancelledError","logger","getLogger","initialSessionState: Partial<SessionState>","webRtcCallSession: CallManager","options: WebRTCCallEventManagerOptions","filterNull","filterAs","logger","getLogger","exactConstraints: MediaStreamConstraints","preferredConstraints: MediaStreamConstraints","defaultConstraints: MediaStreamConstraints","SDP_DIRECTIONS: ReadonlySet<string>","result: MediaDirections","currentMediaKind: 'audio' | 'video' | null","currentDirection: MediaDirection | null","mediaSectionsValidCandidates: number[]","result: string[]","DEFAULT_STEREO_MAX_AVERAGE_BITRATE","updatedLines: string[]","withFmtp: string[]","preferred: string[]","remaining: string[]","logger","getLogger","peerConnection: RTCPeerConnection","peerConnectionControllerNegotiating$: Observable<boolean>","DEFAULT_ICE_CANDIDATE_TIMEOUT_MS","DEFAULT_ICE_GATHERING_TIMEOUT_MS","timer","logger","getLogger","VAD_THRESHOLD","VAD_HOLD_MS","AUDIO_LEVEL_POLL_INTERVAL_MS","animationFrameScheduler","logger","getLogger","options: LocalStreamControllerOptions","stream: MediaStream","constraints: MediaStreamConstraints","logger","getLogger","transceiverParams: RTCRtpTransceiverInit","MediaTrackError","constraints: MediaStreamConstraints","constraintsToApply: MediaTrackConstraints","logger","getLogger","options: RTCPeerConnectionControllerOptionsPartial","filterNull","constraints: MediaTrackConstraints","MediaTrackError","options","DependencyError","options: RTCOfferOptions","ICE_GATHERING_COMPLETE_TIMEOUT_MS","InvalidParams","localStream: MediaStream","MediaAccessError","stream: MediaStream","isMediaDeviceInUse","sender","merged: MediaTrackConstraints","answer: RTCSessionDescriptionInit","logger","getLogger","node: unknown","webRtcCallSession: WebRTCCall","attachManager: AttachManager","deviceController: DeviceController","webRTCApiProvider: WebRTCApiProvider","DependencyError","failures: Error[]","VertoPongError","filterAs","JSONRPCError","getValueFrom","vertoMethod: VertoMethod","vertoByeOrAccepted: boolean | VertoByeInboundParams | null","answerOptions: MediaOptions | undefined","filterNull","INVITE_VERSION","deviceKind: 'audio' | 'video' | 'both' | undefined","InvalidParams","ScreenShareAlreadyActiveError","firstPeerConnectionError: Error | undefined","rtcPeerConnController: RTCPeerConnectionController | null","DEFAULT_AUX_LEG_CONNECT_TIMEOUT_MS","AuxiliaryLegCancelledError","EmptyError","MediaAccessError","TimeoutError","AuxiliaryLegTimeoutError","removeTrack: 'audio' | 'video' | 'both' | undefined","logger","getLogger","AUDIO_LEVEL_POLL_INTERVAL_MS","animationFrameScheduler","logger","getLogger","peerConnection: RTCPeerConnection","EMPTY","now","metrics: NetworkMetrics","availableOutgoingBitrate: number | undefined","issues: NetworkIssue[]","logger","getLogger","DEGRADATION_ONLY_ISSUES: ReadonlySet<string>","EMPTY","executeMethod: ExecuteMethod","vertoManager: VertoManager","deviceController: DeviceController","getValueFrom","logger","getLogger","params: Record<string, unknown>","clientSession: ClientSession","options: CallOptions","address?: Address","getValueFrom","UnimplementedError","InvalidParams","params: JSONRPCParams","response: T","JSONRPCError","control: Record<string, unknown>","CallNotReadyError","PEER_CONNECTION_RECOVERY_WAIT_MS","state: RTCPeerConnectionState","PEER_CONNECTION_RECOVERY_POLL_MS","filterAs","targets: [CallParticipant, VideoPosition][]","RPCTimeoutError","JSONRPCError","MediaTrackError","MediaAccessError","WebSocketConnectionError","TransportConnectionError","RPC_ERROR_REQUESTER_VALIDATION_FAILED","RPC_ERROR_AUTHENTICATION_FAILED","RPC_ERROR_INVALID_PARAMS","VertoPongError","sessionManager: ClientSession","deviceController: DeviceController","attachManager: AttachManager","webRTCApiProvider: WebRTCApiProvider","networkChange$?: Observable<NetworkChangeEvent>","callError: CallError","options","logger","getLogger","endpoint: string","http: HTTPRequestController","fetchController: FetchController<T>","update$: Observable<Partial<T>>","onError?: (error: Error) => void","CollectionFetchError","ReplaySubject","originalCollection: EntityCollection<O>","filter: (i: unknown) => i is O","mapper: (item: O) => T","addressId: string","conversationManager: ConversationsProvider","addressProvider: AddressProvider<Address>","DependencyError","filterNull","UnimplementedError","logger","getLogger","Observable","timer","RPCTimeoutError","NEVER","JSONRPCError","logger","getLogger","MediaAccessError","from","UnexpectedError","getCredential: () => SDKCredential","transport: TransportManager","storage: StorageManager","authorizationStateKey: string","attachManager: AttachManager","dpopManager?: CryptoController","AuthStateHandlerError","EMPTY","VertoInviteHandlerError","VertoAttachHandlerError","filterAs","DependencyError","TransportConnectionError","isRecoverableAuthError","JSONRPCError","RPC_ERROR_REQUESTER_VALIDATION_FAILED","RPC_ERROR_INVALID_PARAMS","RPC_ERROR_AUTHENTICATION_FAILED","throwOnRPCError","dpopToken: string | undefined","CREDENTIAL_EXPIRY_SKEW_MS","callSession: WebRTCCall | undefined","DEFAULT_CALL_SIGNALING_TIMEOUT_MS","CallCreateError","TimeoutError","address: Address | undefined","clientSessionManager: ClientSessionManager","logger","getLogger","groupId: string","clientSession: ClientSessionManager","http: HTTPRequestController","getUserAddressId: () => string","onError?: (error: Error) => void","ConversationError","filterAs","logger","getLogger","DEVICE_TOKEN_DEFAULT_EXPIRE_IN","dpopManager: CryptoController","http: () => HTTPRequestController","errorHandler: (error: Error) => void","getCredential: () => SDKCredential","DEVICE_TOKEN_REFRESH_BUFFER_MS","SAT_REFRESH_SCOPE","rpcProof","DPoPInitError","stamped: DeviceTokenResponse","DEVICE_TOKEN_ENDPOINT","DeviceTokenError","DEVICE_REFRESH_ENDPOINT","TokenRefreshError","lastError: unknown","DEVICE_TOKEN_REFRESH_MAX_RETRIES","DEVICE_TOKEN_REFRESH_RETRY_BASE_MS","logger","getLogger","defaultDeviceTokenManagerFactory: DeviceTokenManagerFactory","deps: RefreshCoordinatorDeps","CREDENTIAL_REFRESH_BUFFER_MS","CREDENTIAL_REFRESH_RETRY_BASE_MS","CREDENTIAL_REFRESH_MAX_DELAY_MS","error: unknown","CREDENTIAL_REFRESH_MAX_RETRIES","TokenRefreshError","InvalidCredentialsError","timer","CREDENTIAL_ACTIVATE_TIMEOUT_MS","logger","getLogger","entry: DiagnosticEvent","logger","getLogger","http: HTTPRequestController","conversationManager: ConversationsProvider","onError?: (error: Error) => void","filterNull","filterAs","logger","getLogger","WebSocketConstructor: WebSocketAdapter | NodeSocketAdapter","endpoint: string","outgoingMessages$: Observable<string | ArrayBuffer | Blob>","UnexpectedError","WebSocketConnectionError","WebSocketTimeoutError","logger","getLogger","storage: StorageManager","protocolKey: string","onError?: (error: Error) => void","eventChannel: string | undefined","MessageParseError","EMPTY","TransportConnectionError","SERVER_PING_TIMEOUT_MS","SERVER_PING_PROBE_TIMEOUT_MS","logger","getLogger","InvalidCredentialsError","decodeHeader: JWTHeader","error: unknown","newCredentials: SDKCredential","user","UnexpectedError","base: SessionDiagnostics","ready","failureCause: unknown","constraints: MediaStreamConstraints","selectedAudioDevice: MediaDeviceInfo | undefined","selectedVideoDevice: MediaDeviceInfo | undefined","getLogger","host: string","embedToken: string","timeout","RequestError","RequestTimeoutError","DependencyError","credentials: SDKCredential","version: string","ready: boolean"],"sources":["../src/behaviors/Destroyable.ts","../src/utils/asyncRetry.ts","../src/controllers/HTTPRequestController.ts","../src/controllers/DeviceHistoryManager.ts","../src/utils/time.ts","../src/containers/PreferencesContainer.ts","../src/utils/toError.ts","../src/controllers/NavigatorDeviceController.ts","../src/dependencies/DefaultLocalStorage.ts","../src/managers/StorageManager.ts","../src/containers/DependencyContainer.ts","../src/controllers/CryptoController.ts","../src/controllers/NetworkMonitor.ts","../src/controllers/PlatformCapabilities.ts","../src/controllers/PreflightRunner.ts","../src/controllers/VisibilityController.ts","../src/behaviors/Fetchable.ts","../src/core/entities/User.ts","../src/core/RPCMessages/helpers.ts","../src/core/RPCMessages/RPCConnect.ts","../src/core/RPCMessages/RPCReauthenticate.ts","../src/core/RPCMessages/RPCPing.ts","../src/core/RPCMessages/RPCExecute.ts","../src/core/RPCMessages/VertoMessages.ts","../src/core/RPCMessages/RPCEventAck.ts","../src/utils/authRecovery.ts","../src/managers/AttachManager.ts","../src/core/capabilities/types.ts","../src/core/capabilities/computeCapabilities.ts","../src/core/capabilities/SelfCapabilities.ts","../src/core/RPCMessages/utils.ts","../src/core/entities/Participant.ts","../src/core/RPCMessages/guards/base.guards.ts","../src/core/RPCMessages/guards/events.guards.ts","../src/managers/CallEventsManager.ts","../src/controllers/ConstraintFallbackHelper.ts","../src/helpers/SDPHelper.ts","../src/controllers/ICEGatheringController.ts","../src/controllers/LocalAudioPipeline.ts","../src/controllers/LocalStreamController.ts","../src/controllers/TransceiverController.ts","../src/controllers/RTCPeerConnectionController.ts","../src/core/RPCMessages/guards/verto.guards.ts","../src/managers/VertoManager.ts","../src/controllers/RemoteAudioMeter.ts","../src/controllers/RTCStatsMonitor.ts","../src/managers/CallRecoveryManager.ts","../src/managers/ParticipantFactory.ts","../src/utils/qualityScore.ts","../src/utils/unwrapVertoReply.ts","../src/core/entities/Call.ts","../src/managers/CallFactory.ts","../src/behaviors/Collection.ts","../src/core/entities/Address.ts","../src/core/utils.ts","../src/managers/ClientSessionManager.ts","../src/utils/isString.ts","../src/managers/ConversationsManager.ts","../src/managers/DeviceTokenManager.ts","../src/managers/CredentialRefreshCoordinator.ts","../src/managers/DiagnosticsCollector.ts","../src/utils/arrays.ts","../src/utils/warmup.ts","../src/managers/DirectoryManager.ts","../src/controllers/WebSocketController.ts","../src/core/RPCMessages/guards/methods.guards.ts","../src/managers/TransportManager.ts","../src/clients/SignalWire.ts","../src/dependencies/EmbedTokenCredentialProvider.ts","../src/utils/embeddableCall.ts","../src/dependencies/StaticCredentialProvider.ts","../src/index.ts"],"sourcesContent":["import { Subject, ReplaySubject, BehaviorSubject, observeOn, asapScheduler } from 'rxjs';\n\nimport type { Observable, Subscription, Observer } from 'rxjs';\n\nexport abstract class Destroyable {\n  protected subscriptions: Subscription[] = [];\n  protected subjects: Subject<unknown>[] = [];\n  protected _destroyed$ = new Subject<void>();\n  private _observableCache?: Map<string, Observable<unknown>>;\n\n  public destroy(): void {\n    this._observableCache?.clear();\n    this.subscriptions.forEach((sub) => sub.unsubscribe());\n    this.subjects.forEach((subject) => subject.complete());\n    this._destroyed$.next();\n    this._destroyed$.complete();\n  }\n\n  protected cachedObservable<T>(key: string, factory: () => Observable<T>): Observable<T> {\n    this._observableCache ??= new Map();\n    let cached = this._observableCache.get(key) as Observable<T> | undefined;\n    if (!cached) {\n      cached = factory();\n      this._observableCache.set(key, cached as Observable<unknown>);\n    }\n    return cached;\n  }\n\n  /**\n   * Like `cachedObservable`, but defers emissions to the microtask queue\n   * via `observeOn(asapScheduler)`.\n   *\n   * Use ONLY for public-facing observable getters that external consumers\n   * subscribe to. Prevents a class of bugs where `BehaviorSubject` or\n   * `ReplaySubject` replays synchronously during `subscribe()`, before\n   * the subscription variable is assigned in the caller's scope.\n   *\n   * Do NOT use for observables consumed internally by the SDK — internal\n   * code using `subscribeTo()`, `firstValueFrom()`, or `withLatestFrom()`\n   * depends on synchronous emission delivery.\n   */\n  protected publicCachedObservable<T>(key: string, factory: () => Observable<T>): Observable<T> {\n    const publicKey = `public:${key}`;\n    this._observableCache ??= new Map();\n    let cached = this._observableCache.get(publicKey) as Observable<T> | undefined;\n    if (!cached) {\n      cached = factory().pipe(observeOn(asapScheduler));\n      this._observableCache.set(publicKey, cached as Observable<unknown>);\n    }\n    return cached;\n  }\n\n  /**\n   * Wraps an observable so emissions are deferred to the microtask queue.\n   *\n   * Use ONLY for public-facing getters that expose a subject via\n   * `.asObservable()` without going through `cachedObservable`.\n   *\n   * Do NOT use for observables consumed internally by the SDK.\n   */\n  protected deferEmission<T>(observable: Observable<T>): Observable<T> {\n    return observable.pipe(observeOn(asapScheduler));\n  }\n\n  protected subscribeTo<T>(\n    observable: Observable<T>,\n    observerOrNext: Partial<Observer<T>> | ((value: T) => void) | undefined\n  ): void {\n    const subscription = observable.subscribe(observerOrNext);\n    this.subscriptions.push(subscription);\n  }\n\n  protected createSubject<T>(): Subject<T> {\n    const subject = new Subject<T>();\n    this.subjects.push(subject as Subject<unknown>);\n    return subject;\n  }\n\n  protected createReplaySubject<T>(bufferSize?: number, windowTime?: number): ReplaySubject<T> {\n    const subject = new ReplaySubject<T>(bufferSize, windowTime);\n    this.subjects.push(subject as Subject<unknown>);\n    return subject;\n  }\n\n  protected createBehaviorSubject<T>(initialValue: T): BehaviorSubject<T> {\n    const subject = new BehaviorSubject<T>(initialValue);\n    this.subjects.push(subject as Subject<unknown>);\n    return subject;\n  }\n\n  /**\n   * Observable that emits when the instance is destroyed\n   */\n  public get destroyed$(): Observable<void> {\n    return this._destroyed$.asObservable();\n  }\n}\n","import { getLogger } from './logger';\nimport { ValidationError } from '../core/errors';\n\nconst DEFAULT_MAX_RETRIES = 10;\nconst DEFAULT_INITIAL_DELAY = 100;\nconst DEFAULT_DELAY_VARIATION = 1;\n\ninterface AsyncRetryOptions<T> {\n  asyncCallable: () => Promise<T>;\n  maxRetries?: number;\n  delayFn?: () => number;\n  validator?: (promiseResult: T) => void | never;\n  expectedErrorHandler?: (error: unknown) => boolean;\n}\n\ninterface DelayOptions {\n  initialDelay?: number;\n  variation?: number;\n  delayLimit?: number;\n}\n\nexport const increasingDelay = ({\n  delayLimit: upperDelayLimit = Number.MAX_SAFE_INTEGER,\n  initialDelay = DEFAULT_INITIAL_DELAY,\n  variation = DEFAULT_DELAY_VARIATION\n}: DelayOptions): (() => number) => {\n  if (initialDelay < 0) {\n    throw new ValidationError('initialDelay must be gte 0');\n  }\n  if (upperDelayLimit < 0) {\n    throw new ValidationError('upperDelayLimit must be gte 0');\n  }\n  if (variation < 0) {\n    throw new ValidationError('variation must be gte 0');\n  }\n  if (initialDelay > upperDelayLimit) {\n    throw new ValidationError('initialDelay must be lte delayLimit');\n  }\n\n  let delay = Math.min(initialDelay, upperDelayLimit);\n  return () => {\n    if (delay === upperDelayLimit) {\n      // stop incrementing the delay and just return upperDelayLimit\n      return upperDelayLimit;\n    }\n    const currentDelay = delay;\n    delay = Math.min(delay + variation, upperDelayLimit);\n\n    return currentDelay;\n  };\n};\n\nexport const decreasingDelay = ({\n  delayLimit: bottomDelayLimit = 0,\n  initialDelay = DEFAULT_INITIAL_DELAY,\n  variation = DEFAULT_DELAY_VARIATION\n}: DelayOptions): (() => number) => {\n  if (initialDelay < 0) {\n    throw new ValidationError('initialDelay must be gte 0');\n  }\n  if (bottomDelayLimit < 0) {\n    throw new ValidationError('bottomDelayLimit must be gte 0');\n  }\n  if (variation < 0) {\n    throw new ValidationError('variation must be gte 0');\n  }\n  if (initialDelay < bottomDelayLimit) {\n    throw new ValidationError('initialDelay must be gte delayLimit');\n  }\n\n  let delay = Math.max(initialDelay, bottomDelayLimit);\n\n  return () => {\n    if (delay === bottomDelayLimit) {\n      // stop incrementing the delay and just return upperDelayLimit\n      return bottomDelayLimit;\n    }\n    const currentDelay = delay;\n    delay = Math.max(delay - variation, bottomDelayLimit);\n\n    return currentDelay;\n  };\n};\n\nexport const constDelay = ({\n  initialDelay = DEFAULT_INITIAL_DELAY\n}: Pick<DelayOptions, 'initialDelay'>): (() => number) => {\n  if (initialDelay < 0) {\n    throw new ValidationError('initialDelay must be gte 0');\n  }\n  return () => initialDelay;\n};\n\nexport const asyncRetry = async <T>({\n  asyncCallable,\n  maxRetries: retries = DEFAULT_MAX_RETRIES,\n  delayFn,\n  validator,\n  expectedErrorHandler\n}: AsyncRetryOptions<T>): Promise<T> => {\n  let remainingAttempts = retries - 1; // the 1st call counts as an attempt\n  let wait = 0;\n\n  const promiseAttempt = async (): Promise<T> => {\n    try {\n      let result: Awaited<T>;\n\n      // Should not defer the call when: wait <= 0\n      if (wait <= 0) {\n        result = await asyncCallable();\n      } else {\n        result = await new Promise<T>((resolve, reject) =>\n          setTimeout(() => {\n            asyncCallable().then(resolve).catch(reject);\n          }, wait)\n        );\n      }\n\n      if (remainingAttempts) {\n        // avoid messing with the normal returns in the last attempt\n        validator?.(result);\n      }\n\n      return result;\n    } catch (error) {\n      if (remainingAttempts-- > 0 && !expectedErrorHandler?.(error)) {\n        wait = delayFn?.() ?? 0;\n        getLogger().debug(`Retrying request: ${retries - remainingAttempts} of ${retries}`);\n        return promiseAttempt();\n      } else {\n        throw error;\n      }\n    }\n  };\n\n  return promiseAttempt();\n};\n","import { Destroyable } from '../behaviors/Destroyable';\nimport { RequestTimeoutError, UnexpectedError } from '../core/errors';\nimport { asyncRetry, increasingDelay } from '../utils/asyncRetry';\nimport { getLogger } from '../utils/logger';\n\nimport type {\n  HTTPRequest,\n  HTTPResponse,\n  HTTPHeaders,\n  SDKCredential\n} from '../core/types/common.types';\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\nexport type HTTPRequestStatus = 'idle' | 'requesting' | 'success' | 'error';\n\nexport interface HTTPRequestControllerOptions {\n  maxRetries?: number;\n  retryDelayMin?: number;\n  retryDelayMax?: number;\n  requestTimeout?: number;\n}\n\nexport const GET_PARAMS = {\n  method: 'GET',\n  headers: {\n    Accept: 'application/json'\n  }\n} as const;\n\nexport const POST_PARAMS = {\n  method: 'POST',\n  headers: {\n    Accept: 'application/json',\n\n    'Content-Type': 'application/json'\n  }\n} as const;\n\nexport class HTTPRequestController extends Destroyable {\n  // Default configuration values\n  private static readonly defaultMaxRetries = 3;\n  private static readonly defaultRetryDelayMinMs = 1000;\n  private static readonly defaultRetryDelayMaxMs = 30000;\n  private static readonly defaultRequestTimeoutMs = 30000;\n\n  /** Sensitive field names to mask in debug logs. */\n  private static readonly SENSITIVE_BODY_FIELDS = new Set(['dpop_token', 'token', 'jwt_token']);\n  // Configuration\n  private readonly maxRetries: number;\n  private readonly retryDelayMin: number;\n  private readonly retryDelayMax: number;\n  private readonly requestTimeout: number;\n  private _responses$ = this.createSubject<HTTPResponse>();\n  private _errors$ = this.createSubject<Error>();\n\n  // Observable streams\n  private _status$ = this.createBehaviorSubject<HTTPRequestStatus>('idle');\n\n  constructor(\n    private baseURL: string,\n    private readonly getCredential: () => SDKCredential,\n    options: HTTPRequestControllerOptions = {}\n  ) {\n    super();\n    this.maxRetries = options.maxRetries ?? HTTPRequestController.defaultMaxRetries;\n    this.retryDelayMin = options.retryDelayMin ?? HTTPRequestController.defaultRetryDelayMinMs;\n    this.retryDelayMax = options.retryDelayMax ?? HTTPRequestController.defaultRetryDelayMaxMs;\n    this.requestTimeout = options.requestTimeout ?? HTTPRequestController.defaultRequestTimeoutMs;\n  }\n\n  public get status$(): Observable<HTTPRequestStatus> {\n    return this._status$.asObservable();\n  }\n\n  public get status(): HTTPRequestStatus {\n    return this._status$.value;\n  }\n\n  public get responses$(): Observable<HTTPResponse> {\n    return this._responses$.asObservable();\n  }\n\n  public get errors$(): Observable<Error> {\n    return this._errors$.asObservable();\n  }\n\n  public async request(request: HTTPRequest): Promise<HTTPResponse> {\n    this._status$.next('requesting');\n\n    try {\n      const response = await this.executeWithRetry(request);\n      this._status$.next('success');\n      this._responses$.next(response);\n      return response;\n    } catch (error) {\n      logger.error('[HTTPRequestController] Request error:', error);\n      this._status$.next('error');\n      const err =\n        error instanceof Error ? error : new Error('HTTP request failed', { cause: error });\n      this._errors$.next(err);\n      throw err;\n    }\n  }\n\n  private async executeWithRetry(request: HTTPRequest): Promise<HTTPResponse> {\n    // Calculate variation to spread delays evenly across retry attempts\n    const variation = Math.ceil(\n      (this.retryDelayMax - this.retryDelayMin) / Math.max(this.maxRetries - 1, 1)\n    );\n\n    const delayFn = increasingDelay({\n      initialDelay: this.retryDelayMin,\n      variation,\n      delayLimit: this.retryDelayMax\n    });\n\n    return asyncRetry({\n      asyncCallable: async () => this.executeRequest(request),\n      maxRetries: this.maxRetries,\n      delayFn,\n      validator: (response) => {\n        // Retry on 5xx server errors\n        if (response.status >= 500 && response.status < 600) {\n          throw new UnexpectedError(`Server error: ${response.status} ${response.statusText}`);\n        }\n      }\n    });\n  }\n\n  private async executeRequest(request: HTTPRequest): Promise<HTTPResponse> {\n    const url = this.buildURL(request.url);\n    const headers = this.buildHeaders(request.headers);\n    const timeout = request.timeout ?? this.requestTimeout;\n\n    logger.debug('[HTTPRequestController] Executing request:', {\n      method: request.method,\n      url,\n      headers: Object.keys(headers).reduce((acc, key) => {\n        // Mask Authorization header for security\n        // eslint-disable-next-line no-param-reassign\n        acc[key] = key === 'Authorization' ? `${headers[key].substring(0, 20)}...` : headers[key];\n        return acc;\n      }, {} as HTTPHeaders),\n      body: this.sanitizeBody(request.body)\n    });\n    // {\"from_fabric_address_id\":\"03a98611-d38f-405a-af49-fcb51e7f22ad\",\"fabric_address_ids\":[\"31c0afc2-93f3-4530-9a7d-55613d40850d\",\"03a98611-d38f-405a-af49-fcb51e7f22ad\"]}\n    // {\"from_fabric_address_id\":\"060b5d3e-5df0-45d9-911e-660779e593da\",\"fabric_address_ids\":[\"31c0afc2-93f3-4530-9a7d-55613d40850d\",\"060b5d3e-5df0-45d9-911e-660779e593da\"]}\n\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n    try {\n      const response = await fetch(url, {\n        method: request.method,\n        headers,\n        body: request.body,\n        signal: controller.signal\n      });\n\n      clearTimeout(timeoutId);\n\n      const httpResponse = await this.convertResponse(response);\n\n      logger.debug('[HTTPRequestController] Response received:', {\n        status: response.status,\n        statusText: response.statusText,\n        headers: [...response.headers.entries()],\n        body: httpResponse.body ? httpResponse.body.substring(0, 200) : '(empty)' // Show first 200 chars\n      });\n\n      return httpResponse;\n    } catch (error) {\n      clearTimeout(timeoutId);\n\n      if (error instanceof Error && error.name === 'AbortError') {\n        throw new RequestTimeoutError(`Request timeout after ${timeout}ms`, { cause: error });\n      }\n\n      logger.error('[HTTPRequestController] Request failed:', error);\n      throw error;\n    }\n  }\n\n  private buildURL(url: string | URL): string {\n    const urlString = typeof url === 'string' ? url : url.toString();\n\n    // If URL is absolute, return as-is\n    if (urlString.startsWith('http://') || urlString.startsWith('https://')) {\n      return urlString;\n    }\n\n    // Ensure base URL doesn't end with '/' and path starts with '/'\n    const base = this.baseURL.endsWith('/') ? this.baseURL.slice(0, -1) : this.baseURL;\n    const path = urlString.startsWith('/') ? urlString : `/${urlString}`;\n\n    return `${base}${path}`;\n  }\n\n  private buildHeaders(requestHeaders?: HTTPHeaders): HTTPHeaders {\n    const headers: HTTPHeaders = { ...(requestHeaders ?? {}) };\n\n    // Add authentication header\n    const credential = this.getCredential();\n    if (credential.token) {\n      headers.Authorization = `Bearer ${credential.token}`;\n      logger.debug(\n        '[HTTPRequestController] Using Bearer token auth, token length:',\n        credential.token.length\n      );\n    } else {\n      logger.warn('[HTTPRequestController] No credentials available for authentication');\n    }\n\n    return headers;\n  }\n\n  /**\n   * Sanitizes a request body for debug logging by masking sensitive fields.\n   */\n  private sanitizeBody(\n    body:\n      | string\n      | Blob\n      | ArrayBuffer\n      | FormData\n      | URLSearchParams\n      | ReadableStream\n      | null\n      | undefined\n  ): string | undefined {\n    if (!body || typeof body !== 'string') {\n      return body ? '(non-string body)' : undefined;\n    }\n\n    try {\n      const parsed = JSON.parse(body) as Record<string, unknown>;\n      const sanitized = { ...parsed };\n      for (const key of Object.keys(sanitized)) {\n        if (\n          HTTPRequestController.SENSITIVE_BODY_FIELDS.has(key) &&\n          typeof sanitized[key] === 'string'\n        ) {\n          sanitized[key] = `${sanitized[key].substring(0, 20)}...[redacted]`;\n        }\n      }\n      return JSON.stringify(sanitized);\n    } catch {\n      // Not JSON — return truncated\n      return body.length > 200 ? `${body.substring(0, 200)}...` : body;\n    }\n  }\n\n  private async convertResponse(response: Response): Promise<HTTPResponse> {\n    // Convert Headers to plain object\n    const headers: HTTPHeaders = {};\n    response.headers.forEach((value, key) => {\n      headers[key] = value;\n    });\n\n    // Read response body as text\n    const bodyText = await response.text();\n\n    return {\n      status: response.status,\n      statusText: response.statusText,\n      headers,\n      body: bodyText,\n      ok: response.ok,\n      url: response.url\n    };\n  }\n}\n","/**\n * DeviceHistoryManager - Maintains a per-kind stack of recently used devices.\n *\n * When a device disappears during a call, the SDK uses this history to fall\n * back to the previously used device rather than picking an arbitrary default.\n * This handles the common scenario of AirPods disconnecting and the user\n * expecting to return to their built-in microphone.\n *\n * This is a pure data structure -- it does not extend Destroyable since it\n * holds no subscriptions or subjects.\n *\n * @see Section 5.2 of the Implementation Guide\n */\n\n/** Maximum number of entries retained per device kind. */\nconst MAX_HISTORY_SIZE = 5;\n\n/** Device kinds tracked by the history manager. */\ntype DeviceKind = 'audioinput' | 'audiooutput' | 'videoinput';\n\n/**\n * Serializable subset of MediaDeviceInfo stored in the history stack.\n *\n * The browser's MediaDeviceInfo is not serializable, so we store only the\n * fields needed for matching.\n */\ninterface HistoryEntry {\n  readonly deviceId: string;\n  readonly label: string;\n  readonly groupId: string;\n  readonly kind: DeviceKind;\n}\n\n/**\n * Converts a MediaDeviceInfo to a serializable HistoryEntry.\n */\nconst toHistoryEntry = (device: MediaDeviceInfo, kind: DeviceKind): HistoryEntry => ({\n  deviceId: device.deviceId,\n  label: device.label,\n  groupId: device.groupId,\n  kind\n});\n\nexport class DeviceHistoryManager {\n  private readonly _stacks: Record<DeviceKind, HistoryEntry[]> = {\n    audioinput: [],\n    audiooutput: [],\n    videoinput: []\n  };\n\n  /**\n   * Push a device onto the history stack for the given kind.\n   *\n   * If the device is already at the top of the stack, this is a no-op.\n   * Duplicate entries deeper in the stack are removed to keep the stack clean.\n   * The stack is capped at {@link MAX_HISTORY_SIZE} entries.\n   *\n   * @param kind - The device kind (audioinput, audiooutput, videoinput)\n   * @param device - The MediaDeviceInfo to record\n   */\n  public push(kind: DeviceKind, device: MediaDeviceInfo): void {\n    const entry = toHistoryEntry(device, kind);\n    const current = this._stacks[kind];\n\n    // Skip if the device is already at the top\n    if (current.length > 0 && current[0].deviceId === entry.deviceId) {\n      return;\n    }\n\n    // Remove any existing entry for this device (dedup)\n    const filtered = current.filter((e) => e.deviceId !== entry.deviceId);\n\n    // Prepend the new entry and cap at MAX_HISTORY_SIZE\n    this._stacks[kind] = [entry, ...filtered].slice(0, MAX_HISTORY_SIZE);\n  }\n\n  /**\n   * Pop the most recent device from the history stack for the given kind.\n   *\n   * @param kind - The device kind\n   * @returns The most recent HistoryEntry, or undefined if the stack is empty\n   */\n  public pop(kind: DeviceKind): HistoryEntry | undefined {\n    const current = this._stacks[kind];\n\n    if (current.length === 0) {\n      return undefined;\n    }\n\n    const [top, ...rest] = current;\n    this._stacks[kind] = rest;\n    return top;\n  }\n\n  /**\n   * Find the most recent device in the history that is still present in the\n   * available device list.\n   *\n   * Searches the history stack from most recent to oldest. A device is\n   * considered a match if its deviceId or (groupId + label) match an\n   * available device.\n   *\n   * @param kind - The device kind\n   * @param availableDevices - The current list of available devices\n   * @returns The matching MediaDeviceInfo from availableDevices, or undefined\n   */\n  public findInHistory(\n    kind: DeviceKind,\n    availableDevices: readonly MediaDeviceInfo[]\n  ): MediaDeviceInfo | undefined {\n    const history = this._stacks[kind];\n\n    for (const entry of history) {\n      // Exact deviceId match\n      const exactMatch = availableDevices.find((d) => d.deviceId === entry.deviceId);\n      if (exactMatch) {\n        return exactMatch;\n      }\n\n      // groupId + label match (same physical device, ID may have changed)\n      const groupMatch = availableDevices.find(\n        (d) => d.groupId === entry.groupId && d.label === entry.label\n      );\n      if (groupMatch) {\n        return groupMatch;\n      }\n    }\n\n    return undefined;\n  }\n\n  /**\n   * Return a read-only snapshot of the history stack for a given kind.\n   *\n   * @param kind - The device kind\n   * @returns A read-only array of HistoryEntry objects, most recent first\n   */\n  public getHistory(kind: DeviceKind): readonly HistoryEntry[] {\n    return this._stacks[kind];\n  }\n\n  /**\n   * Clear all history stacks.\n   */\n  public clear(): void {\n    this._stacks.audioinput = [];\n    this._stacks.audiooutput = [];\n    this._stacks.videoinput = [];\n  }\n}\n","export function fromSecToMs(seconds: number): number {\n  return seconds * 1000;\n}\n\nexport function fromMsToSec(milliseconds: number): number {\n  return Math.round(milliseconds / 100) / 10;\n}\n","import {\n  DEFAULT_CONNECTION_TIMEOUT_MS,\n  DEFAULT_DEVICE_DEBOUNCE_TIME_MS,\n  DEFAULT_DEVICE_POLLING_INTERVAL_MS,\n  DEFAULT_ICE_CANDIDATE_TIMEOUT_MS,\n  DEFAULT_ICE_GATHERING_TIMEOUT_MS,\n  DEFAULT_RECONNECT_CALLS_TIMEOUT_MS,\n  DEFAULT_RECONNECT_DELAY_MAX_MS,\n  DEFAULT_RECONNECT_DELAY_MIN_MS,\n  PREFERENCES_STORAGE_KEY,\n  DEFAULT_STATS_POLLING_INTERVAL_MS,\n  DEFAULT_STATS_BASELINE_SAMPLES,\n  DEFAULT_STATS_NO_PACKET_THRESHOLD_MS,\n  DEFAULT_STATS_RTT_SPIKE_MULTIPLIER,\n  DEFAULT_STATS_PACKET_LOSS_THRESHOLD,\n  DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER,\n  DEFAULT_STATS_HISTORY_SIZE,\n  DEFAULT_KEYFRAME_MAX_BURST,\n  DEFAULT_KEYFRAME_BURST_WINDOW_MS,\n  DEFAULT_KEYFRAME_COOLDOWN_MS,\n  DEFAULT_REINVITE_DEBOUNCE_TIME_MS,\n  DEFAULT_REINVITE_MAX_ATTEMPTS,\n  DEFAULT_REINVITE_TIMEOUT_MS,\n  DEFAULT_RECOVERY_DEBOUNCE_TIME_MS,\n  DEFAULT_RECOVERY_COOLDOWN_MS,\n  DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS,\n  DEFAULT_ICE_RESTART_TIMEOUT_MS,\n  DEFAULT_MAX_RECOVERY_ATTEMPTS,\n  DEFAULT_PERSIST_DEVICE_SELECTION,\n  DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS,\n  DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN,\n  DEFAULT_REFRESH_DEVICES_ON_VISIBLE,\n  DEFAULT_CHECK_CONNECTION_ON_VISIBLE,\n  DEFAULT_ENABLE_AUTO_DEGRADATION,\n  DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS,\n  DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS,\n  DEFAULT_ENABLE_RELAY_FALLBACK,\n  DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION,\n  DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION,\n  DEFAULT_STEREO_AUDIO\n} from '../core/constants';\nimport { getLogger } from '../utils/logger';\nimport { fromMsToSec, fromSecToMs } from '../utils/time';\n\nimport type { MediaOptions } from '../core/types/media.types';\nimport type { StorageManager } from '../managers/StorageManager';\n\nconst logger = getLogger();\n\nexport interface Preferences {\n  connectionTimeout: number;\n  reconnectDelayMin: number;\n  reconnectDelayMax: number;\n  reconnectCallsTimeout: number;\n  relayHost?: string;\n  receiveVideo: boolean;\n  receiveAudio: boolean;\n  preferredAudioInput: MediaDeviceInfo | null;\n  preferredAudioOutput: MediaDeviceInfo | null;\n  preferredVideoInput: MediaDeviceInfo | null;\n  inputAudioDeviceConstraints: MediaTrackConstraints | undefined;\n  inputVideoDeviceConstraints: MediaTrackConstraints | undefined;\n  disableUdpIceServers: boolean;\n  relayOnly: boolean;\n  iceCandidateTimeout: number;\n  iceGatheringTimeout: number;\n  deviceDebounceTime: number;\n  devicePollingInterval: number;\n  iceServers?: RTCIceServer[];\n  defaultSignalWireOptions: {\n    skipConnection: boolean;\n    skipRegister: boolean;\n    reconnectAttachedCalls: boolean;\n    skipDeviceMonitoring: boolean;\n    savePreferences: boolean;\n  };\n  inviteSubscribeScreenshare: string[];\n  inviteSubscribeAdditionalDevice: string[];\n  inviteSubscribeMainDevice: string[];\n  userVariables: Record<string, unknown>;\n  readonly preferredMediaOptions: MediaOptions;\n\n  // Stats monitoring (Section 1)\n  statsPollingInterval: number;\n  statsBaselineSamples: number;\n  statsNoPacketThreshold: number;\n  statsRttSpikeMultiplier: number;\n  statsPacketLossThreshold: number;\n  statsJitterSpikeMultiplier: number;\n  statsHistorySize: number;\n\n  // Keyframe throttling (Section 2)\n  keyframeMaxBurst: number;\n  keyframeBurstWindow: number;\n  keyframeCooldown: number;\n\n  // Re-INVITE / ICE restart (Section 2 & 19)\n  reinviteDebounceTime: number;\n  reinviteMaxAttempts: number;\n  reinviteTimeout: number;\n\n  // Recovery pipeline (Section 19)\n  recoveryDebounceTime: number;\n  recoveryCooldown: number;\n  iceDisconnectedGracePeriod: number;\n  iceRestartTimeout: number;\n  maxRecoveryAttempts: number;\n  enableRelayFallback: boolean;\n  enableNetworkChangeDetection: boolean;\n  enableServerHangupInterception: boolean;\n\n  // Device management (Section 5)\n  persistDeviceSelection: boolean;\n  syncDevicesToActiveCalls: boolean;\n\n  // Visibility (Section 4)\n  autoMuteVideoOnHidden: boolean;\n  refreshDevicesOnVisible: boolean;\n  checkConnectionOnVisible: boolean;\n\n  // Media defaults (Section 16.5 & 16.6)\n  defaultAudioConstraints: MediaTrackConstraints | undefined;\n  defaultVideoConstraints: MediaTrackConstraints | undefined;\n  stereoAudio: boolean;\n\n  // Degradation (Section 22)\n  enableAutoDegradation: boolean;\n  degradationBitrateThreshold: number;\n  degradationRecoveryThreshold: number;\n\n  // Codec preferences (Section 23)\n  preferredVideoCodecs: string[];\n  preferredAudioCodecs: string[];\n}\nexport class PreferencesContainer implements Preferences {\n  static get instance(): Preferences {\n    this._instance ??= new PreferencesContainer();\n    return this._instance;\n  }\n\n  deviceDebounceTime = DEFAULT_DEVICE_DEBOUNCE_TIME_MS;\n  devicePollingInterval = DEFAULT_DEVICE_POLLING_INTERVAL_MS;\n\n  reconnectCallsTimeout = DEFAULT_RECONNECT_CALLS_TIMEOUT_MS;\n  // 5 minutes\n  iceServers?: RTCIceServer[];\n  connectionTimeout = DEFAULT_CONNECTION_TIMEOUT_MS;\n  reconnectDelayMin = DEFAULT_RECONNECT_DELAY_MIN_MS;\n  reconnectDelayMax = DEFAULT_RECONNECT_DELAY_MAX_MS;\n  disableUdpIceServers = false;\n  relayOnly = false;\n  iceCandidateTimeout = DEFAULT_ICE_CANDIDATE_TIMEOUT_MS;\n  iceGatheringTimeout = DEFAULT_ICE_GATHERING_TIMEOUT_MS;\n  defaultSignalWireOptions = {\n    skipConnection: false,\n    skipRegister: false,\n    reconnectAttachedCalls: false,\n    skipDeviceMonitoring: false,\n    savePreferences: false\n  };\n  relayHost?: string;\n  receiveVideo = false;\n  receiveAudio = true;\n  preferredAudioInput: MediaDeviceInfo | null = null;\n  preferredAudioOutput: MediaDeviceInfo | null = null;\n  preferredVideoInput: MediaDeviceInfo | null = null;\n  inviteSubscribeScreenshare: string[] = ['video.room.screenshare'];\n  inviteSubscribeAdditionalDevice: string[] = [\n    // FIXME verify what to subscribe to for additional devices\n  ];\n  inviteSubscribeMainDevice: string[] = [\n    'track',\n    'destroy',\n    'member.updated.videoMuted',\n    'layout.changed',\n    'room.subscribed',\n    'member.updated.audioMuted',\n    'media.connected',\n    'room.updated',\n    'call.joined'\n  ];\n  userVariables = {};\n\n  // Stats monitoring\n  statsPollingInterval = DEFAULT_STATS_POLLING_INTERVAL_MS;\n  statsBaselineSamples = DEFAULT_STATS_BASELINE_SAMPLES;\n  statsNoPacketThreshold = DEFAULT_STATS_NO_PACKET_THRESHOLD_MS;\n  statsRttSpikeMultiplier = DEFAULT_STATS_RTT_SPIKE_MULTIPLIER;\n  statsPacketLossThreshold = DEFAULT_STATS_PACKET_LOSS_THRESHOLD;\n  statsJitterSpikeMultiplier = DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER;\n  statsHistorySize = DEFAULT_STATS_HISTORY_SIZE;\n\n  // Keyframe throttling\n  keyframeMaxBurst = DEFAULT_KEYFRAME_MAX_BURST;\n  keyframeBurstWindow = DEFAULT_KEYFRAME_BURST_WINDOW_MS;\n  keyframeCooldown = DEFAULT_KEYFRAME_COOLDOWN_MS;\n\n  // Re-INVITE / ICE restart\n  reinviteDebounceTime = DEFAULT_REINVITE_DEBOUNCE_TIME_MS;\n  reinviteMaxAttempts = DEFAULT_REINVITE_MAX_ATTEMPTS;\n  reinviteTimeout = DEFAULT_REINVITE_TIMEOUT_MS;\n\n  // Recovery pipeline\n  recoveryDebounceTime = DEFAULT_RECOVERY_DEBOUNCE_TIME_MS;\n  recoveryCooldown = DEFAULT_RECOVERY_COOLDOWN_MS;\n  iceDisconnectedGracePeriod = DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS;\n  iceRestartTimeout = DEFAULT_ICE_RESTART_TIMEOUT_MS;\n  maxRecoveryAttempts = DEFAULT_MAX_RECOVERY_ATTEMPTS;\n  enableRelayFallback = DEFAULT_ENABLE_RELAY_FALLBACK;\n  enableNetworkChangeDetection = DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION;\n  enableServerHangupInterception = DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION;\n\n  // Device management\n  persistDeviceSelection = DEFAULT_PERSIST_DEVICE_SELECTION;\n  syncDevicesToActiveCalls = DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS;\n\n  // Visibility\n  autoMuteVideoOnHidden = DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN;\n  refreshDevicesOnVisible = DEFAULT_REFRESH_DEVICES_ON_VISIBLE;\n  checkConnectionOnVisible = DEFAULT_CHECK_CONNECTION_ON_VISIBLE;\n\n  // Media defaults\n  defaultAudioConstraints: MediaTrackConstraints | undefined = undefined;\n  defaultVideoConstraints: MediaTrackConstraints | undefined = undefined;\n  stereoAudio = DEFAULT_STEREO_AUDIO;\n\n  // Degradation\n  enableAutoDegradation = DEFAULT_ENABLE_AUTO_DEGRADATION;\n  degradationBitrateThreshold = DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS;\n  degradationRecoveryThreshold = DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS;\n\n  // Codec preferences\n  preferredVideoCodecs: string[] = [];\n  preferredAudioCodecs: string[] = [];\n\n  private _inputAudioDeviceConstraints?: MediaTrackConstraints;\n  private _inputVideoDeviceConstraints?: MediaTrackConstraints;\n  private static _instance?: PreferencesContainer;\n\n  private constructor() {\n    // Private constructor to enforce singleton pattern\n  }\n\n  public get preferredMediaOptions(): MediaOptions {\n    return {\n      receiveVideo: this.receiveVideo,\n      receiveAudio: this.receiveAudio,\n      inputAudioDeviceConstraints: this.inputAudioDeviceConstraints,\n      inputVideoDeviceConstraints: this.inputVideoDeviceConstraints\n    };\n  }\n\n  get inputAudioDeviceConstraints(): MediaTrackConstraints | undefined {\n    return this._inputAudioDeviceConstraints;\n  }\n\n  set inputAudioDeviceConstraints(value: MediaTrackConstraints | undefined) {\n    this._inputAudioDeviceConstraints = value;\n  }\n\n  get inputVideoDeviceConstraints(): MediaTrackConstraints | undefined {\n    return this._inputVideoDeviceConstraints;\n  }\n\n  set inputVideoDeviceConstraints(value: MediaTrackConstraints | undefined) {\n    this._inputVideoDeviceConstraints = value;\n  }\n}\n\n/** Serializable subset of preferences that can be persisted to storage. */\ninterface StoredPreferences {\n  connectionTimeout?: number;\n  reconnectCallsTimeout?: number;\n  reconnectDelayMin?: number;\n  reconnectDelayMax?: number;\n  relayHost?: string;\n  receiveVideo?: boolean;\n  receiveAudio?: boolean;\n  disableUdpIceServers?: boolean;\n  relayOnly?: boolean;\n  iceCandidateTimeout?: number;\n  iceGatheringTimeout?: number;\n  deviceDebounceTime?: number;\n  devicePollingInterval?: number;\n  iceServers?: RTCIceServer[];\n  userVariables?: Record<string, unknown>;\n\n  // Stats monitoring\n  statsPollingInterval?: number;\n  statsBaselineSamples?: number;\n  statsNoPacketThreshold?: number;\n  statsRttSpikeMultiplier?: number;\n  statsPacketLossThreshold?: number;\n  statsJitterSpikeMultiplier?: number;\n  statsHistorySize?: number;\n\n  // Keyframe throttling\n  keyframeMaxBurst?: number;\n  keyframeBurstWindow?: number;\n  keyframeCooldown?: number;\n\n  // Re-INVITE / ICE restart\n  reinviteDebounceTime?: number;\n  reinviteMaxAttempts?: number;\n  reinviteTimeout?: number;\n\n  // Recovery pipeline\n  recoveryDebounceTime?: number;\n  recoveryCooldown?: number;\n  iceDisconnectedGracePeriod?: number;\n  iceRestartTimeout?: number;\n  maxRecoveryAttempts?: number;\n  enableRelayFallback?: boolean;\n  enableNetworkChangeDetection?: boolean;\n  enableServerHangupInterception?: boolean;\n\n  // Device management\n  persistDeviceSelection?: boolean;\n  syncDevicesToActiveCalls?: boolean;\n\n  // Visibility\n  autoMuteVideoOnHidden?: boolean;\n  refreshDevicesOnVisible?: boolean;\n  checkConnectionOnVisible?: boolean;\n\n  // Media defaults\n  stereoAudio?: boolean;\n\n  // Degradation\n  enableAutoDegradation?: boolean;\n  degradationBitrateThreshold?: number;\n  degradationRecoveryThreshold?: number;\n\n  // Codec preferences\n  preferredVideoCodecs?: string[];\n  preferredAudioCodecs?: string[];\n}\n\n/** Keys of StoredPreferences that map to number fields on PreferencesContainer. */\nconst STORED_NUMBER_KEYS: (keyof StoredPreferences & keyof Preferences)[] = [\n  'connectionTimeout',\n  'reconnectCallsTimeout',\n  'reconnectDelayMin',\n  'reconnectDelayMax',\n  'iceCandidateTimeout',\n  'iceGatheringTimeout',\n  'deviceDebounceTime',\n  'devicePollingInterval',\n  'statsPollingInterval',\n  'statsBaselineSamples',\n  'statsNoPacketThreshold',\n  'statsRttSpikeMultiplier',\n  'statsPacketLossThreshold',\n  'statsJitterSpikeMultiplier',\n  'statsHistorySize',\n  'keyframeMaxBurst',\n  'keyframeBurstWindow',\n  'keyframeCooldown',\n  'reinviteDebounceTime',\n  'reinviteMaxAttempts',\n  'reinviteTimeout',\n  'recoveryDebounceTime',\n  'recoveryCooldown',\n  'iceDisconnectedGracePeriod',\n  'iceRestartTimeout',\n  'maxRecoveryAttempts',\n  'degradationBitrateThreshold',\n  'degradationRecoveryThreshold'\n];\n\n/** Keys of StoredPreferences that map to boolean fields on PreferencesContainer. */\nconst STORED_BOOLEAN_KEYS: (keyof StoredPreferences & keyof Preferences)[] = [\n  'receiveVideo',\n  'receiveAudio',\n  'disableUdpIceServers',\n  'relayOnly',\n  'enableRelayFallback',\n  'enableNetworkChangeDetection',\n  'enableServerHangupInterception',\n  'persistDeviceSelection',\n  'syncDevicesToActiveCalls',\n  'autoMuteVideoOnHidden',\n  'refreshDevicesOnVisible',\n  'checkConnectionOnVisible',\n  'stereoAudio',\n  'enableAutoDegradation'\n];\n\n/** Collects the serializable preferences from the container. */\nfunction collectStoredPreferences(): StoredPreferences {\n  const container = PreferencesContainer.instance;\n  return {\n    connectionTimeout: container.connectionTimeout,\n    reconnectCallsTimeout: container.reconnectCallsTimeout,\n    reconnectDelayMin: container.reconnectDelayMin,\n    reconnectDelayMax: container.reconnectDelayMax,\n    relayHost: container.relayHost,\n    receiveVideo: container.receiveVideo,\n    receiveAudio: container.receiveAudio,\n    disableUdpIceServers: container.disableUdpIceServers,\n    relayOnly: container.relayOnly,\n    iceCandidateTimeout: container.iceCandidateTimeout,\n    iceGatheringTimeout: container.iceGatheringTimeout,\n    deviceDebounceTime: container.deviceDebounceTime,\n    devicePollingInterval: container.devicePollingInterval,\n    iceServers: container.iceServers,\n    userVariables: container.userVariables,\n    // Stats monitoring\n    statsPollingInterval: container.statsPollingInterval,\n    statsBaselineSamples: container.statsBaselineSamples,\n    statsNoPacketThreshold: container.statsNoPacketThreshold,\n    statsRttSpikeMultiplier: container.statsRttSpikeMultiplier,\n    statsPacketLossThreshold: container.statsPacketLossThreshold,\n    statsJitterSpikeMultiplier: container.statsJitterSpikeMultiplier,\n    statsHistorySize: container.statsHistorySize,\n    // Keyframe throttling\n    keyframeMaxBurst: container.keyframeMaxBurst,\n    keyframeBurstWindow: container.keyframeBurstWindow,\n    keyframeCooldown: container.keyframeCooldown,\n    // Re-INVITE / ICE restart\n    reinviteDebounceTime: container.reinviteDebounceTime,\n    reinviteMaxAttempts: container.reinviteMaxAttempts,\n    reinviteTimeout: container.reinviteTimeout,\n    // Recovery pipeline\n    recoveryDebounceTime: container.recoveryDebounceTime,\n    recoveryCooldown: container.recoveryCooldown,\n    iceDisconnectedGracePeriod: container.iceDisconnectedGracePeriod,\n    iceRestartTimeout: container.iceRestartTimeout,\n    maxRecoveryAttempts: container.maxRecoveryAttempts,\n    enableRelayFallback: container.enableRelayFallback,\n    enableNetworkChangeDetection: container.enableNetworkChangeDetection,\n    enableServerHangupInterception: container.enableServerHangupInterception,\n    // Device management\n    persistDeviceSelection: container.persistDeviceSelection,\n    syncDevicesToActiveCalls: container.syncDevicesToActiveCalls,\n    // Visibility\n    autoMuteVideoOnHidden: container.autoMuteVideoOnHidden,\n    refreshDevicesOnVisible: container.refreshDevicesOnVisible,\n    checkConnectionOnVisible: container.checkConnectionOnVisible,\n    // Media defaults\n    stereoAudio: container.stereoAudio,\n    // Degradation\n    enableAutoDegradation: container.enableAutoDegradation,\n    degradationBitrateThreshold: container.degradationBitrateThreshold,\n    degradationRecoveryThreshold: container.degradationRecoveryThreshold,\n    // Codec preferences\n    preferredVideoCodecs: container.preferredVideoCodecs,\n    preferredAudioCodecs: container.preferredAudioCodecs\n  };\n}\n\n/** Applies stored preferences to the container. */\nfunction applyStoredPreferences(stored: StoredPreferences): void {\n  const container = PreferencesContainer.instance as unknown as Record<string, unknown>;\n  for (const key of STORED_NUMBER_KEYS) {\n    if (stored[key] !== undefined) container[key] = stored[key];\n  }\n  for (const key of STORED_BOOLEAN_KEYS) {\n    if (stored[key] !== undefined) container[key] = stored[key];\n  }\n  if (stored.relayHost !== undefined) container.relayHost = stored.relayHost;\n  if (stored.iceServers !== undefined) container.iceServers = stored.iceServers;\n  if (stored.userVariables !== undefined) container.userVariables = stored.userVariables;\n  if (stored.preferredVideoCodecs !== undefined)\n    container.preferredVideoCodecs = stored.preferredVideoCodecs;\n  if (stored.preferredAudioCodecs !== undefined)\n    container.preferredAudioCodecs = stored.preferredAudioCodecs;\n}\n\n/**\n * Public preferences API for configuring SDK behavior.\n *\n * Exposed as {@link SignalWire.preferences}. All timeout values\n * are in seconds when accessed through this class.\n *\n * When {@link enableSavePreferences} is called, preferences are\n * automatically loaded from storage and synced back on every change.\n */\nexport class ClientPreferences {\n  private _storage: StorageManager | null = null;\n\n  /**\n   * Enables persistence of preferences to storage.\n   * Loads any previously saved preferences and syncs future changes.\n   */\n  public enableSavePreferences(storage: StorageManager): void {\n    this._storage = storage;\n    this._loadFromStorage();\n  }\n\n  /** WebSocket connection timeout in seconds. */\n  public get connectionTimeout(): number {\n    return fromMsToSec(PreferencesContainer.instance.connectionTimeout);\n  }\n  public set connectionTimeout(seconds: number) {\n    PreferencesContainer.instance.connectionTimeout = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Timeout for reconnecting to previously attached calls, in seconds. */\n  public get reconnectCallsTimeout(): number {\n    return fromMsToSec(PreferencesContainer.instance.reconnectCallsTimeout);\n  }\n  public set reconnectCallsTimeout(seconds: number) {\n    PreferencesContainer.instance.reconnectCallsTimeout = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Minimum reconnection backoff delay in seconds. */\n  public get reconnectDelayMin(): number {\n    return fromMsToSec(PreferencesContainer.instance.reconnectDelayMin);\n  }\n  public set reconnectDelayMin(seconds: number) {\n    PreferencesContainer.instance.reconnectDelayMin = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Maximum reconnection backoff delay in seconds. */\n  public get reconnectDelayMax(): number {\n    return fromMsToSec(PreferencesContainer.instance.reconnectDelayMax);\n  }\n  public set reconnectDelayMax(seconds: number) {\n    PreferencesContainer.instance.reconnectDelayMax = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Custom relay host URL. Empty string uses the default. */\n  public get relayHost(): string {\n    return PreferencesContainer.instance.relayHost ?? '';\n  }\n  public set relayHost(value: string) {\n    PreferencesContainer.instance.relayHost = value;\n    this._saveToStorage();\n  }\n\n  /** Whether to receive remote video by default. */\n  public get receiveVideo(): boolean {\n    return PreferencesContainer.instance.receiveVideo;\n  }\n  public set receiveVideo(value: boolean) {\n    PreferencesContainer.instance.receiveVideo = value;\n    this._saveToStorage();\n  }\n\n  /** Whether to receive remote audio by default. */\n  public get receiveAudio(): boolean {\n    return PreferencesContainer.instance.receiveAudio;\n  }\n  public set receiveAudio(value: boolean) {\n    PreferencesContainer.instance.receiveAudio = value;\n    this._saveToStorage();\n  }\n\n  /** Preferred audio input device for new calls. */\n  public get preferredAudioInput(): MediaDeviceInfo | null {\n    return PreferencesContainer.instance.preferredAudioInput;\n  }\n  public set preferredAudioInput(value: MediaDeviceInfo | null) {\n    PreferencesContainer.instance.preferredAudioInput = value;\n  }\n\n  /** Preferred audio output device for new calls. */\n  public get preferredAudioOutput(): MediaDeviceInfo | null {\n    return PreferencesContainer.instance.preferredAudioOutput;\n  }\n  public set preferredAudioOutput(value: MediaDeviceInfo | null) {\n    PreferencesContainer.instance.preferredAudioOutput = value;\n  }\n\n  /** Preferred video input device for new calls. */\n  public get preferredVideoInput(): MediaDeviceInfo | null {\n    return PreferencesContainer.instance.preferredVideoInput;\n  }\n  public set preferredVideoInput(value: MediaDeviceInfo | null) {\n    PreferencesContainer.instance.preferredVideoInput = value;\n  }\n\n  /** Default audio input track constraints. */\n  public get inputAudioConstraints(): MediaTrackConstraints | undefined {\n    return PreferencesContainer.instance.inputAudioDeviceConstraints;\n  }\n  public set inputAudioConstraints(value: MediaTrackConstraints | undefined) {\n    PreferencesContainer.instance.inputAudioDeviceConstraints = value;\n  }\n\n  /** Default video input track constraints. */\n  public get inputVideoConstraints(): MediaTrackConstraints | undefined {\n    return PreferencesContainer.instance.inputVideoDeviceConstraints;\n  }\n  public set inputVideoConstraints(value: MediaTrackConstraints | undefined) {\n    PreferencesContainer.instance.inputVideoDeviceConstraints = value;\n  }\n\n  /** Debounce time for device change events, in seconds. */\n  public get deviceDebounceTime(): number {\n    return fromMsToSec(PreferencesContainer.instance.deviceDebounceTime);\n  }\n  public set deviceDebounceTime(seconds: number) {\n    PreferencesContainer.instance.deviceDebounceTime = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Polling interval for device enumeration, in seconds. */\n  public get devicePollingInterval(): number {\n    return fromMsToSec(PreferencesContainer.instance.devicePollingInterval);\n  }\n  public set devicePollingInterval(seconds: number) {\n    PreferencesContainer.instance.devicePollingInterval = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Whether to filter out UDP-based ICE servers. */\n  public get disableUdpIceServers(): boolean {\n    return PreferencesContainer.instance.disableUdpIceServers;\n  }\n  public set disableUdpIceServers(value: boolean) {\n    PreferencesContainer.instance.disableUdpIceServers = value;\n    this._saveToStorage();\n  }\n\n  /** Whether to force TURN relay-only ICE candidates. */\n  public get relayOnly(): boolean {\n    return PreferencesContainer.instance.relayOnly;\n  }\n  public set relayOnly(value: boolean) {\n    PreferencesContainer.instance.relayOnly = value;\n    this._saveToStorage();\n  }\n\n  /** Timeout for individual ICE candidate gathering, in seconds. */\n  public get iceCandidateTimeout(): number {\n    return fromMsToSec(PreferencesContainer.instance.iceCandidateTimeout);\n  }\n  public set iceCandidateTimeout(seconds: number) {\n    PreferencesContainer.instance.iceCandidateTimeout = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Timeout for the entire ICE gathering phase, in seconds. */\n  public get iceGatheringTimeout(): number {\n    return fromMsToSec(PreferencesContainer.instance.iceGatheringTimeout);\n  }\n  public set iceGatheringTimeout(seconds: number) {\n    PreferencesContainer.instance.iceGatheringTimeout = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Custom ICE servers for TURN/STUN configuration. */\n  public get iceServers(): RTCIceServer[] | undefined {\n    return PreferencesContainer.instance.iceServers;\n  }\n  public set iceServers(value: RTCIceServer[] | undefined) {\n    PreferencesContainer.instance.iceServers = value;\n    this._saveToStorage();\n  }\n\n  /** Custom user variables attached to calls. */\n  public get userVariables(): Record<string, unknown> {\n    return PreferencesContainer.instance.userVariables;\n  }\n  public set userVariables(value: Record<string, unknown>) {\n    PreferencesContainer.instance.userVariables = value;\n    this._saveToStorage();\n  }\n\n  // ===========================================================================\n  // Stats monitoring (Section 1)\n  // ===========================================================================\n\n  /** Stats polling interval in milliseconds. */\n  public get statsPollingInterval(): number {\n    return PreferencesContainer.instance.statsPollingInterval;\n  }\n  public set statsPollingInterval(value: number) {\n    PreferencesContainer.instance.statsPollingInterval = value;\n    this._saveToStorage();\n  }\n\n  /** Number of baseline samples for stats monitoring. */\n  public get statsBaselineSamples(): number {\n    return PreferencesContainer.instance.statsBaselineSamples;\n  }\n  public set statsBaselineSamples(value: number) {\n    PreferencesContainer.instance.statsBaselineSamples = value;\n    this._saveToStorage();\n  }\n\n  /** Duration in ms with no inbound packets before a critical issue is emitted. */\n  public get statsNoPacketThreshold(): number {\n    return PreferencesContainer.instance.statsNoPacketThreshold;\n  }\n  public set statsNoPacketThreshold(value: number) {\n    PreferencesContainer.instance.statsNoPacketThreshold = value;\n    this._saveToStorage();\n  }\n\n  /** Multiplier for RTT spike detection relative to baseline. */\n  public get statsRttSpikeMultiplier(): number {\n    return PreferencesContainer.instance.statsRttSpikeMultiplier;\n  }\n  public set statsRttSpikeMultiplier(value: number) {\n    PreferencesContainer.instance.statsRttSpikeMultiplier = value;\n    this._saveToStorage();\n  }\n\n  /** Packet loss fraction threshold (0-1) for issue detection. */\n  public get statsPacketLossThreshold(): number {\n    return PreferencesContainer.instance.statsPacketLossThreshold;\n  }\n  public set statsPacketLossThreshold(value: number) {\n    PreferencesContainer.instance.statsPacketLossThreshold = value;\n    this._saveToStorage();\n  }\n\n  /** Multiplier for jitter spike detection relative to baseline. */\n  public get statsJitterSpikeMultiplier(): number {\n    return PreferencesContainer.instance.statsJitterSpikeMultiplier;\n  }\n  public set statsJitterSpikeMultiplier(value: number) {\n    PreferencesContainer.instance.statsJitterSpikeMultiplier = value;\n    this._saveToStorage();\n  }\n\n  /** Number of seconds of metrics history to retain. */\n  public get statsHistorySize(): number {\n    return PreferencesContainer.instance.statsHistorySize;\n  }\n  public set statsHistorySize(value: number) {\n    PreferencesContainer.instance.statsHistorySize = value;\n    this._saveToStorage();\n  }\n\n  // ===========================================================================\n  // Keyframe throttling (Section 2)\n  // ===========================================================================\n\n  /** Maximum keyframe requests in a burst window. */\n  public get keyframeMaxBurst(): number {\n    return PreferencesContainer.instance.keyframeMaxBurst;\n  }\n  public set keyframeMaxBurst(value: number) {\n    PreferencesContainer.instance.keyframeMaxBurst = value;\n    this._saveToStorage();\n  }\n\n  /** Keyframe burst window duration in milliseconds. */\n  public get keyframeBurstWindow(): number {\n    return PreferencesContainer.instance.keyframeBurstWindow;\n  }\n  public set keyframeBurstWindow(value: number) {\n    PreferencesContainer.instance.keyframeBurstWindow = value;\n    this._saveToStorage();\n  }\n\n  /** Cooldown period in ms after keyframe burst limit is reached. */\n  public get keyframeCooldown(): number {\n    return PreferencesContainer.instance.keyframeCooldown;\n  }\n  public set keyframeCooldown(value: number) {\n    PreferencesContainer.instance.keyframeCooldown = value;\n    this._saveToStorage();\n  }\n\n  // ===========================================================================\n  // Re-INVITE / ICE restart (Section 2 & 19)\n  // ===========================================================================\n\n  /** Minimum time in ms between re-INVITE attempts. */\n  public get reinviteDebounceTime(): number {\n    return PreferencesContainer.instance.reinviteDebounceTime;\n  }\n  public set reinviteDebounceTime(value: number) {\n    PreferencesContainer.instance.reinviteDebounceTime = value;\n    this._saveToStorage();\n  }\n\n  /** Maximum re-INVITE attempts per call. */\n  public get reinviteMaxAttempts(): number {\n    return PreferencesContainer.instance.reinviteMaxAttempts;\n  }\n  public set reinviteMaxAttempts(value: number) {\n    PreferencesContainer.instance.reinviteMaxAttempts = value;\n    this._saveToStorage();\n  }\n\n  /** Timeout in ms for a single re-INVITE attempt. */\n  public get reinviteTimeout(): number {\n    return PreferencesContainer.instance.reinviteTimeout;\n  }\n  public set reinviteTimeout(value: number) {\n    PreferencesContainer.instance.reinviteTimeout = value;\n    this._saveToStorage();\n  }\n\n  // ===========================================================================\n  // Recovery pipeline (Section 19)\n  // ===========================================================================\n\n  /** Recovery signal debounce window in seconds. */\n  public get recoveryDebounceTime(): number {\n    return fromMsToSec(PreferencesContainer.instance.recoveryDebounceTime);\n  }\n  public set recoveryDebounceTime(seconds: number) {\n    PreferencesContainer.instance.recoveryDebounceTime = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Cooldown period between recovery attempts in seconds. */\n  public get recoveryCooldown(): number {\n    return fromMsToSec(PreferencesContainer.instance.recoveryCooldown);\n  }\n  public set recoveryCooldown(seconds: number) {\n    PreferencesContainer.instance.recoveryCooldown = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Grace period before treating ICE 'disconnected' as failure, in seconds. */\n  public get iceDisconnectedGracePeriod(): number {\n    return fromMsToSec(PreferencesContainer.instance.iceDisconnectedGracePeriod);\n  }\n  public set iceDisconnectedGracePeriod(seconds: number) {\n    PreferencesContainer.instance.iceDisconnectedGracePeriod = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Timeout for a single ICE restart attempt in seconds. */\n  public get iceRestartTimeout(): number {\n    return fromMsToSec(PreferencesContainer.instance.iceRestartTimeout);\n  }\n  public set iceRestartTimeout(seconds: number) {\n    PreferencesContainer.instance.iceRestartTimeout = fromSecToMs(seconds);\n    this._saveToStorage();\n  }\n\n  /** Maximum recovery attempts before giving up. */\n  public get maxRecoveryAttempts(): number {\n    return PreferencesContainer.instance.maxRecoveryAttempts;\n  }\n  public set maxRecoveryAttempts(value: number) {\n    PreferencesContainer.instance.maxRecoveryAttempts = value;\n    this._saveToStorage();\n  }\n\n  /** Whether relay-only escalation is enabled as a last-resort recovery tier. */\n  public get enableRelayFallback(): boolean {\n    return PreferencesContainer.instance.enableRelayFallback;\n  }\n  public set enableRelayFallback(value: boolean) {\n    PreferencesContainer.instance.enableRelayFallback = value;\n    this._saveToStorage();\n  }\n\n  /** Whether browser network change detection (online/offline) is enabled. */\n  public get enableNetworkChangeDetection(): boolean {\n    return PreferencesContainer.instance.enableNetworkChangeDetection;\n  }\n  public set enableNetworkChangeDetection(value: boolean) {\n    PreferencesContainer.instance.enableNetworkChangeDetection = value;\n    this._saveToStorage();\n  }\n\n  /** Whether server-sent media-timeout hangups are intercepted for recovery. */\n  public get enableServerHangupInterception(): boolean {\n    return PreferencesContainer.instance.enableServerHangupInterception;\n  }\n  public set enableServerHangupInterception(value: boolean) {\n    PreferencesContainer.instance.enableServerHangupInterception = value;\n    this._saveToStorage();\n  }\n\n  // ===========================================================================\n  // Device management (Section 5)\n  // ===========================================================================\n\n  /** Whether device selections are persisted to storage. */\n  public get persistDeviceSelection(): boolean {\n    return PreferencesContainer.instance.persistDeviceSelection;\n  }\n  public set persistDeviceSelection(value: boolean) {\n    PreferencesContainer.instance.persistDeviceSelection = value;\n    this._saveToStorage();\n  }\n\n  /** Whether device changes are auto-applied to active calls. */\n  public get syncDevicesToActiveCalls(): boolean {\n    return PreferencesContainer.instance.syncDevicesToActiveCalls;\n  }\n  public set syncDevicesToActiveCalls(value: boolean) {\n    PreferencesContainer.instance.syncDevicesToActiveCalls = value;\n    this._saveToStorage();\n  }\n\n  // ===========================================================================\n  // Visibility (Section 4)\n  // ===========================================================================\n\n  /** Whether to auto-mute video when the tab becomes hidden. */\n  public get autoMuteVideoOnHidden(): boolean {\n    return PreferencesContainer.instance.autoMuteVideoOnHidden;\n  }\n  public set autoMuteVideoOnHidden(value: boolean) {\n    PreferencesContainer.instance.autoMuteVideoOnHidden = value;\n    this._saveToStorage();\n  }\n\n  /** Whether to re-enumerate devices when the page becomes visible. */\n  public get refreshDevicesOnVisible(): boolean {\n    return PreferencesContainer.instance.refreshDevicesOnVisible;\n  }\n  public set refreshDevicesOnVisible(value: boolean) {\n    PreferencesContainer.instance.refreshDevicesOnVisible = value;\n    this._saveToStorage();\n  }\n\n  /** Whether to check peer connection health when the page becomes visible. */\n  public get checkConnectionOnVisible(): boolean {\n    return PreferencesContainer.instance.checkConnectionOnVisible;\n  }\n  public set checkConnectionOnVisible(value: boolean) {\n    PreferencesContainer.instance.checkConnectionOnVisible = value;\n    this._saveToStorage();\n  }\n\n  // ===========================================================================\n  // Media defaults (Section 16.5 & 16.6)\n  // ===========================================================================\n\n  /** Default audio track constraints applied when no explicit constraints are provided. */\n  public get defaultAudioConstraints(): MediaTrackConstraints | undefined {\n    return PreferencesContainer.instance.defaultAudioConstraints;\n  }\n  public set defaultAudioConstraints(value: MediaTrackConstraints | undefined) {\n    PreferencesContainer.instance.defaultAudioConstraints = value;\n  }\n\n  /** Default video track constraints applied when video is enabled without explicit constraints. */\n  public get defaultVideoConstraints(): MediaTrackConstraints | undefined {\n    return PreferencesContainer.instance.defaultVideoConstraints;\n  }\n  public set defaultVideoConstraints(value: MediaTrackConstraints | undefined) {\n    PreferencesContainer.instance.defaultVideoConstraints = value;\n  }\n\n  /** Whether stereo Opus is enabled globally. */\n  public get stereoAudio(): boolean {\n    return PreferencesContainer.instance.stereoAudio;\n  }\n  public set stereoAudio(value: boolean) {\n    PreferencesContainer.instance.stereoAudio = value;\n    this._saveToStorage();\n  }\n\n  // ===========================================================================\n  // Degradation (Section 22)\n  // ===========================================================================\n\n  /** Whether automatic video degradation on low bandwidth is enabled. */\n  public get enableAutoDegradation(): boolean {\n    return PreferencesContainer.instance.enableAutoDegradation;\n  }\n  public set enableAutoDegradation(value: boolean) {\n    PreferencesContainer.instance.enableAutoDegradation = value;\n    this._saveToStorage();\n  }\n\n  /** Bitrate in kbps below which video is automatically disabled. */\n  public get degradationBitrateThreshold(): number {\n    return PreferencesContainer.instance.degradationBitrateThreshold;\n  }\n  public set degradationBitrateThreshold(value: number) {\n    PreferencesContainer.instance.degradationBitrateThreshold = value;\n    this._saveToStorage();\n  }\n\n  /** Bitrate in kbps above which video is automatically re-enabled. */\n  public get degradationRecoveryThreshold(): number {\n    return PreferencesContainer.instance.degradationRecoveryThreshold;\n  }\n  public set degradationRecoveryThreshold(value: number) {\n    PreferencesContainer.instance.degradationRecoveryThreshold = value;\n    this._saveToStorage();\n  }\n\n  // ===========================================================================\n  // Codec preferences (Section 23)\n  // ===========================================================================\n\n  /** Preferred video codecs in priority order. */\n  public get preferredVideoCodecs(): string[] {\n    return PreferencesContainer.instance.preferredVideoCodecs;\n  }\n  public set preferredVideoCodecs(value: string[]) {\n    PreferencesContainer.instance.preferredVideoCodecs = value;\n    this._saveToStorage();\n  }\n\n  /** Preferred audio codecs in priority order. */\n  public get preferredAudioCodecs(): string[] {\n    return PreferencesContainer.instance.preferredAudioCodecs;\n  }\n  public set preferredAudioCodecs(value: string[]) {\n    PreferencesContainer.instance.preferredAudioCodecs = value;\n    this._saveToStorage();\n  }\n\n  /** Saves current preferences to storage (fire-and-forget). */\n  private _saveToStorage(): void {\n    if (!this._storage) return;\n    const data = collectStoredPreferences();\n    this._storage.setItem(PREFERENCES_STORAGE_KEY, data, 'local').catch((error: unknown) => {\n      logger.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);\n    });\n  }\n\n  /** Loads preferences from storage and applies them to the container. */\n  private _loadFromStorage(): void {\n    if (!this._storage) return;\n    this._storage\n      .getItem<StoredPreferences>(PREFERENCES_STORAGE_KEY, 'local')\n      .then((stored) => {\n        if (stored) {\n          applyStoredPreferences(stored);\n        }\n      })\n      .catch((error: unknown) => {\n        logger.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);\n      });\n  }\n}\n","/**\n * Normalizes an unknown caught value into a proper Error instance.\n *\n * In catch blocks, the caught value is `unknown` — it could be an Error,\n * string, number, or any other value. This utility ensures a consistent\n * Error object is produced.\n */\nexport function toError(value: unknown): Error {\n  if (value instanceof Error) return value;\n  return new Error(String(value));\n}\n","import { debounceTime, distinctUntilChanged, interval, map, takeUntil, tap } from 'rxjs';\n\nimport { DeviceHistoryManager } from './DeviceHistoryManager';\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { PreferencesContainer } from '../containers/PreferencesContainer';\nimport {\n  DEVICE_STORAGE_KEY_AUDIO_INPUT,\n  DEVICE_STORAGE_KEY_AUDIO_OUTPUT,\n  DEVICE_STORAGE_KEY_VIDEO_INPUT\n} from '../core/constants';\nimport { getLogger } from '../utils/logger';\nimport { toError } from '../utils/toError';\n\nimport type { StoredDevicePreference, DeviceRecoveryEvent } from '../core/types/resilience.types';\nimport type { WebRTCApiProvider } from '../dependencies/interfaces';\nimport type { DeviceController } from '../interfaces/DeviceController';\nimport type { StorageManager } from '../managers/StorageManager';\nimport type { Observable, Subscription } from 'rxjs';\n\nconst logger = getLogger();\n\ntype DeviceKind = 'audioinput' | 'audiooutput' | 'videoinput';\n\ninterface DevicesState {\n  audioinput: MediaDeviceInfo[];\n  audiooutput: MediaDeviceInfo[];\n  videoinput: MediaDeviceInfo[];\n}\n\ninterface SelectedDevicesState {\n  audioinput: MediaDeviceInfo | null;\n  audiooutput: MediaDeviceInfo | null;\n  videoinput: MediaDeviceInfo | null;\n}\n\n/** Maps a device kind to its storage key. */\nconst DEVICE_STORAGE_KEYS: Record<DeviceKind, string> = {\n  audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,\n  audiooutput: DEVICE_STORAGE_KEY_AUDIO_OUTPUT,\n  videoinput: DEVICE_STORAGE_KEY_VIDEO_INPUT\n};\n\nconst initialDevicesState: DevicesState = {\n  audioinput: [],\n  audiooutput: [],\n  videoinput: []\n};\n\nconst initialSelectedDevicesState: SelectedDevicesState = {\n  audioinput: null,\n  audiooutput: null,\n  videoinput: null\n};\n\nexport class NavigatorDeviceController extends Destroyable implements DeviceController {\n  private deviceChangeHandler = () => {\n    logger.debug('[DeviceController] Device change detected');\n    void this.enumerateDevices();\n  };\n\n  private _devicesPoolingSubscription?: Subscription;\n  private _devicesState$ = this.createBehaviorSubject<DevicesState>(initialDevicesState);\n  private _selectedDevicesState$ = this.createBehaviorSubject<SelectedDevicesState>(\n    initialSelectedDevicesState\n  );\n\n  // Error stream\n  private _errors$ = this.createReplaySubject<Error>(1);\n\n  // Device history for succession chain (Section 5.2)\n  private _deviceHistory = new DeviceHistoryManager();\n  private _deviceRecovered$ = this.createSubject<DeviceRecoveryEvent>();\n\n  // Section 5.9: Intentional device disable\n  private _audioInputDisabled$ = this.createBehaviorSubject<boolean>(false);\n  private _videoInputDisabled$ = this.createBehaviorSubject<boolean>(false);\n\n  // Section 5.9: Last selection before disable, for restore on enable\n  private _lastAudioInputBeforeDisable: MediaDeviceInfo | null = null;\n  private _lastVideoInputBeforeDisable: MediaDeviceInfo | null = null;\n\n  // Section 5.1: Loaded persisted device preferences (populated on init)\n  private _persistedDevices: Partial<Record<DeviceKind, StoredDevicePreference>> = {};\n\n  // Storage manager (optional) for device persistence (Section 5.1)\n  private _storageManager?: StorageManager;\n\n  constructor(\n    private readonly webRTCApiProvider: WebRTCApiProvider,\n    storageManager?: StorageManager\n  ) {\n    super();\n    this._storageManager = storageManager;\n    this.init();\n  }\n\n  // ---- Section 5.1: Storage Manager access ----\n\n  /** Sets the storage manager for device persistence. */\n  public setStorageManager(storageManager: StorageManager): void {\n    this._storageManager = storageManager;\n    // Reload persisted devices when storage becomes available\n    void this.loadPersistedDevices();\n  }\n\n  // ---- Constraints ----\n\n  public get selectedAudioInputDeviceConstraints(): MediaTrackConstraints | boolean {\n    if (this._audioInputDisabled$.value) {\n      return false;\n    }\n    return this.deviceInfoToConstraints(this.selectedAudioInputDevice);\n  }\n\n  public get selectedVideoInputDeviceConstraints(): MediaTrackConstraints | boolean {\n    if (this._videoInputDisabled$.value) {\n      return false;\n    }\n    return this.deviceInfoToConstraints(this.selectedVideoInputDevice);\n  }\n\n  public deviceInfoToConstraints(deviceInfo: MediaDeviceInfo | null): MediaTrackConstraints {\n    if (!deviceInfo?.deviceId || deviceInfo.deviceId.trim() === '') {\n      return {};\n    }\n    const devices =\n      deviceInfo.kind === 'audioinput' ? this.audioInputDevices : this.videoInputDevices;\n    const device =\n      devices.find((device) => device.deviceId === deviceInfo.deviceId) ??\n      devices.find((device) => device.label === deviceInfo.label);\n    if (device) {\n      return { deviceId: { exact: device.deviceId } };\n    }\n    return {};\n  }\n\n  public get errors$(): Observable<Error> {\n    return this.cachedObservable('errors$', () =>\n      this._errors$.asObservable().pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  /** Observable that emits when the SDK auto-switches a device. */\n  public get deviceRecovered$(): Observable<DeviceRecoveryEvent> {\n    return this._deviceRecovered$.asObservable().pipe(takeUntil(this.destroyed$));\n  }\n\n  // ---- Section 5.9: Disable/enable observables ----\n\n  public get videoInputDisabled$(): Observable<boolean> {\n    return this.cachedObservable('videoInputDisabled$', () =>\n      this._videoInputDisabled$\n        .asObservable()\n        .pipe(distinctUntilChanged(), takeUntil(this.destroyed$))\n    );\n  }\n\n  public get audioInputDisabled$(): Observable<boolean> {\n    return this.cachedObservable('audioInputDisabled$', () =>\n      this._audioInputDisabled$\n        .asObservable()\n        .pipe(distinctUntilChanged(), takeUntil(this.destroyed$))\n    );\n  }\n\n  public get videoInputDisabled(): boolean {\n    return this._videoInputDisabled$.value;\n  }\n\n  public get audioInputDisabled(): boolean {\n    return this._audioInputDisabled$.value;\n  }\n\n  // ---- Observable getters for device lists by kind ----\n\n  public get audioInputDevices$(): Observable<MediaDeviceInfo[]> {\n    return this.cachedObservable('audioInputDevices$', () =>\n      this._devicesState$.pipe(\n        map((state) => state.audioinput),\n        distinctUntilChanged(),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  public get audioOutputDevices$(): Observable<MediaDeviceInfo[]> {\n    return this.cachedObservable('audioOutputDevices$', () =>\n      this._devicesState$.pipe(\n        map((state) => state.audiooutput),\n        distinctUntilChanged(),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  public get videoInputDevices$(): Observable<MediaDeviceInfo[]> {\n    return this.cachedObservable('videoInputDevices$', () =>\n      this._devicesState$.pipe(\n        map((state) => state.videoinput),\n        distinctUntilChanged(),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  public get selectedAudioInputDevice$(): Observable<MediaDeviceInfo | null> {\n    return this.cachedObservable('selectedAudioInputDevice$', () =>\n      this._selectedDevicesState$.asObservable().pipe(\n        map((state) => state.audioinput),\n        distinctUntilChanged(),\n        takeUntil(this.destroyed$),\n        tap((info) => logger.debug('[DeviceController] Selected audio input device changed:', info))\n      )\n    );\n  }\n\n  public get selectedAudioOutputDevice$(): Observable<MediaDeviceInfo | null> {\n    return this.cachedObservable('selectedAudioOutputDevice$', () =>\n      this._selectedDevicesState$.asObservable().pipe(\n        map((state) => state.audiooutput),\n        distinctUntilChanged(),\n        takeUntil(this.destroyed$),\n        tap((info) =>\n          logger.debug('[DeviceController] Selected audio output device changed:', info)\n        )\n      )\n    );\n  }\n\n  public get selectedVideoInputDevice$(): Observable<MediaDeviceInfo | null> {\n    return this.cachedObservable('selectedVideoInputDevice$', () =>\n      this._selectedDevicesState$.asObservable().pipe(\n        map((state) => state.videoinput),\n        distinctUntilChanged(),\n        takeUntil(this.destroyed$),\n        tap((info) => logger.debug('[DeviceController] Selected video input device changed:', info))\n      )\n    );\n  }\n\n  // Current value getters for selected devices\n  public get selectedAudioInputDevice(): MediaDeviceInfo | null {\n    if (this._audioInputDisabled$.value) {\n      return null;\n    }\n    return this._selectedDevicesState$.value.audioinput;\n  }\n\n  public get selectedAudioOutputDevice(): MediaDeviceInfo | null {\n    return this._selectedDevicesState$.value.audiooutput;\n  }\n\n  public get selectedVideoInputDevice(): MediaDeviceInfo | null {\n    if (this._videoInputDisabled$.value) {\n      return null;\n    }\n    return this._selectedDevicesState$.value.videoinput;\n  }\n\n  public get audioInputDevices(): MediaDeviceInfo[] {\n    return this._devicesState$.value.audioinput;\n  }\n\n  public get audioOutputDevices(): MediaDeviceInfo[] {\n    return this._devicesState$.value.audiooutput;\n  }\n\n  public get videoInputDevices(): MediaDeviceInfo[] {\n    return this._devicesState$.value.videoinput;\n  }\n\n  // ---- Section 5.9: Disable/enable methods ----\n\n  public disableAudioInput(): void {\n    if (!this._audioInputDisabled$.value) {\n      this._lastAudioInputBeforeDisable = this._selectedDevicesState$.value.audioinput;\n      this._audioInputDisabled$.next(true);\n      // Emit null on selected device when disabled\n      this._selectedDevicesState$.next({\n        ...this._selectedDevicesState$.value,\n        audioinput: null\n      });\n    }\n  }\n\n  public enableAudioInput(): void {\n    if (this._audioInputDisabled$.value) {\n      this._audioInputDisabled$.next(false);\n      // Restore last selection or auto-select\n      const restored = this._lastAudioInputBeforeDisable ?? this.audioInputDevices[0];\n      this._selectedDevicesState$.next({\n        ...this._selectedDevicesState$.value,\n        audioinput: restored\n      });\n      this._lastAudioInputBeforeDisable = null;\n    }\n  }\n\n  public disableVideoInput(): void {\n    if (!this._videoInputDisabled$.value) {\n      this._lastVideoInputBeforeDisable = this._selectedDevicesState$.value.videoinput;\n      this._videoInputDisabled$.next(true);\n      this._selectedDevicesState$.next({\n        ...this._selectedDevicesState$.value,\n        videoinput: null\n      });\n    }\n  }\n\n  public enableVideoInput(): void {\n    if (this._videoInputDisabled$.value) {\n      this._videoInputDisabled$.next(false);\n      const restored = this._lastVideoInputBeforeDisable ?? this.videoInputDevices[0];\n      this._selectedDevicesState$.next({\n        ...this._selectedDevicesState$.value,\n        videoinput: restored\n      });\n      this._lastVideoInputBeforeDisable = null;\n    }\n  }\n\n  // ---- Setters for selected devices ----\n\n  public selectAudioInputDevice(device: MediaDeviceInfo | null): void {\n    // If user selects while disabled, re-enable\n    if (this._audioInputDisabled$.value && device) {\n      this._audioInputDisabled$.next(false);\n    }\n    const previous = this._selectedDevicesState$.value.audioinput;\n    if (previous && previous.deviceId !== device?.deviceId) {\n      this._deviceHistory.push('audioinput', previous);\n    }\n    this._selectedDevicesState$.next({\n      ...this._selectedDevicesState$.value,\n      audioinput: device\n    });\n    if (device) {\n      void this.persistDeviceSelection('audioinput', device);\n    }\n  }\n\n  public selectVideoInputDevice(device: MediaDeviceInfo | null): void {\n    logger.debug('[DeviceController] Setting selected video input device:', device);\n    if (this._videoInputDisabled$.value && device) {\n      this._videoInputDisabled$.next(false);\n    }\n    const previous = this._selectedDevicesState$.value.videoinput;\n    if (previous && previous.deviceId !== device?.deviceId) {\n      this._deviceHistory.push('videoinput', previous);\n    }\n    this._selectedDevicesState$.next({\n      ...this._selectedDevicesState$.value,\n      videoinput: device\n    });\n    if (device) {\n      void this.persistDeviceSelection('videoinput', device);\n    }\n  }\n\n  public selectAudioOutputDevice(device: MediaDeviceInfo | null): void {\n    const previous = this._selectedDevicesState$.value.audiooutput;\n    if (previous && previous.deviceId !== device?.deviceId) {\n      this._deviceHistory.push('audiooutput', previous);\n    }\n    this._selectedDevicesState$.next({\n      ...this._selectedDevicesState$.value,\n      audiooutput: device\n    });\n    if (device) {\n      void this.persistDeviceSelection('audiooutput', device);\n    }\n  }\n\n  private init(): void {\n    // Load persisted device preferences (Section 5.1)\n    void this.loadPersistedDevices();\n\n    // Subscribe to device state changes and auto-select devices\n    this.subscribeTo(\n      this._devicesState$.pipe(debounceTime(PreferencesContainer.instance.deviceDebounceTime)),\n      (devicesState) => {\n        // Skip auto-resolution if syncDevicesToActiveCalls is disabled (Section 5.5)\n        const currentSelected = this._selectedDevicesState$.value;\n\n        const newAudioInput = this._audioInputDisabled$.value\n          ? null\n          : this.resolveDevice(\n              'audioinput',\n              devicesState.audioinput,\n              currentSelected.audioinput,\n              PreferencesContainer.instance.preferredAudioInput\n            );\n\n        const newAudioOutput = this.resolveDevice(\n          'audiooutput',\n          devicesState.audiooutput,\n          currentSelected.audiooutput,\n          PreferencesContainer.instance.preferredAudioOutput\n        );\n\n        const newVideoInput = this._videoInputDisabled$.value\n          ? null\n          : this.resolveDevice(\n              'videoinput',\n              devicesState.videoinput,\n              currentSelected.videoinput,\n              PreferencesContainer.instance.preferredVideoInput\n            );\n\n        // Only update if something changed\n        if (\n          newAudioInput !== currentSelected.audioinput ||\n          newAudioOutput !== currentSelected.audiooutput ||\n          newVideoInput !== currentSelected.videoinput\n        ) {\n          // Section 5.5: Gate auto-switch on syncDevicesToActiveCalls preference.\n          // When disabled, only update if there is no current selection (first init)\n          // or the device was explicitly null already.\n          const shouldSync = PreferencesContainer.instance.syncDevicesToActiveCalls;\n          this._selectedDevicesState$.next({\n            audioinput:\n              shouldSync || !currentSelected.audioinput\n                ? newAudioInput\n                : currentSelected.audioinput,\n            audiooutput:\n              shouldSync || !currentSelected.audiooutput\n                ? newAudioOutput\n                : currentSelected.audiooutput,\n            videoinput:\n              shouldSync || !currentSelected.videoinput ? newVideoInput : currentSelected.videoinput\n          });\n        }\n      }\n    );\n\n    void this.enumerateDevices();\n  }\n\n  /**\n   * Resolves the best device for a given kind, using device history\n   * for succession when the selected device disappears.\n   * Section 5.1 adds persisted device resolution.\n   * Section 5.4 adds duplicate-named device handling.\n   */\n  private resolveDevice(\n    kind: DeviceKind,\n    devices: MediaDeviceInfo[],\n    selected: MediaDeviceInfo | null,\n    preferred: MediaDeviceInfo | null\n  ): MediaDeviceInfo | null {\n    // If no devices, return null\n    if (devices.length === 0) {\n      return null;\n    }\n\n    // If selected device is still available, keep it\n    if (selected) {\n      const exactMatch = devices.find((device) => device.deviceId === selected.deviceId);\n      if (exactMatch) {\n        return selected;\n      }\n\n      // Section 5.4: Label match with duplicate handling\n      const labelMatches = devices.filter((device) => device.label === selected.label);\n      if (labelMatches.length === 1) {\n        return labelMatches[0];\n      }\n      if (labelMatches.length > 1) {\n        // Try groupId match among the label matches\n        const groupMatch = labelMatches.find((device) => device.groupId === selected.groupId);\n        if (groupMatch) {\n          return groupMatch;\n        }\n        // Ambiguous - emit event and don't auto-switch\n        this.emitDeviceRecovered(kind, selected, null, 'ambiguous_match');\n        return null;\n      }\n\n      // Selected device disappeared - try device history succession\n      const fromHistory = this._deviceHistory.findInHistory(kind, devices);\n      if (fromHistory) {\n        logger.debug(\n          `[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`\n        );\n        this.emitDeviceRecovered(kind, selected, fromHistory, 'device_disconnected');\n        return fromHistory;\n      }\n\n      // No history match - fall back to preferred or first available\n      const replacement = preferred\n        ? devices.find(\n            (device) => device.deviceId === preferred.deviceId || device.label === preferred.label\n          )\n        : undefined;\n      const newDevice = replacement ?? devices[0];\n      this.emitDeviceRecovered(kind, selected, newDevice, 'fallback_to_default');\n      return newDevice;\n    }\n\n    // No selected device - try persisted device (Section 5.1)\n    const persisted = this._persistedDevices[kind];\n    if (persisted) {\n      const fromPersisted = this.resolvePersistedDevice(kind, persisted, devices);\n      if (fromPersisted) {\n        return fromPersisted;\n      }\n    }\n\n    // No selected device - use preferred or first available\n    if (preferred) {\n      const preferredDevice = devices.find(\n        (device) => device.deviceId === preferred.deviceId || device.label === preferred.label\n      );\n      if (preferredDevice) {\n        return preferredDevice;\n      }\n    }\n    return devices[0];\n  }\n\n  /**\n   * Section 5.1: Resolves a stored device preference against the current device list.\n   * Priority: exact deviceId > groupId+label > label (single match only)\n   */\n  private resolvePersistedDevice(\n    _kind: DeviceKind,\n    stored: StoredDevicePreference,\n    devices: MediaDeviceInfo[]\n  ): MediaDeviceInfo | null {\n    // 1. Exact deviceId match\n    const exactMatch = devices.find((d) => d.deviceId === stored.deviceId);\n    if (exactMatch) {\n      return exactMatch;\n    }\n\n    // 2. groupId + label match\n    const groupLabelMatch = devices.find(\n      (d) => d.groupId === stored.groupId && d.label === stored.label\n    );\n    if (groupLabelMatch) {\n      return groupLabelMatch;\n    }\n\n    // 3. label match (single match only to avoid ambiguity)\n    const labelMatches = devices.filter((d) => d.label === stored.label);\n    if (labelMatches.length === 1) {\n      return labelMatches[0];\n    }\n\n    return null;\n  }\n\n  private emitDeviceRecovered(\n    kind: DeviceKind,\n    previousDevice: MediaDeviceInfo | null,\n    newDevice: MediaDeviceInfo | null,\n    reason: DeviceRecoveryEvent['reason']\n  ): void {\n    try {\n      this._deviceRecovered$.next({\n        kind,\n        previousDevice,\n        newDevice,\n        reason\n      });\n    } catch {\n      // Non-fatal: observer might already be completed\n    }\n  }\n\n  // ---- Section 5.1: Device persistence ----\n\n  private async persistDeviceSelection(kind: DeviceKind, device: MediaDeviceInfo): Promise<void> {\n    if (!this._storageManager || !PreferencesContainer.instance.persistDeviceSelection) {\n      return;\n    }\n    const stored: StoredDevicePreference = {\n      deviceId: device.deviceId,\n      label: device.label,\n      kind: device.kind,\n      groupId: device.groupId\n    };\n    try {\n      await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, 'local');\n    } catch (error) {\n      logger.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);\n    }\n  }\n\n  private async loadPersistedDevices(): Promise<void> {\n    if (!this._storageManager || !PreferencesContainer.instance.persistDeviceSelection) {\n      return;\n    }\n    const kinds: DeviceKind[] = ['audioinput', 'audiooutput', 'videoinput'];\n    for (const kind of kinds) {\n      try {\n        const stored = await this._storageManager.getItem<StoredDevicePreference>(\n          DEVICE_STORAGE_KEYS[kind],\n          'local'\n        );\n        if (stored) {\n          this._persistedDevices = { ...this._persistedDevices, [kind]: stored };\n        }\n      } catch (error) {\n        logger.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);\n      }\n    }\n  }\n\n  // ---- Section 5.11: Clear device state for factory reset ----\n\n  /** Clears device history, persisted selections, and re-enumerates devices. */\n  public async clearDeviceState(): Promise<void> {\n    this._deviceHistory.clear();\n    this._persistedDevices = {};\n    this._lastAudioInputBeforeDisable = null;\n    this._lastVideoInputBeforeDisable = null;\n    this._audioInputDisabled$.next(false);\n    this._videoInputDisabled$.next(false);\n    this._selectedDevicesState$.next(initialSelectedDevicesState);\n    await this.enumerateDevices();\n  }\n\n  public enableDeviceMonitoring(): void {\n    this.disableDeviceMonitoring();\n    this.webRTCApiProvider.mediaDevices.addEventListener('devicechange', this.deviceChangeHandler);\n\n    if (PreferencesContainer.instance.devicePollingInterval > 0) {\n      this._devicesPoolingSubscription = interval(\n        PreferencesContainer.instance.devicePollingInterval\n      ).subscribe(() => {\n        logger.debug('[DeviceController] Polling devices due to interval');\n        void this.enumerateDevices();\n      });\n    }\n\n    void this.enumerateDevices();\n  }\n\n  public disableDeviceMonitoring(): void {\n    this.webRTCApiProvider.mediaDevices.removeEventListener(\n      'devicechange',\n      this.deviceChangeHandler\n    );\n    if (this._devicesPoolingSubscription) {\n      this._devicesPoolingSubscription.unsubscribe();\n      this._devicesPoolingSubscription = undefined;\n    }\n  }\n\n  public async enumerateDevices(): Promise<void> {\n    try {\n      const devices = await this.webRTCApiProvider.mediaDevices.enumerateDevices();\n\n      const devicesByKind: DevicesState = devices.reduce(\n        (acc, device) => {\n          const kind = device.kind as keyof DevicesState;\n          return {\n            ...acc,\n            [kind]: [...acc[kind], device]\n          };\n        },\n        {\n          audioinput: [],\n          audiooutput: [],\n          videoinput: []\n        } as DevicesState\n      );\n\n      // Update state in a single emission\n      this._devicesState$.next(devicesByKind);\n\n      logger.debug('[DeviceController] Devices enumerated:', {\n        audioInputs: devicesByKind.audioinput.length,\n        audioOutputs: devicesByKind.audiooutput.length,\n        videoInputs: devicesByKind.videoinput.length\n      });\n    } catch (error) {\n      logger.error('[DeviceController] Failed to enumerate devices:', error);\n      this._errors$.next(toError(error));\n    }\n  }\n\n  public async getDeviceCapabilities(\n    deviceInfo: MediaDeviceInfo\n  ): Promise<MediaTrackCapabilities | null> {\n    if (deviceInfo.kind === 'audiooutput') {\n      return null;\n    }\n\n    try {\n      const constraints = this.deviceInfoToConstraints(deviceInfo);\n      const stream = await this.webRTCApiProvider.mediaDevices.getUserMedia({\n        audio: deviceInfo.kind === 'audioinput' ? constraints : false,\n        video: deviceInfo.kind === 'videoinput' ? constraints : false\n      });\n\n      const track =\n        deviceInfo.kind === 'audioinput' ? stream.getAudioTracks()[0] : stream.getVideoTracks()[0];\n\n      const capabilities = track.getCapabilities();\n\n      // Stop all tracks to release devices\n      stream.getTracks().forEach((t) => t.stop());\n\n      return capabilities;\n    } catch (error) {\n      logger.error('[DeviceController] Failed to get device capabilities:', error);\n      this._errors$.next(toError(error));\n      throw error;\n    }\n  }\n\n  public async isValidDevice(deviceInfo: MediaDeviceInfo | null): Promise<boolean> {\n    if (!deviceInfo || deviceInfo.kind === 'audiooutput') {\n      return false;\n    }\n    try {\n      const capabilities = await this.getDeviceCapabilities(deviceInfo);\n      return capabilities !== null;\n    } catch {\n      return false;\n    }\n  }\n\n  public destroy(): void {\n    this.disableDeviceMonitoring();\n    super.destroy();\n  }\n}\n","import { StorageNotAvailableError } from '../core/errors';\nimport { getLogger } from '../utils/logger';\n\nimport type { Storage, StorageScope } from './interfaces';\n\n/** Default storage implementation backed by browser `localStorage` and `sessionStorage`. */\nexport class DefaultLocalStorage implements Storage {\n  constructor() {\n    // Check if localStorage is available\n    if (typeof localStorage === 'undefined') {\n      throw new StorageNotAvailableError('localStorage');\n    }\n    if (typeof sessionStorage === 'undefined') {\n      throw new StorageNotAvailableError('sessionStorage');\n    }\n\n    // Test if localStorage is actually accessible (some browsers block it)\n    try {\n      const testKey = '__storage_test__';\n      localStorage.setItem(testKey, 'test');\n      localStorage.removeItem(testKey);\n    } catch (error) {\n      getLogger().error('LocalStorage is not accessible:', error);\n      throw new StorageNotAvailableError('localStorage');\n    }\n  }\n\n  private storage(scope: StorageScope) {\n    return scope === 'local' ? localStorage : sessionStorage;\n  }\n\n  async setItem(key: string, value: string, scope: StorageScope = 'session'): Promise<void> {\n    this.storage(scope).setItem(key, value);\n    return Promise.resolve();\n  }\n\n  async getItem(key: string, scope: StorageScope = 'session'): Promise<string | null> {\n    return Promise.resolve(this.storage(scope).getItem(key));\n  }\n\n  async removeItem(key: string, scope: StorageScope = 'session'): Promise<void> {\n    this.storage(scope).removeItem(key);\n    return Promise.resolve();\n  }\n\n  async clear(scope: StorageScope = 'session'): Promise<void> {\n    const store = this.storage(scope);\n    const keysToRemove: string[] = [];\n    for (let i = 0; i < store.length; i++) {\n      const key = store.key(i);\n      if (key?.startsWith('sw:')) {\n        keysToRemove.push(key);\n      }\n    }\n    for (const key of keysToRemove) {\n      store.removeItem(key);\n    }\n    return Promise.resolve();\n  }\n}\n","import {\n  SerializationError,\n  StorageWriteError,\n  DeserializationError,\n  StorageReadError\n} from '../core/errors';\nimport { DefaultLocalStorage } from '../dependencies/DefaultLocalStorage';\n\nimport type { Storage, StorageScope } from '../dependencies/interfaces';\n\nexport class StorageManager {\n  constructor(private storageImpl: Storage = new DefaultLocalStorage()) {}\n\n  /**\n   * Validates that a value can be safely serialized to JSON\n   * @throws SerializationError if value contains non-serializable types\n   */\n  private serialize(value: unknown, key?: string): string | null {\n    if (value === undefined || value === null) {\n      // undefined is acceptable, will be stored as null\n      return null;\n    }\n\n    try {\n      return JSON.stringify(value);\n    } catch (e) {\n      throw new SerializationError(key ?? 'unknown', e as Error);\n    }\n  }\n\n  /**\n   * Stores a value in storage\n   * @throws InvalidStorageValueError if value contains non-serializable types\n   * @throws SerializationError if JSON serialization fails\n   * @throws StorageWriteError if writing to storage fails\n   */\n  public async setItem(\n    key: string,\n    value: unknown,\n    scope: StorageScope = 'session'\n  ): Promise<void> {\n    const serialized = this.serialize(value, key);\n\n    try {\n      await this.storageImpl.setItem(key, serialized, scope);\n    } catch (error) {\n      throw new StorageWriteError(key, error as Error);\n    }\n  }\n\n  /**\n   * Retrieves a value from storage\n   *\n   * This method distinguishes between:\n   * - Storage errors (network, permission, etc.) - these are thrown\n   * - JSON parse errors - these trigger onParseError and return raw string\n   * - Missing keys - returns null\n   *\n   * @returns The parsed value, raw string (on parse error), or null\n   * @throws StorageReadError\n   */\n  public async getItem<T = unknown>(\n    key: string,\n    scope: StorageScope = 'session'\n  ): Promise<T | null> {\n    let item: string | null;\n\n    try {\n      item = await this.storageImpl.getItem(key, scope);\n    } catch (error) {\n      throw new StorageReadError(key, error as Error);\n    }\n\n    if (!item) {\n      return null;\n    }\n\n    try {\n      return JSON.parse(item) as T;\n    } catch (error) {\n      throw new DeserializationError(key, error as Error);\n    }\n  }\n\n  /**\n   * Removes a value from storage\n   * @throws Error from underlying storage implementation\n   */\n  public async removeItem(key: string, scope: StorageScope = 'session'): Promise<void> {\n    try {\n      await this.storageImpl.removeItem(key, scope);\n    } catch (error) {\n      throw new StorageWriteError(key, error as Error);\n    }\n  }\n\n  /**\n   * Clears all SDK keys from both 'local' and 'session' scopes.\n   * @throws StorageWriteError if clearing fails\n   */\n  public async clearAll(): Promise<void> {\n    try {\n      await this.storageImpl.clear('local');\n      await this.storageImpl.clear('session');\n    } catch (error) {\n      throw new StorageWriteError('clearAll', error as Error);\n    }\n  }\n}\n","import { HTTPRequestController } from '../controllers/HTTPRequestController';\nimport { NavigatorDeviceController } from '../controllers/NavigatorDeviceController';\nimport { DependencyError } from '../core/errors';\nimport { DefaultLocalStorage } from '../dependencies/DefaultLocalStorage';\nimport { StorageManager } from '../managers/StorageManager';\n\nimport type { User } from '../core/entities/User';\nimport type {\n  NodeSocketAdapter,\n  SDKCredential,\n  WebSocketAdapter\n} from '../core/types/common.types';\nimport type { Storage, WebRTCApiProvider } from '../dependencies/interfaces';\nimport type { ConversationsProvider } from '../interfaces/Conversations';\nimport type { Dependency } from '../interfaces/Dependency';\nimport type { DeviceController } from '../interfaces/DeviceController';\n\nexport class DependencyContainer implements Dependency {\n  /**\n   * When true, credential + auth state + protocol are stored in localStorage\n   * (survives page reload). When false (default), sessionStorage is used.\n   */\n  public persistSession = false;\n  private _conversationManager?: ConversationsProvider;\n\n  private _user?: User;\n\n  private _host?: string;\n\n  private _domain?: string;\n\n  private _storageManager?: StorageManager;\n  private _storageImpl?: Storage;\n  private _webSocketConstructor?: WebSocketAdapter | NodeSocketAdapter =\n    typeof WebSocket !== 'undefined' ? WebSocket : undefined;\n  private _baseURL: string = this.apiHost;\n  private _credential: SDKCredential = {};\n  private _httpRequestController?: HTTPRequestController;\n  private _deviceController?: NavigatorDeviceController;\n  private _webRTCApiProvider?: WebRTCApiProvider;\n\n  public get userId(): string {\n    return this.user.id;\n  }\n\n  public get user(): User {\n    if (!this._user) {\n      throw new DependencyError('User');\n    }\n    return this._user;\n  }\n  public set user(user: User) {\n    this._user = user;\n  }\n\n  public get storage(): StorageManager {\n    if (!this._storageManager) {\n      // Lazily initialize storage implementation if not already set\n      this._storageImpl ??= new DefaultLocalStorage();\n      this._storageManager = new StorageManager(this._storageImpl);\n    }\n    return this._storageManager;\n  }\n\n  public get http(): HTTPRequestController {\n    this._httpRequestController ??= new HTTPRequestController(\n      this._baseURL,\n      () => this._credential\n    );\n    return this._httpRequestController;\n  }\n\n  public get conversationManager(): ConversationsProvider {\n    if (!this._conversationManager) {\n      throw new DependencyError('ConversationsManager');\n    }\n    return this._conversationManager;\n  }\n\n  public set conversationManager(conversationManager: ConversationsProvider) {\n    this._conversationManager = conversationManager;\n  }\n\n  public get WebSocket(): WebSocketAdapter | NodeSocketAdapter {\n    if (!this._webSocketConstructor) {\n      throw new DependencyError('WebSocket constructor');\n    }\n    return this._webSocketConstructor;\n  }\n\n  public set WebSocket(WebSocketConstructor: WebSocketAdapter | NodeSocketAdapter) {\n    this._webSocketConstructor = WebSocketConstructor;\n  }\n\n  public get deviceController(): DeviceController {\n    this._deviceController ??= new NavigatorDeviceController(this.webRTCApiProvider, this.storage);\n    return this._deviceController;\n  }\n\n  public get webRTCApiProvider(): WebRTCApiProvider {\n    if (!this._webRTCApiProvider) {\n      if (typeof RTCPeerConnection === 'undefined' || typeof navigator === 'undefined') {\n        throw new DependencyError(\n          'WebRTCApiProvider: RTCPeerConnection or navigator.mediaDevices is not available. ' +\n            'Please provide a custom webRTCApiProvider in SignalWireOptions.'\n        );\n      }\n      this._webRTCApiProvider = {\n        RTCPeerConnection,\n        mediaDevices: navigator.mediaDevices\n      };\n    }\n    return this._webRTCApiProvider;\n  }\n\n  public set webRTCApiProvider(webRTCApiProvider: WebRTCApiProvider) {\n    this._webRTCApiProvider = webRTCApiProvider;\n    // Reset device controller so it picks up the new provider\n    this._deviceController = undefined;\n  }\n\n  public get authorizationStateKey(): string {\n    return `sw:${this.userId}:as`;\n  }\n\n  public get protocolKey(): string {\n    return `sw:${this.userId}:pt`;\n  }\n\n  public get attachedCallsKey(): string {\n    return `sw:${this.userId}:att`;\n  }\n\n  public getUserFromAddressId(): string {\n    return this.user.addresses[0]?.id ?? '';\n  }\n\n  public set baseURL(baseURL: string) {\n    this._baseURL = baseURL;\n    this._httpRequestController = undefined;\n  }\n\n  public get credential(): SDKCredential {\n    return this._credential;\n  }\n\n  public set credential(credential: SDKCredential) {\n    this._credential = credential;\n  }\n\n  public set storageImpl(storageImpl: Storage) {\n    this._storageImpl = storageImpl;\n    this._storageManager = undefined;\n  }\n\n  public set ch(ch: string | undefined) {\n    if (!ch) {\n      return;\n    }\n\n    const firstDot = ch.indexOf('.');\n    if (firstDot !== -1) {\n      this._host = ch.substring(0, firstDot);\n      this._domain = ch.substring(firstDot + 1);\n    }\n\n    // Update baseURL and reset HTTP controller so it picks up the new host\n    this._baseURL = this.apiHost;\n    this._httpRequestController = undefined;\n  }\n\n  public get relayHost(): string {\n    return `wss://${this._host ?? 'puc'}.${this._domain ?? 'signalwire.com'}`;\n  }\n\n  public get apiHost(): string {\n    return `https://fabric.${this._domain ?? 'signalwire.com'}`;\n  }\n}\n","import { DPoPInitError } from '../core/errors';\nimport { getLogger } from '../utils/logger';\n\nimport type { DPoPHttpProofParams, DPoPRpcProofParams } from '../core/types/crypto.types';\n\nconst logger = getLogger();\n\nconst DPOP_DB_NAME = 'sw-dpop';\nconst DPOP_DB_VERSION = 1;\nconst DPOP_STORE_NAME = 'keys';\nconst DPOP_KEY_ID = 'dpop-keypair';\n\n/**\n * Base64url-encodes an ArrayBuffer (no padding, URL-safe).\n */\nconst base64url = (buffer: ArrayBuffer): string => {\n  const bytes = new Uint8Array(buffer);\n  let binary = '';\n  for (const byte of bytes) {\n    binary += String.fromCharCode(byte);\n  }\n  return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n};\n\n/**\n * Base64url-encodes a UTF-8 string.\n */\nconst base64urlEncode = (str: string): string => {\n  const encoded = new TextEncoder().encode(str);\n  return base64url(encoded.buffer);\n};\n\n/**\n * Computes the JWK Thumbprint per RFC 7638.\n *\n * Only supports RSA keys — the canonical JSON uses lexicographic member ordering: { e, kty, n }.\n * The thumbprint is SHA-256 of the canonical JSON, base64url-encoded.\n *\n * @throws {Error} If the JWK is not an RSA key.\n */\nconst computeJwkThumbprint = async (jwk: JsonWebKey): Promise<string> => {\n  if (jwk.kty !== 'RSA') {\n    throw new Error(`Unsupported key type for JWK Thumbprint: ${jwk.kty}. Only RSA is supported.`);\n  }\n  const canonical = JSON.stringify({ e: jwk.e, kty: jwk.kty, n: jwk.n });\n  const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(canonical));\n  return base64url(digest);\n};\n\n// ============================================================\n// IndexedDB helpers for persisting non-extractable CryptoKeys\n// ============================================================\n\nasync function openDpopDB(): Promise<IDBDatabase> {\n  return new Promise((resolve, reject) => {\n    const request = indexedDB.open(DPOP_DB_NAME, DPOP_DB_VERSION);\n    request.onupgradeneeded = () => {\n      const db = request.result;\n      if (!db.objectStoreNames.contains(DPOP_STORE_NAME)) {\n        db.createObjectStore(DPOP_STORE_NAME);\n      }\n    };\n    request.onsuccess = () => resolve(request.result);\n    request.onerror = () => reject(request.error ?? new Error('Failed to open IndexedDB'));\n  });\n}\n\nasync function loadKeyPairFromDB(): Promise<CryptoKeyPair | null> {\n  try {\n    const db = await openDpopDB();\n    return await new Promise((resolve, reject) => {\n      const tx = db.transaction(DPOP_STORE_NAME, 'readonly');\n      const req = tx.objectStore(DPOP_STORE_NAME).get(DPOP_KEY_ID);\n      req.onsuccess = () => resolve((req.result as CryptoKeyPair | undefined) ?? null);\n      req.onerror = () => reject(req.error ?? new Error('Failed to load key pair from IndexedDB'));\n      tx.oncomplete = () => db.close();\n    });\n  } catch (error) {\n    logger.warn('[DPoP] Failed to load key pair from IndexedDB:', error);\n    return null;\n  }\n}\n\nasync function saveKeyPairToDB(keyPair: CryptoKeyPair): Promise<void> {\n  try {\n    const db = await openDpopDB();\n    await new Promise<void>((resolve, reject) => {\n      const tx = db.transaction(DPOP_STORE_NAME, 'readwrite');\n      tx.objectStore(DPOP_STORE_NAME).put(keyPair, DPOP_KEY_ID);\n      tx.oncomplete = () => {\n        db.close();\n        resolve();\n      };\n      tx.onerror = () => {\n        db.close();\n        reject(tx.error ?? new Error('Failed to save key pair to IndexedDB'));\n      };\n    });\n  } catch (error) {\n    logger.warn('[DPoP] Failed to save key pair to IndexedDB:', error);\n  }\n}\n\nasync function deleteKeyPairFromDB(): Promise<void> {\n  try {\n    const db = await openDpopDB();\n    await new Promise<void>((resolve, reject) => {\n      const tx = db.transaction(DPOP_STORE_NAME, 'readwrite');\n      tx.objectStore(DPOP_STORE_NAME).delete(DPOP_KEY_ID);\n      tx.oncomplete = () => {\n        db.close();\n        resolve();\n      };\n      tx.onerror = () => {\n        db.close();\n        reject(tx.error ?? new Error('Failed to delete key pair from IndexedDB'));\n      };\n    });\n  } catch (error) {\n    logger.warn('[DPoP] Failed to delete key pair from IndexedDB:', error);\n  }\n}\n\n/**\n * Controls DPoP (Demonstrating Proof-of-Possession) cryptographic operations.\n *\n * Generates an RSA-2048 key pair where the private key is non-extractable,\n * computes the JWK Thumbprint (RFC 7638) as the fingerprint, and creates\n * signed DPoP proof JWTs for both HTTP API requests and WebSocket RPC calls.\n *\n * The key pair is persisted in IndexedDB so the same fingerprint survives\n * page reloads. This keeps the Client Bound SAT and stored authorization_state\n * valid across reloads without needing to re-authenticate.\n *\n * @example\n * ```typescript\n * const crypto = new CryptoController();\n * await crypto.init();\n *\n * // Get fingerprint for SAT issuance\n * const fingerprint = crypto.fingerprint;\n *\n * // Create proof for HTTP endpoint\n * const httpProof = await crypto.createHttpProof({\n *   method: 'POST',\n *   uri: '/api/fabric/subscriber/devices/token'\n * });\n *\n * // Create proof for RPC call\n * const rpcProof = await crypto.createRpcProof({\n *   method: 'signalwire.connect'\n * });\n * ```\n */\nexport class CryptoController {\n  private _keyPair: CryptoKeyPair | null = null;\n  private _publicJwk: JsonWebKey | null = null;\n  private _fingerprint: string | null = null;\n  private _initialized = false;\n\n  /**\n   * Initializes the DPoP key pair. Loads an existing key from IndexedDB\n   * if available, otherwise generates a new one and persists it.\n   *\n   * The private key is non-extractable — IndexedDB stores the CryptoKey\n   * handle via the structured clone algorithm without exposing key material.\n   *\n   * @returns The JWK Thumbprint (fingerprint) for the key.\n   */\n  public async init(): Promise<string> {\n    if (this._initialized) {\n      return this.fingerprint;\n    }\n\n    // Try to restore key pair from IndexedDB (survives page reload)\n    const stored = await loadKeyPairFromDB();\n    if (stored) {\n      try {\n        // Verify the stored key is still usable (sign a test payload)\n        const testData = new TextEncoder().encode('dpop-key-check');\n        await crypto.subtle.sign('RSASSA-PKCS1-v1_5', stored.privateKey, testData);\n\n        this._keyPair = stored;\n        this._publicJwk = await crypto.subtle.exportKey('jwk', stored.publicKey);\n        this._fingerprint = await computeJwkThumbprint(this._publicJwk);\n        this._initialized = true;\n\n        logger.debug('[DPoP] Key pair restored from IndexedDB, fingerprint:', this._fingerprint);\n        return this._fingerprint;\n      } catch (error) {\n        logger.warn('[DPoP] Stored key pair unusable, generating new one:', error);\n        await deleteKeyPairFromDB();\n      }\n    }\n\n    logger.debug('[DPoP] Generating RSA key pair');\n\n    this._keyPair = await crypto.subtle.generateKey(\n      {\n        name: 'RSASSA-PKCS1-v1_5',\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: 'SHA-256'\n      },\n      false, // Private key is NOT extractable\n      ['sign', 'verify']\n    );\n\n    this._publicJwk = await crypto.subtle.exportKey('jwk', this._keyPair.publicKey);\n    this._fingerprint = await computeJwkThumbprint(this._publicJwk);\n    this._initialized = true;\n\n    // Persist to IndexedDB so the same key survives page reload\n    await saveKeyPairToDB(this._keyPair);\n\n    logger.debug('[DPoP] Key pair generated and persisted, fingerprint:', this._fingerprint);\n    return this._fingerprint;\n  }\n\n  /**\n   * The JWK Thumbprint (RFC 7638) of the public key.\n   * Used as the `fingerprint` parameter when requesting scoped SATs.\n   *\n   * @throws {DPoPInitError} If {@link init} has not been called.\n   */\n  public get fingerprint(): string {\n    if (!this._fingerprint) {\n      throw new DPoPInitError('CryptoController not initialized. Call init() first.');\n    }\n    return this._fingerprint;\n  }\n\n  /**\n   * Whether the controller has been initialized with a key pair.\n   */\n  public get initialized(): boolean {\n    return this._initialized;\n  }\n\n  /**\n   * Creates a DPoP proof JWT for an HTTP API request.\n   *\n   * Used for Prime API endpoints like `/api/fabric/subscriber/devices/token`\n   * and `/api/fabric/subscriber/devices/refresh`.\n   *\n   * @param params - HTTP method and URI for the proof.\n   * @returns Signed DPoP proof JWT string.\n   */\n  public async createHttpProof(params: DPoPHttpProofParams): Promise<string> {\n    const payload: Record<string, unknown> = {\n      jti: crypto.randomUUID(),\n      htm: params.method,\n      htu: params.uri,\n      iat: Math.floor(Date.now() / 1000)\n    };\n\n    // RFC 9449 Section 4.2: include ath (access token hash) for resource endpoints\n    if (params.accessToken) {\n      const digest = await crypto.subtle.digest(\n        'SHA-256',\n        new TextEncoder().encode(params.accessToken)\n      );\n      payload.ath = base64url(digest);\n    }\n\n    return this.signProof(payload);\n  }\n\n  /**\n   * Creates a DPoP proof JWT for a WebSocket RPC call.\n   *\n   * Used for switchblade RPC methods like `signalwire.connect` and\n   * `signalwire.reauthenticate`.\n   *\n   * @param params - RPC method name for the proof.\n   * @returns Signed DPoP proof JWT string.\n   */\n  public async createRpcProof(params: DPoPRpcProofParams): Promise<string> {\n    const payload = {\n      jti: crypto.randomUUID(),\n      rpc: 'request' as const,\n      mth: params.method,\n      iat: Math.floor(Date.now() / 1000)\n    };\n\n    return this.signProof(payload);\n  }\n\n  /**\n   * Releases the key pair references and removes the persisted key from IndexedDB.\n   * After calling destroy, the controller must be re-initialized to be used again.\n   */\n  public destroy(): void {\n    this._keyPair = null;\n    this._publicJwk = null;\n    this._fingerprint = null;\n    this._initialized = false;\n    // Fire-and-forget — IndexedDB cleanup is best-effort\n    void deleteKeyPairFromDB();\n    logger.debug('[DPoP] Controller destroyed');\n  }\n\n  private get publicJwk(): JsonWebKey {\n    if (!this._publicJwk) {\n      throw new DPoPInitError('CryptoController not initialized. Call init() first.');\n    }\n    return this._publicJwk;\n  }\n\n  private get privateKey(): CryptoKey {\n    if (!this._keyPair) {\n      throw new DPoPInitError('CryptoController not initialized. Call init() first.');\n    }\n    return this._keyPair.privateKey;\n  }\n\n  private async signProof(payload: Record<string, unknown>): Promise<string> {\n    const header = {\n      typ: 'dpop+jwt' as const,\n      alg: 'RS256' as const,\n      jwk: this.publicJwk\n    };\n\n    const headerB64 = base64urlEncode(JSON.stringify(header));\n    const payloadB64 = base64urlEncode(JSON.stringify(payload));\n    const signingInput = `${headerB64}.${payloadB64}`;\n\n    const signature = await crypto.subtle.sign(\n      'RSASSA-PKCS1-v1_5',\n      this.privateKey,\n      new TextEncoder().encode(signingInput)\n    );\n\n    return `${signingInput}.${base64url(signature)}`;\n  }\n}\n","import { takeUntil } from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { getLogger } from '../utils/logger';\n\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface NetworkChangeEvent {\n  type: 'online' | 'offline' | 'connection_change';\n  timestamp: number;\n  networkType?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Safely check whether we are running in a browser environment\n * with `window` and the relevant event targets.\n */\nfunction hasBrowserNetworkEvents(): boolean {\n  return typeof window !== 'undefined' && typeof window.addEventListener === 'function';\n}\n\n/**\n * Attempt to read the current effective type from the Network Information API.\n * Returns `undefined` when the API is not available.\n */\nfunction getNetworkType(): string | undefined {\n  if (typeof navigator === 'undefined') {\n    return undefined;\n  }\n  // Network Information API is not universally typed\n  const { connection } = navigator as NavigatorWithConnection;\n  return connection?.effectiveType ?? undefined;\n}\n\n/**\n * Returns the `NetworkInformation` object when available, or `undefined`.\n */\nfunction getNetworkConnection(): NetworkInformation | undefined {\n  if (typeof navigator === 'undefined') {\n    return undefined;\n  }\n  return (navigator as NavigatorWithConnection).connection ?? undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Navigator extension for Network Information API\n// ---------------------------------------------------------------------------\n\ninterface NetworkInformation extends EventTarget {\n  readonly effectiveType?: string;\n  readonly type?: string;\n}\n\ninterface NavigatorWithConnection extends Navigator {\n  readonly connection?: NetworkInformation;\n}\n\n// ---------------------------------------------------------------------------\n// NetworkMonitor\n// ---------------------------------------------------------------------------\n\n/**\n * Monitors browser-level network connectivity events (online / offline)\n * and, where supported, the Network Information API for connection type changes.\n *\n * Safe for non-browser environments — all event listeners are guarded by\n * feature checks and no-op gracefully when `window` is unavailable.\n */\nexport class NetworkMonitor extends Destroyable {\n  // --- Observables (private subjects) ---\n  private readonly _isOnline$ = this.createBehaviorSubject<boolean>(\n    typeof navigator !== 'undefined' ? navigator.onLine : true\n  );\n\n  private readonly _networkChange$ = this.createSubject<NetworkChangeEvent>();\n\n  // --- Bound listeners (for removal on destroy) ---\n  private readonly _onOnline = this.handleOnline.bind(this);\n  private readonly _onOffline = this.handleOffline.bind(this);\n  private readonly _onConnectionChange = this.handleConnectionChange.bind(this);\n\n  // Track whether listeners are attached\n  private _listenersAttached = false;\n\n  constructor() {\n    super();\n    this.attachListeners();\n  }\n\n  // --- Public observable getters ---\n\n  public get isOnline$(): Observable<boolean> {\n    return this._isOnline$.asObservable().pipe(takeUntil(this._destroyed$));\n  }\n\n  public get isOnline(): boolean {\n    return this._isOnline$.value;\n  }\n\n  public get networkChange$(): Observable<NetworkChangeEvent> {\n    return this._networkChange$.asObservable().pipe(takeUntil(this._destroyed$));\n  }\n\n  // --- Lifecycle ---\n\n  public override destroy(): void {\n    this.removeListeners();\n    super.destroy();\n  }\n\n  // --- Private helpers ---\n\n  private attachListeners(): void {\n    if (!hasBrowserNetworkEvents()) {\n      logger.debug('NetworkMonitor: no browser environment detected, skipping event listeners');\n      return;\n    }\n\n    window.addEventListener('online', this._onOnline);\n    window.addEventListener('offline', this._onOffline);\n\n    const connection = getNetworkConnection();\n    if (connection) {\n      connection.addEventListener('change', this._onConnectionChange);\n    }\n\n    this._listenersAttached = true;\n    logger.debug('NetworkMonitor: event listeners attached');\n  }\n\n  private removeListeners(): void {\n    if (!this._listenersAttached) {\n      return;\n    }\n\n    if (hasBrowserNetworkEvents()) {\n      window.removeEventListener('online', this._onOnline);\n      window.removeEventListener('offline', this._onOffline);\n\n      const connection = getNetworkConnection();\n      if (connection) {\n        connection.removeEventListener('change', this._onConnectionChange);\n      }\n    }\n\n    this._listenersAttached = false;\n    logger.debug('NetworkMonitor: event listeners removed');\n  }\n\n  private handleOnline(): void {\n    logger.info('NetworkMonitor: browser went online');\n    this._isOnline$.next(true);\n    this._networkChange$.next({\n      type: 'online',\n      timestamp: Date.now(),\n      networkType: getNetworkType()\n    });\n  }\n\n  private handleOffline(): void {\n    logger.info('NetworkMonitor: browser went offline');\n    this._isOnline$.next(false);\n    this._networkChange$.next({\n      type: 'offline',\n      timestamp: Date.now()\n    });\n  }\n\n  private handleConnectionChange(): void {\n    const networkType = getNetworkType();\n    logger.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? 'unknown'}`);\n    this._networkChange$.next({\n      type: 'connection_change',\n      timestamp: Date.now(),\n      networkType\n    });\n  }\n}\n","/**\n * PlatformCapabilities - Detects browser/platform WebRTC capabilities.\n *\n * Probes the runtime environment at construction time and exposes a\n * read-only {@link PlatformCapabilities} object. Works with the\n * {@link WebRTCApiProvider} pattern so custom WebRTC implementations\n * (Citrix HDX, React Native, etc.) are supported.\n *\n * @example\n * ```typescript\n * const caps = detectPlatformCapabilities(webRTCApiProvider);\n * if (!caps.screenShare) hideScreenShareButton();\n * ```\n */\n\nimport type { PlatformCapabilities } from '../core/types/resilience.types';\nimport type { WebRTCApiProvider } from '../dependencies/interfaces';\n\n/**\n * Detects the list of supported codecs for a given media kind\n * by calling RTCRtpSender.getCapabilities when available.\n *\n * @param kind - 'audio' or 'video'\n * @returns Array of unique codec names (e.g., ['VP8', 'VP9', 'H264'])\n */\nfunction detectCodecs(kind: 'audio' | 'video'): string[] {\n  if (typeof RTCRtpSender === 'undefined' || typeof RTCRtpSender.getCapabilities !== 'function') {\n    return [];\n  }\n\n  const capabilities = RTCRtpSender.getCapabilities(kind);\n  if (!capabilities) {\n    return [];\n  }\n\n  const seen = new Set<string>();\n  const codecs: string[] = [];\n  for (const codec of capabilities.codecs) {\n    const name = extractCodecName(codec.mimeType);\n    if (name && !seen.has(name)) {\n      seen.add(name);\n      codecs.push(name);\n    }\n  }\n  return codecs;\n}\n\n/**\n * Extracts the codec name from a MIME type string (e.g., 'video/VP8' -> 'VP8').\n */\nfunction extractCodecName(mimeType: string): string {\n  const slashIndex = mimeType.indexOf('/');\n  return slashIndex >= 0 ? mimeType.substring(slashIndex + 1) : mimeType;\n}\n\n/**\n * Checks whether insertable streams / encoded transforms are available.\n *\n * Tests for both the newer RTCRtpScriptTransform (Safari 15.4+, Chrome 128+)\n * and the older MediaStreamTrackProcessor (Chrome 94+).\n */\nfunction detectInsertableStreams(): boolean {\n  if (typeof globalThis === 'undefined') {\n    return false;\n  }\n  return 'RTCRtpScriptTransform' in globalThis || 'MediaStreamTrackProcessor' in globalThis;\n}\n\n/**\n * Checks whether setSinkId (audio output device selection) is available.\n */\nfunction detectAudioOutputSelection(): boolean {\n  if (typeof HTMLMediaElement === 'undefined') {\n    return false;\n  }\n  return 'setSinkId' in HTMLMediaElement.prototype;\n}\n\n/**\n * Checks whether simulcast is supported by checking for RTCRtpSender.setParameters\n * and the presence of encoding parameters support.\n */\nfunction detectSimulcast(): boolean {\n  if (typeof RTCRtpSender === 'undefined') {\n    return false;\n  }\n  return typeof RTCRtpSender.prototype.setParameters === 'function';\n}\n\n/**\n * Detects platform WebRTC capabilities at call time.\n *\n * If a {@link WebRTCApiProvider} is supplied, its mediaDevices are used\n * for `getUserMedia` / `getDisplayMedia` detection. Otherwise the\n * standard `navigator.mediaDevices` is probed.\n *\n * @param provider - Optional custom WebRTC API provider\n * @returns A frozen {@link PlatformCapabilities} object\n */\nexport function detectPlatformCapabilities(provider?: WebRTCApiProvider): PlatformCapabilities {\n  const mediaDevices = provider?.mediaDevices ?? getNavigatorMediaDevices();\n\n  const hasGetUserMedia = typeof mediaDevices?.getUserMedia === 'function';\n  const hasGetDisplayMedia = typeof mediaDevices?.getDisplayMedia === 'function';\n\n  const hasWebRTC = provider\n    ? typeof provider.RTCPeerConnection === 'function'\n    : typeof globalThis !== 'undefined' && 'RTCPeerConnection' in globalThis;\n\n  const capabilities: PlatformCapabilities = {\n    webrtc: hasWebRTC,\n    getUserMedia: hasGetUserMedia,\n    getDisplayMedia: hasGetDisplayMedia,\n    screenShare: hasGetDisplayMedia,\n    screenShareAudio: detectScreenShareAudio(),\n    simulcast: detectSimulcast(),\n    insertableStreams: detectInsertableStreams(),\n    audioOutputSelection: detectAudioOutputSelection(),\n    videoCodecs: detectCodecs('video'),\n    audioCodecs: detectCodecs('audio')\n  };\n\n  return Object.freeze(capabilities);\n}\n\n/**\n * Detects whether screen share with system audio is supported.\n *\n * This is primarily a Chromium feature. We detect it by checking\n * whether getDisplayMedia exists and the browser is Chromium-based.\n */\nfunction detectScreenShareAudio(): boolean {\n  if (typeof navigator === 'undefined') {\n    return false;\n  }\n  const mediaDevices = getNavigatorMediaDevices();\n  if (!mediaDevices || typeof mediaDevices.getDisplayMedia !== 'function') {\n    return false;\n  }\n\n  // All Chromium-based browsers support system audio in screen share.\n  // Modern Chromium Edge uses \"Edg/\" (not \"Edge/\"), and old EdgeHTML Edge\n  // (which used \"Edge/\") is effectively dead. Chrome/ is sufficient.\n  const ua = navigator.userAgent;\n  return ua.includes('Chrome/');\n}\n\n/**\n * Safely retrieves navigator.mediaDevices, returning null if unavailable.\n */\nfunction getNavigatorMediaDevices(): MediaDevices | null {\n  if (typeof navigator !== 'undefined') {\n    return navigator.mediaDevices;\n  }\n  return null;\n}\n","import { filter, firstValueFrom, take, timeout } from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { PreflightError } from '../core/errors';\nimport { getLogger } from '../utils/logger';\n\nimport type { Call } from '../core/entities/types/call.types';\nimport type { PreflightOptions, PreflightResult } from '../core/types/resilience.types';\nimport type { DeviceController } from '../interfaces/DeviceController';\n\nconst logger = getLogger();\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_MEDIA_TEST_DURATION_S = 10;\nconst ICE_GATHERING_TIMEOUT_MS = 10_000;\nconst SIGNALING_RTT_TIMEOUT_MS = 5_000;\nconst DEVICE_TEST_TIMEOUT_MS = 5_000;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ntype DialFn = (destination: string, options?: Record<string, unknown>) => Promise<Call>;\n\ninterface IceTestResult {\n  type: 'direct' | 'relay' | 'failed';\n  turnReachable: boolean;\n  stunReachable: boolean;\n  rttMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// PreflightRunner\n// ---------------------------------------------------------------------------\n\n/**\n * Runs a multi-phase connectivity test:\n *   1. Signaling -- verify WebSocket is connected, measure RTT\n *   2. Devices -- verify getUserMedia works with selected devices\n *   3. ICE/TURN -- gather ICE candidates and check reachability\n *   4. Media/bandwidth -- dial, collect getStats(), compute bandwidth\n *\n * Extends Destroyable so all resources are cleaned up when done.\n */\nexport class PreflightRunner extends Destroyable {\n  private readonly _options: Required<Pick<PreflightOptions, 'duration' | 'skipMediaTest'>> &\n    PreflightOptions;\n\n  constructor(\n    private readonly deviceController: DeviceController,\n    private readonly iceServers: RTCIceServer[],\n    private readonly isConnected: boolean,\n    private readonly transportRttMs: number,\n    private readonly dialFn: DialFn,\n    options: PreflightOptions = {}\n  ) {\n    super();\n    this._options = {\n      duration: options.duration ?? DEFAULT_MEDIA_TEST_DURATION_S,\n      skipMediaTest: options.skipMediaTest ?? false,\n      audioDevice: options.audioDevice,\n      videoDevice: options.videoDevice\n    };\n  }\n\n  /**\n   * Execute the full preflight test and return the result.\n   * Always cleans up resources on completion or error.\n   */\n  public async run(destination: string): Promise<PreflightResult> {\n    const warnings: string[] = [];\n\n    try {\n      // Phase 1: Signaling test\n      const signaling = this.testSignaling();\n      if (!signaling.reachable) {\n        warnings.push('WebSocket not connected');\n      }\n\n      // Phase 2: Device test\n      const devices = await this.testDevices();\n      if (!devices.audioInput.working) {\n        warnings.push('Audio input device not working');\n      }\n      if (!devices.videoInput.working) {\n        warnings.push('Video input device not working');\n      }\n\n      // Phase 3: ICE/TURN test\n      const connectivity = await this.testIceConnectivity();\n      if (connectivity.type === 'failed') {\n        warnings.push('No ICE connectivity (neither STUN nor TURN reachable)');\n      } else if (connectivity.type === 'relay') {\n        warnings.push('Only TURN relay connectivity available (no direct path)');\n      }\n      if (!connectivity.turnReachable) {\n        warnings.push('TURN servers not reachable');\n      }\n\n      // Phase 4: Media/bandwidth test (optional)\n      let bandwidth: PreflightResult['bandwidth'] = null;\n      if (!this._options.skipMediaTest) {\n        try {\n          bandwidth = await this.testMediaBandwidth(destination);\n        } catch (error) {\n          logger.warn('[PreflightRunner] Media bandwidth test failed:', error);\n          warnings.push('Media bandwidth test failed');\n        }\n      }\n\n      const ok =\n        signaling.reachable && connectivity.type !== 'failed' && devices.audioInput.working;\n\n      return {\n        ok,\n        signaling,\n        connectivity,\n        bandwidth,\n        devices,\n        warnings\n      };\n    } catch (error) {\n      logger.error('[PreflightRunner] Preflight test failed:', error);\n      throw new PreflightError(\n        'preflight',\n        error instanceof Error ? error : new Error(String(error))\n      );\n    } finally {\n      this.destroy();\n    }\n  }\n\n  // -------------------------------------------------------------------------\n  // Phase 1: Signaling test\n  // -------------------------------------------------------------------------\n\n  private testSignaling(): PreflightResult['signaling'] {\n    return {\n      reachable: this.isConnected,\n      rttMs: this.transportRttMs\n    };\n  }\n\n  // -------------------------------------------------------------------------\n  // Phase 2: Device test\n  // -------------------------------------------------------------------------\n\n  private async testDevices(): Promise<PreflightResult['devices']> {\n    const audioDevice = this._options.audioDevice ?? this.deviceController.selectedAudioInputDevice;\n    const videoDevice = this._options.videoDevice ?? this.deviceController.selectedVideoInputDevice;\n    const audioOutputDevice = this.deviceController.selectedAudioOutputDevice;\n\n    let audioWorking = false;\n    let videoWorking = false;\n    let audioStream: MediaStream | undefined;\n\n    try {\n      const constraints: MediaStreamConstraints = {};\n      if (audioDevice) {\n        constraints.audio = { deviceId: { exact: audioDevice.deviceId } };\n      } else {\n        constraints.audio = true;\n      }\n      if (videoDevice) {\n        constraints.video = { deviceId: { exact: videoDevice.deviceId } };\n      } else {\n        constraints.video = true;\n      }\n\n      audioStream = await Promise.race([\n        navigator.mediaDevices.getUserMedia(constraints),\n        new Promise<MediaStream>((_, reject) =>\n          setTimeout(() => reject(new Error('getUserMedia timeout')), DEVICE_TEST_TIMEOUT_MS)\n        )\n      ]);\n\n      for (const track of audioStream.getTracks()) {\n        if (track.kind === 'audio' && track.readyState === 'live') {\n          audioWorking = true;\n        }\n        if (track.kind === 'video' && track.readyState === 'live') {\n          videoWorking = true;\n        }\n      }\n    } catch (error) {\n      logger.warn('[PreflightRunner] Device test failed:', error);\n    } finally {\n      // Release tracks\n      if (audioStream) {\n        audioStream.getTracks().forEach((t) => t.stop());\n      }\n    }\n\n    return {\n      audioInput: { working: audioWorking, device: audioDevice },\n      videoInput: { working: videoWorking, device: videoDevice },\n      audioOutput: { available: !!audioOutputDevice, device: audioOutputDevice }\n    };\n  }\n\n  // -------------------------------------------------------------------------\n  // Phase 3: ICE/TURN connectivity test\n  // -------------------------------------------------------------------------\n\n  private async testIceConnectivity(): Promise<IceTestResult> {\n    let pc: RTCPeerConnection | undefined;\n    try {\n      pc = new RTCPeerConnection({ iceServers: this.iceServers });\n      const peerConnection = pc;\n\n      const candidateTypes = new Set<string>();\n      const startTime = Date.now();\n\n      const gatheringComplete = new Promise<void>((resolve) => {\n        const timer = setTimeout(resolve, ICE_GATHERING_TIMEOUT_MS);\n\n        peerConnection.onicecandidate = (event) => {\n          if (event.candidate) {\n            const candidateStr = event.candidate.candidate;\n            if (candidateStr.includes('typ host')) candidateTypes.add('host');\n            if (candidateStr.includes('typ srflx')) candidateTypes.add('srflx');\n            if (candidateStr.includes('typ relay')) candidateTypes.add('relay');\n          } else {\n            clearTimeout(timer);\n            resolve();\n          }\n        };\n      });\n\n      // Create a dummy data channel to trigger ICE gathering\n      pc.createDataChannel('preflight-test');\n      const offer = await pc.createOffer();\n      await pc.setLocalDescription(offer);\n\n      await gatheringComplete;\n      const rttMs = Date.now() - startTime;\n\n      const stunReachable = candidateTypes.has('srflx');\n      const turnReachable = candidateTypes.has('relay');\n      const hasHost = candidateTypes.has('host');\n\n      let type: IceTestResult['type'] = 'failed';\n      if (hasHost || stunReachable) {\n        type = 'direct';\n      } else if (turnReachable) {\n        type = 'relay';\n      }\n\n      return { type, turnReachable, stunReachable, rttMs };\n    } catch (error) {\n      logger.warn('[PreflightRunner] ICE connectivity test failed:', error);\n      return { type: 'failed', turnReachable: false, stunReachable: false, rttMs: 0 };\n    } finally {\n      if (pc) {\n        pc.close();\n      }\n    }\n  }\n\n  // -------------------------------------------------------------------------\n  // Phase 4: Media/bandwidth test\n  // -------------------------------------------------------------------------\n\n  private async testMediaBandwidth(\n    destination: string\n  ): Promise<{ uploadKbps: number; downloadKbps: number }> {\n    let call: Call | undefined;\n    try {\n      call = await this.dialFn(destination, {\n        audio: true,\n        video: false\n      });\n\n      // Wait for call to connect\n      await firstValueFrom(\n        call.status$.pipe(\n          filter((s) => s === 'connected'),\n          take(1),\n          timeout(SIGNALING_RTT_TIMEOUT_MS)\n        )\n      );\n\n      // Collect stats for the test duration\n      const durationMs = this._options.duration * 1000;\n      await new Promise((resolve) => setTimeout(resolve, durationMs));\n\n      // Read final metrics\n      const metrics = call.networkMetrics;\n      let uploadKbps = 0;\n      let downloadKbps = 0;\n\n      if (metrics.length > 0) {\n        const latest = metrics[metrics.length - 1];\n        if (latest.availableOutgoingBitrate !== undefined) {\n          uploadKbps = Math.round(latest.availableOutgoingBitrate / 1000);\n        }\n        // Estimate download from total packets received (rough approximation)\n        const totalPackets = latest.audio.packetsReceived + latest.video.packetsReceived;\n        if (durationMs > 0 && totalPackets > 0) {\n          // Use upload as rough proxy for download when per-track bytes unavailable\n          downloadKbps = uploadKbps;\n        }\n      }\n\n      return { uploadKbps, downloadKbps };\n    } finally {\n      // Always hang up the test call\n      if (call) {\n        try {\n          await call.hangup();\n        } catch {\n          // Best-effort cleanup\n        }\n      }\n    }\n  }\n\n  public override destroy(): void {\n    super.destroy();\n  }\n}\n","import { takeUntil } from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { getLogger } from '../utils/logger';\n\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\n/**\n * Visibility state of the document tab.\n */\nexport type VisibilityState = 'visible' | 'hidden';\n\n/**\n * Event emitted when the document visibility changes.\n */\nexport interface VisibilityChangeEvent {\n  readonly from: VisibilityState;\n  readonly to: VisibilityState;\n  readonly timestamp: number;\n}\n\n/**\n * Checks whether the document visibility API is available.\n */\nfunction isVisibilityApiAvailable(): boolean {\n  try {\n    return (\n      typeof document !== 'undefined' &&\n      typeof document.addEventListener === 'function' &&\n      typeof document.visibilityState === 'string'\n    );\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Returns the current document visibility state, defaulting to 'visible'\n * in non-browser environments.\n */\nfunction getCurrentVisibility(): VisibilityState {\n  try {\n    if (typeof document !== 'undefined' && typeof document.visibilityState === 'string') {\n      return document.visibilityState === 'visible' ? 'visible' : 'hidden';\n    }\n  } catch {\n    // Non-browser environment\n  }\n  return 'visible';\n}\n\n/**\n * VisibilityController tracks browser tab visibility state and exposes\n * it as RxJS observables. Safe for use in non-browser environments\n * where the document API is not available.\n *\n * Extends Destroyable for lifecycle management and automatic cleanup.\n */\nexport class VisibilityController extends Destroyable {\n  private readonly _visibility$ =\n    this.createBehaviorSubject<VisibilityState>(getCurrentVisibility());\n\n  private readonly _visibilityChange$ = this.createSubject<VisibilityChangeEvent>();\n\n  private readonly _boundHandler: () => void;\n  private readonly _hasVisibilityApi: boolean;\n\n  constructor() {\n    super();\n\n    this._hasVisibilityApi = isVisibilityApiAvailable();\n    this._boundHandler = this._handleVisibilityChange.bind(this);\n\n    if (this._hasVisibilityApi) {\n      document.addEventListener('visibilitychange', this._boundHandler);\n      logger.debug('VisibilityController: listening for visibilitychange events');\n    } else {\n      logger.debug(\n        'VisibilityController: document visibility API not available, defaulting to visible'\n      );\n    }\n  }\n\n  /**\n   * Observable of the current visibility state.\n   * Emits 'visible' or 'hidden'. Always starts with the current state.\n   */\n  public get visibility$(): Observable<VisibilityState> {\n    return this._visibility$.pipe(takeUntil(this._destroyed$));\n  }\n\n  /**\n   * The current visibility state value.\n   */\n  public get visibility(): VisibilityState {\n    return this._visibility$.value;\n  }\n\n  /**\n   * Observable that emits transition events when visibility changes.\n   * Each event includes the previous state, new state, and timestamp.\n   */\n  public get visibilityChange$(): Observable<VisibilityChangeEvent> {\n    return this._visibilityChange$.pipe(takeUntil(this._destroyed$));\n  }\n\n  public override destroy(): void {\n    if (this._hasVisibilityApi) {\n      document.removeEventListener('visibilitychange', this._boundHandler);\n      logger.debug('VisibilityController: removed visibilitychange listener');\n    }\n    super.destroy();\n  }\n  /**\n   * Handle the browser's visibilitychange event.\n   */\n  private _handleVisibilityChange(): void {\n    const newState = getCurrentVisibility();\n    const previousState = this._visibility$.value;\n\n    if (newState === previousState) {\n      return;\n    }\n\n    this._visibility$.next(newState);\n\n    const changeEvent: VisibilityChangeEvent = {\n      from: previousState,\n      to: newState,\n      timestamp: Date.now()\n    };\n\n    this._visibilityChange$.next(changeEvent);\n\n    logger.debug('VisibilityController: visibility changed', {\n      from: previousState,\n      to: newState\n    });\n  }\n}\n","import { defer, from, shareReplay, takeUntil } from 'rxjs';\n\nimport { Destroyable } from './Destroyable';\n\nimport type { HTTPRequestController } from '../controllers/HTTPRequestController';\nimport type { Observable } from 'rxjs';\n\nexport abstract class Fetchable<T = unknown> extends Destroyable {\n  public fetched$: Observable<boolean>;\n\n  constructor(\n    public fromPath: string,\n    protected http: HTTPRequestController\n  ) {\n    super();\n    this.fetched$ = defer(() => from(this.fetch())).pipe(\n      shareReplay(1),\n      takeUntil(this.destroyed$)\n    );\n  }\n\n  protected abstract populateInstance(data: T): void;\n\n  private async fetch(): Promise<boolean> {\n    const response = await this.http.request({\n      url: this.fromPath,\n      method: 'GET',\n      headers: {\n        Accept: 'application/json'\n      }\n    });\n    if (response.ok && response.body) {\n      const data = JSON.parse(response.body) as T;\n      this.populateInstance(data);\n      return true;\n    }\n    return false;\n  }\n}\n","import { Fetchable } from '../../behaviors/Fetchable';\n\nimport type { HTTPRequestController } from '../../controllers/HTTPRequestController';\nimport type { GetAddressResponse } from '../types/address.types';\nimport type { SATClaims } from '../types/crypto.types';\nimport type { GetUserInfoResponse } from '../types/user.types';\n\n/** User online presence state. */\nexport type UserPresence = 'online' | 'offline' | 'busy';\n\n/**\n * Authenticated user profile.\n *\n * Fetched automatically when a {@link SignalWire} connects.\n * Contains identity, contact, and organization details.\n */\nexport class User extends Fetchable<GetUserInfoResponse> {\n  /** Unique user identifier. */\n  public id!: string;\n  /** User email address. */\n  public email!: string;\n  /** First name. */\n  public firstName?: string;\n  /** Last name. */\n  public lastName?: string;\n  /** Display name shown to other participants. */\n  public displayName?: string;\n  /** Job title. */\n  public jobTitle?: string;\n  /** Time zone offset. */\n  public timeZone?: number;\n  /** Country code. */\n  public country?: string;\n  /** Region/state. */\n  public region?: string;\n  /** Company name. */\n  public companyName?: string;\n  /** Push notification key for mobile/web push. */\n  public pushNotificationKey!: string;\n  /** Application-level settings (display name, permission scopes). */\n  public appSettings?: {\n    displayName: string;\n    scopes: string[];\n  };\n  /** Fabric addresses associated with this user. */\n  public addresses!: GetAddressResponse[];\n  /** Filtered SAT claims when the token has special capabilities (e.g., refresh scope). */\n  public satClaims?: SATClaims;\n\n  constructor(http: HTTPRequestController) {\n    super('/api/fabric/subscriber/info', http);\n  }\n\n  protected populateInstance(data: GetUserInfoResponse): void {\n    this.id = data.id;\n    this.email = data.email;\n    this.firstName = data.first_name;\n    this.lastName = data.last_name;\n    this.displayName = data.display_name;\n    this.jobTitle = data.job_title;\n    this.timeZone = data.time_zone;\n    this.country = data.country;\n    this.region = data.region;\n    this.companyName = data.company_name;\n    this.pushNotificationKey = data.push_notification_key;\n    this.appSettings = data.app_settings\n      ? {\n          displayName: data.app_settings.display_name,\n          scopes: data.app_settings.scopes\n        }\n      : undefined;\n    this.addresses = data.fabric_addresses;\n    this.satClaims = data.sat_claims;\n  }\n}\n","import { v4 as uuid } from 'uuid';\n\nimport type { JSONRPCRequest, JSONRPCSuccessResponse } from './types/base';\n\ninterface MakeRPCRequestParams<\n  T extends string = 'execute',\n  P extends object = Record<string, unknown>\n> {\n  id?: string;\n  method: T;\n  params: P;\n}\nexport const buildRPCRequest = <\n  T extends string = 'execute',\n  P extends object = Record<string, unknown>\n>(\n  params: MakeRPCRequestParams<T, P>\n): JSONRPCRequest<P> & { method: T; params: P } => {\n  return {\n    jsonrpc: '2.0' as const,\n    id: params.id ?? uuid(),\n    ...params\n  };\n};\n\ninterface MakeRPCResponseParams<TResult = unknown> {\n  id: string;\n  result: TResult;\n}\nexport const makeRPCResponse = <TResult = unknown>(\n  params: MakeRPCResponseParams<TResult>\n): JSONRPCSuccessResponse<TResult> => {\n  return {\n    jsonrpc: '2.0' as const,\n    ...params\n  };\n};\n","import { buildRPCRequest } from './helpers';\n\nimport type { JSONRPCRequest } from './types/base';\n\ninterface WithToken {\n  token: string;\n  jwt_token?: never;\n}\ninterface WithJWT {\n  token?: never;\n  jwt_token: string;\n}\nexport type RPCConnectAuthentication = { project?: string } & (WithToken | WithJWT);\n\n/**\n * Parameters for `signalwire.connect`.\n *\n * **Fresh connect**: `authentication: { jwt_token }` + `dpop_token` (when client-bound).\n * **Reconnect**: `authentication: { jwt_token: \"<stored SAT, even if expired>\" }` +\n *   `authorization_state` + `protocol` + `dpop_token`.\n *   The jwt_token is required for session classification even when expired;\n *   authorization_state short-circuits actual token validation.\n */\nexport interface RPCConnectParams {\n  authentication: RPCConnectAuthentication;\n  version?: typeof DEFAULT_CONNECT_VERSION;\n  agent?: string;\n  /** DPoP proof JWT for Client Bound SAT verification. */\n  dpop_token?: string;\n  /** Encrypted state from a prior session — reconnection optimization. */\n  authorization_state?: string;\n  /** Required when authorization_state is provided. */\n  protocol?: string;\n  contexts?: string[];\n  topics?: string[];\n  eventing?: string[];\n  event_acks?: boolean;\n}\n\nexport interface Authorization {\n  jti: string;\n  project_id: string;\n  data_zone?: string;\n  scope?: string[];\n  fabric_subscriber: {\n    version: number;\n    expires_at: number;\n    subscriber_id: string;\n    application_id: string | null;\n    project_id: string;\n    space_id: string;\n  };\n  cnf?: { jkt: string };\n}\n\nexport interface RPCConnectResult {\n  identity: string;\n  authorization: Authorization;\n  protocol: string;\n\n  ice_servers?: RTCIceServer[];\n}\n\nexport interface RPCConnectRequest extends JSONRPCRequest<RPCConnectParams> {\n  method: 'signalwire.connect';\n  params: RPCConnectParams;\n}\n\nexport const DEFAULT_CONNECT_VERSION = {\n  major: 4,\n  minor: 0,\n  revision: 0\n};\n\nexport const RPCConnect = (params: RPCConnectParams): RPCConnectRequest => {\n  return buildRPCRequest({\n    method: 'signalwire.connect',\n    params: {\n      version: DEFAULT_CONNECT_VERSION,\n\n      event_acks: true,\n      ...params\n    }\n  });\n};\n","import { buildRPCRequest } from './helpers';\n\nimport type { JSONRPCRequest } from './types/base';\n\nexport interface RPCReauthenticateParams {\n  project: string;\n\n  jwt_token: string;\n\n  /** DPoP proof JWT for Client Bound SAT verification. */\n  dpop_token?: string;\n}\n\n/** Wire shape of a `signalwire.reauthenticate` request — auth is nested. */\nexport interface RPCReauthenticateRequestParams {\n  authentication: {\n    project: string;\n    jwt_token: string;\n  };\n  /** DPoP proof JWT for Client Bound SAT verification. */\n  dpop_token?: string;\n}\n\nexport interface RPCReauthenticateRequest extends JSONRPCRequest<RPCReauthenticateRequestParams> {\n  method: 'signalwire.reauthenticate';\n  params: RPCReauthenticateRequestParams;\n}\n\nexport const RPCReauthenticate = (params: RPCReauthenticateParams): RPCReauthenticateRequest => {\n  const { dpop_token, ...authFields } = params;\n  return buildRPCRequest({\n    method: 'signalwire.reauthenticate',\n    params: {\n      authentication: authFields,\n      ...(dpop_token ? { dpop_token } : {})\n    }\n  });\n};\n","import { buildRPCRequest, makeRPCResponse } from './helpers';\n\nimport type { JSONRPCSuccessResponse } from './types/base';\nimport type { SignalwirePingRequest } from './types/events';\nimport type { SignalwirePingResult } from './types/methods';\n\nexport const RPCPing = (): SignalwirePingRequest => {\n  return buildRPCRequest({\n    method: 'signalwire.ping',\n    params: {\n      timestamp: Date.now() / 1000\n    }\n  });\n};\n\nexport const RPCPingResponse = (\n  id: string,\n  timestamp?: number\n): JSONRPCSuccessResponse<SignalwirePingResult> => {\n  return makeRPCResponse<SignalwirePingResult>({\n    id,\n    result: {\n      timestamp: timestamp ?? Date.now() / 1000\n    }\n  });\n};\n","import { buildRPCRequest } from './helpers';\n\nimport type { JSONRPCRequest, JSONRPCMethod } from './types/base';\n\ninterface RPCExecuteParams {\n  id?: string;\n  method: JSONRPCMethod;\n  params: Record<string, unknown>;\n}\n\nexport const RPCExecute = ({ method, params }: RPCExecuteParams): JSONRPCRequest => {\n  return buildRPCRequest({\n    method,\n    params\n  });\n};\n","import { buildRPCRequest, makeRPCResponse } from './helpers';\n\nimport type { VertoMethod } from '../types/rpc.types';\nimport type { JSONRPCSuccessResponse } from './types/base';\nimport type { WebrtcVertoParams, WebrtcVertoRequest } from './types/methods';\nimport type { VertoByeCause } from './types/verto';\n\ntype VertoParams = Record<string, unknown>;\n\nconst SDK_TO_VERTO_FIELD_MAP: Record<string, string> = {\n  id: 'callID',\n  destinationNumber: 'destination_number',\n  remoteCallerName: 'remote_caller_id_name',\n  remoteCallerNumber: 'remote_caller_id_number',\n  callerName: 'caller_id_name',\n  callerNumber: 'caller_id_number',\n  fromCallAddressId: 'from_fabric_address_id'\n};\n\nconst EXCLUDED_DIALOG_PARAMS = new Set(['remoteSdp', 'localStream', 'remoteStream']);\n\n/**\n * Translate SDK fields into verto variables.\n * Returns a new object — the input is never mutated.\n */\n/** @internal Exported for testing only. */\nexport const filterVertoParams = (params: VertoParams): VertoParams => {\n  if (!Object.prototype.hasOwnProperty.call(params, 'dialogParams')) {\n    return params;\n  }\n\n  const sourceDialogParams = params.dialogParams as Record<string, unknown>;\n\n  const filteredDialogParams = Object.entries(sourceDialogParams).reduce<Record<string, unknown>>(\n    (acc, [key, value]) => {\n      if (EXCLUDED_DIALOG_PARAMS.has(key)) {\n        return acc;\n      }\n      const mappedKey = SDK_TO_VERTO_FIELD_MAP[key] ?? key;\n      return { ...acc, [mappedKey]: value };\n    },\n    {}\n  );\n\n  return {\n    ...params,\n    dialogParams: filteredDialogParams\n  };\n};\n\nconst buildVertoRPCMessage = (method: VertoMethod) => {\n  return (params: VertoParams = {}) => {\n    return buildRPCRequest({\n      method,\n      params: filterVertoParams(params)\n    });\n  };\n};\n\nexport type VertoRPCMessage = ReturnType<ReturnType<typeof buildVertoRPCMessage>>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type JSONRPCParams = Record<string, any>;\n\nexport const WebrtcVerto = (params: WebrtcVertoParams): WebrtcVertoRequest => {\n  return buildRPCRequest({\n    method: 'webrtc.verto',\n    params\n  });\n};\n\nexport type WebrtcVertoRPCMessage = ReturnType<ReturnType<typeof buildVertoRPCMessage>>;\n\nexport const VertoInvite = buildVertoRPCMessage('verto.invite');\nexport const VertoBye = buildVertoRPCMessage('verto.bye');\nexport const VertoAttach = buildVertoRPCMessage('verto.attach');\nexport const VertoModify = buildVertoRPCMessage('verto.modify');\nexport const VertoInfo = buildVertoRPCMessage('verto.info');\nexport const VertoAnswer = buildVertoRPCMessage('verto.answer');\nexport const VertoSubscribe = buildVertoRPCMessage('verto.subscribe');\nexport const VertoPong = buildVertoRPCMessage('verto.pong');\nexport const VertoResult = (id: string, method: VertoMethod): JSONRPCSuccessResponse => {\n  return makeRPCResponse({\n    id,\n    result: {\n      method\n    }\n  });\n};\n\nexport interface VertoModifyResponse {\n  action: string;\n  callID: string;\n  holdState: 'held' | 'active';\n\n  node_id?: string;\n  sdp?: string;\n}\n\nexport const VertoByeCauseCodes: Record<VertoByeCause, string> = {\n  NORMAL_CLEARING: '16',\n\n  USER_BUSY: '17',\n\n  MEDIA_TIMEOUT: '804'\n} as const;\n","import { makeRPCResponse } from './helpers';\n\nimport type { JSONRPCSuccessResponse } from './types/base';\n\nexport const RPCEventAckResponse = (id: string): JSONRPCSuccessResponse =>\n  makeRPCResponse({ id, result: {} });\n","import {\n  RPC_ERROR_AUTHENTICATION_FAILED,\n  RPC_ERROR_REQUESTER_VALIDATION_FAILED\n} from '../core/constants';\nimport { JSONRPCError } from '../core/errors';\n\n/**\n * Walk an error's `error`/`cause` chain looking for a {@link JSONRPCError}.\n * Errors thrown by call creation are wrapped (e.g. `CallCreateError`), so the\n * underlying signaling error is nested. Bounded by a visited set to guard\n * against cyclic causes.\n */\nexport function findJSONRPCError(error: unknown): JSONRPCError | undefined {\n  const seen = new Set<unknown>();\n  let current: unknown = error;\n  while (current instanceof Error && !seen.has(current)) {\n    seen.add(current);\n    if (current instanceof JSONRPCError) {\n      return current;\n    }\n    current = (current as { error?: unknown }).error ?? current.cause;\n  }\n  return undefined;\n}\n\n/**\n * Whether an error is a session-recoverable authentication failure\n * (`-32002` authentication failed or `-32003` requester validation failed)\n * that a credential re-mint + retry can heal.\n */\nexport function isRecoverableAuthError(error: unknown): boolean {\n  const rpcError = findJSONRPCError(error);\n  return (\n    rpcError !== undefined &&\n    (rpcError.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED ||\n      rpcError.code === RPC_ERROR_AUTHENTICATION_FAILED)\n  );\n}\n\n/**\n * Whether an error is specifically a requester-validation rejection\n * (`-32003`) — the server refusing the session's credential.\n *\n * Narrower than {@link isRecoverableAuthError} on purpose. `-32002` is\n * overloaded server-side: a rejected reattach arrives as `-32002` with\n * `cause: INVALID_MSG_UNSPECIFIED` and message `CALL ERROR`, which is a\n * call-level rejection and says nothing about the credential. Use this where\n * the decision must not be fooled by that, such as deciding whether retrying\n * an operation could possibly succeed.\n */\nexport function isRequesterValidationError(error: unknown): boolean {\n  return findJSONRPCError(error)?.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED;\n}\n","import { isRequesterValidationError } from '../utils/authRecovery';\nimport { getLogger } from '../utils/logger';\n\nimport type { StorageManager } from './StorageManager';\nimport type { Address } from '../core/entities/Address';\nimport type { Call, CallOptions } from '../core/entities/types/call.types';\nimport type { MediaDirections } from '../core/types/media.types';\nimport type { DeviceController } from '../interfaces/DeviceController';\n\nconst logger = getLogger();\ninterface AttachableCall {\n  id: string;\n  to?: string;\n  mediaDirections: MediaDirections;\n  nodeId?: string;\n}\n\nexport interface OutboundCallProvider {\n  createOutboundCall(destination: string | Address, options?: CallOptions): Promise<Call>;\n}\n\ninterface Attachment {\n  destination: string;\n  mediaDirections: MediaDirections;\n  audioInputDevice: MediaDeviceInfo | null;\n  videoInputDevice: MediaDeviceInfo | null;\n  nodeId?: string;\n  attachedAt: number;\n}\n\nexport class AttachManager {\n  private session!: OutboundCallProvider;\n  // Serializes read-modify-write sequences against storage. Each mutation\n  // chains onto this promise so concurrent attach()/detach() calls can't\n  // interleave reads and clobber each other's writes.\n  private writeQueue: Promise<void> = Promise.resolve();\n\n  constructor(\n    private readonly storage: StorageManager,\n\n    private readonly deviceController: DeviceController,\n    private readonly reconnectCallsTimeout: number,\n    private attachKey: string,\n    /**\n     * Whether a credential recovery has been verified on this client — the\n     * session reauthenticated with a fresh token AND the operation that\n     * reauthentication was meant to unblock then succeeded. Gates attach-record\n     * discard together with the failure kind: a record is dropped only when\n     * this is true AND the reattach refusal was NOT a credential refusal\n     * (-32003). See {@link reattachCalls}.\n     */\n    private readonly credentialRecovered: () => boolean\n  ) {}\n\n  async detachAll(): Promise<void> {\n    await this.mutate((attached) => {\n      // Return an empty record — one atomic write replaces the full map.\n      void attached;\n      return {};\n    });\n  }\n\n  public setSession(session: OutboundCallProvider): void {\n    this.session = session;\n  }\n\n  private async readAttached(): Promise<Record<string, Attachment>> {\n    try {\n      return (await this.storage.getItem(this.attachKey)) ?? {};\n    } catch (error) {\n      logger.warn('[AttachManager] Failed to retrieve attached calls from storage', error);\n      return {};\n    }\n  }\n\n  private async writeAttached(attached: Record<string, Attachment>): Promise<void> {\n    try {\n      await this.storage.setItem(this.attachKey, attached);\n    } catch (error) {\n      logger.warn('[AttachManager] Failed to write attached calls to storage', error);\n    }\n  }\n\n  /**\n   * Serialize a read-modify-write operation against the attached-calls\n   * storage. The mutator receives the current state and returns the new\n   * state. Concurrent calls queue behind the in-flight one so writes never\n   * interleave.\n   */\n  private async mutate(\n    mutator: (\n      current: Record<string, Attachment>\n    ) => Record<string, Attachment> | Promise<Record<string, Attachment>>\n  ): Promise<void> {\n    const next = this.writeQueue.then(async () => {\n      const current = await this.readAttached();\n      const updated = await mutator(current);\n      await this.writeAttached(updated);\n    });\n    // Swallow rejection on the queue itself so one failed mutation doesn't\n    // poison all future ones — individual awaiters still see their rejection.\n    this.writeQueue = next.catch(() => undefined);\n    return next;\n  }\n\n  public async attach(call: AttachableCall): Promise<void> {\n    if (!call.to) {\n      logger.warn('[AttachManager] Skip attach for calls with no destination');\n      return;\n    }\n    const attachment = this.buildAttachment(call, call.to);\n    await this.mutate((attached) => ({ ...attached, [call.id]: attachment }));\n  }\n\n  /**\n   * Keep an already-stored call's reference alive and current — the periodic\n   * refresh the `verto.ping` keepalive drives.\n   *\n   * Only ever updates: a call with no record is one nothing wants reattached,\n   * and re-creating it here would undo a `detach`. That matters because a ping\n   * can land in the window between `bye()` detaching and the call being torn\n   * down, and a record revived there survives the hangup — so the next page\n   * load dials a call nobody is on. The existence check and the write share\n   * one {@link mutate} turn, so a concurrent detach cannot slip between them.\n   */\n  public async refresh(call: AttachableCall): Promise<void> {\n    if (!call.to) {\n      return;\n    }\n    const destination = call.to;\n    await this.mutate((attached) => {\n      // Object.hasOwn — see consumePendingAttachment on why a truthy check\n      // on the value would not type-check here.\n      if (!Object.hasOwn(attached, call.id)) {\n        return attached;\n      }\n      return { ...attached, [call.id]: this.buildAttachment(call, destination) };\n    });\n  }\n\n  private buildAttachment(call: AttachableCall, destination: string): Attachment {\n    return {\n      nodeId: call.nodeId,\n      destination,\n      mediaDirections: call.mediaDirections,\n      audioInputDevice:\n        call.mediaDirections.audio !== 'inactive'\n          ? this.deviceController.selectedAudioInputDevice\n          : null,\n      videoInputDevice:\n        call.mediaDirections.video !== 'inactive'\n          ? this.deviceController.selectedVideoInputDevice\n          : null,\n      attachedAt: Date.now()\n    };\n  }\n\n  public async detach(call: AttachableCall): Promise<void> {\n    await this.mutate((attached) => {\n      const { [call.id]: _, ...remaining } = attached;\n      return remaining;\n    });\n  }\n\n  public async flush(): Promise<void> {\n    await this.mutate(() => ({}));\n  }\n\n  /**\n   * Reattach to previously active calls by sending verto.invite with\n   * reattaching: true.\n   *\n   * NOTE: This currently fails with INVALID_CALL_REFERENCE because the\n   * server's jsock UUID check rejects the new connection's UUID. A\n   * server-side fix is needed: when reattaching: true is explicitly set\n   * in dialogParams, FreeSWITCH's attempt_reattach() should update the\n   * call's jsock reference to the new connection's UUID instead of\n   * rejecting. Once that fix is deployed, this will work for both\n   * page reloads and WebSocket reconnects.\n   *\n   * A failed reattach does NOT generally cost the stored reference. It is\n   * discarded only when the server denied the reattach on a session whose\n   * credential it had already accepted — a verified reauthentication followed\n   * by a refusal is the server saying the call is gone, and that is the one\n   * refusal worth acting on. Until then the credential may be what is being\n   * refused, and the record is the only way a later reload can try again;\n   * keeping it costs nothing, since `detachExpired` reaps it once it is older\n   * than `reconnectCallsTimeout`.\n   */\n  public async reattachCalls(): Promise<void> {\n    const attached = await this.readAttached();\n\n    await this.detachExpired();\n\n    for (const [callId, attachment] of Object.entries(attached)) {\n      const { destination } = attachment;\n      const options = this.buildCallOptions(attachment);\n\n      let succeeded = false;\n      let refusedOnCredentials = false;\n      for (let attempt = 1; attempt <= 3; attempt++) {\n        try {\n          await this.session.createOutboundCall(destination, { callId, ...options });\n          logger.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);\n          succeeded = true;\n          break;\n        } catch (error) {\n          logger.warn(\n            `[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`,\n            error\n          );\n          if (isRequesterValidationError(error)) {\n            // The session credential is what the server refused, so every\n            // attempt gets the same answer. Healing it belongs to the\n            // credential path; spending the remaining attempts and their\n            // backoff here only delays the failure. Deliberately narrower than\n            // isRecoverableAuthError: -32002 is overloaded server-side for\n            // call-level rejections (CALL ERROR / INVALID_MSG_UNSPECIFIED),\n            // which say nothing about the credential.\n            refusedOnCredentials = true;\n            logger.warn(\n              `[AttachManager] Reattach of ${callId} was refused on credentials; not retrying.`\n            );\n            break;\n          }\n          if (attempt < 3) {\n            await new Promise((r) => setTimeout(r, (attempt + 1) * 1000));\n          }\n        }\n      }\n\n      if (!succeeded) {\n        // A credential refusal is never grounds to discard, even after a verified\n        // reauthentication: -32003 means the credential is what the server just\n        // refused, so the call may still be there and a later reload can retry.\n        // Discarding is reserved for a refusal on a credential the server DID\n        // accept — that is the server saying the call itself is gone.\n        if (this.credentialRecovered() && !refusedOnCredentials) {\n          logger.warn(\n            `[AttachManager] Reattach of ${callId} was denied after a verified reauthentication, removing reference`\n          );\n          await this.detach({ id: callId, mediaDirections: attachment.mediaDirections });\n        } else {\n          logger.warn(\n            `[AttachManager] Reattach failed for call ${callId}; keeping the reference (credential refused or never proven good)`\n          );\n        }\n      }\n    }\n  }\n\n  /**\n   * Build CallOptions from stored attachment data for a call being reattached.\n   * Also used by the session-level verto.attach handler.\n   */\n  public buildCallOptions(attachment: Attachment): CallOptions {\n    const { audio: audioDirection, video: videoDirection } = attachment.mediaDirections;\n    const { audioInputDevice, videoInputDevice, nodeId } = attachment;\n    const receiveAudio = audioDirection.includes('recv');\n    const receiveVideo = videoDirection.includes('recv');\n    const sendAudio = audioDirection.includes('send');\n    const sendVideo = videoDirection.includes('send');\n    const inputAudioDeviceConstraints = sendAudio\n      ? { audio: true, ...this.deviceController.deviceInfoToConstraints(audioInputDevice) }\n      : undefined;\n    const inputVideoDeviceConstraints = sendVideo\n      ? { video: true, ...this.deviceController.deviceInfoToConstraints(videoInputDevice) }\n      : undefined;\n    return {\n      nodeId,\n      receiveAudio,\n      receiveVideo,\n      inputAudioDeviceConstraints,\n      inputVideoDeviceConstraints,\n      reattach: true\n    };\n  }\n\n  /**\n   * Look up stored attachment data for a call id and return CallOptions\n   * suitable for rehydrating a reattached call. Returns undefined when no\n   * matching entry exists in storage.\n   *\n   * Used by the session-level verto.attach handler when the server pushes\n   * an attach event for a call the client doesn't have an object for yet\n   * (e.g. after a reload).\n   */\n  public async consumePendingAttachment(callId: string): Promise<CallOptions | undefined> {\n    const attached = await this.readAttached();\n    // Object.hasOwn — the record's type omits `| undefined` on index access\n    // (noUncheckedIndexedAccess is off), so a truthy-check on the value would\n    // be flagged as always-truthy by the type checker even though the key\n    // may be absent at runtime.\n    if (!Object.hasOwn(attached, callId)) {\n      return undefined;\n    }\n    return this.buildCallOptions(attached[callId]);\n  }\n\n  private async detachExpired(): Promise<void> {\n    const now = Date.now();\n    const timeout = this.reconnectCallsTimeout;\n    await this.mutate((attached) => {\n      const remaining = { ...attached };\n      let changed = false;\n      for (const [callId, attachment] of Object.entries(attached)) {\n        if (now - attachment.attachedAt > timeout) {\n          delete remaining[callId];\n          changed = true;\n        }\n      }\n      return changed ? remaining : attached;\n    });\n  }\n}\n","/**\n * Represents an on/off capability state\n * Both `on` and `off` can be true if the parent permission grants both\n */\nexport interface OnOffCapability {\n  readonly on: boolean;\n  readonly off: boolean;\n}\n\n/**\n * Member-level capabilities for self or other members\n */\nexport interface MemberCapabilities {\n  readonly muteAudio: OnOffCapability;\n  readonly muteVideo: OnOffCapability;\n  readonly deaf: OnOffCapability;\n  readonly raisehand: OnOffCapability;\n  readonly microphoneVolume: boolean;\n  readonly microphoneSensitivity: boolean;\n  readonly speakerVolume: boolean;\n  readonly position: boolean;\n  readonly meta: boolean;\n  readonly remove: boolean;\n  readonly audioFlags: boolean;\n  readonly denoise: boolean;\n  readonly lowbitrate: boolean;\n}\n\n/**\n * Call-level capabilities state\n */\nexport interface CallCapabilitiesState {\n  readonly self: MemberCapabilities;\n  readonly member: MemberCapabilities;\n  readonly end: boolean;\n  readonly setLayout: boolean;\n  readonly sendDigit: boolean;\n  readonly vmutedHide: OnOffCapability;\n  readonly lock: OnOffCapability;\n  readonly device: boolean;\n  readonly screenshare: boolean;\n}\n\n/**\n * Default on/off state with no permissions\n */\nexport const DEFAULT_ON_OFF: OnOffCapability = {\n  on: false,\n  off: false\n};\n\n/**\n * Default member capabilities with no permissions\n */\nexport const DEFAULT_MEMBER_CAPABILITIES: MemberCapabilities = {\n  muteAudio: DEFAULT_ON_OFF,\n  muteVideo: DEFAULT_ON_OFF,\n  deaf: DEFAULT_ON_OFF,\n  raisehand: DEFAULT_ON_OFF,\n  microphoneVolume: false,\n  microphoneSensitivity: false,\n  speakerVolume: false,\n  position: false,\n  meta: false,\n  remove: false,\n  audioFlags: false,\n  denoise: false,\n  lowbitrate: false\n};\n\n/**\n * Default call capabilities with no permissions\n */\nexport const DEFAULT_CALL_CAPABILITIES: CallCapabilitiesState = {\n  self: DEFAULT_MEMBER_CAPABILITIES,\n  member: DEFAULT_MEMBER_CAPABILITIES,\n  end: false,\n  setLayout: false,\n  sendDigit: false,\n  vmutedHide: DEFAULT_ON_OFF,\n  lock: DEFAULT_ON_OFF,\n  device: false,\n  screenshare: false\n};\n","import { DEFAULT_CALL_CAPABILITIES, DEFAULT_MEMBER_CAPABILITIES, DEFAULT_ON_OFF } from './types';\n\nimport type { CallCapabilitiesState, MemberCapabilities, OnOffCapability } from './types';\n\ntype MemberType = 'self' | 'member';\n\n/**\n * Computes an on/off capability state from filtered flags\n *\n * Logic:\n * - `on` is true if any flag does NOT end with `.off`\n * - `off` is true if any flag does NOT end with `.on`\n *\n * This means parent permissions (without .on/.off suffix) grant both on and off\n */\nfunction computeOnOffCapability(filteredFlags: string[]): OnOffCapability {\n  if (filteredFlags.length === 0) {\n    return DEFAULT_ON_OFF;\n  }\n\n  return {\n    on: filteredFlags.some((flag) => !flag.endsWith('.off')),\n    off: filteredFlags.some((flag) => !flag.endsWith('.on'))\n  };\n}\n\n/**\n * Filters flags for a specific member capability with on/off support\n *\n * Matches:\n * - The member type itself (e.g., \"self\" grants all self capabilities)\n * - The parent capability (e.g., \"self.mute\" grants all mute capabilities)\n * - The specific capability and its on/off variants (e.g., \"self.mute.audio*\")\n */\nfunction filterMemberOnOffFlags(\n  flags: string[],\n  memberType: MemberType,\n  parent: string,\n  capability: string\n): string[] {\n  return flags.filter(\n    (flag) =>\n      flag === memberType ||\n      flag === `${memberType}.${parent}` ||\n      flag.startsWith(`${memberType}.${parent}.${capability}`)\n  );\n}\n\n/**\n * Checks if a boolean capability is granted\n *\n * Matches:\n * - The member type itself (e.g., \"self\" grants all self capabilities)\n * - The parent capability if provided (e.g., \"self.microphone\" grants volume and sensitivity)\n * - The specific capability (e.g., \"self.microphone.volume.set\")\n */\nfunction hasBooleanCapability(\n  flags: string[],\n  memberType: MemberType,\n  parent: string | null,\n  capability: string\n): boolean {\n  return flags.some(\n    (flag) =>\n      flag === memberType ||\n      (parent !== null && flag === `${memberType}.${parent}`) ||\n      flag.startsWith(`${memberType}.${parent ? `${parent}.` : ''}${capability}`)\n  );\n}\n\n/**\n * Computes member-level capabilities (self or member) from raw flags\n */\nfunction computeMemberCapabilities(flags: string[], memberType: MemberType): MemberCapabilities {\n  // Filter only flags relevant to this member type\n  const memberFlags = flags.filter((flag) => flag.startsWith(memberType) || flag === memberType);\n\n  if (memberFlags.length === 0) {\n    return DEFAULT_MEMBER_CAPABILITIES;\n  }\n\n  return {\n    muteAudio: computeOnOffCapability(filterMemberOnOffFlags(flags, memberType, 'mute', 'audio')),\n    muteVideo: computeOnOffCapability(filterMemberOnOffFlags(flags, memberType, 'mute', 'video')),\n    deaf: computeOnOffCapability(\n      flags.filter((flag) => flag === memberType || flag.startsWith(`${memberType}.deaf`))\n    ),\n    raisehand: computeOnOffCapability(\n      flags.filter((flag) => flag === memberType || flag.startsWith(`${memberType}.raisehand`))\n    ),\n    microphoneVolume: hasBooleanCapability(flags, memberType, 'microphone', 'volume'),\n    microphoneSensitivity: hasBooleanCapability(flags, memberType, 'microphone', 'sensitivity'),\n    speakerVolume: hasBooleanCapability(flags, memberType, 'speaker', 'volume'),\n    position: hasBooleanCapability(flags, memberType, null, 'position'),\n    meta: hasBooleanCapability(flags, memberType, null, 'meta'),\n    remove: hasBooleanCapability(flags, memberType, null, 'remove'),\n    audioFlags: hasBooleanCapability(flags, memberType, null, 'audioflags'),\n    denoise: hasBooleanCapability(flags, memberType, null, 'denoise'),\n    lowbitrate: hasBooleanCapability(flags, memberType, null, 'lowbitrate')\n  };\n}\n\n/**\n * Computes all call capabilities from raw capability strings\n *\n * This is a pure function that transforms the raw capability strings\n * from the `call.joined` event into a structured capabilities state.\n *\n * @param capabilities - Raw capability strings from the server\n * @returns Computed capabilities state\n */\nexport function computeCapabilities(capabilities: string[]): CallCapabilitiesState {\n  if (capabilities.length === 0) {\n    return DEFAULT_CALL_CAPABILITIES;\n  }\n\n  return {\n    self: computeMemberCapabilities(capabilities, 'self'),\n    member: computeMemberCapabilities(capabilities, 'member'),\n    end: capabilities.some((cap) => cap === 'end'),\n    setLayout: capabilities.some((cap) => cap.startsWith('layout')),\n    sendDigit: capabilities.some((cap) => cap.startsWith('digit')),\n    vmutedHide: computeOnOffCapability(capabilities.filter((flag) => flag.startsWith('vmuted'))),\n    lock: computeOnOffCapability(capabilities.filter((flag) => flag.startsWith('lock'))),\n    device: capabilities.some((cap) => cap === 'device'),\n    screenshare: capabilities.some((cap) => cap === 'screenshare')\n  };\n}\n","import { distinctUntilChanged, map } from 'rxjs';\n\nimport { computeCapabilities } from './computeCapabilities';\nimport { DEFAULT_CALL_CAPABILITIES } from './types';\nimport { Destroyable } from '../../behaviors/Destroyable';\n\nimport type { CallCapabilitiesState, MemberCapabilities, OnOffCapability } from './types';\nimport type { Observable } from 'rxjs';\n\n/**\n * SelfCapabilities manages the capability state for the self participant.\n *\n * Capabilities are received from the server via `call.joined` events and determine\n * what actions the current participant is allowed to perform.\n *\n * Each capability is exposed as both:\n * - An observable (e.g., `end$`) for reactive state management\n * - A synchronous getter (e.g., `end`) for immediate access\n *\n * Member-level capabilities are accessed via the grouped `self` / `member` accessors:\n * - `capabilities.self.muteAudio` (sync)\n * - `capabilities.self$` (observable)\n *\n * When a new `call.joined` event is received, the capabilities state is\n * completely replaced (not merged).\n */\nexport class SelfCapabilities extends Destroyable {\n  private _state$ = this.createBehaviorSubject<CallCapabilitiesState>(DEFAULT_CALL_CAPABILITIES);\n\n  /**\n   * Updates the capabilities state from raw capability strings.\n   * This completely replaces the current state.\n   *\n   * @param capabilities - Raw capability strings from the server\n   * @internal\n   */\n  public updateFromRaw(capabilities: string[]): void {\n    const newState = computeCapabilities(capabilities);\n    this._state$.next(newState);\n  }\n\n  // ============================================\n  // Self member capabilities\n  // ============================================\n\n  /** Observable for self member capabilities */\n  public get self$(): Observable<MemberCapabilities> {\n    return this.cachedObservable('self$', () =>\n      this._state$.pipe(\n        map((state) => state.self),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Current self member capabilities */\n  public get self(): MemberCapabilities {\n    return this._state$.value.self;\n  }\n\n  // ============================================\n  // Member capabilities (other participants)\n  // ============================================\n\n  /** Observable for other member capabilities */\n  public get member$(): Observable<MemberCapabilities> {\n    return this.cachedObservable('member$', () =>\n      this._state$.pipe(\n        map((state) => state.member),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Current other member capabilities */\n  public get member(): MemberCapabilities {\n    return this._state$.value.member;\n  }\n\n  // ============================================\n  // Call-level capabilities\n  // ============================================\n\n  /** Observable for end call capability */\n  public get end$(): Observable<boolean> {\n    return this.cachedObservable('end$', () =>\n      this._state$.pipe(\n        map((state) => state.end),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Current end call capability */\n  public get end(): boolean {\n    return this._state$.value.end;\n  }\n\n  /** Observable for set layout capability */\n  public get setLayout$(): Observable<boolean> {\n    return this.cachedObservable('setLayout$', () =>\n      this._state$.pipe(\n        map((state) => state.setLayout),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Current set layout capability */\n  public get setLayout(): boolean {\n    return this._state$.value.setLayout;\n  }\n\n  /** Observable for send digit capability */\n  public get sendDigit$(): Observable<boolean> {\n    return this.cachedObservable('sendDigit$', () =>\n      this._state$.pipe(\n        map((state) => state.sendDigit),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Current send digit capability */\n  public get sendDigit(): boolean {\n    return this._state$.value.sendDigit;\n  }\n\n  /** Observable for vmuted hide capability */\n  public get vmutedHide$(): Observable<OnOffCapability> {\n    return this.cachedObservable('vmutedHide$', () =>\n      this._state$.pipe(\n        map((state) => state.vmutedHide),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Current vmuted hide capability */\n  public get vmutedHide(): OnOffCapability {\n    return this._state$.value.vmutedHide;\n  }\n\n  /** Observable for lock capability */\n  public get lock$(): Observable<OnOffCapability> {\n    return this.cachedObservable('lock$', () =>\n      this._state$.pipe(\n        map((state) => state.lock),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Current lock capability */\n  public get lock(): OnOffCapability {\n    return this._state$.value.lock;\n  }\n\n  /** Observable for device capability */\n  public get device$(): Observable<boolean> {\n    return this.cachedObservable('device$', () =>\n      this._state$.pipe(\n        map((state) => state.device),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Current device capability */\n  public get device(): boolean {\n    return this._state$.value.device;\n  }\n\n  /** Observable for screenshare capability */\n  public get screenshare$(): Observable<boolean> {\n    return this.cachedObservable('screenshare$', () =>\n      this._state$.pipe(\n        map((state) => state.screenshare),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Current screenshare capability */\n  public get screenshare(): boolean {\n    return this._state$.value.screenshare;\n  }\n\n  // ============================================\n  // Full state access\n  // ============================================\n\n  /** Observable for the full capabilities state */\n  public get state$(): Observable<CallCapabilitiesState> {\n    return this._state$.asObservable();\n  }\n\n  /** Current full capabilities state */\n  public get state(): CallCapabilitiesState {\n    return this._state$.value;\n  }\n}\n","export function toggleDeafMethod(is: boolean): 'call.undeaf' | 'call.deaf' {\n  return is ? 'call.undeaf' : 'call.deaf';\n}\n\nexport function toggleHandraiseMethod(is: boolean): 'call.lowerhand' | 'call.raisehand' {\n  return is ? 'call.lowerhand' : 'call.raisehand';\n}\n","import { distinctUntilChanged, map } from 'rxjs/operators';\n\nimport { Destroyable } from '../../behaviors/Destroyable';\nimport { PreferencesContainer } from '../../containers/PreferencesContainer';\nimport { getLogger } from '../../utils/logger';\nimport { SelfCapabilities } from '../capabilities';\nimport {\n  AuxiliaryLegCancelledError,\n  ParticipantNotReadyError,\n  UnimplementedError\n} from '../errors';\nimport { toggleDeafMethod, toggleHandraiseMethod } from '../RPCMessages/utils';\n\nimport type { CallParticipant, CallSelfParticipant } from './types/call.types';\nimport type { SelectDeviceOptions } from './types/participant.types';\nimport type { DeviceController } from '../../interfaces/DeviceController';\nimport type { VertoManager } from '../../interfaces/VertoManager';\nimport type { ScreenShareStatus } from '../../managers/types/verto-manager.types';\nimport type { JSONRPCResponse } from '../RPCMessages/types/base';\nimport type { Member, LayoutLayer, MemberTarget } from '../RPCMessages/types/common';\nimport type { VideoPosition } from '../types/call.types';\nimport type { MediaOptions, ScreenShareOptions } from '../types/media.types';\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\n/**\n * Callback type for executing call methods\n * Injected to avoid circular dependency with Call class\n */\nexport type ExecuteMethod = <T extends JSONRPCResponse = JSONRPCResponse>(\n  target: string | MemberTarget,\n  method: string,\n  args: Record<string, unknown>\n) => Promise<T>;\n\ntype ParticipantState = Member & { position: LayoutLayer };\n\nconst initialState: Partial<ParticipantState> = {};\n\n/**\n * Represents a participant in a call.\n *\n * Provides observable state (audio/video mute, hand raise, volume, etc.)\n * and control methods for the participant. See {@link SelfParticipant} for\n * the local participant with additional device control.\n */\nexport class Participant extends Destroyable implements CallParticipant {\n  /** Unique member ID of this participant. */\n  public readonly id!: string;\n  private _state$ = this.createBehaviorSubject<Partial<ParticipantState>>(initialState);\n  constructor(\n    id: string,\n    private callExecuteMethod: ExecuteMethod,\n    protected deviceController: DeviceController\n  ) {\n    super();\n    this.id = id;\n  }\n  /** @internal */\n  public upnext(data: Partial<ParticipantState>): void {\n    this._state$.next({ ...this._state$.value, ...data });\n  }\n\n  /** Observable of the participant's display name. */\n  public get name$(): Observable<string | undefined> {\n    return this.cachedObservable('name$', () =>\n      this._state$.pipe(\n        map((state) => state.name),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable of the participant type (e.g. `'member'`, `'screen'`). */\n  public get type$(): Observable<string | undefined> {\n    return this.cachedObservable('type$', () =>\n      this._state$.pipe(\n        map((state) => state.type),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether the participant has raised their hand. */\n  public get handraised$(): Observable<boolean | undefined> {\n    return this.cachedObservable('handraised$', () =>\n      this._state$.pipe(\n        map((state) => state.handraised),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether the participant is visible in the layout. */\n  public get visible$(): Observable<boolean | undefined> {\n    return this.cachedObservable('visible$', () =>\n      this._state$.pipe(\n        map((state) => state.visible),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether the participant's audio is muted. */\n  public get audioMuted$(): Observable<boolean | undefined> {\n    return this.cachedObservable('audioMuted$', () =>\n      this._state$.pipe(\n        map((state) => state.audio_muted),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether the participant's video is muted. */\n  public get videoMuted$(): Observable<boolean | undefined> {\n    return this.cachedObservable('videoMuted$', () =>\n      this._state$.pipe(\n        map((state) => state.video_muted),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether the participant is deafened. */\n  public get deaf$(): Observable<boolean | undefined> {\n    return this.cachedObservable('deaf$', () =>\n      this._state$.pipe(\n        map((state) => state.deaf),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /**\n   * Observable of the participant's **server-side** microphone input volume\n   * as reported by the mix engine. This is gain applied on the bridged audio\n   * leg (FreeSWITCH channel read volume), NOT the local browser mic. For a\n   * local PC mic control, see {@link Call.setLocalMicrophoneGain}.\n   *\n   * @see {@link setAudioInputVolume}\n   */\n  public get inputVolume$(): Observable<number | undefined> {\n    return this.cachedObservable('inputVolume$', () =>\n      this._state$.pipe(\n        map((state) => state.input_volume),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /**\n   * Observable of the participant's **server-side** speaker output volume as\n   * reported by the mix engine (FreeSWITCH channel write volume). NOT the\n   * local HTML `<audio>` element volume — set that on your own element.\n   *\n   * @see {@link setAudioOutputVolume}\n   */\n  public get outputVolume$(): Observable<number | undefined> {\n    return this.cachedObservable('outputVolume$', () =>\n      this._state$.pipe(\n        map((state) => state.output_volume),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /**\n   * Observable of the **conference-only** microphone energy/gate sensitivity\n   * level for this member. Routes through the conferencing mix engine and has\n   * no effect on 1:1 WebRTC calls. Populated from `member.updated` events for\n   * conference members.\n   *\n   * @see {@link setAudioInputSensitivity}\n   */\n  public get inputSensitivity$(): Observable<number | undefined> {\n    return this.cachedObservable('inputSensitivity$', () =>\n      this._state$.pipe(\n        map((state) => state.input_sensitivity),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether echo cancellation is enabled. */\n  public get echoCancellation$(): Observable<boolean | undefined> {\n    return this.cachedObservable('echoCancellation$', () =>\n      this._state$.pipe(\n        map((state) => state.echo_cancellation),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether auto-gain control is enabled. */\n  public get autoGain$(): Observable<boolean | undefined> {\n    return this.cachedObservable('autoGain$', () =>\n      this._state$.pipe(\n        map((state) => state.auto_gain),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether noise suppression is enabled. */\n  public get noiseSuppression$(): Observable<boolean | undefined> {\n    return this.cachedObservable('noiseSuppression$', () =>\n      this._state$.pipe(\n        map((state) => state.noise_suppression),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether low-bitrate mode is active. */\n  public get lowbitrate$(): Observable<boolean | undefined> {\n    return this.cachedObservable('lowbitrate$', () =>\n      this._state$.pipe(\n        map((state) => state.lowbitrate),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether noise reduction is active. */\n  public get denoise$(): Observable<boolean | undefined> {\n    return this.cachedObservable('denoise$', () =>\n      this._state$.pipe(\n        map((state) => state.denoise),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable of custom metadata for this participant. */\n  public get meta$(): Observable<Record<string, unknown> | undefined> {\n    return this.cachedObservable('meta$', () =>\n      this._state$.pipe(\n        map((state) => state.meta),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable of the participant's user ID. */\n  public get userId$(): Observable<string | undefined> {\n    return this.cachedObservable('userId$', () =>\n      this._state$.pipe(\n        map((state) => state.subscriber_id),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable of the participant's address ID. */\n  public get addressId$(): Observable<string | undefined> {\n    return this.cachedObservable('addressId$', () =>\n      this._state$.pipe(\n        map((state) => state.address_id),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable of the server node ID for this participant. */\n  public get nodeId$(): Observable<string | undefined> {\n    return this.cachedObservable('nodeId$', () =>\n      this._state$.pipe(\n        map((state) => state.node_id),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Observable indicating whether the participant is currently speaking. */\n  public get isTalking$(): Observable<boolean | undefined> {\n    return this.cachedObservable('isTalking$', () =>\n      this._state$.pipe(\n        map((state) => state.talking),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Whether the participant is currently speaking. */\n  public get isTalking(): boolean {\n    return this._state$.value.talking ?? false;\n  }\n\n  /** Observable of the participant's layout position. */\n  public get position$(): Observable<LayoutLayer | undefined> {\n    return this.cachedObservable('position$', () =>\n      this._state$.pipe(\n        map((state) => state.position),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /** Current layout position. */\n  public get position(): LayoutLayer | undefined {\n    return this._state$.value.position;\n  }\n\n  /** Whether the participant is an audience member (view-only). */\n  public get isAudience(): boolean {\n    return this._state$.value.isAudience ?? false;\n  }\n\n  /** Display name of this participant. */\n  public get name(): string | undefined {\n    return this._state$.value.name;\n  }\n\n  /** Participant type (e.g. `'member'`, `'screen'`). */\n  public get type(): string | undefined {\n    return this._state$.value.type;\n  }\n\n  /** Whether the participant has raised their hand. */\n  public get handraised(): boolean {\n    return this._state$.value.handraised ?? false;\n  }\n\n  /** Whether the participant is visible in the layout. */\n  public get visible(): boolean {\n    return this._state$.value.visible ?? false;\n  }\n\n  /** Whether the participant's audio is muted. */\n  public get audioMuted(): boolean {\n    return this._state$.value.audio_muted ?? false;\n  }\n\n  /** Whether the participant's video is muted. */\n  public get videoMuted(): boolean {\n    return this._state$.value.video_muted ?? false;\n  }\n\n  /** Whether the participant is deafened (incoming audio muted). */\n  public get deaf(): boolean {\n    return this._state$.value.deaf ?? false;\n  }\n\n  /**\n   * Current **server-side** microphone input volume as reported by the mix\n   * engine, or `undefined` if not set. Not the local PC mic — see\n   * {@link Call.setLocalMicrophoneGain} for browser-side control.\n   */\n  public get inputVolume(): number | undefined {\n    return this._state$.value.input_volume;\n  }\n\n  /**\n   * Current **server-side** speaker output volume from the mix engine, or\n   * `undefined` if not set. Not the local `<audio>` element volume.\n   */\n  public get outputVolume(): number | undefined {\n    return this._state$.value.output_volume;\n  }\n\n  /**\n   * Current **conference-only** microphone sensitivity/gate level, or\n   * `undefined` if not set. Applies only to conference members.\n   */\n  public get inputSensitivity(): number | undefined {\n    return this._state$.value.input_sensitivity;\n  }\n\n  /** Whether echo cancellation is enabled. */\n  public get echoCancellation(): boolean {\n    return this._state$.value.echo_cancellation ?? false;\n  }\n\n  /** Whether automatic gain control is enabled. */\n  public get autoGain(): boolean {\n    return this._state$.value.auto_gain ?? false;\n  }\n\n  /** Whether noise suppression is enabled. */\n  public get noiseSuppression(): boolean {\n    return this._state$.value.noise_suppression ?? false;\n  }\n\n  /** Whether low-bitrate mode is active. */\n  public get lowbitrate(): boolean {\n    return this._state$.value.lowbitrate ?? false;\n  }\n\n  /** Whether noise reduction (denoise) is active. */\n  public get denoise(): boolean {\n    return this._state$.value.denoise ?? false;\n  }\n\n  /** Custom metadata for this participant, or `undefined` if not set. */\n  public get meta(): Record<string, unknown> | undefined {\n    return this._state$.value.meta;\n  }\n\n  /** User ID of this participant, or `undefined` if not available. */\n  public get userId(): string | undefined {\n    return this._state$.value.subscriber_id;\n  }\n\n  /** Address ID of this participant, or `undefined` if not available. */\n  public get addressId(): string | undefined {\n    return this._state$.value.address_id;\n  }\n\n  /** Server node ID for this participant, or `undefined` if not available. */\n  public get nodeId(): string | undefined {\n    return this._state$.value.node_id;\n  }\n\n  /** Call ID for this participant's leg, or `undefined` if not available. */\n  public get callId(): string | undefined {\n    return this._state$.value.call_id;\n  }\n\n  /** @internal */\n  public get value(): Partial<Member> {\n    return this._state$.value;\n  }\n\n  /**\n   * Target triple for member RPCs, built from the participant's own state.\n   * The backend locates the member's session by the target `call_id`/`node_id`,\n   * so this must always be the participant's own call context — never the\n   * local call's id (issue #19400).\n   *\n   * Reading it doubles as a readiness probe: it throws until the first full\n   * member event (`member.joined`/`member.updated` or the `call.joined`\n   * roster) arrives, and never regresses afterwards.\n   *\n   * @throws {ParticipantNotReadyError} If the member state has not been\n   * received yet (e.g. a participant first seen via `member.talking`) — an\n   * empty call context can never address the member, so fail fast instead of\n   * sending a doomed RPC.\n   */\n  public get target(): MemberTarget {\n    const { call_id, node_id } = this._state$.value;\n    if (!call_id || !node_id) {\n      throw new ParticipantNotReadyError(this.id);\n    }\n    return { member_id: this.id, call_id, node_id };\n  }\n\n  /**\n   * Executes a member RPC against this participant, injecting its own\n   * {@link target} as the target.\n   *\n   * @throws {ParticipantNotReadyError} Via {@link target}, when the\n   * member state has not been received yet.\n   */\n  protected async executeMethod(\n    method: string,\n    args: Record<string, unknown>\n  ): Promise<JSONRPCResponse> {\n    return this.callExecuteMethod(this.target, method, args);\n  }\n\n  /** Toggles the deafened state (mutes/unmutes incoming audio). */\n  public async toggleDeaf(): Promise<void> {\n    await this.executeMethod(toggleDeafMethod(this.deaf), {});\n  }\n\n  /** Toggles the hand-raised state. */\n  public async toggleHandraise(): Promise<void> {\n    await this.executeMethod(toggleHandraiseMethod(this.handraised), {});\n  }\n\n  /** Mutes the participant's audio. */\n  public async mute(): Promise<void> {\n    await this.executeMethod('call.mute', { channels: ['audio'] });\n  }\n\n  /** Unmutes the participant's audio. */\n  public async unmute(): Promise<void> {\n    await this.executeMethod('call.unmute', { channels: ['audio'] });\n  }\n\n  /** Toggles the participant's audio mute state. */\n  public async toggleMute(): Promise<void> {\n    return this.audioMuted ? this.unmute() : this.mute();\n  }\n\n  /** Mutes the participant's video. */\n  public async muteVideo(): Promise<void> {\n    await this.executeMethod('call.mute', { channels: ['video'] });\n  }\n\n  /** Unmutes the participant's video. */\n  public async unmuteVideo(): Promise<void> {\n    await this.executeMethod('call.unmute', { channels: ['video'] });\n  }\n\n  /** Toggles the participant's video mute state. */\n  public async toggleMuteVideo(): Promise<void> {\n    return this.videoMuted ? this.unmuteVideo() : this.muteVideo();\n  }\n\n  /** Toggles echo cancellation on the audio input. */\n  public async toggleEchoCancellation(): Promise<void> {\n    await this.executeMethod('call.audioflags.set', {\n      echo_cancellation: !this.echoCancellation,\n      auto_gain: this.autoGain,\n      noise_suppression: this.noiseSuppression\n    });\n  }\n\n  /** Toggles automatic gain control on the audio input. */\n  public async toggleAudioInputAutoGain(): Promise<void> {\n    await this.executeMethod('call.audioflags.set', {\n      echo_cancellation: this.echoCancellation,\n      auto_gain: !this.autoGain,\n      noise_suppression: this.noiseSuppression\n    });\n  }\n\n  /** Toggles noise suppression on the audio input. */\n  public async toggleNoiseSuppression(): Promise<void> {\n    await this.executeMethod('call.audioflags.set', {\n      echo_cancellation: this.echoCancellation,\n      auto_gain: this.autoGain,\n      noise_suppression: !this.noiseSuppression\n    });\n  }\n\n  /** Toggles low-bitrate mode for this participant's media. */\n  public async toggleLowbitrate(): Promise<void> {\n    await this.executeMethod('call.lowbitrate.set', {\n      lowbitrate: !this.lowbitrate\n    });\n  }\n\n  /**\n   * Adjusts the **conference-only** microphone energy gate / sensitivity level\n   * for this member. Routes through the conferencing mix engine\n   * (`signalwire.conferencing member.set_input_sensitivity`) and has no effect\n   * on 1:1 WebRTC calls — for those, use browser audio constraints via\n   * {@link Call.setNoiseSuppression} / {@link Call.setAutoGainControl}.\n   *\n   * This is **not** a local PC mic gain control; it only changes how the\n   * server-side mixer decides to open the mic gate on this participant.\n   *\n   * @param value - Sensitivity level as understood by the conference engine\n   *   (integer, larger values are more sensitive).\n   */\n  public async setAudioInputSensitivity(value: number): Promise<void> {\n    await this.executeMethod('call.microphone.sensitivity.set', {\n      sensitivity: value\n    });\n  }\n\n  /**\n   * Sets the **server-side** microphone volume on this participant's bridged\n   * call leg. Applies a multiplier to the audio flowing through the mix\n   * engine (FreeSWITCH channel read volume) — changes what other participants\n   * hear, not what the local browser captures.\n   *\n   * For local PC mic gain, use {@link Call.setLocalMicrophoneGain} instead.\n   *\n   * @param value - Volume level (0-100).\n   */\n  public async setAudioInputVolume(value: number): Promise<void> {\n    await this.executeMethod('call.microphone.volume.set', {\n      volume: value\n    });\n  }\n\n  /**\n   * Sets the **server-side** speaker volume on this participant's bridged call\n   * leg (FreeSWITCH channel write volume) — what this participant hears from\n   * the mix before it reaches their client.\n   *\n   * For local playback volume (the `<audio>` element the consumer attaches\n   * `remoteStream` to), set `audioElement.volume` directly in the consumer's\n   * code.\n   *\n   * @param value - Volume level (0-100).\n   */\n  public async setAudioOutputVolume(value: number): Promise<void> {\n    await this.executeMethod('call.speaker.volume.set', {\n      volume: value\n    });\n  }\n\n  /**\n   * Sets the participant's position in the video layout.\n   *\n   * Requires the `member.position` capability. The gateway requires a\n   * `targets` array of `{ target, position }` entries (issue #19400). A\n   * resolved promise does not guarantee a visible change: the backend silently\n   * returns `200` (no-op) for non-conference targets.\n   *\n   * @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).\n   */\n  public async setPosition(value: VideoPosition): Promise<void> {\n    await this.executeMethod('call.member.position.set', {\n      targets: [{ target: this.target, position: value }]\n    });\n  }\n\n  /** Removes this participant from the call. */\n  public async remove(): Promise<void> {\n    await this.executeMethod('call.member.remove', { targets: [this.target] });\n  }\n\n  /** Ends the call for this participant. */\n  public async end(): Promise<void> {\n    await this.executeMethod('call.end', {});\n  }\n\n  /**\n   * Replaces custom metadata for this participant.\n   * @param _meta - Metadata object to set.\n   * @throws {UnimplementedError} Not yet implemented.\n   */\n  // eslint-disable-next-line @typescript-eslint/require-await\n  public async setMeta(_meta: Record<string, unknown>): Promise<void> {\n    // NEEDS backend implementation\n    throw new UnimplementedError();\n  }\n  /**\n   * Merges values into custom metadata (unlike {@link setMeta} which replaces).\n   * @param _meta - Metadata to merge.\n   * @throws {UnimplementedError} Not yet implemented.\n   */\n  // eslint-disable-next-line @typescript-eslint/require-await\n  public async updateMeta(_meta: Record<string, unknown>): Promise<void> {\n    // NEEDS backend implementation\n    throw new UnimplementedError();\n  }\n\n  /** Destroys the participant, releasing all subscriptions and references. */\n  public destroy(): void {\n    // Cleanup callback reference - intentionally breaking type safety for cleanup\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any\n    this.callExecuteMethod = undefined as any;\n    super.destroy();\n  }\n}\n\n/**\n * The local participant in a call, with additional device and media control.\n *\n * Extends {@link Participant} with screen sharing, device selection,\n * and local media stream management.\n */\nexport class SelfParticipant extends Participant implements CallSelfParticipant {\n  /**\n   * Capabilities for this participant.\n   * Contains all capability flags as both observables and values.\n   */\n  public readonly capabilities: SelfCapabilities;\n\n  /**\n   * Studio audio mode state. When enabled, all audio processing\n   * (echo cancellation, noise suppression, auto gain control) is disabled\n   * to provide raw/unprocessed audio for musicians, podcasters, etc.\n   */\n  private _studioAudio$ = this.createBehaviorSubject<boolean>(false);\n\n  /** @internal */\n  constructor(\n    id: string,\n    callExecuteMethod: ExecuteMethod,\n    private vertoManager: VertoManager,\n    deviceController: DeviceController\n  ) {\n    super(id, callExecuteMethod, deviceController);\n    this.capabilities = new SelfCapabilities();\n  }\n\n  public override destroy(): void {\n    this.capabilities.destroy();\n    super.destroy();\n  }\n\n  /** Observable indicating whether studio audio (raw/unprocessed audio) mode is enabled. */\n  public get studioAudio$(): Observable<boolean> {\n    return this._studioAudio$.asObservable();\n  }\n\n  /** Whether studio audio (raw/unprocessed audio) mode is currently enabled. */\n  public get studioAudio(): boolean {\n    return this._studioAudio$.value;\n  }\n\n  /**\n   * Enables studio audio mode by disabling all audio processing.\n   * Sets echoCancellation, noiseSuppression, and autoGainControl to false.\n   */\n  public async enableStudioAudio(): Promise<void> {\n    if (this._studioAudio$.value) {\n      return;\n    }\n    this._studioAudio$.next(true);\n    await this.executeMethod('call.audioflags.set', {\n      echo_cancellation: false,\n      auto_gain: false,\n      noise_suppression: false\n    });\n  }\n\n  /**\n   * Disables studio audio mode by restoring all audio processing to enabled.\n   * Sets echoCancellation, noiseSuppression, and autoGainControl to true.\n   */\n  public async disableStudioAudio(): Promise<void> {\n    if (!this._studioAudio$.value) {\n      return;\n    }\n    this._studioAudio$.next(false);\n    await this.executeMethod('call.audioflags.set', {\n      echo_cancellation: true,\n      auto_gain: true,\n      noise_suppression: true\n    });\n  }\n\n  /**\n   * Starts sharing the local screen.\n   *\n   * A call carries at most one screen share. Read `screenShareStatus` before\n   * calling and treat `'starting'`/`'stopping'` as busy.\n   *\n   * The call is unaffected when acquisition fails.\n   *\n   * @param options - Pass `{ audio: true }` to also request the shared\n   * surface's audio. Defaults to video only.\n   * @throws {ScreenShareAlreadyActiveError} When this call is already\n   * sharing a screen. Call {@link stopScreenShare} before starting another.\n   * @throws {AuxiliaryLegCancelledError} When {@link stopScreenShare} removes\n   * the share before its leg finishes connecting.\n   * @throws The raw `getDisplayMedia` error. A dismissed picker or a\n   * permission denial rejects with a `NotAllowedError` `DOMException` —\n   * inspect `error.name` to tell benign cancels apart from real failures.\n   */\n  public async startScreenShare(options?: ScreenShareOptions): Promise<void> {\n    try {\n      await this.vertoManager.addScreenMedia(options);\n    } catch (error) {\n      // A cancel is the app getting what it asked for, so it is reported to\n      // the caller but never logged as a failure.\n      if (error instanceof AuxiliaryLegCancelledError) {\n        logger.debug('[Participant.startScreenShare] Screen share cancelled before connecting.');\n      } else {\n        logger.error('[Participant.startScreenShare] Screen share error:', error);\n      }\n      throw error;\n    }\n  }\n\n  /** Observable of the current screen share status. */\n  public get screenShareStatus$(): Observable<ScreenShareStatus> {\n    return this.vertoManager.screenShareStatus$;\n  }\n\n  /** Current screen share status. */\n  public get screenShareStatus(): ScreenShareStatus {\n    return this.vertoManager.screenShareStatus;\n  }\n\n  /** Stops the current screen share. */\n  public async stopScreenShare(): Promise<void> {\n    return this.vertoManager.removeScreenMedia();\n  }\n\n  /**\n   * Adds an additional media input device to the call.\n   *\n   * The call is unaffected when acquisition fails.\n   *\n   * @throws {AuxiliaryLegCancelledError} When {@link removeAdditionalDevice}\n   * removes the device before its leg finishes connecting.\n   * @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on\n   * permission denial) — inspect `error.name` to decide how to react — or\n   * `AuxiliaryLegTimeoutError` if the leg does not connect in time.\n   */\n  public async addAdditionalDevice(options: MediaOptions): Promise<void> {\n    try {\n      await this.vertoManager.addInputDevice(options);\n    } catch (error) {\n      // A cancel is the app getting what it asked for, so it is reported to\n      // the caller but never logged as a failure.\n      if (error instanceof AuxiliaryLegCancelledError) {\n        logger.debug('[Participant.addAdditionalDevice] Device removed before connecting.');\n      } else {\n        logger.error('[Participant.addAdditionalDevice] Additional device error:', error);\n      }\n      throw error;\n    }\n  }\n\n  /** Removes an additional media input device by ID. */\n  public async removeAdditionalDevice(id: string): Promise<void> {\n    return this.vertoManager.removeInputDevices(id);\n  }\n\n  /** Adds or replaces the primary audio input device with optional constraints or stream. */\n  public async addAudioInputDevice({\n    constraints,\n    stream\n  }: {\n    constraints?: MediaTrackConstraints;\n    stream?: MediaStream;\n  } = {}): Promise<void> {\n    const audio = (constraints ?? stream) ? undefined : true;\n    return this.vertoManager.addMainInputDevices({\n      audio,\n      inputAudioDeviceConstraints: constraints,\n      inputAudioStream: stream\n    });\n  }\n\n  /** Adds or replaces the primary video input device with optional constraints or stream. */\n  public async addVideoInputDevice({\n    constraints,\n    stream\n  }: {\n    constraints?: MediaTrackConstraints;\n    stream?: MediaStream;\n  } = {}): Promise<void> {\n    const video = (constraints ?? stream) ? undefined : true;\n    return this.vertoManager.addMainInputDevices({\n      video,\n      inputVideoDeviceConstraints: constraints,\n      inputVideoStream: stream\n    });\n  }\n\n  /** Adds or replaces primary input devices (audio and/or video). */\n  public async addInputDevices(options: MediaOptions = {}): Promise<void> {\n    await this.vertoManager.addMainInputDevices(options);\n  }\n\n  /** Selects the audio input device for future calls. Optionally saves as a preference. */\n  public selectAudioInputDevice(device: MediaDeviceInfo, options: SelectDeviceOptions = {}): void {\n    this.deviceController.selectAudioInputDevice(device);\n    if (options.savePreference) {\n      PreferencesContainer.instance.preferredAudioInput = device;\n    }\n  }\n\n  /**\n   * Updates the audio input track constraints for the active call.\n   * @returns whether the constraints reached the media the call is sending.\n   */\n  public async setAudioInputDeviceConstraints(\n    constraints: MediaTrackConstraints\n  ): Promise<boolean> {\n    return this.vertoManager.updateMediaConstraints({ audio: constraints });\n  }\n\n  /**\n   * Updates both audio and video input track constraints for the active call.\n   * @returns whether both kinds took the constraints.\n   */\n  public async setInputDevicesConstraints(constraints: {\n    audio: MediaTrackConstraints;\n    video: MediaTrackConstraints;\n  }): Promise<boolean> {\n    return this.vertoManager.updateMediaConstraints(constraints);\n  }\n\n  /** Selects the video input device for future calls. Optionally saves as a preference. */\n  public selectVideoInputDevice(device: MediaDeviceInfo, options: SelectDeviceOptions = {}): void {\n    this.deviceController.selectVideoInputDevice(device);\n    if (options.savePreference) {\n      PreferencesContainer.instance.preferredVideoInput = device;\n    }\n  }\n\n  /**\n   * Updates the video input track constraints for the active call.\n   * @returns whether the constraints reached the media the call is sending.\n   */\n  public async setVideoInputDeviceConstraints(\n    constraints: MediaTrackConstraints\n  ): Promise<boolean> {\n    return this.vertoManager.updateMediaConstraints({ video: constraints });\n  }\n\n  /** Selects the audio output device. Optionally saves as a preference. */\n  public selectAudioOutputDevice(device: MediaDeviceInfo, options: SelectDeviceOptions = {}): void {\n    this.deviceController.selectAudioOutputDevice(device);\n    if (options.savePreference) {\n      PreferencesContainer.instance.preferredAudioOutput = device;\n    }\n  }\n\n  /**\n   * Exits studio audio mode without restoring defaults.\n   * Called internally before individual audio flag toggles.\n   */\n  private exitStudioModeIfActive(): void {\n    if (this._studioAudio$.value) {\n      logger.debug('[SelfParticipant] Exiting studio audio mode due to individual flag toggle');\n      this._studioAudio$.next(false);\n    }\n  }\n\n  /** Toggles echo cancellation. Exits studio mode if active. */\n  public override async toggleEchoCancellation(): Promise<void> {\n    this.exitStudioModeIfActive();\n    await super.toggleEchoCancellation();\n  }\n\n  /** Toggles automatic gain control. Exits studio mode if active. */\n  public override async toggleAudioInputAutoGain(): Promise<void> {\n    this.exitStudioModeIfActive();\n    await super.toggleAudioInputAutoGain();\n  }\n\n  /** Toggles noise suppression. Exits studio mode if active. */\n  public override async toggleNoiseSuppression(): Promise<void> {\n    this.exitStudioModeIfActive();\n    await super.toggleNoiseSuppression();\n  }\n\n  /** Mutes local audio. Falls back to local device mute if the server RPC fails. */\n  public async mute(): Promise<void> {\n    try {\n      await super.mute();\n    } catch (error) {\n      logger.warn(\n        '[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway',\n        error\n      );\n    } finally {\n      this.vertoManager.muteMainAudioInputDevice();\n    }\n  }\n\n  /** Unmutes local audio. Falls back to local device unmute if the server RPC fails. */\n  public async unmute(): Promise<void> {\n    try {\n      await super.unmute();\n    } catch (error) {\n      logger.warn(\n        '[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway',\n        error\n      );\n    } finally {\n      await this.vertoManager.unmuteMainAudioInputDevice();\n    }\n  }\n\n  /** Mutes local video. Falls back to local device mute if the server RPC fails. */\n  public async muteVideo(): Promise<void> {\n    try {\n      await super.muteVideo();\n    } catch (error) {\n      logger.warn(\n        '[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway',\n        error\n      );\n    } finally {\n      this.vertoManager.muteMainVideoInputDevice();\n    }\n  }\n\n  /** Unmutes local video. Falls back to local device unmute if the server RPC fails. */\n  public async unmuteVideo(): Promise<void> {\n    try {\n      await super.unmuteVideo();\n    } catch (error) {\n      logger.warn(\n        '[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway',\n        error\n      );\n    } finally {\n      await this.vertoManager.unmuteMainVideoInputDevice();\n    }\n  }\n}\n\n/** Type guard that checks if a participant is the local {@link SelfParticipant}. */\nexport const isSelfParticipant = (participant: Participant): participant is SelfParticipant => {\n  return participant instanceof SelfParticipant;\n};\n","// =============================================================================\n// BASE TYPE GUARDS\n// =============================================================================\n// This file contains type guards for base JSON-RPC types.\n\nimport type { JSONRPCErrorResponse, JSONRPCRequest, JSONRPCResponse } from '../types/base';\n\n// =============================================================================\n// TYPE GUARD HELPERS\n// =============================================================================\n\nexport function isObject(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null;\n}\n\nexport function hasProperty<T extends object, K extends string>(\n  obj: T,\n  key: K\n): obj is T & Record<K, unknown> {\n  return key in obj;\n}\n\nexport function hasStringProperty<T extends object, K extends string>(\n  obj: T,\n  key: K\n): obj is T & Record<K, string> {\n  return key in obj && typeof (obj as Record<string, unknown>)[key] === 'string';\n}\n\n// =============================================================================\n// BASE TYPE GUARDS\n// =============================================================================\n\nexport function isJSONRPCRequest(value: unknown): value is JSONRPCRequest {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'jsonrpc') &&\n    value.jsonrpc === '2.0' &&\n    hasProperty(value, 'id') &&\n    typeof value.id === 'string' &&\n    hasProperty(value, 'method') &&\n    typeof value.method === 'string'\n  );\n}\n\nexport function isJSONRPCResponse(value: unknown): value is JSONRPCResponse {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'jsonrpc') &&\n    value.jsonrpc === '2.0' &&\n    hasProperty(value, 'id') &&\n    typeof value.id === 'string' &&\n    (hasProperty(value, 'result') || hasProperty(value, 'error'))\n  );\n}\n\nexport function isJSONRPCErrorResponse(value: unknown): value is JSONRPCErrorResponse {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'jsonrpc') &&\n    value.jsonrpc === '2.0' &&\n    hasProperty(value, 'id') &&\n    typeof value.id === 'string' &&\n    // Standard JSON-RPC error: has error field\n    ((hasProperty(value, 'error') &&\n      isObject(value.error) &&\n      hasProperty(value.error, 'code') &&\n      hasProperty(value.error, 'message')) ||\n      (hasProperty(value, 'result') &&\n        isObject(value.result) &&\n        hasProperty(value.result, 'code') &&\n        value.result.code !== '200' &&\n        hasProperty(value.result, 'message')))\n  );\n}\n","// =============================================================================\n// SIGNALWIRE EVENT TYPE GUARDS\n// =============================================================================\n// This file contains type guards for SignalWire event types.\n\n/** @internal @packageDocumentation */\n\nimport { hasProperty, isJSONRPCRequest, isObject } from './base.guards';\n\nimport type { TypeGuard, ExtractParams } from '../types/base';\nimport type {\n  CallConnectPayload,\n  CallJoinedPayload,\n  CallJoinedRequest,\n  CallLeftPayload,\n  CallLeftRequest,\n  CallPlayPayload,\n  CallPlayRequest,\n  CallStatePayload,\n  CallStateRequest,\n  CallUpdatedPayload,\n  CallUpdatedRequest,\n  CallConnectRequest,\n  ConversationMessagePayload,\n  ConversationMessageRequest,\n  ConversationMessageUpdatedPayload,\n  ConversationMessageUpdatedRequest,\n  LayoutChangedPayload,\n  LayoutChangedRequest,\n  MemberJoinedPayload,\n  MemberJoinedRequest,\n  MemberLeftPayload,\n  MemberLeftRequest,\n  MemberTalkingPayload,\n  MemberTalkingRequest,\n  MemberUpdatedPayload,\n  MemberUpdatedRequest,\n  RoomUpdatedPayload,\n  RoomUpdatedRequest,\n  SignalwireAuthorizationStatePayload,\n  SignalwireAuthorizationStateRequest,\n  SignalwireCallMetadata,\n  SignalwireCallRequest,\n  SignalwireEventRequestBase,\n  SignalwireMetadata,\n  SignalwireRequest,\n  WebrtcMessagePayload,\n  WebrtcMessageRequest\n} from '../types/events';\n\n// =============================================================================\n// TYPE GUARD FACTORY SYSTEM\n// =============================================================================\n\n/**\n * Registry of all SignalWire event types.\n * This is the single source of truth for event type strings.\n */\n// eslint-disable-next-line unused-imports/no-unused-vars\nconst EVENT_TYPE_REGISTRY = {\n  'signalwire.authorization.state': true,\n  'webrtc.message': true,\n  'call.joined': true,\n  'call.left': true,\n  'call.updated': true,\n  'call.state': true,\n  'call.play': true,\n  'call.connect': true,\n  'room.updated': true,\n  'member.updated': true,\n  'member.joined': true,\n  'member.left': true,\n  'member.talking': true,\n  'layout.changed': true,\n  'conversation.message': true,\n  'conversation.message.updated': true\n} as const;\n\nexport type RegisteredEventType = keyof typeof EVENT_TYPE_REGISTRY;\n\n/**\n * Factory function to create Request-level type guards.\n */\nexport function createEventRequestGuard<T extends SignalwireEventRequestBase>(\n  eventType: RegisteredEventType\n): TypeGuard<T> {\n  return (value: unknown): value is T =>\n    isSignalwireRequest(value) && value.params.event_type === eventType;\n}\n\n/**\n * Factory function to create Metadata-level type guards.\n */\nexport function createEventMetadataGuard<T extends SignalwireMetadata>(\n  eventType: RegisteredEventType\n): TypeGuard<T> {\n  return (value: unknown): value is T =>\n    isSignalwireMetadata(value) && value.event_type === eventType;\n}\n\n// =============================================================================\n// SIGNALWIRE EVENT TYPE GUARDS\n// =============================================================================\n\nexport function isSignalwireRequest(value: unknown): value is SignalwireRequest {\n  return (\n    isJSONRPCRequest(value) &&\n    value.method === 'signalwire.event' &&\n    isObject(value.params) &&\n    hasProperty(value.params, 'event_type')\n  );\n}\n\nexport function isSignalwireCallRequest(value: unknown): value is SignalwireCallRequest {\n  return (\n    isSignalwireRequest(value) &&\n    (isCallJoinedRequest(value) ||\n      isCallLeftRequest(value) ||\n      isCallUpdatedRequest(value) ||\n      isCallStateRequest(value) ||\n      isCallPlayRequest(value) ||\n      isCallConnectRequest(value) ||\n      isRoomUpdatedRequest(value) ||\n      isMemberUpdatedRequest(value) ||\n      isMemberJoinedRequest(value) ||\n      isMemberLeftRequest(value) ||\n      isMemberTalkingRequest(value) ||\n      isLayoutChangedRequest(value) ||\n      isWebrtcMessageRequest(value) ||\n      isConversationMessageRequest(value) ||\n      isConversationMessageUpdatedRequest(value))\n  );\n}\n\n// Generated Request guards using factory pattern\nexport const isSignalwireAuthorizationStateRequest =\n  createEventRequestGuard<SignalwireAuthorizationStateRequest>('signalwire.authorization.state');\n\nexport const isWebrtcMessageRequest =\n  createEventRequestGuard<WebrtcMessageRequest>('webrtc.message');\n\nexport const isCallJoinedRequest = createEventRequestGuard<CallJoinedRequest>('call.joined');\n\nexport const isCallLeftRequest = createEventRequestGuard<CallLeftRequest>('call.left');\n\nexport const isCallUpdatedRequest = createEventRequestGuard<CallUpdatedRequest>('call.updated');\n\nexport const isCallStateRequest = createEventRequestGuard<CallStateRequest>('call.state');\n\nexport const isCallPlayRequest = createEventRequestGuard<CallPlayRequest>('call.play');\n\nexport const isCallConnectRequest = createEventRequestGuard<CallConnectRequest>('call.connect');\n\nexport const isRoomUpdatedRequest = createEventRequestGuard<RoomUpdatedRequest>('room.updated');\n\nexport const isMemberUpdatedRequest =\n  createEventRequestGuard<MemberUpdatedRequest>('member.updated');\n\nexport const isMemberJoinedRequest = createEventRequestGuard<MemberJoinedRequest>('member.joined');\n\nexport const isMemberLeftRequest = createEventRequestGuard<MemberLeftRequest>('member.left');\n\nexport const isMemberTalkingRequest =\n  createEventRequestGuard<MemberTalkingRequest>('member.talking');\n\nexport const isLayoutChangedRequest =\n  createEventRequestGuard<LayoutChangedRequest>('layout.changed');\n\nexport const isConversationMessageRequest =\n  createEventRequestGuard<ConversationMessageRequest>('conversation.message');\n\nexport const isConversationMessageUpdatedRequest =\n  createEventRequestGuard<ConversationMessageUpdatedRequest>('conversation.message.updated');\n\n// =============================================================================\n// SIGNALWIRE EVENT METADATA TYPE GUARDS (outer wrapper with event_type)\n// =============================================================================\n\nexport function isSignalwireMetadata(value: unknown): value is SignalwireMetadata {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'event_type') &&\n    typeof value.event_type === 'string' &&\n    hasProperty(value, 'params')\n  );\n}\n\nexport function isSignalwireCallMetadata(value: unknown): value is SignalwireCallMetadata {\n  return (\n    isSignalwireMetadata(value) &&\n    (isCallJoinedMetadata(value) ||\n      isCallLeftMetadata(value) ||\n      isCallUpdatedMetadata(value) ||\n      isCallStateMetadata(value) ||\n      isCallPlayMetadata(value) ||\n      isCallConnectMetadata(value) ||\n      isRoomUpdatedMetadata(value) ||\n      isMemberUpdatedMetadata(value) ||\n      isMemberJoinedMetadata(value) ||\n      isMemberLeftMetadata(value) ||\n      isMemberTalkingMetadata(value) ||\n      isLayoutChangedMetadata(value) ||\n      isWebrtcMessageMetadata(value) ||\n      isConversationMessageMetadata(value) ||\n      isConversationMessageUpdatedMetadata(value))\n  );\n}\n\n// Generated Metadata guards using factory pattern\nexport const isSignalwireAuthorizationStateMetadata = createEventMetadataGuard<\n  ExtractParams<SignalwireAuthorizationStateRequest>\n>('signalwire.authorization.state');\n\nexport const isWebrtcMessageMetadata =\n  createEventMetadataGuard<ExtractParams<WebrtcMessageRequest>>('webrtc.message');\n\nexport const isCallJoinedMetadata =\n  createEventMetadataGuard<ExtractParams<CallJoinedRequest>>('call.joined');\n\nexport const isCallLeftMetadata =\n  createEventMetadataGuard<ExtractParams<CallLeftRequest>>('call.left');\n\nexport const isCallUpdatedMetadata =\n  createEventMetadataGuard<ExtractParams<CallUpdatedRequest>>('call.updated');\n\nexport const isCallStateMetadata =\n  createEventMetadataGuard<ExtractParams<CallStateRequest>>('call.state');\n\nexport const isCallPlayMetadata =\n  createEventMetadataGuard<ExtractParams<CallPlayRequest>>('call.play');\n\nexport const isCallConnectMetadata =\n  createEventMetadataGuard<ExtractParams<CallConnectRequest>>('call.connect');\n\nexport const isRoomUpdatedMetadata =\n  createEventMetadataGuard<ExtractParams<RoomUpdatedRequest>>('room.updated');\n\nexport const isMemberUpdatedMetadata =\n  createEventMetadataGuard<ExtractParams<MemberUpdatedRequest>>('member.updated');\n\nexport const isMemberJoinedMetadata =\n  createEventMetadataGuard<ExtractParams<MemberJoinedRequest>>('member.joined');\n\nexport const isMemberLeftMetadata =\n  createEventMetadataGuard<ExtractParams<MemberLeftRequest>>('member.left');\n\nexport const isMemberTalkingMetadata =\n  createEventMetadataGuard<ExtractParams<MemberTalkingRequest>>('member.talking');\n\nexport const isLayoutChangedMetadata =\n  createEventMetadataGuard<ExtractParams<LayoutChangedRequest>>('layout.changed');\n\nexport const isConversationMessageMetadata =\n  createEventMetadataGuard<ExtractParams<ConversationMessageRequest>>('conversation.message');\n\nexport const isConversationMessageUpdatedMetadata = createEventMetadataGuard<\n  ExtractParams<ConversationMessageUpdatedRequest>\n>('conversation.message.updated');\n\n// =============================================================================\n// EVENT PAYLOAD TYPE GUARDS\n// =============================================================================\n\nexport function isSignalwireAuthorizationStatePayload(\n  value: unknown\n): value is SignalwireAuthorizationStatePayload {\n  return isObject(value) && hasProperty(value, 'authorization_state');\n}\n\nexport function isCallJoinedPayload(value: unknown): value is CallJoinedPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'room_session') &&\n    hasProperty(value, 'call_id') &&\n    hasProperty(value, 'member_id') &&\n    hasProperty(value, 'capabilities')\n  );\n}\n\nexport function isCallLeftPayload(value: unknown): value is CallLeftPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'room_session') &&\n    hasProperty(value, 'call_id') &&\n    hasProperty(value, 'reason')\n  );\n}\n\n/**\n * Payload-level guard for MemberUpdatedPayload.\n *\n * NOTE: This guard uses negative checks (`!hasProperty(value, 'reason')` and\n * `!hasProperty(member, 'talking')`) to distinguish from MemberLeftPayload and\n * MemberTalkingPayload, which share the same base shape (member, room_id,\n * room_session_id). These negative checks are fragile — if MemberUpdatedPayload\n * ever gains a `reason` or `talking` field, this guard will break.\n *\n * Prefer the metadata-level guard `isMemberUpdatedMetadata` which checks\n * `event_type === 'member.updated'` and is not susceptible to payload shape\n * overlap.\n */\nexport function isMemberUpdatedPayload(value: unknown): value is MemberUpdatedPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'member') &&\n    hasProperty(value, 'room_id') &&\n    hasProperty(value, 'room_session_id') &&\n    !hasProperty(value, 'reason') &&\n    !hasProperty((value as MemberUpdatedPayload).member, 'talking')\n  );\n}\n\nexport function isMemberJoinedPayload(value: unknown): value is MemberJoinedPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'member') &&\n    hasProperty(value, 'room_id') &&\n    !hasProperty(value, 'reason')\n  );\n}\n\nexport function isMemberLeftPayload(value: unknown): value is MemberLeftPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'member') &&\n    hasProperty(value, 'room_id') &&\n    hasProperty(value, 'reason')\n  );\n}\n\nexport function isMemberTalkingPayload(value: unknown): value is MemberTalkingPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'member') &&\n    hasProperty(value, 'room_id') &&\n    hasProperty(value, 'room_session_id') &&\n    !hasProperty(value, 'reason') &&\n    hasProperty((value as MemberTalkingPayload).member, 'talking')\n  );\n}\n\nexport function isLayoutChangedPayload(value: unknown): value is LayoutChangedPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'room_id') &&\n    hasProperty(value, 'room_session_id') &&\n    hasProperty(value, 'layout')\n  );\n}\n\nexport function isCallUpdatedPayload(value: unknown): value is CallUpdatedPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'room_session') &&\n    hasProperty(value, 'room_id') &&\n    hasProperty(value, 'room_session_id')\n  );\n}\n\n/**\n * Payload-level guard for RoomUpdatedPayload.\n *\n * NOTE: RoomUpdatedPayload is structurally identical to CallUpdatedPayload\n * (both have `room_session`, `room_id`, `room_session_id`). This guard cannot\n * distinguish between them at the payload level.\n *\n * Prefer the metadata-level guard `isRoomUpdatedMetadata` which checks\n * `event_type === 'room.updated'` and is not susceptible to payload shape overlap.\n */\nexport function isRoomUpdatedPayload(value: unknown): value is RoomUpdatedPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'room_session') &&\n    hasProperty(value, 'room_id') &&\n    hasProperty(value, 'room_session_id')\n  );\n}\n\nexport function isCallStatePayload(value: unknown): value is CallStatePayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'call_id') &&\n    hasProperty(value, 'call_state') &&\n    hasProperty(value, 'direction')\n  );\n}\n\nexport function isCallPlayPayload(value: unknown): value is CallPlayPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'control_id') &&\n    hasProperty(value, 'call_id') &&\n    hasProperty(value, 'state')\n  );\n}\n\nexport function isCallConnectPayload(value: unknown): value is CallConnectPayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'connect_state') &&\n    hasProperty(value, 'call_id') &&\n    hasProperty(value, 'segment_id')\n  );\n}\n\nexport function isConversationMessagePayload(value: unknown): value is ConversationMessagePayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'id') &&\n    hasProperty(value, 'type') &&\n    hasProperty(value, 'kind') &&\n    hasProperty(value, 'conversation_name')\n  );\n}\n\nexport function isConversationMessageUpdatedPayload(\n  value: unknown\n): value is ConversationMessageUpdatedPayload {\n  return isConversationMessagePayload(value);\n}\n\nexport function isWebrtcMessageEventInnerParams(value: unknown): value is WebrtcMessagePayload {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'jsonrpc') &&\n    value.jsonrpc === '2.0' &&\n    hasProperty(value, 'id') &&\n    hasProperty(value, 'method') &&\n    typeof value.method === 'string'\n  );\n}\n\n// =============================================================================\n// EVENT TYPE MAPPING\n// =============================================================================\n\nexport const EventTypeMap = {\n  'signalwire.authorization.state': isSignalwireAuthorizationStateRequest,\n  'webrtc.message': isWebrtcMessageRequest,\n  'call.joined': isCallJoinedRequest,\n  'call.left': isCallLeftRequest,\n  'call.updated': isCallUpdatedRequest,\n  'call.state': isCallStateRequest,\n  'call.play': isCallPlayRequest,\n  'call.connect': isCallConnectRequest,\n  'room.updated': isRoomUpdatedRequest,\n  'member.updated': isMemberUpdatedRequest,\n  'member.joined': isMemberJoinedRequest,\n  'member.left': isMemberLeftRequest,\n  'member.talking': isMemberTalkingRequest,\n  'layout.changed': isLayoutChangedRequest,\n  'conversation.message': isConversationMessageRequest,\n  'conversation.message.updated': isConversationMessageUpdatedRequest\n} as const;\n\nexport type EventType = keyof typeof EventTypeMap;\n\n/**\n * Gets the appropriate type guard for an event type.\n */\nexport function getEventGuard(eventType: string): TypeGuard<SignalwireRequest> | undefined {\n  return EventTypeMap[eventType as EventType];\n}\n\n// =============================================================================\n// EVENT METADATA TYPE MAPPING\n// =============================================================================\n\nexport const EventMetadataTypeMap = {\n  'signalwire.authorization.state': isSignalwireAuthorizationStateMetadata,\n  'webrtc.message': isWebrtcMessageMetadata,\n  'call.joined': isCallJoinedMetadata,\n  'call.left': isCallLeftMetadata,\n  'call.updated': isCallUpdatedMetadata,\n  'call.state': isCallStateMetadata,\n  'call.play': isCallPlayMetadata,\n  'call.connect': isCallConnectMetadata,\n  'room.updated': isRoomUpdatedMetadata,\n  'member.updated': isMemberUpdatedMetadata,\n  'member.joined': isMemberJoinedMetadata,\n  'member.left': isMemberLeftMetadata,\n  'member.talking': isMemberTalkingMetadata,\n  'layout.changed': isLayoutChangedMetadata,\n  'conversation.message': isConversationMessageMetadata,\n  'conversation.message.updated': isConversationMessageUpdatedMetadata\n} as const;\n\n/**\n * Gets the appropriate metadata type guard for an event type.\n */\nexport function getEventMetadataGuard(\n  eventType: string\n): TypeGuard<SignalwireMetadata> | undefined {\n  return EventMetadataTypeMap[eventType as EventType];\n}\n","import { distinctUntilChanged, filter, map, merge, tap } from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { isSelfParticipant } from '../core/entities/Participant';\nimport {\n  isCallJoinedPayload,\n  isLayoutChangedPayload\n} from '../core/RPCMessages/guards/events.guards';\nimport { filterAs } from '../operators';\nimport { filterNull } from '../operators/filterNull';\nimport { getLogger } from '../utils/logger';\n\nimport type { Participant, SelfParticipant } from '../core/entities/Participant';\nimport type {\n  CallManager,\n  CallParticipant,\n  CallSelfParticipant\n} from '../core/entities/types/call.types';\nimport type {\n  LayoutLayer,\n  Member,\n  RoomSession,\n  Layout,\n  MemberTalkingInfo\n} from '../core/RPCMessages/types/common';\nimport type {\n  CallLayoutListResponse,\n  CallLayoutListResult\n} from '../core/RPCMessages/types/methods';\nimport type { Capability } from '../core/types/call.types';\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\ntype SessionState = RoomSession & { capabilities: Capability[] } & {\n  layouts: string[];\n} & { layout_layers: LayoutLayer[] };\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface WebRTCCallEventManagerOptions {}\n\nconst initialSessionState: Partial<SessionState> = {};\n\n/** @internal */\nexport class CallEventsManager extends Destroyable {\n  private selfId?: string;\n  private originCallId?: string;\n  private callIds = new Set<string>();\n  private roomSessionIds = new Set<string>();\n  private _participants$ = this.createBehaviorSubject<Record<string, Participant>>({});\n\n  private _self$ = this.createBehaviorSubject<SelfParticipant | null>(null);\n  private _sessionState$ = this.createBehaviorSubject<Partial<SessionState>>(initialSessionState);\n\n  constructor(\n    protected webRtcCallSession: CallManager,\n    protected options: WebRTCCallEventManagerOptions = {}\n  ) {\n    super();\n    this.initSubscriptions();\n  }\n  public get participants$(): Observable<CallParticipant[]> {\n    return this.cachedObservable('participants$', () =>\n      this._participants$\n        .asObservable()\n        .pipe(map((participantsRecord) => Object.values(participantsRecord)))\n    );\n  }\n\n  public get participants(): CallParticipant[] {\n    return Object.values(this._participants$.value);\n  }\n\n  public get self$(): Observable<CallSelfParticipant> {\n    return this.cachedObservable('self$', () => this._self$.asObservable().pipe(filterNull()));\n  }\n\n  // check if this call session has joined that same room session\n  public isRoomSessionIdValid(roomSessionId: string): boolean {\n    return this.roomSessionIds.has(roomSessionId);\n  }\n\n  public addCallId(callId: string): void {\n    this.callIds.add(callId);\n  }\n\n  public isCallIdValid(callId: string): boolean {\n    return this.callIds.has(callId);\n  }\n\n  public get recording$(): Observable<boolean> {\n    return this.cachedObservable('recording$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.recording),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get recordings$(): Observable<Record<string, unknown>[]> {\n    return this.cachedObservable('recordings$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.recordings),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get streaming$(): Observable<boolean> {\n    return this.cachedObservable('streaming$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.streaming),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get streams$(): Observable<Record<string, unknown>[]> {\n    return this.cachedObservable('streams$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.streams),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get playbacks$(): Observable<Record<string, unknown>[]> {\n    return this.cachedObservable('playbacks$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.playbacks),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get raiseHandPriority$(): Observable<boolean> {\n    return this.cachedObservable('raiseHandPriority$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.prioritize_handraise),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get locked$(): Observable<boolean> {\n    return this.cachedObservable('locked$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.locked),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get meta$(): Observable<Record<string, unknown>> {\n    return this.cachedObservable('meta$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.meta),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get capabilities$(): Observable<Capability[]> {\n    return this.cachedObservable('capabilities$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.capabilities),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get layout$(): Observable<string> {\n    return this.cachedObservable('layout$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.layout_name),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get layouts$(): Observable<string[]> {\n    return this.cachedObservable('layouts$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.layouts),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get layoutLayers$(): Observable<LayoutLayer[]> {\n    return this.cachedObservable('layoutLayers$', () =>\n      this._sessionState$.pipe(\n        map((state) => state.layout_layers),\n        distinctUntilChanged(),\n        filterNull()\n      )\n    );\n  }\n\n  public get self(): CallSelfParticipant | null {\n    return this._self$.value;\n  }\n\n  public get layoutLayers(): LayoutLayer[] {\n    return this._sessionState$.value.layout_layers ?? [];\n  }\n\n  public get recording(): boolean {\n    return this._sessionState$.value.recording ?? false;\n  }\n\n  public get streaming(): boolean {\n    return this._sessionState$.value.streaming ?? false;\n  }\n\n  public get raiseHandPriority(): boolean {\n    return this._sessionState$.value.prioritize_handraise ?? false;\n  }\n\n  public get locked(): boolean {\n    return this._sessionState$.value.locked ?? false;\n  }\n\n  public get meta(): Record<string, unknown> {\n    return this._sessionState$.value.meta ?? {};\n  }\n\n  public get layout(): string | undefined {\n    return this._sessionState$.value.layout_name;\n  }\n\n  public get layouts(): string[] {\n    return this._sessionState$.value.layouts ?? [];\n  }\n\n  public get capabilities(): Capability[] {\n    return this._sessionState$.value.capabilities ?? [];\n  }\n\n  public isSessionEvent(id: string): boolean {\n    return this.callIds.has(id) || this.roomSessionIds.has(id);\n  }\n\n  protected initSubscriptions(): void {\n    this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {\n      logger.debug('[CallEventsManager] Handling call.joined event for call/session IDs:', {\n        callId: callJoinedEvent.call_id,\n        roomSessionId: callJoinedEvent.room_session_id\n      });\n      const sessionState = callJoinedEvent.room_session;\n      const capabilities = callJoinedEvent.capabilities as Capability[];\n      // Don't update selfId and originCallId from nested call.joined events\n      this.selfId = this.selfId ?? callJoinedEvent.member_id;\n      this.originCallId = this.originCallId ?? callJoinedEvent.origin_call_id;\n      this.callIds.add(callJoinedEvent.call_id);\n      this.roomSessionIds.add(callJoinedEvent.room_session_id);\n\n      this._sessionState$.next({\n        ...this._sessionState$.value,\n        recording: sessionState.recording,\n        recordings: sessionState.recordings,\n        streaming: sessionState.streaming,\n        streams: sessionState.streams,\n        playbacks: sessionState.playbacks,\n\n        prioritize_handraise: sessionState.prioritize_handraise,\n        locked: sessionState.locked,\n        meta: sessionState.meta,\n\n        capabilities\n      });\n\n      this.updateParticipants(sessionState.members);\n\n      // Update capabilities on the self participant\n      // This must happen after updateParticipants to ensure self exists\n      this._self$.value?.capabilities.updateFromRaw(capabilities);\n\n      if (this._self$.value?.capabilities.setLayout) {\n        this.updateLayouts();\n      }\n    });\n    this.subscribeTo(this.memberUpdates$, (member) => {\n      logger.debug('[CallEventsManager] Handling member update event for member ID:', member);\n      this.upsertParticipant(member);\n    });\n    this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {\n      logger.debug(\n        '[CallEventsManager] Handling member.left event for member ID:',\n        memberLeftEvent.member.member_id\n      );\n      const participants = { ...this._participants$.value };\n      if (memberLeftEvent.member.member_id in participants) {\n        delete participants[memberLeftEvent.member.member_id];\n        // in case in subscription is observing the participants collection\n        this._participants$.next(participants);\n      } else {\n        logger.warn(\n          `[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`\n        );\n      }\n    });\n    this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {\n      logger.debug('[CallEventsManager] Handling call.updated event:', callUpdatedEvent);\n      const roomSession = callUpdatedEvent.room_session;\n\n      this._sessionState$.next({\n        ...this._sessionState$.value,\n        recording: roomSession.recording,\n        recordings: roomSession.recordings,\n        streaming: roomSession.streaming,\n        streams: roomSession.streams,\n        playbacks: roomSession.playbacks,\n        prioritize_handraise: roomSession.prioritize_handraise,\n        locked: roomSession.locked,\n        meta: roomSession.meta\n      });\n    });\n    this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {\n      logger.debug('[CallEventsManager] Handling layout.changed event:', layoutChangedEvent);\n      this._sessionState$.next({\n        ...this._sessionState$.value,\n\n        layout_name: layoutChangedEvent.id,\n\n        layout_layers: layoutChangedEvent.layers\n      });\n\n      this.updateParticipantPositions(layoutChangedEvent);\n    });\n  }\n  private updateParticipantPositions(layoutChangedEvent: Layout) {\n    if (\n      Object.keys(this._participants$.value).length > 0 &&\n      !layoutChangedEvent.layers.some((layer) => !!layer.member_id)\n    ) {\n      logger.warn(\n        '[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.'\n      );\n    }\n    layoutChangedEvent.layers\n      .filter((layer): layer is LayoutLayer & { member_id: string } => !!layer.member_id)\n      .filter((layer) => {\n        if (!(layer.member_id in this._participants$.value)) {\n          logger.warn(\n            `[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`\n          );\n          return false;\n        }\n        return true;\n      })\n      .map((layer) => {\n        // update participant state with new position\n        this._participants$.value[layer.member_id].upnext({\n          position: layer\n        });\n        return this._participants$.value[layer.member_id];\n      })\n      .forEach((participant) => {\n        if (isSelfParticipant(participant)) {\n          this._self$.next(participant);\n        }\n        // update the collection observable to notify changes\n        this._participants$.next({\n          ...this._participants$.value,\n          [participant.id]: participant\n        });\n      });\n  }\n\n  updateLayouts(): void {\n    if (!this.selfId) return;\n\n    this.webRtcCallSession\n      .executeMethod<CallLayoutListResponse>(this.selfId, 'call.layout.list', {})\n      .then((response) => {\n        // Both transports resolve the method payload under `.result` by the time\n        // it reaches here: the routed transport does so directly, and the in-dialog\n        // transport already unwrapped its verto envelope in Call.sendCommand. Do NOT\n        // unwrap again — a second pass would over-unwrap any future payload that\n        // itself carries a `result` key.\n        const layouts = (response.result as CallLayoutListResult | undefined)?.layouts;\n        if (!layouts) {\n          // A malformed or not-fully-unwrapped reply would blank the list behind a\n          // resolved request (an empty dropdown, no error). Keep the current\n          // layouts and surface the anomaly instead.\n          logger.warn(\n            '[CallEventsManager] Layout list response carried no layouts; keeping current layouts'\n          );\n          return;\n        }\n        this._sessionState$.next({\n          ...this._sessionState$.value,\n          layouts\n        });\n      })\n      .catch((error) => {\n        logger.error('[CallEventsManager] Error fetching layouts:', error);\n      });\n  }\n\n  private updateParticipants(members: Member[]) {\n    members.forEach((member) => this.upsertParticipant(member));\n  }\n\n  private upsertParticipant(member: Member | MemberTalkingInfo) {\n    if (!(member.member_id in this._participants$.value)) {\n      // Pass selfId from call.joined event to ensure correct self participant identification\n      const newParticipant = this.webRtcCallSession.createParticipant(\n        member.member_id,\n        this.selfId\n      );\n\n      this._participants$.next({\n        ...this._participants$.value,\n        [member.member_id]: newParticipant\n      });\n    }\n\n    const participant = this._participants$.value[member.member_id];\n\n    const oldValue = participant.value;\n    logger.debug('[CallEventsManager] Updating participant:', member.member_id, {\n      oldValue,\n      newValue: member\n    });\n    participant.upnext({\n      ...oldValue,\n      ...member\n    });\n\n    if (isSelfParticipant(participant)) {\n      this._self$.next(participant);\n    }\n    // in case in subscription is observing the participants collection\n    this._participants$.next(this._participants$.value);\n  }\n\n  private get callJoinedEvent$() {\n    return this.cachedObservable('callJoinedEvent$', () =>\n      this.webRtcCallSession.callEvent$.pipe(\n        filter(isCallJoinedPayload),\n        tap((event) => {\n          logger.debug('[CallEventsManager] Call joined event:', event);\n        })\n      )\n    );\n  }\n\n  private get layoutChangedEvent$() {\n    return this.cachedObservable('layoutChangedEvent$', () =>\n      this.webRtcCallSession.callEvent$.pipe(\n        filterAs(isLayoutChangedPayload, 'layout'),\n        tap((event) => {\n          logger.debug('[CallEventsManager] Layout changed event:', event);\n        })\n      )\n    );\n  }\n\n  private get memberUpdates$() {\n    return this.cachedObservable('memberUpdates$', () =>\n      merge(\n        this.webRtcCallSession.memberJoined$,\n        this.webRtcCallSession.memberUpdated$,\n        this.webRtcCallSession.memberTalking$\n      ).pipe(\n        map((event) => event.member),\n        tap((event) => {\n          logger.debug('[CallEventsManager] Member update event:', event);\n        })\n      )\n    );\n  }\n\n  public override destroy(): void {\n    const participants = Object.values(this._participants$.value);\n    participants.forEach((participant) => {\n      participant.destroy();\n    });\n    this._participants$.next({});\n    this._self$.next(null);\n\n    this.callIds.clear();\n    this.roomSessionIds.clear();\n    this.selfId = undefined;\n    this.originCallId = undefined;\n    //@ts-expect-error -- avoiding circular references\n    this.webRtcCallSession = undefined;\n    //@ts-expect-error -- avoiding circular references\n    this.callSession = undefined;\n\n    super.destroy();\n  }\n}\n","/**\n * ConstraintFallbackHelper - Provides getUserMedia with automatic constraint\n * fallback on OverconstrainedError.\n *\n * When a specific device ID is requested, the helper tries progressively\n * looser constraints:\n *   1. `{ deviceId: { exact: deviceId } }` -- exact match\n *   2. `{ deviceId: deviceId }` -- preferred (browser may pick another)\n *   3. `{}` -- no constraint, browser default\n *\n * This prevents stale device IDs from blocking call setup.\n *\n * @see Section 5.8 and Section 11 of the Implementation Guide\n */\n\nimport { getLogger } from '../utils/logger';\n\nimport type { WebRTCMediaDevices } from '../dependencies/interfaces';\n\nconst logger = getLogger();\n\n/** The constraint level that succeeded in getUserMedia. */\nexport type FallbackLevel = 'exact' | 'preferred' | 'default';\n\n/** Result of a getUserMedia call with fallback information. */\nexport interface GetUserMediaFallbackResult {\n  /** The acquired media stream. */\n  readonly stream: MediaStream;\n  /** Which constraint level succeeded. */\n  readonly fallbackLevel: FallbackLevel;\n}\n\n/**\n * Attempts getUserMedia with progressively looser constraints.\n *\n * The function tries three levels of constraint specificity for the given\n * device kind. Each level is only attempted if the previous one fails with\n * an OverconstrainedError. Non-OverconstrainedError failures (e.g.,\n * NotAllowedError) are thrown immediately without fallback.\n *\n * @param mediaDevices - Anything exposing `getUserMedia` (a full\n *   `WebRTCMediaDevices`, or a shim wrapping one)\n * @param constraints - The full MediaStreamConstraints to use as a base\n * @param kind - Which track kind to apply fallback to ('audio' | 'video')\n * @param deviceId - The device ID to try (if undefined, calls getUserMedia as-is)\n * @returns The stream and the fallback level that succeeded\n * @throws When all fallback levels fail, or when a non-OverconstrainedError occurs\n */\nexport async function getUserMediaWithFallback(\n  mediaDevices: Pick<WebRTCMediaDevices, 'getUserMedia'>,\n  constraints: MediaStreamConstraints,\n  kind: 'audio' | 'video',\n  deviceId?: string\n): Promise<GetUserMediaFallbackResult> {\n  // If no deviceId specified, just call getUserMedia directly\n  if (!deviceId) {\n    const stream = await mediaDevices.getUserMedia(constraints);\n    return { stream, fallbackLevel: 'default' };\n  }\n\n  const baseConstraints = typeof constraints[kind] === 'object' ? constraints[kind] : {};\n\n  // Level 1: exact match\n  try {\n    const exactConstraints: MediaStreamConstraints = {\n      ...constraints,\n      [kind]: {\n        ...baseConstraints,\n        deviceId: { exact: deviceId }\n      }\n    };\n    const stream = await mediaDevices.getUserMedia(exactConstraints);\n    return { stream, fallbackLevel: 'exact' };\n  } catch (error) {\n    if (!isOverconstrainedError(error)) {\n      throw error;\n    }\n    logger.debug(\n      `[ConstraintFallbackHelper] Exact constraint failed for ${kind}, trying preferred`,\n      { deviceId }\n    );\n  }\n\n  // Level 2: preferred match\n  try {\n    const preferredConstraints: MediaStreamConstraints = {\n      ...constraints,\n      [kind]: {\n        ...baseConstraints,\n        deviceId\n      }\n    };\n    const stream = await mediaDevices.getUserMedia(preferredConstraints);\n    return { stream, fallbackLevel: 'preferred' };\n  } catch (error) {\n    if (!isOverconstrainedError(error)) {\n      throw error;\n    }\n    logger.debug(\n      `[ConstraintFallbackHelper] Preferred constraint failed for ${kind}, trying default`,\n      { deviceId }\n    );\n  }\n\n  // Level 3: no device constraint (browser default)\n  try {\n    const defaultConstraints: MediaStreamConstraints = {\n      ...constraints,\n      [kind]: {\n        ...baseConstraints\n      }\n    };\n    // Remove deviceId from constraints if it was in the base\n    if (typeof defaultConstraints[kind] === 'object') {\n      const { deviceId: _removed, ...rest } = defaultConstraints[kind] as Record<string, unknown>;\n      defaultConstraints[kind] = rest as MediaTrackConstraints;\n    }\n\n    const stream = await mediaDevices.getUserMedia(defaultConstraints);\n    logger.warn(`[ConstraintFallbackHelper] Fell back to browser default for ${kind}`, {\n      requestedDeviceId: deviceId\n    });\n    return { stream, fallbackLevel: 'default' };\n  } catch (error) {\n    logger.error(`[ConstraintFallbackHelper] All fallback levels exhausted for ${kind}`, {\n      deviceId,\n      error\n    });\n    throw error;\n  }\n}\n\n/**\n * Checks whether an error is an OverconstrainedError.\n *\n * Browsers may throw either a native OverconstrainedError or a DOMException\n * with a specific name.\n */\nfunction isOverconstrainedError(error: unknown): boolean {\n  if (error instanceof Error) {\n    return error.name === 'OverconstrainedError' || error.name === 'ConstraintNotSatisfiedError';\n  }\n  return false;\n}\n","/**\n * SDPHelper - Utility functions for SDP (Session Description Protocol) parsing and validation.\n *\n * This module provides helper functions to analyze and validate SDP content,\n * particularly for ICE candidate validation in WebRTC connections.\n */\n\nimport { DEFAULT_STEREO_MAX_AVERAGE_BITRATE } from '../core/constants';\n\nimport type { MediaDirections, MediaDirection } from '../core/types/media.types';\n\n/** Valid SDP direction attribute values. */\nconst SDP_DIRECTIONS: ReadonlySet<string> = new Set([\n  'sendrecv',\n  'sendonly',\n  'recvonly',\n  'inactive'\n]);\n\n/**\n * Extracts the media directions (audio/video) from an SDP string.\n *\n * Parses each media section (`m=audio` / `m=video`) and reads the `a=` direction\n * attribute (`sendrecv`, `sendonly`, `recvonly`, `inactive`).\n * If no explicit direction attribute is found for a media section, defaults to `sendrecv`\n * per RFC 4566.\n *\n * @param sdp - The SDP string to parse\n * @returns The extracted audio and video directions\n *\n * @example\n * ```typescript\n * const sdp = `v=0\\r\\nm=audio 9 UDP/TLS/RTP/SAVPF 111\\r\\na=sendrecv\\r\\nm=video 9 UDP/TLS/RTP/SAVPF 96\\r\\na=recvonly`;\n * extractMediaDirectionsFromSDP(sdp);\n * // { audio: 'sendrecv', video: 'recvonly' }\n * ```\n */\nexport function extractMediaDirectionsFromSDP(sdp: string): MediaDirections {\n  const result: MediaDirections = {\n    audio: 'inactive',\n    video: 'inactive'\n  };\n\n  if (!sdp) {\n    return result;\n  }\n\n  const lines = sdp.split(/\\r?\\n/);\n  let currentMediaKind: 'audio' | 'video' | null = null;\n  let currentDirection: MediaDirection | null = null;\n\n  for (const line of lines) {\n    if (line.startsWith('m=')) {\n      // Flush direction for previous section\n      if (currentMediaKind) {\n        result[currentMediaKind] = currentDirection ?? 'sendrecv';\n      }\n\n      // Determine new media kind\n      if (line.startsWith('m=audio')) {\n        currentMediaKind = 'audio';\n      } else if (line.startsWith('m=video')) {\n        currentMediaKind = 'video';\n      } else {\n        currentMediaKind = null;\n      }\n      currentDirection = null;\n    } else if (currentMediaKind && line.startsWith('a=')) {\n      const attr = line.substring(2).trim();\n      if (SDP_DIRECTIONS.has(attr)) {\n        currentDirection = attr as MediaDirection;\n      }\n    }\n  }\n\n  // Flush last section\n  if (currentMediaKind) {\n    result[currentMediaKind] = currentDirection ?? 'sendrecv';\n  }\n\n  return result;\n}\n\n/**\n * Validates that an SDP string has at least one non-host ICE candidate\n * for each media section (m= line).\n *\n * Non-host candidates (srflx, prflx, relay) indicate that the SDP has\n * gathered candidates that can be used for connectivity through NAT\n * traversal mechanisms.\n *\n * @param sdp - The SDP string to validate\n * @returns true if the SDP is valid (has non-host candidates for all media sections,\n *          or has no media sections), false otherwise\n *\n * @example\n * ```typescript\n * const sdp = `v=0\n * m=audio 9 UDP/TLS/RTP/SAVPF 111\n * a=candidate:1 1 UDP 1694498815 203.0.113.1 50001 typ srflx\n * m=video 9 UDP/TLS/RTP/SAVPF 96\n * a=candidate:2 1 UDP 1694498815 203.0.113.1 50002 typ relay`;\n *\n * isValidLocalDescription(sdp); // returns true\n * ```\n */\nexport function isValidLocalDescription(sdp: string): boolean {\n  if (!sdp) {\n    return false;\n  }\n\n  // Parse SDP to find media sections (m= lines)\n  const lines = sdp.split('\\r\\n');\n  const mediaSectionsValidCandidates: number[] = [];\n  let currentSection = -1;\n\n  for (const line of lines) {\n    if (line.startsWith('m=')) {\n      // New media section\n      currentSection += 1;\n      mediaSectionsValidCandidates[currentSection] = 0;\n    } else if (line.startsWith('a=candidate:')) {\n      const typeMatch = /\\styp\\s+(host|srflx|prflx|relay)/.exec(line);\n      if (typeMatch && typeMatch[1] !== 'host') {\n        // count only non-host candidates\n        mediaSectionsValidCandidates[currentSection] += 1;\n      }\n    }\n  }\n\n  return (\n    !mediaSectionsValidCandidates.length ||\n    // Check if localDescription has at least one non-host candidate for each media section.\n    mediaSectionsValidCandidates.every((count) => count > 0)\n  );\n}\n\n// =============================================================================\n// CODEC REORDERING (Section 23)\n// =============================================================================\n\n/**\n * Reorders codec payload types in an SDP m= line to match the preferred order.\n *\n * For each media section (`m=audio` / `m=video`), this function parses the\n * `a=rtpmap` lines to build a payload-type-to-codec-name map, then rewrites\n * the m= line so preferred codecs appear first.\n *\n * Codecs not in the preferred list retain their original relative order after\n * the preferred ones. Preferred codecs that do not appear in the SDP are ignored.\n *\n * @param sdp - The SDP string to modify\n * @param preferredVideo - Preferred video codec names in priority order (e.g., ['VP9', 'VP8'])\n * @param preferredAudio - Preferred audio codec names in priority order (e.g., ['opus'])\n * @returns The modified SDP string with reordered codec payload types\n *\n * @example\n * ```typescript\n * const reordered = reorderCodecs(sdp, ['VP9', 'H264'], ['opus']);\n * ```\n */\nexport function reorderCodecs(\n  sdp: string,\n  preferredVideo: readonly string[] = [],\n  preferredAudio: readonly string[] = []\n): string {\n  if (!sdp || (preferredVideo.length === 0 && preferredAudio.length === 0)) {\n    return sdp;\n  }\n\n  const sections = splitIntoMediaSections(sdp);\n  const result: string[] = [sections[0]]; // session-level section\n\n  for (let i = 1; i < sections.length; i++) {\n    const section = sections[i];\n    const mLine = section.split(/\\r?\\n/)[0];\n\n    if (mLine.startsWith('m=video') && preferredVideo.length > 0) {\n      result.push(reorderSectionCodecs(section, preferredVideo));\n    } else if (mLine.startsWith('m=audio') && preferredAudio.length > 0) {\n      result.push(reorderSectionCodecs(section, preferredAudio));\n    } else {\n      result.push(section);\n    }\n  }\n\n  return result.join('');\n}\n\n/**\n * Adds stereo=1 and sprop-stereo=1 to the Opus fmtp line in the SDP.\n *\n * This enables stereo Opus negotiation for music/podcast use cases.\n * Also sets maxaveragebitrate to accommodate stereo audio (default: 510000 bps).\n *\n * @param sdp - The SDP string to modify\n * @param maxBitrate - Maximum average bitrate in bps (default: 510000)\n * @returns The modified SDP string with stereo Opus parameters\n *\n * @example\n * ```typescript\n * const stereoSdp = enableStereoOpus(sdp);\n * // a=fmtp:111 minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1;maxaveragebitrate=510000\n * ```\n */\nexport function enableStereoOpus(\n  sdp: string,\n  maxBitrate: number = DEFAULT_STEREO_MAX_AVERAGE_BITRATE\n): string {\n  if (!sdp) {\n    return sdp;\n  }\n\n  // Find the Opus payload type from rtpmap\n  const opusPayloadType = findOpusPayloadType(sdp);\n  if (opusPayloadType === null) {\n    return sdp;\n  }\n\n  const lines = sdp.split(/\\r?\\n/);\n  const updatedLines: string[] = [];\n  const fmtpPrefix = `a=fmtp:${opusPayloadType} `;\n\n  let foundFmtp = false;\n\n  for (const line of lines) {\n    if (line.startsWith(fmtpPrefix)) {\n      foundFmtp = true;\n      updatedLines.push(appendStereoParams(line, maxBitrate));\n    } else {\n      updatedLines.push(line);\n    }\n  }\n\n  // If no fmtp line exists for Opus, add one after the rtpmap line\n  if (!foundFmtp) {\n    const rtpmapLine = `a=rtpmap:${opusPayloadType} `;\n    const withFmtp: string[] = [];\n    for (const line of updatedLines) {\n      withFmtp.push(line);\n      if (line.startsWith(rtpmapLine)) {\n        withFmtp.push(\n          `a=fmtp:${opusPayloadType} stereo=1;sprop-stereo=1;maxaveragebitrate=${maxBitrate}`\n        );\n      }\n    }\n    return withFmtp.join('\\r\\n');\n  }\n\n  return updatedLines.join('\\r\\n');\n}\n\n/**\n * Convenience wrapper that applies both codec reordering and stereo Opus.\n *\n * @param sdp - The SDP string to modify\n * @param audioCodecs - Preferred audio codecs in priority order\n * @param videoCodecs - Preferred video codecs in priority order\n * @returns The modified SDP string\n */\nexport function setCodecPreferences(\n  sdp: string,\n  audioCodecs: readonly string[] = [],\n  videoCodecs: readonly string[] = []\n): string {\n  return reorderCodecs(sdp, videoCodecs, audioCodecs);\n}\n\n// =============================================================================\n// INTERNAL HELPERS\n// =============================================================================\n\n/**\n * Splits an SDP string into session-level + per-media sections.\n *\n * The first element is the session-level content (before the first m= line).\n * Subsequent elements each start with an m= line.\n */\nfunction splitIntoMediaSections(sdp: string): string[] {\n  // Split on the boundary just before each m= line.\n  // String.split always returns at least one element, so no fallback needed.\n  return sdp.split(/(?=m=)/);\n}\n\n/**\n * Reorders codecs within a single media section based on preferred names.\n */\nfunction reorderSectionCodecs(section: string, preferredNames: readonly string[]): string {\n  const lines = section.split(/\\r?\\n/);\n  const mLine = lines[0];\n\n  // Parse the m= line: m=<kind> <port> <proto> <payload types...>\n  const mLineParts = mLine.split(' ');\n  if (mLineParts.length < 4) {\n    return section;\n  }\n\n  const payloadTypes = mLineParts.slice(3);\n\n  // Build payload-type-to-codec-name map from a=rtpmap lines\n  const ptToCodec = new Map<string, string>();\n  for (const line of lines) {\n    const match = /^a=rtpmap:(\\d+)\\s+([^\\s/]+)/.exec(line);\n    if (match) {\n      ptToCodec.set(match[1], match[2]);\n    }\n  }\n\n  // Build the preferred list: PTs whose codec name matches a preferred name\n  const preferredNamesUpper = preferredNames.map((n) => n.toUpperCase());\n  const preferred: string[] = [];\n  const remaining: string[] = [];\n\n  for (const pt of payloadTypes) {\n    const codecName = ptToCodec.get(pt)?.toUpperCase() ?? '';\n    const preferIndex = preferredNamesUpper.indexOf(codecName);\n    if (preferIndex >= 0) {\n      preferred.push(pt);\n    } else {\n      remaining.push(pt);\n    }\n  }\n\n  // Sort preferred PTs by the order in preferredNames\n  preferred.sort((a, b) => {\n    const nameA = ptToCodec.get(a)?.toUpperCase() ?? '';\n    const nameB = ptToCodec.get(b)?.toUpperCase() ?? '';\n    return preferredNamesUpper.indexOf(nameA) - preferredNamesUpper.indexOf(nameB);\n  });\n\n  const reorderedPTs = [...preferred, ...remaining];\n  const newMLine = [...mLineParts.slice(0, 3), ...reorderedPTs].join(' ');\n\n  // Replace the m= line in the section\n  const newLines = [newMLine, ...lines.slice(1)];\n  return newLines.join('\\r\\n');\n}\n\n/**\n * Finds the Opus payload type number from a=rtpmap lines in the SDP.\n *\n * @returns The payload type number as a string, or null if Opus is not found\n */\nfunction findOpusPayloadType(sdp: string): string | null {\n  const match = /a=rtpmap:(\\d+)\\s+opus\\/48000/i.exec(sdp);\n  return match ? match[1] : null;\n}\n\n/**\n * Appends stereo and bitrate parameters to an existing Opus fmtp line.\n *\n * Avoids duplicating parameters if they already exist.\n */\nfunction appendStereoParams(fmtpLine: string, maxBitrate: number): string {\n  let result = fmtpLine;\n\n  if (!result.includes('stereo=')) {\n    result += ';stereo=1';\n  }\n  if (!result.includes('sprop-stereo=')) {\n    result += ';sprop-stereo=1';\n  }\n  if (!result.includes('maxaveragebitrate=')) {\n    result += `;maxaveragebitrate=${maxBitrate}`;\n  }\n\n  return result;\n}\n","import { filter, map, withLatestFrom } from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport {\n  DEFAULT_ICE_CANDIDATE_TIMEOUT_MS,\n  DEFAULT_ICE_GATHERING_TIMEOUT_MS\n} from '../core/constants';\nimport { isValidLocalDescription } from '../helpers/SDPHelper';\nimport { getLogger } from '../utils/logger';\n\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\nexport interface ICEGatheringControllerOptions {\n  iceCandidateTimeout?: number;\n  iceGatheringTimeout?: number;\n  relayOnly?: boolean;\n}\n\nexport type ICEGatheringStates = 'new' | 'gathering' | 'complete' | 'timeout';\nexport interface ICECandidateState {\n  state: ICEGatheringStates;\n  validSDP: boolean;\n}\n\nexport class ICEGatheringController extends Destroyable {\n  private iceCandidateTimeout: number;\n  private iceGatheringTimeout: number;\n  private iceCandidateTimer?: ReturnType<typeof setTimeout>;\n  private iceGatheringTimer?: ReturnType<typeof setTimeout>;\n  private relayOnly: boolean;\n\n  // Event handlers (bound to this instance)\n  private onicegatheringstatechangeHandler = () => {\n    const { iceGatheringState } = this.peerConnection;\n    logger.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);\n    if (iceGatheringState === 'gathering') {\n      this._iceCandidatesState.next({\n        state: 'gathering',\n        validSDP: false\n      });\n    }\n  };\n\n  private onicecandidateHandler = (event: RTCPeerConnectionIceEvent) => {\n    logger.debug('[ICEGatheringController] ICE candidate event received:', event.candidate);\n    this.removeTimer('iceCandidateTimer');\n\n    if (event.candidate) {\n      this.iceCandidateTimer = setTimeout(() => {\n        if (this.peerConnection.iceGatheringState !== 'complete') {\n          logger.warn('[ICEGatheringController] ICE candidate timeout, using current SDP');\n          this.handleICECandidateTimeout();\n        }\n      }, this.iceCandidateTimeout);\n    } else {\n      logger.debug('[ICEGatheringController] ICE gathering completed: null candidate received');\n      this.removeTimer('iceGatheringTimer');\n\n      this.handleICEGatheringComplete();\n    }\n  };\n\n  private _iceCandidatesState = this.createBehaviorSubject<ICECandidateState>({\n    state: 'new',\n    validSDP: false\n  });\n  constructor(\n    private peerConnection: RTCPeerConnection,\n    private peerConnectionControllerNegotiating$: Observable<boolean>,\n    options: ICEGatheringControllerOptions = {}\n  ) {\n    super();\n    this.iceCandidateTimeout = options.iceCandidateTimeout ?? DEFAULT_ICE_CANDIDATE_TIMEOUT_MS;\n    this.iceGatheringTimeout = options.iceGatheringTimeout ?? DEFAULT_ICE_GATHERING_TIMEOUT_MS;\n    this.relayOnly = options.relayOnly ?? false;\n    // Setup ICE candidate handling\n    this.setupEventListeners();\n    this.subscribeTo(\n      this.peerConnectionControllerNegotiating$.pipe(filter((isNegotiating) => isNegotiating)),\n      (isNegotiating) => {\n        if (isNegotiating) {\n          this.setupEventListeners();\n          this.iceGatheringTimer = setTimeout(() => {\n            if (this.peerConnection.iceGatheringState !== 'complete') {\n              logger.warn('[ICEGatheringController] ICE gathering timeout, using current SDP');\n              this.handleICEGatheringTimeout();\n            }\n          }, this.iceGatheringTimeout);\n        }\n      }\n    );\n  }\n\n  private setupEventListeners() {\n    this.peerConnection.removeEventListener('icecandidate', this.onicecandidateHandler);\n    this.peerConnection.addEventListener('icecandidate', this.onicecandidateHandler);\n\n    // Setup ICE gathering state change handling\n    this.peerConnection.removeEventListener(\n      'icegatheringstatechange',\n      this.onicegatheringstatechangeHandler\n    );\n    this.peerConnection.addEventListener(\n      'icegatheringstatechange',\n      this.onicegatheringstatechangeHandler\n    );\n  }\n\n  public get iceCandidatesState$(): Observable<ICEGatheringStates> {\n    return this._iceCandidatesState.pipe(\n      withLatestFrom(this.peerConnectionControllerNegotiating$),\n      filter(([_, isNegotiating]) => isNegotiating),\n      map(([state, _]) => state.state)\n    );\n  }\n\n  public get hasValidLocalDescriptionSDP(): boolean {\n    const sdp = this.peerConnection.localDescription?.sdp;\n    return isValidLocalDescription(sdp ?? '');\n  }\n\n  public get isRelayOnly(): boolean {\n    return this.relayOnly;\n  }\n\n  public setRelayOnly(value: boolean): void {\n    this.relayOnly = value;\n  }\n\n  private handleICEGatheringComplete(): void {\n    logger.debug('[ICEGatheringController] Handling ICE gathering complete');\n\n    logger.debug(\n      `[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`\n    );\n\n    logger.debug('[ICEGatheringController] ICE gathering complete');\n\n    this._iceCandidatesState.next({\n      state: 'complete',\n      validSDP: this.hasValidLocalDescriptionSDP\n    });\n    this.stopGathering();\n  }\n\n  private stopGathering(): void {\n    this.peerConnection.removeEventListener(\n      'icegatheringstatechange',\n      this.onicegatheringstatechangeHandler\n    );\n    this.peerConnection.removeEventListener('icecandidate', this.onicecandidateHandler);\n    this.clearAllTimers();\n  }\n\n  private handleICEGatheringTimeout(): void {\n    this.removeTimer('iceGatheringTimer');\n\n    const validSDP = this.hasValidLocalDescriptionSDP;\n    if (validSDP) {\n      logger.debug('[ICEGatheringController] Local SDP is valid');\n      this._iceCandidatesState.next({\n        state: 'timeout',\n        validSDP: validSDP\n      });\n      this.stopGathering();\n    } else {\n      logger.debug('### ICE gathering timeout\\n', this.peerConnection.localDescription?.sdp);\n    }\n  }\n\n  public handleICECandidateTimeout(): void {\n    if (this.iceCandidateTimer) {\n      this.removeTimer('iceCandidateTimer');\n    }\n\n    logger.warn('[ICEGatheringController] ICE candidate timeout');\n\n    const validSDP = this.hasValidLocalDescriptionSDP;\n    if (!validSDP && !this.relayOnly) {\n      this.restartICEGatheringWithRelayOnly();\n    } else {\n      logger.debug('[ICEGatheringController] Using current SDP due to ICE candidate timeout');\n      this._iceCandidatesState.next({\n        state: 'timeout',\n        validSDP: validSDP\n      });\n      this.stopGathering();\n    }\n  }\n\n  public restartICEGatheringWithRelayOnly(): void {\n    logger.debug('[ICEGatheringController] Restarting ICE gathering with relay-only candidates');\n    this.relayOnly = true;\n    this.peerConnection.setConfiguration({\n      ...this.peerConnection.getConfiguration(),\n      iceTransportPolicy: 'relay'\n    });\n    // Always restart after a policy change — setConfiguration() alone does not\n    // re-gather; only a restart actually engages the new iceTransportPolicy.\n    // Skipping when connectionState === 'connected' left a degraded-but-connected\n    // session stuck with the old (non-relay) candidate set.\n    this.peerConnection.restartIce();\n  }\n\n  public removeTimer(timer: 'iceGatheringTimer' | 'iceCandidateTimer'): void {\n    if (this[timer]) {\n      clearTimeout(this[timer]);\n      this[timer] = undefined;\n    }\n  }\n\n  private clearAllTimers(): void {\n    logger.debug('[ICEGatheringController] Clearing all timers');\n\n    this.removeTimer('iceGatheringTimer');\n    this.removeTimer('iceCandidateTimer');\n  }\n\n  public removeEventListeners(): void {\n    this.peerConnection.removeEventListener(\n      'icegatheringstatechange',\n      this.onicegatheringstatechangeHandler\n    );\n    this.peerConnection.removeEventListener('icecandidate', this.onicecandidateHandler);\n  }\n\n  public destroy(): void {\n    logger.debug('[ICEGatheringController] Destroying ICEGatheringController');\n    this.clearAllTimers();\n    this.removeEventListeners();\n    super.destroy();\n  }\n}\n","import { animationFrameScheduler, distinctUntilChanged, interval, map } from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { AUDIO_LEVEL_POLL_INTERVAL_MS, VAD_HOLD_MS, VAD_THRESHOLD } from '../core/constants';\nimport { getLogger } from '../utils/logger';\n\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\n/**\n * Options for {@link LocalAudioPipeline}.\n */\nexport interface LocalAudioPipelineOptions {\n  /** Factory for AudioContext — override for tests. Defaults to `new AudioContext()`. */\n  audioContextFactory?: () => AudioContext;\n  /** Initial gain (0..2, where 1 is unity). Defaults to 1. */\n  initialGain?: number;\n  /** RMS level [0..1] above which speaking$ emits true. Defaults to {@link VAD_THRESHOLD}. */\n  speakingThreshold?: number;\n  /**\n   * Milliseconds of silence below the threshold before speaking$ flips back to\n   * false. Prevents flicker on normal speech gaps. Defaults to {@link VAD_HOLD_MS}.\n   */\n  speakingHoldMs?: number;\n  /** Polling interval for level$. Defaults to {@link AUDIO_LEVEL_POLL_INTERVAL_MS}. */\n  pollIntervalMs?: number;\n}\n\n/**\n * Web Audio pipeline for the local microphone stream.\n *\n * Wraps the raw mic `MediaStreamTrack` in a graph of:\n *\n * ```\n *   MediaStreamAudioSourceNode  →  GainNode  →  AnalyserNode  →  MediaStreamAudioDestinationNode\n * ```\n *\n * The {@link outputTrack} from the destination node is what callers should\n * attach to the `RTCRtpSender` in place of the raw mic track. The same\n * destination track is reused across input changes (device switch, mute /\n * unmute track replacement) so the sender reference stays stable — only the\n * source end of the graph is rebuilt.\n *\n * The pipeline owns a single {@link AudioContext}. Callers must invoke\n * {@link destroy} to release it when the call ends.\n */\nexport class LocalAudioPipeline extends Destroyable {\n  private readonly _audioContext: AudioContext;\n  private readonly _gainNode: GainNode;\n  private readonly _analyser: AnalyserNode;\n  private readonly _destination: MediaStreamAudioDestinationNode;\n  private readonly _analyserBuffer: Uint8Array<ArrayBuffer>;\n  private readonly _speakingThreshold: number;\n  private readonly _speakingHoldMs: number;\n  private readonly _pollIntervalMs: number;\n\n  private _inputSource: MediaStreamAudioSourceNode | null = null;\n  private _inputStream: MediaStream | null = null;\n  private _lastSpokeAt = 0;\n\n  private _gain$ = this.createBehaviorSubject<number>(1);\n  /** 1 when audio should pass through, 0 when silenced by PTT. */\n  private _pttMultiplier = 1;\n\n  constructor(options: LocalAudioPipelineOptions = {}) {\n    super();\n    const ctxFactory = options.audioContextFactory ?? ((): AudioContext => new AudioContext());\n    this._audioContext = ctxFactory();\n    this._gainNode = this._audioContext.createGain();\n    this._analyser = this._audioContext.createAnalyser();\n    this._analyser.fftSize = 2048;\n    this._analyser.smoothingTimeConstant = 0.3;\n    this._analyserBuffer = new Uint8Array(new ArrayBuffer(this._analyser.fftSize));\n    this._destination = this._audioContext.createMediaStreamDestination();\n\n    this._gainNode.connect(this._analyser);\n    this._analyser.connect(this._destination);\n\n    this._speakingThreshold = options.speakingThreshold ?? VAD_THRESHOLD;\n    this._speakingHoldMs = options.speakingHoldMs ?? VAD_HOLD_MS;\n    this._pollIntervalMs = options.pollIntervalMs ?? AUDIO_LEVEL_POLL_INTERVAL_MS;\n\n    const initial = options.initialGain ?? 1;\n    this._gain$.next(initial);\n    this.applyEffectiveGain();\n  }\n\n  /** Observable of the current gain value (0..2). */\n  public get gain$(): Observable<number> {\n    return this._gain$.asObservable();\n  }\n\n  /** Current gain value (0..2). */\n  public get gain(): number {\n    return this._gain$.value;\n  }\n\n  /**\n   * Processed output track to attach to the RTCRtpSender. Stable reference\n   * across input changes, so `sender.replaceTrack(pipeline.outputTrack)` only\n   * needs to be called once.\n   */\n  public get outputTrack(): MediaStreamTrack {\n    const [track] = this._destination.stream.getAudioTracks();\n    return track;\n  }\n\n  /**\n   * Root-mean-square audio level of the input signal, 0..1. Emits on a fixed\n   * interval (~30fps by default).\n   */\n  public get level$(): Observable<number> {\n    return this.deferEmission(\n      interval(this._pollIntervalMs, animationFrameScheduler).pipe(map(() => this.computeLevel()))\n    );\n  }\n\n  /**\n   * Boolean VAD derived from {@link level$}. True while level ≥ threshold or\n   * during the hold window after the last frame that crossed the threshold.\n   */\n  public get speaking$(): Observable<boolean> {\n    return this.deferEmission(\n      this.level$.pipe(\n        map((level) => this.evaluateSpeaking(level)),\n        distinctUntilChanged()\n      )\n    );\n  }\n\n  /**\n   * Set gain multiplier applied to the input signal. 0 = silence,\n   * 1 = unity, 2 = 2x. Values are clamped to [0, 2]. The effective gain on\n   * the graph also respects the current PTT state.\n   */\n  public setGain(value: number): void {\n    const clamped = Math.max(0, Math.min(2, value));\n    this._gain$.next(clamped);\n    this.applyEffectiveGain();\n  }\n\n  /**\n   * Silence the graph when `active = false`, otherwise restore the configured\n   * gain. Use this from a PTT handler: released → `false`, held → `true`.\n   * Orthogonal to {@link setGain} — once PTT returns to active, the last\n   * configured gain reappears.\n   */\n  public setPTTActive(active: boolean): void {\n    this._pttMultiplier = active ? 1 : 0;\n    this.applyEffectiveGain();\n  }\n\n  private applyEffectiveGain(): void {\n    this._gainNode.gain.value = this._gain$.value * this._pttMultiplier;\n  }\n\n  /**\n   * Wire a new raw mic track as the pipeline's input. Replaces any previous\n   * input source and reconnects the graph so {@link outputTrack} continues\n   * to emit the processed audio. Pass `null` to disconnect the input (the\n   * output track stays alive but emits silence).\n   *\n   * Also resumes the underlying AudioContext on attach — Chrome creates it\n   * in a suspended state and the graph won't process (the destination\n   * track emits silence) until resume() succeeds.\n   */\n  public setInputTrack(track: MediaStreamTrack | null): void {\n    if (this._inputSource) {\n      try {\n        this._inputSource.disconnect();\n      } catch (error) {\n        logger.debug('[LocalAudioPipeline] input disconnect warning:', error);\n      }\n      this._inputSource = null;\n    }\n    if (this._inputStream) {\n      this._inputStream = null;\n    }\n    if (!track) {\n      return;\n    }\n    this._inputStream = new MediaStream([track]);\n    this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);\n    this._inputSource.connect(this._gainNode);\n    if (this._audioContext.state === 'suspended') {\n      void this._audioContext.resume().catch((error: unknown) => {\n        logger.warn('[LocalAudioPipeline] AudioContext resume failed:', error);\n      });\n    }\n  }\n\n  public override destroy(): void {\n    if (this._inputSource) {\n      try {\n        this._inputSource.disconnect();\n      } catch {\n        // already disconnected\n      }\n      this._inputSource = null;\n    }\n    try {\n      this._gainNode.disconnect();\n      this._analyser.disconnect();\n    } catch {\n      // already disconnected\n    }\n    void this._audioContext.close().catch((error: unknown) => {\n      logger.debug('[LocalAudioPipeline] audio context close warning:', error);\n    });\n    super.destroy();\n  }\n\n  private computeLevel(): number {\n    if (!this._inputSource) {\n      return 0;\n    }\n    this._analyser.getByteTimeDomainData(this._analyserBuffer);\n    let sum = 0;\n    for (const sample of this._analyserBuffer) {\n      // Samples are unsigned 8-bit, centered at 128. Convert to [-1, 1].\n      const normalized = (sample - 128) / 128;\n      sum += normalized * normalized;\n    }\n    return Math.sqrt(sum / this._analyserBuffer.length);\n  }\n\n  private evaluateSpeaking(level: number): boolean {\n    const now = Date.now();\n    if (level >= this._speakingThreshold) {\n      this._lastSpokeAt = now;\n      return true;\n    }\n    return now - this._lastSpokeAt < this._speakingHoldMs;\n  }\n}\n","import { takeUntil } from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { getLogger } from '../utils/logger';\n\nimport type { MediaOptions, TrackOrigin } from '../core/types/media.types';\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\nexport interface LocalStreamControllerOptions extends Omit<\n  MediaOptions,\n  'inputAudioDeviceConstraints' | 'inputVideoDeviceConstraints'\n> {\n  getUserMedia: (constraints: MediaStreamConstraints) => Promise<MediaStream>;\n  getDisplayMedia: (options: DisplayMediaStreamOptions) => Promise<MediaStream>;\n  propose: 'main' | 'screenshare' | 'additional-device';\n  inputAudioDeviceConstraints?: MediaTrackConstraints | boolean;\n  inputVideoDeviceConstraints?: MediaTrackConstraints | boolean;\n  screenShareAudio?: boolean;\n}\n\nexport class LocalStreamController extends Destroyable {\n  private mediaTrackEndedHandler = (event: unknown) => {\n    this._mediaTrackEnded$.next(event as MediaStreamTrack);\n  };\n  private _localStream$ = this.createBehaviorSubject<MediaStream | null>(null);\n  private _localAudioTracks$ = this.createBehaviorSubject<MediaStreamTrack[]>([]);\n  private _localVideoTracks$ = this.createBehaviorSubject<MediaStreamTrack[]>([]);\n  private _mediaTrackEnded$ = this.createSubject<MediaStreamTrack>();\n  /** A lookup, not a subject — nothing observes provenance. */\n  private _trackOrigins = new WeakMap<MediaStreamTrack, TrackOrigin>();\n\n  constructor(private options: LocalStreamControllerOptions) {\n    super();\n  }\n\n  public get localStream$(): Observable<MediaStream | null> {\n    return this._localStream$.asObservable().pipe(takeUntil(this.destroyed$));\n  }\n\n  public get localAudioTracks$(): Observable<MediaStreamTrack[]> {\n    return this._localAudioTracks$.asObservable().pipe(takeUntil(this.destroyed$));\n  }\n\n  public get localVideoTracks$(): Observable<MediaStreamTrack[]> {\n    return this._localVideoTracks$.asObservable().pipe(takeUntil(this.destroyed$));\n  }\n\n  public get mediaTrackEnded$(): Observable<MediaStreamTrack> {\n    return this._mediaTrackEnded$.asObservable().pipe(takeUntil(this.destroyed$));\n  }\n\n  public get localStream(): MediaStream | null {\n    return this._localStream$.value;\n  }\n\n  public get localAudioTracks(): MediaStreamTrack[] {\n    return this._localAudioTracks$.value;\n  }\n\n  public get localVideoTracks(): MediaStreamTrack[] {\n    return this._localVideoTracks$.value;\n  }\n\n  private tagTracks(tracks: MediaStreamTrack[], origin: TrackOrigin): void {\n    for (const track of tracks) {\n      this._trackOrigins.set(track, origin);\n    }\n  }\n\n  public setTrackOrigin(track: MediaStreamTrack, origin: TrackOrigin): void {\n    this._trackOrigins.set(track, origin);\n  }\n\n  public getTrackOrigin(track: MediaStreamTrack): TrackOrigin | undefined {\n    return this._trackOrigins.get(track);\n  }\n\n  /**\n   * Fail-safe: an unrecorded track reads as not-a-device-capture, so a missed\n   * tagging site leaves media alone rather than destroying it.\n   */\n  public isDeviceCapture(track: MediaStreamTrack): boolean {\n    return this._trackOrigins.get(track) === 'device';\n  }\n\n  /**\n   * Build the local media stream based on the provided options.\n   */\n  public async buildLocalStream(): Promise<MediaStream> {\n    logger.debug('[LocalStreamController] Building local media stream.');\n    let stream: MediaStream;\n    if (this.options.inputAudioStream ?? this.options.inputVideoStream) {\n      const tracks = [\n        ...(this.options.inputAudioStream?.getTracks() ?? []),\n        ...(this.options.inputVideoStream?.getTracks() ?? [])\n      ];\n      stream = new MediaStream(tracks);\n      this.tagTracks(tracks, 'application');\n    } else if (this.options.propose === 'screenshare') {\n      const audio = this.options.screenShareAudio ?? false;\n      logger.debug(\n        '[LocalStreamController] Requesting display media for screen sharing with audio:',\n        audio\n      );\n      stream = await this.options.getDisplayMedia({\n        video: true,\n        audio\n      });\n      logger.debug('[LocalStreamController] Screen share media obtained:', stream);\n      this.tagTracks(stream.getTracks(), 'display');\n    } else {\n      const constraints: MediaStreamConstraints = {\n        audio: this.options.inputAudioDeviceConstraints,\n        video: this.options.inputVideoDeviceConstraints\n      };\n      logger.debug('[LocalStreamController] Requesting user media with constraints:', constraints);\n      stream = await this.options.getUserMedia(constraints);\n      logger.debug('[LocalStreamController] User media obtained:', stream);\n      this.tagTracks(stream.getTracks(), 'device');\n    }\n    this._localStream$.next(stream);\n    // Emit the kind-specific track lists so observers (and synchronous\n    // readers of localAudioTracks / localVideoTracks) see the freshly-built\n    // stream's tracks. Without this, the subjects stay [] until a later\n    // addTrack() call — leaving any code that depends on the synchronous\n    // getter (e.g. LocalAudioPipeline track hookup) running against empty\n    // arrays right after call setup.\n    this._localAudioTracks$.next(stream.getAudioTracks());\n    this._localVideoTracks$.next(stream.getVideoTracks());\n    return stream;\n  }\n\n  /**\n   * Add a local media track to the local stream.\n   * @param track - The MediaStreamTrack to add\n   * @param origin - Defaults to `'device'`; every internal caller passes a\n   * fresh `getUserMedia` capture.\n   * @returns The MediaStream (either existing or newly created)\n   */\n  public addTrack(track: MediaStreamTrack, origin: TrackOrigin = 'device'): MediaStream {\n    const localStream = this._localStream$.value ?? new MediaStream();\n\n    this._trackOrigins.set(track, origin);\n    track.addEventListener('ended', this.mediaTrackEndedHandler);\n    localStream.addTrack(track);\n    this._localStream$.next(localStream);\n\n    if (track.kind === 'video') {\n      this._localVideoTracks$.next(localStream.getVideoTracks());\n    } else {\n      this._localAudioTracks$.next(localStream.getAudioTracks());\n    }\n\n    logger.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);\n    return localStream;\n  }\n\n  /**\n   * Remove a local media track from the local stream.\n   * @param trackId - The ID of the track to remove\n   * @returns The removed track, or undefined if not found\n   */\n  public removeTrack(trackId: string): MediaStreamTrack | undefined {\n    const stream = this._localStream$.value;\n    const track = stream?.getTracks().find((t: MediaStreamTrack) => t.id === trackId);\n\n    if (!track) {\n      logger.debug(`[LocalStreamController] track not found: ${trackId}`);\n      return undefined;\n    }\n\n    track.removeEventListener('ended', this.mediaTrackEndedHandler);\n    stream?.removeTrack(track);\n    track.stop();\n    this._localStream$.next(stream);\n\n    if (track.kind === 'video') {\n      this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);\n    } else {\n      this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);\n    }\n\n    logger.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);\n    return track;\n  }\n\n  /**\n   * Get or create a local stream.\n   */\n  public getOrCreateLocalStream(): MediaStream {\n    return this._localStream$.value ?? new MediaStream();\n  }\n\n  /**\n   * Set the local stream directly.\n   */\n  public setLocalStream(stream: MediaStream | null): void {\n    this._localStream$.next(stream);\n  }\n\n  /**\n   * Add a track ended event listener to a track.\n   */\n  public addTrackEndedListener(track: MediaStreamTrack): void {\n    track.addEventListener('ended', this.mediaTrackEndedHandler);\n  }\n\n  /**\n   * Update the controller options (e.g., when media overrides are applied).\n   */\n  public updateOptions(options: Partial<LocalStreamControllerOptions>): void {\n    this.options = {\n      ...this.options,\n      ...options\n    };\n  }\n\n  /**\n   * Stop all local tracks and clean up.\n   */\n  public stopAllTracks(): void {\n    const localStream = this._localStream$.value;\n    localStream?.getTracks().forEach((track: MediaStreamTrack) => {\n      logger.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);\n      track.removeEventListener('ended', this.mediaTrackEndedHandler);\n      track.stop();\n    });\n  }\n\n  /**\n   * Clean up resources.\n   */\n  public override destroy(): void {\n    this.stopAllTracks();\n    super.destroy();\n  }\n}\n","import { getUserMediaWithFallback } from './ConstraintFallbackHelper';\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { MediaTrackError } from '../core/errors';\nimport { getLogger } from '../utils/logger';\n\nimport type { LocalStreamController } from './LocalStreamController';\nimport type { RTCPeerConnectionPropose } from '../core/types/call.types';\n\nconst logger = getLogger();\n\nconst getDirection = (send: boolean, recv: boolean): RTCRtpTransceiverDirection => {\n  if (send && recv) {\n    return 'sendrecv';\n  } else if (send && !recv) {\n    return 'sendonly';\n  } else if (!send && recv) {\n    return 'recvonly';\n  }\n\n  return 'inactive';\n};\n\nexport interface TransceiverControllerOptions {\n  peerConnection: RTCPeerConnection;\n  propose: RTCPeerConnectionPropose;\n  simulcast?: boolean;\n  sfu?: boolean;\n  msStreamsNumber?: number;\n  receiveAudio?: boolean;\n  receiveVideo?: boolean;\n  localStreamController: LocalStreamController;\n  getInputAudioDeviceConstraints: () => MediaTrackConstraints | boolean;\n  getInputVideoDeviceConstraints: () => MediaTrackConstraints | boolean;\n  getUserMedia: (constraints: MediaStreamConstraints) => Promise<MediaStream>;\n  onError?: (error: Error) => void;\n}\n\nexport class TransceiverController extends Destroyable {\n  private peerConnection: RTCPeerConnection;\n  private options: TransceiverControllerOptions;\n\n  constructor(options: TransceiverControllerOptions) {\n    super();\n    this.peerConnection = options.peerConnection;\n    this.options = options;\n  }\n\n  public get useAddTransceivers(): boolean {\n    return typeof this.peerConnection.addTransceiver === 'function';\n  }\n\n  public get useAddTrack(): boolean {\n    return typeof this.peerConnection.addTrack === 'function';\n  }\n\n  public get useAddStream(): boolean {\n    return (\n      // @ts-expect-error -- Ignore ---\n      typeof this.peerConnection.addStream === 'function' &&\n      !this.useAddTransceivers &&\n      !this.useAddTrack\n    );\n  }\n\n  private get propose(): RTCPeerConnectionPropose {\n    return this.options.propose;\n  }\n\n  private get isAdditionalDevice(): boolean {\n    return this.propose === 'additional-device';\n  }\n\n  private get isScreenShare(): boolean {\n    return this.propose === 'screenshare';\n  }\n\n  private get isSimulcast(): boolean {\n    return Boolean(this.options.simulcast);\n  }\n\n  private get isSFU(): boolean {\n    return Boolean(this.options.sfu);\n  }\n\n  private get receiveVideo(): boolean {\n    return Boolean(this.options.receiveVideo);\n  }\n\n  private get receiveAudio(): boolean {\n    return Boolean(this.options.receiveAudio);\n  }\n\n  private get localStream(): MediaStream | null {\n    return this.options.localStreamController.localStream;\n  }\n\n  private get inputAudioDeviceConstraints(): MediaTrackConstraints | boolean {\n    return this.options.getInputAudioDeviceConstraints();\n  }\n\n  private get inputVideoDeviceConstraints(): MediaTrackConstraints | boolean {\n    return this.options.getInputVideoDeviceConstraints();\n  }\n\n  public get audioDirection(): RTCRtpTransceiverDirection {\n    if (this.isAdditionalDevice) {\n      return 'sendonly';\n    }\n    const { localStream } = this;\n    const hasAudioTrack = localStream?.getAudioTracks().some((track) => track.enabled);\n    const wantsToSendAudio = Boolean(this.inputAudioDeviceConstraints);\n    const wantsToReceiveAudio = Boolean(this.receiveAudio);\n    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- .some() returns boolean; ?? doesn't fall through on false\n    const send = hasAudioTrack || wantsToSendAudio;\n    const recv = wantsToReceiveAudio;\n    return getDirection(send, recv);\n  }\n\n  public get videoDirection(): RTCRtpTransceiverDirection {\n    if (this.isAdditionalDevice || this.isScreenShare) {\n      return 'sendonly';\n    }\n\n    if (this.isSFU) {\n      return 'recvonly';\n    }\n\n    const { localStream } = this;\n    const hasVideoTrack = localStream?.getVideoTracks().some((track) => track.enabled);\n    const wantsToSendVideo = Boolean(this.inputVideoDeviceConstraints);\n    const wantsToReceiveVideo = Boolean(this.receiveVideo);\n    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- .some() returns boolean; ?? doesn't fall through on false\n    const send = hasVideoTrack || wantsToSendVideo;\n    const recv = wantsToReceiveVideo;\n    return getDirection(send, recv);\n  }\n\n  private get sendEncodings(): RTCRtpEncodingParameters[] | undefined {\n    if (!this.isSimulcast) {\n      return undefined;\n    }\n\n    return ['0', '1', '2'].map((rid) => ({\n      active: true,\n      rid,\n      scaleResolutionDownBy: Number(rid) * 6 || 1.0\n    }));\n  }\n\n  /**\n   * Resolve the current MediaTrackConstraints for an input kind, normalising\n   * boolean shorthand to an empty object. Public so the surrounding\n   * RTCPeerConnectionController can drive its own pipeline-aware getUserMedia\n   * call with the same effective constraints the transceiver would have used.\n   */\n  public getConstraintsFor(kind: 'audio' | 'video'): MediaTrackConstraints {\n    const constraints =\n      kind === 'audio' ? this.inputAudioDeviceConstraints : this.inputVideoDeviceConstraints;\n\n    // If constraints is a boolean (true/false), return empty object for track constraints\n    // false case means no media of this kind is requested, but that's handled elsewhere\n    return typeof constraints === 'boolean' ? {} : constraints;\n  }\n\n  public transceiverByKind(kind: 'audio' | 'video' | 'both'): RTCRtpTransceiver[] {\n    return this.peerConnection\n      .getTransceivers()\n      .filter((t) => kind === 'both' || t.receiver.track.kind === kind);\n  }\n\n  public get audioTransceivers(): RTCRtpTransceiver[] {\n    return this.transceiverByKind('audio');\n  }\n\n  public get videoTransceivers(): RTCRtpTransceiver[] {\n    return this.transceiverByKind('video');\n  }\n\n  public async setupTransceiverSender(\n    track: MediaStreamTrack,\n    localStream: MediaStream,\n    transceiver?: RTCRtpTransceiver\n  ): Promise<void> {\n    const isAudio = track.kind === 'audio';\n    const direction = isAudio ? this.audioDirection : this.videoDirection;\n    const transceiverParams: RTCRtpTransceiverInit = {\n      direction,\n      sendEncodings: isAudio ? undefined : this.sendEncodings,\n      streams: direction === 'recvonly' ? undefined : [localStream]\n    };\n    logger.debug(\n      `[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`,\n      { transceiver, transceiverParams }\n    );\n    if (\n      transceiverParams.direction &&\n      ['sendonly', 'sendrecv'].includes(transceiverParams.direction)\n    ) {\n      if (transceiver) {\n        await transceiver.sender.replaceTrack(track);\n        // eslint-disable-next-line no-param-reassign\n        transceiver.direction = transceiverParams.direction;\n        if (transceiverParams.streams?.some((stream) => Boolean(stream))) {\n          logger.debug(\n            `[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`,\n            transceiverParams.streams\n          );\n          transceiver.sender.setStreams(...transceiverParams.streams);\n        }\n      } else {\n        logger.debug(\n          `[TransceiverController] Adding new transceiver for local ${track.kind} track:`,\n          track.id\n        );\n        this.peerConnection.addTransceiver(track, transceiverParams);\n      }\n    }\n  }\n\n  public stopTrackSender(\n    kind: 'audio' | 'video' | 'both',\n    options = { updateTransceiverDirection: false }\n  ): void {\n    try {\n      const transceivers = this.transceiverByKind(kind);\n      for (const transceiver of transceivers) {\n        if (transceiver.sender.track?.readyState === 'live') {\n          const trackId = transceiver.sender.track.id;\n          transceiver.sender.track.stop();\n          this.options.localStreamController.removeTrack(trackId);\n          if (options.updateTransceiverDirection) {\n            transceiver.direction = 'inactive';\n          }\n        }\n      }\n    } catch (error) {\n      logger.error('[TransceiverController] stopTrackSender error', kind, error);\n      this.options.onError?.(new MediaTrackError('stopTrackSender', kind, error));\n    }\n  }\n\n  public async restoreTrackSender(kind: 'audio' | 'video' | 'both'): Promise<void> {\n    try {\n      logger.debug('[TransceiverController] restoreTrackSender called', kind);\n      const constraints: MediaStreamConstraints = {};\n      const transceivers = this.transceiverByKind(kind);\n      for (const transceiver of transceivers) {\n        const { track } = transceiver.sender;\n        // Check if track is null, ended - all need restoration\n        const needsRestore = !track || track.readyState === 'ended';\n        if (needsRestore) {\n          const trackKind = track?.kind ?? transceiver.receiver.track.kind;\n          if (trackKind === 'audio' || trackKind === 'video') {\n            constraints[trackKind] = this.getConstraintsFor(trackKind);\n          }\n        }\n      }\n\n      logger.debug('[TransceiverController] restoreTrackSender constraints:', constraints);\n\n      // Don't call getUserMedia if no tracks need restoration\n      if (Object.keys(constraints).length === 0) {\n        logger.warn('[TransceiverController] restoreTrackSender: no tracks need restoration', kind);\n        return;\n      }\n\n      const stream = await this.options.getUserMedia(constraints);\n      const newTracks = stream.getTracks();\n\n      logger.debug('[TransceiverController] restoreTrackSender new tracks:', newTracks);\n      for (const newTrack of newTracks) {\n        this.options.localStreamController.addTrack(newTrack);\n        const trackKind = newTrack.kind as 'audio' | 'video';\n        const transceiverOfKind = this.transceiverByKind(trackKind)[0];\n        transceiverOfKind.direction =\n          trackKind === 'audio' ? this.audioDirection : this.videoDirection;\n        logger.debug(\n          '[TransceiverController] restoreTrackSender setting direction for',\n          trackKind,\n          transceiverOfKind.direction\n        );\n        await transceiverOfKind.sender.replaceTrack(newTrack);\n      }\n    } catch (error) {\n      logger.error('[TransceiverController] restoreTrackSender error', kind, error);\n      this.options.onError?.(new MediaTrackError('restoreTrackSender', kind, error));\n    }\n  }\n\n  public async replaceSenderTrack(kind: 'audio' | 'video', track: MediaStreamTrack): Promise<void> {\n    const transceivers = kind === 'audio' ? this.audioTransceivers : this.videoTransceivers;\n    for (const transceiver of transceivers) {\n      await transceiver.sender.replaceTrack(track);\n    }\n  }\n\n  public async setupRemoteTransceivers(type: 'offer' | 'answer'): Promise<void> {\n    if (type === 'answer') {\n      // remote setup was made by the offerer\n      return;\n    }\n\n    for (const kind of ['audio', 'video']) {\n      const transceivers = kind === 'audio' ? this.audioTransceivers : this.videoTransceivers;\n      for (const transceiver of transceivers) {\n        const direction = kind === 'audio' ? this.audioDirection : this.videoDirection;\n\n        if (['inactive', 'recvonly'].includes(direction)) {\n          transceiver.direction = direction;\n          await transceiver.sender.replaceTrack(null);\n          transceiver.sender.setStreams();\n        }\n      }\n    }\n\n    if (this.videoDirection === 'recvonly' && this.isSFU && this.useAddTransceivers) {\n      const { msStreamsNumber = 5 } = this.options;\n      for (let i = 0; i < Number(msStreamsNumber); i++) {\n        this.peerConnection.addTransceiver('video', { direction: 'recvonly' });\n      }\n    }\n  }\n\n  /**\n   * @returns whether every live sender of the kind took the constraints. A\n   * skipped non-device sender, an exhausted fallback, and having no live sender\n   * at all all report `false` — `mediaParamsUpdated.applied` is built from this,\n   * and an application told `true` cannot tell a working push from a no-op.\n   */\n  public async updateSendersConstraints(\n    kind: 'audio' | 'video',\n    constraints?: MediaTrackConstraints\n  ): Promise<boolean> {\n    if (!constraints) {\n      this.stopTrackSender(kind);\n      return false;\n    }\n\n    const senders = this.peerConnection\n      .getSenders()\n      .filter((sender) => sender.track?.kind === kind && sender.track.readyState === 'live');\n\n    let applied = senders.length > 0;\n\n    for (const sender of senders) {\n      const { track } = sender;\n      if (track) {\n        // Non-device tracks report synthetic deviceIds no getUserMedia can\n        // satisfy, and replacing one would swap out media the SDK does not own.\n        if (!this.options.localStreamController.isDeviceCapture(track)) {\n          logger.debug(\n            `[TransceiverController] Skipping ${kind} constraints for a non-device track ` +\n              `(origin: ${this.options.localStreamController.getTrackOrigin(track) ?? 'unrecorded'}), ` +\n              `track ${track.id}`\n          );\n          applied = false;\n          continue;\n        }\n        const constraintsToApply: MediaTrackConstraints = {\n          ...track.getConstraints(),\n          ...constraints\n        };\n        try {\n          await track.applyConstraints(constraintsToApply);\n          logger.debug(\n            `[TransceiverController] Updated ${kind} sender constraints:`,\n            constraintsToApply\n          );\n          logger.debug(\n            `[TransceiverController] Updated ${kind} sender constraints:`,\n            track.getConstraints()\n          );\n        } catch (error) {\n          logger.warn(\n            `[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`,\n            error\n          );\n          try {\n            await this.replaceTrackFallback(sender, track, kind, constraintsToApply);\n          } catch (fallbackError) {\n            logger.warn(\n              `[TransceiverController] Track replacement fallback also failed for ${kind} track:`,\n              fallbackError\n            );\n            this.options.onError?.(\n              new MediaTrackError('updateSendersConstraints', kind, fallbackError)\n            );\n            applied = false;\n          }\n        }\n      }\n    }\n\n    return applied;\n  }\n\n  /**\n   * Fallback when applyConstraints fails, which on iOS Safari it silently does.\n   *\n   * Order matters: acquiring before stopping means a failed acquisition leaves\n   * the existing media playing. The deviceId goes through the fallback ladder\n   * rather than pinned `{ exact }`, so a stale id degrades instead of failing.\n   */\n  private async replaceTrackFallback(\n    sender: RTCRtpSender,\n    oldTrack: MediaStreamTrack,\n    kind: 'audio' | 'video',\n    mergedConstraints: MediaTrackConstraints\n  ): Promise<void> {\n    const { deviceId } = oldTrack.getSettings();\n\n    // A rejection here must leave the current track sending.\n    const { stream, fallbackLevel } = await getUserMediaWithFallback(\n      { getUserMedia: this.options.getUserMedia },\n      { [kind]: mergedConstraints },\n      kind,\n      deviceId\n    );\n    const newTrack = stream.getTracks().find((t) => t.kind === kind);\n\n    if (!newTrack) {\n      // Nothing was swapped; release only what we just acquired.\n      stream.getTracks().forEach((t) => t.stop());\n      throw new MediaTrackError(\n        'replaceTrackFallback',\n        kind,\n        new Error('getUserMedia returned no track of the requested kind')\n      );\n    }\n\n    try {\n      await sender.replaceTrack(newTrack);\n    } catch (error) {\n      // The leg can be torn down inside the ladder's getUserMedia round-trips.\n      // Nothing holds this capture yet — not the sender, not the local stream —\n      // so no destroy path could reach it and the device would stay open for the\n      // lifetime of the page.\n      stream.getTracks().forEach((t) => t.stop());\n      throw error;\n    }\n    const oldTrackId = oldTrack.id;\n    this.options.localStreamController.removeTrack(oldTrackId);\n    oldTrack.stop();\n    this.options.localStreamController.addTrack(newTrack);\n\n    logger.debug(\n      `[TransceiverController] Track replacement fallback succeeded for ${kind} ` +\n        `(deviceId fallback level: ${fallbackLevel}). New track: ${newTrack.id}`\n    );\n  }\n\n  public getMediaDirections(): {\n    audio: RTCRtpTransceiverDirection;\n    video: RTCRtpTransceiverDirection;\n  } {\n    if (this.peerConnection.connectionState === 'connected') {\n      // If we are connected let's get the actual directions from the transceivers\n      return this.peerConnection.getTransceivers().reduce(\n        (acc, transceiver) => {\n          return {\n            ...acc,\n            [transceiver.receiver.track.kind]: transceiver.direction\n          };\n        },\n        { audio: 'inactive', video: 'inactive' }\n      );\n    }\n\n    return {\n      audio: this.audioDirection,\n      video: this.videoDirection\n    };\n  }\n\n  public updatePeerConnection(peerConnection: RTCPeerConnection): void {\n    this.peerConnection = peerConnection;\n  }\n\n  public updateOptions(options: Partial<TransceiverControllerOptions>): void {\n    this.options = {\n      ...this.options,\n      ...options\n    };\n  }\n}\n","/* eslint-disable max-lines */\nimport {\n  auditTime,\n  defer,\n  exhaustMap,\n  filter,\n  firstValueFrom,\n  from,\n  map,\n  shareReplay,\n  switchMap,\n  take,\n  takeUntil,\n  tap,\n  skipWhile,\n  merge,\n  race,\n  timer\n} from 'rxjs';\nimport { v4 as uuid } from 'uuid';\n\nimport { getUserMediaWithFallback } from './ConstraintFallbackHelper';\nimport { ICEGatheringController } from './ICEGatheringController';\nimport { LocalAudioPipeline } from './LocalAudioPipeline';\nimport { LocalStreamController } from './LocalStreamController';\nimport { TransceiverController } from './TransceiverController';\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { PreferencesContainer } from '../containers/PreferencesContainer';\nimport { ICE_GATHERING_COMPLETE_TIMEOUT_MS } from '../core/constants';\nimport {\n  DependencyError,\n  InvalidParams,\n  isMediaDeviceInUse,\n  MediaAccessError,\n  MediaTrackError\n} from '../core/errors';\nimport {\n  enableStereoOpus,\n  extractMediaDirectionsFromSDP,\n  isValidLocalDescription,\n  setCodecPreferences\n} from '../helpers/SDPHelper';\nimport { filterNull } from '../operators';\nimport { getLogger } from '../utils/logger';\nimport { toError } from '../utils/toError';\n\nimport type { RTCPeerConnectionPropose, RTCPeerConnectionType } from '../core/types/call.types';\nimport type { MediaDirections, MediaOptions } from '../core/types/media.types';\nimport type { WebRTCApiProvider } from '../dependencies/interfaces';\nimport type { DeviceController } from '../interfaces/DeviceController';\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\nexport interface RTCPeerConnectionControllerOptions extends MediaOptions {\n  callId?: string;\n  rtcConfiguration?: RTCConfiguration;\n  simulcast?: boolean;\n  sfu?: boolean;\n  msStreamsNumber?: number;\n  propose: RTCPeerConnectionPropose;\n  iceServers?: RTCIceServer[];\n  disableUdpIceServers?: boolean;\n  relayOnly?: boolean;\n  iceCandidateTimeout?: number;\n  iceGatheringTimeout?: number;\n  webRTCApiProvider?: WebRTCApiProvider;\n  /** Per-call preferred video codecs (overrides global preferences). */\n  preferredVideoCodecs?: string[];\n  /** Per-call preferred audio codecs (overrides global preferences). */\n  preferredAudioCodecs?: string[];\n  /** Per-call stereo Opus setting (overrides global preferences). */\n  stereo?: boolean;\n  /**\n   * Request the shared surface's audio on a `'screenshare'` connection. Kept\n   * apart from `audio`, which selects a microphone the share must not inherit.\n   */\n  screenShareAudio?: boolean;\n}\n\nexport type RTCPeerConnectionControllerOptionsPartial = Partial<RTCPeerConnectionControllerOptions>;\n\nexport interface UpdateSDPStatusParams {\n  status: 'received' | 'sent' | 'failed';\n  sdp?: string;\n}\n\nexport class RTCPeerConnectionController extends Destroyable {\n  public readonly id: string;\n  public firstSDPExchangeCompleted = false;\n  public sdpInit?: RTCSessionDescriptionInit;\n  private negotiationNeeded$ = this.createSubject<void>();\n  private deviceController: DeviceController;\n  private localStreamController: LocalStreamController;\n  // Transceiver controller - initialized lazily when peerConnection is created\n  private transceiverController?: TransceiverController;\n  public readonly localDescription$: Observable<RTCSessionDescription | null> = defer(() =>\n    from(this.init())\n  ).pipe(\n    switchMap(() =>\n      // Wait for ICE negotiation before emitting localDescription\n      this.iceGatheringController.iceCandidatesState$.pipe(\n        filter((iceCandidateState) => !['new', 'gathering'].includes(iceCandidateState)),\n        tap(() => {\n          this.negotiationEnded();\n        }),\n        // Only emit when signaling state is 'have-local-offer'\n        filter(() => this.shouldEmitLocalDescription),\n        map(() => this.peerConnection?.localDescription),\n        filterNull(),\n        tap((desc) => {\n          if (desc.type === 'answer') {\n            // Once the answer is emitted, switch type to offer for future negotiations\n            this._type = 'offer';\n          }\n        })\n      )\n    ),\n    shareReplay(1),\n    takeUntil(this.destroyed$)\n  );\n  public peerConnection?: RTCPeerConnection;\n  private initPromise?: Promise<void>;\n  private connectionTimeout = 3_000;\n  private connectionTimer?: ReturnType<typeof setTimeout>;\n  private oniceconnectionstatechangeHandler = () => {\n    if (this.peerConnection) {\n      const { iceConnectionState } = this.peerConnection;\n      logger.debug(\n        `[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`\n      );\n      this._iceConnectionState$.next(this.peerConnection.iceConnectionState);\n    }\n  };\n  private onconnectionstatechangeHandler = () => {\n    if (this.peerConnection) {\n      const { connectionState } = this.peerConnection;\n      logger.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);\n      if (connectionState === 'connected') {\n        this.removeConnectionTimer();\n      }\n      this._connectionState$.next(this.peerConnection.connectionState);\n    }\n  };\n  private onsignalingstatechangeHandler = () => {\n    logger.debug(\n      `[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`\n    );\n  };\n  private onicegatheringstatechangeHandler = () => {\n    if (this.peerConnection) {\n      this._iceGatheringState$.next(this.peerConnection.iceGatheringState);\n    }\n  };\n  private onnegotiationneededHandler = (event: unknown) => {\n    logger.debug('[RTCPeerConnectionController] Negotiation needed event received.', event);\n    this.negotiationNeeded$.next();\n  };\n  private updateSelectedInputDevice = async (\n    kind: 'audio' | 'video',\n    deviceInfo: MediaDeviceInfo | null\n  ): Promise<void> => {\n    const { localStream } = this;\n    if (!localStream) {\n      logger.warn(\n        '[RTCPeerConnectionController] No local stream available to update input device.'\n      );\n      return;\n    }\n\n    const currentTrack = localStream.getTracks().find((track) => track.kind === kind);\n    if (!currentTrack) {\n      logger.debug(`[RTCPeerConnectionController] No ${kind} track to switch.`);\n      return;\n    }\n\n    if (!deviceInfo) {\n      logger.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);\n      this.stopTrackSender(kind);\n      return;\n    }\n\n    const constraints: MediaTrackConstraints = {\n      ...currentTrack.getConstraints(),\n      ...this.deviceController.deviceInfoToConstraints(deviceInfo)\n    };\n\n    try {\n      const newTrack = await this.acquireInputTrack(kind, constraints, deviceInfo, currentTrack);\n      await this.attachInputTrack(kind, newTrack, currentTrack);\n      logger.debug(\n        `[RTCPeerConnectionController] ${kind} input device selected:`,\n        deviceInfo.label,\n        newTrack.id\n      );\n    } catch (error) {\n      logger.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);\n      // Reported, not thrown: the only caller is an async subscriber callback\n      // nobody awaits, so a rejection here would surface as an unhandled one.\n      this._errors$.next(new MediaTrackError('updateSelectedInputDevice', kind, error));\n    }\n  };\n  private _isNegotiating$ = this.createBehaviorSubject<boolean>(false);\n  private _iceGatheringController?: ICEGatheringController;\n  private _memberId: string | null = null;\n  private _nodeId: string | null = null;\n  private _type: RTCPeerConnectionType;\n  // Observable state streams - exposed as public observables\n  private _iceConnectionState$ = this.createReplaySubject<RTCIceConnectionState>(1);\n  private _connectionState$ = this.createReplaySubject<RTCPeerConnectionState>(1);\n  private _signalingState$ = this.createReplaySubject<RTCSignalingState>(1);\n  private _iceGatheringState$ = this.createReplaySubject<RTCIceGatheringState>(1);\n  // Error stream\n  private _errors$ = this.createReplaySubject<Error>(1);\n  // ICE candidates stream\n  private _iceCandidates$ = this.createReplaySubject<RTCIceCandidate[]>(1);\n  /**\n   * Emits once local media is settled: acquired, intentionally receive-only, or\n   * failed and degraded. Separates the media and signalling phases of call\n   * creation. Not `localStream$` — the receive-only paths never build a stream,\n   * so that would hang exactly the calls with nothing to acquire.\n   */\n  private _localMediaSettled$ = this.createReplaySubject<void>(1);\n  // Initialization state\n  private _initialized$ = this.createReplaySubject<boolean>(1);\n  // Remote description\n  private _remoteDescription$ = this.createReplaySubject<RTCSessionDescription | null>(1);\n  private _remoteStream$ = this.createBehaviorSubject<MediaStream | null>(null);\n  private _remoteOfferMediaDirections: MediaDirections | null = null;\n  private _localAudioPipeline: LocalAudioPipeline | null = null;\n  constructor(\n    protected options: RTCPeerConnectionControllerOptionsPartial = {},\n    remoteSessionDescription?: string,\n    deviceController?: DeviceController\n  ) {\n    super();\n    this.deviceController = deviceController ?? ({} as DeviceController);\n    this.id = options.callId ?? uuid();\n    this._type = remoteSessionDescription ? 'answer' : 'offer';\n\n    this.sdpInit = remoteSessionDescription\n      ? {\n          type: 'offer',\n          sdp: remoteSessionDescription\n        }\n      : undefined;\n\n    this._remoteOfferMediaDirections = remoteSessionDescription\n      ? extractMediaDirectionsFromSDP(remoteSessionDescription)\n      : null;\n\n    // For inbound calls, derive send/receive defaults from the remote offer directions.\n    // Remote 'sendrecv' → remote sends (we receive) AND remote receives (we send)\n    // Remote 'sendonly' → remote sends (we receive) but doesn't receive (we don't send)\n    // Remote 'recvonly' → remote receives (we send) but doesn't send (we don't receive)\n    const offerDefaults = this._remoteOfferMediaDirections\n      ? {\n          audio: this._remoteOfferMediaDirections.audio.includes('recv'),\n          video: this._remoteOfferMediaDirections.video.includes('recv'),\n          receiveAudio: this._remoteOfferMediaDirections.audio.includes('send'),\n          receiveVideo: this._remoteOfferMediaDirections.video.includes('send')\n        }\n      : {};\n\n    this.options = {\n      ...options,\n      audio: options.audio ?? offerDefaults.audio,\n      video: options.video ?? offerDefaults.video,\n      receiveAudio:\n        options.receiveAudio ??\n        offerDefaults.receiveAudio ??\n        PreferencesContainer.instance.receiveAudio,\n      receiveVideo:\n        options.receiveVideo ??\n        offerDefaults.receiveVideo ??\n        PreferencesContainer.instance.receiveVideo\n    };\n\n    // Initialize the local stream controller\n    this.localStreamController = new LocalStreamController({\n      propose: this.propose,\n      inputAudioStream: this.options.inputAudioStream,\n      inputVideoStream: this.options.inputVideoStream,\n      inputAudioDeviceConstraints: this.inputAudioDeviceConstraints,\n      inputVideoDeviceConstraints: this.inputVideoDeviceConstraints,\n      screenShareAudio: this.options.screenShareAudio,\n      getUserMedia: async (constraints: MediaStreamConstraints) => this.getUserMedia(constraints),\n      getDisplayMedia: async (options: DisplayMediaStreamOptions) => this.getDisplayMedia(options)\n    });\n  }\n\n  private get iceGatheringController(): ICEGatheringController {\n    if (!this._iceGatheringController) {\n      throw new DependencyError('ICEGatheringController is not initialized');\n    }\n    return this._iceGatheringController;\n  }\n\n  private get shouldEmitLocalDescription(): boolean {\n    if (!this.peerConnection) {\n      return false;\n    }\n\n    const { localDescription, signalingState } = this.peerConnection;\n\n    if (!localDescription || !isValidLocalDescription(localDescription.sdp)) {\n      return false;\n    }\n\n    return (\n      (localDescription.type === 'offer' && signalingState === 'have-local-offer') ||\n      (localDescription.type === 'answer' && signalingState === 'stable')\n    );\n  }\n\n  private removeConnectionTimer(): void {\n    if (this.connectionTimer) {\n      clearTimeout(this.connectionTimer);\n      this.connectionTimer = undefined;\n    }\n  }\n\n  public setMemberId(memberId: string | null): void {\n    this._memberId = memberId;\n  }\n\n  public get memberId(): string | null {\n    return this._memberId;\n  }\n\n  /** The node this leg's invite landed on — auxiliary legs are placed independently. */\n  public setNodeId(nodeId: string | null): void {\n    this._nodeId = nodeId;\n  }\n\n  public get nodeId(): string | null {\n    return this._nodeId;\n  }\n\n  public stopTrackSender(\n    kind: 'audio' | 'video' | 'both',\n    options = { updateTransceiverDirection: false }\n  ): void {\n    // When the local audio pipeline is engaged, the audio sender's track is\n    // pipeline.outputTrack (a synthetic track from a MediaStreamAudioDestinationNode).\n    // The default mute path would call sender.track.stop(), which permanently\n    // ends that destination track and breaks the pipeline. Instead, stop the\n    // raw mic track (releases the OS mic light) and disconnect the pipeline\n    // input — the sender stays pointed at the still-alive outputTrack and\n    // emits silence until the pipeline gets a fresh raw input on unmute.\n    const audioCovered = kind === 'audio' || kind === 'both';\n    if (audioCovered && this._localAudioPipeline) {\n      this.stopRawAudioInputForPipeline();\n    }\n    if (!audioCovered) {\n      this.transceiverController?.stopTrackSender(kind, options);\n    } else if (kind === 'both') {\n      this.transceiverController?.stopTrackSender('video', options);\n    } else if (!this._localAudioPipeline) {\n      // Pure audio mute and no pipeline → use the default path.\n      this.transceiverController?.stopTrackSender(kind, options);\n    }\n  }\n\n  private stopRawAudioInputForPipeline(): void {\n    const rawTracks = this.localStreamController.localAudioTracks;\n    for (const track of rawTracks) {\n      if (track.readyState === 'live') {\n        this.localStreamController.removeTrack(track.id);\n      }\n    }\n    this._localAudioPipeline?.setInputTrack(null);\n  }\n\n  public get isNegotiating$(): Observable<boolean> {\n    return this._isNegotiating$.asObservable();\n  }\n\n  public get isNegotiating(): boolean {\n    return this._isNegotiating$.value;\n  }\n\n  public updateMediaDevicesOptions(options: MediaOptions): void {\n    this.options = {\n      ...this.options,\n      ...options\n    };\n  }\n\n  public get iceGatheringState$(): Observable<RTCIceGatheringState> {\n    return this.cachedObservable('iceGatheringState$', () =>\n      this._iceGatheringState$.asObservable().pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get mediaTrackEnded$(): Observable<MediaStreamTrack> {\n    return this.cachedObservable('mediaTrackEnded$', () =>\n      this.localStreamController.mediaTrackEnded$.pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get errors$(): Observable<Error> {\n    return this.cachedObservable('errors$', () =>\n      this._errors$.asObservable().pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get iceCandidates$(): Observable<RTCIceCandidate[]> {\n    return this.cachedObservable('iceCandidates$', () =>\n      this._iceCandidates$.asObservable().pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get initialized$(): Observable<boolean> {\n    return this.cachedObservable('initialized$', () =>\n      this._initialized$.asObservable().pipe(\n        filter((initialized) => initialized),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  public get remoteDescription$(): Observable<RTCSessionDescription | null> {\n    return this.cachedObservable('remoteDescription$', () =>\n      this._remoteDescription$.asObservable().pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  /** Emits once local media is settled — acquired, or knowingly receive-only. */\n  public get localMediaSettled$(): Observable<void> {\n    return this.cachedObservable('localMediaSettled$', () =>\n      this._localMediaSettled$.asObservable().pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get localStream$(): Observable<MediaStream | null> {\n    return this.cachedObservable('localStream$', () =>\n      this.localStreamController.localStream$.pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get remoteStream$(): Observable<MediaStream | null> {\n    return this.cachedObservable('remoteStream$', () =>\n      this._remoteStream$.asObservable().pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get localAudioTracks$(): Observable<MediaStreamTrack[]> {\n    return this.cachedObservable('localAudioTracks$', () =>\n      this.localStreamController.localAudioTracks$.pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get localVideoTracks$(): Observable<MediaStreamTrack[]> {\n    return this.cachedObservable('localVideoTracks$', () =>\n      this.localStreamController.localVideoTracks$.pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get iceConnectionState$(): Observable<RTCIceConnectionState> {\n    return this.cachedObservable('iceConnectionState$', () =>\n      this._iceConnectionState$.asObservable().pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get connectionState$(): Observable<RTCPeerConnectionState> {\n    return this.cachedObservable('connectionState$', () =>\n      this._connectionState$.asObservable().pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get signalingState$(): Observable<RTCSignalingState> {\n    return this.cachedObservable('signalingState$', () =>\n      this._signalingState$.asObservable().pipe(takeUntil(this.destroyed$))\n    );\n  }\n\n  public get type(): RTCPeerConnectionType {\n    return this._type;\n  }\n\n  public get propose(): RTCPeerConnectionPropose {\n    return this.options.propose ?? 'main';\n  }\n\n  public get connectionState(): RTCPeerConnectionState | undefined {\n    return this.peerConnection?.connectionState;\n  }\n\n  public get isAdditionalDevice(): boolean {\n    return this.propose === 'additional-device';\n  }\n\n  public get isMainDevice(): boolean {\n    return this.propose === 'main';\n  }\n\n  public get isScreenShare(): boolean {\n    return this.propose === 'screenshare';\n  }\n\n  protected get iceServers(): RTCIceServer[] {\n    if (!this.options.disableUdpIceServers) {\n      return this.options.iceServers ?? [];\n    }\n    const tcpTransportParam = 'transport=tcp';\n\n    // When disabling UDP, keep only URLs that explicitly specify transport=tcp\n    // URLs without a transport parameter default to UDP and should be filtered out\n    return (this.options.iceServers ?? []).map((server) => {\n      const urls = Array.isArray(server.urls) ? server.urls : [server.urls];\n      return {\n        ...server,\n        urls: urls.filter((url) => url.includes(tcpTransportParam))\n      } as RTCIceServer;\n    });\n  }\n\n  private get rtcConfiguration(): RTCConfiguration {\n    // Destructure to exclude iceServers from options spread, since we use the filtered this.iceServers\n    const { iceServers: _iceServers, ...restOptions } = this.options;\n    return {\n      bundlePolicy: 'max-compat',\n      iceCandidatePoolSize: 10,\n      iceServers: this.iceServers,\n      iceTransportPolicy: this.options.relayOnly ? 'relay' : 'all',\n      //@ts-expect-error -- Ignore ---\n      sdpSemantics: 'unified-plan',\n      ...restOptions\n    };\n  }\n\n  public get receiveVideo(): boolean {\n    return Boolean(this.options.receiveVideo);\n  }\n\n  public get receiveAudio(): boolean {\n    return Boolean(this.options.receiveAudio);\n  }\n\n  public get localStream(): MediaStream | null {\n    return this.localStreamController.localStream;\n  }\n\n  public get remoteStream(): MediaStream | null {\n    return this._remoteStream$.value;\n  }\n\n  private get inputAudioDeviceConstraints(): MediaTrackConstraints | boolean {\n    if (this.options.audio === false && !this.options.inputAudioDeviceConstraints) {\n      return false;\n    }\n    // Section 5.9: When device is disabled, return false (receive-only)\n    const deviceConstraints = this.deviceController.selectedAudioInputDeviceConstraints;\n    if (deviceConstraints === false) {\n      return false;\n    }\n    const audioBase =\n      typeof this.options.inputAudioDeviceConstraints === 'object'\n        ? this.options.inputAudioDeviceConstraints\n        : {};\n    const audioDevice = typeof deviceConstraints === 'object' ? deviceConstraints : {};\n    return {\n      ...audioBase,\n      ...audioDevice\n    };\n  }\n\n  private get inputVideoDeviceConstraints(): MediaTrackConstraints | boolean {\n    if (!this.options.video && !this.options.inputVideoDeviceConstraints) {\n      return false;\n    }\n    // Section 5.9: When device is disabled, return false (receive-only)\n    const deviceConstraints = this.deviceController.selectedVideoInputDeviceConstraints;\n    if (deviceConstraints === false) {\n      return false;\n    }\n    const videoBase =\n      typeof this.options.inputVideoDeviceConstraints === 'object'\n        ? this.options.inputVideoDeviceConstraints\n        : {};\n    const videoDevice = typeof deviceConstraints === 'object' ? deviceConstraints : {};\n    return {\n      ...videoBase,\n      ...videoDevice\n    };\n  }\n\n  private get WebRTCPeerConnectionConstructor(): typeof RTCPeerConnection {\n    return this.options.webRTCApiProvider?.RTCPeerConnection ?? RTCPeerConnection;\n  }\n\n  private get offerOptions(): RTCOfferOptions {\n    const options: RTCOfferOptions = {\n      iceRestart: this.firstSDPExchangeCompleted ? true : undefined\n    };\n    switch (this.propose) {\n      case 'screenshare':\n      case 'additional-device':\n        return {\n          ...options,\n          offerToReceiveAudio: false,\n          offerToReceiveVideo: false\n        };\n      case 'main':\n      default:\n        return {\n          ...options,\n          offerToReceiveAudio: this.options.receiveAudio ?? true,\n          offerToReceiveVideo:\n            this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)\n        };\n    }\n  }\n\n  private get answerOptions(): RTCAnswerOptions {\n    return {\n      iceRestart: this.firstSDPExchangeCompleted ? true : undefined\n    };\n  }\n\n  /**\n   * Initialize the RTCPeerConnection and setup event listeners.\n   * Called automatically when localDescription$ is subscribed to (deferred pattern).\n   * Uses Promise memoization to ensure initialization only happens once,\n   * even if called concurrently.\n   */\n  private async init(): Promise<void> {\n    this.initPromise ??= this.doInit();\n    return this.initPromise;\n  }\n\n  /**\n   * Internal initialization implementation.\n   * Should only be called via init() to ensure single execution.\n   */\n  private async doInit(): Promise<void> {\n    try {\n      this.setupPeerConnection();\n\n      this.subscribeTo(\n        this.negotiationNeeded$.pipe(\n          auditTime(0), //When updating multiple tracks, batches all the events together\n          exhaustMap(async () => this.startNegotiation()) // Ignore new events while negotiation is ongoing\n        ),\n        {\n          next: () => {\n            logger.debug('[RTCPeerConnectionController] Start Negotiation completed successfully');\n          },\n          error: (error) => {\n            logger.error('[RTCPeerConnectionController] Start Negotiation error:', error);\n            this._errors$.next(toError(error));\n          }\n        }\n      );\n\n      this.subscribeTo(\n        merge(\n          this.deviceController.selectedAudioInputDevice$.pipe(\n            map((deviceInfo) => ['audio', deviceInfo] as const)\n          ),\n          this.deviceController.selectedVideoInputDevice$.pipe(\n            map((deviceInfo) => ['video', deviceInfo] as const)\n          )\n        ).pipe(\n          // we only want to react to changes after the localstream is created\n          // before that the device selection is handle int the localstream creation\n          skipWhile(() => !this.localStreamController.localStream)\n        ),\n        async ([kind, deviceInfo]) => {\n          logger.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {\n            kind,\n            deviceInfo\n          });\n          await this.updateSelectedInputDevice(kind, deviceInfo);\n        }\n      );\n\n      // For inbound calls: only setup remote tracks (ontrack handler) during init.\n      // Local track setup is deferred to acceptInbound() so that:\n      // 1. Remote description is set first, creating transceivers from the offer\n      // 2. Local tracks reuse those transceivers instead of creating duplicates\n      // 3. Media overrides can be applied before tracks are acquired\n      if (this.type === 'answer' && this.sdpInit) {\n        await this.setupRemoteTracks();\n\n        this._initialized$.next(true);\n\n        this.setupEventListeners();\n        this._isNegotiating$.next(true);\n        await this._setRemoteDescription(this.sdpInit);\n      } else {\n        if (!(await this.setupTrackHandling())) {\n          // The connection went away while media was being acquired; there is\n          // nothing left to initialize.\n          return;\n        }\n\n        this._initialized$.next(true);\n      }\n    } catch (error) {\n      logger.error('[RTCPeerConnectionController] Initialization error:', error);\n      this._errors$.next(toError(error));\n      this.destroy();\n    }\n  }\n\n  private setupPeerConnection() {\n    this.peerConnection = new this.WebRTCPeerConnectionConstructor(this.rtcConfiguration);\n    this.peerConnection.addEventListener('negotiationneeded', this.onnegotiationneededHandler);\n    this._iceGatheringController = new ICEGatheringController(\n      this.peerConnection,\n      this.isNegotiating$,\n      {\n        iceCandidateTimeout: this.options.iceCandidateTimeout,\n        iceGatheringTimeout: this.options.iceGatheringTimeout,\n        relayOnly: this.options.relayOnly\n      }\n    );\n\n    // Initialize the transceiver controller\n    this.transceiverController = new TransceiverController({\n      peerConnection: this.peerConnection,\n      propose: this.propose,\n      simulcast: this.options.simulcast,\n      sfu: this.options.sfu,\n      msStreamsNumber: this.options.msStreamsNumber,\n      receiveAudio: this.receiveAudio,\n      receiveVideo: this.receiveVideo,\n      localStreamController: this.localStreamController,\n      getInputAudioDeviceConstraints: () => this.inputAudioDeviceConstraints,\n      getInputVideoDeviceConstraints: () => this.inputVideoDeviceConstraints,\n      getUserMedia: async (constraints: MediaStreamConstraints) => this.getUserMedia(constraints),\n      onError: (error: Error) => {\n        this._errors$.next(error);\n      }\n    });\n  }\n\n  private async startNegotiation() {\n    if (this.isNegotiating) {\n      logger.debug('[RTCPeerConnectionController] Negotiation already in progress, skipping.');\n      return;\n    }\n\n    this.setupEventListeners();\n\n    if (this.type === 'answer') {\n      logger.debug(\n        '[RTCPeerConnectionController] This is an answer type still, skipping offer creation.'\n      );\n      return;\n    }\n\n    this._isNegotiating$.next(true);\n    logger.debug('[RTCPeerConnectionController] Starting negotiation.');\n\n    try {\n      const { offerOptions } = this;\n      logger.debug('[RTCPeerConnectionController] Creating offer with options:', offerOptions);\n      await this.createOffer(offerOptions);\n    } catch (error) {\n      logger.error('[RTCPeerConnectionController] Error during negotiation:', error);\n      this._errors$.next(toError(error));\n    }\n  }\n\n  /**\n   * Create an SDP offer and set it as local description.\n   */\n  private async createOffer(options?: RTCOfferOptions): Promise<void> {\n    if (!this.peerConnection) {\n      throw new DependencyError('RTCPeerConnection is not initialized');\n    }\n\n    const offer = await this.peerConnection.createOffer(options);\n    await this.setLocalDescription(offer);\n\n    // Note: localDescription will be emitted by setupEventListeners initial emission\n    // and updated when ICE gathering state changes\n  }\n\n  public async updateAnswerStatus({ status, sdp }: UpdateSDPStatusParams): Promise<void> {\n    let readyToConnect = status !== 'failed';\n\n    try {\n      if (status === 'received' && sdp) {\n        logger.debug('[RTCPeerConnectionController] Received answer SDP:', sdp);\n        await this._setRemoteDescription({\n          type: 'answer',\n          sdp\n        });\n      }\n    } catch (error) {\n      logger.error('[RTCPeerConnectionController] Error updating answer status:', error);\n      this._errors$.next(toError(error));\n      readyToConnect = false;\n    } finally {\n      if (readyToConnect) {\n        this.readyToConnect();\n      } else {\n        this.iceGatheringController.restartICEGatheringWithRelayOnly();\n      }\n    }\n  }\n\n  public async updateOfferStatus({ status, sdp }: UpdateSDPStatusParams): Promise<void> {\n    switch (status) {\n      case 'received':\n        this._type = 'answer';\n        this.sdpInit = {\n          type: 'offer',\n          sdp: sdp\n        };\n        await this.handleOfferReceived();\n        break;\n      case 'failed':\n        logger.error('[RTCPeerConnectionController] Offer failed to be processed by remote.');\n        break;\n      case 'sent':\n      default:\n      // No action needed for sent offers\n    }\n  }\n\n  /**\n   * Accept an inbound call by creating the SDP answer.\n   * Optionally override media options before the answer is generated.\n   * Must be called after initialization for inbound (answer-type) connections.\n   */\n  public async acceptInbound(mediaOverrides?: MediaOptions): Promise<void> {\n    if (mediaOverrides) {\n      const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;\n      this.options = {\n        ...this.options,\n        ...(audio !== undefined ? { audio } : {}),\n        ...(video !== undefined ? { video } : {}),\n        ...(receiveAudio !== undefined ? { receiveAudio } : {}),\n        ...(receiveVideo !== undefined ? { receiveVideo } : {}),\n        ...(fallbackToReceiveOnly !== undefined ? { fallbackToReceiveOnly } : {})\n      };\n      this.transceiverController?.updateOptions({\n        receiveAudio: this.receiveAudio,\n        receiveVideo: this.receiveVideo\n      });\n      this.localStreamController.updateOptions({\n        inputAudioDeviceConstraints: this.inputAudioDeviceConstraints,\n        inputVideoDeviceConstraints: this.inputVideoDeviceConstraints\n      });\n    }\n\n    // Setup local tracks after remote description is set and media overrides applied.\n    // This ensures local tracks reuse transceivers from the remote offer\n    // instead of creating duplicate transceivers via addTransceiver().\n    if (!(await this.setupLocalTracks())) {\n      logger.debug(\n        '[RTCPeerConnectionController] Inbound answer abandoned; the connection went away.'\n      );\n      return;\n    }\n\n    const { answerOptions } = this;\n    logger.debug(\n      '[RTCPeerConnectionController] Creating inbound answer with options:',\n      answerOptions\n    );\n    await this.createAnswer(answerOptions);\n  }\n\n  private async handleOfferReceived() {\n    if (!this.sdpInit) {\n      throw new DependencyError('SDP initialization parameters are not set');\n    }\n\n    this._isNegotiating$.next(true);\n    await this._setRemoteDescription(this.sdpInit);\n\n    const { answerOptions } = this;\n    logger.debug('[RTCPeerConnectionController] Creating answer with options:', answerOptions);\n    await this.createAnswer(answerOptions);\n  }\n\n  private readyToConnect() {\n    this.firstSDPExchangeCompleted = true;\n    this.connectionTimer = setTimeout(() => {\n      this.removeConnectionTimer();\n      if (this.peerConnection?.connectionState !== 'connected') {\n        logger.debug(\n          '[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.'\n        );\n        this.iceGatheringController.restartICEGatheringWithRelayOnly();\n      }\n    }, this.connectionTimeout);\n  }\n\n  private async setRemoteDescriptionBefore(sdp: string = ''): Promise<string> {\n    // TODO use the options hooks\n    return Promise.resolve(sdp);\n  }\n  protected async setLocalDescription(params: RTCSessionDescriptionInit): Promise<void> {\n    const finalLocal = await this.setLocalDescriptionBefore(params.sdp);\n    return this.peerConnection?.setLocalDescription({\n      ...params,\n      sdp: finalLocal\n    });\n  }\n  async setLocalDescriptionBefore(sdp: string = ''): Promise<string> {\n    let result = sdp;\n\n    // Per-call overrides take precedence, then global preferences\n    const preferredAudioCodecs =\n      this.options.preferredAudioCodecs ?? PreferencesContainer.instance.preferredAudioCodecs;\n    const preferredVideoCodecs =\n      this.options.preferredVideoCodecs ?? PreferencesContainer.instance.preferredVideoCodecs;\n    const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;\n\n    // Apply codec reordering if preferences are set\n    if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {\n      result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);\n      logger.debug('[RTCPeerConnectionController] Applied codec preferences to SDP', {\n        preferredAudioCodecs,\n        preferredVideoCodecs\n      });\n    }\n\n    // Apply stereo Opus if enabled\n    if (stereo) {\n      result = enableStereoOpus(result);\n      logger.debug('[RTCPeerConnectionController] Applied stereo Opus to SDP');\n    }\n\n    return Promise.resolve(result);\n  }\n  /**\n   * Create an SDP answer and set it as local description.\n   */\n  private async createAnswer(options?: RTCAnswerOptions): Promise<void> {\n    if (!this.peerConnection) {\n      throw new DependencyError('RTCPeerConnection is not initialized');\n    }\n\n    const answer = await this.peerConnection.createAnswer(options);\n    await this.setLocalDescription(answer);\n    // Note: localDescription will be emitted by setupEventListeners initial emission\n    // and updated when ICE gathering state changes\n  }\n  /**\n   * Setup event listeners on RTCPeerConnection for state changes.\n   */\n  private setupEventListeners(): void {\n    if (!this.peerConnection) {\n      throw new DependencyError('RTCPeerConnection is not initialized');\n    }\n\n    // Emit initial states from the actual RTCPeerConnection\n    this._iceConnectionState$.next(this.peerConnection.iceConnectionState);\n    this._connectionState$.next(this.peerConnection.connectionState);\n    this._signalingState$.next(this.peerConnection.signalingState);\n    this._iceGatheringState$.next(this.peerConnection.iceGatheringState);\n    // Note: localDescription is NOT emitted here - it will be emitted when ICE gathering completes\n    this._remoteDescription$.next(this.peerConnection.remoteDescription);\n\n    this.peerConnection.removeEventListener(\n      'icegatheringstatechange',\n      this.onicegatheringstatechangeHandler\n    );\n    this.peerConnection.addEventListener(\n      'icegatheringstatechange',\n      this.onicegatheringstatechangeHandler\n    );\n    this.peerConnection.removeEventListener(\n      'iceconnectionstatechange',\n      this.oniceconnectionstatechangeHandler\n    );\n    this.peerConnection.addEventListener(\n      'iceconnectionstatechange',\n      this.oniceconnectionstatechangeHandler\n    );\n\n    // Signaling state changes\n    this.peerConnection.removeEventListener(\n      'connectionstatechange',\n      this.onconnectionstatechangeHandler\n    );\n    this.peerConnection.addEventListener(\n      'connectionstatechange',\n      this.onconnectionstatechangeHandler\n    );\n\n    this.peerConnection.removeEventListener(\n      'signalingstatechange',\n      this.onsignalingstatechangeHandler\n    );\n\n    this.peerConnection.addEventListener(\n      'signalingstatechange',\n      this.onsignalingstatechangeHandler\n    );\n  }\n\n  private negotiationEnded() {\n    this._isNegotiating$.next(false);\n  }\n\n  /**\n   * Trigger an ICE restart through the existing negotiation pipeline.\n   *\n   * This creates an offer with iceRestart: true and goes through the full\n   * SDP pipeline (setLocalDescription → ICE gathering → localDescription$ emission).\n   * The caller should NOT send the SDP manually — the existing\n   * setupLocalDescriptionHandler in VertoManager will pick up the emission\n   * from localDescription$ and send it as a verto.modify.\n   *\n   * Unlike calling pc.createOffer/setLocalDescription directly, this method:\n   * - Sets _isNegotiating$ so ICEGatheringController arms its timers\n   * - Waits for ICE gathering to complete before localDescription$ emits\n   * - Goes through setLocalDescriptionBefore() for any SDP munging\n   */\n  public async triggerIceRestart(relayOnly?: boolean): Promise<void> {\n    if (!this.peerConnection) {\n      throw new DependencyError('RTCPeerConnection is not initialized');\n    }\n\n    // Tier 3: constrain to relay candidates only (TURN servers).\n    // setConfiguration() updates the ICE transport policy for the next gather.\n    const policyChanged = relayOnly && !this.options.relayOnly;\n    if (policyChanged) {\n      try {\n        this.peerConnection.setConfiguration({\n          ...this.peerConnection.getConfiguration(),\n          iceTransportPolicy: 'relay'\n        });\n        logger.debug('[RTCPeerConnectionController] ICE transport policy set to relay-only');\n      } catch (error) {\n        logger.warn('[RTCPeerConnectionController] Failed to set relay-only policy:', error);\n      }\n    }\n\n    this.setupEventListeners();\n    this._isNegotiating$.next(true);\n\n    logger.debug(\n      `[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? ' (relay-only)' : ''}.`\n    );\n\n    try {\n      const offer = await this.peerConnection.createOffer({ iceRestart: true });\n      await this.setLocalDescription(offer);\n    } catch (error) {\n      logger.error('[RTCPeerConnectionController] ICE restart offer failed:', error);\n      this._errors$.next(toError(error));\n      this.negotiationEnded();\n      // Restore original policy on failure\n      if (policyChanged) {\n        this.restoreIceTransportPolicy();\n      }\n      throw error;\n    }\n    // Restore original policy only after gathering actually completes. setLocalDescription\n    // resolves before the browser finishes gathering candidates, so restoring synchronously\n    // here would let non-relay candidates leak back into the current gather — defeating\n    // Tier-3 escalation. Wait for iceGatheringState === 'complete' (bounded by a timeout\n    // so we don't leak the policy override if gathering never completes).\n    if (policyChanged) {\n      void firstValueFrom(\n        race(\n          this._iceGatheringState$.pipe(\n            filter((state) => state === 'complete'),\n            take(1)\n          ),\n          timer(ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe(map(() => 'timeout' as const))\n        )\n      )\n        .then(() => this.restoreIceTransportPolicy())\n        .catch((error: unknown) => {\n          logger.warn(\n            '[RTCPeerConnectionController] Error waiting for ICE gathering to complete:',\n            error\n          );\n          this.restoreIceTransportPolicy();\n        });\n    }\n  }\n\n  private restoreIceTransportPolicy(): void {\n    try {\n      this.peerConnection?.setConfiguration({\n        ...this.peerConnection.getConfiguration(),\n        iceTransportPolicy: this.options.relayOnly ? 'relay' : 'all'\n      });\n      logger.debug('[RTCPeerConnectionController] ICE transport policy restored');\n    } catch (error) {\n      logger.warn('[RTCPeerConnectionController] Failed to restore ICE transport policy:', error);\n    }\n  }\n  /**\n   * Setup track handling for remote tracks.\n   *\n   * @returns `false` when the connection went away while local media was being\n   * acquired — see {@link setupLocalTracks}.\n   */\n  private async setupTrackHandling(): Promise<boolean> {\n    if (!this.peerConnection) {\n      throw new DependencyError('RTCPeerConnection is not initialized');\n    }\n\n    if (!(await this.setupLocalTracks())) {\n      return false;\n    }\n\n    await this.setupRemoteTracks();\n    return true;\n  }\n\n  /**\n   * @returns `false` when the connection was torn down while getUserMedia was\n   * in flight. The acquisition is not cancellable, so the caller must stop\n   * rather than go on to touch a peer connection that is closed or gone.\n   */\n  private async setupLocalTracks(): Promise<boolean> {\n    logger.debug('[RTCPeerConnectionController] Setting up local tracks/transceivers.');\n\n    // Intentional receive-only call: nothing to send, so skip acquisition\n    // (getUserMedia rejects when neither kind is requested).\n    if (this.hasNoLocalMediaToSend()) {\n      if (!this.receiveAudio && !this.receiveVideo) {\n        // Degenerate configuration: nothing to send AND nothing to receive.\n        throw new InvalidParams(\n          'Call requests no media: enable audio/video or receiveAudio/receiveVideo'\n        );\n      }\n      logger.debug(\n        '[RTCPeerConnectionController] No local media requested; negotiating receive-only.'\n      );\n      this.setupReceiveOnlyTransceivers();\n      this._localMediaSettled$.next();\n      return true;\n    }\n\n    let localStream: MediaStream;\n    try {\n      localStream = this.localStream ?? (await this.localStreamController.buildLocalStream());\n    } catch (error) {\n      // A fatal failure throws out of here; a handled one (receive-only\n      // fallback) leaves the connection usable, so the init continues.\n      this.handleLocalMediaFailure(error);\n      this._localMediaSettled$.next();\n      return true;\n    }\n\n    // getUserMedia is not cancellable, so a caller that gave up leaves this\n    // promise in flight. Resuming would add transceivers to a closed connection\n    // and leave the devices open for a call that no longer exists.\n    if (!this.peerConnection || this.peerConnection.signalingState === 'closed') {\n      logger.debug(\n        '[RTCPeerConnectionController] Local media arrived after teardown; releasing it.'\n      );\n      localStream.getTracks().forEach((track) => track.stop());\n      return false;\n    }\n\n    this._localMediaSettled$.next();\n\n    if (this.transceiverController?.useAddStream ?? false) {\n      logger.warn(\n        '[RTCPeerConnectionController] Using deprecated addStream API to add local stream.'\n      );\n      //@ts-expect-error -- Ignore -- useAddStream checked if the deprecated API should be used\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n      this.peerConnection.addStream(localStream);\n      // In case the browser doesn't fire negotiationneeded automatically\n      if (!this.isNegotiating) {\n        logger.debug(\n          '[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.'\n        );\n        this.negotiationNeeded$.next();\n      }\n      return true;\n    }\n\n    for (const kind of ['audio', 'video']) {\n      const tracks = (\n        kind === 'audio' ? localStream.getAudioTracks() : localStream.getVideoTracks()\n      ).map((track, index) => ({ index, track }));\n      for (const { index, track } of tracks) {\n        this.localStreamController.addTrackEndedListener(track);\n        if (this.transceiverController?.useAddTransceivers ?? false) {\n          const transceivers =\n            (kind === 'audio'\n              ? this.transceiverController?.audioTransceivers\n              : this.transceiverController?.videoTransceivers) ?? [];\n          await this.transceiverController?.setupTransceiverSender(\n            track,\n            localStream,\n            transceivers[index]\n          );\n        } else {\n          logger.debug(\n            `[RTCPeerConnectionController] Using addTrack for local ${kind} track:`,\n            track.id\n          );\n          this.peerConnection.addTrack(track, localStream);\n        }\n      }\n    }\n\n    return true;\n  }\n\n  /** True for a main connection with no local media to send. */\n  private hasNoLocalMediaToSend(): boolean {\n    const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);\n    return (\n      this.propose === 'main' &&\n      !this.localStream &&\n      !hasInputStreams &&\n      !this.inputAudioDeviceConstraints &&\n      !this.inputVideoDeviceConstraints\n    );\n  }\n\n  /** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */\n  private get requestedMediaKinds(): string {\n    const wantsAudio = Boolean(this.inputAudioDeviceConstraints);\n    const wantsVideo = Boolean(this.inputVideoDeviceConstraints);\n    if (wantsAudio && wantsVideo) {\n      return 'audiovideo';\n    }\n    return wantsVideo ? 'video' : 'audio';\n  }\n\n  /**\n   * Handle a local media acquisition failure with a typed, semantically\n   * accurate MediaAccessError created at the acquisition site:\n   * - Auxiliary connections (screenshare / additional-device) throw a\n   *   non-fatal error — VertoManager surfaces it and the call is unaffected.\n   * - The main connection degrades to receive-only when allowed (default),\n   *   otherwise fails with a fatal error.\n   */\n  private handleLocalMediaFailure(error: unknown): void {\n    if (this.propose === 'screenshare') {\n      throw new MediaAccessError('startScreenShare', 'screen', error, false);\n    }\n    if (this.propose === 'additional-device') {\n      throw new MediaAccessError('addInputDevice', this.requestedMediaKinds, error, false);\n    }\n    const canReceive = this.receiveAudio || this.receiveVideo;\n    const wantsFallback = this.options.fallbackToReceiveOnly ?? true;\n    if (!(wantsFallback && canReceive)) {\n      // Fatal: doInit's catch emits the error and destroys the connection.\n      throw new MediaAccessError('acquireLocalMedia', this.requestedMediaKinds, error, true);\n    }\n    logger.warn(\n      '[RTCPeerConnectionController] Local media unavailable; continuing receive-only:',\n      error\n    );\n    this._errors$.next(\n      new MediaAccessError('acquireLocalMedia', this.requestedMediaKinds, error, false)\n    );\n    this.setupReceiveOnlyTransceivers();\n  }\n\n  /**\n   * Negotiate receive-only m-lines when there are no local tracks to send.\n   * Only offer-type connections add transceivers — answer-type connections\n   * reuse the transceivers created from the remote offer.\n   */\n  private setupReceiveOnlyTransceivers(): void {\n    if (this.type !== 'offer') {\n      return;\n    }\n    if (this.transceiverController?.useAddTransceivers ?? false) {\n      this.peerConnection?.addTransceiver('audio', {\n        direction: this.receiveAudio ? 'recvonly' : 'inactive'\n      });\n      this.peerConnection?.addTransceiver('video', {\n        direction: this.receiveVideo ? 'recvonly' : 'inactive'\n      });\n    }\n    // With no addTrack/addTransceiver call the browser may never fire\n    // negotiationneeded — force the offer.\n    if (!this.isNegotiating) {\n      this.negotiationNeeded$.next();\n    }\n  }\n\n  private async getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream> {\n    const mediaDevices = this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices;\n    return mediaDevices.getUserMedia(constraints);\n  }\n\n  private async getDisplayMedia(options: DisplayMediaStreamOptions): Promise<MediaStream> {\n    const mediaDevices = this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices;\n    if (!mediaDevices.getDisplayMedia) {\n      throw new DependencyError('getDisplayMedia is not supported by the current WebRTC provider');\n    }\n    return mediaDevices.getDisplayMedia(options);\n  }\n  private async setupRemoteTracks(): Promise<void> {\n    if (!this.peerConnection) {\n      throw new DependencyError('RTCPeerConnection is not initialized');\n    }\n    this.peerConnection.ontrack = (event) => {\n      logger.debug('[RTCPeerConnectionController] Remote track received:', event.track.kind);\n\n      if (event.streams[0]) {\n        this._remoteStream$.next(event.streams[0]);\n      } else {\n        const existingTracks = this._remoteStream$.value?.getTracks() ?? [];\n        const newStream = new MediaStream([...existingTracks, event.track]);\n        this._remoteStream$.next(newStream);\n      }\n    };\n\n    await this.transceiverController?.setupRemoteTransceivers(this.type);\n  }\n\n  public async restoreTrackSender(kind: 'audio' | 'video' | 'both'): Promise<void> {\n    // Mirror of stopTrackSender: when the pipeline is engaged, take the audio\n    // path through pipeline.setInputTrack so the sender's outputTrack reference\n    // stays stable across mute/unmute cycles. The default restoreTrackSender\n    // would replaceTrack(newRaw) on the sender, which would unhook the\n    // pipeline output for that one cycle.\n    const audioCovered = kind === 'audio' || kind === 'both';\n    if (audioCovered && this._localAudioPipeline) {\n      await this.restoreRawAudioInputForPipeline();\n    }\n    if (!audioCovered) {\n      await this.transceiverController?.restoreTrackSender(kind);\n    } else if (kind === 'both') {\n      await this.transceiverController?.restoreTrackSender('video');\n    } else if (!this._localAudioPipeline) {\n      // Pure audio unmute and no pipeline → use the default path.\n      await this.transceiverController?.restoreTrackSender(kind);\n    }\n  }\n\n  private async restoreRawAudioInputForPipeline(): Promise<void> {\n    if (!this._localAudioPipeline) return;\n    const constraints = this.transceiverController?.getConstraintsFor('audio') ?? {};\n    let stream: MediaStream;\n    try {\n      stream = await this.getUserMedia({ audio: constraints });\n    } catch (error) {\n      logger.error(\n        '[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:',\n        error\n      );\n      // Mid-call track op: non-fatal — the pipeline just stays silent.\n      this._errors$.next(new MediaTrackError('restoreAudioPipelineInput', 'audio', error));\n      return;\n    }\n    const newTrack = stream.getAudioTracks().at(0);\n    if (!newTrack) return;\n    this.localStreamController.addTrack(newTrack);\n    this._localAudioPipeline.setInputTrack(newTrack);\n  }\n\n  /**\n   * Capture the newly selected device, leaving the current capture running.\n   *\n   * A rejection must leave the current track sending, so nothing is released\n   * until the replacement is in hand. The one exception is hardware that admits\n   * a single opener — a phone's front and back cameras, typically — which\n   * rejects the second capture until the first is closed.\n   */\n  private async acquireInputTrack(\n    kind: 'audio' | 'video',\n    constraints: MediaTrackConstraints,\n    deviceInfo: MediaDeviceInfo,\n    currentTrack: MediaStreamTrack\n  ): Promise<MediaStreamTrack> {\n    try {\n      return await this.captureTrack(kind, constraints, deviceInfo.deviceId);\n    } catch (error) {\n      if (!isMediaDeviceInUse(error)) {\n        throw error;\n      }\n      logger.warn(\n        `[RTCPeerConnectionController] ${kind} device is held exclusively; releasing the current capture to retry:`,\n        error\n      );\n      const previousDeviceId = currentTrack.getSettings().deviceId;\n      this.stopTrackSender(kind);\n      try {\n        return await this.captureTrack(kind, constraints, deviceInfo.deviceId);\n      } catch (retryError) {\n        await this.restorePreviousInputTrack(kind, constraints, previousDeviceId, currentTrack);\n        throw retryError;\n      }\n    }\n  }\n\n  private async captureTrack(\n    kind: 'audio' | 'video',\n    constraints: MediaTrackConstraints,\n    deviceId?: string\n  ): Promise<MediaStreamTrack> {\n    const { stream, fallbackLevel } = await getUserMediaWithFallback(\n      { getUserMedia: async (c: MediaStreamConstraints) => this.getUserMedia(c) },\n      { [kind]: constraints },\n      kind,\n      deviceId\n    );\n    const track = stream.getTracks().find((t) => t.kind === kind);\n    if (!track) {\n      stream.getTracks().forEach((t) => t.stop());\n      throw new MediaTrackError(\n        'updateSelectedInputDevice',\n        kind,\n        new Error('getUserMedia returned no track of the requested kind')\n      );\n    }\n    if (fallbackLevel !== 'exact') {\n      logger.warn(\n        `[RTCPeerConnectionController] ${kind} device acquired at fallback level '${fallbackLevel}'; ` +\n          `the capture may not be the requested device.`\n      );\n    }\n    return track;\n  }\n\n  /** Best-effort return to the device that was released for an exclusive retry. */\n  private async restorePreviousInputTrack(\n    kind: 'audio' | 'video',\n    constraints: MediaTrackConstraints,\n    previousDeviceId: string | undefined,\n    releasedTrack: MediaStreamTrack\n  ): Promise<void> {\n    try {\n      const restored = await this.captureTrack(kind, constraints, previousDeviceId);\n      await this.attachInputTrack(kind, restored, releasedTrack);\n    } catch (error) {\n      logger.error(\n        `[RTCPeerConnectionController] Failed to restore the previous ${kind} device:`,\n        error\n      );\n    }\n  }\n\n  private async attachInputTrack(\n    kind: 'audio' | 'video',\n    newTrack: MediaStreamTrack,\n    oldTrack: MediaStreamTrack\n  ): Promise<void> {\n    // With the pipeline engaged the audio sender carries pipeline.outputTrack;\n    // pointing it at the raw capture instead would drop the processing and race\n    // the re-hook driven by localAudioTracks$.\n    const pipelineOwnsAudio = kind === 'audio' && this._localAudioPipeline;\n    if (!pipelineOwnsAudio) {\n      try {\n        await this.transceiverController?.replaceSenderTrack(kind, newTrack);\n      } catch (error) {\n        // Nothing holds this capture yet, so no destroy path could reach it.\n        newTrack.stop();\n        throw error;\n      }\n    }\n\n    this.localStreamController.removeTrack(oldTrack.id);\n    this.localStreamController.addTrack(newTrack);\n    if (pipelineOwnsAudio) {\n      this._localAudioPipeline?.setInputTrack(newTrack);\n    }\n  }\n\n  /**\n   * Return the lazily-created {@link LocalAudioPipeline}, constructing it on\n   * first access. On creation the current audio sender's track is routed\n   * through the pipeline (input → gain → analyser → destination) and the\n   * sender is switched to emit the processed track. Returns `null` when no\n   * audio sender exists yet (pre-negotiation).\n   */\n  public ensureLocalAudioPipeline(): LocalAudioPipeline | null {\n    if (this._localAudioPipeline) {\n      return this._localAudioPipeline;\n    }\n    if (!this.peerConnection) {\n      return null;\n    }\n    try {\n      this._localAudioPipeline = new LocalAudioPipeline();\n    } catch (error) {\n      logger.warn('[RTCPeerConnectionController] Failed to create LocalAudioPipeline:', error);\n      return null;\n    }\n    this.subscribeTo(this.localStreamController.localAudioTracks$, () => {\n      // Re-hook whenever the raw audio track changes (device switch,\n      // mute/unmute reacquire). setInputTrack is called inside apply... so\n      // the graph also rebuilds.\n      void this.applyLocalAudioPipelineToSender();\n    });\n    void this.applyLocalAudioPipelineToSender();\n    return this._localAudioPipeline;\n  }\n\n  /** The active LocalAudioPipeline, or null if it hasn't been created yet. */\n  public get localAudioPipeline(): LocalAudioPipeline | null {\n    return this._localAudioPipeline;\n  }\n\n  private async applyLocalAudioPipelineToSender(): Promise<void> {\n    if (!this._localAudioPipeline || !this.peerConnection) {\n      return;\n    }\n    const raw = this.localStreamController.localAudioTracks.at(0);\n    this._localAudioPipeline.setInputTrack(raw ?? null);\n\n    // Resolve the audio sender via TransceiverController when available — it\n    // already filters audio transceivers reliably. Fall back to scanning\n    // peerConnection.getSenders() for an audio track (matches the classic\n    // addTrack path where no transceiver wrapper is tracked).\n    const audioTransceiver = this.transceiverController?.audioTransceivers.at(0);\n    const sender =\n      audioTransceiver?.sender ??\n      this.peerConnection.getSenders().find((s) => s.track?.kind === 'audio');\n    if (!sender || !raw) {\n      return;\n    }\n    try {\n      // Not a device capture, so the constraint paths must never re-acquire it.\n      this.localStreamController.setTrackOrigin(this._localAudioPipeline.outputTrack, 'processed');\n      await sender.replaceTrack(this._localAudioPipeline.outputTrack);\n    } catch (error) {\n      logger.warn(\n        '[RTCPeerConnectionController] Failed to route audio sender through pipeline:',\n        error\n      );\n    }\n  }\n  /**\n   * Add a local media track to the peer connection.\n   * @param track - The MediaStreamTrack to add\n   */\n  public addLocalTrack(track: MediaStreamTrack): void {\n    if (!this.peerConnection) {\n      const error = new DependencyError('RTCPeerConnection is not initialized');\n      this._errors$.next(error);\n      throw error;\n    }\n\n    try {\n      // Add track to local stream controller\n      const localStream = this.localStreamController.addTrack(track);\n\n      // Add track to peer connection\n      this.peerConnection.addTrack(track, localStream);\n\n      logger.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);\n    } catch (error) {\n      logger.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);\n      // Mid-call track op: non-fatal — the call continues without the track.\n      this._errors$.next(new MediaTrackError('addLocalTrack', track.kind, error));\n      throw error;\n    }\n  }\n  /**\n   * Remove a local media track from the peer connection.\n   * @param trackId - The ID of the track to remove\n   */\n  public removeLocalTrack(trackId: string): void {\n    if (!this.peerConnection) {\n      const error = new DependencyError('RTCPeerConnection is not initialized');\n      this._errors$.next(error);\n      throw error;\n    }\n\n    const sender = this.peerConnection.getSenders().find((sender) => sender.track?.id === trackId);\n    if (!sender) {\n      logger.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);\n      return;\n    }\n\n    try {\n      // Remove sender from peer connection\n      this.peerConnection.removeTrack(sender);\n\n      // Remove track from local stream controller\n      this.localStreamController.removeTrack(trackId);\n\n      logger.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);\n    } catch (error) {\n      logger.error(\n        `[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`,\n        error\n      );\n      // Mid-call track op: non-fatal — the call continues as-is.\n      this._errors$.next(\n        new MediaTrackError('removeLocalTrack', sender.track?.kind ?? 'unknown', error)\n      );\n      throw error;\n    }\n  }\n  /**\n   * Replace all existing media tracks with a new media  track.\n   * Convenience method for single-track scenarios.\n   * @param track - The MediaStreamTrack to set\n   */\n  public setLocalTrack(track: MediaStreamTrack): void {\n    // Remove all existing media tracks\n    const existingTracks = [\n      ...(track.kind === 'audio'\n        ? this.localStreamController.localAudioTracks\n        : this.localStreamController.localVideoTracks)\n    ];\n    for (const existingTrack of existingTracks) {\n      this.removeLocalTrack(existingTrack.id);\n    }\n\n    // Add the new track\n    this.addLocalTrack(track);\n  }\n  /**\n   * @returns whether the constraints reached the media the leg is sending.\n   *\n   * With the pipeline engaged the audio sender carries the processed\n   * destination track, so the sender scan would find nothing it may touch and\n   * every audio constraint API would silently no-op. The constraints belong to\n   * the pipeline's device source, which is the capture that sender ultimately\n   * carries.\n   */\n  public async updateSendersConstraints(\n    kind: 'audio' | 'video',\n    constraints?: MediaTrackConstraints\n  ): Promise<boolean> {\n    if (kind === 'audio' && this._localAudioPipeline) {\n      if (!constraints) {\n        // Omitted constraints mean \"release the sender\", which the scan\n        // implements as sender.track.stop() — here the destination track, which\n        // would stay ended for the rest of the call.\n        this.stopTrackSender('audio');\n        return false;\n      }\n      return this.applyPipelineSourceConstraints(constraints);\n    }\n    return (await this.transceiverController?.updateSendersConstraints(kind, constraints)) ?? false;\n  }\n\n  /**\n   * Mirror of the sender path for a piped audio leg: same merge, same fallback\n   * ladder, same device-capture invariant — but the swap target is the pipeline\n   * input, so the sender keeps emitting the pipeline's output track and its\n   * identity survives the change.\n   */\n  private async applyPipelineSourceConstraints(\n    constraints: MediaTrackConstraints\n  ): Promise<boolean> {\n    const pipeline = this._localAudioPipeline;\n    const source = this.localStreamController.localAudioTracks.at(0);\n    if (!pipeline || !source) {\n      logger.debug('[RTCPeerConnectionController] No pipeline input to constrain.');\n      return false;\n    }\n    if (!this.localStreamController.isDeviceCapture(source)) {\n      logger.debug(\n        '[RTCPeerConnectionController] Skipping audio constraints for a non-device pipeline ' +\n          `input (origin: ${this.localStreamController.getTrackOrigin(source) ?? 'unrecorded'}).`\n      );\n      return false;\n    }\n\n    const merged: MediaTrackConstraints = { ...source.getConstraints(), ...constraints };\n    try {\n      await source.applyConstraints(merged);\n      logger.debug('[RTCPeerConnectionController] Pipeline input constraints updated:', merged);\n      return true;\n    } catch (error) {\n      logger.warn(\n        '[RTCPeerConnectionController] applyConstraints failed on the pipeline input, re-acquiring:',\n        error\n      );\n    }\n\n    try {\n      const { stream } = await getUserMediaWithFallback(\n        { getUserMedia: async (c: MediaStreamConstraints) => this.getUserMedia(c) },\n        { audio: merged },\n        'audio',\n        source.getSettings().deviceId\n      );\n      const newTrack = stream.getAudioTracks().at(0);\n      if (!newTrack) {\n        stream.getTracks().forEach((track) => track.stop());\n        throw new Error('getUserMedia returned no audio track');\n      }\n      this.localStreamController.removeTrack(source.id);\n      this.localStreamController.addTrack(newTrack);\n      pipeline.setInputTrack(newTrack);\n      return true;\n    } catch (error) {\n      logger.warn(\n        '[RTCPeerConnectionController] Failed to re-acquire the pipeline input for constraints:',\n        error\n      );\n      // Mid-call track op: non-fatal — the constraints just did not take.\n      this._errors$.next(new MediaTrackError('updateSendersConstraints', 'audio', error));\n      return false;\n    }\n  }\n\n  /**\n   * Clean up resources and close the peer connection.\n   * Completes all observables to prevent memory leaks.\n   */\n  public destroy(): void {\n    logger.debug(\n      `[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`\n    );\n    this.removeConnectionTimer();\n    this._iceGatheringController?.destroy();\n    this._localAudioPipeline?.destroy();\n    this._localAudioPipeline = null;\n    this.localStreamController.destroy();\n    this.transceiverController?.destroy();\n\n    // Close peer connection\n    if (this.peerConnection) {\n      this.stopRemoteTracks();\n      this.removeAllListeners();\n      this.peerConnection.close();\n      this.peerConnection = undefined;\n    }\n\n    // Call parent destroy to clean up subscriptions and complete destroyed$\n    super.destroy();\n  }\n  private removeAllListeners() {\n    if (this.peerConnection) {\n      this.peerConnection.removeEventListener(\n        'icegatheringstatechange',\n        this.onicegatheringstatechangeHandler\n      );\n      this.peerConnection.removeEventListener(\n        'iceconnectionstatechange',\n        this.oniceconnectionstatechangeHandler\n      );\n      this.peerConnection.removeEventListener(\n        'connectionstatechange',\n        this.onconnectionstatechangeHandler\n      );\n      this.peerConnection.removeEventListener(\n        'signalingstatechange',\n        this.onsignalingstatechangeHandler\n      );\n      this.peerConnection.removeEventListener('negotiationneeded', this.onnegotiationneededHandler);\n    }\n  }\n\n  private stopRemoteTracks() {\n    const remoteStream = this._remoteStream$.value;\n    remoteStream?.getTracks().forEach((track: MediaStreamTrack) => {\n      logger.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);\n      track.stop();\n    });\n  }\n\n  public get mediaDirections(): {\n    audio: RTCRtpTransceiverDirection;\n    video: RTCRtpTransceiverDirection;\n  } {\n    return (\n      this.transceiverController?.getMediaDirections() ??\n      this._remoteOfferMediaDirections ?? {\n        audio: 'inactive',\n        video: 'inactive'\n      }\n    );\n  }\n  protected async _setRemoteDescription(params: RTCSessionDescriptionInit): Promise<void> {\n    if (!this.peerConnection) {\n      throw new DependencyError('RTCPeerConnection is not initialized');\n    }\n\n    const finalRemote = await this.setRemoteDescriptionBefore(params.sdp);\n\n    const answer: RTCSessionDescriptionInit = {\n      ...params,\n      sdp: finalRemote\n    };\n    logger.debug('[RTCPeerConnectionController] Setting remote description:', answer);\n    return this.peerConnection.setRemoteDescription(answer);\n  }\n}\n","// =============================================================================\n// VERTO TYPE GUARDS\n// =============================================================================\n// This file contains type guards for Verto protocol types.\n\nimport { hasProperty, isObject } from './base.guards';\n\nimport type { VertoMethod } from '../../types/rpc.types';\nimport type { TypeGuard } from '../types/base';\nimport type { WebrtcMessagePayload } from '../types/events';\nimport type {\n  VertoAnswerMessage,\n  VertoAnswerParams,\n  VertoAnswerResultMessage,\n  VertoAttachMessage,\n  VertoAttachParams,\n  VertoByeInboundMessage,\n  VertoByeInboundParams,\n  VertoByeParams,\n  VertoInviteMessage,\n  VertoInviteParams,\n  VertoMediaMessage,\n  VertoMediaParams,\n  VertoMediaParamsMessage,\n  VertoMediaParamsParams,\n  VertoMethodMessage,\n  VertoPingMessage,\n  VertoPingParams,\n  VertoPongMessage,\n  VertoPongParams\n} from '../types/verto';\n\n// =============================================================================\n// VERTO MESSAGE TYPE GUARDS\n// =============================================================================\n\nexport function isVertoMethodMessage(value: unknown): value is VertoMethodMessage {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'jsonrpc') &&\n    value.jsonrpc === '2.0' &&\n    hasProperty(value, 'id')\n  );\n}\n\nexport function isVertoAnswerMessage(value: unknown): value is VertoAnswerMessage {\n  if (!isVertoMethodMessage(value)) return false;\n  const msg = value as unknown as Record<string, unknown>;\n  return msg.method === 'verto.answer' && isObject(msg.params) && hasProperty(msg.params, 'callID');\n}\n\nexport function isVertoMediaMessage(value: unknown): value is VertoMediaMessage {\n  if (!isVertoMethodMessage(value)) return false;\n  const msg = value as unknown as Record<string, unknown>;\n  return (\n    msg.method === 'verto.media' &&\n    isObject(msg.params) &&\n    hasProperty(msg.params, 'callID') &&\n    hasProperty(msg.params, 'sdp')\n  );\n}\n\nexport function isVertoMediaParamsMessage(value: unknown): value is VertoMediaParamsMessage {\n  if (!isVertoMethodMessage(value)) return false;\n  const msg = value as unknown as Record<string, unknown>;\n  return (\n    msg.method === 'verto.mediaParams' &&\n    isObject(msg.params) &&\n    hasProperty(msg.params, 'mediaParams')\n  );\n}\n\nexport function isVertoPingMessage(value: unknown): value is VertoPingMessage {\n  if (!isVertoMethodMessage(value)) return false;\n  const msg = value as unknown as Record<string, unknown>;\n  return msg.method === 'verto.ping';\n}\n\nexport function isVertoPongMessage(value: unknown): value is VertoPongMessage {\n  if (!isVertoMethodMessage(value)) return false;\n  const msg = value as unknown as Record<string, unknown>;\n  return msg.method === 'verto.pong';\n}\n\nexport function isVertoInviteMessage(value: unknown): value is VertoInviteMessage {\n  if (!isVertoMethodMessage(value)) return false;\n  const msg = value as unknown as Record<string, unknown>;\n  return (\n    msg.method === 'verto.invite' &&\n    isObject(msg.params) &&\n    hasProperty(msg.params, 'sdp') &&\n    hasProperty(msg.params, 'callID')\n  );\n}\n\n/**\n * Guards inbound `verto.bye` frames (server → client). Outbound bye messages are\n * built locally and never type-guarded, so only the inbound shape is asserted.\n */\nexport function isVertoByeInboundMessage(value: unknown): value is VertoByeInboundMessage {\n  if (!isVertoMethodMessage(value)) return false;\n  const msg = value as unknown as Record<string, unknown>;\n  return msg.method === 'verto.bye';\n}\n\nexport function isVertoAnswerResultMessage(value: unknown): value is VertoAnswerResultMessage {\n  if (!isVertoMethodMessage(value)) return false;\n  const msg = value as unknown as Record<string, unknown>;\n  return (\n    isObject(msg.result) &&\n    hasProperty(msg.result, 'method') &&\n    msg.result.method === 'verto.answer'\n  );\n}\n\nexport function isVertoAttachMessage(value: unknown): value is VertoAttachMessage {\n  if (!isVertoMethodMessage(value)) return false;\n  const msg = value as unknown as Record<string, unknown>;\n  return msg.method === 'verto.attach';\n}\n\n// =============================================================================\n// WEBRTC MESSAGE EVENT DATA (inner params.params) TYPE GUARDS\n// =============================================================================\n\nexport function isVertoAnswerInnerParams(value: unknown): value is WebrtcMessagePayload & {\n  method: 'verto.answer';\n  params: VertoAnswerParams;\n} {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'jsonrpc') &&\n    value.jsonrpc === '2.0' &&\n    hasProperty(value, 'method') &&\n    value.method === 'verto.answer' &&\n    isObject(value.params) &&\n    hasProperty(value.params, 'callID')\n  );\n}\n\nexport function isVertoMediaInnerParams(value: unknown): value is WebrtcMessagePayload & {\n  method: 'verto.media';\n  params: VertoMediaParams;\n} {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'jsonrpc') &&\n    value.jsonrpc === '2.0' &&\n    hasProperty(value, 'method') &&\n    value.method === 'verto.media' &&\n    isObject(value.params) &&\n    hasProperty(value.params, 'callID') &&\n    hasProperty(value.params, 'sdp')\n  );\n}\n\nexport function isVertoMediaParamsInnerParams(value: unknown): value is WebrtcMessagePayload & {\n  method: 'verto.mediaParams';\n  params: VertoMediaParamsParams;\n} {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'jsonrpc') &&\n    value.jsonrpc === '2.0' &&\n    hasProperty(value, 'method') &&\n    value.method === 'verto.mediaParams' &&\n    isObject(value.params) &&\n    hasProperty(value.params, 'mediaParams')\n  );\n}\n\nexport function isVertoPingInnerParams(value: unknown): value is WebrtcMessagePayload & {\n  method: 'verto.ping';\n  params: VertoPingParams;\n} {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'jsonrpc') &&\n    value.jsonrpc === '2.0' &&\n    hasProperty(value, 'method') &&\n    value.method === 'verto.ping'\n  );\n}\n\n// =============================================================================\n// VERTO PARAMS TYPE GUARDS (for use with filterAs on Verto method params)\n// =============================================================================\n\nexport function isVertoAnswerParamsGuard(value: unknown): value is VertoAnswerParams {\n  return isObject(value) && hasProperty(value, 'callID') && typeof value.callID === 'string';\n}\n\nexport function isVertoMediaSdpParamsGuard(value: unknown): value is VertoMediaParams {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'callID') &&\n    typeof value.callID === 'string' &&\n    hasProperty(value, 'sdp') &&\n    typeof value.sdp === 'string'\n  );\n}\n\nexport function isVertoMediaParamsParamsGuard(value: unknown): value is VertoMediaParamsParams {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'callID') &&\n    typeof value.callID === 'string' &&\n    hasProperty(value, 'mediaParams') &&\n    isObject(value.mediaParams)\n  );\n}\n\nexport function isVertoPingParamsGuard(value: unknown): value is VertoPingParams {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'callID') &&\n    typeof value.callID === 'string' &&\n    hasProperty(value, 'dialogParams') &&\n    isObject(value.dialogParams)\n  );\n}\n\nexport function isVertoPongParamsGuard(value: unknown): value is VertoPongParams {\n  return isVertoPingParamsGuard(value);\n}\n\nexport function isVertoInviteParamsGuard(value: unknown): value is VertoInviteParams {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'dialogParams') &&\n    isObject(value.dialogParams) &&\n    hasProperty(value, 'sdp') &&\n    typeof value.sdp === 'string'\n  );\n}\n\nexport function isVertoByeParamsGuard(value: unknown): value is VertoByeParams {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'dialogParams') &&\n    isObject(value.dialogParams) &&\n    hasProperty(value.dialogParams, 'callID') &&\n    typeof value.dialogParams.callID === 'string'\n  );\n}\n\n/**\n * Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use\n * this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing\n * it against a boolean answer/reject signal.\n */\nexport function isVertoByeInboundParamsGuard(value: unknown): value is VertoByeInboundParams {\n  return isObject(value) && hasProperty(value, 'callID') && typeof value.callID === 'string';\n}\n\nexport function isVertoAttachParamsGuard(value: unknown): value is VertoAttachParams {\n  return (\n    isObject(value) &&\n    hasProperty(value, 'callID') &&\n    typeof value.callID === 'string' &&\n    hasProperty(value, 'sdp') &&\n    typeof value.sdp === 'string' &&\n    hasProperty(value, 'callee_id_number') &&\n    typeof value.callee_id_number === 'string' &&\n    hasProperty(value, 'callee_id_name') &&\n    typeof value.callee_id_name === 'string' &&\n    hasProperty(value, 'caller_id_number') &&\n    typeof value.caller_id_number === 'string' &&\n    hasProperty(value, 'caller_id_name') &&\n    typeof value.caller_id_name === 'string'\n  );\n}\n\n// =============================================================================\n// VERTO METHOD TYPE MAPPING\n// =============================================================================\n\nexport const VertoMethodTypeMap = {\n  'verto.answer': isVertoAnswerMessage,\n  'verto.media': isVertoMediaMessage,\n  'verto.mediaParams': isVertoMediaParamsMessage,\n  'verto.ping': isVertoPingMessage,\n  'verto.pong': isVertoPongMessage,\n  'verto.invite': isVertoInviteMessage,\n  'verto.bye': isVertoByeInboundMessage\n} as const satisfies Partial<Record<VertoMethod, TypeGuard<VertoMethodMessage>>>;\n\nexport type VertoMethodType = keyof typeof VertoMethodTypeMap;\n\n/**\n * Gets the appropriate type guard for a Verto method.\n */\nexport function getVertoMethodGuard(method: string): TypeGuard<VertoMethodMessage> | undefined {\n  return VertoMethodTypeMap[method as VertoMethodType];\n}\n\n// =============================================================================\n// VERTO PARAMS TYPE MAPPING\n// =============================================================================\n\nexport const VertoParamsTypeMap = {\n  'verto.answer': isVertoAnswerParamsGuard,\n  'verto.media': isVertoMediaSdpParamsGuard,\n  'verto.mediaParams': isVertoMediaParamsParamsGuard,\n  'verto.ping': isVertoPingParamsGuard,\n  'verto.pong': isVertoPongParamsGuard,\n  'verto.invite': isVertoInviteParamsGuard,\n  'verto.attach': isVertoAttachParamsGuard,\n  'verto.bye': isVertoByeParamsGuard\n} as const satisfies Partial<Record<VertoMethod, TypeGuard<unknown>>>;\n\n/**\n * Gets the appropriate params type guard for a Verto method.\n */\nexport function getVertoParamsGuard(\n  method: string\n):\n  | TypeGuard<\n      | VertoAnswerParams\n      | VertoMediaParams\n      | VertoMediaParamsParams\n      | VertoPingParams\n      | VertoPongParams\n      | VertoInviteParams\n      | VertoByeParams\n      | VertoAttachParams\n    >\n  | undefined {\n  return VertoParamsTypeMap[method as VertoMethodType];\n}\n","/* eslint-disable max-lines */\nimport {\n  EmptyError,\n  filter,\n  firstValueFrom,\n  map,\n  merge,\n  race,\n  skip,\n  startWith,\n  take,\n  takeUntil,\n  timeout,\n  TimeoutError,\n  switchMap\n} from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { PreferencesContainer } from '../containers/PreferencesContainer';\nimport { RTCPeerConnectionController } from '../controllers/RTCPeerConnectionController';\nimport { DEFAULT_AUX_LEG_CONNECT_TIMEOUT_MS, INVITE_VERSION } from '../core/constants';\nimport {\n  AuxiliaryLegCancelledError,\n  AuxiliaryLegTimeoutError,\n  DependencyError,\n  InvalidParams,\n  JSONRPCError,\n  MediaAccessError,\n  ScreenShareAlreadyActiveError,\n  VertoPongError\n} from '../core/errors';\nimport {\n  VertoAnswer,\n  VertoBye,\n  VertoByeCauseCodes,\n  VertoInfo,\n  VertoInvite,\n  VertoModify,\n  VertoPong,\n  WebrtcVerto\n} from '../core/RPCMessages';\nimport { isCallJoinedPayload } from '../core/RPCMessages/guards/events.guards';\nimport {\n  isVertoAnswerInnerParams,\n  isVertoAttachMessage,\n  isVertoByeInboundMessage,\n  isVertoByeInboundParamsGuard,\n  isVertoInviteMessage,\n  isVertoMediaInnerParams,\n  isVertoMediaParamsInnerParams,\n  isVertoPingInnerParams\n} from '../core/RPCMessages/guards/verto.guards';\nimport { filterAs } from '../operators';\nimport { filterNull } from '../operators/filterNull';\nimport { getValueFrom } from '../utils/getValueFrom';\nimport { getLogger } from '../utils/logger';\nimport { toError } from '../utils/toError';\n\nimport type { AttachManager } from './AttachManager';\nimport type { LocalAudioPipeline } from '../controllers/LocalAudioPipeline';\nimport type {\n  ExecuteVertoOptions,\n  ScreenShareStatus,\n  SignalingStatus,\n  TransferOptions,\n  WebRTCVertoManagerOptions\n} from './types/verto-manager.types';\nimport type { RTCPeerConnectionControllerOptionsPartial } from '../controllers/RTCPeerConnectionController';\nimport type { WebRTCCall } from '../core/entities/Call';\nimport type { VertoRPCMessage } from '../core/RPCMessages';\nimport type { JSONRPCResponse } from '../core/RPCMessages/types/base';\nimport type { CallJoinedPayload } from '../core/RPCMessages/types/events';\nimport type {\n  VertoAnswerParams,\n  VertoAttachParams,\n  VertoByeCause,\n  VertoByeInboundParams,\n  VertoMediaParams,\n  VertoMediaParamsParams,\n  VertoPingParams\n} from '../core/RPCMessages/types/verto';\nimport type { RTCPeerConnectionPropose } from '../core/types/call.types';\nimport type { MediaOptions, MediaDirections, ScreenShareOptions } from '../core/types/media.types';\nimport type { VertoMethod } from '../core/types/rpc.types';\nimport type { WebRTCApiProvider } from '../dependencies/interfaces';\nimport type { DeviceController } from '../interfaces/DeviceController';\nimport type { WebRTCVerto } from '../interfaces/WebRTCVerto';\nimport type { BehaviorSubject, Observable } from 'rxjs';\n\nconst logger = getLogger();\n\n/**\n * Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.\n *\n * - **Reattach invite:** must carry the persisted nodeId so the server routes\n *   the new connection to the FreeSWITCH instance that holds the existing call.\n * - **Fresh invite, caller-supplied `CallOptions.nodeId`:** carry the explicit\n *   value as a steering hint (dev/staging traffic pinning). Server may honour\n *   or ignore for placement reasons.\n * - **Fresh invite, no caller nodeId:** strip to `''` = \"server picks\".\n * - **Non-invite frames** (verto.modify, verto.bye, etc.): always carry the\n *   current `_nodeId$.value` so the frame targets the node hosting the call.\n *\n * Pure function — exported for unit testing.\n */\nexport function resolveInviteNodeId(args: {\n  isInvite: boolean;\n  reattach: boolean;\n  explicitNodeId: string | undefined;\n  currentNodeId: string | null;\n}): string {\n  const stripForFresh = args.isInvite && !args.reattach && !args.explicitNodeId;\n  return stripForFresh ? '' : (args.currentNodeId ?? '');\n}\n\n/**\n * Surface the real outcome of a `webrtc.verto` reply.\n *\n * A webrtc.verto response nests several envelopes, each keyed by a verto-style\n * string `code` (\"200\" ok, \"400\"/etc. fail) rather than a JSON-RPC `error`. An outer\n * layer reports only whether the frame was delivered; an inner layer carries the op's\n * own outcome:\n *\n *   response.result = { code:\"200\", result:{…} }            ← delivery acknowledgement\n *     .result       = { jsonrpc, id, result:{…} }           ← the reply payload\n *       .result     = { code:\"400\", message:\"Bad request\" } ← the actual op outcome\n *\n * A failure can appear at any layer (delivery refused, or the op itself rejected\n * deeper down), so walk every nested `.result` object and return the FIRST non-2xx\n * `code` with its message. Returns null when every `code` seen is 2xx or absent —\n * i.e. the op succeeded. This is the only way to detect that e.g. a mute/kick was\n * rejected, since the outer delivery `code` is \"200\" (delivered) even then.\n *\n * Pure function — exported for unit testing.\n */\nexport function findNestedVertoFailure(\n  response: unknown\n): { code: string; message?: string } | null {\n  let node: unknown = response;\n  while (node !== null && typeof node === 'object') {\n    const obj = node as { code?: unknown; message?: unknown; result?: unknown; error?: unknown };\n\n    // A refused op can arrive in either of two shapes at any depth. A JSONRPC `error`\n    // member is checked FIRST: its code lives one level in ({ error: { code, message } }),\n    // so a walk that only looks at a bare `code` steps straight past it and reports\n    // success — which is how an \"Invalid Request\" reply surfaced as a silent no-op, with\n    // the caller's promise resolving and the state observable simply never changing.\n    const err = obj.error;\n    if (err !== null && typeof err === 'object') {\n      const e = err as { code?: unknown; message?: unknown };\n      const errCode =\n        typeof e.code === 'string' || typeof e.code === 'number' ? String(e.code) : undefined;\n\n      if (errCode !== undefined && !/^2\\d\\d$/.test(errCode)) {\n        return {\n          code: errCode,\n          message: typeof e.message === 'string' ? e.message : undefined\n        };\n      }\n    }\n\n    // A verto `code` is a string or a number (\"200\" / 200) and a `message` is a string.\n    // Narrow to those rather than stringifying `unknown`, so a non-primitive never becomes\n    // the literal \"[object Object]\" and get mistaken for a failure code.\n    const code =\n      typeof obj.code === 'string' || typeof obj.code === 'number' ? String(obj.code) : undefined;\n    if (code !== undefined && !/^2\\d\\d$/.test(code)) {\n      return {\n        code,\n        message: typeof obj.message === 'string' ? obj.message : undefined\n      };\n    }\n    node = obj.result !== null && typeof obj.result === 'object' ? obj.result : null;\n  }\n  return null;\n}\n\nexport abstract class VertoManager extends Destroyable {\n  protected callSession?: WebRTCCall;\n\n  constructor(callSession?: WebRTCCall) {\n    super();\n    this.callSession = callSession;\n  }\n\n  public destroy(): void {\n    this.callSession = undefined;\n    super.destroy();\n  }\n}\nexport class WebRTCVertoManager extends VertoManager implements WebRTCVerto {\n  public mediaDirections$!: Observable<MediaDirections>;\n  public localStream$!: Observable<MediaStream>;\n  public remoteStream$!: Observable<MediaStream>;\n  private readonly onError?: (error: Error, options?: { fatal?: boolean }) => void;\n  private readonly onModifyFailed?: () => void;\n  private _rtcPeerConnections$ = this.createBehaviorSubject<RTCPeerConnectionController[]>([]);\n\n  private _nodeId$: BehaviorSubject<string | null>;\n  private _selfId$ = this.createBehaviorSubject<string | null>(null);\n  private _signalingStatus$ = this.createReplaySubject<SignalingStatus>(1);\n  private _screenShareStatus$ = this.createBehaviorSubject<ScreenShareStatus>('none');\n  private _rtcPeerConnectionsMap = new Map<string, RTCPeerConnectionController>();\n  private _screenShareId?: string;\n  /** Last verto.ping of each leg, answered again after a reconnect. */\n  private _lastVertoPings = new Map<string, VertoPingParams>();\n  /** Every leg-scoped error report, so a wait on one leg can end with it. */\n  private _legErrors$ = this.createSubject<{ legId: string; error: Error }>();\n\n  constructor(\n    protected webRtcCallSession: WebRTCCall,\n    private readonly attachManager: AttachManager,\n    private readonly deviceController: DeviceController,\n    private readonly webRTCApiProvider: WebRTCApiProvider,\n    options: WebRTCVertoManagerOptions = {}\n  ) {\n    super(webRtcCallSession);\n    this._nodeId$ = this.createBehaviorSubject<string | null>(options.nodeId ?? null);\n    this.onError = options.onError;\n    this.onModifyFailed = options.onModifyFailed;\n    this.initSubscriptions();\n    this.initMainPeerConnection();\n  }\n  async hold(): Promise<void> {\n    const vertoModifyMessage = VertoModify({\n      sessid: this.webRtcCallSession.id,\n      dialogParams: {\n        callID: this.webRtcCallSession.id\n      },\n      action: 'hold'\n    });\n\n    try {\n      await this.executeVerto(vertoModifyMessage);\n    } catch (error) {\n      logger.warn(\n        '[WebRTCManager] Call might already be disconnected, error sending Verto hold:',\n        error\n      );\n      throw error;\n    }\n  }\n  async unhold(): Promise<void> {\n    const vertoModifyMessage = VertoModify({\n      sessid: this.webRtcCallSession.id,\n      dialogParams: {\n        callID: this.webRtcCallSession.id\n      },\n      action: 'unhold'\n    });\n    try {\n      await this.executeVerto(vertoModifyMessage);\n    } catch (error) {\n      logger.warn(\n        '[WebRTCManager] Call might already be disconnected, error sending Verto unhold:',\n        error\n      );\n      throw error;\n    }\n  }\n\n  public get mediaDirections(): MediaDirections {\n    return this.mainPeerConnection.mediaDirections;\n  }\n\n  public get rtcPeerConnections$(): Observable<RTCPeerConnectionController[]> {\n    return this._rtcPeerConnections$.asObservable();\n  }\n\n  public get rtcPeerConnections(): RTCPeerConnectionController[] {\n    return this._rtcPeerConnections$.value;\n  }\n\n  public get nodeId$(): Observable<string | null> {\n    return this._nodeId$.asObservable();\n  }\n\n  public get selfId$(): Observable<string | null> {\n    return this._selfId$.asObservable();\n  }\n\n  /** Separates the media phase of call creation from the signalling phase. */\n  public get localMediaSettled$(): Observable<void> {\n    return this.mainPeerConnection.localMediaSettled$;\n  }\n\n  public get localStream(): MediaStream | null {\n    return this._rtcPeerConnectionsMap.get(this.webRtcCallSession.id)?.localStream ?? null;\n  }\n\n  public get remoteStream(): MediaStream | null {\n    return this._rtcPeerConnectionsMap.get(this.webRtcCallSession.id)?.remoteStream ?? null;\n  }\n\n  public get nodeId(): string | null {\n    return this._nodeId$.value;\n  }\n\n  public get screenShareStatus(): ScreenShareStatus {\n    return this._screenShareStatus$.value;\n  }\n\n  public get screenShareStatus$(): Observable<ScreenShareStatus> {\n    return this._screenShareStatus$.asObservable();\n  }\n\n  public get mainPeerConnection(): RTCPeerConnectionController {\n    const rtcPeerConnection = this._rtcPeerConnectionsMap.get(this.webRtcCallSession.id);\n    if (!rtcPeerConnection) {\n      throw new DependencyError('Main peer connection not found');\n    }\n    return rtcPeerConnection;\n  }\n\n  public get signalingStatus$(): Observable<SignalingStatus> {\n    return this.cachedObservable('signalingStatus$', () =>\n      merge(\n        this._signalingStatus$.asObservable(),\n        this.mainPeerConnection.connectionState$.pipe(\n          filter((connectionState) =>\n            ['connected', 'disconnected', 'failed'].includes(connectionState)\n          )\n        ) as Observable<SignalingStatus>\n      )\n    );\n  }\n\n  private initSubscriptions() {\n    // Eagerly populate node_id and selfId from call.joined events.\n    // During reattach, call.joined often arrives before the verto.invite\n    // RPC response (CALL CREATED) which is the authoritative source for\n    // these values. Populating them early prevents downstream RPCs\n    // (e.g. call.layout.list) from failing with empty identifiers.\n    this.subscribeTo(this.callJoinedEvent$, (event: CallJoinedPayload) => {\n      const memberNodeId = event.room_session.members.find(\n        (m) => m.call_id === event.call_id\n      )?.node_id;\n      if (memberNodeId) {\n        this.setNodeIdIfNull(memberNodeId);\n      }\n      if (event.member_id) {\n        this.setSelfIdIfNull(event.member_id);\n      }\n    });\n\n    this.subscribeTo(this.vertoMedia$, (event: VertoMediaParams) => {\n      logger.debug('[WebRTCManager] Received Verto media event (early media SDP):', event);\n      const { sdp, callID } = event;\n      this.emitMainSignalingStatus(callID, 'ringing');\n      const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);\n      void rtcPeerConnController?.updateAnswerStatus({\n        status: 'received',\n        sdp: sdp\n      });\n    });\n\n    this.subscribeTo(this.vertoAnswer$, (event: VertoAnswerParams) => {\n      logger.debug('[WebRTCManager] Received Verto answer event:', event);\n      const { sdp, callID } = event;\n      this.emitMainSignalingStatus(callID, 'connecting');\n      const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);\n      void rtcPeerConnController?.updateAnswerStatus({\n        status: 'received',\n        sdp: sdp\n      });\n    });\n\n    this.subscribeTo(this.vertoMediaParams$, (event: VertoMediaParamsParams) => {\n      logger.debug('[WebRTCManager] Received Verto mediaParams event:', event);\n\n      const { mediaParams, callID } = event;\n      const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);\n      const { audio, video } = mediaParams;\n\n      if (!rtcPeerConnController) {\n        // Emitting mediaParamsUpdated here would claim the constraints were\n        // applied when no sender was touched.\n        logger.warn(\n          `[WebRTCManager] Ignoring server-pushed media params for unknown leg ${callID}`\n        );\n        return;\n      }\n\n      void this.applyServerMediaParams(rtcPeerConnController, audio, video);\n    });\n\n    this.subscribeTo(this.vertoPing$, (vertoPing: VertoPingParams) => {\n      this._lastVertoPings.set(vertoPing.callID, vertoPing);\n      void this.attachManager.refresh(this.buildAttachableCall());\n      void this.sendVertoPong(vertoPing);\n    });\n\n    // The server ends a call when no verto.pong arrives for a full ping\n    // interval. It checks only the callID, not which ping the pong answers. A\n    // pong lost in a reconnect, or a ping never delivered, is not sent again,\n    // so answer the last ping of each leg when the session re-authenticates.\n    this.subscribeTo(\n      this.webRtcCallSession.clientSession.authenticated$.pipe(skip(1), filter(Boolean)),\n      () => {\n        this._lastVertoPings.forEach((vertoPing) => void this.sendVertoPong(vertoPing));\n      }\n    );\n  }\n\n  /**\n   * An auxiliary-leg failure must never destroy the call; main-leg and\n   * call-level errors keep `CallFactory.isFatalError`'s classification.\n   *\n   * Every site holding a peer connection reports through here, so the invariant\n   * is structural rather than per-call-site — which is how cloud-product#20523\n   * happened, with only one of fourteen sites passing `{ fatal: false }`.\n   *\n   * `override` composes rather than replaces: a caller may force non-fatal for a\n   * reason of its own (a `verto.info` frame is best-effort whichever leg carries\n   * it), and an auxiliary leg stays non-fatal regardless.\n   */\n  private reportLegError(\n    error: Error,\n    rtcPeerConnController?: RTCPeerConnectionController | null,\n    override?: { fatal?: boolean }\n  ): void {\n    const leg = rtcPeerConnController?.propose;\n    const legId = rtcPeerConnController?.id;\n    const auxiliary = Boolean(rtcPeerConnController) && !rtcPeerConnController?.isMainDevice;\n    this.onError?.(error, {\n      ...(override?.fatal === false || auxiliary ? { fatal: false } : {}),\n      ...(leg ? { leg } : {}),\n      ...(legId ? { legId } : {})\n    });\n    if (legId) {\n      this._legErrors$.next({ legId, error });\n    }\n  }\n\n  /**\n   * Errors reported for one leg, as a stream that fails with them.\n   *\n   * Signaling failures are reported, never thrown — so nothing that waits on a\n   * leg's progress would otherwise learn of a rejected invite. Merging this in\n   * lets the wait end with the reason the server gave.\n   */\n  private legError$(legId: string): Observable<never> {\n    return this._legErrors$.pipe(\n      filter((report) => report.legId === legId),\n      map((report) => {\n        throw report.error;\n      })\n    );\n  }\n\n  /**\n   * Audio and video are applied independently so a failure in one cannot\n   * suppress the other, and `mediaParamsUpdated` is emitted whatever happens —\n   * an application should not be starved of the params by a constraint failure.\n   */\n  private async applyServerMediaParams(\n    rtcPeerConnController: RTCPeerConnectionController,\n    audio: MediaTrackConstraints | undefined,\n    video: MediaTrackConstraints | undefined\n  ): Promise<void> {\n    const failures: Error[] = [];\n    // Every way the params can fail to reach the wire has to reach `applied`:\n    // a throw here, and — far more common — a leg that reports it left the\n    // media alone or could not constrain it.\n    let applied = true;\n\n    if (audio) {\n      try {\n        applied = (await rtcPeerConnController.updateSendersConstraints('audio', audio)) && applied;\n      } catch (error) {\n        applied = false;\n        failures.push(toError(error));\n      }\n    }\n\n    if (video) {\n      try {\n        applied = (await rtcPeerConnController.updateSendersConstraints('video', video)) && applied;\n      } catch (error) {\n        applied = false;\n        failures.push(toError(error));\n      }\n    }\n\n    this.webRtcCallSession.emitMediaParamsUpdated({\n      audio,\n      video,\n      timestamp: Date.now(),\n      applied\n    });\n\n    for (const failure of failures) {\n      logger.warn('[WebRTCManager] Error applying server-pushed media params:', failure);\n      // A push the media could not take is not a reason to end the call.\n      this.reportLegError(failure, rtcPeerConnController, { fatal: false });\n    }\n  }\n\n  /**\n   * Set node_id/selfId only when the current value is null.\n   *\n   * During reattach, `call.joined` and `verto.answer` events can deliver\n   * these identifiers before the `verto.invite` RPC response (`CALL CREATED`)\n   * arrives. These methods let early events populate them eagerly so that\n   * downstream RPC calls (e.g. `call.layout.list`) don't fail with empty\n   * identifiers. `processInviteResponse()` remains the authoritative source and\n   * overwrites unconditionally — for selfId, on the main leg only.\n   */\n  private setNodeIdIfNull(nodeId: string): void {\n    if (!this._nodeId$.value && nodeId) {\n      logger.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);\n      this._nodeId$.next(nodeId);\n    }\n  }\n\n  private setSelfIdIfNull(selfId: string): void {\n    if (!this._selfId$.value && selfId) {\n      logger.debug(`[WebRTCManager] Early selfId set: ${selfId}`);\n      this._selfId$.next(selfId);\n    }\n  }\n\n  private async sendVertoPong(vertoPing: VertoPingParams) {\n    try {\n      const vertoPongMessage = VertoPong({\n        ...vertoPing\n      });\n      await this.executeVerto(vertoPongMessage);\n    } catch (error) {\n      logger.warn('[WebRTCManager] Call might disconnect, error sending Verto pong:', error);\n      this.onError?.(new VertoPongError(error));\n    }\n  }\n\n  /**\n   * @returns whether the constraints reached the media the call is sending.\n   * `false` is an outcome, not a failure: the leg may have no live sender of\n   * the kind, or carry media the SDK did not capture and may not replace.\n   * A failure behind it still reaches the call's `errors$`, so a caller that\n   * ignores this value learns of it there.\n   */\n  public async updateMediaConstraints(\n    options: {\n      audio?: MediaTrackConstraints;\n      video?: MediaTrackConstraints;\n    } = {}\n  ): Promise<boolean> {\n    const { audio, video } = options;\n    let applied = true;\n    try {\n      if (audio) {\n        applied =\n          (await this.mainPeerConnection.updateSendersConstraints('audio', audio)) && applied;\n      }\n      if (video) {\n        applied =\n          (await this.mainPeerConnection.updateSendersConstraints('video', video)) && applied;\n      }\n    } catch (error) {\n      logger.warn('[WebRTCManager] Error updating media constraints:', error);\n      this.reportLegError(toError(error), this.mainPeerConnection);\n      throw error;\n    }\n    return applied;\n  }\n\n  public get selfId(): string | null {\n    return this._selfId$.value;\n  }\n\n  /** Build an AttachableCall from the current call state. */\n  private buildAttachableCall(idOverride?: string, nodeIdOverride?: string) {\n    return {\n      nodeId: nodeIdOverride ?? this.nodeId ?? undefined,\n      id: idOverride ?? this.webRtcCallSession.id,\n      to: this.webRtcCallSession.to,\n      mediaDirections: this.webRtcCallSession.mediaDirections\n    };\n  }\n\n  /**\n   * Request a video keyframe via RTCP PLI/FIR.\n   *\n   * Uses RTCRtpReceiver.requestKeyFrame() (Chrome 124+) to send a\n   * Picture Loss Indication to the remote sender. This is a client-side\n   * WebRTC operation — no server RPC is needed.\n   *\n   * Best-effort: logs a warning on failure, never emits on errors$.\n   */\n  public requestKeyframe(): void {\n    try {\n      const pc = this.mainPeerConnection.peerConnection;\n      if (!pc) {\n        logger.warn('[WebRTCManager] No peer connection for keyframe request');\n        return;\n      }\n\n      const videoReceiver = pc.getReceivers().find((r) => r.track.kind === 'video');\n      if (!videoReceiver) {\n        logger.warn('[WebRTCManager] No video receiver for keyframe request');\n        return;\n      }\n\n      // RTCRtpReceiver.requestKeyFrame() sends a PLI/FIR via RTCP (Chrome 124+)\n      if (\n        typeof (videoReceiver as unknown as { requestKeyFrame?: () => void }).requestKeyFrame ===\n        'function'\n      ) {\n        (videoReceiver as unknown as { requestKeyFrame: () => void }).requestKeyFrame();\n        logger.debug('[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()');\n      } else {\n        logger.debug('[WebRTCManager] requestKeyFrame() not supported, skipping');\n      }\n    } catch (error) {\n      logger.warn('[WebRTCManager] Keyframe request failed (non-fatal):', error);\n    }\n  }\n\n  /**\n   * Request an ICE restart via the controller's negotiation pipeline.\n   *\n   * Triggers an ICE restart offer on the controller, which goes through the\n   * full SDP pipeline: createOffer → setLocalDescription → ICE gathering →\n   * localDescription$ emission → setupLocalDescriptionHandler sends verto.modify.\n   *\n   * This ensures the SDP sent to the server has fully gathered ICE candidates,\n   * real ports/IPs, and any configured SDP munging applied — matching the\n   * same pipeline used for the initial verto.invite.\n   */\n  public async requestIceRestart(relayOnly?: boolean): Promise<void> {\n    try {\n      const controller = this.mainPeerConnection;\n      if (!controller.peerConnection) {\n        logger.warn('[WebRTCManager] No peer connection for ICE restart');\n        return;\n      }\n\n      await controller.triggerIceRestart(relayOnly);\n      logger.info(`[WebRTCManager] ICE restart initiated${relayOnly ? ' (relay-only)' : ''}`);\n    } catch (error) {\n      logger.error('[WebRTCManager] ICE restart failed:', error);\n      throw error;\n    }\n  }\n\n  /**\n   * Request an ICE restart on ALL active peer connections (main + additional legs).\n   *\n   * Screen share and additional device legs each get their own ICE restart\n   * via the controller's negotiation pipeline. The SDP flows through\n   * localDescription$ → setupLocalDescriptionHandler → verto.modify,\n   * ensuring ICE gathering completes before the offer is sent.\n   *\n   * @param relayOnly - If true, constrain to TURN relay candidates only (Tier 3).\n   */\n  public async requestIceRestartAll(relayOnly?: boolean): Promise<void> {\n    const entries = Array.from(this._rtcPeerConnectionsMap.entries());\n    for (const [id, controller] of entries) {\n      try {\n        if (!controller.peerConnection) {\n          logger.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);\n          continue;\n        }\n\n        await controller.triggerIceRestart(relayOnly);\n        logger.info(\n          `[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? ' (relay-only)' : ''}`\n        );\n      } catch (error) {\n        logger.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);\n      }\n    }\n  }\n\n  /**\n   * Request a keyframe on video-receiving legs only.\n   *\n   * Screen share legs are send-only (getDisplayMedia) so they have no\n   * video receiver to request a keyframe from — they are skipped.\n   */\n  public requestKeyframeAll(): void {\n    for (const [id, controller] of this._rtcPeerConnectionsMap) {\n      if (controller.isScreenShare) {\n        logger.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);\n        continue;\n      }\n\n      try {\n        const pc = controller.peerConnection;\n        if (!pc) continue;\n\n        const videoReceiver = pc.getReceivers().find((r) => r.track.kind === 'video');\n        if (!videoReceiver) continue;\n\n        if (\n          typeof (videoReceiver as unknown as { requestKeyFrame?: () => void }).requestKeyFrame ===\n          'function'\n        ) {\n          (videoReceiver as unknown as { requestKeyFrame: () => void }).requestKeyFrame();\n          logger.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);\n        }\n      } catch (error) {\n        logger.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);\n      }\n    }\n  }\n\n  private get callJoinedEvent$() {\n    return this.webRtcCallSession.callEvent$.pipe(\n      filter(isCallJoinedPayload),\n      takeUntil(this.destroyed$)\n    );\n  }\n\n  private get vertoMedia$() {\n    return this.webRtcCallSession.webrtcMessages$.pipe(\n      filterAs(isVertoMediaInnerParams, 'params'),\n      takeUntil(this.destroyed$)\n    );\n  }\n\n  private get vertoAnswer$() {\n    return this.cachedObservable('vertoAnswer$', () =>\n      this.webRtcCallSession.webrtcMessages$.pipe(\n        filterAs(isVertoAnswerInnerParams, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  private get vertoMediaParams$() {\n    return this.cachedObservable('vertoMediaParams$', () =>\n      this.webRtcCallSession.webrtcMessages$.pipe(\n        filterAs(isVertoMediaParamsInnerParams, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  private get vertoBye$() {\n    return this.cachedObservable('vertoBye$', () =>\n      this.webRtcCallSession.webrtcMessages$.pipe(\n        filterAs(isVertoByeInboundMessage, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  private get vertoAttach$() {\n    return this.cachedObservable('vertoAttach$', () =>\n      this.webRtcCallSession.webrtcMessages$.pipe(\n        filterAs(isVertoAttachMessage, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  private get vertoPing$() {\n    return this.cachedObservable('vertoPing$', () =>\n      this.webRtcCallSession.webrtcMessages$.pipe(\n        filterAs(isVertoPingInnerParams, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /**\n   * Send a member-control op in-dialog via verto.info.\n   *\n   * The control payload rides in `params.command` — a sibling of `dialogParams`,\n   * at the same level as `dtmf` in {@link sendDigits} — and the inner verto.info\n   * is matched to this call's channel by `dialogParams.callID`, the in-dialog\n   * convention for member-scoped frames. Because it is delivered on the dialog\n   * itself, control lands on the call's own channel with no {node_id,call_id,member_id}\n   * \"self\" tuple to get wrong. The outer webrtc.verto envelope (added by executeVerto)\n   * still carries the own-leg callID + node_id for session routing.\n   *\n   * Keep `command` OUT of `dialogParams`: it is read at the params level, and\n   * filterVertoParams rewrites/filters dialogParams keys but passes params-level\n   * keys through verbatim.\n   */\n  public async sendCallControl(\n    method: string,\n    params: Record<string, unknown>\n  ): Promise<JSONRPCResponse<unknown>> {\n    const response = await this.executeVerto(\n      VertoInfo({\n        dialogParams: {\n          callID: this.webRtcCallSession.id\n        },\n        command: { method, params }\n      })\n    );\n    // A member-control command reports success/failure as a verto-style `code` buried in\n    // nested `.result` envelopes (see findNestedVertoFailure) — a shape executeVerto does\n    // not treat as an error at all, so it must be surfaced here. Reject the promise so\n    // callers handle it locally: CallEventsManager.updateLayouts .catch()es it;\n    // Participant.mute/kick etc. are awaited with try/catch.\n    //\n    // A rejected control op must never kill the call. That is enforced in executeVerto,\n    // which reports ANY verto.info failure non-fatally — including the JSONRPC-`error`\n    // shape that never reaches the check below.\n    const failure = findNestedVertoFailure(response);\n    if (failure) {\n      // JSONRPCError's `code` is numeric, so a non-numeric server code cannot be\n      // represented there — always carry the raw code in the message so it is not\n      // lost when the server also sends a `message`.\n      throw new JSONRPCError(\n        Number.parseInt(failure.code, 10) || 0,\n        `Call control \"${method}\" failed (code ${failure.code})${failure.message ? `: ${failure.message}` : ''}`,\n        undefined\n      );\n    }\n    return response;\n  }\n\n  private async executeVerto(\n    message: VertoRPCMessage,\n    optionals: ExecuteVertoOptions = {}\n  ): Promise<JSONRPCResponse<unknown>> {\n    const params = {\n      callID: optionals.callID ?? this.webRtcCallSession.id,\n\n      node_id: optionals.node_id ?? this._nodeId$.value ?? '',\n      message,\n      subscribe: optionals.subscribe\n    };\n\n    const webrtcVertoMessage = WebrtcVerto(params);\n\n    const response = await this.webRtcCallSession.execute(webrtcVertoMessage);\n\n    // A verto.info is a best-effort IN-DIALOG frame (DTMF via sendDigits, member control\n    // via sendCallControl). Its failure means \"that op did not happen\" — never \"the call\n    // is dead\"; the authoritative death signals are verto.bye and call.state. So report\n    // it non-fatally: CallFactory otherwise falls back to isFatalError(), which treats a\n    // JSONRPCError as fatal, and Call.emitError would then mark the call failed, send bye\n    // and destroy it. A rejected mute or DTMF digit must not tear down the call.\n    //\n    // Keyed off the method rather than a per-call-site option so it holds for every\n    // verto.info sender, including future ones. Signaling-critical frames\n    // (verto.invite/modify/bye) keep the default fatal classification.\n    const nonFatal = message.method === 'verto.info' ? { fatal: false } : undefined;\n\n    // The server states a failure either at the top level or nested under result.result,\n    // where the webrtc.verto envelope wraps the inner verto response.\n    const innerResult = getValueFrom<{ error?: { code: number; message: string; data?: unknown } }>(\n      response,\n      'result.result'\n    );\n    const failure = response.error ?? innerResult?.error;\n    if (failure) {\n      const error = new JSONRPCError(failure.code, failure.message, failure.data);\n      // verto.invite and verto.answer are the frames whose sender owns the failure:\n      // sendLocalDescription (or, for verto.answer, sendLocalDescriptionOnceAccepted)\n      // catches it and reports once, with the leg's identity and fatality. Reporting\n      // here as well would report it twice, and the second report — a bare Error from\n      // processInviteResponse, which cannot see a nested failure — is fatal whatever code\n      // the server sent, including the recoverable ones the session re-authenticates for.\n      if (message.method === 'verto.invite' || message.method === 'verto.answer') {\n        throw error;\n      }\n      this.reportLegError(error, this._rtcPeerConnectionsMap.get(params.callID), nonFatal);\n    }\n\n    return response;\n  }\n\n  private async sendLocalDescription(\n    message: VertoRPCMessage,\n    rtcPeerConnController: RTCPeerConnectionController\n  ): Promise<void> {\n    const vertoMethod: VertoMethod = message.method;\n\n    const optionalsParams = this.getSendLocalSDPOptionalParams(rtcPeerConnController, message);\n\n    try {\n      const response = await this.executeVerto(message, optionalsParams);\n\n      switch (vertoMethod) {\n        case 'verto.invite':\n          this.processInviteResponse(response, rtcPeerConnController);\n          break;\n        case 'verto.modify':\n          await this.processModifyResponse(response, rtcPeerConnController);\n          break;\n        default:\n      }\n    } catch (error) {\n      // A refused verto.answer is sendLocalDescriptionOnceAccepted's to report and\n      // mark 'failed' — rethrow so it doesn't land here as a false 'sent'.\n      if (vertoMethod === 'verto.answer') {\n        throw error;\n      }\n      // The one place a failed send is reported: execute() can reject before\n      // executeVerto inspects the response, and executeVerto throws a refused invite\n      // to here rather than returning it for processInviteResponse to re-diagnose.\n      logger.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);\n      this.reportLegError(toError(error), rtcPeerConnController);\n      if (vertoMethod === 'verto.modify') {\n        this.onModifyFailed?.();\n      }\n    }\n  }\n  private async processModifyResponse(\n    response: JSONRPCResponse<unknown>,\n    rtcPeerConnController: RTCPeerConnectionController\n  ) {\n    if (!response.error) {\n      const action = getValueFrom<string>(response, 'result.result.result.action');\n      const sdp = getValueFrom<string>(response, 'result.result.result.sdp');\n      if (action === 'updateMedia' && !!sdp) {\n        try {\n          await rtcPeerConnController.updateAnswerStatus({\n            status: 'received',\n            sdp\n          });\n        } catch (error) {\n          logger.warn('[WebRTCManager] Error processing modify response:', error);\n          const modifyError =\n            error instanceof Error ? error : new Error(String(error), { cause: error });\n          this.reportLegError(modifyError, rtcPeerConnController);\n        }\n      }\n    }\n  }\n\n  private emitMainSignalingStatus(callId: string, status: SignalingStatus): void {\n    const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);\n    if (!rtcPeerConnController) {\n      const signalingError = new DependencyError(\n        `Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`\n      );\n      logger.error('[WebRTCManager] Failed to emit signaling status:', {\n        callId,\n        status,\n        signalingError\n      });\n      // A failed auxiliary leg is now removed from the registry, so a late\n      // verto.answer/verto.media for it lands here. There is no leg left to\n      // report against and no status to emit — never a reason to end the call.\n      this.reportLegError(signalingError, null, { fatal: false });\n      return;\n    }\n\n    if (rtcPeerConnController.isMainDevice) {\n      this._signalingStatus$.next(status);\n    }\n  }\n\n  private processInviteResponse(\n    response: JSONRPCResponse<unknown>,\n    rtcPeerConnController: RTCPeerConnectionController\n  ) {\n    if (getValueFrom(response, 'result.result.result.message') === 'CALL CREATED') {\n      this.emitMainSignalingStatus(rtcPeerConnController.id, 'trying');\n      const nodeId = getValueFrom<string>(response, 'result.node_id') ?? null;\n      const memberId = getValueFrom<string>(response, 'result.result.result.memberID') ?? null;\n      const callId = getValueFrom<string>(response, 'result.result.result.callID');\n      logger.debug('[WebRTCManager] Verto invite response:', { callId, memberId, response });\n\n      // An auxiliary leg invite goes out with node_id '' (\"server picks\") and joins\n      // the conference as its OWN member, so the response names a member and a node\n      // that are the LEG's, not the call's. Keep them on the leg: `selfId`/`nodeId`\n      // are what Call.callSelf sends as the `self`/`target` of every member RPC, and\n      // what every main-leg frame is routed by.\n      rtcPeerConnController.setMemberId(memberId);\n      rtcPeerConnController.setNodeId(nodeId);\n      if (rtcPeerConnController.isMainDevice) {\n        this._selfId$.next(memberId);\n        this._nodeId$.next(nodeId);\n        void this.attachManager.attach(this.buildAttachableCall(callId, nodeId ?? undefined));\n      }\n      if (callId) {\n        this.webRtcCallSession.addCallId(callId);\n      }\n      logger.info('[WebRTCManager] Verto invite successful');\n      logger.debug(\n        `[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`\n      );\n    } else {\n      logger.error('[WebRTCManager] Verto invite failed:', response);\n      // A refusal never reaches here — executeVerto throws it to sendLocalDescription,\n      // which reports it with the code the server sent. What is left is a response that\n      // states no error and still is not the 'CALL CREATED' the invite asked for.\n      this.reportLegError(\n        new Error('Verto invite failed: unexpected response'),\n        rtcPeerConnController\n      );\n    }\n  }\n\n  private get RTCPeerConnectionConfig() {\n    return {\n      iceServers:\n        this.webRtcCallSession.clientSession.iceServers ?? PreferencesContainer.instance.iceServers,\n      relayOnly:\n        PreferencesContainer.instance.relayOnly ||\n        PreferencesContainer.instance.disableUdpIceServers,\n      disableUdpIceServers: PreferencesContainer.instance.disableUdpIceServers,\n      iceCandidateTimeout: PreferencesContainer.instance.iceCandidateTimeout,\n      iceGatheringTimeout: PreferencesContainer.instance.iceGatheringTimeout\n    };\n  }\n\n  private initMainPeerConnection() {\n    //if (this.webRtcCallSession.direction === 'outbound') {\n    const { options } = this.webRtcCallSession;\n    const rtcPeerConnController = new RTCPeerConnectionController(\n      {\n        propose: 'main',\n        callId: this.webRtcCallSession.id,\n        audio: options.audio,\n        video: options.video,\n        inputAudioDeviceConstraints: options.inputAudioDeviceConstraints,\n        inputVideoDeviceConstraints: options.inputVideoDeviceConstraints,\n        inputAudioStream: options.inputAudioStream,\n        inputVideoStream: options.inputVideoStream,\n        receiveAudio: options.receiveAudio,\n        receiveVideo: options.receiveVideo,\n        fallbackToReceiveOnly: options.fallbackToReceiveOnly,\n        webRTCApiProvider: this.webRTCApiProvider,\n        preferredVideoCodecs: options.preferredVideoCodecs,\n        preferredAudioCodecs: options.preferredAudioCodecs,\n        stereo: options.stereo,\n        ...this.RTCPeerConnectionConfig\n      },\n      options.initOffer,\n      this.deviceController\n    );\n    this.setupLocalDescriptionHandler(rtcPeerConnController);\n    this.setupVertoByeHandler();\n    this.setupVertoAttachHandler();\n    this.initObservables(rtcPeerConnController);\n    this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);\n    this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));\n    this.subscribeTo(rtcPeerConnController.errors$, (error) => {\n      this.reportLegError(error, rtcPeerConnController);\n    });\n\n    // For inbound calls, wait for answer()/reject() then trigger SDP answer creation\n    if (options.initOffer) {\n      void this.handleInboundAnswer(rtcPeerConnController);\n    }\n  }\n\n  private async handleInboundAnswer(\n    rtcPeerConnController: RTCPeerConnectionController\n  ): Promise<void> {\n    logger.debug('[WebRTCManager] Waiting for inbound call to be accepted or rejected');\n    const vertoByeOrAccepted: boolean | VertoByeInboundParams | null = await firstValueFrom(\n      race(this.vertoBye$, this.webRtcCallSession.answered$).pipe(takeUntil(this.destroyed$))\n    ).catch(() => null);\n\n    if (vertoByeOrAccepted === null) {\n      logger.debug('[WebRTCManager] Inbound answer handler aborted (destroyed).');\n      return;\n    }\n\n    if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {\n      logger.info('[WebRTCManager] Inbound call ended by remote before answer.');\n      this.callSession?.destroy();\n    } else if (!vertoByeOrAccepted) {\n      logger.info('[WebRTCManager] Inbound call rejected by user.');\n      try {\n        await this.bye('USER_BUSY');\n      } finally {\n        this._signalingStatus$.next('disconnected');\n        this.callSession?.destroy();\n      }\n    } else {\n      logger.debug('[WebRTCManager] Inbound call accepted, creating SDP answer');\n      const answerOptions: MediaOptions | undefined = this.webRtcCallSession.answerMediaOptions;\n      try {\n        await rtcPeerConnController.acceptInbound(answerOptions);\n      } catch (error) {\n        logger.error('[WebRTCManager] Error creating inbound answer:', error);\n        this.reportLegError(toError(error), rtcPeerConnController);\n      }\n    }\n  }\n\n  private setupVertoAttachHandler(): void {\n    this.subscribeTo(this.vertoAttach$, async (vertoAttach: VertoAttachParams) => {\n      logger.debug('[WebRTCManager] Received Verto attach event for existing call:', vertoAttach);\n      const { callID } = vertoAttach;\n      await this.attachManager.attach({\n        nodeId: this.nodeId ?? undefined,\n        id: callID,\n        to: vertoAttach.callee_id_number,\n        mediaDirections: {\n          audio: 'sendrecv',\n          // this might be changed in future to support video attach, but this feature was originally supposed in the non-video SDK.\n          video: 'inactive'\n        }\n      });\n    });\n  }\n\n  private initObservables(rtcPeerConnController: RTCPeerConnectionController): void {\n    this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe(\n      filter((state) => state === 'connected'),\n      map(() => rtcPeerConnController.mediaDirections),\n      startWith(rtcPeerConnController.mediaDirections),\n      takeUntil(this.destroyed$)\n    );\n    this.localStream$ = rtcPeerConnController.localStream$.pipe(\n      filterNull(),\n      takeUntil(this.destroyed$)\n    );\n    this.remoteStream$ = rtcPeerConnController.remoteStream$.pipe(\n      filterNull(),\n      takeUntil(this.destroyed$)\n    );\n  }\n  private setupLocalDescriptionHandler(rtcPeerConnController: RTCPeerConnectionController): void {\n    this.subscribeTo(\n      // watch for local description from the RTCPeerConnection and send it to remote peer\n      rtcPeerConnController.localDescription$.pipe(\n        // Filter out null descriptions\n        filter((description): description is RTCSessionDescription => description !== null),\n        takeUntil(this.destroyed$)\n      ),\n      (description) => {\n        const { type, sdp } = description;\n        const dialogParams = this.dialogParams(rtcPeerConnController);\n        const initial = !rtcPeerConnController.firstSDPExchangeCompleted;\n        if (type === 'answer') {\n          {\n            const vertoMessageRequest = VertoAnswer({\n              dialogParams,\n              sdp: sdp\n            });\n            void this.sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnController);\n          }\n        } else if (initial) {\n          const vertoMessageRequest = VertoInvite({\n            dialogParams,\n            sdp\n          });\n          void this.sendLocalDescription(vertoMessageRequest, rtcPeerConnController);\n        } else {\n          const vertoMessageRequest = VertoModify({\n            dialogParams,\n            sdp,\n            action: 'updateMedia'\n          });\n          void this.sendLocalDescription(vertoMessageRequest, rtcPeerConnController);\n        }\n      }\n    );\n  }\n\n  private setupVertoByeHandler() {\n    this.subscribeTo(this.vertoBye$, () => {\n      this._signalingStatus$.next('disconnected');\n      void this.attachManager.detach(this.buildAttachableCall());\n      this.callSession?.destroy();\n    });\n  }\n\n  private getSendLocalSDPOptionalParams(\n    rtcPeerConnController: RTCPeerConnectionController,\n    vertoMessage: VertoRPCMessage\n  ): ExecuteVertoOptions {\n    let subscribe = undefined;\n    const initial = !rtcPeerConnController.firstSDPExchangeCompleted;\n    if (initial) {\n      subscribe = [];\n      if (rtcPeerConnController.isMainDevice) {\n        subscribe.push(...PreferencesContainer.instance.inviteSubscribeMainDevice);\n      } else if (rtcPeerConnController.isAdditionalDevice) {\n        subscribe.push(...PreferencesContainer.instance.inviteSubscribeAdditionalDevice);\n      } else if (rtcPeerConnController.isScreenShare) {\n        subscribe.push(...PreferencesContainer.instance.inviteSubscribeScreenshare);\n      }\n    }\n    const optionalsParams = {\n      callID: rtcPeerConnController.id,\n      node_id: resolveInviteNodeId({\n        isInvite: isVertoInviteMessage(vertoMessage),\n        reattach: this.webRtcCallSession.options.reattach === true,\n        explicitNodeId: this.webRtcCallSession.options.nodeId,\n        currentNodeId: rtcPeerConnController.nodeId ?? this._nodeId$.value\n      }),\n      subscribe\n    };\n    return optionalsParams;\n  }\n\n  async sendLocalDescriptionOnceAccepted(\n    vertoMessageRequest: VertoRPCMessage,\n    rtcPeerConnectionController: RTCPeerConnectionController\n  ): Promise<void> {\n    logger.debug('[WebRTCManager] Waiting for call to be accepted or ended before sending answer');\n    const vertoByeOrAccepted: boolean | VertoByeInboundParams | null = await firstValueFrom(\n      race(this.vertoBye$, this.webRtcCallSession.answered$).pipe(takeUntil(this.destroyed$))\n    ).catch(() => null);\n\n    if (vertoByeOrAccepted === null) {\n      logger.debug('[WebRTCManager] Destroyed while waiting for call acceptance');\n      return;\n    }\n\n    if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {\n      logger.info('[WebRTCManager] Call ended before answer was sent.');\n      this.callSession?.destroy();\n    } else if (!vertoByeOrAccepted) {\n      logger.info('[WebRTCManager] Call was not accepted, sending verto.bye.');\n      try {\n        await this.bye('USER_BUSY');\n      } finally {\n        this._signalingStatus$.next('disconnected');\n        this.callSession?.destroy();\n      }\n    } else {\n      logger.debug('[WebRTCManager] Call accepted, sending answer');\n      try {\n        this.emitMainSignalingStatus(rtcPeerConnectionController.id, 'connecting');\n        await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);\n        await rtcPeerConnectionController.updateAnswerStatus({\n          status: 'sent'\n        });\n        await this.attachManager.attach(this.buildAttachableCall());\n      } catch (error) {\n        logger.error('[WebRTCManager] Error sending Verto answer:', error);\n        this.reportLegError(toError(error), rtcPeerConnectionController);\n        await rtcPeerConnectionController.updateAnswerStatus({\n          status: 'failed'\n        });\n      }\n    }\n  }\n\n  dialogParams(rtcPeerConnectionController: RTCPeerConnectionController): Record<string, unknown> {\n    const memberId = rtcPeerConnectionController.memberId ?? this._selfId$.value ?? undefined;\n    const attach =\n      rtcPeerConnectionController.propose === 'main' &&\n      !rtcPeerConnectionController.firstSDPExchangeCompleted &&\n      this.webRtcCallSession.options.reattach;\n\n    return {\n      id: rtcPeerConnectionController.isMainDevice\n        ? this.webRtcCallSession.id\n        : rtcPeerConnectionController.id,\n      destinationNumber: this.webRtcCallSession.to ?? this.webRtcCallSession.from,\n      attach,\n      reattaching: attach,\n      callerName: this.webRtcCallSession.fromName,\n      callerNumber: this.webRtcCallSession.from,\n      remoteCallerName: this.webRtcCallSession.toName,\n      remoteCallerNumber: this.webRtcCallSession.to,\n      userVariables: {\n        memberCallId: this.webRtcCallSession.id,\n        memberId,\n        ...this.webRtcCallSession.userVariables\n      },\n      screenShare: rtcPeerConnectionController.isScreenShare,\n      additionalDevice: rtcPeerConnectionController.isAdditionalDevice,\n      pingSupported: true,\n      version: INVITE_VERSION\n    };\n  }\n\n  public muteMainAudioInputDevice(): void {\n    return this.mainPeerConnection.stopTrackSender('audio');\n  }\n\n  public muteMainVideoInputDevice(): void {\n    return this.mainPeerConnection.stopTrackSender('video');\n  }\n\n  public async unmuteMainAudioInputDevice(): Promise<void> {\n    return this.mainPeerConnection.restoreTrackSender('audio');\n  }\n\n  public async unmuteMainVideoInputDevice(): Promise<void> {\n    return this.mainPeerConnection.restoreTrackSender('video');\n  }\n\n  /** Get or lazily create the local audio pipeline for the main peer connection. */\n  public ensureLocalAudioPipeline(): LocalAudioPipeline | null {\n    return this.mainPeerConnection.ensureLocalAudioPipeline();\n  }\n\n  /** The currently-active local audio pipeline, or null if it hasn't been created. */\n  public get localAudioPipeline(): LocalAudioPipeline | null {\n    return this.mainPeerConnection.localAudioPipeline;\n  }\n\n  public async addInputDevice(\n    options: MediaOptions = { audio: false, video: true }\n  ): Promise<string | undefined> {\n    return this.initAdditionalPeerConnection('additional-device', options);\n  }\n\n  /**\n   * Add a new input device to the main peer connection,\n   * only if a device of the same kind is not present already.\n   *\n   * @see selectAudioInputDevice\n   * @see selectVideoInputDevice\n   * @param options - Media options specifying which input devices to add (defaults to audio only).\n   */\n  public async addMainInputDevices(options: MediaOptions = { audio: true }): Promise<void> {\n    let deviceKind: 'audio' | 'video' | 'both' | undefined = undefined;\n\n    const { mediaDirections } = this.mainPeerConnection;\n\n    if (\n      options.audio ??\n      options.inputAudioDeviceConstraints ??\n      (options.inputAudioStream && mediaDirections.audio.startsWith('send'))\n    ) {\n      deviceKind = 'audio';\n    }\n    if (\n      options.video ??\n      options.inputVideoDeviceConstraints ??\n      (options.inputVideoStream && !mediaDirections.video.startsWith('send'))\n    ) {\n      deviceKind = deviceKind === 'audio' ? 'both' : 'video';\n    }\n    if (deviceKind) {\n      this.mainPeerConnection.updateMediaDevicesOptions(options);\n      await this.mainPeerConnection.restoreTrackSender(deviceKind);\n    } else {\n      const error = new InvalidParams('No valid device to be added');\n      // Main leg: CallFactory.isFatalError decides; this only attaches leg identity.\n      this.reportLegError(error, this.mainPeerConnection);\n      throw error;\n    }\n  }\n\n  public async addScreenMedia(options: ScreenShareOptions = {}): Promise<void> {\n    // `audio: false` keeps the share off the microphone; the surface's own\n    // audio is a separate request the display capture reads directly.\n    await this.initAdditionalPeerConnection('screenshare', {\n      audio: false,\n      screenShareAudio: options.audio ?? false\n    });\n  }\n\n  private async initAdditionalPeerConnection(\n    propose: RTCPeerConnectionPropose,\n    options: Omit<RTCPeerConnectionControllerOptionsPartial, 'propose'>\n  ): Promise<string | undefined> {\n    const isScreenShare = propose === 'screenshare';\n    // `_screenShareId` is the only handle to a share, so entering again would\n    // overwrite it and leave the first capture running with nothing able to\n    // stop it. Thrown before the try, whose shared catch would otherwise reset\n    // the live share's status; the registry check keeps a stale id from\n    // wedging screen share for the rest of the call.\n    if (\n      isScreenShare &&\n      this._screenShareId &&\n      this._rtcPeerConnectionsMap.has(this._screenShareId)\n    ) {\n      throw new ScreenShareAlreadyActiveError(this._screenShareId);\n    }\n    let firstPeerConnectionError: Error | undefined;\n    let rtcPeerConnController: RTCPeerConnectionController | null = null;\n    try {\n      if (isScreenShare) {\n        this._screenShareStatus$.next('starting');\n      }\n      rtcPeerConnController = new RTCPeerConnectionController(\n        {\n          ...options,\n          ...this.RTCPeerConnectionConfig,\n          propose,\n          webRTCApiProvider: this.webRTCApiProvider\n        },\n        undefined,\n        this.deviceController\n      );\n      this.setupLocalDescriptionHandler(rtcPeerConnController);\n      if (isScreenShare) {\n        this._screenShareId = rtcPeerConnController.id;\n      }\n      this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);\n      this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));\n      this.subscribeTo(rtcPeerConnController.errors$, (error) => {\n        // Forward auxiliary errors with their real type (the controller\n        // already emits typed MediaAccessError for acquisition failures) but\n        // never fatal: an auxiliary leg failure must not destroy the call.\n        firstPeerConnectionError ??= error;\n        this.reportLegError(error, rtcPeerConnController);\n      });\n      // Acquisition is unbounded — a screen-share picker is human time, and the\n      // human may take as long as they like. Only the connect that follows it is\n      // bounded — the same budget for either leg kind.\n      const pc = rtcPeerConnController;\n      await firstValueFrom(\n        merge(\n          pc.localMediaSettled$.pipe(\n            take(1),\n            switchMap(() =>\n              pc.connectionState$.pipe(\n                filter((state) => state === 'connected'),\n                take(1),\n                timeout(DEFAULT_AUX_LEG_CONNECT_TIMEOUT_MS)\n              )\n            )\n          ),\n          // A rejected invite or a failed SDP send is reported rather than\n          // thrown, and never reaches the controller's errors$ — so without\n          // this the leg would wait out the whole connect budget and then\n          // report a bare timeout instead of the reason the server gave.\n          this.legError$(pc.id)\n        ).pipe(\n          // Must stay AFTER timeout(): that ordering is what surfaces a\n          // destroy-during-wait as EmptyError, handled below as a benign cancel.\n          // Both lifetimes belong here: branch A is leg-scoped and completes\n          // when the leg is destroyed, but legError$ is manager-scoped and\n          // would hold the merge open for the rest of the call.\n          takeUntil(merge(this.destroyed$, pc.destroyed$))\n        )\n      );\n      if (isScreenShare) {\n        this._screenShareStatus$.next('started');\n      }\n      logger.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);\n      return rtcPeerConnController.id;\n    } catch (error) {\n      const cancelled = error instanceof AuxiliaryLegCancelledError;\n      const aborted = error instanceof EmptyError && !firstPeerConnectionError;\n      if (!cancelled && !aborted) {\n        logger.warn('[WebRTCManager] Error initializing additional peer connection:', error);\n      }\n      // A cancel destroys and deregisters the leg on its way in; destroying it\n      // again re-emits an identical registry update.\n      if (rtcPeerConnController && this._rtcPeerConnectionsMap.has(rtcPeerConnController.id)) {\n        rtcPeerConnController.destroy();\n        // Destroying the controller alone left a dead entry that\n        // requestIceRestartAll() and requestKeyframeAll() kept iterating, and\n        // that would also wedge the single-share guard above.\n        this._rtcPeerConnectionsMap.delete(rtcPeerConnController.id);\n        this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));\n      }\n      if (isScreenShare) {\n        // Or a later removeScreenMedia() would verto.bye a destroyed leg.\n        this._screenShareId = undefined;\n        this._screenShareStatus$.next('none');\n      }\n      if (cancelled) {\n        // Outranks any failure already reported on this leg: onError has had\n        // that one, and what the caller asked about is why the start ended.\n        logger.debug('[WebRTCManager] Additional peer connection removed before connecting.');\n        throw error;\n      }\n      if (firstPeerConnectionError) {\n        // Reject the public API with the raw getUserMedia/getDisplayMedia\n        // error so apps can inspect error.name (e.g. 'NotAllowedError').\n        throw firstPeerConnectionError instanceof MediaAccessError &&\n          firstPeerConnectionError.originalError instanceof Error\n          ? firstPeerConnectionError.originalError\n          : firstPeerConnectionError;\n      }\n      if (error instanceof EmptyError) {\n        // The wait for 'connected' was aborted with no failure: the call was\n        // hung up or the device removed while connecting — a benign cancel.\n        logger.debug('[WebRTCManager] Additional peer connection aborted before connecting.');\n        return undefined;\n      }\n      if (error instanceof TimeoutError) {\n        // A bare RxJS TimeoutError carries no leg and no cause.\n        throw new AuxiliaryLegTimeoutError(propose, error);\n      }\n      throw error instanceof Error ? error : new Error(String(error), { cause: error });\n    }\n  }\n\n  public async removeInputDevices(id: string): Promise<void> {\n    return this.removeAdditionalPeerConnection(id);\n  }\n\n  public removeMainInputDevice(options = { removeAudio: false, removeVideo: true }): void {\n    let removeTrack: 'audio' | 'video' | 'both' | undefined = undefined;\n    if (options.removeAudio) {\n      removeTrack = 'audio';\n    }\n    if (options.removeVideo) {\n      removeTrack = removeTrack === 'audio' ? 'both' : 'video';\n    }\n\n    if (removeTrack) {\n      return this.mainPeerConnection.stopTrackSender(removeTrack, {\n        updateTransceiverDirection: true\n      });\n    }\n  }\n\n  public async removeScreenMedia(): Promise<void> {\n    if (!['starting', 'started'].includes(this._screenShareStatus$.value)) {\n      // Returning matters while a stop is already in flight: the id outlives\n      // the awaited bye, so falling through sends a second bye for one leg.\n      logger.warn('[WebRTCManager] No active screen share to stop.');\n      return;\n    }\n    if (!this._screenShareId) {\n      logger.debug('[WebRTCManager] No screen share peer connection found.');\n      return;\n    }\n    this._screenShareStatus$.next('stopping');\n    await this.removeAdditionalPeerConnection(this._screenShareId);\n    this._screenShareId = undefined;\n    this._screenShareStatus$.next('none');\n  }\n\n  public async removeAdditionalPeerConnection(id: string): Promise<void> {\n    const rtcPeerConnController = this._rtcPeerConnectionsMap.get(id);\n    try {\n      if (rtcPeerConnController) {\n        await this.executeVertoBye(rtcPeerConnController);\n      }\n    } finally {\n      // Only a leg that never connected can still have an init waiting on it, so\n      // a cancel reported for one that came up would be a lie to this stream.\n      // Before the destroy, or that init ends on the bare EmptyError the destroy\n      // produces instead. Not via reportLegError(): an app-initiated removal is\n      // not a failure and must not reach onError.\n      if (rtcPeerConnController && rtcPeerConnController.connectionState !== 'connected') {\n        this._legErrors$.next({\n          legId: id,\n          error: new AuxiliaryLegCancelledError(rtcPeerConnController.propose)\n        });\n      }\n      rtcPeerConnController?.destroy();\n      this._rtcPeerConnectionsMap.delete(id);\n      this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));\n    }\n  }\n\n  private async executeVertoBye(\n    rtcPeerConnController: RTCPeerConnectionController,\n    cause?: VertoByeCause\n  ): Promise<void> {\n    try {\n      const causeParams = cause\n        ? {\n            cause: cause,\n            causeCode: VertoByeCauseCodes[cause]\n          }\n        : {};\n\n      await this.executeVerto(\n        VertoBye({\n          ...causeParams,\n          dialogParams: this.dialogParams(rtcPeerConnController)\n        }),\n        // Without this the frame — and any failure of it — is addressed to the\n        // main leg, so ending an auxiliary leg the server had already dropped\n        // was reported as a fatal main-leg error and destroyed the call.\n        {\n          callID: rtcPeerConnController.id,\n          node_id: rtcPeerConnController.nodeId ?? undefined\n        }\n      );\n    } catch (error) {\n      logger.warn(\n        '[WebRTCManager] Call might already be disconnected, error sending Verto bye:',\n        error\n      );\n      throw error;\n    }\n  }\n  public async bye(cause?: VertoByeCause): Promise<void> {\n    void this.attachManager.detach(this.buildAttachableCall());\n    const rtcPeerConnController = this._rtcPeerConnectionsMap.get(this.webRtcCallSession.id);\n    if (rtcPeerConnController) {\n      await this.executeVertoBye(rtcPeerConnController, cause);\n    }\n  }\n\n  public async sendDigits(dtmf: string): Promise<void> {\n    const vertoInfoMessage = VertoInfo({\n      sessid: this.webRtcCallSession.id,\n      dialogParams: {\n        callID: this.webRtcCallSession.id\n      },\n      dtmf\n    });\n\n    try {\n      await this.executeVerto(vertoInfoMessage);\n    } catch (error) {\n      logger.warn('[WebRTCManager] Error sending DTMF digits:', error);\n      throw error;\n    }\n  }\n\n  public async transfer(options: TransferOptions): Promise<void> {\n    const message = VertoModify({\n      ...options,\n      dialogParams: this.dialogParams(this.mainPeerConnection),\n      action: 'transfer'\n    });\n    try {\n      logger.debug('[WebRTCManager] Transferring call with options:', options);\n      await this.executeVerto(message);\n    } catch (error) {\n      logger.error('[WebRTCManager] Error transferring call:', error);\n      throw error;\n    }\n  }\n\n  public destroy(): void {\n    this._rtcPeerConnectionsMap.forEach((rtcPeerConnController) => {\n      rtcPeerConnController.destroy();\n    });\n    this._rtcPeerConnectionsMap.clear();\n    this._rtcPeerConnections$.complete();\n    super.destroy();\n  }\n}\n","import { animationFrameScheduler, interval, map } from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { AUDIO_LEVEL_POLL_INTERVAL_MS } from '../core/constants';\nimport { getLogger } from '../utils/logger';\n\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\nexport interface RemoteAudioMeterOptions {\n  /** Factory for AudioContext — override for tests. */\n  audioContextFactory?: () => AudioContext;\n  /** Polling interval for level$ in ms. Defaults to AUDIO_LEVEL_POLL_INTERVAL_MS. */\n  pollIntervalMs?: number;\n}\n\n/**\n * Read-only audio level meter for a remote MediaStream. Attaches an\n * AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream\n * without affecting the caller's playback path (no GainNode, no destination).\n *\n * The server delivers all remote audio as a single mixed stream — there is\n * no per-participant demux — so this meter reports the aggregate remote\n * level, not per-member.\n */\nexport class RemoteAudioMeter extends Destroyable {\n  private readonly _audioContext: AudioContext;\n  private readonly _analyser: AnalyserNode;\n  private readonly _analyserBuffer: Uint8Array<ArrayBuffer>;\n  private readonly _pollIntervalMs: number;\n\n  private _source: MediaStreamAudioSourceNode | null = null;\n  private _stream: MediaStream | null = null;\n\n  constructor(options: RemoteAudioMeterOptions = {}) {\n    super();\n    const ctxFactory = options.audioContextFactory ?? ((): AudioContext => new AudioContext());\n    this._audioContext = ctxFactory();\n    this._analyser = this._audioContext.createAnalyser();\n    this._analyser.fftSize = 2048;\n    this._analyser.smoothingTimeConstant = 0.3;\n    this._analyserBuffer = new Uint8Array(new ArrayBuffer(this._analyser.fftSize));\n    this._pollIntervalMs = options.pollIntervalMs ?? AUDIO_LEVEL_POLL_INTERVAL_MS;\n  }\n\n  /** RMS level of the remote audio, 0..1. 0 when no stream is attached. */\n  public get level$(): Observable<number> {\n    return this.deferEmission(\n      interval(this._pollIntervalMs, animationFrameScheduler).pipe(map(() => this.computeLevel()))\n    );\n  }\n\n  /**\n   * Attach (or replace) the MediaStream whose audio track is being metered.\n   * Pass null to detach without destroying the meter.\n   */\n  public setStream(stream: MediaStream | null): void {\n    if (this._source) {\n      try {\n        this._source.disconnect();\n      } catch (error) {\n        logger.debug('[RemoteAudioMeter] source disconnect warning:', error);\n      }\n      this._source = null;\n      this._stream = null;\n    }\n    if (!stream || stream.getAudioTracks().length === 0) {\n      return;\n    }\n    // Only meter audio tracks — build a dedicated stream to avoid AudioContext\n    // refcounting on video tracks.\n    this._stream = new MediaStream(stream.getAudioTracks());\n    this._source = this._audioContext.createMediaStreamSource(this._stream);\n    // Intentionally do NOT connect to destination — we're observing only.\n  }\n\n  public override destroy(): void {\n    if (this._source) {\n      try {\n        this._source.disconnect();\n      } catch {\n        // already disconnected\n      }\n      this._source = null;\n    }\n    void this._audioContext.close().catch((error: unknown) => {\n      logger.debug('[RemoteAudioMeter] audio context close warning:', error);\n    });\n    super.destroy();\n  }\n\n  private computeLevel(): number {\n    if (!this._source) {\n      return 0;\n    }\n    this._analyser.getByteTimeDomainData(this._analyserBuffer);\n    let sum = 0;\n    for (const sample of this._analyserBuffer) {\n      const normalized = (sample - 128) / 128;\n      sum += normalized * normalized;\n    }\n    return Math.sqrt(sum / this._analyserBuffer.length);\n  }\n}\n","/**\n * RTCStatsMonitor — per-call WebRTC stats polling, baseline calculation,\n * and network issue detection (audio/video packets, RTT, jitter, packet loss).\n *\n * Extends Destroyable for automatic RxJS cleanup.\n */\n\nimport {\n  map,\n  distinctUntilChanged,\n  takeUntil,\n  interval,\n  switchMap,\n  from,\n  filter,\n  EMPTY,\n  catchError,\n  scan,\n  take,\n  toArray,\n  mergeMap\n} from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { getLogger } from '../utils/logger';\n\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface NetworkIssue {\n  type:\n    | 'no_inbound_audio'\n    | 'no_inbound_video'\n    | 'high_rtt'\n    | 'high_packet_loss'\n    | 'high_jitter'\n    | 'ice_disconnected';\n  severity: 'warning' | 'critical';\n  timestamp: number;\n  value?: number;\n  threshold?: number;\n}\n\nexport interface NetworkMetrics {\n  timestamp: number;\n  audio: {\n    packetsReceived: number;\n    packetsLost: number;\n    jitter: number;\n  };\n  video: {\n    packetsReceived: number;\n    packetsLost: number;\n  };\n  roundTripTime: number;\n  availableOutgoingBitrate?: number;\n}\n\nexport interface RTCStatsMonitorConfig {\n  /** Polling interval in milliseconds. */\n  pollingIntervalMs?: number;\n  /** Number of initial samples used to build baselines. */\n  baselineSamples?: number;\n  /** Max duration (ms) of no inbound audio packets before critical. */\n  noAudioPacketThresholdMs?: number;\n  /** Max duration (ms) of no inbound video packets before warning. */\n  noVideoPacketThresholdMs?: number;\n  /** RTT multiplier over baseline for warning. */\n  rttSpikeWarningMultiplier?: number;\n  /** RTT multiplier over baseline for critical. */\n  rttSpikeCriticalMultiplier?: number;\n  /** Packet-loss percentage for warning (e.g. 5 means 5%). */\n  packetLossWarningPercent?: number;\n  /** Packet-loss percentage for critical. */\n  packetLossCriticalPercent?: number;\n  /** Jitter multiplier over baseline for warning. */\n  jitterSpikeMultiplier?: number;\n  /** Rolling history window in seconds. */\n  historyWindowSeconds?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_POLLING_INTERVAL_MS = 1000;\nconst DEFAULT_BASELINE_SAMPLES = 10;\nconst DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2000;\nconst DEFAULT_NO_VIDEO_PACKET_THRESHOLD_MS = 3000;\nconst DEFAULT_RTT_SPIKE_WARNING_MULTIPLIER = 3;\nconst DEFAULT_RTT_SPIKE_CRITICAL_MULTIPLIER = 5;\nconst DEFAULT_PACKET_LOSS_WARNING_PERCENT = 5;\nconst DEFAULT_PACKET_LOSS_CRITICAL_PERCENT = 10;\nconst DEFAULT_JITTER_SPIKE_MULTIPLIER = 4;\nconst DEFAULT_HISTORY_WINDOW_SECONDS = 30;\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\ninterface RawSample {\n  audioPacketsReceived: number;\n  audioPacketsLost: number;\n  audioJitter: number;\n  videoPacketsReceived: number;\n  videoPacketsLost: number;\n  roundTripTime: number;\n  availableOutgoingBitrate: number | undefined;\n  timestamp: number;\n}\n\ninterface Baseline {\n  rtt: number;\n  jitter: number;\n  ready: boolean;\n}\n\nfunction computePacketLossPercent(lost: number, received: number): number {\n  const total = lost + received;\n  if (total === 0) {\n    return 0;\n  }\n  return (lost / total) * 100;\n}\n\n// Typed subsets of W3C RTCStats — only the fields we actually read.\n\ninterface InboundRtpStat {\n  type: 'inbound-rtp';\n  kind: 'audio' | 'video';\n  packetsReceived?: number;\n  packetsLost?: number;\n  jitter?: number;\n}\n\ninterface CandidatePairStat {\n  type: 'candidate-pair';\n  state: string;\n  nominated?: boolean;\n  currentRoundTripTime?: number;\n  availableOutgoingBitrate?: number;\n}\n\nfunction isInboundRtpStat(stat: unknown): stat is InboundRtpStat {\n  return typeof stat === 'object' && stat !== null && 'type' in stat && stat.type === 'inbound-rtp';\n}\n\nfunction isCandidatePairStat(stat: unknown): stat is CandidatePairStat {\n  return (\n    typeof stat === 'object' && stat !== null && 'type' in stat && stat.type === 'candidate-pair'\n  );\n}\n\n// ---------------------------------------------------------------------------\n// RTCStatsMonitor\n// ---------------------------------------------------------------------------\n\nexport class RTCStatsMonitor extends Destroyable {\n  // Config (immutable after construction)\n  private readonly pollingIntervalMs: number;\n  private readonly baselineSampleCount: number;\n  private readonly noAudioPacketThresholdMs: number;\n  private readonly noVideoPacketThresholdMs: number;\n  private readonly rttSpikeWarningMultiplier: number;\n  private readonly rttSpikeCriticalMultiplier: number;\n  private readonly packetLossWarningPercent: number;\n  private readonly packetLossCriticalPercent: number;\n  private readonly jitterSpikeMultiplier: number;\n  private readonly historyWindowSeconds: number;\n\n  // Polling state\n  private running = false;\n\n  // Packet-tracking for \"no packets\" detection\n  private lastAudioPacketsReceived = 0;\n  private lastAudioPacketChangeTime = 0;\n  private lastVideoPacketsReceived = 0;\n  private lastVideoPacketChangeTime = 0;\n  private lastRoundTripTime = 0;\n  private lastAvailableOutgoingBitrate: number | undefined = undefined;\n\n  // Subjects\n  private _sample$ = this.createReplaySubject<RawSample>(1);\n  private _baseline$ = this.createBehaviorSubject<Baseline>({ rtt: 0, jitter: 0, ready: false });\n  private _networkIssues$ = this.createBehaviorSubject<NetworkIssue[]>([]);\n  private _networkMetrics$ = this.createBehaviorSubject<NetworkMetrics[]>([]);\n\n  constructor(\n    private readonly peerConnection: RTCPeerConnection,\n    config: RTCStatsMonitorConfig = {}\n  ) {\n    super();\n\n    this.pollingIntervalMs = config.pollingIntervalMs ?? DEFAULT_POLLING_INTERVAL_MS;\n    this.baselineSampleCount = config.baselineSamples ?? DEFAULT_BASELINE_SAMPLES;\n    this.noAudioPacketThresholdMs =\n      config.noAudioPacketThresholdMs ?? DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS;\n    this.noVideoPacketThresholdMs =\n      config.noVideoPacketThresholdMs ?? DEFAULT_NO_VIDEO_PACKET_THRESHOLD_MS;\n    this.rttSpikeWarningMultiplier =\n      config.rttSpikeWarningMultiplier ?? DEFAULT_RTT_SPIKE_WARNING_MULTIPLIER;\n    this.rttSpikeCriticalMultiplier =\n      config.rttSpikeCriticalMultiplier ?? DEFAULT_RTT_SPIKE_CRITICAL_MULTIPLIER;\n    this.packetLossWarningPercent =\n      config.packetLossWarningPercent ?? DEFAULT_PACKET_LOSS_WARNING_PERCENT;\n    this.packetLossCriticalPercent =\n      config.packetLossCriticalPercent ?? DEFAULT_PACKET_LOSS_CRITICAL_PERCENT;\n    this.jitterSpikeMultiplier = config.jitterSpikeMultiplier ?? DEFAULT_JITTER_SPIKE_MULTIPLIER;\n    this.historyWindowSeconds = config.historyWindowSeconds ?? DEFAULT_HISTORY_WINDOW_SECONDS;\n  }\n\n  // -----------------------------------------------------------------------\n  // Public observables\n  // -----------------------------------------------------------------------\n\n  /** Current list of detected network issues (empty array = healthy). */\n  public get networkIssues$(): Observable<NetworkIssue[]> {\n    return this._networkIssues$.asObservable();\n  }\n\n  /** Snapshot of current issues (defensive copy). */\n  public get networkIssues(): NetworkIssue[] {\n    return [...this._networkIssues$.value];\n  }\n\n  /** Simple boolean health indicator. */\n  public get isNetworkHealthy$(): Observable<boolean> {\n    return this.cachedObservable('isNetworkHealthy$', () =>\n      this._networkIssues$.pipe(\n        map((issues) => issues.length === 0),\n        distinctUntilChanged(),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Whether the network is currently healthy. */\n  public get isNetworkHealthy(): boolean {\n    return this._networkIssues$.value.length === 0;\n  }\n\n  /** Rolling metrics history. */\n  public get networkMetrics$(): Observable<NetworkMetrics[]> {\n    return this._networkMetrics$.asObservable();\n  }\n\n  /** Snapshot of rolling metrics (defensive copy). */\n  public get networkMetrics(): NetworkMetrics[] {\n    return [...this._networkMetrics$.value];\n  }\n\n  /** Emits individual critical issues for the recovery pipeline. */\n  public get criticalIssue$(): Observable<NetworkIssue> {\n    return this.cachedObservable('criticalIssue$', () =>\n      this._networkIssues$.pipe(\n        mergeMap((issues) => from(issues.filter((i) => i.severity === 'critical'))),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Emits each raw stats sample extracted from the peer connection. */\n  public get sample$(): Observable<RawSample> {\n    return this._sample$.asObservable();\n  }\n\n  // -----------------------------------------------------------------------\n  // Lifecycle\n  // -----------------------------------------------------------------------\n\n  public start(): void {\n    if (this.running) {\n      return;\n    }\n    this.running = true;\n    const now = Date.now();\n    this.lastAudioPacketChangeTime = now;\n    this.lastVideoPacketChangeTime = now;\n\n    logger.debug('[RTCStatsMonitor] Starting stats monitoring');\n\n    // Poll peer connection stats on a fixed interval → extract raw samples\n    this.subscribeTo(\n      interval(this.pollingIntervalMs).pipe(\n        filter(() => this.running),\n        switchMap(() =>\n          from(this.peerConnection.getStats()).pipe(\n            catchError((err) => {\n              logger.warn('[RTCStatsMonitor] Failed to get stats:', err);\n              return EMPTY;\n            })\n          )\n        ),\n        filter(() => this.running),\n        map((report) => this.extractSample(report))\n      ),\n      (sample) => this._sample$.next(sample)\n    );\n\n    // Baseline: collect first N samples, compute averages, emit once\n    this.subscribeTo(\n      this._sample$.pipe(\n        take(this.baselineSampleCount),\n        toArray(),\n        map((samples) => ({\n          rtt: samples.reduce((s, b) => s + b.roundTripTime, 0) / samples.length,\n          jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,\n          ready: true as const\n        }))\n      ),\n      (baseline) => {\n        logger.debug(\n          `[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`\n        );\n        this._baseline$.next(baseline);\n      }\n    );\n\n    // Issue detection: carry previous sample via scan for delta-based packet loss\n    this.subscribeTo(\n      this._sample$.pipe(\n        scan((acc, sample) => ({ prev: acc.current, current: sample }), {\n          prev: null as RawSample | null,\n          current: null as RawSample | null\n        }),\n        filter(\n          (pair): pair is { prev: RawSample | null; current: RawSample } => pair.current !== null\n        )\n      ),\n      ({ prev, current }) => {\n        const now = current.timestamp;\n        this.updatePacketTracking(current, now);\n        const issues = this.detectIssues(current, prev, now);\n        this._networkIssues$.next(issues);\n      }\n    );\n\n    // Rolling metrics history via scan accumulator\n    this.subscribeTo(\n      this._sample$.pipe(\n        scan((history, sample) => {\n          const cutoff = sample.timestamp - this.historyWindowSeconds * 1000;\n          const metrics: NetworkMetrics = {\n            timestamp: sample.timestamp,\n            audio: {\n              packetsReceived: sample.audioPacketsReceived,\n              packetsLost: sample.audioPacketsLost,\n              jitter: sample.audioJitter\n            },\n            video: {\n              packetsReceived: sample.videoPacketsReceived,\n              packetsLost: sample.videoPacketsLost\n            },\n            roundTripTime: sample.roundTripTime,\n            availableOutgoingBitrate: sample.availableOutgoingBitrate\n          };\n          return [...history.filter((m) => m.timestamp > cutoff), metrics];\n        }, [] as NetworkMetrics[])\n      ),\n      (metrics) => this._networkMetrics$.next(metrics)\n    );\n  }\n\n  public stop(): void {\n    if (!this.running) {\n      return;\n    }\n    this.running = false;\n    logger.debug('[RTCStatsMonitor] Stopping stats monitoring');\n  }\n\n  public override destroy(): void {\n    logger.debug('[RTCStatsMonitor] Destroying RTCStatsMonitor');\n    this.stop();\n    super.destroy();\n  }\n\n  // -----------------------------------------------------------------------\n  // Sample extraction\n  // -----------------------------------------------------------------------\n\n  private extractSample(report: RTCStatsReport): RawSample {\n    let audioPacketsReceived = 0;\n    let audioPacketsLost = 0;\n    let audioJitter = 0;\n    let videoPacketsReceived = 0;\n    let videoPacketsLost = 0;\n    let roundTripTime = 0;\n    let availableOutgoingBitrate: number | undefined;\n\n    report.forEach((stat: unknown) => {\n      if (isInboundRtpStat(stat)) {\n        if (stat.kind === 'audio') {\n          // Accumulate across all audio inbound-rtp entries\n          audioPacketsReceived += stat.packetsReceived ?? 0;\n          audioPacketsLost += stat.packetsLost ?? 0;\n          audioJitter = Math.max(audioJitter, (stat.jitter ?? 0) * 1000); // seconds → ms, take worst\n        } else {\n          // Accumulate across all video inbound-rtp entries (simulcast layers)\n          videoPacketsReceived += stat.packetsReceived ?? 0;\n          videoPacketsLost += stat.packetsLost ?? 0;\n        }\n      }\n\n      if (isCandidatePairStat(stat) && stat.state === 'succeeded' && stat.nominated) {\n        roundTripTime = stat.currentRoundTripTime\n          ? stat.currentRoundTripTime * 1000\n          : this.lastRoundTripTime; // seconds → ms\n        availableOutgoingBitrate =\n          stat.availableOutgoingBitrate ?? this.lastAvailableOutgoingBitrate;\n      }\n    });\n\n    return {\n      audioPacketsReceived,\n      audioPacketsLost,\n      audioJitter,\n      videoPacketsReceived,\n      videoPacketsLost,\n      roundTripTime,\n      availableOutgoingBitrate,\n      timestamp: Date.now()\n    };\n  }\n\n  // -----------------------------------------------------------------------\n  // Packet tracking\n  // -----------------------------------------------------------------------\n\n  private updatePacketTracking(sample: RawSample, now: number): void {\n    if (sample.audioPacketsReceived !== this.lastAudioPacketsReceived) {\n      this.lastAudioPacketsReceived = sample.audioPacketsReceived;\n      this.lastAudioPacketChangeTime = now;\n    }\n\n    if (sample.videoPacketsReceived !== this.lastVideoPacketsReceived) {\n      this.lastVideoPacketsReceived = sample.videoPacketsReceived;\n      this.lastVideoPacketChangeTime = now;\n    }\n\n    if (sample.roundTripTime > 0) {\n      this.lastRoundTripTime = sample.roundTripTime;\n    }\n    if (sample.availableOutgoingBitrate !== undefined) {\n      this.lastAvailableOutgoingBitrate = sample.availableOutgoingBitrate;\n    }\n  }\n\n  // -----------------------------------------------------------------------\n  // Issue detection\n  // -----------------------------------------------------------------------\n\n  private detectIssues(\n    sample: RawSample,\n    prevSample: RawSample | null,\n    now: number\n  ): NetworkIssue[] {\n    const issues: NetworkIssue[] = [];\n    const baseline = this._baseline$.value;\n\n    // --- No inbound audio packets ---\n    const audioSilenceMs = now - this.lastAudioPacketChangeTime;\n    if (audioSilenceMs > this.noAudioPacketThresholdMs) {\n      issues.push({\n        type: 'no_inbound_audio',\n        severity: 'critical',\n        timestamp: now,\n        value: audioSilenceMs,\n        threshold: this.noAudioPacketThresholdMs\n      });\n    }\n\n    // --- No inbound video packets ---\n    const videoSilenceMs = now - this.lastVideoPacketChangeTime;\n    if (videoSilenceMs > this.noVideoPacketThresholdMs) {\n      issues.push({\n        type: 'no_inbound_video',\n        severity: 'warning',\n        timestamp: now,\n        value: videoSilenceMs,\n        threshold: this.noVideoPacketThresholdMs\n      });\n    }\n\n    // Baseline-dependent checks\n    if (baseline.ready) {\n      // --- RTT spike ---\n      const rtt = sample.roundTripTime;\n      const baselineRtt = baseline.rtt;\n      if (baselineRtt > 0) {\n        const rttRatio = rtt / baselineRtt;\n        if (rttRatio > this.rttSpikeCriticalMultiplier) {\n          issues.push({\n            type: 'high_rtt',\n            severity: 'critical',\n            timestamp: now,\n            value: rtt,\n            threshold: baselineRtt * this.rttSpikeCriticalMultiplier\n          });\n        } else if (rttRatio > this.rttSpikeWarningMultiplier) {\n          issues.push({\n            type: 'high_rtt',\n            severity: 'warning',\n            timestamp: now,\n            value: rtt,\n            threshold: baselineRtt * this.rttSpikeWarningMultiplier\n          });\n        }\n      }\n\n      // --- Jitter spike ---\n      const jitter = sample.audioJitter;\n      const baselineJitter = baseline.jitter;\n      if (baselineJitter > 0) {\n        const jitterRatio = jitter / baselineJitter;\n        if (jitterRatio > this.jitterSpikeMultiplier) {\n          issues.push({\n            type: 'high_jitter',\n            severity: 'warning',\n            timestamp: now,\n            value: jitter,\n            threshold: baselineJitter * this.jitterSpikeMultiplier\n          });\n        }\n      }\n    }\n\n    // --- Packet loss (delta-based via previous sample) ---\n    if (prevSample) {\n      // Clamp deltas to 0 — counters can reset to lower values after ICE restart\n      const deltaAudioReceived = Math.max(\n        0,\n        sample.audioPacketsReceived - prevSample.audioPacketsReceived\n      );\n      const deltaAudioLost = Math.max(0, sample.audioPacketsLost - prevSample.audioPacketsLost);\n      const deltaVideoReceived = Math.max(\n        0,\n        sample.videoPacketsReceived - prevSample.videoPacketsReceived\n      );\n      const deltaVideoLost = Math.max(0, sample.videoPacketsLost - prevSample.videoPacketsLost);\n\n      const totalDeltaReceived = deltaAudioReceived + deltaVideoReceived;\n      const totalDeltaLost = deltaAudioLost + deltaVideoLost;\n      const lossPercent = computePacketLossPercent(totalDeltaLost, totalDeltaReceived);\n\n      if (lossPercent > this.packetLossCriticalPercent) {\n        issues.push({\n          type: 'high_packet_loss',\n          severity: 'critical',\n          timestamp: now,\n          value: lossPercent,\n          threshold: this.packetLossCriticalPercent\n        });\n      } else if (lossPercent > this.packetLossWarningPercent) {\n        issues.push({\n          type: 'high_packet_loss',\n          severity: 'warning',\n          timestamp: now,\n          value: lossPercent,\n          threshold: this.packetLossWarningPercent\n        });\n      }\n    }\n\n    return issues;\n  }\n}\n","import {\n  merge,\n  debounceTime,\n  exhaustMap,\n  filter,\n  takeUntil,\n  timer,\n  from,\n  EMPTY,\n  take,\n  tap,\n  catchError,\n  combineLatest,\n  switchMap,\n  withLatestFrom,\n  map\n} from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { getLogger } from '../utils/logger';\n\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\n// ---------------------------------------------------------------------------\n// Constants (sensible defaults)\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_DEBOUNCE_TIME_MS = 2_000;\nconst DEFAULT_COOLDOWN_MS = 10_000;\nconst DEFAULT_ICE_GRACE_PERIOD_MS = 3_000;\nconst DEFAULT_ICE_RESTART_TIMEOUT_MS = 5_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\n\nconst DEFAULT_KEYFRAME_MAX_BURST = 3;\nconst DEFAULT_KEYFRAME_BURST_WINDOW_MS = 3_000;\nconst DEFAULT_KEYFRAME_COOLDOWN_MS = 10_000;\n\nconst DEFAULT_DEGRADATION_BITRATE_THRESHOLD = 150; // kbps\nconst DEFAULT_DEGRADATION_RECOVERY_THRESHOLD = 300; // kbps\nconst DEFAULT_PACKET_LOSS_RECOVERY_DELAY_SEC = 5;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type RecoveryState = 'idle' | 'debouncing' | 'recovering' | 'cooldown';\n\nexport interface RecoveryEvent {\n  action:\n    | 'keyframe_requested'\n    | 'reinvite_started'\n    | 'reinvite_succeeded'\n    | 'reinvite_failed'\n    | 'reinvite_timeout'\n    | 'max_attempts_reached'\n    | 'signal_reconnect'\n    | 'full_reconnect'\n    | 'video_disabled'\n    | 'video_restored';\n  reason: string;\n  attempt?: number;\n  maxAttempts?: number;\n  timestamp: number;\n}\n\nexport interface RecoveryConfig {\n  debounceTimeMs?: number;\n  cooldownMs?: number;\n  iceGracePeriodMs?: number;\n  iceRestartTimeoutMs?: number;\n  maxAttempts?: number;\n  enableRelayFallback?: boolean;\n  keyframeMaxBurst?: number;\n  keyframeBurstWindowMs?: number;\n  keyframeCooldownMs?: number;\n  degradationBitrateThreshold?: number;\n  degradationRecoveryThreshold?: number;\n  enableAutoDegradation?: boolean;\n  /** Seconds without packet loss before restoring video after degradation. */\n  packetLossRecoveryDelaySec?: number;\n}\n\nexport interface RecoveryCallbacks {\n  requestKeyframe: () => void;\n  requestIceRestart: (relayOnly?: boolean) => Promise<boolean>;\n  disableVideo: () => void;\n  enableVideo: () => void;\n  isNegotiating: () => boolean;\n  isCallConnected: () => boolean;\n  getPeerConnectionState: () => string | null;\n}\n\nexport interface RecoveryInputs {\n  /** Transport-level signaling readiness (true after WebSocket auth, false on disconnect). */\n  signalingReady$: Observable<boolean>;\n}\n\nexport interface RecoveryTrigger {\n  source: 'stats' | 'ice' | 'network';\n  detail: string;\n  /**\n   * Structured issue type from the stats monitor or ICE state machine.\n   * Used by runTiers() to route degradation-only triggers (high_rtt, high_jitter,\n   * high_packet_loss) to Tier 1 only, and connectivity-breaking triggers\n   * (no_inbound_audio, ice_disconnected) to the full tiered pipeline.\n   */\n  issueType?: string;\n}\n\n/**\n * Issue types that indicate quality degradation but NOT connectivity loss.\n * These should only trigger Tier 1 (keyframe) — never ICE restart.\n * The network path is slow or lossy, but media is still flowing.\n */\nconst DEGRADATION_ONLY_ISSUES: ReadonlySet<string> = new Set([\n  'high_rtt',\n  'high_jitter',\n  'high_packet_loss'\n]);\n\n// ---------------------------------------------------------------------------\n// CallRecoveryManager\n// ---------------------------------------------------------------------------\n\n/**\n * Implements the tiered recovery pipeline described in Sections 2, 19, 22,\n * and 25 of the implementation guide.\n *\n * Three detection signals (stats critical, ICE state, network events) are\n * merged, debounced, and processed through tiered recovery:\n *   Tier 1 - Request video keyframe (throttled burst)\n *   Tier 2 - ICE restart via verto.modify\n *   Tier 3 - Relay-only ICE restart\n *\n * Gate checks prevent recovery when the call is not in a recoverable state.\n * A state machine tracks the pipeline: IDLE -> DEBOUNCING -> RECOVERING -> COOLDOWN.\n */\nexport class CallRecoveryManager extends Destroyable {\n  // --- Configuration (immutable after construction) ---\n  private readonly _config: Required<RecoveryConfig>;\n  private readonly _callbacks: RecoveryCallbacks;\n  private readonly _inputs: RecoveryInputs;\n\n  // --- Observables (private subjects) ---\n  private readonly _recoveryState$ = this.createBehaviorSubject<RecoveryState>('idle');\n  private readonly _recoveryEvent$ = this.createSubject<RecoveryEvent>();\n  private readonly _bandwidthConstrained$ = this.createBehaviorSubject<boolean>(false);\n  private readonly _hasPacketLoss$ = this.createBehaviorSubject<boolean>(false);\n\n  // --- Input signals (callers push triggers here) ---\n  private readonly _trigger$ = this.createSubject<RecoveryTrigger>();\n\n  // --- Internal state ---\n  private _attemptCount = 0;\n  private _keyframeBurstCount = 0;\n  private _keyframeBurstStart = 0;\n  private _keyframeCooldownUntil = 0;\n  private _cooldownUntil = 0;\n  private _cooldownSubscription?: { unsubscribe(): void };\n\n  // --- Pipeline subscription lifecycle ---\n  private readonly _pipelineStop$ = this.createSubject<void>();\n\n  constructor(callbacks: RecoveryCallbacks, inputs: RecoveryInputs, config: RecoveryConfig = {}) {\n    super();\n    this._callbacks = callbacks;\n    this._inputs = inputs;\n    this._config = {\n      debounceTimeMs: config.debounceTimeMs ?? DEFAULT_DEBOUNCE_TIME_MS,\n      cooldownMs: config.cooldownMs ?? DEFAULT_COOLDOWN_MS,\n      iceGracePeriodMs: config.iceGracePeriodMs ?? DEFAULT_ICE_GRACE_PERIOD_MS,\n      iceRestartTimeoutMs: config.iceRestartTimeoutMs ?? DEFAULT_ICE_RESTART_TIMEOUT_MS,\n      maxAttempts: config.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,\n      enableRelayFallback: config.enableRelayFallback ?? true,\n      keyframeMaxBurst: config.keyframeMaxBurst ?? DEFAULT_KEYFRAME_MAX_BURST,\n      keyframeBurstWindowMs: config.keyframeBurstWindowMs ?? DEFAULT_KEYFRAME_BURST_WINDOW_MS,\n      keyframeCooldownMs: config.keyframeCooldownMs ?? DEFAULT_KEYFRAME_COOLDOWN_MS,\n      degradationBitrateThreshold:\n        config.degradationBitrateThreshold ?? DEFAULT_DEGRADATION_BITRATE_THRESHOLD,\n      degradationRecoveryThreshold:\n        config.degradationRecoveryThreshold ?? DEFAULT_DEGRADATION_RECOVERY_THRESHOLD,\n      enableAutoDegradation: config.enableAutoDegradation ?? true,\n      packetLossRecoveryDelaySec:\n        config.packetLossRecoveryDelaySec ?? DEFAULT_PACKET_LOSS_RECOVERY_DELAY_SEC\n    };\n\n    this.initPipeline();\n    this.initDegradationRecoveryPipeline();\n  }\n\n  // --- Public observable getters ---\n\n  public get recoveryState$(): Observable<RecoveryState> {\n    return this._recoveryState$.asObservable().pipe(takeUntil(this._destroyed$));\n  }\n\n  public get recoveryState(): RecoveryState {\n    return this._recoveryState$.value;\n  }\n\n  public get recoveryEvent$(): Observable<RecoveryEvent> {\n    return this._recoveryEvent$.asObservable().pipe(takeUntil(this._destroyed$));\n  }\n\n  public get bandwidthConstrained$(): Observable<boolean> {\n    return this._bandwidthConstrained$.asObservable().pipe(takeUntil(this._destroyed$));\n  }\n\n  public get bandwidthConstrained(): boolean {\n    return this._bandwidthConstrained$.value;\n  }\n\n  // --- Public API ---\n\n  /**\n   * Feed an external detection signal into the recovery pipeline.\n   * Multiple signals are debounced and collapsed automatically.\n   */\n  public pushTrigger(trigger: RecoveryTrigger): void {\n    this._trigger$.next(trigger);\n  }\n\n  /**\n   * Manual ICE restart request — bypasses cooldown and gate checks,\n   * but skips if the pipeline is already recovering to avoid races.\n   */\n  public async requestIceRestart(): Promise<void> {\n    if (this._recoveryState$.value === 'recovering') {\n      logger.info('CallRecoveryManager: manual ICE restart skipped — recovery already in progress');\n      return;\n    }\n    logger.info('CallRecoveryManager: manual ICE restart requested');\n    this.transitionTo('recovering');\n    await this.executeIceRestart(false);\n    this.startCooldown();\n  }\n\n  /**\n   * Manual keyframe request — subject to burst throttling only.\n   */\n  public requestKeyframe(): void {\n    this.executeKeyframe('manual request');\n  }\n\n  /**\n   * Reset all attempt counters and cooldowns. Should be called after\n   * WebSocket reconnect or call state recovers to 'connected'.\n   */\n  public reset(): void {\n    logger.info('CallRecoveryManager: resetting counters');\n    this._attemptCount = 0;\n    this._keyframeBurstCount = 0;\n    this._keyframeBurstStart = 0;\n    this._keyframeCooldownUntil = 0;\n    this._cooldownUntil = 0;\n    this.transitionTo('idle');\n  }\n\n  /**\n   * Notify the recovery manager that a verto.modify (ICE restart SDP exchange)\n   * failed at the signaling layer. Resets cooldown and pushes a new trigger\n   * so recovery can re-attempt.\n   */\n  public notifyModifyFailed(): void {\n    if (this._recoveryState$.value === 'cooldown' || this._recoveryState$.value === 'idle') {\n      logger.info('CallRecoveryManager: verto.modify failed — re-entering recovery');\n      this._cooldownUntil = 0;\n      this.transitionTo('idle');\n      this.pushTrigger({ source: 'network', detail: 'modify_failed_during_recovery' });\n    }\n  }\n\n  /**\n   * Feed bandwidth information for graceful degradation (Section 22).\n   * Call this from the stats monitor with current available outgoing bitrate.\n   */\n  public reportBandwidth(bitrateKbps: number): void {\n    if (!this._config.enableAutoDegradation) {\n      return;\n    }\n\n    const wasConstrained = this._bandwidthConstrained$.value;\n\n    if (!wasConstrained && bitrateKbps < this._config.degradationBitrateThreshold) {\n      this._bandwidthConstrained$.next(true);\n      this._callbacks.disableVideo();\n      this.emitEvent({\n        action: 'video_disabled',\n        reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,\n        timestamp: Date.now()\n      });\n      logger.warn(\n        `CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`\n      );\n    } else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {\n      this._bandwidthConstrained$.next(false);\n      this._callbacks.enableVideo();\n      this.emitEvent({\n        action: 'video_restored',\n        reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,\n        timestamp: Date.now()\n      });\n      logger.info(\n        `CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`\n      );\n    }\n  }\n\n  /**\n   * Feed current network issues for degradation recovery.\n   * When video was disabled due to bandwidth constraints, this pipeline\n   * monitors packet loss clearance to trigger video restoration.\n   */\n  public reportNetworkIssues(issues: { type: string }[]): void {\n    const hasPacketLoss = issues.some((i) => i.type === 'high_packet_loss');\n    if (hasPacketLoss !== this._hasPacketLoss$.value) {\n      this._hasPacketLoss$.next(hasPacketLoss);\n    }\n  }\n\n  /**\n   * Signal-only vs full reconnect distinction (Section 25).\n   * Call this when WebSocket reconnects to determine the appropriate action.\n   */\n  public handleWebSocketReconnect(): void {\n    const pcState = this._callbacks.getPeerConnectionState();\n    const isMediaAlive = pcState === 'connected' || pcState === 'completed';\n\n    if (isMediaAlive) {\n      logger.info('CallRecoveryManager: signal-only reconnect — peer connection still alive');\n      this.emitEvent({\n        action: 'signal_reconnect',\n        reason: 'WebSocket reconnected, peer connection still connected',\n        timestamp: Date.now()\n      });\n    } else {\n      logger.info('CallRecoveryManager: full reconnect — peer connection also down');\n      this.emitEvent({\n        action: 'full_reconnect',\n        reason: 'WebSocket reconnected, peer connection not connected — ICE restart needed',\n        timestamp: Date.now()\n      });\n      this.pushTrigger({ source: 'network', detail: 'full_reconnect_after_ws' });\n    }\n    this.reset();\n  }\n\n  // --- Lifecycle ---\n\n  public override destroy(): void {\n    this._pipelineStop$.next();\n    this._pipelineStop$.complete();\n    super.destroy();\n  }\n\n  // --- Pipeline initialization ---\n\n  private initPipeline(): void {\n    this.subscribeTo(\n      this._trigger$.pipe(\n        tap(() => {\n          if (this._recoveryState$.value === 'idle') {\n            this.transitionTo('debouncing');\n          }\n        }),\n        debounceTime(this._config.debounceTimeMs),\n        withLatestFrom(this._inputs.signalingReady$),\n        filter(([, signalingReady]) => this.passGateChecks(signalingReady)),\n        map(([trigger]) => trigger),\n        exhaustMap((trigger) => this.executeTieredRecovery(trigger)),\n        takeUntil(merge(this._destroyed$, this._pipelineStop$))\n      ),\n      {\n        next: () => {\n          /* side effects handled inside executeTieredRecovery */\n        },\n        error: (err) => {\n          logger.error('CallRecoveryManager: pipeline error', err);\n          this.transitionTo('idle');\n        }\n      }\n    );\n  }\n\n  /**\n   * When video was disabled due to bandwidth constraints, the browser's\n   * bandwidth estimator loses its primary probe signal (video traffic).\n   * This means availableOutgoingBitrate may never recover to the restoration\n   * threshold. Instead, we watch for packet loss clearance as a proxy for\n   * network recovery: if high_packet_loss stays absent for a configurable\n   * duration while video is degraded, we restore the video track.\n   */\n  private initDegradationRecoveryPipeline(): void {\n    if (!this._config.enableAutoDegradation) {\n      return;\n    }\n\n    const delayMs = this._config.packetLossRecoveryDelaySec * 1000;\n\n    this.subscribeTo(\n      combineLatest([this._bandwidthConstrained$, this._hasPacketLoss$]).pipe(\n        switchMap(([constrained, hasPacketLoss]) => {\n          if (constrained && !hasPacketLoss) {\n            return timer(delayMs);\n          }\n          return EMPTY;\n        }),\n        takeUntil(this._destroyed$)\n      ),\n      () => {\n        this._bandwidthConstrained$.next(false);\n        this._callbacks.enableVideo();\n        this.emitEvent({\n          action: 'video_restored',\n          reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,\n          timestamp: Date.now()\n        });\n        logger.info(\n          `CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`\n        );\n      }\n    );\n  }\n\n  // --- Gate checks ---\n\n  private passGateChecks(signalingReady: boolean): boolean {\n    if (this._callbacks.isNegotiating()) {\n      logger.debug('CallRecoveryManager: gate blocked — negotiation in progress');\n      this.transitionTo('idle');\n      return false;\n    }\n\n    if (!signalingReady) {\n      logger.debug('CallRecoveryManager: gate blocked — signaling not ready');\n      this.transitionTo('idle');\n      return false;\n    }\n\n    if (!this._callbacks.isCallConnected()) {\n      logger.debug('CallRecoveryManager: gate blocked — call not connected');\n      this.transitionTo('idle');\n      return false;\n    }\n\n    if (this.isCooldownActive()) {\n      logger.debug('CallRecoveryManager: gate blocked — cooldown active');\n      this.transitionTo('cooldown');\n      return false;\n    }\n\n    return true;\n  }\n\n  private isCooldownActive(): boolean {\n    return Date.now() < this._cooldownUntil;\n  }\n\n  // --- Tiered recovery ---\n\n  private executeTieredRecovery(trigger: RecoveryTrigger): Observable<void> {\n    this.transitionTo('recovering');\n    logger.info(\n      `CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`\n    );\n\n    return from(this.runTiers(trigger)).pipe(\n      tap(() => this.startCooldown()),\n      catchError((err) => {\n        logger.error('CallRecoveryManager: tiered recovery failed', err);\n        this.startCooldown();\n        return EMPTY;\n      })\n    );\n  }\n\n  private async runTiers(trigger: RecoveryTrigger): Promise<void> {\n    // Tier 1: keyframe request (always executed)\n    this.executeKeyframe(trigger.detail);\n\n    // Degradation-only issues (high_rtt, high_jitter, high_packet_loss) stop at Tier 1.\n    // The network path is slow/lossy but media is still flowing — ICE restart would\n    // cause an unnecessary media interruption on a connection that isn't broken.\n    if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {\n      logger.debug(\n        `CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`\n      );\n      return;\n    }\n\n    // Tier 2: ICE restart (standard) — only for connectivity-breaking issues\n    if (this._attemptCount < this._config.maxAttempts) {\n      const tier2Success = await this.executeIceRestart(false);\n      if (tier2Success) {\n        return;\n      }\n    }\n\n    // Tier 3: Relay-only ICE restart (when enabled)\n    if (this._config.enableRelayFallback && this._attemptCount < this._config.maxAttempts) {\n      const tier3Success = await this.executeIceRestart(true);\n      if (tier3Success) {\n        return;\n      }\n    }\n\n    // All tiers exhausted\n    if (this._attemptCount >= this._config.maxAttempts) {\n      this.emitEvent({\n        action: 'max_attempts_reached',\n        reason: `all ${this._config.maxAttempts} recovery attempts exhausted`,\n        attempt: this._attemptCount,\n        maxAttempts: this._config.maxAttempts,\n        timestamp: Date.now()\n      });\n      logger.warn('CallRecoveryManager: max recovery attempts reached');\n    }\n  }\n\n  // --- Tier 1: Keyframe ---\n\n  private executeKeyframe(reason: string): void {\n    const now = Date.now();\n\n    // Check cooldown\n    if (now < this._keyframeCooldownUntil) {\n      logger.debug('CallRecoveryManager: keyframe request skipped — cooldown active');\n      return;\n    }\n\n    // Check burst window\n    if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {\n      this._keyframeBurstCount = 0;\n      this._keyframeBurstStart = now;\n    }\n\n    if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {\n      // Burst limit reached — start cooldown\n      this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;\n      logger.debug(\n        `CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`\n      );\n      return;\n    }\n\n    this._keyframeBurstCount += 1;\n    this._callbacks.requestKeyframe();\n    this.emitEvent({\n      action: 'keyframe_requested',\n      reason,\n      timestamp: now\n    });\n    logger.debug(\n      `CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`\n    );\n  }\n\n  // --- Tier 2 & 3: ICE restart ---\n\n  private async executeIceRestart(relayOnly: boolean): Promise<boolean> {\n    this._attemptCount += 1;\n    const tier = relayOnly ? 'Tier 3 (relay-only)' : 'Tier 2 (standard)';\n    logger.info(\n      `CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`\n    );\n\n    this.emitEvent({\n      action: 'reinvite_started',\n      reason: `${tier} ICE restart`,\n      attempt: this._attemptCount,\n      maxAttempts: this._config.maxAttempts,\n      timestamp: Date.now()\n    });\n\n    try {\n      const success = await this.withTimeout(\n        this._callbacks.requestIceRestart(relayOnly),\n        this._config.iceRestartTimeoutMs\n      );\n\n      if (success) {\n        this.emitEvent({\n          action: 'reinvite_succeeded',\n          reason: `${tier} ICE restart succeeded`,\n          attempt: this._attemptCount,\n          maxAttempts: this._config.maxAttempts,\n          timestamp: Date.now()\n        });\n        logger.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);\n        this._attemptCount = 0;\n        return true;\n      }\n\n      this.emitEvent({\n        action: 'reinvite_failed',\n        reason: `${tier} ICE restart returned false`,\n        attempt: this._attemptCount,\n        maxAttempts: this._config.maxAttempts,\n        timestamp: Date.now()\n      });\n      logger.warn(`CallRecoveryManager: ${tier} ICE restart failed`);\n      return false;\n    } catch {\n      this.emitEvent({\n        action: 'reinvite_timeout',\n        reason: `${tier} ICE restart timed out after ${this._config.iceRestartTimeoutMs}ms`,\n        attempt: this._attemptCount,\n        maxAttempts: this._config.maxAttempts,\n        timestamp: Date.now()\n      });\n      logger.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);\n      return false;\n    }\n  }\n\n  // --- Utility ---\n\n  private async withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n    return new Promise<T>((resolve, reject) => {\n      const timeoutId = setTimeout(() => {\n        reject(new Error(`Timeout after ${timeoutMs}ms`));\n      }, timeoutMs);\n\n      promise.then(\n        (value) => {\n          clearTimeout(timeoutId);\n          resolve(value);\n        },\n        (err: unknown) => {\n          clearTimeout(timeoutId);\n          reject(err instanceof Error ? err : new Error(String(err)));\n        }\n      );\n    });\n  }\n\n  private transitionTo(state: RecoveryState): void {\n    const prev = this._recoveryState$.value;\n    if (prev !== state) {\n      logger.debug(`CallRecoveryManager: state ${prev} -> ${state}`);\n      this._recoveryState$.next(state);\n    }\n  }\n\n  private startCooldown(): void {\n    this._cooldownUntil = Date.now() + this._config.cooldownMs;\n    this.transitionTo('cooldown');\n\n    // Cancel any existing cooldown timer to prevent accumulation\n    if (this._cooldownSubscription) {\n      this._cooldownSubscription.unsubscribe();\n    }\n\n    // Schedule transition back to idle after cooldown\n    this._cooldownSubscription = timer(this._config.cooldownMs)\n      .pipe(\n        take(1),\n        takeUntil(this._destroyed$),\n        filter(() => this._recoveryState$.value === 'cooldown')\n      )\n      .subscribe(() => this.transitionTo('idle'));\n  }\n\n  private emitEvent(event: RecoveryEvent): void {\n    this._recoveryEvent$.next(event);\n  }\n}\n","import { Participant, SelfParticipant, type ExecuteMethod } from '../core/entities/Participant';\n\nimport type { DeviceController } from '../interfaces/DeviceController';\nimport type { VertoManager } from '../interfaces/VertoManager';\n\n/**\n * Factory for creating Participant instances with proper dependency injection\n * Eliminates circular dependency between Call and Participant\n */\nexport class ParticipantFactory {\n  constructor(\n    private executeMethod: ExecuteMethod,\n    private vertoManager: VertoManager,\n    private deviceController: DeviceController\n  ) {}\n\n  /**\n   * Create a self participant (the local user in the call)\n   */\n  public createSelfParticipant(id: string): SelfParticipant {\n    return new SelfParticipant(id, this.executeMethod, this.vertoManager, this.deviceController);\n  }\n\n  /**\n   * Create a remote participant\n   */\n  public createParticipant(id: string): Participant {\n    return new Participant(id, this.executeMethod, this.deviceController);\n  }\n}\n","/**\n * MOS (Mean Opinion Score) quality computation based on the simplified\n * ITU-T G.107 E-model.\n *\n * Provides a single 1-5 number that applications can use for a\n * green / yellow / red quality indicator without understanding raw\n * jitter and packet-loss values.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type QualityLevel = 'excellent' | 'good' | 'fair' | 'poor' | 'critical';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** E-model base R factor for a perfect channel. */\nconst E_MODEL_BASE_R = 93.2;\n\n/** Delay/jitter weight factor. */\nconst E_MODEL_DELAY_WEIGHT = 0.024;\n\n/** Packet-loss weight factor. */\nconst E_MODEL_LOSS_WEIGHT = 2.5;\n\n/** R-factor valid range for the E-model polynomial. */\nconst R_FACTOR_MIN = 0;\nconst R_FACTOR_MAX = 100;\n\n/** Lower bound for MOS. */\nconst MOS_MIN = 1.0;\n\n/** Upper bound for MOS. */\nconst MOS_MAX = 5.0;\n\n/** MOS threshold boundaries (lower bound of each level). */\nconst MOS_EXCELLENT_THRESHOLD = 4.0;\nconst MOS_GOOD_THRESHOLD = 3.5;\nconst MOS_FAIR_THRESHOLD = 3.0;\nconst MOS_POOR_THRESHOLD = 2.0;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Compute a Mean Opinion Score from network metrics using the simplified\n * E-model formula (ITU-T G.107).\n *\n * ```\n * R   = 93.2 - (rtt/2 + jitter) * 0.024 - packetLoss * 2.5\n * MOS = 1 + 0.035 * R + R * (R - 60) * (100 - R) * 7e-6\n * ```\n *\n * @param rtt         Round-trip time in milliseconds\n * @param jitter      Jitter in milliseconds\n * @param packetLoss  Packet loss as a percentage (e.g. 5 means 5%)\n * @returns MOS value clamped to [1.0, 5.0]\n */\nexport function computeMOS(rtt: number, jitter: number, packetLoss: number): number {\n  const rawR =\n    E_MODEL_BASE_R - (rtt / 2 + jitter) * E_MODEL_DELAY_WEIGHT - packetLoss * E_MODEL_LOSS_WEIGHT;\n\n  // Clamp R to [0, 100] — the MOS polynomial is only valid in this range\n  const r = Math.min(R_FACTOR_MAX, Math.max(R_FACTOR_MIN, rawR));\n\n  const mos = 1 + 0.035 * r + r * (r - 60) * (100 - r) * 7e-6;\n\n  return Math.min(MOS_MAX, Math.max(MOS_MIN, mos));\n}\n\n/**\n * Map a numeric MOS score to a human-readable quality level.\n *\n * | MOS Range   | Level       |\n * |-------------|-------------|\n * | 4.0 - 5.0   | excellent   |\n * | 3.5 - 4.0   | good        |\n * | 3.0 - 3.5   | fair        |\n * | 2.0 - 3.0   | poor        |\n * | 1.0 - 2.0   | critical    |\n */\nexport function mosToQualityLevel(mos: number): QualityLevel {\n  if (mos >= MOS_EXCELLENT_THRESHOLD) {\n    return 'excellent';\n  }\n  if (mos >= MOS_GOOD_THRESHOLD) {\n    return 'good';\n  }\n  if (mos >= MOS_FAIR_THRESHOLD) {\n    return 'fair';\n  }\n  if (mos >= MOS_POOR_THRESHOLD) {\n    return 'poor';\n  }\n  return 'critical';\n}\n","import { getValueFrom } from './getValueFrom';\n\nimport type { JSONRPCResponse } from '../core/RPCMessages/types/base';\n\n/**\n * Unwrap a method's own reply from a `webrtc.verto` envelope.\n *\n * A control verb sent in-dialog comes back nested two levels deep:\n *\n * ```\n * { result: { node_id, code, result: { jsonrpc, id, result: <method payload> } } }\n * ```\n *\n * whereas the routed transport resolves the method payload directly under\n * `.result`. Readers want the latter shape, and the difference is silent when it\n * is wrong — `response.result.layouts` simply evaluates to `undefined` against the\n * envelope, so the data arrives, nothing throws, and the caller sees an empty\n * value. That exact failure produced an empty layout dropdown with a successful\n * request behind it.\n *\n * Accepting both shapes here keeps every reader indifferent to which transport\n * produced the response. Anything that is not a nested envelope (a plain ack, or\n * an already-unwrapped reply) passes through untouched.\n */\nexport function unwrapVertoReply<T extends JSONRPCResponse = JSONRPCResponse>(\n  response: unknown\n): T {\n  const inner = getValueFrom<JSONRPCResponse>(response, 'result.result');\n  return (inner && typeof inner === 'object' && 'result' in inner ? inner : response) as T;\n}\n","import {\n  distinctUntilChanged,\n  filter,\n  firstValueFrom,\n  from,\n  map,\n  merge,\n  of,\n  share,\n  skip,\n  takeUntil,\n  tap\n} from 'rxjs';\nimport { v4 as uuid } from 'uuid';\n\nimport { Destroyable } from '../../behaviors/Destroyable';\nimport { PreferencesContainer } from '../../containers/PreferencesContainer';\nimport { RemoteAudioMeter } from '../../controllers/RemoteAudioMeter';\nimport { RTCStatsMonitor } from '../../controllers/RTCStatsMonitor';\nimport { CallRecoveryManager } from '../../managers/CallRecoveryManager';\nimport { ParticipantFactory } from '../../managers/ParticipantFactory';\nimport { filterAs } from '../../operators';\nimport { getValueFrom } from '../../utils/getValueFrom';\nimport { getLogger } from '../../utils/logger';\nimport { computeMOS, mosToQualityLevel } from '../../utils/qualityScore';\nimport { unwrapVertoReply } from '../../utils/unwrapVertoReply';\nimport { PEER_CONNECTION_RECOVERY_POLL_MS, PEER_CONNECTION_RECOVERY_WAIT_MS } from '../constants';\nimport { CallNotReadyError, InvalidParams, JSONRPCError, UnimplementedError } from '../errors';\nimport { buildRPCRequest, VertoSubscribe, WebrtcVerto } from '../RPCMessages';\nimport { isJSONRPCErrorResponse } from '../RPCMessages/guards/base.guards';\nimport {\n  isCallStateMetadata,\n  isCallUpdatedMetadata,\n  isLayoutChangedMetadata,\n  isMemberJoinedMetadata,\n  isMemberLeftMetadata,\n  isMemberTalkingMetadata,\n  isMemberUpdatedMetadata,\n  isSignalwireCallMetadata,\n  isWebrtcMessageMetadata\n} from '../RPCMessages/guards/events.guards';\n\nimport type { Address } from './Address';\nimport type { Participant, SelfParticipant } from './Participant';\nimport type {\n  Call,\n  CallStatus,\n  CallOptions,\n  CallManager,\n  CallParticipant,\n  CallSelfParticipant\n} from './types/call.types';\nimport type { NetworkChangeEvent } from '../../controllers/NetworkMonitor';\nimport type {\n  NetworkIssue as StatsNetworkIssue,\n  NetworkMetrics as StatsNetworkMetrics\n} from '../../controllers/RTCStatsMonitor';\nimport type { ClientSession } from '../../interfaces/ClientSession';\nimport type { DeviceController } from '../../interfaces/DeviceController';\nimport type { WebRTCVerto } from '../../interfaces/WebRTCVerto';\nimport type { CallEventsManager } from '../../managers/CallEventsManager';\nimport type {\n  RecoveryState,\n  RecoveryEvent,\n  RecoveryCallbacks\n} from '../../managers/CallRecoveryManager';\nimport type { TransferOptions } from '../../managers/types/verto-manager.types';\nimport type { QualityLevel } from '../../utils/qualityScore';\nimport type { CallError } from '../errors';\nimport type { JSONRPCParams } from '../RPCMessages';\nimport type { JSONRPCRequest, JSONRPCResponse } from '../RPCMessages/types/base';\nimport type { LayoutLayer, MemberTarget } from '../RPCMessages/types/common';\nimport type {\n  CallStatePayload,\n  CallUpdatedPayload,\n  LayoutChangedPayload,\n  MemberJoinedPayload,\n  MemberLeftPayload,\n  MemberTalkingPayload,\n  MemberUpdatedPayload\n} from '../RPCMessages/types/events';\nimport type { Capability, CallDirection, VideoPosition } from '../types/call.types';\nimport type { MediaOptions, MediaDirections } from '../types/media.types';\nimport type { MediaParamsEvent } from '../types/resilience.types';\nimport type { PendingRPCOptions } from '../utils';\nimport type { Observable, BehaviorSubject } from 'rxjs';\n\nconst logger = getLogger();\n\n/**\n * Ratio between the critical and warning RTT spike multipliers.\n * Warning threshold = baseline * warningMultiplier (default 3x)\n * Critical threshold = baseline * warningMultiplier * RTT_CRITICAL_TO_WARNING_RATIO\n * With default 3x warning: critical = 3 * 5/3 = 5x baseline.\n */\nconst RTT_CRITICAL_TO_WARNING_RATIO = 5 / 3;\n\n/**\n * Manager instances returned by initialization callback\n */\nexport interface CallManagers {\n  vertoManager: WebRTCVerto;\n  callEventsManager: CallEventsManager;\n}\n\n/**\n * Initialization callback that creates managers for a Call instance\n * @param call - The WebRTCCall instance being initialized\n * @returns Manager instances for the call\n */\nexport type ManagerInitializer = (call: WebRTCCall) => CallManagers;\n\n/**\n * Required initialization configuration for Call constructor.\n * Calls must be created via {@link CallFactory} which provides these dependencies.\n */\nexport interface CallInitialization {\n  /**\n   * Callback function that creates and wires manager instances\n   */\n  initializeManagers: ManagerInitializer;\n  /**\n   * Device controller for media device access\n   */\n  deviceController: DeviceController;\n  /**\n   * Network change events for feeding recovery pipeline\n   */\n  networkChange$?: Observable<NetworkChangeEvent>;\n}\n\nconst fromDestinationParams = (destination?: string): Record<string, unknown> => {\n  if (!destination) return {};\n  try {\n    const url = new URL(`destination:${destination}`);\n    const params: Record<string, unknown> = {};\n    url.searchParams.forEach((value, key) => {\n      params[key] = value;\n    });\n    return params;\n  } catch (error) {\n    logger.warn(`Failed to parse destination URI: ${destination}`, error);\n    return {};\n  }\n};\n\n/**\n * Concrete WebRTC call implementation.\n *\n * Manages the full lifecycle of a call including signaling, media streams,\n * participants, layout, and event routing. Created via {@link SignalWire.dial}\n * or received as an inbound call.\n */\nexport class WebRTCCall extends Destroyable implements CallManager, Call {\n  /** Unique identifier for this call. */\n  public readonly id: string;\n  /** Destination URI this call was placed to. */\n  public to?: string;\n  private vertoManager!: WebRTCVerto;\n  private callEventsManager!: CallEventsManager;\n  private participantFactory: ParticipantFactory;\n  private _errors$ = this.createReplaySubject<CallError>(1);\n  private _status$: BehaviorSubject<CallStatus>;\n  private _lastMergedStatus: CallStatus = 'new';\n  private _answered$ = this.createReplaySubject<boolean>();\n  private _answerMediaOptions?: MediaOptions;\n  private _holdState = false;\n  private _userVariables$ = this.createBehaviorSubject<Record<string, unknown>>({\n    ...PreferencesContainer.instance.userVariables\n  });\n\n  // Resilience subsystems (created lazily on 'connected')\n  private _statsMonitor?: RTCStatsMonitor;\n  private _recoveryManager?: CallRecoveryManager;\n  private _networkChange$?: Observable<NetworkChangeEvent>;\n\n  // Resilience subjects — created eagerly so subscribers can attach before 'connected'.\n  // Fed by stats monitor and recovery manager once they're initialized.\n  private _networkIssues$ = this.createBehaviorSubject<StatsNetworkIssue[]>([]);\n  private _networkMetrics$ = this.createBehaviorSubject<StatsNetworkMetrics[]>([]);\n  private _isNetworkHealthy$ = this.createBehaviorSubject<boolean>(true);\n  private _qualityScore$ = this.createBehaviorSubject<number>(5.0);\n  private _qualityLevel$ = this.createBehaviorSubject<QualityLevel>('excellent');\n  private _recoveryState$ = this.createBehaviorSubject<RecoveryState>('idle');\n  private _recoveryEvent$ = this.createSubject<RecoveryEvent>();\n  private _bandwidthConstrained$ = this.createBehaviorSubject<boolean>(false);\n  private _mediaParamsUpdated$ = this.createSubject<MediaParamsEvent>();\n\n  // ===========================================================================\n  private _customSubscriptions = new Map<string, Observable<Record<string, unknown>>>();\n  private _pushToTalkEnabled = false;\n  private _remoteAudioMeter: RemoteAudioMeter | null = null;\n  constructor(\n    public clientSession: ClientSession,\n    public options: CallOptions,\n    initialization: CallInitialization,\n    public address?: Address\n  ) {\n    super();\n    this.id = options.callId ?? uuid();\n    this.to = options.to;\n    this._userVariables$.next({\n      ...this._userVariables$.value,\n      ...fromDestinationParams(options.to),\n      ...options.userVariables\n    });\n\n    this.subscribeTo(this.webrtcMessages$, (message) => {\n      const userVars = getValueFrom<Record<string, unknown>>(message, 'params.userVariables');\n      if (userVars) {\n        this._userVariables$.next({\n          ...this._userVariables$.value,\n          ...userVars\n        });\n      }\n    });\n\n    const managers = initialization.initializeManagers(this);\n    this.vertoManager = managers.vertoManager;\n    this.callEventsManager = managers.callEventsManager;\n\n    if (options.initOffer) {\n      this._status$ = this.createBehaviorSubject<CallStatus>('ringing');\n      this._lastMergedStatus = 'ringing';\n    } else {\n      this._status$ = this.createBehaviorSubject<CallStatus>('new');\n    }\n\n    const { deviceController, networkChange$ } = initialization;\n    this._networkChange$ = networkChange$;\n\n    // Create participant factory with bound executeMethod\n    this.participantFactory = new ParticipantFactory(\n      this.executeMethod.bind(this),\n      this.vertoManager,\n      deviceController\n    );\n\n    // Wire resilience lifecycle to call status.\n    // IMPORTANT: Use the raw internal merge (not the public status$ getter)\n    // because publicCachedObservable defers emissions via asapScheduler,\n    // which can cause us to miss the 'connected' transition.\n    this.subscribeTo(\n      merge(this._status$.asObservable(), this.vertoManager.signalingStatus$).pipe(\n        distinctUntilChanged(),\n        takeUntil(this._destroyed$)\n      ),\n      (status) => {\n        // Keep _lastMergedStatus in sync so recovery callbacks (isCallConnected)\n        // work even if nobody subscribes to the public status$ getter.\n        this._lastMergedStatus = status;\n\n        if (status === 'connected' && !this._statsMonitor) {\n          this.initResilienceSubsystems();\n        } else if (status === 'disconnected') {\n          // Stop stats polling (no useful metrics during disconnect) but keep\n          // the recovery manager alive — it's needed to restore the call.\n          this._statsMonitor?.destroy();\n          this._statsMonitor = undefined;\n        } else if (status === 'destroyed' || status === 'failed') {\n          this.stopResilienceSubsystems();\n        }\n      }\n    );\n  }\n\n  /** Observable stream of errors from media, signaling, and peer connection layers. */\n  public get errors$(): Observable<CallError> {\n    return this.deferEmission(this._errors$.asObservable());\n  }\n\n  /**\n   * @internal Push an error to the call's error stream.\n   * Fatal errors automatically transition the call to `'failed'` and destroy it.\n   */\n  public emitError(callError: CallError): void {\n    if (this._status$.value === 'destroyed' || this._status$.value === 'failed') return;\n    this._errors$.next(callError);\n    // A fatal error arriving while hangup() is already tearing the call down must not\n    // start a SECOND teardown: hangup() has already sent its own bye and will destroy\n    // in its `finally`. Report the error on errors$, then let the graceful path finish —\n    // otherwise a normal hangup whose bye races a remote hangup emits a duplicate bye and\n    // reports 'failed' for what the app asked to be a clean teardown.\n    if (callError.fatal && this._status$.value !== 'disconnecting') {\n      this._status$.next('failed');\n      // Best-effort: send verto.bye so the server releases our leg instead of\n      // leaving it to linger as a stale member. The bye frame is flushed\n      // synchronously here; signaling may already be dead, so ignore failures\n      // and destroy immediately rather than awaiting a response that may never\n      // come. (hangup() is the graceful path; this covers fatal teardown.)\n      void this.vertoManager.bye().catch((error: unknown) => {\n        logger.debug('[Call] fatal-teardown bye failed (signaling likely already dead):', error);\n      });\n      this.destroy();\n    }\n  }\n\n  /** Notify the recovery manager that a verto.modify signaling exchange failed. */\n  public notifyModifyFailed(): void {\n    this._recoveryManager?.notifyModifyFailed();\n  }\n\n  /** Whether this call is `'inbound'` or `'outbound'`. */\n  public get direction(): CallDirection {\n    return this.options.initOffer ? 'inbound' : 'outbound';\n  }\n\n  /** Observable of the address associated with this call. */\n  public get address$(): Observable<Address | undefined> {\n    return this.deferEmission(from([this.address])).pipe(takeUntil(this._destroyed$));\n  }\n\n  /** Display name of the caller. */\n  public get fromName(): string | undefined {\n    return this.options.fromName;\n  }\n\n  /** Address URI of the caller. */\n  public get from(): string | undefined {\n    return this.options.from;\n  }\n\n  /** Display name of the callee. */\n  public get toName(): string | undefined {\n    return this.options.toName;\n  }\n\n  /** Toggles whether incoming video is received. @throws {UnimplementedError} Not yet implemented. */\n  // eslint-disable-next-line @typescript-eslint/require-await\n  public async toggleIncomingVideo(): Promise<void> {\n    throw new UnimplementedError();\n  }\n\n  /** Toggles whether incoming audio is received. @throws {UnimplementedError} Not yet implemented. */\n  // eslint-disable-next-line @typescript-eslint/require-await\n  public async toggleIncomingAudio(): Promise<void> {\n    throw new UnimplementedError();\n  }\n\n  /** @internal Registers an additional call ID for event routing. */\n  public addCallId(callId: string): void {\n    this.callEventsManager.addCallId(callId);\n  }\n\n  /** List of capabilities available in the current call. */\n  public get capabilities(): Capability[] {\n    return this.callEventsManager.capabilities;\n  }\n\n  /** Current snapshot of all participants in the call. */\n  public get participants(): CallParticipant[] {\n    return this.callEventsManager.participants;\n  }\n\n  /** The local participant, or `null` if not yet joined. */\n  public get self(): CallSelfParticipant | null {\n    return this.callEventsManager.self;\n  }\n\n  /** Toggles the call lock state, preventing or allowing new participants from joining. */\n  async toggleLock(): Promise<void> {\n    const method = this.locked ? 'call.unlock' : 'call.lock';\n    await this.executeMethod(this.callSelf, method, {});\n  }\n\n  /**\n   * Toggles the hold state of the call (pauses/resumes local media transmission).\n   *\n   * Distinct from {@link Participant.toggleMute} which mutes individual tracks.\n   */\n  async toggleHold(): Promise<void> {\n    if (this._holdState) {\n      await this.vertoManager.unhold();\n    } else {\n      await this.vertoManager.hold();\n    }\n    this._holdState = !this._holdState;\n  }\n\n  /** @throws {UnimplementedError} Not yet implemented. Status tracked via {@link recording$}. */\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async startRecording(): Promise<void> {\n    // NEEDS check backend API status\n    throw new UnimplementedError();\n  }\n\n  /** @throws {UnimplementedError} Not yet implemented. Status tracked via {@link streaming$}. */\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async startStreaming(): Promise<void> {\n    // V3 VIDEO\n    // NEEDS check backend API status\n    throw new UnimplementedError();\n  }\n\n  /**\n   * Replaces the call's custom metadata.\n   * @param _meta - Metadata object to set.\n   * @throws {UnimplementedError} Not yet implemented.\n   */\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async setMeta(_meta: Record<string, unknown>): Promise<void> {\n    // NEEDS backend implementation\n    throw new UnimplementedError();\n  }\n  /**\n   * Merges values into the call's custom metadata (unlike {@link setMeta} which replaces).\n   * @param _meta - Metadata to merge.\n   * @throws {UnimplementedError} Not yet implemented.\n   */\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async updateMeta(_meta: Record<string, unknown>): Promise<void> {\n    // NEEDS backend implementation\n    throw new UnimplementedError();\n  }\n\n  /** Observable of layout layer positions for all participants. */\n  public get layoutLayers$(): Observable<LayoutLayer[]> {\n    return this.deferEmission(this.callEventsManager.layoutLayers$).pipe(\n      takeUntil(this._destroyed$)\n    );\n  }\n\n  /** Current snapshot of layout layers. */\n  public get layoutLayers(): LayoutLayer[] {\n    return this.callEventsManager.layoutLayers;\n  }\n\n  /**\n   * Executes a Verto RPC method targeting a specific participant.\n   *\n   * Constructs call context (node_id, call_id, member_id) and sends the RPC request.\n   *\n   * @param target - Target {@link MemberTarget} triple, or the local member's\n   *   ID string for self-operations (any other string is rejected — a bare\n   *   member id cannot carry the remote member's own call context).\n   * @param method - Verto method name (e.g. `'call.mute'`, `'call.member.remove'`).\n   * @param args - Parameters for the RPC method.\n   * @returns The RPC response.\n   * @throws {CallNotReadyError} If the call has no self member context yet.\n   * @throws {InvalidParams} If a string target is not the local member's ID.\n   * @throws {JSONRPCError} If the RPC call returns an error.\n   */\n  public async executeMethod<T extends JSONRPCResponse = JSONRPCResponse>(\n    target: string | MemberTarget,\n    method: string,\n    args: Record<string, unknown>\n  ): Promise<T> {\n    // Readiness + target-validity guards run on BOTH transports (see @throws above):\n    // fail fast before the call has joined, and reject a string target that is not our\n    // own member id. The in-dialog branch used to return before these ran, so both were\n    // silently skipped for in-dialog callers.\n    const self = this.callSelf;\n    if (typeof target === 'string' && target !== self.member_id) {\n      throw new InvalidParams(\n        `Target member ID ${target} does not match call's self member ID ${self.member_id}`\n      );\n    }\n\n    if (this.clientSession.callControl === 'in-dialog') {\n      return this.executeMethodInDialog<T>(target, method, args);\n    }\n\n    const params: JSONRPCParams = {\n      ...args,\n      self,\n      target: typeof target === 'string' ? self : target\n    };\n\n    const request = buildRPCRequest({\n      method,\n      params\n    });\n\n    try {\n      const response: T = await this.clientSession.execute(request);\n      if (isJSONRPCErrorResponse(response)) {\n        throw new JSONRPCError(\n          parseInt(response.result?.code ?? '0'),\n          `Error response from method ${method}: ${response.result?.code} ${response.result?.message}`,\n          undefined,\n          undefined,\n          request.id\n        );\n      }\n      return response;\n    } catch (error) {\n      logger.error(`[Call] Error executing method ${method} with params`, params, error);\n      throw error;\n    }\n  }\n\n  /**\n   * `executeMethod` for a call opened with `callControl: 'in-dialog'`.\n   *\n   * Translates the routed transport's calling convention into the in-dialog one. No\n   * `self` tuple is sent, but a `target` is — the same {call_id, member_id} the routed\n   * transport puts in `target` (minus node_id), for self-ops and cross-member ops alike.\n   *\n   * Target shapes are per-verb and irregular, so they are centralised here rather\n   * than left to callers: most verbs take a singular `target`, `call.member.remove`\n   * takes a plural `targets` array, and `call.member.position.set` takes a flat\n   * `targets` of `{call_id, position}` — the one verb keyed on call_id rather than\n   * member_id, so the member triple `Participant.setPosition` built is unwrapped.\n   */\n  private async executeMethodInDialog<T extends JSONRPCResponse = JSONRPCResponse>(\n    target: string | MemberTarget,\n    method: string,\n    args: Record<string, unknown>\n  ): Promise<T> {\n    const control: Record<string, unknown> = { ...args };\n\n    if (method === 'call.member.position.set') {\n      const entries =\n        (args.targets as\n          | { target?: MemberTarget; call_id?: string; position?: unknown }[]\n          | undefined) ?? [];\n      // Accept both the member-triple shape Participant.setPosition builds and the flat\n      // {call_id, position} shape sendCommand documents — a caller reaching executeMethod\n      // directly may pass either, and reading the wrong one sends call_id: undefined,\n      // which the server takes as a silent no-op.\n      control.targets = entries.map((entry) => ({\n        call_id: entry.target?.call_id ?? entry.call_id,\n        position: entry.position\n      }));\n    } else {\n      // Attach the member target, mirroring the routed transport which always sends\n      // `target` (its `self` triple for a call-scoped op, the named member otherwise);\n      // in-dialog carries the same {call_id, member_id}, minus node_id. An object names a\n      // member (self or another); a bare string is our own selfId (guaranteed by the\n      // InvalidParams guard in executeMethod) and addresses self, so it must carry the\n      // self target too — omitting it gets call-scoped verbs like call.layout.set refused\n      // for lack of permission.\n      const member =\n        typeof target === 'object'\n          ? { call_id: target.call_id, member_id: target.member_id }\n          : { call_id: this.id, member_id: target };\n      if (method === 'call.member.remove') {\n        control.targets = [member];\n      } else {\n        control.target = member;\n      }\n    }\n\n    return this.sendCommand<T>(method, control);\n  }\n\n  /**\n   * Sends a `call.*` control verb **in-dialog** via `verto.info`, as an alternative\n   * to the routed {@link executeMethod} transport.\n   *\n   * Why both exist: `executeMethod` addresses the member with an explicit\n   * `{node_id, call_id, member_id}` tuple, which does not resolve for every conference,\n   * so the op can fail. An in-dialog frame carries the verb on the member's own\n   * signaling channel instead, so control works without the client needing to know how\n   * the conference is hosted.\n   *\n   * The trade-off is reach: the in-dialog transport is only accepted for calls that\n   * join a conference over SWML (e.g. an SWML `join_conference`); use the routed\n   * default otherwise.\n   *\n   * `params` are sent verbatim — nothing is built for you, which includes the target.\n   * **A self-directed op still needs one**, or it is refused; name yourself explicitly:\n   *\n   * ```ts\n   * const { call_id, member_id } = call.self.target;\n   * await call.sendCommand('call.mute', { channels: ['audio'], target: { call_id, member_id } });\n   * ```\n   *\n   * Never include `node_id` — only the two ids. The shapes are per-verb: most take a\n   * singular `target`, `call.member.remove` takes a plural `targets` array, and\n   * `call.member.position.set` takes a flat `targets: [{call_id, position}]` (the one\n   * verb keyed on `call_id` rather than `member_id`). Verbs that act on the call as a\n   * whole, or that the SDK does not wrap at all, take no target.\n   *\n   * For the typed alternative that handles all of this, create the client with\n   * `callControl: 'in-dialog'` and use the ordinary `Call`/`Participant` methods.\n   *\n   * @internal Not part of the supported surface while the in-dialog transport is still\n   * rolling out. `WebRTCCall` is exported from the package entry, so without this tag\n   * TypeDoc publishes the method — and the example above — as public API.\n   *\n   * @param method - A `call.*` method name (e.g. `'call.mute'`).\n   * @param params - Method parameters, sent verbatim.\n   * @returns The method's own reply, unwrapped from the `verto.info` envelope.\n   * @throws {JSONRPCError} If the control op fails.\n   */\n  public async sendCommand<T extends JSONRPCResponse = JSONRPCResponse>(\n    method: string,\n    params: Record<string, unknown> = {}\n  ): Promise<T> {\n    const response = await this.vertoManager.sendCallControl(method, params);\n    return unwrapVertoReply<T>(response);\n  }\n\n  /**\n   * The local leg's member triple — sent as `self` in every member RPC\n   * envelope, and as the `target` of call-scoped self-operations (e.g. lock,\n   * layout).\n   *\n   * @throws {CallNotReadyError} Before `call.joined` delivers the self member\n   * context (`selfId`/`nodeId`) — an RPC without it cannot be routed, so fail\n   * fast instead of sending a doomed request.\n   */\n  private get callSelf(): MemberTarget {\n    const node_id = this.nodeId;\n    const member_id = this.vertoManager.selfId;\n    if (!node_id || !member_id) {\n      throw new CallNotReadyError(this.id);\n    }\n    return { node_id, call_id: this.id, member_id };\n  }\n\n  /** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */\n  public get status$(): Observable<CallStatus> {\n    return this.publicCachedObservable('status$', () =>\n      merge(this._status$.asObservable(), this.vertoManager.signalingStatus$).pipe(\n        distinctUntilChanged(),\n        tap((status) => {\n          this._lastMergedStatus = status;\n        })\n      )\n    );\n  }\n  /** Observable of the participants list, emits on join/leave/update. */\n  public get participants$(): Observable<CallParticipant[]> {\n    return this.deferEmission(this.callEventsManager.participants$).pipe(\n      takeUntil(this._destroyed$)\n    );\n  }\n  /** Observable of the local (self) participant. */\n  public get self$(): Observable<CallSelfParticipant> {\n    return this.deferEmission(this.callEventsManager.self$).pipe(takeUntil(this._destroyed$));\n  }\n  /** Observable indicating whether the call is being recorded. */\n  public get recording$(): Observable<boolean> {\n    return this.deferEmission(this.callEventsManager.recording$).pipe(takeUntil(this._destroyed$));\n  }\n\n  /** Observable indicating whether the call is being streamed. */\n  public get streaming$(): Observable<boolean> {\n    return this.deferEmission(this.callEventsManager.streaming$).pipe(takeUntil(this._destroyed$));\n  }\n\n  /** Observable indicating whether raise-hand priority is active. */\n  public get raiseHandPriority$(): Observable<boolean> {\n    return this.deferEmission(this.callEventsManager.raiseHandPriority$).pipe(\n      takeUntil(this._destroyed$)\n    );\n  }\n\n  /** Observable indicating whether the call room is locked. */\n  public get locked$(): Observable<boolean> {\n    return this.deferEmission(this.callEventsManager.locked$).pipe(takeUntil(this._destroyed$));\n  }\n\n  /** Observable of custom metadata associated with the call. */\n  public get meta$(): Observable<Record<string, unknown>> {\n    return this.deferEmission(this.callEventsManager.meta$).pipe(takeUntil(this._destroyed$));\n  }\n\n  /** Observable of the call's capability flags. */\n  public get capabilities$(): Observable<Capability[]> {\n    return this.deferEmission(this.callEventsManager.capabilities$).pipe(\n      takeUntil(this._destroyed$)\n    );\n  }\n\n  /** Observable of the current layout name. */\n  public get layout$(): Observable<string> {\n    return this.deferEmission(this.callEventsManager.layout$).pipe(takeUntil(this._destroyed$));\n  }\n\n  /** Current call status. */\n  public get status(): CallStatus {\n    return this._lastMergedStatus;\n  }\n\n  /** Whether the call is currently being recorded. */\n  public get recording(): boolean {\n    return this.callEventsManager.recording;\n  }\n\n  /** Whether the call is currently being streamed. */\n  public get streaming(): boolean {\n    return this.callEventsManager.streaming;\n  }\n\n  /** Whether raise-hand priority is active. */\n  public get raiseHandPriority(): boolean {\n    return this.callEventsManager.raiseHandPriority;\n  }\n\n  /** Whether the call room is locked. */\n  public get locked(): boolean {\n    return this.callEventsManager.locked;\n  }\n\n  /** Current custom metadata of the call. */\n  public get meta(): Record<string, unknown> {\n    return this.callEventsManager.meta;\n  }\n\n  /** Current layout name, or `undefined` if not set. */\n  public get layout(): string | undefined {\n    return this.callEventsManager.layout;\n  }\n\n  /** Observable of available layout names. */\n  public get layouts$(): Observable<string[]> {\n    return this.deferEmission(this.callEventsManager.layouts$).pipe(takeUntil(this._destroyed$));\n  }\n\n  /** Current snapshot of available layout names. */\n  public get layouts(): string[] {\n    return this.callEventsManager.layouts;\n  }\n\n  /** Observable of the local media stream (camera/microphone). */\n  public get localStream$(): Observable<MediaStream> {\n    return this.deferEmission(this.vertoManager.localStream$).pipe(takeUntil(this._destroyed$));\n  }\n  /** Current local media stream, or `null` if not available. */\n  public get localStream(): MediaStream | null {\n    return this.vertoManager.localStream;\n  }\n  /** Observable of the remote media stream from the far end. */\n  public get remoteStream$(): Observable<MediaStream> {\n    return this.deferEmission(this.vertoManager.remoteStream$).pipe(takeUntil(this._destroyed$));\n  }\n  /** Current remote media stream, or `null` if not available. */\n  public get remoteStream(): MediaStream | null {\n    return this.vertoManager.remoteStream;\n  }\n\n  /** Observable of custom user variables associated with the call. */\n  public get userVariables$(): Observable<Record<string, unknown>> {\n    return this.deferEmission(this._userVariables$.asObservable());\n  }\n\n  /** a copy of the current custom user variables of the call. */\n  public get userVariables(): Record<string, unknown> {\n    return { ...this._userVariables$.value };\n  }\n\n  /** Merge current custom user variables of the call. */\n  public set userVariables(variables: Record<string, unknown>) {\n    this._userVariables$.next({ ...this._userVariables$.value, ...variables });\n  }\n\n  // ===========================================================================\n  // Resilience observables & methods\n  //\n  // These use eagerly-created BehaviorSubjects (not publicCachedObservable)\n  // so subscribers can attach before 'connected' and still receive updates\n  // once the stats monitor and recovery manager are initialized.\n  // ===========================================================================\n\n  /** Observable of current network health issues (empty array = healthy). */\n  public get networkIssues$(): Observable<StatsNetworkIssue[]> {\n    return this.deferEmission(this._networkIssues$.asObservable());\n  }\n\n  /** Current snapshot of network issues. */\n  public get networkIssues(): StatsNetworkIssue[] {\n    return this._networkIssues$.value;\n  }\n\n  /** Simple boolean health indicator derived from stats monitor. */\n  public get isNetworkHealthy$(): Observable<boolean> {\n    return this.deferEmission(this._isNetworkHealthy$.asObservable());\n  }\n\n  /** Whether the network is currently healthy. */\n  public get isNetworkHealthy(): boolean {\n    return this._isNetworkHealthy$.value;\n  }\n\n  /** Rolling history of raw network metrics (RTT, jitter, packet loss, bitrate). */\n  public get networkMetrics$(): Observable<StatsNetworkMetrics[]> {\n    return this.deferEmission(this._networkMetrics$.asObservable());\n  }\n\n  /** Current snapshot of the metrics rolling window. */\n  public get networkMetrics(): StatsNetworkMetrics[] {\n    return this._networkMetrics$.value;\n  }\n\n  /** Observable of MOS quality score (1-5) computed from stats metrics. */\n  public get qualityScore$(): Observable<number> {\n    return this.deferEmission(this._qualityScore$.asObservable());\n  }\n\n  /** Observable of simplified quality level (excellent/good/fair/poor/critical). */\n  public get qualityLevel$(): Observable<QualityLevel> {\n    return this.deferEmission(this._qualityLevel$.asObservable());\n  }\n\n  /** Observable of the recovery pipeline state machine. */\n  public get recoveryState$(): Observable<RecoveryState> {\n    return this.deferEmission(this._recoveryState$.asObservable());\n  }\n\n  /** Observable of recovery events (keyframe requested, ICE restart, etc.). */\n  public get recoveryEvent$(): Observable<RecoveryEvent> {\n    return this.deferEmission(this._recoveryEvent$.asObservable());\n  }\n\n  /** Observable indicating whether the call is bandwidth-constrained. */\n  public get bandwidthConstrained$(): Observable<boolean> {\n    return this.deferEmission(this._bandwidthConstrained$.asObservable());\n  }\n\n  /** Observable that emits when the server pushes media params. */\n  public get mediaParamsUpdated$(): Observable<MediaParamsEvent> {\n    return this.deferEmission(this._mediaParamsUpdated$.asObservable());\n  }\n\n  /**\n   * @internal Emit a media params update event.\n   * Called by the VertoManager when the server pushes media params.\n   */\n  public emitMediaParamsUpdated(event: MediaParamsEvent): void {\n    this._mediaParamsUpdated$.next(event);\n  }\n\n  /** Request a video keyframe via RTCP PLI/FIR. */\n  public requestKeyframe(): void {\n    this.vertoManager.requestKeyframe?.();\n  }\n\n  /** Force an ICE restart / re-INVITE. */\n  public async requestIceRestart(): Promise<void> {\n    await this.vertoManager.requestIceRestart?.();\n  }\n\n  /**\n   * @internal Initialize resilience subsystems when the call reaches 'connected'.\n   * Called from within the status subscription to wire stats and recovery.\n   */\n  private initResilienceSubsystems(): void {\n    const pc = this.rtcPeerConnection;\n    logger.debug(\n      `[Call] initResilienceSubsystems: pc=${pc ? 'exists' : 'undefined'}, connectionState=${pc?.connectionState}`\n    );\n    if (!pc) {\n      logger.warn('[Call] No peer connection available, skipping resilience init');\n      return;\n    }\n\n    try {\n      const prefs = PreferencesContainer.instance;\n\n      // Create stats monitor\n      this._statsMonitor = new RTCStatsMonitor(pc, {\n        pollingIntervalMs: prefs.statsPollingInterval,\n        baselineSamples: prefs.statsBaselineSamples,\n        noAudioPacketThresholdMs: prefs.statsNoPacketThreshold,\n        rttSpikeWarningMultiplier: prefs.statsRttSpikeMultiplier,\n        rttSpikeCriticalMultiplier: prefs.statsRttSpikeMultiplier * RTT_CRITICAL_TO_WARNING_RATIO,\n        packetLossWarningPercent: prefs.statsPacketLossThreshold * 100,\n        packetLossCriticalPercent: prefs.statsPacketLossThreshold * 200,\n        jitterSpikeMultiplier: prefs.statsJitterSpikeMultiplier,\n        historyWindowSeconds: prefs.statsHistorySize\n      });\n\n      // Create recovery manager with callbacks\n      const callbacks: RecoveryCallbacks = {\n        requestKeyframe: () => {\n          try {\n            // Use multi-leg keyframe when available (skips send-only screen share legs)\n            if (this.vertoManager.requestKeyframeAll) {\n              this.vertoManager.requestKeyframeAll();\n            } else {\n              this.vertoManager.requestKeyframe?.();\n            }\n          } catch {\n            // Non-fatal: keyframe request is best-effort\n          }\n        },\n        requestIceRestart: async (relayOnly?: boolean) => {\n          try {\n            if (this.vertoManager.requestIceRestartAll) {\n              await this.vertoManager.requestIceRestartAll(relayOnly);\n            } else {\n              await this.vertoManager.requestIceRestart?.(relayOnly);\n            }\n          } catch {\n            return false;\n          }\n          // The verto.modify came back without throwing — but that only confirms\n          // the offer was sent, not that connectivity was actually restored.\n          // Wait for the peer connection to reach 'connected' before reporting\n          // success; otherwise the recovery manager would reset its attempt\n          // count on a partial recovery and skip escalating to the next tier.\n          return this.waitForPeerConnectionConnected();\n        },\n        disableVideo: () => {\n          try {\n            this.vertoManager.muteMainVideoInputDevice();\n            logger.debug('[Call] Recovery manager disabled video');\n          } catch {\n            logger.debug('[Call] Recovery manager failed to disable video');\n          }\n        },\n        enableVideo: () => {\n          this.vertoManager.unmuteMainVideoInputDevice().catch(() => {\n            logger.debug('[Call] Recovery manager failed to enable video');\n          });\n        },\n        isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,\n        isCallConnected: () => this._lastMergedStatus === 'connected',\n        getPeerConnectionState: () => pc.connectionState\n      };\n\n      const inputs = {\n        signalingReady$: this.clientSession.authenticated$\n      };\n\n      this._recoveryManager = new CallRecoveryManager(callbacks, inputs, {\n        debounceTimeMs: prefs.recoveryDebounceTime,\n        cooldownMs: prefs.recoveryCooldown,\n        iceGracePeriodMs: prefs.iceDisconnectedGracePeriod,\n        iceRestartTimeoutMs: prefs.iceRestartTimeout,\n        maxAttempts: prefs.maxRecoveryAttempts,\n        enableRelayFallback: prefs.enableRelayFallback,\n        keyframeMaxBurst: prefs.keyframeMaxBurst,\n        keyframeBurstWindowMs: prefs.keyframeBurstWindow,\n        keyframeCooldownMs: prefs.keyframeCooldown\n      });\n\n      // Wire stats monitor → internal subjects so subscribers get live data\n      this.subscribeTo(this._statsMonitor.networkIssues$, (issues) => {\n        this._networkIssues$.next(issues);\n        this._recoveryManager?.reportNetworkIssues(issues);\n      });\n      this.subscribeTo(this._statsMonitor.isNetworkHealthy$, (healthy) => {\n        this._isNetworkHealthy$.next(healthy);\n      });\n      this.subscribeTo(this._statsMonitor.networkMetrics$, (metrics) => {\n        this._networkMetrics$.next(metrics);\n\n        // Compute MOS from latest metrics\n        if (metrics.length > 0) {\n          const latest = metrics[metrics.length - 1];\n          const totalRecv = latest.audio.packetsReceived + latest.video.packetsReceived;\n          const totalLost = latest.audio.packetsLost + latest.video.packetsLost;\n          const lossPct =\n            totalRecv + totalLost > 0 ? (totalLost / (totalRecv + totalLost)) * 100 : 0;\n          const mos = computeMOS(latest.roundTripTime, latest.audio.jitter, lossPct);\n          this._qualityScore$.next(mos);\n          this._qualityLevel$.next(mosToQualityLevel(mos));\n\n          // Feed bandwidth to recovery manager\n          if (latest.availableOutgoingBitrate !== undefined) {\n            this._recoveryManager?.reportBandwidth(latest.availableOutgoingBitrate / 1000);\n          }\n        }\n      });\n\n      // Wire stats critical issues into recovery triggers\n      this.subscribeTo(this._statsMonitor.criticalIssue$, (issue) => {\n        this._recoveryManager?.pushTrigger({\n          source: 'stats',\n          detail: `${issue.type}: ${issue.severity}`,\n          issueType: issue.type\n        });\n      });\n\n      // Wire recovery manager → internal subjects\n      this.subscribeTo(this._recoveryManager.recoveryState$, (state) => {\n        this._recoveryState$.next(state);\n      });\n      this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {\n        this._recoveryEvent$.next(event);\n        if (event.action === 'max_attempts_reached') {\n          logger.warn('[Call] All recovery attempts exhausted, terminating call');\n          this.emitError({\n            kind: 'network',\n            fatal: true,\n            error: new Error('Call recovery failed: all attempts exhausted'),\n            callId: this.id\n          });\n        }\n      });\n      this.subscribeTo(this._recoveryManager.bandwidthConstrained$, (constrained) => {\n        this._bandwidthConstrained$.next(constrained);\n      });\n\n      // Feed browser network events (online/offline/connection_change) into recovery\n      if (this._networkChange$) {\n        this.subscribeTo(this._networkChange$, (event) => {\n          if (event.type === 'offline') {\n            this._recoveryManager?.pushTrigger({\n              source: 'network',\n              detail: 'browser went offline'\n            });\n          } else if (event.type === 'online') {\n            this._recoveryManager?.handleWebSocketReconnect();\n          }\n          // connection_change (e.g. wifi→cellular) is informational, logged by NetworkMonitor\n        });\n      }\n\n      // Detect WebSocket reconnections: authenticated$ emits true again after\n      // the initial connect. Signal the recovery manager so it can distinguish\n      // signaling loss (reset counters) from peer connection loss (ICE restart).\n      this.subscribeTo(this.clientSession.authenticated$.pipe(skip(1), filter(Boolean)), () => {\n        logger.debug('[Call] WebSocket reconnected — notifying recovery manager');\n        this._recoveryManager?.handleWebSocketReconnect();\n      });\n\n      // Start monitoring\n      this._statsMonitor.start();\n      logger.debug('[Call] Resilience subsystems initialized for call', this.id);\n    } catch (error) {\n      // Non-fatal: call should still work without resilience features\n      logger.warn('[Call] Failed to initialize resilience subsystems:', error);\n    }\n  }\n\n  /**\n   * Wait for the underlying RTCPeerConnection to reach 'connected' after\n   * triggering an ICE restart. Resolves true on success, false on failure\n   * or if the state doesn't transition within the configured timeout.\n   *\n   * Polls connectionState directly because the recovery manager already\n   * wraps this call in its own withTimeout(); a separate listener-based\n   * implementation would race the outer timeout in subtle ways.\n   */\n  private async waitForPeerConnectionConnected(): Promise<boolean> {\n    const pc = this.rtcPeerConnection;\n    if (!pc) return false;\n\n    const deadline = Date.now() + PEER_CONNECTION_RECOVERY_WAIT_MS;\n    for (;;) {\n      const state: RTCPeerConnectionState = pc.connectionState;\n      if (state === 'connected') return true;\n      if (state === 'failed' || state === 'closed') return false;\n      if (Date.now() >= deadline) return false;\n      await new Promise((resolve) => setTimeout(resolve, PEER_CONNECTION_RECOVERY_POLL_MS));\n    }\n  }\n\n  /**\n   * @internal Stop and destroy resilience subsystems (on disconnect/destroy).\n   * Clears references so they can be re-created on reconnect.\n   */\n  private stopResilienceSubsystems(): void {\n    try {\n      this._statsMonitor?.destroy();\n      this._recoveryManager?.destroy();\n    } catch {\n      // Non-fatal\n    }\n    this._statsMonitor = undefined;\n    this._recoveryManager = undefined;\n  }\n\n  /** @internal */\n  public createParticipant(\n    memberId: string,\n    selfId?: string | null\n  ): Participant | SelfParticipant {\n    // Use provided selfId (from call.joined event) or fall back to vertoManager.selfId\n    const effectiveSelfId = selfId ?? this.vertoManager.selfId;\n    if (memberId === effectiveSelfId) {\n      return this.participantFactory.createSelfParticipant(memberId);\n    }\n    return this.participantFactory.createParticipant(memberId);\n  }\n\n  /** Observable of the current audio/video send/receive directions. */\n  public get mediaDirections$(): Observable<MediaDirections> {\n    return this.deferEmission(this.vertoManager.mediaDirections$).pipe(takeUntil(this._destroyed$));\n  }\n\n  /** Current audio/video send/receive directions. */\n  public get mediaDirections(): MediaDirections {\n    return this.vertoManager.mediaDirections;\n  }\n\n  protected get participantsId$(): Observable<string[]> {\n    return this.cachedObservable('participantsId$', () =>\n      this.participants$.pipe(\n        map((participants) => participants.map((participant) => participant.id))\n      )\n    );\n  }\n\n  /**\n   * Executes a raw JSON-RPC request on the client session.\n   *\n   * Lower-level than {@link executeMethod} — allows full control over the RPC request structure.\n   *\n   * @param request - Complete JSON-RPC request object.\n   * @param options - Optional RPC execution options (timeout, etc.).\n   * @returns The RPC response.\n   * @throws {JSONRPCError} If the RPC call returns an error response.\n   */\n  public async execute<T extends JSONRPCResponse = JSONRPCResponse>(\n    request: JSONRPCRequest,\n    options?: PendingRPCOptions\n  ): Promise<T> {\n    return this.clientSession.execute(request, options);\n  }\n\n  /** Observable of the local participant's member ID. */\n  public get selfId$(): Observable<string | null> {\n    return this.vertoManager.selfId$;\n  }\n\n  /** @internal Lets call creation bound the media and signalling phases apart. */\n  public get localMediaSettled$(): Observable<void> {\n    return this.vertoManager.localMediaSettled$;\n  }\n\n  /** Local participant's member ID, or `null` if not joined. */\n  public get selfId(): string | null {\n    return this.vertoManager.selfId;\n  }\n\n  /** Observable of the server node ID handling this call. */\n  public get nodeId$(): Observable<string | null> {\n    return this.vertoManager.nodeId$;\n  }\n\n  /** Server node ID handling this call, or `null`. */\n  public get nodeId(): string | null {\n    return this.vertoManager.nodeId;\n  }\n\n  private isCallSessionEvent(event: unknown): event is Event {\n    try {\n      logger.debug('[Call] Checking if event is for this call session:', event);\n      const callId =\n        getValueFrom<string>(event, 'params.params.callID') ??\n        getValueFrom<string>(event, 'params.call_id');\n      const roomSessionId = getValueFrom<string>(event, 'params.room_session_id');\n      logger.debug(\n        `[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`\n      );\n      return (\n        callId === this.id ||\n        (!!callId && this.callEventsManager.isCallIdValid(callId)) ||\n        (!!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId))\n      );\n    } catch (error) {\n      logger.error('[Call] Error checking if event is for this call session:', error);\n      return false;\n    }\n  }\n\n  private get callSessionEvents$() {\n    return this.cachedObservable('callSessionEvents$', () =>\n      this.clientSession.signalingEvent$.pipe(\n        filter((event) => this.isCallSessionEvent(event)),\n        tap((event) => {\n          logger.debug('[Call] Received call session event:', event);\n        }),\n        takeUntil(this.destroyed$),\n        share()\n      )\n    );\n  }\n\n  /** Observable of call-updated events. */\n  public get callUpdated$(): Observable<CallUpdatedPayload> {\n    return this.publicCachedObservable('callUpdated$', () =>\n      this.callSessionEvents$.pipe(\n        filterAs(isCallUpdatedMetadata, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Observable of member-joined events, emitted when a remote participant joins the call. */\n  public get memberJoined$(): Observable<MemberJoinedPayload> {\n    return this.publicCachedObservable('memberJoined$', () =>\n      this.callSessionEvents$.pipe(\n        filterAs(isMemberJoinedMetadata, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Observable of member-left events, emitted when a participant leaves the call. */\n  public get memberLeft$(): Observable<MemberLeftPayload> {\n    return this.publicCachedObservable('memberLeft$', () =>\n      this.callSessionEvents$.pipe(\n        filterAs(isMemberLeftMetadata, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n  /** Observable of member-updated events (mute, volume, etc.). */\n  public get memberUpdated$(): Observable<MemberUpdatedPayload> {\n    return this.publicCachedObservable('memberUpdated$', () =>\n      this.callSessionEvents$.pipe(\n        filterAs(isMemberUpdatedMetadata, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Observable of member-talking events (speech start/stop). */\n  public get memberTalking$(): Observable<MemberTalkingPayload> {\n    return this.publicCachedObservable('memberTalking$', () =>\n      this.callSessionEvents$.pipe(\n        filterAs(isMemberTalkingMetadata, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Observable of call state-change events. */\n  public get callStates$(): Observable<CallStatePayload> {\n    return this.publicCachedObservable('callStates$', () =>\n      this.callSessionEvents$.pipe(\n        filterAs(isCallStateMetadata, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Observable of layout-changed events. */\n  public get layoutUpdates$(): Observable<LayoutChangedPayload> {\n    return this.publicCachedObservable('layoutUpdates$', () =>\n      this.callSessionEvents$.pipe(\n        filterAs(isLayoutChangedMetadata, 'params'),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Underlying `RTCPeerConnection`, for advanced use cases. */\n  public get rtcPeerConnection(): RTCPeerConnection | undefined {\n    return this.vertoManager.mainPeerConnection.peerConnection;\n  }\n  /** Observable of raw signaling events as plain objects. */\n  public get signalingEvent$(): Observable<Record<string, unknown>> {\n    return this.publicCachedObservable('signalingEvent$', () =>\n      this.callEvent$.pipe(\n        map((event) => JSON.parse(JSON.stringify(event)) as Record<string, unknown>)\n      )\n    );\n  }\n\n  // ===========================================================================\n  // Custom event subscriptions (Section 18)\n\n  /**\n   * Subscribe to a custom signaling event type on this call.\n   *\n   * Returns a cached observable that filters `callSessionEvents$` for events\n   * whose `event_type` matches the given string. The observable completes\n   * when the call is destroyed.\n   *\n   * Unlike `signalingEvent$` (which only emits known call-level event types),\n   * this method also matches custom/user-defined event types.\n   *\n   * The SDK does not validate event type strings --- the server decides\n   * whether a given type is valid.\n   *\n   * @param eventType - The event type to subscribe to (e.g. `'my.custom.event'`).\n   * @returns An observable that emits matching signaling events.\n   *\n   * @example\n   * ```ts\n   * call.subscribe('my.custom.event').subscribe(event => {\n   *   console.log('Custom event:', event);\n   * });\n   * ```\n   */\n  public subscribe(eventType: string): Observable<Record<string, unknown>> {\n    const cached = this._customSubscriptions.get(eventType);\n    if (cached) {\n      return cached;\n    }\n\n    const filtered$ = this.callSessionEvents$.pipe(\n      filter((event) => (event as { event_type?: string }).event_type === eventType),\n      map((event) => JSON.parse(JSON.stringify(event)) as Record<string, unknown>),\n      takeUntil(this._destroyed$)\n    );\n\n    // Send verto.subscribe to the server to add this event type mid-call.\n    // The server decides whether the event type is valid — we don't validate.\n    // Only cache on success: if the RPC fails we want the next call to retry\n    // rather than returning a dead observable that will never emit.\n    this._sendVertoSubscribe(eventType).then(\n      () => {\n        this._customSubscriptions.set(eventType, filtered$);\n      },\n      (error: unknown) => {\n        this._customSubscriptions.delete(eventType);\n        logger.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);\n      }\n    );\n\n    // Temporarily cache so concurrent subscribe(eventType) calls share the\n    // same in-flight observable. On RPC failure the entry is replaced above.\n    this._customSubscriptions.set(eventType, filtered$);\n\n    return filtered$;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n  public get webrtcMessages$() {\n    return this.cachedObservable('webrtcMessages$', () =>\n      this.callSessionEvents$.pipe(\n        filterAs(isWebrtcMessageMetadata, 'params'),\n        tap((event) => logger.debug('[Call] Event is a WebRTC message event:', event)),\n        takeUntil(this.destroyed$),\n        share()\n      )\n    );\n  }\n  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n  public get callEvent$() {\n    return this.cachedObservable('callEvent$', () =>\n      this.callSessionEvents$.pipe(\n        filterAs(isSignalwireCallMetadata, 'params'),\n        tap((event) => logger.debug('[Call] Event is a call event:', event)),\n        takeUntil(this.destroyed$),\n        share()\n      )\n    );\n  }\n  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n  public get layoutEvent$() {\n    return this.cachedObservable('layoutEvent$', () =>\n      this.callEvent$.pipe(filterAs(isLayoutChangedMetadata, 'params'))\n    );\n  }\n  /**\n   * Hangs up the call and releases all resources.\n   *\n   * Sends a Verto `bye` to the server, transitions status to `'disconnecting'`,\n   * then destroys the call. After this, the call instance is no longer usable.\n   *\n   * @example\n   * ```ts\n   * await call.hangup();\n   * ```\n   */\n  async hangup(): Promise<void> {\n    this._status$.next('disconnecting');\n    try {\n      await this.vertoManager.bye();\n    } finally {\n      this.destroy();\n    }\n  }\n  /**\n   * Sends DTMF digits on the call.\n   *\n   * @param dtmf - The digit string to send (e.g. `'1234#'`).\n   *\n   * @example\n   * ```ts\n   * await call.sendDigits('1234#');\n   * ```\n   */\n  async sendDigits(dtmf: string): Promise<void> {\n    return this.vertoManager.sendDigits(dtmf);\n  }\n\n  /** Observable of WebRTC-specific signaling messages. */\n\n  /** Observable of call-level signaling events. */\n\n  /** Observable of layout-changed signaling events. */\n\n  /**\n   * Accepts an inbound call, optionally overriding media options for the answer.\n   *\n   * @param options - Optional media constraints for the answer (audio/video).\n   *\n   * @example\n   * ```ts\n   * // Accept with defaults\n   * call.answer();\n   *\n   * // Accept audio-only\n   * call.answer({ audio: true, video: false });\n   * ```\n   * @see {@link reject} to decline the call instead.\n   * @see {@link answered$} to observe the acceptance state.\n   */\n  public answer(options?: MediaOptions): void {\n    this._answerMediaOptions = options;\n    this._answered$.next(true);\n  }\n\n  /** Media options provided when answering. Used internally by the VertoManager. */\n  public get answerMediaOptions(): MediaOptions | undefined {\n    return this._answerMediaOptions;\n  }\n\n  /**\n   * Rejects an inbound call, preventing media negotiation.\n   *\n   * @see {@link answer} to accept the call instead.\n   * @see {@link answered$} to observe the rejection state.\n   */\n  public reject(): void {\n    this._answered$.next(false);\n  }\n\n  /** Observable that emits `true` when answered, `false` when rejected. */\n  public get answered$(): Observable<boolean> {\n    return this.deferEmission(this._answered$.asObservable());\n  }\n\n  /**\n   * Sets the call layout and, optionally, individual participant positions.\n   *\n   * The gateway `call.layout.set` DTO has **no** `positions` member, so when\n   * `positions` is provided this method issues a `call.member.position.set`\n   * request per member (via {@link Participant.setPosition}, which keys each\n   * position by that member's own call context) alongside `call.layout.set`\n   * (issue #19400, Flag #6).\n   *\n   * **These operations are NOT atomic.** The layout is applied first, then each\n   * member position sequentially, so members may briefly flash into their\n   * default slots before being moved to the requested positions. Targeted\n   * members are validated upfront, though: when any of them has no\n   * {@link Participant.target | member call context} yet, the whole call\n   * rejects before any request is sent and the layout is left unchanged.\n   *\n   * @param layout - Layout name (must be one of {@link layouts}).\n   * @param positions - Optional map of member IDs to {@link VideoPosition} values.\n   *   When omitted or empty, only the layout is changed.\n   * @throws {InvalidParams} If the layout is not in the available {@link layouts}.\n   * @throws {ParticipantNotReadyError} If a targeted member's call context has\n   *   not been received yet — thrown before any request is sent.\n   *\n   * @example\n   * ```ts\n   * await call.setLayout('grid-responsive', {\n   *   [participantId]: 'reserved-0',\n   * });\n   * ```\n   */\n  async setLayout(layout: string, positions?: Record<string, VideoPosition>): Promise<void> {\n    if (!this.layouts.includes(layout)) {\n      throw new InvalidParams(\n        `Layout ${layout} is not available in the current call layouts: ${this.layouts.join(', ')}`\n      );\n    }\n\n    // Resolve and validate targets BEFORE any RPC: a known-but-not-ready\n    // member fails the whole operation while the layout is still unchanged.\n    // Unknown members keep the lenient warn+skip behavior.\n    const targets: [CallParticipant, VideoPosition][] = [];\n    for (const [memberId, position] of Object.entries(positions ?? {})) {\n      const participant = this.participants.find((p) => p.id === memberId);\n      if (!participant) {\n        logger.warn(\n          `[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`\n        );\n        continue;\n      }\n      // Reading target validates the member's call context — it throws\n      // ParticipantNotReadyError when the member state has not arrived yet.\n      void participant.target;\n      targets.push([participant, position]);\n    }\n\n    const selfId = await firstValueFrom(\n      this.selfId$.pipe(filter((id): id is string => id !== null))\n    );\n\n    await this.executeMethod(selfId, 'call.layout.set', { layout });\n\n    for (const [participant, position] of targets) {\n      await participant.setPosition(position);\n    }\n  }\n\n  /**\n   * Transfers the call to another destination.\n   *\n   * @param options - Transfer configuration including the target destination.\n   * @see {@link status$} to observe the transfer progress.\n   */\n  public async transfer(options: TransferOptions): Promise<void> {\n    return this.vertoManager.transfer(options);\n  }\n\n  // =========================================================================\n  // Local audio pipeline — mic gain, level meter, speaking VAD, PTT\n  // =========================================================================\n\n  /**\n   * Set the local microphone gain as a percentage applied before transmission.\n   *\n   *   - `0`   = silent\n   *   - `100` = unity (no change, default)\n   *   - `200` = 2× digital boost (max; expect clipping / noise amplification)\n   *\n   * Values are clamped to [0, 200]. Engages the local audio pipeline on\n   * first use (one-time cost).\n   *\n   * Note: this is a **digital** multiplier applied in a Web Audio GainNode\n   * between your mic track and the RTCRtpSender — it does not change the\n   * physical mic's hardware sensitivity. Browsers' autoGainControl can\n   * fight the setting; call {@link setAutoGainControl}(false) for\n   * predictable behaviour.\n   *\n   * @param value - Gain percentage (0..200; 100 = unity).\n   */\n  public setLocalMicrophoneGain(value: number): void {\n    const pipeline = this.vertoManager.ensureLocalAudioPipeline();\n    if (!pipeline) {\n      logger.warn('[Call] setLocalMicrophoneGain: audio pipeline unavailable');\n      return;\n    }\n    const percent = Math.max(0, Math.min(200, value));\n    pipeline.setGain(percent / 100);\n  }\n\n  /** Observable of the current local microphone gain (0..200, where 100 = unity). */\n  public get localMicrophoneGain$(): Observable<number> {\n    const pipeline = this.vertoManager.ensureLocalAudioPipeline();\n    if (!pipeline) {\n      return of(100).pipe(takeUntil(this._destroyed$));\n    }\n    // No share() here: pipeline.gain$ is a BehaviorSubject, so each subscriber\n    // is a cheap fan-out that receives the current value on subscribe. Adding\n    // share() would route late subscribers through a plain Subject and they'd\n    // miss the replay until the next setGain().\n    return this.publicCachedObservable('localMicrophoneGain$', () =>\n      pipeline.gain$.pipe(\n        map((multiplier) => multiplier * 100),\n        takeUntil(this._destroyed$)\n      )\n    );\n  }\n\n  /**\n   * Observable of the RMS audio level of the local microphone, 0..1.\n   * Emits at ~30fps while a mic track is active. Engages the local audio\n   * pipeline on first subscription.\n   */\n  public get localAudioLevel$(): Observable<number> {\n    const pipeline = this.vertoManager.ensureLocalAudioPipeline();\n    if (!pipeline) {\n      return of(0).pipe(takeUntil(this._destroyed$));\n    }\n    return this.publicCachedObservable('localAudioLevel$', () =>\n      pipeline.level$.pipe(takeUntil(this._destroyed$), share())\n    );\n  }\n\n  /**\n   * Observable that is `true` while the local participant is speaking\n   * (RMS level above the VAD threshold, with hold time to avoid flicker).\n   */\n  public get localSpeaking$(): Observable<boolean> {\n    const pipeline = this.vertoManager.ensureLocalAudioPipeline();\n    if (!pipeline) {\n      return of(false).pipe(takeUntil(this._destroyed$));\n    }\n    return this.publicCachedObservable('localSpeaking$', () =>\n      pipeline.speaking$.pipe(takeUntil(this._destroyed$), share())\n    );\n  }\n\n  /**\n   * Enable push-to-talk: while {@link setPushToTalkActive} has been called\n   * with `false`, the microphone gain is forced to 0; calling\n   * {@link setPushToTalkActive} with `true` restores the configured gain.\n   * Use this instead of mute/unmute for instant talk/silence transitions\n   * because it doesn't rebuild the track.\n   *\n   * This method installs the pipeline but does not attach any keyboard\n   * listener — consumers bind the key themselves and call\n   * {@link setPushToTalkActive} on keydown/keyup.\n   */\n  public enablePushToTalk(): void {\n    const pipeline = this.vertoManager.ensureLocalAudioPipeline();\n    if (!pipeline) {\n      logger.warn('[Call] enablePushToTalk: audio pipeline unavailable');\n      return;\n    }\n    // Start in the \"released\" state — caller must set pushToTalkActive(true)\n    // to transmit. This matches the mental model of a radio mic: released = silent.\n    pipeline.setPTTActive(false);\n    this._pushToTalkEnabled = true;\n  }\n\n  /** Disable push-to-talk; mic gain returns to the configured value. */\n  public disablePushToTalk(): void {\n    // Restore normal transmission — setPTTActive(true) means \"audio passes through\".\n    this.vertoManager.localAudioPipeline?.setPTTActive(true);\n    this._pushToTalkEnabled = false;\n  }\n\n  /**\n   * While push-to-talk is enabled, sets the talk state. `true` = transmitting,\n   * `false` = silent. No-op if push-to-talk has not been enabled.\n   */\n  public setPushToTalkActive(active: boolean): void {\n    if (!this._pushToTalkEnabled) return;\n    this.vertoManager.localAudioPipeline?.setPTTActive(active);\n  }\n\n  /**\n   * Toggle echo cancellation on the local mic at runtime. Applied via\n   * `track.applyConstraints`; browsers that don't honour runtime constraints\n   * (notably iOS Safari) fall back to re-acquiring the track with the new\n   * constraint set and plumbing the replacement through the local audio\n   * pipeline if one is active.\n   *\n   * @returns whether the constraint reached the microphone. `false` is an\n   * outcome rather than an error — a leg sending media the SDK did not capture\n   * is left alone — so a UI that reflects the toggle must read it. Any failure\n   * behind a `false` is also reported on {@link errors$}.\n   */\n  public async setEchoCancellation(enabled: boolean): Promise<boolean> {\n    return this.vertoManager.updateMediaConstraints({ audio: { echoCancellation: enabled } });\n  }\n\n  /**\n   * Toggle browser noise suppression on the local mic at runtime.\n   * @returns whether the constraint reached the microphone.\n   */\n  public async setNoiseSuppression(enabled: boolean): Promise<boolean> {\n    return this.vertoManager.updateMediaConstraints({ audio: { noiseSuppression: enabled } });\n  }\n\n  /**\n   * Toggle browser automatic gain control on the local mic at runtime.\n   * @returns whether the constraint reached the microphone.\n   */\n  public async setAutoGainControl(enabled: boolean): Promise<boolean> {\n    return this.vertoManager.updateMediaConstraints({ audio: { autoGainControl: enabled } });\n  }\n\n  /**\n   * Observable of the aggregate remote audio level, 0..1 RMS. The server\n   * delivers a single mixed audio stream for all remote participants — this\n   * meter reports that mix. Per-participant audio is not available client-side.\n   *\n   * Engages a shared AudioContext on first subscription (cheap — one\n   * AnalyserNode, no GainNode, no destination) so it does not affect the\n   * caller's audio element playback.\n   */\n  public get remoteAudioLevel$(): Observable<number> {\n    return this.publicCachedObservable('remoteAudioLevel$', () => {\n      this._remoteAudioMeter ??= new RemoteAudioMeter();\n      const meter = this._remoteAudioMeter;\n\n      this.subscribeTo(this.vertoManager.remoteStream$, (stream) => {\n        meter.setStream(stream);\n      });\n\n      return meter.level$.pipe(takeUntil(this._destroyed$), share());\n    });\n  }\n\n  /** Destroys the call, releasing all resources and subscriptions. */\n  public destroy(): void {\n    if (this._status$.value === 'destroyed') return;\n    this._status$.next('destroyed');\n\n    // Destroy resilience subsystems\n    this.stopResilienceSubsystems();\n    this._remoteAudioMeter?.destroy();\n    this._remoteAudioMeter = null;\n\n    this.vertoManager.destroy();\n    this.callEventsManager.destroy();\n    super.destroy();\n  }\n\n  /**\n   * @internal Send a verto.subscribe message to add an event type to the\n   * server's subscription list for this call. Returns the underlying RPC\n   * promise so callers can decide whether to cache the observable on success\n   * or retry on failure.\n   */\n  private async _sendVertoSubscribe(eventType: string): Promise<void> {\n    const message = VertoSubscribe({\n      sessid: this.id,\n      eventChannel: [eventType]\n    });\n    const params = {\n      callID: this.id,\n      node_id: this.vertoManager.nodeId ?? '',\n      message\n    };\n    await this.clientSession.execute(WebrtcVerto(params));\n  }\n}\n","import { CallEventsManager } from './CallEventsManager';\nimport { WebRTCVertoManager } from './VertoManager';\nimport {\n  RPC_ERROR_AUTHENTICATION_FAILED,\n  RPC_ERROR_INVALID_PARAMS,\n  RPC_ERROR_REQUESTER_VALIDATION_FAILED\n} from '../core/constants';\nimport { WebRTCCall } from '../core/entities/Call';\nimport {\n  JSONRPCError,\n  MediaAccessError,\n  MediaTrackError,\n  RPCTimeoutError,\n  TransportConnectionError,\n  VertoPongError,\n  WebSocketConnectionError\n} from '../core/errors';\n\nimport type { AttachManager } from './AttachManager';\nimport type { NetworkChangeEvent } from '../controllers/NetworkMonitor';\nimport type { Address } from '../core/entities/Address';\nimport type { CallOptions } from '../core/entities/types/call.types';\nimport type { CallError } from '../core/errors';\nimport type { RTCPeerConnectionPropose } from '../core/types/call.types';\nimport type { WebRTCApiProvider } from '../dependencies/interfaces';\nimport type { ClientSession } from '../interfaces/ClientSession';\nimport type { DeviceController } from '../interfaces/DeviceController';\nimport type { Observable } from 'rxjs';\n\n/**\n * Infers the semantic error category from a raw Error thrown by VertoManager\n * or an RTCPeerConnection layer.\n *\n * Pure function — exported for unit testing.\n * @internal\n */\nexport function inferCallErrorKind(error: Error): CallError['kind'] {\n  if (error instanceof RPCTimeoutError) return 'timeout';\n  if (error instanceof JSONRPCError) return 'signaling';\n  if (error instanceof MediaTrackError) return 'media';\n  if (error instanceof MediaAccessError) return 'media';\n  if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError)\n    return 'network';\n  return 'internal';\n}\n\n/** JSON-RPC error codes that ClientSessionManager treats as recoverable at the\n * session level. Surfacing one of these against an in-flight call should not\n * destroy the call, because the session will reauthenticate and any pending\n * RPC can then be retried. */\nconst RECOVERABLE_RPC_CODES = new Set<number | string>([\n  RPC_ERROR_REQUESTER_VALIDATION_FAILED,\n  RPC_ERROR_AUTHENTICATION_FAILED,\n  RPC_ERROR_INVALID_PARAMS\n]);\n\n/**\n * A *fallback*: callers knowing which leg failed pass an explicit `fatal` and\n * never reach here, so the default-fatal branch only sees call- and main-leg\n * errors. Auxiliary legs go through `WebRTCVertoManager.reportLegError`.\n *\n * Pure function — exported for unit testing.\n * @internal\n */\nexport function isFatalError(error: Error): boolean {\n  // Transient signaling issues — the call may survive\n  if (error instanceof VertoPongError) return false;\n  // Media device errors degrade quality but don't end the call\n  if (error instanceof MediaTrackError) return false;\n  // Local media acquisition failures: the wrapping site knows whether the\n  // call can continue (receive-only fallback, aux connection) and sets fatal.\n  if (error instanceof MediaAccessError) return error.fatal;\n  // RPC timeouts during active calls are transient (network outage) — recovery may retry\n  if (error instanceof RPCTimeoutError) return false;\n  // JSONRPCError covers both hard signaling failures and transient auth-recoverable\n  // errors. Inspect the code so the latter don't destroy the call when the session\n  // is already set up to self-heal them.\n  if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;\n  // All other errors (JSONRPCError on invite with a fatal code, connection failures, etc.)\n  return true;\n}\n\n/**\n * Factory for creating WebRTCCall instances with proper manager wiring.\n * Eliminates circular dependencies by centralizing Call and Manager creation.\n */\nexport class CallFactory {\n  constructor(\n    private sessionManager: ClientSession,\n    private deviceController: DeviceController,\n    private attachManager: AttachManager,\n    private webRTCApiProvider: WebRTCApiProvider,\n    private networkChange$?: Observable<NetworkChangeEvent>\n  ) {}\n\n  /**\n   * Create a new WebRTCCall with properly initialized managers\n   */\n  createCall(address: Address | undefined, options: CallOptions): WebRTCCall {\n    const call = new WebRTCCall(\n      this.sessionManager,\n      options,\n      {\n        initializeManagers: (callInstance: WebRTCCall) => {\n          const vertoManager = new WebRTCVertoManager(\n            callInstance,\n            this.attachManager,\n            this.deviceController,\n            this.webRTCApiProvider,\n            {\n              nodeId: options.nodeId,\n              onError: (\n                error: Error,\n                options?: { fatal?: boolean; leg?: RTCPeerConnectionPropose; legId?: string }\n              ) => {\n                const callError: CallError = {\n                  kind: inferCallErrorKind(error),\n                  fatal: options?.fatal ?? isFatalError(error),\n                  error,\n                  callId: callInstance.id,\n                  ...(options?.leg ? { leg: options.leg } : {}),\n                  ...(options?.legId ? { legId: options.legId } : {})\n                };\n                callInstance.emitError(callError);\n              },\n              onModifyFailed: () => {\n                callInstance.notifyModifyFailed();\n              }\n            }\n          );\n\n          const callEventsManager = new CallEventsManager(callInstance);\n\n          return {\n            vertoManager,\n            callEventsManager\n          };\n        },\n        deviceController: this.deviceController,\n        networkChange$: this.networkChange$\n      },\n      address\n    );\n\n    return call;\n  }\n}\n","import {\n  defer,\n  distinctUntilChanged,\n  filter,\n  from,\n  map,\n  ReplaySubject,\n  shareReplay,\n  switchMap,\n  takeUntil,\n  skip,\n  pipe\n} from 'rxjs';\n\nimport { Destroyable } from './Destroyable';\nimport { GET_PARAMS } from '../controllers/HTTPRequestController';\nimport { CollectionFetchError } from '../core/errors';\nimport { getLogger } from '../utils/logger';\n\nimport type {\n  PaginatedResponse,\n  Entity,\n  FetchController,\n  Collection\n} from './types/collection.types';\nimport type { HTTPRequestController } from '../controllers/HTTPRequestController';\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\nexport class Fetcher<T extends Entity = Entity> implements FetchController<Entity> {\n  private nextUrl?: string;\n  public hasMore: boolean | undefined;\n  public filter = (_item: Entity): _item is T => true;\n  public mapper = (item: unknown): T => item as T;\n\n  constructor(\n    protected endpoint: string,\n    params: string,\n    protected http: HTTPRequestController\n  ) {\n    this.nextUrl = `${this.endpoint}?${params}`;\n  }\n\n  public async next(): Promise<T[]> {\n    if (!this.nextUrl) {\n      this.hasMore = false;\n      return [];\n    }\n\n    const response = await this.http.request({\n      ...GET_PARAMS,\n      url: this.nextUrl\n    });\n    if (response.ok && !!response.body) {\n      const result = JSON.parse(response.body) as PaginatedResponse<T>;\n      this.nextUrl = result.links.next;\n      this.hasMore = !!this.nextUrl;\n      const filtered = result.data.filter(this.filter);\n      return filtered.map(this.mapper);\n    }\n    logger.error('Failed to fetch entity');\n    return [];\n  }\n\n  public async id(v: unknown): Promise<T | undefined> {\n    const response = await this.http.request({\n      ...GET_PARAMS,\n      url: `${this.endpoint}/${String(v)}`\n    });\n    if (response.ok && !!response.body) {\n      return JSON.parse(response.body) as T;\n    }\n  }\n}\n\nexport class EntityCollection<T extends Entity = Entity>\n  extends Destroyable\n  implements Collection<T>\n{\n  public hasMore$: Observable<boolean>;\n  private collectionData = new Map<string, T>();\n  private observablesRegistry = new Map<string, ReplaySubject<T>>();\n  private upsertData = (data: Partial<T>) => {\n    if (!data.id) return;\n    const existing = this.collectionData.get(data.id) ?? {};\n    const updated = {} as Record<string, unknown>;\n    const allKeys = new Set([...Object.keys(existing), ...Object.keys(data)]);\n    for (const key of allKeys) {\n      const existingVal = (existing as Record<string, unknown>)[key];\n      const newVal = (data as Record<string, unknown>)[key];\n      if (\n        newVal !== undefined &&\n        existingVal !== undefined &&\n        typeof existingVal === 'object' &&\n        existingVal !== null &&\n        !Array.isArray(existingVal) &&\n        typeof newVal === 'object' &&\n        newVal !== null &&\n        !Array.isArray(newVal)\n      ) {\n        updated[key] = { ...existingVal, ...newVal };\n      } else if (newVal !== undefined) {\n        updated[key] = newVal;\n      } else {\n        updated[key] = existingVal;\n      }\n    }\n    this.collectionData.set(data.id, updated as T);\n    this.observablesRegistry.get(data.id)?.next(updated as T);\n    this._values$.next(Array.from(this.collectionData.values()));\n  };\n  private _loading$ = this.createBehaviorSubject<boolean>(false);\n  private _values$ = this.createReplaySubject<T[]>(1);\n  private _hasMore$ = this.createBehaviorSubject<boolean>(true);\n\n  constructor(\n    private fetchController: FetchController<T>,\n    private update$: Observable<Partial<T>>,\n    private readonly onError?: (error: Error) => void\n  ) {\n    super();\n    this.subscribeTo(this.update$, this.upsertData);\n    this.hasMore$ = defer(() => from(this.init())).pipe(\n      switchMap(() => this._hasMore$),\n      distinctUntilChanged(),\n      shareReplay(1),\n      takeUntil(this.destroyed$)\n    );\n  }\n\n  public get loading$(): Observable<boolean> {\n    return this._loading$.asObservable();\n  }\n\n  public get loading(): boolean {\n    return this._loading$.value;\n  }\n\n  public get values$(): Observable<T[]> {\n    return this._values$.asObservable();\n  }\n\n  public get hasMore(): boolean {\n    return this.fetchController.hasMore ?? true;\n  }\n\n  public get updated$(): Observable<void> {\n    return this.cachedObservable('updated$', () =>\n      this._loading$.pipe(\n        distinctUntilChanged(),\n        skip(1), // skipping the loading === true event\n        filter((loading) => !loading),\n        map(() => void 0),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  public get values(): T[] {\n    return Array.from(this.collectionData.values());\n  }\n\n  private async init(): Promise<void> {\n    if (this.fetchController.hasMore === false) {\n      this._hasMore$.next(false);\n      return;\n    }\n    await this.fetchMore();\n  }\n\n  private async fetchMore(): Promise<void> {\n    try {\n      this._loading$.next(true);\n      const datas = await this.fetchController.next();\n      datas.forEach(this.upsertData);\n      this._hasMore$.next(this.fetchController.hasMore ?? false);\n      this._loading$.next(false);\n    } catch (error) {\n      logger.error(`Failed to fetch initial collection data`, error);\n      this._hasMore$.next(this.fetchController.hasMore ?? false);\n      this._loading$.next(false);\n      this.onError?.(new CollectionFetchError('fetchMore', error));\n    }\n  }\n\n  private async tryFetch(key: keyof FetchController<T>, value: unknown): Promise<T | undefined> {\n    try {\n      this._loading$.next(true);\n      const data = await this.fetchController[key]?.(value);\n      this._loading$.next(false);\n      if (data) {\n        this.upsertData(data);\n      }\n      return data;\n    } catch (error) {\n      logger.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);\n      this._loading$.next(false);\n      this.onError?.(new CollectionFetchError(`tryFetch(${String(key)})`, error));\n    }\n  }\n\n  public get$(id: string): Observable<T> | undefined {\n    if (!this.observablesRegistry.has(id)) {\n      this.observablesRegistry.set(id, new ReplaySubject<T>(1));\n      const data = this.collectionData.get(id);\n      if (data) {\n        this.observablesRegistry.get(id)?.next(data);\n      } else {\n        void this.tryFetch('id', id);\n      }\n    }\n    return this.observablesRegistry.get(id)?.asObservable();\n  }\n\n  public async find$(key: keyof T, value: unknown): Promise<Observable<T> | undefined> {\n    const data =\n      Array.from(this.collectionData.values()).find((item) => item[key] === value) ??\n      (await this.tryFetch(key, value));\n\n    return data ? this.get$(data.id) : undefined;\n  }\n\n  public loadMore(): void {\n    if (this.fetchController.hasMore !== false) {\n      void this.fetchMore();\n    }\n  }\n\n  public override destroy(): void {\n    this.observablesRegistry.forEach((subject) => subject.complete());\n    this.observablesRegistry.clear();\n    super.destroy();\n  }\n}\n\nexport class EntityCollectionTransformed<\n  O extends Entity = Entity,\n  T extends Entity = Entity\n> implements Collection<T> {\n  private _values$?: Observable<T[]>;\n\n  constructor(\n    private originalCollection: EntityCollection<O>,\n    private filter: (i: unknown) => i is O = (i): i is O => !!i,\n    private mapper: (item: O) => T = (item) => item as unknown as T\n  ) {}\n\n  public get loading$(): Observable<boolean> {\n    return this.originalCollection.loading$;\n  }\n\n  public get loading(): boolean {\n    return this.originalCollection.loading;\n  }\n  public get hasMore$(): Observable<boolean> {\n    return this.originalCollection.hasMore$;\n  }\n  public get hasMore(): boolean {\n    return this.originalCollection.hasMore;\n  }\n  public get values(): T[] {\n    return this.originalCollection.values.filter(this.filter).map(this.mapper);\n  }\n  public get values$(): Observable<T[]> {\n    return (this._values$ ??= this.originalCollection.values$.pipe(\n      map((values) => values.filter(this.filter).map(this.mapper))\n    ));\n  }\n\n  public get$(id: string): Observable<T> | undefined {\n    const original$ = this.originalCollection.get$(id);\n    return !original$ ? original$ : original$.pipe(pipe(filter(this.filter), map(this.mapper)));\n  }\n\n  public async find$(key: keyof T, value: unknown): Promise<Observable<T> | undefined> {\n    const original$ = await this.originalCollection.find$(key as keyof O, value);\n    return !original$ ? original$ : original$.pipe(pipe(filter(this.filter), map(this.mapper)));\n  }\n  public loadMore(): void {\n    this.originalCollection.loadMore();\n  }\n  public destroy(): void {\n    this.originalCollection.destroy();\n  }\n}\n","import { defer, map, shareReplay, takeUntil, type Observable } from 'rxjs';\n\nimport { EntityCollectionTransformed } from '../../behaviors/Collection';\nimport { Destroyable } from '../../behaviors/Destroyable';\nimport { filterNull } from '../../operators';\nimport { DependencyError, UnimplementedError } from '../errors';\n\nimport type { CallState } from './types/call.types';\nimport type { AddressProvider } from '../../interfaces/AddressProvider';\nimport type {\n  ConversationMessageCollection,\n  ConversationsProvider\n} from '../../interfaces/Conversations';\nimport type { GetAddressResponse } from '../types/address.types';\nimport type { ResourceType } from '../types/common.types';\nimport type {\n  AddressHistory,\n  AddressHistoryCollection,\n  GetConversationMessageResponse,\n  TextMessage,\n  TextMessageCollection\n} from '../types/conversation.types';\n\ntype AddressState = GetAddressResponse;\n/**\n * Represents a contact or room in the directory.\n *\n * Provides identity metadata, conversation history, text messaging,\n * and activity state for an address entry.\n */\nexport class Address extends Destroyable {\n  private initConversationMessages = async (): Promise<ConversationMessageCollection> => {\n    this._conversationMessages =\n      this._conversationMessages ??\n      (await this.conversationManager.getConversationMessageCollection(this.id));\n    if (this._conversationMessages.hasMore) {\n      this._conversationMessages.loadMore();\n    }\n    return this._conversationMessages;\n  };\n\n  /** Observable of text messages for this address. Lazily loads conversation data. */\n  public textMessages$ = defer(this.initConversationMessages).pipe(\n    map(() => this.textMessage),\n    shareReplay(1),\n    takeUntil(this.destroyed$)\n  );\n  /** Observable of call history for this address. Lazily loads conversation data. */\n  public history$ = defer(this.initConversationMessages).pipe(\n    map(() => this.history),\n    shareReplay(1),\n    takeUntil(this.destroyed$)\n  );\n  private _conversationMessages?: ConversationMessageCollection;\n\n  private _state$ = this.createBehaviorSubject<AddressState | null>(null);\n\n  private _history$?: AddressHistoryCollection<Address>;\n\n  private _textMessages$?: TextMessageCollection<Address>;\n\n  // FIXME after presence API is available\n  // this should be a dynamic view of the address existing calls,\n  // independent if the user is on the call or not.\n  // private _callsStates$ = this.createBehaviorSubject<CallState[]>([]);\n\n  constructor(\n    private readonly addressId: string,\n    private conversationManager: ConversationsProvider,\n    private addressProvider: AddressProvider<Address>\n  ) {\n    super();\n  }\n\n  /** @internal */\n  public upnext(state: Partial<AddressState>): void {\n    const update = {\n      ...this._state$.value,\n      ...state\n    } as AddressState;\n    this._state$.next(update);\n  }\n\n  /** @internal */\n  public get state(): AddressState | null {\n    return this._state$.value;\n  }\n\n  /** Unique address identifier. */\n  public get id(): string {\n    return this.addressId;\n  }\n  /** Address name (resource identifier). */\n  public get name(): string {\n    if (!this._state$.value) {\n      throw new DependencyError('state not initialized');\n    }\n    return this._state$.value.name;\n  }\n\n  /** ISO timestamp of when the address was created. */\n  public get createdAt(): string {\n    if (!this._state$.value) {\n      throw new DependencyError('state not initialized');\n    }\n    return this._state$.value.created_at;\n  }\n\n  /** Default communication channel URI (video for rooms, audio otherwise). */\n  public get defaultChannel(): string | undefined {\n    return this.type === 'room' ? this.channels.video : this.channels.audio;\n  }\n\n  /** Observable of the human-readable display name. */\n  public get displayName$(): Observable<string> {\n    return this.cachedObservable('displayName$', () =>\n      this._state$.pipe(\n        filterNull(),\n        map((state) => state.display_name),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Human-readable display name. */\n  public get displayName(): string {\n    if (!this._state$.value) {\n      throw new DependencyError('state not initialized');\n    }\n    return this._state$.value.display_name;\n  }\n\n  /** Observable of the preview image URL. */\n  public get previewUrl$(): Observable<string | undefined> {\n    return this.cachedObservable('previewUrl$', () =>\n      this._state$.pipe(\n        filterNull(),\n        map((state) => state.preview_url),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Preview image URL. */\n  public get previewUrl(): string | undefined {\n    if (!this._state$.value) {\n      throw new DependencyError('state not initialized');\n    }\n    return this._state$.value.preview_url;\n  }\n\n  /** Observable of the cover image URL. */\n  public get coverUrl$(): Observable<string | undefined> {\n    return this.cachedObservable('coverUrl$', () =>\n      this._state$.pipe(\n        filterNull(),\n        shareReplay(1),\n        map((state) => state.cover_url),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Cover image URL. */\n  public get coverUrl(): string | undefined {\n    if (!this._state$.value) {\n      throw new DependencyError('state not initialized');\n    }\n    return this._state$.value.cover_url;\n  }\n\n  /** Observable of the underlying resource ID. */\n  public get resourceId$(): Observable<string> {\n    return this.cachedObservable('resourceId$', () =>\n      this._state$.pipe(\n        filterNull(),\n        shareReplay(1),\n        map((state) => state.resource_id),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Underlying resource ID. */\n  public get resourceId(): string {\n    if (!this._state$.value) {\n      throw new DependencyError('state not initialized');\n    }\n    return this._state$.value.resource_id;\n  }\n\n  /** Observable of the resource type (e.g. `'room'`, `'subscriber'`). */\n  public get type$(): Observable<ResourceType> {\n    return this.cachedObservable('type$', () =>\n      this._state$.pipe(\n        filterNull(),\n        shareReplay(1),\n        map((state) => state.type),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Resource type (e.g. `'room'`, `'subscriber'`). */\n  public get type(): ResourceType {\n    if (!this._state$.value) {\n      throw new DependencyError('state not initialized');\n    }\n    return this._state$.value.type;\n  }\n\n  /** Observable of available communication channels (audio, video, messaging). */\n  public get channels$(): Observable<{\n    audio?: string;\n    messaging?: string;\n    video?: string;\n  }> {\n    return this.cachedObservable('channels$', () =>\n      this._state$.pipe(\n        filterNull(),\n        shareReplay(1),\n        map((state) => state.channels),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /** Available communication channels. */\n  public get channels(): {\n    audio?: string;\n    messaging?: string;\n    video?: string;\n  } {\n    if (!this._state$.value) {\n      throw new DependencyError('state not initialized');\n    }\n    return this._state$.value.channels;\n  }\n\n  /** Whether the address (room) is locked. */\n  public get locked(): boolean {\n    if (!this._state$.value) {\n      throw new DependencyError('state not initialized');\n    }\n    return this._state$.value.locked;\n  }\n\n  /** Observable indicating whether the address (room) is locked. */\n  public get locked$(): Observable<boolean> {\n    return this.cachedObservable('locked$', () =>\n      this._state$.pipe(\n        filterNull(),\n        shareReplay(1),\n        map((state) => state.locked),\n        takeUntil(this.destroyed$)\n      )\n    );\n  }\n\n  /**\n   * Sends a text message to this address.\n   *\n   * @param text - The message text to send.\n   *\n   * @example\n   * ```ts\n   * await address.sendText('Hello!');\n   * ```\n   */\n  public async sendText(text: string): Promise<void> {\n    return this.conversationManager.sendText(text, this.id);\n  }\n\n  /**\n   * Collection of text messages for this address, with pagination support.\n   *\n   * Returns `undefined` until {@link textMessages$} has been subscribed to (lazy-loaded).\n   * Filters to `'chat'` subtype messages from the conversation.\n   *\n   * @see {@link textMessages$} to trigger lazy loading.\n   * @see {@link sendText} to send a new message.\n   */\n  public get textMessage():\n    | EntityCollectionTransformed<GetConversationMessageResponse, TextMessage<Address>>\n    | undefined {\n    if (!this._conversationMessages) {\n      return;\n    }\n    this._textMessages$ =\n      this._textMessages$ ??\n      new EntityCollectionTransformed<GetConversationMessageResponse, TextMessage<Address>>(\n        this._conversationMessages,\n        (item): item is GetConversationMessageResponse =>\n          (item as GetConversationMessageResponse).subtype === 'chat',\n        (item) =>\n          ({\n            id: item.id,\n            text: item.text,\n            created: item.ts,\n            fromAddress$: this.addressProvider.get$(item.from_fabric_address_id)\n          }) as TextMessage<Address>\n      );\n    return this._textMessages$;\n  }\n\n  /**\n   * Collection of call history entries for this address, with pagination support.\n   *\n   * Returns `undefined` until {@link history$} has been subscribed to (lazy-loaded).\n   * Filters to `'log'` subtype messages including kind, status, start/end times.\n   *\n   * @see {@link history$} to trigger lazy loading.\n   */\n  public get history():\n    | EntityCollectionTransformed<GetConversationMessageResponse, AddressHistory<Address>>\n    | undefined {\n    if (!this._conversationMessages) {\n      return;\n    }\n    this._history$ =\n      this._history$ ??\n      new EntityCollectionTransformed<GetConversationMessageResponse, AddressHistory<Address>>(\n        this._conversationMessages,\n        (item): item is GetConversationMessageResponse =>\n          (item as GetConversationMessageResponse).subtype === 'log',\n        (item) =>\n          ({\n            id: item.id,\n            kind: item.kind,\n            status: item.details.status,\n            started: item.details.start_time,\n            ended: item.details.end_time,\n            fromAddress$: this.addressProvider.get$(item.from_fabric_address_id)\n          }) as AddressHistory<Address>\n      );\n    return this._history$;\n  }\n\n  /** Observable of active call states for this address. @throws {UnimplementedError} Requires presence support. */\n  public get activity$(): Observable<CallState[]> {\n    // NEEDS Presence\n    throw new UnimplementedError();\n  }\n\n  /** Active call states for this address. @throws {UnimplementedError} Requires presence support. */\n  public get activity(): CallState[] {\n    // NEEDS Presence\n    throw new UnimplementedError();\n  }\n}\n","import { filter, NEVER, Observable, race, take } from 'rxjs';\nimport { v4 as uuid } from 'uuid';\n\nimport { InvalidListenerError, JSONRPCError, RPCTimeoutError } from './errors';\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { getLogger } from '../utils/logger';\n\nimport type { RPCConnectResult } from './RPCMessages';\nimport type { JSONRPCRequest, JSONRPCResponse } from './RPCMessages/types/base';\n\nconst logger = getLogger();\n\nexport async function callListener<T>(\n  listener: ((value: T) => void) | ((value: T) => Promise<void>),\n  value: T,\n  onError?: (error: unknown) => void\n): Promise<void> {\n  try {\n    if (typeof listener !== 'function') {\n      throw new InvalidListenerError();\n    }\n    await listener(value);\n  } catch (error) {\n    if (error instanceof InvalidListenerError) {\n      logger.error(error.message);\n    } else {\n      logger.warn('Error calling listener:', error);\n    }\n    onError?.(error);\n  }\n}\n\nexport const isRPCConnectResult = (e: unknown): e is RPCConnectResult => {\n  logger.debug('isRPCConnectResult check:', e);\n  if (!e || typeof e !== 'object') return false;\n\n  // Check if this is a JSON-RPC response with a result property\n\n  const result = e as RPCConnectResult;\n\n  const is =\n    typeof result.identity === 'string' &&\n    typeof result.protocol === 'string' &&\n    typeof result.authorization === 'object' &&\n    typeof result.authorization.jti === 'string' &&\n    typeof result.authorization.project_id === 'string' &&\n    typeof result.authorization.fabric_subscriber === 'object';\n\n  logger.debug('isRPCConnectResult check result:', is);\n  return is;\n};\n\nexport interface PendingRPCOptions {\n  /**\n   * Timeout in milliseconds. Defaults to 5000ms (5 seconds).\n   * If the response is not received within this time, the promise will reject with RPCTimeoutError.\n   */\n  timeoutMs?: number;\n\n  /**\n   * Optional AbortSignal for cancellation support.\n   * If the signal is aborted, the promise will reject with an AbortError.\n   */\n  signal?: AbortSignal;\n}\n\nexport class PendingRPC<T extends JSONRPCResponse = JSONRPCResponse> {\n  private static readonly defaultTimeoutMs = 5000;\n  private id = uuid();\n\n  public readonly request: JSONRPCRequest;\n  public promise: Promise<T>;\n\n  constructor(request: JSONRPCRequest, responses$: Observable<T>, options?: PendingRPCOptions) {\n    logger.debug(\n      `[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`\n    );\n    this.request = request;\n\n    const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;\n    const signal = options?.signal;\n\n    this.promise = new Promise<T>((resolve, reject) => {\n      // Check if already aborted\n      if (signal?.aborted) {\n        reject(new DOMException('The operation was aborted', 'AbortError'));\n        return;\n      }\n\n      // Track if promise has been settled to prevent unhandled rejections\n      let isSettled = false;\n\n      // Create the main response observable\n      const response$ = responses$.pipe(\n        filter((result) => result.id === request.id),\n        take(1)\n      );\n\n      // Create timeout observable\n      const timeout$ = new Observable<never>((subscriber) => {\n        const timer = setTimeout(() => {\n          subscriber.error(new RPCTimeoutError(request.id, timeoutMs));\n        }, timeoutMs);\n\n        return () => clearTimeout(timer);\n      });\n\n      // Create abort observable if signal provided\n      const abort$ = signal\n        ? new Observable<never>((subscriber) => {\n            const abortHandler = () => {\n              subscriber.error(new DOMException('The operation was aborted', 'AbortError'));\n            };\n            signal.addEventListener('abort', abortHandler);\n\n            return () => signal.removeEventListener('abort', abortHandler);\n          })\n        : NEVER; // Observable that never emits\n\n      // Race between response, timeout, and abort\n      const subscription = race(response$, timeout$, abort$).subscribe({\n        next: (response) => {\n          isSettled = true;\n          if (response.error) {\n            const rpcError = new JSONRPCError(\n              response.error.code,\n              response.error.message,\n              response.error.data,\n              undefined,\n              request.id\n            );\n            logger.debug(\n              `[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`,\n              rpcError\n            );\n            reject(rpcError);\n          } else {\n            logger.debug(\n              `[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`,\n              response\n            );\n            resolve(response);\n          }\n          subscription.unsubscribe();\n        },\n        error: (error) => {\n          logger.debug(\n            `[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`,\n            error\n          );\n          isSettled = true;\n          reject(error as Error);\n          subscription.unsubscribe();\n        },\n        complete: () => {\n          logger.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);\n          if (!isSettled) {\n            reject(new RPCTimeoutError(request.id, timeoutMs));\n          }\n          subscription.unsubscribe();\n        }\n      });\n    });\n  }\n\n  // Make it thenable (Promise-like)\n  async then<TResult1 = JSONRPCResponse, TResult2 = never>(\n    onfulfilled?: ((value: JSONRPCResponse) => TResult1 | PromiseLike<TResult1>) | null,\n    onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null\n  ): Promise<TResult1 | TResult2> {\n    return this.promise.then(onfulfilled, onrejected);\n  }\n\n  async catch<TResult = never>(\n    onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null\n  ): Promise<JSONRPCResponse | TResult> {\n    return this.promise.catch(onrejected);\n  }\n\n  async finally(onfinally?: (() => void) | null): Promise<JSONRPCResponse> {\n    return this.promise.finally(onfinally);\n  }\n}\n\n// Re-export Destroyable for backward compatibility\nexport { Destroyable };\n","import {\n  catchError,\n  combineLatest,\n  defer,\n  distinctUntilChanged,\n  EMPTY,\n  exhaustMap,\n  filter,\n  firstValueFrom,\n  from,\n  lastValueFrom,\n  map,\n  merge,\n  race,\n  share,\n  shareReplay,\n  skip,\n  switchMap,\n  take,\n  takeUntil,\n  tap,\n  throwError,\n  timeout,\n  TimeoutError\n} from 'rxjs';\n\nimport { CallFactory } from './CallFactory';\nimport {\n  RPC_ERROR_REQUESTER_VALIDATION_FAILED,\n  RPC_ERROR_INVALID_PARAMS,\n  RPC_ERROR_AUTHENTICATION_FAILED,\n  CREDENTIAL_EXPIRY_SKEW_MS,\n  DEFAULT_CALL_SIGNALING_TIMEOUT_MS\n} from '../core/constants';\nimport { Address } from '../core/entities/Address';\nimport {\n  AuthStateHandlerError,\n  CallCreateError,\n  DependencyError,\n  JSONRPCError,\n  MediaAccessError,\n  TransportConnectionError,\n  UnexpectedError,\n  VertoAttachHandlerError,\n  VertoInviteHandlerError\n} from '../core/errors';\nimport { RPCConnect, RPCReauthenticate } from '../core/RPCMessages';\nimport {\n  isSignalwireAuthorizationStateMetadata,\n  isSignalwireRequest,\n  isWebrtcMessageMetadata\n} from '../core/RPCMessages/guards/events.guards';\nimport {\n  isVertoAttachMessage,\n  isVertoInviteMessage\n} from '../core/RPCMessages/guards/verto.guards';\nimport { Destroyable, isRPCConnectResult } from '../core/utils';\nimport { filterAs } from '../operators/filterEventAs';\nimport { throwOnRPCError } from '../operators/throwOnRPCError';\nimport { getLogger } from '../utils/logger';\n\nimport type { AttachManager } from './AttachManager';\nimport type { StorageManager } from './StorageManager';\nimport type { TransportManager } from './TransportManager';\nimport type { CryptoController } from '../controllers/CryptoController';\nimport type { NetworkChangeEvent } from '../controllers/NetworkMonitor';\nimport type { WebRTCCall } from '../core/entities/Call';\nimport type { Directory } from '../core/entities/Directory';\nimport type { Call, CallOptions } from '../core/entities/types/call.types';\nimport type { CallError } from '../core/errors';\nimport type {\n  RPCConnectParams,\n  RPCConnectAuthentication,\n  Authorization,\n  RPCReauthenticateParams\n} from '../core/RPCMessages';\nimport type { JSONRPCRequest, JSONRPCResponse } from '../core/RPCMessages/types/base';\nimport type { VertoAttachParams, VertoInviteParams } from '../core/RPCMessages/types/verto';\nimport type { SDKCredential, JSONSerializable } from '../core/types/common.types';\nimport type { PendingRPCOptions } from '../core/utils';\nimport type { WebRTCApiProvider } from '../dependencies/interfaces';\nimport type { DeviceController } from '../interfaces/DeviceController';\nimport type { SessionState } from '../interfaces/SessionState';\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\n/**\n * Decide whether an error emitted on `call.errors$` during dial should\n * abort the dial. A non-fatal MediaAccessError means the call degraded to\n * receive-only and still connects — everything else rejects `dial()` with\n * the real cause.\n *\n * Pure function — exported for unit testing.\n */\nexport function shouldAbortDial(callError: CallError): boolean {\n  return callError.fatal || !(callError.error instanceof MediaAccessError);\n}\n\n/**\n * Wait for a dialed call to be ready, or for the failure that stops it.\n *\n * Local media acquisition is deliberately unbounded: a permission prompt or a\n * device picker is human time, and `getUserMedia` cannot be cancelled anyway.\n * The clock starts only once acquisition settles, so a slow human never spends\n * the server's budget.\n *\n * `merge` rather than `race`, because the two legs settle asymmetrically. A\n * fatal acquisition failure reports the error and then destroys the call in the\n * same synchronous step; `errors$` defers delivery by a microtask while\n * `localMediaSettled$` completes immediately. Under `race` that bare completion\n * ended the wait first and `dial()` rejected with an RxJS `EmptyError`, burying\n * the `NotAllowedError` applications are told to inspect. Under `merge` the\n * completed leg is simply spent, and the queued error — enqueued before the\n * completion, so delivered before it — arrives to reject the wait. A dial\n * abandoned with no error at all still ends both legs, and the resulting\n * `EmptyError` remains the benign-cancel signal.\n *\n * Exported for unit testing.\n */\nexport async function awaitDialReady(\n  session: Pick<WebRTCCall, 'localMediaSettled$' | 'selfId$' | 'errors$'>,\n  signalingTimeoutMs: number\n): Promise<unknown> {\n  return firstValueFrom(\n    merge(\n      session.localMediaSettled$.pipe(\n        take(1),\n        switchMap(() =>\n          session.selfId$.pipe(\n            filter((id) => Boolean(id)),\n            take(1),\n            timeout(signalingTimeoutMs)\n          )\n        )\n      ),\n      session.errors$.pipe(\n        filter(shouldAbortDial),\n        take(1),\n        switchMap((callError) => throwError(() => callError.error))\n      )\n    )\n  );\n}\n\nconst getAddressSearchURI = (options: CallOptions): string => {\n  const to = options.to?.split('?')[0];\n  const from = options.from?.startsWith('subscriber://')\n    ? options.from.replace('subscriber://', '')\n    : options.from;\n  const name = to ?? from;\n  if (!name) {\n    throw new UnexpectedError('Error building Address name');\n  }\n  return name;\n};\n\n/**\n * Discriminated union for session authentication state.\n * clientBound is tracked separately via _wasClientBound (sticky flag)\n * to avoid dual sources of truth.\n */\nexport type SessionAuthState = { kind: 'unauthenticated' } | { kind: 'authenticated' };\n\nexport class ClientSessionManager extends Destroyable implements SessionState {\n  private callFactory: CallFactory;\n  private readonly agent = `signalwire-js/4.0.0`;\n  private readonly eventAcks = true;\n  public initialized$: Observable<boolean>;\n  private authorizationState$ = this.createReplaySubject<string | undefined>(1);\n  private connectVersion = {\n    major: 4,\n    minor: 0,\n    revision: 0\n  };\n  /**\n   * Optional hook called before a fresh connect on reconnect.\n   * Used by SignalWire to refresh expired credentials before re-authenticating.\n   * @internal\n   */\n  public onBeforeReconnect?: () => Promise<void>;\n  /**\n   * Called when the server refuses a fresh connect on authentication and no\n   * re-minted credential is left to try; the session has stopped reconnecting.\n   * @internal\n   */\n  public onAuthenticationRejected?: () => void;\n  /**\n   * Session-wide call control transport (see {@link ClientSession.callControl}).\n   * Set from {@link SignalWireOptions.callControl} by SignalWire after construction;\n   * defaults to `'routed'`. Read by every Call, so it needs no per-call persistence.\n   */\n  public callControl: 'routed' | 'in-dialog' = 'routed';\n  private _authorization$ = this.createBehaviorSubject<Authorization | undefined>(undefined);\n  private _errors$ = this.createReplaySubject<Error>(1);\n  private _directory?: Directory;\n\n  private _authState$ = this.createBehaviorSubject<SessionAuthState>({ kind: 'unauthenticated' });\n  /** Sticky flag — once true, stays true for the session lifetime. */\n  private _wasClientBound = false;\n  private _lastFreshConnectReminted = false;\n  private _remintBeforeNextFreshConnect = false;\n\n  private _userInfo$ = this.createBehaviorSubject<Address | null>(null);\n  private _calls$ = this.createBehaviorSubject<Record<string, Call>>({});\n  private _iceServers$ = this.createBehaviorSubject<RTCIceServer[]>([]);\n\n  constructor(\n    private readonly getCredential: () => SDKCredential,\n    private readonly transport: TransportManager,\n    private readonly storage: StorageManager,\n    private readonly authorizationStateKey: string,\n    deviceController: DeviceController,\n    private readonly attachManager: AttachManager,\n    webRTCApiProvider: WebRTCApiProvider,\n    private readonly dpopManager?: CryptoController,\n    networkChange$?: Observable<NetworkChangeEvent>\n  ) {\n    super();\n    attachManager.setSession(this);\n    this.callFactory = new CallFactory(\n      this,\n      deviceController,\n      attachManager,\n      webRTCApiProvider,\n      networkChange$\n    );\n    this.initialized$ = defer(() => from(this.init())).pipe(\n      shareReplay(1),\n      takeUntil(this.destroyed$)\n    );\n  }\n  public get incomingCalls$(): Observable<Call[]> {\n    return this.cachedObservable('incomingCalls$', () =>\n      this.calls$.pipe(map((calls) => calls.filter((call) => call.direction === 'inbound')))\n    );\n  }\n\n  public get incomingCalls(): Call[] {\n    const calls = Object.values(this._calls$.value);\n    return calls.filter((call) => call.direction === 'inbound');\n  }\n\n  public get userInfo$(): Observable<Address | null> {\n    return this._userInfo$.asObservable();\n  }\n\n  public get userInfo(): Address | null {\n    return this._userInfo$.value;\n  }\n\n  public get calls$(): Observable<Call[]> {\n    return this.cachedObservable('calls$', () =>\n      this._calls$.pipe(map((calls) => Object.values(calls)))\n    );\n  }\n\n  public get calls(): Call[] {\n    return Object.values(this._calls$.value);\n  }\n\n  public get iceServers(): RTCIceServer[] | undefined {\n    return this._iceServers$.value;\n  }\n\n  public get authorization$(): Observable<Authorization | undefined> {\n    return this._authorization$.asObservable();\n  }\n\n  public get authorization(): Authorization | undefined {\n    return this._authorization$.value;\n  }\n\n  public get errors$(): Observable<Error> {\n    return this._errors$.asObservable();\n  }\n\n  public get authenticated$(): Observable<boolean> {\n    return this._authState$.pipe(\n      map((state) => state.kind === 'authenticated'),\n      distinctUntilChanged()\n    );\n  }\n\n  public get authenticated(): boolean {\n    return this._authState$.value.kind === 'authenticated';\n  }\n\n  /**\n   * Whether this session is client-bound (using a Client Bound SAT).\n   * When client-bound, DPoP proof creation failures are treated as\n   * authentication errors rather than silently degraded.\n   * @internal\n   */\n  public get clientBound(): boolean {\n    return this._wasClientBound;\n  }\n\n  /** @internal Current auth state for debugging/testing. */\n  public get authState(): SessionAuthState {\n    return this._authState$.value;\n  }\n\n  /**\n   * Set the directory instance\n   * Called by SignalWire after directory is created\n   * @internal\n   */\n  public setDirectory(directory: Directory): void {\n    this._directory = directory;\n  }\n\n  public async execute<T extends JSONRPCResponse = JSONRPCResponse>(\n    request: JSONRPCRequest,\n    options?: PendingRPCOptions\n  ): Promise<T> {\n    try {\n      return await this.transport.execute(request, options);\n    } catch (error) {\n      logger.debug('[Session] Execute Error', error);\n      this._errors$.next(\n        error instanceof Error ? error : new Error(String(error), { cause: error })\n      );\n      throw error;\n    }\n  }\n\n  public send(message: JSONSerializable): void {\n    this.transport.send(message);\n  }\n\n  private async init(): Promise<boolean> {\n    await this.loadAuthorizationStateFromStorage();\n    this.setupMessageHandlers();\n    return true;\n  }\n\n  private setupMessageHandlers(): void {\n    logger.debug('[Session] Setting up message handlers');\n\n    this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {\n      logger.debug('[Session] Authorization state event received:', authStateEvent);\n      try {\n        await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);\n      } catch (error) {\n        logger.error('[Session] Failed to handle authorization state update:', error);\n        this._errors$.next(new AuthStateHandlerError(error));\n      }\n    });\n\n    // Reset auth state when transport disconnects or starts reconnecting,\n    // so that authenticated$ transitions through false → true on reconnect.\n    // This makes the state model truthful and allows distinctUntilChanged()\n    // to work correctly for subscribers that detect reconnect.\n    // Note: WebSocket close with auto-reconnect emits 'reconnecting', not\n    // 'disconnected'. Only explicit disconnect() emits 'disconnected'.\n    this.subscribeTo(\n      this.transport.connectionStatus$.pipe(\n        filter((status) => status === 'disconnected' || status === 'reconnecting')\n      ),\n      () => {\n        if (this._authState$.value.kind === 'authenticated') {\n          this._authState$.next({ kind: 'unauthenticated' });\n        }\n      }\n    );\n\n    this.subscribeTo(\n      this.transport.connectionStatus$.pipe(\n        filter((status) => status === 'connected'),\n        exhaustMap(() => {\n          logger.debug('[Session] Connection established, initiating authentication');\n          return from(this.authenticate()).pipe(\n            catchError((error) => {\n              this.handleAuthenticationError(error as Error).catch((err) => {\n                logger.error('[Session] Error handling authentication failure:', err);\n              });\n              return EMPTY;\n            })\n          );\n        })\n      ),\n      undefined\n    );\n\n    this.subscribeTo(this.vertoInvite$, async (invite) => {\n      logger.debug('[Session] Verto invite received:', invite);\n      try {\n        await this.createInboundCall(invite);\n      } catch (error) {\n        logger.error('[Session] Error handling Verto invite:', error);\n        this._errors$.next(new VertoInviteHandlerError(error));\n      }\n    });\n\n    this.subscribeTo(this.vertoAttach$, async (attach) => {\n      logger.debug('[Session] Verto attach received:', attach);\n      try {\n        await this.handleVertoAttach(attach);\n      } catch (error) {\n        logger.error('[Session] Error handling Verto attach:', error);\n        this._errors$.next(new VertoAttachHandlerError(error));\n      }\n    });\n  }\n\n  private async loadAuthorizationStateFromStorage(): Promise<void> {\n    try {\n      const storedState = await this.storage.getItem<string>(this.authorizationStateKey);\n      // Always emit a value, even if undefined, so combineLatest can proceed\n      this.authorizationState$.next(storedState ?? undefined);\n    } catch (error) {\n      logger.error('Failed to retrieve authorization state from storage:', error);\n      // Emit undefined on error so authentication can proceed without stored state\n      this.authorizationState$.next(undefined);\n    }\n  }\n\n  private async updateAuthorizationStateInStorage(authorizationState?: string): Promise<void> {\n    if (!authorizationState) {\n      logger.debug('[Session] Removing authorization state from storage');\n      try {\n        await this.storage.removeItem(this.authorizationStateKey);\n        this.authorizationState$.next(undefined);\n      } catch (error) {\n        logger.error('Failed to remove authorization state from storage:', error);\n        throw error;\n      }\n      return;\n    }\n\n    try {\n      logger.debug('[Session] Updating authorization state in storage');\n      await this.storage.setItem(this.authorizationStateKey, authorizationState);\n      this.authorizationState$.next(authorizationState);\n    } catch (error) {\n      logger.error('Failed to retrieve authorization state from storage:', error);\n      throw error;\n    }\n  }\n\n  private get authStateEvent$() {\n    return this.cachedObservable('authStateEvent$', () =>\n      this.signalingEvent$.pipe(\n        tap((msg) => {\n          logger.debug('[Session] Received incoming message:', msg);\n        }),\n        filterAs(isSignalwireAuthorizationStateMetadata, 'params'),\n        tap((event) => {\n          logger.debug('[Session] Authorization state event received:', event.authorization_state);\n        })\n      )\n    );\n  }\n\n  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n  public get signalingEvent$() {\n    return this.cachedObservable('signalingEvent$', () =>\n      this.transport.incomingEvent$.pipe(filterAs(isSignalwireRequest, 'params'), share())\n    );\n  }\n\n  private get vertoInvite$() {\n    return this.cachedObservable('vertoInvite$', () =>\n      this.signalingEvent$.pipe(\n        filter(isWebrtcMessageMetadata),\n        filter((event) => isVertoInviteMessage(event.params)),\n        map((event) => ({\n          node_id: event.node_id,\n          ...(event.params.params as VertoInviteParams)\n        }))\n      )\n    );\n  }\n\n  private get vertoAttach$() {\n    return this.cachedObservable('vertoAttach$', () =>\n      this.signalingEvent$.pipe(\n        filter(isWebrtcMessageMetadata),\n        filter((event) => isVertoAttachMessage(event.params)),\n        map((event) => ({\n          node_id: event.node_id,\n          ...(event.params.params as VertoAttachParams)\n        }))\n      )\n    );\n  }\n\n  private get contexts(): string[] {\n    return [];\n  }\n\n  private get eventing(): string[] {\n    return [];\n  }\n\n  private get topics(): string[] {\n    return [];\n  }\n\n  private get authentication(): RPCConnectAuthentication {\n    const credential = this.getCredential();\n    if (!credential.token) {\n      throw new DependencyError('Credential token is undefined');\n    }\n    return {\n      jwt_token: credential.token\n    };\n  }\n\n  async connect(): Promise<void> {\n    // Ensure session is initialized before proceeding\n    await firstValueFrom(this.initialized$);\n\n    // Initiate the WebSocket connection. The exhaustMap subscription in\n    // setupMessageHandlers triggers authenticate() when the WS opens.\n    await this.transport.connect();\n\n    // Gate: don't return until authentication succeeds or fails permanently.\n    // If auth fails with stale authorization_state, handleAuthenticationError\n    // cleans up and reconnects. The exhaustMap fires authenticate() again on\n    // the new 'connected' event. When it succeeds, authenticated$ emits true.\n    // takeUntil(destroyed$) terminates if the session is destroyed mid-auth.\n    // timeout prevents hanging when the server never responds (e.g. test mocks).\n    await firstValueFrom(\n      this.authenticated$.pipe(\n        takeUntil(this.destroyed$),\n        filter(Boolean),\n        take(1),\n        timeout({ first: 15000 })\n      )\n    );\n  }\n\n  private async handleAuthenticationError(error: Error): Promise<void> {\n    // The socket closed before signalwire.connect was answered. The next\n    // 'connected' event starts authentication again.\n    if (error instanceof TransportConnectionError) {\n      logger.debug('[Session] Socket closed during authentication, waiting for reconnect');\n      return;\n    }\n\n    logger.error('Authentication error:', error);\n\n    const isRecoverableAuthError =\n      error instanceof JSONRPCError &&\n      (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED ||\n        error.code === RPC_ERROR_INVALID_PARAMS ||\n        error.code === RPC_ERROR_AUTHENTICATION_FAILED);\n\n    // Only fall back if there was stored authorization_state to clear (reconnect attempt).\n    // If this was already a fresh connect, don't loop — the credentials are genuinely bad.\n    // Check authorizationState$ (the string we sent) not _authorization$ (the response object).\n    const sentStoredState = await firstValueFrom(this.authorizationState$.pipe(take(1)));\n    const hasStoredState = sentStoredState !== undefined;\n\n    if (isRecoverableAuthError && hasStoredState) {\n      // Server rejected reconnect — expired/corrupted authorization_state or jti drift.\n      // Clean up stored state and reconnect fresh with SAT + DPoP.\n      // Don't push to _errors$ — this is a transient error the SDK will self-heal.\n      logger.debug(\n        '[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh'\n      );\n      await this.discardResumeStateAndReconnect();\n    } else if (\n      isRecoverableAuthError &&\n      this.onBeforeReconnect &&\n      !this._lastFreshConnectReminted\n    ) {\n      // The token went out unrefreshed because it looked valid by this\n      // client's clock, which can be wrong; a re-minted one may be accepted.\n      logger.debug('[Session] Fresh connect refused — re-minting the credential and retrying once');\n      this._remintBeforeNextFreshConnect = true;\n      this.transport.reconnect();\n    } else {\n      // Fatal auth error — surface to consumers\n      this._errors$.next(error);\n      if (isRecoverableAuthError) {\n        // The server closes the socket after refusing it, and the transport\n        // would reconnect straight into the same refusal.\n        this.transport.disconnect();\n        this.onAuthenticationRejected?.();\n      }\n    }\n  }\n\n  /**\n   * Clear the resume state (authorization_state + protocol) and ask the\n   * transport to reconnect. The `connected` event re-triggers\n   * `authenticate()`, which now has no stored state and so performs a fresh\n   * connect.\n   *\n   * This is the stale-auth-state recovery helper used by handleAuthError:\n   * the server rejected a reconnect, so the resume state is discarded and a\n   * fresh connect follows. Attach records are deliberately preserved — the\n   * session lives on through the reconnect and reattachCalls() needs the\n   * stored call references afterwards. Do NOT add detachAll() here.\n   *\n   * Connect-time recovery only. A *request* refused on an already\n   * authenticated session is never healed here: dropping the resume state\n   * destroys the association between the socket and the previous session,\n   * which is what reattach depends on. That path mints a fresh credential and\n   * reauthenticates instead (see `SignalWire.recoverAndRetry`).\n   *\n   * For public teardown (disconnect/destroy), use {@link teardownSessionState}\n   * instead, which clears the attach records as well.\n   */\n  private async discardResumeStateAndReconnect(): Promise<void> {\n    try {\n      await this.cleanupStoredConnectionParams();\n    } catch (cleanupError) {\n      logger.error('Failed to cleanup stored connection params:', cleanupError);\n    } finally {\n      this.transport.reconnect();\n    }\n  }\n\n  async cleanupStoredConnectionParams(): Promise<void> {\n    await this.transport.setProtocol(undefined);\n    await this.updateAuthorizationStateInStorage(undefined);\n    this._authorization$.next(undefined);\n  }\n\n  /**\n   * Public-teardown helper for disconnect()/destroy(). Clears the resume\n   * state (authorization_state + protocol) AND the attach records as one\n   * atomic unit.\n   *\n   * The two stores are coupled: the backend only honors attach records\n   * within the session identified by the resume state, so ending the\n   * session must clear both. Clearing one without the other strands records\n   * no future session can honor (disconnect) or revives a session the\n   * developer explicitly ended (destroy).\n   *\n   * Distinct from {@link cleanupStoredConnectionParams}, which keeps the\n   * attach records for the stale-auth-state recovery path.\n   */\n  async teardownSessionState(): Promise<void> {\n    await this.cleanupStoredConnectionParams();\n    await this.attachManager.detachAll();\n  }\n\n  protected async updateAuthState(authorization_state: string): Promise<void> {\n    try {\n      await this.storage.setItem(this.authorizationStateKey, authorization_state);\n    } catch (error) {\n      logger.error('Failed to update authorization state in storage:', error);\n      this._errors$.next(new AuthStateHandlerError(error));\n    }\n  }\n\n  public async reauthenticate(\n    token: string,\n    dpopToken?: string,\n    options?: { clientBound?: boolean }\n  ): Promise<void> {\n    logger.debug('[Session] Re-authenticating session');\n    try {\n      // Resolve DPoP proof: use provided, generate fresh, or omit\n      let resolvedDpopToken = dpopToken;\n      if (!resolvedDpopToken && this.dpopManager?.initialized) {\n        try {\n          resolvedDpopToken = await this.dpopManager.createRpcProof({\n            method: 'signalwire.reauthenticate'\n          });\n        } catch (error) {\n          if (this.clientBound) {\n            // Client-bound sessions must not silently degrade to unbound\n            throw error;\n          }\n          logger.warn('[Session] Failed to create DPoP proof for reauthenticate:', error);\n        }\n      }\n\n      const params: RPCReauthenticateParams = {\n        project: this._authorization$.value?.project_id ?? '',\n        jwt_token: token,\n        ...(resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {})\n      };\n\n      const request = RPCReauthenticate(params);\n\n      await lastValueFrom(\n        from(this.transport.execute(request)).pipe(\n          throwOnRPCError(),\n          take(1),\n          catchError((err) => {\n            logger.error('[Session] Re-authentication RPC failed:', err);\n            throw err;\n          })\n        )\n      );\n\n      // Mark session as client-bound if requested (set-once semantics)\n      if (options?.clientBound) {\n        this._wasClientBound = true;\n      }\n\n      logger.debug('[Session] Re-authentication successful, updating stored auth state');\n    } catch (error) {\n      logger.error('[Session] Re-authentication failed:', error);\n      this._errors$.next(new AuthStateHandlerError(error));\n      throw error;\n    }\n  }\n\n  private async authenticate(): Promise<void> {\n    logger.debug('[Session] Starting authentication process');\n    this._lastFreshConnectReminted = false;\n\n    const persistedParams = await firstValueFrom(\n      combineLatest({\n        protocol: this.transport.protocol$,\n        authorization_state: this.authorizationState$\n      }).pipe(take(1))\n    );\n\n    logger.debug('[Session] Persisted params:\\n', {\n      protocol: persistedParams.protocol,\n      authStateLength: persistedParams.authorization_state?.length\n    });\n\n    const hasReconnectState = persistedParams.authorization_state && persistedParams.protocol;\n    const storedToken = this.getCredential().token;\n    // Reconnect if we have stored state AND a stored token\n    const isReconnect = hasReconnectState && storedToken;\n\n    let dpopToken: string | undefined;\n\n    if (isReconnect) {\n      // RECONNECT: send stored jwt_token (even if expired) + authorization_state + protocol + DPoP\n      // authorization_state short-circuits token validation; jwt_token is needed for session classification\n      logger.debug('[Session] Reconnecting with stored jwt_token + authorization_state');\n    } else {\n      // FRESH CONNECT: refresh credentials if needed, then use fresh SAT + DPoP.\n      // Invoke the reconnect hook when the session is client-bound (it needs a\n      // fresh base SAT to re-bind) OR when the in-memory token is expired OR\n      // when the server refused the token the previous fresh connect sent\n      // as-is. An unbound SAT that is expired must be re-minted via the\n      // developer's refresh handler before authenticating — otherwise the\n      // fresh connect replays a dead token and the server rejects it with -32003.\n      const credential = this.getCredential();\n      const credentialExpired =\n        credential.expiry_at !== undefined &&\n        credential.expiry_at <= Date.now() + CREDENTIAL_EXPIRY_SKEW_MS;\n      const remint = this.clientBound || credentialExpired || this._remintBeforeNextFreshConnect;\n      this._remintBeforeNextFreshConnect = false;\n      if (this.onBeforeReconnect && remint) {\n        logger.debug('[Session] Refreshing credentials before fresh connect');\n        this._lastFreshConnectReminted = true;\n        await this.onBeforeReconnect();\n      }\n    }\n\n    // DPoP proof: send a fresh proof whenever the key is available.\n    // - Fresh connect: the server needs it to mint a DPoP binding.\n    // - Resume (live reconnect OR page reload): the persisted\n    //   authorization_state carries the BOUND authorization when the session\n    //   was client-bound — the server re-verifies the proof against its\n    //   cnf.jkt and rejects the resume with -32002 when it is missing. The\n    //   sticky clientBound flag does not survive a reload, so the initialized\n    //   DPoP key (restored from IndexedDB) is the trigger; an unsolicited\n    //   proof on an unbound resume is ignored by the server.\n    if (this.dpopManager?.initialized) {\n      try {\n        dpopToken = await this.dpopManager.createRpcProof({\n          method: 'signalwire.connect'\n        });\n      } catch (error) {\n        if (this.clientBound) {\n          throw error;\n        }\n        logger.warn(\n          '[Session] Failed to create DPoP proof for connect, proceeding without:',\n          error\n        );\n      }\n    }\n\n    const params: RPCConnectParams = {\n      authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,\n      version: this.connectVersion,\n      agent: this.agent,\n      contexts: this.contexts,\n      eventing: this.eventing,\n      topics: this.topics,\n      event_acks: this.eventAcks,\n      ...(dpopToken ? { dpop_token: dpopToken } : {}),\n      ...(isReconnect\n        ? {\n            authorization_state: persistedParams.authorization_state,\n            protocol: persistedParams.protocol\n          }\n        : {})\n    };\n\n    const rpcConnectRequest = RPCConnect(params);\n\n    // The socket can close during the awaits above. Do not send connect for a\n    // socket that is gone: the next 'connected' event authenticates again.\n    if (this.transport.connectionStatus !== 'connected') {\n      throw new TransportConnectionError('WebSocket closed during authentication');\n    }\n\n    // Fail at once if the socket closes before the server answers. Otherwise\n    // the request waits for the full RPC timeout, and exhaustMap ignores the\n    // next socket until then.\n    const socketClosed$ = this.transport.connectionStatus$.pipe(\n      skip(1),\n      filter((status) => status === 'reconnecting' || status === 'disconnected'),\n      take(1),\n      switchMap(() =>\n        throwError(() => new TransportConnectionError('WebSocket closed during authentication'))\n      )\n    );\n\n    const response = await lastValueFrom(\n      race(from(this.transport.execute(rpcConnectRequest)), socketClosed$).pipe(\n        throwOnRPCError(),\n        map((res) => res.result),\n        filter(isRPCConnectResult),\n        tap(() => {\n          logger.debug('[Session] Response passed filter, processing authentication result');\n        }),\n        take(1),\n        catchError((err) => {\n          logger.error('[Session] Authentication RPC failed:', err);\n          throw err;\n        })\n      )\n    );\n\n    logger.debug('[Session] Processing authentication result:', {\n      hasProtocol: !!response.protocol,\n      hasAuthorization: !!response.authorization,\n      hasIceServers: !!response.ice_servers\n    });\n\n    if (response.protocol) {\n      await this.transport.setProtocol(response.protocol);\n    }\n    this._authorization$.next(response.authorization);\n    // Server-authoritative binding classification: the authorization carries\n    // cnf.jkt when the session is client-bound (it rides in the persisted\n    // authorization_state, so this also restores the sticky flag after a\n    // page-reload resume — the in-memory flag does not survive the reload).\n    if (response.authorization.cnf?.jkt) {\n      this._wasClientBound = true;\n    }\n    this._iceServers$.next(response.ice_servers ?? []);\n    this.transport.setAuthenticated();\n    this._authState$.next({ kind: 'authenticated' });\n\n    logger.debug('[Session] Authentication completed successfully');\n  }\n\n  async disconnect(): Promise<void> {\n    this.transport.disconnect();\n    this._authState$.next({ kind: 'unauthenticated' });\n    // disconnect() ends the session — clear the resume state AND attach\n    // records together (they are a coupled unit). A new session created\n    // later via connect() with the same credentials starts clean.\n    await this.teardownSessionState();\n  }\n\n  private async createInboundCall(invite: VertoInviteParams & { node_id: string }): Promise<void> {\n    const callSession = await this.createCall({\n      nodeId: invite.node_id,\n      callId: invite.callID,\n      initOffer: invite.sdp,\n      toName: invite.callee_id_name,\n      to: invite.callee_id_number,\n      fromName: invite.caller_id_name,\n      from: invite.caller_id_number,\n      displayDirection: invite.display_direction,\n      userVariables: invite.userVariables\n    });\n\n    // Publish the inbound call immediately so consumers can subscribe and\n    // answer/reject. The call's status$ BehaviorSubject seeds to 'ringing',\n    // so awaiting firstValueFrom(status$) would resolve synchronously and\n    // add nothing.\n    this._calls$.next({\n      [`${callSession.id}`]: callSession,\n      ...this._calls$.value\n    });\n  }\n\n  /**\n   * Handle a server-pushed verto.attach event at the session level.\n   *\n   * On page reload the server detects the reconnected session and pushes\n   * verto.attach for any active calls. If a call object already exists\n   * (network blip, no reload), the per-call handler in VertoManager deals\n   * with it. This method only creates a new call object when no existing\n   * one matches the callID.\n   */\n  private async handleVertoAttach(attach: VertoAttachParams & { node_id: string }): Promise<void> {\n    const { callID } = attach;\n\n    // If a call object already exists, the per-call VertoManager handler\n    // will handle the attach event. Skip session-level creation.\n    const existingCalls = this._calls$.value;\n    if (callID in existingCalls) {\n      logger.debug(\n        `[Session] Verto attach for existing call ${callID}, deferring to per-call handler`\n      );\n      return;\n    }\n\n    // Look up stored attachment data for media options\n    const storedOptions = await this.attachManager.consumePendingAttachment(callID);\n\n    logger.debug(`[Session] Creating reattached call for callID: ${callID}`);\n\n    const callSession = await this.createCall({\n      nodeId: attach.node_id,\n      callId: callID,\n      toName: attach.callee_id_name,\n      to: attach.callee_id_number,\n      fromName: attach.caller_id_name,\n      from: attach.caller_id_number,\n      reattach: true,\n      ...storedOptions\n    });\n\n    // Publish the reattached call immediately. status$ seeds to its initial\n    // value synchronously, so awaiting firstValueFrom(status$) would resolve\n    // on the same microtask and add nothing.\n    this._calls$.next({\n      [`${callSession.id}`]: callSession,\n      ...this._calls$.value\n    });\n  }\n\n  public async createOutboundCall(\n    destination: string | Address,\n    options: CallOptions = {}\n  ): Promise<Call> {\n    const destinationURI =\n      destination instanceof Address ? destination.defaultChannel : destination;\n    let callSession: WebRTCCall | undefined;\n    try {\n      callSession = await this.createCall({\n        to: destinationURI,\n        ...options\n      });\n\n      await awaitDialReady(callSession, DEFAULT_CALL_SIGNALING_TIMEOUT_MS);\n\n      this._calls$.next({\n        [`${callSession.id}`]: callSession,\n        ...this._calls$.value\n      });\n\n      return callSession;\n    } catch (error) {\n      logger.error('[Session] Error creating outbound call:', error);\n      callSession?.destroy();\n      const message =\n        error instanceof TimeoutError ? 'Call create timeout' : 'Call creation failed';\n      const callError = new CallCreateError(message, error, 'outbound');\n      this._errors$.next(callError);\n      throw callError;\n    }\n  }\n\n  private async createCall(options: CallOptions = {}): Promise<WebRTCCall> {\n    try {\n      const addressURI = getAddressSearchURI(options);\n\n      // For PSTN numbers (starting with +), skip the directory lookup\n      // and create the call directly with the phone number as destination.\n      let address: Address | undefined;\n      try {\n        if (!this._directory) {\n          throw new DependencyError('Directory not initialized');\n        }\n\n        const addressId = await this._directory.findAddressIdByURI(addressURI);\n        if (!addressId) {\n          throw new DependencyError(`Address name: ${addressURI} not found`);\n        }\n\n        address = this._directory.get(addressId);\n        if (!address) {\n          throw new DependencyError(`Address ID: ${addressId} not found`);\n        }\n      } catch {\n        logger.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);\n      }\n\n      const callSession = this.callFactory.createCall(address, {\n        ...options\n      });\n\n      // Use subscribeTo() so this subscription is tracked by Destroyable\n      // and cleaned up when the session is destroyed. Previously used raw\n      // .subscribe() which leaked on every call created.\n      this.subscribeTo(\n        callSession.status$.pipe(\n          filter((status) => status === 'destroyed'),\n          take(1)\n        ),\n        () => {\n          const { [`${callSession.id}`]: _, ...remainingCalls } = this._calls$.value;\n          this._calls$.next(remainingCalls);\n        }\n      );\n\n      return callSession;\n    } catch (error) {\n      logger.error('[Session] Error creating call session:', error);\n      const direction = options.initOffer ? 'inbound' : 'outbound';\n      throw new CallCreateError('Call create error', error, direction);\n    }\n  }\n\n  public destroy(): void {\n    for (const call of Object.values(this._calls$.value)) {\n      void call.hangup();\n    }\n    super.destroy();\n  }\n}\n\nexport class ClientSessionWrapper implements SessionState {\n  constructor(private clientSessionManager: ClientSessionManager) {}\n\n  public get authenticated$(): Observable<boolean> {\n    return this.clientSessionManager.authenticated$;\n  }\n\n  public get authenticated(): boolean {\n    return this.clientSessionManager.authenticated;\n  }\n\n  /**\n   * Whether the session is using a Client Bound SAT (DPoP). Sticky — set\n   * when the binding is established or restored from a resumed session's\n   * server authorization.\n   */\n  public get clientBound(): boolean {\n    return this.clientSessionManager.clientBound;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n  public get signalingEvent$() {\n    return this.clientSessionManager.signalingEvent$;\n  }\n\n  public get iceServers(): RTCIceServer[] | undefined {\n    return this.clientSessionManager.iceServers;\n  }\n\n  public get callControl(): 'routed' | 'in-dialog' {\n    return this.clientSessionManager.callControl;\n  }\n\n  public async execute<T extends JSONRPCResponse = JSONRPCResponse>(\n    request: JSONRPCRequest,\n    options?: PendingRPCOptions\n  ): Promise<T> {\n    return this.clientSessionManager.execute(request, options);\n  }\n\n  public get incomingCalls$(): Observable<Call[]> {\n    return this.clientSessionManager.incomingCalls$;\n  }\n\n  public get incomingCalls(): Call[] {\n    return this.clientSessionManager.incomingCalls;\n  }\n\n  public get calls$(): Observable<Call[]> {\n    return this.clientSessionManager.calls$;\n  }\n\n  public get calls(): Call[] {\n    return this.clientSessionManager.calls;\n  }\n}\n","export const isString = (obj: unknown): obj is string => typeof obj === 'string';\n","import { map, tap, type Observable } from 'rxjs';\n\nimport { EntityCollection, Fetcher } from '../behaviors/Collection';\nimport { POST_PARAMS } from '../controllers/HTTPRequestController';\nimport { ConversationError } from '../core/errors';\nimport { isConversationMessageMetadata } from '../core/RPCMessages/guards/events.guards';\nimport { filterAs } from '../operators/filterEventAs';\nimport { isString } from '../utils/isString';\nimport { getLogger } from '../utils/logger';\n\nimport type { ClientSessionManager } from './ClientSessionManager';\nimport type { HTTPRequestController } from '../controllers/HTTPRequestController';\nimport type {\n  GetConversationMessageResponse,\n  GetConversationResponse\n} from '../core/types/conversation.types';\nimport type { ConversationsProvider } from '../interfaces/Conversations';\n\nconst logger = getLogger();\n\nconst toAddressId = (groupId: string): string => {\n  const [, toAddressId] = groupId.split('_');\n  return toAddressId;\n};\n\nclass ConversationMessagesFetcher extends Fetcher<GetConversationMessageResponse> {\n  constructor(\n    public readonly groupId: string,\n    http: HTTPRequestController\n  ) {\n    super(`/api/fabric/conversations/${groupId}/messages`, 'page_size=100', http);\n  }\n}\n\nclass ConversationsFetcher extends Fetcher<GetConversationResponse> {\n  filter = (item: unknown): item is GetConversationResponse =>\n    !!(item as GetConversationResponse).from_fabric_address_id;\n  mapper = (item: unknown) => ({\n    ...(item as GetConversationResponse),\n    id: (item as GetConversationResponse).group_id,\n\n    address_id: toAddressId((item as GetConversationResponse).group_id)\n  });\n  constructor(http: HTTPRequestController) {\n    super(`/api/fabric/conversations`, 'page_size=100', http);\n  }\n}\n\nexport class ConversationMessageCollection extends EntityCollection<GetConversationMessageResponse> {\n  constructor(\n    groupId: string,\n    update$: Observable<Partial<GetConversationMessageResponse>>,\n    http: HTTPRequestController,\n    onError?: (error: Error) => void\n  ) {\n    super(new ConversationMessagesFetcher(groupId, http), update$, onError);\n  }\n}\n\nexport class ConversationCollection extends EntityCollection<GetConversationResponse> {\n  constructor(\n    update$: Observable<Partial<GetConversationResponse>>,\n    http: HTTPRequestController,\n    onError?: (error: Error) => void\n  ) {\n    super(new ConversationsFetcher(http), update$, onError);\n  }\n}\n\nexport class ConversationsManager implements ConversationsProvider {\n  private groupIds = new Map<string, string>();\n\n  constructor(\n    private clientSession: ClientSessionManager,\n    private http: HTTPRequestController,\n    private getUserAddressId: () => string,\n    private readonly onError?: (error: Error) => void\n  ) {}\n  private async join(addressId: string): Promise<string> {\n    const userFromAddressId = this.getUserAddressId();\n\n    try {\n      const response = await this.http.request({\n        ...POST_PARAMS,\n        url: `/api/fabric/conversations/join`,\n        body: JSON.stringify({\n          from_fabric_address_id: userFromAddressId,\n          fabric_address_ids: [addressId, userFromAddressId]\n        })\n      });\n\n      if (response.ok && !!response.body) {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n        const data = JSON.parse(response.body);\n        /* eslint-disable @typescript-eslint/no-unsafe-member-access */\n\n        if (isString(data.group_id)) {\n          this.groupIds.set(addressId, data.group_id as string);\n          return data.group_id as string;\n        }\n        /* eslint-enable @typescript-eslint/no-unsafe-member-access */\n      }\n      throw new ConversationError('Join Failed - Unexpected response');\n    } catch (error) {\n      logger.error('[ConversationsManager] Failed to join conversation:', error);\n      throw error;\n    }\n  }\n\n  public async getConversationMessageCollection(\n    addressId: string\n  ): Promise<ConversationMessageCollection> {\n    const groupId = this.groupIds.get(addressId) ?? (await this.join(addressId));\n\n    return Promise.resolve(\n      new ConversationMessageCollection(\n        groupId,\n        this.clientSession.signalingEvent$.pipe(\n          filterAs(isConversationMessageMetadata, 'params'),\n          tap((event) => logger.debug('[ConversationsManager ] Conversation Event:', event)),\n          // FIXME after Conversation API Fixes\n          map(\n            (params) =>\n              ({\n                ...params\n              }) as Partial<GetConversationMessageResponse>\n          )\n        ),\n        this.http,\n        this.onError\n      )\n    );\n  }\n\n  public async sendText(text: string, destinationAddressId: string): Promise<void> {\n    const groupId =\n      this.groupIds.get(destinationAddressId) ?? (await this.join(destinationAddressId));\n    const userFromAddressId = this.getUserAddressId();\n\n    try {\n      const response = await this.http.request({\n        ...POST_PARAMS,\n        url: '/api/fabric/messages',\n        body: JSON.stringify({\n          group_id: groupId,\n          from_fabric_address_id: userFromAddressId,\n          text\n        })\n      });\n      if (response.ok) {\n        return;\n      }\n      throw new ConversationError('Send Text Failed - Unexpected response');\n    } catch (error) {\n      logger.error('[ConversationsManager] Failed to send text message:', error);\n      throw error;\n    }\n  }\n}\n","import { filter, switchMap, timer } from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { POST_PARAMS } from '../controllers/HTTPRequestController';\nimport {\n  SAT_REFRESH_SCOPE,\n  DEVICE_TOKEN_ENDPOINT,\n  DEVICE_REFRESH_ENDPOINT,\n  DEVICE_TOKEN_DEFAULT_EXPIRE_IN,\n  DEVICE_TOKEN_REFRESH_BUFFER_MS,\n  DEVICE_TOKEN_REFRESH_MAX_RETRIES,\n  DEVICE_TOKEN_REFRESH_RETRY_BASE_MS\n} from '../core/constants';\nimport { DeviceTokenError, DPoPInitError, TokenRefreshError } from '../core/errors';\nimport { getLogger } from '../utils/logger';\n\nimport type { ClientSessionManager } from './ClientSessionManager';\nimport type { CryptoController } from '../controllers/CryptoController';\nimport type { HTTPRequestController } from '../controllers/HTTPRequestController';\nimport type { User } from '../core/entities/User';\nimport type { SDKCredential } from '../core/types/common.types';\nimport type { DeviceTokenResponse } from '../core/types/crypto.types';\n\nconst logger = getLogger();\n\n/** Callback to update the credential in the dependency container. */\nexport type CredentialUpdater = (credential: Partial<SDKCredential>) => void;\n\n/**\n * Reason `activate()` declined to take over refresh duties. Used as\n * diagnostic metadata only — callers must branch on the `activated`\n * boolean, never on the `reason` string.\n */\nexport type ActivationDeclineReason =\n  | 'no-scope'\n  | 'no-dpop-support'\n  | 'endpoint-failed'\n  | 'activation-timeout'\n  | 'already-active';\n\n/**\n * Result of `DeviceTokenManager.activate()`. When `activated` is `true`,\n * the manager owns refresh duties and the caller should disarm any\n * developer-provided refresh timer. When `activated` is `false`, the\n * caller must continue (or arm) the developer-provided refresh path.\n */\nexport type ActivationResult =\n  | { activated: true }\n  | { activated: false; reason: ActivationDeclineReason };\n\n/**\n * Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:\n * 1. `data.expires_at` — server-provided absolute timestamp\n * 2. `data.expires_in` — server-provided relative lifetime\n * 3. Fallback to `DEVICE_TOKEN_DEFAULT_EXPIRE_IN` with a warning\n */\nexport function resolveExpiresAt(data: DeviceTokenResponse): number {\n  if (data.expires_at) return data.expires_at;\n  if (data.expires_in) return Math.floor(Date.now() / 1000) + data.expires_in;\n\n  logger.warn('[DeviceToken] Could not determine token expiry, using default');\n  return Math.floor(Date.now() / 1000) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;\n}\n\n/**\n * Resolves the token TTL in seconds from a fresh response.\n * Called at token receive time so `expires_at - now` reflects the original lifetime.\n *\n * 1. `data.expires_in` — server-provided TTL directly\n * 2. `data.expires_at - now` — derive TTL from absolute timestamp\n * 3. Fallback to `DEVICE_TOKEN_DEFAULT_EXPIRE_IN`\n */\nexport function resolveExpireIn(data: DeviceTokenResponse): number {\n  if (data.expires_in) return data.expires_in;\n  if (data.expires_at) return Math.max(data.expires_at - Math.floor(Date.now() / 1000), 1);\n  return DEVICE_TOKEN_DEFAULT_EXPIRE_IN;\n}\n\n/**\n * Manages the Client Bound SAT lifecycle: activation, token exchange,\n * reauthentication, and automatic refresh scheduling.\n *\n * Extends {@link Destroyable} for automatic RxJS subscription and subject cleanup.\n * Uses a reactive pipeline (`BehaviorSubject` + `switchMap(timer())`) instead of\n * raw `setTimeout` for refresh scheduling.\n */\nexport class DeviceTokenManager extends Destroyable {\n  private _currentToken$ = this.createBehaviorSubject<DeviceTokenResponse | null>(null);\n  private _refreshInProgress = false;\n  private _paused = false;\n  private _effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;\n  private _session?: ClientSessionManager;\n  private _updateCredential?: CredentialUpdater;\n\n  constructor(\n    private readonly dpopManager: CryptoController,\n    /**\n     * Resolved per request, NOT captured. The container swaps its HTTP controller\n     * once the credential's `ch` claim reveals the real host; anything holding an\n     * instance from before that keeps the default host and talks to the wrong\n     * environment. Holding a reference here sent staging-minted tokens to\n     * production, where they 401.\n     */\n    private readonly http: () => HTTPRequestController,\n    private readonly errorHandler: (error: Error) => void,\n    private readonly getCredential: () => SDKCredential\n  ) {\n    super();\n\n    // Reactive refresh pipeline: whenever a new token is emitted, schedule\n    // the next refresh using switchMap (auto-cancels previous timer).\n    this.subscribeTo(\n      this._currentToken$.pipe(\n        filter(Boolean),\n        switchMap((tokenData) => {\n          const expiresAt = resolveExpiresAt(tokenData);\n          const refreshIn = Math.max(\n            expiresAt * 1000 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS,\n            1000\n          );\n          logger.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);\n          return timer(refreshIn);\n        })\n      ),\n      () => {\n        void this.executeRefresh();\n      }\n    );\n  }\n  /** Current token TTL in milliseconds. Used to extend cached credential expiry on refresh. */\n  public get effectiveExpireIn(): number {\n    return this._effectiveExpireIn;\n  }\n\n  /**\n   * Activates the Client Bound SAT flow when the user's token has\n   * `sat:refresh` scope.\n   *\n   * Returns an {@link ActivationResult} indicating whether the manager\n   * took ownership of refresh duties. The caller must use the `activated`\n   * boolean to decide whether to keep its own refresh path armed — when\n   * `activated` is `false`, the caller is responsible for refresh.\n   *\n   * Steps on success:\n   * 1. Check user's `sat_claims` for `sat:refresh` scope\n   * 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof\n   * 3. Reauthenticate the session with the Client Bound SAT + DPoP proof\n   * 4. Emit token to trigger the reactive refresh pipeline\n   */\n  public async activate(\n    user: User,\n    session: ClientSessionManager,\n    updateCredential: CredentialUpdater\n  ): Promise<ActivationResult> {\n    const { satClaims } = user;\n    if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {\n      logger.debug('[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation');\n      return { activated: false, reason: 'no-scope' };\n    }\n\n    this._session = session;\n    this._updateCredential = updateCredential;\n\n    try {\n      // Fast path: if we already have a cached token from a prior activation\n      // (e.g., reconnect after a transient drop), skip the `/devices/token`\n      // exchange and just reauthenticate the new session with the existing\n      // token. Saves a roundtrip and reduces token-endpoint load on every\n      // reconnect.\n      //\n      // Server contract: a Client Bound SAT minted for DPoP key K is\n      // accepted by `signalwire.reauthenticate` for any WebSocket session\n      // when paired with a fresh RPC proof signed by K. The token's `cnf`\n      // claim binds it to K, not to a particular session. If the server\n      // ever revokes a token mid-session, reauthenticate will reject\n      // loudly and the catch block below falls back to developer refresh.\n      const cached = this._currentToken$.value;\n      if (cached && this.isTokenFresh(cached)) {\n        logger.debug('[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token');\n        const rpcProof = await this.dpopManager.createRpcProof({\n          method: 'signalwire.reauthenticate'\n        });\n        await session.reauthenticate(cached.token, rpcProof, { clientBound: true });\n        updateCredential({ token: cached.token });\n        return { activated: true };\n      }\n\n      const tokenData = await this.obtainToken();\n\n      // Enrich token data with SAT claims expiry when the server response\n      // lacks expiry fields (common with JWE tokens that can't be decoded).\n      if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) {\n        tokenData.expires_at = satClaims.expires_at;\n      }\n\n      // Store the effective TTL so refresh requests use the same lifetime\n      this._effectiveExpireIn = resolveExpireIn(tokenData);\n\n      // Reauthenticate with the Client Bound SAT and mark session as client-bound\n      const rpcProof = await this.dpopManager.createRpcProof({\n        method: 'signalwire.reauthenticate'\n      });\n      await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });\n\n      // Update the stored credential, merging with existing fields\n      updateCredential({ token: tokenData.token });\n\n      logger.info('[DeviceToken] Client Bound SAT activated successfully');\n\n      // Emit token to trigger reactive refresh pipeline\n      this.emitCurrentToken(tokenData);\n      return { activated: true };\n    } catch (error) {\n      logger.error('[DeviceToken] Failed to activate Client Bound SAT:', error);\n      this.errorHandler(new DPoPInitError(error, 'Failed to activate Client Bound SAT'));\n\n      // Do NOT seed the reactive pipeline with the developer's unbound token —\n      // attempting refresh against DEVICE_REFRESH_ENDPOINT would 401-loop because\n      // the server never bound that SAT to the DPoP key. Decline ownership so\n      // the orchestrator keeps the developer-provided refresh path armed.\n      return { activated: false, reason: 'endpoint-failed' };\n    }\n  }\n\n  /**\n   * Emit a freshly received token to the reactive pipeline, stamping an\n   * absolute `expires_at` when the response carried only `expires_in`.\n   * Resolving the expiry at RECEIVE time (not at read time) is what lets\n   * {@link refreshNowIfDue} detect due-ness on resume: a bare `expires_in`\n   * re-resolved later would always compute a full TTL from \"now\" and never\n   * cross the refresh buffer.\n   */\n  private emitCurrentToken(token: DeviceTokenResponse): void {\n    const stamped: DeviceTokenResponse = token.expires_at\n      ? token\n      : { ...token, expires_at: resolveExpiresAt(token) };\n    this._currentToken$.next(stamped);\n  }\n\n  /**\n   * Returns true when the cached token has enough headroom before expiry to\n   * be safely reused on reactivation. The headroom matches the refresh\n   * buffer, so a token within the refresh window is treated as stale (the\n   * reactive pipeline is about to refresh it anyway).\n   */\n  private isTokenFresh(token: DeviceTokenResponse): boolean {\n    const expiresAt = resolveExpiresAt(token);\n    return expiresAt * 1000 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;\n  }\n\n  /**\n   * Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.\n   * Returns the full {@link DeviceTokenResponse} including expiry metadata.\n   */\n  public async obtainToken(): Promise<DeviceTokenResponse> {\n    const dpopProof = await this.dpopManager.createHttpProof({\n      method: 'POST',\n      uri: DEVICE_TOKEN_ENDPOINT\n    });\n\n    const response = await this.http().request({\n      url: DEVICE_TOKEN_ENDPOINT,\n      ...POST_PARAMS,\n      body: JSON.stringify({\n        dpop_token: dpopProof,\n        expire_in: DEVICE_TOKEN_DEFAULT_EXPIRE_IN\n      })\n    });\n\n    if (!response.ok || !response.body) {\n      throw new DeviceTokenError(\n        `Failed to obtain device token: ${response.status} ${response.statusText}`\n      );\n    }\n\n    const data = JSON.parse(response.body) as DeviceTokenResponse;\n    if (!data.token) {\n      throw new DeviceTokenError('Device token response missing token field');\n    }\n\n    return data;\n  }\n\n  /**\n   * Refreshes the Client Bound SAT via `/api/fabric/subscriber/devices/refresh`.\n   *\n   * Creates a fresh DPoP proof, calls the refresh endpoint, reauthenticates\n   * the WebSocket session, and returns the new token data (scheduling is\n   * handled by the reactive pipeline).\n   */\n  public async refreshToken(\n    session: ClientSessionManager,\n    currentToken: string,\n    updateCredential: CredentialUpdater\n  ): Promise<DeviceTokenResponse> {\n    logger.debug('[DeviceToken] Refreshing Client Bound SAT');\n\n    const dpopProof = await this.dpopManager.createHttpProof({\n      method: 'POST',\n      uri: DEVICE_REFRESH_ENDPOINT,\n      accessToken: currentToken\n    });\n\n    const response = await this.http().request({\n      url: DEVICE_REFRESH_ENDPOINT,\n      ...POST_PARAMS,\n      body: JSON.stringify({\n        dpop_token: dpopProof,\n        expire_in: this._effectiveExpireIn\n      })\n    });\n\n    if (!response.ok || !response.body) {\n      throw new TokenRefreshError(\n        `Failed to refresh device token: ${response.status} ${response.statusText}`\n      );\n    }\n\n    const data = JSON.parse(response.body) as DeviceTokenResponse;\n    if (!data.token) {\n      throw new TokenRefreshError('Device token refresh response missing token field');\n    }\n\n    // When the refresh response lacks expiry fields (common with JWE tokens),\n    // enrich it with the effective TTL from the previous token cycle.\n    if (!data.expires_at && !data.expires_in) {\n      data.expires_in = this._effectiveExpireIn;\n    }\n\n    // Update effective TTL from the refreshed token's actual expiry\n    this._effectiveExpireIn = resolveExpireIn(data);\n\n    // Reauthenticate with the refreshed Client Bound SAT\n    const rpcProof = await this.dpopManager.createRpcProof({\n      method: 'signalwire.reauthenticate'\n    });\n    await session.reauthenticate(data.token, rpcProof);\n\n    // Update the stored credential with the refreshed token (merge via callback)\n    updateCredential({ token: data.token });\n\n    logger.info('[DeviceToken] Client Bound SAT refreshed successfully');\n\n    return data;\n  }\n\n  /**\n   * Executes a refresh with retry and exponential backoff.\n   * On success, emits to `_currentToken$` to schedule the next refresh.\n   * On all retries exhausted, emits to `errorHandler`.\n   */\n  private async executeRefresh(): Promise<void> {\n    if (this._paused) {\n      logger.debug('[DeviceToken] Manager paused, skipping refresh');\n      return;\n    }\n\n    if (this._refreshInProgress) {\n      logger.debug('[DeviceToken] Refresh already in progress, skipping');\n      return;\n    }\n\n    const session = this._session;\n    const updateCredential = this._updateCredential;\n    if (!session || !updateCredential) {\n      logger.warn('[DeviceToken] Cannot refresh: session or updateCredential not set');\n      return;\n    }\n\n    // Skip refresh if the session is not authenticated (e.g., during disconnect).\n    // The refresh will be rescheduled when a new token is emitted after reconnect.\n    if (!session.authenticated) {\n      logger.debug('[DeviceToken] Session not authenticated, deferring refresh');\n      return;\n    }\n\n    this._refreshInProgress = true;\n\n    try {\n      const currentToken = this.getCredential().token;\n      if (!currentToken) {\n        throw new TokenRefreshError('No current token available for refresh');\n      }\n\n      const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);\n      this.emitCurrentToken(newTokenData);\n    } catch (error) {\n      logger.error('[DeviceToken] Automatic Client Bound SAT refresh failed:', error);\n      this.errorHandler(\n        error instanceof TokenRefreshError\n          ? error\n          : new TokenRefreshError('Automatic token refresh failed', error)\n      );\n    } finally {\n      this._refreshInProgress = false;\n    }\n  }\n\n  /**\n   * Retries `refreshToken()` up to `DEVICE_TOKEN_REFRESH_MAX_RETRIES` times\n   * with exponential backoff (1s, 2s, 4s).\n   */\n  private async retryRefresh(\n    session: ClientSessionManager,\n    currentToken: string,\n    updateCredential: CredentialUpdater\n  ): Promise<DeviceTokenResponse> {\n    let lastError: unknown;\n\n    for (let attempt = 0; attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES; attempt++) {\n      try {\n        return await this.refreshToken(session, currentToken, updateCredential);\n      } catch (error) {\n        lastError = error;\n        if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {\n          const delay = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);\n          logger.warn(\n            `[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay}ms`\n          );\n          await new Promise((resolve) => setTimeout(resolve, delay));\n        }\n      }\n    }\n\n    throw lastError instanceof Error\n      ? lastError\n      : new TokenRefreshError('All refresh retries exhausted', lastError);\n  }\n\n  /**\n   * Force an immediate refresh when the cached Client Bound SAT is already\n   * past its refresh window. Called on resume from suspension where\n   * background-tab throttling can delay the reactive timer past the buffer.\n   * A no-op when no token is cached or it still has headroom; the normal\n   * {@link executeRefresh} guards (paused / in-progress / unauthenticated)\n   * still apply.\n   */\n  public refreshNowIfDue(): void {\n    const token = this._currentToken$.value;\n    if (!token) {\n      return;\n    }\n    const dueWithinMs = resolveExpiresAt(token) * 1000 - Date.now();\n    if (dueWithinMs <= DEVICE_TOKEN_REFRESH_BUFFER_MS) {\n      logger.debug('[DeviceToken] Resume: cached SAT past refresh window; refreshing now');\n      void this.executeRefresh();\n    }\n  }\n\n  /**\n   * Stops the reactive refresh pipeline from firing. Use when the underlying\n   * session is being torn down (e.g., during {@link SignalWire.disconnect})\n   * so a scheduled refresh cannot fire against a destroyed session.\n   *\n   * The manager's state (cached token, effective TTL, subscriptions) is\n   * preserved — call {@link resume} to re-enable firing after reconnect.\n   */\n  public pause(): void {\n    this._paused = true;\n  }\n\n  /** Re-enables the reactive refresh pipeline after a previous {@link pause}. */\n  public resume(): void {\n    this._paused = false;\n  }\n\n  /** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */\n  public override destroy(): void {\n    super.destroy();\n  }\n}\n","import { DeviceTokenManager, type ActivationResult } from './DeviceTokenManager';\nimport { Destroyable } from '../behaviors/Destroyable';\nimport {\n  CREDENTIAL_ACTIVATE_TIMEOUT_MS,\n  CREDENTIAL_REFRESH_BUFFER_MS,\n  CREDENTIAL_REFRESH_MAX_DELAY_MS,\n  CREDENTIAL_REFRESH_MAX_RETRIES,\n  CREDENTIAL_REFRESH_RETRY_BASE_MS\n} from '../core/constants';\nimport { InvalidCredentialsError, TokenRefreshError } from '../core/errors';\nimport { getLogger } from '../utils/logger';\n\nimport type { ClientSessionManager } from './ClientSessionManager';\nimport type { CryptoController } from '../controllers/CryptoController';\nimport type { HTTPRequestController } from '../controllers/HTTPRequestController';\nimport type { User } from '../core/entities/User';\nimport type { Authorization } from '../core/RPCMessages';\nimport type { SDKCredential } from '../core/types/common.types';\nimport type { SDKWarning } from '../core/types/warnings.types';\nimport type { CredentialProvider } from '../dependencies/interfaces';\n\nconst logger = getLogger();\n\nconst defaultDeviceTokenManagerFactory: DeviceTokenManagerFactory = (\n  dpopManager,\n  http,\n  errorHandler,\n  getCredential\n) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);\n\n/**\n * Read/write port for the orchestrator's credential storage. The coordinator\n * never touches `SignalWire`'s internal `_deps.credential` directly — every\n * read/write goes through this interface so the coordinator stays decoupled\n * from the dependency container.\n */\nexport interface CredentialStore {\n  /** Read the current credential. */\n  read(): SDKCredential;\n  /** Replace the credential entirely (after a developer-provided refresh). */\n  write(credential: SDKCredential): void;\n  /** Merge a partial update (used when Client Bound SAT replaces only the token). */\n  merge(partial: Partial<SDKCredential>): void;\n  /** Persist the credential to long-term storage. */\n  persist(credential: SDKCredential): void;\n}\n\n/**\n * Outbound notification port for the orchestrator. The coordinator emits\n * errors, warnings, and lifecycle events through this interface instead of\n * holding references to `_errors$` / `_warnings$` subjects.\n */\nexport interface RefreshNotifier {\n  /** Emit a fatal error (e.g., refresh retries exhausted). */\n  onError(error: Error): void;\n  /** Emit a non-fatal warning (e.g., fallback to developer refresh). */\n  onWarning(warning: SDKWarning): void;\n  /**\n   * Hook fired when developer refresh retries exhaust. The orchestrator\n   * typically disconnects in response.\n   */\n  onRefreshExhausted(): void;\n  /**\n   * Fired after a successful developer refresh so the orchestrator can\n   * reauthenticate the **live** session with the new token — without this,\n   * the fresh token only takes effect on the next reconnect and the open\n   * socket keeps failing `-32003`. The orchestrator handles its own errors;\n   * a rejection here does not abort or retry the refresh.\n   */\n  onCredentialRefreshed(credential: SDKCredential): void | Promise<void>;\n}\n\n/**\n * Optional factory for constructing the internal {@link DeviceTokenManager}.\n * Tests use this to inject a stub without typed-cast reflection. Production\n * code leaves it undefined; the coordinator constructs the default manager.\n */\nexport type DeviceTokenManagerFactory = (\n  dpopManager: CryptoController,\n  http: () => HTTPRequestController,\n  errorHandler: (error: Error) => void,\n  getCredential: () => SDKCredential\n) => DeviceTokenManager;\n\n/** Callbacks the coordinator needs from the orchestrator. */\nexport interface RefreshCoordinatorDeps {\n  /** HTTP client used by the internal Client Bound SAT path. */\n  /** Provider, not an instance — see DeviceTokenManager's `http`. */\n  http: () => HTTPRequestController;\n  /** Outbound notification port. */\n  notifier: RefreshNotifier;\n  /** Credential read/write port. */\n  store: CredentialStore;\n  /** Test seam — override the internal manager. Production leaves undefined. */\n  deviceTokenManagerFactory?: DeviceTokenManagerFactory;\n}\n\n/**\n * Centralizes credential-refresh ownership across the two competing\n * mechanisms — developer-provided `CredentialProvider.refresh()` and the\n * Client Bound SAT path via {@link DeviceTokenManager}.\n *\n * Maintains the invariant: **at most one refresh mechanism is armed at a\n * time, and at least one is armed whenever the current credential has an\n * `expiry_at`**.\n *\n * Replaces the previous design where refresh state was distributed across\n * `SignalWire` (timer field, scheduler method, activation helper) and\n * `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in\n * one component eliminates the bug class that produced issue #19074.\n *\n * Race-safety:\n * - `_activating` flag prevents overlapping `activate()` calls from racing.\n * - `_activationGeneration` lets late resolutions detect they've been\n *   preempted by a newer activation (e.g., reconnect during in-flight\n *   `obtainToken`).\n */\nexport class CredentialRefreshCoordinator extends Destroyable {\n  private _developerTimerId?: ReturnType<typeof setTimeout>;\n  private _deviceTokenManager?: DeviceTokenManager;\n  private _activating = false;\n  private _activationGeneration = 0;\n  /** Provider bound to the armed developer timer; reused by {@link forceRefreshIfDue}. */\n  private _activeProvider?: CredentialProvider;\n  /** Guards against the scheduled tick and a forced refresh running concurrently. */\n  private _developerRefreshInProgress = false;\n  /**\n   * In-flight `provider.refresh()` shared across every re-mint path (scheduled\n   * tick, resume-forced refresh, and the orchestrator's -32003 recovery /\n   * reconnect re-mint). Concurrent callers await the same promise so a provider\n   * backed by one-time-use rotating refresh tokens is never hit twice in\n   * parallel — the exact failure this coordinator exists to prevent.\n   */\n  private _refreshInFlight?: Promise<SDKCredential>;\n\n  constructor(\n    dpopManager: CryptoController | undefined,\n    private readonly deps: RefreshCoordinatorDeps\n  ) {\n    super();\n    if (dpopManager?.initialized) {\n      const factory = deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory;\n      this._deviceTokenManager = factory(\n        dpopManager,\n        deps.http,\n        (error) => deps.notifier.onError(error),\n        () => deps.store.read()\n      );\n    }\n  }\n\n  /** True when the Client Bound SAT path is available (DPoP initialized). */\n  public get clientBoundSATAvailable(): boolean {\n    return this._deviceTokenManager !== undefined;\n  }\n\n  /** True when the developer-provided refresh timer is currently armed. */\n  public get developerRefreshArmed(): boolean {\n    return this._developerTimerId !== undefined;\n  }\n\n  /**\n   * Arms the developer-provided refresh timer to fire shortly before\n   * `expiresAt`. Replaces any previously scheduled developer refresh.\n   *\n   * Idempotent — multiple calls just reschedule. On retry exhaustion,\n   * invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.\n   */\n  public scheduleDeveloperRefresh(\n    provider: CredentialProvider,\n    expiresAt: number,\n    attempt = 0\n  ): void {\n    this._activeProvider = provider;\n    if (this._developerTimerId !== undefined) {\n      clearTimeout(this._developerTimerId);\n    }\n\n    const refreshInterval =\n      attempt === 0\n        ? Math.max(expiresAt - Date.now() - CREDENTIAL_REFRESH_BUFFER_MS, 1000)\n        : Math.min(\n            CREDENTIAL_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5), // equal-jitter\n            CREDENTIAL_REFRESH_MAX_DELAY_MS\n          );\n\n    this._developerTimerId = setTimeout(() => {\n      // Clear the handle before running so developerRefreshArmed reflects\n      // reality mid-tick and a concurrent forceRefreshIfDue() cannot re-fire.\n      this._developerTimerId = undefined;\n      void this.executeDeveloperRefresh(provider, expiresAt, attempt);\n    }, refreshInterval);\n  }\n\n  /**\n   * Runs the developer-provided refresh once: mints a new credential, stores\n   * and persists it, reauthenticates the live session (via the notifier), and\n   * reschedules against the new expiry. On failure retries with backoff up to\n   * {@link CREDENTIAL_REFRESH_MAX_RETRIES}, then signals exhaustion.\n   *\n   * Shared by the scheduled timer tick and {@link forceRefreshIfDue}. The\n   * `_developerRefreshInProgress` guard prevents the two from overlapping.\n   */\n  private async executeDeveloperRefresh(\n    provider: CredentialProvider,\n    expiresAt: number,\n    attempt: number\n  ): Promise<void> {\n    if (this._developerRefreshInProgress) {\n      logger.debug('[Coordinator] Developer refresh already in progress; skipping');\n      return;\n    }\n    this._developerRefreshInProgress = true;\n    try {\n      const newCredentials = await this.refreshCredential(provider);\n      this.deps.store.write(newCredentials);\n      this.deps.store.persist(newCredentials);\n      // Reauthenticate the live session with the new token. Best-effort:\n      // a rejection here must NOT be treated as a refresh failure (no retry),\n      // since the refresh itself succeeded and the schedule must continue.\n      try {\n        await this.deps.notifier.onCredentialRefreshed(newCredentials);\n      } catch (reauthError) {\n        logger.warn('[Coordinator] onCredentialRefreshed rejected (non-fatal):', reauthError);\n      }\n      logger.info('[Coordinator] Credentials refreshed successfully.');\n      if (newCredentials.expiry_at) {\n        this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);\n      }\n    } catch (error: unknown) {\n      const nextAttempt = attempt + 1;\n      logger.error(\n        `[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`,\n        error\n      );\n      this.deps.notifier.onError(\n        error instanceof Error ? error : new Error(String(error), { cause: error })\n      );\n      if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) {\n        this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);\n      } else {\n        logger.error('[Coordinator] Credential refresh exhausted all retries. Disconnecting.');\n        this.deps.notifier.onError(\n          new TokenRefreshError('Credential refresh failed after max retries')\n        );\n        this.deps.notifier.onRefreshExhausted();\n      }\n    } finally {\n      this._developerRefreshInProgress = false;\n    }\n  }\n\n  /**\n   * Force an immediate refresh when the current credential is already past its\n   * scheduled refresh window. Called on resume from suspension, where\n   * background-tab timer throttling can delay the armed refresh well past\n   * expiry, leaving the live session stale.\n   *\n   * Routes to whichever mechanism is armed: the developer timer if armed,\n   * otherwise the Client Bound SAT pipeline. A no-op when nothing is due.\n   */\n  public forceRefreshIfDue(): void {\n    if (this._developerTimerId !== undefined && this._activeProvider) {\n      const expiry = this.deps.store.read().expiry_at;\n      const due = expiry !== undefined && Date.now() >= expiry - CREDENTIAL_REFRESH_BUFFER_MS;\n      if (due) {\n        logger.debug('[Coordinator] Resume: credential past refresh window; forcing refresh');\n        clearTimeout(this._developerTimerId);\n        this._developerTimerId = undefined;\n        void this.executeDeveloperRefresh(this._activeProvider, expiry, 0);\n      }\n      return;\n    }\n    // Developer path not armed — the Client Bound SAT pipeline (if active)\n    // owns refresh; let it revalidate its own cached token.\n    this._deviceTokenManager?.refreshNowIfDue();\n  }\n\n  /**\n   * Sync the credential's expiry from the server-provided authorization (the\n   * `signalwire.connect` result). SATs are opaque JWE, so\n   * `fabric_subscriber.expires_at` is the authoritative expiry of the token\n   * the session actually connected with — the provider-reported `expiry_at`\n   * is only a hint (and may be wrong or absent). Corrects the stored\n   * credential and re-arms the developer refresh timer against the real\n   * deadline when the provider supports `refresh()`.\n   */\n  public syncExpiryFromAuthorization(\n    authorization: Authorization | undefined,\n    provider?: CredentialProvider\n  ): void {\n    // fabric_subscriber is required by the type but server-supplied at\n    // runtime — guard it so a variant omitting it cannot throw inside the\n    // authorization$ subscription (which would silently kill the sync).\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    const expiresAtSec = authorization?.fabric_subscriber?.expires_at;\n    if (!expiresAtSec) {\n      return;\n    }\n    const expiryAt = expiresAtSec * 1000;\n    const credential = this.deps.store.read();\n    if (credential.expiry_at === expiryAt) {\n      return;\n    }\n    logger.debug(\n      `[Coordinator] Correcting credential expiry from server authorization: ${new Date(expiryAt).toISOString()}`\n    );\n    const updated = { ...credential, expiry_at: expiryAt };\n    this.deps.store.write(updated);\n    this.deps.store.persist(updated);\n    if (provider?.refresh) {\n      this.scheduleDeveloperRefresh(provider, expiryAt);\n    }\n  }\n\n  /**\n   * Invoke `provider.refresh()` deduped against any concurrent developer\n   * refresh. Concurrent callers — the scheduled tick, a resume-forced refresh,\n   * and the orchestrator's -32003 recovery / reconnect re-mint — share one\n   * in-flight promise, so a provider backed by one-time-use rotating refresh\n   * tokens is never invoked twice in parallel.\n   *\n   * The caller owns applying the returned credential (store write, session\n   * reauth, rescheduling); this method only serializes the network call.\n   */\n  public async refreshCredential(provider: CredentialProvider): Promise<SDKCredential> {\n    if (this._refreshInFlight) {\n      return this._refreshInFlight;\n    }\n    if (!provider.refresh) {\n      throw new InvalidCredentialsError('Credential provider does not support refresh');\n    }\n    const run = provider.refresh();\n    this._refreshInFlight = run;\n    const clear = (): void => {\n      if (this._refreshInFlight === run) {\n        this._refreshInFlight = undefined;\n      }\n    };\n    // Clear the handle on settle without swallowing the result the caller awaits.\n    run.then(clear, clear);\n    return run;\n  }\n\n  /**\n   * Cancels any scheduled developer-provided refresh. Idempotent.\n   *\n   * @internal Used by the coordinator's own activation flow. External\n   * callers should use {@link suspend} for disconnect-time quiescence —\n   * `suspend()` also pauses the internal Client Bound SAT pipeline.\n   */\n  public cancelDeveloperRefresh(): void {\n    if (this._developerTimerId !== undefined) {\n      clearTimeout(this._developerTimerId);\n      this._developerTimerId = undefined;\n    }\n  }\n\n  /**\n   * Suspends both refresh paths — cancels the developer timer and pauses\n   * the internal reactive pipeline. Use when the underlying session is\n   * being torn down (e.g., {@link SignalWire.disconnect}). The next\n   * {@link activate} call re-enables the internal pipeline.\n   *\n   * The internal manager's cached token survives — see\n   * {@link DeviceTokenManager.pause} — so a subsequent reconnect can\n   * skip the `/devices/token` exchange entirely.\n   */\n  public suspend(): void {\n    this.cancelDeveloperRefresh();\n    this._deviceTokenManager?.pause();\n  }\n\n  /**\n   * Asks the Client Bound SAT path to take over refresh. If it accepts,\n   * the developer-provided timer (if any) is cancelled. If it declines, the\n   * developer timer remains armed and a `credential_refresh_fallback`\n   * warning is emitted.\n   *\n   * **Idempotent** — re-entrant calls during an in-flight activation are\n   * dropped. Use `_activationGeneration` to detect stale resolutions\n   * (e.g., a reconnect-triggered activate() that races with an earlier one).\n   */\n  public async activate(user: User, session: ClientSessionManager): Promise<void> {\n    if (this._activating) {\n      logger.debug('[Coordinator] activate() in flight; ignoring re-entrant call');\n      return;\n    }\n    if (!this._deviceTokenManager) {\n      // No DPoP — developer refresh (if armed by validateCredentials) is the\n      // only available path.\n      return;\n    }\n    // Re-enable the reactive pipeline in case a previous `suspend()` paused it.\n    this._deviceTokenManager.resume();\n    const generation = ++this._activationGeneration;\n    this._activating = true;\n    try {\n      const result = await this.withActivationTimeout(\n        this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred))\n      );\n      // Race-safety: if a newer activate() has bumped the generation while we\n      // were awaiting, the result is stale — let the newer activation own\n      // the timer state.\n      if (generation !== this._activationGeneration) {\n        logger.debug('[Coordinator] activate() result discarded (preempted by newer activation)');\n        return;\n      }\n      if (result.activated) {\n        this.cancelDeveloperRefresh();\n        logger.debug('[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh');\n        return;\n      }\n      logger.warn(\n        `[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`\n      );\n      this.deps.notifier.onWarning({\n        code: 'credential_refresh_fallback',\n        source: 'CredentialProvider',\n        reason: result.reason,\n        message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`\n      });\n    } finally {\n      this._activating = false;\n    }\n  }\n\n  public override destroy(): void {\n    this.cancelDeveloperRefresh();\n    this._deviceTokenManager?.destroy();\n    this._deviceTokenManager = undefined;\n    super.destroy();\n  }\n\n  /**\n   * Races the manager's `activate()` against a hard timeout. A wedged HTTP\n   * layer (e.g., proxy issues) could otherwise hang the activation\n   * indefinitely, leaving the session with no refresh mechanism while the\n   * `_activating` guard blocks subsequent retries.\n   *\n   * On timeout, the manager is paused immediately. The inner `activate()`\n   * may still complete in the background and emit to `_currentToken$`,\n   * which would normally arm the reactive refresh pipeline; pausing\n   * prevents that pipeline from firing while the developer refresh path\n   * is the active mechanism — preserving the \"at most one mechanism\n   * armed\" invariant. The next `activate()` call resumes the manager.\n   */\n  private async withActivationTimeout(inner: Promise<ActivationResult>): Promise<ActivationResult> {\n    return new Promise<ActivationResult>((resolve) => {\n      const timer = setTimeout(() => {\n        // Pause the manager so any late-completing inner mutations cannot\n        // arm a competing refresh pipeline.\n        this._deviceTokenManager?.pause();\n        resolve({ activated: false, reason: 'activation-timeout' });\n      }, CREDENTIAL_ACTIVATE_TIMEOUT_MS);\n      inner.then(\n        (result) => {\n          clearTimeout(timer);\n          resolve(result);\n        },\n        (error) => {\n          clearTimeout(timer);\n          // Manager exceptions surface to errorHandler internally; treat the\n          // timeout race winner as a normal decline so the caller falls back\n          // to developer refresh.\n          this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));\n          resolve({ activated: false, reason: 'endpoint-failed' });\n        }\n      );\n    });\n  }\n}\n","import { Destroyable } from '../behaviors/Destroyable';\nimport { getLogger } from '../utils/logger';\n\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\n/** Default maximum number of diagnostic events to retain in the ring buffer. */\nconst DEFAULT_MAX_EVENTS = 1000;\n\n/**\n * Categories for diagnostic events, covering the main areas of SDK operation.\n */\nexport type DiagnosticCategory = 'connection' | 'call' | 'device' | 'recovery' | 'error';\n\n/**\n * A single diagnostic event recorded during a session.\n */\nexport interface DiagnosticEvent {\n  readonly timestamp: number;\n  readonly category: DiagnosticCategory;\n  readonly event: string;\n  readonly details?: Record<string, unknown>;\n}\n\n/**\n * Summary of a single call for diagnostic export.\n */\nexport interface CallDiagnosticSummary {\n  readonly callId: string;\n  readonly direction: 'inbound' | 'outbound';\n  readonly destination?: string;\n  readonly startTime: number;\n  readonly endTime?: number;\n  readonly duration: number;\n  readonly finalStatus: string;\n  readonly avgQualityScore?: number;\n  readonly minQualityScore?: number;\n  readonly recoveryAttempts: number;\n  readonly iceCandidateTypes: string[];\n}\n\n/**\n * Structured diagnostic bundle exported for support/debugging.\n */\nexport interface SessionDiagnostics {\n  readonly sdkVersion: string;\n  readonly userAgent: string;\n  readonly exportedAt: number;\n  readonly events: readonly DiagnosticEvent[];\n  readonly calls: readonly CallDiagnosticSummary[];\n  readonly deviceChanges: readonly DiagnosticEvent[];\n}\n\n/**\n * Options for constructing a DiagnosticsCollector.\n */\nexport interface DiagnosticsCollectorOptions {\n  /** SDK version string to include in exports. */\n  readonly sdkVersion: string;\n  /** Maximum number of events to retain in the ring buffer. Defaults to 1000. */\n  readonly maxEvents?: number;\n  /** Optional callback to retrieve the current device list. */\n  readonly getDevices?: () => MediaDeviceInfo[];\n}\n\n/**\n * Returns the user agent string, safely handling non-browser environments.\n */\nfunction getUserAgent(): string {\n  try {\n    if (typeof navigator !== 'undefined' && navigator.userAgent) {\n      return navigator.userAgent;\n    }\n  } catch {\n    // Non-browser environment\n  }\n  return 'unknown';\n}\n\n/**\n * DiagnosticsCollector maintains a ring buffer of diagnostic events\n * for structured export. It records connection, call, device, recovery,\n * and error events during a session, and can serialize them into a\n * plain object for support tickets or debugging.\n *\n * Extends Destroyable for lifecycle management.\n */\nexport class DiagnosticsCollector extends Destroyable {\n  private _events: DiagnosticEvent[] = [];\n  private _calls: CallDiagnosticSummary[] = [];\n  private _deviceChanges: DiagnosticEvent[] = [];\n  private readonly _sdkVersion: string;\n  private readonly _maxEvents: number;\n\n  private readonly _eventRecorded$ = this.createSubject<DiagnosticEvent>();\n\n  constructor(options: DiagnosticsCollectorOptions) {\n    super();\n    this._sdkVersion = options.sdkVersion;\n    this._maxEvents = options.maxEvents ?? DEFAULT_MAX_EVENTS;\n\n    logger.debug('DiagnosticsCollector initialized', {\n      sdkVersion: this._sdkVersion,\n      maxEvents: this._maxEvents\n    });\n  }\n\n  /**\n   * Observable that emits each time a diagnostic event is recorded.\n   */\n  public get eventRecorded$(): Observable<DiagnosticEvent> {\n    return this._eventRecorded$.asObservable();\n  }\n\n  /**\n   * Record a diagnostic event into the ring buffer.\n   *\n   * @param category - The event category (connection, call, device, recovery, error).\n   * @param event - A short description of the event.\n   * @param details - Optional additional details as a key-value map.\n   */\n  public record(\n    category: DiagnosticCategory,\n    event: string,\n    details?: Record<string, unknown>\n  ): void {\n    const entry: DiagnosticEvent = {\n      timestamp: Date.now(),\n      category,\n      event,\n      ...(details !== undefined ? { details } : {})\n    };\n\n    this._events = this._appendToBuffer(this._events, entry);\n    this._eventRecorded$.next(entry);\n  }\n\n  /**\n   * Shorthand to record a device change event.\n   *\n   * @param event - Description of the device change.\n   * @param details - Optional additional details.\n   */\n  public recordDeviceChange(event: string, details?: Record<string, unknown>): void {\n    const entry: DiagnosticEvent = {\n      timestamp: Date.now(),\n      category: 'device',\n      event,\n      ...(details !== undefined ? { details } : {})\n    };\n\n    this._deviceChanges = this._appendToBuffer(this._deviceChanges, entry);\n    this._events = this._appendToBuffer(this._events, entry);\n    this._eventRecorded$.next(entry);\n  }\n\n  /**\n   * Record a call summary for diagnostic export.\n   *\n   * @param summary - The call diagnostic summary to add.\n   */\n  public recordCallSummary(summary: CallDiagnosticSummary): void {\n    this._calls = this._appendCallToBuffer(this._calls, summary);\n\n    this.record('call', 'call_summary', {\n      callId: summary.callId,\n      direction: summary.direction,\n      duration: summary.duration,\n      finalStatus: summary.finalStatus\n    });\n  }\n\n  /**\n   * Export the current diagnostic buffer as a structured plain object.\n   * This can be JSON.stringify'd and attached to support tickets.\n   */\n  public export(): SessionDiagnostics {\n    return {\n      sdkVersion: this._sdkVersion,\n      userAgent: getUserAgent(),\n      exportedAt: Date.now(),\n      events: [...this._events],\n      calls: [...this._calls],\n      deviceChanges: [...this._deviceChanges]\n    };\n  }\n\n  /**\n   * Clear all diagnostic buffers, resetting to empty state.\n   */\n  public clear(): void {\n    this._events = [];\n    this._calls = [];\n    this._deviceChanges = [];\n    logger.debug('DiagnosticsCollector buffers cleared');\n  }\n\n  public override destroy(): void {\n    logger.debug('DiagnosticsCollector destroyed');\n    super.destroy();\n  }\n  /**\n   * Append an entry to a buffer array, enforcing the max size via ring buffer semantics.\n   * Returns a new array (immutable pattern).\n   */\n  private _appendToBuffer(buffer: DiagnosticEvent[], entry: DiagnosticEvent): DiagnosticEvent[] {\n    const updated = [...buffer, entry];\n    if (updated.length > this._maxEvents) {\n      return updated.slice(updated.length - this._maxEvents);\n    }\n    return updated;\n  }\n\n  /**\n   * Append a call summary to the calls buffer, enforcing max size.\n   * Returns a new array (immutable pattern).\n   */\n  private _appendCallToBuffer(\n    buffer: CallDiagnosticSummary[],\n    entry: CallDiagnosticSummary\n  ): CallDiagnosticSummary[] {\n    const updated = [...buffer, entry];\n    if (updated.length > this._maxEvents) {\n      return updated.slice(updated.length - this._maxEvents);\n    }\n    return updated;\n  }\n}\n","export const isEmptyArray = (a?: unknown[]): boolean => {\n  return (a?.length ?? 0) === 0;\n};\n","import { take } from 'rxjs';\n\nimport type { Observable } from 'rxjs';\n\nexport const warmup = (observable: Observable<unknown>): void => {\n  observable.pipe(take(1)).subscribe();\n};\n","import { firstValueFrom, map, type Observable } from 'rxjs';\n\nimport { EntityCollection, Fetcher } from '../behaviors/Collection';\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { GET_PARAMS } from '../controllers/HTTPRequestController';\nimport { Address } from '../core/entities/Address';\nimport { isConversationMessageUpdatedMetadata } from '../core/RPCMessages/guards/events.guards';\nimport { filterAs, filterNull } from '../operators';\nimport { isEmptyArray } from '../utils/arrays';\nimport { getLogger } from '../utils/logger';\nimport { warmup } from '../utils/warmup';\n\nimport type { ClientSessionManager } from './ClientSessionManager';\nimport type { PaginatedResponse } from '../behaviors/types/collection.types';\nimport type { HTTPRequestController } from '../controllers/HTTPRequestController';\nimport type { Directory } from '../core/entities/Directory';\nimport type { GetAddressResponse } from '../core/types/address.types';\nimport type { ConversationsProvider } from '../interfaces/Conversations';\n\nconst logger = getLogger();\n\nclass AddressFetcher extends Fetcher<GetAddressResponse> {\n  constructor(http: HTTPRequestController) {\n    super('/api/fabric/addresses', 'sort_by=name&sort_order=asc', http);\n  }\n\n  async name(name: unknown): Promise<GetAddressResponse | undefined> {\n    const response = await this.http.request({\n      ...GET_PARAMS,\n      url: `${this.endpoint}?name=${encodeURIComponent(name as string)}`\n    });\n    if (response.ok && !!response.body) {\n      const result = JSON.parse(response.body) as PaginatedResponse<GetAddressResponse>;\n      if (!isEmptyArray(result.data)) {\n        return result.data[0];\n      }\n    }\n    logger.error('Failed to fetch addresses');\n  }\n}\n\n/** Collection of address states with reactive updates and pagination. */\nexport class AddressStateCollection extends EntityCollection<GetAddressResponse> {\n  constructor(\n    update$: Observable<Partial<GetAddressResponse>>,\n    http: HTTPRequestController,\n    onError?: (error: Error) => void\n  ) {\n    super(new AddressFetcher(http), update$, onError);\n  }\n}\n\n/**\n * Manages the directory of {@link Address} entries with paginated loading.\n *\n * Implements the {@link Directory} interface and provides reactive access to\n * addresses via observables, along with pagination and lookup methods.\n */\nexport class DirectoryManager extends Destroyable implements Directory {\n  private addNewAddress = (id: string): void => {\n    const address = new Address(id, this.conversationManager, this);\n    const observable = this._statesCollection.get$(id)?.pipe(\n      filterNull(),\n      map((data) => {\n        address.upnext(data);\n        return address;\n      })\n    );\n    if (observable) {\n      warmup(observable);\n      this._observableRegistry.set(id, observable);\n    }\n    this._addressesInstances.set(id, address);\n  };\n  private _addresses$ = this.createBehaviorSubject<Address[]>([]);\n  private _statesCollection: AddressStateCollection;\n  private _addressesInstances = new Map<string, Address>();\n  private _observableRegistry = new Map<string, Observable<Address>>();\n\n  constructor(\n    private http: HTTPRequestController,\n    clientSession: ClientSessionManager,\n    private conversationManager: ConversationsProvider,\n    private readonly onError?: (error: Error) => void\n  ) {\n    super();\n    this._statesCollection = new AddressStateCollection(\n      clientSession.signalingEvent$.pipe(\n        filterAs(isConversationMessageUpdatedMetadata, 'params'),\n        // FIXME after Conversation API Fixes\n        map((_) => ({}) as Partial<GetAddressResponse>)\n      ),\n      this.http,\n      this.onError\n    );\n    this.initSubscriptions();\n  }\n\n  /** Whether addresses are currently being loaded from the server. */\n  public get loading(): boolean {\n    return this._statesCollection.loading;\n  }\n\n  private initSubscriptions(): void {\n    this.subscribeTo(this._statesCollection.updated$, () => {\n      const existing = Array.from(this._addressesInstances.values().map((address) => address.id));\n      const newStates = this._statesCollection.values.filter(\n        (state) => !existing.includes(state.id)\n      );\n      if (!isEmptyArray(newStates)) {\n        newStates.forEach((state) => this.addNewAddress(state.id));\n        this._addresses$.next(Array.from(this._addressesInstances.values()));\n      }\n    });\n  }\n\n  /** Observable stream of all addresses in the directory. */\n  public get addresses$(): Observable<Address[]> {\n    return this._addresses$.asObservable();\n  }\n\n  /** Current snapshot of all loaded addresses. */\n  public get addresses(): Address[] {\n    return this._addresses$.value;\n  }\n\n  /** Observable indicating whether more addresses can be loaded. */\n  public get hasMore$(): Observable<boolean> {\n    return this._statesCollection.hasMore$;\n  }\n\n  /** Observable of the current loading state. */\n  public get loading$(): Observable<boolean> {\n    return this._statesCollection.loading$;\n  }\n\n  /**\n   * Loads the next page of addresses from the server.\n   *\n   * No-op if {@link hasMore$} is `false`. Loading state is observable via {@link loading$}.\n   * New addresses are appended to {@link addresses$} when the page loads.\n   */\n  public loadMore(): void {\n    if (this._statesCollection.hasMore) {\n      this._statesCollection.loadMore();\n    }\n  }\n\n  /**\n   * Returns a reactive observable for a specific address by ID.\n   * @param id - The address ID to observe.\n   * @returns An observable of the address, or `undefined` if not found.\n   */\n  public get$(id: string): Observable<Address> | undefined {\n    if (!this._observableRegistry.has(id)) {\n      this.addNewAddress(id);\n    }\n    return this._observableRegistry.get(id);\n  }\n\n  /**\n   * Returns an address by ID from the local cache.\n   * @param addressId - The address ID to look up.\n   * @returns The address, or `undefined` if not found.\n   */\n  public get(addressId: string): Address | undefined {\n    return this._addressesInstances.get(addressId);\n  }\n\n  /**\n   * Finds an address ID by its resource name (URI).\n   *\n   * Searches locally loaded addresses first, then queries the server if not found.\n   * The resource name is the {@link Address.name} identifier, distinct from display name.\n   *\n   * @param name - The resource name to search for (exact match, e.g. `'/public/conference'`).\n   * @returns The address ID, or `undefined` if no match is found locally or on server.\n   */\n  public async findAddressIdByURI(name: string): Promise<string | undefined> {\n    let addressId = this._addressesInstances.values().find((addr) => addr.name === name)?.id;\n    if (!addressId) {\n      const found$ = await this._statesCollection.find$('name', name);\n      if (found$) {\n        const state = await firstValueFrom(found$);\n        this.addNewAddress(state.id);\n        addressId = state.id;\n      }\n    }\n    return addressId;\n  }\n}\n","import { Destroyable } from '../behaviors/Destroyable';\nimport { UnexpectedError, WebSocketConnectionError, WebSocketTimeoutError } from '../core/errors';\nimport { getLogger } from '../utils/logger';\n\nimport type {\n  NodeSocketAdapter,\n  NodeSocketClient,\n  WebSocketAdapter,\n  WebSocketClient\n} from '../core/types/common.types';\nimport type { Observable } from 'rxjs';\n\nconst logger = getLogger();\n\nexport type WebSocketConnectionStatus =\n  | 'disconnected'\n  | 'disconnecting'\n  | 'reconnecting'\n  | 'connected'\n  | 'connecting';\n\nexport interface WebSocketControllerOptions {\n  reconnectDelayMin?: number;\n  reconnectDelayMax?: number;\n  connectionTimeout?: number;\n}\n\nexport class WebSocketController extends Destroyable {\n  // Default configuration values\n\n  private static readonly DEFAULT_RECONNECT_DELAY_MIN_MS = 1_000;\n\n  private static readonly DEFAULT_RECONNECT_DELAY_MAX_MS = 30_000;\n\n  private static readonly DEFAULT_CONNECTION_TIMEOUT_MS = 10_000;\n\n  // Private state\n  private socket?: WebSocketClient | NodeSocketClient;\n  private messageQueue: (string | ArrayBuffer | Blob)[] = [];\n  private reconnectTimer?: ReturnType<typeof setTimeout>;\n  private connectionTimeoutTimer?: ReturnType<typeof setTimeout>;\n  private currentReconnectDelay: number;\n  private shouldReconnect = false;\n  // Configuration\n  private readonly reconnectDelayMin: number;\n  private readonly reconnectDelayMax: number;\n  private readonly connectionTimeout: number;\n  // Bound event handlers for proper addEventListener/removeEventListener pairing\n  private readonly boundHandleOpen = (): void => this.handleOpen();\n  private readonly boundHandleClose = (event: CloseEvent): void => this.handleClose(event);\n  private readonly boundHandleError = (): void => this.handleError();\n  private readonly boundHandleMessage = (event: MessageEvent): void => this.handleMessage(event);\n\n  // Observable streams\n  private _status$ = this.createBehaviorSubject<WebSocketConnectionStatus>('disconnected');\n\n  private _incomingMessages$ = this.createSubject<MessageEvent>();\n\n  private _errors$ = this.createReplaySubject<Error>(1);\n\n  constructor(\n    private WebSocketConstructor: WebSocketAdapter | NodeSocketAdapter,\n    private endpoint: string,\n    private outgoingMessages$: Observable<string | ArrayBuffer | Blob>,\n    options: WebSocketControllerOptions = {}\n  ) {\n    super();\n    this.reconnectDelayMin =\n      options.reconnectDelayMin ?? WebSocketController.DEFAULT_RECONNECT_DELAY_MIN_MS;\n    this.reconnectDelayMax =\n      options.reconnectDelayMax ?? WebSocketController.DEFAULT_RECONNECT_DELAY_MAX_MS;\n    this.connectionTimeout =\n      options.connectionTimeout ?? WebSocketController.DEFAULT_CONNECTION_TIMEOUT_MS;\n    this.currentReconnectDelay = this.reconnectDelayMin;\n\n    // Subscribe to send$ to handle message sending\n    this.subscriptions.push(\n      this.outgoingMessages$.subscribe((data) => {\n        this.send(data);\n      })\n    );\n  }\n\n  public get status$(): Observable<WebSocketConnectionStatus> {\n    return this._status$.asObservable();\n  }\n  public get incomingMessages$(): Observable<MessageEvent> {\n    return this._incomingMessages$.asObservable();\n  }\n  public get errors$(): Observable<Error> {\n    return this._errors$.asObservable();\n  }\n  public connect(): void {\n    if (this._status$.value === 'connecting' || this._status$.value === 'connected') {\n      return;\n    }\n\n    this.shouldReconnect = true;\n    this._status$.next('connecting');\n    this.createWebSocket();\n  }\n\n  public disconnect(): void {\n    this.shouldReconnect = false;\n    this.clearReconnectTimer();\n    this.clearConnectionTimeout();\n\n    const currentStatus = this._status$.value;\n\n    if (\n      currentStatus === 'connected' ||\n      currentStatus === 'connecting' ||\n      currentStatus === 'reconnecting'\n    ) {\n      if (this.socket) {\n        this._status$.next('disconnecting');\n        this.socket.close();\n      } else {\n        this._status$.next('disconnected');\n      }\n    } else {\n      this._status$.next('disconnected');\n    }\n  }\n\n  reconnect(): void {\n    // Re-arm shouldReconnect so callers can force a reconnect after a prior\n    // disconnect() (which sets shouldReconnect=false). Without this, a call\n    // to reconnect() after disconnect() would silently no-op, breaking\n    // auth-recovery paths that disconnect and then reconnect fresh.\n    this.shouldReconnect = true;\n    this._status$.next('reconnecting');\n    this.scheduleReconnection();\n  }\n\n  public send(data: string | ArrayBuffer | Blob): void {\n    if (\n      this._status$.value === 'connected' &&\n      this.socket?.readyState === 1 // WebSocket.OPEN\n    ) {\n      logger.wsTraffic({ type: 'send', raw: data as string });\n      this.socket.send(data);\n    } else {\n      this.messageQueue.push(data);\n    }\n  }\n\n  private createWebSocket(): void {\n    try {\n      this.closeExistingSocket();\n      this.socket = new this.WebSocketConstructor(this.endpoint);\n      this.setupWebSocketListeners();\n      this.startConnectionTimeout();\n    } catch (error) {\n      const err =\n        error instanceof Error ? error : new UnexpectedError('Failed to create WebSocket');\n      this._errors$.next(err);\n      this.handleConnectionError();\n    }\n  }\n\n  /**\n   * Closes the existing socket and removes its event listeners to prevent\n   * phantom 'open'/'close' events from firing on the orphaned socket.\n   */\n  private closeExistingSocket(): void {\n    if (!this.socket) return;\n\n    const oldSocket = this.socket;\n    this.socket = undefined;\n\n    this.removeWebSocketListeners(oldSocket);\n\n    try {\n      oldSocket.close();\n    } catch {\n      // Ignore errors closing already-closed sockets\n    }\n  }\n\n  private setupWebSocketListeners(): void {\n    if (!this.socket) return;\n\n    this.socket.addEventListener('open', this.boundHandleOpen);\n    // @ts-expect-error -- CloseEvent type mismatch between browser/node adapters\n    this.socket.addEventListener('close', this.boundHandleClose);\n    this.socket.addEventListener('error', this.boundHandleError);\n    // @ts-expect-error -- MessageEvent type mismatch between browser/node adapters\n    this.socket.addEventListener('message', this.boundHandleMessage);\n  }\n\n  private removeWebSocketListeners(socket: WebSocketClient | NodeSocketClient): void {\n    try {\n      socket.removeEventListener('open', this.boundHandleOpen);\n      // @ts-expect-error -- WebSocket listener type mismatch between browser/node adapters\n      socket.removeEventListener('close', this.boundHandleClose);\n      socket.removeEventListener('error', this.boundHandleError);\n      // @ts-expect-error -- WebSocket listener type mismatch between browser/node adapters\n      socket.removeEventListener('message', this.boundHandleMessage);\n    } catch {\n      // Some environments may not support removeEventListener on WebSocket\n    }\n  }\n\n  private handleOpen(): void {\n    this.clearConnectionTimeout();\n    this._status$.next('connected');\n    this.currentReconnectDelay = this.reconnectDelayMin;\n    this.flushMessageQueue();\n  }\n\n  private handleClose(_event: CloseEvent): void {\n    this.clearConnectionTimeout();\n    // A frame belongs to the socket it was written for. The server closes a\n    // new socket whose first frame is not signalwire.connect.\n    this.messageQueue = [];\n\n    if (this.shouldReconnect) {\n      this._status$.next('reconnecting');\n      this.scheduleReconnection();\n    } else {\n      this._status$.next('disconnected');\n    }\n  }\n\n  private handleError(): void {\n    const error = new WebSocketConnectionError('WebSocket connection error');\n    this._errors$.next(error);\n    this.handleConnectionError();\n  }\n\n  private handleMessage(event: MessageEvent): void {\n    logger.wsTraffic({ type: 'recv', raw: event.data as string });\n    this._incomingMessages$.next(event);\n  }\n\n  private handleConnectionError(): void {\n    this.reconnect();\n  }\n\n  private scheduleReconnection(): void {\n    this.clearReconnectTimer();\n\n    // Equal jitter: use half the ceiling as a guaranteed base, plus a random\n    // portion of the other half. This prevents thundering herd when many clients\n    // reconnect simultaneously (e.g., after a server restart) while still\n    // respecting the exponential backoff envelope.\n    // Range: [currentDelay * 0.5, currentDelay * 1.0]\n    // Reference: https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/\n    const jitteredDelay = this.currentReconnectDelay * (0.5 + Math.random() * 0.5);\n\n    this.reconnectTimer = setTimeout(() => {\n      if (this.shouldReconnect) {\n        this._status$.next('connecting');\n        this.createWebSocket();\n        this.increaseReconnectDelay();\n      }\n    }, jitteredDelay);\n  }\n\n  private increaseReconnectDelay(): void {\n    this.currentReconnectDelay = Math.min(this.currentReconnectDelay * 2, this.reconnectDelayMax);\n  }\n\n  private startConnectionTimeout(): void {\n    this.clearConnectionTimeout();\n\n    this.connectionTimeoutTimer = setTimeout(() => {\n      if (this._status$.value === 'connecting') {\n        const error = new WebSocketTimeoutError('WebSocket connection timeout');\n        this._errors$.next(error);\n\n        if (this.socket) {\n          this.socket.close();\n        }\n      }\n    }, this.connectionTimeout);\n  }\n\n  private clearConnectionTimeout(): void {\n    if (this.connectionTimeoutTimer) {\n      clearTimeout(this.connectionTimeoutTimer);\n      this.connectionTimeoutTimer = undefined;\n    }\n  }\n\n  private clearReconnectTimer(): void {\n    if (this.reconnectTimer) {\n      clearTimeout(this.reconnectTimer);\n      this.reconnectTimer = undefined;\n    }\n  }\n\n  private flushMessageQueue(): void {\n    while (this.messageQueue.length > 0 && this.socket?.readyState === 1) {\n      const message = this.messageQueue.shift();\n      if (message !== undefined) {\n        this.socket.send(message);\n      }\n    }\n  }\n}\n","// =============================================================================\n// CLIENT/SERVER METHOD TYPE GUARDS\n// =============================================================================\n// This file contains type guards for client requests, server responses,\n// and method params/results.\n\nimport { isJSONRPCRequest } from './base.guards';\n\nimport type { SignalwirePingRequest } from '../types/events';\n\nexport function isSignalwirePingRequest(value: unknown): value is SignalwirePingRequest {\n  return isJSONRPCRequest(value) && value.method === 'signalwire.ping';\n}\n\nexport function isSignalwireConnectRequest(value: unknown): boolean {\n  return isJSONRPCRequest(value) && value.method === 'signalwire.connect';\n}\n","import {\n  EMPTY,\n  catchError,\n  defer,\n  filter,\n  from,\n  map,\n  share,\n  shareReplay,\n  take,\n  takeUntil,\n  tap,\n  timeout\n} from 'rxjs';\n\nimport { PreferencesContainer } from '../containers/PreferencesContainer';\nimport { WebSocketController } from '../controllers/WebSocketController';\nimport { SERVER_PING_PROBE_TIMEOUT_MS, SERVER_PING_TIMEOUT_MS } from '../core/constants';\nimport { MessageParseError, TransportConnectionError } from '../core/errors';\nimport { RPCEventAckResponse, RPCPing, RPCPingResponse } from '../core/RPCMessages';\nimport { isJSONRPCRequest, isJSONRPCResponse } from '../core/RPCMessages/guards/base.guards';\nimport { isSignalwireRequest } from '../core/RPCMessages/guards/events.guards';\nimport {\n  isSignalwireConnectRequest,\n  isSignalwirePingRequest\n} from '../core/RPCMessages/guards/methods.guards';\nimport { Destroyable, PendingRPC } from '../core/utils';\nimport { getLogger } from '../utils/logger';\n\nimport type { StorageManager } from './StorageManager';\nimport type { JSONRPCRequest, JSONRPCResponse } from '../core/RPCMessages/types/base';\nimport type {\n  JSONSerializable,\n  WebSocketAdapter,\n  NodeSocketAdapter\n} from '../core/types/common.types';\nimport type { PendingRPCOptions } from '../core/utils';\nimport type { Observable, OperatorFunction } from 'rxjs';\n\nconst logger = getLogger();\n\nexport class TransportManager extends Destroyable {\n  private initialized$: Observable<boolean>;\n\n  public protocol$ = this.createReplaySubject<string | undefined>(1);\n  // Connection state tracking\n  private isConnecting = false;\n  private isConnected = false;\n  // Outbound gate: requests wait until signalwire.connect succeeds on the\n  // current socket, and responses are dropped while no socket is open.\n  private socketStatus = 'disconnected';\n  private sessionAuthenticated = false;\n  private heldRequests: { id: string; payload: string }[] = [];\n  private serverPingTimer?: ReturnType<typeof setTimeout>;\n  private serverPingWatch = 0;\n\n  // Session epoch for stale event detection (epoch seconds).\n  // Set from the first timestamped event after each signalwire.connect.\n  private ackEvent = <T extends JSONRPCRequest | JSONRPCResponse>(): OperatorFunction<T, T> => {\n    return tap((message) => {\n      if (isSignalwireRequest(message)) {\n        try {\n          logger.debug('[Transport] Sending event ack', {\n            eventId: message.id\n          });\n          this.send(RPCEventAckResponse(message.id));\n        } catch (error) {\n          logger.error('[Transport] Failed to send event acknowledgment:', error);\n        }\n      }\n    });\n  };\n  private replySignalwirePing = <T extends JSONRPCRequest | JSONRPCResponse>(): OperatorFunction<\n    T,\n    T\n  > => {\n    return filter((message) => {\n      if (isSignalwirePingRequest(message)) {\n        try {\n          logger.debug('[Transport] Received ping, sending pong', {\n            pingId: message.id\n          });\n          if (this.sessionAuthenticated) this.armServerPingWatchdog();\n          this.send(RPCPingResponse(message.id));\n        } catch (error) {\n          logger.error('[Transport] Failed to send ping response:', error);\n        }\n        return false;\n      }\n      return true;\n    });\n  };\n  /**\n   * Filter that drops events from a previous session after reconnect.\n   *\n   * Compares the event's `event_channel` against the current protocol.\n   * Events whose channel doesn't contain the current protocol are from\n   * a stale session and are discarded. Events without an event_channel\n   * (auth state events, RPC responses) always pass through.\n   */\n  private discardStaleEvents = <T extends JSONRPCRequest | JSONRPCResponse>(): OperatorFunction<\n    T,\n    T\n  > => {\n    return filter((message) => {\n      if (!isSignalwireRequest(message)) return true;\n\n      const eventChannel: string | undefined = (message.params as Record<string, unknown>)\n        .event_channel as string | undefined;\n      if (!eventChannel) return true;\n\n      if (message.params.event_type.startsWith('conversation.')) {\n        // Conversation events are broadcast using a channel that is not tied to the current protocol\n        logger.debug(\n          `[Transport] Received conversation event: ${message.params.event_type}` +\n            ` (event_channel: ${eventChannel})`\n        );\n        return true;\n      }\n\n      const currentProtocol = this._currentProtocol;\n      if (!currentProtocol) return true;\n\n      if (!eventChannel.includes(currentProtocol)) {\n        const eventType = message.params.event_type;\n        logger.warn(\n          `[Transport] Discarding stale event: ${eventType}` +\n            ` (event_channel does not match current protocol)`\n        );\n        return false;\n      }\n\n      return true;\n    });\n  };\n\n  private _currentProtocol: string | undefined;\n  private _outgoingMessages$ = this.createSubject<string | ArrayBuffer | Blob>();\n  private _webSocketConnections!: WebSocketController;\n  private _jsonRPCMessage$!: Observable<JSONRPCResponse | JSONRPCRequest>;\n  private _jsonRPCResponse$!: Observable<JSONRPCResponse>;\n  private _incomingEvent$!: Observable<JSONRPCRequest | JSONRPCResponse>;\n\n  constructor(\n    private readonly storage: StorageManager,\n    private readonly protocolKey: string,\n    webSocketConstructor: WebSocketAdapter | NodeSocketAdapter,\n    relayHost: string,\n    private readonly onError?: (error: Error) => void\n  ) {\n    super();\n    this._webSocketConnections = new WebSocketController(\n      webSocketConstructor,\n      relayHost,\n      this._outgoingMessages$.asObservable(),\n      {\n        connectionTimeout: PreferencesContainer.instance.connectionTimeout,\n        reconnectDelayMin: PreferencesContainer.instance.reconnectDelayMin,\n        reconnectDelayMax: PreferencesContainer.instance.reconnectDelayMax\n      }\n    );\n    this.subscribeTo(this._webSocketConnections.errors$, (error) => {\n      this.onError?.(error);\n    });\n    this.subscribeTo(this._webSocketConnections.status$, (status) => {\n      this.socketStatus = status;\n      if (status !== 'connected') {\n        this.sessionAuthenticated = false;\n        this.clearServerPingWatchdog();\n      }\n      // An explicit disconnect ends the session: held requests must not go\n      // out on a later connect. A reconnect keeps them.\n      if (status === 'disconnected') {\n        this.heldRequests = [];\n      }\n    });\n    this.initialized$ = defer(() => from(this._init())).pipe(\n      shareReplay(1),\n      takeUntil(this.destroyed$)\n    );\n\n    this._jsonRPCMessage$ = this._webSocketConnections.incomingMessages$.pipe(\n      map((event: MessageEvent) => {\n        try {\n          return JSON.parse(event.data as string) as object;\n        } catch (error) {\n          logger.error('[Transport] Failed to parse incoming message:', error);\n          this.onError?.(new MessageParseError(error));\n          return null;\n        }\n      }),\n      filter(\n        (message): message is JSONRPCResponse | JSONRPCRequest =>\n          message !== null && (isJSONRPCResponse(message) || isJSONRPCRequest(message))\n      ),\n      catchError((error) => {\n        logger.error('[Transport] Message processing error:', error);\n        this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));\n        return EMPTY;\n      }),\n      share(),\n      takeUntil(this.destroyed$)\n    );\n\n    this._jsonRPCResponse$ = this._jsonRPCMessage$.pipe(filter(isJSONRPCResponse));\n\n    this._incomingEvent$ = this._jsonRPCMessage$.pipe(\n      this.ackEvent(),\n      this.replySignalwirePing(),\n      filter((message) => !isJSONRPCResponse(message)),\n      this.discardStaleEvents(),\n      share(),\n      takeUntil(this.destroyed$)\n    );\n  }\n\n  public async setProtocol(protocol: string | undefined): Promise<void> {\n    this._currentProtocol = protocol;\n    this.protocol$.next(protocol);\n    await this._updateProtocolInStorage(protocol);\n  }\n  public get incomingEvent$(): Observable<JSONRPCRequest | JSONRPCResponse> {\n    return this._incomingEvent$;\n  }\n\n  public get connectionStatus$(): Observable<string> {\n    return this._webSocketConnections.status$;\n  }\n\n  public get connectionStatus(): string {\n    return this.socketStatus;\n  }\n\n  public async connect(): Promise<void> {\n    // Prevent duplicate connections\n    if (this.isConnecting || this.isConnected) {\n      logger.warn('[Transport] Already connecting or connected');\n      return Promise.resolve();\n    }\n\n    return new Promise<void>((resolve, reject) => {\n      this.isConnecting = true;\n\n      this.subscribeTo(this.initialized$, () => {\n        this._webSocketConnections.connect();\n\n        // Wait for actual connection\n        const connectionSub = this._webSocketConnections.status$\n          .pipe(\n            filter((status) => status === 'connected' || status === 'disconnected'),\n            take(1),\n            timeout(10000) // 10 second timeout\n          )\n          .subscribe({\n            next: (status) => {\n              if (status === 'connected') {\n                this.isConnecting = false;\n                this.isConnected = true;\n                logger.debug('[Transport] Connection established');\n                resolve();\n              } else {\n                this.isConnecting = false;\n                const error = new TransportConnectionError('Failed to connect');\n                logger.error('[Transport] Connection failed');\n                this.onError?.(error);\n                reject(error);\n              }\n            },\n            error: (err) => {\n              this.isConnecting = false;\n              logger.error('[Transport] Connection error:', err);\n              this.onError?.(err instanceof Error ? err : new Error(String(err), { cause: err }));\n              reject(err as Error);\n            }\n          });\n\n        this.subscriptions.push(connectionSub);\n\n        // Track disconnection\n        this.subscribeTo(\n          this._webSocketConnections.status$.pipe(filter((status) => status === 'disconnected')),\n          () => {\n            logger.debug('[Transport] Disconnected');\n            this.isConnected = false;\n          }\n        );\n      });\n    });\n  }\n\n  public reconnect(): void {\n    this._webSocketConnections.reconnect();\n  }\n\n  public async execute<T extends JSONRPCResponse = JSONRPCResponse>(\n    request: JSONRPCRequest,\n    options?: PendingRPCOptions\n  ): Promise<T> {\n    // Send the request through the WebSocket\n    this.send(request as unknown as JSONSerializable);\n\n    // Create and return a PendingRPC promise that will resolve when the matching response arrives\n    const pending = new PendingRPC<T>(request, this._jsonRPCResponse$ as Observable<T>, options)\n      .promise;\n    // The caller already saw the failure, so a held request must not go out later.\n    pending.catch(() => {\n      this.heldRequests = this.heldRequests.filter((held) => held.id !== request.id);\n    });\n    return pending;\n  }\n  public send(message: unknown): void {\n    const payload = JSON.stringify(message);\n    if (isJSONRPCResponse(message)) {\n      // A response answers a request received on the current socket. The\n      // server rejects a response that arrives before signalwire.connect on a\n      // new socket and closes it, so drop it when no socket is open.\n      if (this.socketStatus !== 'connected') {\n        logger.debug('[Transport] Dropping response while disconnected', { id: message.id });\n        return;\n      }\n    } else if (!this.sessionAuthenticated && !isSignalwireConnectRequest(message)) {\n      this.heldRequests.push({ id: (message as JSONRPCRequest).id, payload });\n      return;\n    }\n    this._outgoingMessages$.next(payload);\n  }\n\n  /**\n   * Called by the session when signalwire.connect succeeds on the current\n   * socket. Sends the requests held since the socket opened, in order.\n   */\n  public setAuthenticated(): void {\n    if (this.socketStatus !== 'connected') return;\n    this.sessionAuthenticated = true;\n    this.armServerPingWatchdog();\n    const held = this.heldRequests;\n    this.heldRequests = [];\n    held.forEach(({ payload }) => this._outgoingMessages$.next(payload));\n  }\n  //   request(request: HTTPRequest): Promise<HTTPResponse> {}\n  public disconnect(): void {\n    logger.debug('[Transport] Disconnecting');\n    this.isConnected = false;\n    this.isConnecting = false;\n    this.clearServerPingWatchdog();\n\n    // Disconnect WebSocket\n    this._webSocketConnections.disconnect();\n  }\n  public destroy(): void {\n    logger.debug('[Transport] Destroying');\n    this.disconnect();\n    super.destroy();\n    this._webSocketConnections.destroy();\n  }\n  private armServerPingWatchdog(): void {\n    this.clearServerPingWatchdog();\n    const watch = this.serverPingWatch;\n    this.serverPingTimer = setTimeout(() => void this.probeServer(watch), SERVER_PING_TIMEOUT_MS);\n  }\n\n  /** Bumps the watch so a probe still in flight no longer acts on its result. */\n  private clearServerPingWatchdog(): void {\n    clearTimeout(this.serverPingTimer);\n    this.serverPingTimer = undefined;\n    this.serverPingWatch += 1;\n  }\n\n  private async probeServer(watch: number): Promise<void> {\n    logger.debug(\n      `[Transport] No signalwire.ping for ${SERVER_PING_TIMEOUT_MS}ms, probing the server`\n    );\n    try {\n      await this.execute(RPCPing(), { timeoutMs: SERVER_PING_PROBE_TIMEOUT_MS });\n      if (watch === this.serverPingWatch) this.armServerPingWatchdog();\n    } catch (error) {\n      if (watch !== this.serverPingWatch) return;\n      logger.warn('[Transport] Server did not answer the ping probe, replacing the socket', error);\n      this.reconnect();\n    }\n  }\n\n  private async _loadProtocolFromStorage(): Promise<void> {\n    try {\n      const storedProtocol = await this.storage.getItem<string>(this.protocolKey);\n      this._currentProtocol = storedProtocol ?? undefined;\n      this.protocol$.next(storedProtocol ?? undefined);\n    } catch (error) {\n      logger.error('Failed to retrieve protocol from storage:', error);\n      throw error;\n    }\n  }\n\n  private async _updateProtocolInStorage(protocol: string | undefined): Promise<void> {\n    if (!protocol) {\n      try {\n        await this.storage.removeItem(this.protocolKey);\n      } catch (error) {\n        logger.error('Failed to remove protocol from storage:', error);\n        throw error;\n      }\n      return;\n    }\n\n    try {\n      const storedProtocol = await this.storage.getItem<string>(this.protocolKey);\n      if (!storedProtocol || storedProtocol !== protocol) {\n        await this.storage.setItem(this.protocolKey, protocol);\n      }\n    } catch (error) {\n      logger.error('Failed to update protocol in storage:', error);\n      throw error;\n    }\n  }\n\n  private async _init(): Promise<boolean> {\n    await this._loadProtocolFromStorage();\n    return true;\n  }\n}\n","import { jwtDecode } from 'jwt-decode';\nimport { filter, firstValueFrom, of, skip, switchMap, type Observable } from 'rxjs';\n\nimport { Destroyable } from '../behaviors/Destroyable';\nimport { DependencyContainer } from '../containers/DependencyContainer';\nimport { ClientPreferences, PreferencesContainer } from '../containers/PreferencesContainer';\nimport { CryptoController } from '../controllers/CryptoController';\nimport { HTTPRequestController } from '../controllers/HTTPRequestController';\nimport { NetworkMonitor } from '../controllers/NetworkMonitor';\nimport { detectPlatformCapabilities } from '../controllers/PlatformCapabilities';\nimport { PreflightRunner } from '../controllers/PreflightRunner';\nimport { VisibilityController } from '../controllers/VisibilityController';\nimport { User } from '../core/entities/User';\nimport { InvalidCredentialsError, UnexpectedError } from '../core/errors';\nimport { RPCExecute } from '../core/RPCMessages';\nimport { AttachManager } from '../managers/AttachManager';\nimport { ClientSessionManager, ClientSessionWrapper } from '../managers/ClientSessionManager';\nimport { ConversationsManager } from '../managers/ConversationsManager';\nimport { CredentialRefreshCoordinator } from '../managers/CredentialRefreshCoordinator';\nimport { DiagnosticsCollector } from '../managers/DiagnosticsCollector';\nimport { DirectoryManager } from '../managers/DirectoryManager';\nimport { TransportManager } from '../managers/TransportManager';\nimport { isRecoverableAuthError } from '../utils/authRecovery';\nimport { getLogger, setLogger, setDebugOptions, setLogLevel } from '../utils/logger';\n\nimport type { Address } from '../core/entities/Address';\nimport type { Directory } from '../core/entities/Directory';\nimport type { Call } from '../core/entities/types/call.types';\nimport type {\n  NodeSocketAdapter,\n  SDKCredential,\n  WebSocketAdapter\n} from '../core/types/common.types';\nimport type { MediaOptions } from '../core/types/media.types';\nimport type {\n  PlatformCapabilities,\n  DeviceRecoveryEvent,\n  PermissionResult,\n  PreflightOptions,\n  PreflightResult,\n  SessionDiagnostics\n} from '../core/types/resilience.types';\nimport type { SDKWarning } from '../core/types/warnings.types';\nimport type { CredentialProvider, Storage, WebRTCApiProvider } from '../dependencies/interfaces';\nimport type { DeviceController } from '../interfaces/DeviceController';\nimport type { SDKLogger, LogLevel, DebugOptions } from '../utils/logger';\n\nconst logger = getLogger();\n\n/**\n * Storage key for the client-bound marker. The SAT and authorization_state are\n * both opaque to the SDK, so on a page reload the preflight recovery — which\n * runs before any session exists — has no other way to know the session was\n * client-bound. See {@link SignalWire.persistClientBoundMarker}.\n */\nconst CLIENT_BOUND_STORAGE_KEY = 'sw:client_bound';\n\ninterface JWTHeader {\n  ch?: string;\n  typ?: string;\n}\n/** Options for constructing a {@link SignalWire}. */\nexport interface SignalWireOptions {\n  /** Skip automatic WebSocket connection on construction. */\n  skipConnection?: boolean;\n  /** Skip automatic user registration on construction. */\n  skipRegister?: boolean;\n  /** Skip monitoring media device changes. */\n  skipDeviceMonitoring?: boolean;\n  /** Whether to reconnect to previously attached calls. */\n  reconnectAttachedCalls?: boolean;\n  /** Whether to save preferences. */\n  savePreferences?: boolean;\n  /**\n   * Persist the session across page reloads.\n   *\n   * When `true`, credential, authorization state, and protocol are stored in\n   * `localStorage` (survives reload). The DPoP key pair is persisted in\n   * IndexedDB. On reload, the SDK restores the session from cache\n   * without calling `credentialProvider.authenticate()`.\n   *\n   * When `false` (default), session data lives in `sessionStorage` and is\n   * lost on reload.\n   *\n   * Both {@link SignalWire.disconnect | disconnect()} and\n   * {@link SignalWire.destroy | destroy()} end the session and clear the\n   * persisted resume state and attach records; credentials and device\n   * preferences survive. Use `resetToDefaults()` for a full wipe, or\n   * `unregister()` to temporarily stop receiving inbound calls while keeping\n   * the session alive.\n   */\n  persistSession?: boolean;\n  /** Custom storage implementation for persistence. */\n  storageImplementation?: Storage;\n  /** Custom WebSocket constructor */\n  webSocketConstructor?: WebSocketAdapter | NodeSocketAdapter;\n  /** Custom WebRTC API provider */\n  webRTCApiProvider?: WebRTCApiProvider;\n  /**\n   * Custom logger implementation. Must implement the {@link SDKLogger} interface.\n   * Pass `null` to restore the built-in logger.\n   *\n   * **Note:** Logger configuration is global — setting it on one instance affects all instances.\n   */\n  logger?: SDKLogger | null;\n  /**\n   * Log level for the built-in logger.\n   * Default: `'warn'`. Set to `'debug'` for verbose SDK output.\n   * Has no effect when a custom `logger` is provided.\n   *\n   * **Note:** Logger configuration is global — setting it on one instance affects all instances.\n   */\n  logLevel?: LogLevel;\n  /** Debug options for verbose SDK diagnostics (e.g., `{ logWsTraffic: true }`). */\n  debug?: DebugOptions;\n  /**\n   * Control transport for `call.*` verbs across ALL calls in this session:\n   * - `'routed'` (default) sends them on the client's session channel.\n   * - `'in-dialog'` carries them on each call's own signaling channel via `verto.info`,\n   *   so control works without the client needing to know how the conference is hosted.\n   *\n   * `'in-dialog'` is opt-in because it does not reach everywhere `'routed'` does: use it\n   * only for calls that join a conference over SWML (e.g. an SWML `join_conference`).\n   *\n   * It is a client-wide setting rather than per-`dial()` because the SDK issues some\n   * control RPCs itself (fetching the layout list on join, for example), and those must\n   * travel the same way as the app's own, or reads and writes land on different transports.\n   *\n   * @experimental A rollout switch, not a long-term part of the API. Expected to\n   * disappear once `'in-dialog'` becomes the only transport, so it carries no semver\n   * promise and application code should not depend on it.\n   */\n  callControl?: 'routed' | 'in-dialog';\n}\n\n/** Options for {@link SignalWire.dial}. Extends {@link MediaOptions} with dial-specific settings. */\nexport interface DialOptions extends MediaOptions {\n  /** Preferred video codecs for this call (overrides global preferences). */\n  preferredVideoCodecs?: string[];\n  /** Preferred audio codecs for this call (overrides global preferences). */\n  preferredAudioCodecs?: string[];\n  /** Enable stereo Opus for this call (overrides global preferences). */\n  stereo?: boolean;\n  /** Optional node ID for routing the call */\n  nodeId?: string;\n  /**\n   * Custom variables sent with the Verto invite. Merged with\n   * `client.preferences.userVariables` and any query-string variables on the\n   * destination URI; values here take precedence.\n   */\n  userVariables?: Record<string, unknown>;\n}\n\nconst buildOptionsFromDestination = (destination: string | Address): DialOptions => {\n  if (typeof destination === 'string') {\n    const queryStartIndex = destination.indexOf('?');\n    if (queryStartIndex !== -1) {\n      const queryString = destination.substring(queryStartIndex + 1);\n      const params = new URLSearchParams(queryString);\n      const channel = params.get('channel');\n\n      if (channel === 'video') {\n        return { audio: true, video: true, receiveVideo: true };\n      } else if (channel === 'audio') {\n        return { audio: true, video: false };\n      }\n    }\n  }\n\n  return {};\n};\n/**\n * Main entry point for the SignalWire Browser SDK.\n *\n * Manages authentication, WebSocket transport, call creation, and media devices.\n *\n * @example\n * ```ts\n * const client = new SignalWire(credentialProvider);\n * client.isConnected$.subscribe(connected => console.log('Connected:', connected));\n * const call = await client.dial('/public/my-room');\n * ```\n */\nclass SignalWire extends Destroyable implements DeviceController {\n  /** Global SDK preferences (timeouts, ICE config, media defaults). */\n  public preferences = new ClientPreferences();\n  private _user$ = this.createBehaviorSubject<User | undefined>(undefined);\n  private _directory$ = this.createBehaviorSubject<Directory | undefined>(undefined);\n  private _transport!: TransportManager;\n  private _clientSession!: ClientSessionManager;\n  private _publicSession!: ClientSessionWrapper;\n  private _deviceController!: DeviceController;\n  private _attachManager?: AttachManager;\n  /**\n   * Set once a credential recovery has been *verified* — reauthenticated and\n   * then proven by the operation it was meant to unblock. Read by the attach\n   * path, which may only discard an attach record when a reattach is refused\n   * on a credential the server has already accepted.\n   */\n  private _credentialRecovered = false;\n  private _isConnected$ = this.createBehaviorSubject<boolean>(false);\n  private _isRegistered$ = this.createBehaviorSubject<boolean>(false);\n  private _errors$ = this.createReplaySubject<Error>(1);\n  private _warnings$ = this.createReplaySubject<SDKWarning>(10);\n  private _options: SignalWireOptions = {};\n  private _dpopManager?: CryptoController;\n  private _refreshCoordinator?: CredentialRefreshCoordinator;\n  /** The refresh path's own HTTP controller — see resolveCredentials. */\n  private _refreshHttp?: HTTPRequestController;\n  /** Host `_refreshHttp` was built against, so it can be rebuilt when the token's `ch` changes. */\n  private _refreshHttpHost?: string;\n  private _credentialProvider?: CredentialProvider;\n  private _deps = new DependencyContainer();\n\n  // Resilience subsystems\n  private _networkMonitor?: NetworkMonitor;\n  private _visibilityController?: VisibilityController;\n  private _diagnosticsCollector?: DiagnosticsCollector;\n  private _platformCapabilities?: PlatformCapabilities;\n\n  /**\n   * Creates a new SignalWire client and begins connecting.\n   *\n   * @param credentialProvider - Provider that supplies authentication credentials.\n   * @param options - Configuration options (connection, device monitoring, preferences).\n   */\n  constructor(credentialProvider: CredentialProvider | undefined, options: SignalWireOptions = {}) {\n    super();\n    this._credentialProvider = credentialProvider;\n    this._options = {\n      ...PreferencesContainer.instance.defaultSignalWireOptions,\n      ...options\n    };\n\n    // Set custom storage implementation if provided\n    if (this._options.storageImplementation) {\n      this._deps.storageImpl = this._options.storageImplementation;\n    }\n\n    if (this._options.persistSession) {\n      this._deps.persistSession = true;\n    }\n\n    if (this._options.webSocketConstructor) {\n      this._deps.WebSocket = this._options.webSocketConstructor;\n    }\n\n    if (this._options.savePreferences) {\n      this.preferences.enableSavePreferences(this._deps.storage);\n    }\n\n    if (this._options.webRTCApiProvider) {\n      this._deps.webRTCApiProvider = this._options.webRTCApiProvider;\n    }\n\n    // Configure logger from options\n    if (this._options.logger !== undefined) {\n      setLogger(this._options.logger);\n    }\n    if (this._options.logLevel) {\n      setLogLevel(this._options.logLevel);\n    }\n    if (this._options.debug) {\n      setDebugOptions(this._options.debug);\n    }\n\n    this._deviceController = this._deps.deviceController;\n    if (!this._options.skipDeviceMonitoring) {\n      this._deviceController.enableDeviceMonitoring();\n    }\n\n    this.subscribeTo(this._deviceController.errors$, (error) => {\n      this._errors$.next(error);\n    });\n\n    // Initialize resilience subsystems (non-fatal)\n    this.initResilienceSubsystems();\n\n    this.resolveCredentials()\n      .then(() => {\n        this.init().catch((error: unknown) => {\n          logger.error('[SignalWire] Initialization error:', error);\n          // Clear stale cached credential so next attempt starts fresh\n          void this._deps.storage.removeItem('sw:cached_credential');\n          void this._deps.storage.removeItem('sw:cached_credential', 'local');\n          this._errors$.next(\n            error instanceof Error ? error : new Error(String(error), { cause: error })\n          );\n        });\n      })\n      .catch((error: unknown) => {\n        logger.error('[SignalWire] Initialization error:', error);\n        this._errors$.next(\n          error instanceof Error ? error : new Error(String(error), { cause: error })\n        );\n      });\n  }\n\n  /**\n   * Build the refresh path's own HTTP controller, against whatever host is current.\n   *\n   * Called on first use rather than up front, so `apiHost` already reflects the\n   * token's `ch` claim. Same credential source as the container's controller — only\n   * the instance, and therefore its observable streams, is separate.\n   */\n  private createRefreshHttpController(): HTTPRequestController {\n    return new HTTPRequestController(this._deps.apiHost, () => this._deps.credential);\n  }\n\n  /**\n   * Initializes DPoP if not already set up. Returns the fingerprint on success.\n   */\n  private async initDPoP(): Promise<string | undefined> {\n    if (this._dpopManager?.initialized) {\n      return this._dpopManager.fingerprint;\n    }\n    try {\n      this._dpopManager = new CryptoController();\n      const fingerprint = await this._dpopManager.init();\n      logger.debug('[SignalWire] DPoP initialized, fingerprint available');\n      return fingerprint;\n    } catch (error) {\n      logger.warn('[SignalWire] DPoP initialization failed, proceeding without DPoP:', error);\n      this._dpopManager = undefined;\n      return undefined;\n    }\n  }\n\n  /**\n   * Resolves credentials using cache-first strategy when persistSession is enabled.\n   *\n   * 1. If persistSession → check localStorage for cached credential\n   * 2. If cached and not expired → use it (skip provider.authenticate())\n   * 3. If no cache or expired → call provider.authenticate()\n   * 4. If no provider AND no cache → throw\n   */\n  private async resolveCredentials(): Promise<void> {\n    const fingerprint = await this.initDPoP();\n\n    // Coordinator centralizes refresh ownership across the developer-provided\n    // and Client Bound SAT paths. Constructed after DPoP init so it can decide\n    // whether the internal path is available.\n    this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {\n      // Dedicated and lazily built, and both halves are load-bearing.\n      //\n      // Lazy, because this constructor runs BEFORE validateCredentials decodes the\n      // token's `ch` claim, so the host is not known yet. Resolving on first use\n      // instead means `_deps.apiHost` has already been corrected.\n      //\n      // Dedicated, because the obvious lazy form — `() => this._deps.http` — hands\n      // this path the container's SHARED controller, and that regressed reattach:\n      // register came back \"Requester validation failed\" on a reload, on a host\n      // where no request changed URL at all. Reading `_deps.http` eagerly, as the\n      // original code did, memoized a controller that `set ch` then discarded, so\n      // the refresh path had an instance of its own with its own status/error/\n      // response subjects. That isolation was accidental but it is real, so keep it\n      // deliberately rather than moving device-token traffic onto the shared one.\n      http: () => {\n        // Rebuild if the host changed since the cached controller was built. A second\n        // credential with a different `ch` claim repoints _deps.apiHost (the container\n        // drops its own controller too); refresh/device-token traffic must follow it or\n        // it 401s against the old host with the same misleading bare `Unauthorized`.\n        if (!this._refreshHttp || this._refreshHttpHost !== this._deps.apiHost) {\n          this._refreshHttp?.destroy();\n          this._refreshHttp = this.createRefreshHttpController();\n          this._refreshHttpHost = this._deps.apiHost;\n        }\n        return this._refreshHttp;\n      },\n      notifier: {\n        onError: (error) => this._errors$.next(error),\n        onWarning: (warning) => this._warnings$.next(warning),\n        onRefreshExhausted: () => void this.disconnect(),\n        onCredentialRefreshed: async (credential) => this.reauthenticateLiveSession(credential)\n      },\n      store: {\n        read: () => this._deps.credential,\n        write: (credential) => {\n          this._deps.credential = credential;\n        },\n        merge: (partial) => {\n          this._deps.credential = { ...this._deps.credential, ...partial };\n          // Persist so a Client Bound SAT survives a page reload. The stored\n          // token is useless without the DPoP key (IndexedDB) — the binding\n          // itself is the theft protection.\n          this.persistCredential(this._deps.credential);\n        },\n        persist: (credential) => this.persistCredential(credential)\n      }\n    });\n\n    // If a provider is given, use it — fresh login always wins over stale cache.\n    // Cache is only used when there's no provider (reload / session restore).\n    if (this._credentialProvider) {\n      return this.validateCredentials(this._credentialProvider, undefined, fingerprint);\n    }\n\n    // No provider — check for cached credential: localStorage first (persistSession),\n    // then sessionStorage (reload within same tab).\n    for (const scope of this._deps.persistSession\n      ? (['local', 'session'] as const)\n      : (['session'] as const)) {\n      try {\n        const cached = await this._deps.storage.getItem<SDKCredential>(\n          'sw:cached_credential',\n          scope\n        );\n        if (cached?.token) {\n          logger.debug(`[SignalWire] Using cached credential from ${scope}Storage`);\n          return await this.validateCredentials(undefined, cached);\n        }\n      } catch {\n        // continue to next scope\n      }\n    }\n\n    throw new InvalidCredentialsError(\n      'No credential provider and no cached session. Provide a CredentialProvider or enable persistSession with a prior login.'\n    );\n  }\n\n  private async validateCredentials(\n    credentialProvider: CredentialProvider | undefined,\n    credentials?: SDKCredential,\n    fingerprint?: string\n  ): Promise<void> {\n    const _fingerprint =\n      fingerprint ?? (this._dpopManager?.initialized ? this._dpopManager.fingerprint : undefined);\n\n    const _credentials =\n      credentials ??\n      (credentialProvider\n        ? await credentialProvider.authenticate(\n            _fingerprint ? { fingerprint: _fingerprint } : undefined\n          )\n        : undefined);\n\n    if (!_credentials) {\n      throw new InvalidCredentialsError('No credentials available.');\n    }\n    if (_credentials.token) {\n      try {\n        const decodeHeader: JWTHeader = jwtDecode(_credentials.token, { header: true });\n        this._deps.ch = decodeHeader.ch;\n      } catch (error) {\n        logger.error('[SignalWire] Invalid JWT token provided in credentials:', error);\n        throw new InvalidCredentialsError('Invalid JWT token provided in credentials.', {\n          cause: error\n        });\n      }\n    }\n    if (!_credentials.token && !_credentials.authorizationState) {\n      logger.error('[SignalWire] No valid authentication credentials provided.');\n      throw new InvalidCredentialsError('No valid authentication credentials provided.');\n    }\n\n    // Skip client-side expiry check when using cached credentials for reconnect.\n    // The server validates via authorization_state — even expired tokens work\n    // when authorization_state is present (session continuity).\n    if (\n      !this._deps.persistSession &&\n      _credentials.expiry_at &&\n      _credentials.expiry_at < Date.now()\n    ) {\n      logger.error('[SignalWire] Provided credentials have expired.');\n      throw new InvalidCredentialsError('Provided credentials have expired.');\n    }\n\n    if (_credentials.expiry_at && credentialProvider?.refresh) {\n      this._refreshCoordinator?.scheduleDeveloperRefresh(\n        credentialProvider,\n        _credentials.expiry_at\n      );\n    } else if (_credentials.expiry_at && !credentialProvider?.refresh) {\n      // Credential has a known expiry but no refresh handler. Unless the SAT\n      // has `sat:refresh` scope (handled by DeviceTokenManager), the session\n      // will terminate at expiry with no fallback.\n      logger.warn(\n        `[SignalWire] [SW-NO-REFRESH-HANDLER] Credential has expiry_at=${_credentials.expiry_at} but no refresh handler. Session will terminate at expiry unless the SAT has 'sat:refresh' scope.`\n      );\n      this._warnings$.next({\n        code: 'credential_no_refresh_handler',\n        source: 'CredentialProvider',\n        message:\n          \"Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.\",\n        expiresAt: _credentials.expiry_at\n      });\n    }\n\n    this._deps.credential = _credentials;\n    this.persistCredential(_credentials);\n\n    await this.reauthenticateLiveSession(_credentials);\n  }\n\n  /**\n   * Reauthenticate the currently-open session with a freshly obtained\n   * credential so the new token takes effect on the live socket immediately —\n   * not just on the next reconnect. No-op when the session is not\n   * connected/authenticated or the credential carries no token (e.g. an\n   * authorization-state-only refresh). Non-fatal: reauth failures surface on\n   * `errors$` without aborting the refresh that triggered this.\n   */\n  private async reauthenticateLiveSession(credential: SDKCredential): Promise<void> {\n    if (!this.isConnected || !this._clientSession.authenticated || !credential.token) {\n      return;\n    }\n    try {\n      await this._clientSession.reauthenticate(credential.token);\n      logger.info('[SignalWire] Session refreshed with new credentials.');\n    } catch (error: unknown) {\n      logger.error('[SignalWire] Failed to refresh session with new credentials:', error);\n      this._errors$.next(\n        error instanceof Error ? error : new Error(String(error), { cause: error })\n      );\n    }\n  }\n\n  /**\n   * Recover a session the server is refusing: mint a fresh credential,\n   * reauthenticate the live session with it, and retry the operation.\n   *\n   * The connection is deliberately kept. A reload authenticates the new socket\n   * against the persisted `authorization_state`, and that handshake is what\n   * associates the socket with the previous session — the association reattach\n   * depends on. `signalwire.reauthenticate` swaps the credential *on that same\n   * session*, so recovery never touches the resume state. Discarding it would\n   * heal the credential by destroying the very thing the caller is trying to\n   * get back to.\n   *\n   * The operation is still the verdict, never the RPC. Reauthenticating with\n   * the in-memory token is accepted by a resume even while requests stay\n   * refused, because the persisted `authorization_state` short-circuits token\n   * validation — and `signalwire.reauthenticate` with a *freshly minted* token\n   * has also been observed accepted while `subscriber.online` keeps being\n   * refused (staging run 33826974634). Both look like success and are not.\n   *\n   * @returns the operation's value, or the reason recovery could not deliver\n   *   one. `error` is undefined when there was no way to mint at all.\n   */\n  private async recoverAndRetry<T>(\n    operation: () => Promise<T>\n  ): Promise<{ ok: true; value: T } | { ok: false; error?: unknown }> {\n    if (!(await this.remintAndReauthenticate())) {\n      return { ok: false };\n    }\n    try {\n      const value = await operation();\n      // The reauthentication is only now known to have taken effect. Anything\n      // that must distinguish \"the server refuses this credential\" from \"the\n      // server refuses this request\" reads this, not the RPC result.\n      this._credentialRecovered = true;\n      return { ok: true, value };\n    } catch (error) {\n      logger.warn(\n        '[SignalWire] Reauthentication was accepted but the operation is still refused:',\n        error\n      );\n      return { ok: false, error };\n    }\n  }\n\n  /**\n   * Re-mint a credential and adopt it only if the live session accepts it.\n   *\n   * The mechanism follows the binding: a client-bound session re-mints a bound\n   * base SAT through `authenticate()` with the DPoP fingerprint, because the\n   * developer refresh handler would hand back an unbound token and silently\n   * degrade the session. An unbound session uses the refresh handler. Rotation\n   * cost is not a reason to skip this — the only reason is having no mechanism.\n   *\n   * @returns whether the session is now running on a freshly accepted credential.\n   */\n  private async remintAndReauthenticate(): Promise<boolean> {\n    const provider = this._credentialProvider;\n    if (!provider) {\n      return false;\n    }\n    const { clientBound } = this._clientSession;\n    if (!clientBound && !provider.refresh) {\n      // authenticate() may be interactive, so it is not a silent fallback here.\n      logger.debug(\n        '[SignalWire] [SW-NO-REFRESH-HANDLER] Unbound session with no refresh handler; cannot re-mint.'\n      );\n      return false;\n    }\n\n    try {\n      const newCredentials = clientBound\n        ? await provider.authenticate(\n            this._dpopManager?.initialized\n              ? { fingerprint: this._dpopManager.fingerprint }\n              : undefined\n          )\n        : await this.remintCredential(provider);\n\n      if (!newCredentials.token) {\n        logger.warn('[SignalWire] Re-minted credential has no token; keeping the current one.');\n        return false;\n      }\n\n      // Adopt only after the server accepts it, so a refused token is never\n      // left behind as the current credential.\n      await this._clientSession.reauthenticate(newCredentials.token);\n      this._deps.credential = newCredentials;\n      this.persistCredential(newCredentials);\n      if (newCredentials.expiry_at && provider.refresh) {\n        this._refreshCoordinator?.scheduleDeveloperRefresh(provider, newCredentials.expiry_at);\n      }\n      return true;\n    } catch (error) {\n      logger.warn('[SignalWire] Re-mint recovery failed:', error);\n      return false;\n    }\n  }\n\n  /**\n   * Re-mint a credential via `provider.refresh()`, routed through the\n   * coordinator's shared in-flight guard so concurrent re-mint paths (a\n   * scheduled/resume refresh, -32003 recovery, and reconnect) never fire a\n   * second `provider.refresh()` in parallel — which rotating one-time-use\n   * refresh tokens reject. Falls back to a direct call only if the coordinator\n   * has not been constructed yet.\n   */\n  private async remintCredential(provider: CredentialProvider): Promise<SDKCredential> {\n    if (this._refreshCoordinator) {\n      return this._refreshCoordinator.refreshCredential(provider);\n    }\n    if (!provider.refresh) {\n      throw new InvalidCredentialsError('Credential provider does not support refresh');\n    }\n    return provider.refresh();\n  }\n\n  /**\n   * Re-mint credentials before a fresh (re)connect (`onBeforeReconnect` hook).\n   * The session invokes this only when it is client-bound OR the in-memory\n   * token is expired. The re-mint mechanism depends on the binding:\n   *   - Client-bound: `authenticate()` with the DPoP fingerprint to obtain a\n   *     fresh base SAT the upcoming reconnect can re-bind (the\n   *     DeviceTokenManager re-activates afterwards).\n   *   - Unbound: the developer's non-interactive `refresh()` handler.\n   *     `authenticate()` is deliberately NOT used here — it may be interactive\n   *     (a login prompt) and must not fire on a background reconnect.\n   *\n   * Rejects on failure so the session aborts the reconnect rather than\n   * replaying a stale token.\n   */\n  private async refreshCredentialForReconnect(): Promise<void> {\n    if (!this._credentialProvider) return;\n    try {\n      let newCredentials: SDKCredential;\n      // A live session reports its own binding. The preflight-fetch recovery runs\n      // before any session exists (page reload), where the token and\n      // authorization_state are both opaque to the SDK — so the persisted marker\n      // is the only way to know the reloaded session was client-bound. Without it\n      // a client-bound session with a refresh handler is re-minted as an unbound\n      // token and silently degraded.\n      // `_clientSession` is `!`-typed so the optional chain looks unnecessary to\n      // the linter, but it is genuinely undefined on that pre-connect path. `??`\n      // (not `||`) is deliberate: a live unbound session reports `false` and must\n      // keep it, not fall through to the marker.\n      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n      const clientBound = this._clientSession?.clientBound ?? (await this.wasClientBound());\n      if (clientBound) {\n        logger.debug('[SignalWire] Re-minting client-bound base SAT before reconnect');\n        newCredentials = await this._credentialProvider.authenticate(\n          this._dpopManager?.initialized\n            ? { fingerprint: this._dpopManager.fingerprint }\n            : undefined\n        );\n      } else if (this._credentialProvider.refresh) {\n        logger.debug('[SignalWire] Refreshing unbound credential before reconnect');\n        newCredentials = await this.remintCredential(this._credentialProvider);\n      } else {\n        // Unbound and expired but no refresh handler: cannot re-mint\n        // non-interactively. Let the connect proceed with the existing token\n        // (it will fail and surface a credentials error) rather than trigger\n        // a potentially-interactive authenticate().\n        logger.warn(\n          '[SignalWire] [SW-NO-REFRESH-HANDLER] Token expired on reconnect but no refresh handler; reconnecting with the existing token.'\n        );\n        return;\n      }\n      if (!newCredentials.token) {\n        // A fresh connect authenticates with jwt_token; an authorization-state-\n        // only re-mint cannot satisfy it. Keep the existing credential so the\n        // reconnect surfaces a clear failure rather than a DependencyError.\n        logger.warn(\n          '[SignalWire] Re-minted credential has no token; keeping the existing credential for reconnect.'\n        );\n        return;\n      }\n      this._deps.credential = newCredentials;\n      this.persistCredential(newCredentials);\n      // Re-arm the developer refresh timer against the new credential's expiry.\n      // If Client Bound SAT activation succeeds on the upcoming reconnect, the\n      // coordinator will cancel it. This closes the \"reconnect did not re-arm\n      // developer refresh\" gap.\n      if (newCredentials.expiry_at && this._credentialProvider.refresh) {\n        this._refreshCoordinator?.scheduleDeveloperRefresh(\n          this._credentialProvider,\n          newCredentials.expiry_at\n        );\n      }\n      logger.debug('[SignalWire] Credential refreshed successfully for reconnect');\n    } catch (error) {\n      logger.error('[SignalWire] Failed to refresh credentials for reconnect:', error);\n      this._errors$.next(\n        error instanceof Error ? error : new Error(String(error), { cause: error })\n      );\n      // Propagate to prevent reconnect with stale credentials\n      throw error;\n    }\n  }\n\n  /** Persist credential to localStorage when persistSession is enabled. */\n  private persistCredential(credential: SDKCredential): void {\n    if (!credential.token) return;\n    // Always write to sessionStorage (reload within same tab)\n    void this._deps.storage.setItem('sw:cached_credential', credential);\n    // Also write to localStorage when persistSession (survives new tabs)\n    if (this._deps.persistSession) {\n      void this._deps.storage.setItem('sw:cached_credential', credential, 'local');\n    }\n  }\n\n  /**\n   * Persist whether the session is client-bound, mirroring the credential's\n   * storage scopes so it survives a reload. The preflight recovery reads it\n   * before any session exists to decide whether to re-bind via `authenticate()`\n   * or refresh an unbound token; the marker tracks the latest binding, so an\n   * unbound reconnect clears a stale marker from an earlier client-bound login.\n   */\n  private persistClientBoundMarker(bound: boolean): void {\n    const scopes = this._deps.persistSession\n      ? (['session', 'local'] as const)\n      : (['session'] as const);\n    for (const scope of scopes) {\n      if (bound) {\n        void this._deps.storage.setItem(CLIENT_BOUND_STORAGE_KEY, true, scope);\n      } else {\n        void this._deps.storage.removeItem(CLIENT_BOUND_STORAGE_KEY, scope);\n      }\n    }\n  }\n\n  /** Read the persisted client-bound marker (see {@link persistClientBoundMarker}). */\n  private async wasClientBound(): Promise<boolean> {\n    const scopes = this._deps.persistSession\n      ? (['local', 'session'] as const)\n      : (['session'] as const);\n    for (const scope of scopes) {\n      try {\n        if (await this._deps.storage.getItem<boolean>(CLIENT_BOUND_STORAGE_KEY, scope)) {\n          return true;\n        }\n      } catch {\n        // try the next scope\n      }\n    }\n    return false;\n  }\n\n  private async init() {\n    // Initialize user first (before transport) to fetch user info\n    this._user$.next(new User(this._deps.http));\n\n    if (!this._options.skipConnection) {\n      await this.connect();\n    }\n\n    // Flush attached calls after connect creates the AttachManager\n    if (!this._options.reconnectAttachedCalls && this._attachManager) {\n      await this._attachManager.flush();\n    }\n\n    if (!this._options.skipRegister) {\n      // eventually register after the authentication\n      try {\n        await this.register();\n      } catch (error) {\n        logger.error('[SignalWire] Registration failed:', error);\n        this._errors$.next(\n          error instanceof Error ? error : new Error(String(error), { cause: error })\n        );\n      }\n    }\n\n    // eventually reconnect to attached calls after the authentication\n    void this.handleAttachments();\n  }\n\n  private async handleAttachments() {\n    if (!this._attachManager) {\n      logger.error('[SignalWire] AttachManager not initialized');\n      return;\n    }\n    if (!this._options.reconnectAttachedCalls) {\n      return;\n    }\n    try {\n      await this._attachManager.reattachCalls();\n    } catch (error) {\n      logger.error('[SignalWire] Failed to reattach calls:', error);\n      this._errors$.next(\n        error instanceof Error ? error : new Error(String(error), { cause: error })\n      );\n    } finally {\n      // Consume the recovery signal: reattach has read it, and a stale `true`\n      // must not authorize a discard on a later reattach whose failure was only\n      // transient. It reflects a recovery since the last reattach, nothing older.\n      this._credentialRecovered = false;\n    }\n  }\n\n  /**\n   * Fetch the authenticated user profile, recovering a stale credential.\n   *\n   * On a reload the persisted credential can be expired. Unlike the WS resume —\n   * which the server accepts against the persisted `authorization_state` even\n   * with an expired token — this REST preflight has no such short-circuit and is\n   * refused (401). There is no session yet to reauthenticate, so recovery\n   * re-mints the credential through the provider ({@link refreshCredentialForReconnect})\n   * and retries with a FRESH {@link User}: Fetchable memoizes its result\n   * (shareReplay), so reusing the instance would replay the 401 instead of\n   * re-fetching with the new token. Without the user id the transport/session —\n   * and the reattach a reload is trying to preserve — cannot even be addressed.\n   */\n  private async fetchUserOrRecover(): Promise<void> {\n    const fetchUser = async (user: User): Promise<void> => {\n      const fetched = await firstValueFrom(user.fetched$);\n      if (!fetched) {\n        throw new UnexpectedError('Failed to fetch user information - fetched$ emitted false');\n      }\n      this._deps.user = user;\n    };\n\n    const user = this._user$.value;\n    if (!user) {\n      throw new UnexpectedError('User not initialized before connect');\n    }\n\n    try {\n      await fetchUser(user);\n    } catch (firstError) {\n      logger.error(\n        `[SignalWire] Failed to fetch user information: ${firstError instanceof Error ? firstError.message : 'Unknown error'}. ` +\n          `This usually means the user token is invalid or expired. Re-minting the credential and retrying.`\n      );\n      try {\n        await this.refreshCredentialForReconnect();\n        const refetched = new User(this._deps.http);\n        await fetchUser(refetched);\n        this._user$.next(refetched);\n      } catch (retryError) {\n        throw new UnexpectedError('Error fetching user information', { cause: retryError });\n      }\n    }\n  }\n\n  /**\n   * Establishes the WebSocket connection and authenticates the session.\n   *\n   * ## Reconnection behavior\n   *\n   * After a successful connection the underlying {@link WebSocketController}\n   * automatically attempts to reconnect whenever the socket closes\n   * unexpectedly (e.g. network change, server restart). Reconnection uses an\n   * **exponential back-off** strategy:\n   *\n   * - First retry after `reconnectDelayMin` (default **0.1 s**).\n   * - Each subsequent retry doubles the delay up to `reconnectDelayMax`\n   *   (default **3 s**).\n   * - The delay resets to `reconnectDelayMin` once a connection succeeds.\n   * - A per-attempt `connectionTimeout` (default **10 s**) aborts the\n   *   attempt and schedules the next retry if the server does not respond.\n   *\n   * Calling {@link disconnect} stops the reconnection loop entirely.\n   *\n   * ## Message handling during temporary disconnections\n   *\n   * While the socket is not in the `connected` state, **outgoing messages\n   * are queued** in an internal buffer. Once the connection is\n   * re-established the queue is flushed in order so no outgoing RPC call is\n   * lost.\n   *\n   * **Incoming** server-to-client messages that arrive while the socket is\n   * down are *not* buffered by the SDK — they are expected to be\n   * re-delivered by the server after the session is re-authenticated.\n   * Active RPC calls that were awaiting a response will time out\n   * (default **5 s**) and reject with an `RPCTimeoutError`; callers should\n   * handle this and retry if appropriate.\n   *\n   * The connection status can be observed via the `status$` observable on\n   * the transport layer, which emits `'connecting'`, `'connected'`,\n   * `'reconnecting'`, `'disconnecting'`, or `'disconnected'`.\n   */\n  public async connect(): Promise<void> {\n    // If a prior connect() left a session/transport in place, tear them down\n    // before allocating fresh ones. Otherwise the previous TransportManager,\n    // ClientSessionManager, and AttachManager would be orphaned with their\n    // subscriptions and timers still alive — disconnect() only destroys the\n    // most recent ClientSessionManager.\n    await this.teardownTransportAndSession();\n\n    // Fetch the user before building the transport/session: their storage keys\n    // (protocol / authorization_state / attach records) are all namespaced by\n    // the user id, including the resume/attach state a reload depends on.\n    await this.fetchUserOrRecover();\n\n    const errorHandler = (error: Error) => {\n      this._errors$.next(error);\n    };\n\n    // Now initialize transport and session with user ID available\n    this._transport = new TransportManager(\n      this._deps.storage,\n      this._deps.protocolKey,\n      this._deps.WebSocket,\n      PreferencesContainer.instance.relayHost ?? this._deps.relayHost,\n      errorHandler\n    );\n\n    // Create AttachManager (needed by CallFactory -> VertoManager)\n    this._attachManager = new AttachManager(\n      this._deps.storage,\n      this._deps.deviceController,\n      PreferencesContainer.instance.reconnectCallsTimeout,\n      this._deps.attachedCallsKey,\n      () => this._credentialRecovered\n    );\n\n    this._clientSession = new ClientSessionManager(\n      () => this._deps.credential,\n      this._transport,\n      this._deps.storage,\n      this._deps.authorizationStateKey,\n      this._deps.deviceController,\n      this._attachManager,\n      this._deps.webRTCApiProvider,\n      this._dpopManager,\n      this._networkMonitor?.networkChange$\n    );\n    this._publicSession = new ClientSessionWrapper(this._clientSession);\n\n    // Session-wide control transport for every call in this session (see\n    // SignalWireOptions.callControl). Client config, so a reattached call reads it here\n    // rather than restoring it from persisted attachment state.\n    this._clientSession.callControl = this._options.callControl ?? 'routed';\n\n    // Hook: re-mint credentials before a fresh (re)connect. Invoked by the\n    // session only when it is client-bound OR the in-memory token is expired.\n    this._clientSession.onBeforeReconnect = async () => this.refreshCredentialForReconnect();\n\n    this.subscribeTo(this._clientSession.errors$, (error) => {\n      this._errors$.next(error);\n    });\n\n    // Server-authoritative expiry: every connect/resume response carries the\n    // decrypted SAT's `fabric_subscriber.expires_at` — the token is opaque to\n    // the client, so this is the only trustworthy source of its real expiry.\n    this.subscribeTo(this._clientSession.authorization$, (authorization) => {\n      this._refreshCoordinator?.syncExpiryFromAuthorization(\n        authorization,\n        this._credentialProvider\n      );\n      // Server-authoritative binding: cnf.jkt rides in every connect/resume\n      // authorization when the session is client-bound. Persist it so a later\n      // reload's preflight recovery re-binds via authenticate() rather than\n      // degrading the session to an unbound refresh token.\n      this.persistClientBoundMarker(Boolean(authorization?.cnf?.jkt));\n    });\n\n    await this._clientSession.connect();\n\n    // Assigned only after the session authenticates: disconnecting earlier\n    // would destroy the session that connect() above is still waiting on.\n    this._clientSession.onAuthenticationRejected = () => void this.disconnect();\n\n    // Delegate refresh arbitration to the coordinator. It only cancels the\n    // developer refresh timer when the Client Bound SAT path actually takes\n    // ownership; otherwise the developer path remains armed.\n    await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);\n\n    // Re-register user (and re-activate Client Bound SAT) after WebSocket reconnect.\n    // activate() must complete before register() so the fresh Client Bound SAT\n    // is used for the subscriber.online RPC.\n    this.subscribeTo(\n      this._clientSession.authenticated$.pipe(skip(1), filter(Boolean)),\n      async () => {\n        try {\n          await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);\n        } catch (error) {\n          logger.error('[SignalWire] Refresh re-arm after reconnect failed (non-fatal):', error);\n          this._errors$.next(\n            error instanceof Error ? error : new Error(String(error), { cause: error })\n          );\n        }\n\n        try {\n          logger.debug('[SignalWire] Re-registering user after reconnect');\n          await this.register();\n          logger.debug('[SignalWire] User re-registered successfully after reconnect');\n        } catch (error) {\n          logger.error('[SignalWire] Re-registration failed after reconnect:', error);\n          this._errors$.next(\n            error instanceof Error ? error : new Error(String(error), { cause: error })\n          );\n        }\n      }\n    );\n\n    // Create conversationManager first to inject into DirectoryManager\n    const conversationManager = new ConversationsManager(\n      this._clientSession,\n      this._deps.http,\n      () => this._deps.getUserFromAddressId(),\n      errorHandler\n    );\n\n    // Create directory with all dependencies injected\n    const directory = new DirectoryManager(\n      this._deps.http,\n      this._clientSession,\n      conversationManager,\n      errorHandler\n    );\n    this._directory$.next(directory);\n    this._clientSession.setDirectory(directory);\n\n    this._isConnected$.next(true);\n\n    // Record connection event in diagnostics\n    this._diagnosticsCollector?.record('connection', 'connected');\n\n    // Wire reconnect events to diagnostics.\n    // skip(1) skips the replayed BehaviorSubject value (false).\n    // skip(2) also skips the initial auth success (true).\n    // Only subsequent true emissions (re-authentication after reconnect) trigger this.\n    this.subscribeTo(this._clientSession.authenticated$.pipe(skip(2), filter(Boolean)), () => {\n      this._diagnosticsCollector?.record('connection', 'reconnected');\n    });\n  }\n\n  /**\n   * Observable that emits the {@link User} profile once fetched,\n   * or `undefined` before authentication completes.\n   *\n   * @example\n   * ```ts\n   * client.user$.subscribe(u => {\n   *   if (u) console.log('Logged in as', u.email);\n   * });\n   * ```\n   */\n  public get user$(): Observable<User | undefined> {\n    return this.deferEmission(this._user$.asObservable());\n  }\n\n  /** Current user snapshot, or `undefined` if not yet authenticated. */\n  public get user(): User | undefined {\n    return this._user$.value;\n  }\n\n  /**\n   * Observable that emits the {@link Directory} instance once the client is connected,\n   * or `undefined` while disconnected. Subscribe to this to safely wait for the directory\n   * to become available without risking an error.\n   *\n   * @example\n   * ```ts\n   * client.directory$.subscribe(dir => {\n   *   if (dir) dir.addresses$.subscribe(console.log);\n   * });\n   * ```\n   */\n  public get directory$(): Observable<Directory | undefined> {\n    return this.deferEmission(this._directory$.asObservable());\n  }\n\n  /**\n   * Current directory snapshot, or `undefined` if the client is not yet connected.\n   * Prefer {@link directory$} when you need to react to the directory becoming available.\n   */\n  public get directory(): Directory | undefined {\n    return this._directory$.value;\n  }\n\n  /** Observable that emits when the user registration state changes. */\n  public get isRegistered$(): Observable<boolean> {\n    return this.deferEmission(this._isRegistered$.asObservable());\n  }\n\n  /** Whether the user is currently registered. */\n  public get isRegistered(): boolean {\n    return this._isRegistered$.value;\n  }\n\n  /** Whether the client is currently connected. */\n  public get isConnected(): boolean {\n    return this._isConnected$.value;\n  }\n\n  /** Observable that emits when the connection state changes. */\n  public get isConnected$(): Observable<boolean> {\n    return this.deferEmission(this._isConnected$.asObservable());\n  }\n\n  /** Observable that emits `true` when the client is both connected and authenticated. */\n  public get ready$(): Observable<boolean> {\n    return this.publicCachedObservable('ready$', () =>\n      this._isConnected$.pipe(\n        switchMap((connected) => (connected ? this._clientSession.authenticated$ : of(false)))\n      )\n    );\n  }\n\n  /** Observable stream of errors from transport, authentication, and devices. */\n  public get errors$(): Observable<Error> {\n    return this.deferEmission(this._errors$.asObservable());\n  }\n\n  /**\n   * Observable stream of non-fatal SDK warnings.\n   *\n   * Subscribe to detect SDK behaviors that affect session liveness or developer-facing\n   * contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT\n   * refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`\n   * scope. Discriminated by `code`.\n   *\n   * Independent from {@link errors$}: existing error consumers are not notified.\n   */\n  public get warnings$(): Observable<SDKWarning> {\n    return this.deferEmission(this._warnings$.asObservable());\n  }\n\n  // ===========================================================================\n  // Resilience API\n  // ===========================================================================\n\n  /** Platform WebRTC capabilities detected at construction time. */\n  public get platformCapabilities(): PlatformCapabilities {\n    this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);\n    return this._platformCapabilities;\n  }\n\n  /** Observable that emits when the SDK auto-switches a device. */\n  public get deviceRecovered$(): Observable<DeviceRecoveryEvent> {\n    return this.deferEmission(this._deviceController.deviceRecovered$);\n  }\n\n  /**\n   * Export a structured diagnostic bundle for support/debugging.\n   * Includes connection events, call summaries, and device changes.\n   */\n  public exportDiagnostics(): SessionDiagnostics {\n    const devices = {\n      audioInput: this.audioInputDevices,\n      audioOutput: this.audioOutputDevices,\n      videoInput: this.videoInputDevices\n    };\n\n    const base: SessionDiagnostics = {\n      sdkVersion: 'unknown',\n      userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',\n      capabilities: this.platformCapabilities,\n      events: [],\n      calls: [],\n      deviceChanges: [],\n      devices\n    };\n\n    if (!this._diagnosticsCollector) {\n      return base;\n    }\n\n    const raw = this._diagnosticsCollector.export();\n    // The collector's internal types (CallDiagnosticSummary, DiagnosticEvent)\n    // are structurally compatible with the public SessionDiagnostics shape\n    // for JSON serialization / support ticket purposes.\n    return {\n      ...base,\n      sdkVersion: raw.sdkVersion,\n      userAgent: raw.userAgent,\n      events: raw.events as unknown as SessionDiagnostics['events'],\n      calls: raw.calls as unknown as SessionDiagnostics['calls'],\n      deviceChanges: raw.deviceChanges as unknown as SessionDiagnostics['deviceChanges'],\n      devices\n    };\n  }\n\n  /**\n   * Initialize resilience subsystems. Non-fatal: any failure is logged and\n   * the SDK continues working without the failing subsystem.\n   */\n  private initResilienceSubsystems(): void {\n    try {\n      this._platformCapabilities = detectPlatformCapabilities(this._options.webRTCApiProvider);\n    } catch (error) {\n      logger.warn('[SignalWire] Failed to detect platform capabilities:', error);\n    }\n\n    try {\n      this._networkMonitor = new NetworkMonitor();\n    } catch (error) {\n      logger.warn('[SignalWire] Failed to initialize NetworkMonitor:', error);\n    }\n\n    try {\n      this._visibilityController = new VisibilityController();\n\n      // Resume from suspension: when the tab becomes visible again, revalidate\n      // the credential deadline and force a refresh if the armed pre-expiry\n      // refresh was throttled past due while backgrounded. Without this, a slept\n      // tab wakes with a stale token and fails -32003 until the (late) timer\n      // eventually fires.\n      this.subscribeTo(\n        this._visibilityController.visibilityChange$.pipe(\n          filter((event) => event.to === 'visible')\n        ),\n        () => {\n          try {\n            this._refreshCoordinator?.forceRefreshIfDue();\n          } catch (error) {\n            logger.warn('[SignalWire] Resume credential revalidation failed (non-fatal):', error);\n          }\n        }\n      );\n\n      // Re-enumerate devices when page becomes visible (if pref enabled)\n      this.subscribeTo(\n        this._visibilityController.visibilityChange$.pipe(\n          filter(\n            (event) =>\n              event.to === 'visible' && PreferencesContainer.instance.refreshDevicesOnVisible\n          )\n        ),\n        () => {\n          logger.debug('[SignalWire] Page visible, re-enumerating devices');\n          // Trigger device re-enumeration by disabling/re-enabling monitoring\n          try {\n            this._deviceController.disableDeviceMonitoring();\n            this._deviceController.enableDeviceMonitoring();\n          } catch {\n            // Non-fatal\n          }\n        }\n      );\n    } catch (error) {\n      logger.warn('[SignalWire] Failed to initialize VisibilityController:', error);\n    }\n\n    try {\n      const sdkVersion: string = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'unknown';\n      this._diagnosticsCollector = new DiagnosticsCollector({\n        sdkVersion\n      });\n    } catch (error) {\n      logger.warn('[SignalWire] Failed to initialize DiagnosticsCollector:', error);\n    }\n  }\n\n  /**\n   * Disconnects the WebSocket and tears down the current session.\n   *\n   * Ends the session identified by the protocol and clears its persisted\n   * resume state (`authorization_state` + protocol) and attach records\n   * together — a later {@link connect} with the same credentials starts a\n   * fresh session and cannot reattach to the ended session's calls.\n   * Credentials and device preferences are preserved. To temporarily stop\n   * receiving inbound calls while keeping the session alive, use\n   * `unregister()` instead.\n   *\n   * The client can be reconnected by calling {@link connect} again,\n   * which creates a fresh transport and session.\n   */\n  public async disconnect(): Promise<void> {\n    // Suspend both refresh paths to prevent any timer from firing against the\n    // torn-down session. The coordinator's internal DeviceTokenManager\n    // survives (cached token + DPoP key are preserved) — the next reconnect\n    // calls activate() which resumes the pipeline.\n    this._refreshCoordinator?.suspend();\n    this._diagnosticsCollector?.record('connection', 'disconnected');\n    await this.teardownTransportAndSession();\n    this._isConnected$.next(false);\n  }\n\n  /**\n   * Tear down the current transport / session / attach manager. Safe to call\n   * when nothing has been initialized yet (e.g. first connect()).\n   */\n  private async teardownTransportAndSession(): Promise<void> {\n    const session = this._clientSession as ClientSessionManager | undefined;\n    const transport = this._transport as TransportManager | undefined;\n\n    if (session) {\n      try {\n        await session.disconnect();\n      } catch (error) {\n        logger.warn('[SignalWire] Error disconnecting previous session:', error);\n      }\n      session.destroy();\n    }\n    if (transport) {\n      transport.destroy();\n    }\n\n    this._clientSession = undefined as unknown as ClientSessionManager;\n    this._publicSession = undefined as unknown as ClientSessionWrapper;\n    this._transport = undefined as unknown as TransportManager;\n    this._attachManager = undefined;\n  }\n\n  private async waitAuthentication(): Promise<void> {\n    // Wait for client to be ready (authenticated)\n    await firstValueFrom(this.ready$.pipe(filter((ready) => ready === true)));\n  }\n\n  /**\n   * Registers the user as online to receive inbound calls and events.\n   *\n   * Waits for authentication to complete before sending the registration.\n   * If the initial attempt fails, reauthentication is attempted automatically.\n   *\n   * @throws {InvalidCredentialsError} If registration and reauthentication both fail.\n   */\n  public async register(): Promise<void> {\n    try {\n      // Wait for client session to be authenticated before registering user\n      await this.waitAuthentication();\n      await this._transport.execute(RPCExecute({ method: 'subscriber.online', params: {} }));\n      this._isRegistered$.next(true);\n      return;\n    } catch (error) {\n      if (!this._deps.credential.token) {\n        this._errors$.next(\n          error instanceof Error ? error : new Error(String(error), { cause: error })\n        );\n        throw error;\n      }\n\n      logger.debug('[SignalWire] Failed to register user, attempting credential recovery...');\n      // Not gated on the error kind. A rotation spent on a failure that turned\n      // out to be transient costs one token; skipping the re-mint on a failure\n      // that was not transient costs the session.\n      const outcome = await this.recoverAndRetry(async () => {\n        await this._transport.execute(RPCExecute({ method: 'subscriber.online', params: {} }));\n      });\n      if (outcome.ok) {\n        logger.debug('[SignalWire] Recovery restored registration');\n        this._isRegistered$.next(true);\n        return;\n      }\n      const failureCause: unknown = outcome.error ?? error;\n\n      const registerError = new InvalidCredentialsError(\n        'Failed to register user, and credential recovery also failed. Please check your credentials.',\n        {\n          cause:\n            failureCause instanceof Error\n              ? failureCause\n              : new Error(String(failureCause), { cause: failureCause })\n        }\n      );\n      this._errors$.next(registerError);\n      throw registerError;\n    }\n  }\n\n  /**\n   * Unregisters the user, going offline for inbound calls.\n   *\n   * The WebSocket connection remains open; use {@link disconnect} to fully close it.\n   */\n  public async unregister(): Promise<void> {\n    try {\n      await this._transport.execute(RPCExecute({ method: 'subscriber.offline', params: {} }));\n      this._isRegistered$.next(false);\n    } catch (error) {\n      logger.error('[SignalWire] Failed to unregister user:', error);\n      this._errors$.next(\n        error instanceof Error ? error : new Error(String(error), { cause: error })\n      );\n      throw error;\n    }\n  }\n\n  /**\n   * Places an outbound call to the given destination.\n   *\n   * Waits for authentication before dialing. Media options are merged from\n   * saved preferences, destination query parameters (e.g. `?channel=video`),\n   * and the provided `options` (highest priority).\n   *\n   * Returns a {@link Call} in `'ringing'` state. Subscribe to {@link Call.status$}\n   * to track progression through `'connected'` → `'disconnected'`.\n   *\n   * Local media acquisition is deliberately unbounded: an unanswered permission\n   * prompt leaves this promise pending indefinitely, so apply your own bound if\n   * your UI needs one. The 12 s signaling budget starts only once acquisition\n   * settles.\n   *\n   * @param destination - Address URI string (e.g. `'/public/my-room'`) or {@link Address} instance.\n   * @param options - Media and dial options (audio/video, device constraints). Overrides defaults.\n   * @returns The created {@link Call} instance.\n   * @throws {Error} If authentication is not complete or call creation fails.\n   *\n   * @example\n   * ```ts\n   * const call = await client.dial('/public/conference', {\n   *   audio: true,\n   *   video: true,\n   * });\n   * call.status$.subscribe(status => console.log('Call:', status));\n   * ```\n   */\n  public async dial(destination: string | Address, options: DialOptions = {}): Promise<Call> {\n    const computed_options = {\n      ...PreferencesContainer.instance.preferredMediaOptions,\n      ...buildOptionsFromDestination(destination),\n      ...options\n    };\n\n    // Wait for client to be ready\n    await this.waitAuthentication();\n\n    logger.debug('[SignalWire] Dialing with options:', computed_options);\n    try {\n      return await this._clientSession.createOutboundCall(destination, computed_options);\n    } catch (error) {\n      // A -32002/-32003 on the invite means the session token went stale\n      // (e.g. a throttled refresh timer). Recover the credential and retry the\n      // dial once. Any other error, or a failed recovery, propagates.\n      if (!isRecoverableAuthError(error)) {\n        throw error;\n      }\n      logger.debug(\n        '[SignalWire] Dial hit a recoverable auth error; recovering the session and retrying'\n      );\n      const outcome = await this.recoverAndRetry(async () => {\n        await this.waitAuthentication();\n        return this._clientSession.createOutboundCall(destination, computed_options);\n      });\n      if (outcome.ok) {\n        return outcome.value;\n      }\n      throw outcome.error ?? error;\n    }\n  }\n\n  /**\n   * Runs a multi-phase connectivity test against the given destination.\n   *\n   * The test checks:\n   *   1. **Signaling** -- WebSocket connected, RTT measurement\n   *   2. **Devices** -- getUserMedia succeeds with selected (or specified) devices\n   *   3. **ICE/TURN** -- gathers ICE candidates to verify STUN/TURN reachability\n   *   4. **Media/bandwidth** (unless `skipMediaTest`) -- dials the destination,\n   *      collects getStats() for `duration` seconds, computes bandwidth estimates\n   *\n   * @param destination - A destination to dial for the media test (e.g. `'/private/network-test'`).\n   * @param options - Preflight options (duration, skipMediaTest, device overrides).\n   * @returns A {@link PreflightResult} describing connectivity health.\n   *\n   * @example\n   * ```ts\n   * const result = await client.preflight('/private/network-test', { duration: 5 });\n   * if (!result.ok) console.warn('Connectivity issues:', result.warnings);\n   * ```\n   */\n  public async preflight(\n    destination: string,\n    options?: PreflightOptions\n  ): Promise<PreflightResult> {\n    const iceServers =\n      this._clientSession.iceServers ?? PreferencesContainer.instance.iceServers ?? [];\n    const isConnected = this._isConnected$.value;\n    const transportRtt = 0; // RTT is best-effort; 0 when not measurable\n\n    const runner = new PreflightRunner(\n      this._deviceController,\n      iceServers,\n      isConnected,\n      transportRtt,\n      async (dest, opts) => this.dial(dest, opts),\n      options\n    );\n\n    return runner.run(destination);\n  }\n\n  /** The underlying client session for advanced RPC operations. */\n  public get session(): ClientSessionWrapper {\n    return this._publicSession;\n  }\n\n  // DeviceController interface implementation\n\n  /** Observable list of available audio input (microphone) devices. */\n  public get audioInputDevices$(): Observable<MediaDeviceInfo[]> {\n    return this.deferEmission(this._deviceController.audioInputDevices$);\n  }\n\n  /** Current snapshot of available audio input devices. */\n  public get audioInputDevices(): MediaDeviceInfo[] {\n    return this._deviceController.audioInputDevices;\n  }\n\n  /** Observable list of available audio output (speaker) devices. */\n  public get audioOutputDevices$(): Observable<MediaDeviceInfo[]> {\n    return this.deferEmission(this._deviceController.audioOutputDevices$);\n  }\n\n  /** Current snapshot of available audio output devices. */\n  public get audioOutputDevices(): MediaDeviceInfo[] {\n    return this._deviceController.audioOutputDevices;\n  }\n\n  /** Observable list of available video input (camera) devices. */\n  public get videoInputDevices$(): Observable<MediaDeviceInfo[]> {\n    return this.deferEmission(this._deviceController.videoInputDevices$);\n  }\n\n  /** Current snapshot of available video input devices. */\n  public get videoInputDevices(): MediaDeviceInfo[] {\n    return this._deviceController.videoInputDevices;\n  }\n\n  /** Observable of the currently selected audio input device. */\n  public get selectedAudioInputDevice$(): Observable<MediaDeviceInfo | null> {\n    return this.deferEmission(this._deviceController.selectedAudioInputDevice$);\n  }\n  /** Observable of the currently selected audio output device. */\n  public get selectedAudioOutputDevice$(): Observable<MediaDeviceInfo | null> {\n    return this.deferEmission(this._deviceController.selectedAudioOutputDevice$);\n  }\n  /** Observable of the currently selected video input device. */\n  public get selectedVideoInputDevice$(): Observable<MediaDeviceInfo | null> {\n    return this.deferEmission(this._deviceController.selectedVideoInputDevice$);\n  }\n  /** Currently selected audio input device, or `null` if none. */\n  public get selectedAudioInputDevice(): MediaDeviceInfo | null {\n    return this._deviceController.selectedAudioInputDevice;\n  }\n  /** Currently selected audio output device, or `null` if none. */\n  public get selectedAudioOutputDevice(): MediaDeviceInfo | null {\n    return this._deviceController.selectedAudioOutputDevice;\n  }\n  /** Currently selected video input device, or `null` if none. */\n  public get selectedVideoInputDevice(): MediaDeviceInfo | null {\n    return this._deviceController.selectedVideoInputDevice;\n  }\n  /** Media track constraints for the selected audio input device. Returns `false` when disabled. */\n  public get selectedAudioInputDeviceConstraints(): MediaTrackConstraints | boolean {\n    return this._deviceController.selectedAudioInputDeviceConstraints;\n  }\n  /** Media track constraints for the selected video input device. Returns `false` when disabled. */\n  public get selectedVideoInputDeviceConstraints(): MediaTrackConstraints | boolean {\n    return this._deviceController.selectedVideoInputDeviceConstraints;\n  }\n\n  /** Converts a `MediaDeviceInfo` to track constraints suitable for `getUserMedia`. */\n  public deviceInfoToConstraints(deviceInfo: MediaDeviceInfo | null): MediaTrackConstraints {\n    return this._deviceController.deviceInfoToConstraints(deviceInfo);\n  }\n\n  /** Sets the preferred audio input device. */\n  public selectAudioInputDevice(device: MediaDeviceInfo | null): void {\n    this._deviceController.selectAudioInputDevice(device);\n  }\n\n  /** Sets the preferred video input device. */\n  public selectVideoInputDevice(device: MediaDeviceInfo | null): void {\n    this._deviceController.selectVideoInputDevice(device);\n  }\n\n  /** Sets the preferred audio output device. */\n  public selectAudioOutputDevice(device: MediaDeviceInfo | null): void {\n    this._deviceController.selectAudioOutputDevice(device);\n  }\n\n  /**\n   * Apply the currently selected audio output device to an HTMLMediaElement\n   * (e.g. the `<audio>` or `<video>` element the consumer attached the\n   * remote stream to). Uses `HTMLMediaElement.setSinkId` under the hood.\n   * Returns a `Promise<boolean>`: `true` if the sink was applied,\n   * `false` if the browser doesn't support `setSinkId` or no device is\n   * selected.\n   *\n   * @example\n   * ```ts\n   * audioEl.srcObject = call.remoteStream;\n   * await client.applySelectedAudioOutputDevice(audioEl);\n   * ```\n   */\n  public async applySelectedAudioOutputDevice(element: HTMLMediaElement): Promise<boolean> {\n    const device = this._deviceController.selectedAudioOutputDevice;\n    if (!device?.deviceId) {\n      return false;\n    }\n    const withSink = element as HTMLMediaElement & {\n      setSinkId?: (sinkId: string) => Promise<void>;\n    };\n    if (typeof withSink.setSinkId !== 'function') {\n      logger.warn('[SignalWire] setSinkId not supported on this element / browser');\n      return false;\n    }\n    try {\n      await withSink.setSinkId(device.deviceId);\n      return true;\n    } catch (error) {\n      logger.warn('[SignalWire] Failed to apply audio output device:', error);\n      return false;\n    }\n  }\n\n  /** Starts monitoring for media device changes (connect/disconnect). */\n  public enableDeviceMonitoring(): void {\n    this._deviceController.enableDeviceMonitoring();\n  }\n\n  /** Stops monitoring for media device changes. */\n  public disableDeviceMonitoring(): void {\n    this._deviceController.disableDeviceMonitoring();\n  }\n\n  /**\n   * Returns the capabilities of a media device.\n   * @param deviceInfo - The device to query.\n   * @returns The device capabilities, or `null` if unavailable.\n   */\n  public async getDeviceCapabilities(\n    deviceInfo: MediaDeviceInfo\n  ): Promise<MediaTrackCapabilities | null> {\n    return this._deviceController.getDeviceCapabilities(deviceInfo);\n  }\n\n  /**\n   * Checks whether a device is still available and usable.\n   * @param deviceInfo - The device to validate, or `null`.\n   * @returns `true` if the device is valid and available. Returns `false` for `null`, audio output devices, or unavailable devices.\n   */\n  public async isValidDevice(deviceInfo: MediaDeviceInfo | null): Promise<boolean> {\n    return this._deviceController.isValidDevice(deviceInfo);\n  }\n\n  // ===========================================================================\n  // DeviceController passthrough: storage and device state\n  // ===========================================================================\n\n  /** Injects a storage manager into the device controller for persistence. */\n  public setStorageManager(\n    storageManager: import('../managers/StorageManager').StorageManager\n  ): void {\n    this._deviceController.setStorageManager(storageManager);\n  }\n\n  /** Clears all device state and re-enumerates. */\n  public async clearDeviceState(): Promise<void> {\n    return this._deviceController.clearDeviceState();\n  }\n\n  /** Forces a device re-enumeration. */\n  public async enumerateDevices(): Promise<void> {\n    return this._deviceController.enumerateDevices();\n  }\n\n  // ===========================================================================\n  // Section 5.9: Intentional device disable\n  // ===========================================================================\n\n  /** Disables audio input (receive-only mode). No audio track will be acquired. */\n  public disableAudioInput(): void {\n    this._deviceController.disableAudioInput();\n  }\n\n  /** Re-enables audio input, restoring the last selection or auto-selecting. */\n  public enableAudioInput(): void {\n    this._deviceController.enableAudioInput();\n  }\n\n  /** Disables video input (receive-only mode). No video track will be acquired. */\n  public disableVideoInput(): void {\n    this._deviceController.disableVideoInput();\n  }\n\n  /** Re-enables video input, restoring the last selection or auto-selecting. */\n  public enableVideoInput(): void {\n    this._deviceController.enableVideoInput();\n  }\n\n  /** Observable that emits `true` when video input is disabled (receive-only). */\n  public get videoInputDisabled$(): Observable<boolean> {\n    return this.deferEmission(this._deviceController.videoInputDisabled$);\n  }\n\n  /** Observable that emits `true` when audio input is disabled (receive-only). */\n  public get audioInputDisabled$(): Observable<boolean> {\n    return this.deferEmission(this._deviceController.audioInputDisabled$);\n  }\n\n  /** Whether video input is currently disabled. */\n  public get videoInputDisabled(): boolean {\n    return this._deviceController.videoInputDisabled;\n  }\n\n  /** Whether audio input is currently disabled. */\n  public get audioInputDisabled(): boolean {\n    return this._deviceController.audioInputDisabled;\n  }\n\n  // ===========================================================================\n  // Section 5.10: Permissions bootstrapping\n  // ===========================================================================\n\n  /**\n   * Triggers the browser's media permission dialog and captures the user's device selections.\n   *\n   * @param options - Which permissions to request.\n   * @param options.audio - Whether to request audio permission.\n   * @param options.video - Whether to request video permission.\n   * @returns The permission result with selected devices.\n   */\n  public async requestMediaPermissions(\n    options: { audio?: boolean; video?: boolean } = { audio: true, video: true }\n  ): Promise<PermissionResult> {\n    const constraints: MediaStreamConstraints = {\n      audio: options.audio ?? false,\n      video: options.video ?? false\n    };\n\n    let audioGranted = false;\n    let videoGranted = false;\n    let selectedAudioDevice: MediaDeviceInfo | undefined;\n    let selectedVideoDevice: MediaDeviceInfo | undefined;\n\n    try {\n      const stream = await this._deps.webRTCApiProvider.mediaDevices.getUserMedia(constraints);\n      const tracks = stream.getTracks();\n\n      for (const track of tracks) {\n        const settings = track.getSettings();\n        if (track.kind === 'audio') {\n          audioGranted = true;\n          if (settings.deviceId) {\n            selectedAudioDevice = this.audioInputDevices.find(\n              (d) => d.deviceId === settings.deviceId\n            );\n          }\n        } else if (track.kind === 'video') {\n          videoGranted = true;\n          if (settings.deviceId) {\n            selectedVideoDevice = this.videoInputDevices.find(\n              (d) => d.deviceId === settings.deviceId\n            );\n          }\n        }\n        track.stop();\n      }\n    } catch (error) {\n      logger.warn('[SignalWire] Media permission request failed:', error);\n    }\n\n    // Re-enumerate devices (labels are now available after permission grant)\n    await this._deviceController.enumerateDevices();\n\n    // Re-resolve selected devices after re-enumeration\n    if (audioGranted && selectedAudioDevice) {\n      const audioDeviceId = selectedAudioDevice.deviceId;\n      const resolved = this.audioInputDevices.find((d) => d.deviceId === audioDeviceId);\n      selectedAudioDevice = resolved ?? selectedAudioDevice;\n    }\n    if (videoGranted && selectedVideoDevice) {\n      const videoDeviceId = selectedVideoDevice.deviceId;\n      const resolved = this.videoInputDevices.find((d) => d.deviceId === videoDeviceId);\n      selectedVideoDevice = resolved ?? selectedVideoDevice;\n    }\n\n    // Auto-select the browser-picked devices if no explicit selection yet\n    if (audioGranted && selectedAudioDevice && !this.selectedAudioInputDevice) {\n      this.selectAudioInputDevice(selectedAudioDevice);\n    }\n    if (videoGranted && selectedVideoDevice && !this.selectedVideoInputDevice) {\n      this.selectVideoInputDevice(selectedVideoDevice);\n    }\n\n    return {\n      audio: audioGranted,\n      video: videoGranted,\n      selectedAudioDevice,\n      selectedVideoDevice\n    };\n  }\n\n  // ===========================================================================\n  // Section 5.11: Factory reset\n  // ===========================================================================\n\n  /**\n   * Clears all SDK-persisted state and resets to defaults.\n   *\n   * This clears device preferences, device history, authorization state,\n   * attached call IDs, and all SDK storage keys, then re-enumerates devices.\n   */\n  public async resetToDefaults(): Promise<void> {\n    // 1. Clear all SDK storage keys\n    await this._deps.storage.clearAll();\n\n    // 2. Reset PreferencesContainer to defaults\n    const prefs = PreferencesContainer.instance;\n    prefs.preferredAudioInput = null;\n    prefs.preferredAudioOutput = null;\n    prefs.preferredVideoInput = null;\n\n    // 3. Clear device history and selection\n    await this._deviceController.clearDeviceState();\n  }\n\n  /**\n   * Destroys the client, clearing timers and releasing all resources.\n   *\n   * Intentionally destroying the client ends its session: the resume state\n   * (`authorization_state` + protocol) and the attach records are both\n   * cleared. Credentials and device preferences are preserved — use\n   * {@link resetToDefaults} for a full wipe. To temporarily stop receiving\n   * inbound calls while keeping the session alive, use `unregister()`.\n   */\n  public override destroy(): void {\n    this._refreshCoordinator?.destroy();\n    this._refreshCoordinator = undefined;\n    this._refreshHttp?.destroy();\n    this._refreshHttp = undefined;\n    this._dpopManager?.destroy();\n\n    // Intentionally destroying the client ends its session: clear the\n    // resume state (authorization_state + protocol) AND the attach records\n    // together (they are a coupled unit). Run before tearing down the\n    // transport/session so the storage writes still go through. Credentials\n    // and device preferences survive — resetToDefaults() is the full wipe.\n    // Session/transport only exist after a successful connect() (and are\n    // reset by cleanupSession) — a never-connected or already-disconnected\n    // client must still destroy cleanly.\n    const session = this._clientSession as ClientSessionManager | undefined;\n    void session?.teardownSessionState();\n\n    // Stop the transport to prevent reconnection loops\n    (this._transport as TransportManager | undefined)?.destroy();\n    session?.destroy();\n\n    // Destroy resilience subsystems\n    try {\n      this._networkMonitor?.destroy();\n    } catch {\n      /* non-fatal */\n    }\n    try {\n      this._visibilityController?.destroy();\n    } catch {\n      /* non-fatal */\n    }\n    try {\n      this._diagnosticsCollector?.destroy();\n    } catch {\n      /* non-fatal */\n    }\n    this._networkMonitor = undefined;\n    this._visibilityController = undefined;\n    this._diagnosticsCollector = undefined;\n\n    super.destroy();\n  }\n}\nexport { SignalWire };\n","import { RequestError, RequestTimeoutError } from '../core/errors';\nimport { getLogger } from '../utils/logger';\n\nimport type { CredentialProvider } from './interfaces';\n\nconst logger = getLogger();\n\n/** Credential provider that exchanges an embed token for a SAT via the host's token endpoint. */\nexport class EmbedTokenCredentialProvider implements CredentialProvider {\n  constructor(\n    private host: string,\n    private embedToken: string\n  ) {}\n\n  private async fetchSAT(): Promise<string> {\n    const url = `https://${this.host}/api/fabric/embeds/tokens`;\n\n    const timeout = 10000; // 10 seconds\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n    try {\n      const response = await fetch(url, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ token: this.embedToken }),\n        signal: controller.signal\n      });\n\n      clearTimeout(timeoutId);\n\n      if (response.ok) {\n        const data = (await response.json()) as { token: string };\n        return data.token;\n      }\n\n      throw new RequestError(\n        `Failed to fetch SAT using embed token: ${response.status} ${response.statusText}`\n      );\n    } catch (error) {\n      clearTimeout(timeoutId);\n\n      if (error instanceof Error && error.name === 'AbortError') {\n        throw new RequestTimeoutError(`Request timeout after ${timeout}ms`, { cause: error });\n      }\n\n      logger.error('[EmbedCredentialProvider] Request failed:', error);\n      throw error;\n    }\n  }\n\n  public async authenticate(): Promise<{ token: string; expiry_at: number }> {\n    const sat = await this.fetchSAT();\n    const expiryAt = Date.now() + 3600 * 1000;\n    return { token: sat, expiry_at: expiryAt };\n  }\n\n  public async refresh(): Promise<{ token: string; expiry_at: number }> {\n    return this.authenticate();\n  }\n}\n","import { first } from 'rxjs';\n\nimport { SignalWire } from '../clients/SignalWire';\nimport { DependencyError } from '../core/errors';\nimport { EmbedTokenCredentialProvider } from '../dependencies/EmbedTokenCredentialProvider';\n\nimport type { Call } from '../core/entities/types/call.types';\n\n/** Options for {@link embeddableCall}. */\nexport interface EmbeddableCallOptions {\n  /** Destination URI to call. */\n  to: string;\n  /** Embed token for authentication. */\n  embedToken: string;\n  /** SignalWire host URL. */\n  host: string;\n}\n\n/**\n * Creates a call using an embed token for simple, embeddable integrations.\n *\n * Handles client creation, authentication, and dialing in a single call.\n *\n * @param options - Embed token, host, and destination.\n * @returns The created {@link Call} instance.\n */\nexport async function embeddableCall(options: EmbeddableCallOptions): Promise<Call> {\n  const { to, embedToken, host } = options;\n  const requiredFailed = [];\n\n  if (!to) {\n    requiredFailed.push('to');\n  }\n\n  if (!embedToken) {\n    requiredFailed.push('embedToken');\n  }\n\n  if (!host) {\n    requiredFailed.push('host');\n  }\n\n  if (requiredFailed.length > 0) {\n    return Promise.reject(\n      new DependencyError(`Missing required options: ${requiredFailed.join(', ')}`)\n    );\n  }\n\n  const credentialProvider = new EmbedTokenCredentialProvider(host, embedToken);\n  const client = new SignalWire(credentialProvider);\n\n  const call = await client.dial(to);\n\n  // Destroy the client when the call ends to clean up WebSocket, timers, and session\n  call.status$.pipe(first((status) => status === 'destroyed')).subscribe(() => {\n    void client.disconnect();\n  });\n\n  return call;\n}\n","import type { CredentialProvider } from './interfaces';\nimport type { SDKCredential } from '../core/types/common.types';\n\n/**\n * Credential provider that returns a fixed set of credentials.\n *\n * Use when the token is already available (e.g. from a backend endpoint).\n *\n * @example\n * ```ts\n * const provider = new StaticCredentialProvider({ token: 'my-sat-token' });\n * const client = new SignalWire(provider);\n * ```\n */\nexport class StaticCredentialProvider implements CredentialProvider {\n  constructor(private credentials: SDKCredential) {}\n\n  /** Returns the static credentials. */\n  public async authenticate(): Promise<SDKCredential> {\n    return Promise.resolve(this.credentials);\n  }\n}\n","/**\n * Public API entry point for @signalwire/js\n *\n * IMPORTANT: This file should NEVER be imported by internal modules.\n * Internal modules must import directly from source files.\n *\n * This file defines the minimal public API surface for external consumers.\n */\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\nexport { SignalWire } from './clients/SignalWire';\n\nexport { embeddableCall } from './utils/embeddableCall';\n\nexport { StaticCredentialProvider } from './dependencies/StaticCredentialProvider';\nexport { EmbedTokenCredentialProvider } from './dependencies/EmbedTokenCredentialProvider';\nexport { getLogger } from './utils/logger';\nexport type {\n  CredentialProvider,\n  AuthenticateContext,\n  WebRTCApiProvider,\n  WebRTCMediaDevices,\n  Storage\n} from './dependencies/interfaces';\n// ============================================================================\n// Error Types (exported as values for instanceof checks)\n// ============================================================================\n\nexport {\n  CallCreateError,\n  CallNotReadyError,\n  CollectionFetchError,\n  DeviceTokenError,\n  DPoPInitError,\n  InvalidCredentialsError,\n  AuxiliaryLegCancelledError,\n  AuxiliaryLegTimeoutError,\n  MediaAccessError,\n  MediaTrackError,\n  MessageParseError,\n  ParticipantNotReadyError,\n  ScreenShareAlreadyActiveError,\n  TokenRefreshError,\n  UnexpectedError,\n  VertoPongError,\n  RecoveryError,\n  OverconstrainedFallbackError,\n  PreflightError\n} from './core/errors';\nexport type { CallError, CallErrorKind } from './core/errors';\n\n// ============================================================================\n// Domain Model Classes (exported as values for instanceof and type guards)\n// ============================================================================\n\nexport { Address } from './core/entities/Address';\nexport { WebRTCCall } from './core/entities/Call';\nexport { Participant, SelfParticipant } from './core/entities/Participant';\nexport { User } from './core/entities/User';\nexport { SelfCapabilities } from './core/capabilities';\n\n// ============================================================================\n// Domain Model Interfaces\n// ============================================================================\n\nexport type { Directory } from './core/entities/Directory';\nexport type { SessionState } from './interfaces/SessionState';\n\n// ============================================================================\n// Type Guards (exported as values for runtime checks)\n// ============================================================================\n\nexport { isSelfParticipant } from './core/entities/Participant';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport { ClientPreferences } from './containers/PreferencesContainer';\n\n// ============================================================================\n// Logging\n// ============================================================================\n\nexport { setLogger, setLogLevel, setDebugOptions } from './utils/logger';\nexport type { SDKLogger, LogLevel, DebugOptions } from './utils/logger';\n\n// ============================================================================\n// Essential Types (type-only exports)\n// ============================================================================\n\n// Client types\nexport type { SignalWireOptions, DialOptions } from './clients/SignalWire';\n\n// Call types\nexport type {\n  Call,\n  CallOptions,\n  CallStatus,\n  CallState,\n  CallParticipant,\n  CallSelfParticipant,\n  CallAddress\n} from './core/entities/types/call.types';\n\n// Types used in Call interface methods\nexport type { TransferOptions, ScreenShareStatus } from './managers/types/verto-manager.types';\nexport type { VideoPosition, CallDirection, Capability } from './core/types/call.types';\nexport type {\n  JSONRPCRequest,\n  JSONRPCResponse,\n  JSONRPCSuccessResponse,\n  JSONRPCErrorResponse\n} from './core/RPCMessages/types/base';\nexport type { PendingRPCOptions } from './core/utils';\n\n// Types needed by web-components\nexport type { LayoutLayer } from './core/RPCMessages/types/common';\n\n// Media types\nexport type {\n  MediaOptions,\n  MediaDirections,\n  MediaDirection,\n  ScreenShareOptions\n} from './core/types/media.types';\n\n// Conversation types\nexport type { AddressHistory, TextMessage } from './core/types/conversation.types';\n\n// Participant types\nexport type { ExecuteMethod } from './core/entities/Participant';\nexport type { SelectDeviceOptions } from './core/entities/types/participant.types';\nexport type { UserPresence } from './core/entities/User';\n\n// Common types (credentials, adapters)\nexport type { SDKCredential, WebSocketAdapter, NodeSocketAdapter } from './core/types/common.types';\n\n// Warning types (non-fatal events on SignalWire.warnings$)\nexport type {\n  SDKWarning,\n  CredentialRefreshFallbackWarning,\n  CredentialRefreshFallbackReason,\n  CredentialNoRefreshHandlerWarning\n} from './core/types/warnings.types';\n\n// DPoP types (for consumers inspecting user.satClaims)\nexport type { SATClaims } from './core/types/crypto.types';\n\n// Capability types\nexport type {\n  OnOffCapability,\n  MemberCapabilities,\n  CallCapabilitiesState\n} from './core/capabilities/types';\n\n// Device types\nexport type { DeviceController } from './interfaces/DeviceController';\n\n// Stats monitor types — backward-compatible aliases (now the same types as NetworkIssue/NetworkMetrics)\nexport type {\n  NetworkIssue as CallNetworkIssue,\n  NetworkMetrics as CallNetworkMetrics\n} from './controllers/RTCStatsMonitor';\n\n// Resilience types (NetworkIssue and NetworkMetrics are canonical re-exports from RTCStatsMonitor)\nexport type {\n  NetworkIssue,\n  NetworkMetrics,\n  RecoveryEvent,\n  RecoveryState,\n  QualityLevel,\n  DeviceRecoveryEvent,\n  StoredDevicePreference,\n  ConstraintFallbackEvent,\n  PermissionResult,\n  PlatformCapabilities,\n  PreflightOptions,\n  PreflightResult,\n  AudioConstraintsEvent,\n  MediaParamsEvent,\n  SessionDiagnostics,\n  DiagnosticEvent,\n  CallDiagnosticSummary,\n  ResilienceCallStatus\n} from './core/types/resilience.types';\n\n// ============================================================================\n// DO NOT EXPORT (Internal Implementation):\n// - Behaviors (Destroyable, Fetchable)\n// - Controllers (RTCPeerConnectionController, etc.)\n// - Managers (DirectoryManager, TransportManager, ConversationsManager, etc.)\n// - Containers (DependencyContainer, PreferencesContainer singleton)\n// - Internal utilities and helpers\n// - RPC Messages and protocol internals\n// ============================================================================\n\n// ============================================================================\n// Library Ready Event (for async/dynamic script loading)\n// ============================================================================\n\n/**\n * Library version from package.json, injected at build time.\n */\nexport const version: string = __VERSION__;\n\n/**\n * Flag indicating the library has been loaded and is ready to use.\n * For UMD builds: `window.SignalWire.ready`\n * For ES modules: `import { ready } from '@signalwire/js'`\n */\nexport const ready: boolean = true;\n\n/**\n * Emits 'signalwire:js:ready' event when the library is loaded.\n *\n * Scripts that might load BEFORE the library (check flag first):\n *    ```js\n *    if (window.SignalWire?.ready) {\n *      // Library already loaded, use it directly\n *      initApp();\n *    } else {\n *      window.addEventListener('signalwire:js:ready', () => initApp());\n *    }\n *    ```\n */\nconst emitReadyEvent = (): void => {\n  if (typeof window !== 'undefined') {\n    const event = new CustomEvent('signalwire:js:ready', {\n      detail: { version: __VERSION__ }\n    });\n    window.dispatchEvent(event);\n  }\n};\n\nemitReadyEvent();\n"],"mappings":"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"}