安全的移动应用遥测代理
移动应用程序分发到您无法控制的设备。任何编译到 React Native 包、Swift 应用程序、Kotlin 应用程序或 Flutter 二进制文件中的内容最终都可以被检查。请勿在应用程序中传送 Telemetry API 密钥、传送到应用程序的远程配置值或客户端可读的环境文件。
相反,将一个小事件发送到您的应用程序拥有的经过身份验证的端点。该端点验证固定合同,添加受信任的服务器上下文,并使用服务器端密钥转发事件。
移动端代理示意图:应用将事件发送至自己的 API,API 验证身份、校验允许的事件并限制请求速率,再转发至 Telemetry。 移动应用不会收到 Telemetry 的摄取密钥。
当这个模式适合时
使用移动代理来获取产品里程碑、有限性能测量、同步结果、受控错误类别和发布质量信号。示例包括:
- 本地到服务器同步达到最终结果后
mobile_sync_completed。 mobile_screen_ready用于一小组命名屏幕和测量的持续时间。- 服务器确认持久结果后
mobile_purchase_flow_completed。 mobile_api_request_completed用于采样、分类的依赖性结果。
这不能替代崩溃符号、设备日志、分布式跟踪或精确的审计分类帐。移动交付受到连接、后台执行限制、应用程序终止、同意和客户端时钟质量的影响。将专业诊断保留在其专业系统中,并仅发送 SQL 分析所需的结果。
定义客户合同
客户端应从事件名称和字段的版本列表中进行选择。它不应选择 Telemetry 表名、发送任意属性包或转发异常消息。
{
"event_name": "mobile_sync_completed",
"event_version": 1,
"event_id": "0196f37e-6e83-7b75-9d04-6b8283b35f74",
"occurred_at": "2026-07-30T18:42:11.150Z",
"status": "success",
"duration_ms": 842,
"item_count": 12,
"network_type": "wifi"
}
保持尺寸有界。优先选择受控的 screen_name 而不是原始路由,优先选择 error_type 枚举而不是异常字符串,优先选择粗略的 network_type 而不是网络标识符。请勿包含访问令牌、设备广告标识符、联系人数据、消息内容、文件路径、剪贴板数据、自由格式搜索文本或原始 URL。
event_id支持重试重复数据删除。 occurred_at 记录客户端观察,但代理还应添加服务器接收的时间戳。使用服务器时间戳进行新鲜度和摄取监控,因为设备时钟可能是错误的。
实施应用程序拥有的端点
以下 TypeScript 草图显示了边界。将身份验证和速率限制调整为 API 已使用的框架。
const allowedEvents = {
mobile_sync_completed: {
statuses: new Set(["success", "failed", "cancelled"]),
maximumDurationMs: 300_000,
maximumItemCount: 10_000,
},
} as const;
export async function postMobileTelemetry(request: Request) {
const actor = await authenticateApplicationRequest(request);
if (!actor) return new Response("unauthorized", { status: 401 });
await enforceRateLimit({
accountId: actor.accountId,
deviceSessionId: actor.deviceSessionId,
});
const body = await readBoundedJson(request, { maximumBytes: 4096 });
const policy = allowedEvents[body.event_name as keyof typeof allowedEvents];
if (
!policy ||
body.event_version !== 1 ||
!isUuid(body.event_id) ||
!policy.statuses.has(body.status) ||
!isIntegerInRange(body.duration_ms, 0, policy.maximumDurationMs) ||
!isIntegerInRange(body.item_count, 0, policy.maximumItemCount)
) {
return new Response("invalid event", { status: 422 });
}
await telemetry.log("mobile_sync_completed", {
event_id: body.event_id,
event_version: 1,
occurred_at: parseBoundedClientTimestamp(body.occurred_at),
received_at: new Date().toISOString(),
status: body.status,
duration_ms: body.duration_ms,
item_count: body.item_count,
network_type: normalizeNetworkType(body.network_type),
account_id: actor.accountId,
app_platform: actor.platform,
app_version: actor.appVersion,
environment: process.env.APP_ENV ?? "development",
});
return new Response(null, { status: 202 });
}
API 决定Telemetry 事件名称。它从受信任的服务器状态派生身份、平台、环境和任何授权上下文,而不是从客户端接受这些值。如果端点支持多个事件,请为每个事件提供独立的模式和测试装置。
应用四个服务器端控件
认证
需要 API 的其余部分使用相同的签名应用程序会话或安装凭据。 CORS 不是移动安全边界,仅自定义标头并不能证明谁发送了请求。
允许名单
拒绝未知的事件名称、字段、枚举值、超大字符串、无效数字、未来时间戳和超出小尺寸限制的正文。允许名单既是隐私控制又是基数控制。
速率限制
通过经过身份验证的帐户和适当的安装或会话标识符进行限制。还施加了全球上限。当客户端超出限制时,返回正常的应用程序错误,而不会无限期地重试。
添加可信上下文
代理应添加帐户标识符、服务器接收时间、环境和经过验证的应用程序版本(如果这些值可用)。如果帐户标识符在您的上下文中敏感,请在收集之前对其进行一致的假名化,并记录谁可以反转映射。
选择移动交付行为
对于 React Native 和 Flutter,请使用已经负责经过身份验证的 API 请求的平台 HTTP 客户端。对于本机 iOS 和 Android,请使用应用程序使用的相同 URLSession 或 HTTP 堆栈。端点和事件契约在不同平台上应该保持相同。
当离线交付很重要时,保持一个小的有界队列。限制项目数量和寿命,丢弃超出记录窗口的事件,并使用带有抖动的指数退避。不要让分析重试延迟产品操作。在尝试中保留相同的 event_id ,以便服务器可以进行重复数据删除。
有些结果最好由服务器发出。购买、订阅更改、访问策略决策或完成的导入仅在后端提交后才具有权威性。让服务器直接发出这些结果,而不是信任客户端断言。
查询并验证结果
发送一个合成的成功、失败、取消、无效负载、未经身份验证的请求、限速突发、离线重试和重复的 event_id。然后检查有界样本:
SELECT
received_at,
event_id,
app_platform,
app_version,
status,
duration_ms,
item_count
FROM mobile_sync_completed
ORDER BY received_at DESC
LIMIT 50;
在推出之前,请验证:
- 提供的应用程序二进制文件和 JavaScript 捆绑包不包含 Telemetry API 密钥。
- 未知的事件和字段会被拒绝,而不是默默地转发。
- 不存在原始异常文本、URL、用户输入、凭据和设备标识符。
- 重试保留一个
event_id,并且重复交付不会增加结果计数。 - 仪表板在比率和百分位数旁边显示样本计数。
- 同意、保留、删除和帐户删除行为符合应用程序策略。
需要规划的故障模式
将代理视为尽力可观测性,除非该事件是单独设计的持久业务工作流程的一部分。通常应记录 Telemetry 超时并对其进行限制,而不会导致移动产品操作失败。监控代理拒绝率、转发失败、队列寿命和事件新鲜度,以便静默检测中断看起来不像产品使用率下降。
不要自动接受任意客户端事件以“使调试更容易”。这将端点变成未经审核的数据收集表面。仅在查看其目的、类型、基数、隐私分类和删除要求后才添加新的版本化字段或事件。