dsh-pocket 1.12.2 → 1.12.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.
package/README.en.md CHANGED
@@ -41,7 +41,7 @@ What it looks like — the phone shows the exact same UI as your computer, live:
41
41
 
42
42
  | Feature | Description |
43
43
  |---|---|
44
- | 📶 LAN QR access | Works out of the box: Settings → Phone access — scan the LAN QR on the same Wi-Fi |
44
+ | 📶 LAN QR access | Works out of the box: Settings → Phone access — scan the LAN QR on the same Wi-Fi (auto-detects the LAN IP; **under WSL it picks the Windows host's physical NIC IP**) |
45
45
  | 🌐 Public QR (from anywhere) | Click "Enable anywhere" → cloudflared tunnel → scan the public QR over 4G / any network |
46
46
  | 🔐 Access PIN | Public links require an **8-digit PIN** (rotated on every tunnel start by default; **customizable to a fixed PIN** — custom PINs are not rotated); LAN has its own separate **8-digit PIN** (on by default; switchable off in Settings — then LAN scans connect directly) |
47
47
  | 🔑 Custom PINs | Both the public and LAN PINs can be **set to your own fixed 8-digit number** in Settings (custom PINs are never auto-rotated) |
package/README.md CHANGED
@@ -41,7 +41,7 @@ DSH Pocket 就是干这个的:**装上它,手机扫个码,就能实时看
41
41
 
42
42
  | 特性 | 说明 |
43
43
  |---|---|
44
- | 📶 局域网扫码 | 装好即用:设置 → 手机访问,打开就有局域网二维码,手机连同一 WiFi 扫码即开 |
44
+ | 📶 局域网扫码 | 装好即用:设置 → 手机访问,打开就有局域网二维码,手机连同一 WiFi 扫码即开(自动识别本机局域网 IP,**WSL 环境自动取 Windows 物理网卡 IP**) |
45
45
  | 🌐 公网扫码(人在外面) | 点「开启公网访问」→ cloudflared 隧道 → 出公网二维码,4G/任何网络都能访问 |
46
46
  | 🔐 访问密码 | 公网链接需输入 **8 位数字密码**(默认每次开启公网自动换新;**可自定义固定密码**——自定义后不再换新);局域网有独立 **8 位数字密码**(默认开启,设置页可**一键关闭**——关闭后局域网扫码直连) |
47
47
  | 🔑 自定义密码 | 公网/局域网密码都可在设置页**设成自己固定的 8 位数字**(自定义后公网不再自动换新) |
package/lib/service.mjs CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  import { networkInterfaces } from 'node:os';
10
10
  import { createRequire } from 'node:module';
11
+ import { readFileSync } from 'node:fs';
12
+ import { execFile } from 'node:child_process';
11
13
  import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
12
14
  import { join, dirname } from 'node:path';
13
15
  import { createPocketProxy } from './proxy.mjs';
@@ -67,7 +69,62 @@ export function selectLanIPv4(interfaces) {
67
69
  return candidates[0]?.ip ?? null;
68
70
  }
69
71
 
70
- function lanIPv4() {
72
+ // ---------- WSL 局域网 IP(issue #39) ----------
73
+ // WSL2 是 NAT 模式:WSL 内部 os.networkInterfaces() 只能看到自己的虚拟网卡
74
+ // (172.x.x.x),看不到 Windows 宿主机的物理网卡 IP(192.168.x.x)——手机在
75
+ // 同一 WiFi 下访问的是 Windows 宿主机,拿 WSL 的 IP 生成的二维码必然打不开。
76
+ // 解法:检测到 WSL 时,通过 WSL interop 直接执行 Windows 的 ipconfig.exe,
77
+ // 解析出 Windows 侧非虚拟网卡的 IPv4 作为局域网地址;失败回退本机探测。
78
+
79
+ /** WSL 检测:/proc/version 含 microsoft/wsl,或 WSL 专属环境变量存在。 */
80
+ export function detectWsl() {
81
+ try {
82
+ const v = readFileSync('/proc/version', 'utf8').toLowerCase();
83
+ if (v.includes('microsoft') || v.includes('wsl')) return true;
84
+ } catch { /* 非 Linux:无 /proc/version */ }
85
+ return Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP || process.env.WSLENV);
86
+ }
87
+
88
+ /**
89
+ * 解析 ipconfig.exe 输出,取非虚拟网卡块的 IPv4 地址(保持输出顺序)。
90
+ * 支持中文(`IPv4 地址 . . . :`)与英文(`IPv4 Address. . . :`)两种格式。
91
+ * @param {string} text ipconfig.exe 的完整输出
92
+ * @returns {string[]} 候选 IPv4 列表(虚拟网卡块已排除)
93
+ */
94
+ export function parseIpconfig(text) {
95
+ const out = [];
96
+ // 网卡块:块标题行顶格(行首无缩进),其后内容行带缩进
97
+ const blocks = String(text).split(/\r?\n(?=\S)/);
98
+ const ipRe = /IPv4[^0-9]{0,40}((?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)/;
99
+ for (const block of blocks) {
100
+ const title = String(block.split(/\r?\n/)[0] ?? '');
101
+ // 跳过虚拟网卡块(vEthernet (WSL)、Docker、VirtualBox、VPN 等)
102
+ if (VPN_IFACE_RE.test(title)) continue;
103
+ const m = block.match(ipRe);
104
+ if (m) out.push(m[0].replace(/^IPv4[^0-9]*/i, ''));
105
+ }
106
+ return out;
107
+ }
108
+
109
+ function runIpconfig() {
110
+ return new Promise((resolve) => {
111
+ execFile('ipconfig.exe', [], { timeout: 5000, windowsHide: true }, (err, stdout) => {
112
+ if (err || !stdout) return resolve(null);
113
+ resolve(String(stdout));
114
+ });
115
+ });
116
+ }
117
+
118
+ async function lanIPv4() {
119
+ // WSL:优先 Windows 物理网卡 IP(手机可达);超时/失败回退本机探测
120
+ if (detectWsl()) {
121
+ try {
122
+ const out = await runIpconfig();
123
+ const candidates = parseIpconfig(out ?? '');
124
+ const ip = candidates.find((c) => PRIVATE_IPV4_RE.test(c)) ?? candidates[0];
125
+ if (ip) return ip;
126
+ } catch { /* 回退 */ }
127
+ }
71
128
  return selectLanIPv4(networkInterfaces());
72
129
  }
73
130
 
@@ -244,7 +301,7 @@ export function createPocketService({
244
301
 
245
302
  /** 状态快照(RPC 返回,不含敏感信息;二维码 data URL 本地生成 + 缓存)。 */
246
303
  async status() {
247
- const lan = getLan();
304
+ const lan = await getLan();
248
305
  const proxyPort = proxy?.port ?? null;
249
306
  const lanUrl = lan && proxyPort ? `http://${lan}:${proxyPort}` : null;
250
307
  return {
package/package.json CHANGED
@@ -76,5 +76,5 @@
76
76
  "access": "public",
77
77
  "registry": "https://registry.npmjs.org/"
78
78
  },
79
- "version": "1.12.2"
79
+ "version": "1.12.3"
80
80
  }