From 665ae063fa49bc94762ea10a9098b57e95327d9c Mon Sep 17 00:00:00 2001 From: Cafe137 <77121044+Cafe137@users.noreply.github.com> Date: Thu, 1 Dec 2022 12:36:15 +0100 Subject: [PATCH] fix: handle auth and server error during swap (#593) * fix: change execution order for light node upgrade * refactor: grab new configuration from post config request * fix: only print successful light node upgrade when it really happens * fix: log full desktop side swap error (#596) * refactor: try to make the auth error in swap nicer * refactor: make error instruction consistent * fix: avoid overwriting daiToSwap when it is set manually --- src/pages/restart/LightModeRestart.tsx | 5 +++- src/pages/top-up/GiftCardFund.tsx | 7 +++--- src/pages/top-up/Swap.tsx | 33 ++++++++++++++++++++------ src/pages/top-up/index.tsx | 17 +++++++------ src/utils/AuthError.ts | 10 ++++++++ src/utils/SwapError.ts | 5 ++++ src/utils/desktop.ts | 8 +++---- src/utils/net.ts | 12 +++++++++- 8 files changed, 71 insertions(+), 26 deletions(-) create mode 100644 src/utils/AuthError.ts diff --git a/src/pages/restart/LightModeRestart.tsx b/src/pages/restart/LightModeRestart.tsx index b2bb66e..30edf22 100644 --- a/src/pages/restart/LightModeRestart.tsx +++ b/src/pages/restart/LightModeRestart.tsx @@ -1,5 +1,6 @@ import { BeeModes } from '@ethersphere/bee-js' import { Box, Grid, Typography } from '@material-ui/core' +import { useSnackbar } from 'notistack' import { ReactElement, useContext, useEffect, useState } from 'react' import { useNavigate } from 'react-router' import { Waiting } from '../../components/Waiting' @@ -12,6 +13,7 @@ export default function LightModeRestart(): ReactElement { const [startedAt] = useState(Number.parseInt(localStorage.getItem(STARTED_UPGRADE_AT) ?? Date.now().toFixed())) const { apiHealth, nodeInfo } = useContext(Context) const navigate = useNavigate() + const { enqueueSnackbar } = useSnackbar() useEffect(() => { localStorage.setItem(STARTED_UPGRADE_AT, startedAt.toFixed()) @@ -23,10 +25,11 @@ export default function LightModeRestart(): ReactElement { } if (apiHealth && nodeInfo?.beeMode === BeeModes.LIGHT) { + enqueueSnackbar('Upgraded to light node', { variant: 'success' }) localStorage.removeItem(STARTED_UPGRADE_AT) navigate(ROUTES.INFO) } - }, [startedAt, navigate, nodeInfo, apiHealth]) + }, [startedAt, navigate, nodeInfo, apiHealth, enqueueSnackbar]) return ( diff --git a/src/pages/top-up/GiftCardFund.tsx b/src/pages/top-up/GiftCardFund.tsx index 92e1b10..f844345 100644 --- a/src/pages/top-up/GiftCardFund.tsx +++ b/src/pages/top-up/GiftCardFund.tsx @@ -1,9 +1,10 @@ +import { BeeModes } from '@ethersphere/bee-js' import { Box, Typography } from '@material-ui/core' import { useSnackbar } from 'notistack' import { ReactElement, useContext, useEffect, useState } from 'react' -import Check from 'remixicon-react/CheckLineIcon' -import ArrowDown from 'remixicon-react/ArrowDownLineIcon' import { useNavigate, useParams } from 'react-router' +import ArrowDown from 'remixicon-react/ArrowDownLineIcon' +import Check from 'remixicon-react/CheckLineIcon' import ExpandableListItem from '../../components/ExpandableListItem' import ExpandableListItemKey from '../../components/ExpandableListItemKey' import { HistoryHeader } from '../../components/HistoryHeader' @@ -18,7 +19,6 @@ import { ROUTES } from '../../routes' import { sleepMs } from '../../utils' import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop' import { ResolvedWallet } from '../../utils/wallet' -import { BeeModes } from '@ethersphere/bee-js' export function GiftCardFund(): ReactElement { const { nodeAddresses, nodeInfo } = useContext(BeeContext) @@ -52,7 +52,6 @@ export function GiftCardFund(): ReactElement { await sleepMs(5_000) await upgradeToLightNode(desktopUrl, rpcProviderUrl) await restartBeeNode(desktopUrl) - enqueueSnackbar('Upgraded to light node', { variant: 'success' }) navigate(ROUTES.RESTART_LIGHT) } catch (error) { console.error(error) // eslint-disable-line diff --git a/src/pages/top-up/Swap.tsx b/src/pages/top-up/Swap.tsx index fd48634..e93b419 100644 --- a/src/pages/top-up/Swap.tsx +++ b/src/pages/top-up/Swap.tsx @@ -65,7 +65,7 @@ export function Swap({ header }: Props): ReactElement { // Set the initial xDAI to swap useEffect(() => { - if (!balance) { + if (!balance || userInputSwap) { return } @@ -78,7 +78,7 @@ export function Swap({ header }: Props): ReactElement { // Balance is low, halve the amount setDaiToSwap(new DaiToken(balance.dai.toBigNumber.dividedToIntegerBy(2))) } - }, [balance]) + }, [balance, userInputSwap]) // Set the xDAI to swap based on user input useEffect(() => { @@ -126,10 +126,8 @@ export function Swap({ header }: Props): ReactElement { async function restart() { try { await sleepMs(5_000) - await upgradeToLightNode(desktopUrl, rpcProviderUrl) await restartBeeNode(desktopUrl) - enqueueSnackbar('Upgraded to light node', { variant: 'success' }) navigate(ROUTES.RESTART_LIGHT) } catch (error) { console.error(error) // eslint-disable-line @@ -137,12 +135,33 @@ export function Swap({ header }: Props): ReactElement { } } + async function sendSwapRequest(daiToSwap: DaiToken) { + try { + await performSwap(desktopUrl, daiToSwap.toString) + } catch (error) { + // eslint-disable-next-line no-console + console.error(error) + throw error + } + } + async function performSwapWithChecks(daiToSwap: DaiToken) { - const desktopConfiguration = await wrapWithSwapError( + if (!localStorage.getItem('apiKey')) { + throw new SwapError('API key is not set, reopen dashboard through Swarm Desktop') + } + + let desktopConfiguration = await wrapWithSwapError( getDesktopConfiguration(desktopUrl), - 'Unable to reach Desktop API. Is Swarm Desktop running?', + 'Unable to reach Desktop API, Swarm Desktop may not be running', ) + if (canUpgradeToLightNode) { + desktopConfiguration = await wrapWithSwapError( + upgradeToLightNode(desktopUrl, rpcProviderUrl), + 'Failed to update the configuration file with the new swap values using the Desktop API', + ) + } + if (!desktopConfiguration['swap-endpoint']) { throw new SwapError('Swap endpoint is not configured in Swarm Desktop') } @@ -150,7 +169,7 @@ export function Swap({ header }: Props): ReactElement { Rpc.getNetworkChainId(desktopConfiguration['swap-endpoint']), `Swap endpoint not reachable at ${desktopConfiguration['swap-endpoint']}`, ) - await wrapWithSwapError(performSwap(desktopUrl, daiToSwap.toString), GENERIC_SWAP_FAILED_ERROR_MESSAGE) + await wrapWithSwapError(sendSwapRequest(daiToSwap), GENERIC_SWAP_FAILED_ERROR_MESSAGE) } async function onSwap() { diff --git a/src/pages/top-up/index.tsx b/src/pages/top-up/index.tsx index 06ac434..3e7041a 100644 --- a/src/pages/top-up/index.tsx +++ b/src/pages/top-up/index.tsx @@ -1,23 +1,23 @@ +import { BeeModes } from '@ethersphere/bee-js' import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core' +import { useSnackbar } from 'notistack' import { ReactElement, useContext, useState } from 'react' +import { useNavigate } from 'react-router' +import BankCard from 'remixicon-react/BankCard2LineIcon' import Check from 'remixicon-react/CheckLineIcon' import Download from 'remixicon-react/DownloadLineIcon' -import BankCard from 'remixicon-react/BankCard2LineIcon' -import MoneyDollarCircle from 'remixicon-react/MoneyDollarCircleLineIcon' import Gift from 'remixicon-react/GiftLineIcon' -import { useNavigate } from 'react-router' +import MoneyDollarCircle from 'remixicon-react/MoneyDollarCircleLineIcon' import ExpandableListItemActions from '../../components/ExpandableListItemActions' import { HistoryHeader } from '../../components/HistoryHeader' +import { Loading } from '../../components/Loading' import { SwarmButton } from '../../components/SwarmButton' -import { ROUTES } from '../../routes' +import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' import { CheckState, Context as BeeContext } from '../../providers/Bee' import { Context as SettingsContext } from '../../providers/Settings' import { Context as BalanceProvider } from '../../providers/WalletBalance' -import { BeeModes } from '@ethersphere/bee-js' +import { ROUTES } from '../../routes' import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop' -import { Loading } from '../../components/Loading' -import { useSnackbar } from 'notistack' -import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' const useStyles = makeStyles(() => createStyles({ @@ -57,7 +57,6 @@ export default function TopUp(): ReactElement { try { await upgradeToLightNode(desktopUrl, rpcProviderUrl) await restartBeeNode(desktopUrl) - enqueueSnackbar('Upgraded to light node', { variant: 'success' }) navigate(ROUTES.RESTART_LIGHT) } catch (error) { console.error(error) // eslint-disable-line diff --git a/src/utils/AuthError.ts b/src/utils/AuthError.ts new file mode 100644 index 0000000..4e7faf0 --- /dev/null +++ b/src/utils/AuthError.ts @@ -0,0 +1,10 @@ +export class AuthError extends Error { + constructor() { + super('Bad API key') + this.name = 'AuthError' + } +} + +export function isAuthError(error: unknown): error is AuthError { + return error instanceof Error && error.name === 'AuthError' +} diff --git a/src/utils/SwapError.ts b/src/utils/SwapError.ts index b360214..3c0e04b 100644 --- a/src/utils/SwapError.ts +++ b/src/utils/SwapError.ts @@ -1,3 +1,5 @@ +import { isAuthError } from './AuthError' + export class SwapError extends Error { snackbarMessage: string originalError?: Error @@ -16,6 +18,9 @@ export function isSwapError(error: unknown): error is SwapError { export function wrapWithSwapError(promise: Promise, snackbarMessage: string): Promise { return promise.catch((error: Error) => { + if (isAuthError(error)) { + throw new SwapError('Bad API key, reopen dashboard through Swarm Desktop', error) + } throw new SwapError(snackbarMessage, error) }) } diff --git a/src/utils/desktop.ts b/src/utils/desktop.ts index 805dbe9..a96d798 100644 --- a/src/utils/desktop.ts +++ b/src/utils/desktop.ts @@ -26,8 +26,8 @@ export async function getBzzPriceAsDai(desktopUrl: string): Promise { return DaiToken.fromDecimal(response.data) } -export async function upgradeToLightNode(desktopUrl: string, rpcProvider: string): Promise { - await updateDesktopConfiguration(desktopUrl, { +export function upgradeToLightNode(desktopUrl: string, rpcProvider: string): Promise { + return updateDesktopConfiguration(desktopUrl, { 'swap-enable': true, 'swap-endpoint': rpcProvider, }) @@ -43,8 +43,8 @@ export function getDesktopConfiguration(desktopUrl: string): Promise return getJson(`${desktopUrl}/config`) } -async function updateDesktopConfiguration(desktopUrl: string, values: Record): Promise { - await postJson(`${desktopUrl}/config`, values) +function updateDesktopConfiguration(desktopUrl: string, values: Record): Promise { + return postJson(`${desktopUrl}/config`, values) } export async function restartBeeNode(desktopUrl: string): Promise { diff --git a/src/utils/net.ts b/src/utils/net.ts index e215f6d..4dd4f94 100644 --- a/src/utils/net.ts +++ b/src/utils/net.ts @@ -1,12 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import axios from 'axios' +import { AuthError } from './AuthError' export function getJson>(url: string): Promise { return sendRequest(url, 'GET') as Promise } -export function postJson>(url: string, data?: T): Promise { +export function postJson>(url: string, data?: Record): Promise { return sendRequest(url, 'POST', data) as Promise } @@ -27,6 +28,15 @@ export async function sendRequest( method, headers, data, + }).catch(error => { + if (error?.response?.status === 401) { + throw new AuthError() + } + + if (error?.response?.data) { + throw Error(`Request ${method} ${url} failed: ${JSON.stringify(error.response.data)}`) + } + throw error }) return response.data