From 7c9fb0909822655689729cf3f3fb1453f53d7833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 21 May 2026 19:28:06 +0200 Subject: [PATCH] refactor(withings): extract recordWithingsSyncFailure for sync.ts catch-block dedup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The W6 swap from `isWithingsRefreshReauthFailure(message)` to the typed `classifyError(err)` + `classificationToFailureKind()` chain left two byte-identical 14-LOC catch-blocks in `sync.ts` — one in `getValidToken`, one in `syncUserMeasurements`. Extract the shared recording shape into `recordWithingsSyncFailure(userId, err)` so the two call sites collapse to a single `await`. Sync-activity / sync- sleep migration to typed errors (W6 deferred to v1.4.43) gets a single one-line catch block as a bonus when it lands. No behaviour change — the 140 withings / integrations unit tests still pin every failure path. --- src/lib/withings/sync.ts | 55 +++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/src/lib/withings/sync.ts b/src/lib/withings/sync.ts index 25e4ef4cb..3fdd20b7f 100644 --- a/src/lib/withings/sync.ts +++ b/src/lib/withings/sync.ts @@ -115,23 +115,7 @@ export async function getValidToken(userId: string): Promise<{ }; } catch (err) { getEvent()?.addWarning(`Token refresh failed for user ${userId}: ${err}`); - // v1.4.42 W6 — the typed `WithingsApiError` carries the - // classification verdict directly; the regex fallback in - // `classifyError` handles plain `Error` instances (e.g. a - // pg-boss job retry that lost the prototype during JSON - // round-trip). - const message = err instanceof Error ? err.message : String(err); - const classification = classifyError(err); - await recordSyncFailure({ - userId, - integration: "withings", - kind: classificationToFailureKind(classification), - message, - errorCode: - err instanceof WithingsApiError - ? err.withingsStatus?.toString() - : extractWithingsStatus(message), - }); + await recordWithingsSyncFailure(userId, err); return null; } } @@ -193,18 +177,7 @@ export async function syncUserMeasurements( try { measures = await fetchMeasurements(tokenInfo.accessToken, startDate); } catch (err) { - const message = err instanceof Error ? err.message : String(err); - const classification = classifyError(err); - await recordSyncFailure({ - userId, - integration: "withings", - kind: classificationToFailureKind(classification), - message, - errorCode: - err instanceof WithingsApiError - ? err.withingsStatus?.toString() - : extractWithingsStatus(message), - }); + await recordWithingsSyncFailure(userId, err); throw err; } @@ -307,6 +280,30 @@ export async function syncUserMeasurements( * `reauth_required` → `reauth_required` (park at `error_reauth`) * `persistent` → `persistent` (audited; next sync still runs) */ +/** + * v1.4.42 — shared failure-recording shape for the two Withings sync + * catch-blocks. The typed `WithingsApiError` carries the classification + * verdict directly; `classifyError`'s regex fallback handles plain + * `Error` instances (e.g. a pg-boss job retry that lost the prototype + * during the JSON round-trip). + */ +async function recordWithingsSyncFailure( + userId: string, + err: unknown, +): Promise { + const message = err instanceof Error ? err.message : String(err); + await recordSyncFailure({ + userId, + integration: "withings", + kind: classificationToFailureKind(classifyError(err)), + message, + errorCode: + err instanceof WithingsApiError + ? err.withingsStatus?.toString() + : extractWithingsStatus(message), + }); +} + export function classificationToFailureKind( classification: WithingsClassification, ): FailureKind {