跳到正文
CF / PROJECT 01

Litho

linux.do 原生客户端

linux.do 的原生 iOS / Android 客户端,让浏览器替它发出每一个请求,从而穿过 Cloudflare。

状态
日常主力 · iOS 公测 / Android 已发布
技术栈
Swift · SwiftUI · iOS 17+ · Kotlin
01

Why

为什么

linux.do 是我每天都在看的社区,它值得在手机上拥有原生、保持登录的体验。

Discourse 本身有完善的 API。难点从来不是数据,而是请求根本发不出去。

02

Problem

问题
  1. 01

    linux.do 部署在 Cloudflare 之后,普通的 URLSession 或 curl 请求会直接吃 403 人机验证页。

  2. 02

    第三方工具甚至简单脚本都会反复撞盾、丢失会话。

  3. 03

    用 WebView 套壳能过验证,却放弃了原生的阅读、互动和通知。

03

System

系统

Litho 不自己发请求。所有调用都经过一个离屏的浏览器网关。

  1. L1原生 App

    iOS 用 SwiftUI,Android 用 Kotlin + Compose:话题流、阅读、表情回应、聊天、私信与通知。

  2. L2统一入口

    TopicList、Reply、Reaction 等所有业务只调用一个网关 API,不直接碰网络。

  3. L3WebView 网关

    离屏 WKWebView 用页面自身的 fetch() 发出请求,带着真实浏览器的 UA、Cookie 和 TLS 指纹。

  4. L4Discourse

    REST API 返回真实 JSON,MessageBus 长轮询承载实时更新。

04

Implementation

实现
  • 图片也过盾

    头像、帖内图、徽章图在页面里以二进制 fetch,base64 回传给原生,再交给带缓存的加载器。

  • 挑战自愈

    偶发撞盾时,网关自动重载页面完成挑战并恢复请求,顶部状态横幅提示,无需重启 App。

  • 实时通道

    通过网关订阅 Discourse MessageBus,驱动聊天、新回复与通知的实时推送。

  • 渲染与存储

    帖子用单个 WKWebView 文档渲染;GRDB 负责本地缓存;UserNotifications 加后台刷新负责通知。

  • Android 端

    Kotlin + Compose,按 iOS 架构 1:1 移植。

05

Decisions

决策
  1. D1

    借用浏览器,而不是对抗它

    不去模仿指纹、不去破解挑战,而是把请求交还给真实的浏览器上下文。对 Cloudflare 来说,这就是一个浏览器。

  2. D2

    一层网关承载所有网络

    业务从不直接访问网络,过盾、图片和实时更新都只需要在一个地方解决一次。

  3. D3

    只把三件事做到位

    打穿 Cloudflare、原生体验、对齐网页端交互。不做全能论坛客户端。

06

Result

结果
状态
日常主力
iOS
TestFlight 公测 · iOS 17+
Android
APK 已发布

它已经从 MVP 长成我每天在用的客户端:阅读、表情回应、聊天、私信和实时通知。