@agent-p2p/peer 0.0.4 → 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.
@@ -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;IAIhB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAYjC"}
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;
@@ -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,8 +23,15 @@ 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
36
  destroy(): Promise<void>;
30
37
  }
@@ -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;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;IAkFtB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,cAAc;IA6BtB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,OAAO;IAQT,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAuB9B"}
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"}
@@ -22,7 +22,7 @@ export class TrysteroProvider {
22
22
  this.roomName = roomName;
23
23
  this.awareness = new Awareness(doc);
24
24
  this._onDocUpdate = (update, origin) => {
25
- if (origin === this)
25
+ if (this._destroyed || origin === this)
26
26
  return;
27
27
  const encoder = encoding.createEncoder();
28
28
  encoding.writeVarUint(encoder, MSG_SYNC);
@@ -61,47 +61,51 @@ export class TrysteroProvider {
61
61
  if (this._destroyed)
62
62
  return;
63
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
+ }
64
70
  this._room = room;
65
71
  const [send, receive] = room.makeAction("yjs-sync");
66
72
  this._send = send;
73
+ // Trystero action receivers can't be unregistered, so _handleMessage
74
+ // guards against calls after destruction.
67
75
  receive((data, peerId) => {
68
76
  this._handleMessage(new Uint8Array(data), peerId);
69
77
  });
70
78
  room.onPeerJoin((peerId) => {
79
+ if (this._destroyed)
80
+ return;
71
81
  this._peers.add(peerId);
72
82
  this._sendSyncStep1(peerId);
73
83
  this._sendAwareness(peerId);
74
84
  });
75
85
  room.onPeerLeave((peerId) => {
86
+ if (this._destroyed)
87
+ return;
76
88
  this._peers.delete(peerId);
77
- // Remove awareness states that came from this trystero peer
78
89
  const clientIds = this._peerClients.get(peerId);
79
90
  if (clientIds && clientIds.size > 0) {
80
91
  removeAwarenessStates(this.awareness, Array.from(clientIds), "peer left");
81
- this._peerClients.delete(peerId);
82
92
  }
93
+ this._peerClients.delete(peerId);
83
94
  });
84
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.
85
99
  this.awareness.on("update", ({ added, updated, removed }, origin) => {
100
+ if (this._destroyed || origin === this)
101
+ return;
86
102
  const changedClients = [...added, ...updated, ...removed];
103
+ if (changedClients.length === 0)
104
+ return;
87
105
  const encoder = encoding.createEncoder();
88
106
  encoding.writeVarUint(encoder, MSG_AWARENESS);
89
107
  encoding.writeVarUint8Array(encoder, encodeAwarenessUpdate(this.awareness, changedClients));
90
108
  this._broadcast(encoding.toUint8Array(encoder));
91
- // Track which Yjs clientIDs belong to which trystero peer
92
- if (typeof origin === "string" && origin !== "local") {
93
- let ids = this._peerClients.get(origin);
94
- if (!ids) {
95
- ids = new Set();
96
- this._peerClients.set(origin, ids);
97
- }
98
- for (const id of added)
99
- ids.add(id);
100
- for (const id of updated)
101
- ids.add(id);
102
- for (const id of removed)
103
- ids.delete(id);
104
- }
105
109
  });
106
110
  }
107
111
  _sendSyncStep1(peerId) {
@@ -119,7 +123,33 @@ export class TrysteroProvider {
119
123
  encoding.writeVarUint8Array(encoder, encodeAwarenessUpdate(this.awareness, clients));
120
124
  this._sendTo(encoding.toUint8Array(encoder), peerId);
121
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
+ }
122
150
  _handleMessage(data, peerId) {
151
+ if (this._destroyed)
152
+ return;
123
153
  const decoder = decoding.createDecoder(data);
124
154
  const msgType = decoding.readVarUint(decoder);
125
155
  switch (msgType) {
@@ -134,7 +164,10 @@ export class TrysteroProvider {
134
164
  }
135
165
  case MSG_AWARENESS: {
136
166
  const update = decoding.readVarUint8Array(decoder);
137
- 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);
138
171
  break;
139
172
  }
140
173
  case MSG_LEAVE: {
@@ -146,19 +179,23 @@ export class TrysteroProvider {
146
179
  }
147
180
  }
148
181
  }
182
+ /** Broadcast data to all connected peers. Fire-and-forget with error suppression. */
149
183
  _broadcast(data) {
184
+ if (!this._send || this._peers.size === 0)
185
+ return;
150
186
  try {
151
- if (this._send && this._peers.size > 0) {
152
- this._send(data.buffer);
153
- }
187
+ this._send(data.buffer)?.catch(() => { });
154
188
  }
155
189
  catch {
156
- // Data channel may already be closed during shutdown
190
+ // Data channel may already be closed
157
191
  }
158
192
  }
193
+ /** Send data to a specific peer. Fire-and-forget with error suppression. */
159
194
  _sendTo(data, peerId) {
195
+ if (!this._send)
196
+ return;
160
197
  try {
161
- this._send?.(data.buffer, peerId);
198
+ this._send(data.buffer, peerId)?.catch(() => { });
162
199
  }
163
200
  catch {
164
201
  // Peer's data channel may already be closed
@@ -168,21 +205,33 @@ export class TrysteroProvider {
168
205
  if (this._destroyed)
169
206
  return;
170
207
  this._destroyed = true;
171
- // Send explicit leave message over data channel (most reliable)
208
+ // Send explicit leave message over data channel before tearing down.
172
209
  const encoder = encoding.createEncoder();
173
210
  encoding.writeVarUint(encoder, MSG_LEAVE);
174
211
  encoding.writeVarUint(encoder, this.doc.clientID);
175
212
  this._broadcast(encoding.toUint8Array(encoder));
176
- // Also broadcast awareness removal
213
+ // Broadcast awareness removal so peers can clean up immediately.
177
214
  removeAwarenessStates(this.awareness, [this.doc.clientID], "provider destroyed");
178
215
  this.doc.off("update", this._onDocUpdate);
179
- // Brief delay for data channel to flush before tearing down
180
- await new Promise((resolve) => setTimeout(resolve, 100));
181
- 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;
182
219
  this._room = null;
183
220
  this._send = null;
184
221
  this._peers.clear();
185
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
+ }
186
235
  }
187
236
  }
188
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.4",
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> {
@@ -39,7 +39,7 @@ export class TrysteroProvider {
39
39
  this.awareness = new Awareness(doc);
40
40
 
41
41
  this._onDocUpdate = (update: Uint8Array, origin: unknown) => {
42
- if (origin === this) return;
42
+ if (this._destroyed || origin === this) return;
43
43
  const encoder = encoding.createEncoder();
44
44
  encoding.writeVarUint(encoder, MSG_SYNC);
45
45
  syncProtocol.writeUpdate(encoder, update);
@@ -80,53 +80,57 @@ export class TrysteroProvider {
80
80
  if (this._destroyed) return;
81
81
 
82
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
+
83
91
  this._room = room;
84
92
 
85
93
  const [send, receive] = room.makeAction<ArrayBuffer>("yjs-sync");
86
94
  this._send = send;
87
95
 
96
+ // Trystero action receivers can't be unregistered, so _handleMessage
97
+ // guards against calls after destruction.
88
98
  (receive as ActionReceiver<ArrayBuffer>)((data: ArrayBuffer, peerId: string) => {
89
99
  this._handleMessage(new Uint8Array(data), peerId);
90
100
  });
91
101
 
92
102
  room.onPeerJoin((peerId) => {
103
+ if (this._destroyed) return;
93
104
  this._peers.add(peerId);
94
105
  this._sendSyncStep1(peerId);
95
106
  this._sendAwareness(peerId);
96
107
  });
97
108
 
98
109
  room.onPeerLeave((peerId) => {
110
+ if (this._destroyed) return;
99
111
  this._peers.delete(peerId);
100
- // Remove awareness states that came from this trystero peer
101
112
  const clientIds = this._peerClients.get(peerId);
102
113
  if (clientIds && clientIds.size > 0) {
103
114
  removeAwarenessStates(this.awareness, Array.from(clientIds), "peer left");
104
- this._peerClients.delete(peerId);
105
115
  }
116
+ this._peerClients.delete(peerId);
106
117
  });
107
118
 
108
119
  this.doc.on("update", this._onDocUpdate);
109
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.
110
124
  this.awareness.on(
111
125
  "update",
112
126
  ({ added, updated, removed }: { added: number[]; updated: number[]; removed: number[] }, origin: unknown) => {
127
+ if (this._destroyed || origin === this) return;
113
128
  const changedClients = [...added, ...updated, ...removed];
129
+ if (changedClients.length === 0) return;
114
130
  const encoder = encoding.createEncoder();
115
131
  encoding.writeVarUint(encoder, MSG_AWARENESS);
116
132
  encoding.writeVarUint8Array(encoder, encodeAwarenessUpdate(this.awareness, changedClients));
117
133
  this._broadcast(encoding.toUint8Array(encoder));
118
-
119
- // Track which Yjs clientIDs belong to which trystero peer
120
- if (typeof origin === "string" && origin !== "local") {
121
- let ids = this._peerClients.get(origin);
122
- if (!ids) {
123
- ids = new Set();
124
- this._peerClients.set(origin, ids);
125
- }
126
- for (const id of added) ids.add(id);
127
- for (const id of updated) ids.add(id);
128
- for (const id of removed) ids.delete(id);
129
- }
130
134
  },
131
135
  );
132
136
  }
@@ -147,7 +151,33 @@ export class TrysteroProvider {
147
151
  this._sendTo(encoding.toUint8Array(encoder), peerId);
148
152
  }
149
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
+
150
178
  private _handleMessage(data: Uint8Array, peerId: string): void {
179
+ if (this._destroyed) return;
180
+
151
181
  const decoder = decoding.createDecoder(data);
152
182
  const msgType = decoding.readVarUint(decoder);
153
183
 
@@ -163,7 +193,10 @@ export class TrysteroProvider {
163
193
  }
164
194
  case MSG_AWARENESS: {
165
195
  const update = decoding.readVarUint8Array(decoder);
166
- 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);
167
200
  break;
168
201
  }
169
202
  case MSG_LEAVE: {
@@ -176,19 +209,21 @@ export class TrysteroProvider {
176
209
  }
177
210
  }
178
211
 
212
+ /** Broadcast data to all connected peers. Fire-and-forget with error suppression. */
179
213
  private _broadcast(data: Uint8Array): void {
214
+ if (!this._send || this._peers.size === 0) return;
180
215
  try {
181
- if (this._send && this._peers.size > 0) {
182
- this._send(data.buffer as ArrayBuffer);
183
- }
216
+ this._send(data.buffer as ArrayBuffer)?.catch(() => {});
184
217
  } catch {
185
- // Data channel may already be closed during shutdown
218
+ // Data channel may already be closed
186
219
  }
187
220
  }
188
221
 
222
+ /** Send data to a specific peer. Fire-and-forget with error suppression. */
189
223
  private _sendTo(data: Uint8Array, peerId: string): void {
224
+ if (!this._send) return;
190
225
  try {
191
- this._send?.(data.buffer as ArrayBuffer, peerId);
226
+ this._send(data.buffer as ArrayBuffer, peerId)?.catch(() => {});
192
227
  } catch {
193
228
  // Peer's data channel may already be closed
194
229
  }
@@ -198,23 +233,35 @@ export class TrysteroProvider {
198
233
  if (this._destroyed) return;
199
234
  this._destroyed = true;
200
235
 
201
- // Send explicit leave message over data channel (most reliable)
236
+ // Send explicit leave message over data channel before tearing down.
202
237
  const encoder = encoding.createEncoder();
203
238
  encoding.writeVarUint(encoder, MSG_LEAVE);
204
239
  encoding.writeVarUint(encoder, this.doc.clientID);
205
240
  this._broadcast(encoding.toUint8Array(encoder));
206
241
 
207
- // Also broadcast awareness removal
242
+ // Broadcast awareness removal so peers can clean up immediately.
208
243
  removeAwarenessStates(this.awareness, [this.doc.clientID], "provider destroyed");
209
244
  this.doc.off("update", this._onDocUpdate);
210
245
 
211
- // Brief delay for data channel to flush before tearing down
212
- await new Promise((resolve) => setTimeout(resolve, 100));
213
-
214
- this._room?.leave();
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;
215
249
  this._room = null;
216
250
  this._send = null;
217
251
  this._peers.clear();
218
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
+ }
219
266
  }
220
267
  }