Folo 移动端 v0.5.7 发版解析:订阅计费链路、错误体验与渲染样式修复
Folo 移动端 v0.5.7 发版解析订阅计费链路、错误体验与渲染样式修复【免费下载链接】follow Folo is the AI RSS Reader项目地址: https://gitcode.com/GitHub_Trending/fol/follow本文以 Folo 移动端版本记录 apps/mobile/changelog/0.5.7.md 为线索逐条拆解 v0.5.7 的改进与修复项RSSHub 订阅上限的本地化升级引导、苹果内购IAP产品与交易标识混淆导致的购买/恢复故障、Stripe 活跃与逾期订阅者的升级流程、深色模式文本可读性以及 Web 渲染内容因共享 CSS token 冲突产生的边框样式问题。文章结合apps/mobile与locales下的源码与测试用例帮助读者理解每项变更背后的错误处理模型、购买校验链路与样式作用域掌握这类发版级问题定位与验证的通用方法。一、版本概览与变更主线v0.5.7 是一次典型的「体验修复型」版本变更集中在移动端两条核心链路上付费与订阅RSSHub 订阅数量超限的错误提示、App Store 内购购买/恢复、Stripe 订阅者升级共 3 项其中内购修复直接关系到交易能否落账属于计费正确性问题渲染与视觉深色模式文本可读性与 Web 渲染内容边框样式共 2 项属于界面一致性问题。从 apps/mobile/changelog/0.5.7.md 的目录结构看该仓库的移动端版本记录采用固定骨架Improvements/No longer broken/Thanks与 apps/mobile/changelog/next.md 模板保持一致。下文按版本记录原文顺序展开源码级解读。二、ImprovementsRSSHub 订阅上限错误的本地化升级引导Improved RSSHub subscription-limit errors with localized upgrade guidance and without internal request details这一项同时解决了三个问题错误信息是否本地化、是否给出升级引导、是否会泄露内部请求细节。2.1 错误码 2012 与多语言翻译Folo 将服务端错误码映射到 i18n keyerrors:{code}。RSSHub 订阅超限对应错误码2012在 locales/errors/en.json 及各语言文件中均存在对应文案例如zh-CN为「RSSHub 订阅源数量限制已超出」。这一点在 apps/mobile/src/lib/error-parser.test.ts 的 mock 中也有体现测试用t(errors:2012)返回RSSHub feed subscription limit exceeded验证的正是本地化取词路径。2.2 从错误对象到升级弹窗的解析链路错误解析入口位于 apps/mobile/src/lib/error-parser.ts 的getFetchErrorInfo它对ofetch的FetchError与自定义的FollowAPIError分别处理先按code尝试命中errors:{code}翻译若翻译命中则优先展示翻译文案。真正决定「展示升级引导还是仅 Toast 提示」的是 toastFetchError关键分支逻辑如下const isPaymentFeatureEnabled getIsPaymentEnabled() // 支付功能关闭时402 统一落到通用付费提示 if (status 402 !isPaymentFeatureEnabled) { return toast.error(t(errors:1004)) } // 支付功能开启时402 认为是需要升级弹出升级引导对话框 const needUpgradeError status 402 isPaymentFeatureEnabled if (needUpgradeError) { showUpgradeRequiredDialog({ title: message || t(settings:subscription.actions.upgrade), message: t(settings:subscription.summary.free_description), }) return }因此 RSSHub 订阅超限HTTP 402 错误码 2012在有付费能力的版本上会弹出UpgradeRequiredDialog标题使用本地化后的错误文案正文使用settings:subscription.summary.free_description这类套餐说明文案引导用户前往订阅页升级。对应测试 apps/mobile/src/lib/error-parser.test.ts 明确断言此时应调用showUpgradeRequiredDialog且不再弹出普通错误 Toast。值得一提的是MAX_RSSHUB_SUBSCRIPTIONS也出现在订阅页的特性清单 Plan.tsx 中说明「RSSHub 订阅数量」本身即是一项按套餐区分的限额资源这解释了为什么超限时最合理的动作是引导升级而非仅提示错误。2.3 剥离内部请求细节sanitizeErrorMessage「不暴露内部请求细节」由独立的 sanitizeErrorMessage 实现const FOLLOW_API_REQUEST_CONTEXT_PATTERN /\r?\nRequest:[\s\S]*$/u export const sanitizeErrorMessage (message: string) message.replace(FOLLOW_API_REQUEST_CONTEXT_PATTERN, ).trim()该函数把从Request: ...起含可选的\r\n换行、兼容 CRLF到消息结尾的全部上下文裁掉。服务端返回的错误信息通常形如RSSHub feed subscription limit exceeded Request: POST /subscriptions (original: /subscriptions) Args: { headers: { cookie: sessionsecret } }其中Args可能携带 cookie 等敏感内容。经裁剪后仅保留首行可读文案。在 apps/mobile/src/lib/error-parser.ts 与toastFetchError的fallbackMessage第 62 行、最终兜底处第 117 行都会调用该函数。配套测试 apps/mobile/src/lib/error-message.test.ts 覆盖了普通消息保留、多行请求上下文剔除、CRLF 兼容三种场景。error-parser.test.ts中则进一步断言当 API 错误码没有翻译时仅展示剥离上下文后的首行避免把Request: POST /subscriptions这类内部细节透传给用户。三、修复苹果内购购买与恢复时「产品 ID 与交易 ID 混淆」Fixed Apple subscription purchases and restores failing when product and transaction identifiers were confusedApp Store 内购涉及多套标识符产品标识productId、单笔交易标识transactionId、原始交易标识originalTransactionIdentifier、以及带签名信息的signedTransactionInfo。若向服务端校验时把它们张冠李戴服务端将无法把交易对账到正确的订阅导致购买或恢复失败。3.1 标识建模与请求组装标识关系的清晰化体现在 apple-iap-purchase.ts 的类型定义中export type ApplePurchaseIdentity { id: string originalTransactionIdentifierIOS?: string | null productId: string purchaseToken?: string | null transactionId?: string | null } export type AppleVerificationRequest { originalTransactionId?: string signedTransactionInfo?: string transactionId?: string }组装服务端校验请求的 buildAppleVerificationRequest 严格按字段语义取值originalTransactionId取originalTransactionIdentifierIOS、transactionId取transactionId || id而signedTransactionInfo只接受经isCompactJws校验的 JWS 串第 17-27 行标准三段式header.payload.signature。从源码结构看v0.5.7 的修复点就在于把此前可能混用的「购买事件里携带的产品标识」与「服务端真正需要的交易/原始交易标识」做了语义分离。3.2 只有「已知订阅产品」才进入自动核验iOS 购买完成后会回调currentPurchase。在 AppleIAPProvider.tsx 的处理useEffect中先通过isKnownAppleSubscriptionPurchase实现见 apple-iap-purchase.ts判断该 purchase 的productId是否属于已知订阅套餐合法 ID 集合由服务端配置PAYMENT_PLAN_LIST中每个套餐的appleProductIdentifier/appleProductIdentifierAnnual汇总而来AppleIAPProvider.tsx与订阅页 Plan.tsx 按计费周期取产品 ID 的逻辑一致。只有命中集合的交易才继续自动核验否则直接跳过从入口处规避了非订阅事件被误当成订阅处理的问题。3.3 防重复处理的交易去重processedTransactionsRef以transactionId/originalTransactionIdentifierIOS/id:transactionDate兜底组合作为 key 对交易去重AppleIAPProvider.tsx同一笔交易在核验成功前若再次回调会被忽略核验失败时则从集合中删除以便重试第 196 行。这套去重逻辑配合「先verifyPurchase成功再finishTransaction收尾、最后refreshBillingState刷新用户与账单状态」的顺序第 190-203 行确保购买与恢复都不会因重复提交产生重复核验或丢失交易。3.4 恢复购买按到期时间排序逐一核验restoreSubscriptionPurchases 首先调用restorePurchases()拉取 Apple 侧的历史购买随后把「非已知订阅产品」过滤掉剩余候选项按expirationDateIOS ?? transactionDate倒序到期更晚的排前面依次尝试verifyPurchase第一个核验成功的即作为恢复结果并刷新账单状态。按到期时间排序可优先命中当前仍有效的订阅避免用已过期订阅覆盖活跃状态。四、修复Stripe 活跃或逾期订阅者升级时打开账单管理Fixed upgrades for active or past-due Stripe subscribers by opening billing management当用户已有 Stripe 订阅处于 active 或 past-due 状态却再次发起升级时合理的做法不是再走一遍 Stripe Checkout 新建订阅而是引导用户进入账单管理门户修改既有订阅。对应错误码常量定义在 Plan.tsxconst ACTIVE_STRIPE_SUBSCRIPTION_EXISTS_ERROR_CODE ACTIVE_STRIPE_SUBSCRIPTION_EXISTS升级逻辑 upgradeMutation 中非 iOS或 iOS 但当前订阅源为 Stripe时走authClient.subscription.upgrade发起 Checkout并在服务端返回ACTIVE_STRIPE_SUBSCRIPTION_EXISTS错误后调用 openStripeBillingPortal该函数请求POST /billing/portal换取url再通过openURL打开 Stripe 托管门户。这样一来Stripe 渠道用户不会因「已有订阅却再次下单」而重复扣费或冲突而是进入门户自行调整套餐/付款方式iOS Apple 渠道用户仍走 App Store 内购requestSubscriptionPurchase见 Plan.tsx 的渠道分流。订阅卡片上的管理入口onManageSubscription/canManageSubscription判断、取消/试用/续费状态的到期日期展示位于 PlanAction配合billingSubscription查询第 370-379 行返回的source/status/productId/canManage字段保证「当前订阅是 Apple 时提示去系统设置管理、是 Stripe 时提供门户按钮」的体验分叉正确。五、修复深色模式文本颜色可读性Restored readable text colors in dark mode深色模式文案发灰、对比度不足是移动端常见回归。从源码布局看移动端界面大量使用语义化 Tailwind 类如text-label/text-secondary-label而非硬编码色值主题由 theme/colors 相关模块统一提供渲染入口见 Plan.tsx 的useColor使用方式。可以推断v0.5.7 对该项的修复方式是恢复语义色 token 在dark变体下的取值使依赖这些 token 的文本如设置页的说明文字、订阅页的text-secondary-label摘要重新满足对比度要求。由于该项属于全局视觉修复覆盖面广而单个文件改动小若读者需要自行排查类似问题建议从「是否硬编码颜色」「是否有dark:前缀缺失」「语义色 token 定义」三个方向入手。六、修复Web 渲染内容边框样式受共享 CSS token 冲突影响Fixed border styling in web-rendered content affected by a shared CSS token collisionFolo 的文章正文等富内容通过独立的 HTML 渲染包渲染位于 apps/mobile/web-app/html-renderer其组件同时被多个入口复用移动端 WebView 与桌面端共享同一套渲染实现。这类「共享渲染包」容易出现全局 CSS token 被一处样式改动污染、进而影响他处的隐性耦合——v0.5.7 修复的正是边框样式border被共享 token 覆盖的问题。从 HTML.tsx 可见正文排版同时依赖dark:prose-invert等 Tailwind 工具类切换明暗主题而shiki代码高亮组件也维护独立的明暗主题 tokenshiki/hooks.ts可见该渲染包中存在多套明暗样式体系并存。可以推断修复方案是为边框样式的 token 增加更精确的作用域或独立命名避免与其它共享 token 互相覆盖。此类问题在共享前端包中尤为典型排查时可以先定位渲染样式入口此处为html-renderer再对比明暗模式下相同元素的 border 样式差异最后检查是否存在同名 CSS 变量在不同模块中被重复定义。桌面端与移动端共用同一渲染实现的架构详见仓库中 apps/desktop 与 apps/mobile/web-app 两个应用目录下的实现对照。七、小结版本修复背后的工程模式从 v0.5.7 的五个条目可以提炼出 Folo 移动端在计费与渲染两个领域的工程质量实践变更领域关键落点仓库证据错误信息本地化 升级引导按错误码取 i18n 文案、402 分流到升级弹窗error-parser.ts、locales/errors敏感信息不落地正则裁掉Request:之后的请求上下文error-message.ts 及配套测试IAP 标识语义分离productId/transactionId/originalTransactionId分字段建模apple-iap-purchase.ts、AppleIAPProvider.tsxStripe 升级冲突服务端返回已有订阅错误时转门户管理Plan.tsx渲染样式隔离明暗 token 与共享边框样式作用域治理web-app/html-renderer对普通用户而言升级到 v0.5.7 后即可在订阅超限、内购恢复与深色模式阅读中获得更稳定的体验对开发者而言本版本的测试用例error-parser.test.ts、error-message.test.ts是理解错误处理约定最直接的入口值得作为回归验证的基线保留。【免费下载链接】follow Folo is the AI RSS Reader项目地址: https://gitcode.com/GitHub_Trending/fol/follow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考