fix(withings): widen incremental overlap from 60s to 10 min

Withings backdates some readings and lastSyncedAt advances on every
success (including a healthy 200-with-0 cycle), so a reading landing more
than 60s before the next cycle's now() could be missed. Widen the poll
catch-up overlap to 10 minutes — the upserts are idempotent, so re-touching
a handful of rows is harmless.
This commit is contained in:
Marc-André Bombeck committed 2026-07-08 23:57:02 +02:00
1 parent 4c158f6abc
commit 9eadd0755f
2 files changed
+34 -2

No files matched your search

+17 -1
View File
@@ -1,5 +1,21 @@
import { afterEach, describe, expect, it } from "vitest";
import { getWithingsWebhookCallbackUrl } from "../sync";
import {
getWithingsWebhookCallbackUrl,
WITHINGS_INCREMENTAL_OVERLAP_MS,
} from "../sync";
/**
* F-SYNC-5 — the incremental overlap must stay comfortably wider than the old
* 60s so a backdated Withings reading landing just before the next cycle's
* `now()` is not missed (compounded by `lastSyncedAt` advancing on a healthy
* 200-with-0 cycle). The upserts are idempotent, so a wider overlap is safe.
*/
describe("WITHINGS_INCREMENTAL_OVERLAP_MS", () => {
it("is at least a few minutes (not the old 60s)", () => {
expect(WITHINGS_INCREMENTAL_OVERLAP_MS).toBeGreaterThanOrEqual(5 * 60_000);
expect(WITHINGS_INCREMENTAL_OVERLAP_MS).toBeGreaterThan(60_000);
});
});
/**
* v1.4.25 W17a — Withings webhook callback URL must put the shared
+17 -1
View File
@@ -131,6 +131,20 @@ export async function getValidToken(userId: string): Promise<{
};
}
/**
* Incremental overlap window (ms). Withings backdates some readings (a scale
* step syncs to the cloud minutes after the reading's timestamp), and
* `lastSyncedAt` advances on every success — including a healthy 200-with-0
* cycle (F-SYNC-1). A too-tight overlap let a reading that landed just before
* the next cycle's `now()` slip through the gap. 10 minutes is comfortably wider
* than the observed backdating skew while staying far short of a full re-scan;
* the upserts are idempotent, so a wider overlap only re-touches a handful of
* rows. (Still narrower than Google's 24h / Oura's 7-day because Withings is
* webhook-primary — this overlap is the poll-catch-up safety net, not the main
* path.)
*/
export const WITHINGS_INCREMENTAL_OVERLAP_MS = 10 * 60 * 1000;
/**
* Sync measurements from Withings for a given user.
* Fetches data since last sync (or last 30 days if first sync).
@@ -172,7 +186,9 @@ export async function syncUserMeasurements(
const startDate = opts.fullSync
? undefined
: connection.lastSyncedAt
? new Date(connection.lastSyncedAt.getTime() - 60 * 1000) // overlap 1 min to avoid gaps
? new Date(
connection.lastSyncedAt.getTime() - WITHINGS_INCREMENTAL_OVERLAP_MS,
)
: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 days back
let measures: Awaited<ReturnType<typeof fetchMeasurements>>;