@agent-p2p/peer 0.0.3 → 0.0.5
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/dist/radio-peer.d.ts +1 -1
- package/dist/radio-peer.d.ts.map +1 -1
- package/dist/radio-peer.js +60 -54
- package/dist/radio-peer.js.map +1 -1
- package/dist/transport/trystero-provider.d.ts +8 -1
- package/dist/transport/trystero-provider.d.ts.map +1 -1
- package/dist/transport/trystero-provider.js +90 -25
- package/dist/transport/trystero-provider.js.map +1 -1
- package/package.json +1 -1
- package/src/radio-peer.ts +58 -53
- package/src/transport/trystero-provider.ts +90 -23
package/dist/radio-peer.d.ts
CHANGED
|
@@ -25,6 +25,6 @@ export declare class RadioPeer extends TypedEmitter<PeerEventMap> {
|
|
|
25
25
|
subscribe(topic: string, fn: (signal: SignalEnvelope) => void): () => void;
|
|
26
26
|
getHistory(topic: string): SignalEnvelope[];
|
|
27
27
|
getPeers(): PeerInfo[];
|
|
28
|
-
disconnect(): void
|
|
28
|
+
disconnect(): Promise<void>;
|
|
29
29
|
}
|
|
30
30
|
//# sourceMappingURL=radio-peer.d.ts.map
|
package/dist/radio-peer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-peer.d.ts","sourceRoot":"","sources":["../src/radio-peer.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,OAAO,EAIZ,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAK3E,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AASpE,qBAAa,SAAU,SAAQ,YAAY,CAAC,YAAY,CAAC;IACxD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,KAAK,CAAsE;IACnF,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,IAAI,CAAqC;IACjD,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,WAAW,CAA6B;gBAEpC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB;YAQ5C,KAAK;
|
|
1
|
+
{"version":3,"file":"radio-peer.d.ts","sourceRoot":"","sources":["../src/radio-peer.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,OAAO,EAIZ,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAK3E,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AASpE,qBAAa,SAAU,SAAQ,YAAY,CAAC,YAAY,CAAC;IACxD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,KAAK,CAAsE;IACnF,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,IAAI,CAAqC;IACjD,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,WAAW,CAA6B;gBAEpC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB;YAQ5C,KAAK;IA6Eb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,WAAW,IAAI,eAAe,CAEjC;IAEK,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAkBpE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI;IAe1E,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,EAAE;IAI3C,QAAQ,IAAI,QAAQ,EAAE;IAIhB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAYjC"}
|
package/dist/radio-peer.js
CHANGED
|
@@ -33,63 +33,69 @@ export class RadioPeer extends TypedEmitter {
|
|
|
33
33
|
}
|
|
34
34
|
async _init() {
|
|
35
35
|
this.emit("status", "connecting");
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
else {
|
|
40
|
-
this._keypair = await generateKeypair();
|
|
41
|
-
}
|
|
42
|
-
this._peerId = peerId(this._keypair.publicKey);
|
|
43
|
-
const guard = (pid, topic) => {
|
|
44
|
-
return this._permissions.canPublish(pid, topic);
|
|
45
|
-
};
|
|
46
|
-
this._docManager = new DocManager(this._opts.maxSignalsPerChannel, guard);
|
|
47
|
-
this._permissions = new RoomPermissions(this._docManager.doc);
|
|
48
|
-
const roomName = `agent-p2p:${this.stationId}`;
|
|
49
|
-
const transport = this._opts.transport;
|
|
50
|
-
this._provider = new TrysteroProvider(this._docManager.doc, roomName, {
|
|
51
|
-
backend: transport.backend,
|
|
52
|
-
password: this._opts.password || undefined,
|
|
53
|
-
relayUrls: transport.relayUrls,
|
|
54
|
-
});
|
|
55
|
-
await this._provider.connected;
|
|
56
|
-
if (this._opts.enablePersistence) {
|
|
57
|
-
try {
|
|
58
|
-
this._idb = createIndexedDBProvider(this._docManager.doc, roomName);
|
|
36
|
+
try {
|
|
37
|
+
if (this._opts.keypair) {
|
|
38
|
+
this._keypair = this._opts.keypair;
|
|
59
39
|
}
|
|
60
|
-
|
|
61
|
-
|
|
40
|
+
else {
|
|
41
|
+
this._keypair = await generateKeypair();
|
|
62
42
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (!this._knownPeers.has(p.peerId)) {
|
|
81
|
-
this._knownPeers.add(p.peerId);
|
|
82
|
-
this.emit("peer:join", p);
|
|
43
|
+
this._peerId = peerId(this._keypair.publicKey);
|
|
44
|
+
const guard = (pid, topic) => {
|
|
45
|
+
return this._permissions.canPublish(pid, topic);
|
|
46
|
+
};
|
|
47
|
+
this._docManager = new DocManager(this._opts.maxSignalsPerChannel, guard);
|
|
48
|
+
this._permissions = new RoomPermissions(this._docManager.doc);
|
|
49
|
+
const roomName = `agent-p2p:${this.stationId}`;
|
|
50
|
+
const transport = this._opts.transport;
|
|
51
|
+
this._provider = new TrysteroProvider(this._docManager.doc, roomName, {
|
|
52
|
+
backend: transport.backend,
|
|
53
|
+
password: this._opts.password || undefined,
|
|
54
|
+
relayUrls: transport.relayUrls,
|
|
55
|
+
});
|
|
56
|
+
await this._provider.connected;
|
|
57
|
+
if (this._opts.enablePersistence) {
|
|
58
|
+
try {
|
|
59
|
+
this._idb = createIndexedDBProvider(this._docManager.doc, roomName);
|
|
83
60
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!currentIds.has(id)) {
|
|
87
|
-
this._knownPeers.delete(id);
|
|
88
|
-
this.emit("peer:leave", { peerId: id, joinedAt: 0 });
|
|
61
|
+
catch {
|
|
62
|
+
// IndexedDB not available (e.g. Node.js) — skip
|
|
89
63
|
}
|
|
90
64
|
}
|
|
91
|
-
|
|
92
|
-
|
|
65
|
+
setLocalPeer(this._provider.awareness, this._peerId);
|
|
66
|
+
// TOFU: first peer becomes owner, later peers become members
|
|
67
|
+
if (!this._permissions.isInitialized) {
|
|
68
|
+
this._permissions.initializeAsOwner(this._peerId);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this._permissions.acceptExisting(this._peerId);
|
|
72
|
+
}
|
|
73
|
+
// Forward role changes as events
|
|
74
|
+
this._unsubRoles = this._permissions.observeRoles((pid, role) => {
|
|
75
|
+
this.emit("roles:changed", { peerId: pid, role });
|
|
76
|
+
});
|
|
77
|
+
this._provider.awareness.on("change", () => {
|
|
78
|
+
const current = getConnectedPeers(this._provider.awareness);
|
|
79
|
+
const currentIds = new Set(current.map((p) => p.peerId));
|
|
80
|
+
for (const p of current) {
|
|
81
|
+
if (!this._knownPeers.has(p.peerId)) {
|
|
82
|
+
this._knownPeers.add(p.peerId);
|
|
83
|
+
this.emit("peer:join", p);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const id of this._knownPeers) {
|
|
87
|
+
if (!currentIds.has(id)) {
|
|
88
|
+
this._knownPeers.delete(id);
|
|
89
|
+
this.emit("peer:leave", { peerId: id, joinedAt: 0 });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
this.emit("status", "connected");
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
this.emit("status", "disconnected");
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
93
99
|
}
|
|
94
100
|
async ready() {
|
|
95
101
|
return this._readyPromise;
|
|
@@ -137,14 +143,14 @@ export class RadioPeer extends TypedEmitter {
|
|
|
137
143
|
getPeers() {
|
|
138
144
|
return getConnectedPeers(this._provider.awareness);
|
|
139
145
|
}
|
|
140
|
-
disconnect() {
|
|
146
|
+
async disconnect() {
|
|
141
147
|
this.emit("status", "disconnected");
|
|
142
148
|
for (const unsub of this._subscriptions.values()) {
|
|
143
149
|
unsub();
|
|
144
150
|
}
|
|
145
151
|
this._subscriptions.clear();
|
|
146
152
|
this._unsubRoles?.();
|
|
147
|
-
this._provider.destroy();
|
|
153
|
+
await this._provider.destroy();
|
|
148
154
|
this._idb?.destroy();
|
|
149
155
|
this._docManager.destroy();
|
|
150
156
|
this.removeAllListeners();
|
package/dist/radio-peer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-peer.js","sourceRoot":"","sources":["../src/radio-peer.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,eAAe,EACf,MAAM,EACN,aAAa,GACb,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEpE,MAAM,eAAe,GAAgD;IACpE,QAAQ,EAAE,EAAE;IACZ,oBAAoB,EAAE,IAAI;IAC1B,iBAAiB,EAAE,IAAI;IACvB,SAAS,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;CACjC,CAAC;AAEF,MAAM,OAAO,SAAU,SAAQ,YAA0B;IAC/C,SAAS,CAAS;IACnB,QAAQ,CAAW;IACnB,OAAO,CAAU;IACjB,KAAK,CAAsE;IAC3E,WAAW,CAAc;IACzB,SAAS,CAAoB;IAC7B,IAAI,GAAgC,IAAI,CAAC;IACzC,cAAc,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC/C,aAAa,CAAgB;IAC7B,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,YAAY,CAAmB;IAC/B,WAAW,GAAwB,IAAI,CAAC;IAEhD,YAAY,SAAiB,EAAE,OAAyB,EAAE;QACzD,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;QAClC,IAAI,CAAC,KAAK,GAAG,EAAE,GAAG,eAAe,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC;QACtD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;IACnC,CAAC;IAEO,KAAK,CAAC,KAAK;QAClB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAElC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"radio-peer.js","sourceRoot":"","sources":["../src/radio-peer.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,9 +23,16 @@ export declare class TrysteroProvider {
|
|
|
23
23
|
private _connect;
|
|
24
24
|
private _sendSyncStep1;
|
|
25
25
|
private _sendAwareness;
|
|
26
|
+
/**
|
|
27
|
+
* Extract Yjs awareness clientIDs from a raw awareness update binary.
|
|
28
|
+
* Format: [count: varuint] [entries: {clientId: varuint, clock: varuint, state: varstring}...]
|
|
29
|
+
*/
|
|
30
|
+
private _trackPeerClients;
|
|
26
31
|
private _handleMessage;
|
|
32
|
+
/** Broadcast data to all connected peers. Fire-and-forget with error suppression. */
|
|
27
33
|
private _broadcast;
|
|
34
|
+
/** Send data to a specific peer. Fire-and-forget with error suppression. */
|
|
28
35
|
private _sendTo;
|
|
29
|
-
destroy(): void
|
|
36
|
+
destroy(): Promise<void>;
|
|
30
37
|
}
|
|
31
38
|
//# sourceMappingURL=trystero-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trystero-provider.d.ts","sourceRoot":"","sources":["../../src/transport/trystero-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EAAE,SAAS,EAAsE,MAAM,uBAAuB,CAAC;AAKtH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"trystero-provider.d.ts","sourceRoot":"","sources":["../../src/transport/trystero-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EAAE,SAAS,EAAsE,MAAM,uBAAuB,CAAC;AAKtH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAMpD,MAAM,WAAW,cAAc;IAC9B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAID,qBAAa,gBAAgB;IAC5B,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAElC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAgD;IACpE,oFAAoF;IACpF,OAAO,CAAC,YAAY,CAAkC;gBAE1C,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,cAAmB;YAgBvD,QAAQ;IAsFtB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,cAAc;IAStB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,CAAC,cAAc;IAkCtB,qFAAqF;IACrF,OAAO,CAAC,UAAU;IASlB,4EAA4E;IAC5E,OAAO,CAAC,OAAO;IAST,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAmC9B"}
|
|
@@ -4,6 +4,7 @@ import * as encoding from "lib0/encoding";
|
|
|
4
4
|
import * as decoding from "lib0/decoding";
|
|
5
5
|
const MSG_SYNC = 0;
|
|
6
6
|
const MSG_AWARENESS = 1;
|
|
7
|
+
const MSG_LEAVE = 2;
|
|
7
8
|
export class TrysteroProvider {
|
|
8
9
|
doc;
|
|
9
10
|
awareness;
|
|
@@ -21,7 +22,7 @@ export class TrysteroProvider {
|
|
|
21
22
|
this.roomName = roomName;
|
|
22
23
|
this.awareness = new Awareness(doc);
|
|
23
24
|
this._onDocUpdate = (update, origin) => {
|
|
24
|
-
if (origin === this)
|
|
25
|
+
if (this._destroyed || origin === this)
|
|
25
26
|
return;
|
|
26
27
|
const encoder = encoding.createEncoder();
|
|
27
28
|
encoding.writeVarUint(encoder, MSG_SYNC);
|
|
@@ -60,47 +61,51 @@ export class TrysteroProvider {
|
|
|
60
61
|
if (this._destroyed)
|
|
61
62
|
return;
|
|
62
63
|
const room = joinRoom(roomConfig, this.roomName);
|
|
64
|
+
// Re-check after joinRoom — destroy() may have been called during the
|
|
65
|
+
// synchronous joinRoom call or between the destroyed check and here.
|
|
66
|
+
if (this._destroyed) {
|
|
67
|
+
room.leave().catch(() => { });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
63
70
|
this._room = room;
|
|
64
71
|
const [send, receive] = room.makeAction("yjs-sync");
|
|
65
72
|
this._send = send;
|
|
73
|
+
// Trystero action receivers can't be unregistered, so _handleMessage
|
|
74
|
+
// guards against calls after destruction.
|
|
66
75
|
receive((data, peerId) => {
|
|
67
76
|
this._handleMessage(new Uint8Array(data), peerId);
|
|
68
77
|
});
|
|
69
78
|
room.onPeerJoin((peerId) => {
|
|
79
|
+
if (this._destroyed)
|
|
80
|
+
return;
|
|
70
81
|
this._peers.add(peerId);
|
|
71
82
|
this._sendSyncStep1(peerId);
|
|
72
83
|
this._sendAwareness(peerId);
|
|
73
84
|
});
|
|
74
85
|
room.onPeerLeave((peerId) => {
|
|
86
|
+
if (this._destroyed)
|
|
87
|
+
return;
|
|
75
88
|
this._peers.delete(peerId);
|
|
76
|
-
// Remove awareness states that came from this trystero peer
|
|
77
89
|
const clientIds = this._peerClients.get(peerId);
|
|
78
90
|
if (clientIds && clientIds.size > 0) {
|
|
79
91
|
removeAwarenessStates(this.awareness, Array.from(clientIds), "peer left");
|
|
80
|
-
this._peerClients.delete(peerId);
|
|
81
92
|
}
|
|
93
|
+
this._peerClients.delete(peerId);
|
|
82
94
|
});
|
|
83
95
|
this.doc.on("update", this._onDocUpdate);
|
|
96
|
+
// Only broadcast LOCAL awareness changes. Updates received from remote
|
|
97
|
+
// peers (origin === this) don't need re-broadcasting because trystero's
|
|
98
|
+
// send already reaches all peers in the room.
|
|
84
99
|
this.awareness.on("update", ({ added, updated, removed }, origin) => {
|
|
100
|
+
if (this._destroyed || origin === this)
|
|
101
|
+
return;
|
|
85
102
|
const changedClients = [...added, ...updated, ...removed];
|
|
103
|
+
if (changedClients.length === 0)
|
|
104
|
+
return;
|
|
86
105
|
const encoder = encoding.createEncoder();
|
|
87
106
|
encoding.writeVarUint(encoder, MSG_AWARENESS);
|
|
88
107
|
encoding.writeVarUint8Array(encoder, encodeAwarenessUpdate(this.awareness, changedClients));
|
|
89
108
|
this._broadcast(encoding.toUint8Array(encoder));
|
|
90
|
-
// Track which Yjs clientIDs belong to which trystero peer
|
|
91
|
-
if (typeof origin === "string" && origin !== "local") {
|
|
92
|
-
let ids = this._peerClients.get(origin);
|
|
93
|
-
if (!ids) {
|
|
94
|
-
ids = new Set();
|
|
95
|
-
this._peerClients.set(origin, ids);
|
|
96
|
-
}
|
|
97
|
-
for (const id of added)
|
|
98
|
-
ids.add(id);
|
|
99
|
-
for (const id of updated)
|
|
100
|
-
ids.add(id);
|
|
101
|
-
for (const id of removed)
|
|
102
|
-
ids.delete(id);
|
|
103
|
-
}
|
|
104
109
|
});
|
|
105
110
|
}
|
|
106
111
|
_sendSyncStep1(peerId) {
|
|
@@ -118,7 +123,33 @@ export class TrysteroProvider {
|
|
|
118
123
|
encoding.writeVarUint8Array(encoder, encodeAwarenessUpdate(this.awareness, clients));
|
|
119
124
|
this._sendTo(encoding.toUint8Array(encoder), peerId);
|
|
120
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Extract Yjs awareness clientIDs from a raw awareness update binary.
|
|
128
|
+
* Format: [count: varuint] [entries: {clientId: varuint, clock: varuint, state: varstring}...]
|
|
129
|
+
*/
|
|
130
|
+
_trackPeerClients(peerId, update) {
|
|
131
|
+
const decoder = decoding.createDecoder(update);
|
|
132
|
+
const len = decoding.readVarUint(decoder);
|
|
133
|
+
let ids = this._peerClients.get(peerId);
|
|
134
|
+
if (!ids) {
|
|
135
|
+
ids = new Set();
|
|
136
|
+
this._peerClients.set(peerId, ids);
|
|
137
|
+
}
|
|
138
|
+
for (let i = 0; i < len; i++) {
|
|
139
|
+
const clientId = decoding.readVarUint(decoder);
|
|
140
|
+
decoding.readVarUint(decoder); // clock
|
|
141
|
+
const state = decoding.readVarString(decoder);
|
|
142
|
+
if (state === "null") {
|
|
143
|
+
ids.delete(clientId);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
ids.add(clientId);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
121
150
|
_handleMessage(data, peerId) {
|
|
151
|
+
if (this._destroyed)
|
|
152
|
+
return;
|
|
122
153
|
const decoder = decoding.createDecoder(data);
|
|
123
154
|
const msgType = decoding.readVarUint(decoder);
|
|
124
155
|
switch (msgType) {
|
|
@@ -133,40 +164,74 @@ export class TrysteroProvider {
|
|
|
133
164
|
}
|
|
134
165
|
case MSG_AWARENESS: {
|
|
135
166
|
const update = decoding.readVarUint8Array(decoder);
|
|
136
|
-
|
|
167
|
+
this._trackPeerClients(peerId, update);
|
|
168
|
+
// Use `this` as origin (standard y-webrtc pattern) so the
|
|
169
|
+
// awareness "update" handler can skip re-broadcasting.
|
|
170
|
+
applyAwarenessUpdate(this.awareness, update, this);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case MSG_LEAVE: {
|
|
174
|
+
const clientId = decoding.readVarUint(decoder);
|
|
175
|
+
this._peers.delete(peerId);
|
|
176
|
+
removeAwarenessStates(this.awareness, [clientId], "peer left");
|
|
177
|
+
this._peerClients.delete(peerId);
|
|
137
178
|
break;
|
|
138
179
|
}
|
|
139
180
|
}
|
|
140
181
|
}
|
|
182
|
+
/** Broadcast data to all connected peers. Fire-and-forget with error suppression. */
|
|
141
183
|
_broadcast(data) {
|
|
184
|
+
if (!this._send || this._peers.size === 0)
|
|
185
|
+
return;
|
|
142
186
|
try {
|
|
143
|
-
|
|
144
|
-
this._send(data.buffer);
|
|
145
|
-
}
|
|
187
|
+
this._send(data.buffer)?.catch(() => { });
|
|
146
188
|
}
|
|
147
189
|
catch {
|
|
148
|
-
// Data channel may already be closed
|
|
190
|
+
// Data channel may already be closed
|
|
149
191
|
}
|
|
150
192
|
}
|
|
193
|
+
/** Send data to a specific peer. Fire-and-forget with error suppression. */
|
|
151
194
|
_sendTo(data, peerId) {
|
|
195
|
+
if (!this._send)
|
|
196
|
+
return;
|
|
152
197
|
try {
|
|
153
|
-
this._send
|
|
198
|
+
this._send(data.buffer, peerId)?.catch(() => { });
|
|
154
199
|
}
|
|
155
200
|
catch {
|
|
156
201
|
// Peer's data channel may already be closed
|
|
157
202
|
}
|
|
158
203
|
}
|
|
159
|
-
destroy() {
|
|
204
|
+
async destroy() {
|
|
160
205
|
if (this._destroyed)
|
|
161
206
|
return;
|
|
162
207
|
this._destroyed = true;
|
|
208
|
+
// Send explicit leave message over data channel before tearing down.
|
|
209
|
+
const encoder = encoding.createEncoder();
|
|
210
|
+
encoding.writeVarUint(encoder, MSG_LEAVE);
|
|
211
|
+
encoding.writeVarUint(encoder, this.doc.clientID);
|
|
212
|
+
this._broadcast(encoding.toUint8Array(encoder));
|
|
213
|
+
// Broadcast awareness removal so peers can clean up immediately.
|
|
163
214
|
removeAwarenessStates(this.awareness, [this.doc.clientID], "provider destroyed");
|
|
164
215
|
this.doc.off("update", this._onDocUpdate);
|
|
165
|
-
|
|
216
|
+
// Null out send/peers before awaiting leave — any queued callbacks
|
|
217
|
+
// that fire during teardown will hit the _destroyed guard.
|
|
218
|
+
const room = this._room;
|
|
166
219
|
this._room = null;
|
|
167
220
|
this._send = null;
|
|
168
221
|
this._peers.clear();
|
|
169
222
|
this._peerClients.clear();
|
|
223
|
+
// Clear the awareness check interval to prevent keeping the event loop alive.
|
|
224
|
+
this.awareness.destroy();
|
|
225
|
+
// Await room.leave() — trystero caches rooms in a global `occupiedRooms`
|
|
226
|
+
// map keyed by appId+roomId. The entry is only removed when leave() fully
|
|
227
|
+
// completes (~99ms internal delay). Without awaiting, a subsequent
|
|
228
|
+
// joinRoom() with the same roomId returns the stale room object.
|
|
229
|
+
try {
|
|
230
|
+
await room?.leave();
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// Room may already be torn down
|
|
234
|
+
}
|
|
170
235
|
}
|
|
171
236
|
}
|
|
172
237
|
//# sourceMappingURL=trystero-provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trystero-provider.js","sourceRoot":"","sources":["../../src/transport/trystero-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACtH,OAAO,KAAK,YAAY,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAI1C,MAAM,QAAQ,GAAG,CAAC,CAAC;AACnB,MAAM,aAAa,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"trystero-provider.js","sourceRoot":"","sources":["../../src/transport/trystero-provider.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/radio-peer.ts
CHANGED
|
@@ -46,73 +46,78 @@ export class RadioPeer extends TypedEmitter<PeerEventMap> {
|
|
|
46
46
|
private async _init(): Promise<void> {
|
|
47
47
|
this.emit("status", "connecting");
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
try {
|
|
50
|
+
if (this._opts.keypair) {
|
|
51
|
+
this._keypair = this._opts.keypair;
|
|
52
|
+
} else {
|
|
53
|
+
this._keypair = await generateKeypair();
|
|
54
|
+
}
|
|
55
|
+
this._peerId = peerId(this._keypair.publicKey);
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const guard = (pid: string, topic: string) => {
|
|
58
|
+
return this._permissions.canPublish(pid, topic);
|
|
59
|
+
};
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
this._docManager = new DocManager(this._opts.maxSignalsPerChannel, guard);
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
this._permissions = new RoomPermissions(this._docManager.doc);
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
const roomName = `agent-p2p:${this.stationId}`;
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
const transport = this._opts.transport;
|
|
68
|
+
this._provider = new TrysteroProvider(this._docManager.doc, roomName, {
|
|
69
|
+
backend: transport.backend,
|
|
70
|
+
password: this._opts.password || undefined,
|
|
71
|
+
relayUrls: transport.relayUrls,
|
|
72
|
+
});
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
await this._provider.connected;
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
if (this._opts.enablePersistence) {
|
|
77
|
+
try {
|
|
78
|
+
this._idb = createIndexedDBProvider(this._docManager.doc, roomName);
|
|
79
|
+
} catch {
|
|
80
|
+
// IndexedDB not available (e.g. Node.js) — skip
|
|
81
|
+
}
|
|
80
82
|
}
|
|
81
|
-
}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
setLocalPeer(this._provider.awareness, this._peerId);
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
// TOFU: first peer becomes owner, later peers become members
|
|
87
|
+
if (!this._permissions.isInitialized) {
|
|
88
|
+
this._permissions.initializeAsOwner(this._peerId);
|
|
89
|
+
} else {
|
|
90
|
+
this._permissions.acceptExisting(this._peerId);
|
|
91
|
+
}
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
// Forward role changes as events
|
|
94
|
+
this._unsubRoles = this._permissions.observeRoles((pid, role) => {
|
|
95
|
+
this.emit("roles:changed", { peerId: pid, role });
|
|
96
|
+
});
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
this._provider.awareness.on("change", () => {
|
|
99
|
+
const current = getConnectedPeers(this._provider.awareness);
|
|
100
|
+
const currentIds = new Set(current.map((p) => p.peerId));
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
for (const p of current) {
|
|
103
|
+
if (!this._knownPeers.has(p.peerId)) {
|
|
104
|
+
this._knownPeers.add(p.peerId);
|
|
105
|
+
this.emit("peer:join", p);
|
|
106
|
+
}
|
|
105
107
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
for (const id of this._knownPeers) {
|
|
109
|
+
if (!currentIds.has(id)) {
|
|
110
|
+
this._knownPeers.delete(id);
|
|
111
|
+
this.emit("peer:leave", { peerId: id, joinedAt: 0 });
|
|
112
|
+
}
|
|
111
113
|
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
+
});
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
this.emit("status", "connected");
|
|
117
|
+
} catch (err) {
|
|
118
|
+
this.emit("status", "disconnected");
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
async ready(): Promise<void> {
|
|
@@ -172,14 +177,14 @@ export class RadioPeer extends TypedEmitter<PeerEventMap> {
|
|
|
172
177
|
return getConnectedPeers(this._provider.awareness);
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
disconnect(): void {
|
|
180
|
+
async disconnect(): Promise<void> {
|
|
176
181
|
this.emit("status", "disconnected");
|
|
177
182
|
for (const unsub of this._subscriptions.values()) {
|
|
178
183
|
unsub();
|
|
179
184
|
}
|
|
180
185
|
this._subscriptions.clear();
|
|
181
186
|
this._unsubRoles?.();
|
|
182
|
-
this._provider.destroy();
|
|
187
|
+
await this._provider.destroy();
|
|
183
188
|
this._idb?.destroy();
|
|
184
189
|
this._docManager.destroy();
|
|
185
190
|
this.removeAllListeners();
|
|
@@ -8,6 +8,7 @@ import type { SignalingBackend } from "../types.js";
|
|
|
8
8
|
|
|
9
9
|
const MSG_SYNC = 0;
|
|
10
10
|
const MSG_AWARENESS = 1;
|
|
11
|
+
const MSG_LEAVE = 2;
|
|
11
12
|
|
|
12
13
|
export interface TrysteroConfig {
|
|
13
14
|
backend?: SignalingBackend;
|
|
@@ -38,7 +39,7 @@ export class TrysteroProvider {
|
|
|
38
39
|
this.awareness = new Awareness(doc);
|
|
39
40
|
|
|
40
41
|
this._onDocUpdate = (update: Uint8Array, origin: unknown) => {
|
|
41
|
-
if (origin === this) return;
|
|
42
|
+
if (this._destroyed || origin === this) return;
|
|
42
43
|
const encoder = encoding.createEncoder();
|
|
43
44
|
encoding.writeVarUint(encoder, MSG_SYNC);
|
|
44
45
|
syncProtocol.writeUpdate(encoder, update);
|
|
@@ -79,53 +80,57 @@ export class TrysteroProvider {
|
|
|
79
80
|
if (this._destroyed) return;
|
|
80
81
|
|
|
81
82
|
const room = joinRoom(roomConfig, this.roomName);
|
|
83
|
+
|
|
84
|
+
// Re-check after joinRoom — destroy() may have been called during the
|
|
85
|
+
// synchronous joinRoom call or between the destroyed check and here.
|
|
86
|
+
if (this._destroyed) {
|
|
87
|
+
room.leave().catch(() => {});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
82
91
|
this._room = room;
|
|
83
92
|
|
|
84
93
|
const [send, receive] = room.makeAction<ArrayBuffer>("yjs-sync");
|
|
85
94
|
this._send = send;
|
|
86
95
|
|
|
96
|
+
// Trystero action receivers can't be unregistered, so _handleMessage
|
|
97
|
+
// guards against calls after destruction.
|
|
87
98
|
(receive as ActionReceiver<ArrayBuffer>)((data: ArrayBuffer, peerId: string) => {
|
|
88
99
|
this._handleMessage(new Uint8Array(data), peerId);
|
|
89
100
|
});
|
|
90
101
|
|
|
91
102
|
room.onPeerJoin((peerId) => {
|
|
103
|
+
if (this._destroyed) return;
|
|
92
104
|
this._peers.add(peerId);
|
|
93
105
|
this._sendSyncStep1(peerId);
|
|
94
106
|
this._sendAwareness(peerId);
|
|
95
107
|
});
|
|
96
108
|
|
|
97
109
|
room.onPeerLeave((peerId) => {
|
|
110
|
+
if (this._destroyed) return;
|
|
98
111
|
this._peers.delete(peerId);
|
|
99
|
-
// Remove awareness states that came from this trystero peer
|
|
100
112
|
const clientIds = this._peerClients.get(peerId);
|
|
101
113
|
if (clientIds && clientIds.size > 0) {
|
|
102
114
|
removeAwarenessStates(this.awareness, Array.from(clientIds), "peer left");
|
|
103
|
-
this._peerClients.delete(peerId);
|
|
104
115
|
}
|
|
116
|
+
this._peerClients.delete(peerId);
|
|
105
117
|
});
|
|
106
118
|
|
|
107
119
|
this.doc.on("update", this._onDocUpdate);
|
|
108
120
|
|
|
121
|
+
// Only broadcast LOCAL awareness changes. Updates received from remote
|
|
122
|
+
// peers (origin === this) don't need re-broadcasting because trystero's
|
|
123
|
+
// send already reaches all peers in the room.
|
|
109
124
|
this.awareness.on(
|
|
110
125
|
"update",
|
|
111
126
|
({ added, updated, removed }: { added: number[]; updated: number[]; removed: number[] }, origin: unknown) => {
|
|
127
|
+
if (this._destroyed || origin === this) return;
|
|
112
128
|
const changedClients = [...added, ...updated, ...removed];
|
|
129
|
+
if (changedClients.length === 0) return;
|
|
113
130
|
const encoder = encoding.createEncoder();
|
|
114
131
|
encoding.writeVarUint(encoder, MSG_AWARENESS);
|
|
115
132
|
encoding.writeVarUint8Array(encoder, encodeAwarenessUpdate(this.awareness, changedClients));
|
|
116
133
|
this._broadcast(encoding.toUint8Array(encoder));
|
|
117
|
-
|
|
118
|
-
// Track which Yjs clientIDs belong to which trystero peer
|
|
119
|
-
if (typeof origin === "string" && origin !== "local") {
|
|
120
|
-
let ids = this._peerClients.get(origin);
|
|
121
|
-
if (!ids) {
|
|
122
|
-
ids = new Set();
|
|
123
|
-
this._peerClients.set(origin, ids);
|
|
124
|
-
}
|
|
125
|
-
for (const id of added) ids.add(id);
|
|
126
|
-
for (const id of updated) ids.add(id);
|
|
127
|
-
for (const id of removed) ids.delete(id);
|
|
128
|
-
}
|
|
129
134
|
},
|
|
130
135
|
);
|
|
131
136
|
}
|
|
@@ -146,7 +151,33 @@ export class TrysteroProvider {
|
|
|
146
151
|
this._sendTo(encoding.toUint8Array(encoder), peerId);
|
|
147
152
|
}
|
|
148
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Extract Yjs awareness clientIDs from a raw awareness update binary.
|
|
156
|
+
* Format: [count: varuint] [entries: {clientId: varuint, clock: varuint, state: varstring}...]
|
|
157
|
+
*/
|
|
158
|
+
private _trackPeerClients(peerId: string, update: Uint8Array): void {
|
|
159
|
+
const decoder = decoding.createDecoder(update);
|
|
160
|
+
const len = decoding.readVarUint(decoder);
|
|
161
|
+
let ids = this._peerClients.get(peerId);
|
|
162
|
+
if (!ids) {
|
|
163
|
+
ids = new Set();
|
|
164
|
+
this._peerClients.set(peerId, ids);
|
|
165
|
+
}
|
|
166
|
+
for (let i = 0; i < len; i++) {
|
|
167
|
+
const clientId = decoding.readVarUint(decoder);
|
|
168
|
+
decoding.readVarUint(decoder); // clock
|
|
169
|
+
const state = decoding.readVarString(decoder);
|
|
170
|
+
if (state === "null") {
|
|
171
|
+
ids.delete(clientId);
|
|
172
|
+
} else {
|
|
173
|
+
ids.add(clientId);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
149
178
|
private _handleMessage(data: Uint8Array, peerId: string): void {
|
|
179
|
+
if (this._destroyed) return;
|
|
180
|
+
|
|
150
181
|
const decoder = decoding.createDecoder(data);
|
|
151
182
|
const msgType = decoding.readVarUint(decoder);
|
|
152
183
|
|
|
@@ -162,39 +193,75 @@ export class TrysteroProvider {
|
|
|
162
193
|
}
|
|
163
194
|
case MSG_AWARENESS: {
|
|
164
195
|
const update = decoding.readVarUint8Array(decoder);
|
|
165
|
-
|
|
196
|
+
this._trackPeerClients(peerId, update);
|
|
197
|
+
// Use `this` as origin (standard y-webrtc pattern) so the
|
|
198
|
+
// awareness "update" handler can skip re-broadcasting.
|
|
199
|
+
applyAwarenessUpdate(this.awareness, update, this);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case MSG_LEAVE: {
|
|
203
|
+
const clientId = decoding.readVarUint(decoder);
|
|
204
|
+
this._peers.delete(peerId);
|
|
205
|
+
removeAwarenessStates(this.awareness, [clientId], "peer left");
|
|
206
|
+
this._peerClients.delete(peerId);
|
|
166
207
|
break;
|
|
167
208
|
}
|
|
168
209
|
}
|
|
169
210
|
}
|
|
170
211
|
|
|
212
|
+
/** Broadcast data to all connected peers. Fire-and-forget with error suppression. */
|
|
171
213
|
private _broadcast(data: Uint8Array): void {
|
|
214
|
+
if (!this._send || this._peers.size === 0) return;
|
|
172
215
|
try {
|
|
173
|
-
|
|
174
|
-
this._send(data.buffer as ArrayBuffer);
|
|
175
|
-
}
|
|
216
|
+
this._send(data.buffer as ArrayBuffer)?.catch(() => {});
|
|
176
217
|
} catch {
|
|
177
|
-
// Data channel may already be closed
|
|
218
|
+
// Data channel may already be closed
|
|
178
219
|
}
|
|
179
220
|
}
|
|
180
221
|
|
|
222
|
+
/** Send data to a specific peer. Fire-and-forget with error suppression. */
|
|
181
223
|
private _sendTo(data: Uint8Array, peerId: string): void {
|
|
224
|
+
if (!this._send) return;
|
|
182
225
|
try {
|
|
183
|
-
this._send
|
|
226
|
+
this._send(data.buffer as ArrayBuffer, peerId)?.catch(() => {});
|
|
184
227
|
} catch {
|
|
185
228
|
// Peer's data channel may already be closed
|
|
186
229
|
}
|
|
187
230
|
}
|
|
188
231
|
|
|
189
|
-
destroy(): void {
|
|
232
|
+
async destroy(): Promise<void> {
|
|
190
233
|
if (this._destroyed) return;
|
|
191
234
|
this._destroyed = true;
|
|
235
|
+
|
|
236
|
+
// Send explicit leave message over data channel before tearing down.
|
|
237
|
+
const encoder = encoding.createEncoder();
|
|
238
|
+
encoding.writeVarUint(encoder, MSG_LEAVE);
|
|
239
|
+
encoding.writeVarUint(encoder, this.doc.clientID);
|
|
240
|
+
this._broadcast(encoding.toUint8Array(encoder));
|
|
241
|
+
|
|
242
|
+
// Broadcast awareness removal so peers can clean up immediately.
|
|
192
243
|
removeAwarenessStates(this.awareness, [this.doc.clientID], "provider destroyed");
|
|
193
244
|
this.doc.off("update", this._onDocUpdate);
|
|
194
|
-
|
|
245
|
+
|
|
246
|
+
// Null out send/peers before awaiting leave — any queued callbacks
|
|
247
|
+
// that fire during teardown will hit the _destroyed guard.
|
|
248
|
+
const room = this._room;
|
|
195
249
|
this._room = null;
|
|
196
250
|
this._send = null;
|
|
197
251
|
this._peers.clear();
|
|
198
252
|
this._peerClients.clear();
|
|
253
|
+
|
|
254
|
+
// Clear the awareness check interval to prevent keeping the event loop alive.
|
|
255
|
+
this.awareness.destroy();
|
|
256
|
+
|
|
257
|
+
// Await room.leave() — trystero caches rooms in a global `occupiedRooms`
|
|
258
|
+
// map keyed by appId+roomId. The entry is only removed when leave() fully
|
|
259
|
+
// completes (~99ms internal delay). Without awaiting, a subsequent
|
|
260
|
+
// joinRoom() with the same roomId returns the stale room object.
|
|
261
|
+
try {
|
|
262
|
+
await room?.leave();
|
|
263
|
+
} catch {
|
|
264
|
+
// Room may already be torn down
|
|
265
|
+
}
|
|
199
266
|
}
|
|
200
267
|
}
|