mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 23:12:04 +00:00
Garmin Connect now offers a Personal API: a person creates a key for their own account and any client can buy short-lived tokens with it. HealthLog takes that key in a new Garmin card, checks it with Garmin before storing it sealed on the user row, and polls every hour. - Transport: key exchange with a per-process token cache keyed on the key, one re-exchange on a refused token and then reauth_required, the 429 Retry-After carried to the caller, the China host tried when the global one does not know the key, answers read with a byte cap, all egress through safeFetch to the two fixed hosts. - Sync: daily summaries, sleep summaries, HRV, weigh-ins and the activity search, chunked to each resource's documented range, read one after another and stopped at the first rate limit; today's menstrual cycle summary adds logged period days the person has not logged. - Backfill: a resumable walk through the shared admission lane that learns where the history starts from the daily summaries and the activities, waits out Garmin's rate limit and stamps its marker. - Types: GARMIN source; BODY_BATTERY and STRESS_LEVEL for Garmin's own day scores, kept apart from the computed STRESS_SCORE; TRAINING_READINESS and ACUTE_TRAINING_LOAD for figures a bridge pushes through the ingest token. All four chart on the recovery page and sit on the ladders, units, ranges, labels and report groups like the device-native scores. - Key expiry: learned from a token capped short of a day, else estimated from the validity picked at connect; the card warns a week ahead and asks for a new key once Garmin refuses the old one. - Ledger, health, freshness, watchdog, backup classes, wipe plan, encrypted-column registry, rotation script, OpenAPI and all ten locales carry the new integration. The old "no Garmin connector" note is gone.
90 lines
3.6 KiB
TypeScript
90 lines
3.6 KiB
TypeScript
/**
|
|
* Names and crons of the integration sync queues.
|
|
*
|
|
* A light module on purpose: the worker registry declares these queues from
|
|
* here (`registry/integration-sync.ts`), and a request-path caller (an OAuth
|
|
* callback enqueueing the first sync, a webhook enqueueing one resource) names
|
|
* a queue without loading the sync it feeds.
|
|
*
|
|
* The hourly minutes are staggered so the polls do not land on one boss poll:
|
|
* used are :00 :05 :08 :11 :13 :15 :17 :18 :20 :23 :35 :41 :47 :50.
|
|
*/
|
|
|
|
/** Withings measure catch-net (the webhook is primary). */
|
|
export const WITHINGS_SYNC_QUEUE = "withings-fallback-sync";
|
|
export const WITHINGS_SYNC_CRON = "0 * * * *";
|
|
|
|
/**
|
|
* Withings activity and sleep: webhook-primary (appli 16 / 44), with an hourly
|
|
* catch-net for the deliveries Withings drops.
|
|
*/
|
|
export const WITHINGS_ACTIVITY_QUEUE = "withings-activity-sync";
|
|
export const WITHINGS_ACTIVITY_CRON = "0 * * * *";
|
|
export const WITHINGS_SLEEP_QUEUE = "withings-sleep-sync";
|
|
export const WITHINGS_SLEEP_CRON = "15 * * * *";
|
|
|
|
/**
|
|
* Withings ECG catch-net. The queue is webhook-primary (appli 54), but appli 54
|
|
* is not in the subscription set and the manual sync measures only, so a
|
|
* watch-only account would never have its strips pulled without this walk.
|
|
*/
|
|
export const WITHINGS_ECG_SYNC_CRON = "41 * * * *";
|
|
|
|
/** Daily sweep of the Withings OAuth state ledger. */
|
|
export const WITHINGS_OAUTH_STATE_CLEANUP_QUEUE =
|
|
"withings-oauth-state-cleanup";
|
|
export const WITHINGS_OAUTH_STATE_CLEANUP_CRON = "20 3 * * *";
|
|
|
|
/**
|
|
* WHOOP: recovery, sleep and workout are webhook-primary with an hourly
|
|
* catch-net; cycle has no webhook, so its cron is the only driver.
|
|
*/
|
|
export const WHOOP_RECOVERY_SYNC_QUEUE = "whoop-recovery-sync";
|
|
export const WHOOP_RECOVERY_SYNC_CRON = "5 * * * *";
|
|
export const WHOOP_SLEEP_SYNC_QUEUE = "whoop-sleep-sync";
|
|
export const WHOOP_SLEEP_SYNC_CRON = "20 * * * *";
|
|
export const WHOOP_WORKOUT_SYNC_QUEUE = "whoop-workout-sync";
|
|
export const WHOOP_WORKOUT_SYNC_CRON = "35 * * * *";
|
|
export const WHOOP_CYCLE_SYNC_QUEUE = "whoop-cycle-sync";
|
|
export const WHOOP_CYCLE_SYNC_CRON = "50 * * * *";
|
|
|
|
/** Daily sweep of the WHOOP OAuth state ledger. */
|
|
export const WHOOP_OAUTH_STATE_CLEANUP_QUEUE = "whoop-oauth-state-cleanup";
|
|
export const WHOOP_OAUTH_STATE_CLEANUP_CRON = "22 3 * * *";
|
|
|
|
/** Google Health poll (no webhook). */
|
|
export const GOOGLE_HEALTH_SYNC_QUEUE = "google-health-sync";
|
|
export const GOOGLE_HEALTH_SYNC_CRON = "18 * * * *";
|
|
|
|
/** Daily sweep of the Google Health OAuth state ledger. */
|
|
export const GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE =
|
|
"google-health-oauth-state-cleanup";
|
|
export const GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_CRON = "26 3 * * *";
|
|
|
|
/** Nightscout CGM poll. */
|
|
export const NIGHTSCOUT_SYNC_QUEUE = "nightscout-sync";
|
|
export const NIGHTSCOUT_SYNC_CRON = "11 * * * *";
|
|
|
|
/** Polar AccessLink poll. */
|
|
export const POLAR_SYNC_QUEUE = "polar-sync";
|
|
export const POLAR_SYNC_CRON = "13 * * * *";
|
|
|
|
/** Oura Cloud poll. */
|
|
export const OURA_SYNC_QUEUE = "oura-sync";
|
|
export const OURA_SYNC_CRON = "15 * * * *";
|
|
|
|
/** Garmin Personal API poll (no webhooks exist for the personal tier). */
|
|
export const GARMIN_SYNC_QUEUE = "garmin-sync";
|
|
export const GARMIN_SYNC_CRON = "23 * * * *";
|
|
|
|
/** Strava poll (the webhook is not wired). */
|
|
export const STRAVA_SYNC_QUEUE = "strava-sync";
|
|
export const STRAVA_SYNC_CRON = "17 * * * *";
|
|
|
|
/**
|
|
* Daily sweep of the native OIDC handoff ledger. Handoff codes are single-use
|
|
* and expire at read, so this only reaps rows the app never came back for.
|
|
*/
|
|
export const OIDC_NATIVE_HANDOFF_CLEANUP_QUEUE = "oidc-native-handoff-cleanup";
|
|
export const OIDC_NATIVE_HANDOFF_CLEANUP_CRON = "28 3 * * *";
|