loro-repo 0.5.0 → 0.5.1

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.
@@ -0,0 +1,430 @@
1
+ import { LoroAdaptor } from "loro-adaptors/loro";
2
+ import { bytesToHex } from "loro-protocol";
3
+ import { LoroWebsocketClient } from "loro-websocket";
4
+ import { FlockAdaptor } from "loro-adaptors/flock";
5
+
6
+ //#region src/internal/debug.ts
7
+ const getEnv = () => {
8
+ if (typeof globalThis !== "object" || globalThis === null) return;
9
+ return globalThis.process?.env;
10
+ };
11
+ const rawNamespaceConfig = (getEnv()?.LORO_REPO_DEBUG ?? "").trim();
12
+ const normalizedNamespaces = rawNamespaceConfig.length > 0 ? rawNamespaceConfig.split(/[\s,]+/).map((token) => token.toLowerCase()).filter(Boolean) : [];
13
+ const wildcardTokens = new Set([
14
+ "*",
15
+ "1",
16
+ "true",
17
+ "all"
18
+ ]);
19
+ const namespaceSet = new Set(normalizedNamespaces);
20
+ const hasWildcard = namespaceSet.size > 0 && normalizedNamespaces.some((token) => wildcardTokens.has(token));
21
+ const isDebugEnabled = (namespace) => {
22
+ if (!namespaceSet.size) return false;
23
+ if (!namespace) return hasWildcard;
24
+ const normalized = namespace.toLowerCase();
25
+ if (hasWildcard) return true;
26
+ if (namespaceSet.has(normalized)) return true;
27
+ const [root] = normalized.split(":");
28
+ return namespaceSet.has(root);
29
+ };
30
+ const createDebugLogger = (namespace) => {
31
+ const normalized = namespace.toLowerCase();
32
+ return (...args) => {
33
+ if (!isDebugEnabled(normalized)) return;
34
+ const prefix = `[loro-repo:${namespace}]`;
35
+ if (args.length === 0) {
36
+ console.info(prefix);
37
+ return;
38
+ }
39
+ console.info(prefix, ...args);
40
+ };
41
+ };
42
+
43
+ //#endregion
44
+ //#region src/transport/websocket.ts
45
+ const debug = createDebugLogger("transport:websocket");
46
+ function withTimeout(promise, timeoutMs) {
47
+ if (!timeoutMs || timeoutMs <= 0) return promise;
48
+ return new Promise((resolve, reject) => {
49
+ const timer = setTimeout(() => {
50
+ reject(/* @__PURE__ */ new Error(`Operation timed out after ${timeoutMs}ms`));
51
+ }, timeoutMs);
52
+ promise.then((value) => {
53
+ clearTimeout(timer);
54
+ resolve(value);
55
+ }).catch((error) => {
56
+ clearTimeout(timer);
57
+ reject(error);
58
+ });
59
+ });
60
+ }
61
+ function normalizeRoomId(roomId, fallback) {
62
+ if (typeof roomId === "string" && roomId.length > 0) return roomId;
63
+ if (roomId instanceof Uint8Array && roomId.length > 0) try {
64
+ return bytesToHex(roomId);
65
+ } catch {
66
+ return fallback;
67
+ }
68
+ return fallback;
69
+ }
70
+ function bytesEqual(a, b) {
71
+ if (a === b) return true;
72
+ if (!a || !b) return false;
73
+ if (a.length !== b.length) return false;
74
+ for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;
75
+ return true;
76
+ }
77
+ /**
78
+ * loro-websocket backed {@link TransportAdapter} implementation for LoroRepo.
79
+ * It uses loro-protocol as the underlying protocol for the transport.
80
+ */
81
+ var WebSocketTransportAdapter = class {
82
+ options;
83
+ client;
84
+ metadataSession;
85
+ docSessions = /* @__PURE__ */ new Map();
86
+ constructor(options) {
87
+ this.options = options;
88
+ }
89
+ async connect(_options) {
90
+ const client = this.ensureClient();
91
+ debug("connect requested", { status: client.getStatus() });
92
+ try {
93
+ await client.connect();
94
+ debug("client.connect resolved");
95
+ await client.waitConnected();
96
+ debug("client.waitConnected resolved", { status: client.getStatus() });
97
+ } catch (error) {
98
+ debug("connect failed", error);
99
+ throw error;
100
+ }
101
+ }
102
+ async close() {
103
+ debug("close requested", {
104
+ docSessions: this.docSessions.size,
105
+ metadataSession: Boolean(this.metadataSession)
106
+ });
107
+ for (const [docId] of this.docSessions) await this.leaveDocSession(docId).catch(() => {});
108
+ this.docSessions.clear();
109
+ await this.teardownMetadataSession().catch(() => {});
110
+ if (this.client) {
111
+ const client = this.client;
112
+ this.client = void 0;
113
+ client.destroy();
114
+ debug("websocket client destroyed");
115
+ }
116
+ debug("close completed");
117
+ }
118
+ isConnected() {
119
+ return this.client?.getStatus() === "connected";
120
+ }
121
+ async syncMeta(flock, options) {
122
+ debug("syncMeta requested", { roomId: this.options.metadataRoomId });
123
+ try {
124
+ let auth;
125
+ if (this.options.metadataAuth) if (typeof this.options.metadataAuth === "function") auth = await this.options.metadataAuth();
126
+ else auth = this.options.metadataAuth;
127
+ await withTimeout((await this.ensureMetadataSession(flock, {
128
+ roomId: this.options.metadataRoomId ?? "repo:meta",
129
+ auth
130
+ })).firstSynced, options?.timeout);
131
+ debug("syncMeta completed", { roomId: this.options.metadataRoomId });
132
+ return { ok: true };
133
+ } catch (error) {
134
+ debug("syncMeta failed", error);
135
+ return { ok: false };
136
+ }
137
+ }
138
+ joinMetaRoom(flock, params) {
139
+ const fallback = this.options.metadataRoomId ?? "";
140
+ const roomId = normalizeRoomId(params?.roomId, fallback);
141
+ if (!roomId) throw new Error("Metadata room id not configured");
142
+ const session = (async () => {
143
+ let auth;
144
+ const authWay = params?.auth ?? this.options.metadataAuth;
145
+ if (typeof authWay === "function") auth = await authWay();
146
+ else auth = authWay;
147
+ debug("joinMetaRoom requested", {
148
+ roomId,
149
+ hasAuth: Boolean(auth && auth.length)
150
+ });
151
+ return this.ensureMetadataSession(flock, {
152
+ roomId,
153
+ auth
154
+ });
155
+ })();
156
+ const firstSynced = session.then((session$1) => session$1.firstSynced);
157
+ const getConnected = () => this.isConnected();
158
+ const subscription = {
159
+ unsubscribe: () => {
160
+ session.then((session$1) => {
161
+ session$1.refCount = Math.max(0, session$1.refCount - 1);
162
+ debug("metadata session refCount decremented", {
163
+ roomId: session$1.roomId,
164
+ refCount: session$1.refCount
165
+ });
166
+ if (session$1.refCount === 0) {
167
+ debug("tearing down metadata session due to refCount=0", { roomId: session$1.roomId });
168
+ this.teardownMetadataSession(session$1).catch(() => {});
169
+ }
170
+ });
171
+ },
172
+ firstSyncedWithRemote: firstSynced,
173
+ get connected() {
174
+ return getConnected();
175
+ }
176
+ };
177
+ session.then((session$1) => {
178
+ session$1.refCount += 1;
179
+ debug("metadata session refCount incremented", {
180
+ roomId: session$1.roomId,
181
+ refCount: session$1.refCount
182
+ });
183
+ });
184
+ return subscription;
185
+ }
186
+ async syncDoc(docId, doc, options) {
187
+ debug("syncDoc requested", { docId });
188
+ try {
189
+ const session = await this.ensureDocSession(docId, doc, {});
190
+ await withTimeout(session.firstSynced, options?.timeout);
191
+ debug("syncDoc completed", {
192
+ docId,
193
+ roomId: session.roomId
194
+ });
195
+ return { ok: true };
196
+ } catch (error) {
197
+ debug("syncDoc failed", {
198
+ docId,
199
+ error
200
+ });
201
+ return { ok: false };
202
+ }
203
+ }
204
+ joinDocRoom(docId, doc, params) {
205
+ debug("joinDocRoom requested", {
206
+ docId,
207
+ roomParamType: params?.roomId ? typeof params.roomId === "string" ? "string" : "uint8array" : void 0,
208
+ hasAuthOverride: Boolean(params?.auth && params.auth.length)
209
+ });
210
+ const ensure = this.ensureDocSession(docId, doc, params ?? {});
211
+ const firstSynced = ensure.then((session) => session.firstSynced);
212
+ const getConnected = () => this.isConnected();
213
+ const subscription = {
214
+ unsubscribe: () => {
215
+ ensure.then((session) => {
216
+ session.refCount = Math.max(0, session.refCount - 1);
217
+ debug("doc session refCount decremented", {
218
+ docId,
219
+ roomId: session.roomId,
220
+ refCount: session.refCount
221
+ });
222
+ if (session.refCount === 0) this.leaveDocSession(docId).catch(() => {});
223
+ });
224
+ },
225
+ firstSyncedWithRemote: firstSynced,
226
+ get connected() {
227
+ return getConnected();
228
+ }
229
+ };
230
+ ensure.then((session) => {
231
+ session.refCount += 1;
232
+ debug("doc session refCount incremented", {
233
+ docId,
234
+ roomId: session.roomId,
235
+ refCount: session.refCount
236
+ });
237
+ });
238
+ return subscription;
239
+ }
240
+ ensureClient() {
241
+ if (this.client) {
242
+ debug("reusing websocket client", { status: this.client.getStatus() });
243
+ return this.client;
244
+ }
245
+ const { url, client: clientOptions } = this.options;
246
+ debug("creating websocket client", {
247
+ url,
248
+ clientOptionsKeys: clientOptions ? Object.keys(clientOptions) : []
249
+ });
250
+ const client = new LoroWebsocketClient({
251
+ url,
252
+ ...clientOptions
253
+ });
254
+ this.client = client;
255
+ return client;
256
+ }
257
+ async ensureMetadataSession(flock, params) {
258
+ debug("ensureMetadataSession invoked", {
259
+ roomId: params.roomId,
260
+ hasAuth: Boolean(params.auth && params.auth.length)
261
+ });
262
+ const client = this.ensureClient();
263
+ await client.waitConnected();
264
+ debug("websocket client ready for metadata session", { status: client.getStatus() });
265
+ if (this.metadataSession && this.metadataSession.flock === flock && this.metadataSession.roomId === params.roomId && bytesEqual(this.metadataSession.auth, params.auth)) {
266
+ debug("reusing metadata session", {
267
+ roomId: this.metadataSession.roomId,
268
+ refCount: this.metadataSession.refCount
269
+ });
270
+ return this.metadataSession;
271
+ }
272
+ if (this.metadataSession) {
273
+ debug("tearing down previous metadata session", { roomId: this.metadataSession.roomId });
274
+ await this.teardownMetadataSession(this.metadataSession).catch(() => {});
275
+ }
276
+ const adaptor = new FlockAdaptor(flock, this.options.metadataAdaptorConfig);
277
+ debug("joining metadata room", {
278
+ roomId: params.roomId,
279
+ hasAuth: Boolean(params.auth && params.auth.length)
280
+ });
281
+ const room = await client.join({
282
+ roomId: params.roomId,
283
+ crdtAdaptor: adaptor,
284
+ auth: params.auth
285
+ });
286
+ const firstSynced = room.waitForReachingServerVersion();
287
+ firstSynced.then(() => {
288
+ debug("metadata session firstSynced resolved", { roomId: params.roomId });
289
+ }, (error) => {
290
+ debug("metadata session firstSynced rejected", {
291
+ roomId: params.roomId,
292
+ error
293
+ });
294
+ });
295
+ const session = {
296
+ adaptor,
297
+ room,
298
+ firstSynced,
299
+ flock,
300
+ roomId: params.roomId,
301
+ auth: params.auth,
302
+ refCount: 0
303
+ };
304
+ this.metadataSession = session;
305
+ return session;
306
+ }
307
+ async teardownMetadataSession(session) {
308
+ const target = session ?? this.metadataSession;
309
+ if (!target) return;
310
+ debug("teardownMetadataSession invoked", { roomId: target.roomId });
311
+ if (this.metadataSession === target) this.metadataSession = void 0;
312
+ const { adaptor, room } = target;
313
+ try {
314
+ await room.leave();
315
+ debug("metadata room left", { roomId: target.roomId });
316
+ } catch (error) {
317
+ debug("metadata room leave failed; destroying", {
318
+ roomId: target.roomId,
319
+ error
320
+ });
321
+ await room.destroy().catch(() => {});
322
+ }
323
+ adaptor.destroy();
324
+ debug("metadata session destroyed", { roomId: target.roomId });
325
+ }
326
+ async ensureDocSession(docId, doc, params) {
327
+ debug("ensureDocSession invoked", { docId });
328
+ const client = this.ensureClient();
329
+ await client.waitConnected();
330
+ debug("websocket client ready for doc session", {
331
+ docId,
332
+ status: client.getStatus()
333
+ });
334
+ const existing = this.docSessions.get(docId);
335
+ const derivedRoomId = this.options.docRoomId?.(docId) ?? docId;
336
+ const roomId = normalizeRoomId(params.roomId, derivedRoomId);
337
+ let auth;
338
+ auth = await (params.auth ?? this.options.docAuth?.(docId));
339
+ debug("doc session params resolved", {
340
+ docId,
341
+ roomId,
342
+ hasAuth: Boolean(auth && auth.length)
343
+ });
344
+ if (existing && existing.doc === doc && existing.roomId === roomId) {
345
+ debug("reusing doc session", {
346
+ docId,
347
+ roomId,
348
+ refCount: existing.refCount
349
+ });
350
+ return existing;
351
+ }
352
+ if (existing) {
353
+ debug("doc session mismatch; leaving existing session", {
354
+ docId,
355
+ previousRoomId: existing.roomId,
356
+ nextRoomId: roomId
357
+ });
358
+ await this.leaveDocSession(docId).catch(() => {});
359
+ }
360
+ const adaptor = new LoroAdaptor(doc);
361
+ debug("joining doc room", {
362
+ docId,
363
+ roomId,
364
+ hasAuth: Boolean(auth && auth.length)
365
+ });
366
+ const room = await client.join({
367
+ roomId,
368
+ crdtAdaptor: adaptor,
369
+ auth
370
+ });
371
+ const firstSynced = room.waitForReachingServerVersion();
372
+ firstSynced.then(() => {
373
+ debug("doc session firstSynced resolved", {
374
+ docId,
375
+ roomId
376
+ });
377
+ }, (error) => {
378
+ debug("doc session firstSynced rejected", {
379
+ docId,
380
+ roomId,
381
+ error
382
+ });
383
+ });
384
+ const session = {
385
+ adaptor,
386
+ room,
387
+ firstSynced,
388
+ doc,
389
+ roomId,
390
+ refCount: 0
391
+ };
392
+ this.docSessions.set(docId, session);
393
+ return session;
394
+ }
395
+ async leaveDocSession(docId) {
396
+ const session = this.docSessions.get(docId);
397
+ if (!session) {
398
+ debug("leaveDocSession invoked but no session found", { docId });
399
+ return;
400
+ }
401
+ this.docSessions.delete(docId);
402
+ debug("leaving doc session", {
403
+ docId,
404
+ roomId: session.roomId
405
+ });
406
+ try {
407
+ await session.room.leave();
408
+ debug("doc room left", {
409
+ docId,
410
+ roomId: session.roomId
411
+ });
412
+ } catch (error) {
413
+ debug("doc room leave failed; destroying", {
414
+ docId,
415
+ roomId: session.roomId,
416
+ error
417
+ });
418
+ await session.room.destroy().catch(() => {});
419
+ }
420
+ session.adaptor.destroy();
421
+ debug("doc session destroyed", {
422
+ docId,
423
+ roomId: session.roomId
424
+ });
425
+ }
426
+ };
427
+
428
+ //#endregion
429
+ export { WebSocketTransportAdapter };
430
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","names":["auth: Uint8Array | undefined","session","subscription: TransportSubscription","session: MetadataSession","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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"}