Release v1.27.0 — Fitbit and Pixel Watch through Google Health#400
Draft
MBombeck wants to merge 7 commits into
Draft
Release v1.27.0 — Fitbit and Pixel Watch through Google Health#400MBombeck wants to merge 7 commits into
MBombeck wants to merge 7 commits into
Conversation
Introduce the data model behind a Google Health provider that reads Fitbit, Pixel Watch, and Fitbit Air data through a user's Google account. - GoogleHealthConnection: 1:1 with the user, encrypted access/refresh tokens, token expiry, external Google user id, sync high-water-mark, and a needsReauth flag for the periodic re-consent that Google's Testing-mode clients require. - GoogleHealthOAuthState: the connect-flow nonce ledger with the PKCE verifier. - Two encrypted BYO-client columns on User for the operator's own Google OAuth client id and secret. - GOOGLE_HEALTH added to the measurement_source enum. Migration 0224 mirrors the existing integration shape: a standalone enum-add, guarded CREATE TABLE / ADD COLUMN, forward-only and idempotent.
Add the transport that reads a user's device data from the Google Health API (health.googleapis.com/v4): Google OAuth with PKCE and offline access, the paginated dataPoints reader with a daily-rollup fallback for the types that lack a list method, the metric/activity/sleep/workout mappers onto the shared measurement shape, and a response classifier that separates retryable errors, hard rejects, and the re-consent case. Access tokens live an hour and refresh tokens are reused, not rotated. A refresh that comes back invalid_grant raises a distinct reauth-required signal so the app can prompt a fresh consent rather than fail silently — the periodic re-consent a Testing-mode Google client needs. The four core readonly scopes ship by default; the Pixel Watch ECG and irregular-rhythm scopes stay behind an opt-in flag. Only the transport is new: the mapped-measurement output, the upsert/dedup/rollup tail, and the source ladders are the shared ones.
…ority Add the request surface and the background sync for the Google Health provider, all built on the shared integration plumbing. - Routes under /api/google-health: connect (mint the PKCE pair and state nonce, redirect to Google), callback (timing-safe CSRF, atomic state consume, token exchange, connection upsert, clear the reauth flag, enqueue backfill), credentials (encrypt the operator's BYO client id and secret field-by-field), disconnect, status, and a rate-limited manual sync. - Jobs: a boot-time backfill, an hourly poll cohort, and a daily oauth-state cleanup, registered under the existing dead-queue contract. - Rank GOOGLE_HEALTH just after Fitbit in the per-metric source ladders; it is the successor API for the same devices. - Add GOOGLE_HEALTH to the readable measurement-source enum but leave it out of the writable set — the provider is server-owned, so a client cannot attribute a write to it. - Register the two new encrypted token columns and the two BYO-client columns in the encryption-rotation registry and the rotation script.
Surface the Google Health integration to the operator and document the setup honestly. - A settings card matching the other integration cards: the BYO Google client id and secret form, connect and disconnect, a manual sync, and a distinct reconnect prompt when the connection needs fresh consent. - Register the card in the connections panel and extend the integration key, the docs-link provider, and the query-key factory. - Translations for the card copy across all six locales. The copy states plainly that this reads Fitbit, Pixel Watch, and Fitbit Air through the user's Google account, that it succeeds the Fitbit connection Google retires in September 2026, that the operator registers their own Google client, that stress and readiness are not available, and that a periodic reconnect may be needed. - Rewrite the Google Health runbook into the real setup: a Google Cloud project, the Health API, an OAuth consent screen kept in Testing, a Web Server client, the callback redirect, the four Restricted scopes, the hundred-test-user ceiling, and the reconnect caveat. - Regenerate the OpenAPI contract for the new measurement source.
…e handling Tighten the transport so device data lands on the right day and in the right slot. - Cumulative daily totals (steps, distance, energy, floors) now key their measuredAt and stats:<tag>:<day> external id off the interval's civil start date anchored at UTC-midday, not the physical instant. Keying off the physical instant put a positive-UTC-offset user's day one calendar day early and split the same local day across the Google, Apple, and Fitbit sources so neither won the per-day source pick. - Thread the user's timezone through the sleep, workout, and interval parses so an offset-less wall-clock timestamp resolves in the user's zone rather than the process zone — the same fix the classic Fitbit sleep path already carries. - Drop skin temperature from the launch set. Google reports a signed nightly deviation from baseline, not an absolute reading, so mapping it into the absolute wrist-temperature slot stored a delta as a temperature and dropped every cold-night negative. It returns once a signed-delta model exists. - Remove the unused daily-rollup read path and field-map, and the experimental ECG and irregular-rhythm scopes that had no reader — the connect flow now requests exactly the four core scopes it uses.
…and test routes Wire the settings card to the live connection so it reflects reality after a connect. - Emit a google-health entry on the consolidated integration-status envelope (configured, connected, last sync, backfill, and the needs-reconnect flag). Without it the card read an undefined status and showed a permanently disconnected connection even after a successful OAuth grant, hiding the sync, disconnect, and reconnect controls. - Add the resume and test-connection routes the card calls, mirroring the classic Fitbit ones. - Use the shared refresh icon on the reconnect action. - Drop the experimental-scope opt-in from the runbook and describe ECG and irregular-rhythm as a planned addition rather than a current toggle.
Add a Google Health connection that reads Fitbit, Pixel Watch, and Fitbit Air data through the user's Google account, alongside the existing Fitbit connection, as the path forward before the classic Fitbit Web API sunsets in September 2026.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Google Health connection that reads Fitbit, Pixel Watch, and Fitbit Air data through the user's own Google account, running alongside the existing classic Fitbit connection. This is the path forward before Google retires the classic Fitbit Web API in September 2026: the classic connection keeps working through the sunset window, and Google Health is the successor that keeps the same devices syncing.
It is built on the Google Health API (
health.googleapis.com/v4), not the deprecated Google Fit REST API. Each operator brings their own Google Cloud OAuth client — the same per-user, bring-your-own-credentials model as WHOOP, Withings, and the classic Fitbit connection.What lands
google-healthprovider — a separate, coexisting integration. Google OAuth 2.0 with PKCE and offline access; per-user encrypted tokens; poll-only sync on the existing background-job cadence.GOOGLE_HEALTHmeasurement source — readable, and server-owned: it is deliberately excluded from the client-writable sources, so a client cannot forge a row attributed to it. (This is the one real contract change the iOS client consumes — coordination issue opened separately.)Honest constraints
Correctness notes folded in during review
Verification
pnpm typecheckclean ·pnpm lintclean ·TZ=UTC pnpm test(12,688 passing) ·pnpm buildclean · OpenAPI regenerated for the new source.Poll-only at launch; Google's change-webhook (a rotating signed keyset) is a later enhancement.