Files
svfs/svfs-plugins-root/com.svfs.demo/blobs/d4/d4aaec893ca84ba86e7b365ed55d18ac5f902f4090051d59b5d6779b0b657df8
chakcy f62704ca0e feat: 插件化桌面客户端 svfs-client(含凭据/会话/网络/日志的脚手架补齐)
一个插件 = 一个完整的 .svfs 包:客户端把插件前端经 svfs://<插件id>/ 直接加载
进 WebView(不落盘、不起端口、不解包),每个插件一个独立 origin(浏览器按
origin 隔离 localStorage)与独立键值存储;快照即版本——升级 = 派生快照的增量
打包,回滚 = 把注册表指针切回旧快照,包文件一个字节不用动。svfs_client 复用
svfs_studio 的主题与基础部件,故两个包一并入库。

按评估结论补齐的 P0 三块:

* 会话:桥在"宿主注入的 token 被 401 拒绝"时通知壳层,提示一次并给出一键
  「重新登录…」;服务器状态灯把「在线但登录失效」(黄)与「离线」(红)分开,
  不再把凭据失效误报成网络不可达;
* 凭据:新增 credentials.py——token 不再明文进 settings.json。Windows 走 DPAPI
  (密文只有当前用户在本机解得开),其它平台走系统凭据库(可选依赖 keyring),
  都没有才明文兜底并在「设置 → 登录凭据」如实提示;令牌与签发它的 base_url
  绑定(换服务器不会把旧令牌发出去);老字段 auth_token 启动时自动迁移;
* 强登录链路保持:启动动画 → 静默校验 → 登录窗 → 主窗口,不可达时离线宽限。

P1/P2:net 超时与重试(只对幂等方法,避免重发 POST 变两单)可配、代理与
自签 CA 可配(loopback 永远直连);诊断日志滚动落盘(1MB×3,可关)——崩溃后
还能回看现场;客户端自更新检查(静态 <base_url>/client/manifest.json,缺失
即静默);插件清单可选 net_hosts 白名单;notify 改走系统托盘(无托盘降级状态栏);
设置界面新增网络 / 登录凭据 / 诊断日志三块。

examples/:示例服务端(FastAPI,含登录、上传即 publish、客户端版本接口)、
Vue 3 插件模板(带浏览器版宿主模拟器)、svfs-plugins-root/ 为模板配套的演示
插件实体。测试:tests/test_client_*.py(纯层 + Qt 层 + 一次真实 WebEngine 冒烟)、
tests/test_studio_*.py、tests/test_plugin_server.py。
2026-09-16 17:15:48 +08:00

29 lines
1.3 KiB
Plaintext

:root {
--bg: #16181d; --panel: #1c1f26; --border: #2b3038;
--text: #e4e7ec; --muted: #9aa3b2; --accent: #5b9dff;
}
* { box-sizing: border-box; }
body {
margin: 0; padding: 28px; background: var(--bg); color: var(--text);
font-family: "Segoe UI", "Microsoft YaHei", system-ui, sans-serif;
}
main { max-width: 640px; margin: 0 auto; }
header { display: flex; align-items: center; gap: 14px; margin-bottom: 20px; }
h1 { font-size: 20px; margin: 0; }
#version { color: var(--muted); margin: 2px 0 0; font-size: 13px; }
.card {
background: var(--panel); border: 1px solid var(--border);
border-radius: 8px; padding: 14px 16px; margin-bottom: 12px;
}
.card h2 { font-size: 13px; color: var(--muted); margin: 0 0 10px; font-weight: 600; }
dl { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin: 0; }
dt { color: var(--muted); } dd { margin: 0; font-family: Consolas, monospace; font-size: 12px; }
b { color: var(--accent); font-size: 16px; }
button {
background: #22262f; color: var(--text); border: 1px solid #3a4150;
border-radius: 6px; padding: 6px 14px; cursor: pointer; margin-right: 8px;
}
button:hover { border-color: var(--accent); color: var(--accent); }
.note { color: var(--muted); font-size: 12px; }
#missing { border-color: #6b552a; color: #f5b544; }