{"version":3,"file":"index.cjs","names":["ops: LoroWebsocketClientOptions","ws: WebSocket","authValue: Uint8Array","pending: PendingRoom","MessageType","RoomErrorCode","UpdateStatusCode","JoinErrorCode","resolve!: (res: JoinResponseOk) => void","reject!: (error: Error) => void","room","MAX_MESSAGE_SIZE","header: DocUpdateFragmentHeader","msg: DocUpdateFragment"],"sources":["../../src/client/index.ts"],"sourcesContent":["import {\n  CrdtType,\n  ProtocolMessage,\n  tryDecode,\n  MessageType,\n  JoinResponseOk,\n  encode,\n  JoinRequest,\n  DocUpdate,\n  JoinError,\n  DocUpdateFragmentHeader,\n  DocUpdateFragment,\n  Ack,\n  RoomError,\n  RoomErrorCode,\n  UpdateStatusCode,\n  Leave,\n  JoinErrorCode,\n  MAX_MESSAGE_SIZE,\n  bytesToHex,\n  HexString,\n} from \"loro-protocol\";\nimport type { CrdtDocAdaptor } from \"loro-adaptors\";\n\nexport * from \"loro-adaptors\";\n\nexport type AuthProvider = () => Uint8Array | Promise<Uint8Array>;\ntype AuthOption = Uint8Array | AuthProvider;\n\ninterface FragmentBatch {\n  header: DocUpdateFragmentHeader;\n  fragments: Map<number, Uint8Array>;\n  timeoutId: ReturnType<typeof setTimeout>;\n}\n\ninterface PendingRoom {\n  room: Promise<LoroWebsocketClientRoom>;\n  resolve: (res: JoinResponseOk) => void;\n  reject: (error: Error) => void;\n  adaptor: CrdtDocAdaptor;\n  roomId: string;\n  auth?: AuthOption;\n  isRejoin?: boolean;\n}\n\ninterface InternalRoomHandler {\n  handleDocUpdate(updates: Uint8Array[], refId?: HexString): void;\n  handleAck(ack: Ack): void;\n  handleRoomError(error: RoomError): void;\n}\n\ninterface ActiveRoom {\n  room: LoroWebsocketClientRoom;\n  handler: InternalRoomHandler;\n}\n\ninterface SocketListeners {\n  open: () => void;\n  error: (event: Event) => void;\n  close: (event: CloseEvent) => void;\n  message: (event: MessageEvent<string | ArrayBuffer>) => void;\n}\n\ntype NodeProcessLike = {\n  on?: (event: string, listener: () => void) => unknown;\n  off?: (event: string, listener: () => void) => unknown;\n  removeListener?: (event: string, listener: () => void) => unknown;\n};\n\nfunction randomBatchId(): HexString {\n  const bytes = new Uint8Array(8);\n  crypto.getRandomValues(bytes);\n  return bytesToHex(bytes);\n}\n\n/**\n * The websocket client's high-level connection status.\n * - `Connecting`: initial connect or a manual `connect()` in progress.\n * - `Connected`: the websocket is open and usable.\n * - `Disconnected`: the client is not connected. Call `connect()` to retry.\n */\nexport const ClientStatus = {\n  Connecting: \"connecting\",\n  Connected: \"connected\",\n  Disconnected: \"disconnected\",\n} as const;\nexport type ClientStatusValue =\n  (typeof ClientStatus)[keyof typeof ClientStatus];\n\n/**\n * Options for `LoroWebsocketClient`.\n *\n * Behavior summary:\n * - The client auto-connects on construction and retries on unexpected closures with an exponential backoff.\n * - Call `close()` to stop auto-reconnect and move to `Disconnected`. Call `connect()` to resume.\n * - Pings are sent periodically to keep the connection alive; `latency` estimates are updated on pong.\n */\nexport interface LoroWebsocketClientOptions {\n  /** WebSocket URL (ws:// or wss://). */\n  url: string;\n  /** Optional custom ping interval. Defaults to 30s. Set with `disablePing` to stop timers. */\n  pingIntervalMs?: number;\n  /** Ping timeout; after two consecutive misses the client will force-close and reconnect. Defaults to 10s. */\n  pingTimeoutMs?: number;\n  /** Disable periodic ping/pong entirely. */\n  disablePing?: boolean;\n  /** Optional callback for low-level ws close (before status transitions). */\n  onWsClose?: () => void;\n  /** Optional callback for any client-level errors (socket error, decode/apply failures, send on closed, etc.). */\n  onError?: (error: Error) => void;\n  /**\n   * Reconnect policy (kept minimal).\n   * - enabled: toggle auto-retry (default true)\n   * - initialDelayMs: starting backoff delay (default 500)\n   * - maxDelayMs: max backoff delay (default 15000)\n   * - jitter: 0-1 multiplier applied randomly around the delay (default 0.25)\n   * - maxAttempts: number | \"infinite\" (default \"infinite\")\n   * - fatalCloseCodes: close codes that should not retry (default 4400-4499, 1008, 1011)\n   * - fatalCloseReasons: close reasons that should not retry (default permission_changed, room_closed, auth_failed)\n   */\n  reconnect?: {\n    enabled?: boolean;\n    initialDelayMs?: number;\n    maxDelayMs?: number;\n    jitter?: number;\n    maxAttempts?: number | \"infinite\";\n    fatalCloseCodes?: number[];\n    fatalCloseReasons?: string[];\n  };\n}\n\nexport const RoomJoinStatus = {\n  Connecting: \"connecting\",\n  Joined: \"joined\",\n  Reconnecting: \"reconnecting\",\n  Disconnected: \"disconnected\",\n  Error: \"error\",\n} as const;\nexport type RoomJoinStatusValue =\n  (typeof RoomJoinStatus)[keyof typeof RoomJoinStatus];\n\n/**\n * Loro websocket client with auto-reconnect, connection status events, and latency tracking.\n *\n * Status model:\n * - `Connected`: ws open.\n * - `Disconnected`: socket closed. Auto-reconnect retries run unless `close()`/`destroy()` stop them.\n * - `Connecting`: initial or manual connect in progress.\n *\n * Events:\n * - `onStatusChange(cb)`: called whenever status changes.\n * - `onLatency(cb)`: called when a new RTT estimate is measured from ping/pong.\n */\nexport class LoroWebsocketClient {\n  private ws!: WebSocket;\n  // Invariant: `connectedPromise` always represents the next transition to `Connected`.\n  // - It resolves exactly once, when the currently active socket fires `open`.\n  // - It is replaced (via `ensureConnectedPromise`) whenever we start a new connect\n  //   attempt or a reconnect is scheduled, so callers blocking on `waitConnected()`\n  //   will wait for the next successful connection.\n  // - It rejects only when we deliberately stop reconnecting (`close()` or fatal close).\n  private connectedPromise!: Promise<void>;\n  private resolveConnected?: () => void;\n  private rejectConnected?: (e: Error) => void;\n  private status: ClientStatusValue = ClientStatus.Connecting;\n  private statusListeners = new Set<(s: ClientStatusValue) => void>();\n  private latencyListeners = new Set<(ms: number) => void>();\n  private lastLatencyMs?: number;\n  private awaitingPongSince?: number;\n\n  private pendingRooms: Map<string, PendingRoom> = new Map();\n  private activeRooms: Map<string, ActiveRoom> = new Map();\n  // Buffer for %ELO only: backfills can arrive immediately after JoinResponseOk\n  private preJoinUpdates: Map<string, Array<{ updates: Uint8Array[]; refId?: HexString }>> = new Map();\n  // Track outbound update batches so we can surface errors with payload context\n  private sentUpdateBatches: Map<HexString, { roomKey: string; updates: Uint8Array[] }> = new Map();\n  private fragmentBatches: Map<string, FragmentBatch> = new Map();\n  private roomAdaptors: Map<string, CrdtDocAdaptor> = new Map();\n  // Track roomId for each active id so we can rejoin on reconnect\n  private roomIds: Map<string, string> = new Map();\n  private roomAuth: Map<string, AuthOption | undefined> = new Map();\n  private roomStatusListeners: Map<\n    string,\n    Set<(s: RoomJoinStatusValue) => void>\n  > = new Map();\n  private socketListeners = new WeakMap<WebSocket, SocketListeners>();\n\n  private pingTimer?: ReturnType<typeof setInterval>;\n  private pingWaiters: Array<{\n    resolve: () => void;\n    reject: (err: Error) => void;\n    timeoutId: ReturnType<typeof setTimeout>;\n  }> = [];\n  private missedPongs = 0;\n\n  // Reconnect controls\n  private shouldReconnect = true;\n  private reconnectAttempts = 0;\n  private reconnectTimer?: ReturnType<typeof setTimeout>;\n  private removeNetworkListeners?: () => void;\n  private offline = false;\n\n  // Join requests issued while socket is still connecting\n  private queuedJoins: Uint8Array[] = [];\n\n  constructor(private ops: LoroWebsocketClientOptions) {\n    this.attachNetworkListeners();\n\n    // Start initial connection\n    this.ensureConnectedPromise();\n    void this.connect();\n  }\n\n  private async resolveAuth(auth?: AuthOption): Promise<Uint8Array> {\n    if (typeof auth === \"function\") {\n      const value = await auth();\n      if (!(value instanceof Uint8Array)) {\n        throw new Error(\"Auth provider must return Uint8Array\");\n      }\n      return value;\n    }\n    return auth ?? new Uint8Array();\n  }\n\n  get socket(): WebSocket {\n    return this.ws;\n  }\n\n  private ensureConnectedPromise(): void {\n    if (this.resolveConnected) return;\n    this.connectedPromise = new Promise<void>((resolve, reject) => {\n      this.resolveConnected = () => {\n        this.resolveConnected = undefined;\n        this.rejectConnected = undefined;\n        resolve();\n      };\n      this.rejectConnected = (err: Error) => {\n        this.resolveConnected = undefined;\n        this.rejectConnected = undefined;\n        reject(err);\n      };\n    });\n    // prevent unhandled rejection if nobody awaits\n    void this.connectedPromise.catch(() => { });\n  }\n\n  private attachNetworkListeners(): void {\n    this.removeNetworkListeners?.();\n    this.removeNetworkListeners = undefined;\n\n    if (\n      typeof window !== \"undefined\" &&\n      typeof window.addEventListener === \"function\"\n    ) {\n      window.addEventListener(\"online\", this.handleOnline);\n      window.addEventListener(\"offline\", this.handleOffline);\n      this.removeNetworkListeners = () => {\n        window.removeEventListener(\"online\", this.handleOnline);\n        window.removeEventListener(\"offline\", this.handleOffline);\n      };\n      return;\n    }\n\n    const globalScope = globalThis as typeof globalThis & {\n      addEventListener?: (\n        type: string,\n        listener: EventListenerOrEventListenerObject\n      ) => void;\n      removeEventListener?: (\n        type: string,\n        listener: EventListenerOrEventListenerObject\n      ) => void;\n      process?: NodeProcessLike;\n    };\n\n    if (typeof globalScope.addEventListener === \"function\") {\n      const online = this.handleOnline as EventListener;\n      const offline = this.handleOffline as EventListener;\n      globalScope.addEventListener(\"online\", online);\n      globalScope.addEventListener(\"offline\", offline);\n      this.removeNetworkListeners = () => {\n        globalScope.removeEventListener?.(\"online\", online);\n        globalScope.removeEventListener?.(\"offline\", offline);\n      };\n      return;\n    }\n\n    const maybeProcess = globalScope.process;\n    if (maybeProcess && typeof maybeProcess.on === \"function\") {\n      // Node environments may surface online/offline via the global process emitter.\n      const online = () => {\n        this.handleOnline();\n      };\n      const offline = () => {\n        this.handleOffline();\n      };\n      maybeProcess.on(\"online\", online);\n      maybeProcess.on(\"offline\", offline);\n      this.removeNetworkListeners = () => {\n        if (typeof maybeProcess.off === \"function\") {\n          maybeProcess.off(\"online\", online);\n          maybeProcess.off(\"offline\", offline);\n        } else if (typeof maybeProcess.removeListener === \"function\") {\n          maybeProcess.removeListener(\"online\", online);\n          maybeProcess.removeListener(\"offline\", offline);\n        }\n      };\n    }\n  }\n\n  /** Current client status. */\n  getStatus(): ClientStatusValue {\n    return this.status;\n  }\n\n  /** Latest measured RTT in ms (if any). */\n  getLatency(): number | undefined {\n    return this.lastLatencyMs;\n  }\n\n  /** Subscribe to status changes. Returns an unsubscribe function. */\n  onStatusChange(cb: (s: ClientStatusValue) => void): () => void {\n    this.statusListeners.add(cb);\n    // Emit current immediately to inform subscribers\n    try {\n      cb(this.status);\n    } catch (err) {\n      this.logCbError(\"onStatusChange\", err);\n    }\n    return () => this.statusListeners.delete(cb);\n  }\n\n  /** Subscribe to latency updates (RTT via ping/pong). Returns an unsubscribe function. */\n  onLatency(cb: (ms: number) => void): () => void {\n    this.latencyListeners.add(cb);\n    if (this.lastLatencyMs != null) {\n      try {\n        cb(this.lastLatencyMs);\n      } catch (err) {\n        this.logCbError(\"onLatency\", err);\n      }\n    }\n    return () => this.latencyListeners.delete(cb);\n  }\n\n  private setStatus(s: ClientStatusValue) {\n    if (this.status === s) return;\n    this.status = s;\n    const listeners = Array.from(this.statusListeners);\n    for (const cb of listeners) {\n      try {\n        cb(s);\n      } catch (err) {\n        this.logCbError(\"onStatusChange\", err);\n      }\n    }\n  }\n\n  /** Initiate or resume connection. Resolves when `Connected`. */\n  async connect(opts?: { resetBackoff?: boolean }): Promise<void> {\n    if (opts?.resetBackoff) {\n      this.reconnectAttempts = 0;\n    }\n    // Ensure future unexpected closes will auto-reconnect again\n    this.shouldReconnect = true;\n    const current = this.ws;\n    if (current) {\n      const state = current.readyState;\n      if (state === WebSocket.OPEN || state === WebSocket.CONNECTING) {\n        return this.connectedPromise;\n      }\n    }\n    this.clearReconnectTimer();\n    // Ensure there's a pending promise for this attempt\n    this.ensureConnectedPromise();\n\n    this.setStatus(ClientStatus.Connecting);\n\n    let ws: WebSocket;\n    try {\n      ws = new WebSocket(this.ops.url);\n    } catch (err) {\n      const error = err instanceof Error ? err : new Error(String(err));\n      this.rejectConnected?.(error);\n      this.setStatus(ClientStatus.Disconnected);\n      throw error;\n    }\n    this.ws = ws;\n\n    if (current && current !== ws) {\n      this.detachSocketListeners(current);\n    }\n\n    this.attachSocketListeners(ws);\n\n    ws.binaryType = \"arraybuffer\";\n\n    return this.connectedPromise;\n  }\n\n  private attachSocketListeners(ws: WebSocket): void {\n    const open = () => {\n      this.onSocketOpen(ws);\n    };\n    const error = (event: Event) => {\n      this.onSocketError(ws, event);\n    };\n    const close = (event: CloseEvent) => {\n      this.onSocketClose(ws, event);\n    };\n    const message = (event: MessageEvent<string | ArrayBuffer>) => {\n      void this.onSocketMessage(ws, event).catch(err => {\n        this.emitError(err instanceof Error ? err : new Error(String(err)));\n      });\n    };\n\n    ws.addEventListener(\"open\", open);\n    ws.addEventListener(\"error\", error);\n    ws.addEventListener(\"close\", close);\n    ws.addEventListener(\"message\", message);\n\n    this.socketListeners.set(ws, {\n      open,\n      error,\n      close,\n      message,\n    });\n  }\n\n  private onSocketOpen(ws: WebSocket): void {\n    if (ws !== this.ws) {\n      // TODO: REVIEW stale sockets bail early so they can't tear down the new connection\n      this.detachSocketListeners(ws);\n      try {\n        ws.close(1000, \"Superseded\");\n      } catch { }\n      return;\n    }\n    this.clearReconnectTimer();\n    this.reconnectAttempts = 0;\n    this.setStatus(ClientStatus.Connected);\n    this.startPingTimer();\n    this.resolveConnected?.();\n    // Rejoin rooms after reconnect\n    this.rejoinActiveRooms();\n    // Flush any queued joins that were requested while connecting\n    this.flushQueuedJoins();\n  }\n\n  private onSocketError(ws: WebSocket, _event: Event): void {\n    if (ws !== this.ws) {\n      this.detachSocketListeners(ws);\n    }\n    this.emitError(new Error(\"WebSocket error\"));\n    // Leave further handling to the close event for the active socket\n  }\n\n  private onSocketClose(ws: WebSocket, event?: CloseEvent): void {\n    const isCurrent = ws === this.ws;\n    this.detachSocketListeners(ws);\n    if (!isCurrent) {\n      return;\n    }\n\n    const closeCode = event?.code;\n    const closeReason = event?.reason;\n\n    if (this.isFatalClose(closeCode, closeReason)) {\n      this.shouldReconnect = false;\n    }\n\n    this.clearPingTimer();\n    this.missedPongs = 0;\n    // Clear any pending fragment reassembly timers to avoid late callbacks\n    if (this.fragmentBatches.size) {\n      for (const [, batch] of this.fragmentBatches) {\n        clearTimeout(batch.timeoutId);\n      }\n      this.fragmentBatches.clear();\n    }\n    // Drop any unacked outbound batches to avoid leaking memory across reconnects\n    if (this.sentUpdateBatches.size) {\n      this.sentUpdateBatches.clear();\n    }\n    // Reset any in-flight RTT probe to allow future pings after reconnect\n    this.awaitingPongSince = undefined;\n    this.ops.onWsClose?.();\n    this.rejectAllPingWaiters(new Error(\"WebSocket closed\"));\n    const maxAttempts = this.getReconnectPolicy().maxAttempts;\n    if (\n      typeof maxAttempts === \"number\" &&\n      maxAttempts > 0 &&\n      this.reconnectAttempts >= maxAttempts\n    ) {\n      this.shouldReconnect = false;\n    }\n\n    // Update room-level status based on whether we will retry\n    for (const [id] of this.activeRooms) {\n      if (this.shouldReconnect) {\n        this.emitRoomStatus(id, RoomJoinStatus.Reconnecting);\n      } else {\n        this.emitRoomStatus(id, RoomJoinStatus.Disconnected);\n      }\n    }\n\n    if (!this.shouldReconnect) {\n      this.setStatus(ClientStatus.Disconnected);\n      this.rejectConnected?.(new Error(\"Disconnected\"));\n      // Fail all pending joins and mark rooms disconnected/error\n      const err = new Error(\n        closeReason ? `Disconnected: ${closeReason}` : \"Disconnected\"\n      );\n      this.failAllPendingRooms(err, this.shouldReconnect ? RoomJoinStatus.Reconnecting : RoomJoinStatus.Disconnected);\n      return;\n    }\n    // Renew the promise so callers waiting on waitConnected() block until the next successful reconnect.\n    this.ensureConnectedPromise();\n    // Start (or continue) exponential backoff retries\n    this.setStatus(ClientStatus.Disconnected);\n    this.scheduleReconnect();\n  }\n\n  private async onSocketMessage(\n    ws: WebSocket,\n    event: MessageEvent<string | ArrayBuffer>\n  ): Promise<void> {\n    if (ws !== this.ws) {\n      return;\n    }\n    try {\n      if (typeof event.data === \"string\") {\n        if (event.data === \"ping\") {\n          this.safeSend(ws, \"pong\", \"pong\");\n          return;\n        }\n        if (event.data === \"pong\") {\n          this.handlePong();\n          return;\n        }\n        return; // ignore other texts\n      }\n      const dataU8 = new Uint8Array(event.data);\n      const msg = tryDecode(dataU8);\n      if (msg != null) await this.handleMessage(msg);\n    } catch (err) {\n      this.emitError(err instanceof Error ? err : new Error(String(err)));\n    }\n  }\n\n  private scheduleReconnect(immediate = false) {\n    if (this.reconnectTimer) return;\n    if (this.offline) return;\n    const policy = this.getReconnectPolicy();\n    if (!policy.enabled) return;\n    const attempt = ++this.reconnectAttempts;\n    const delay = immediate ? 0 : this.computeBackoffDelay(attempt);\n    this.reconnectTimer = setTimeout(() => {\n      this.reconnectTimer = undefined;\n      void this.connect();\n    }, delay);\n  }\n\n  private clearReconnectTimer() {\n    if (this.reconnectTimer) clearTimeout(this.reconnectTimer);\n    this.reconnectTimer = undefined;\n  }\n\n  private handleOnline = () => {\n    this.offline = false;\n    if (!this.shouldReconnect) return;\n    if (this.status === ClientStatus.Connected) return;\n    this.clearReconnectTimer();\n    this.scheduleReconnect(true);\n  };\n\n  private handleOffline = () => {\n    this.offline = true;\n    // Pause scheduled retries until online\n    this.clearReconnectTimer();\n    if (this.shouldReconnect) {\n      this.setStatus(ClientStatus.Disconnected);\n      try {\n        this.ws?.close(1001, \"Offline\");\n      } catch { }\n    }\n  };\n\n  // Re-send JoinRequest for all active rooms after reconnect\n  private rejoinActiveRooms() {\n    for (const [id, adaptor] of this.roomAdaptors) {\n      const roomId = this.roomIds.get(id);\n      if (!roomId) continue;\n      const active = this.activeRooms.get(id);\n      if (!active) continue;\n      void this.sendRejoinRequest(id, roomId, adaptor, active.room, this.roomAuth.get(id));\n    }\n  }\n\n  private async sendRejoinRequest(\n    id: string,\n    roomId: string,\n    adaptor: CrdtDocAdaptor,\n    room: LoroWebsocketClientRoom,\n    auth?: AuthOption\n  ) {\n    let authValue: Uint8Array;\n    try {\n      authValue = await this.resolveAuth(auth);\n    } catch (e) {\n      console.error(\"Failed to resolve auth for rejoin:\", e);\n      this.cleanupRoom(roomId, adaptor.crdtType);\n      this.emitRoomStatus(id, RoomJoinStatus.Error);\n      return;\n    }\n\n    // Prepare a lightweight pending entry so JoinError handling can retry version formats\n    const pending: PendingRoom = {\n      room: Promise.resolve(room),\n      resolve: (res: JoinResponseOk) => {\n        adaptor\n          .handleJoinOk(res)\n          .catch(e => {\n            console.error(e);\n          })\n          .finally(() => {\n            this.pendingRooms.delete(id);\n            this.emitRoomStatus(id, RoomJoinStatus.Joined);\n          });\n      },\n      reject: (error: Error) => {\n        console.error(\"Rejoin failed:\", error);\n        this.pendingRooms.delete(id);\n        this.cleanupRoom(roomId, adaptor.crdtType);\n        this.emitRoomStatus(id, RoomJoinStatus.Error);\n      },\n      adaptor,\n      roomId,\n      auth,\n      isRejoin: true,\n    };\n    this.pendingRooms.set(id, pending);\n\n    const payload = encode({\n      type: MessageType.JoinRequest,\n      crdt: adaptor.crdtType,\n      roomId,\n      auth: authValue,\n      version: adaptor.getVersion(),\n    } as JoinRequest);\n\n    try {\n      this.sendJoinPayload(payload);\n      this.emitRoomStatus(id, RoomJoinStatus.Reconnecting);\n    } catch (e) {\n      console.error(\"Failed to send rejoin request:\", e);\n      this.cleanupRoom(roomId, adaptor.crdtType);\n      this.emitRoomStatus(id, RoomJoinStatus.Error);\n    }\n  }\n\n  private async handleMessage(msg: ProtocolMessage) {\n    const roomIdStr = msg.roomId;\n    const roomId = msg.crdt + roomIdStr;\n\n    switch (msg.type) {\n      case MessageType.JoinRequest: {\n        throw new Error(\"JoinRequest should not be received by client\");\n      }\n      case MessageType.JoinResponseOk: {\n        const pending = this.pendingRooms.get(roomId);\n        if (pending) {\n          pending.resolve(msg);\n        }\n        break;\n      }\n      case MessageType.JoinError: {\n        const pending = this.pendingRooms.get(roomId);\n        if (pending) {\n          await this.handleJoinError(msg, pending, roomId);\n        }\n        break;\n      }\n      case MessageType.DocUpdate: {\n        const active = this.activeRooms.get(roomId);\n        if (active) {\n          active.handler.handleDocUpdate(msg.updates, msg.batchId);\n        } else {\n          const pending = this.pendingRooms.get(roomId);\n          if (pending) {\n            const buf = this.preJoinUpdates.get(roomId) ?? [];\n            buf.push({ updates: msg.updates, refId: msg.batchId });\n            this.preJoinUpdates.set(roomId, buf);\n          }\n        }\n        break;\n      }\n      case MessageType.DocUpdateFragmentHeader: {\n        this.handleFragmentHeader(msg);\n        break;\n      }\n      case MessageType.DocUpdateFragment: {\n        this.handleFragment(msg);\n        break;\n      }\n      case MessageType.RoomError: {\n        const active = this.activeRooms.get(roomId);\n        const adaptor = this.roomAdaptors.get(roomId);\n        const auth = this.roomAuth.get(roomId);\n        const shouldRejoin = msg.code === RoomErrorCode.RejoinSuggested;\n        if (active) {\n          active.handler.handleRoomError(msg);\n        }\n        // Drop any in-flight join since the server explicitly removed us\n        this.pendingRooms.delete(roomId);\n        if (shouldRejoin && active && adaptor) {\n          void this.sendRejoinRequest(roomId, msg.roomId, adaptor, active.room, auth);\n        } else {\n          // Remove local room state so client does not auto-retry unless requested\n          this.cleanupRoom(msg.roomId, msg.crdt);\n          this.emitRoomStatus(roomId, RoomJoinStatus.Error);\n        }\n        break;\n      }\n      case MessageType.Ack: {\n        const active = this.activeRooms.get(roomId);\n        if (active) {\n          active.handler.handleAck(msg);\n        }\n        break;\n      }\n    }\n  }\n\n  private handleFragmentHeader(msg: DocUpdateFragmentHeader) {\n    const roomIdStr = msg.roomId;\n    const batchKey = `${msg.crdt}-${roomIdStr}-${msg.batchId}`;\n\n    // Clear any existing batch with same ID\n    const existing = this.fragmentBatches.get(batchKey);\n    if (existing) {\n      clearTimeout(existing.timeoutId);\n    }\n\n    // Set up timeout (10 seconds default)\n    const timeoutId = setTimeout(() => {\n      this.fragmentBatches.delete(batchKey);\n      // Notify server to prompt resend\n      try {\n        const payload = encode({\n          type: MessageType.Ack,\n          crdt: msg.crdt,\n          roomId: msg.roomId,\n          refId: msg.batchId,\n          status: UpdateStatusCode.FragmentTimeout,\n        } as Ack);\n        this.safeSend(this.ws, payload, \"fragment-timeout-ack\");\n      } catch { }\n    }, 10000);\n\n    this.fragmentBatches.set(batchKey, {\n      header: msg,\n      fragments: new Map(),\n      timeoutId,\n    });\n  }\n\n  private handleFragment(msg: DocUpdateFragment) {\n    const roomIdStr = msg.roomId;\n    const batchKey = `${msg.crdt}-${roomIdStr}-${msg.batchId}`;\n    const batch = this.fragmentBatches.get(batchKey);\n\n    if (!batch) {\n      console.error(`Received fragment for unknown batch ${msg.batchId}`);\n      return;\n    }\n\n    batch.fragments.set(msg.index, msg.fragment);\n\n    // Check if all fragments received\n    if (batch.fragments.size === batch.header.fragmentCount) {\n      clearTimeout(batch.timeoutId);\n      this.fragmentBatches.delete(batchKey);\n\n      // Reassemble fragments\n      const reassembledData = new Uint8Array(batch.header.totalSizeBytes);\n      let offset = 0;\n\n      // Reassemble in order\n      for (let i = 0; i < batch.header.fragmentCount; i++) {\n        const fragment = batch.fragments.get(i);\n        if (!fragment) {\n          console.error(`Missing fragment ${i} in batch ${msg.batchId}`);\n          return;\n        }\n\n        reassembledData.set(fragment, offset);\n        offset += fragment.length;\n      }\n\n      // Deliver to room\n      const id = msg.crdt + roomIdStr;\n      const active = this.activeRooms.get(id);\n      if (active) {\n        // Treat reassembled data as a single update\n        active.handler.handleDocUpdate([reassembledData], batch.header.batchId);\n      } else {\n        const pending = this.pendingRooms.get(id);\n        if (pending) {\n          const buf = this.preJoinUpdates.get(id) ?? [];\n          buf.push({ updates: [reassembledData], refId: batch.header.batchId });\n          this.preJoinUpdates.set(id, buf);\n        }\n      }\n    }\n  }\n\n  private registerActiveRoom(\n    roomId: string,\n    crdtType: CrdtType,\n    room: LoroWebsocketClientRoom,\n    handler: InternalRoomHandler,\n    adaptor: CrdtDocAdaptor\n  ) {\n    const id = crdtType + roomId;\n    this.activeRooms.set(id, { room, handler });\n    this.roomAdaptors.set(id, adaptor);\n    this.roomIds.set(id, roomId);\n\n    // Flush buffered updates if any\n    const buf = this.preJoinUpdates.get(id);\n    if (buf && buf.length) {\n      try {\n        for (const entry of buf) {\n          handler.handleDocUpdate(entry.updates, entry.refId);\n        }\n      } finally {\n        this.preJoinUpdates.delete(id);\n      }\n    }\n\n    this.pendingRooms.delete(id);\n    this.emitRoomStatus(id, RoomJoinStatus.Joined);\n  }\n\n  private async handleJoinError(\n    msg: JoinError,\n    pending: PendingRoom,\n    roomId: string\n  ) {\n    if (msg.code === JoinErrorCode.VersionUnknown) {\n      let authValue: Uint8Array;\n      try {\n        authValue = await this.resolveAuth(pending.auth);\n      } catch (e) {\n        pending.reject(e as Error);\n        this.pendingRooms.delete(roomId);\n        this.emitRoomStatus(\n          pending.adaptor.crdtType + pending.roomId,\n          RoomJoinStatus.Error\n        );\n        return;\n      }\n\n      // Try alternative version format\n      const currentVersion = pending.adaptor.getVersion();\n      const alternativeVersion =\n        pending.adaptor.getAlternativeVersion?.(currentVersion);\n      if (alternativeVersion) {\n        // Retry with alternative version format\n        const payload = encode({\n          type: MessageType.JoinRequest,\n          crdt: pending.adaptor.crdtType,\n          roomId: pending.roomId,\n          auth: authValue,\n          version: alternativeVersion,\n        } as JoinRequest);\n        this.sendJoinPayload(payload);\n        return;\n      } else {\n        console.warn(\"Version unknown. Now join with an empty version\");\n        const payload = encode({\n          type: MessageType.JoinRequest,\n          crdt: pending.adaptor.crdtType,\n          roomId: pending.roomId,\n          auth: authValue,\n          version: new Uint8Array(),\n        } as JoinRequest);\n        this.sendJoinPayload(payload);\n        return;\n      }\n    }\n\n    // No retry possible, reject the promise\n    const err = new Error(`Join failed: ${msg.code} - ${msg.message}`);\n    this.emitRoomStatus(\n      pending.adaptor.crdtType + pending.roomId,\n      RoomJoinStatus.Error\n    );\n    // Remove active room references so caller can rejoin manually if this was a rejoin\n    if (pending.isRejoin) {\n      this.cleanupRoom(pending.roomId, pending.adaptor.crdtType);\n    }\n    pending.reject(err);\n    this.pendingRooms.delete(roomId);\n  }\n\n  cleanupRoom(roomId: string, crdtType: CrdtType) {\n    const id = crdtType + roomId;\n    this.purgeSentBatchesForRoom(id);\n    this.activeRooms.delete(id);\n    this.pendingRooms.delete(id);\n    this.roomAdaptors.delete(id);\n    this.roomIds.delete(id);\n    this.roomAuth.delete(id);\n    this.roomStatusListeners.delete(id);\n  }\n\n  waitConnected() {\n    return this.connectedPromise;\n  }\n\n  // Send an application-level ping and resolve on matching pong\n  async ping(timeoutMs: number = 5000): Promise<void> {\n    // Ensure connection\n    await this.connectedPromise;\n    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n      throw new Error(\"WebSocket is not open\");\n    }\n\n    return new Promise<void>((resolve, reject) => {\n      const timeoutId = setTimeout(\n        () => {\n          reject(new Error(\"Ping timeout\"));\n        },\n        Math.max(1, timeoutMs)\n      );\n\n      const waiter = {\n        resolve: () => {\n          clearTimeout(timeoutId);\n          resolve();\n        },\n        reject: (err: Error) => {\n          clearTimeout(timeoutId);\n          reject(err);\n        },\n        timeoutId,\n      };\n\n      // If there's already a pending ping, just wait for the pong\n      if (this.awaitingPongSince != null) {\n        this.pingWaiters.push(waiter);\n        return;\n      }\n\n      // Try to send ping; if it fails, reject immediately instead of waiting for timeout\n      const sent = this.safeSend(this.ws, \"ping\", \"ping\");\n      if (!sent) {\n        clearTimeout(timeoutId);\n        reject(new Error(\"Failed to send ping: WebSocket not open\"));\n        return;\n      }\n\n      this.awaitingPongSince = Date.now();\n      this.pingWaiters.push(waiter);\n    });\n  }\n\n  /**\n   * Join a room.\n   * - `auth` may be a `Uint8Array` or a provider function.\n   * - The provider is invoked on the initial join and again on protocol-driven retries\n   *   (e.g. `VersionUnknown`) and reconnect rejoins, so it can refresh short-lived tokens.\n   *   If callers need a stable token, memoize in the provider.\n   */\n  join({\n    roomId,\n    crdtAdaptor,\n    auth,\n    onStatusChange,\n  }: {\n    roomId: string;\n    crdtAdaptor: CrdtDocAdaptor;\n    auth?: AuthOption;\n    onStatusChange?: (s: RoomJoinStatusValue) => void;\n  }): Promise<LoroWebsocketClientRoom> {\n    const id = crdtAdaptor.crdtType + roomId;\n    // Check if already joining or joined\n    const pending = this.pendingRooms.get(id);\n    if (pending) {\n      return pending.room;\n    }\n\n    const active = this.activeRooms.get(id);\n    if (active) {\n      return Promise.resolve(active.room);\n    }\n\n    let resolve!: (res: JoinResponseOk) => void;\n    let reject!: (error: Error) => void;\n\n    const response = new Promise<JoinResponseOk>((resolve_, reject_) => {\n      resolve = resolve_;\n      reject = reject_;\n    });\n\n    if (onStatusChange) {\n      let set = this.roomStatusListeners.get(id);\n      if (!set) {\n        set = new Set();\n        this.roomStatusListeners.set(id, set);\n      }\n      set.add(onStatusChange);\n    }\n    this.emitRoomStatus(id, RoomJoinStatus.Connecting);\n\n    const room = response.then(res => {\n      // Set adaptor ctx first so it's ready to send updates\n      crdtAdaptor.setCtx({\n        send: (updates: Uint8Array[]) => {\n          // Send each update individually, fragmenting when necessary\n          for (const upd of updates) {\n            this.sendUpdateOrFragments(crdtAdaptor.crdtType, roomId, upd);\n          }\n        },\n        onJoinFailed: (reason: string) => {\n          console.error(`Join failed: ${reason}`);\n          this.safeSend(\n            this.ws,\n            encode({\n              type: MessageType.JoinError,\n              crdt: crdtAdaptor.crdtType,\n              roomId,\n              code: JoinErrorCode.AppError,\n              message: reason,\n            } as JoinError),\n            \"join-error\"\n          );\n          reject(new Error(`Join failed: ${reason}`));\n        },\n        onImportError: (error: Error, data: Uint8Array[]) => {\n          console.error(`Import error: ${error.message}`, data);\n        },\n      });\n      // Create room and register before invoking adaptor.handleJoinOk to ensure\n      // any immediate backfills from the server are routed to the adaptor.\n      const { room, handler } = createLoroWebsocketClientRoom({\n        client: this,\n        roomId,\n        crdtType: crdtAdaptor.crdtType,\n        crdtAdaptor,\n      });\n      this.registerActiveRoom(\n        roomId,\n        crdtAdaptor.crdtType,\n        room,\n        handler,\n        crdtAdaptor\n      );\n      crdtAdaptor.handleJoinOk(res).catch(e => {\n        console.error(e);\n      });\n      return room;\n    });\n\n    // Register pending room immediately so concurrent join calls dedupe\n    this.pendingRooms.set(id, {\n      room,\n      resolve: resolve!,\n      reject: reject!,\n      adaptor: crdtAdaptor,\n      roomId,\n      auth,\n    });\n    this.roomAuth.set(id, auth);\n\n    void this.resolveAuth(auth)\n      .then(authValue => {\n        const joinPayload = encode({\n          type: MessageType.JoinRequest,\n          crdt: crdtAdaptor.crdtType,\n          roomId,\n          auth: authValue,\n          version: crdtAdaptor.getVersion(),\n        } as JoinRequest);\n\n        this.sendJoinPayload(joinPayload);\n      })\n      .catch(err => {\n        const error = err instanceof Error ? err : new Error(String(err));\n        this.emitRoomStatus(id, RoomJoinStatus.Error);\n        reject(error);\n        this.cleanupRoom(roomId, crdtAdaptor.crdtType);\n      });\n\n    return room;\n  }\n\n  /**\n   * Manually close the connection and stop auto-reconnect.\n   * To reconnect later, call `connect()`.\n   */\n  close() {\n    this.shouldReconnect = false;\n    this.clearReconnectTimer();\n    this.clearPingTimer();\n    this.reconnectAttempts = 0;\n    this.rejectConnected?.(new Error(\"Disconnected\"));\n    void this.connectedPromise?.catch(() => { });\n    this.rejectConnected = undefined;\n    this.resolveConnected = undefined;\n    this.rejectAllPingWaiters(new Error(\"Disconnected\"));\n    if (this.fragmentBatches.size) {\n      for (const [, batch] of this.fragmentBatches) {\n        try {\n          clearTimeout(batch.timeoutId);\n        } catch { }\n      }\n      this.fragmentBatches.clear();\n    }\n    this.awaitingPongSince = undefined;\n    const ws = this.ws;\n    if (ws && this.socketListeners.has(ws)) {\n      this.ops.onWsClose?.();\n    }\n    this.queuedJoins = [];\n    this.detachSocketListeners(ws);\n    this.flushAndCloseWebSocket(ws, {\n      code: 1000,\n      reason: \"Client closed\",\n    });\n    this.setStatus(ClientStatus.Disconnected);\n  }\n\n  // Fragment and send a single update if it exceeds safe payload size\n  private sendUpdateOrFragments(\n    crdt: CrdtType,\n    roomId: string,\n    update: Uint8Array\n  ): void {\n    const ws = this.ws;\n    if (!ws || ws.readyState !== WebSocket.OPEN) {\n      return;\n    }\n    // Leave headroom for protocol overhead to stay under MAX_MESSAGE_SIZE\n    const FRAG_LIMIT = Math.max(\n      1,\n      Math.min(240 * 1024, MAX_MESSAGE_SIZE - 4096)\n    );\n\n    const batchId = randomBatchId();\n    const roomKey = `${crdt}${roomId}`;\n    // Store the original payload so we can surface detailed errors on Ack\n    this.sentUpdateBatches.set(batchId, {\n      roomKey,\n      updates: [update.slice()],\n    });\n\n    if (update.length <= FRAG_LIMIT) {\n      // Send as a single DocUpdate with one update entry\n      this.safeSend(\n        ws,\n        encode({\n          type: MessageType.DocUpdate,\n          crdt,\n          roomId,\n          updates: [update],\n          batchId,\n        } as DocUpdate),\n        \"send-update\"\n      );\n      return;\n    }\n\n    // Fragment the update into multiple DocUpdateFragment messages\n    const fragmentCount = Math.ceil(update.length / FRAG_LIMIT);\n\n    const header: DocUpdateFragmentHeader = {\n      type: MessageType.DocUpdateFragmentHeader,\n      crdt,\n      roomId,\n      batchId,\n      fragmentCount,\n      totalSizeBytes: update.length,\n    };\n    this.safeSend(ws, encode(header), \"send-fragment-header\");\n\n    for (let i = 0; i < fragmentCount; i++) {\n      const start = i * FRAG_LIMIT;\n      const end = Math.min(start + FRAG_LIMIT, update.length);\n      const fragment = update.subarray(start, end);\n      const msg: DocUpdateFragment = {\n        type: MessageType.DocUpdateFragment,\n        crdt,\n        roomId,\n        batchId,\n        index: i,\n        fragment,\n      };\n      this.safeSend(ws, encode(msg), \"send-fragment\");\n    }\n  }\n\n  /** @internal Send Leave on the current websocket. */\n  sendLeave(crdt: CrdtType, roomId: string) {\n    this.safeSend(\n      this.ws,\n      encode({\n        type: MessageType.Leave,\n        crdt,\n        roomId,\n      } as Leave),\n      \"leave\"\n    );\n  }\n\n  /**\n   * Send additional updates to a specified room.\n   *\n   * This method allows sending updates that are not from the local peer's document,\n   * such as updates received from other sources or peers. The updates will be\n   * broadcast to the room and other connected clients.\n   *\n   * @param crdt - The CRDT type of the room\n   * @param roomId - The room ID to send updates to\n   * @param updates - Array of update payloads to send\n   * @throws Error if not connected or room is not active\n   */\n  sendExternalUpdates(\n    crdt: CrdtType,\n    roomId: string,\n    updates: Uint8Array[]\n  ): void {\n    const id = crdt + roomId;\n    const active = this.activeRooms.get(id);\n    if (!active) {\n      throw new Error(\n        `Cannot send updates: room ${roomId} (${crdt}) is not active`\n      );\n    }\n\n    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n      throw new Error(\"Cannot send updates: WebSocket is not open\");\n    }\n\n    // Send each update individually, fragmenting when necessary\n    for (const update of updates) {\n      this.sendUpdateOrFragments(crdt, roomId, update);\n    }\n  }\n\n  consumeSentBatch(refId: HexString): { roomKey: string; updates: Uint8Array[] } | undefined {\n    const entry = this.sentUpdateBatches.get(refId);\n    if (entry) {\n      this.sentUpdateBatches.delete(refId);\n    }\n    return entry;\n  }\n\n  private purgeSentBatchesForRoom(roomKey: string): void {\n    for (const [refId, entry] of Array.from(this.sentUpdateBatches.entries())) {\n      if (entry.roomKey === roomKey) {\n        this.sentUpdateBatches.delete(refId);\n      }\n    }\n  }\n\n  /**\n   * Destroy the client, removing listeners and stopping timers.\n   * After destroy, the instance should not be used.\n   */\n  destroy(): void {\n    this.shouldReconnect = false;\n    this.clearReconnectTimer();\n    this.clearPingTimer();\n    this.reconnectAttempts = 0;\n    this.rejectConnected?.(new Error(\"Destroyed\"));\n    void this.connectedPromise?.catch(() => { });\n    this.rejectConnected = undefined;\n    this.resolveConnected = undefined;\n    this.rejectAllPingWaiters(new Error(\"Destroyed\"));\n    if (this.fragmentBatches.size) {\n      for (const [, batch] of this.fragmentBatches) {\n        try {\n          clearTimeout(batch.timeoutId);\n        } catch { }\n      }\n      this.fragmentBatches.clear();\n    }\n    this.awaitingPongSince = undefined;\n    const ws = this.ws;\n    if (ws && this.socketListeners.has(ws)) {\n      this.ops.onWsClose?.();\n    }\n    this.queuedJoins = [];\n    this.sentUpdateBatches.clear();\n    this.detachSocketListeners(ws);\n    try {\n      this.removeNetworkListeners?.();\n    } catch { }\n    this.removeNetworkListeners = undefined;\n    this.roomStatusListeners.clear();\n    // Close websocket after flushing pending frames\n    try {\n      this.flushAndCloseWebSocket(ws, {\n        code: 1000,\n        reason: \"Client destroyed\",\n      });\n    } catch { }\n    this.setStatus(ClientStatus.Disconnected);\n  }\n\n  private flushAndCloseWebSocket(\n    ws: WebSocket | undefined,\n    opts?: { code?: number; reason?: string; timeoutMs?: number }\n  ): void {\n    if (!ws) return;\n    const { code, reason, timeoutMs = 2000 } = opts ?? {};\n\n    const readBufferedAmount = (): number | undefined => {\n      const raw = Reflect.get(ws, \"bufferedAmount\") as unknown;\n      return typeof raw === \"number\" ? raw : undefined;\n    };\n\n    const safeClose = () => {\n      try {\n        ws.close(code, reason);\n      } catch { }\n    };\n\n    if (readBufferedAmount() == null) {\n      safeClose();\n      return;\n    }\n\n    const start = Date.now();\n    let requested = false;\n    const attemptClose = () => {\n      if (requested) return;\n      const state = ws.readyState;\n      if (state === WebSocket.CLOSED || state === WebSocket.CLOSING) {\n        requested = true;\n        safeClose();\n        return;\n      }\n\n      const buffered = readBufferedAmount();\n      if (\n        buffered == null ||\n        buffered <= 0 ||\n        Date.now() - start >= timeoutMs\n      ) {\n        requested = true;\n        safeClose();\n        return;\n      }\n\n      setTimeout(attemptClose, 25);\n    };\n\n    attemptClose();\n  }\n\n  private detachSocketListeners(ws: WebSocket | undefined): void {\n    if (!ws) return;\n    const handlers = this.socketListeners.get(ws);\n    if (!handlers) return;\n    try {\n      ws.removeEventListener?.(\"open\", handlers.open);\n      ws.removeEventListener?.(\"error\", handlers.error);\n      ws.removeEventListener?.(\"close\", handlers.close);\n      ws.removeEventListener?.(\"message\", handlers.message);\n    } catch { }\n    this.socketListeners.delete(ws);\n  }\n\n  private startPingTimer(): void {\n    const interval = getPingIntervalMs(this.ops);\n    if (!interval) return;\n    this.clearPingTimer();\n    this.pingTimer = setInterval(() => {\n      const now = Date.now();\n      const timeoutMs = Math.max(1, this.ops.pingTimeoutMs ?? 10_000);\n      if (\n        this.awaitingPongSince != null &&\n        now - this.awaitingPongSince > timeoutMs\n      ) {\n        this.missedPongs += 1;\n        this.awaitingPongSince = now;\n        if (this.missedPongs >= 2) {\n          try {\n            this.ws?.close(1001, \"ping_timeout\");\n          } catch (err) {\n            this.logCbError(\"pingTimer close\", err);\n          }\n          return;\n        }\n      }\n      try {\n        if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n          // Avoid overlapping RTT probes\n          if (this.awaitingPongSince == null) {\n            if (this.safeSend(this.ws, \"ping\", \"ping\")) {\n              this.awaitingPongSince = Date.now();\n            }\n          } else {\n            // Still awaiting a pong; skip sending another ping\n          }\n        }\n      } catch (err) {\n        this.logCbError(\"pingTimer send\", err);\n      }\n    }, interval);\n  }\n\n  private clearPingTimer(): void {\n    if (this.pingTimer) clearInterval(this.pingTimer);\n    this.pingTimer = undefined;\n  }\n\n  private handlePong(): void {\n    // RTT measurement\n    if (this.awaitingPongSince != null) {\n      const rtt = Date.now() - this.awaitingPongSince;\n      if (rtt >= 0 && isFinite(rtt)) {\n        this.lastLatencyMs = rtt;\n        const listeners = Array.from(this.latencyListeners);\n        for (const cb of listeners) {\n          try {\n            cb(rtt);\n          } catch (err) {\n            this.logCbError(\"onLatency\", err);\n          }\n        }\n      }\n      this.awaitingPongSince = undefined;\n    }\n    this.missedPongs = 0;\n    // Resolve all waiters on any pong\n    if (this.pingWaiters.length > 0) {\n      const waiters = this.pingWaiters.splice(0, this.pingWaiters.length);\n      for (const w of waiters) w.resolve();\n    }\n  }\n\n  private rejectAllPingWaiters(err: Error): void {\n    while (this.pingWaiters.length) {\n      const w = this.pingWaiters.shift()!;\n      try {\n        clearTimeout(w.timeoutId);\n        w.reject(err);\n      } catch { }\n    }\n  }\n\n  /** Manual reconnect helper that resets backoff and attempts immediately. */\n  retryNow(): Promise<void> {\n    return this.connect({ resetBackoff: true });\n  }\n\n  private getReconnectPolicy() {\n    const p = this.ops.reconnect ?? {};\n    return {\n      enabled: p.enabled ?? true,\n      initialDelayMs: Math.max(1, p.initialDelayMs ?? 500),\n      maxDelayMs: Math.max(1, p.maxDelayMs ?? 15_000),\n      jitter: Math.max(0, Math.min(1, p.jitter ?? 0.25)),\n      maxAttempts: p.maxAttempts ?? \"infinite\",\n      fatalCloseCodes: p.fatalCloseCodes ?? [\n        1008,\n        1011,\n        // 4400-4499\n        ...Array.from({ length: 100 }, (_, i) => 4400 + i),\n      ],\n      fatalCloseReasons: p.fatalCloseReasons ?? [\n        \"permission_changed\",\n        \"room_closed\",\n        \"auth_failed\",\n      ],\n    };\n  }\n\n  private computeBackoffDelay(attempt: number): number {\n    const policy = this.getReconnectPolicy();\n    const base = policy.initialDelayMs;\n    const max = policy.maxDelayMs;\n    const raw = base * 2 ** Math.max(0, attempt - 1);\n    const jitterFactor =\n      1 +\n      (policy.jitter === 0\n        ? 0\n        : (Math.random() * 2 - 1) * policy.jitter);\n    const withJitter = raw * jitterFactor;\n    return Math.min(max, Math.max(0, Math.floor(withJitter)));\n  }\n\n  private safeSend(\n    ws: WebSocket | undefined,\n    data: Parameters<WebSocket[\"send\"]>[0],\n    context?: string\n  ): boolean {\n    if (!ws || ws !== this.ws) return false;\n    if (ws.readyState !== WebSocket.OPEN) {\n      if (context) {\n        this.emitError(new Error(`WebSocket not open during ${context}`));\n      }\n      return false;\n    }\n    try {\n      ws.send(data);\n      return true;\n    } catch (err) {\n      this.emitError(err instanceof Error ? err : new Error(String(err)));\n      return false;\n    }\n  }\n\n  private logCbError(context: string, err: unknown) {\n    // eslint-disable-next-line no-console\n    console.error(`[loro-websocket] ${context} callback threw`, err);\n    this.emitError(err instanceof Error ? err : new Error(String(err)));\n  }\n\n  private emitError(err: Error) {\n    try {\n      this.ops.onError?.(err);\n    } catch (cbErr) {\n      // eslint-disable-next-line no-console\n      console.error(\"[loro-websocket] onError callback threw\", cbErr);\n    }\n  }\n\n  private isFatalClose(code?: number, reason?: string): boolean {\n    const policy = this.getReconnectPolicy();\n    if (code != null && policy.fatalCloseCodes.includes(code)) return true;\n    if (reason && policy.fatalCloseReasons.includes(reason)) return true;\n    return false;\n  }\n\n  private enqueueJoin(payload: Uint8Array) {\n    this.queuedJoins.push(payload);\n  }\n\n  private sendJoinPayload(payload: Uint8Array) {\n    if (this.safeSend(this.ws, payload, \"join\")) return;\n    this.enqueueJoin(payload);\n    void this.connect();\n  }\n\n  private flushQueuedJoins() {\n    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n    if (!this.queuedJoins.length) return;\n    const items = this.queuedJoins.splice(0, this.queuedJoins.length);\n    for (const payload of items) {\n      try {\n        this.ws.send(payload);\n      } catch (e) {\n        console.error(\"Failed to flush queued join:\", e);\n      }\n    }\n  }\n\n  private emitRoomStatus(roomKey: string, status: RoomJoinStatusValue) {\n    const set = this.roomStatusListeners.get(roomKey);\n    if (!set || set.size === 0) return;\n    for (const cb of Array.from(set)) {\n      try {\n        cb(status);\n      } catch (err) {\n        this.logCbError(\"onRoomStatusChange\", err);\n      }\n    }\n  }\n\n  private failAllPendingRooms(err: Error, status: RoomJoinStatusValue) {\n    const entries = Array.from(this.pendingRooms.entries());\n    for (const [id, pending] of entries) {\n      try {\n        this.emitRoomStatus(id, status);\n      } catch { }\n      try {\n        pending.reject(err);\n      } catch { }\n      try {\n        this.cleanupRoom(pending.roomId, pending.adaptor.crdtType);\n      } catch { }\n      this.pendingRooms.delete(id);\n    }\n  }\n}\n\nexport interface LoroWebsocketClientRoom {\n  /**\n   * Leave the room.\n   */\n  leave(): Promise<void>;\n  /**\n   * This method returns a promise that resolves when the client document version is >= the server's version.\n   */\n  waitForReachingServerVersion(): Promise<void>;\n  destroy(): Promise<void>;\n}\n\nclass LoroWebsocketClientRoomImpl\n  implements LoroWebsocketClientRoom, InternalRoomHandler {\n  private client: LoroWebsocketClient;\n  private roomId: string;\n  private crdtType: CrdtType;\n  private crdtAdaptor: CrdtDocAdaptor;\n  private destroyed: boolean = false;\n  private unsubscribe: (() => void)[] = [];\n\n  constructor(opts: {\n    client: LoroWebsocketClient;\n    roomId: string;\n    crdtType: CrdtType;\n    crdtAdaptor: CrdtDocAdaptor;\n  }) {\n    this.client = opts.client;\n    this.roomId = opts.roomId;\n    this.crdtType = opts.crdtType;\n    this.crdtAdaptor = opts.crdtAdaptor;\n\n    // Room lifetime is controlled explicitly by leave()/destroy().\n    // Do not auto-destroy on underlying ws close; client handles reconnects.\n  }\n\n  /**\n   * This method returns a promise that resolves when the client document version is >= the server's version.\n   */\n  async waitForReachingServerVersion(): Promise<void> {\n    return this.crdtAdaptor.waitForReachingServerVersion();\n  }\n\n  handleDocUpdate(updates: Uint8Array[], refId?: HexString) {\n    try {\n      this.crdtAdaptor.applyUpdate(updates);\n    } catch (error) {\n      // Surface failure for visibility and inform server about invalid batch\n      console.error(\"Failed to apply remote update\", error);\n      // Inform server that the update failed if we can reference the batch ID\n      if (refId && this.client.socket?.readyState === WebSocket.OPEN) {\n        try {\n          this.client.socket.send(\n            encode({\n              type: MessageType.Ack,\n              crdt: this.crdtType,\n              roomId: this.roomId,\n              refId,\n              status: UpdateStatusCode.InvalidUpdate,\n            } as Ack)\n          );\n        } catch (err) {\n          console.error(\"Failed to send failure Ack\", err);\n        }\n      }\n    }\n  }\n\n  handleAck(ack: Ack) {\n    const sent = this.client.consumeSentBatch(ack.refId);\n    if (ack.status !== UpdateStatusCode.Ok) {\n      const updates = sent?.updates ?? [];\n      const reason = updateStatusToReason(ack.status);\n      this.crdtAdaptor.onUpdateError?.(updates, ack.status, reason);\n      if (!sent) {\n        console.warn(`Ack status ${ack.status} for ${this.crdtType}:${this.roomId} (ref ${ack.refId}) with no matching batch`);\n      }\n    }\n  }\n\n  handleRoomError(error: RoomError) {\n    // Only mark destroyed for terminal errors; rejoin-suggested errors keep the room alive for retry.\n    if (error.code !== RoomErrorCode.RejoinSuggested) {\n      this.destroyed = true;\n    }\n  }\n\n  async leave() {\n    if (this.destroyed) {\n      return;\n    }\n\n    // Send Leave message\n    // Use client's current websocket to ensure it works across reconnects\n    this.client.sendLeave(this.crdtType, this.roomId);\n  }\n\n  async destroy() {\n    if (this.destroyed) {\n      return;\n    }\n\n    await this.leave();\n    this.destroyed = true;\n    this.crdtAdaptor.destroy();\n    this.unsubscribe.forEach(fn => {\n      fn();\n    });\n    this.unsubscribe = [];\n\n    // Unregister from client\n    this.client.cleanupRoom(this.roomId, this.crdtType);\n  }\n}\n\n// --- Keepalive helpers (ping/pong) ---\n\nfunction updateStatusToReason(status: UpdateStatusCode): string | undefined {\n  switch (status) {\n    case UpdateStatusCode.Unknown:\n      return \"unknown\";\n    case UpdateStatusCode.PermissionDenied:\n      return \"permission_denied\";\n    case UpdateStatusCode.InvalidUpdate:\n      return \"invalid_update\";\n    case UpdateStatusCode.PayloadTooLarge:\n      return \"payload_too_large\";\n    case UpdateStatusCode.RateLimited:\n      return \"rate_limited\";\n    case UpdateStatusCode.FragmentTimeout:\n      return \"fragment_timeout\";\n    case UpdateStatusCode.AppError:\n      return \"app_error\";\n    default:\n      return undefined;\n  }\n}\n\n// --- Internal ping helpers ---\nfunction isPositive(v: unknown): v is number {\n  return typeof v === \"number\" && isFinite(v) && v > 0;\n}\n\n// Use default 30s unless disabled\nfunction getPingIntervalMs(opts: {\n  pingIntervalMs?: number;\n  disablePing?: boolean;\n}): number | undefined {\n  if (opts.disablePing) return undefined;\n  const v = opts.pingIntervalMs;\n  if (isPositive(v)) return v;\n  return 30_000;\n}\n\nfunction createLoroWebsocketClientRoom(opts: {\n  client: LoroWebsocketClient;\n  roomId: string;\n  crdtType: CrdtType;\n  crdtAdaptor: CrdtDocAdaptor;\n}): { room: LoroWebsocketClientRoom; handler: InternalRoomHandler } {\n  const impl = new LoroWebsocketClientRoomImpl(opts);\n  return {\n    room: impl,\n    handler: impl,\n  };\n}\n"],"mappings":"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"}