fix(enable-banking): recover error-state connections, respect PSD2 balance quota, clean error surface (#968)
* fix(enable-banking): recover error-state connections, respect PSD2 balance quota, clean error surface Three defects from the 2026-07-09 production log triage, all in how the enable-banking extension handles upstream (Enable Banking / ASPSP) failures: 1. Retry dead-end: a non-session sync failure parked the connection in status='error', but POST /sync rejected anything not 'active' with 400, so the UI's "Försök igen" button could never succeed and the connection stayed stranded until a full re-auth. /sync now accepts 'error' (while still rejecting 'expired': a dead consent needs re-authorization), and a successful sync restores status='active' and clears error_message. 2. Balance quota burn: every sync (manual or cron) called the BALANCES endpoint although PSD2 unattended consents allow only 4 calls/day (observed 429 "Consent daily limit 4 is exceeded"), and the retry wrapper retried those 429s twice against a daily quota. The sync now skips the balance call while the stored balance_updated_at is fresher than 12 hours, and authenticatedFetchWithRetry fails fast on a 429 whose body signals a daily limit. 3. Raw JSON in UI: sync failures persisted the raw English Enable Banking error body into bank_connections.error_message, which the settings panel renders verbatim. Failures are now mapped to short Swedish user messages (shared constants in api-client.ts); the raw body stays in server logs only. Also ratchets the eslint baseline down by 1: the no-explicit-any disable in the cron route was on the wrong line and never suppressed anything. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(enable-banking): treat future balance timestamps as stale (CodeRabbit) A future balance_updated_at yielded a negative age that always passed the freshness check, suppressing balance refreshes indefinitely; only 0 <= age < BALANCE_MAX_AGE_MS now counts as fresh. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
6a9adcf00a
commit
b06d73c23e
@@ -223,6 +223,18 @@ export function isSessionExpiredResponse(status: number, body: string): boolean
|
||||
return SESSION_DEAD_NEEDLES.some(needle => normalized.includes(needle))
|
||||
}
|
||||
|
||||
/**
|
||||
* User-facing (Swedish) messages persisted to bank_connections.error_message
|
||||
* and returned to the settings UI. error_message is a literal string in the
|
||||
* DB, not an i18n key, matching the extension's other user-facing strings.
|
||||
* Raw Enable Banking error bodies are English JSON envelopes and must never
|
||||
* land here: they belong in server logs only.
|
||||
*/
|
||||
export const REAUTH_REQUIRED_MESSAGE =
|
||||
'Bankanslutningen har löpt ut. Förnya anslutningen för att fortsätta synka.'
|
||||
export const SYNC_FAILED_MESSAGE =
|
||||
'Banksynkningen misslyckades. Försök igen, eller förnya anslutningen om felet kvarstår.'
|
||||
|
||||
/**
|
||||
* Thrown when a transactions fetch fails because the PSD2 session is dead
|
||||
* (closed/expired/invalid). Distinct from TransactionsFetchError so the sync
|
||||
@@ -279,6 +291,21 @@ async function authenticatedFetchWithRetry(
|
||||
try {
|
||||
const response = await authenticatedFetch(endpoint, options)
|
||||
if (attempt < MAX_RETRIES && [429, 502, 503, 504].includes(response.status)) {
|
||||
// A 429 caused by a DAILY quota cannot clear within the retry window:
|
||||
// PSD2 unattended consents allow only a handful of balance calls per
|
||||
// day (observed body: "Consent daily limit 4 is exceeded"), so
|
||||
// retrying just burns time and duplicates the failure in logs. Read
|
||||
// the body from a clone so the returned response stays consumable.
|
||||
if (response.status === 429) {
|
||||
const body = await response.clone().text().catch(() => '')
|
||||
if (/daily limit/i.test(body)) {
|
||||
console.warn(`[enable-banking] 429 daily quota exhausted for ${endpoint}: not retrying`, {
|
||||
status: response.status,
|
||||
body,
|
||||
})
|
||||
return response
|
||||
}
|
||||
}
|
||||
console.warn(`[enable-banking] Retrying ${endpoint} (attempt ${attempt + 1}/${MAX_RETRIES})`, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
|
||||
Reference in New Issue
Block a user