feat(withings): subscribe to BP + temperature webhooks alongside weight

setupWebhook now subscribes to appli=1 (weight + composition),
appli=2 (temperature), and appli=4 (pressure family: BP, pulse, SpO2)
in one pass. The previous implementation only registered appli=1, so a
new BP reading from a BPM cuff sat for up to an hour waiting for the
hourly poll fallback instead of arriving via push within seconds.

A failure on one category is logged and we proceed with the rest —
losing one webhook is strictly better than rolling back all three.
The disconnect path mirrors the same iteration so cleanup unsubscribes
every category we ever subscribed to. Webhook handler already
dispatches `syncUserMeasurements` regardless of appli, so no change
needed on the receiver side.

Exports `WITHINGS_NOTIFY_APPLIS` as the single source of truth so the
unit test and the disconnect route stay in lockstep with setupWebhook.
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 13:27:37 +02:00
1 parent 5cb4a1dde1
commit a732b1926e
3 files changed
+68 -15

No files matched your search

+15 -4
View File
@@ -5,7 +5,10 @@ import { apiSuccess, apiError } from "@/lib/api-response";
import { annotate } from "@/lib/logging/context";
import { decrypt } from "@/lib/crypto";
import { unsubscribeWebhook } from "@/lib/withings/client";
import { getWithingsWebhookCallbackUrl } from "@/lib/withings/sync";
import {
WITHINGS_NOTIFY_APPLIS,
getWithingsWebhookCallbackUrl,
} from "@/lib/withings/sync";
import { markDisconnected } from "@/lib/integrations/status";
/**
@@ -23,13 +26,21 @@ export const POST = apiHandler(async () => {
return apiError("No Withings connection", 404);
}
// Try to unsubscribe webhook (best-effort)
// Best-effort unsubscribe across every appli category we subscribed
// to in setupWebhook. A token-expired failure on one category should
// not stop us from cleaning up the others.
try {
const accessToken = decrypt(connection.accessToken);
const callbackUrl = getWithingsWebhookCallbackUrl();
await unsubscribeWebhook(accessToken, callbackUrl);
for (const appli of WITHINGS_NOTIFY_APPLIS) {
try {
await unsubscribeWebhook(accessToken, callbackUrl, appli);
} catch {
// Ignore — token might be expired or the appli was never subscribed.
}
}
} catch {
// Ignore — token might be expired
// Ignore — token might be undecryptable.
}
await prisma.withingsConnection.delete({