dsh-pocket 1.3.2 → 1.3.3

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 (2) hide show
  1. package/lib/service.mjs +46 -5
  2. package/package.json +1 -1
package/lib/service.mjs CHANGED
@@ -19,13 +19,54 @@ export async function qrDataUrl(text, { width = 220, margin = 1 } = {}) {
19
19
  return QRCode.toDataURL(text, { errorCorrectionLevel: 'M', margin, width, type: 'image/png' });
20
20
  }
21
21
 
22
- function lanIPv4() {
23
- for (const ifaces of Object.values(networkInterfaces())) {
24
- for (const i of ifaces ?? []) {
25
- if (i.family === 'IPv4' && !i.internal) return i.address;
22
+ /** RFC1918 私网地址:手机与电脑连同一局域网时通常可直连。 */
23
+ const PRIVATE_IPV4_RE = /^(?:10\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/;
24
+
25
+ /** 名称像真实物理网卡的接口(WLAN / Wi-Fi / Ethernet / 以太网 / en / eth)。 */
26
+ const PHYSICAL_IFACE_RE = /^(?:wlan|wi-?fi|wireless|ethernet|eth\d|en\d|wlp\d|以太网|本地连接)/i;
27
+
28
+ /** 常见的 VPN / 虚拟网卡名称:手机通常无法通过它们直连电脑。 */
29
+ const VPN_IFACE_RE = /(?:radmin|tailscale|zerotier|tun|tap|vpn|vethernet|virtual|vmware|virtualbox|wsl|docker|teredo|hamachi|bluetooth|bridge)/i;
30
+
31
+ /**
32
+ * 从 networkInterfaces() 返回的接口表里选出手机最可能可达的 IPv4。
33
+ *
34
+ * `os.networkInterfaces()` 的枚举顺序不可靠:Windows 上 Radmin VPN / Tailscale /
35
+ * vEthernet 等虚拟网卡常排在 WLAN 前面,旧实现直接取第一张非回环网卡,会生成
36
+ * 手机打不开的二维码。这里按以下规则打分排序:
37
+ * - RFC1918 私网地址优先(10/8、172.16/12、192.168/16);
38
+ * - 名称像物理网卡再加分;
39
+ * - 名称像 VPN/虚拟网卡减分;
40
+ * - 同分保持原枚举顺序。
41
+ * 没有任何私网地址时回退到最高分地址(例如纯 VPN 环境仍可用)。
42
+ *
43
+ * @param {ReturnType<typeof networkInterfaces>} interfaces
44
+ * @returns {string|null}
45
+ */
46
+ export function selectLanIPv4(interfaces) {
47
+ const candidates = [];
48
+ for (const [name, addrs] of Object.entries(interfaces ?? {})) {
49
+ for (const addr of addrs ?? []) {
50
+ if (addr.family !== 'IPv4' || addr.internal) continue;
51
+ const ip = addr.address;
52
+ // 排除 loopback 与 link-local;其余地址即使不是私网(如 Radmin 的 26.x)也保留兜底
53
+ if (!ip || ip.startsWith('127.') || ip.startsWith('169.254.')) continue;
54
+
55
+ let score = 0;
56
+ if (PRIVATE_IPV4_RE.test(ip)) score += 100;
57
+ if (PHYSICAL_IFACE_RE.test(name)) score += 20;
58
+ else if (VPN_IFACE_RE.test(name)) score -= 50;
59
+
60
+ candidates.push({ ip, score, order: candidates.length });
26
61
  }
27
62
  }
28
- return null;
63
+
64
+ candidates.sort((a, b) => b.score - a.score || a.order - b.order);
65
+ return candidates[0]?.ip ?? null;
66
+ }
67
+
68
+ function lanIPv4() {
69
+ return selectLanIPv4(networkInterfaces());
29
70
  }
30
71
 
31
72
  /**
package/package.json CHANGED
@@ -76,5 +76,5 @@
76
76
  "access": "public",
77
77
  "registry": "https://registry.npmjs.org/"
78
78
  },
79
- "version": "1.3.2"
79
+ "version": "1.3.3"
80
80
  }