> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirobody.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth 2.0 Vendor 示例（Fitbit）

> 一个对照 C++ vendor 契约的完整 OAuth 2.0 设备品牌客户端 —— 以及关于 Whoop 的说明。

<Warning>
  **Whoop 不是已发布的 vendor 客户端。** `src/health/vendor/device/` 中已确认的设备品牌客户端仅有 **Fitbit、Garmin 和 Withings**。Whoop 没有第一方 `vendor::Vendor` —— 它需通过**聚合平台**（如 Terra、Rook、Spike）接入，或由你自己添加一个客户端。*(verify — Whoop 可能只能经聚合器接入。)* 因此本页以 **Fitbit** 作为 OAuth 2.0 示例，因为它是一个真实、已完整实现的设备客户端。
</Warning>

## 概览

**Fitbit**（[`src/health/vendor/device/fitbit.cpp`](https://github.com/thetahealth/mirobody/blob/main/src/health/vendor/device/fitbit.cpp)，id 为 `fitbit`）是对照 vendor 契约、拥有**完整** OAuth 2.0 实现的设备品牌的最佳范例：它实现了 `fetch`、`authorize_url`、`revoke` 以及带签名的 `handle_webhook`。可将它与 [Garmin 示例](/zh/examples/garmin-provider)对照，后者因 Garmin 的数据 API 需合作方授权而大多为桩。

<Info>
  这是一个 `vendor::Vendor`。它所实现的接口参见 [Vendor 系统](/zh/concepts/providers)。
</Info>

## `authorize_url` —— 构建同意 URL

保密型服务端客户端在 token 步骤用其 secret 认证，因此**不使用 PKCE**；`authorize_url` 是纯字符串构建（无网络调用），需要 OAuth `client_id`：

```cpp theme={null}
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：

```cpp theme={null}
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 + `"&"` 为密钥）对订阅通知签名。客户端在解析前用恒定时间比较验证它：

```cpp theme={null}
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`）。

## 配置

```bash theme={null}
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

两条诚实的路径：

1. **经聚合器** —— 通过 Terra / Rook / Spike（哪个在其 provider 目录中列出了 Whoop 就用哪个）连接 Whoop，并经该聚合器的 `vendor::Vendor` 获取。*(verify the aggregator's current Whoop support.)*
2. **编写一个第一方客户端** —— 添加 `src/health/vendor/device/whoop.cpp`，对照 Whoop 的公开 OAuth 2.0 API 实现契约并注册它。参照 [Provider 集成](/zh/development/provider-integration)；上面的 Fitbit 是最接近的模板。

## 下一步

<CardGroup cols={2}>
  <Card title="Garmin 示例" icon="watch" href="/zh/examples/garmin-provider">
    一个大多为桩的设备客户端及其原因
  </Card>

  <Card title="Provider 集成" icon="code" href="/zh/development/provider-integration">
    构建一个新的 `vendor::Vendor`
  </Card>

  <Card title="OAuth 实现" icon="key" href="/zh/development/oauth-implementation">
    各 vendor 客户端中的 OAuth 模式
  </Card>

  <Card title="Provider 概览" icon="plug" href="/zh/providers/overview">
    每一个已发布的 vendor
  </Card>
</CardGroup>
