loro-repo 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.cjs","names":["auth: Uint8Array | undefined","session","subscription: TransportSubscription","LoroWebsocketClient","FlockAdaptor","session: MetadataSession","LoroAdaptor","session: DocSession"],"sources":["../../src/internal/debug.ts","../../src/transport/websocket.ts"],"sourcesContent":["type EnvRecord = Record<string, string | undefined>;\n\nconst getEnv = (): EnvRecord | undefined => {\n if (typeof globalThis !== \"object\" || globalThis === null) {\n return undefined;\n }\n const processLike = (globalThis as { process?: { env?: EnvRecord } }).process;\n return processLike?.env;\n};\n\nconst rawNamespaceConfig = (getEnv()?.LORO_REPO_DEBUG ?? \"\").trim();\n\nconst normalizedNamespaces =\n rawNamespaceConfig.length > 0\n ? rawNamespaceConfig\n .split(/[\\s,]+/)\n .map((token) => token.toLowerCase())\n .filter(Boolean)\n : [];\n\nconst wildcardTokens = new Set([\"*\", \"1\", \"true\", \"all\"]);\nconst namespaceSet = new Set(normalizedNamespaces);\nconst hasWildcard =\n namespaceSet.size > 0 &&\n normalizedNamespaces.some((token) => wildcardTokens.has(token));\n\nexport const isDebugEnabled = (namespace?: string): boolean => {\n if (!namespaceSet.size) {\n return false;\n }\n if (!namespace) {\n return hasWildcard;\n }\n const normalized = namespace.toLowerCase();\n if (hasWildcard) {\n return true;\n }\n if (namespaceSet.has(normalized)) {\n return true;\n }\n const [root] = normalized.split(\":\");\n return namespaceSet.has(root);\n};\n\nexport type DebugLogger = (...args: unknown[]) => void;\n\nexport const createDebugLogger = (namespace: string): DebugLogger => {\n const normalized = namespace.toLowerCase();\n return (...args: unknown[]) => {\n if (!isDebugEnabled(normalized)) {\n return;\n }\n const prefix = `[loro-repo:${namespace}]`;\n if (args.length === 0) {\n console.info(prefix);\n return;\n }\n console.info(prefix, ...args);\n };\n};\n","import { Flock } from \"@loro-dev/flock\";\nimport { LoroDoc } from \"loro-crdt\";\nimport { type CrdtDocAdaptor } from \"loro-adaptors\"\nimport { LoroAdaptor, LoroAdaptorConfig } from \"loro-adaptors/loro\";\nimport { bytesToHex } from \"loro-protocol\";\nimport {\n LoroWebsocketClient,\n type LoroWebsocketClientOptions,\n type LoroWebsocketClientRoom,\n} from \"loro-websocket\";\nimport { createDebugLogger } from \"../internal/debug\";\n\nimport type {\n RepoSyncOptions,\n TransportAdapter,\n TransportJoinParams,\n TransportSubscription,\n TransportSyncResult,\n} from \"../types\";\nimport { FlockAdaptor } from \"loro-adaptors/flock\";\n\ntype MetadataSession = {\n adaptor: CrdtDocAdaptor;\n room: LoroWebsocketClientRoom;\n firstSynced: Promise<void>;\n flock: Flock;\n roomId: string;\n auth?: Uint8Array;\n refCount: number;\n};\n\ntype DocSession = {\n adaptor: CrdtDocAdaptor;\n room: LoroWebsocketClientRoom;\n firstSynced: Promise<void>;\n doc: LoroDoc;\n roomId: string;\n refCount: number;\n};\n\nconst debug = createDebugLogger(\"transport:websocket\");\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs?: number): Promise<T> {\n if (!timeoutMs || timeoutMs <= 0) {\n return promise;\n }\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error(`Operation timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n promise\n .then((value) => {\n clearTimeout(timer);\n resolve(value);\n })\n .catch((error) => {\n clearTimeout(timer);\n reject(error);\n });\n });\n}\n\nfunction normalizeRoomId(roomId: unknown, fallback: string): string {\n if (typeof roomId === \"string\" && roomId.length > 0) {\n return roomId;\n }\n if (roomId instanceof Uint8Array && roomId.length > 0) {\n try {\n return bytesToHex(roomId);\n } catch {\n return fallback;\n }\n }\n return fallback;\n}\n\nexport interface WebSocketTransportOptions {\n /**\n * WebSocket endpoint provided to the loro-websocket client.\n */\n readonly url: string;\n /**\n * Metadata room identifier. Defaults to \"repo:meta\".\n */\n readonly metadataRoomId?: string;\n /**\n * Optional loro-websocket client configuration.\n */\n readonly client?: Omit<LoroWebsocketClientOptions, \"url\">;\n /**\n * Optional adaptor configuration for metadata joins.\n */\n readonly metadataAdaptorConfig?: LoroAdaptorConfig;\n /**\n * Static auth payload for metadata joins.\n */\n readonly metadataAuth?: Uint8Array | (() => Promise<Uint8Array>);\n /**\n * Factory that maps document ids to room identifiers. Defaults to the doc id.\n */\n readonly docRoomId?: (docId: string) => string;\n /**\n * Optional auth provider for document joins.\n */\n readonly docAuth?: (docId: string) => Promise<Uint8Array>;\n}\n\nfunction bytesEqual(a?: Uint8Array, b?: Uint8Array): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i += 1) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * loro-websocket backed {@link TransportAdapter} implementation for LoroRepo. \n * It uses loro-protocol as the underlying protocol for the transport.\n */\nexport class WebSocketTransportAdapter implements TransportAdapter {\n private readonly options: WebSocketTransportOptions;\n private client?: LoroWebsocketClient;\n private metadataSession?: MetadataSession;\n private readonly docSessions = new Map<string, DocSession>();\n\n constructor(options: WebSocketTransportOptions) {\n this.options = options;\n }\n\n async connect(_options?: { timeout?: number }): Promise<void> {\n const client = this.ensureClient();\n debug(\"connect requested\", { status: client.getStatus() });\n try {\n await client.connect();\n debug(\"client.connect resolved\");\n await client.waitConnected();\n debug(\"client.waitConnected resolved\", { status: client.getStatus() });\n } catch (error) {\n debug(\"connect failed\", error);\n throw error;\n }\n }\n\n async close(): Promise<void> {\n debug(\"close requested\", {\n docSessions: this.docSessions.size,\n metadataSession: Boolean(this.metadataSession),\n });\n for (const [docId] of this.docSessions) {\n await this.leaveDocSession(docId).catch(() => { });\n }\n this.docSessions.clear();\n\n await this.teardownMetadataSession().catch(() => { });\n\n if (this.client) {\n const client = this.client;\n this.client = undefined;\n client.destroy();\n debug(\"websocket client destroyed\");\n }\n debug(\"close completed\");\n }\n\n isConnected(): boolean {\n return this.client?.getStatus() === \"connected\";\n }\n\n async syncMeta(\n flock: Flock,\n options?: { timeout?: number },\n ): Promise<TransportSyncResult> {\n debug(\"syncMeta requested\", { roomId: this.options.metadataRoomId });\n try {\n let auth: Uint8Array | undefined;\n if (this.options.metadataAuth) {\n if (typeof this.options.metadataAuth === \"function\") {\n auth = await this.options.metadataAuth();\n } else {\n auth = this.options.metadataAuth;\n }\n }\n const session = await this.ensureMetadataSession(flock, {\n roomId: this.options.metadataRoomId ?? \"repo:meta\",\n auth: auth\n });\n await withTimeout(session.firstSynced, options?.timeout);\n debug(\"syncMeta completed\", { roomId: this.options.metadataRoomId });\n return { ok: true };\n } catch (error) {\n debug(\"syncMeta failed\", error);\n return { ok: false };\n }\n }\n\n joinMetaRoom(\n flock: Flock,\n params?: TransportJoinParams,\n ): TransportSubscription {\n const fallback = this.options.metadataRoomId ?? \"\";\n const roomId = normalizeRoomId(params?.roomId, fallback);\n if (!roomId) {\n throw new Error(\"Metadata room id not configured\");\n }\n\n const session = (async () => {\n let auth: Uint8Array | undefined;\n const authWay = params?.auth ?? this.options.metadataAuth;\n if (typeof authWay === \"function\") {\n auth = await authWay();\n } else {\n auth = authWay;\n }\n debug(\"joinMetaRoom requested\", {\n roomId,\n hasAuth: Boolean(auth && auth.length),\n });\n const ensure = this.ensureMetadataSession(flock, {\n roomId,\n auth,\n });\n return ensure;\n })();\n const firstSynced = session.then((session) => session.firstSynced);\n const getConnected = () => this.isConnected();\n const subscription: TransportSubscription = {\n unsubscribe: () => {\n void session.then((session) => {\n session.refCount = Math.max(0, session.refCount - 1);\n debug(\"metadata session refCount decremented\", {\n roomId: session.roomId,\n refCount: session.refCount,\n });\n if (session.refCount === 0) {\n debug(\"tearing down metadata session due to refCount=0\", {\n roomId: session.roomId,\n });\n void this.teardownMetadataSession(session).catch(() => { });\n }\n });\n },\n firstSyncedWithRemote: firstSynced,\n get connected() {\n return getConnected();\n },\n };\n\n void session.then((session) => {\n session.refCount += 1;\n debug(\"metadata session refCount incremented\", {\n roomId: session.roomId,\n refCount: session.refCount,\n });\n });\n return subscription;\n }\n\n async syncDoc(\n docId: string,\n doc: LoroDoc,\n options?: { timeout?: number },\n ): Promise<TransportSyncResult> {\n debug(\"syncDoc requested\", { docId });\n try {\n const session = await this.ensureDocSession(docId, doc, {});\n await withTimeout(session.firstSynced, options?.timeout);\n debug(\"syncDoc completed\", { docId, roomId: session.roomId });\n return { ok: true };\n } catch (error) {\n debug(\"syncDoc failed\", { docId, error });\n return { ok: false };\n }\n }\n\n joinDocRoom(\n docId: string,\n doc: LoroDoc,\n params?: TransportJoinParams,\n ): TransportSubscription {\n debug(\"joinDocRoom requested\", {\n docId,\n roomParamType: params?.roomId\n ? typeof params.roomId === \"string\"\n ? \"string\"\n : \"uint8array\"\n : undefined,\n hasAuthOverride: Boolean(params?.auth && params.auth.length),\n });\n const ensure = this.ensureDocSession(docId, doc, params ?? {});\n const firstSynced = ensure.then((session) => session.firstSynced);\n const getConnected = () => this.isConnected();\n const subscription: TransportSubscription = {\n unsubscribe: () => {\n void ensure.then((session) => {\n session.refCount = Math.max(0, session.refCount - 1);\n debug(\"doc session refCount decremented\", {\n docId,\n roomId: session.roomId,\n refCount: session.refCount,\n });\n if (session.refCount === 0) {\n void this.leaveDocSession(docId).catch(() => { });\n }\n });\n },\n firstSyncedWithRemote: firstSynced,\n get connected() {\n return getConnected();\n },\n };\n void ensure.then((session) => {\n session.refCount += 1;\n debug(\"doc session refCount incremented\", {\n docId,\n roomId: session.roomId,\n refCount: session.refCount,\n });\n });\n return subscription;\n }\n\n private ensureClient(): LoroWebsocketClient {\n if (this.client) {\n debug(\"reusing websocket client\", { status: this.client.getStatus() });\n return this.client;\n }\n const { url, client: clientOptions } = this.options;\n debug(\"creating websocket client\", {\n url,\n clientOptionsKeys: clientOptions ? Object.keys(clientOptions) : [],\n });\n const client = new LoroWebsocketClient({\n url,\n ...clientOptions,\n });\n this.client = client;\n return client;\n }\n\n private async ensureMetadataSession(\n flock: Flock,\n params: { roomId: string; auth?: Uint8Array },\n ): Promise<MetadataSession> {\n debug(\"ensureMetadataSession invoked\", {\n roomId: params.roomId,\n hasAuth: Boolean(params.auth && params.auth.length),\n });\n const client = this.ensureClient();\n await client.waitConnected();\n debug(\"websocket client ready for metadata session\", {\n status: client.getStatus(),\n });\n\n if (\n this.metadataSession &&\n this.metadataSession.flock === flock &&\n this.metadataSession.roomId === params.roomId &&\n bytesEqual(this.metadataSession.auth, params.auth)\n ) {\n debug(\"reusing metadata session\", {\n roomId: this.metadataSession.roomId,\n refCount: this.metadataSession.refCount,\n });\n return this.metadataSession;\n }\n\n if (this.metadataSession) {\n debug(\"tearing down previous metadata session\", {\n roomId: this.metadataSession.roomId,\n });\n await this.teardownMetadataSession(this.metadataSession).catch(() => { });\n }\n\n const adaptor = new FlockAdaptor(\n flock,\n this.options.metadataAdaptorConfig\n );\n debug(\"joining metadata room\", {\n roomId: params.roomId,\n hasAuth: Boolean(params.auth && params.auth.length),\n });\n const room = await client.join({\n roomId: params.roomId,\n crdtAdaptor: adaptor,\n auth: params.auth,\n });\n const firstSynced = room.waitForReachingServerVersion();\n firstSynced.then(\n () => {\n debug(\"metadata session firstSynced resolved\", {\n roomId: params.roomId,\n });\n },\n (error) => {\n debug(\"metadata session firstSynced rejected\", {\n roomId: params.roomId,\n error,\n });\n },\n );\n const session: MetadataSession = {\n adaptor,\n room,\n firstSynced,\n flock,\n roomId: params.roomId,\n auth: params.auth,\n refCount: 0,\n };\n this.metadataSession = session;\n return session;\n }\n\n private async teardownMetadataSession(\n session?: MetadataSession,\n ): Promise<void> {\n const target = session ?? this.metadataSession;\n if (!target) return;\n debug(\"teardownMetadataSession invoked\", { roomId: target.roomId });\n if (this.metadataSession === target) {\n this.metadataSession = undefined;\n }\n const { adaptor, room } = target;\n try {\n await room.leave();\n debug(\"metadata room left\", { roomId: target.roomId });\n } catch (error) {\n debug(\"metadata room leave failed; destroying\", {\n roomId: target.roomId,\n error,\n });\n await room.destroy().catch(() => { });\n }\n adaptor.destroy();\n debug(\"metadata session destroyed\", { roomId: target.roomId });\n }\n\n private async ensureDocSession(\n docId: string,\n doc: LoroDoc,\n params: TransportJoinParams,\n ): Promise<DocSession> {\n debug(\"ensureDocSession invoked\", { docId });\n const client = this.ensureClient();\n await client.waitConnected();\n debug(\"websocket client ready for doc session\", {\n docId,\n status: client.getStatus(),\n });\n\n const existing = this.docSessions.get(docId);\n const derivedRoomId = this.options.docRoomId?.(docId) ?? docId;\n const roomId = normalizeRoomId(params.roomId, derivedRoomId);\n let auth: Uint8Array | undefined;\n const authWay = params.auth ?? this.options.docAuth?.(docId);\n auth = await authWay;\n debug(\"doc session params resolved\", {\n docId,\n roomId,\n hasAuth: Boolean(auth && auth.length),\n });\n\n if (existing && existing.doc === doc && existing.roomId === roomId) {\n debug(\"reusing doc session\", {\n docId,\n roomId,\n refCount: existing.refCount,\n });\n return existing;\n }\n\n if (existing) {\n debug(\"doc session mismatch; leaving existing session\", {\n docId,\n previousRoomId: existing.roomId,\n nextRoomId: roomId,\n });\n await this.leaveDocSession(docId).catch(() => { });\n }\n\n const adaptor = new LoroAdaptor(doc);\n debug(\"joining doc room\", {\n docId,\n roomId,\n hasAuth: Boolean(auth && auth.length),\n });\n const room = await client.join({\n roomId,\n crdtAdaptor: adaptor,\n auth,\n });\n const firstSynced = room.waitForReachingServerVersion();\n firstSynced.then(\n () => {\n debug(\"doc session firstSynced resolved\", { docId, roomId });\n },\n (error) => {\n debug(\"doc session firstSynced rejected\", { docId, roomId, error });\n },\n );\n const session: DocSession = {\n adaptor,\n room,\n firstSynced,\n doc,\n roomId,\n refCount: 0,\n };\n this.docSessions.set(docId, session);\n return session;\n }\n\n private async leaveDocSession(docId: string): Promise<void> {\n const session = this.docSessions.get(docId);\n if (!session) {\n debug(\"leaveDocSession invoked but no session found\", { docId });\n return;\n }\n this.docSessions.delete(docId);\n debug(\"leaving doc session\", { docId, roomId: session.roomId });\n try {\n await session.room.leave();\n debug(\"doc room left\", { docId, roomId: session.roomId });\n } catch (error) {\n debug(\"doc room leave failed; destroying\", {\n docId,\n roomId: session.roomId,\n error,\n });\n await session.room.destroy().catch(() => { });\n }\n session.adaptor.destroy();\n debug(\"doc session destroyed\", { docId, roomId: session.roomId });\n }\n}\n\nexport type {\n TransportJoinParams,\n TransportSubscription,\n TransportSyncResult,\n RepoSyncOptions,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"websocket.cjs","names":["auth: Uint8Array | undefined","statusEmitterRef: StatusEmitter | undefined","session","subscription: TransportSubscription","subscription: TransportSubscription & { store: EphemeralStore }","LoroWebsocketClient","FlockAdaptor","session: MetadataSession","LoroAdaptor","session: DocSession","LoroEphemeralAdaptor","session: EphemeralSession"],"sources":["../../src/internal/debug.ts","../../src/transport/websocket.ts"],"sourcesContent":["type EnvRecord = Record<string, string | undefined>;\n\nconst getEnv = (): EnvRecord | undefined => {\n if (typeof globalThis !== \"object\" || globalThis === null) {\n return undefined;\n }\n const processLike = (globalThis as { process?: { env?: EnvRecord } }).process;\n return processLike?.env;\n};\n\nconst rawNamespaceConfig = (getEnv()?.LORO_REPO_DEBUG ?? \"\").trim();\n\nconst normalizedNamespaces =\n rawNamespaceConfig.length > 0\n ? rawNamespaceConfig\n .split(/[\\s,]+/)\n .map((token) => token.toLowerCase())\n .filter(Boolean)\n : [];\n\nconst wildcardTokens = new Set([\"*\", \"1\", \"true\", \"all\"]);\nconst namespaceSet = new Set(normalizedNamespaces);\nconst hasWildcard =\n namespaceSet.size > 0 &&\n normalizedNamespaces.some((token) => wildcardTokens.has(token));\n\nexport const isDebugEnabled = (namespace?: string): boolean => {\n if (!namespaceSet.size) {\n return false;\n }\n if (!namespace) {\n return hasWildcard;\n }\n const normalized = namespace.toLowerCase();\n if (hasWildcard) {\n return true;\n }\n if (namespaceSet.has(normalized)) {\n return true;\n }\n const [root] = normalized.split(\":\");\n return namespaceSet.has(root);\n};\n\nexport type DebugLogger = (...args: unknown[]) => void;\n\nexport const createDebugLogger = (namespace: string): DebugLogger => {\n const normalized = namespace.toLowerCase();\n return (...args: unknown[]) => {\n if (!isDebugEnabled(normalized)) {\n return;\n }\n const prefix = `[loro-repo:${namespace}]`;\n if (args.length === 0) {\n console.info(prefix);\n return;\n }\n console.info(prefix, ...args);\n };\n};\n","import { Flock } from \"@loro-dev/flock\";\nimport { EphemeralStore, LoroDoc } from \"loro-crdt\";\nimport { type CrdtDocAdaptor } from \"loro-adaptors\";\nimport {\n LoroAdaptor,\n LoroAdaptorConfig,\n LoroEphemeralAdaptor,\n} from \"loro-adaptors/loro\";\nimport {\n LoroWebsocketClient,\n type LoroWebsocketClientOptions,\n type LoroWebsocketClientRoom,\n type ClientStatusValue,\n} from \"loro-websocket\";\nimport { createDebugLogger } from \"../internal/debug\";\n\nimport type {\n RepoSyncOptions,\n TransportAdapter,\n TransportJoinParams,\n TransportSubscription,\n TransportSyncResult,\n TransportRoomStatus,\n TransportConnectionStatus,\n} from \"../types\";\nimport { FlockAdaptor } from \"loro-adaptors/flock\";\n\ntype RoomJoinStatusValue = TransportRoomStatus;\nexport type WebSocketRoomKind = \"meta\" | \"doc\" | \"ephemeral\";\nexport interface WebSocketRoomStatusEvent {\n kind: WebSocketRoomKind;\n roomId: string;\n docId?: string;\n status: TransportRoomStatus;\n}\ntype WebSocketRoomStatusEventHandler = (event: WebSocketRoomStatusEvent) => void;\n\ntype MetadataSession = {\n adaptor: CrdtDocAdaptor;\n room: LoroWebsocketClientRoom;\n firstSynced: Promise<void>;\n flock: Flock;\n roomId: string;\n auth?: Uint8Array;\n refCount: number;\n statusEmitter: StatusEmitter;\n statusListener?: (status: TransportRoomStatus) => void;\n};\n\ntype DocSession = {\n adaptor: CrdtDocAdaptor;\n room: LoroWebsocketClientRoom;\n firstSynced: Promise<void>;\n doc: LoroDoc;\n roomId: string;\n docId: string;\n refCount: number;\n statusEmitter: StatusEmitter;\n statusListener?: (status: TransportRoomStatus) => void;\n};\n\ntype EphemeralSession = {\n adaptor: LoroEphemeralAdaptor;\n room?: LoroWebsocketClientRoom;\n firstSynced: Promise<void>;\n store: EphemeralStore;\n roomId: string;\n refCount: number;\n statusEmitter: StatusEmitter;\n};\n\nconst debug = createDebugLogger(\"transport:websocket\");\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs?: number): Promise<T> {\n if (!timeoutMs || timeoutMs <= 0) {\n return promise;\n }\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error(`Operation timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n promise\n .then((value) => {\n clearTimeout(timer);\n resolve(value);\n })\n .catch((error) => {\n clearTimeout(timer);\n reject(error);\n });\n });\n}\n\ntype StatusEmitter = {\n status: TransportRoomStatus;\n listeners: Set<(status: TransportRoomStatus) => void>;\n};\n\nfunction createStatusEmitter(\n initial: RoomJoinStatusValue = \"connecting\",\n): StatusEmitter {\n return { status: initial, listeners: new Set() };\n}\n\nfunction emitStatus(\n emitter: StatusEmitter | undefined,\n status: TransportRoomStatus,\n): void {\n if (!emitter) return;\n emitter.status = status;\n const listeners = Array.from(emitter.listeners);\n for (const cb of listeners) {\n try {\n cb(status as TransportRoomStatus);\n } catch (error) {\n debug(\"status listener error\", error);\n }\n }\n}\n\nfunction mapClientStatusToRoom(status: ClientStatusValue): TransportRoomStatus {\n switch (status) {\n case \"connected\":\n return \"joined\";\n case \"connecting\":\n return \"reconnecting\";\n default:\n return \"disconnected\";\n }\n}\n\nexport interface WebSocketTransportOptions {\n /**\n * WebSocket endpoint provided to the loro-websocket client.\n */\n readonly url: string;\n /**\n * Metadata room identifier. Defaults to \"repo:meta\".\n */\n readonly metadataRoomId?: string;\n /**\n * Optional loro-websocket client configuration.\n */\n readonly client?: Omit<LoroWebsocketClientOptions, \"url\">;\n /**\n * Optional adaptor configuration for metadata joins.\n */\n readonly metadataAdaptorConfig?: LoroAdaptorConfig;\n /**\n * Static auth payload for metadata joins.\n */\n readonly metadataAuth?: Uint8Array | (() => Promise<Uint8Array>);\n /**\n * Factory that maps document ids to room identifiers. Defaults to the doc id.\n */\n readonly docRoomId?: (docId: string) => string;\n /**\n * Optional auth provider for document joins.\n */\n readonly docAuth?: (docId: string) => Promise<Uint8Array>;\n /**\n * Client-level status listener (connecting/connected/disconnected).\n * Invoked immediately with the current status when registered.\n */\n readonly onStatusChange?: (status: ClientStatusValue) => void;\n /**\n * Listener fed by ping/pong RTT measurements from the underlying websocket client.\n */\n readonly onLatency?: (latencyMs: number) => void;\n /**\n * Global room status listener invoked for metadata, document, and ephemeral joins.\n */\n readonly onRoomStatusChange?: WebSocketRoomStatusEventHandler;\n}\n\nfunction bytesEqual(a?: Uint8Array, b?: Uint8Array): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i += 1) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * loro-websocket backed {@link TransportAdapter} implementation for LoroRepo. \n * It uses loro-protocol as the underlying protocol for the transport.\n */\nexport class WebSocketTransportAdapter implements TransportAdapter {\n private readonly options: WebSocketTransportOptions;\n private client?: LoroWebsocketClient;\n private readonly clientListeners: Array<() => void> = [];\n private metadataSession?: MetadataSession;\n private readonly docSessions = new Map<string, DocSession>();\n private readonly ephemeralSessions = new Map<string, EphemeralSession>();\n\n constructor(options: WebSocketTransportOptions) {\n this.options = options;\n }\n\n private trackClientListener(unsubscribe: () => void): void {\n this.clientListeners.push(unsubscribe);\n }\n\n private propagateClientStatus(status: ClientStatusValue): void {\n const mapped = mapClientStatusToRoom(status);\n const meta = this.metadataSession;\n if (meta) {\n this.dispatchRoomStatus(\n \"meta\",\n { roomId: meta.roomId },\n mapped,\n meta.statusEmitter,\n meta.statusListener,\n );\n }\n for (const session of this.docSessions.values()) {\n this.dispatchRoomStatus(\n \"doc\",\n { roomId: session.roomId, docId: session.docId },\n mapped,\n session.statusEmitter,\n session.statusListener,\n );\n }\n for (const session of this.ephemeralSessions.values()) {\n this.dispatchRoomStatus(\n \"ephemeral\",\n { roomId: session.roomId },\n mapped,\n session.statusEmitter,\n undefined,\n );\n }\n }\n\n private dispatchRoomStatus(\n kind: WebSocketRoomKind,\n payload: { roomId: string; docId?: string },\n status: TransportRoomStatus,\n emitter?: StatusEmitter,\n listener?: (status: TransportRoomStatus) => void,\n ): void {\n emitStatus(emitter, status);\n try {\n listener?.(status);\n } catch (error) {\n debug(\"room listener error\", error);\n }\n try {\n this.options.onRoomStatusChange?.({ kind, ...payload, status });\n } catch (error) {\n debug(\"global room status listener error\", error);\n }\n }\n\n private cleanupClientListeners(): void {\n while (this.clientListeners.length > 0) {\n const unsubscribe = this.clientListeners.pop();\n try {\n unsubscribe?.();\n } catch {\n /* noop */\n }\n }\n }\n\n async connect(options?: { timeout?: number; resetBackoff?: boolean }): Promise<void> {\n const client = this.ensureClient();\n debug(\"connect requested\", {\n status: client.getStatus(),\n resetBackoff: Boolean(options?.resetBackoff),\n });\n try {\n const connectFn = (client as unknown as {\n connect: (opts?: unknown) => Promise<void>;\n }).connect;\n await connectFn.call(\n client,\n options?.resetBackoff ? { resetBackoff: options.resetBackoff } : undefined,\n );\n debug(\"client.connect resolved\");\n await withTimeout(client.waitConnected(), options?.timeout);\n debug(\"client.waitConnected resolved\", { status: client.getStatus() });\n } catch (error) {\n debug(\"connect failed\", error);\n throw error;\n }\n }\n\n async reconnect(options?: { timeout?: number; resetBackoff?: boolean }): Promise<void> {\n const client = this.ensureClient();\n debug(\"reconnect requested\", {\n status: client.getStatus(),\n resetBackoff: Boolean(options?.resetBackoff),\n });\n try {\n const connectFn = (client as unknown as {\n connect: (opts?: unknown) => Promise<void>;\n retryNow?: () => Promise<void>;\n }).connect;\n if (options?.resetBackoff) {\n await connectFn.call(client, { resetBackoff: true });\n } else if ((client as unknown as { retryNow?: () => Promise<void> }).retryNow) {\n await (client as unknown as { retryNow: () => Promise<void> }).retryNow.call(client);\n } else {\n await connectFn.call(client);\n }\n await withTimeout(client.waitConnected(), options?.timeout);\n debug(\"reconnect completed\", { status: client.getStatus() });\n } catch (error) {\n debug(\"reconnect failed\", error);\n throw error;\n }\n }\n\n async close(): Promise<void> {\n debug(\"close requested\", {\n docSessions: this.docSessions.size,\n metadataSession: Boolean(this.metadataSession),\n });\n for (const [docId] of this.docSessions) {\n await this.leaveDocSession(docId).catch(() => { });\n }\n this.docSessions.clear();\n for (const [roomId] of this.ephemeralSessions) {\n await this.leaveEphemeralSession(roomId).catch(() => { });\n }\n this.ephemeralSessions.clear();\n\n await this.teardownMetadataSession().catch(() => { });\n\n if (this.client) {\n const client = this.client;\n this.client = undefined;\n this.cleanupClientListeners();\n client.destroy();\n debug(\"websocket client destroyed\");\n }\n debug(\"close completed\");\n }\n\n isConnected(): boolean {\n return this.client?.getStatus() === \"connected\";\n }\n\n getStatus(): TransportConnectionStatus {\n return this.ensureClient().getStatus();\n }\n\n getLatency(): number | undefined {\n return this.ensureClient().getLatency?.();\n }\n\n onStatusChange(\n listener: (status: TransportConnectionStatus) => void,\n ): () => void {\n const unsubscribe = this.ensureClient().onStatusChange(listener);\n this.trackClientListener(unsubscribe);\n return () => {\n unsubscribe();\n const idx = this.clientListeners.indexOf(unsubscribe);\n if (idx >= 0) {\n this.clientListeners.splice(idx, 1);\n }\n };\n }\n\n onLatency(listener: (latencyMs: number) => void): () => void {\n const unsubscribe = this.ensureClient().onLatency(listener);\n this.trackClientListener(unsubscribe);\n return () => {\n unsubscribe();\n const idx = this.clientListeners.indexOf(unsubscribe);\n if (idx >= 0) {\n this.clientListeners.splice(idx, 1);\n }\n };\n }\n\n async syncMeta(\n flock: Flock,\n options?: { timeout?: number },\n ): Promise<TransportSyncResult> {\n debug(\"syncMeta requested\", { roomId: this.options.metadataRoomId });\n try {\n let auth: Uint8Array | undefined;\n if (this.options.metadataAuth) {\n if (typeof this.options.metadataAuth === \"function\") {\n auth = await this.options.metadataAuth();\n } else {\n auth = this.options.metadataAuth;\n }\n }\n const session = await this.ensureMetadataSession(flock, {\n roomId: this.options.metadataRoomId ?? \"repo:meta\",\n auth: auth\n });\n await withTimeout(session.firstSynced, options?.timeout);\n debug(\"syncMeta completed\", { roomId: this.options.metadataRoomId });\n return { ok: true };\n } catch (error) {\n debug(\"syncMeta failed\", error);\n return { ok: false };\n }\n }\n\n joinMetaRoom(\n flock: Flock,\n params?: TransportJoinParams,\n ): TransportSubscription {\n const roomId = this.options.metadataRoomId ?? \"repo:meta\";\n let statusEmitterRef: StatusEmitter | undefined;\n const session = (async () => {\n let auth: Uint8Array | undefined;\n const authWay = params?.auth ?? this.options.metadataAuth;\n if (typeof authWay === \"function\") {\n auth = await authWay();\n } else {\n auth = authWay;\n }\n debug(\"joinMetaRoom requested\", {\n roomId,\n hasAuth: Boolean(auth && auth.length),\n });\n const ensure = this.ensureMetadataSession(flock, {\n roomId,\n auth,\n onStatusChange: params?.onStatusChange,\n });\n const resolved = await ensure;\n statusEmitterRef = resolved.statusEmitter;\n return ensure;\n })();\n const firstSynced = session.then((session) => session.firstSynced);\n const getConnected = () => this.isConnected();\n const subscription: TransportSubscription = {\n unsubscribe: () => {\n void session.then((session) => {\n session.refCount = Math.max(0, session.refCount - 1);\n debug(\"metadata session refCount decremented\", {\n roomId: session.roomId,\n refCount: session.refCount,\n });\n if (session.refCount === 0) {\n debug(\"tearing down metadata session due to refCount=0\", {\n roomId: session.roomId,\n });\n void this.teardownMetadataSession(session).catch(() => { });\n }\n });\n },\n firstSyncedWithRemote: firstSynced,\n get connected() {\n return getConnected();\n },\n get status() {\n return (\n statusEmitterRef?.status ?? \"connecting\"\n ) as TransportRoomStatus;\n },\n onStatusChange: (listener) => {\n const attach = (emitter: StatusEmitter): (() => void) => {\n emitter.listeners.add(listener);\n try {\n listener(emitter.status as TransportRoomStatus);\n } catch (error) {\n debug(\"metadata onStatusChange listener error\", error);\n }\n return () => emitter.listeners.delete(listener);\n };\n if (statusEmitterRef) {\n const cleanup = attach(statusEmitterRef);\n return () => cleanup();\n }\n let unsubscribed = false;\n const cleanupPromise = session.then((resolved) =>\n attach(resolved.statusEmitter),\n );\n return () => {\n if (unsubscribed) return;\n unsubscribed = true;\n void cleanupPromise.then((cleanup) => cleanup()).catch(() => { });\n };\n },\n };\n\n void session.then((session) => {\n session.refCount += 1;\n debug(\"metadata session refCount incremented\", {\n roomId: session.roomId,\n refCount: session.refCount,\n });\n });\n return subscription;\n }\n\n async syncDoc(\n docId: string,\n doc: LoroDoc,\n options?: { timeout?: number },\n ): Promise<TransportSyncResult> {\n debug(\"syncDoc requested\", { docId });\n try {\n const session = await this.ensureDocSession(docId, doc, {});\n await withTimeout(session.firstSynced, options?.timeout);\n debug(\"syncDoc completed\", { docId, roomId: session.roomId });\n return { ok: true };\n } catch (error) {\n debug(\"syncDoc failed\", { docId, error });\n return { ok: false };\n }\n }\n\n joinDocRoom(\n docId: string,\n doc: LoroDoc,\n params?: TransportJoinParams,\n ): TransportSubscription {\n debug(\"joinDocRoom requested\", {\n docId,\n hasAuthOverride: Boolean(params?.auth && params.auth.length),\n });\n let statusEmitterRef: StatusEmitter | undefined;\n const ensure = this.ensureDocSession(docId, doc, params ?? {}).then(\n (session) => {\n statusEmitterRef = session.statusEmitter;\n return session;\n },\n );\n const firstSynced = ensure.then((session) => session.firstSynced);\n const getConnected = () => this.isConnected();\n const subscription: TransportSubscription = {\n unsubscribe: () => {\n void ensure.then((session) => {\n session.refCount = Math.max(0, session.refCount - 1);\n debug(\"doc session refCount decremented\", {\n docId,\n roomId: session.roomId,\n refCount: session.refCount,\n });\n if (session.refCount === 0) {\n void this.leaveDocSession(docId).catch(() => { });\n }\n });\n },\n firstSyncedWithRemote: firstSynced,\n get connected() {\n return getConnected();\n },\n get status() {\n return (\n statusEmitterRef?.status ?? \"connecting\"\n ) as TransportRoomStatus;\n },\n onStatusChange: (listener) => {\n const attach = (emitter: StatusEmitter): (() => void) => {\n emitter.listeners.add(listener);\n try {\n listener(emitter.status as TransportRoomStatus);\n } catch (error) {\n debug(\"doc onStatusChange listener error\", error);\n }\n return () => emitter.listeners.delete(listener);\n };\n if (statusEmitterRef) {\n const cleanup = attach(statusEmitterRef);\n return () => cleanup();\n }\n let unsubscribed = false;\n const cleanupPromise = ensure.then((session) =>\n attach(session.statusEmitter),\n );\n return () => {\n if (unsubscribed) return;\n unsubscribed = true;\n void cleanupPromise.then((cleanup) => cleanup()).catch(() => { });\n };\n },\n };\n void ensure.then((session) => {\n session.refCount += 1;\n debug(\"doc session refCount incremented\", {\n docId,\n roomId: session.roomId,\n refCount: session.refCount,\n });\n });\n return subscription;\n }\n\n joinEphemeralRoom(\n roomId: string,\n ): TransportSubscription & { store: EphemeralStore } {\n debug(\"joinEphemeralRoom requested\", { roomId });\n let statusEmitterRef: StatusEmitter | undefined;\n const ensure = this.ensureEphemeralSession(roomId).then((session) => {\n statusEmitterRef = session.statusEmitter;\n return session;\n });\n const session = this.ephemeralSessions.get(roomId);\n const store = session?.store;\n if (!store) {\n throw new Error(\"Failed to initialize ephemeral session\");\n }\n const firstSynced = ensure.then((session) => session.firstSynced);\n const getConnected = () => this.isConnected();\n const subscription: TransportSubscription & { store: EphemeralStore } = {\n store,\n unsubscribe: () => {\n void ensure.then((session) => {\n session.refCount = Math.max(0, session.refCount - 1);\n debug(\"ephemeral session refCount decremented\", {\n roomId,\n refCount: session.refCount,\n });\n if (session.refCount === 0) {\n void this.leaveEphemeralSession(roomId).catch(() => { });\n }\n });\n },\n firstSyncedWithRemote: firstSynced,\n get connected() {\n return getConnected();\n },\n get status() {\n return (\n statusEmitterRef?.status ?? \"connecting\"\n ) as TransportRoomStatus;\n },\n onStatusChange: (listener) => {\n const attach = (emitter: StatusEmitter): (() => void) => {\n emitter.listeners.add(listener);\n try {\n listener(emitter.status as TransportRoomStatus);\n } catch (error) {\n debug(\"ephemeral onStatusChange listener error\", error);\n }\n return () => emitter.listeners.delete(listener);\n };\n if (statusEmitterRef) {\n const cleanup = attach(statusEmitterRef);\n return () => cleanup();\n }\n let unsubscribed = false;\n const cleanupPromise = ensure.then((session) =>\n attach(session.statusEmitter),\n );\n return () => {\n if (unsubscribed) return;\n unsubscribed = true;\n void cleanupPromise.then((cleanup) => cleanup()).catch(() => { });\n };\n },\n };\n void ensure.then((session) => {\n subscription.store = session.store;\n session.refCount += 1;\n debug(\"ephemeral session refCount incremented\", {\n roomId,\n refCount: session.refCount,\n });\n });\n return subscription;\n }\n\n private ensureClient(): LoroWebsocketClient {\n if (this.client) {\n debug(\"reusing websocket client\", { status: this.client.getStatus() });\n return this.client;\n }\n const { url, client: clientOptions } = this.options;\n debug(\"creating websocket client\", {\n url,\n clientOptionsKeys: clientOptions ? Object.keys(clientOptions) : [],\n });\n const client = new LoroWebsocketClient({\n url,\n ...clientOptions,\n });\n this.trackClientListener(\n client.onStatusChange((status) => {\n this.propagateClientStatus(status);\n this.options.onStatusChange?.(status);\n }),\n );\n if (this.options.onLatency) {\n this.trackClientListener(client.onLatency(this.options.onLatency));\n }\n this.client = client;\n // seed current status to sessions\n this.propagateClientStatus(client.getStatus());\n return client;\n }\n\n public get websocketClient(): LoroWebsocketClient {\n return this.ensureClient();\n }\n\n private async ensureMetadataSession(\n flock: Flock,\n params: {\n roomId: string;\n auth?: Uint8Array;\n onStatusChange?: TransportJoinParams[\"onStatusChange\"];\n },\n ): Promise<MetadataSession> {\n debug(\"ensureMetadataSession invoked\", {\n roomId: params.roomId,\n hasAuth: Boolean(params.auth && params.auth.length),\n });\n const client = this.ensureClient();\n await client.waitConnected();\n debug(\"websocket client ready for metadata session\", {\n status: client.getStatus(),\n });\n\n if (\n this.metadataSession &&\n this.metadataSession.flock === flock &&\n this.metadataSession.roomId === params.roomId &&\n bytesEqual(this.metadataSession.auth, params.auth)\n ) {\n debug(\"reusing metadata session\", {\n roomId: this.metadataSession.roomId,\n refCount: this.metadataSession.refCount,\n });\n return this.metadataSession;\n }\n\n if (this.metadataSession) {\n debug(\"tearing down previous metadata session\", {\n roomId: this.metadataSession.roomId,\n });\n await this.teardownMetadataSession(this.metadataSession).catch(() => { });\n }\n\n const adaptor = new FlockAdaptor(flock, this.options.metadataAdaptorConfig);\n const statusEmitter = createStatusEmitter(\"connecting\");\n if (params.onStatusChange) {\n statusEmitter.listeners.add(params.onStatusChange);\n }\n this.dispatchRoomStatus(\n \"meta\",\n { roomId: params.roomId },\n \"connecting\",\n statusEmitter,\n params.onStatusChange,\n );\n debug(\"joining metadata room\", {\n roomId: params.roomId,\n hasAuth: Boolean(params.auth && params.auth.length),\n });\n const room = await client.join({\n roomId: params.roomId,\n crdtAdaptor: adaptor,\n auth: params.auth,\n });\n this.dispatchRoomStatus(\n \"meta\",\n { roomId: params.roomId },\n \"joined\",\n statusEmitter,\n params.onStatusChange,\n );\n const firstSynced = room.waitForReachingServerVersion();\n firstSynced.then(\n () => {\n debug(\"metadata session firstSynced resolved\", {\n roomId: params.roomId,\n });\n },\n (error) => {\n debug(\"metadata session firstSynced rejected\", {\n roomId: params.roomId,\n error,\n });\n },\n );\n const session: MetadataSession = {\n adaptor,\n room,\n firstSynced,\n flock,\n roomId: params.roomId,\n auth: params.auth,\n refCount: 0,\n statusEmitter,\n statusListener: params.onStatusChange,\n };\n this.metadataSession = session;\n return session;\n }\n\n private async teardownMetadataSession(\n session?: MetadataSession,\n ): Promise<void> {\n const target = session ?? this.metadataSession;\n if (!target) return;\n debug(\"teardownMetadataSession invoked\", { roomId: target.roomId });\n if (this.metadataSession === target) {\n this.metadataSession = undefined;\n }\n const { adaptor, room } = target;\n try {\n await room.leave();\n debug(\"metadata room left\", { roomId: target.roomId });\n } catch (error) {\n debug(\"metadata room leave failed; destroying\", {\n roomId: target.roomId,\n error,\n });\n await room.destroy().catch(() => { });\n }\n adaptor.destroy();\n this.dispatchRoomStatus(\n \"meta\",\n { roomId: target.roomId },\n \"disconnected\",\n target.statusEmitter,\n target.statusListener,\n );\n debug(\"metadata session destroyed\", { roomId: target.roomId });\n }\n\n private async ensureDocSession(\n docId: string,\n doc: LoroDoc,\n params: TransportJoinParams,\n ): Promise<DocSession> {\n debug(\"ensureDocSession invoked\", { docId });\n const client = this.ensureClient();\n await client.waitConnected();\n debug(\"websocket client ready for doc session\", {\n docId,\n status: client.getStatus(),\n });\n\n const existing = this.docSessions.get(docId);\n const roomId = this.options.docRoomId?.(docId) ?? docId;\n let auth: Uint8Array | undefined;\n const authWay = params.auth ?? this.options.docAuth?.(docId);\n auth = await authWay;\n debug(\"doc session params resolved\", {\n docId,\n roomId,\n hasAuth: Boolean(auth && auth.length),\n });\n\n if (existing && existing.doc === doc && existing.roomId === roomId) {\n debug(\"reusing doc session\", {\n docId,\n roomId,\n refCount: existing.refCount,\n });\n return existing;\n }\n\n if (existing) {\n debug(\"doc session mismatch; leaving existing session\", {\n docId,\n previousRoomId: existing.roomId,\n nextRoomId: roomId,\n });\n await this.leaveDocSession(docId).catch(() => { });\n }\n\n const adaptor = new LoroAdaptor(doc);\n const statusEmitter = createStatusEmitter(\"connecting\");\n if (params.onStatusChange) {\n statusEmitter.listeners.add(params.onStatusChange);\n }\n this.dispatchRoomStatus(\n \"doc\",\n { roomId, docId },\n \"connecting\",\n statusEmitter,\n params.onStatusChange,\n );\n debug(\"joining doc room\", {\n docId,\n roomId,\n hasAuth: Boolean(auth && auth.length),\n });\n const room = await client.join({\n roomId,\n crdtAdaptor: adaptor,\n auth,\n });\n this.dispatchRoomStatus(\n \"doc\",\n { roomId, docId },\n \"joined\",\n statusEmitter,\n params.onStatusChange,\n );\n const firstSynced = room.waitForReachingServerVersion();\n firstSynced.then(\n () => {\n debug(\"doc session firstSynced resolved\", { docId, roomId });\n },\n (error) => {\n debug(\"doc session firstSynced rejected\", { docId, roomId, error });\n },\n );\n const session: DocSession = {\n adaptor,\n room,\n firstSynced,\n doc,\n roomId,\n docId,\n refCount: 0,\n statusEmitter,\n statusListener: params.onStatusChange,\n };\n this.docSessions.set(docId, session);\n return session;\n }\n\n private async ensureEphemeralSession(\n roomId: string,\n ): Promise<EphemeralSession> {\n debug(\"ensureEphemeralSession invoked\", { roomId });\n const existing = this.ephemeralSessions.get(roomId);\n if (existing) {\n debug(\"reusing ephemeral session\", { roomId, refCount: existing.refCount });\n return existing;\n }\n\n const adaptor = new LoroEphemeralAdaptor();\n const statusEmitter = createStatusEmitter(\"connecting\");\n this.dispatchRoomStatus(\n \"ephemeral\",\n { roomId },\n \"connecting\",\n statusEmitter,\n undefined,\n );\n const store = adaptor.getStore();\n const session: EphemeralSession = {\n adaptor,\n store,\n roomId,\n firstSynced: Promise.resolve(),\n refCount: 0,\n statusEmitter,\n };\n this.ephemeralSessions.set(roomId, session);\n\n const client = this.ensureClient();\n await client.waitConnected();\n debug(\"websocket client ready for ephemeral session\", {\n roomId,\n status: client.getStatus(),\n });\n\n try {\n const room = await client.join({\n roomId,\n crdtAdaptor: adaptor,\n });\n this.dispatchRoomStatus(\n \"ephemeral\",\n { roomId },\n \"joined\",\n statusEmitter,\n undefined,\n );\n const firstSynced = room.waitForReachingServerVersion();\n firstSynced.then(\n () => {\n debug(\"ephemeral session firstSynced resolved\", { roomId });\n },\n (error) => {\n debug(\"ephemeral session firstSynced rejected\", { roomId, error });\n },\n );\n session.room = room;\n session.firstSynced = firstSynced;\n return session;\n } catch (error) {\n this.ephemeralSessions.delete(roomId);\n adaptor.destroy();\n throw error;\n }\n }\n\n private async leaveDocSession(docId: string): Promise<void> {\n const session = this.docSessions.get(docId);\n if (!session) {\n debug(\"leaveDocSession invoked but no session found\", { docId });\n return;\n }\n this.docSessions.delete(docId);\n debug(\"leaving doc session\", { docId, roomId: session.roomId });\n try {\n await session.room.leave();\n debug(\"doc room left\", { docId, roomId: session.roomId });\n } catch (error) {\n debug(\"doc room leave failed; destroying\", {\n docId,\n roomId: session.roomId,\n error,\n });\n await session.room.destroy().catch(() => { });\n }\n session.adaptor.destroy();\n this.dispatchRoomStatus(\n \"doc\",\n { roomId: session.roomId, docId: session.docId },\n \"disconnected\",\n session.statusEmitter,\n session.statusListener,\n );\n debug(\"doc session destroyed\", { docId, roomId: session.roomId });\n }\n\n private async leaveEphemeralSession(roomId: string): Promise<void> {\n const session = this.ephemeralSessions.get(roomId);\n if (!session) {\n debug(\"leaveEphemeralSession invoked but no session found\", { roomId });\n return;\n }\n this.ephemeralSessions.delete(roomId);\n debug(\"leaving ephemeral session\", { roomId });\n try {\n await session.room?.leave();\n debug(\"ephemeral room left\", { roomId });\n } catch (error) {\n debug(\"ephemeral room leave failed; destroying\", { roomId, error });\n await session.room?.destroy().catch(() => { });\n }\n session.adaptor.destroy();\n this.dispatchRoomStatus(\n \"ephemeral\",\n { roomId: session.roomId },\n \"disconnected\",\n session.statusEmitter,\n undefined,\n );\n debug(\"ephemeral session destroyed\", { roomId });\n }\n}\n\nexport type {\n TransportJoinParams,\n TransportSubscription,\n TransportSyncResult,\n RepoSyncOptions,\n};\n"],"mappings":"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"}
@@ -1,10 +1,18 @@
1
- import { A as TransportSyncResult, D as TransportJoinParams, E as TransportAdapter, O as TransportSubscription, x as RepoSyncOptions } from "../types.cjs";
1
+ import { A as TransportConnectionStatus, F as TransportSyncResult, M as TransportRoomStatus, N as TransportSubscription, j as TransportJoinParams, k as TransportAdapter, w as RepoSyncOptions } from "../types.cjs";
2
2
  import { Flock } from "@loro-dev/flock";
3
- import { LoroDoc } from "loro-crdt";
3
+ import { EphemeralStore, LoroDoc } from "loro-crdt";
4
4
  import { LoroAdaptorConfig } from "loro-adaptors/loro";
5
- import { LoroWebsocketClientOptions } from "loro-websocket";
5
+ import { ClientStatusValue, LoroWebsocketClient, LoroWebsocketClientOptions } from "loro-websocket";
6
6
 
7
7
  //#region src/transport/websocket.d.ts
8
+ type WebSocketRoomKind = "meta" | "doc" | "ephemeral";
9
+ interface WebSocketRoomStatusEvent {
10
+ kind: WebSocketRoomKind;
11
+ roomId: string;
12
+ docId?: string;
13
+ status: TransportRoomStatus;
14
+ }
15
+ type WebSocketRoomStatusEventHandler = (event: WebSocketRoomStatusEvent) => void;
8
16
  interface WebSocketTransportOptions {
9
17
  /**
10
18
  * WebSocket endpoint provided to the loro-websocket client.
@@ -34,6 +42,19 @@ interface WebSocketTransportOptions {
34
42
  * Optional auth provider for document joins.
35
43
  */
36
44
  readonly docAuth?: (docId: string) => Promise<Uint8Array>;
45
+ /**
46
+ * Client-level status listener (connecting/connected/disconnected).
47
+ * Invoked immediately with the current status when registered.
48
+ */
49
+ readonly onStatusChange?: (status: ClientStatusValue) => void;
50
+ /**
51
+ * Listener fed by ping/pong RTT measurements from the underlying websocket client.
52
+ */
53
+ readonly onLatency?: (latencyMs: number) => void;
54
+ /**
55
+ * Global room status listener invoked for metadata, document, and ephemeral joins.
56
+ */
57
+ readonly onRoomStatusChange?: WebSocketRoomStatusEventHandler;
37
58
  }
38
59
  /**
39
60
  * loro-websocket backed {@link TransportAdapter} implementation for LoroRepo.
@@ -42,14 +63,29 @@ interface WebSocketTransportOptions {
42
63
  declare class WebSocketTransportAdapter implements TransportAdapter {
43
64
  private readonly options;
44
65
  private client?;
66
+ private readonly clientListeners;
45
67
  private metadataSession?;
46
68
  private readonly docSessions;
69
+ private readonly ephemeralSessions;
47
70
  constructor(options: WebSocketTransportOptions);
48
- connect(_options?: {
71
+ private trackClientListener;
72
+ private propagateClientStatus;
73
+ private dispatchRoomStatus;
74
+ private cleanupClientListeners;
75
+ connect(options?: {
76
+ timeout?: number;
77
+ resetBackoff?: boolean;
78
+ }): Promise<void>;
79
+ reconnect(options?: {
49
80
  timeout?: number;
81
+ resetBackoff?: boolean;
50
82
  }): Promise<void>;
51
83
  close(): Promise<void>;
52
84
  isConnected(): boolean;
85
+ getStatus(): TransportConnectionStatus;
86
+ getLatency(): number | undefined;
87
+ onStatusChange(listener: (status: TransportConnectionStatus) => void): () => void;
88
+ onLatency(listener: (latencyMs: number) => void): () => void;
53
89
  syncMeta(flock: Flock, options?: {
54
90
  timeout?: number;
55
91
  }): Promise<TransportSyncResult>;
@@ -58,12 +94,18 @@ declare class WebSocketTransportAdapter implements TransportAdapter {
58
94
  timeout?: number;
59
95
  }): Promise<TransportSyncResult>;
60
96
  joinDocRoom(docId: string, doc: LoroDoc, params?: TransportJoinParams): TransportSubscription;
97
+ joinEphemeralRoom(roomId: string): TransportSubscription & {
98
+ store: EphemeralStore;
99
+ };
61
100
  private ensureClient;
101
+ get websocketClient(): LoroWebsocketClient;
62
102
  private ensureMetadataSession;
63
103
  private teardownMetadataSession;
64
104
  private ensureDocSession;
105
+ private ensureEphemeralSession;
65
106
  private leaveDocSession;
107
+ private leaveEphemeralSession;
66
108
  }
67
109
  //#endregion
68
- export { type RepoSyncOptions, type TransportJoinParams, type TransportSubscription, type TransportSyncResult, WebSocketTransportAdapter, WebSocketTransportOptions };
110
+ export { type RepoSyncOptions, type TransportJoinParams, type TransportSubscription, type TransportSyncResult, WebSocketRoomKind, WebSocketRoomStatusEvent, WebSocketTransportAdapter, WebSocketTransportOptions };
69
111
  //# sourceMappingURL=websocket.d.cts.map
@@ -1,10 +1,18 @@
1
- import { A as TransportSyncResult, D as TransportJoinParams, E as TransportAdapter, O as TransportSubscription, x as RepoSyncOptions } from "../types.js";
1
+ import { A as TransportConnectionStatus, F as TransportSyncResult, M as TransportRoomStatus, N as TransportSubscription, j as TransportJoinParams, k as TransportAdapter, w as RepoSyncOptions } from "../types.js";
2
2
  import { Flock } from "@loro-dev/flock";
3
- import { LoroDoc } from "loro-crdt";
3
+ import { EphemeralStore, LoroDoc } from "loro-crdt";
4
4
  import { LoroAdaptorConfig } from "loro-adaptors/loro";
5
- import { LoroWebsocketClientOptions } from "loro-websocket";
5
+ import { ClientStatusValue, LoroWebsocketClient, LoroWebsocketClientOptions } from "loro-websocket";
6
6
 
7
7
  //#region src/transport/websocket.d.ts
8
+ type WebSocketRoomKind = "meta" | "doc" | "ephemeral";
9
+ interface WebSocketRoomStatusEvent {
10
+ kind: WebSocketRoomKind;
11
+ roomId: string;
12
+ docId?: string;
13
+ status: TransportRoomStatus;
14
+ }
15
+ type WebSocketRoomStatusEventHandler = (event: WebSocketRoomStatusEvent) => void;
8
16
  interface WebSocketTransportOptions {
9
17
  /**
10
18
  * WebSocket endpoint provided to the loro-websocket client.
@@ -34,6 +42,19 @@ interface WebSocketTransportOptions {
34
42
  * Optional auth provider for document joins.
35
43
  */
36
44
  readonly docAuth?: (docId: string) => Promise<Uint8Array>;
45
+ /**
46
+ * Client-level status listener (connecting/connected/disconnected).
47
+ * Invoked immediately with the current status when registered.
48
+ */
49
+ readonly onStatusChange?: (status: ClientStatusValue) => void;
50
+ /**
51
+ * Listener fed by ping/pong RTT measurements from the underlying websocket client.
52
+ */
53
+ readonly onLatency?: (latencyMs: number) => void;
54
+ /**
55
+ * Global room status listener invoked for metadata, document, and ephemeral joins.
56
+ */
57
+ readonly onRoomStatusChange?: WebSocketRoomStatusEventHandler;
37
58
  }
38
59
  /**
39
60
  * loro-websocket backed {@link TransportAdapter} implementation for LoroRepo.
@@ -42,14 +63,29 @@ interface WebSocketTransportOptions {
42
63
  declare class WebSocketTransportAdapter implements TransportAdapter {
43
64
  private readonly options;
44
65
  private client?;
66
+ private readonly clientListeners;
45
67
  private metadataSession?;
46
68
  private readonly docSessions;
69
+ private readonly ephemeralSessions;
47
70
  constructor(options: WebSocketTransportOptions);
48
- connect(_options?: {
71
+ private trackClientListener;
72
+ private propagateClientStatus;
73
+ private dispatchRoomStatus;
74
+ private cleanupClientListeners;
75
+ connect(options?: {
76
+ timeout?: number;
77
+ resetBackoff?: boolean;
78
+ }): Promise<void>;
79
+ reconnect(options?: {
49
80
  timeout?: number;
81
+ resetBackoff?: boolean;
50
82
  }): Promise<void>;
51
83
  close(): Promise<void>;
52
84
  isConnected(): boolean;
85
+ getStatus(): TransportConnectionStatus;
86
+ getLatency(): number | undefined;
87
+ onStatusChange(listener: (status: TransportConnectionStatus) => void): () => void;
88
+ onLatency(listener: (latencyMs: number) => void): () => void;
53
89
  syncMeta(flock: Flock, options?: {
54
90
  timeout?: number;
55
91
  }): Promise<TransportSyncResult>;
@@ -58,12 +94,18 @@ declare class WebSocketTransportAdapter implements TransportAdapter {
58
94
  timeout?: number;
59
95
  }): Promise<TransportSyncResult>;
60
96
  joinDocRoom(docId: string, doc: LoroDoc, params?: TransportJoinParams): TransportSubscription;
97
+ joinEphemeralRoom(roomId: string): TransportSubscription & {
98
+ store: EphemeralStore;
99
+ };
61
100
  private ensureClient;
101
+ get websocketClient(): LoroWebsocketClient;
62
102
  private ensureMetadataSession;
63
103
  private teardownMetadataSession;
64
104
  private ensureDocSession;
105
+ private ensureEphemeralSession;
65
106
  private leaveDocSession;
107
+ private leaveEphemeralSession;
66
108
  }
67
109
  //#endregion
68
- export { type RepoSyncOptions, type TransportJoinParams, type TransportSubscription, type TransportSyncResult, WebSocketTransportAdapter, WebSocketTransportOptions };
110
+ export { type RepoSyncOptions, type TransportJoinParams, type TransportSubscription, type TransportSyncResult, WebSocketRoomKind, WebSocketRoomStatusEvent, WebSocketTransportAdapter, WebSocketTransportOptions };
69
111
  //# sourceMappingURL=websocket.d.ts.map