@crvouga/mockingbird-service-fitbit (英语)
本地模拟器。 真正的API合同. 部分 嘲笑鸟。 。 。 .
WIP Fitbit遗留的Web API模拟器:OAuth 2.0授权代码与PKCE,刷新旋转并撤销,配置,活动日志列表,睡眠日志按日期和日常活动摘要,超过合成数据. Fitbit于2026-09-30宣布终止对遗产Web API的支持并停用于2026-10-30 (移民目标:Google Health API);此仿真器在此后保留了被记录下来的遗产合同可以测试.
安装
bun add @crvouga/mockingbird-service-fitbit
使用量
import { createServer } from "@crvouga/mockingbird-service-fitbit/server"
// Test setup: a local Fitbit. One origin serves the API, token, revoke and authorize routes, so it
// replaces https://api.fitbit.com and https://www.fitbit.com in the application.
const server = await createServer({
activityLogs: [
{
userId: "ABC123",
log: { logId: 1, activityName: "Walk", startTime: "2026-09-02T06:00:00.000-07:00", steps: 3000 },
},
],
})
const baseUrl = server.url
// Application code: the requests it sends to Fitbit (default synthetic user ABC123 and its token).
const response = await fetch(
`${baseUrl}/1/user/-/activities/list.json?afterDate=2026-09-01&sort=asc&limit=20&offset=0`,
{ headers: { authorization: "Bearer mock_fitbit_token", accept: "application/json" } },
)
const { activities, pagination } = await response.json()
await server.close()
运行 mockingbird-fitbit serve --port 12141Fitbit没有发布JS SDK,消费者使用原始的HTTP,因此将模拟源作为API源,代号源,撤销源和授权页源(一个本地源服务于全部4个)注入. 浏览器或模拟器在 HTTP 上到达授权页面, 因此使用已服务模拟器而不是在进程内 fetch 同意步骤.
路线
| 路线 | 它会做什么 |
|---|---|
GET /oauth2/authorize | 同意页. 验证 client_id, (中文(简体) ), redirect_uri, (中文(简体) ), response_type=code, (中文(简体) ), scope和S256号 code_challenge,然后将(302)重定向到重新定向的URI上 code 财务报告和审定财务报表 state,或与 error, (中文(简体) ), error_description 财务报告和审定财务报表 state。未注册的客户端或重定向 URI 无法重定向并回答 400 HTML . |
POST /oauth2/token | 基本客户验证、形式机构(a) type: "client" 应用程序发送 client_id 而不是在体内,没有授权头. authorization_code (与 redirect_uri 财务报告和审定财务报表 code_verifier和 refresh_token 赠款。 答复 access_token, (中文(简体) ), refresh_token, (中文(简体) ), expires_in: 28800, (中文(简体) ), token_type: "Bearer", (中文(简体) ), scope, (中文(简体) ), user_id。 。 。 . |
POST /oauth2/revoke | 基本客户端认证(或,用于 type: "client" 应用程序,可选机构 client_id),表单 token=<access or refresh token>. 200; 用户给客户的每个符号都不见了. |
GET /1/user/{-|encodedId}/profile.json | { user: { encodedId, displayName, ... } }1. 范围 profile。 。 。 . |
GET /1/user/-/activities/list.json | afterDate (与 sort=asc)或(或) beforeDate (与 sort=desc, (中文(简体) ), limit (1至100个), offset1. 范围 activity。 。 。 . |
GET /1/user/-/activities/date/{date}.json | { activities, goals, summary }1. 范围 activity。 。 。 . |
GET /1/user/-/sleep/date/{date}.json 财务报告和审定财务报表 /1.2/... | { sleep, summary }1. 范围 sleep。两个路径版本服务于相同的数据. |
授权代码是单用并持续了10分钟;访问令牌在模拟器时钟上持续了28800秒. 刷新为同一用户和范围发布新访问并刷新令牌,并去除前一对(单用旋转,所以旧访问令牌回答401和旧刷新令牌回答) invalid_grant) (中文(简体) ). 一个失败的代码交换(错误的校验器,错误的重定向URI)不会消耗代码. 默认客户端是 mock_client 编号 : mock_client_secret (任何方向调整的URI,除非 redirectUris 种子; 种子客户端 type: "client" 以模拟 Fitbit “ client”( 公用) app, 以认证 client_id 单独(Fitbit 文档),并且应当使用 PKCE;默认用户是 ABC123 无记名者 mock_fitbit_token 并刷新标志 mock_fitbit_refresh所有瞄准镜,直到2100年的时钟.
数据语义:活动日志被过滤并由本地墙钟部分命令 startTime (单位:千美元)afterDate 包括:, beforeDate 除外); pagination.next 是一个完整的 URL 在模拟器源头上的下一个 offset 是最后一页的空字符串; previous 同样的。 睡眠记录属于白天 dateOfSleep (自 endTime因此,在前一天晚上开始的日志,在它结束的那天被归还; summary 总计 minutesAsleep 财务报告和审定财务报表 timeInBed 数额 levels.summary.{deep,light,rem,wake}.minutes 输入 summary.stages 当任何木材携带的时候, 没有睡眠记录的一天是200 { sleep: [], summary: { totalMinutesAsleep: 0, totalSleepRecords: 0, totalTimeInBed: 0 } }; 无活动数据的一天为 200 个零 summary默认值 goals 财务报告和审定财务报表 activities: []日期为 YYYY-MM-DD (实际日历日期)或 today. 日志是传递的:种子的每一个字段都会回来.
测试控制
种子与 createRuntime编号 :createServer 选项(users, (中文(简体) ), clients, (中文(简体) ), grants, (中文(简体) ), codes, (中文(简体) ), activityLogs, (中文(简体) ), sleepLogs, (中文(简体) ), dailySummaries, (中文(简体) ), consent)或下面的管理员路线(均相对于 /__admin,可通过 adminPrefix:
| 管理路线 | 对 |
|---|---|
PUT /consent { mode: "approve" | "deny" | "error", userId? } 编号 : GET /consent | 授权页面下一步要做什么, 以及该代码属于哪个种子用户 . |
POST /users { encodedId, profile? } | 种子用户; profile 字段返回配置路径. |
POST /activity-logs 编号 : POST /sleep-logs { userId?, logs: [...] } | 种子原木(logId 财务报告和审定财务报表 startTime 需要睡觉 endTime) (中文(简体) ). 重复 logId 替换日志. |
POST /daily-summaries { userId?, date, activities?, goals?, summary? } | 种子一天; 字段在默认情况下合并 . |
POST /tokens/expire { userId } | 将用户的访问符号过期, 不移动时钟; 刷新符号仍然有效 . |
POST /tokens/revoke { userId } | 撤销用户的每个符号, 所以下一个刷新是 invalid_grant。 。 。 . |
POST /clock {"advance": ms} 移动命名空间时钟:一个超过28800秒的访问符,然后回答401 expired_token,而超过10分钟的代码被拒绝。 标准 /__admin/health, (中文(简体) ), /state (会费) users, (中文(简体) ), clients, (中文(简体) ), grants, (中文(简体) ), codes, (中文(简体) ), activity_logs, (中文(简体) ), sleep_logs, (中文(简体) ), daily_summaries, (中文(简体) ), settings, (中文(简体) ), /reset, (中文(简体) ), /requests,有时间线检查站和故障。 标题 x-mockingbird-namespace, (中文(简体) ), /__admin/ns/<name> 路径和证书映射(持有者令牌或 Basic client id) 隔离数据。 期刊中包含元数据,从不表示或健康有效载荷.
预设POST /__admin/faults {"preset": "<name>", "count": n}: unauthorized (401美元) expired_token, (中文(简体) ), forbidden (403) (中文(简体) ) insufficient_scope, (中文(简体) ), rate_limited (429处) Retry-After: 2 财务报告和审定财务报表 fitbit-rate-limit-* 信头, rate_limited_no_retry_after, (中文(简体) ), server_error (500个), connection_drop。没有webhooks:订阅 API 不是模拟.
证据和最佳猜测
这里没有观察到任何针对一个活的Fitbit账户(遗留的API没有沙盒,需要一个同意的用户). 合同遵循供应商参考书目 https://dev.fitbit.com/building/reference/web-api/ 页面存档备份,存于互联网档案馆 (授权,令牌,刷新,撤销,活动清单,按日期入睡,简介). 下方所有的东西都是 最好猜,不是甲骨文验证 并被跟踪到本议题 needs-oracle-check数字 :
- 页:1
{"errors":[{"errorType","message","fieldName"?}],"success":false}信封和密码errorType名称 (invalid_client, (中文(简体) ),invalid_grant, (中文(简体) ),invalid_request, (中文(简体) ),unsupported_grant_type, (中文(简体) ),invalid_token, (中文(简体) ),expired_token, (中文(简体) ),insufficient_scope, (中文(简体) ),insufficient_permissions, (中文(简体) ),validation, (中文(简体) ),too_many_requests• 其信息和状况(401份客户资格差和坏或过期的无记名信使,400份赠款和参数差,403份范围或另一个用户的代号); - 单用途刷新旋转会杀死先前的访问令牌,而不在失败的交换上消耗授权代码;
offset0以上者,享有荣誉(参考文件)Supported: 0和建设next,下页),next作为最后一页的空字符串,并且next使用下一个offset而不是移动afterDate· ;- 页:1
afterDate编号 :sort配对规则和limit1至100 约束为400validation· ; /1/财务报告和审定财务报表/1.2/睡眠服务于相同的数据,包括levels.summary阶段总计;- 撤销回答 200
{}和一个未知的代号,还有200(RFC 7009),一个未知的用户代号,在回答403的路径中; - 429个正文和标题集, 以及配置文件、目标和一个空日的默认值.
甲骨文和测试
bun test 运行接受测试(服务请求的每个编号行为一次,通过 test/consumer.ts,消费者的 httpx 客户端的端口)和每一次操作的自平并有差异检测. bun run parity 需求 FITBIT_ACCESS_TOKEN,进行安全的只读配置和活性清单信封检查,并永远不打印健康数据或证书.
故意不模拟
内部时间序列、心率和SpO2时间序列、设备、朋友、订阅和网络用户、写作(创建日志、目标)、隐性赠款和客户证书赠款, GET /1/user/-/activities/{id}.json (没有记录每个用户的路由),TCX下载,Google Health API,同意UI(同意由W consent 控件和浏览器仅重定向) #_=_ 将销售商附件分解到重定向、费率限制(限制仅通过预设脚本)、真实的健康数据和设备配对.
API 密码
根运行时间导出 : FitbitAPI, (中文(简体) ), FITBIT_NAMESPACE, (中文(简体) ), FITBIT_SCOPES, (中文(简体) ), ACCESS_TOKEN_SECONDS, (中文(简体) ), CODE_SECONDS, (中文(简体) ), fitbitError, (中文(简体) ), createRuntime, (中文(简体) ), FITBIT_PRESETS, (中文(简体) ), document, (中文(简体) ), operationIds, (中文(简体) ), supportedOperationIds类型包括: FitbitAPIOptions, (中文(简体) ), Client, (中文(简体) ), User, (中文(简体) ), Grant, (中文(简体) ), OAuthCode, (中文(简体) ), ActivityLog, (中文(简体) ), ActivityLogEntry, (中文(简体) ), SleepLog, (中文(简体) ), SleepLogEntry, (中文(简体) ), DailySummary, (中文(简体) ), Consent, (中文(简体) ), FitbitRuntimeOptions, (中文(简体) ), FitbitRuntime, (中文(简体) ), OperationId, (中文(简体) ), SupportedOperationId。 。 。 .
/server 出口 createServer, (中文(简体) ), serveTarget, (中文(简体) ), DEFAULT_PORT 类型 FitbitServerOptions, (中文(简体) ), FitbitServer。 。 。 .