fix(fitbit): correct the setup guide to the classic Fitbit Web API

The Fitbit integration connects through the classic Fitbit Web API, so a
self-hoster registers a Fitbit developer app at dev.fitbit.com — but the
setup guide walked users through creating a Google Cloud OAuth client,
whose client id the Fitbit sign-in rejects (unauthorized_client). Rewrite
the guide for the dev.fitbit.com flow (app type, HTTPS callback, scopes,
PKCE), leave the old page as a pointer, and add a test that keeps the
documented scopes and endpoints in sync with the client so the guide
cannot drift from the code again. Flags the September 2026 sunset of the
classic Fitbit Web API.
This commit is contained in:
Marc-André Bombeck committed 2026-07-02 09:10:38 +02:00
1 parent 3bceb61aed
commit 46db21dbeb
4 files changed
+162 -77

No files matched your search

@@ -1,6 +1,6 @@
"use client";
// v1.12.0 — Google Health (Fitbit & Pixel) card. Mirrors the WHOOP card: a
// v1.12.0 — Fitbit (Fitbit & Pixel) card. Mirrors the WHOOP card: a
// BYO-key credentials form first, then an OAuth connect, then the
// sync/test/disconnect action row + parked-resume banner. v1.12.1 — status
// reads off the consolidated /api/integrations/status envelope (the per-card
@@ -0,0 +1,49 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { FITBIT_OAUTH_SCOPE } from "../client";
/**
* Guard against the class of bug reported in #396: the setup docs drifting away
* from what the code actually does. The Fitbit integration uses the CLASSIC
* Fitbit Web API (an app from dev.fitbit.com), not a Google Cloud OAuth client —
* a doc that says otherwise sends users to the wrong registry and fails with
* `unauthorized_client — Invalid client_id`.
*/
const readDoc = (name: string): string =>
readFileSync(join(process.cwd(), "docs", "integrations", name), "utf8");
describe("Fitbit setup docs stay in sync with the code", () => {
const fitbitDoc = readDoc("fitbit.md");
it("documents every OAuth scope the client actually requests", () => {
for (const scope of FITBIT_OAUTH_SCOPE.split(" ")) {
expect(fitbitDoc).toContain(scope);
}
});
it("points at the classic Fitbit endpoints and dev.fitbit.com", () => {
expect(fitbitDoc).toContain("dev.fitbit.com");
expect(fitbitDoc).toContain("www.fitbit.com/oauth2/authorize");
expect(fitbitDoc).toContain("api.fitbit.com/oauth2/token");
expect(fitbitDoc).toContain("/api/fitbit/callback");
});
it("never instructs the user to create a Google Cloud OAuth client (the #396 mistake)", () => {
// The "do this" markers of the wrong walkthrough: the Google Cloud console
// and the Google Health scope URLs. Mentioning `apps.googleusercontent.com`
// as a "don't paste this" warning is allowed and intentional.
expect(fitbitDoc).not.toContain("console.cloud.google");
expect(fitbitDoc).not.toContain("googleapis.com/auth/googlehealth");
});
it("keeps the old google-health page as a pointer to the Fitbit setup", () => {
const legacy = readDoc("google-health.md");
expect(legacy).toContain("fitbit.md");
// The legacy page must not re-introduce the wrong Google Cloud walkthrough.
expect(legacy).not.toContain("console.cloud.google");
expect(legacy).not.toContain("googleapis.com/auth/googlehealth");
});
});