{"version":3,"sources":["../src/room.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Mutex } from '@livekit/mutex';\nimport { EncryptionState, type EncryptionType } from '@livekit/rtc-ffi-bindings';\nimport type {\n  FfiEvent,\n  GetSessionStatsCallback,\n  GetSessionStatsResponse,\n} from '@livekit/rtc-ffi-bindings';\nimport { DisconnectReason, type OwnedParticipant } from '@livekit/rtc-ffi-bindings';\nimport { type DisconnectCallback, type TrackPublicationInfo } from '@livekit/rtc-ffi-bindings';\nimport {\n  ByteStreamReaderReadIncrementalRequest,\n  type ByteStreamReaderReadIncrementalResponse,\n  type ConnectCallback,\n  ConnectRequest,\n  type ConnectResponse,\n  type ConnectionQuality,\n  ConnectionState,\n  ContinualGatheringPolicy,\n  type DataPacketKind,\n  DataStream_Chunk,\n  type DisconnectResponse,\n  RoomDataStreamOptions as FfiRoomDataStreamOptions,\n  RoomOptions as FfiRoomOptions,\n  type IceServer,\n  IceTransportType,\n  type OwnedByteStreamReader,\n  type OwnedTextStreamReader,\n  type ReadyForRoomEventResponse,\n  type RoomInfo,\n  type SimulateScenarioCallback,\n  type SimulateScenarioKind,\n  type SimulateScenarioResponse,\n  TextStreamReaderReadIncrementalRequest,\n  type TextStreamReaderReadIncrementalResponse,\n} from '@livekit/rtc-ffi-bindings';\nimport { TrackKind } from '@livekit/rtc-ffi-bindings';\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport EventEmitter from 'events';\nimport { ByteStreamReader, TextStreamReader } from './data_streams/stream_reader.js';\nimport { type ByteStreamHandler, type TextStreamHandler } from './data_streams/types.js';\nimport type { E2EEOptions } from './e2ee.js';\nimport { E2EEManager, defaultE2EEOptions } from './e2ee.js';\nimport { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';\nimport { log } from './log.js';\nimport type { Participant } from './participant.js';\nimport { LocalParticipant, RemoteParticipant } from './participant.js';\nimport type { LocalTrack, RemoteTrack } from './track.js';\nimport { RemoteAudioTrack, RemoteVideoTrack } from './track.js';\nimport type { LocalTrackPublication, TrackPublication } from './track_publication.js';\nimport { RemoteTrackPublication } from './track_publication.js';\nimport type { ChatMessage } from './types.js';\nimport {\n  bigIntToNumber,\n  byteStreamInfoFromProto,\n  numberToBigInt,\n  textStreamInfoFromProto,\n} from './utils.js';\n\nexport interface RtcConfiguration {\n  iceTransportType: IceTransportType;\n  continualGatheringPolicy: ContinualGatheringPolicy;\n  iceServers: IceServer[];\n}\n\nexport const defaultRtcConfiguration: RtcConfiguration = {\n  iceTransportType: IceTransportType.TRANSPORT_ALL,\n  continualGatheringPolicy: ContinualGatheringPolicy.GATHER_CONTINUALLY,\n  iceServers: [],\n};\n\nexport interface RoomDataStreamOptions {\n  /**\n   * Maximum decompressed payload size in bytes accepted for a single incoming\n   * data stream. Incoming streams exceeding this limit terminate with an error\n   * on the receiving side. Unset falls back to the SDK default of 5gb.\n   */\n  maxPayloadByteLength?: number;\n}\n\nexport interface RoomOptions {\n  autoSubscribe: boolean;\n  dynacast: boolean;\n  /**\n   * @deprecated Use `encryption` instead. See x for details\n   */\n  e2ee?: E2EEOptions;\n  encryption?: E2EEOptions;\n  rtcConfig?: RtcConfiguration;\n  /** Options controlling data stream behavior for this room. */\n  dataStream?: RoomDataStreamOptions;\n}\n\nexport const defaultRoomOptions = new FfiRoomOptions({\n  autoSubscribe: true,\n  dynacast: false,\n  e2ee: undefined,\n  encryption: undefined,\n  rtcConfig: undefined,\n  adaptiveStream: false,\n  joinRetries: 1,\n});\n\nexport class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>) {\n  private info?: RoomInfo;\n  private ffiHandle?: FfiHandle;\n  /**\n   * used to ensure events are processed sequentially and allow for\n   * the local participant to acquire the lock while doing state updates related to FFI events\n   * before processing the next events\n   */\n  private ffiEventLock = new Mutex();\n\n  // Active incoming stream readers keyed by their FFI reader handle id. Each entry\n  // owns the FfiClient listener feeding reader events into the ReadableStream.\n  private streamReaders = new Map<\n    bigint,\n    {\n      controller: ReadableStreamDefaultController<DataStream_Chunk>;\n      listener: (ev: FfiEvent) => void;\n    }\n  >();\n  private byteStreamHandlers = new Map<string, ByteStreamHandler>();\n  private textStreamHandlers = new Map<string, TextStreamHandler>();\n\n  private preConnectEvents: FfiEvent[] = [];\n\n  // Aborted on disconnect to cancel any pending FfiClient.waitFor() listeners,\n  // preventing them from leaking when the room goes away.\n  private disconnectController = new AbortController();\n\n  // Guards cleanupOnDisconnect so the ConnectionStateChanged/Disconnected\n  // events fire exactly once, no matter which path (explicit disconnect()\n  // vs. FFI 'disconnected' event) wins the race.\n  private hasCleanedUp = false;\n\n  private _token?: string;\n  private _serverUrl?: string;\n  private _connectionState: ConnectionState = ConnectionState.CONN_DISCONNECTED;\n\n  e2eeManager?: E2EEManager;\n\n  remoteParticipants: Map<string, RemoteParticipant> = new Map();\n  localParticipant?: LocalParticipant;\n\n  constructor() {\n    super();\n  }\n\n  get connectionState() {\n    return this._connectionState;\n  }\n\n  get name(): string | undefined {\n    return this.info?.name;\n  }\n\n  get metadata(): string | undefined {\n    return this.info?.metadata;\n  }\n\n  get isConnected(): boolean {\n    return this.ffiHandle != undefined && this.connectionState != ConnectionState.CONN_DISCONNECTED;\n  }\n\n  /** @internal */\n  get token(): string | undefined {\n    return this._token;\n  }\n\n  /** @internal */\n  get serverUrl(): string | undefined {\n    return this._serverUrl;\n  }\n\n  // Shared promise for concurrent getSid() callers. Without this, each call\n  // registers its own RoomSidChanged + Disconnected listeners, and if many\n  // calls race only one of each pair is cleaned up — leaking the rest.\n  private sidPromise?: Promise<string>;\n\n  /**\n   * Gets the room's server ID. This ID is assigned by the LiveKit server\n   * and is unique for each room session.\n   * SID is assigned asynchronously after connection.\n   * @returns Promise that resolves to the room's server ID, or empty string if not connected\n   */\n  async getSid(): Promise<string> {\n    if (!this.isConnected) {\n      return '';\n    }\n    if (this.info?.sid && this.info.sid !== '') {\n      return this.info.sid;\n    }\n    if (!this.sidPromise) {\n      this.sidPromise = new Promise<string>((resolve, reject) => {\n        const handleDisconnect = () => {\n          this.off(RoomEvent.RoomSidChanged, handleRoomUpdate);\n          this.sidPromise = undefined;\n          reject('Room disconnected before room server id was available');\n        };\n        const handleRoomUpdate = (sid: string) => {\n          if (sid !== '') {\n            this.off(RoomEvent.RoomSidChanged, handleRoomUpdate);\n            this.off(RoomEvent.Disconnected as any, handleDisconnect);\n            this.sidPromise = undefined;\n            resolve(sid);\n          }\n        };\n        this.on(RoomEvent.RoomSidChanged, handleRoomUpdate);\n        this.once(RoomEvent.Disconnected, handleDisconnect);\n      });\n    }\n    return this.sidPromise;\n  }\n\n  async getRtcStats() {\n    if (!this.isConnected || !this.ffiHandle) {\n      throw new Error('getRtcStats requires a connected room');\n    }\n    // subscribe before issuing the request\n    const requestId = FfiClient.instance.getNextRequestAsyncId();\n    const cbPromise = FfiClient.instance.waitFor<GetSessionStatsCallback>(\n      (ev: FfiEvent) =>\n        ev.message.case === 'getSessionStats' && ev.message.value.asyncId === requestId,\n      { signal: this.disconnectController.signal },\n    );\n    FfiClient.instance.request<GetSessionStatsResponse>({\n      message: {\n        case: 'getSessionStats',\n        value: {\n          roomHandle: this.ffiHandle.handle,\n          requestAsyncId: requestId,\n        },\n      },\n    });\n    const cb = await cbPromise;\n    if (cb.message.case === 'error') {\n      throw new Error(cb.message.value);\n    } else if (cb.message.case === 'result') {\n      return cb.message.value;\n    } else {\n      throw new Error('could not retrieve rtc stats');\n    }\n  }\n\n  get numParticipants(): number {\n    return this.info?.numParticipants ?? 0;\n  }\n\n  get numPublishers(): number {\n    return this.info?.numPublishers ?? 0;\n  }\n\n  get creationTime(): Date {\n    // TODO: workaround for Rust SDK bug, remove after updating to:\n    //   https://github.com/livekit/rust-sdks/pull/822\n    // check if creationTime looks like seconds (less than year 3000 in ms), convert to ms if needed\n    let creationTimeMs = Number(this.info?.creationTime ?? 0);\n    if (creationTimeMs > 0 && creationTimeMs < 1e12) {\n      creationTimeMs *= 1000;\n    }\n    return new Date(creationTimeMs);\n  }\n\n  get isRecording(): boolean {\n    return this.info?.activeRecording ?? false;\n  }\n\n  /**\n   * The time in seconds after which a room will be closed after the last\n   * participant has disconnected.\n   */\n  get departureTimeout(): number {\n    return this.info?.departureTimeout ?? 0;\n  }\n\n  /**\n   * The time in seconds after which an empty room will be automatically closed.\n   */\n  get emptyTimeout(): number {\n    return this.info?.emptyTimeout ?? 0;\n  }\n\n  /**\n   * Connects to a LiveKit room using the provided URL and access token.\n   * @param url - The WebSocket URL of the LiveKit server\n   * @param token - A valid LiveKit access token for authentication\n   * @param opts - Optional room configuration options\n   * @throws ConnectError - if connection fails\n   */\n  async connect(url: string, token: string, opts?: RoomOptions) {\n    // dataStream is a plain user-facing object; convert it to its proto\n    // message separately instead of spreading it into the FFI options.\n    const { dataStream, ...restOpts } = opts ?? {};\n    const options = { ...defaultRoomOptions, ...restOpts };\n    const e2eeEnabled = options.encryption || options.e2ee;\n    const userE2EEOptions = options.encryption ?? options.e2ee;\n    const e2eeOptions: E2EEOptions = {\n      ...defaultE2EEOptions,\n      ...userE2EEOptions,\n      keyProviderOptions: {\n        ...defaultE2EEOptions.keyProviderOptions,\n        ...userE2EEOptions?.keyProviderOptions,\n      },\n    };\n\n    const mergedOptions = { ...options };\n\n    if (options.encryption) {\n      mergedOptions.encryption = e2eeOptions;\n    } else if (options.e2ee) {\n      mergedOptions.e2ee = e2eeOptions;\n    }\n\n    if (dataStream) {\n      mergedOptions.dataStream = new FfiRoomDataStreamOptions({\n        maxPayloadByteLength: numberToBigInt(dataStream.maxPayloadByteLength),\n      });\n    }\n\n    const req = new ConnectRequest({\n      url: url,\n      token: token,\n      options: mergedOptions,\n    });\n\n    FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onFfiEvent);\n\n    try {\n      const res = FfiClient.instance.request<ConnectResponse>({\n        message: {\n          case: 'connect',\n          value: req,\n        },\n      });\n\n      const cb = await FfiClient.instance.waitFor<ConnectCallback>((ev: FfiEvent) => {\n        return ev.message.case == 'connect' && ev.message.value.asyncId == res.asyncId;\n      });\n\n      log.debug('Connect callback received');\n\n      switch (cb.message.case) {\n        case 'result':\n          this.ffiHandle = new FfiHandle(cb.message.value.room!.handle!.id!);\n          this.e2eeManager = e2eeEnabled && new E2EEManager(this.ffiHandle.handle, e2eeOptions);\n\n          this._token = token;\n          this._serverUrl = url;\n          this.info = cb.message.value.room!.info;\n          // Reset the abort controller for this connection session so that\n          // a previous disconnect doesn't immediately cancel new operations.\n          this.disconnectController = new AbortController();\n          this.hasCleanedUp = false;\n          this.localParticipant = new LocalParticipant(\n            cb.message.value.localParticipant!,\n            this.ffiEventLock,\n            this.disconnectController.signal,\n          );\n\n          for (const pt of cb.message.value.participants) {\n            const rp = this.createRemoteParticipant(pt.participant!);\n\n            for (const pub of pt.publications) {\n              const publication = new RemoteTrackPublication(pub);\n              rp.trackPublications.set(publication.sid!, publication);\n            }\n          }\n          this.updateConnectionState(ConnectionState.CONN_CONNECTED);\n          // Release the FFI room event gate after our listener and local state are ready.\n          FfiClient.instance.request<ReadyForRoomEventResponse>({\n            message: {\n              case: 'readyForRoomEvent',\n              value: {\n                roomHandle: this.ffiHandle.handle,\n              },\n            },\n          });\n          break;\n        case 'error':\n        default:\n          throw new ConnectError(cb.message.value || '');\n      }\n    } catch (e) {\n      FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onFfiEvent);\n      this.preConnectEvents = [];\n      throw e;\n    }\n  }\n\n  /**\n   * Disconnects from the room and cleans up all resources.\n   * This will stop all tracks and close the connection.\n   */\n  async disconnect() {\n    if (!this.isConnected) {\n      return;\n    }\n\n    const res = FfiClient.instance.request<DisconnectResponse>({\n      message: {\n        case: 'disconnect',\n        value: {\n          roomHandle: this.ffiHandle?.handle,\n        },\n      },\n    });\n\n    await FfiClient.instance.waitFor<DisconnectCallback>((ev: FfiEvent) => {\n      return ev.message.case == 'disconnect' && ev.message.value.asyncId == res.asyncId;\n    });\n\n    this.cleanupOnDisconnect(DisconnectReason.CLIENT_INITIATED);\n    FfiClient.instance.removeListener(FfiClientEvent.FfiEvent, this.onFfiEvent);\n\n    this.removeAllListeners();\n  }\n\n  /**\n   * Trigger a reconnection / chaos scenario for testing. Most useful in\n   * tests to deterministically force a Resume (signal-only reconnect that\n   * preserves the PeerConnection and existing publications) or a full\n   * reconnect (the SDK rebuilds the RtcSession and re-publishes existing\n   * local tracks; `RoomEvent.Reconnected` fires).\n   */\n  async simulateScenario(scenario: SimulateScenarioKind): Promise<void> {\n    if (!this.isConnected || !this.ffiHandle) {\n      throw new Error('simulateScenario requires a connected room');\n    }\n    const res = FfiClient.instance.request<SimulateScenarioResponse>({\n      message: {\n        case: 'simulateScenario',\n        value: {\n          roomHandle: this.ffiHandle.handle,\n          scenario,\n        },\n      },\n    });\n    const cb = await FfiClient.instance.waitFor<SimulateScenarioCallback>(\n      (ev: FfiEvent) =>\n        ev.message.case === 'simulateScenario' && ev.message.value.asyncId === res.asyncId,\n      { signal: this.disconnectController.signal },\n    );\n    if (cb.error) {\n      throw new Error(`simulateScenario failed: ${cb.error}`);\n    }\n  }\n\n  private updateConnectionState(newState: ConnectionState) {\n    if (this._connectionState === newState) {\n      return;\n    }\n    this._connectionState = newState;\n    this.emit(RoomEvent.ConnectionStateChanged, this._connectionState);\n  }\n\n  // Runs at most once per connection session. The FFI layer and explicit\n  // disconnect() both race to get here — whichever wins emits the events,\n  // the other is a no-op. A reconnect via connect() clears hasCleanedUp.\n  private cleanupOnDisconnect(reason: DisconnectReason = DisconnectReason.CLIENT_INITIATED) {\n    if (this.hasCleanedUp) return;\n    this.hasCleanedUp = true;\n\n    // Error all in-progress stream readers to prevent FD leaks.\n    // Streams that were receiving data but never reached end-of-stream (e.g. the\n    // sender disconnected mid-transfer) would otherwise keep their ReadableStream\n    // open indefinitely, leaking the underlying controller and any buffered chunks.\n    // Using error() instead of close() signals an abnormal termination to consumers.\n    for (const [, { controller, listener }] of this.streamReaders) {\n      FfiClient.instance.off(FfiClientEvent.FfiEvent, listener);\n      try {\n        controller.error(new Error('Disconnected while receiving'));\n      } catch {\n        // controller may already be closed or errored\n      }\n    }\n    this.streamReaders.clear();\n\n    // Detach every track from this room so attached FrameProcessors receive\n    // onStreamInfoCleared/onCredentialsCleared and the tokenRefreshed listener\n    // is removed. Otherwise a server-initiated disconnect leaves processors with\n    // stale room context and the Room holding a strong ref to each Track's bound\n    // listener until GC. setRoom(null) is idempotent, so this is safe even if a\n    // track was already detached (e.g. via unsubscribe/unpublish).\n    if (this.localParticipant) {\n      // Data stream writers that were never closed (abandoned by the caller, or\n      // left errored by a failed write) keep their native writer alive until\n      // their handle is dropped.\n      this.localParticipant.disposeOpenStreamWriters();\n\n      for (const pub of this.localParticipant.trackPublications.values()) {\n        pub.track?.setRoom(null);\n      }\n    }\n    for (const participant of this.remoteParticipants.values()) {\n      for (const pub of participant.trackPublications.values()) {\n        pub.track?.setRoom(null);\n      }\n    }\n\n    // Clear sidPromise before removing listeners so that a reconnect\n    // doesn't return a stale, permanently-pending promise.\n    this.sidPromise = undefined;\n    // Abort all pending FfiClient.waitFor() listeners so they don't leak.\n    // This causes any in-flight operations (publishData, publishTrack, etc.)\n    // to reject and clean up their event listeners.\n    this.disconnectController.abort();\n\n    this.updateConnectionState(ConnectionState.CONN_DISCONNECTED);\n    this.emit(RoomEvent.Disconnected, reason);\n  }\n\n  /**\n   * Registers a handler for incoming text data streams on a specific topic.\n   * Text streams are used for receiving structured text data from other participants.\n   * @param topic - The topic to listen for text streams on\n   * @param callback - Function to handle incoming text stream data\n   * @throws Error - if a handler for this topic is already registered\n   */\n  registerTextStreamHandler(topic: string, callback: TextStreamHandler) {\n    if (this.textStreamHandlers.has(topic)) {\n      throw new Error(`A text stream handler for topic \"${topic}\" has already been set.`);\n    }\n    this.textStreamHandlers.set(topic, callback);\n  }\n\n  unregisterTextStreamHandler(topic: string) {\n    this.textStreamHandlers.delete(topic);\n  }\n\n  /**\n   * Registers a handler for incoming byte data streams on a specific topic.\n   * Byte streams are used for receiving binary data like files from other participants.\n   * @param topic - The topic to listen for byte streams on\n   * @param callback - Function to handle incoming byte stream data\n   * @throws Error - if a handler for this topic is already registered\n   */\n  registerByteStreamHandler(topic: string, callback: ByteStreamHandler) {\n    if (this.byteStreamHandlers.has(topic)) {\n      throw new Error(`A byte stream handler for topic \"${topic}\" has already been set.`);\n    }\n    this.byteStreamHandlers.set(topic, callback);\n  }\n\n  unregisterByteStreamHandler(topic: string) {\n    this.byteStreamHandlers.delete(topic);\n  }\n\n  private onFfiEvent = async (ffiEvent: FfiEvent) => {\n    const unlock = await this.ffiEventLock.lock();\n    try {\n      if (!this.localParticipant || !this.ffiHandle || !this.info) {\n        this.preConnectEvents.push(ffiEvent);\n        return;\n      }\n\n      // process preConnectEvents if we received the connectCallback after the events were queued\n      for (const ev of this.preConnectEvents) {\n        await this.processFfiEvent(ev);\n      }\n      this.preConnectEvents = [];\n\n      await this.processFfiEvent(ffiEvent);\n    } finally {\n      unlock();\n    }\n  };\n\n  private processFfiEvent = async (ffiEvent: FfiEvent) => {\n    if (!this.localParticipant || !this.ffiHandle || !this.info) {\n      throw new Error('processFfiEvent called before connect');\n    }\n\n    if (ffiEvent.message.case == 'rpcMethodInvocation') {\n      if (\n        ffiEvent.message.value.localParticipantHandle == this.localParticipant.ffi_handle.handle\n      ) {\n        this.localParticipant.handleRpcMethodInvocation(\n          ffiEvent.message.value.invocationId!,\n          ffiEvent.message.value.method!,\n          ffiEvent.message.value.requestId!,\n          ffiEvent.message.value.callerIdentity!,\n          ffiEvent.message.value.payload!,\n          ffiEvent.message.value.responseTimeoutMs!,\n        );\n      }\n      return;\n    } else if (\n      ffiEvent.message.case != 'roomEvent' ||\n      ffiEvent.message.value.roomHandle != this.ffiHandle.handle\n    ) {\n      return;\n    }\n\n    const ev = ffiEvent.message.value.message;\n    if (process.env.LIVEKIT_DEBUG_LOG_ROOM_EVENTS) {\n      console.log('Room event:', ev);\n    }\n    if (ev.case == 'participantConnected') {\n      const participant = this.createRemoteParticipant(ev.value.info!);\n      this.remoteParticipants.set(participant.identity!, participant);\n      this.emit(RoomEvent.ParticipantConnected, participant);\n    } else if (ev.case == 'participantDisconnected') {\n      const participant = this.remoteParticipants.get(ev.value.participantIdentity!);\n      if (participant) {\n        this.remoteParticipants.delete(participant.identity);\n        participant.info.disconnectReason = ev.value.disconnectReason;\n        this.emit(RoomEvent.ParticipantDisconnected, participant);\n      } else {\n        log.warn(`RoomEvent.ParticipantDisconnected: Could not find participant`);\n      }\n    } else if (ev.case == 'localTrackPublished') {\n      const publication = this.localParticipant.trackPublications.get(ev.value.trackSid!);\n      if (publication?.track) {\n        publication.track.setRoom(this);\n      }\n      this.emit(RoomEvent.LocalTrackPublished, publication!, this.localParticipant);\n    } else if (ev.case == 'localTrackUnpublished') {\n      const publication = this.localParticipant.trackPublications.get(ev.value.publicationSid!);\n      const track = publication?.track;\n      if (track) {\n        track.setRoom(null);\n      }\n      this.localParticipant.trackPublications.delete(ev.value.publicationSid!);\n      // Emit while `publication.track` is still set, preserving the pre-existing\n      // payload for callbacks. The handler is synchronous, so nulling the track\n      // right after still completes before any other turn can observe it.\n      this.emit(RoomEvent.LocalTrackUnpublished, publication!, this.localParticipant!);\n      // Mirror trackUnsubscribed: drop the publication's track reference. This\n      // also makes unpublishTrack's own setRoom(null) a no-op when it loses the\n      // race (its `pub.track` guard short-circuits), avoiding a redundant clear.\n      if (track && publication) {\n        publication.track = undefined;\n      }\n    } else if ((ev.case as string) == 'localTrackRepublished') {\n      const value = (ev as any).value;\n      const previousSid: string = value.previousSid!;\n      const newInfo: TrackPublicationInfo = value.info!;\n      const publication = this.localParticipant.trackPublications.get(previousSid);\n      if (publication) {\n        publication.updateInfo(newInfo);\n        this.localParticipant.trackPublications.delete(previousSid);\n        this.localParticipant.trackPublications.set(publication.sid!, publication);\n        if (publication.track?.info) {\n          // Keep the local-track invariant (track.sid == publication.sid, set at\n          // publishTrack) intact across republish, then re-push metadata so any\n          // attached FrameProcessor learns the new publication SID / credentials.\n          // setRoom with the same room is a no-op for the tokenRefreshed listener\n          // but re-fans the metadata to every registered AudioStream.\n          publication.track.info.sid = publication.sid;\n          publication.track.setRoom(this);\n        }\n        this.emit(RoomEvent.LocalTrackRepublished, publication, previousSid, this.localParticipant);\n      } else {\n        log.warn(`RoomEvent.LocalTrackRepublished: previous publication not found: ${previousSid}`);\n      }\n    } else if (ev.case == 'localTrackSubscribed') {\n      const publication = this.localParticipant.trackPublications.get(ev.value.trackSid!);\n      if (publication) {\n        publication.resolveFirstSubscription();\n        this.emit(RoomEvent.LocalTrackSubscribed, publication!.track!);\n      } else {\n        log.warn(`RoomEvent.LocalTrackSubscribed: Publication not found: ${ev.value.trackSid}`);\n      }\n    } else if (ev.case == 'trackPublished') {\n      const participant = this.remoteParticipants.get(ev.value.participantIdentity!);\n      const publication = new RemoteTrackPublication(ev.value.publication!);\n      if (participant) {\n        participant.trackPublications.set(publication.sid!, publication);\n      } else {\n        log.warn(\n          `RoomEvent.TrackPublished: Could not find participant: ${ev.value.participantIdentity}`,\n        );\n      }\n      this.emit(RoomEvent.TrackPublished, publication, participant!);\n    } else if (ev.case == 'trackUnpublished') {\n      const participant = this.requireRemoteParticipant(ev.value.participantIdentity!);\n      const publication = participant.trackPublications.get(ev.value.publicationSid!);\n      participant.trackPublications.delete(ev.value.publicationSid!);\n      if (publication) {\n        this.emit(RoomEvent.TrackUnpublished, publication, participant);\n        // Dispose eagerly so handles don't accumulate when a participant\n        // publishes and unpublishes many tracks during a long-lived session.\n        publication.ffiHandle.dispose();\n      } else {\n        log.warn(`RoomEvent.TrackUnpublished: Could not find publication`);\n      }\n    } else if (ev.case == 'trackSubscribed') {\n      const ownedTrack = ev.value.track!;\n      const trackInfo = ownedTrack.info!;\n      try {\n        const { participant, publication } = this.requirePublicationOfRemoteParticipant(\n          ev.value.participantIdentity!,\n          trackInfo.sid!,\n        );\n        publication.subscribed = true;\n        if (trackInfo.kind == TrackKind.KIND_VIDEO) {\n          publication.track = new RemoteVideoTrack(ownedTrack);\n        } else if (trackInfo.kind == TrackKind.KIND_AUDIO) {\n          publication.track = new RemoteAudioTrack(ownedTrack);\n        }\n        publication.track?.setRoom(this);\n\n        this.emit(RoomEvent.TrackSubscribed, publication.track!, publication, participant);\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.TrackSubscribed: ${(e as Error).message}`);\n      }\n    } else if (ev.case == 'trackUnsubscribed') {\n      try {\n        const { participant, publication } = this.requirePublicationOfRemoteParticipant(\n          ev.value.participantIdentity!,\n          ev.value.trackSid!,\n        );\n        const track = publication.track!;\n        track.setRoom(null);\n        publication.track = undefined;\n        publication.subscribed = false;\n        this.emit(RoomEvent.TrackUnsubscribed, track, publication, participant);\n        // An unsubscribed track never gets an `eos`, so any AudioStream on it\n        // keeps delivering frames. Tear them down here — after the event, so\n        // handlers still see a live track.\n        track.closeAudioStreams();\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.TrackUnsubscribed: ${(e as Error).message}`);\n      }\n    } else if (ev.case == 'trackSubscriptionFailed') {\n      try {\n        const participant = this.requireRemoteParticipant(ev.value.participantIdentity!);\n        this.emit(\n          RoomEvent.TrackSubscriptionFailed,\n          ev.value.trackSid!,\n          participant,\n          ev.value.error,\n        );\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.TrackSubscriptionFailed: ${(e as Error).message}`);\n      }\n    } else if (ev.case == 'trackMuted') {\n      try {\n        const { participant, publication } = this.requirePublicationOfParticipant(\n          ev.value.participantIdentity!,\n          ev.value.trackSid!,\n        );\n        publication.info!.muted = true;\n        if (publication.track) {\n          publication.track.info!.muted = true;\n        }\n        this.emit(RoomEvent.TrackMuted, publication, participant);\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.TrackMuted: ${(e as Error).message}`);\n      }\n    } else if (ev.case == 'trackUnmuted') {\n      try {\n        const { participant, publication } = this.requirePublicationOfParticipant(\n          ev.value.participantIdentity!,\n          ev.value.trackSid!,\n        );\n        publication.info!.muted = false;\n        if (publication.track) {\n          publication.track.info!.muted = false;\n        }\n        this.emit(RoomEvent.TrackUnmuted, publication, participant);\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.TrackUnmuted: ${(e as Error).message}`);\n      }\n    } else if (ev.case == 'activeSpeakersChanged') {\n      try {\n        const activeSpeakers = ev.value.participantIdentities.map((identity) =>\n          this.requireParticipantByIdentity(identity),\n        );\n        this.emit(RoomEvent.ActiveSpeakersChanged, activeSpeakers);\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.ActiveSpeakersChanged: ${(e as Error).message}`);\n      }\n    } else if (ev.case == 'roomMetadataChanged') {\n      this.info.metadata = ev.value.metadata ?? '';\n      this.emit(RoomEvent.RoomMetadataChanged, this.info.metadata);\n    } else if (ev.case == 'participantMetadataChanged') {\n      try {\n        const participant = this.requireParticipantByIdentity(ev.value.participantIdentity!);\n        participant.info.metadata = ev.value.metadata;\n        this.emit(RoomEvent.ParticipantMetadataChanged, participant.metadata, participant);\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.ParticipantMetadataChanged: ${(e as Error).message}`);\n      }\n    } else if (ev.case == 'participantNameChanged') {\n      try {\n        const participant = this.requireParticipantByIdentity(ev.value.participantIdentity!);\n        participant.info.name = ev.value.name;\n        this.emit(RoomEvent.ParticipantNameChanged, participant.name!, participant);\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.ParticipantNameChanged: ${(e as Error).message}`);\n      }\n    } else if (ev.case == 'participantAttributesChanged') {\n      try {\n        const participant = this.requireParticipantByIdentity(ev.value.participantIdentity!);\n        participant.info.attributes = ev.value.attributes.reduce(\n          (acc, value) => {\n            acc[value.key!] = value.value!;\n            return acc;\n          },\n          {} as Record<string, string>,\n        );\n        if (Object.keys(ev.value.changedAttributes).length > 0) {\n          const changedAttributes = ev.value.changedAttributes.reduce(\n            (acc, value) => {\n              acc[value.key!] = value.value!;\n              return acc;\n            },\n            {} as Record<string, string>,\n          );\n          this.emit(RoomEvent.ParticipantAttributesChanged, changedAttributes, participant);\n        }\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.ParticipantAttributesChanged: ${(e as Error).message}`);\n      }\n    } else if (ev.case == 'connectionQualityChanged') {\n      try {\n        const participant = this.requireParticipantByIdentity(ev.value.participantIdentity!);\n        this.emit(RoomEvent.ConnectionQualityChanged, ev.value.quality!, participant);\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.ConnectionQualityChanged: ${(e as Error).message}`);\n      }\n    } else if (ev.case == 'chatMessage') {\n      const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity!);\n      const { id, message: messageText, timestamp, editTimestamp, generated } = ev.value.message!;\n      const message: ChatMessage = {\n        id: id!,\n        message: messageText!,\n        timestamp: Number(timestamp),\n        editTimestamp: Number(editTimestamp),\n        generated,\n      };\n      this.emit(RoomEvent.ChatMessage, message, participant);\n    } else if (ev.case == 'dataPacketReceived') {\n      // Can be undefined if the data is sent from a Server SDK\n      const participant = this.remoteParticipants.get(ev.value.participantIdentity!);\n      const dataPacket = ev.value.value;\n      switch (dataPacket.case) {\n        case 'user':\n          const buffer = FfiClient.instance.copyBuffer(\n            dataPacket.value.data!.data!.dataPtr!,\n            Number(dataPacket.value.data!.data!.dataLen),\n          );\n          new FfiHandle(dataPacket.value.data!.handle!.id!).dispose();\n          this.emit(\n            RoomEvent.DataReceived,\n            buffer,\n            participant,\n            ev.value.kind,\n            dataPacket.value.topic,\n          );\n          break;\n        case 'sipDtmf':\n          const { code, digit } = dataPacket.value;\n          this.emit(RoomEvent.DtmfReceived, code!, digit!, participant!);\n          break;\n        default:\n          break;\n      }\n    } else if (ev.case == 'e2eeStateChanged') {\n      if (ev.value.state == EncryptionState.INTERNAL_ERROR) {\n        // throw generic error until Rust SDK is updated to supply the error alongside INTERNAL_ERROR\n        this.emit(RoomEvent.EncryptionError, new Error('internal server error'));\n      }\n    } else if (ev.case == 'connectionStateChanged') {\n      this.updateConnectionState(ev.value.state!);\n      /*} else if (ev.case == 'connected') {\n      this.emit(RoomEvent.Connected);*/\n    } else if (ev.case == 'disconnected') {\n      // cleanupOnDisconnect emits RoomEvent.Disconnected itself (guarded by\n      // hasCleanedUp so it fires exactly once across both disconnect paths).\n      this.cleanupOnDisconnect(ev.value.reason!);\n    } else if (ev.case == 'reconnecting') {\n      this.emit(RoomEvent.Reconnecting);\n    } else if (ev.case == 'reconnected') {\n      this.emit(RoomEvent.Reconnected);\n    } else if (ev.case == 'roomSidChanged') {\n      this.emit(RoomEvent.RoomSidChanged, ev.value.sid!);\n    } else if (ev.case === 'byteStreamOpened' && ev.value.reader) {\n      this.handleByteStreamOpened(ev.value.reader, ev.value.participantIdentity!);\n    } else if (ev.case === 'textStreamOpened' && ev.value.reader) {\n      this.handleTextStreamOpened(ev.value.reader, ev.value.participantIdentity!);\n    } else if (ev.case === 'roomUpdated') {\n      this.info = ev.value;\n      this.emit(RoomEvent.RoomUpdated);\n    } else if (ev.case === 'moved') {\n      this.info = ev.value;\n      this.emit(RoomEvent.Moved);\n    } else if (ev.case === 'participantsUpdated') {\n      for (const info of ev.value.participants) {\n        const participant = this.retrieveParticipantByIdentity(info.identity!);\n        if (participant) {\n          participant.info = info;\n        }\n      }\n    } else if (ev.case === 'participantEncryptionStatusChanged') {\n      try {\n        const participant = this.requireParticipantByIdentity(ev.value.participantIdentity!);\n        this.emit(\n          RoomEvent.ParticipantEncryptionStatusChanged,\n          !!ev.value.isEncrypted,\n          participant,\n        );\n      } catch (e: unknown) {\n        log.warn(`RoomEvent.ParticipantEncryptionStatusChanged: ${(e as Error).message}`);\n      }\n    } else if (ev.case === 'tokenRefreshed') {\n      this._token = ev.value.token;\n      this.emit('tokenRefreshed');\n    }\n  };\n\n  private retrieveParticipantByIdentity(identity: string): Participant | undefined {\n    if (this.localParticipant?.identity === identity) {\n      return this.localParticipant;\n    } else {\n      return this.remoteParticipants.get(identity);\n    }\n  }\n\n  private requireParticipantByIdentity(identity: string): Participant {\n    if (this.localParticipant?.identity === identity) {\n      return this.localParticipant;\n    } else if (this.remoteParticipants.has(identity)) {\n      return this.remoteParticipants.get(identity)!;\n    } else {\n      throw new TypeError(`participant ${identity} not found`);\n    }\n  }\n\n  private requireRemoteParticipant(identity: string) {\n    const participant = this.remoteParticipants.get(identity);\n    if (!participant) {\n      throw new TypeError(`participant ${identity} not found`);\n    }\n    return participant;\n  }\n\n  private requirePublicationOfParticipant(identity: string, trackSid: string) {\n    const participant = this.requireParticipantByIdentity(identity);\n    const publication = participant.trackPublications.get(trackSid);\n    if (!publication) {\n      throw new TypeError(`publication ${trackSid} not found`);\n    }\n    return { participant, publication };\n  }\n\n  private requirePublicationOfRemoteParticipant(identity: string, trackSid: string) {\n    const participant = this.requireRemoteParticipant(identity);\n    const publication = participant.trackPublications.get(trackSid);\n    if (!publication) {\n      throw new TypeError(`publication ${trackSid} not found`);\n    }\n    return { participant, publication };\n  }\n\n  private createRemoteParticipant(ownedInfo: OwnedParticipant) {\n    if (this.remoteParticipants.has(ownedInfo.info!.identity!)) {\n      throw new Error('Participant already exists');\n    }\n\n    const participant = new RemoteParticipant(ownedInfo);\n    this.remoteParticipants.set(ownedInfo.info!.identity!, participant);\n    return participant;\n  }\n\n  private handleByteStreamOpened(ownedReader: OwnedByteStreamReader, participantIdentity: string) {\n    const info = byteStreamInfoFromProto(ownedReader.info!);\n    const readerHandle = ownedReader.handle!.id!;\n\n    const streamHandlerCallback = this.byteStreamHandlers.get(info.topic);\n    if (!streamHandlerCallback) {\n      log.debug('ignoring incoming byte stream due to no handler for topic: %s', info.topic);\n      // The native reader was never consumed — dispose it to free the handle\n      // and any chunks it has buffered.\n      new FfiHandle(readerHandle).dispose();\n      return;\n    }\n\n    // Assigned by start(); unsubscribe() drops it once the stream is done with,\n    // whether that's end-of-stream or the consumer walking away early.\n    let listener: ((ev: FfiEvent) => void) | null = null;\n    const unsubscribe = () => {\n      if (!listener) {\n        return;\n      }\n      FfiClient.instance.off(FfiClientEvent.FfiEvent, listener);\n      listener = null;\n      this.streamReaders.delete(readerHandle);\n    };\n\n    const stream = new ReadableStream<DataStream_Chunk>({\n      start: (controller) => {\n        listener = (ev: FfiEvent) => {\n          if (\n            ev.message.case !== 'byteStreamReaderEvent' ||\n            ev.message.value.readerHandle !== readerHandle\n          ) {\n            return;\n          }\n          const detail = ev.message.value.detail;\n          if (detail.case === 'chunkReceived') {\n            const content = detail.value.content!;\n            if (content.length > 0) {\n              controller.enqueue(new DataStream_Chunk({ content }));\n            }\n          } else if (detail.case === 'eos') {\n            unsubscribe();\n            const error = detail.value.error;\n            if (error) {\n              // Abnormal termination (e.g. remote abort, payload over the\n              // receiver's size limit): surface the error to the consumer\n              // instead of presenting a truncated payload as a clean EOF.\n              controller.error(new Error(error.description ?? 'stream terminated'));\n            } else {\n              controller.close();\n            }\n          }\n        };\n        FfiClient.instance.on(FfiClientEvent.FfiEvent, listener);\n        this.streamReaders.set(readerHandle, { controller, listener });\n\n        // Start the incremental read; the native reader buffers chunks received\n        // before this request, so nothing is lost. This consumes the FFI handle.\n        FfiClient.instance.request<ByteStreamReaderReadIncrementalResponse>({\n          message: {\n            case: 'byteReadIncremental',\n            value: new ByteStreamReaderReadIncrementalRequest({ readerHandle }),\n          },\n        });\n      },\n      // The consumer stopped reading before end-of-stream — ByteStreamReader\n      // .close(), or breaking out of a for-await. The eos event that would have\n      // unsubscribed is never consumed, so the listener would otherwise be run\n      // against every FFI event for the rest of the process. Only the\n      // subscription is released: the reader handle was consumed by the\n      // readIncremental request above, so there is nothing left to dispose.\n      cancel: () => unsubscribe(),\n    });\n\n    streamHandlerCallback(\n      new ByteStreamReader(info, stream, bigIntToNumber(ownedReader.info!.totalLength)),\n      { identity: participantIdentity },\n    );\n  }\n\n  private handleTextStreamOpened(ownedReader: OwnedTextStreamReader, participantIdentity: string) {\n    const info = textStreamInfoFromProto(ownedReader.info!);\n    const readerHandle = ownedReader.handle!.id!;\n\n    const streamHandlerCallback = this.textStreamHandlers.get(info.topic);\n    if (!streamHandlerCallback) {\n      log.debug('ignoring incoming text stream due to no handler for topic: %s', info.topic);\n      // The native reader was never consumed — dispose it to free the handle\n      // and any chunks it has buffered.\n      new FfiHandle(readerHandle).dispose();\n      return;\n    }\n\n    const textEncoder = new TextEncoder();\n\n    // Assigned by start(); unsubscribe() drops it once the stream is done with,\n    // whether that's end-of-stream or the consumer walking away early.\n    let listener: ((ev: FfiEvent) => void) | null = null;\n    const unsubscribe = () => {\n      if (!listener) {\n        return;\n      }\n      FfiClient.instance.off(FfiClientEvent.FfiEvent, listener);\n      listener = null;\n      this.streamReaders.delete(readerHandle);\n    };\n\n    const stream = new ReadableStream<DataStream_Chunk>({\n      start: (controller) => {\n        listener = (ev: FfiEvent) => {\n          if (\n            ev.message.case !== 'textStreamReaderEvent' ||\n            ev.message.value.readerHandle !== readerHandle\n          ) {\n            return;\n          }\n          const detail = ev.message.value.detail;\n          if (detail.case === 'chunkReceived') {\n            const content = textEncoder.encode(detail.value.content!);\n            if (content.length > 0) {\n              controller.enqueue(new DataStream_Chunk({ content }));\n            }\n          } else if (detail.case === 'eos') {\n            unsubscribe();\n            const error = detail.value.error;\n            if (error) {\n              // Abnormal termination (e.g. remote abort, payload over the\n              // receiver's size limit): surface the error to the consumer\n              // instead of presenting a truncated payload as a clean EOF.\n              controller.error(new Error(error.description ?? 'stream terminated'));\n            } else {\n              controller.close();\n            }\n          }\n        };\n        FfiClient.instance.on(FfiClientEvent.FfiEvent, listener);\n        this.streamReaders.set(readerHandle, { controller, listener });\n\n        // Start the incremental read; the native reader buffers chunks received\n        // before this request, so nothing is lost. This consumes the FFI handle.\n        FfiClient.instance.request<TextStreamReaderReadIncrementalResponse>({\n          message: {\n            case: 'textReadIncremental',\n            value: new TextStreamReaderReadIncrementalRequest({ readerHandle }),\n          },\n        });\n      },\n      // The consumer stopped reading before end-of-stream — TextStreamReader\n      // .close(), or breaking out of a for-await. The eos event that would have\n      // unsubscribed is never consumed, so the listener would otherwise be run\n      // against every FFI event for the rest of the process. Only the\n      // subscription is released: the reader handle was consumed by the\n      // readIncremental request above, so there is nothing left to dispose.\n      cancel: () => unsubscribe(),\n    });\n\n    streamHandlerCallback(\n      new TextStreamReader(info, stream, bigIntToNumber(ownedReader.info!.totalLength)),\n      { identity: participantIdentity },\n    );\n  }\n}\n\nexport class ConnectError extends Error {\n  constructor(message: string) {\n    super(message);\n  }\n}\n\nexport type RoomCallbacks = {\n  participantConnected: (participant: RemoteParticipant) => void;\n  participantDisconnected: (participant: RemoteParticipant) => void;\n  localTrackPublished: (publication: LocalTrackPublication, participant: LocalParticipant) => void;\n  localTrackUnpublished: (\n    publication: LocalTrackPublication,\n    participant: LocalParticipant,\n  ) => void;\n  /**\n   * Fired when the SDK auto-republished a local track during a full\n   * reconnect. The publication object's identity is preserved (the same\n   * instance is updated in place with the new server-assigned SIDs);\n   * `previousSid` is provided for callers that key external state on the\n   * old SID and need to reconcile.\n   */\n  localTrackRepublished: (\n    publication: LocalTrackPublication,\n    previousSid: string,\n    participant: LocalParticipant,\n  ) => void;\n  localTrackSubscribed: (track: LocalTrack) => void;\n  trackPublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void;\n  trackUnpublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void;\n  trackSubscribed: (\n    track: RemoteTrack,\n    publication: RemoteTrackPublication,\n    participant: RemoteParticipant,\n  ) => void;\n  trackUnsubscribed: (\n    track: RemoteTrack,\n    publication: RemoteTrackPublication,\n    participant: RemoteParticipant,\n  ) => void;\n  trackSubscriptionFailed: (\n    trackSid: string,\n    participant: RemoteParticipant,\n    reason?: string,\n  ) => void;\n  trackMuted: (publication: TrackPublication, participant: Participant) => void;\n  trackUnmuted: (publication: TrackPublication, participant: Participant) => void;\n  activeSpeakersChanged: (speakers: Participant[]) => void;\n  roomMetadataChanged: (metadata: string) => void;\n  roomInfoUpdated: (info: RoomInfo) => void;\n  participantMetadataChanged: (metadata: string | undefined, participant: Participant) => void;\n  participantNameChanged: (name: string, participant: Participant) => void;\n  participantAttributesChanged: (\n    changedAttributes: Record<string, string>,\n    participant: Participant,\n  ) => void;\n  participantEncryptionStatusChanged: (isEncrypted: boolean, participant: Participant) => void;\n  connectionQualityChanged: (quality: ConnectionQuality, participant: Participant) => void;\n  dataReceived: (\n    payload: Uint8Array,\n    participant?: RemoteParticipant,\n    kind?: DataPacketKind,\n    topic?: string,\n    encryptionType?: EncryptionType,\n  ) => void;\n  chatMessage: (message: ChatMessage, participant?: Participant) => void;\n  dtmfReceived: (code: number, digit: string, participant: RemoteParticipant) => void;\n  encryptionError: (error: Error) => void;\n  connectionStateChanged: (state: ConnectionState) => void;\n  connected: () => void;\n  disconnected: (reason: DisconnectReason) => void;\n  reconnecting: () => void;\n  reconnected: () => void;\n  roomSidChanged: (sid: string) => void;\n  roomUpdated: () => void;\n  moved: () => void;\n  tokenRefreshed: () => void;\n};\n\nexport enum RoomEvent {\n  ParticipantConnected = 'participantConnected',\n  ParticipantDisconnected = 'participantDisconnected',\n  LocalTrackPublished = 'localTrackPublished',\n  LocalTrackUnpublished = 'localTrackUnpublished',\n  LocalTrackRepublished = 'localTrackRepublished',\n  LocalTrackSubscribed = 'localTrackSubscribed',\n  TrackPublished = 'trackPublished',\n  TrackUnpublished = 'trackUnpublished',\n  TrackSubscribed = 'trackSubscribed',\n  TrackUnsubscribed = 'trackUnsubscribed',\n  TrackSubscriptionFailed = 'trackSubscriptionFailed',\n  TrackMuted = 'trackMuted',\n  TrackUnmuted = 'trackUnmuted',\n  ActiveSpeakersChanged = 'activeSpeakersChanged',\n  RoomMetadataChanged = 'roomMetadataChanged',\n  RoomSidChanged = 'roomSidChanged',\n  ParticipantMetadataChanged = 'participantMetadataChanged',\n  ParticipantNameChanged = 'participantNameChanged',\n  ParticipantAttributesChanged = 'participantAttributesChanged',\n  ParticipantEncryptionStatusChanged = 'participantEncryptionStatusChanged',\n  ConnectionQualityChanged = 'connectionQualityChanged',\n  DataReceived = 'dataReceived',\n  ChatMessage = 'chatMessage',\n  DtmfReceived = 'dtmfReceived',\n  EncryptionError = 'encryptionError',\n  ConnectionStateChanged = 'connectionStateChanged',\n  Connected = 'connected',\n  Disconnected = 'disconnected',\n  Reconnecting = 'reconnecting',\n  Reconnected = 'reconnected',\n  RoomUpdated = 'roomUpdated',\n  Moved = 'moved',\n  TokenRefreshed = 'tokenRefreshed',\n}\n"],"mappings":"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","names":["import_rtc_ffi_bindings","FfiRoomOptions","EventEmitter","FfiRoomDataStreamOptions","RoomEvent"]}