diff --git a/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx b/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx index 6778992340c2f29829f60dc02dab38a10fa3bedd..989e585c7611856fe3d6c950daeaa3dd3a874f9b 100644 --- a/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx +++ b/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx @@ -35,6 +35,12 @@ function getErrorMessage(type, code) { case 'RATE_LIMIT_EXCEEDED': return 'Rate limit exceeded for this item. Please try again later.'; + case 'INVALID_ACCESS_TOKEN': + return 'Your SimpleFIN Access Token is no longer valid. Please reset and generate a new token.'; + + case 'ACCOUNT_NEEDS_ATTENTION': + return 'The account needs your attention at [SimpleFIN](https://beta-bridge.simplefin.org/auth/login).'; + default: } diff --git a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx index 492a03f8427e8b0bbe13a24a35a1ffb5b1de9af9..4f042cd1d57e21f091aad4b8d3ae6c0dbba57639 100644 --- a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx @@ -67,36 +67,46 @@ export function CreateAccountModal({ setLoadingSimpleFinAccounts(true); - const results = await send('simplefin-accounts'); + try { + const results = await send('simplefin-accounts'); + if (results.error_code) { + throw new Error(results.reason); + } - const newAccounts = []; + const newAccounts = []; - type NormalizedAccount = { - account_id: string; - name: string; - institution: string; - orgDomain: string; - orgId: string; - balance: number; - }; - - for (const oldAccount of results.accounts) { - const newAccount: NormalizedAccount = { - account_id: oldAccount.id, - name: oldAccount.name, - institution: oldAccount.org.name, - orgDomain: oldAccount.org.domain, - orgId: oldAccount.org.id, - balance: oldAccount.balance, + type NormalizedAccount = { + account_id: string; + name: string; + institution: string; + orgDomain: string; + orgId: string; + balance: number; }; - newAccounts.push(newAccount); - } + for (const oldAccount of results.accounts) { + const newAccount: NormalizedAccount = { + account_id: oldAccount.id, + name: oldAccount.name, + institution: oldAccount.org.name, + orgDomain: oldAccount.org.domain, + orgId: oldAccount.org.id, + balance: oldAccount.balance, + }; - actions.pushModal('select-linked-accounts', { - accounts: newAccounts, - syncSource: 'simpleFin', - }); + newAccounts.push(newAccount); + } + + actions.pushModal('select-linked-accounts', { + accounts: newAccounts, + syncSource: 'simpleFin', + }); + } catch (err) { + console.error(err); + actions.pushModal('simplefin-init', { + onSuccess: () => setIsSimpleFinSetupComplete(true), + }); + } setLoadingSimpleFinAccounts(false); }; diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts index 2b96092040baec638a061cc38aea23aac5230ebd..034a2123104fe22cb5c56dc0c75240aad3950772 100644 --- a/packages/loot-core/src/server/accounts/sync.ts +++ b/packages/loot-core/src/server/accounts/sync.ts @@ -142,6 +142,7 @@ async function downloadSimpleFinTransactions(acctId, since) { { 'X-ACTUAL-TOKEN': userToken, }, + 60000, ); if (res.error_code) { diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index 6af6f82dc558b778cb28c1e79a939ff50074c364..70617167357c45de4028c7ca8868ebd5d69dce4c 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -982,13 +982,18 @@ handlers['simplefin-accounts'] = async function () { return { error: 'unauthorized' }; } - return post( - getServer().SIMPLEFIN_SERVER + '/accounts', - {}, - { - 'X-ACTUAL-TOKEN': userToken, - }, - ); + try { + return await post( + getServer().SIMPLEFIN_SERVER + '/accounts', + {}, + { + 'X-ACTUAL-TOKEN': userToken, + }, + 60000, + ); + } catch (error) { + return { error_code: 'TIMED_OUT' }; + } }; handlers['gocardless-get-banks'] = async function (country) { @@ -1093,7 +1098,9 @@ handlers['accounts-bank-sync'] = async function ({ id }) { } else if (err instanceof PostError && err.reason !== 'internal') { errors.push({ accountId: acct.id, - message: `Account “${acct.name}†is not linked properly. Please link it again`, + message: err.reason + ? err.reason + : `Account “${acct.name}†is not linked properly. Please link it again.`, }); } else { errors.push({ diff --git a/packages/loot-core/src/server/post.ts b/packages/loot-core/src/server/post.ts index cc6bc8f13854aefc32c2d3deb23a12af385f8f10..8b49c359d41be0fb25ac0b83487ae91788500081 100644 --- a/packages/loot-core/src/server/post.ts +++ b/packages/loot-core/src/server/post.ts @@ -19,19 +19,24 @@ function throwIfNot200(res, text) { } } -export async function post(url, data, headers = {}) { +export async function post(url, data, headers = {}, timeout = null) { let text; let res; try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); + const signal = timeout ? controller.signal : null; res = await fetch(url, { method: 'POST', body: JSON.stringify(data), + signal, headers: { ...headers, 'Content-Type': 'application/json', }, }); + clearTimeout(timeoutId); text = await res.text(); } catch (err) { throw new PostError('network-failure'); diff --git a/upcoming-release-notes/2891.md b/upcoming-release-notes/2891.md new file mode 100644 index 0000000000000000000000000000000000000000..ec18e60e6cb109202f647e5db037ab7d0a035c14 --- /dev/null +++ b/upcoming-release-notes/2891.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [psybers] +--- + +Timeout SimpleFIN sync calls after 60 seconds.