fix(simplefin): retry transient fetch failures (3 attempts, 1s/2s backoff)
This commit is contained in:
parent
a66fe13bc6
commit
7d42d119c0
|
|
@ -83,6 +83,9 @@ async function claimSetupToken(setupToken) {
|
||||||
return accessUrl;
|
return accessUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FETCH_RETRY_ATTEMPTS = 3;
|
||||||
|
const FETCH_RETRY_DELAYS = [1000, 2000]; // ms between attempts 1→2 and 2→3
|
||||||
|
|
||||||
async function fetchAccountsAndTransactions(accessUrl, sinceEpoch) {
|
async function fetchAccountsAndTransactions(accessUrl, sinceEpoch) {
|
||||||
let url;
|
let url;
|
||||||
try {
|
try {
|
||||||
|
|
@ -95,32 +98,50 @@ async function fetchAccountsAndTransactions(accessUrl, sinceEpoch) {
|
||||||
const baseUrl = `${url.protocol}//${url.host}${url.pathname.replace(/\/?$/, '')}`;
|
const baseUrl = `${url.protocol}//${url.host}${url.pathname.replace(/\/?$/, '')}`;
|
||||||
const endpoint = `${baseUrl}/accounts?start-date=${Math.floor(sinceEpoch)}&version=2`;
|
const endpoint = `${baseUrl}/accounts?start-date=${Math.floor(sinceEpoch)}&version=2`;
|
||||||
|
|
||||||
let data;
|
let lastErr;
|
||||||
try {
|
for (let attempt = 0; attempt < FETCH_RETRY_ATTEMPTS; attempt++) {
|
||||||
const res = await fetch(endpoint, {
|
if (attempt > 0) await new Promise(r => setTimeout(r, FETCH_RETRY_DELAYS[attempt - 1]));
|
||||||
headers: { 'Authorization': `Basic ${basicAuth}` },
|
|
||||||
signal: AbortSignal.timeout(30000),
|
let res;
|
||||||
});
|
try {
|
||||||
|
res = await fetch(endpoint, {
|
||||||
|
headers: { 'Authorization': `Basic ${basicAuth}` },
|
||||||
|
signal: AbortSignal.timeout(30000),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
// Network error or timeout — retry unless this was the last attempt
|
||||||
|
if (attempt < FETCH_RETRY_ATTEMPTS - 1) { lastErr = err; continue; }
|
||||||
|
throw sanitizeError(err);
|
||||||
|
}
|
||||||
|
|
||||||
if (res.status === 403) {
|
if (res.status === 403) {
|
||||||
throw Object.assign(new Error('SimpleFIN access has been revoked — please reconnect'), { code: 'SIMPLEFIN_REVOKED' });
|
throw Object.assign(new Error('SimpleFIN access has been revoked — please reconnect'), { code: 'SIMPLEFIN_REVOKED' });
|
||||||
}
|
}
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(`SimpleFIN fetch failed (HTTP ${res.status})`);
|
const err = new Error(`SimpleFIN fetch failed (HTTP ${res.status})`);
|
||||||
|
// Retry transient server errors; surface client errors immediately
|
||||||
|
if (attempt < FETCH_RETRY_ATTEMPTS - 1 && res.status >= 500) { lastErr = err; continue; }
|
||||||
|
throw sanitizeError(err);
|
||||||
}
|
}
|
||||||
data = await res.json();
|
|
||||||
} catch (err) {
|
let data;
|
||||||
throw sanitizeError(err);
|
try {
|
||||||
|
data = await res.json();
|
||||||
|
} catch (err) {
|
||||||
|
throw sanitizeError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surface any connection-level errors from the errlist so callers can log them
|
||||||
|
if (Array.isArray(data.errlist) && data.errlist.length > 0) {
|
||||||
|
const msgs = data.errlist
|
||||||
|
.map(e => sanitizeErrorMessage(e.message || e.msg || e.code || 'Unknown error'))
|
||||||
|
.join('; ');
|
||||||
|
data._errlistSummary = msgs;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Surface any connection-level errors from the errlist so callers can log them
|
throw sanitizeError(lastErr);
|
||||||
if (Array.isArray(data.errlist) && data.errlist.length > 0) {
|
|
||||||
const msgs = data.errlist
|
|
||||||
.map(e => sanitizeErrorMessage(e.message || e.msg || e.code || 'Unknown error'))
|
|
||||||
.join('; ');
|
|
||||||
data._errlistSummary = msgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeAccount(rawAccount, dataSourceId, userId) {
|
function normalizeAccount(rawAccount, dataSourceId, userId) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue