UI 模块:嵌入方式与弹窗交互逻辑(v1.1.0 新增)

September 4, 2026 · View on GitHub

1. 架构总览

v1.1.0 在原有后端插件(cordis 插件,运行于 dsh 进程内)之上新增了浏览器端 UI 模块。一个包、一条 patch 行,两端各取所需:

dsh-autoupdate(单个 npm 包)
├── 后端半边  lib/index.js      cordis 插件(检测/武装/回滚,原有逻辑)
├── 前端半边  lib/client.js     设置页 UI(本模块)
└── 通道      lib/channel.js    后端 RPC 通道注册(POST /dsh-autoupdate/*)

装载链:cordis.patch.yml 里的单行 name: dsh-autoupdate → cordis loader 加载后端入口;宿主(dsh-client-modules)扫描到 package.jsondsh.client 声明后,把 exports["./client"] 作为浏览器模块伺服到 /plugins/dsh-autoupdate/client.js,由 window.__ModuleLoader__ 懒加载执行。

2. UI 嵌入方式(插槽契约)

设置页是插槽式组合:壳插件(dsh-client-ui-settings-general)声明插槽树,各功能包向插槽注册内容。本插件注册的插槽:

插槽类型注册内容
settings.sectionlist(设置页左侧导航 + 内容区){ id: "dsh-autoupdate", order: 100, label, locale } + Section 组件

注册代码(lib/client.jsapply(ctx)):

ctx.slots.inject("settings.section", () =>
  ctx.slots.register({
    name: "settings.section",
    id: "dsh-autoupdate",
    order: 100,
    label: labelFn,      // 走 locale 服务,失败回落 "自动更新"
    locale: "dsh-autoupdate",
  }, Section));
  • ctx.slots.inject(name, fn):等插槽声明出现在账本上再注册——与壳插件的加载顺序解耦。
  • locale: "dsh-autoupdate":壳会给组件注入绑定到该命名空间的 t 函数;组件同时内置字典兜底(t 缺失/抛错时用 navigator.language 选 zh/en)。
  • Section 组件用原生 Reactrequire("react"),ModuleLoader 静态表提供),不依赖 dsh 的组件库(primitives),样式用 dsw 设计变量 + 硬编码回退(var(--dsw-alias-bg-layer-2, #fff))。

3. 前后端通道

不引入 dsh 的 RPC 框架(typert/zod 描述符),直接用宿主连接服务的通用通道注册

后端(lib/channel.js,cordis 侧):

ctx.inject(["connection"], (cctx) => {
  cctx.connection.rpc.handle("/dsh-autoupdate", handler);
});

前端(lib/client.js,浏览器侧)——纯同源 fetch:

fetch(location.origin + "/dsh-autoupdate/autoupdate/check", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ type: "client-request", rpcId, method: "autoupdate/check", payload: {} }),
});
// 响应: { type: "server-response", rpcId, result: { ok, value | error } }

三个端点:

端点payload返回 value
autoupdate/status{}{ installedVersion, pendingVersion, level, lastCheckAt, channel }
autoupdate/check{}{ state: "up-to-date" | "update-available", current, latest, channel, checkedAt }
autoupdate/apply{ targetVersion }{ armed: true, targetVersion, fromVersion }

安全边界:

  • applytargetVersion 必须等于最近一次 check 解析出的 pendingVersion——UI 不能任意指定版本安装。
  • 通道信任检查(loopback / 同源 Origin / trustedHosts)由宿主连接层统一执行。
  • connection 服务的 profile(headless/tui)里通道不注册,后端照常工作。

4. 弹窗交互逻辑

点击【检查更新】


┌─ checking ────────────────────────► 文案「正在检查更新…」
│        │(POST autoupdate/check)
│        ▼
│   ┌─── state === "update-available" ──► ┌──────────────────────────────┐
│   │                                     │ 发现新版本                    │
│   │                                     │ 当前版本 X / 最新版本 Y / 通道 │
│   │                                     │ [确认更新]      [取消]        │
│   │                                     └──────┬───────────┬───────────┘
│   │                              点击确认更新   │           │ 点击取消 → 关闭
│   │                                            ▼
│   │                                     arming →(POST autoupdate/apply)
│   │                                            ▼
│   │                                     armed:「更新已排定:dsh 退出后
│   │                                     将自动完成安装与验证,重启后生效」
│   │                                     + 目标版本行 + [关闭]
│   │
│   └─── state === "up-to-date" ──────► 「当前已是最新版本,暂无可用更新」+ [关闭]

└─── 失败(网络/registry/断路器等)──► 红色错误文案 + [关闭]

实现要点:

  • 弹窗本体:fixed 遮罩 + 居中面板,role="dialog",点遮罩或按钮关闭。
  • 并发防护:组件内 seq 引用计数——每次请求自增,响应回来时序号不匹配直接丢弃(连续点击/关闭后再返回都不会写脏状态)。
  • 后端并发防护checkForUi 复用 _checking 锁,重复点击返回"已有检测正在进行"。
  • armed 之后:更新在 dsh 进程退出后由 detached helper 执行(v1.0.0 原有事务:安装→双验证→失败回滚→断路器记账),UI 不做安装,只做排定。
  • 通道暂时不可用:挂载时 autoupdate/status 失败会显示提示,仍允许点击检查重试;明确禁用的插件以及正在执行的操作才会禁用按钮。

5. 抗破坏性设计(UI 部分)

依赖点失稳后果防护
settings.section 插槽被移除/改名UI 不显示slots.inject 永不触发,静默缺席;后端与手动 CLI 流程不受影响
locale 服务 API 变化文案异常t 调用全程 try/catch,内置 zh/en 字典兜底
ModuleLoader 中 react 缺席组件无法构建factory 内探测,缺失则跳过注册
dsw 设计变量改名样式走样每个变量带硬编码回退值
通道信封格式(client-request/server-response)变化UI 请求 4xx前端报错文案明确提示"dsh 版本可能已变更",并给出手动命令
primitives 组件库变化不使用(原生 button/div)

6. 触发规则变更(v1.0.0 → v1.1.0)

新增配置 autoCheck(默认 false):

  • false(默认):无任何静默检测——启动与周期定时器均不注册;检测只在用户点击【检查更新】时发生。
  • true:恢复 v1.0.0 的启动 30s 首检 + 每 6 小时周期检测。

原有检测/武装/验证/回滚/断路器代码一行未改,仅定时器调度被 autoCheck 门控。想恢复自动检测,在 profile 的 cordis.patch.yml 里:

- id: auto-update
  config:
    autoCheck: true