@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.
@@ -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
@@ -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;IAwEb,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;IAItB,UAAU,IAAI,IAAI;CAYlB"}
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"}
@@ -33,63 +33,69 @@ export class RadioPeer extends TypedEmitter {
33
33
  }
34
34
  async _init() {
35
35
  this.emit("status", "connecting");
36
- if (this._opts.keypair) {
37
- this._keypair = this._opts.keypair;
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
- catch {
61
- // IndexedDB not available (e.g. Node.js) — skip
40
+ else {
41
+ this._keypair = await generateKeypair();
62
42
  }
63
- }
64
- setLocalPeer(this._provider.awareness, this._peerId);
65
- // TOFU: first peer becomes owner, later peers become members
66
- if (!this._permissions.isInitialized) {
67
- this._permissions.initializeAsOwner(this._peerId);
68
- }
69
- else {
70
- this._permissions.acceptExisting(this._peerId);
71
- }
72
- // Forward role changes as events
73
- this._unsubRoles = this._permissions.observeRoles((pid, role) => {
74
- this.emit("roles:changed", { peerId: pid, role });
75
- });
76
- this._provider.awareness.on("change", () => {
77
- const current = getConnectedPeers(this._provider.awareness);
78
- const currentIds = new Set(current.map((p) => p.peerId));
79
- for (const p of current) {
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
- for (const id of this._knownPeers) {
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
- this.emit("status", "connected");
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();
@@ -1 +1 @@
1
- {"version":3,"file":"radio-peer.js","sourceRoot":"","sources":["../src/radio-peer.ts"],"names":[],"mappings":"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"}
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,CAAC;YACJ,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;gBACxB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACP,IAAI,CAAC,QAAQ,GAAG,MAAM,eAAe,EAAE,CAAC;YACzC,CAAC;YACD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YAE/C,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,KAAa,EAAE,EAAE;gBAC5C,OAAO,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACjD,CAAC,CAAC;YAEF,IAAI,CAAC,WAAW,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;YAE1E,IAAI,CAAC,YAAY,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YAE9D,MAAM,QAAQ,GAAG,aAAa,IAAI,CAAC,SAAS,EAAE,CAAC;YAE/C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;YACvC,IAAI,CAAC,SAAS,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE;gBACrE,OAAO,EAAE,SAAS,CAAC,OAAO;gBAC1B,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,SAAS;gBAC1C,SAAS,EAAE,SAAS,CAAC,SAAS;aAC9B,CAAC,CAAC;YAEH,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;YAE/B,IAAI,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC;gBAClC,IAAI,CAAC;oBACJ,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;gBACrE,CAAC;gBAAC,MAAM,CAAC;oBACR,gDAAgD;gBACjD,CAAC;YACF,CAAC;YAED,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YAErD,6DAA6D;YAC7D,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,aAAa,EAAE,CAAC;gBACtC,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACnD,CAAC;iBAAM,CAAC;gBACP,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAChD,CAAC;YAED,iCAAiC;YACjC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;gBAC/D,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;gBAC1C,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;gBAC5D,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;gBAEzD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;oBACzB,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;wBACrC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;wBAC/B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;oBAC3B,CAAC;gBACF,CAAC;gBACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;oBACnC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;wBACzB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;wBAC5B,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;oBACtD,CAAC;gBACF,CAAC;YACF,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;YACpC,MAAM,GAAG,CAAC;QACX,CAAC;IACF,CAAC;IAED,KAAK,CAAC,KAAK;QACV,OAAO,IAAI,CAAC,aAAa,CAAC;IAC3B,CAAC;IAED,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;IAED,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED,IAAI,WAAW;QACd,OAAO,IAAI,CAAC,YAAY,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAAa,EAAE,IAAa;QACzC,MAAM,IAAI,CAAC,aAAa,CAAC;QAEzB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;YACxD,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YAC7D,MAAM,IAAI,KAAK,CAAC,yCAAyC,KAAK,GAAG,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,aAAa,EAAE;aACtC,OAAO,CAAC,KAAK,CAAC;aACd,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;aAClB,IAAI,CAAC,IAAI,CAAC;aACV,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAEjE,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,OAAO,MAAM,CAAC;IACf,CAAC;IAED,SAAS,CAAC,KAAa,EAAE,EAAoC;QAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,UAAU,GAAG,CAAC,MAAsB,EAAE,EAAE;YAC7C,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;QACtE,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YACzC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACzB,EAAE,CAAC,CAAC,CAAC,CAAC;gBACN,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;YACxB,CAAC;QACF,CAAC,EAAE,UAAU,CAAC,CAAC;QACf,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACtC,OAAO,KAAK,CAAC;IACd,CAAC;IAED,UAAU,CAAC,KAAa;QACvB,OAAO,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;IACpD,CAAC;IAED,QAAQ;QACP,OAAO,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,UAAU;QACf,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QACpC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC;YAClD,KAAK,EAAE,CAAC;QACT,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QACrB,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC3B,CAAC;CACD"}
@@ -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;AAKpD,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;IAkFtB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,cAAc;IAsBtB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,OAAO;IAQf,OAAO,IAAI,IAAI;CAWf"}
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
- applyAwarenessUpdate(this.awareness, update, peerId);
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
- if (this._send && this._peers.size > 0) {
144
- this._send(data.buffer);
145
- }
187
+ this._send(data.buffer)?.catch(() => { });
146
188
  }
147
189
  catch {
148
- // Data channel may already be closed during shutdown
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?.(data.buffer, peerId);
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
- this._room?.leave();
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":"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"}
1
+ {"version":3,"file":"trystero-provider.js","sourceRoot":"","sources":["../../src/transport/trystero-provider.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-p2p/peer",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "P2P peer transport for agent-p2p — Yjs + trystero (serverless WebRTC)",
5
5
  "type": "module",
6
6
  "exports": {
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
- if (this._opts.keypair) {
50
- this._keypair = this._opts.keypair;
51
- } else {
52
- this._keypair = await generateKeypair();
53
- }
54
- this._peerId = peerId(this._keypair.publicKey);
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
- const guard = (pid: string, topic: string) => {
57
- return this._permissions.canPublish(pid, topic);
58
- };
57
+ const guard = (pid: string, topic: string) => {
58
+ return this._permissions.canPublish(pid, topic);
59
+ };
59
60
 
60
- this._docManager = new DocManager(this._opts.maxSignalsPerChannel, guard);
61
+ this._docManager = new DocManager(this._opts.maxSignalsPerChannel, guard);
61
62
 
62
- this._permissions = new RoomPermissions(this._docManager.doc);
63
+ this._permissions = new RoomPermissions(this._docManager.doc);
63
64
 
64
- const roomName = `agent-p2p:${this.stationId}`;
65
+ const roomName = `agent-p2p:${this.stationId}`;
65
66
 
66
- const transport = this._opts.transport;
67
- this._provider = new TrysteroProvider(this._docManager.doc, roomName, {
68
- backend: transport.backend,
69
- password: this._opts.password || undefined,
70
- relayUrls: transport.relayUrls,
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
- await this._provider.connected;
74
+ await this._provider.connected;
74
75
 
75
- if (this._opts.enablePersistence) {
76
- try {
77
- this._idb = createIndexedDBProvider(this._docManager.doc, roomName);
78
- } catch {
79
- // IndexedDB not available (e.g. Node.js) — skip
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
- setLocalPeer(this._provider.awareness, this._peerId);
84
+ setLocalPeer(this._provider.awareness, this._peerId);
84
85
 
85
- // TOFU: first peer becomes owner, later peers become members
86
- if (!this._permissions.isInitialized) {
87
- this._permissions.initializeAsOwner(this._peerId);
88
- } else {
89
- this._permissions.acceptExisting(this._peerId);
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
- // Forward role changes as events
93
- this._unsubRoles = this._permissions.observeRoles((pid, role) => {
94
- this.emit("roles:changed", { peerId: pid, role });
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
- this._provider.awareness.on("change", () => {
98
- const current = getConnectedPeers(this._provider.awareness);
99
- const currentIds = new Set(current.map((p) => p.peerId));
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
- for (const p of current) {
102
- if (!this._knownPeers.has(p.peerId)) {
103
- this._knownPeers.add(p.peerId);
104
- this.emit("peer:join", p);
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
- for (const id of this._knownPeers) {
108
- if (!currentIds.has(id)) {
109
- this._knownPeers.delete(id);
110
- this.emit("peer:leave", { peerId: id, joinedAt: 0 });
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
- this.emit("status", "connected");
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
- applyAwarenessUpdate(this.awareness, update, peerId);
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
- if (this._send && this._peers.size > 0) {
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 during shutdown
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?.(data.buffer as ArrayBuffer, peerId);
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
- this._room?.leave();
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
  }