Skip to content

fix(client): 页面身份改用可降级的 randomId,修复 http 局域网访问下设置页空白(#102) - #104

Merged
Mars-Sea merged 4 commits into
Mars-Sea:mainfrom
GT-610:fix/issue-102-non-secure-context-uuid
Oct 27, 2026
Merged

Mars-Sea merged 4 commits into
Mars-Sea:mainfrom
GT-610:fix/issue-102-non-secure-context-uuid

Conversation

@GT-610

@GT-610 GT-610 commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

问题

issue #102:DSH 跑在家用 NAS 的 Docker 里,仅以 http://<局域网 IP>:3080 访问(无 HTTPS、无反向代理,window.isSecureContext === false)。设置页左侧「Command Code」标签正常,右侧内容区整块空白,且没有任何插件侧提示。控制台:

TypeError: crypto.randomUUID is not a function
    at new AccountEnrollmentController → createEnrollment → useState → AccountsCard
client.js:638 slot entry crashed in 'settings.section'

定因

crypto.randomUUID() 是安全上下文专属 API,http:// 下为 undefined(crypto.getRandomValues 仍可用)。插件里唯一的浏览器侧调用是 AccountEnrollmentController 构造函数的默认参数、构造时立即求值;而控制器由 AccountsCard 的 useState(createEnrollment) 惰性初始化构造,异常落在 React 渲染期、被设置页插槽的错误边界接住——于是「标签注册成功、内容区不渲染」。main 上复核仍在(src/client/enrollment.ts),并全量检索过 src/ 的 randomUUID / crypto / navigator. / isSecureContext / subtle / clipboard:浏览器侧只此一处,其余五处是 Host 端 node:crypto。

改动

  • 新增 src/client/random-id.ts:randomId() 在 randomUUID 可用时直接用;否则用非安全上下文同样可用的 getRandomValues,按 RFC 4122 置版本位与变体位,产出与 randomUUID 同形的 v4。
  • AccountEnrollmentController 的默认 newId 改用它,注入接缝保留。
  • 页面身份只是宿主把开通过程绑到单个浏览器页面的分组键(src/enrollment-wire.ts 只校验是字符串),不参与鉴权,因此不要求 HTTPS;控制器接口、宿主契约、开通过程本身零变化。

测试

tests/random-id.test.ts 3 条:

  • 安全上下文直接用平台 randomUUID(哨兵值分辨分支,不走自行拼装);
  • 没有 randomUUID(非安全上下文)时兜底产出 100 个互不相同的 v4;
  • 非安全上下文下构造控制器(复现 useState 惰性初始化的崩溃路径)并把同一页面身份带给宿主。

三处定向变异各只命中一条:关闭快速路径 → 只第一条失败;版本位 | 0x40 改 0 → 只后两条失败;默认 newId 改回 () => crypto.randomUUID() → 只第三条失败。

验证

  • npm run typecheck 退出码 0
  • npm ci 干净安装后 npm test 1129/1129
  • npm run build 两段 Build complete,git diff --exit-code -- lib/ 无差异(lib/index.js 与 lib/index.d.ts 字节不变,只有 lib/client.js 随源码变化)

未在真实 NAS/局域网环境端到端验证(开发机本身是安全上下文,只能以去掉 randomUUID 的方式模拟)。不涉及引擎面、peer 范围与包清单,故未跑 test:engine / test:install / test:pack。

Fixes #102

crypto.randomUUID 只在安全上下文可用;以 http://<局域网 IP>:3080 打开设置页时它是
undefined,而 AccountEnrollmentController 在 AccountsCard 的 useState 惰性初始化里
构造并立刻取页面身份,异常落在渲染期、被设置页插槽错误边界接住,表现为标签正常、
右侧内容区整块空白且无提示。

新增 src/client/random-id.ts:randomUUID 可用时照旧,否则用非安全上下文同样可用的
crypto.getRandomValues 按 RFC 4122 拼同形 v4;控制器默认 newId 改用它。页面身份只是
宿主把开通过程绑到单个浏览器页面的分组键,不参与鉴权,因此不要求 HTTPS。

用例:tests/random-id.test.ts(v4 形态、去掉 randomUUID 后的兜底、非安全上下文下
构造控制器并把页面身份带给宿主);改回 () => crypto.randomUUID() 时第三条失败。
验证:typecheck 退出码 0;npm ci 后 npm test 1129/1129;build 重建 lib/client.js
(lib/index.js 与 lib/index.d.ts 字节不变)。
CHANGELOG 的 [Unreleased] → Fixed 补 1 条,并附用例与验证边界(1129/1129);
客户端契约新增「页面身份不依赖安全上下文」;仓库地图补 src/client/random-id.ts
与 tests/random-id.test.ts;决策记录第四章第 9 条记录现象、定因、未采用的两种
方案(要求 HTTPS、彻底降级到 Math.random)与边界;提交历史补总览与阶段条目。
质量复查(范围限本次改动):

- 去掉 randomId() 里永不可达的 `source?.`——globalThis.crypto 在所有浏览器里都存在,
  可选链只会把抛错点挪到下一行 source.getRandomValues;改为 source.randomUUID,并用
  一行注释说明为什么不给 crypto 对象本身设兜底。
- 去掉 newId 的 `: () => string` 标注:与 randomId 的返回类型完全一致,可由默认值推论,
  该行回到「只换表达式」的最小 diff。
- 第一条用例原本与第二条断言同一条 v4 正则,关掉快速路径仍能通过,不具判别力;改为
  「安全上下文直接用平台 randomUUID」的哨兵用例(兜底产出的 v4 与哨兵必然不同)。
- 测试辅助改为先取属性描述符、finally 按描述符还原,避免误删本就在自有的属性;第三条
  用例的五个「未使用」抛错桩合并成一个 unused。

三处定向变异各只命中一条:关掉快速路径 → 只第一条;版本位置 0 → 只后两条;控制器默认
值改回 crypto.randomUUID() → 只第三条。typecheck 退出码 0,npm test 1129/1129,
build 后 lib/client.js 相对上一提交只差 1 行(内联注释不进产物)。
CHANGELOG 的 [Unreleased] 用例段改为三条各管一件事(平台 API 哨兵、兜底形态与唯一性、
控制器接线)并写明三处定向变异各命中哪一条;决策记录第四章第 9 条核验段同步;提交历史
的阶段条目补第 8 条(dead code、冗余标注、无判别力用例、测试卫生、有意不做的优化),
并注明第 3、4 条被它调整的部分。
@Mars-Sea
Mars-Sea merged commit 1cc923a into Mars-Sea:main Oct 27, 2026
5 checks passed
@GT-610
GT-610 deleted the fix/issue-102-non-secure-context-uuid branch October 27, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DSH左下角设置 → Command Code 右边整块显示空白(在局域网访问,crypto.randomUUID is not a function)

2 participants