Files
Commilitia-Drop/CHANGELOG.src.html
T
admin 1a8db5686a changelog:2026-06-26 鉴权并入 Auth Broker(委派设备会话统一模型)+ 四端迁移 + 显示名/头像修复
- 新增 2026-06-26 条目:后端委托 Auth Broker(路径 A,删自建鉴权)/ 统一“委派设备会话”模型 / 设备管理回归修复 / X-Auth-Name·X-Auth-Avatar 端到端补显示名与头像(含桌面 HealIdentity 自愈陈旧身份)/ 四端迁移(对应 main 10cf36e)
- 重新构建 CHANGELOG.html
2026-06-26 22:17:31 +08:00

258 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>cdrop — Changelog</title>
<meta name="generated-at" content="2026-06-16">
<meta name="source" content="cdrop · docs branch">
<!-- RUNTIME_CSS -->
</head>
<body>
<header class="page">
<div class="meta">
<h1>cdrop — Changelog</h1>
<p class="subtitle">时间倒序。细粒度迭代见 git 历史;本档只记录结构性 / 行为性变更与重要 fix。</p>
<p class="timestamp"><time>2026-06-16</time> · <span>docs branch</span></p>
</div>
<div class="actions">
<!-- ACTIONS -->
</div>
</header>
<main>
<p class="lede">本 Changelog 自 2026-05-22 起以 HTML 格式维护。此前之变更可从 main 分支 <code>git log</code> 取得;下表为近期较显著之 commit。</p>
<h2>2026-06-26 — 鉴权并入 Auth Broker:委派设备会话统一模型 · 四端迁移 · 显示名与头像修复</h2>
<p>cdrop 的登录 / 会话 / 设备 / 令牌生命周期并入既有 <strong>Auth Broker</strong>(边缘身份中介),删去 cdrop 内的自建鉴权副本;浏览器、桌面、手机一律收敛为同一套“委派设备会话”模型,统一管理;并修复部分客户端显示名为账户 UUID、头像缺失的回归。</p>
<ul>
<li><strong>后端委托 Auth Broker(路径 A</strong>:删自建 OIDC 交换 / 自签会话 / step-up / shortcut / <code>web_sessions</code> / <code>accounts</code> 及验证链,cdrop 不再存任何凭证;鉴权中间件改读边缘 <code>broker { app cdrop }</code> 注入的 <code>X-Auth-Subject/Name/Avatar/Scope/Meta</code> 头。新增 <code>internal/brokerclient</code>(委托铸造 / 吊销 / 续期 / 列举)</li>
<li><strong>统一会话模型“委派设备会话”</strong>:每个客户端 = 一条带 <code>meta</code>(device_id) + <code>label</code>(设备名) 的 broker 机器会话,Broker 作设备会话唯一注册表。浏览器全局 SSO、桌面设备授权(RFC 8252 loopback PKCE)、扫码批准,登录后一律“代铸”出设备会话(同一 device_id 幂等轮换、不再堆重复设备);cdrop 退化为薄覆盖层,会话真相与吊销授权一律走 Broker</li>
<li><strong>设备管理</strong>:浏览器 / 桌面 / 手机统一进一个设备列表,显示在线、当前设备与类型,可吊销、可改名(同 device_id 重铸换名、不产生重复行)。修复迁移引入的一组回归:设备列表为空 / 重复无名设备 / 看不到在线的另一台 / 吊销报“设备不存在”</li>
<li><strong>显示名与头像修复</strong>:经 Broker 端到端补 <code>X-Auth-Name</code> / <code>X-Auth-Avatar</code>,浏览器与桌面恢复真实显示名与头像(此前部分客户端显示账户 UUID、无头像)。桌面新增启动期 <code>HealIdentity</code>——注入前从 <code>/api/me</code> 自愈旧版烤进会话的陈旧身份并持久化,根治偶发 UUID</li>
<li><strong>四端迁移</strong>:后端、Web 前端、iOS、桌面(Wails)客户端同步迁移;存量会话于翻闸时失效、需重新登录</li>
<li>commit <code>10cf36e</code>main</li>
</ul>
<h2>2026-06-23 — P2P 传输修复:接收混合落盘 sink · 发送进度与完成语义 · 文字累积 · 速度显示</h2>
<p>修复 iOS PWA → Mac 桌面客户端大文件 P2P 传输约 15MB 处死锁,及随之暴露的两处发送端语义问题;并修一类前端 CJK 文字累积 bug、为传输卡片增设实时速度。</p>
<ul>
<li><strong>接收端混合有界内存 sink</strong>(桌面):<code>wails://</code> 自定义 scheme 无持久 WebView 存储 → OPFS 降级、<code>createWritable</code> 回压不畅,把接收 promise 链堵死、饿死 <code>dc.onmessage</code> → 接收方 SCTP <code>rwnd=0</code> → 发送方 <code>bufferedAmount</code> 卡死 16MiB(即“PWA 16.0MB / Mac 0B、约 15MB 卡住”)。改为:小文件(暂存 &lt; 256MB)纯内存、<code>close</code> 整文件过 Go 写盘;超上限大文件流式 spill 到 Go 临时文件(每 64MB 一批),内存恒定有界。浏览器 / iOS 接收仍走 OPFS(其内存预算紧、又无 Go 落盘,OPFS 才有意义)</li>
<li><strong>桌面流式落盘</strong><code>download.go</code> 新增 <code>Begin/Append/Finalize/AbortStreamingDownload</code>(复用 <code>SaveDownload</code> 的目录解析 / 文件名 sanitize / 不可写回退 / reveal,临时文件与目标同目录便于 finalize 原子 rename),<code>app.go</code> 加 4 个绑定 + <code>net/desktop.ts</code> base64 封装;<code>IncomingSink.close()</code> 返回 <code>SinkResult</code><code>blob</code> 已落盘 <code>path</code>),<code>deliverIncoming</code> 统一落地;relay 接收一并改用,桌面大文件经中继也有界落盘。含单测 <code>download_stream_test.go</code></li>
<li><strong>发送端进度按“已交付字节”计</strong>:原先用 <code>bytesSent</code>(已塞进本地 16MiB buffer 的量),buffer 被秒满后定格、长 backpressure 等待期误报“疑似卡住”。改用 <code>bytesSent bufferedAmount</code>(已离开本地 buffer ≈ 已交付)+ 250ms 轮询在等待期持续刷新,进度随接收端实际收程平滑爬升、不误判 stalled</li>
<li><strong>发送端等抽干再宣告完成</strong>:原先送完入 buffer 即 <code>setState("completed")</code>,此时仍可能有多达 16MiB 在途,与接收端“仍在下载”状态不一致。改为送完 + done 帧后等 <code>bufferedAmount</code> 落到 0(字节已全部交付网络)再宣告完成</li>
<li><strong>前端动态 CJK 文字累积修复</strong>:聚珍 shim 把会就地更新的 CJK 文本节点 charify 拆段后,旧碎片成了 React 不再追踪的兄弟节点残留、每更新一次累积一个多余字(典型“发送到 X”切设备后多出的“到”)。新增 <code>DynText</code><code>key</code>=文本内容,内容一变即整体卸载旧 span、连同 shim 碎片,再挂新 span 交 shim 处理一次——既消累积又<strong>保留</strong>中西排版,区别于 <code>data-jz-skip</code> 那样关掉排版),应用于发送按钮 / 设备名 / 在线数 / 相对时间 / 会话与令牌活跃时间 / 消息计数 / 剪贴板来源 / 问候语等就地更新处</li>
<li><strong>传输卡片实时速度</strong>store <code>upsertTransfer</code> 据相邻样本的字节 / 时间差分 + EMA(α=0.3)平滑算 <code>bytesPerSec</code><code>TransferRow</code> 仅在字节流动且未卡死、且速率为正时以“X/秒”展示(点分隔 meta 行追加),卡死时既有“似乎卡住了”状态已表达停滞、不显误导性 0 速</li>
<li>commit <code>0451549</code>main</li>
</ul>
<h2>2026-06-22 — 账户与登录子系统:扫码快登 · 自签会话 · OIDC 统一 · 即时吊销 · 设备=会话</h2>
<p>“OAuth 为账户来源与凭证提供者、cdrop 自维护薄账户层 + 自签会话”的折中架构落地(蓝本见仓库 <code>AUTH.md</code>)。一台设备 = 一条会话,吊销即时生效、被吊销设备自动回到未登录态。</p>
<ul>
<li><strong>扫码快速登录</strong>:陌生设备显码、已登录手机扫码批准(微信 / WhatsApp 网页版模型)。三密钥分离(<code>request_id</code> / <code>approval_code</code> / 仅新设备私有的 <code>poll_secret</code>),偷拍 QR 无法领取会话;会话只经新设备自身轮询连接下发 cookie。两档:“信任此设备”(<code>scope=full</code>、7 天滑动) / “仅此次”(<code>scope=guest</code>、1 小时受限),默认偏安全的“仅此次”。访客会话服务端强制受限(<code>requireFullSession</code> 守门)</li>
<li><strong>薄账户层 + 自签会话令牌</strong>:新增 <code>accounts</code> 表(cdrop 侧账户数据、不含凭证,存稳定 <code>match_key</code> 供管理员开启迁移时跨源关联);<code>web_sessions</code> 泛化出 <code>kind</code> / <code>scope</code> / <code>granted_by</code> / <code>stepped_up_at</code>。access token 改 cdrop 自签 HS256(密钥自 <code>CDROP_SESSION_SECRET</code> 域分离派生),脱离 IdP refresh、短 TTL(默认 900s)、载 <code>sid</code></li>
<li><strong>OIDC 统一到自签 token</strong>:浏览器登录(含 OIDC)一律持自签 token —— <code>/auth/exchange</code> 先 JWKS 验 <code>id_token</code> 签名(唯一信任锚)再签自签 token,<code>/auth/refresh</code> 仍打 IdP 轮换 refresh_token 但回自签 token。RS256 退出浏览器每请求热路径,仅保留分支供桌面端 loopback 直带 IdP token</li>
<li><strong>吊销即时 + 设备自知</strong><code>verifySelfToken</code> 每请求按 <code>sid</code> 查会话行,行删即拒 → 吊销在下次请求即生效(不等 TTL)。前端仅在“服务端以 401 确证失效”时清登录态回登录页(短期连接失败 / 5xx 不误清);离线设备下次使用即知,在线设备经 <code>Hub.Kick</code> 关流、约 1s 重连撞 401 即知</li>
<li><strong>设备列表 = 会话列表</strong>:“登录会话”面板统一 <code>web_sessions</code> 与原生客户端设备(桌面 / iOS,自 <code>devices</code> 登记呈现,<code>native=true</code>)。吊销网页会话连带删其设备登记、即时从所有列表消失;孤儿 <code>browser</code> 设备由清扫器(<code>DeleteOrphanBrowserDevices</code>)自动清除。原生设备“登出”走 <code>DELETE /api/devices/{name}</code>(同要求 step-up</li>
<li><strong>2FA step-up 钩子</strong>(默认关,<code>CDROP_STEP_UP_ENABLED</code>):批准扫码设备 / 吊销会话 / 注销设备等敏感动作要求一次现场 <code>prompt=login</code> 再认证(provider 的 2FA 即在此过),按会话绑定、<code>StepUpMaxAgeSeconds</code>(默认 300s)窗口内不复要求。<code>auth_time</code> 可选(Casdoor 不下发)</li>
<li><strong>应用内扫码器 + 聚珍崩溃修复</strong>:登录页内置 <code>getUserMedia</code> + jsqr 扫码(不再外跳系统应用、避免开错浏览器);扫码 / 批准等三状态机页整页跳过聚珍(<code>data-jz-skip</code>),修 MutationObserver 与 React 重渲染同子树冲突的 <code>insertBefore</code> 崩溃</li>
<li>蓝本 <code>AUTH.md</code>commit 待提交</li>
</ul>
<h2>2026-06-18 — 推送通知:网页 / PWA Web Push + 桌面原生通知</h2>
<p>页面 / PWA 关闭、或桌面窗口非前台时也能收到系统通知;判定统一为“页面打开走应用内提示,关闭则系统通知”,三端各自落地。</p>
<ul>
<li><strong>网页 / PWAWeb Push, VAPID</strong>:服务端在事件未经 SSE 投递到设备时(即该设备页面已关)发 Web Push —— 收到文件、消息、传输完成 / 失败均可。新增 <code>push_subscriptions</code> 表(含 <code>locale</code>,主键为 endpoint 的 SHA-256 以幂等 upsert+ <code>/api/push/{vapid-key, subscribe}</code> 端点 + <code>internal/push</code> 发送器(404 / 410 自动清理死订阅)。文案按订阅设备语言在服务端渲染(中简 / 中繁 / 英)。覆盖浏览器 / Android / iOS 16.4+ 已安装 PWA</li>
<li><strong>桌面客户端原生通知</strong>:仅窗口非前台时弹原生系统通知(前台仍走应用内提示),复用 WebView 事件分发 + 客户端本地化。macOS 走 <code>UNUserNotificationCenter</code>(未签名时安全空操作,待签名 / 公证后显示)、Windows 走原生 toast</li>
<li><strong>判定复用既有信号</strong>:网页端“页面是否打开”直接取 <code>Hub.SendTo</code> 的投递结果,零额外状态;离线消息改返 202,文本随推送送达</li>
<li><strong>开关与配置</strong>:设置页新增“推送通知”面板(仅浏览器 / PWA),登录后自动把既有订阅重新登记到当前用户;prod 可选设 <code>CDROP_VAPID_PUBLIC_KEY</code> + <code>CDROP_VAPID_PRIVATE_KEY</code><code>just vapid-keygen</code> 生成一对,二者须同时设置,都留空则推送惰性关闭)</li>
<li>commit <code>92a03ae</code></li>
</ul>
<h2>2026-06-16 — 浏览器免重登 + 设备名持久化</h2>
<p>浏览器端不再每次重启都要重新登录;长寿命 refresh_token 从此完全不进 JS,改由服务端 HttpOnly cookie 会话持有。对长寿命凭据而言比原先(refresh_token 在 JS 内存、开页期 XSS 可读)严格更安全。</p>
<ul>
<li><strong>服务端会话表 + cookie</strong>:新增 <code>web_sessions</code> 表(sqlc)。<code>/auth/exchange</code> 换完 token 后建会话并下发 cookie<code>HttpOnly; Secure; SameSite=Lax; Path=/api/auth</code>),响应体不再回传 refresh_tokenrefresh_token 以 AES-256-GCM 落盘(密钥取自 <code>CDROP_SESSION_SECRET</code>,在 env、不在库内),cookie 只放不透明随机串、表 id 存其 SHA-256 —— DB 单独泄露既换不出可用 cookie、也解不出 token</li>
<li><strong>开机静默免重登</strong><code>/auth/refresh</code> 改为 cookie 驱动(7 天滑动失活)—— 这条同时就是开机静默免重登路径,前端首屏渲染前 <code>bootstrapAuth</code> 用 cookie 续期,命中直接进已登录态、避免登录页闪现;新增 <code>/auth/logout</code>(删会话 + 清 cookie)、<code>/auth/device</code>(写设备名入会话)</li>
<li><strong>设备名持久化</strong>:设备名随会话存,开机 refresh 带回前端,抗 iOS PWA 存储清除 —— localStorage 被驱逐也能恢复,不再误跳设置页</li>
<li><strong>多 tab 并发 refresh 防护</strong>:同一用户多 tab 共享一个 cookie → 一个 refresh_token,加 striped per-session 锁串行化并发 refresh,防一次性 refresh_token 被花两次而把用户从所有 tab 一起登出</li>
<li><strong>凭据面收紧</strong>:前端 store 彻底移除 <code>refreshToken</code> 字段,浏览器侧长寿命凭据不再触及 JS。CSRF 由 SameSite=Lax + Origin 校验双保险。桌面端不受影响(自有 loopback flow + 系统密钥库,不碰这两端点)</li>
<li><strong>部署要求</strong>prod 须设 <code>CDROP_SESSION_SECRET</code>(任意长度高熵串,内部 SHA-256 派生 AES-256 密钥),缺则拒启动</li>
<li>commit <code>e51666c</code></li>
</ul>
<h2>2026-06-15 — PWA 安装支持 + Safari OAuth 回跳卡顿修复</h2>
<ul>
<li><strong>PWA 安装支持</strong>:web 可“添加到主屏幕”独立启动(manifest + iOS meta + 同源静态壳 service worker)。SW 绝不拦截 <code>/api</code>(SSE / 剪贴板 / 中继需活连接)、亦不拦导航;PKCE verifier 改 <code>localStorage</code>,使独立 PWA 在 OAuth 回跳重启上下文后仍能完成登录。面向用户短名统一小写 <code>cdrop</code></li>
<li><strong>Safari OAuth 回跳卡顿修复</strong>Safari / iOS PWA 从 IdP 回跳登录后,<code>setup.js</code> 与头像约 2 分钟拿不到连接(Chrome 正常)。根因是 <code>oauth.callback</code> 登录成功后用路由软导航,复用了 OAuth 流程遗留的 HTTP 连接,这些连接在 Safari 下卡死后续资源请求;改为整页跳转(<code>window.location.replace</code>)拿全新连接绕开。曾误判 service worker / h3 为主因,均已排除</li>
<li>commit <code>5c28ddf</code>PWA 安装)/ <code>7082ccc</code>(短名)/ <code>af754dc</code> · <code>8f550ad</code>Safari 修复)</li>
</ul>
<h2>2026-06-14 — 安全加固:中继会话防耗尽 + 令牌入系统密钥库 + 鉴权 / 限流多项收紧</h2>
<p>多 agent 安全审计后的一轮加固,按信任模型(信任账号所有者 + 服务端;不信任跨用户 / 网络 hop peer / 本机其他软件)逐项收紧。</p>
<ul>
<li><strong>中继会话防耗尽重构</strong>:中继改为“预登记”模型 —— 传输进入 <code>RELAY_ACTIVE</code> 时由服务端登记其两个合法参与方,之后仅已登记会话可接入、且调用方须为 sender / receiver。叠加 per-user 配额(3)+ 全局上限(8+ 空闲回收(2min+ ring 单 reader 断言。一举堵住“任意 id 凭空铸造会话耗尽全实例并钉 512 MiB”“幽灵会话”“id 泄露后无第二道防线”“双 reader 撕裂数据”四类问题</li>
<li><strong>桌面 refresh_token 入系统密钥库</strong>:长效 refresh_token 不再明文落盘,改存系统密钥库(macOS Keychain 经 <code>security</code> / Windows Credential Manager·DPAPI / Linux Secret Service),同机软件无法直读冒充;会话文件只留短效 access_token。遗留明文文件首次启动自动迁移并抹除</li>
<li><strong>HS256 token 强制 jti</strong>HS256 路径只签 clipboard 限域的快捷指令 token(必带 jti),无 jti 的有效签名一律拒签,把密钥泄露的爆炸半径压回 clipboard-only</li>
<li><strong>prod 强制 OIDC audience</strong>prod 下 <code>CDROP_OIDC_AUDIENCE</code> 为空即拒绝启动 —— 空 audience 会跳过校验、令同一 JWKS 下兄弟应用的 token 也能验过(跨 app 冒充)。<strong>部署要求</strong>:prod 须设该变量(web + 桌面 client_id 逗号分隔)</li>
<li><strong>请求面收紧</strong>:信令 / 文本消息加请求体上限(防 OOM);内置 TURN 凭据 TTL 由 24h 降至 1h(缩泄露窗口 24×);<code>/auth/exchange</code><code>/auth/refresh</code> 加 per-IP 限流(防把服务当 OIDC 暴力跳板)</li>
<li>commit <code>bcdc2b4</code>(中继 / HS256 / 密钥库 / body 上限)·<code>c536a35</code>audience / TURN TTL / 限流)</li>
</ul>
<h2>2026-06-14 — 桌面文件落盘可配置 + 传输列表行内操作 + macOS 下载兜底修正</h2>
<ul>
<li><strong>桌面端接收文件落盘到可配置目录</strong>(默认系统 <code>~/Downloads</code>):设置页加“下载目录”+ 原生目录选择框(<code>ChooseDownloadDir</code> → Wails <code>OpenDirectoryDialog</code>)。机制为 Go 绑定写盘 —— 桌面模式下 <code>downloadBlob</code> 改走 <code>SaveDownload</code>blob → base64 过桥 → Go 写盘),对端文件名经 <code>sanitizeFileName</code> 剥目录成分防路径穿越、重名加 <code>(n)</code>、落盘后 <code>open -R</code> / <code>explorer /select</code> 揭示</li>
<li><strong>macOS 下载兜底修正</strong>WKWebView 不处理 <code>&lt;a download&gt;</code><code>blob:</code>,原“失败回退浏览器下载”在 macOS 是空操作、会静默丢文件;改为写配置目录失败时自动改写系统 <code>~/Downloads</code>,连默认目录也失败才显式报错(<code>toast.error</code>),不再丢文件</li>
<li><strong>传输列表行内操作</strong>:每行加 —— 非终态“取消”(拆本端 P2P / relay + <code>POST /transfer/{id}/cancel</code>)、终态“删除”(从 active + history 移除)、发送方仍在试 P2P 时“立即中继”(清 30s ICE watchdog + <code>POST /transfer/{id}/fallback</code>,跳过等待)</li>
<li>双端桌面客户端(macOS universal <code>.app</code> + Windows <code>.exe</code>)重构并同步;web 端 prod 部署(<code>drop.commilitia.net</code></li>
<li>commit <code>43ddf36</code>(下载路径)/ <code>10a4c99</code>(传输 UX + 兜底修正)</li>
</ul>
<h2>2026-06-12 — 接入聚珍(Juzhen2.0 综合 CJK 排版:全角式 + 功能分级 + 本地优先字体</h2>
<ul>
<li>submodule <code>cjk-autospace</code> 升级 <code>a5faec5 → a3c94dd</code>:v1 自动空格 shim 从零重写为“聚珍(Juzhen)”2.0 综合排版引擎 —— 中西间隙 / 标点挤压(全角式)/ 禁则避头尾 / 垂悬字避免 / 长英文断词 / 功能分级;JS 只注入 <code>jz-*</code> 结构、视觉尺寸全在 <code>juzhen.css</code>(必配)</li>
<li><strong>功能分级</strong>:功能分文本级(中西间隙 / 标点挤压,任意文本片段适用)与段落级(禁则 / 垂悬字 / 长词断字,需行段布局,对单行 UI 文本无意义甚至有害)。薄壳 <code>level:{text:".juzhen"}</code> 令全树默认文本级(cdrop 绝大多数是 UI chrome);消息正文 / 剪贴板内容 / Callout / Toast 正文 / Field hint / 登录说明等 8 处流动正文加 <code>data-jz-level="paragraph"</code> opt-in 段落级,失败模式良性</li>
<li><strong>字体逐 region 明确化</strong><code>fonts.css</code><code>:lang()</code> 切 SC/TC/JP/KR):Sans Orbit Gothic CJKSerif Noto Serif CJK(本地合一)+ Noto Serif &lt;R&gt;(Web 子集、跨区并集覆盖);Mono Maple Mono + Orbit Gothic CJK</li>
<li><strong>本地优先字体层</strong>(新增 <code>fontface.css</code>):CDN 的 <code>@font-face</code><code>local()</code> 且 family 名与本机安装同名 → 被捕获、永远走网络。本层在 CDN <code>&lt;link&gt;</code> 之后用 <code>src: local(), url()</code> 重声明项目用到的字重,本机命中即零网络、否则回退 CDN(swap)</li>
<li>Badge 加固(children 包单个 inline span,防聚珍注入元素在 <code>inline-flex</code> gap 中各成 flex item 致 gap 叠加);术语“Pangu”统一改“聚珍”;commit <code>907d132</code> / <code>b3c74a6</code></li>
</ul>
<h2>2026-05-22 — README 补 clone 注意事项</h2>
<ul>
<li>新 clone 之 repo 预设不自动 fetch submodule<code>web/src/lib/cjk-autospace/</code> 为空时 <code>vite build</code> 会因找不到 import 而失败;错误信息虽含路径但不直观</li>
<li><code>README.md</code> 顶部加“克隆”小节,指引 <code>git clone --recurse-submodules &lt;URL&gt;</code> 或补拉 <code>git submodule update --init --recursive</code></li>
<li>commit <code>1443543</code></li>
</ul>
<h2>2026-05-22 — cjk-autospace 抽出为共用 submodule</h2>
<ul>
<li>把原内联在 <code>web/src/utils/cjkAutospace.ts</code> 的 Pangu shim 抽出为上游 git 仓库 <a href="https://git.commilitia.net/admin/cjk-autospace">cjk-autospace</a>(双 pass 架构:跨样式标签透明 + 行内块边界,commit <code>c3defc4</code></li>
<li>以 git submodule 引入 <code>web/src/lib/cjk-autospace/</code><code>cjkAutospace.ts</code> 缩为 ~50 行薄壳,只负责 cdrop 的 <code>pillSelector="code, kbd, .cjk-pill"</code> 配置 + React 下的 MutationObserver 增量观察(自反馈防护、<code>queueMicrotask</code> 防同 batch 重排)</li>
<li>单一真实源:lib 修改一次三端(HTML 模板 / Peek.nvim shim / cdrop)同步生效;本仓库 deploy 时 Vite 把 submodule 检出之文件当一般 TS / JS 模块打包,运行时不读 Dropbox 也不依赖 git 元数据</li>
<li>架构文档(<code>PROJECT_BRIEF.md</code> + <code>FRONTEND_DESIGN.md</code>+ Changelog 移到 docs 分支(本档)</li>
</ul>
<h2>历史变更(移转前未以本档记录)</h2>
<table>
<thead>
<tr><th>commit</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><code>aa80793</code></td><td>CJK 排版子系统:Pangu.js v7.2.1 端口 + cjk.css + Button label 包裹</td></tr>
<tr><td><code>4e8c541</code></td><td>前端视觉重塑后续迭代 + i18n 本地化清理</td></tr>
<tr><td><code>81dfbe1</code></td><td>前端重设计:tokens 变量化 + features-first + 原子组件 + 首页重画 + Toast + Dracula Pro 主题</td></tr>
<tr><td><code>6db6444</code></td><td>新增繁体中文(臺灣)+ locale 检测按 Hans / Hant + 区域子标签细分</td></tr>
</tbody>
</table>
</main>
<footer class="page">
<p>cdrop · docs branch · CHANGELOG</p>
</footer>
<!-- RUNTIME_SPRITE -->
<script type="text/markdown" id="md-source">
# cdrop Changelog
时间倒序细粒度迭代见 git 历史本档只记录结构性 / 行为性变更与重要 fix
## 2026-06-18 推送通知网页 / PWA Web Push + 桌面原生通知
页面 / PWA 关闭或桌面窗口非前台时也能收到系统通知判定统一为页面打开走应用内提示关闭则系统通知
- 网页 / PWAWeb Push, VAPID服务端在事件未经 SSE 投递到设备时发 Web Push收到文件 / 消息 / 传输完成 / 失败新增 push_subscriptions locale主键为 endpoint SHA-256+ /api/push/{vapid-key, subscribe} + internal/push 404 / 410 / / / Android / iOS 16.4+ PWA
- 桌面客户端原生通知仅窗口非前台时弹前台走应用内macOS UNUserNotificationCenter未签名时安全空操作待签名后显示Windows 原生 toast
- 判定复用 Hub.SendTo 的投递结果零额外状态离线消息改返 202文本随推送送达
- 设置页新增推送通知面板仅浏览器 / PWAprod 可选设 CDROP_VAPID_PUBLIC_KEY + CDROP_VAPID_PRIVATE_KEYjust vapid-keygen 生成都留空则惰性关闭
- commit 92a03ae
## 2026-06-16 浏览器免重登 + 设备名持久化
浏览器端不再每次重启都要重新登录长寿命 refresh_token 完全不进 JS改由服务端 HttpOnly cookie 会话持有比原先 refresh_token JS 内存严格更安全
- **服务端会话表 + cookie**新增 `web_sessions` sqlc`/auth/exchange` 建会话并下发 `HttpOnly; Secure; SameSite=Lax; Path=/api/auth` cookie响应体不再回传 refresh_tokenrefresh_token AES-256-GCM 落盘密钥 `CDROP_SESSION_SECRET` env不在库内cookie 只放不透明随机串 id 存其 SHA-256 DB 单独泄露既换不出可用 cookie也解不出 token
- **开机静默免重登**`/auth/refresh` cookie 驱动7 天滑动失活前端首屏前 `bootstrapAuth` 续期命中直接进已登录态新增 `/auth/logout``/auth/device`写设备名入会话
- **设备名持久化**设备名随会话存开机 refresh 带回前端 iOS PWA 存储清除localStorage 被驱逐也能恢复不再误跳设置页
- ** tab 并发 refresh 防护** tab 共享一个 cookie 一个 refresh_tokenstriped per-session 锁串行化并发 refresh防一次性 token 被花两次而全端登出
- **凭据面收紧**store 移除 `refreshToken` 字段浏览器侧长寿命凭据不再触及 JSCSRF SameSite=Lax + Origin 校验双保险桌面端不受影响
- **部署要求**prod 须设 `CDROP_SESSION_SECRET`任意长度高熵串缺则拒启动
- commit `e51666c`
## 2026-06-15 PWA 安装支持 + Safari OAuth 回跳卡顿修复
- **PWA 安装支持**web 添加到主屏幕独立启动manifest + iOS meta + 同源静态壳 SWSW 绝不拦截 `/api`亦不拦导航PKCE verifier `localStorage`使独立 PWA OAuth 回跳重启上下文后仍能完成登录短名统一小写 `cdrop`
- **Safari OAuth 回跳卡顿修复**Safari / iOS PWA 回跳登录后 `setup.js` 与头像约 2 分钟拿不到连接Chrome 正常根因是 `oauth.callback` 成功后路由软导航复用了 OAuth 流程遗留的 HTTP 连接 Safari 下卡死后续资源改为整页跳转`window.location.replace`拿全新连接绕开
- commit `5c28ddf`PWA/ `7082ccc`短名/ `af754dc` · `8f550ad`Safari 修复
## 2026-06-14 安全加固中继会话防耗尽 + 令牌入系统密钥库 + 鉴权 / 限流多项收紧
agent 安全审计后的一轮加固按信任模型信任账号所有者 + 服务端不信任跨用户 / 网络 hop peer / 本机其他软件逐项收紧
- **中继会话防耗尽重构**中继改为预登记模型 传输进入 `RELAY_ACTIVE` 时服务端登记两个合法参与方之后仅已登记会话可接入且调用方须为 sender / receiver叠加 per-user 配额3+ 全局上限8+ 空闲回收2min+ ring reader 断言堵住任意 id 铸造会话耗尽实例并钉 512 MiB幽灵会话id 泄露 reader 撕裂四类
- **桌面 refresh_token 入系统密钥库**长效令牌不再明文落盘改存 macOS Keychain / Windows DPAPI / Linux Secret Service同机软件无法直读会话文件只留短效 access_token遗留明文文件首次启动自动迁移并抹除
- **HS256 token 强制 jti** jti 的有效签名一律拒签把密钥泄露爆炸半径压回 clipboard-only
- **prod 强制 OIDC audience**`CDROP_OIDC_AUDIENCE` 为空即拒绝启动 audience 跳过校验 兄弟 app 跨冒充部署须设该变量web + 桌面 client_id 逗号分隔
- **请求面收紧**信令 / 消息加 body 上限TURN 凭据 TTL 24h 1h`/auth/exchange` + `/auth/refresh` per-IP 限流
- commit `bcdc2b4` · `c536a35`
## 2026-06-14 桌面文件落盘可配置 + 传输列表行内操作 + macOS 下载兜底修正
- **桌面端接收文件落盘到可配置目录**默认系统 `~/Downloads`设置页加下载目录+ 原生目录选择框机制为 Go 绑定写盘blob base64 过桥 `SaveDownload`文件名 `sanitizeFileName` 防路径穿越重名加 `(n)`落盘后揭示 Finder / Explorer
- **macOS 下载兜底修正**WKWebView 不处理 `<a download>` blob回退浏览器下载 macOS 空操作会丢文件改为配置目录失败时改写系统 `~/Downloads`仍失败才 `toast.error`
- **传输列表行内操作**非终态取消`/cancel`终态删除移除记录发送方立即中继 ICE watchdog + `/fallback`跳过 P2P 等待
- 双端桌面macOS universal `.app` + Windows `.exe`重构并同步web prod 部署
- commit `43ddf36` / `10a4c99`
## 2026-06-12 接入聚珍Juzhen2.0 综合 CJK 排版全角式 + 功能分级 + 本地优先字体
- submodule `cjk-autospace` 升级 `a5faec5 → a3c94dd`v1 自动空格 shim 重写为聚珍Juzhen2.0 综合排版引擎中西间隙 / 标点挤压全角式 / 禁则 / 垂悬字避免 / 长词断字 / 功能分级
- **功能分级**文本级间隙 / 标点挤压任意文本vs 段落级禁则 / 垂悬字 / 长词需行段布局薄壳 `level:{text:".juzhen"}` 全树默认文本级8 处流动正文加 `data-jz-level="paragraph"` opt-in 段落级
- **字体逐 region 明确化**`fonts.css`+ **本地优先字体层**新增 `fontface.css``local()` 优先覆盖 CDN url `@font-face`本机命中零网络
- Badge 加固术语 Pangu 聚珍commit `907d132` / `b3c74a6`
## 2026-05-22 README clone 注意事项
- `README.md` 顶部加克隆小节指引 `git clone --recurse-submodules <URL>` `git submodule update --init --recursive`避免 `web/src/lib/cjk-autospace/` 为空时 vite build 失败
- commit `1443543`
## 2026-05-22 cjk-autospace 抽出为共用 submodule
- 把原内联在 `cjkAutospace.ts` Pangu shim 抽出为上游 git 仓库 cjk-autospace pass 架构跨样式标签透明 + 行内块边界
- git submodule 引入 `web/src/lib/cjk-autospace/``cjkAutospace.ts` 缩为 ~50 行薄壳
- 架构文档 + Changelog 移到 docs 分支
## 历史变更
- `aa80793` CJK 排版子系统Pangu.js v7.2.1 端口
- `4e8c541` 前端视觉重塑后续迭代
- `81dfbe1` 前端重设计
- `6db6444` 新增繁体中文 locale 检测
</script>
<!-- RUNTIME_JS -->
</body>
</html>