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 +1 -1
- package/README.md +1 -1
- package/client/api.js +18 -2
- package/client/build.mjs +7 -0
- package/client/client.js +7 -0
- package/lib/proxy.mjs +19 -4
- package/lib/restart.js +7 -3
- package/package.json +1 -1
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: `
|
|
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:`
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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 {
|
|
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) =>
|
|
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
|
|
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
|
-
|
|
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