@dolusoft/claude-collab 1.9.6 → 1.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp-main.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { WebSocket, WebSocketServer } from 'ws';
3
3
  import { v4 } from 'uuid';
4
- import { createSocket } from 'dgram';
5
- import { tmpdir, networkInterfaces } from 'os';
4
+ import dgram from 'dgram';
5
+ import os, { tmpdir } from 'os';
6
6
  import { EventEmitter } from 'events';
7
7
  import { execFile, spawn } from 'child_process';
8
8
  import { unlinkSync } from 'fs';
@@ -18,79 +18,121 @@ function serialize(msg) {
18
18
  function parse(data) {
19
19
  return JSON.parse(data);
20
20
  }
21
- var PEER_DISCOVERY_PORT = 9998;
22
- var BROADCAST_INTERVAL_MS = 3e3;
23
- function getBroadcastAddresses() {
24
- const addrs = /* @__PURE__ */ new Set(["255.255.255.255"]);
25
- for (const ifaces of Object.values(networkInterfaces())) {
26
- for (const iface of ifaces ?? []) {
27
- if (iface.family !== "IPv4" || iface.internal) continue;
28
- const ip = iface.address.split(".").map(Number);
29
- const mask = iface.netmask.split(".").map(Number);
30
- const broadcast = ip.map((b, i) => b | ~mask[i] & 255).join(".");
31
- addrs.add(broadcast);
32
- }
33
- }
34
- return [...addrs];
35
- }
36
- var PeerBroadcaster = class {
21
+ var MULTICAST_ADDR = "239.255.42.42";
22
+ var MULTICAST_PORT = 11776;
23
+ var HEARTBEAT_INTERVAL_MS = 5e3;
24
+ var PEER_TIMEOUT_MS = 2e4;
25
+ var MulticastDiscovery = class extends EventEmitter {
37
26
  socket = null;
38
- timer = null;
39
- start(name, port) {
40
- if (this.socket) return;
41
- const socket = createSocket({ type: "udp4", reuseAddr: true });
27
+ heartbeatTimer = null;
28
+ timeoutTimer = null;
29
+ peers = /* @__PURE__ */ new Map();
30
+ myName = "";
31
+ myWsPort = 0;
32
+ start(name, wsPort) {
33
+ this.myName = name;
34
+ this.myWsPort = wsPort;
35
+ const socket = dgram.createSocket({ type: "udp4", reuseAddr: true });
42
36
  this.socket = socket;
43
37
  socket.on("error", (err) => {
44
- console.error("[peer-broadcaster] error:", err.message);
38
+ console.error("[multicast] socket error:", err.message);
45
39
  });
46
- socket.bind(0, () => {
47
- socket.setBroadcast(true);
48
- const send = () => {
49
- if (!this.socket) return;
50
- const msg = Buffer.from(JSON.stringify({ type: "claude-collab-peer", name, port }));
51
- for (const addr of getBroadcastAddresses()) {
52
- socket.send(msg, 0, msg.length, PEER_DISCOVERY_PORT, addr, (err) => {
53
- if (err) console.error(`[peer-broadcaster] send to ${addr} error:`, err.message);
54
- });
55
- }
56
- };
57
- send();
58
- this.timer = setInterval(send, BROADCAST_INTERVAL_MS);
40
+ socket.on("message", (buf, rinfo) => {
41
+ try {
42
+ const msg = JSON.parse(buf.toString());
43
+ this.handleMessage(msg, rinfo.address);
44
+ } catch {
45
+ }
46
+ });
47
+ socket.bind(MULTICAST_PORT, () => {
48
+ try {
49
+ socket.addMembership(MULTICAST_ADDR);
50
+ socket.setMulticastTTL(1);
51
+ socket.setMulticastLoopback(false);
52
+ } catch (err) {
53
+ console.error("[multicast] membership error:", err);
54
+ }
55
+ this.announce();
56
+ this.heartbeatTimer = setInterval(() => this.announce(), HEARTBEAT_INTERVAL_MS);
57
+ this.timeoutTimer = setInterval(() => this.checkTimeouts(), 5e3);
59
58
  });
60
59
  }
61
60
  stop() {
62
- if (this.timer) {
63
- clearInterval(this.timer);
64
- this.timer = null;
61
+ if (this.heartbeatTimer) {
62
+ clearInterval(this.heartbeatTimer);
63
+ this.heartbeatTimer = null;
64
+ }
65
+ if (this.timeoutTimer) {
66
+ clearInterval(this.timeoutTimer);
67
+ this.timeoutTimer = null;
65
68
  }
66
69
  if (this.socket) {
67
- this.socket.close();
70
+ this.sendMessage({ type: "LEAVE", name: this.myName });
71
+ try {
72
+ this.socket.dropMembership(MULTICAST_ADDR);
73
+ this.socket.close();
74
+ } catch {
75
+ }
68
76
  this.socket = null;
69
77
  }
78
+ this.peers.clear();
70
79
  }
71
- };
72
- function watchForPeer(onFound) {
73
- const socket = createSocket({ type: "udp4", reuseAddr: true });
74
- socket.on("error", (err) => {
75
- console.error("[peer-listener] bind failed on port", PEER_DISCOVERY_PORT, "\u2014", err.message);
76
- });
77
- socket.on("message", (msg, rinfo) => {
78
- try {
79
- const data = JSON.parse(msg.toString());
80
- if (data.type === "claude-collab-peer" && typeof data.name === "string" && typeof data.port === "number") {
81
- onFound({ name: data.name, host: rinfo.address, port: data.port });
80
+ resolveLocalIp() {
81
+ const interfaces = os.networkInterfaces();
82
+ for (const iface of Object.values(interfaces)) {
83
+ if (!iface) continue;
84
+ for (const addr of iface) {
85
+ if (addr.family === "IPv4" && !addr.internal) return addr.address;
82
86
  }
83
- } catch {
84
87
  }
85
- });
86
- socket.bind(PEER_DISCOVERY_PORT, "0.0.0.0");
87
- return () => {
88
- try {
89
- socket.close();
90
- } catch {
88
+ return "127.0.0.1";
89
+ }
90
+ // ---------------------------------------------------------------------------
91
+ // Private
92
+ // ---------------------------------------------------------------------------
93
+ announce() {
94
+ this.sendMessage({ type: "ANNOUNCE", name: this.myName, wsPort: this.myWsPort });
95
+ }
96
+ sendMessage(msg) {
97
+ if (!this.socket) return;
98
+ const buf = Buffer.from(JSON.stringify(msg));
99
+ this.socket.send(buf, MULTICAST_PORT, MULTICAST_ADDR, (err) => {
100
+ if (err) console.error("[multicast] send error:", err.message);
101
+ });
102
+ }
103
+ handleMessage(msg, fromIp) {
104
+ if (msg.type === "ANNOUNCE") {
105
+ if (msg.name === this.myName) return;
106
+ const existing = this.peers.get(msg.name);
107
+ if (!existing) {
108
+ const peer = { name: msg.name, ip: fromIp, wsPort: msg.wsPort, lastSeen: Date.now() };
109
+ this.peers.set(msg.name, peer);
110
+ this.emit("peer-found", { name: peer.name, ip: peer.ip, wsPort: peer.wsPort });
111
+ console.error(`[multicast] discovered peer: ${msg.name} @ ${fromIp}:${msg.wsPort}`);
112
+ } else {
113
+ existing.lastSeen = Date.now();
114
+ existing.ip = fromIp;
115
+ existing.wsPort = msg.wsPort;
116
+ }
117
+ } else if (msg.type === "LEAVE") {
118
+ if (this.peers.has(msg.name)) {
119
+ this.peers.delete(msg.name);
120
+ this.emit("peer-lost", msg.name);
121
+ console.error(`[multicast] peer left: ${msg.name}`);
122
+ }
91
123
  }
92
- };
93
- }
124
+ }
125
+ checkTimeouts() {
126
+ const now = Date.now();
127
+ for (const [name, peer] of this.peers) {
128
+ if (now - peer.lastSeen > PEER_TIMEOUT_MS) {
129
+ this.peers.delete(name);
130
+ this.emit("peer-lost", name);
131
+ console.error(`[multicast] peer timed out: ${name}`);
132
+ }
133
+ }
134
+ }
135
+ };
94
136
  var CS_CONINJECT = `
95
137
  using System;
96
138
  using System.Collections.Generic;
@@ -332,8 +374,7 @@ var P2PNode = class {
332
374
  answerWaiters = /* @__PURE__ */ new Map();
333
375
  // Answers queued for offline peers: peerName → AnswerMsg (delivered on reconnect)
334
376
  pendingOutboundAnswers = /* @__PURE__ */ new Map();
335
- broadcaster = null;
336
- stopPeerWatcher = null;
377
+ discovery = null;
337
378
  boundPort = 0;
338
379
  // ---------------------------------------------------------------------------
339
380
  // ICollabClient implementation
@@ -476,8 +517,8 @@ var P2PNode = class {
476
517
  return entries.sort((a, b) => a.askedAt.localeCompare(b.askedAt));
477
518
  }
478
519
  async disconnect() {
479
- this.stopPeerWatcher?.();
480
- this.broadcaster?.stop();
520
+ this.discovery?.stop();
521
+ this.discovery = null;
481
522
  for (const ws of this.peerConnections.values()) ws.close();
482
523
  this.peerConnections.clear();
483
524
  this.wsToName.clear();
@@ -548,14 +589,14 @@ var P2PNode = class {
548
589
  // Private: discovery + outbound connections
549
590
  // ---------------------------------------------------------------------------
550
591
  startDiscovery() {
551
- this.broadcaster = new PeerBroadcaster();
552
- this.broadcaster.start(this.myName, this.boundPort);
553
- this.stopPeerWatcher = watchForPeer((peer) => {
554
- if (peer.name === this.myName) return;
592
+ const discovery = new MulticastDiscovery();
593
+ this.discovery = discovery;
594
+ discovery.on("peer-found", (peer) => {
555
595
  if (this.peerConnections.has(peer.name)) return;
556
596
  if (this.connectingPeers.has(peer.name)) return;
557
- this.connectToPeer(peer.name, peer.host, peer.port);
597
+ this.connectToPeer(peer.name, peer.ip, peer.wsPort);
558
598
  });
599
+ discovery.start(this.myName, this.boundPort);
559
600
  }
560
601
  connectToPeer(peerName, host, port) {
561
602
  this.connectingPeers.add(peerName);
@@ -972,7 +1013,7 @@ async function addFirewallRule(port) {
972
1013
  "name=claude-collab-discovery",
973
1014
  "protocol=UDP",
974
1015
  "dir=in",
975
- "localport=9998",
1016
+ "localport=11776",
976
1017
  "action=allow"
977
1018
  ]);
978
1019
  } catch {