拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Cherry Studio 隐私政策确认机制技术解析:从版本号到数据收集开关的完整实现链路

Cherry Studio 隐私政策确认机制技术解析从版本号到数据收集开关的完整实现链路【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300 assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio导读隐私政策确认Privacy Policy Acknowledgement是桌面应用合规体系中的关键一环当隐私政策更新后应用必须在不打断用户主流程的前提下引导用户重新阅读并确认最新版本同时尊重用户拒绝数据收集的权利。本文基于 Cherry Studio 当前仓库的真实源码完整剖析其隐私政策版本号 数据收集开关双偏好Preference驱动的确认机制覆盖从偏好存储、版本常量、更新门控组件、政策全文弹窗到首次启动引导Onboarding联动的全链路实现并给出每个环节对应的源码路径与可验证的配置键名帮助读者理解此类机制在 Electron React 架构中的落地范式。一、机制总览双偏好驱动的门控 弹窗设计Cherry Studio 的隐私政策确认机制并非简单的弹窗提示而是由两个偏好Preference键共同决定用户是否需要重新确认偏好键类型默认值语义app.privacy.policy_versionstring空字符串用户当前已确认的隐私政策版本号未确认过则为空app.privacy.data_collection.enabledboolean—由用户选择是否允许应用收集诊断数据遥测开关这两个键的 Schema 定义位于 src/shared/data/preference/preferenceSchemas.ts其中policy_version的类型注解为stringdata_collection.enabled为boolean。从源码结构看该键体系同时被渲染进程Renderer与主进程偏好服务共享是典型的共享层定义、双端消费模式。判断是否需要弹窗的核心逻辑封装在useIsPrivacyUpdateRequiredHook 中src/renderer/hooks/useIsPrivacyUpdateRequired.tsexport function useIsPrivacyUpdateRequired(): boolean { const [policyVersion] usePreference(app.privacy.policy_version) const [dataCollectionEnabled] usePreference(app.privacy.data_collection.enabled) return dataCollectionEnabled policyVersion ! LATEST_PRIVACY_POLICY_VERSION }该 Hook 的语义非常明确仅当数据收集开关为开启状态时才要求用户确认最新政策——如果用户此前已关闭数据收集则无需再次打扰仅当已确认版本号不等于最新版本号时才触发更新确认流程——已确认过最新版本的用户直接放行。配套的 Hook 测试位于 src/renderer/hooks/tests/useIsPrivacyUpdateRequired.test.ts可在仓库中直接查看其针对开关关闭不弹窗版本一致不弹窗两者同时满足才弹窗等分支的断言。二、版本号机制一个常量的作用与升级策略2.1 最新版本常量定义当前仓库中最新隐私政策版本号被定义为共享常量export const LATEST_PRIVACY_POLICY_VERSION 20260820见 src/shared/utils/constants.ts。该常量以日期字符串YYYYMMDD作为版本标识位于shared/utils/constants中可被主进程、预加载与渲染进程统一引用从机制上避免了主进程与渲染进程各存一份版本号导致不同步的经典问题。2.2 版本升级的工作流当团队更新隐私政策全文时需要同步做两件事更新政策全文资源替换打包资源中的privacy-en.html英文版与privacy-zh.html中文版文件更新LATEST_PRIVACY_POLICY_VERSION常量为新的日期字符串。由于useIsPrivacyUpdateRequired是已确认版本 ≠ 最新版本即触发弹窗因此每次递增版本号都会让所有已开启数据收集的老用户自动进入需重新确认状态无需任何迁移逻辑——这是该方案最优雅的一点确认状态天然具备版本感知能力。2.3 首次启动的特殊语义对于从未确认过政策的用户policy_version默认为空字符串。此时只要数据收集开关默认开启Onboarding 流程的默认行为详见第五节useIsPrivacyUpdateRequired同样返回true从而在首次启动时也能正确触发政策确认弹窗——新用户首次确认与老用户版本更新确认被同一套逻辑覆盖无需区分两条代码路径。三、更新门控组件PrivacyPolicyUpdateGate 的交互细节PrivacyPolicyUpdateGatesrc/renderer/windows/main/privacy/PrivacyPolicyUpdateGate.tsx是政策已更新但用户尚未确认场景下的门控组件挂在主窗口应用根组件附近负责在open即useIsPrivacyUpdateRequired()为真时拦截用户操作。3.1 两种用户选择与对应的状态写入组件提供两个按钮对应两条完全不同的偏好写入路径路径 A接受最新政策acknowledgeconst acknowledge useCallback(async () { setIsUpdatingPrivacy(true) try { await setPolicyVersion(LATEST_PRIVACY_POLICY_VERSION) } catch { toast.error(t(privacy_policy_update.acknowledge_failed)) } finally { setIsUpdatingPrivacy(false) } }, [setPolicyVersion, t])仅写入app.privacy.policy_version LATEST_PRIVACY_POLICY_VERSION保持数据收集开关不变若此前开启则继续开启。路径 B拒绝接受continueWithoutConsentconst continueWithoutConsent useCallback(async () { setIsUpdatingPrivacy(true) try { await setDataCollectionEnabled(false) setShowPolicy(false) } catch { toast.error(t(privacy_policy_update.acknowledge_failed)) } finally { setIsUpdatingPrivacy(false) } }, [setDataCollectionEnabled, t])关闭数据收集开关app.privacy.data_collection.enabled false不更新版本号。由于useIsPrivacyUpdateRequired的判定条件是开关开启 且 版本不一致关闭开关后门控条件自然解除用户可继续使用应用——拒绝确认政策的后果是放弃数据收集而不是被锁死在弹窗前。3.2 强制的模态交互从 JSX 细节可以看到该弹窗是不可绕过的强制模态showCloseButton{false}不显示关闭按钮closeOnOverlayClick{false}与onPointerDownOutside{(event) event.preventDefault()}点击遮罩层不关闭onEscapeKeyDown{(event) event.preventDefault()}按 Esc 键不关闭。这意味着用户只有接受并继续或拒绝关闭数据收集两条出路符合隐私合规场景下必须给出明确选择的要求。同时组件通过loading与disabled属性在写入偏好期间禁用按钮避免重复提交写入失败时通过toast提示无法保存确认状态请重试。3.3 乐观更新被显式关闭组件在读取偏好时特意传入了PESSIMISTIC_PREFERENCE_OPTIONS { optimistic: false }即使用悲观更新模式偏好写入必须等待持久化完成后再更新 UI 状态。这是有意的设计取舍——隐私确认状态属于敏感持久化数据若使用乐观更新先改 UI 再异步落盘一旦落盘失败会导致用户以为已确认、实际重启后再次弹窗的矛盾体验。四、政策全文弹窗PrivacyPolicyDialog 与资源加载链路PrivacyPolicyDialogsrc/renderer/windows/main/privacy/PrivacyPolicyDialog.tsx是展示政策全文的可复用弹窗组件同时服务于首次启动引导Onboarding与更新确认两个场景。4.1 多语言资源选择export function getPrivacyPolicyAsset(language: string): privacy-en.html | privacy-zh.html { return language.toLowerCase().startsWith(zh) ? privacy-zh.html : privacy-en.html }根据i18n.resolvedLanguage回退到i18n.language再回退到en-US判断语言前缀是否以zh开头选择privacy-zh.html或privacy-en.html。政策全文以独立 HTML 资源形式随应用打包而非硬编码在组件内便于后续迭代时仅替换资源文件即可完成政策更新。4.2 文件 URL 构建与主题注入export function buildPrivacyPolicyUrl(resourcesPath: string, language: string, theme: ThemeMode): string { const filePath AbsoluteFilePathSchema.parse( joinPath(resourcesPath, cherry-studio/${getPrivacyPolicyAsset(language)}) ) const themeName theme ThemeMode.dark ? dark : light return ${toFileUrl(filePath)}?theme${themeName} }加载链路为通过ipcApi.request(app.get_info)获取主进程返回的resourcesPath资源目录绝对路径拼接出政策 HTML 的绝对路径经AbsoluteFilePathSchema校验再通过toFileUrl转换为file://URL并附带?themedark|light查询参数——政策 HTML 可据此自行适配明暗主题保证弹窗内政策阅读体验与应用主题一致。该 URL 最终通过沙箱 iframe 加载iframe src{privacyUrl} title{t(privacy_policy.title)} sandboxallow-scripts ... /sandboxallow-scripts意味着政策页面仅允许执行脚本不授予同源、表单提交、弹窗等权限属于安全沙箱策略。加载失败如资源缺失时组件记录日志并展示privacy_policy.load_failed错误文案而不是白屏。4.3 关闭策略与确认回调与PrivacyPolicyUpdateGate一致该弹窗同样不可通过关闭按钮、遮罩点击或 Esc 键关闭只能通过底部的接受并继续acceptButtonText可定制或拒绝按钮完成交互。isPending状态控制按钮 loading 展示回调通过 props 注入onAccept/onDecline使同一组件可被门控与 Onboarding 两处复用。五、与首次启动引导Onboarding的联动首次启动时Onboarding 页面同样承担隐私政策的初始确认职责src/renderer/windows/main/onboarding/OnboardingPage.tsx页面读取app.privacy.data_collection.enabled与app.privacy.policy_version两个偏好对应源码第 63-64 行的偏好键映射通过privacyAccepted状态记录用户是否勾选同意隐私政策组件中第 96 行useState(true)的默认行为从源码看是可配置的初始值实际以页面内复选框交互为准当用户完成引导时若同意政策写入{ policyVersion: LATEST_PRIVACY_POLICY_VERSION }保持数据收集开关为用户选择的状态若拒绝政策写入{ dataCollectionEnabled: false, policyVersion: }即关闭数据收集且不记录版本号写入失败时提示onboarding.privacy.update_failed无法保存隐私协议同意状态请重试。由此Onboarding 与PrivacyPolicyUpdateGate形成了完整的覆盖闭环场景触发组件判定条件处理结果全新用户首次启动OnboardingpolicyVersion 且未勾选同意勾选同意→记录最新版本号拒绝→关闭数据收集老用户政策升级PrivacyPolicyUpdateGate数据收集开启 且 版本号 ≠ 最新接受→更新版本号拒绝→关闭数据收集已关闭数据收集的用户均不触发开关为 false直接放行不打扰六、偏好持久化与多语言文案6.1 偏好键与默认值app.privacy.*两个键被纳入共享偏好 Schemasrc/shared/data/preference/preferenceSchemas.ts其默认值在 Schema 对应的默认值映射中定义为policy_version: 同一文件的第 606 行附近即默认视为从未确认。该默认值保证任何未写过版本号的安装包括升级自旧版本的安装在数据收集开启时都会被视为需要确认最新政策。6.2 渲染层多语言文案更新确认弹窗与 Onboarding 的文案分布在渲染层各语言包中如 src/renderer/i18n/locales/en-us.json 与 zh-cn.json关键文案键包括privacy_policy_update.title中文隐私协议更新英文Privacy Policy Updatedprivacy_policy_update.description_before_link我们更新了隐私协议。请查看最新的…后接可点击的privacy_policy_update.policy链接按钮点击后打开政策全文弹窗privacy_policy_update.acknowledge_failed无法保存确认状态请重试onboarding.privacy.accept_and_continue同意并继续 / Accept and Continueonboarding.privacy.notice/onboarding.privacy.policyOnboarding 页面的我已阅读并同意 [隐私协议]复选框文案privacy_policy.title/privacy_policy.load_failed政策全文弹窗的标题与加载失败提示。值得注意的设计细节更新弹窗的描述文案刻意把政策链接做成行内按钮variantlink、下划线样式而不是让用户先读完 HTML 全文再回到门控弹窗确认——用户点击链接后门控弹窗保持打开状态政策全文弹窗叠层展示open{open showPolicy}接受/拒绝操作仍然落回门控弹窗底部按钮交互路径闭环且不易迷路。七、从源码看可复用的实现范式综合以上分析Cherry Studio 的隐私政策确认机制可以抽象为以下可复用的实现范式供其他 Electron/桌面应用参考版本号即状态用YYYYMMDD日期字符串作为政策版本与政策资源文件名解耦确认状态 用户已确认的版本号版本升级天然触发重新确认零迁移成本数据收集开关作为免打扰出口拒绝确认 ≠ 禁用应用而是优雅降级为关闭数据收集既满足合规又不损害核心功能可用性共享常量防漂移LATEST_PRIVACY_POLICY_VERSION放在shared/utils/constants双端主进程/渲染进程引用同一来源避免版本号不同步悲观更新保证一致性隐私类敏感偏好使用optimistic: false写入确保落盘成功后才更新 UI杜绝假确认强制模态 沙箱 iframe不可绕过的弹窗保证用户必须给出明确选择政策全文以sandboxallow-scripts的 iframe 加载本地 HTML 资源兼顾展示灵活性主题注入与安全性单一弹窗组件多场景复用PrivacyPolicyDialog同时服务 Onboarding 与更新门控通过 props 注入回调解耦业务。八、结语与延伸阅读隐私政策确认看似是弹个窗的小功能但 Cherry Studio 的实现将其落成了偏好键 共享常量 门控 Hook 强制模态 多语言资源的完整体系其版本号驱动与拒绝即关闭数据收集的设计尤其值得借鉴。读者可继续深入以下仓库路径进行验证与扩展门控与判定逻辑PrivacyPolicyUpdateGate.tsx、useIsPrivacyUpdateRequired.ts 及对应测试 useIsPrivacyUpdateRequired.test.ts政策全文弹窗与资源加载PrivacyPolicyDialog.tsx 及组件测试 PrivacyPolicyDialog.test.tsx、PrivacyPolicyUpdateGate.test.tsx偏好 Schema 与默认值preferenceSchemas.ts版本常量constants.tsOnboarding 联动OnboardingPage.tsx多语言文案en-us.json 与 zh-cn.json。【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300 assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门