news 2026/9/10 14:36:46

Folo Desktop v1.7.0 源码级解读:认证 Cookie 治理、OTA 更新决策与桌面导航修复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Folo Desktop v1.7.0 源码级解读:认证 Cookie 治理、OTA 更新决策与桌面导航修复

Folo Desktop v1.7.0 源码级解读:认证 Cookie 治理、OTA 更新决策与桌面导航修复

【免费下载链接】follow🧡 Folo is the AI RSS Reader项目地址: https://gitcode.com/GitHub_Trending/fol/follow

本文面向桌面端开发者与 Folo 高级用户,以 v1.7.0 官方变更日志 为骨架,逐一深入其背后实现:认证会话 Cookie 的去重与刷新、Mac App Store(MAS)分发下的 OTA 更新判定、Discover 深链导航,以及 API 请求超时策略。读完你会理解 Folo 桌面端主进程(Electron main process)在「登录态一致性」与「更新决策」两条链路中的真实工程细节,并能据此排查自己桌面应用中遇到的重复 Cookie、登出/刷新失效、分发商店更新误报等同类问题。

版本全景:一次聚焦稳定性的桌面端迭代

Folo 是一个跨平台的 AI RSS 阅读器(Monorepo 见 packages 与 apps 结构),桌面端应用位于 apps/desktop。v1.7.0 是一次典型的“稳定性迭代”,变更日志(apps/desktop/changelog/1.7.0.md)内容如下:

  • Improvements
    • 移除桌面应用中的连接状态指示器
  • No longer broken
    • 修复重复的桌面认证会话 Cookie
    • 修复 Cookie 更新后的会话刷新
    • 修复从 Discover 路由返回
    • 修复桌面下载链接
    • 修复从 OTA 版本检测 MAS 审核状态
    • 延长 API 请求超时

其中绝大多数修复都落在主进程目录 apps/desktop/layer/main/src 的认证(authauth-cookies)与更新(updater)子系统中,并配有对应单元测试(如auth-cookies.test.tsupdater/api.test.tsupdater/index.test.ts),因此可以用源码逐条还原“为什么会出现问题”与“现在如何被解决”。

Improvements:移除桌面连接状态指示器

v1.7.0 在界面层面做减法:去掉了桌面客户端里常驻的连接状态指示器,让顶部布局更干净。从渲染层现状看,主布局 MainDestopLayout.tsx 中只保留了对“环境”而非“网络连接”的提示:仅当非生产环境(!PROD)时才渲染 EnvironmentIndicator.tsx——实际文件为 EnvironmentIndicator.tsx,用于标识当前运行环境而非联网状态;真正的网络连通性问题应通过请求超时、重试与同步队列(如 SyncIndicator.tsx)反馈给用户。此次移除意味着:

  • 网络状态不再以常驻徽标干扰阅读主界面,回归“阅读器优先”的交互;
  • 断网感知职责转向具体操作的错误提示与自动重试。

注:此条为界面取舍,代码上无对应“删除 diff”可引用,以上为该组件当前职责与布局的描述,可作为理解改动的背景。

修复 1:重复桌面认证会话 Cookie

问题背景

Folo 桌面端使用 better-auth 做账号体系。登录后服务端会种下多组 Cookie,例如:

  • better-auth.session_token(非安全前缀)
  • __Secure-better-auth.session_token(Secure 前缀)
  • better-auth.session_data及配套的dont_remembertrust_devicetwo_factor等状态 Cookie

由于桌面端需要同时支持「主窗口内登录」与「主进程直接携带 Cookie 调用 API」,如果同一名称的 Cookie 因securehostOnlydomainpath差异在 Electron session 中出现多份,拼装请求头时就会重复携带甚至互相覆盖,导致鉴权不稳定(出现重复的会话 Cookie)。

解决方案:统一的受管 Cookie 清单 + 写入前清理 + 去重

主进程 auth-cookies.ts 是该修复的核心实现。它把 better-auth 会写入的所有 Cookie 名集中收编:

const MANAGED_AUTH_COOKIE_NAMES = [ BETTER_AUTH_SECURE_SESSION_TOKEN_COOKIE_NAME, BETTER_AUTH_SESSION_TOKEN_COOKIE_NAME, "__Secure-better-auth.session_data", "better-auth.session_data", "better-auth.last_used_login_method", "dont_remember", "__Secure-better-auth.dont_remember", "better-auth.dont_remember", "trust_device", "__Secure-better-auth.trust_device", "better-auth.trust_device", "two_factor", "__Secure-better-auth.two_factor", "better-auth.two_factor", ] as const

关键设计有三点:

  1. 写入前先清旧值applySessionToken()(见 auth.ts)在写入新 token 前,会按名字removeManagedAuthCookies清掉旧session_token,再以统一的path: "/"httpOnly: truesameSite: "no_restriction"写入,最后调用去重例程。
  2. 解析并复写服务端 Set-CookiepersistManagedAuthCookiesFromSetCookieHeader()(auth-cookies.ts)手写了解析器:splitSetCookieHeader()能正确处理Expires中带逗号的多段头;对maxAge <= 0或已过期的 Cookie 只删不写;写入后再统一dedupe
  3. 最终去重兜底dedupeManagedAuthCookies()(auth-cookies.ts)对每个受管名字挑选“最优”的那份——评分函数getCookieHeaderPriority()认为hostOnly、值中含.securepath === "/"更可信:
const getCookieHeaderPriority = (cookie: ManagedAuthCookie) => { let priority = 0 if (cookie.hostOnly) priority += 8 if (cookie.value.includes(".")) priority += 4 if (cookie.secure) priority += 2 if ((cookie.path ?? "/") === "/") priority += 1 return priority }

此外,一旦存在__Secure-better-auth.session_token,非 Secure 前缀的旧session_token会被视为冗余而移除(auth-cookies.ts),保证发出请求时每种 Cookie 至多一份、且永远优先携带最可信的会话令牌。对应逻辑由 auth-cookies.test.ts 覆盖。

修复 2:Cookie 更新后的会话刷新

问题背景

修复 1 解决的是“Cookie 变多”,修复 2 解决的是“Cookie 变了之后会话没跟上”。典型场景:主进程在某次带认证的请求中收到服务端Set-Cookie(例如刷新了 session token、或触发了 TOTP 验证后的会话升级),但渲染层仍在用旧的会话缓存数据,导致界面显示与真实登录态不一致,甚至被服务端拒绝后才被迫重登。

当前实现链路

主进程在每次与认证相关的请求后都会持久化响应中的 Cookie:auth.ts 的persistAuthCookiesFromResponse()会把set-cookie全量交给上文的管理器写入并去重;它被requestCredentialAuth(邮箱登录/注册)、verifyTotp(两步验证)以及带认证的fetchWithAuth统一调用。

会话变化后需要“通知”渲染层失效旧缓存。桌面渲染层把主进程能力通过window.expose暴露,见 extension-expose-provider.tsx:

refreshSession: invalidateUserSession,

即主进程/深链触发的refreshSession最终映射为失效用户会话相关 query,让 React Query 重新拉取最新登录态;配套的sessionChanged()(auth.ts)还会把当前桌面会话同步到 npm 版 CLI(folocli)的登录态,相关实现见 cli-session-sync.ts 的syncSessionToCliConfig()

另外,桌面端支持通过深链手动刷新会话:协议路由处理器 router.ts 中/refresh会调用caller.refreshSession(),这为「Cookie 更新后强制重刷」提供了外部触发入口。

修复 3:从 Discover 路由返回

Discover 是 Folo 的 RSSHub/内容发现功能。修复点在于“从 Discover 路由返回”时的导航状态:当用户通过协议深链或内部跳转进入 Discover 某个 RSSHub route 后,再返回探索页应回到正确位置,而不是丢失上下文或触发错误查询。

相关实现与数据流:

  • 主进程深链入口:handleUrlRouting()/discover?route=xxx解析 route 参数并调用caller.rsshubRoute(route)(router.ts);
  • 渲染层暴露端:rsshubRoute(route)的桥接处理位于 extension-expose-provider.tsx;
  • 数据层:Hook useDiscoverRSSHubRoute.tsx 通过useAuthQuery(discover.rsshubRoute({ route }))加载数据,对应 API 封装在 queries/discover.ts。

v1.7.0 的修复目标是确保这些路由状态在“进入 Discover → 加载某个 route → 返回”的闭环中保持一致。对该改动的精确 diff 无法从仓库只读现状还原,但从上述调用链可见其职责边界:主进程只负责解析协议并转发 route,渲染层负责查询与返回态管理。

修复 4:桌面下载链接

桌面端涉及“下载”的场景主要是应用/渲染层更新包的拉取。主进程提供了统一下载工具 download.ts:

  • downloadFile(url, dest):基础流式下载,非 2xx 直接抛错;
  • downloadFileWithProgress(options):进阶版,支持目录自动创建、onLog日志回传、onProgress进度回调(每 500ms 节流上报一次百分比与已下载/总 MB),以及可选的SHA-256 哈希校验——服务端下发 manifest 时附带期望哈希,下载完成后比对expectedHash,不一致则返回失败并记录日志,防止下载到被篡改或损坏的文件。
if (expectedHash && sha256) { sha256.update(buffer) const hash = sha256.digest("hex") if (hash !== expectedHash) { onLog?.(`Hash verification failed. Expected: ${expectedHash}, Got: ${hash}`) return false } }

Windows 平台另有一套基于electron-updater的实现(windows-updater.ts 与 dev 模式使用的 dev-app-update.yml)。v1.7.0 对“桌面下载链接”的修复即落在这些下载入口所指向 URL 的正确性上(更新 feed 与发布产物地址),保证用户能从桌面端打开正确的下载/安装来源。

修复 5:从 OTA 版本判定 MAS 审核状态

这是本次修复中最“架构化”的一条,涉及OTA 更新协议商店分发(MAS,即 Mac App Store)的协同。

分发类型与开关

桌面端在构建期区分分发渠道:isStoreDistribution = Boolean(process.mas || MICROSOFT_STORE_BUILD)(updater/configs.ts),由此决定走“直连分发(direct)”还是“商店分发(mas/mss)”的更新策略:

export const appUpdaterConfig = { enableRenderHotUpdate: !DEV && MODE !== ModeEnum.staging, enableCoreUpdate: !isStoreDistribution, enableAppUpdate: true, enableDistributionStoreUpdate: isStoreDistribution, app: { autoCheckUpdate: true, checkUpdateInterval: 15 * 60 * 1000 }, }

商店分发的应用无法自行替换主程序包,只能走两件事:渲染层热更新(OTA)+ 引导用户去商店更新。调度入口在 updater/index.ts 的handleDistributionAppDecision():先尝试把服务端下发的 renderer 清单作为热更新应用,若不需要或不允许再评估是否要提示商店更新。

OTA 版本与审核状态的关系

每次更新检查,客户端都向 OTA 服务端上报自身“运行时版本”,见 updater/api.ts:

export const getDesktopRuntimeVersion = () => configuredRuntimeVersion ?? appVersion export const buildDesktopOtaHeaders = (includeRenderer = false) => ({ ...createDesktopAPIHeaders({ version: PKG.version }), "X-App-Channel": channel, "X-App-Runtime-Version": getDesktopRuntimeVersion(), // includeRenderer 时追加 X-App-Renderer-Version })

随后拉取两个端点:

  • /manifest:OTA 热更清单,含runtimeVersionrendererapp载荷(updater/api.ts);
  • /policy:分发策略,返回actionnone | prompt | block)、distributiondirect | mas | mss)、targetVersiondownloadUrlstoreUrl等(updater/api.ts)。

对 MAS 用户而言,一个关键的竞态是:开发者向 App Store 提交了新版本,但苹果仍处于审核期(尚未上架)。此时如果 OTA 服务端按“未来版本”判定当前客户端过旧,就可能误导用户去商店找并不存在的更新。v1.7.0 修复的正是这种状态误判——客户端通过/manifest中的runtimeVersion(而非简单的 app 版本号)与服务端/policy协同,正确识别“本地运行的是否是商店当前已批准发布的版本”,只有当商店侧确实存在更新的已发布版本时才触发distributionUpdateAvailable提示。

决策侧的逻辑可对应到 updater/index.ts:shouldPromptDistributionStoreUpdate()要求分发类型非 direct、存在storeUrl、且action !== "none"notifyDistributionUpdate()再向渲染层广播distributionUpdateAvailable({ distribution, targetUrl, storeVersion, currentVersion }),UI 据此展示“前往商店更新”弹层。对“商店构建禁止 OTA 主程序更新、仅允许渲染层热更”的边界,tryDistributionRendererUpdate()(updater/index.ts)也用RendererEligibilityStatusAlreadyCurrent / RequiresFullAppUpdate / Eligible)做了显式分流。相关解析与决策逻辑在 updater/api.test.ts(含distribution: "mas"用例)与 updater/index.test.ts 中有测试覆盖。

修复 6:延长 API 请求超时

慢网络、大响应或代理环境会让原本偏紧的请求超时被过早触发,表现为“接口偶发失败”。v1.7.0 做了放宽。从当前代码可见桌面端两类网络通道的默认超时配置:

  1. 渲染层到主进程的 API 客户端:api-client.ts 中timeout: 10000(ky 选项,即 10 秒);
  2. 主进程自定义 fetch 通道(供集成/扩展使用的带超时 fetch):integration.ts 中const { url, method, headers, body, timeout = 10_000 } = input,超时后打印Request timeout triggered after ${timeout}ms并中断该请求(integration.ts),最终以Request timeout after ${timeout}ms的形式把错误抛给调用方。

统一把默认超时窗口拉长到 10 秒量级后,结合错误日志中带有的请求 ID([CustomFetch:${requestId}])与耗时毫秒数,可以显著减少弱网下“还没等到响应就被超时掐断”的误报,同时保留可观测性以便后续针对慢接口单独调优。

说明:仓库当前代码展示的是修复后的默认值;更早版本的具体数值无提交记录可考,本文不臆测。

如何在仓库中进一步验证

如果你希望基于该仓库复现或深入验证上述结论,可按如下路径阅读:

  • 变更日志本体:apps/desktop/changelog/1.7.0.md,其姊妹版本next.md记录了后续待发布内容,可对照看修复是否延续;
  • Cookie 管理核心:apps/desktop/layer/main/src/lib/auth-cookies.ts 及其测试 auth-cookies.test.ts;
  • 认证服务与会话同步:apps/desktop/layer/main/src/ipc/services/auth.ts、cli-session-sync.ts;
  • 更新决策与 OTA:apps/desktop/layer/main/src/updater/index.ts、api.ts、configs.ts,以及对应单测index.test.tsapi.test.ts
  • 渲染层桥接:extension-expose-provider.tsx(refreshSessionrsshubRoute的映射);
  • OTA 服务端协议实现位于 apps/ota,供对照/manifest/policy的服务端语义。

小结

Folo Desktop v1.7.0 表面上是常规 bugfix 版本,实质上集中解决了两大类工程问题:登录态的“单一事实来源”(Cookie 去重、写入清理、更新后刷新与 CLI 同步)与更新分发决策的准确性(直连 / 渲染层热更 / 商店分发的分流,以及 OTAruntimeVersion与商店审核状态的匹配)。这些改动全部集中在 Electron 主进程,体现了多端客户端对“会话可信度”的强约束:宁可写入前先清理、写入后再去重,也要保证任何时刻发往 API 的 Cookie 头是确定的、唯一的。对需要维护 Electron + 服务端认证 + OTA 更新的团队而言,auth-cookies.ts 与 updater/index.ts 两处实现值得作为同类系统的设计参考。

【免费下载链接】follow🧡 Folo is the AI RSS Reader项目地址: https://gitcode.com/GitHub_Trending/fol/follow

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 14:32:35

CANN/ge EsGetProducer API文档

EsGetProducer 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow …

作者头像 李华
网站建设 2026/9/10 14:31:09

Tracy Profiler 实战指南:10 分钟给项目接入帧分析并看懂时间线

Tracy Profiler 实战指南&#xff1a;10 分钟给项目接入帧分析并看懂时间线 【免费下载链接】tracy Frame profiler 项目地址: https://gitcode.com/GitHub_Trending/tr/tracy 调帧率时你大概率经历过这种局面&#xff1a;日志打在 A 函数耗时正常&#xff0c;打在 B 函…

作者头像 李华