dsh-pocket 1.14.4 → 1.15.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 CHANGED
@@ -44,8 +44,8 @@ What it looks like — the phone shows the exact same UI as your computer, live:
44
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
  | 🚪 LAN switch | **Turn LAN access off/on with one click** in Settings (a confirmation dialog shows each time): off kills the LAN QR code and link instantly; public access is unaffected |
46
46
  | 🌐 Public QR (from anywhere) | Click "Enable anywhere" → cloudflared tunnel → scan the public QR over 4G / any network |
47
- | 🔐 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) |
48
- | 🔑 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) |
47
+ | 🔐 Access PIN | Public links require an **8-character PIN** (rotated on every tunnel start by default; **customizable to a fixed PIN** — custom PINs are not rotated); LAN has its own separate **8-character PIN** (on by default; switchable off in Settings — then LAN scans connect directly) |
48
+ | 🔑 Custom PINs | Both the public and LAN PINs can be **set to your own fixed 8-character PIN (letters and digits) in Settings** (custom PINs are never auto-rotated) |
49
49
  | 🧘 Session persistence | Enter the PIN once and you're set for a long time (login is tied to the computer's dsh web process: as long as it stays up, the phone won't ask again; **after a dsh web restart/update, enter it once more**) |
50
50
  | ⚡ Real-time sync | Streaming output passes through WebSocket untouched — what the computer renders, the phone renders live; fully interactive both ways; built-in WS heartbeat keep-alive (defeats silent NAT/battery link drops with auto-reconnect) |
51
51
  | 📱 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 |
@@ -91,15 +91,15 @@ Settings → **Phone access** → scan the "📶 LAN" QR code → enter the **LA
91
91
 
92
92
  ### Public (from anywhere)
93
93
 
94
- 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).
94
+ 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-character PIN** (shown in the settings page's public section; **rotated on every tunnel start by default**, or **Customize** it to a fixed PIN — letters and digits — that is never rotated) → works from outside (4G / office network).
95
95
 
96
96
  > 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).
97
97
 
98
98
  ## ⚠️ Security (read first)
99
99
 
100
- - **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**.
100
+ - **DSH can execute code on your computer.** **LAN** QR/URL plus its own **8-character 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**.
101
101
  - **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.
102
- - **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).
102
+ - **Public** access is protected by an **8-character 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; custom PINs may use letters and digits).
103
103
  - 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**.
104
104
  - **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.
105
105
  - The public URL is randomly assigned by cloudflared and **changes on every restart** (old links die automatically — a natural key rotation).
package/README.md CHANGED
@@ -44,8 +44,8 @@ DSH Pocket 就是干这个的:**装上它,手机扫个码,就能实时看
44
44
  | 📶 局域网扫码 | 装好即用:设置 → 手机访问,打开就有局域网二维码,手机连同一 WiFi 扫码即开(自动识别本机局域网 IP,**WSL 环境自动取 Windows 物理网卡 IP**) |
45
45
  | 🚪 局域网开关 | 设置页可**一键关闭/开启局域网访问**(切换时弹窗提醒):关闭后局域网二维码/链接立即失效,仅公网可用 |
46
46
  | 🌐 公网扫码(人在外面) | 点「开启公网访问」→ cloudflared 隧道 → 出公网二维码,4G/任何网络都能访问 |
47
- | 🔐 访问密码 | 公网链接需输入 **8 位数字密码**(默认每次开启公网自动换新;**可自定义固定密码**——自定义后不再换新);局域网有独立 **8 位数字密码**(默认开启,设置页可**一键关闭**——关闭后局域网扫码直连) |
48
- | 🔑 自定义密码 | 公网/局域网密码都可在设置页**设成自己固定的 8 位数字**(自定义后公网不再自动换新) |
47
+ | 🔐 访问密码 | 公网链接需输入 **8 位密码**(默认每次开启公网自动换新;**可自定义固定密码**——自定义后不再换新);局域网有独立 **8 位密码**(默认开启,设置页可**一键关闭**——关闭后局域网扫码直连) |
48
+ | 🔑 自定义密码 | 公网/局域网密码都可在设置页**设成自己固定的 8 位密码(英文字母大小写或数字)**(自定义后公网不再自动换新) |
49
49
  | 🧘 会话保持 | 手机输一次密码后**长期免输**(登录状态绑定电脑上的 dsh web 进程:只要它不重启,手机不用再输;**dsh web 重启/更新后需重新输入一次**) |
50
50
  | ⚡ 实时同步 | 流式输出走 WebSocket 全透传——**电脑上在输出,手机上同步在滚**,可双向操作;内置心跳保活(防路由器 NAT/省电机制静默断链,断线自动重连) |
51
51
  | 📱 移动端适配 | 窄屏自动变抽屉布局(移植 dsh-web-mobile,MIT):侧栏抽屉、会话全宽、状态栏安全区、触控优化 |
@@ -79,7 +79,7 @@ npx @deepseek-ai/dsh web
79
79
 
80
80
  ### 局域网(同一 WiFi)
81
81
 
82
- 设置 → **手机访问** → 手机扫「📶 局域网」二维码 → 打开链接**输入局域网密码**(显示在设置页局域网区块,点「刷新」可换新,或点「自定义」设成自己固定的 8 位数字)→ 打开的就是电脑上的 DSH,实时同步。
82
+ 设置 → **手机访问** → 手机扫「📶 局域网」二维码 → 打开链接**输入局域网密码**(显示在设置页局域网区块,点「刷新」可换新,或点「自定义」设成自己固定的 8 位密码——英文字母大小写或数字)→ 打开的就是电脑上的 DSH,实时同步。
83
83
 
84
84
  > 「**局域网访问**」开关默认**开**:可一键**关闭/开启**(切换时弹窗提醒)——关闭后局域网二维码/链接立即失效(手机打不开),**公网不受影响**;想恢复时再点「开」即可。
85
85
  >
@@ -91,15 +91,15 @@ npx @deepseek-ai/dsh web
91
91
 
92
92
  ### 公网(人在外面)
93
93
 
94
- 同一页点「**开启公网访问**」→ **每次都会先弹出安全免责声明**,勾选「我已知情」后才能开启(公司/涉密网络请先确认合规)→ 等隧道建立(首次会下载 cloudflared,macOS/Linux 走清华镜像秒下)→ 手机扫「🌐 公网」二维码 → 打开链接**输入 8 位访问密码**(密码显示在设置页公网区块,默认**每次开启公网变新**,也可点「自定义」设成固定密码——自定义后不再换新)→ 人在外面(4G/公司网)也能访问。
94
+ 同一页点「**开启公网访问**」→ **每次都会先弹出安全免责声明**,勾选「我已知情」后才能开启(公司/涉密网络请先确认合规)→ 等隧道建立(首次会下载 cloudflared,macOS/Linux 走清华镜像秒下)→ 手机扫「🌐 公网」二维码 → 打开链接**输入 8 位访问密码**(密码显示在设置页公网区块,默认**每次开启公网变新**,也可点「自定义」设成固定密码——英文字母大小写或数字,自定义后不再换新)→ 人在外面(4G/公司网)也能访问。
95
95
 
96
96
  > 更新到新版本:`dsh plugin --profile web update dsh-pocket --latest -w`(跨大版本时 `--latest` 是必须的,`^0.x` 范围不会自动升到 1.x)。
97
97
 
98
98
  ## ⚠️ 安全(必读)
99
99
 
100
- - **DSH 能执行你电脑上的代码**。**局域网**二维码/URL 配上独立 **8 位数字密码**才是钥匙(密码**默认开启**,可关——关闭后局域网扫码直连,仅同一网络设备可访问),**请勿把局域网二维码、URL 或密码发给别人**
100
+ - **DSH 能执行你电脑上的代码**。**局域网**二维码/URL 配上独立 **8 位密码**才是钥匙(密码**默认开启**,可关——关闭后局域网扫码直连,仅同一网络设备可访问),**请勿把局域网二维码、URL 或密码发给别人**
101
101
  - **开启公网访问前必须阅读并勾选免责声明**(每次开启都会弹框;服务端强制校验,无法绕过):公网 = 把能执行代码的 DSH 暴露到互联网,请使用强密码、用完即关、涉密网络勿用
102
- - **公网**有 **8 位数字密码**保护:链接随机分配、默认每次开启换新密码、旧链接立即作废——泄露了也进不来,改密码/重开即可作废;**自定义密码后不再自动换新**(你设的值即稳定密码)
102
+ - **公网**有 **8 位密码**保护:链接随机分配、默认每次开启换新密码、旧链接立即作废——泄露了也进不来,改密码/重开即可作废;**自定义密码后不再自动换新**(你设的值即稳定密码,可为英文字母大小写或数字)
103
103
  - 手机登录状态与电脑上的 dsh web 进程绑定:**电脑 dsh web 一直开着就不用重复输入;重启/更新后需重新输入一次**
104
104
  - **登录限速**(防暴力破解):同一 IP 连续输错 **5 次**锁定 **60 秒**;全局失败超阈值时短暂全锁(防换 IP 分布式扫描);输对密码后计数清零
105
105
  - 公网 URL 由 cloudflared 随机分配,**每次重启会变化**(旧链接自动失效,相当于天然轮换)
package/client/client.js CHANGED
@@ -1382,10 +1382,10 @@ var zh2 = {
1382
1382
  "lanPinCustomValue": "\u{1F510} \u8BBF\u95EE\u5BC6\u7801\uFF1A{pin}\uFF08\u81EA\u5B9A\u4E49\uFF1B\u624B\u673A\u6253\u5F00\u9700\u8F93\u5165\uFF09",
1383
1383
  "refresh": "\u5237\u65B0",
1384
1384
  "customize": "\u81EA\u5B9A\u4E49",
1385
- "customizing": "\u65B0\u5BC6\u7801\uFF088 \u4F4D\u6570\u5B57\uFF09\uFF1A",
1385
+ "customizing": "\u65B0\u5BC6\u7801\uFF088 \u4F4D\uFF0C\u82F1\u6587\u5B57\u6BCD\u6216\u6570\u5B57\uFF09\uFF1A",
1386
1386
  "save": "\u4FDD\u5B58",
1387
1387
  "cancel": "\u53D6\u6D88",
1388
- "pinInvalid": "\u5BC6\u7801\u5FC5\u987B\u662F 8 \u4F4D\u6570\u5B57",
1388
+ "pinInvalid": "\u5BC6\u7801\u5FC5\u987B\u662F 8 \u4F4D\u82F1\u6587\u5B57\u6BCD\u6216\u6570\u5B57",
1389
1389
  "pinCustomHint": "\u81EA\u5B9A\u4E49\u540E\u5F00\u542F\u516C\u7F51\u4E0D\u518D\u81EA\u52A8\u6362\u65B0",
1390
1390
  "lanPinOff": "\u{1F513} \u5BC6\u7801\u5DF2\u5173\u95ED\uFF1A\u626B\u7801\u76F4\u8FDE\uFF0C\u65E0\u9700\u5BC6\u7801\uFF08\u4EC5\u540C\u4E00\u5C40\u57DF\u7F51\u8BBE\u5907\u53EF\u8BBF\u95EE\uFF1B\u516C\u7F51\u4ECD\u8981\u5BC6\u7801\uFF09",
1391
1391
  "lanStarting": "\u4EE3\u7406\u672A\u5C31\u7EEA\u2026",
@@ -1451,10 +1451,10 @@ var en2 = {
1451
1451
  "lanPinCustomValue": "\u{1F510} PIN: {pin} (custom; required on the phone)",
1452
1452
  "refresh": "Refresh",
1453
1453
  "customize": "Customize",
1454
- "customizing": "New PIN (8 digits): ",
1454
+ "customizing": "New PIN (8 chars, letters/digits): ",
1455
1455
  "save": "Save",
1456
1456
  "cancel": "Cancel",
1457
- "pinInvalid": "PIN must be exactly 8 digits",
1457
+ "pinInvalid": "PIN must be exactly 8 characters (letters and digits only)",
1458
1458
  "pinCustomHint": "custom PINs are not rotated on tunnel start",
1459
1459
  "lanPinOff": "\u{1F513} PIN off \u2014 scan & go, no PIN (LAN devices only; public still requires PIN)",
1460
1460
  "lanStarting": "Proxy starting\u2026",
@@ -1693,13 +1693,12 @@ function PocketSettingsTab({ rpcCall, t }) {
1693
1693
  { style: { marginTop: 6, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)", lineHeight: 1.5 } },
1694
1694
  t("customizing"),
1695
1695
  (0, import_react2.createElement)("input", {
1696
- style: { width: 110, margin: "0 6px", padding: "4px 8px", fontSize: 14, letterSpacing: 2, textAlign: "center", border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", borderRadius: 6, outline: "none" },
1696
+ style: { width: 130, margin: "0 6px", padding: "4px 8px", fontSize: 14, letterSpacing: 1, textAlign: "center", border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", borderRadius: 6, outline: "none" },
1697
1697
  type: "password",
1698
- inputMode: "numeric",
1699
1698
  maxLength: 8,
1700
1699
  value: customPin?.value ?? "",
1701
1700
  autoFocus: true,
1702
- onChange: (e) => setCustomPin((c) => ({ ...c, value: e.target.value.replace(/\D/g, ""), err: null })),
1701
+ onChange: (e) => setCustomPin((c) => ({ ...c, value: e.target.value.replace(/[^a-zA-Z0-9]/g, ""), err: null })),
1703
1702
  onKeyDown: (e) => {
1704
1703
  if (e.key === "Enter") saveCustomPin(which);
1705
1704
  if (e.key === "Escape") setCustomPin(null);
package/client/index.jsx CHANGED
@@ -236,7 +236,7 @@ function PocketSettingsTab({ rpcCall, t }) {
236
236
  }
237
237
  };
238
238
 
239
- // 自定义访问密码(issue #33):公网/局域网各自设固定 8 位数字;自定义后公网不再自动轮换。
239
+ // 自定义访问密码(issue #33):公网/局域网各自设固定 8 位密码(英文字母大小写或数字);自定义后公网不再自动轮换。
240
240
  // customPin: { which: 'public'|'lan', value, err } | null —— 正在输入自定义密码的区块
241
241
  const [customPin, setCustomPin] = useState(null);
242
242
  const saveCustomPin = async (which) => {
@@ -258,13 +258,12 @@ function PocketSettingsTab({ rpcCall, t }) {
258
258
  const customPinRow = (which) => h('div', { style: { marginTop: 6, fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)', lineHeight: 1.5 } },
259
259
  t('customizing'),
260
260
  h('input', {
261
- style: { width: 110, margin: '0 6px', padding: '4px 8px', fontSize: 14, letterSpacing: 2, textAlign: 'center', border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', borderRadius: 6, outline: 'none' },
261
+ style: { width: 130, margin: '0 6px', padding: '4px 8px', fontSize: 14, letterSpacing: 1, textAlign: 'center', border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', borderRadius: 6, outline: 'none' },
262
262
  type: 'password',
263
- inputMode: 'numeric',
264
263
  maxLength: 8,
265
264
  value: customPin?.value ?? '',
266
265
  autoFocus: true,
267
- onChange: (e) => setCustomPin((c) => ({ ...c, value: e.target.value.replace(/\D/g, ''), err: null })),
266
+ onChange: (e) => setCustomPin((c) => ({ ...c, value: e.target.value.replace(/[^a-zA-Z0-9]/g, ''), err: null })),
268
267
  onKeyDown: (e) => { if (e.key === 'Enter') saveCustomPin(which); if (e.key === 'Escape') setCustomPin(null); },
269
268
  }),
270
269
  h('button', { style: { ...styles.btn, height: 26, padding: '0 10px', fontSize: 12, marginLeft: 2 }, onClick: () => saveCustomPin(which) }, t('save')),
@@ -47,10 +47,10 @@ export const zh = {
47
47
  'lanPinCustomValue': '🔐 访问密码:{pin}(自定义;手机打开需输入)',
48
48
  'refresh': '刷新',
49
49
  'customize': '自定义',
50
- 'customizing': '新密码(8 位数字):',
50
+ 'customizing': '新密码(8 位,英文字母或数字):',
51
51
  'save': '保存',
52
52
  'cancel': '取消',
53
- 'pinInvalid': '密码必须是 8 位数字',
53
+ 'pinInvalid': '密码必须是 8 位英文字母或数字',
54
54
  'pinCustomHint': '自定义后开启公网不再自动换新',
55
55
  'lanPinOff': '🔓 密码已关闭:扫码直连,无需密码(仅同一局域网设备可访问;公网仍要密码)',
56
56
  'lanStarting': '代理未就绪…',
@@ -118,10 +118,10 @@ export const en = {
118
118
  'lanPinCustomValue': '🔐 PIN: {pin} (custom; required on the phone)',
119
119
  'refresh': 'Refresh',
120
120
  'customize': 'Customize',
121
- 'customizing': 'New PIN (8 digits): ',
121
+ 'customizing': 'New PIN (8 chars, letters/digits): ',
122
122
  'save': 'Save',
123
123
  'cancel': 'Cancel',
124
- 'pinInvalid': 'PIN must be exactly 8 digits',
124
+ 'pinInvalid': 'PIN must be exactly 8 characters (letters and digits only)',
125
125
  'pinCustomHint': 'custom PINs are not rotated on tunnel start',
126
126
  'lanPinOff': '🔓 PIN off — scan & go, no PIN (LAN devices only; public still requires PIN)',
127
127
  'lanStarting': 'Proxy starting…',
package/lib/index.js CHANGED
@@ -44,12 +44,13 @@ function currentVersion() {
44
44
  const loadedVersion = currentVersion();
45
45
 
46
46
  // ---------- 访问密码(issue #13 + #18 + #33) ----------
47
- // 公网与局域网**分开**:各自 8 位数字,存本机 $DSH_HOME/dsh-pocket/。
47
+ // 公网与局域网**分开**:各自 8 位,存本机 $DSH_HOME/dsh-pocket/。
48
48
  // 公网密码(token):默认每次开启公网时轮换(旧链接作废);**用户自定义后不再轮换**;
49
49
  // 局域网密码(token-lan):默认手动刷新(设置页按钮);自定义后刷新会换回随机值。
50
50
  // 会话保持(issue #33):登录 cookie 绑定进程级 sessionKey(见 apply)——
51
51
  // dsh web 重启/更新后 sessionKey 变化 → 手机需重新输入。
52
- const PIN_RE = /^\d{8}$/;
52
+ // 密码规则:8 位英文字母(大小写)或数字(自动生成的为 8 位数字,自定义可为字母+数字)。
53
+ const PIN_RE = /^[a-zA-Z0-9]{8}$/;
53
54
  function writePinToFile(p, fresh) {
54
55
  try {
55
56
  mkdirSync(dirname(p), { recursive: true });
@@ -102,13 +103,13 @@ function tokenForHost(host) {
102
103
  return /trycloudflare\.com$/i.test(String(host ?? '')) ? getAccessToken() : getLanToken();
103
104
  }
104
105
  /**
105
- * 用户自定义访问密码(issue #33):公网/局域网各自设一个固定的 8 位数字。
106
+ * 用户自定义访问密码(issue #33):公网/局域网各自设一个固定的 8 位密码(英文字母大小写或数字)。
106
107
  * 自定义后公网开启时不再自动轮换(rotateAccessToken 见上)。
107
- * 非法输入(非 8 位数字)抛错,由 RPC 层转成错误响应。
108
+ * 非法输入(非 8 位、含字母数字以外字符)抛错,由 RPC 层转成错误响应。
108
109
  */
109
110
  export function setCustomPin(which, value) {
110
111
  const v = String(value ?? '').trim();
111
- if (!PIN_RE.test(v)) throw new Error('密码必须是 8 位数字 | PIN must be exactly 8 digits');
112
+ if (!PIN_RE.test(v)) throw new Error('密码必须是 8 位英文字母或数字 | PIN must be exactly 8 characters (letters and digits only)');
112
113
  if (which === 'public') {
113
114
  writePinToFile(tokenPath(), v);
114
115
  setPinCustom('public', true);
package/lib/proxy.mjs CHANGED
@@ -27,16 +27,13 @@ const DEFAULT_UPSTREAM = { host: '127.0.0.1', port: 3080 };
27
27
  * 带 data-dsh-pocket-polyfill 标记:注入判重用它,而不是搜索 "crypto.randomUUID"
28
28
  * 字样(dsh 页面源码里可能恰好出现该字符串,导致误判为已注入而跳过)。
29
29
  */
30
- export const RANDOM_UUID_POLYFILL = `<script data-dsh-pocket-polyfill="1">/* issue #58:局域网访问时 location.hostname 是局域网 IP,DSH 客户端据此判定
31
- isLoopback=false settings mirror 降级 memory 模式 模型设置页
32
- "settings are unavailable in this browser"。location.hostname WebIDL
33
- unforgeable 属性无法直接覆盖;改用全局 let location 词法绑定遮蔽 +
34
- Proxy 转发(其他属性/方法转发真实 location,只把 hostname 伪装成 127.0.0.1)。
35
- DSH API 基址走 globalThis.location.origin(不受影响)。*/
36
- let __dshPocketLoc = new Proxy(window.location, { get: function (t, p) { if (p === 'hostname') return '127.0.0.1'; var v = t[p]; if (typeof v === 'function') return v.bind(t); return v; }, set: function (t, p, v) { t[p] = v; return true; } });
37
- let location = __dshPocketLoc;
38
- !function(){try{if(self.crypto&&!self.crypto.randomUUID){self.crypto.randomUUID=function(){var b=new Uint8Array(16);self.crypto.getRandomValues(b);b[6]=b[6]&15|64;b[8]=b[8]&63|128;var h="";for(var i=0;i<16;i++){var x=b[i].toString(16);h+=(x.length<2?"0":"")+x;if(i===3||i===5||i===7||i===9)h+="-";}return h;}}}catch(e){}}();
39
- !function(){try{if(self.AbortSignal&&!self.AbortSignal.any){self.AbortSignal.any=function(signals){var controller=new AbortController();var list=Array.from(signals||[]);var done=false;var handlers=list.map(function(signal){return function(){abort(signal);};});function cleanup(){for(var i=0;i<list.length;i++){try{list[i].removeEventListener('abort',handlers[i]);}catch(e){}}}function abort(signal){if(done)return;done=true;cleanup();try{controller.abort(signal.reason);}catch(e){controller.abort();}}for(var j=0;j<list.length;j++){var sig=list[j];if(sig.aborted){abort(sig);break;}sig.addEventListener('abort',handlers[j],{once:true});}return controller.signal;};}}catch(e){}}();</script>`;
30
+ export const RANDOM_UUID_POLYFILL = `<script data-dsh-pocket-polyfill="1">!function(){try{if(self.crypto&&!self.crypto.randomUUID){self.crypto.randomUUID=function(){var b=new Uint8Array(16);self.crypto.getRandomValues(b);b[6]=b[6]&15|64;b[8]=b[8]&63|128;var h="";for(var i=0;i<16;i++){var x=b[i].toString(16);h+=(x.length<2?"0":"")+x;if(i===3||i===5||i===7||i===9)h+="-";}return h;}}}catch(e){}}();
31
+ !function(){try{if(self.AbortSignal&&!self.AbortSignal.any){self.AbortSignal.any=function(signals){var controller=new AbortController();var list=Array.from(signals||[]);var done=false;var handlers=list.map(function(signal){return function(){abort(signal);};});function cleanup(){for(var i=0;i<list.length;i++){try{list[i].removeEventListener('abort',handlers[i]);}catch(e){}}}function abort(signal){if(done)return;done=true;cleanup();try{controller.abort(signal.reason);}catch(e){controller.abort();}}for(var j=0;j<list.length;j++){var sig=list[j];if(sig.aborted){abort(sig);break;}sig.addEventListener('abort',handlers[j],{once:true});}return controller.signal;};}}catch(e){}}();
32
+ /* 注:曾用「全局 let location + Proxy」伪装 location.hostname DSH isLoopback 判定
33
+ (issue #58:局域网访问模型设置页报 settings unavailable)——但 let location 全局
34
+ 词法绑定会让任何恰好顶层声明 location 的脚本(DSH 插件经典 script)SyntaxError 崩溃,
35
+ 导致会话列表不显示(实测 PAGEERROR: Identifier 'location' has already been declared)。
36
+ 已回退;该问题属 DSH 客户端限制(location.hostname unforgeable 属性)无法安全绕过。*/</script>`;
40
37
 
41
38
  const INJECT_MARK = 'data-dsh-pocket-polyfill="1"';
42
39
 
@@ -181,10 +178,10 @@ button{width:100%;padding:10px;font-size:15px;background:#4f6ef7;color:#fff;bord
181
178
  .err{color:#dc2626;font-size:12px;margin-bottom:10px;min-height:16px}
182
179
  </style></head><body><div class="card">
183
180
  <h1>🔐 DSH Pocket</h1>
184
- <p>${where}受访问密码保护,请输入 8 位数字密码 | ${whereEn} is password-protected — enter the 8-digit PIN</p>
181
+ <p>${where}受访问密码保护,请输入 8 位密码(英文字母或数字) | ${whereEn} is password-protected — enter the 8-character PIN (letters/digits)</p>
185
182
  <div class="err">${errMsg}</div>
186
183
  <form method="post" action="/pocket-login">
187
- <input name="token" type="password" inputmode="numeric" maxlength="8" autocomplete="one-time-code" autofocus required>
184
+ <input name="token" type="password" maxlength="8" autocomplete="one-time-code" autofocus required>
188
185
  <button type="submit">进入 | Enter</button>
189
186
  </form>
190
187
  </div></body></html>`;
package/lib/settings.mjs CHANGED
@@ -79,7 +79,7 @@ export function setLanIpOverride(value) {
79
79
  }
80
80
 
81
81
  // ---------- 访问密码「自定义」标记(issue #33) ----------
82
- // 用户可把公网/局域网密码设成自己固定的 8 位数字(自定义后不再自动轮换)。
82
+ // 用户可把公网/局域网密码设成自己固定的 8 位密码(英文字母大小写或数字;自定义后不再自动轮换)。
83
83
  // 标记存 settings.json:publicPinCustom / lanPinCustom。
84
84
  const PIN_CUSTOM_KEYS = { public: 'publicPinCustom', lan: 'lanPinCustom' };
85
85
 
package/package.json CHANGED
@@ -23,6 +23,7 @@
23
23
  ],
24
24
  "scripts": {
25
25
  "build:client": "node client/build.mjs",
26
+ "release": "semantic-release",
26
27
  "test": "node --test --test-timeout=30000 \"test/*.test.js\""
27
28
  },
28
29
  "dependencies": {
@@ -30,7 +31,10 @@
30
31
  "qrcode-terminal": "^0.12.0"
31
32
  },
32
33
  "devDependencies": {
34
+ "@semantic-release/changelog": "^6.0.3",
35
+ "@semantic-release/git": "^10.0.1",
33
36
  "esbuild": "^0.25.9",
37
+ "semantic-release": "^24.2.9",
34
38
  "ws": "^8.18.0"
35
39
  },
36
40
  "peerDependencies": {
@@ -76,5 +80,5 @@
76
80
  "access": "public",
77
81
  "registry": "https://registry.npmjs.org/"
78
82
  },
79
- "version": "1.14.4"
83
+ "version": "1.15.0"
80
84
  }