@decentnetwork/peer 0.1.38 → 0.1.40

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/peer.js CHANGED
@@ -592,8 +592,8 @@ export class Peer {
592
592
  }
593
593
  this.#debugLog(`friend-request: announce stored on ${this.#lastSelfAnnounceStoredCount} node(s) before send`);
594
594
  const appPayload = encodeFriendRequestPacket({
595
- name: PEER_NICKNAME,
596
- descr: PEER_STATUS_MESSAGE || "decent peer",
595
+ name: this.#opts.nickname ?? PEER_NICKNAME,
596
+ descr: (this.#opts.statusMessage ?? PEER_STATUS_MESSAGE) || "decent peer",
597
597
  hello: hello ?? ""
598
598
  });
599
599
  const friendReqPayload = concatBytes([
@@ -2812,9 +2812,11 @@ export class Peer {
2812
2812
  // callback. Most Carrier UIs read the displayed name from the
2813
2813
  // userinfo flatbuffers payload below, but this keeps both paths
2814
2814
  // consistent.
2815
- const nameBytes = new TextEncoder().encode(PEER_NICKNAME);
2815
+ const nick = this.#opts.nickname ?? PEER_NICKNAME;
2816
+ const descr = this.#opts.statusMessage ?? PEER_STATUS_MESSAGE;
2817
+ const nameBytes = new TextEncoder().encode(nick);
2816
2818
  await this.#sendMessengerPacket(friendId, PACKET_ID_NICKNAME, nameBytes);
2817
- this.#debugLog(`nickname "${PEER_NICKNAME}" sent to ${friendId}`);
2819
+ this.#debugLog(`nickname "${nick}" sent to ${friendId}`);
2818
2820
  // Carrier C SDK transports its full user-profile (name, descr,
2819
2821
  // gender, phone, email, region, has_avatar) as a FlatBuffers
2820
2822
  // PACKET_TYPE_USERINFO (3) payload sent via toxcore's
@@ -2826,11 +2828,11 @@ export class Peer {
2826
2828
  // __flatbuffers_soffset_read_from_pe (observed in Beagle 1.8.6).
2827
2829
  await sleep(250);
2828
2830
  const userInfo = encodeUserInfoPacket({
2829
- name: PEER_NICKNAME,
2830
- descr: PEER_STATUS_MESSAGE
2831
+ name: nick,
2832
+ descr
2831
2833
  });
2832
2834
  await this.#sendMessengerPacket(friendId, PACKET_ID_STATUSMESSAGE, userInfo);
2833
- this.#debugLog(`userinfo sent to ${friendId} (name="${PEER_NICKNAME}", descr="${PEER_STATUS_MESSAGE}")`);
2835
+ this.#debugLog(`userinfo sent to ${friendId} (name="${nick}", descr="${descr}")`);
2834
2836
  }
2835
2837
  catch (error) {
2836
2838
  this.#debugLog(`send profile failed for ${friendId}: ${error.message}`);
@@ -3058,7 +3060,7 @@ export class Peer {
3058
3060
  // "direct" option we advertise is our srflx (public) address, which the
3059
3061
  // NAT must hairpin back onto the LAN — most don't, so two machines in one
3060
3062
  // office silently fall back to the TURN relay (~260ms for a <1ms hop).
3061
- const lanIp = getLocalIpv4Addresses().find((ip) => isPrivateAddress(ip));
3063
+ const lanIp = getPhysicalLanAddresses().find((ip) => isPrivateAddress(ip));
3062
3064
  const lanOctets = lanIp ? lanIp.split(".").map((s) => parseInt(s, 10)) : undefined;
3063
3065
  const lanPort = this.#udp.localPort() ?? 0;
3064
3066
  const lanValid = !!lanOctets && lanOctets.length === 4 && !lanOctets.some((o) => Number.isNaN(o)) && lanPort > 0;
@@ -3147,8 +3149,9 @@ export class Peer {
3147
3149
  const lanPort = ((payload[16] << 8) | payload[17]) >>> 0;
3148
3150
  const sameLan = lanPort !== 0 &&
3149
3151
  isPrivateAddress(lanHost) &&
3150
- getLocalIpv4Subnets().some((s) => isInIpv4Subnet(lanHost, s)) &&
3151
- !(getLocalIpv4Addresses().includes(lanHost) && this.#udp.localPort() === lanPort);
3152
+ !isCgnatAddress(lanHost) &&
3153
+ getPhysicalLanSubnets().some((s) => isInIpv4Subnet(lanHost, s)) &&
3154
+ !(getPhysicalLanAddresses().includes(lanHost) && this.#udp.localPort() === lanPort);
3152
3155
  if (sameLan) {
3153
3156
  this.#rememberEndpointCandidate(session, lanHost, lanPort);
3154
3157
  this.#debugLog(`udp-endpoint LAN candidate from ${friendId}: ${lanHost}:${lanPort} (same subnet) — punching`);
@@ -4433,6 +4436,72 @@ function isInIpv4Subnet(host, subnet) {
4433
4436
  return false;
4434
4437
  return ((ip & subnet.maskBits) >>> 0) === subnet.networkBits;
4435
4438
  }
4439
+ // Interface names that are VIRTUAL / overlay networks, not a physical LAN:
4440
+ // Tailscale & utun tunnels (utun* on macOS, tailscale*/tun* on Linux),
4441
+ // WireGuard (wg*), ZeroTier (zt*), the decentlan TUN itself (agentnet*), and
4442
+ // assorted macOS virtual links (awdl/llw/gif/stf/bridge). A LAN-direct host
4443
+ // candidate MUST be a physical-LAN address: advertising/punching an overlay
4444
+ // address (e.g. Tailscale 100.x, or our own 10.86.x mesh IP) sends the "direct"
4445
+ // path onto the overlay or loops into our own mesh, then silently falls back to
4446
+ // the relay — the snoopy/lili "lastUdpRecv=never -> bufferbloat" path. (lili
4447
+ // runs Tailscale; that's exactly this.)
4448
+ const VIRTUAL_IFACE_RE = /^(utun|tun|tap|wg|tailscale|zt|ham|agentnet|awdl|llw|gif|stf|bridge|vnic|vmnet|veth|docker)/i;
4449
+ /** True for RFC 6598 carrier-grade-NAT space 100.64.0.0/10 (Tailscale's range). */
4450
+ function isCgnatAddress(host) {
4451
+ const o = host.split(".");
4452
+ if (o.length !== 4)
4453
+ return false;
4454
+ const a = Number(o[0]);
4455
+ const b = Number(o[1]);
4456
+ return a === 100 && b >= 64 && b <= 127;
4457
+ }
4458
+ /** Local IPv4 addresses on PHYSICAL interfaces only (excludes overlay/virtual
4459
+ * interfaces by name and the CGNAT range). Used for LAN-direct host candidates. */
4460
+ function getPhysicalLanAddresses() {
4461
+ const out = [];
4462
+ try {
4463
+ for (const [name, list] of Object.entries(networkInterfaces())) {
4464
+ if (!list || VIRTUAL_IFACE_RE.test(name))
4465
+ continue;
4466
+ for (const info of list) {
4467
+ if (info.family !== "IPv4" || info.internal)
4468
+ continue;
4469
+ if (isCgnatAddress(info.address))
4470
+ continue;
4471
+ out.push(info.address);
4472
+ }
4473
+ }
4474
+ }
4475
+ catch {
4476
+ // best-effort
4477
+ }
4478
+ return out;
4479
+ }
4480
+ /** Subnets of PHYSICAL interfaces only — for matching a peer's host candidate
4481
+ * to OUR real LAN (so we never match a peer's Tailscale/TUN address against our
4482
+ * own overlay subnet). */
4483
+ function getPhysicalLanSubnets() {
4484
+ const out = [];
4485
+ try {
4486
+ for (const [name, list] of Object.entries(networkInterfaces())) {
4487
+ if (!list || VIRTUAL_IFACE_RE.test(name))
4488
+ continue;
4489
+ for (const info of list) {
4490
+ if (info.family !== "IPv4" || info.internal || isCgnatAddress(info.address))
4491
+ continue;
4492
+ const addr = ipv4ToInt(info.address);
4493
+ const mask = netmaskToInt(info.netmask);
4494
+ if (addr === undefined || mask === undefined || mask === 0)
4495
+ continue;
4496
+ out.push({ networkBits: (addr & mask) >>> 0, maskBits: mask });
4497
+ }
4498
+ }
4499
+ }
4500
+ catch {
4501
+ // best-effort
4502
+ }
4503
+ return out;
4504
+ }
4436
4505
  function isPrivateAddress(host) {
4437
4506
  // RFC1918 IPv4: 10/8, 172.16/12, 192.168/16; loopback 127/8;
4438
4507
  // link-local 169.254/16; carrier-grade NAT 100.64/10. IPv6 link-local fe80::/10
@@ -40,6 +40,16 @@ export type PeerOptions = {
40
40
  * optimisation only applies to packet 64 and custom-id data is duplicated.
41
41
  */
42
42
  bulkDataPacketId?: number;
43
+ /**
44
+ * Display name this peer advertises to friends (Carrier nickname, packet 48 +
45
+ * the userinfo profile). Without it every node reports the build default
46
+ * "@decentnetwork/peer" and friend lists can't tell anyone apart. decentlan
47
+ * passes its node name (e.g. "mac-dev", "cn", "tokyo"). Falls back to
48
+ * DECENT_PEER_NAME / the build default when unset.
49
+ */
50
+ nickname?: string;
51
+ /** Status/description line shown alongside the nickname. */
52
+ statusMessage?: string;
43
53
  compatibilityMode?: CompatibilityMode;
44
54
  debugLabel?: string;
45
55
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "Pure TypeScript port of Elastos Carrier (toxcore-derived) P2P messaging. DHT, onion routing, TCP relay, FlatBuffers app payloads, Express offline relay. Wire-compatible with iOS Beagle and the Carrier C SDK.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",