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。
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
dl[data-v-d22dfe76]{display:grid;grid-template-columns:auto 1fr;gap:4px 16px;margin:0}dt[data-v-d22dfe76]{color:var(--muted)}dd[data-v-d22dfe76]{margin:0;font-family:Consolas,monospace;font-size:12px}:root{--bg: #16181d;--panel: #1c1f26;--border: #2b3038;--text: #e4e7ec;--muted: #9aa3b2;--accent: #5b9dff}*{box-sizing:border-box}body{margin:0;padding:24px;background:var(--bg);color:var(--text);font-family:Segoe UI,Microsoft YaHei,system-ui,sans-serif}main{max-width:680px;margin:0 auto}h1{font-size:20px;margin:0 0 14px}nav{display:flex;gap:8px;margin-bottom:16px}nav button{background:var(--panel);color:var(--muted);border:1px solid var(--border);border-radius:6px;padding:6px 14px;cursor:pointer}nav button.active{color:var(--accent);border-color:var(--accent)}.panel{background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin-bottom:12px}.panel h2{font-size:13px;color:var(--muted);margin:0 0 10px}button.act{background:#22262f;color:var(--text);border:1px solid #3a4150;border-radius:6px;padding:6px 14px;cursor:pointer;margin-right:8px}button.act:hover{border-color:var(--accent);color:var(--accent)}input,textarea,select{background:#14171c;color:var(--text);border:1px solid #3a4150;border-radius:6px;padding:6px 8px;font-size:13px}pre{background:#14171c;border:1px solid var(--border);border-radius:6px;padding:8px 10px;font-size:12px;white-space:pre-wrap;word-break:break-all;min-height:18px}.muted{color:var(--muted);font-size:12px}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "com.example.vue-demo",
|
||||
"name": "Vue 模板插件",
|
||||
"version": "0.1.0",
|
||||
"api_version": 4,
|
||||
"entry": "index.html",
|
||||
"permissions": ["storage", "notify", "title", "net", "dialog"],
|
||||
"description": "用 Vue 3 + Vite 开发的 SVFS 插件模板,演示全部桥能力"
|
||||
}
|
||||
+22
File diff suppressed because one or more lines are too long
+14
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Vue 模板插件</title>
|
||||
<script type="module" crossorigin src="./assets/index-Cbfj95Ib.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CAnw4XNC.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 挂载点:样式由各组件自行负责(插件页面没有全局样式约束) -->
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"blob_dir": "blobs",
|
||||
"entries": {
|
||||
"assets": {
|
||||
"dir": true
|
||||
},
|
||||
"assets/index-CAnw4XNC.css": {
|
||||
"hash": "1252ac1b43e4d94d0a608eed0c76449112bee5f79613b66e6ec8c21b981a4dae",
|
||||
"mime": "text/css",
|
||||
"mode": 438,
|
||||
"mtime": 1789521653,
|
||||
"size": 1485
|
||||
},
|
||||
"assets/index-Cbfj95Ib.js": {
|
||||
"hash": "770e86ec29471f0553cb3bef36063ebcc6fcf2fc7902ccd5e5d16665a91425ad",
|
||||
"mime": "text/javascript",
|
||||
"mode": 438,
|
||||
"mtime": 1789521653,
|
||||
"size": 72828
|
||||
},
|
||||
"index.html": {
|
||||
"hash": "8030640addc4c64069ee0d5302b4884b262c0e3069d92db99940edc626538292",
|
||||
"mime": "text/html",
|
||||
"mode": 438,
|
||||
"mtime": 1789521653,
|
||||
"size": 501
|
||||
},
|
||||
"plugin.json": {
|
||||
"hash": "43fa3c1fed2269b7afa1872274a002ef52e9e4c97eb6c44eecb575928d8e7710",
|
||||
"mime": "application/json",
|
||||
"mode": 438,
|
||||
"mtime": 1789467293,
|
||||
"size": 287
|
||||
}
|
||||
},
|
||||
"format": "svfs-objects",
|
||||
"name": "packed_filesystem",
|
||||
"shard": 2,
|
||||
"snapshot": {
|
||||
"created_time": "2026-09-16T09:33:37.202155",
|
||||
"digest": "b3bd3c62b80df3f97922abca255c16035c6a546d65a7f36b29b4857670179729",
|
||||
"id": 1,
|
||||
"name": "packed_filesystem"
|
||||
},
|
||||
"stats": {
|
||||
"directories": 1,
|
||||
"files": 4,
|
||||
"objects": 4,
|
||||
"objects_stored_size": 30200,
|
||||
"total_size": 75101
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
Reference in New Issue
Block a user