dsh-pocket 1.11.2 → 1.12.0
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 +2 -1
- package/README.md +2 -1
- package/client/client.js +59 -4
- package/client/index.jsx +38 -2
- package/client/mobile/mobile.css.ts +10 -2
- package/client/pocket-locales.js +8 -0
- package/lib/web-rpc.js +5 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -85,13 +85,14 @@ Settings → **Phone access** → scan the "📶 LAN" QR code → enter the **LA
|
|
|
85
85
|
|
|
86
86
|
### Public (from anywhere)
|
|
87
87
|
|
|
88
|
-
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 by default**, or **Customize** it to a fixed PIN that is never rotated) → works from outside (4G / office network).
|
|
88
|
+
On the same page click "**Enable anywhere**" → **a security disclaimer pops up every time — check "I understand and agree" to proceed** (on a corporate/classified network, confirm compliance first) → 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 by default**, or **Customize** it to a fixed PIN that is never rotated) → works from outside (4G / office network).
|
|
89
89
|
|
|
90
90
|
> 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).
|
|
91
91
|
|
|
92
92
|
## ⚠️ Security (read first)
|
|
93
93
|
|
|
94
94
|
- **DSH can execute code on your computer.** **LAN** QR/URL plus its own **8-digit PIN** is the key (PIN **on by default**, switchable off — then LAN scans connect directly, same-network devices only) — **never share the LAN QR, URL or PIN**.
|
|
95
|
+
- **Read and accept the security disclaimer before enabling public access** (the dialog shows on every enable; the server enforces it, so it can't be bypassed): public = exposing a code-executing DSH to the internet — use a strong PIN, turn it off when done, never on classified networks.
|
|
95
96
|
- **Public** access is protected by an **8-digit PIN**: the link is random, the PIN rotates on every tunnel start by default, and old links die instantly — even a leaked link can't get in. **A custom PIN is never auto-rotated** (your value stays stable).
|
|
96
97
|
- Phone login state is tied to the computer's dsh web process: **no re-entry while dsh web stays up; one re-entry after a restart/update**.
|
|
97
98
|
- **Login rate limiting** (anti brute-force): **5** consecutive wrong PINs from the same IP lock it for **60s**; a global failure threshold briefly locks everyone (blocks distributed IP-rotation scans); a successful login resets the counter.
|
package/README.md
CHANGED
|
@@ -85,13 +85,14 @@ npx @deepseek-ai/dsh web
|
|
|
85
85
|
|
|
86
86
|
### 公网(人在外面)
|
|
87
87
|
|
|
88
|
-
同一页点「**开启公网访问**」→ 等隧道建立(首次会下载 cloudflared,macOS/Linux 走清华镜像秒下)→ 手机扫「🌐 公网」二维码 → 打开链接**输入 8 位访问密码**(密码显示在设置页公网区块,默认**每次开启公网变新**,也可点「自定义」设成固定密码——自定义后不再换新)→ 人在外面(4G/公司网)也能访问。
|
|
88
|
+
同一页点「**开启公网访问**」→ **每次都会先弹出安全免责声明**,勾选「我已知情」后才能开启(公司/涉密网络请先确认合规)→ 等隧道建立(首次会下载 cloudflared,macOS/Linux 走清华镜像秒下)→ 手机扫「🌐 公网」二维码 → 打开链接**输入 8 位访问密码**(密码显示在设置页公网区块,默认**每次开启公网变新**,也可点「自定义」设成固定密码——自定义后不再换新)→ 人在外面(4G/公司网)也能访问。
|
|
89
89
|
|
|
90
90
|
> 更新到新版本:`dsh plugin --profile web update dsh-pocket --latest -w`(跨大版本时 `--latest` 是必须的,`^0.x` 范围不会自动升到 1.x)。
|
|
91
91
|
|
|
92
92
|
## ⚠️ 安全(必读)
|
|
93
93
|
|
|
94
94
|
- **DSH 能执行你电脑上的代码**。**局域网**二维码/URL 配上独立 **8 位数字密码**才是钥匙(密码**默认开启**,可关——关闭后局域网扫码直连,仅同一网络设备可访问),**请勿把局域网二维码、URL 或密码发给别人**
|
|
95
|
+
- **开启公网访问前必须阅读并勾选免责声明**(每次开启都会弹框;服务端强制校验,无法绕过):公网 = 把能执行代码的 DSH 暴露到互联网,请使用强密码、用完即关、涉密网络勿用
|
|
95
96
|
- **公网**有 **8 位数字密码**保护:链接随机分配、默认每次开启换新密码、旧链接立即作废——泄露了也进不来,改密码/重开即可作废;**自定义密码后不再自动换新**(你设的值即稳定密码)
|
|
96
97
|
- 手机登录状态与电脑上的 dsh web 进程绑定:**电脑 dsh web 一直开着就不用重复输入;重启/更新后需重新输入一次**
|
|
97
98
|
- **登录限速**(防暴力破解):同一 IP 连续输错 **5 次**锁定 **60 秒**;全局失败超阈值时短暂全锁(防换 IP 分布式扫描);输对密码后计数清零
|
package/client/client.js
CHANGED
|
@@ -458,13 +458,21 @@ var MOBILE_CSS = `
|
|
|
458
458
|
the viewport. A mere -100% leaves a sliver on screen; -105% (as used
|
|
459
459
|
before) left 14px of the drawer plus a long 32px-blur shadow gradient
|
|
460
460
|
visible along the left edge of the main UI. No box-shadow at all: the
|
|
461
|
-
dimmed backdrop already separates drawer from content.
|
|
461
|
+
dimmed backdrop already separates drawer from content.
|
|
462
|
+
Z-index note: the backdrop renders inside the shell's overlay layer
|
|
463
|
+
([data-shell-overlay]), which forms its own stacking context. Third-party
|
|
464
|
+
plugins can force that layer up with !important (dsh-update-checker sets
|
|
465
|
+
it to 500), and when the layer outranks the drawer, the backdrop paints
|
|
466
|
+
ABOVE the drawer and swallows every tap \u2014 the drawer opens but no row
|
|
467
|
+
can be pressed (every tap just closes it). The drawer must therefore
|
|
468
|
+
outrank any such raise: 600 clears the known 500 while staying under the
|
|
469
|
+
fixed-position banners/toasts (z 9999) that float at the viewport level. */
|
|
462
470
|
[data-mobile-nav="frame"] > :first-child {
|
|
463
471
|
position: absolute !important;
|
|
464
472
|
inset: 0 auto 0 0 !important;
|
|
465
473
|
width: max-content !important;
|
|
466
474
|
max-width: 92vw !important;
|
|
467
|
-
z-index:
|
|
475
|
+
z-index: 600 !important;
|
|
468
476
|
transform: translateX(-110%);
|
|
469
477
|
transition: transform .28s var(--ds-ease-in-out, ease-in-out);
|
|
470
478
|
background: var(--dsw-alias-bg-base, #ffffff);
|
|
@@ -1350,6 +1358,10 @@ var zh2 = {
|
|
|
1350
1358
|
"stopTunnel": "\u5173\u95ED\u516C\u7F51",
|
|
1351
1359
|
"enable": "\u5F00\u542F\u516C\u7F51\u8BBF\u95EE",
|
|
1352
1360
|
"opening": "\u5F00\u542F\u4E2D\u2026",
|
|
1361
|
+
"disclaimerTitle": "\u26A0\uFE0F \u5B89\u5168\u514D\u8D23\u58F0\u660E",
|
|
1362
|
+
"disclaimerBody": "\u5F00\u542F\u516C\u7F51 = \u628A\u672C\u673A DSH\uFF08\u80FD\u6267\u884C\u4EE3\u7801\uFF09\u66B4\u9732\u5230\u4E92\u8054\u7F51\u3002\u4EFB\u4F55\u4EBA\u62FF\u5230\u516C\u7F51\u94FE\u63A5\u548C\u5BC6\u7801\uFF0C\u90FD\u80FD\u8BBF\u95EE\u751A\u81F3\u64CD\u4F5C\u4F60\u7684\u7535\u8111\u3002\u8BF7\u786E\u8BA4\uFF1A\u2460 \u4F7F\u7528\u81EA\u5B9A\u4E49\u5F3A\u5BC6\u7801\u6216\u59A5\u5584\u4FDD\u7BA1\u81EA\u52A8\u5BC6\u7801\uFF1B\u2461 \u7528\u5B8C\u7ACB\u5373\u300C\u5173\u95ED\u516C\u7F51\u300D\uFF1B\u2462 \u516C\u53F8/\u6D89\u5BC6\u7F51\u7EDC\u8BF7\u5148\u786E\u8BA4\u5408\u89C4\u3002",
|
|
1363
|
+
"disclaimerAgree": "\u6211\u5DF2\u77E5\u60C5\uFF0C\u540C\u610F\u5F00\u542F",
|
|
1364
|
+
"disclaimerHint": "\u8BF7\u52FE\u9009\u300C\u6211\u5DF2\u77E5\u60C5\u300D\u540E\u518D\u5F00\u542F\u516C\u7F51",
|
|
1353
1365
|
"downloading": "\u23F3 \u4E0B\u8F7D cloudflared\uFF08\u9996\u6B21\u7EA6 20-50MB\uFF0C\u901A\u5E38 1-2 \u5206\u949F\uFF1B\u4E4B\u540E\u79D2\u5F00\uFF09\xB7 \u5DF2\u7B49\u5F85 {s} \u79D2",
|
|
1354
1366
|
"connecting": "\u23F3 \u8FDE\u63A5 Cloudflare \u8FB9\u7F18\uFF08\u901A\u5E38 5-30 \u79D2\uFF09\xB7 \u5DF2\u7B49\u5F85 {s} \u79D2{suffix}",
|
|
1355
1367
|
"slowHint": " \u2014 \u6709\u70B9\u4E45\uFF1F\u68C0\u67E5\u662F\u5426\u5F00\u7740\u4EE3\u7406/VPN\uFF08Clash TUN \u7B49\uFF09",
|
|
@@ -1405,6 +1417,10 @@ var en2 = {
|
|
|
1405
1417
|
"stopTunnel": "Stop",
|
|
1406
1418
|
"enable": "Enable anywhere",
|
|
1407
1419
|
"opening": "Enabling\u2026",
|
|
1420
|
+
"disclaimerTitle": "\u26A0\uFE0F Security disclaimer",
|
|
1421
|
+
"disclaimerBody": "Enabling public access exposes this computer\u2019s DSH (which can execute code) to the internet. Anyone with the public link and PIN can reach \u2014 and operate \u2014 your computer. Please confirm: \u2460 use a strong custom PIN or keep the auto-generated one safe; \u2461 turn public access OFF as soon as you\u2019re done; \u2462 on a corporate/classified network, confirm compliance first.",
|
|
1422
|
+
"disclaimerAgree": "I understand and agree",
|
|
1423
|
+
"disclaimerHint": 'Check "I understand" before enabling public access',
|
|
1408
1424
|
"downloading": "\u23F3 Downloading cloudflared (first run ~20-50MB, usually 1-2 min; instant afterward) \xB7 {s}s elapsed",
|
|
1409
1425
|
"connecting": "\u23F3 Connecting to Cloudflare edge (usually 5-30s) \xB7 {s}s elapsed{suffix}",
|
|
1410
1426
|
"slowHint": " \u2014 taking long? Check for a proxy/VPN (e.g., Clash TUN)",
|
|
@@ -1545,18 +1561,29 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
1545
1561
|
setUpdateInfo((u) => ({ ...u, updating: false, result: "fail", output: err.message }));
|
|
1546
1562
|
}
|
|
1547
1563
|
};
|
|
1548
|
-
const
|
|
1564
|
+
const [disclaimerOpen, setDisclaimerOpen] = (0, import_react2.useState)(false);
|
|
1565
|
+
const [disclaimerChecked, setDisclaimerChecked] = (0, import_react2.useState)(false);
|
|
1566
|
+
const doStartTunnel = async () => {
|
|
1549
1567
|
setBusy(true);
|
|
1550
1568
|
setError(null);
|
|
1551
1569
|
setTunnelState({ phase: "starting", detail: "\u6B63\u5728\u5F00\u542F\u2026", startedAt: Date.now() });
|
|
1552
1570
|
try {
|
|
1553
|
-
setStatus(await call(POCKET_ENDPOINTS.tunnelStart, {}));
|
|
1571
|
+
setStatus(await call(POCKET_ENDPOINTS.tunnelStart, { disclaimer: true }));
|
|
1554
1572
|
} catch (err) {
|
|
1555
1573
|
setError(err.message);
|
|
1556
1574
|
} finally {
|
|
1557
1575
|
setBusy(false);
|
|
1558
1576
|
}
|
|
1559
1577
|
};
|
|
1578
|
+
const startTunnel = () => {
|
|
1579
|
+
setDisclaimerChecked(false);
|
|
1580
|
+
setDisclaimerOpen(true);
|
|
1581
|
+
};
|
|
1582
|
+
const confirmDisclaimer = () => {
|
|
1583
|
+
if (!disclaimerChecked) return;
|
|
1584
|
+
setDisclaimerOpen(false);
|
|
1585
|
+
doStartTunnel();
|
|
1586
|
+
};
|
|
1560
1587
|
const stopTunnel = async () => {
|
|
1561
1588
|
try {
|
|
1562
1589
|
setStatus(await call(POCKET_ENDPOINTS.tunnelStop, {}));
|
|
@@ -1752,6 +1779,34 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
1752
1779
|
)
|
|
1753
1780
|
),
|
|
1754
1781
|
error ? (0, import_react2.createElement)("div", { style: { color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 12, marginTop: 8 } }, `\u274C ${error}`) : null,
|
|
1782
|
+
// 安全免责声明弹框(issue #31):每次开启公网访问前确认
|
|
1783
|
+
disclaimerOpen ? (0, import_react2.createElement)(
|
|
1784
|
+
"div",
|
|
1785
|
+
{ style: { position: "fixed", inset: 0, zIndex: 1e4, background: "rgba(0,0,0,.5)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20 } },
|
|
1786
|
+
(0, import_react2.createElement)(
|
|
1787
|
+
"div",
|
|
1788
|
+
{ style: { background: "var(--dsw-alias-bg-layer-1,#fff)", borderRadius: 12, maxWidth: 420, width: "100%", padding: "20px 22px", boxShadow: "0 8px 32px rgba(0,0,0,.18)" } },
|
|
1789
|
+
(0, import_react2.createElement)("div", { style: { fontWeight: 600, fontSize: 15, color: "var(--dsw-alias-state-warn-primary,#b45309)", marginBottom: 10 } }, t("disclaimerTitle")),
|
|
1790
|
+
(0, import_react2.createElement)("div", { style: { fontSize: 13, lineHeight: 1.7, color: "var(--dsw-alias-label-primary,inherit)" } }, t("disclaimerBody")),
|
|
1791
|
+
(0, import_react2.createElement)(
|
|
1792
|
+
"label",
|
|
1793
|
+
{ style: { display: "flex", alignItems: "center", gap: 8, marginTop: 14, fontSize: 13, cursor: "pointer" } },
|
|
1794
|
+
(0, import_react2.createElement)("input", { type: "checkbox", checked: disclaimerChecked, onChange: (e) => setDisclaimerChecked(e.target.checked), style: { width: 16, height: 16 } }),
|
|
1795
|
+
t("disclaimerAgree")
|
|
1796
|
+
),
|
|
1797
|
+
(0, import_react2.createElement)(
|
|
1798
|
+
"div",
|
|
1799
|
+
{ style: { display: "flex", gap: 8, marginTop: 16 } },
|
|
1800
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, flex: 1 }, onClick: () => setDisclaimerOpen(false) }, t("cancel")),
|
|
1801
|
+
(0, import_react2.createElement)("button", {
|
|
1802
|
+
style: { ...styles.primary, flex: 1, opacity: disclaimerChecked ? 1 : 0.5 },
|
|
1803
|
+
disabled: !disclaimerChecked,
|
|
1804
|
+
onClick: confirmDisclaimer
|
|
1805
|
+
}, t("disclaimerAgree"))
|
|
1806
|
+
),
|
|
1807
|
+
!disclaimerChecked ? (0, import_react2.createElement)("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsw-alias-state-error-primary,#dc2626)" } }, t("disclaimerHint")) : null
|
|
1808
|
+
)
|
|
1809
|
+
) : null,
|
|
1755
1810
|
// 页面最底部:反馈入口
|
|
1756
1811
|
(0, import_react2.createElement)(
|
|
1757
1812
|
"div",
|
package/client/index.jsx
CHANGED
|
@@ -163,18 +163,33 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
163
163
|
}
|
|
164
164
|
};
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
// 安全免责声明(issue #31):每次开启公网都必须先弹框勾选「我已知情」。
|
|
167
|
+
// 服务端同样强制(tunnel.start 需 disclaimer: true),防绕过前端直接调 RPC。
|
|
168
|
+
const [disclaimerOpen, setDisclaimerOpen] = useState(false);
|
|
169
|
+
const [disclaimerChecked, setDisclaimerChecked] = useState(false);
|
|
170
|
+
|
|
171
|
+
const doStartTunnel = async () => {
|
|
167
172
|
setBusy(true);
|
|
168
173
|
setError(null);
|
|
169
174
|
setTunnelState({ phase: 'starting', detail: '正在开启…', startedAt: Date.now() });
|
|
170
175
|
try {
|
|
171
|
-
setStatus(await call(POCKET_ENDPOINTS.tunnelStart, {}));
|
|
176
|
+
setStatus(await call(POCKET_ENDPOINTS.tunnelStart, { disclaimer: true }));
|
|
172
177
|
} catch (err) {
|
|
173
178
|
setError(err.message);
|
|
174
179
|
} finally {
|
|
175
180
|
setBusy(false);
|
|
176
181
|
}
|
|
177
182
|
};
|
|
183
|
+
const startTunnel = () => {
|
|
184
|
+
// 每次开启都弹免责确认(勾选后才能继续)
|
|
185
|
+
setDisclaimerChecked(false);
|
|
186
|
+
setDisclaimerOpen(true);
|
|
187
|
+
};
|
|
188
|
+
const confirmDisclaimer = () => {
|
|
189
|
+
if (!disclaimerChecked) return; // 未勾选不允许
|
|
190
|
+
setDisclaimerOpen(false);
|
|
191
|
+
doStartTunnel();
|
|
192
|
+
};
|
|
178
193
|
|
|
179
194
|
const stopTunnel = async () => {
|
|
180
195
|
try { setStatus(await call(POCKET_ENDPOINTS.tunnelStop, {})); } catch { /* 忽略 */ }
|
|
@@ -363,6 +378,27 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
363
378
|
|
|
364
379
|
error ? h('div', { style: { color: 'var(--dsw-alias-state-error-primary,#dc2626)', fontSize: 12, marginTop: 8 } }, `❌ ${error}`) : null,
|
|
365
380
|
|
|
381
|
+
// 安全免责声明弹框(issue #31):每次开启公网访问前确认
|
|
382
|
+
disclaimerOpen ? h('div', { style: { position: 'fixed', inset: 0, zIndex: 10000, background: 'rgba(0,0,0,.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 } },
|
|
383
|
+
h('div', { style: { background: 'var(--dsw-alias-bg-layer-1,#fff)', borderRadius: 12, maxWidth: 420, width: '100%', padding: '20px 22px', boxShadow: '0 8px 32px rgba(0,0,0,.18)' } },
|
|
384
|
+
h('div', { style: { fontWeight: 600, fontSize: 15, color: 'var(--dsw-alias-state-warn-primary,#b45309)', marginBottom: 10 } }, t('disclaimerTitle')),
|
|
385
|
+
h('div', { style: { fontSize: 13, lineHeight: 1.7, color: 'var(--dsw-alias-label-primary,inherit)' } }, t('disclaimerBody')),
|
|
386
|
+
h('label', { style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 14, fontSize: 13, cursor: 'pointer' } },
|
|
387
|
+
h('input', { type: 'checkbox', checked: disclaimerChecked, onChange: (e) => setDisclaimerChecked(e.target.checked), style: { width: 16, height: 16 } }),
|
|
388
|
+
t('disclaimerAgree'),
|
|
389
|
+
),
|
|
390
|
+
h('div', { style: { display: 'flex', gap: 8, marginTop: 16 } },
|
|
391
|
+
h('button', { style: { ...styles.btn, flex: 1 }, onClick: () => setDisclaimerOpen(false) }, t('cancel')),
|
|
392
|
+
h('button', {
|
|
393
|
+
style: { ...styles.primary, flex: 1, opacity: disclaimerChecked ? 1 : .5 },
|
|
394
|
+
disabled: !disclaimerChecked,
|
|
395
|
+
onClick: confirmDisclaimer,
|
|
396
|
+
}, t('disclaimerAgree')),
|
|
397
|
+
),
|
|
398
|
+
!disclaimerChecked ? h('div', { style: { marginTop: 8, fontSize: 12, color: 'var(--dsw-alias-state-error-primary,#dc2626)' } }, t('disclaimerHint')) : null,
|
|
399
|
+
),
|
|
400
|
+
) : null,
|
|
401
|
+
|
|
366
402
|
// 页面最底部:反馈入口
|
|
367
403
|
h('div', { style: { ...styles.block, textAlign: 'center' } },
|
|
368
404
|
h('a', { href: 'https://github.com/shaobeichen/dsh-pocket/issues', target: '_blank', rel: 'noreferrer', style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)', textDecoration: 'none' } },
|
|
@@ -197,13 +197,21 @@ export const MOBILE_CSS = `
|
|
|
197
197
|
the viewport. A mere -100% leaves a sliver on screen; -105% (as used
|
|
198
198
|
before) left 14px of the drawer plus a long 32px-blur shadow gradient
|
|
199
199
|
visible along the left edge of the main UI. No box-shadow at all: the
|
|
200
|
-
dimmed backdrop already separates drawer from content.
|
|
200
|
+
dimmed backdrop already separates drawer from content.
|
|
201
|
+
Z-index note: the backdrop renders inside the shell's overlay layer
|
|
202
|
+
([data-shell-overlay]), which forms its own stacking context. Third-party
|
|
203
|
+
plugins can force that layer up with !important (dsh-update-checker sets
|
|
204
|
+
it to 500), and when the layer outranks the drawer, the backdrop paints
|
|
205
|
+
ABOVE the drawer and swallows every tap — the drawer opens but no row
|
|
206
|
+
can be pressed (every tap just closes it). The drawer must therefore
|
|
207
|
+
outrank any such raise: 600 clears the known 500 while staying under the
|
|
208
|
+
fixed-position banners/toasts (z 9999) that float at the viewport level. */
|
|
201
209
|
[data-mobile-nav="frame"] > :first-child {
|
|
202
210
|
position: absolute !important;
|
|
203
211
|
inset: 0 auto 0 0 !important;
|
|
204
212
|
width: max-content !important;
|
|
205
213
|
max-width: 92vw !important;
|
|
206
|
-
z-index:
|
|
214
|
+
z-index: 600 !important;
|
|
207
215
|
transform: translateX(-110%);
|
|
208
216
|
transition: transform .28s var(--ds-ease-in-out, ease-in-out);
|
|
209
217
|
background: var(--dsw-alias-bg-base, #ffffff);
|
package/client/pocket-locales.js
CHANGED
|
@@ -51,6 +51,10 @@ export const zh = {
|
|
|
51
51
|
'stopTunnel': '关闭公网',
|
|
52
52
|
'enable': '开启公网访问',
|
|
53
53
|
'opening': '开启中…',
|
|
54
|
+
'disclaimerTitle': '⚠️ 安全免责声明',
|
|
55
|
+
'disclaimerBody': '开启公网 = 把本机 DSH(能执行代码)暴露到互联网。任何人拿到公网链接和密码,都能访问甚至操作你的电脑。请确认:① 使用自定义强密码或妥善保管自动密码;② 用完立即「关闭公网」;③ 公司/涉密网络请先确认合规。',
|
|
56
|
+
'disclaimerAgree': '我已知情,同意开启',
|
|
57
|
+
'disclaimerHint': '请勾选「我已知情」后再开启公网',
|
|
54
58
|
'downloading': '⏳ 下载 cloudflared(首次约 20-50MB,通常 1-2 分钟;之后秒开)· 已等待 {s} 秒',
|
|
55
59
|
'connecting': '⏳ 连接 Cloudflare 边缘(通常 5-30 秒)· 已等待 {s} 秒{suffix}',
|
|
56
60
|
'slowHint': ' — 有点久?检查是否开着代理/VPN(Clash TUN 等)',
|
|
@@ -108,6 +112,10 @@ export const en = {
|
|
|
108
112
|
'stopTunnel': 'Stop',
|
|
109
113
|
'enable': 'Enable anywhere',
|
|
110
114
|
'opening': 'Enabling…',
|
|
115
|
+
'disclaimerTitle': '⚠️ Security disclaimer',
|
|
116
|
+
'disclaimerBody': 'Enabling public access exposes this computer\u2019s DSH (which can execute code) to the internet. Anyone with the public link and PIN can reach — and operate — your computer. Please confirm: ① use a strong custom PIN or keep the auto-generated one safe; ② turn public access OFF as soon as you\u2019re done; ③ on a corporate/classified network, confirm compliance first.',
|
|
117
|
+
'disclaimerAgree': 'I understand and agree',
|
|
118
|
+
'disclaimerHint': 'Check "I understand" before enabling public access',
|
|
111
119
|
'downloading': '⏳ Downloading cloudflared (first run ~20-50MB, usually 1-2 min; instant afterward) · {s}s elapsed',
|
|
112
120
|
'connecting': '⏳ Connecting to Cloudflare edge (usually 5-30s) · {s}s elapsed{suffix}',
|
|
113
121
|
'slowHint': ' — taking long? Check for a proxy/VPN (e.g., Clash TUN)',
|
package/lib/web-rpc.js
CHANGED
|
@@ -77,6 +77,11 @@ export function installPocketRpc(ctx, { service, log = console, desktop = false,
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
if (endpoint === POCKET_ENDPOINTS.tunnelStart) {
|
|
80
|
+
// 安全免责声明(issue #31):每次开启公网都必须先确认(前端弹框勾选)。
|
|
81
|
+
// 服务端强制校验,防止绕过前端直接调 RPC。
|
|
82
|
+
if (payload?.disclaimer !== true) {
|
|
83
|
+
return fail('bad-request', '开启公网前请先阅读并勾选安全免责声明 | please accept the security disclaimer before enabling public access');
|
|
84
|
+
}
|
|
80
85
|
await service.startTunnel();
|
|
81
86
|
return await statusPayload();
|
|
82
87
|
}
|
package/package.json
CHANGED