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
This commit is contained in:
Cafe137
2022-12-01 12:36:15 +01:00
committed by GitHub
parent dc04e26db4
commit 665ae063fa
8 changed files with 71 additions and 26 deletions
+4 -1
View File
@@ -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 (
<Grid container direction="column" justifyContent="center" alignItems="center">
+3 -4
View File
@@ -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
+26 -7
View File
@@ -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() {
+8 -9
View File
@@ -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
+10
View File
@@ -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'
}
+5
View File
@@ -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<T>(promise: Promise<T>, snackbarMessage: string): Promise<T> {
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)
})
}
+4 -4
View File
@@ -26,8 +26,8 @@ export async function getBzzPriceAsDai(desktopUrl: string): Promise<Token> {
return DaiToken.fromDecimal(response.data)
}
export async function upgradeToLightNode(desktopUrl: string, rpcProvider: string): Promise<void> {
await updateDesktopConfiguration(desktopUrl, {
export function upgradeToLightNode(desktopUrl: string, rpcProvider: string): Promise<BeeConfig> {
return updateDesktopConfiguration(desktopUrl, {
'swap-enable': true,
'swap-endpoint': rpcProvider,
})
@@ -43,8 +43,8 @@ export function getDesktopConfiguration(desktopUrl: string): Promise<BeeConfig>
return getJson(`${desktopUrl}/config`)
}
async function updateDesktopConfiguration(desktopUrl: string, values: Record<string, unknown>): Promise<void> {
await postJson(`${desktopUrl}/config`, values)
function updateDesktopConfiguration(desktopUrl: string, values: Record<string, unknown>): Promise<BeeConfig> {
return postJson(`${desktopUrl}/config`, values)
}
export async function restartBeeNode(desktopUrl: string): Promise<void> {
+11 -1
View File
@@ -1,12 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios from 'axios'
import { AuthError } from './AuthError'
export function getJson<T extends Record<string, any>>(url: string): Promise<T> {
return sendRequest(url, 'GET') as Promise<T>
}
export function postJson<T extends Record<string, any>>(url: string, data?: T): Promise<T> {
export function postJson<T extends Record<string, any>>(url: string, data?: Record<string, any>): Promise<T> {
return sendRequest(url, 'POST', data) as Promise<T>
}
@@ -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