dsh-pocket 1.0.25 → 1.0.26

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
@@ -129,7 +129,7 @@ Such tools take over all traffic and often cut cloudflared's tunnel-edge connect
129
129
  | `lib/service.mjs` | Service: proxy lifecycle, public tunnel, status snapshot (with QR data URLs) |
130
130
  | `lib/proxy.mjs` | Header-rewriting reverse proxy: Host/Origin → loopback, HTTP + WebSocket passthrough + polyfill injection |
131
131
  | `lib/tunnel.mjs` | cloudflared quick tunnel: download/extract/start/parse public URL (HTTP/2) |
132
- | `lib/web-rpc.js` | Loopback RPC: `pocket.status` / `tunnel.start` / `tunnel.stop` |
132
+ | `lib/web-rpc.js` | Loopback RPC: `status` / `tunnel.start` / `tunnel.stop` / `version` / `update` / `restart` |
133
133
  | `client/` | "Phone access" settings tab + mobile adaptation (dsh-web-mobile port) |
134
134
  | `bin/dsh-pocket.mjs` | CLI: LAN/public modes, prints URL + QR |
135
135
 
package/README.md CHANGED
@@ -130,7 +130,7 @@ npx @deepseek-ai/dsh web
130
130
  | `lib/service.mjs` | 服务:代理生命周期、公网隧道、状态快照(含二维码 data URL) |
131
131
  | `lib/proxy.mjs` | 改头反向代理:Host/Origin → loopback,HTTP + WebSocket 全透传 + polyfill 注入 |
132
132
  | `lib/tunnel.mjs` | cloudflared 快速隧道:下载/解压/启动/解析公网 URL(HTTP/2) |
133
- | `lib/web-rpc.js` | loopback RPC:`pocket.status` / `tunnel.start` / `tunnel.stop` |
133
+ | `lib/web-rpc.js` | loopback RPC:`status` / `tunnel.start` / `tunnel.stop` / `version` / `update` / `restart` |
134
134
  | `client/` | 设置页「手机访问」+ 移动端适配(dsh-web-mobile 移植) |
135
135
  | `bin/dsh-pocket.mjs` | CLI:局域网/公网模式,打印 URL + 二维码 |
136
136
 
package/client/api.js CHANGED
@@ -19,13 +19,29 @@ export function compareVersions(a, b) {
19
19
  const y = parseInt(pb[i], 10) || 0;
20
20
  if (x !== y) return x - y;
21
21
  }
22
- // 数字段相等:无预发布后缀的更新;都有后缀时按后缀字典序(alpha < beta < rc…)
22
+ // 数字段相等:无预发布后缀的更新;都有后缀时按段比较(alpha < beta < rc…,
23
+ // 数字段按数值:rc.9 < rc.10)
23
24
  const aPre = String(a).replace(/^[vV]/, '').match(/-.*$/)?.[0] ?? '';
24
25
  const bPre = String(b).replace(/^[vV]/, '').match(/-.*$/)?.[0] ?? '';
25
26
  if (!aPre && !bPre) return 0;
26
27
  if (!aPre) return 1;
27
28
  if (!bPre) return -1;
28
- return aPre < bPre ? -1 : aPre > bPre ? 1 : 0;
29
+ // 逐段比较:数字段按数值、文本段按字典序
30
+ const aParts = aPre.slice(1).split('.');
31
+ const bParts = bPre.slice(1).split('.');
32
+ const len = Math.max(aParts.length, bParts.length);
33
+ for (let i = 0; i < len; i++) {
34
+ const ax = aParts[i] ?? '';
35
+ const bx = bParts[i] ?? '';
36
+ if (ax === bx) continue;
37
+ const aNum = /^\d+$/.test(ax);
38
+ const bNum = /^\d+$/.test(bx);
39
+ if (aNum && bNum) return Number(ax) - Number(bx); // 数值比较
40
+ if (aNum) return 1; // 数字段 > 文本段
41
+ if (bNum) return -1;
42
+ return ax < bx ? -1 : 1; // 字典序
43
+ }
44
+ return 0;
29
45
  }
30
46
 
31
47
  /** 浏览器可见的状态字段(无敏感信息;含二维码 data URL)。 */
package/client/build.mjs CHANGED
@@ -29,6 +29,13 @@ const wrapped = `window.__ModuleLoader__.load({
29
29
  factory: (require) => {
30
30
  var module = { exports: {} };
31
31
  var exports = module.exports;
32
+ // The DSH client module system provides react as a module, never as a
33
+ // global. esbuild keeps react external (see the build config above) and
34
+ // its classic JSX transform emits bare React.createElement calls for the
35
+ // mobile components (which import only named hooks, not React itself), so
36
+ // the bundle must bind React itself - otherwise every mobile component
37
+ // crashes at render time with "ReferenceError: React is not defined".
38
+ var React = require("react");
32
39
  ${bundled}
33
40
  return module.exports;
34
41
  }
package/client/client.js CHANGED
@@ -3,6 +3,13 @@ window.__ModuleLoader__.load({
3
3
  factory: (require) => {
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
+ // The DSH client module system provides react as a module, never as a
7
+ // global. esbuild keeps react external (see the build config above) and
8
+ // its classic JSX transform emits bare React.createElement calls for the
9
+ // mobile components (which import only named hooks, not React itself), so
10
+ // the bundle must bind React itself - otherwise every mobile component
11
+ // crashes at render time with "ReferenceError: React is not defined".
12
+ var React = require("react");
6
13
  var __defProp = Object.defineProperty;
7
14
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
15
  var __getOwnPropNames = Object.getOwnPropertyNames;
package/lib/proxy.mjs CHANGED
@@ -84,8 +84,11 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
84
84
  }
85
85
  res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
86
86
  proxyRes.pipe(res);
87
- // 客户端断开时销毁上游响应(SSE 流手机断连后不留僵尸连接)
87
+ // 任一端断开都要清理另一端:客户端断连销毁上游流(不留僵尸),
88
+ // 上游流中途断开也要掐断客户端(否则响应头已发、体没发完 → 悬挂)
88
89
  res.on('close', () => proxyRes.destroy());
90
+ proxyRes.on('error', () => res.destroy());
91
+ proxyRes.on('close', () => { if (!res.writableEnded) res.destroy(); });
89
92
  },
90
93
  );
91
94
  proxyReq.on('error', (err) => {
@@ -125,10 +128,10 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
125
128
  for (const [k, v] of Object.entries(proxyRes.headers)) {
126
129
  raw.push(`${k}: ${Array.isArray(v) ? v.join(', ') : v}`);
127
130
  }
131
+ // end 会 flush 响应头再 FIN——不要紧跟 destroy(),否则排队的头会被丢弃
128
132
  socket.end(raw.join('\r\n') + '\r\n\r\n');
129
133
  proxyRes.resume(); // 消费掉上游响应体,释放连接
130
- } catch { /* socket 已关 */ }
131
- socket.destroy();
134
+ } catch { socket.destroy(); }
132
135
  });
133
136
  proxyReq.on('error', () => socket.destroy());
134
137
  // 关键:浏览器在握手请求后可能立即发出首帧(如 mux 流的初始 RPC),
@@ -140,6 +143,15 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
140
143
  socket.on('error', () => socket.destroy());
141
144
  });
142
145
 
146
+ // 跟踪所有 TCP 连接(含 WebSocket upgrade 后的 socket——Node 的
147
+ // closeAllConnections 不包含它们,不手动销毁 close() 会永远等)
148
+ const clientSockets = new Set();
149
+ server.on('connection', (sock) => {
150
+ clientSockets.add(sock);
151
+ sock.on('close', () => clientSockets.delete(sock));
152
+ sock.on('error', () => {}); // 防未处理 error 崩进程
153
+ });
154
+
143
155
  return new Promise((resolve, reject) => {
144
156
  server.once('error', reject);
145
157
  server.listen(port, host, () => {
@@ -147,7 +159,10 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
147
159
  resolve({
148
160
  server,
149
161
  port: actualPort,
150
- close: () => new Promise((r) => server.close(() => r())),
162
+ close: () => new Promise((r) => {
163
+ for (const s of clientSockets) { try { s.destroy(); } catch { /* 忽略 */ } }
164
+ server.close(() => r());
165
+ }),
151
166
  });
152
167
  });
153
168
  });
package/lib/restart.js CHANGED
@@ -15,12 +15,16 @@ import { spawn } from 'node:child_process';
15
15
  import { tmpdir } from 'node:os';
16
16
  import { join } from 'node:path';
17
17
 
18
- /** 从启动参数里解析 dsh web 端口(--port/-p),默认 3080。 */
18
+ /** 从启动参数里解析 dsh web 端口(--port/-p,含 --port=3080 形式),默认 3080。 */
19
19
  export function dshPortFromArgs(args) {
20
20
  for (let i = 0; i < args.length; i++) {
21
- if ((args[i] === '--port' || args[i] === '-p') && args[i + 1]) {
21
+ const a = args[i];
22
+ if (a === '--port' || a === '-p') {
22
23
  const n = Number(args[i + 1]);
23
24
  if (Number.isInteger(n) && n > 0 && n < 65536) return n;
25
+ } else if (a.startsWith('--port=')) {
26
+ const n = Number(a.slice('--port='.length));
27
+ if (Number.isInteger(n) && n > 0 && n < 65536) return n;
24
28
  }
25
29
  }
26
30
  return 3080;
@@ -57,7 +61,7 @@ function helperCode(launch, logOut, logErr, port) {
57
61
  ' else setTimeout(() => waitPort(p, tries - 1, cb), 200)',
58
62
  ' })',
59
63
  '}',
60
- `waitPort(${port}, 100, (free) => {`, // 最多 100×200ms = 20s
64
+ `waitPort(${port}, 100, (free) => {`, // 最多 100×200ms = 20s;超时也照常拉起(宁可试拉、日志留痕,也不让 dsh 起不来)
61
65
  ' setTimeout(() => {',
62
66
  ' try {',
63
67
  ' const out = fs.openSync(logOut, "a")',
package/package.json CHANGED
@@ -76,5 +76,5 @@
76
76
  "access": "public",
77
77
  "registry": "https://registry.npmjs.org/"
78
78
  },
79
- "version": "1.0.25"
79
+ "version": "1.0.26"
80
80
  }