refactor(withings): extract recordWithingsSyncFailure for sync.ts catch-block dedup

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.
This commit is contained in:
Marc-André Bombeck committed 2026-05-21 19:28:06 +02:00
1 parent d2adf265e4
commit 7c9fb09098
1 file changed
+26 -29
+26 -29
View File
@@ -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<void> {
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 {