dsh-pocket 1.7.2 → 1.7.4
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 +15 -12
- package/README.md +15 -12
- package/client/client.js +1 -1
- package/client/index.jsx +1 -1
- package/lib/tunnel.mjs +21 -3
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -41,12 +41,14 @@ 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 →
|
|
44
|
+
| 📶 LAN QR access | Works out of the box: Settings → Phone access — scan the LAN QR on the same Wi-Fi |
|
|
45
45
|
| 🌐 Public QR (from anywhere) | Click "Enable anywhere" → cloudflared tunnel → scan the public QR over 4G / any network |
|
|
46
|
+
| 🔐 Public access PIN | Public links require an **8-digit PIN** (rotated every time the tunnel starts — old links die instantly); LAN stays password-free |
|
|
46
47
|
| ⚡ Real-time sync | Streaming output passes through WebSocket untouched — what the computer renders, the phone renders live; fully interactive both ways |
|
|
47
48
|
| 📱 Mobile-adaptive layout | Narrow screens get a drawer layout automatically (ported from dsh-web-mobile, MIT): sidebar drawer, full-width conversation, safe-area insets, touch optimizations |
|
|
49
|
+
| 🗜️ Transfer compression | Large JSON responses are gzip/brotli'd on the fly (17MB session history → ~1.3MB) — faster loads, less mobile data |
|
|
50
|
+
| 🔁 Tunnel auto-restore | After a DSH restart the previously-running public tunnel comes back automatically |
|
|
48
51
|
| 🧩 Zero-dependency install | One npm package, one settings tab — no core/adapter split, no account, no server |
|
|
49
|
-
| 🔒 URL is the key | No public URL exposure in LAN mode; public URL rotates on every restart |
|
|
50
52
|
|
|
51
53
|
## 🚀 Usage
|
|
52
54
|
|
|
@@ -73,20 +75,21 @@ npx @deepseek-ai/dsh web
|
|
|
73
75
|
|
|
74
76
|
### LAN (same Wi-Fi)
|
|
75
77
|
|
|
76
|
-
Settings →
|
|
78
|
+
Settings → **Phone access** → scan the "📶 LAN" QR code → the phone opens the exact same DSH, in real time.
|
|
77
79
|
|
|
78
80
|
### Public (from anywhere)
|
|
79
81
|
|
|
80
|
-
On the same page click "**Enable anywhere**" → wait for the tunnel (first run downloads cloudflared) → scan the "🌐 Public" QR code → works from outside (4G / office network).
|
|
82
|
+
On the same page click "**Enable anywhere**" → wait for the tunnel (first run downloads cloudflared; macOS/Linux use the Tsinghua mirror, seconds) → scan the "🌐 Public" QR code → the phone opens the link and **enters the 8-digit PIN** (shown in the settings page's public section, **rotated on every tunnel start**) → works from outside (4G / office network).
|
|
81
83
|
|
|
82
84
|
> Upgrading: `dsh plugin --profile web update dsh-pocket --latest -w` (`--latest` is required across major versions — a `^0.x` range won't auto-jump to 1.x).
|
|
83
85
|
|
|
84
86
|
## ⚠️ Security (read first)
|
|
85
87
|
|
|
86
|
-
- **DSH can execute code on your computer.**
|
|
88
|
+
- **DSH can execute code on your computer.** **LAN** QR/URL is the key (no password) — **never share the LAN QR or URL**.
|
|
89
|
+
- **Public** access is protected by an **8-digit PIN**: the link is random, the PIN rotates on every tunnel start, and old links die instantly — even a leaked link can't get in.
|
|
87
90
|
- The public URL is randomly assigned by cloudflared and **changes on every restart** (old links die automatically — a natural key rotation).
|
|
88
91
|
- LAN mode exposes nothing publicly; only devices on the same network can reach it.
|
|
89
|
-
- Built for personal use;
|
|
92
|
+
- Built for personal use; the PIN lives in `$DSH_HOME/dsh-pocket/token` and is re-rolled by restarting the public tunnel.
|
|
90
93
|
|
|
91
94
|
## 🩹 Troubleshooting (traps users step on)
|
|
92
95
|
|
|
@@ -135,16 +138,16 @@ Such tools take over all traffic and often cut cloudflared's tunnel-edge connect
|
|
|
135
138
|
- Windows: `winget install cloudflared` or from the official site
|
|
136
139
|
- Any platform: `npm i -g cloudflared`
|
|
137
140
|
2. Enable a proxy (system proxy / Clash etc.) and click "Enable anywhere" again
|
|
138
|
-
3. Manually download the binary into `$DSH_HOME/dsh-pocket/bin/` (`$DSH_HOME` is usually `~/.dsh`, on Windows `%USERPROFILE%\.dsh`)
|
|
141
|
+
3. Manually download the binary into `$DSH_HOME/dsh-pocket/bin/` (`$DSH_HOME` is usually `~/.dsh`, on Windows `%USERPROFILE%\.dsh`; name it `cloudflared` (add `.exe` on Windows) **or** the release asset name — both are recognized)
|
|
139
142
|
|
|
140
143
|
## 🗂 Architecture (single package)
|
|
141
144
|
|
|
142
145
|
| File | Purpose |
|
|
143
146
|
|---|---|
|
|
144
|
-
| `lib/index.js` | Plugin entry: auto-start proxy + register RPC (
|
|
145
|
-
| `lib/service.mjs` | Service: proxy lifecycle, public tunnel, status snapshot (with QR data URLs) |
|
|
146
|
-
| `lib/proxy.mjs` | Header-rewriting reverse proxy: Host/Origin → loopback, HTTP + WebSocket passthrough + polyfill injection |
|
|
147
|
-
| `lib/tunnel.mjs` | cloudflared
|
|
147
|
+
| `lib/index.js` | Plugin entry: auto-start proxy + register RPC + public-access PIN management (8 digits, rotated per tunnel start) + DSH Desktop detection |
|
|
148
|
+
| `lib/service.mjs` | Service: proxy lifecycle (port auto-fallback), public tunnel (auto-restore), status snapshot (with QR data URLs) |
|
|
149
|
+
| `lib/proxy.mjs` | Header-rewriting reverse proxy: Host/Origin → loopback, HTTP + WebSocket passthrough + polyfill injection + gzip/brotli compression + public-access PIN auth |
|
|
150
|
+
| `lib/tunnel.mjs` | cloudflared: multi-mirror download (Tsinghua first) / adaptive parallel / start / parse public URL (HTTP/2) |
|
|
148
151
|
| `lib/web-rpc.js` | Loopback RPC: `status` / `tunnel.start` / `tunnel.stop` / `version` / `update` / `restart` |
|
|
149
152
|
| `client/` | "Phone access" settings tab + mobile adaptation (dsh-web-mobile port) |
|
|
150
153
|
| `bin/dsh-pocket.mjs` | CLI: LAN/public modes, prints URL + QR |
|
|
@@ -154,7 +157,7 @@ Such tools take over all traffic and often cut cloudflared's tunnel-edge connect
|
|
|
154
157
|
```sh
|
|
155
158
|
npm install
|
|
156
159
|
node client/build.mjs # rebuild after editing client/
|
|
157
|
-
npm test # proxy
|
|
160
|
+
npm test # proxy / auth / compression / tunnel / service / RPC (40 tests)
|
|
158
161
|
```
|
|
159
162
|
|
|
160
163
|
## 🤝 Credits
|
package/README.md
CHANGED
|
@@ -41,12 +41,14 @@ DSH Pocket 就是干这个的:**装上它,手机扫个码,就能实时看
|
|
|
41
41
|
|
|
42
42
|
| 特性 | 说明 |
|
|
43
43
|
|---|---|
|
|
44
|
-
| 📶 局域网扫码 | 装好即用:设置 →
|
|
44
|
+
| 📶 局域网扫码 | 装好即用:设置 → 手机访问,打开就有局域网二维码,手机连同一 WiFi 扫码即开 |
|
|
45
45
|
| 🌐 公网扫码(人在外面) | 点「开启公网访问」→ cloudflared 隧道 → 出公网二维码,4G/任何网络都能访问 |
|
|
46
|
+
| 🔐 公网访问密码 | 公网链接需输入 **8 位数字密码**(每次开启公网自动换新,旧链接立即作废);局域网免密码 |
|
|
46
47
|
| ⚡ 实时同步 | 流式输出走 WebSocket 全透传——**电脑上在输出,手机上同步在滚**,可双向操作 |
|
|
47
48
|
| 📱 移动端适配 | 窄屏自动变抽屉布局(移植 dsh-web-mobile,MIT):侧栏抽屉、会话全宽、状态栏安全区、触控优化 |
|
|
49
|
+
| 🗜️ 传输压缩 | 大 JSON 响应自动 gzip/brotli(长会话 17MB → ~1.3MB),手机加载更快、更省流量 |
|
|
50
|
+
| 🔁 隧道自动恢复 | DSH 重启后自动重新拉起之前开着的公网隧道,无需手动重开 |
|
|
48
51
|
| 🧩 零依赖安装 | 一个 npm 包、一个设置页,没有核心/适配器要分开装;无需账号、无需服务器 |
|
|
49
|
-
| 🔒 URL 即钥匙 | 无公网 URL 暴露给第三方(局域网模式);公网 URL 每次重启自动换新 |
|
|
50
52
|
|
|
51
53
|
## 🚀 怎么用
|
|
52
54
|
|
|
@@ -73,20 +75,21 @@ npx @deepseek-ai/dsh web
|
|
|
73
75
|
|
|
74
76
|
### 局域网(同一 WiFi)
|
|
75
77
|
|
|
76
|
-
设置 →
|
|
78
|
+
设置 → **手机访问** → 手机扫「📶 局域网」二维码 → 打开的就是电脑上的 DSH,实时同步。
|
|
77
79
|
|
|
78
80
|
### 公网(人在外面)
|
|
79
81
|
|
|
80
|
-
同一页点「**开启公网访问**」→ 等隧道建立(首次会下载 cloudflared
|
|
82
|
+
同一页点「**开启公网访问**」→ 等隧道建立(首次会下载 cloudflared,macOS/Linux 走清华镜像秒下)→ 手机扫「🌐 公网」二维码 → 打开链接**输入 8 位访问密码**(密码显示在设置页公网区块,**每次开启公网变新**)→ 人在外面(4G/公司网)也能访问。
|
|
81
83
|
|
|
82
84
|
> 更新到新版本:`dsh plugin --profile web update dsh-pocket --latest -w`(跨大版本时 `--latest` 是必须的,`^0.x` 范围不会自动升到 1.x)。
|
|
83
85
|
|
|
84
86
|
## ⚠️ 安全(必读)
|
|
85
87
|
|
|
86
|
-
- **DSH
|
|
88
|
+
- **DSH 能执行你电脑上的代码**。**局域网**二维码/URL 就是钥匙(无密码),**请勿把局域网二维码或 URL 发给别人**
|
|
89
|
+
- **公网**有 **8 位数字密码**保护:链接随机分配、每次开启换新密码、旧链接立即作废——泄露了也进不来,改密码/重开即可作废
|
|
87
90
|
- 公网 URL 由 cloudflared 随机分配,**每次重启会变化**(旧链接自动失效,相当于天然轮换)
|
|
88
91
|
- 局域网模式不暴露公网,只有同一网络内的设备能访问
|
|
89
|
-
-
|
|
92
|
+
- 适合个人自用;密码存本机 `$DSH_HOME/dsh-pocket/token`,可随时重开公网换新
|
|
90
93
|
|
|
91
94
|
## 🩹 常见问题(别踩的坑)
|
|
92
95
|
|
|
@@ -136,16 +139,16 @@ npx @deepseek-ai/dsh web
|
|
|
136
139
|
- Windows:`winget install cloudflared` 或官网下载
|
|
137
140
|
- 任何平台:`npm i -g cloudflared`
|
|
138
141
|
2. 挂代理(系统代理/Clash 等)后重新点「开启公网访问」
|
|
139
|
-
3. 手动下载二进制放到 `$DSH_HOME/dsh-pocket/bin/` 目录(`$DSH_HOME` 一般是 `~/.dsh`,Windows 是 `%USERPROFILE%\.dsh
|
|
142
|
+
3. 手动下载二进制放到 `$DSH_HOME/dsh-pocket/bin/` 目录(`$DSH_HOME` 一般是 `~/.dsh`,Windows 是 `%USERPROFILE%\.dsh`;文件名用 `cloudflared`(Windows 加 `.exe`)或发布资产名均可,插件都认)
|
|
140
143
|
|
|
141
144
|
## 🗂 架构(单包)
|
|
142
145
|
|
|
143
146
|
| 文件 | 说明 |
|
|
144
147
|
|---|---|
|
|
145
|
-
| `lib/index.js` | 插件入口:自动起代理 + 注册 RPC
|
|
146
|
-
| `lib/service.mjs` |
|
|
147
|
-
| `lib/proxy.mjs` | 改头反向代理:Host/Origin → loopback,HTTP + WebSocket
|
|
148
|
-
| `lib/tunnel.mjs` | cloudflared
|
|
148
|
+
| `lib/index.js` | 插件入口:自动起代理 + 注册 RPC + 公网访问密码管理(8 位数字,每次开启变新)+ 桌面端环境适配 |
|
|
149
|
+
| `lib/service.mjs` | 服务:代理生命周期(端口自适应)、公网隧道(自动恢复)、状态快照(含二维码) |
|
|
150
|
+
| `lib/proxy.mjs` | 改头反向代理:Host/Origin → loopback,HTTP + WebSocket 透传 + polyfill 注入 + gzip/brotli 压缩 + 公网访问令牌认证 |
|
|
151
|
+
| `lib/tunnel.mjs` | cloudflared:多镜像源下载(清华优先)/自适应多线程/启动/解析公网 URL(HTTP/2) |
|
|
149
152
|
| `lib/web-rpc.js` | loopback RPC:`status` / `tunnel.start` / `tunnel.stop` / `version` / `update` / `restart` |
|
|
150
153
|
| `client/` | 设置页「手机访问」+ 移动端适配(dsh-web-mobile 移植) |
|
|
151
154
|
| `bin/dsh-pocket.mjs` | CLI:局域网/公网模式,打印 URL + 二维码 |
|
|
@@ -155,7 +158,7 @@ npx @deepseek-ai/dsh web
|
|
|
155
158
|
```sh
|
|
156
159
|
npm install
|
|
157
160
|
node client/build.mjs # 改 client/ 后重新打包
|
|
158
|
-
npm test #
|
|
161
|
+
npm test # 代理 / 认证 / 压缩 / 隧道 / 服务 / RPC(40 测试)
|
|
159
162
|
```
|
|
160
163
|
|
|
161
164
|
## 🤝 致谢
|
package/client/client.js
CHANGED
|
@@ -1297,7 +1297,7 @@ var styles = {
|
|
|
1297
1297
|
muted: { color: "var(--dsw-alias-label-tertiary,#8b93a1)", fontSize: 12, lineHeight: 1.5 },
|
|
1298
1298
|
code: { fontFamily: "ui-monospace,Menlo,monospace", fontSize: 12, wordBreak: "break-all", margin: "6px 0 10px", color: "var(--dsw-alias-label-primary,inherit)" },
|
|
1299
1299
|
// 主按钮:官方 md 胶囊形(36px)
|
|
1300
|
-
primary: { font: "inherit", cursor: "pointer", border: "none", background: "var(--dsw-alias-button-primary-fill, var(--dsw-alias-brand-primary,#4f6ef7))", color: "#fff", height: 36, padding: "0 16px", borderRadius: 999, fontSize: 13, fontWeight: 500, display: "inline-flex", alignItems: "center", justifyContent: "center" },
|
|
1300
|
+
primary: { font: "inherit", cursor: "pointer", border: "none", background: "var(--dsw-alias-button-primary-fill, var(--dsw-alias-brand-primary,#4f6ef7))", color: "var(--dsw-alias-label-primary-foreground, #fff)", height: 36, padding: "0 16px", borderRadius: 999, fontSize: 13, fontWeight: 500, display: "inline-flex", alignItems: "center", justifyContent: "center" },
|
|
1301
1301
|
// 次级按钮:官方 outline/ghost 胶囊形
|
|
1302
1302
|
btn: { font: "inherit", cursor: "pointer", border: "1px solid var(--dsw-alias-button-ghost-active-border, var(--dsw-alias-border-l2,#d1d5db))", background: "var(--dsw-alias-bg-layer-1,#fff)", color: "var(--dsw-alias-label-primary,inherit)", height: 36, padding: "0 16px", borderRadius: 999, fontSize: 13, display: "inline-flex", alignItems: "center", justifyContent: "center" },
|
|
1303
1303
|
qr: { width: 220, height: 220, borderRadius: 10, border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", margin: "8px 0" },
|
package/client/index.jsx
CHANGED
|
@@ -24,7 +24,7 @@ const styles = {
|
|
|
24
24
|
muted: { color: 'var(--dsw-alias-label-tertiary,#8b93a1)', fontSize: 12, lineHeight: 1.5 },
|
|
25
25
|
code: { fontFamily: 'ui-monospace,Menlo,monospace', fontSize: 12, wordBreak: 'break-all', margin: '6px 0 10px', color: 'var(--dsw-alias-label-primary,inherit)' },
|
|
26
26
|
// 主按钮:官方 md 胶囊形(36px)
|
|
27
|
-
primary: { font: 'inherit', cursor: 'pointer', border: 'none', background: 'var(--dsw-alias-button-primary-fill, var(--dsw-alias-brand-primary,#4f6ef7))', color: '#fff', height: 36, padding: '0 16px', borderRadius: 999, fontSize: 13, fontWeight: 500, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' },
|
|
27
|
+
primary: { font: 'inherit', cursor: 'pointer', border: 'none', background: 'var(--dsw-alias-button-primary-fill, var(--dsw-alias-brand-primary,#4f6ef7))', color: 'var(--dsw-alias-label-primary-foreground, #fff)', height: 36, padding: '0 16px', borderRadius: 999, fontSize: 13, fontWeight: 500, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' },
|
|
28
28
|
// 次级按钮:官方 outline/ghost 胶囊形
|
|
29
29
|
btn: { font: 'inherit', cursor: 'pointer', border: '1px solid var(--dsw-alias-button-ghost-active-border, var(--dsw-alias-border-l2,#d1d5db))', background: 'var(--dsw-alias-bg-layer-1,#fff)', color: 'var(--dsw-alias-label-primary,inherit)', height: 36, padding: '0 16px', borderRadius: 999, fontSize: 13, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' },
|
|
30
30
|
qr: { width: 220, height: 220, borderRadius: 10, border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', margin: '8px 0' },
|
package/lib/tunnel.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// 无密码模式:URL 即钥匙(dsh web 能执行代码,请勿把二维码/URL 发给别人)。
|
|
5
5
|
|
|
6
6
|
import { spawn, execSync } from 'node:child_process';
|
|
7
|
-
import { mkdir, access, chmod, rm, stat, rename, cp } from 'node:fs/promises';
|
|
7
|
+
import { mkdir, access, chmod, rm, stat, rename, cp, open } from 'node:fs/promises';
|
|
8
8
|
import { homedir } from 'node:os';
|
|
9
9
|
import { join, dirname } from 'node:path';
|
|
10
10
|
import { pipeline } from 'node:stream/promises';
|
|
@@ -146,13 +146,15 @@ export async function downloadFile(url, dest, { signal, segments = PARALLEL_SEGM
|
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
148
|
* 清华 TUNA 镜像的 cloudflared Homebrew bottle URL(国内 CDN,实测 ~3MB/s)。
|
|
149
|
-
*
|
|
149
|
+
* **仅 macOS**——Linux 的 Homebrew bottle 其 ELF 解释器是 `@@HOMEBREW_PREFIX@@`
|
|
150
|
+
* 占位符(需 brew install 时 patchelf 替换),没装 Homebrew 的机器直接 spawn 会
|
|
151
|
+
* ENOENT(issue #22);Linux 走官方 GitHub tgz(解压即用)+ 加速源。
|
|
150
152
|
* 匹配按 CPU 架构取清华目录里版本号最新的 bottle——Homebrew 构建时部署目标
|
|
151
153
|
* 设得较老、向后兼容,所以旧系统(如 Ventura)也能用新一点的 bottle。
|
|
152
154
|
* 抓目录失败/无匹配 → null(调用方回退 GitHub/加速源,不影响可用性)。
|
|
153
155
|
*/
|
|
154
156
|
async function tsinghuaBottleUrl({ os, a }) {
|
|
155
|
-
if (os !== 'darwin'
|
|
157
|
+
if (os !== 'darwin') return null;
|
|
156
158
|
let res;
|
|
157
159
|
try {
|
|
158
160
|
res = await fetch(TUNA_BOTTLES, { signal: AbortSignal.timeout(20_000) });
|
|
@@ -303,6 +305,22 @@ export async function resolveCloudflared({ home, onPhase = () => {}, signal } =
|
|
|
303
305
|
for (const bin of candidates) {
|
|
304
306
|
try {
|
|
305
307
|
await access(bin);
|
|
308
|
+
// Linux:识别并丢弃 Homebrew bottle 坏缓存(issue #22)——其 ELF 解释器是
|
|
309
|
+
// @@HOMEBREW_PREFIX@@ 占位符,直接 spawn 报 ENOENT;读文件头(解释器路径在
|
|
310
|
+
// ELF 头部附近)即可识别,命中则删掉走重新下载
|
|
311
|
+
if (os === 'linux') {
|
|
312
|
+
try {
|
|
313
|
+
const fd = await open(bin, 'r');
|
|
314
|
+
const head = Buffer.alloc(8192);
|
|
315
|
+
await fd.read(head, 0, 8192, 0);
|
|
316
|
+
await fd.close();
|
|
317
|
+
if (head.includes('@@HOMEBREW_PREFIX@@')) {
|
|
318
|
+
await rm(bin, { force: true }).catch(() => {});
|
|
319
|
+
console.warn('dsh-pocket: discarding unusable Homebrew-bottle cloudflared cache | 丢弃不可用的 Homebrew bottle 缓存,重新下载');
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
} catch { /* 读失败按正常缓存处理 */ }
|
|
323
|
+
}
|
|
306
324
|
return bin; // 缓存命中,秒开
|
|
307
325
|
} catch { /* 继续找下一个 */ }
|
|
308
326
|
}
|
package/package.json
CHANGED