← 返回信息流
AI 资讯Hacker News·5 天前

Umami自托管用于iOS应用分析

原标题:Using self-hosted Umami for iOS app analytics

速览

Umami是一款注重隐私的开源网站分析工具,支持自托管部署。现在它也能用于iOS应用分析,帮助开发者收集用户行为数据,无需依赖第三方服务。这一方案适合对数据隐私有严格要求的应用团队,可替代Google Analytics等商业工具。

AI 深度解读

背景

Umami 是一款开源、隐私友好的网站分析工具,许多开发者选择自托管来避免第三方数据收集。iOS 应用开发者通常需要嵌入重量级的分析 SDK(如 Firebase Analytics),并处理 IDFA 权限提示和 App Tracking Transparency 框架,这不仅增加了隐私合规负担,也让用户敏感。作者此前已经在自托管 Umami 用于网站分析,希望为 iOS 应用获得同样的轻量级分析体验,而不引入重量级 SDK、不弹出 IDFA 提示、也不让第三方代收数据。为此,他创建了一个开源 Swift 包 umami-swift,让 iOS 应用直接复用自托管的 Umami 实例进行事件追踪和页面浏览统计。

核心内容

实现方式

umami-swift 是一个无外部依赖的开源 Swift 包,公开 API 非常简洁:

import Umami

// 在应用启动时配置
Umami.configure(
    websiteId: "your-website-id",
    host: "cardgame.ios",
    baseURL: URL(string: "https://your-umami-host")!
)

// 任意位置追踪事件
Umami.track("game_started")
Umami.track("level_completed", ["level": 7, "won": true])

// 用户切换屏幕时
Umami.screen("settings")

// 用户选择退出分析
Umami.setEnabled(false)
  • configure 接受 websiteId 和 host 字符串。host 是在 Umami 中添加网站时输入的域名;应用没有真实域名,因此使用伪域名如 simplestworkouttracker.iosbaseURL 指向自托管的 Umami 实例地址。配置时还会排队发送启动时的 pageview 和 app_started 事件,因此无需额外代码即可在 Umami 仪表盘看到启动记录。
  • track 发送一个命名事件,可选附带字典形式的额外数据。
  • screen 发送一个命名屏幕的 pageview。
  • 所有事件都会先写入磁盘再批量发送,即使启动时无网络也不会丢失数据,且 UI 不会因等待分析而阻塞。

将包集成到应用中

集成步骤:

  1. 在 Xcode 中选择 File → Add Package Dependencies,粘贴 https://github.com/hjerpbakk/umami-swift,设置版本规则为 Up to Next Major Version 从 1.3.0,将 Umami 产品添加到应用 target。如果是 Package.swift 项目,则在 dependencies 中添加 .package(url: "https://github.com/hjerpbakk/umami-swift", from: "1.3.0"),并将 Umami 产品添加到目标。
  2. 在 Umami 仪表盘中为应用添加一个新网站。应用没有真实域名,因此使用稳定的伪域名如 myapp.ios,并复制生成的 websiteId。
  3. 在应用启动时调用 Umami.configure,传入同样的 websiteId、host 和 baseURL。
  4. 运行应用,在 Umami 仪表盘中打开该网站,几秒内应出现 app_started 事件,带有 visitor id,表明事件流已建立。

客户端与后端通信

Umami 的追踪脚本与两个端点通信:/api/send(单个事件)和 /api/batch(批量事件)。两者都接受 JSON 体,且不需要 API 密钥或签名。Umami 通过 payload 中的 website-id 识别网站,而非 sender。因此 Swift 应用可以直接调用相同端点,发送与追踪脚本相同格式的 JSON,并设置合适的 User-Agent 以避免触发 Umami 的机器人过滤器。上一篇文章提到的蜜罐机制同样适用于应用:如果请求看起来像机器人,会返回 200 但数据不会出现在仪表盘。

唯一用户标识

Umami 以两种单位计数流量:visitors(访客)和 visits(访问)。visitor 是跨访问持久化的身份,visit 是一次会话。在 Web 上,Umami 通过 IP 和 User-Agent 的无 cookie 哈希计算 visitor。应用没有浏览器会话可供哈希,因此 umami-swift 自行提供 visitor id:每个事件携带 payload.id,是一个 UUID,Umami 将其视为已计算好的访客标识符。

该 UUID 随机生成,仅存在于内存中,每日历日变更时自动轮换。不会写入设备,因此无法回读,不同日期的使用也无法关联。由于 id 随进程终止而消失,每次启动实际上都会计为一个新访客,以一天为上限而非典型生命周期。这使得应用与 Umami 的无 cookie Web 追踪处于同一基线上,那里 visitor 是轮换的服务端哈希,而非存储的标识符。整个过程不涉及 IDFA 或 App Tracking Transparency,该 id 无法跨应用、跨安装或跨天追踪用户。

页面浏览使概览生效

Umami 的 visitors、visits 和 views 数据来自 pageview。带有 name 的 payload 是自定义事件,而非 pageview,因此仅追踪命名事件会导致概览页显示为零,事件只出现在 Events 下。

因此客户端会在每次启动和每次从后台返回前台时发送一个针对 / 的 pageview。在传输层,这个 pageview 和 app_started 事件的 payload 几乎相同,只是 pageview 没有 name。这样概览页就能显示真实数字:每次启动一个 view,visits 和 visitors 的计数方式与网站一致。

屏幕也可以选择加入。Umami.screen("settings") 会发送一个针对 /settings 的 pageview,该屏幕会出现在 Umami 的页面列表中,就像网站上的页面一样。

广告拦截器的影响

在浏览器中,Umami 的追踪脚本可能被广告拦截器或隐私扩展拦截,因为请求路径包含熟悉的脚本路径。上一篇文章通过 Cloudflare Worker 的 /np/ 路径绕过了这个问题。应用则不同:iOS 上常见的内容拦截器(Safari 内容拦截器)仅作用于 Safari,不会影响原生应用的网络流量。系统级拦截器(如 1Blocker 的 Firewall、NextDNS、AdGuard)作为设备上的 VPN 或 DNS 解析器运行,确实会拦截流量,但它们只看到域名(通过 DNS 查询和 TLS 握手中的服务器名称),路径保留在加密请求中,因此浏览器中用于匹配 Umami 的路径和脚本名规则无效,只剩下主机名。自托管在普通域名(如 hjerpbakk-analytics.fly.dev)上的实例不在任何拦截列表中,请求可以正常通过。除非手动将该域名加入自定义列表,否则不会自动拦截。

因此 umami-swift 直接与 Fly 源站通信,跳过 Cloudflare Worker。但 Worker 仍然为网站服务,并且还承担了一项额外任务:显示应用图标。

在 Umami 中显示应用图标

Umami 仪表盘会在每个网站旁边显示一个小图标,图标来自 DuckDuckGo 的 favicon 服务,基于注册域名获取。对于真实网站这没问题。但应用注册在伪域名如 simplestworkouttracker.ios 下,任何 favicon 服务都无法解析,因此所有应用都显示相同的灰色地球图标。

自托管意味着可以修改这一行为。Umami 读取 FAVICON_URL 环境变量,该变量将 favicon 服务替换为包含 {{domain}} 占位符的 URL 模板。作者将其指向上一篇文章中的 Cloudflare Worker:

FAVICON_URL=https://hjerpbakk.com/icons/{{domain}}

因此应用虽然跳过 Worker 进行追踪,但 Worker 仍然为它们做了一件事:当请求 /icons/<domain> 时,Worker 检查该域名是否属于已知应用,若是则返回对应的应用图标(通过其他方式提供),从而让每个应用在仪表盘中显示自己的图标。

关键要点

  • umami-swift 是一个
查看原文 →hjerpbakk.com