loro-repo 0.5.2 → 0.6.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.
- package/README.md +40 -1
- package/dist/index.cjs +644 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -5
- package/dist/index.d.ts +48 -5
- package/dist/index.js +644 -133
- package/dist/index.js.map +1 -1
- package/dist/storage/filesystem.cjs +60 -10
- package/dist/storage/filesystem.cjs.map +1 -1
- package/dist/storage/filesystem.d.cts +8 -2
- package/dist/storage/filesystem.d.ts +8 -2
- package/dist/storage/filesystem.js +60 -10
- package/dist/storage/filesystem.js.map +1 -1
- package/dist/storage/indexeddb.cjs +51 -9
- package/dist/storage/indexeddb.cjs.map +1 -1
- package/dist/storage/indexeddb.d.cts +7 -1
- package/dist/storage/indexeddb.d.ts +7 -1
- package/dist/storage/indexeddb.js +51 -9
- package/dist/storage/indexeddb.js.map +1 -1
- package/dist/transport/broadcast-channel.cjs +131 -1
- package/dist/transport/broadcast-channel.cjs.map +1 -1
- package/dist/transport/broadcast-channel.d.cts +20 -3
- package/dist/transport/broadcast-channel.d.ts +20 -3
- package/dist/transport/broadcast-channel.js +130 -1
- package/dist/transport/broadcast-channel.js.map +1 -1
- package/dist/transport/websocket.cjs +348 -24
- package/dist/transport/websocket.cjs.map +1 -1
- package/dist/transport/websocket.d.cts +47 -5
- package/dist/transport/websocket.d.ts +47 -5
- package/dist/transport/websocket.js +349 -24
- package/dist/transport/websocket.js.map +1 -1
- package/dist/types.d.cts +121 -4
- package/dist/types.d.ts +121 -4
- package/package.json +7 -7
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { LoroAdaptor } from "loro-adaptors/loro";
|
|
2
|
-
import { bytesToHex } from "loro-protocol";
|
|
1
|
+
import { LoroAdaptor, LoroEphemeralAdaptor } from "loro-adaptors/loro";
|
|
3
2
|
import { LoroWebsocketClient } from "loro-websocket";
|
|
4
3
|
import { FlockAdaptor } from "loro-adaptors/flock";
|
|
5
4
|
|
|
@@ -58,14 +57,28 @@ function withTimeout(promise, timeoutMs) {
|
|
|
58
57
|
});
|
|
59
58
|
});
|
|
60
59
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
60
|
+
function createStatusEmitter(initial = "connecting") {
|
|
61
|
+
return {
|
|
62
|
+
status: initial,
|
|
63
|
+
listeners: /* @__PURE__ */ new Set()
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function emitStatus(emitter, status) {
|
|
67
|
+
if (!emitter) return;
|
|
68
|
+
emitter.status = status;
|
|
69
|
+
const listeners = Array.from(emitter.listeners);
|
|
70
|
+
for (const cb of listeners) try {
|
|
71
|
+
cb(status);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
debug("status listener error", error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function mapClientStatusToRoom(status) {
|
|
77
|
+
switch (status) {
|
|
78
|
+
case "connected": return "joined";
|
|
79
|
+
case "connecting": return "reconnecting";
|
|
80
|
+
default: return "disconnected";
|
|
67
81
|
}
|
|
68
|
-
return fallback;
|
|
69
82
|
}
|
|
70
83
|
function bytesEqual(a, b) {
|
|
71
84
|
if (a === b) return true;
|
|
@@ -81,24 +94,85 @@ function bytesEqual(a, b) {
|
|
|
81
94
|
var WebSocketTransportAdapter = class {
|
|
82
95
|
options;
|
|
83
96
|
client;
|
|
97
|
+
clientListeners = [];
|
|
84
98
|
metadataSession;
|
|
85
99
|
docSessions = /* @__PURE__ */ new Map();
|
|
100
|
+
ephemeralSessions = /* @__PURE__ */ new Map();
|
|
86
101
|
constructor(options) {
|
|
87
102
|
this.options = options;
|
|
88
103
|
}
|
|
89
|
-
|
|
104
|
+
trackClientListener(unsubscribe) {
|
|
105
|
+
this.clientListeners.push(unsubscribe);
|
|
106
|
+
}
|
|
107
|
+
propagateClientStatus(status) {
|
|
108
|
+
const mapped = mapClientStatusToRoom(status);
|
|
109
|
+
const meta = this.metadataSession;
|
|
110
|
+
if (meta) this.dispatchRoomStatus("meta", { roomId: meta.roomId }, mapped, meta.statusEmitter, meta.statusListener);
|
|
111
|
+
for (const session of this.docSessions.values()) this.dispatchRoomStatus("doc", {
|
|
112
|
+
roomId: session.roomId,
|
|
113
|
+
docId: session.docId
|
|
114
|
+
}, mapped, session.statusEmitter, session.statusListener);
|
|
115
|
+
for (const session of this.ephemeralSessions.values()) this.dispatchRoomStatus("ephemeral", { roomId: session.roomId }, mapped, session.statusEmitter, void 0);
|
|
116
|
+
}
|
|
117
|
+
dispatchRoomStatus(kind, payload, status, emitter, listener) {
|
|
118
|
+
emitStatus(emitter, status);
|
|
119
|
+
try {
|
|
120
|
+
listener?.(status);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
debug("room listener error", error);
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
this.options.onRoomStatusChange?.({
|
|
126
|
+
kind,
|
|
127
|
+
...payload,
|
|
128
|
+
status
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
debug("global room status listener error", error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
cleanupClientListeners() {
|
|
135
|
+
while (this.clientListeners.length > 0) {
|
|
136
|
+
const unsubscribe = this.clientListeners.pop();
|
|
137
|
+
try {
|
|
138
|
+
unsubscribe?.();
|
|
139
|
+
} catch {}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async connect(options) {
|
|
90
143
|
const client = this.ensureClient();
|
|
91
|
-
debug("connect requested", {
|
|
144
|
+
debug("connect requested", {
|
|
145
|
+
status: client.getStatus(),
|
|
146
|
+
resetBackoff: Boolean(options?.resetBackoff)
|
|
147
|
+
});
|
|
92
148
|
try {
|
|
93
|
-
await client.connect();
|
|
149
|
+
await client.connect.call(client, options?.resetBackoff ? { resetBackoff: options.resetBackoff } : void 0);
|
|
94
150
|
debug("client.connect resolved");
|
|
95
|
-
await client.waitConnected();
|
|
151
|
+
await withTimeout(client.waitConnected(), options?.timeout);
|
|
96
152
|
debug("client.waitConnected resolved", { status: client.getStatus() });
|
|
97
153
|
} catch (error) {
|
|
98
154
|
debug("connect failed", error);
|
|
99
155
|
throw error;
|
|
100
156
|
}
|
|
101
157
|
}
|
|
158
|
+
async reconnect(options) {
|
|
159
|
+
const client = this.ensureClient();
|
|
160
|
+
debug("reconnect requested", {
|
|
161
|
+
status: client.getStatus(),
|
|
162
|
+
resetBackoff: Boolean(options?.resetBackoff)
|
|
163
|
+
});
|
|
164
|
+
try {
|
|
165
|
+
const connectFn = client.connect;
|
|
166
|
+
if (options?.resetBackoff) await connectFn.call(client, { resetBackoff: true });
|
|
167
|
+
else if (client.retryNow) await client.retryNow.call(client);
|
|
168
|
+
else await connectFn.call(client);
|
|
169
|
+
await withTimeout(client.waitConnected(), options?.timeout);
|
|
170
|
+
debug("reconnect completed", { status: client.getStatus() });
|
|
171
|
+
} catch (error) {
|
|
172
|
+
debug("reconnect failed", error);
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
102
176
|
async close() {
|
|
103
177
|
debug("close requested", {
|
|
104
178
|
docSessions: this.docSessions.size,
|
|
@@ -106,10 +180,13 @@ var WebSocketTransportAdapter = class {
|
|
|
106
180
|
});
|
|
107
181
|
for (const [docId] of this.docSessions) await this.leaveDocSession(docId).catch(() => {});
|
|
108
182
|
this.docSessions.clear();
|
|
183
|
+
for (const [roomId] of this.ephemeralSessions) await this.leaveEphemeralSession(roomId).catch(() => {});
|
|
184
|
+
this.ephemeralSessions.clear();
|
|
109
185
|
await this.teardownMetadataSession().catch(() => {});
|
|
110
186
|
if (this.client) {
|
|
111
187
|
const client = this.client;
|
|
112
188
|
this.client = void 0;
|
|
189
|
+
this.cleanupClientListeners();
|
|
113
190
|
client.destroy();
|
|
114
191
|
debug("websocket client destroyed");
|
|
115
192
|
}
|
|
@@ -118,6 +195,30 @@ var WebSocketTransportAdapter = class {
|
|
|
118
195
|
isConnected() {
|
|
119
196
|
return this.client?.getStatus() === "connected";
|
|
120
197
|
}
|
|
198
|
+
getStatus() {
|
|
199
|
+
return this.ensureClient().getStatus();
|
|
200
|
+
}
|
|
201
|
+
getLatency() {
|
|
202
|
+
return this.ensureClient().getLatency?.();
|
|
203
|
+
}
|
|
204
|
+
onStatusChange(listener) {
|
|
205
|
+
const unsubscribe = this.ensureClient().onStatusChange(listener);
|
|
206
|
+
this.trackClientListener(unsubscribe);
|
|
207
|
+
return () => {
|
|
208
|
+
unsubscribe();
|
|
209
|
+
const idx = this.clientListeners.indexOf(unsubscribe);
|
|
210
|
+
if (idx >= 0) this.clientListeners.splice(idx, 1);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
onLatency(listener) {
|
|
214
|
+
const unsubscribe = this.ensureClient().onLatency(listener);
|
|
215
|
+
this.trackClientListener(unsubscribe);
|
|
216
|
+
return () => {
|
|
217
|
+
unsubscribe();
|
|
218
|
+
const idx = this.clientListeners.indexOf(unsubscribe);
|
|
219
|
+
if (idx >= 0) this.clientListeners.splice(idx, 1);
|
|
220
|
+
};
|
|
221
|
+
}
|
|
121
222
|
async syncMeta(flock, options) {
|
|
122
223
|
debug("syncMeta requested", { roomId: this.options.metadataRoomId });
|
|
123
224
|
try {
|
|
@@ -136,9 +237,8 @@ var WebSocketTransportAdapter = class {
|
|
|
136
237
|
}
|
|
137
238
|
}
|
|
138
239
|
joinMetaRoom(flock, params) {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
if (!roomId) throw new Error("Metadata room id not configured");
|
|
240
|
+
const roomId = this.options.metadataRoomId ?? "repo:meta";
|
|
241
|
+
let statusEmitterRef;
|
|
142
242
|
const session = (async () => {
|
|
143
243
|
let auth;
|
|
144
244
|
const authWay = params?.auth ?? this.options.metadataAuth;
|
|
@@ -148,10 +248,13 @@ var WebSocketTransportAdapter = class {
|
|
|
148
248
|
roomId,
|
|
149
249
|
hasAuth: Boolean(auth && auth.length)
|
|
150
250
|
});
|
|
151
|
-
|
|
251
|
+
const ensure = this.ensureMetadataSession(flock, {
|
|
152
252
|
roomId,
|
|
153
|
-
auth
|
|
253
|
+
auth,
|
|
254
|
+
onStatusChange: params?.onStatusChange
|
|
154
255
|
});
|
|
256
|
+
statusEmitterRef = (await ensure).statusEmitter;
|
|
257
|
+
return ensure;
|
|
155
258
|
})();
|
|
156
259
|
const firstSynced = session.then((session$1) => session$1.firstSynced);
|
|
157
260
|
const getConnected = () => this.isConnected();
|
|
@@ -172,6 +275,31 @@ var WebSocketTransportAdapter = class {
|
|
|
172
275
|
firstSyncedWithRemote: firstSynced,
|
|
173
276
|
get connected() {
|
|
174
277
|
return getConnected();
|
|
278
|
+
},
|
|
279
|
+
get status() {
|
|
280
|
+
return statusEmitterRef?.status ?? "connecting";
|
|
281
|
+
},
|
|
282
|
+
onStatusChange: (listener) => {
|
|
283
|
+
const attach = (emitter) => {
|
|
284
|
+
emitter.listeners.add(listener);
|
|
285
|
+
try {
|
|
286
|
+
listener(emitter.status);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
debug("metadata onStatusChange listener error", error);
|
|
289
|
+
}
|
|
290
|
+
return () => emitter.listeners.delete(listener);
|
|
291
|
+
};
|
|
292
|
+
if (statusEmitterRef) {
|
|
293
|
+
const cleanup = attach(statusEmitterRef);
|
|
294
|
+
return () => cleanup();
|
|
295
|
+
}
|
|
296
|
+
let unsubscribed = false;
|
|
297
|
+
const cleanupPromise = session.then((resolved) => attach(resolved.statusEmitter));
|
|
298
|
+
return () => {
|
|
299
|
+
if (unsubscribed) return;
|
|
300
|
+
unsubscribed = true;
|
|
301
|
+
cleanupPromise.then((cleanup) => cleanup()).catch(() => {});
|
|
302
|
+
};
|
|
175
303
|
}
|
|
176
304
|
};
|
|
177
305
|
session.then((session$1) => {
|
|
@@ -204,10 +332,13 @@ var WebSocketTransportAdapter = class {
|
|
|
204
332
|
joinDocRoom(docId, doc, params) {
|
|
205
333
|
debug("joinDocRoom requested", {
|
|
206
334
|
docId,
|
|
207
|
-
roomParamType: params?.roomId ? typeof params.roomId === "string" ? "string" : "uint8array" : void 0,
|
|
208
335
|
hasAuthOverride: Boolean(params?.auth && params.auth.length)
|
|
209
336
|
});
|
|
210
|
-
|
|
337
|
+
let statusEmitterRef;
|
|
338
|
+
const ensure = this.ensureDocSession(docId, doc, params ?? {}).then((session) => {
|
|
339
|
+
statusEmitterRef = session.statusEmitter;
|
|
340
|
+
return session;
|
|
341
|
+
});
|
|
211
342
|
const firstSynced = ensure.then((session) => session.firstSynced);
|
|
212
343
|
const getConnected = () => this.isConnected();
|
|
213
344
|
const subscription = {
|
|
@@ -225,6 +356,31 @@ var WebSocketTransportAdapter = class {
|
|
|
225
356
|
firstSyncedWithRemote: firstSynced,
|
|
226
357
|
get connected() {
|
|
227
358
|
return getConnected();
|
|
359
|
+
},
|
|
360
|
+
get status() {
|
|
361
|
+
return statusEmitterRef?.status ?? "connecting";
|
|
362
|
+
},
|
|
363
|
+
onStatusChange: (listener) => {
|
|
364
|
+
const attach = (emitter) => {
|
|
365
|
+
emitter.listeners.add(listener);
|
|
366
|
+
try {
|
|
367
|
+
listener(emitter.status);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
debug("doc onStatusChange listener error", error);
|
|
370
|
+
}
|
|
371
|
+
return () => emitter.listeners.delete(listener);
|
|
372
|
+
};
|
|
373
|
+
if (statusEmitterRef) {
|
|
374
|
+
const cleanup = attach(statusEmitterRef);
|
|
375
|
+
return () => cleanup();
|
|
376
|
+
}
|
|
377
|
+
let unsubscribed = false;
|
|
378
|
+
const cleanupPromise = ensure.then((session) => attach(session.statusEmitter));
|
|
379
|
+
return () => {
|
|
380
|
+
if (unsubscribed) return;
|
|
381
|
+
unsubscribed = true;
|
|
382
|
+
cleanupPromise.then((cleanup) => cleanup()).catch(() => {});
|
|
383
|
+
};
|
|
228
384
|
}
|
|
229
385
|
};
|
|
230
386
|
ensure.then((session) => {
|
|
@@ -237,6 +393,69 @@ var WebSocketTransportAdapter = class {
|
|
|
237
393
|
});
|
|
238
394
|
return subscription;
|
|
239
395
|
}
|
|
396
|
+
joinEphemeralRoom(roomId) {
|
|
397
|
+
debug("joinEphemeralRoom requested", { roomId });
|
|
398
|
+
let statusEmitterRef;
|
|
399
|
+
const ensure = this.ensureEphemeralSession(roomId).then((session) => {
|
|
400
|
+
statusEmitterRef = session.statusEmitter;
|
|
401
|
+
return session;
|
|
402
|
+
});
|
|
403
|
+
const store = this.ephemeralSessions.get(roomId)?.store;
|
|
404
|
+
if (!store) throw new Error("Failed to initialize ephemeral session");
|
|
405
|
+
const firstSynced = ensure.then((session) => session.firstSynced);
|
|
406
|
+
const getConnected = () => this.isConnected();
|
|
407
|
+
const subscription = {
|
|
408
|
+
store,
|
|
409
|
+
unsubscribe: () => {
|
|
410
|
+
ensure.then((session) => {
|
|
411
|
+
session.refCount = Math.max(0, session.refCount - 1);
|
|
412
|
+
debug("ephemeral session refCount decremented", {
|
|
413
|
+
roomId,
|
|
414
|
+
refCount: session.refCount
|
|
415
|
+
});
|
|
416
|
+
if (session.refCount === 0) this.leaveEphemeralSession(roomId).catch(() => {});
|
|
417
|
+
});
|
|
418
|
+
},
|
|
419
|
+
firstSyncedWithRemote: firstSynced,
|
|
420
|
+
get connected() {
|
|
421
|
+
return getConnected();
|
|
422
|
+
},
|
|
423
|
+
get status() {
|
|
424
|
+
return statusEmitterRef?.status ?? "connecting";
|
|
425
|
+
},
|
|
426
|
+
onStatusChange: (listener) => {
|
|
427
|
+
const attach = (emitter) => {
|
|
428
|
+
emitter.listeners.add(listener);
|
|
429
|
+
try {
|
|
430
|
+
listener(emitter.status);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
debug("ephemeral onStatusChange listener error", error);
|
|
433
|
+
}
|
|
434
|
+
return () => emitter.listeners.delete(listener);
|
|
435
|
+
};
|
|
436
|
+
if (statusEmitterRef) {
|
|
437
|
+
const cleanup = attach(statusEmitterRef);
|
|
438
|
+
return () => cleanup();
|
|
439
|
+
}
|
|
440
|
+
let unsubscribed = false;
|
|
441
|
+
const cleanupPromise = ensure.then((session) => attach(session.statusEmitter));
|
|
442
|
+
return () => {
|
|
443
|
+
if (unsubscribed) return;
|
|
444
|
+
unsubscribed = true;
|
|
445
|
+
cleanupPromise.then((cleanup) => cleanup()).catch(() => {});
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
ensure.then((session) => {
|
|
450
|
+
subscription.store = session.store;
|
|
451
|
+
session.refCount += 1;
|
|
452
|
+
debug("ephemeral session refCount incremented", {
|
|
453
|
+
roomId,
|
|
454
|
+
refCount: session.refCount
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
return subscription;
|
|
458
|
+
}
|
|
240
459
|
ensureClient() {
|
|
241
460
|
if (this.client) {
|
|
242
461
|
debug("reusing websocket client", { status: this.client.getStatus() });
|
|
@@ -251,9 +470,18 @@ var WebSocketTransportAdapter = class {
|
|
|
251
470
|
url,
|
|
252
471
|
...clientOptions
|
|
253
472
|
});
|
|
473
|
+
this.trackClientListener(client.onStatusChange((status) => {
|
|
474
|
+
this.propagateClientStatus(status);
|
|
475
|
+
this.options.onStatusChange?.(status);
|
|
476
|
+
}));
|
|
477
|
+
if (this.options.onLatency) this.trackClientListener(client.onLatency(this.options.onLatency));
|
|
254
478
|
this.client = client;
|
|
479
|
+
this.propagateClientStatus(client.getStatus());
|
|
255
480
|
return client;
|
|
256
481
|
}
|
|
482
|
+
get websocketClient() {
|
|
483
|
+
return this.ensureClient();
|
|
484
|
+
}
|
|
257
485
|
async ensureMetadataSession(flock, params) {
|
|
258
486
|
debug("ensureMetadataSession invoked", {
|
|
259
487
|
roomId: params.roomId,
|
|
@@ -274,6 +502,9 @@ var WebSocketTransportAdapter = class {
|
|
|
274
502
|
await this.teardownMetadataSession(this.metadataSession).catch(() => {});
|
|
275
503
|
}
|
|
276
504
|
const adaptor = new FlockAdaptor(flock, this.options.metadataAdaptorConfig);
|
|
505
|
+
const statusEmitter = createStatusEmitter("connecting");
|
|
506
|
+
if (params.onStatusChange) statusEmitter.listeners.add(params.onStatusChange);
|
|
507
|
+
this.dispatchRoomStatus("meta", { roomId: params.roomId }, "connecting", statusEmitter, params.onStatusChange);
|
|
277
508
|
debug("joining metadata room", {
|
|
278
509
|
roomId: params.roomId,
|
|
279
510
|
hasAuth: Boolean(params.auth && params.auth.length)
|
|
@@ -283,6 +514,7 @@ var WebSocketTransportAdapter = class {
|
|
|
283
514
|
crdtAdaptor: adaptor,
|
|
284
515
|
auth: params.auth
|
|
285
516
|
});
|
|
517
|
+
this.dispatchRoomStatus("meta", { roomId: params.roomId }, "joined", statusEmitter, params.onStatusChange);
|
|
286
518
|
const firstSynced = room.waitForReachingServerVersion();
|
|
287
519
|
firstSynced.then(() => {
|
|
288
520
|
debug("metadata session firstSynced resolved", { roomId: params.roomId });
|
|
@@ -299,7 +531,9 @@ var WebSocketTransportAdapter = class {
|
|
|
299
531
|
flock,
|
|
300
532
|
roomId: params.roomId,
|
|
301
533
|
auth: params.auth,
|
|
302
|
-
refCount: 0
|
|
534
|
+
refCount: 0,
|
|
535
|
+
statusEmitter,
|
|
536
|
+
statusListener: params.onStatusChange
|
|
303
537
|
};
|
|
304
538
|
this.metadataSession = session;
|
|
305
539
|
return session;
|
|
@@ -321,6 +555,7 @@ var WebSocketTransportAdapter = class {
|
|
|
321
555
|
await room.destroy().catch(() => {});
|
|
322
556
|
}
|
|
323
557
|
adaptor.destroy();
|
|
558
|
+
this.dispatchRoomStatus("meta", { roomId: target.roomId }, "disconnected", target.statusEmitter, target.statusListener);
|
|
324
559
|
debug("metadata session destroyed", { roomId: target.roomId });
|
|
325
560
|
}
|
|
326
561
|
async ensureDocSession(docId, doc, params) {
|
|
@@ -332,8 +567,7 @@ var WebSocketTransportAdapter = class {
|
|
|
332
567
|
status: client.getStatus()
|
|
333
568
|
});
|
|
334
569
|
const existing = this.docSessions.get(docId);
|
|
335
|
-
const
|
|
336
|
-
const roomId = normalizeRoomId(params.roomId, derivedRoomId);
|
|
570
|
+
const roomId = this.options.docRoomId?.(docId) ?? docId;
|
|
337
571
|
let auth;
|
|
338
572
|
auth = await (params.auth ?? this.options.docAuth?.(docId));
|
|
339
573
|
debug("doc session params resolved", {
|
|
@@ -358,6 +592,12 @@ var WebSocketTransportAdapter = class {
|
|
|
358
592
|
await this.leaveDocSession(docId).catch(() => {});
|
|
359
593
|
}
|
|
360
594
|
const adaptor = new LoroAdaptor(doc);
|
|
595
|
+
const statusEmitter = createStatusEmitter("connecting");
|
|
596
|
+
if (params.onStatusChange) statusEmitter.listeners.add(params.onStatusChange);
|
|
597
|
+
this.dispatchRoomStatus("doc", {
|
|
598
|
+
roomId,
|
|
599
|
+
docId
|
|
600
|
+
}, "connecting", statusEmitter, params.onStatusChange);
|
|
361
601
|
debug("joining doc room", {
|
|
362
602
|
docId,
|
|
363
603
|
roomId,
|
|
@@ -368,6 +608,10 @@ var WebSocketTransportAdapter = class {
|
|
|
368
608
|
crdtAdaptor: adaptor,
|
|
369
609
|
auth
|
|
370
610
|
});
|
|
611
|
+
this.dispatchRoomStatus("doc", {
|
|
612
|
+
roomId,
|
|
613
|
+
docId
|
|
614
|
+
}, "joined", statusEmitter, params.onStatusChange);
|
|
371
615
|
const firstSynced = room.waitForReachingServerVersion();
|
|
372
616
|
firstSynced.then(() => {
|
|
373
617
|
debug("doc session firstSynced resolved", {
|
|
@@ -387,11 +631,66 @@ var WebSocketTransportAdapter = class {
|
|
|
387
631
|
firstSynced,
|
|
388
632
|
doc,
|
|
389
633
|
roomId,
|
|
390
|
-
|
|
634
|
+
docId,
|
|
635
|
+
refCount: 0,
|
|
636
|
+
statusEmitter,
|
|
637
|
+
statusListener: params.onStatusChange
|
|
391
638
|
};
|
|
392
639
|
this.docSessions.set(docId, session);
|
|
393
640
|
return session;
|
|
394
641
|
}
|
|
642
|
+
async ensureEphemeralSession(roomId) {
|
|
643
|
+
debug("ensureEphemeralSession invoked", { roomId });
|
|
644
|
+
const existing = this.ephemeralSessions.get(roomId);
|
|
645
|
+
if (existing) {
|
|
646
|
+
debug("reusing ephemeral session", {
|
|
647
|
+
roomId,
|
|
648
|
+
refCount: existing.refCount
|
|
649
|
+
});
|
|
650
|
+
return existing;
|
|
651
|
+
}
|
|
652
|
+
const adaptor = new LoroEphemeralAdaptor();
|
|
653
|
+
const statusEmitter = createStatusEmitter("connecting");
|
|
654
|
+
this.dispatchRoomStatus("ephemeral", { roomId }, "connecting", statusEmitter, void 0);
|
|
655
|
+
const session = {
|
|
656
|
+
adaptor,
|
|
657
|
+
store: adaptor.getStore(),
|
|
658
|
+
roomId,
|
|
659
|
+
firstSynced: Promise.resolve(),
|
|
660
|
+
refCount: 0,
|
|
661
|
+
statusEmitter
|
|
662
|
+
};
|
|
663
|
+
this.ephemeralSessions.set(roomId, session);
|
|
664
|
+
const client = this.ensureClient();
|
|
665
|
+
await client.waitConnected();
|
|
666
|
+
debug("websocket client ready for ephemeral session", {
|
|
667
|
+
roomId,
|
|
668
|
+
status: client.getStatus()
|
|
669
|
+
});
|
|
670
|
+
try {
|
|
671
|
+
const room = await client.join({
|
|
672
|
+
roomId,
|
|
673
|
+
crdtAdaptor: adaptor
|
|
674
|
+
});
|
|
675
|
+
this.dispatchRoomStatus("ephemeral", { roomId }, "joined", statusEmitter, void 0);
|
|
676
|
+
const firstSynced = room.waitForReachingServerVersion();
|
|
677
|
+
firstSynced.then(() => {
|
|
678
|
+
debug("ephemeral session firstSynced resolved", { roomId });
|
|
679
|
+
}, (error) => {
|
|
680
|
+
debug("ephemeral session firstSynced rejected", {
|
|
681
|
+
roomId,
|
|
682
|
+
error
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
session.room = room;
|
|
686
|
+
session.firstSynced = firstSynced;
|
|
687
|
+
return session;
|
|
688
|
+
} catch (error) {
|
|
689
|
+
this.ephemeralSessions.delete(roomId);
|
|
690
|
+
adaptor.destroy();
|
|
691
|
+
throw error;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
395
694
|
async leaveDocSession(docId) {
|
|
396
695
|
const session = this.docSessions.get(docId);
|
|
397
696
|
if (!session) {
|
|
@@ -418,11 +717,37 @@ var WebSocketTransportAdapter = class {
|
|
|
418
717
|
await session.room.destroy().catch(() => {});
|
|
419
718
|
}
|
|
420
719
|
session.adaptor.destroy();
|
|
720
|
+
this.dispatchRoomStatus("doc", {
|
|
721
|
+
roomId: session.roomId,
|
|
722
|
+
docId: session.docId
|
|
723
|
+
}, "disconnected", session.statusEmitter, session.statusListener);
|
|
421
724
|
debug("doc session destroyed", {
|
|
422
725
|
docId,
|
|
423
726
|
roomId: session.roomId
|
|
424
727
|
});
|
|
425
728
|
}
|
|
729
|
+
async leaveEphemeralSession(roomId) {
|
|
730
|
+
const session = this.ephemeralSessions.get(roomId);
|
|
731
|
+
if (!session) {
|
|
732
|
+
debug("leaveEphemeralSession invoked but no session found", { roomId });
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
this.ephemeralSessions.delete(roomId);
|
|
736
|
+
debug("leaving ephemeral session", { roomId });
|
|
737
|
+
try {
|
|
738
|
+
await session.room?.leave();
|
|
739
|
+
debug("ephemeral room left", { roomId });
|
|
740
|
+
} catch (error) {
|
|
741
|
+
debug("ephemeral room leave failed; destroying", {
|
|
742
|
+
roomId,
|
|
743
|
+
error
|
|
744
|
+
});
|
|
745
|
+
await session.room?.destroy().catch(() => {});
|
|
746
|
+
}
|
|
747
|
+
session.adaptor.destroy();
|
|
748
|
+
this.dispatchRoomStatus("ephemeral", { roomId: session.roomId }, "disconnected", session.statusEmitter, void 0);
|
|
749
|
+
debug("ephemeral session destroyed", { roomId });
|
|
750
|
+
}
|
|
426
751
|
};
|
|
427
752
|
|
|
428
753
|
//#endregion
|