agent-comms 1.28.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +1 -1
  4. package/dist/core/comms-store.d.ts +4 -37
  5. package/dist/core/comms-store.d.ts.map +1 -1
  6. package/dist/core/comms-store.js +3 -3
  7. package/dist/core/identity-store.d.ts.map +1 -1
  8. package/dist/core/identity-store.js +2 -1
  9. package/dist/core/identity-store.js.map +1 -1
  10. package/dist/core/identity.d.ts +6 -0
  11. package/dist/core/identity.d.ts.map +1 -1
  12. package/dist/core/identity.js +17 -0
  13. package/dist/core/identity.js.map +1 -1
  14. package/dist/core/mesh-store.d.ts +12 -3
  15. package/dist/core/mesh-store.d.ts.map +1 -1
  16. package/dist/core/mesh-store.js +52 -33
  17. package/dist/core/mesh-store.js.map +1 -1
  18. package/dist/core/store.d.ts +1 -27
  19. package/dist/core/store.d.ts.map +1 -1
  20. package/dist/core/store.js +0 -62
  21. package/dist/core/store.js.map +1 -1
  22. package/dist/core/tls-transport.d.ts +12 -9
  23. package/dist/core/tls-transport.d.ts.map +1 -1
  24. package/dist/core/tls-transport.js +62 -25
  25. package/dist/core/tls-transport.js.map +1 -1
  26. package/dist/core/tool.d.ts +34 -5
  27. package/dist/core/tool.d.ts.map +1 -1
  28. package/dist/core/tool.js +41 -0
  29. package/dist/core/tool.js.map +1 -1
  30. package/dist/core/wire-protocol.d.ts +3 -2
  31. package/dist/core/wire-protocol.d.ts.map +1 -1
  32. package/dist/core/wire-protocol.js +3 -2
  33. package/dist/core/wire-protocol.js.map +1 -1
  34. package/dist/test/approval.integration.test.js +320 -285
  35. package/dist/test/approval.integration.test.js.map +1 -1
  36. package/dist/test/become-coordinator-actual-port.integration.test.js +0 -19
  37. package/dist/test/become-coordinator-actual-port.integration.test.js.map +1 -1
  38. package/dist/test/coordinator-socket-error.integration.test.js +2 -0
  39. package/dist/test/coordinator-socket-error.integration.test.js.map +1 -1
  40. package/dist/test/delivery-receipt.helper.js +15 -0
  41. package/dist/test/delivery-receipt.helper.js.map +1 -1
  42. package/dist/test/downtime-replay.test.js +5 -2
  43. package/dist/test/downtime-replay.test.js.map +1 -1
  44. package/dist/test/federation.integration.test.js +2 -0
  45. package/dist/test/federation.integration.test.js.map +1 -1
  46. package/dist/test/identity-device-id.test.d.ts +5 -0
  47. package/dist/test/identity-device-id.test.d.ts.map +1 -0
  48. package/dist/test/identity-device-id.test.js +35 -0
  49. package/dist/test/identity-device-id.test.js.map +1 -0
  50. package/dist/test/listener-policy.integration.test.js +214 -158
  51. package/dist/test/listener-policy.integration.test.js.map +1 -1
  52. package/dist/test/mesh-e2e.integration.test.js +2 -0
  53. package/dist/test/mesh-e2e.integration.test.js.map +1 -1
  54. package/dist/test/mesh-smoke.integration.test.d.ts +2 -4
  55. package/dist/test/mesh-smoke.integration.test.d.ts.map +1 -1
  56. package/dist/test/mesh-smoke.integration.test.js +28 -5
  57. package/dist/test/mesh-smoke.integration.test.js.map +1 -1
  58. package/dist/test/state-sync-convergence.test.js +5 -2
  59. package/dist/test/state-sync-convergence.test.js.map +1 -1
  60. package/dist/test/test-transport.d.ts +8 -0
  61. package/dist/test/test-transport.d.ts.map +1 -0
  62. package/dist/test/test-transport.js +29 -0
  63. package/dist/test/test-transport.js.map +1 -0
  64. package/dist/test/visibility.integration.test.js +6 -0
  65. package/dist/test/visibility.integration.test.js.map +1 -1
  66. package/package.json +2 -2
  67. package/dist/core/tcp-transport.d.ts +0 -85
  68. package/dist/core/tcp-transport.d.ts.map +0 -1
  69. package/dist/core/tcp-transport.js +0 -659
  70. package/dist/core/tcp-transport.js.map +0 -1
  71. package/dist/core/ws-transport.d.ts +0 -79
  72. package/dist/core/ws-transport.d.ts.map +0 -1
  73. package/dist/core/ws-transport.js +0 -622
  74. package/dist/core/ws-transport.js.map +0 -1
  75. package/dist/test/ws-broadcast-window.integration.test.d.ts +0 -7
  76. package/dist/test/ws-broadcast-window.integration.test.d.ts.map +0 -1
  77. package/dist/test/ws-broadcast-window.integration.test.js +0 -94
  78. package/dist/test/ws-broadcast-window.integration.test.js.map +0 -1
@@ -1,659 +0,0 @@
1
- /**
2
- * TcpTransport — TCP localhost transport for the peer mesh.
3
- *
4
- * Owns all net.Server and net.Socket instances. Handles connection
5
- * establishment, framing, and message routing. Fires events via
6
- * TransportEvents for MeshStore to handle state management.
7
- *
8
- * Lifecycle mirrors the MeshTransport interface:
9
- * 1. startDataServer() — listen for incoming peer data connections
10
- * 2. connectToCoordinator() — join an existing mesh
11
- * OR becomeCoordinator() — start a new mesh as coordinator
12
- * 3. connectToPeer() — establish data connection to a discovered peer
13
- * 4. send() / broadcast() — send wire messages
14
- * 5. shutdown() — close all connections and servers
15
- */
16
- import * as net from "node:net";
17
- import { encode, isMeshMessage, MessageBuffer } from "./wire-protocol.js";
18
- import { attachSocketHandshake } from "./handshake.js";
19
- import { nanoid } from "./nanoid.js";
20
- // ---------------------------------------------------------------------------
21
- // Constants
22
- // ---------------------------------------------------------------------------
23
- /** Host all mesh servers bind to. */
24
- const COORDINATOR_HOST = "127.0.0.1";
25
- /** Timeout for connecting to the coordinator before giving up. */
26
- const CONNECT_TIMEOUT_MS = 1000;
27
- // ---------------------------------------------------------------------------
28
- // Async socket write helper (not exported)
29
- // ---------------------------------------------------------------------------
30
- /**
31
- * Safety valve so a dial that never completes cannot grow its outbound queue
32
- * unbounded; oldest entries are dropped first (#23).
33
- */
34
- const MAX_PENDING_PER_PEER = 100;
35
- function writeAsync(socket, data) {
36
- return new Promise((resolve, reject) => {
37
- if (socket.destroyed) {
38
- resolve();
39
- return;
40
- }
41
- socket.write(data, "utf-8", (err) => {
42
- if (err)
43
- reject(err);
44
- else
45
- resolve();
46
- });
47
- });
48
- }
49
- // ---------------------------------------------------------------------------
50
- // TcpTransport
51
- // ---------------------------------------------------------------------------
52
- export class TcpTransport {
53
- // -- Data server (accepts incoming peer data connections) --
54
- dataServer;
55
- _dataPort = 0;
56
- // -- Coordinator listeners (multiple adapters) --
57
- coordinatorListeners = new Map();
58
- /** The default localhost listener ID (set once during becomeCoordinator). */
59
- defaultListenerId;
60
- _isCoordinator = false;
61
- // -- Coordinator client socket (connection to the coordinator) --
62
- coordinatorSocket;
63
- // -- Coordinator introduction handshake (resolved on the peer list) --
64
- resolveCoordinatorHandshake;
65
- coordinatorHandshakeTimer;
66
- // -- Peer data connections (peer ID → socket + buffer) --
67
- peerConnections = new Map();
68
- // -- Messages queued for peers whose dial is still in flight (#23) --
69
- pendingOutbound = new Map();
70
- // -- All sockets accepted by the data server (for shutdown cleanup) --
71
- dataServerSockets = new Set();
72
- // -- All sockets accepted by coordinator servers (for shutdown cleanup) --
73
- coordinatorServerSockets = new Set();
74
- // -- Coordinator introduction connections (handle ID → socket) --
75
- // Maps the introducing peer's ID to the coordinator server socket, so
76
- // MeshStore can send the peer_list response via transport.send().
77
- introConnections = new Map();
78
- // -- Pending connections awaiting approval (handle ID → socket + request info) --
79
- pendingConnections = new Map();
80
- // -- Shutdown sentinel — prevents callbacks after shutdown() --
81
- shutDown = false;
82
- // -- This peer's ID (set during connectToCoordinator or becomeCoordinator) --
83
- _peerId = "";
84
- events;
85
- constructor(events) {
86
- this.events = events;
87
- }
88
- // -- Public getters for interface properties --
89
- get dataPort() {
90
- return this._dataPort;
91
- }
92
- get isCoordinator() {
93
- return this._isCoordinator;
94
- }
95
- get hasCoordinatorConnection() {
96
- return (this.coordinatorSocket !== undefined && !this.coordinatorSocket.destroyed);
97
- }
98
- // -----------------------------------------------------------------------
99
- // MeshTransport — Data server
100
- // -----------------------------------------------------------------------
101
- async startDataServer() {
102
- await new Promise((resolve, reject) => {
103
- this.dataServer = net.createServer((socket) => {
104
- this.handleIncomingDataConnection(socket);
105
- });
106
- this.dataServer.listen(0, COORDINATOR_HOST, () => {
107
- const addr = this.dataServer?.address();
108
- if (typeof addr === "object" && addr !== null) {
109
- this._dataPort = addr.port;
110
- }
111
- resolve();
112
- });
113
- this.dataServer.on("error", reject);
114
- });
115
- }
116
- // -----------------------------------------------------------------------
117
- // MeshTransport — Coordinator connection (client side)
118
- // -----------------------------------------------------------------------
119
- async connectToCoordinator(host, port, peerId, localDataPort) {
120
- this._peerId = peerId;
121
- await new Promise((resolve, reject) => {
122
- const socket = net.createConnection({ port, host }, () => {
123
- this.coordinatorSocket = socket;
124
- // Wire up the protocol handshake first (client role: sends its frame immediately as the connection's very first bytes) so the introduction below lands after it on the wire, not before.
125
- const buffer = new MessageBuffer();
126
- attachSocketHandshake(socket, "client", (data) => {
127
- const items = buffer.append(data.toString());
128
- for (const item of items) {
129
- if (isMeshMessage(item)) {
130
- this.dispatchCoordinatorClientMessage(item);
131
- }
132
- }
133
- }, (error) => this.events.onError?.(error));
134
- // Send introduction
135
- const intro = {
136
- method: "introduce",
137
- peerId,
138
- dataPort: localDataPort,
139
- };
140
- socket.write(encode(intro));
141
- socket.on("error", () => {
142
- /* ignore late errors on coordinator connection */
143
- });
144
- clearTimeout(timer);
145
- // Resolve on the coordinator's peer list rather than on sending the
146
- // introduction: MeshStore.init() then returns only after the post-join
147
- // peer dials have started, so the first broadcasts are queued for the
148
- // dialling peers instead of dropped (#23). A timeout keeps today's
149
- // degraded behaviour for a coordinator that never answers.
150
- this.resolveCoordinatorHandshake = resolve;
151
- this.coordinatorHandshakeTimer = setTimeout(() => {
152
- this.resolveCoordinatorHandshake = undefined;
153
- resolve();
154
- }, CONNECT_TIMEOUT_MS);
155
- });
156
- const timer = setTimeout(() => {
157
- socket.destroy();
158
- reject(new Error("Coordinator connection timeout"));
159
- }, CONNECT_TIMEOUT_MS);
160
- socket.on("error", (err) => {
161
- clearTimeout(timer);
162
- socket.destroy();
163
- reject(err);
164
- });
165
- });
166
- }
167
- // -----------------------------------------------------------------------
168
- // MeshTransport — Connect to remote coordinator with approval
169
- // -----------------------------------------------------------------------
170
- async connectToRemote(host, port, peerId, localDataPort, name, fingerprint) {
171
- this._peerId = peerId;
172
- await new Promise((resolve, reject) => {
173
- const socket = net.createConnection({ port, host }, () => {
174
- this.coordinatorSocket = socket;
175
- // Wire up the protocol handshake first (client role) so connect_request below lands after it on the wire, pending: wait for connect_accepted/rejected, then normal dispatch
176
- const buffer = new MessageBuffer();
177
- let approved = false;
178
- attachSocketHandshake(socket, "client", (data) => {
179
- const items = buffer.append(data.toString());
180
- for (const item of items) {
181
- if (!isMeshMessage(item))
182
- continue;
183
- if (!approved) {
184
- if (item.method === "connect_accepted") {
185
- approved = true;
186
- resolve();
187
- }
188
- else if (item.method === "connect_rejected") {
189
- socket.destroy();
190
- reject(new Error(`Connection rejected: ${item.reason}`));
191
- return;
192
- }
193
- }
194
- else {
195
- this.dispatchCoordinatorClientMessage(item);
196
- }
197
- }
198
- }, (error) => this.events.onError?.(error));
199
- // Send connect_request instead of introduce
200
- const req = {
201
- method: "connect_request",
202
- peerId,
203
- dataPort: localDataPort,
204
- name,
205
- fingerprint,
206
- };
207
- socket.write(encode(req));
208
- socket.on("error", () => {
209
- /* ignore late errors on coordinator connection */
210
- });
211
- });
212
- const timer = setTimeout(() => {
213
- socket.destroy();
214
- reject(new Error("Remote connection timeout"));
215
- }, CONNECT_TIMEOUT_MS);
216
- socket.on("error", (err) => {
217
- clearTimeout(timer);
218
- socket.destroy();
219
- reject(err);
220
- });
221
- });
222
- }
223
- // -----------------------------------------------------------------------
224
- // MeshTransport — Become coordinator (server side)
225
- // -----------------------------------------------------------------------
226
- async becomeCoordinator(host, port) {
227
- const id = nanoid(8);
228
- await new Promise((resolve, reject) => {
229
- const server = net.createServer((socket) => {
230
- this.handleCoordinatorServerConnection(socket, "full");
231
- });
232
- server.listen(port, host, () => {
233
- const addr = server.address();
234
- const actualPort = typeof addr === "object" && addr !== null ? addr.port : port;
235
- this._isCoordinator = true;
236
- this.coordinatorListeners.set(id, {
237
- server,
238
- policy: "full",
239
- host,
240
- port: actualPort,
241
- isDefault: true,
242
- });
243
- this.defaultListenerId = id;
244
- resolve();
245
- });
246
- server.on("error", (err) => {
247
- reject(err instanceof Error ? err : new Error(String(err)));
248
- });
249
- });
250
- }
251
- // -----------------------------------------------------------------------
252
- // MeshTransport — Multi-listener management
253
- // -----------------------------------------------------------------------
254
- async addListener(host, port, policy) {
255
- if (!this._isCoordinator) {
256
- throw new Error("Only the coordinator can add listeners");
257
- }
258
- const id = nanoid(8);
259
- await new Promise((resolve, reject) => {
260
- const server = net.createServer((socket) => {
261
- this.handleCoordinatorServerConnection(socket, policy);
262
- });
263
- server.listen(port, host, () => {
264
- const addr = server.address();
265
- const actualPort = typeof addr === "object" && addr !== null ? addr.port : port;
266
- this.coordinatorListeners.set(id, {
267
- server,
268
- policy,
269
- host,
270
- port: actualPort,
271
- isDefault: false,
272
- });
273
- resolve();
274
- });
275
- server.on("error", (err) => {
276
- reject(err instanceof Error ? err : new Error(String(err)));
277
- });
278
- });
279
- return id;
280
- }
281
- removeListener(id) {
282
- const listener = this.coordinatorListeners.get(id);
283
- if (!listener) {
284
- throw new Error(`Listener ${id} not found`);
285
- }
286
- if (listener.isDefault) {
287
- throw new Error("Cannot remove the default localhost listener");
288
- }
289
- listener.server.unref();
290
- listener.server.close();
291
- this.coordinatorListeners.delete(id);
292
- return Promise.resolve();
293
- }
294
- listListeners() {
295
- const result = [];
296
- for (const [id, listener] of this.coordinatorListeners) {
297
- result.push({
298
- id,
299
- host: listener.host,
300
- port: listener.port,
301
- policy: listener.policy,
302
- isDefault: listener.isDefault,
303
- });
304
- }
305
- return result;
306
- }
307
- // -----------------------------------------------------------------------
308
- // MeshTransport — Connect to a peer's data server
309
- // -----------------------------------------------------------------------
310
- async connectToPeer(peer, ownPeerId) {
311
- if (this.peerConnections.has(peer.id))
312
- return;
313
- // Queue broadcasts until the connection registers: messages sent in the
314
- // dial window previously had nowhere to go and were silently dropped.
315
- this.pendingOutbound.set(peer.id, this.pendingOutbound.get(peer.id) ?? []);
316
- await new Promise((resolve) => {
317
- const socket = net.createConnection({ port: peer.port, host: COORDINATOR_HOST }, () => {
318
- const buffer = new MessageBuffer();
319
- this.peerConnections.set(peer.id, { socket, buffer });
320
- // Wire up the protocol handshake first (client role) so everything below lands after it on the wire, not before.
321
- attachSocketHandshake(socket, "client", (data) => {
322
- const items = buffer.append(data.toString());
323
- for (const item of items) {
324
- if (isMeshMessage(item)) {
325
- const handle = { id: peer.id };
326
- this.dispatchDataMessage(handle, item);
327
- }
328
- }
329
- }, (error) => this.events.onError?.(error));
330
- // Identify ourselves
331
- const pong = { method: "pong", peerId: ownPeerId };
332
- socket.write(encode(pong));
333
- void this.flushPending(peer.id, socket);
334
- resolve();
335
- });
336
- const handle = { id: peer.id };
337
- let disconnected = false;
338
- const onDisconnect = () => {
339
- if (disconnected)
340
- return;
341
- disconnected = true;
342
- const wasConnected = this.peerConnections.has(peer.id);
343
- this.peerConnections.delete(peer.id);
344
- if (wasConnected && !this.shutDown) {
345
- this.events.onPeerDisconnected(handle);
346
- }
347
- };
348
- socket.on("close", onDisconnect);
349
- socket.on("error", () => {
350
- this.pendingOutbound.delete(peer.id);
351
- onDisconnect();
352
- socket.destroy();
353
- resolve();
354
- });
355
- });
356
- }
357
- // -----------------------------------------------------------------------
358
- // MeshTransport — Send / broadcast
359
- // -----------------------------------------------------------------------
360
- async send(handle, message) {
361
- // Check data connections first
362
- const peerConn = this.peerConnections.get(handle.id);
363
- if (peerConn) {
364
- await writeAsync(peerConn.socket, encode(message));
365
- return;
366
- }
367
- // Check coordinator introduction connections
368
- const introSocket = this.introConnections.get(handle.id);
369
- if (introSocket) {
370
- await writeAsync(introSocket, encode(message));
371
- return;
372
- }
373
- throw new Error(`No connection for handle ${handle.id}`);
374
- }
375
- async acceptConnection(handle) {
376
- const pending = this.pendingConnections.get(handle.id);
377
- if (!pending) {
378
- throw new Error(`No pending connection for handle ${handle.id}`);
379
- }
380
- this.pendingConnections.delete(handle.id);
381
- const { socket, peerId, dataPort, policy } = pending;
382
- // Move to introConnections so send() can reach this peer
383
- this.introConnections.set(peerId, socket);
384
- // Send acceptance to the connecting peer
385
- const accepted = {
386
- method: "connect_accepted",
387
- peerId: this._peerId,
388
- dataPort: this._dataPort,
389
- };
390
- await writeAsync(socket, encode(accepted));
391
- // Fire onIntroduction so MeshStore processes the new peer normally
392
- const connHandle = { id: peerId, policy };
393
- this.events.onIntroduction(connHandle, { peerId, dataPort });
394
- }
395
- async rejectConnection(handle, reason) {
396
- const pending = this.pendingConnections.get(handle.id);
397
- if (!pending) {
398
- throw new Error(`No pending connection for handle ${handle.id}`);
399
- }
400
- this.pendingConnections.delete(handle.id);
401
- const { socket } = pending;
402
- const rejected = {
403
- method: "connect_rejected",
404
- peerId: handle.id,
405
- reason,
406
- };
407
- await writeAsync(socket, encode(rejected));
408
- socket.destroy();
409
- }
410
- async broadcast(message) {
411
- const data = encode(message);
412
- const writes = [];
413
- for (const [, peer] of this.peerConnections) {
414
- writes.push(writeAsync(peer.socket, data).catch(() => {
415
- /* broken connection — cleanup handled by close/error listeners */
416
- }));
417
- }
418
- for (const queue of this.pendingOutbound.values()) {
419
- queue.push(message);
420
- if (queue.length > MAX_PENDING_PER_PEER)
421
- queue.shift();
422
- }
423
- await Promise.all(writes);
424
- }
425
- /** Send messages queued while the peer's connection was being dialled. */
426
- async flushPending(peerId, socket) {
427
- const queue = this.pendingOutbound.get(peerId);
428
- this.pendingOutbound.delete(peerId);
429
- if (queue === undefined)
430
- return;
431
- for (const message of queue) {
432
- const sent = await writeAsync(socket, encode(message)).then(() => true, () => false);
433
- if (!sent)
434
- return; // connection is dying; close/error listeners clean up
435
- }
436
- }
437
- // -----------------------------------------------------------------------
438
- // MeshTransport — Shutdown / unref
439
- // -----------------------------------------------------------------------
440
- shutdown() {
441
- this.shutDown = true;
442
- if (this.coordinatorHandshakeTimer !== undefined) {
443
- clearTimeout(this.coordinatorHandshakeTimer);
444
- this.coordinatorHandshakeTimer = undefined;
445
- }
446
- this.resolveCoordinatorHandshake = undefined;
447
- // Destroy the coordinator client socket
448
- this.coordinatorSocket?.unref();
449
- this.coordinatorSocket?.destroy();
450
- this.coordinatorSocket = undefined;
451
- // Destroy all identified peer connections
452
- for (const [, peer] of this.peerConnections) {
453
- peer.socket.unref();
454
- peer.socket.destroy();
455
- }
456
- this.peerConnections.clear();
457
- // Destroy all data server accepted sockets (including unidentified)
458
- for (const socket of this.dataServerSockets) {
459
- socket.unref();
460
- socket.destroy();
461
- }
462
- this.dataServerSockets.clear();
463
- // Destroy all coordinator server accepted sockets
464
- for (const socket of this.coordinatorServerSockets) {
465
- socket.unref();
466
- socket.destroy();
467
- }
468
- this.coordinatorServerSockets.clear();
469
- // Clear introduction connection tracking
470
- this.introConnections.clear();
471
- // Clear pending connections
472
- for (const [, pending] of this.pendingConnections) {
473
- pending.socket.unref();
474
- pending.socket.destroy();
475
- }
476
- this.pendingConnections.clear();
477
- // Close data server
478
- this.dataServer?.unref();
479
- this.dataServer?.close();
480
- this.dataServer = undefined;
481
- // Close coordinator listener servers — stop accepting new connections
482
- for (const [, listener] of this.coordinatorListeners) {
483
- listener.server.unref();
484
- listener.server.close();
485
- }
486
- this.coordinatorListeners.clear();
487
- this.defaultListenerId = undefined;
488
- this._isCoordinator = false;
489
- return Promise.resolve();
490
- }
491
- unref() {
492
- this.dataServer?.unref();
493
- for (const [, listener] of this.coordinatorListeners) {
494
- listener.server.unref();
495
- }
496
- this.coordinatorSocket?.unref();
497
- }
498
- // -----------------------------------------------------------------------
499
- // Internal — Coordinator client message dispatch
500
- // -----------------------------------------------------------------------
501
- /**
502
- * Handles messages received on the coordinator client socket (the
503
- * connection from this peer TO the coordinator). Fires the appropriate
504
- * TransportEvents for peer_list, peer_joined, and become_coordinator.
505
- */
506
- dispatchCoordinatorClientMessage(msg) {
507
- if (this.shutDown)
508
- return;
509
- if (msg.method === "peer_list") {
510
- // Fire onPeerList first: it starts the post-join dials (and their
511
- // broadcast queues) before init() resolves.
512
- this.events.onPeerList(msg.peers);
513
- this.completeCoordinatorHandshake();
514
- }
515
- else if (msg.method === "peer_joined") {
516
- this.events.onPeerJoined(msg.peer);
517
- }
518
- else if (msg.method === "become_coordinator") {
519
- this.events.onBecomeCoordinator(msg.peerList);
520
- }
521
- }
522
- /** Complete connectToCoordinator's handshake after the peer list arrives. */
523
- completeCoordinatorHandshake() {
524
- if (this.coordinatorHandshakeTimer !== undefined) {
525
- clearTimeout(this.coordinatorHandshakeTimer);
526
- this.coordinatorHandshakeTimer = undefined;
527
- }
528
- this.resolveCoordinatorHandshake?.();
529
- this.resolveCoordinatorHandshake = undefined;
530
- }
531
- // -----------------------------------------------------------------------
532
- // Internal — Data message dispatch
533
- // -----------------------------------------------------------------------
534
- /**
535
- * Routes a message received on a peer data connection to the
536
- * appropriate event. become_coordinator has its own event; all
537
- * other messages fire onMessage for MeshStore to handle.
538
- */
539
- dispatchDataMessage(handle, msg) {
540
- if (this.shutDown)
541
- return;
542
- if (msg.method === "become_coordinator") {
543
- this.events.onBecomeCoordinator(msg.peerList);
544
- }
545
- else {
546
- this.events.onMessage(handle, msg);
547
- }
548
- }
549
- // -----------------------------------------------------------------------
550
- // Internal — Coordinator server connection handling
551
- // -----------------------------------------------------------------------
552
- /**
553
- * Accepts a new connection on a coordinator server. Reads
554
- * introduce messages and fires onIntroduction so MeshStore can
555
- * respond with the peer list and broadcast the arrival.
556
- * The connection is tagged with the listener's policy.
557
- */
558
- handleCoordinatorServerConnection(socket, policy) {
559
- if (this.shutDown) {
560
- socket.destroy();
561
- return;
562
- }
563
- this.coordinatorServerSockets.add(socket);
564
- socket.on("close", () => this.coordinatorServerSockets.delete(socket));
565
- socket.on("error", () => this.coordinatorServerSockets.delete(socket));
566
- const buffer = new MessageBuffer();
567
- attachSocketHandshake(socket, "server", (data) => {
568
- const items = buffer.append(data.toString());
569
- for (const item of items) {
570
- if (!isMeshMessage(item))
571
- continue;
572
- if (item.method === "introduce") {
573
- const handle = { id: item.peerId, policy };
574
- this.introConnections.set(handle.id, socket);
575
- this.events.onIntroduction(handle, {
576
- peerId: item.peerId,
577
- dataPort: item.dataPort,
578
- });
579
- }
580
- else if (item.method === "connect_request") {
581
- const handle = { id: item.peerId, policy };
582
- this.pendingConnections.set(handle.id, {
583
- socket,
584
- peerId: item.peerId,
585
- dataPort: item.dataPort,
586
- name: item.name,
587
- fingerprint: item.fingerprint,
588
- policy,
589
- });
590
- this.events.onConnectionRequest(handle, {
591
- peerId: item.peerId,
592
- dataPort: item.dataPort,
593
- name: item.name,
594
- fingerprint: item.fingerprint,
595
- });
596
- }
597
- }
598
- }, (error) => this.events.onError?.(error));
599
- }
600
- // -----------------------------------------------------------------------
601
- // Internal — Incoming data connection handling
602
- // -----------------------------------------------------------------------
603
- /**
604
- * Accepts a new connection on the data server. Reads the initial
605
- * pong to identify the peer, then fires onMessage for subsequent
606
- * messages. Tracks the connection for cleanup on close/error.
607
- */
608
- handleIncomingDataConnection(socket) {
609
- if (this.shutDown) {
610
- socket.destroy();
611
- return;
612
- }
613
- this.dataServerSockets.add(socket);
614
- socket.on("close", () => this.dataServerSockets.delete(socket));
615
- const buffer = new MessageBuffer();
616
- let remotePeerId;
617
- let disconnected = false;
618
- attachSocketHandshake(socket, "server", (data) => {
619
- const items = buffer.append(data.toString());
620
- for (const item of items) {
621
- if (isMeshMessage(item)) {
622
- if (item.method === "pong") {
623
- const peerId = item.peerId;
624
- remotePeerId = peerId;
625
- if (!this.peerConnections.has(peerId)) {
626
- this.peerConnections.set(peerId, { socket, buffer });
627
- }
628
- void this.flushPending(peerId, socket);
629
- const handle = { id: peerId };
630
- const info = {
631
- id: peerId,
632
- port: 0,
633
- startedAt: new Date().toISOString(),
634
- };
635
- this.events.onPeerConnected(handle, info);
636
- }
637
- else if (remotePeerId !== undefined) {
638
- const handle = { id: remotePeerId };
639
- this.dispatchDataMessage(handle, item);
640
- }
641
- }
642
- }
643
- }, (error) => this.events.onError?.(error));
644
- const onDisconnect = () => {
645
- if (disconnected)
646
- return;
647
- disconnected = true;
648
- if (remotePeerId !== undefined) {
649
- this.peerConnections.delete(remotePeerId);
650
- if (!this.shutDown) {
651
- this.events.onPeerDisconnected({ id: remotePeerId });
652
- }
653
- }
654
- };
655
- socket.on("close", onDisconnect);
656
- socket.on("error", onDisconnect);
657
- }
658
- }
659
- //# sourceMappingURL=tcp-transport.js.map