Whoop 不是已发布的 vendor 客户端。 src/health/vendor/device/ 中已确认的设备品牌客户端仅有 Fitbit、Garmin 和 Withings 。Whoop 没有第一方 vendor::Vendor —— 它需通过聚合平台 (如 Terra、Rook、Spike)接入,或由你自己添加一个客户端。(verify — Whoop 可能只能经聚合器接入。) 因此本页以 Fitbit 作为 OAuth 2.0 示例,因为它是一个真实、已完整实现的设备客户端。
Fitbit (src/health/vendor/device/fitbit.cpp ,id 为 fitbit)是对照 vendor 契约、拥有完整 OAuth 2.0 实现的设备品牌的最佳范例:它实现了 fetch、authorize_url、revoke 以及带签名的 handle_webhook。可将它与 Garmin 示例 对照,后者因 Garmin 的数据 API 需合作方授权而大多为桩。
authorize_url —— 构建同意 URL
保密型服务端客户端在 token 步骤用其 secret 认证,因此不使用 PKCE ;authorize_url 是纯字符串构建(无网络调用),需要 OAuth client_id:
std :: string authorize_url ( const std :: string & redirect_uri , const std :: string & state ,
const std :: string & /*user_id*/ , const std :: string & /*provider*/ ) override {
if ( config (). client_id . empty ()) {
throw VendorError ( info_ . id + ": authorize_url requires an OAuth client_id — set "
"MIROBODY_VENDOR_FITBIT_CLIENT_ID" );
}
std ::string url = std :: string (kAuthorizeUrl) + // https://www.fitbit.com/oauth2/authorize
"?response_type=code&client_id=" + url_encode ( config (). client_id ) +
"&scope=" + url_encode (kDefaultScope); // "activity heartrate sleep weight profile"
if ( ! redirect_uri . empty ()) url += "&redirect_uri=" + url_encode (redirect_uri);
if ( ! state . empty ()) url += "&state=" + url_encode (state);
return url;
}
token 交换本身在带外于 /oauth2/token 完成;所得的 access token 存为 config.api_key。
fetch —— 按域的时间序列 GET
Fitbit 的 Web API 以整天区间提供数据(yyyy-MM-dd)。fetch 把一个 DataDomain 映射到有文档的端点并返回 Fitbit 的 JSON:
static std :: string endpoint ( DataDomain d , const std :: string & u ,
const std :: string & s , const std :: string & e ) {
switch (d) {
case DataDomain ::Activity: return "/1/user/" + u + "/activities/steps/date/" + s + "/" + e + ".json" ;
case DataDomain ::HeartRate: return "/1/user/" + u + "/activities/heart/date/" + s + "/" + e + ".json" ;
case DataDomain ::Sleep: return "/1.2/user/" + u + "/sleep/date/" + s + "/" + e + ".json" ;
case DataDomain ::BodyMetrics: return "/1/user/" + u + "/body/log/weight/date/" + s + "/" + e + ".json" ;
default :
throw VendorError ( "fitbit: unsupported domain (brokers: activity, heart_rate, sleep, body_metrics)" );
}
}
用户路径段是 Fitbit 用户 id,或 -(该 API 中 token 拥有者的别名)。access token(config.api_key)以 bearer 头携带。
handle_webhook —— 验证签名
Fitbit 用 X-Fitbit-Signature = base64(HMAC-SHA1(raw_body))(以 client secret + "&" 为密钥)对订阅通知签名。客户端在解析前用恒定时间比较验证它:
const std ::string key = config (). client_secret + "&" ;
std ::array < unsigned char , 20 > mac = storage :: hmac_sha1 (key, body);
const std ::string expected = storage :: base64_encode ( mac . data (), mac . size ());
if ( ! ct_equal (expected, sig)) {
throw VendorError ( info_ . id + ": webhook signature verification failed (X-Fitbit-Signature)" );
}
由于签名方案是公开有文档的 ,这能被忠实实现 —— 这正是 Fitbit 的 webhook 为真、而 Garmin 的为桩的原因。
revoke —— 撤销授权
revoke 把 token POST 到 /oauth2/revoke,以保密客户端身份认证(HTTP Basic client_id:client_secret)。
MIROBODY_VENDOR_FITBIT_CLIENT_ID = your_client_id
MIROBODY_VENDOR_FITBIT_CLIENT_SECRET = your_client_secret
# after the OAuth token exchange, the access token is used as the API key:
MIROBODY_VENDOR_FITBIT_API_KEY = fitbit_oauth2_access_token
# optional host override (default https://api.fitbit.com)
MIROBODY_VENDOR_FITBIT_BASE_URL = https://api.fitbit.com
如果你确实需要 Whoop
两条诚实的路径:
经聚合器 —— 通过 Terra / Rook / Spike(哪个在其 provider 目录中列出了 Whoop 就用哪个)连接 Whoop,并经该聚合器的 vendor::Vendor 获取。(verify the aggregator’s current Whoop support.)
编写一个第一方客户端 —— 添加 src/health/vendor/device/whoop.cpp,对照 Whoop 的公开 OAuth 2.0 API 实现契约并注册它。参照 Provider 集成 ;上面的 Fitbit 是最接近的模板。
下一步
Garmin 示例 一个大多为桩的设备客户端及其原因
Provider 集成 构建一个新的 vendor::Vendor
OAuth 实现 各 vendor 客户端中的 OAuth 模式
Provider 概览 每一个已发布的 vendor