@yezack/dsh-remote-mobile 1.9.2 → 1.10.2

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/CHANGELOG.md CHANGED
@@ -5,25 +5,99 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [1.9.2] - 2026-10-13
8
+ > **本仓库是 [`IceApriler/dsh-remote-mobile`](https://github.com/IceApriler/dsh-remote-mobile) 的 fork,以 `@yezack/dsh-remote-mobile` 发布。**
9
+ > 自 **1.10.2** 起内容对齐上游正式版(含 1.10.0 安全接入代理、1.10.1 DSH 0.2.1 路由/端口修复、1.10.2 目录选择器),并保留 fork 侧修复:
10
+ > `cordis.patch.yml` 与客户端注册 id 跟随 `package.json#name`(否则整包无法加载)、失败提示 `isEn` 插值、`test/login-page.test.js` 回归套件。
11
+ > fork 版本轨迹:`1.9.1`(授权页正则转义 + `isEn`)→ `1.9.2`(包身份修复)→ `1.10.0`(基于上游 feat/gateway 分支的预发布内容)→ `1.10.2`(对齐上游正式版);上游 `1.9.1` 与本 fork `1.9.1` 同号不同内容。
12
+
13
+ ## [1.10.2] - 2026-10-30
14
+
15
+ ### Added & Fixed (移动端添加工作区 / 选择文件夹受限检测与一键开启网页目录选择器)
16
+
17
+ - **问题背景**:DSH 官方默认目录选择器 `dsh-host-directory-picker-auto`(`id: directory-picker`)仅在进程启动时根据 `webServer.host` 是否绑定 `127.0.0.1` 回环来判断“本机 / 远程”。由于本插件默认采用安全接入代理模式(DSH 核心保持 `127.0.0.1:3080` 回环,由 `3081` 代理对外),手机端点击「添加工作区 / 选择文件夹」时会被 `auto` 误判为本机桌面操作,将原生文件夹选择窗(`native`)弹在 PC 屏幕上,导致手机端一直等待且无法添加工作区。
18
+ - **双维度精准状态检测(`src/bridge/directory-picker.ts`)**:
19
+ - 结合 Cordis 运行时 `loader.entries()` 与 `~/.dsh/profiles/web/cordis.patch.yml` 配置文件解析,精准识别当前是否已启用应用内网页目录选择器(`@deepseek-ai/dsh-host-directory-picker-browse` + `@deepseek-ai/dsh-client-ui-directory-picker-browse`)、是否仅部分配置(`patchPartial`)、是否待重启/热重载生效(`restartRequired`),以及是否需要展示受限提醒(`needsWarning`);
20
+ - `/api/remote-mobile/status` 新增 `directoryPicker` 状态契约字段;终端启动就绪横幅(`printReadyBanner`)在未开启网页目录选择器时同步输出友好提示。
21
+ - **严格幂等去重的补丁写入与无损还原**:
22
+ - 新增 `POST /api/remote-mobile/directory-picker` 路由(`exact` 契约),支持在 Web 设置页一键开启(`enabled: true`)或恢复 PC 原生目录选择器(`enabled: false`);
23
+ - **严格结构化去重**:写入 `~/.dsh/profiles/web/cordis.patch.yml` 时,若已存在 `- id: directory-picker`(未设 `disabled` 或为 `false`)则就地更新为 `disabled: true` 并清理重复块;对 `directory-picker-browse` 与 `ui-directory-picker-browse` 逐项独立查重,仅补充缺失项,绝不产生重复条目;
24
+ - **安全无损还原**:恢复 PC 原生目录选择器时,仅移除与目录选择器相关的禁用项、两个 `browse` 子项及专属注释,若与其它插件共用 `- insert:` 块则完整保留共用块与用户其它配置。
25
+ - **设置面板醒目提醒横幅与常驻控制区**:
26
+ - **顶部醒目提醒横幅(`MainSection.jsx`)**:当处于回环核心且未启用 `browse` 模式时,在「设置 -> 远程与移动端」顶部展示醒目提醒,提供 **「⚡ 一键开启网页目录选择器」** 与 **「📋 复制 YAML 配置」** 按钮;写入后若尚未热重载生效,自动切换为蓝色待生效提示;
27
+ - **「接入」页签常驻开关(`ProxyConfigCard.jsx`)**:在「接入代理配置」卡片底部常驻展示当前目录选择器状态与目标 `cordis.patch.yml` 路径,支持随时一键开启或点击 **「🔄 恢复 PC 原生目录选择器」** 撤回配置;中英文国际化(`i18n.js`)及中英文 README(`Q12`)同步更新。
28
+ - **测试覆盖**:新增 `test/directory-picker.test.js`,覆盖空配置检测、多次调用严格幂等去重、已有 `directory-picker` 就地更新、单边 `browse` 缺失补齐、共用 `insert` 块无损还原、运行时 Loader 状态探测及 API 路由闭环。
9
29
 
10
- ### Fixed (修复按新包名发布后插件整包无法加载:包身份不一致)
30
+ ---
31
+
32
+ ## [1.10.1] - 2026-10-24
33
+
34
+ ### Fixed (解决 Issue #9 · 适配 DSH 0.2.1 路由契约并修复对外端口来源)
35
+
36
+ - **DSH 0.2.1+ 路由契约兼容**:18 条 API 路由显式声明 `kind: 'exact'`,不再因缺少 `kind` 全部进入 `prefixes` 表。修复插件重复激活、组件级关闭/启用或热重载时抛出 `webserver: duplicate undefined route "/api/remote-mobile/public-key"`,导致条目启用失败的问题。
37
+ - **路由生命周期管理**:新增 `src/routes/register.ts`,通过 `ctx.effect` 收集 `WebServer.register()` 返回的 disposer,在插件 dispose/热重载时撤销全部路由;注册中途失败会回滚已注册路由。没有 `ctx.effect` 的旧宿主降级使用 `ctx.on('dispose')` 回收。
38
+ - **消除 prefix 误匹配**:`/api/remote-mobile/public-key/foo` 等未知子路径不再命中父级 handler,路由恢复严格的 exact 语义。
39
+ - **对外端口来源统一**:新增 `src/client/utils/access.js`,二维码、复制链接和网络地址卡片统一使用 `status.proxy.activePort`;移除 `window.location.port` 与硬编码 `3080` 兜底,修复在 3080 / DSH Desktop 50905 页面复制或扫码得到不可访问端口的问题。
40
+ - **代理未就绪处理**:`activePort` 缺失或非法时不再生成错误链接,改为提示“安全接入代理未就绪”并禁用相关复制/二维码操作;中英文文案同步。
41
+ - **测试覆盖**:
42
+ - 新增 `test/routes-contract.test.js`,覆盖 18 条路由 exact 契约、dispose 撤销、同一 WebServer 二次注册、注册失败回滚及旧宿主降级;
43
+ - 新增 `test/access-port.test.js`,覆盖端口解析与直达链接构造;
44
+ - `test/client-bundle.test.js` 增加打包产物中禁止 `window.location.port` 的防回归断言。
45
+ - **升级提示**:DSH 0.2.1+ 的 WebServer 不按 `method` 字段分派,本次保留该字段用于旧版兼容;升级到 1.10.1 后建议重启一次 DSH,以清理由 1.10.0 旧代码遗留在当前进程中的路由。
46
+
47
+ ---
11
48
 
12
- - **背景**:本仓库以 `@yezack/dsh-remote-mobile` 发布(上游 `dsh-remote-mobile` 的 npm 所有权不在本人名下)。DSH 以**包名**作为插件身份,改名后残留的两处旧包名会让整包不可用:
13
- - **宿主侧**:`cordis.patch.yml` 条目 `name` 仍指向 `dsh-remote-mobile`,loader 解析不到该模块,该 bundle 被跳过(插件完全不激活);
14
- - **客户端侧**:`lib/client.js` 的 `window.__ModuleLoader__.load({ id })` 仍注册为 `dsh-remote-mobile`,与 `window.__DSH_BOOT__.entries[].id`(等于包名 `@yezack/dsh-remote-mobile`)不匹配,该 row 永远等不到自己的 factory(设置面板等前端功能不加载)。
15
- - **修复**:`cordis.patch.yml` 条目名与客户端注册 id 改为跟随 `package.json` 的 `name`;`scripts/build-client.js` 的注册 id 与产物自动同步路径不再硬编码包名(统一由 `pkg.name` 推导),从根上避免改名时再次漏改。
16
- - **测试**:`test/client-bundle.test.js` 新增包身份一致性断言 —— 客户端注册 id、`cordis.patch.yml` 条目 `name` 必须等于 `package.json` 的 `name`,bundle 内联版本号必须跟随包版本。
49
+ ## [1.10.0] - 2026-10-21
50
+
51
+ ### Added & Fixed (解决 Issue #8 · 适配 DSH 0.2.1+ 核心禁止监听 0.0.0.0,内置智能自适应安全接入代理)
52
+
53
+ - **命名与模式调整**:`mode` 的 `gateway` 改为 `proxy`,`gatewayPort` / `gatewayHost` 改为 `proxyPort` / `proxyHost`;`auto` 模式移除,默认 `proxy`,核心已对外监听时回退 `direct`。用户可见文案统一为“安全接入代理(Proxy)”,避免与网络网关混淆。内部 `GatewayServer` 类名保持不变。
54
+ - **设置页接入配置**:新增「接入代理配置」卡片,可编辑 `mode` / `proxyPort` / `proxyHost` / `trustedHosts`;保存后提示需重启 DSH 生效。
55
+ - **启动失败友好提示**:精确匹配 DSH 的 `webserver` 通配地址错误,在进程退出后追加配置文件路径与处理步骤;其他启动错误不会触发。
56
+ - **双层体系解耦与代理握手 Secret 闭环(修复真实 IP 丢失与防暴破失效)**:
57
+ - **根本矛盾解耦**:彻底拆分物理 Socket IP(`getPhysicalSocketIp`)与业务有效客户端 IP(代理转发头 / 底座补丁内计算)。代理到底座物理 Socket 恒为 `127.0.0.1`,底座补丁绝不因代理携带外部真实 IP 而误判物理 Socket 逃逸回环、杜绝二次无条件洗白抵消安全策略;
58
+ - **Secret 进程级安全握手**:新增 `src/gateway/secret.ts`,基于 `crypto.randomBytes(32)` 生成高强度内部密钥,通过 `WeakMap<server, secret>` 深度支持 Cordis 插件热重载与多实例隔离;
59
+ - **三头强制净化与同步**:底座补丁在物理 Socket 为回环且 Secret 恒定时间比对(`crypto.timingSafeEqual`)一致时,才采纳有效真实 IP,并同步覆盖 `x-dsh-real-ip`、`x-real-ip` 与 `x-forwarded-for`,随后安全抹除私密 Secret 头;客户端伪造头被一律纠正为物理 Socket IP,防暴破与审计精准绑定外部真实客户端。
60
+ - **模式判定顺序保护与动态 Host 白名单(修复 0.0.0.0 模式误切与 DNS-Rebinding)**:
61
+ - **proxy-first 模式判定**:`detectServerMode` 默认 `proxy`;核心回环时启用安全接入代理,检测到 `0.0.0.0` 或具体网卡 IP 时自动回退 `direct`,兼容旧版 DSH;仅当确认生效为 `proxy` 时,才将内部 proxyTarget 规范化为回环地址;
62
+ - **动态 Host 白名单校验 (`isHostAllowed`)**:安全接入代理在接收请求第一步严格校验 Host 主机名(本机网卡 IP 短缓存 + Tailscale CGNAT + 回环 + 静态 trustedHosts),未知域名直接 400 阻断,杜绝 DNS-Rebinding 攻击;外部端口不参与信任判定,以兼容反向代理、TLS 终结与端口映射。
63
+ - **Host 与 Origin 成对虚拟化决策矩阵(解决 /auth 登录错位 403 与生态插件拦截)**:
64
+ - **固定执行顺序**:`Host 白名单校验 -> 门禁鉴权与 gateResult 标记 -> 跨站写拦截 (在虚拟化之前执行) -> 成对虚拟化 -> 注入 Secret 与 real-ip -> 转发`;
65
+ - **跨站写请求全量拦截**:非 GET/HEAD 请求不论回环还是外部,若携带恶意 `Origin`、不透明 `Origin: null` 或无 Origin 但携带 `Sec-Fetch-Site: cross-site`,一律在虚拟化改写前 403 阻断;
66
+ - **成对改写原则**:
67
+ - `public` 路径(如 `/auth`)与外部已认证流量:在确认同源或本地等价后,`Host` 与 `Origin` 成对虚拟化为内部核心权威源,彻底杜绝下游因 Host/Origin 错位导致的 403 拒绝;跨站 Origin 绝不洗白,原样保留供下游 CSRF 拦截;
68
+ - `loopback` 本机流量:成对规范化本地 Host 变体与 Origin,`Origin: null` 严格保持原样防跨站。
69
+ - **WebSocket 代理闭环与超时机制完善**:
70
+ - **握手超时保护**:握手阶段设置超时守护(默认 10s),协议升级为 101 成功后立即清除超时,保障长连接 WebSocket 不被误杀;
71
+ - **非 101 状态透明透传**:捕获底座的 403 / 401 等 HTTP 响应头与 Body 并原样返回给客户端;CSWSH 跨站拦截前置阻断。
72
+ - **状态机与前端卡片联动**:
73
+ - `SessionStore` 提供标准的安全接入代理状态机(`stopped | starting | running | error`);
74
+ - `/api/remote-mobile/status` 返回 `proxy.running` 与 `proxy.error` 完整契约;前端卡片在安全接入代理未就绪或端口冲突时展示友好警示横幅。
75
+ - **direct 模式与反向代理场景加固(复审补丁)**:
76
+ - **direct 模式 Host/WS 白名单**:非回环请求必须先通过 Host 静态白名单(本机网卡 IP / 回环 / 静态 `trustedHosts`),否则 403;WebSocket Upgrade 增加 Host + Origin 同源校验,阻断 direct 模式下的 DNS-Rebinding 与 CSWSH;
77
+ - **反向代理/端口映射兼容**:Host 白名单只校验主机名,不再强制外部端口等于代理 `boundPort`;`isSameOriginOrLocalEquivalent` 在协议一致的前提下兼容 80/443 默认端口;`X-Forwarded-Proto` 与 302 `Location` 保留外部 HTTPS,避免反代场景降级或 400/403;
78
+ - **反代回环/LAN 免密绕过修复**:同机 127 反代通过外部 Host、LAN/Tailscale 反代通过 `X-Forwarded-*` 识别为代理流量,不再走 `isLoopbackRequest` / `allowLan` / `allowTailscale` 免密;受信代理通过内部 Secret 打标例外,并优先使用 `X-Forwarded-For` 首个合法 IP 作为真实来源;WebSocket Upgrade 同样覆盖;
79
+ - **配置错误兜底**:`detectServerMode` 遇到 “proxy + 具体 LAN IP” 配置错误时由 `apply` 捕获并回退 direct,同时输出 Banner 警告;`printReadyBanner` 在等待安全接入代理后重新检查 `disposed`,避免 dispose 后重新注入 Secret/补丁;
80
+ - **本机 3080 根路径回归修复**:`isLoopbackRequest` 现在只把**外部值**的 `X-Forwarded-For` / `X-Real-IP` / `X-Forwarded-Host` 或 `X-Forwarded-Proto` 当作反向代理信号,插件自身为 loopback 请求注入的 `127.0.0.1` 头不再误伤;修复“3080 根路径被 DSH 官方 401 拦截,但 3081 正常”的回归;
81
+ - **测试隔离修复**:`SessionStore` 在测试环境且未显式指定 `devicesFile` 时自动使用 `/tmp/dsh-rm-test-devices-*.json`,不再写入真实的 `~/.dsh/remote-mobile/devices.json`;修复反复运行 `npm test` 会持续产生 `127.0.0.1 / iPhone` 假设备的问题,并新增隔离回归测试;
82
+ - **测试补充**:新增 direct 模式 DNS-Rebinding/CSWSH、反向代理端口/默认端口/HTTPS Location、代理外部来源真实 IP 注入、内部 127 Forwarded 头回归等用例。`npm test` 会先构建 server/client;在 Node 20.12 / OpenSSL 3 下,全量 155 项中 154 项通过,1 项(PKCS#1 v1.5 legacy)因 CVE-2023-46809 自动跳过,OAEP 主路径正常通过。
83
+ - **反代部署要求**:反向代理必须保留外部 Host,并透传 `X-Forwarded-For` / `X-Forwarded-Host` / `X-Forwarded-Proto`。若 Host 被改写为 `127.0.0.1` 且没有任何 Forwarded 头,插件无法区分本机浏览器与同机反代;LAN/Tailscale 反代若缺少 Forwarded 头也可能被误判为真实局域网客户端。
84
+ - **测试覆盖与基础设施**:
85
+ - 新增专用安全测试套件 `test/gateway-security.test.js`,包含模式判定、防暴破锁定真实 IP、Secret 伪造防御与 WeakMap 热重载、Drive-by 跨站拦截、本地变体等价放行、换 Wi-Fi 动态 IP 注入测试钩子(`setMockNetworkIps` / `clearNetworkIpCache`)、真实 DSH 0.2.1 风格 `/auth` + `/verify` 登录链路、WebSocket 代理闭环、direct 模式 DNS-Rebinding/CSWSH、反向代理端口与外部来源真实 IP 注入等核心用例。
86
+ - **文档更新**:
87
+ - 更新中英文 README,提供老用户升级至 DSH 0.2.1+ 的 YAML 配置对比示例,新增 `trustedHosts` / 反向代理 Host、XFF、XFP 配置说明,并明确安全接入代理默认绑定 `0.0.0.0:3081` 的安全权衡与直连模式注意事项。
17
88
 
18
89
  ---
19
90
 
20
- ## [1.9.1] - 2026-10-13
91
+ ## [1.9.1] - 2026-10-17
21
92
 
22
- ### Fixed (修复授权页无法登录:模板字符串转义与未插值的服务端标识符)
93
+ ### Fixed (解决 Issue #7 · 修复 /auth 登录页内联脚本语法错误导致局域网密码登录完全失效)
23
94
 
24
- - **授权页三处正则转义被模板字符串吞掉(致命)**:内联脚本里的 `/\/auth\/?$/` 写在 TS 模板字符串内,`\/` 在字符串求值时退化为 `/`,服务端实际发出的 HTML 变成 `replace(//auth/?$/, '/')` —— `//` 被解析为行注释、语句被截断,整个 `<script>` 抛 SyntaxError,`form` 的 submit 回调从未绑定,表现为**任何浏览器下点「立即授权连接」都毫无反应、无法登录**。三处均改为双反斜杠形式。
25
- - **失败提示引用未定义的服务端变量**:`data.reason` 缺失时的回退文案写成了裸 `isEn`(服务端变量未做插值),浏览器求值即抛 `ReferenceError`,被外层 catch 吞掉后把真实失败原因显示为「网络连接异常」。改为 `${JSON.stringify(...)}` 插值。
26
- - **测试**:`test/login-page.test.js` 新增断言 —— 内联脚本必须可被解析、不得出现被吞掉反斜杠的 `replace(//auth`、不得残留裸服务端标识符(`isEn` / `i18n` / `lang` 等);并用 `node:vm` 假浏览器覆盖「失败响应缺 `reason`」时的提示文案与网关子路径下的 URL 推导。
95
+ - **修复登录页内联脚本模板转义缺陷**:在 1.8.0 引入子路径/网关前缀自适应时,`src/routes/login-page.ts` 内使用 ES6 模板字符串拼接登录页内联 `<script>`,其中第 344/356/365 行的正则字面量写为 `window.location.pathname.replace(/\/auth\/?$/, ...)`。外层模板字符串反引号在求值时将 `\/` 折叠为 `/`,导致下发给客户端浏览器的内联脚本退化为 `replace(//auth/?$, ...)`,被 JS 词法解析器识别为单行注释吞掉后续代码并抛出 `SyntaxError: Unexpected token 'return'`:
96
+ - 内联脚本因此整体中断执行,表单 submit 监听器、`doVerify()`、RSA 密钥提取与 `/api/remote-mobile/verify` 接口调用均未注册,表现为局域网设备输入正确长期密码点击「立即授权连接」无任何反应,输入错误密码也无错误提示并退化为原生 GET 刷新;
97
+ - 现改用 `new RegExp('/auth/?$')` 显式构造正则对象,彻底消除模板字符串与正则字面量转义规则的歧义与折叠风险。
98
+ - **测试覆盖与语法保障**:
99
+ - 在 `test/routes.test.js` 中新增针对中英双语 `/auth` 页面内联 `<script>` 的自动化测试,通过 `new Function(...)` 语法解析并断言无 `SyntaxError`,且严禁出现未转义退化的 `//auth` 注释;
100
+ - 在 `test/compat.test.js` 中新增对所有注入到客户端的 5 个 Snippet 脚本的 `new Function(...)` 语法合法性全量断言。
27
101
 
28
102
  ---
29
103
 
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.