diff --git a/src/App.tsx b/src/App.tsx index c6b8dce..b79d13e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,6 +11,7 @@ import { lightTheme, darkTheme } from './theme' import { Provider as StampsProvider } from './providers/Stamps' import { Provider as PlatformProvider } from './providers/Platform' import { Provider as BeeProvider } from './providers/Bee' +import { Provider as SettingsProvider } from './providers/Settings' const App = (): ReactElement => { const [themeMode, toggleThemeMode] = useState('light') @@ -37,20 +38,22 @@ const App = (): ReactElement => { return (
- - - - - <> - - - - - - - - - + + + + + + <> + + + + + + + + + +
) diff --git a/src/components/CashoutModal.tsx b/src/components/CashoutModal.tsx index 7c89f50..e542c51 100644 --- a/src/components/CashoutModal.tsx +++ b/src/components/CashoutModal.tsx @@ -6,8 +6,8 @@ import DialogContent from '@material-ui/core/DialogContent' import DialogContentText from '@material-ui/core/DialogContentText' import DialogTitle from '@material-ui/core/DialogTitle' import { useSnackbar } from 'notistack' -import { ReactElement, useState } from 'react' -import { beeDebugApi } from '../services/bee' +import { ReactElement, useState, useContext } from 'react' +import { Context as SettingsContext } from '../providers/Settings' import EthereumAddress from './EthereumAddress' interface Props { @@ -19,6 +19,7 @@ export default function CheckoutModal({ peerId, uncashedAmount }: Props): ReactE const [open, setOpen] = useState(false) const [loadingCashout, setLoadingCashout] = useState(false) const { enqueueSnackbar } = useSnackbar() + const { beeDebugApi } = useContext(SettingsContext) const handleClickOpen = () => { setOpen(true) @@ -29,10 +30,12 @@ export default function CheckoutModal({ peerId, uncashedAmount }: Props): ReactE } const handleCashout = () => { + if (!beeDebugApi) return + if (peerId) { setLoadingCashout(true) - beeDebugApi.chequebook - .peerCashout(peerId) + beeDebugApi + .cashoutLastCheque(peerId) .then(res => { setOpen(false) enqueueSnackbar( diff --git a/src/components/ConnectToHost.tsx b/src/components/ConnectToHost.tsx index 15ddfab..4c5b084 100644 --- a/src/components/ConnectToHost.tsx +++ b/src/components/ConnectToHost.tsx @@ -1,62 +1,50 @@ import React, { ReactElement, useState } from 'react' -import { TextField, Button, CircularProgress, Container } from '@material-ui/core' +import { TextField, Button } from '@material-ui/core' interface Props { defaultHost?: string - hostName: string + setHost: (host: string) => void } export default function ConnectToHost(props: Props): ReactElement { const [hostInputVisible, toggleHostInputVisibility] = useState(false) - const [connectingToHost, setConnectingToHost] = useState(false) const [host, setHost] = useState('') const handleNewHostConnection = () => { if (host) { - setConnectingToHost(true) - sessionStorage.setItem(props.hostName, host) + props.setHost(host) toggleHostInputVisibility(!hostInputVisible) - window.location.reload() } } return (
- { - // FIXME: this should be broken up - /* eslint-disable no-nested-ternary */ - hostInputVisible ? ( -
- setHost(e.target.value)} - style={{ marginRight: '15px', minWidth: '300px' }} - /> - - -
- ) : connectingToHost ? ( - - - - ) : ( - - ) - /* eslint-enable no-nested-ternary */ - } + +
+ ) : ( + + )} ) } diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index 5244117..0000000 --- a/src/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -// These values can for now be constants because their change in the app reloads the page -export const apiHost = sessionStorage.getItem('api_host') || process.env.REACT_APP_BEE_HOST || 'http://localhost:1633' -export const debugApiHost = - sessionStorage.getItem('debug_api_host') || process.env.REACT_APP_BEE_DEBUG_HOST || 'http://localhost:1635' diff --git a/src/containers/DepositModal.tsx b/src/containers/DepositModal.tsx index 5d73b93..2896cdd 100644 --- a/src/containers/DepositModal.tsx +++ b/src/containers/DepositModal.tsx @@ -1,10 +1,12 @@ -import type { ReactElement } from 'react' -import { beeDebugApi } from '../services/bee' +import { ReactElement, useContext } from 'react' +import { Context as SettingsContext } from '../providers/Settings' import WDModal from '../components/WDModal' import { BigNumber } from 'bignumber.js' export default function DepositModal(): ReactElement { + const { beeDebugApi } = useContext(SettingsContext) + return ( { + if (!beeDebugApi) throw new Error('Bee Debug URL is not valid') + + return beeDebugApi.depositTokens(amount.toString()) + }} /> ) } diff --git a/src/containers/WithdrawModal.tsx b/src/containers/WithdrawModal.tsx index b606b86..5197a84 100644 --- a/src/containers/WithdrawModal.tsx +++ b/src/containers/WithdrawModal.tsx @@ -1,10 +1,12 @@ -import type { ReactElement } from 'react' -import { beeDebugApi } from '../services/bee' +import { ReactElement, useContext } from 'react' +import { Context as SettingsContext } from '../providers/Settings' import WDModal from '../components/WDModal' import { BigNumber } from 'bignumber.js' export default function WithdrawModal(): ReactElement { + const { beeDebugApi } = useContext(SettingsContext) + return ( { + if (!beeDebugApi) throw new Error('Bee Debug URL is not valid') + + return beeDebugApi.withdrawTokens(amount.toString()) + }} /> ) } diff --git a/src/hooks/accounting.ts b/src/hooks/accounting.ts index 6480f97..d5caf7f 100644 --- a/src/hooks/accounting.ts +++ b/src/hooks/accounting.ts @@ -1,16 +1,11 @@ -import { LastCashoutActionResponse } from '@ethersphere/bee-js' +import { LastCashoutActionResponse, BeeDebug } from '@ethersphere/bee-js' import { useEffect, useState } from 'react' import { Token } from '../models/Token' -import { beeDebugApi } from '../services/bee' import { makeRetriablePromise, unwrapPromiseSettlements } from '../utils' -import { Balance, Settlement, useApiPeerBalances, useApiSettlements } from './apiHooks' +import { Balance, Settlements, Settlement } from '../types' interface UseAccountingHook { - isLoading: boolean isLoadingUncashed: boolean - error: Error | null - totalsent: Token - totalreceived: Token accounting: Accounting[] | null } @@ -77,39 +72,34 @@ function mergeAccounting( ) } -export const useAccounting = (): UseAccountingHook => { - const settlements = useApiSettlements() - const balances = useApiPeerBalances() - +export const useAccounting = ( + beeDebugApi: BeeDebug | null, + settlements: Settlements | null, + balances: Balance[] | null, +): UseAccountingHook => { const [isLoadingUncashed, setIsloadingUncashed] = useState(false) const [uncashedAmounts, setUncashedAmounts] = useState(undefined) - const error = balances.error || settlements.error - useEffect(() => { // We don't have any settlements loaded yet or we are already loading/have loaded the uncashed amounts - if (isLoadingUncashed || !settlements.settlements || uncashedAmounts || error) return + if (isLoadingUncashed || !beeDebugApi || !settlements || uncashedAmounts) return setIsloadingUncashed(true) - const promises = settlements.settlements.settlements + const promises = settlements.settlements .filter(({ received }) => received.toBigNumber.gt('0')) - .map(({ peer }) => makeRetriablePromise(() => beeDebugApi.chequebook.getPeerLastCashout(peer))) + .map(({ peer }) => makeRetriablePromise(() => beeDebugApi.getLastCashoutAction(peer))) Promise.allSettled(promises).then(settlements => { const results = unwrapPromiseSettlements(settlements) setUncashedAmounts(results.fulfilled) setIsloadingUncashed(false) }) - }, [settlements, isLoadingUncashed, uncashedAmounts, error]) + }, [settlements, isLoadingUncashed, uncashedAmounts]) - const accounting = mergeAccounting(balances.peerBalances, settlements.settlements?.settlements, uncashedAmounts) + const accounting = mergeAccounting(balances, settlements?.settlements, uncashedAmounts) return { - isLoading: settlements.isLoadingSettlements || balances.isLoadingPeerBalances, isLoadingUncashed, - error, accounting, - totalsent: settlements.settlements?.totalSent || new Token('0'), - totalreceived: settlements.settlements?.totalReceived || new Token('0'), } } diff --git a/src/hooks/apiHooks.tsx b/src/hooks/apiHooks.tsx index 2ac0741..7361525 100644 --- a/src/hooks/apiHooks.tsx +++ b/src/hooks/apiHooks.tsx @@ -1,94 +1,5 @@ import { useState, useEffect } from 'react' - -import { beeDebugApi } from '../services/bee' import axios from 'axios' -import { Token } from '../models/Token' - -export interface Balance { - peer: string - balance: Token -} - -export interface PeerBalanceHook { - peerBalances: Balance[] | null - isLoadingPeerBalances: boolean - error: Error | null -} - -export const useApiPeerBalances = (): PeerBalanceHook => { - const [peerBalances, setPeerBalances] = useState(null) - const [isLoadingPeerBalances, setLoading] = useState(true) - const [error, setError] = useState(null) - - useEffect(() => { - setLoading(true) - beeDebugApi.balance - .balances() - .then(res => { - // for some reason sometimes these are numbers and not BigInts - const balances = res.balances.map(({ peer, balance }) => ({ peer, balance: new Token(balance) })) - setPeerBalances(balances) - }) - .catch(error => { - setError(error) - }) - .finally(() => { - setLoading(false) - }) - }, []) - - return { peerBalances, isLoadingPeerBalances, error } -} - -export interface Settlement { - peer: string - received: Token - sent: Token -} - -export interface Settlements { - totalReceived: Token - totalSent: Token - settlements: Settlement[] -} - -export interface SettlementsHook { - settlements: Settlements | null - isLoadingSettlements: boolean - error: Error | null -} - -export const useApiSettlements = (): SettlementsHook => { - const [settlements, setSettlements] = useState(null) - const [isLoadingSettlements, setLoading] = useState(true) - const [error, setError] = useState(null) - - useEffect(() => { - setLoading(true) - beeDebugApi.settlements - .getSettlements() - .then(({ totalReceived, settlements, totalSent }) => { - const set = { - totalReceived: new Token(totalReceived), - totalSent: new Token(totalSent), - settlements: settlements.map(({ peer, received, sent }) => ({ - peer, - received: new Token(received), - sent: new Token(sent), - })), - } - setSettlements(set) - }) - .catch(error => { - setError(error) - }) - .finally(() => { - setLoading(false) - }) - }, []) - - return { settlements, isLoadingSettlements, error } -} export interface LatestBeeReleaseHook { latestBeeRelease: LatestBeeRelease | null diff --git a/src/pages/accounting/index.tsx b/src/pages/accounting/index.tsx index 36be448..de1bd19 100644 --- a/src/pages/accounting/index.tsx +++ b/src/pages/accounting/index.tsx @@ -1,12 +1,12 @@ import { ReactElement, useContext } from 'react' import { Theme, createStyles, makeStyles } from '@material-ui/core/styles' -import { Container } from '@material-ui/core' import AccountCard from '../accounting/AccountCard' import BalancesTable from './BalancesTable' import EthereumAddressCard from '../../components/EthereumAddressCard' import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard' -import { Context } from '../../providers/Bee' +import { Context as BeeContext } from '../../providers/Bee' +import { Context as SettingsContext } from '../../providers/Settings' import { useAccounting } from '../../hooks/accounting' const useStyles = makeStyles((theme: Theme) => @@ -22,9 +22,12 @@ const useStyles = makeStyles((theme: Theme) => export default function Accounting(): ReactElement { const classes = useStyles() - const { status, nodeAddresses, chequebookAddress, chequebookBalance, settlements } = useContext(Context) + const { status, nodeAddresses, chequebookAddress, chequebookBalance, settlements, peerBalances } = useContext( + BeeContext, + ) + const { beeDebugApi } = useContext(SettingsContext) - const { accounting, isLoadingUncashed, error } = useAccounting() + const { accounting, isLoadingUncashed } = useAccounting(beeDebugApi, settlements, peerBalances) if (!status.all) return @@ -37,12 +40,7 @@ export default function Accounting(): ReactElement { totalreceived={settlements?.totalReceived} /> - {error && ( - - Error loading accounting details: {error.message} - - )} - {!error && } + ) } diff --git a/src/pages/files/Download.tsx b/src/pages/files/Download.tsx index 5641e12..f596c4f 100644 --- a/src/pages/files/Download.tsx +++ b/src/pages/files/Download.tsx @@ -1,8 +1,8 @@ -import { ReactElement, useState } from 'react' +import { ReactElement, useState, useContext } from 'react' import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' import { Paper, InputBase, IconButton, FormHelperText } from '@material-ui/core' import { Search } from '@material-ui/icons' -import { apiHost } from '../../constants' +import { Context as SettingsContext } from '../../providers/Settings' import { Utils } from '@ethersphere/bee-js' const useStyles = makeStyles((theme: Theme) => @@ -28,6 +28,7 @@ const useStyles = makeStyles((theme: Theme) => export default function Files(): ReactElement { const classes = useStyles() + const { apiUrl } = useContext(SettingsContext) const [referenceInput, setReferenceInput] = useState('') const [referenceError, setReferenceError] = useState(null) @@ -50,7 +51,7 @@ export default function Files(): ReactElement { onChange={handleReferenceChange} /> (null) const { isLoading, error, stamps } = useContext(Context) + const { beeApi } = useContext(SettingsContext) const { enqueueSnackbar } = useSnackbar() // Choose a postage stamp that has the lowest usage @@ -40,8 +41,11 @@ export default function Files(): ReactElement { const uploadFile = () => { if (file === null || selectedStamp === null) return + + if (!beeApi) return + setIsUploadingFile(true) - beeApi.files + beeApi .uploadFile(selectedStamp.batchID, file) .then(hash => { window.setTimeout(() => { diff --git a/src/pages/peers/PeerTable.tsx b/src/pages/peers/PeerTable.tsx index 585155e..18fbc88 100644 --- a/src/pages/peers/PeerTable.tsx +++ b/src/pages/peers/PeerTable.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useState } from 'react' +import { ReactElement, useState, useContext } from 'react' import { makeStyles } from '@material-ui/core/styles' import { Table, @@ -14,7 +14,7 @@ import { } from '@material-ui/core' import { Autorenew } from '@material-ui/icons' -import { beeDebugApi } from '../../services/bee' +import { Context as SettingsContext } from '../../providers/Settings' import type { Peer } from '@ethersphere/bee-js' const useStyles = makeStyles({ @@ -29,13 +29,14 @@ interface Props { function PeerTable(props: Props): ReactElement { const classes = useStyles() + const { beeDebugApi } = useContext(SettingsContext) const [peerLatency, setPeerLatency] = useState([{ peerId: '', rtt: '', loading: false }]) const PingPeer = (peerId: string) => { setPeerLatency([...peerLatency, { peerId: peerId, rtt: '', loading: true }]) - beeDebugApi.connectivity - .ping(peerId) + beeDebugApi + ?.pingPeer(peerId) .then(res => { setPeerLatency([...peerLatency, { peerId: peerId, rtt: res.rtt, loading: false }]) }) diff --git a/src/pages/settings/index.tsx b/src/pages/settings/index.tsx index cf08a4c..9fa9ad3 100644 --- a/src/pages/settings/index.tsx +++ b/src/pages/settings/index.tsx @@ -1,26 +1,20 @@ -import React, { ReactElement, useState } from 'react' +import React, { ReactElement, useState, useContext } from 'react' import { Paper, Container, TextField, Typography, Button } from '@material-ui/core' +import { Context as SettingsContext } from '../../providers/Settings' export default function Settings(): ReactElement { - const [refreshVisibility, toggleRefreshVisibility] = useState(false) - const [host, setHost] = useState('') - const [debugHost, setDebugHost] = useState('') + const { apiUrl, apiDebugUrl, setApiUrl, setDebugApiUrl } = useContext(SettingsContext) + const [host, setHost] = useState(apiUrl) + const [debugHost, setDebugHost] = useState(apiDebugUrl) - const handleNewHostConnection = () => { - if (host) { - sessionStorage.setItem('api_host', host) - } + const submit = () => { + if (host !== apiUrl) setApiUrl(host) - if (debugHost) { - sessionStorage.setItem('debug_api_host', debugHost) - } - - if (host || debugHost) { - toggleRefreshVisibility(!refreshVisibility) - window.location.reload() - } + if (debugHost !== apiDebugUrl) setDebugApiUrl(debugHost) } + const touched = host !== apiUrl || debugHost !== apiDebugUrl + return (
@@ -34,16 +28,13 @@ export default function Settings(): ReactElement { placeholder="ex: 127.0.0.0.1:1633" helperText="Enter node host override / port" fullWidth - defaultValue={ - sessionStorage.getItem('api_host') ? sessionStorage.getItem('api_host') : process.env.REACT_APP_BEE_HOST - } + defaultValue={apiUrl} margin="normal" InputLabelProps={{ shrink: true, }} onChange={e => { setHost(e.target.value) - toggleRefreshVisibility(true) }} variant="filled" /> @@ -55,14 +46,9 @@ export default function Settings(): ReactElement { placeholder="ex: 127.0.0.0.1:1635" helperText="Enter node debug host override / port" fullWidth - defaultValue={ - sessionStorage.getItem('debug_api_host') - ? sessionStorage.getItem('debug_api_host') - : process.env.REACT_APP_BEE_DEBUG_HOST - } + defaultValue={apiDebugUrl} onChange={e => { setDebugHost(e.target.value) - toggleRefreshVisibility(true) }} margin="normal" InputLabelProps={{ @@ -71,9 +57,9 @@ export default function Settings(): ReactElement { variant="filled" /> - {refreshVisibility ? ( + {touched ? (
-
diff --git a/src/pages/stamps/CreatePostageStampModal.tsx b/src/pages/stamps/CreatePostageStampModal.tsx index 14c3480..2379aa5 100644 --- a/src/pages/stamps/CreatePostageStampModal.tsx +++ b/src/pages/stamps/CreatePostageStampModal.tsx @@ -9,7 +9,7 @@ import DialogTitle from '@material-ui/core/DialogTitle' import BigNumber from 'bignumber.js' import { FormikHelpers, Form, Field, Formik } from 'formik' import { TextField } from 'formik-material-ui' -import { beeApi } from '../../services/bee' +import { Context as SettingsContext } from '../../providers/Settings' import { Context } from '../../providers/Stamps' import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' import { useSnackbar } from 'notistack' @@ -54,6 +54,7 @@ export default function FormDialog({ label }: Props): ReactElement { const classes = useStyles() const [open, setOpen] = React.useState(false) const { refresh } = useContext(Context) + const { beeApi } = useContext(SettingsContext) const handleClickOpen = () => setOpen(true) const handleClose = () => setOpen(false) const { enqueueSnackbar } = useSnackbar() @@ -66,10 +67,12 @@ export default function FormDialog({ label }: Props): ReactElement { // This is really just a typeguard, the validation pretty much guarantees these will have the right values if (!values.depth || !values.amount) return + if (!beeApi) return + const amount = BigInt(values.amount) const depth = Number.parseInt(values.depth) const options = values.label ? { label: values.label } : undefined - await beeApi.stamps.buyPostageStamp(amount, depth, options) + await beeApi.createPostageBatch(amount.toString(), depth, options) actions.resetForm() await refresh() handleClose() diff --git a/src/pages/status/SetupSteps/DebugConnectionCheck.tsx b/src/pages/status/SetupSteps/DebugConnectionCheck.tsx index 94a512b..dc2fb95 100644 --- a/src/pages/status/SetupSteps/DebugConnectionCheck.tsx +++ b/src/pages/status/SetupSteps/DebugConnectionCheck.tsx @@ -1,21 +1,29 @@ -import type { ReactElement } from 'react' +import { ReactElement, useContext } from 'react' import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/' import MuiAlert from '@material-ui/lab/Alert' import { ExpandMoreSharp } from '@material-ui/icons/' import ConnectToHost from '../../../components/ConnectToHost' import CodeBlockTabs from '../../../components/CodeBlockTabs' -import { debugApiHost } from '../../../constants' +import { Context as SettingsContext } from '../../../providers/Settings' type Props = StatusHookCommon export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null { + const { setDebugApiUrl, apiDebugUrl } = useContext(SettingsContext) + const changeDebugApiUrl = (
- Debug API ({debugApiHost}) + Debug API ({apiDebugUrl}) - + { + console.log(host) // eslint-disable-line + setDebugApiUrl(host) + }} + defaultHost={apiDebugUrl} + />
) @@ -29,7 +37,7 @@ export default function NodeConnectionCheck({ isOk }: Props): ReactElement | nul
- We cannot connect to your nodes debug API at {debugApiHost}. Please + We cannot connect to your nodes debug API at {apiDebugUrl}. Please check the following to troubleshoot your issue. } aria-controls="panel1a-content" id="panel1a-header"> diff --git a/src/pages/status/SetupSteps/NodeConnectionCheck.tsx b/src/pages/status/SetupSteps/NodeConnectionCheck.tsx index 6078937..91f4a1d 100644 --- a/src/pages/status/SetupSteps/NodeConnectionCheck.tsx +++ b/src/pages/status/SetupSteps/NodeConnectionCheck.tsx @@ -1,26 +1,28 @@ -import React, { ReactElement } from 'react' +import { ReactElement, useContext } from 'react' import { Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core/' import { ExpandMoreSharp } from '@material-ui/icons/' import ConnectToHost from '../../../components/ConnectToHost' import CodeBlockTabs from '../../../components/CodeBlockTabs' -import { apiHost } from '../../../constants' +import { Context as SettingsContext } from '../../../providers/Settings' type Props = StatusHookCommon export default function NodeConnectionCheck({ isOk }: Props): ReactElement | null { + const { setApiUrl, apiUrl } = useContext(SettingsContext) + return (
- Node API ({apiHost}) + Node API ({apiUrl}) - +
{!isOk && ( - We cannot connect to your nodes API at {apiHost}. Please check the + We cannot connect to your nodes API at {apiUrl}. Please check the following to troubleshoot your issue. } aria-controls="panel1a-content" id="panel1a-header"> diff --git a/src/pages/status/index.tsx b/src/pages/status/index.tsx index 5d4ed16..a65ba71 100644 --- a/src/pages/status/index.tsx +++ b/src/pages/status/index.tsx @@ -4,7 +4,7 @@ import { makeStyles, Theme, createStyles } from '@material-ui/core/styles' import NodeSetupWorkflow from './NodeSetupWorkflow' import StatusCard from './StatusCard' import EthereumAddressCard from '../../components/EthereumAddressCard' -import { Context } from '../../providers/Bee' +import { Context as BeeContext } from '../../providers/Bee' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -27,7 +27,7 @@ export default function Status(): ReactElement { topology, nodeAddresses, chequebookAddress, - } = useContext(Context) + } = useContext(BeeContext) return (
diff --git a/src/providers/Bee.tsx b/src/providers/Bee.tsx index 89ff3a5..4327834 100644 --- a/src/providers/Bee.tsx +++ b/src/providers/Bee.tsx @@ -1,9 +1,9 @@ import type { ChequebookBalance, Balance, Settlements } from '../types' -import { createContext, ReactChild, ReactElement, useEffect, useState } from 'react' -import { beeApi, beeDebugApi } from '../services/bee' +import { createContext, ReactChild, ReactElement, useEffect, useState, useContext } from 'react' import { Token } from '../models/Token' import semver from 'semver' import { engines } from '../../package.json' +import { Context as SettingsContext } from './Settings' import type { NodeAddresses, @@ -125,6 +125,7 @@ function getStatus( } export function Provider({ children }: Props): ReactElement { + const { beeApi, beeDebugApi } = useContext(SettingsContext) const [apiHealth, setApiHealth] = useState(false) const [debugApiHealth, setDebugApiHealth] = useState(null) const [nodeAddresses, setNodeAddresses] = useState(null) @@ -147,47 +148,143 @@ export function Provider({ children }: Props): ReactElement { const latestUserVersion = semver.coerce(debugApiHealth?.version)?.version const latestUserVersionExact = debugApiHealth?.version + useEffect(() => { + setIsLoading(true) + + setApiHealth(false) + + refresh() + }, [beeApi]) + + useEffect(() => { + setIsLoading(true) + + setDebugApiHealth(null) + setNodeAddresses(null) + setNodeTopology(null) + setPeers(null) + setChequebookAddress(null) + setChequebookBalance(null) + setPeerBalances(null) + setPeerCheques(null) + setSettlements(null) + + refresh() + }, [beeDebugApi]) + const refresh = async () => { // Don't want to refresh when already refreshing if (isRefreshing) return + // Not a valid bee api + if (!beeApi || !beeDebugApi) { + setIsLoading(false) + + return + } + try { setIsRefreshing(true) + setError(null) - setApiHealth(await beeApi.status.health()) - setDebugApiHealth(await beeDebugApi.status.nodeHealth()) - setNodeAddresses(await beeDebugApi.connectivity.addresses()) - setNodeTopology(await beeDebugApi.connectivity.topology()) - setChequebookAddress(await beeDebugApi.chequebook.address()) - setPeers(await beeDebugApi.connectivity.listPeers()) + // Wrap the chequebook balance call to return BZZ values as Token object + const chequeBalanceWrapper = async () => { + const { totalBalance, availableBalance } = await beeDebugApi.getChequebookBalance() - const { totalBalance, availableBalance } = await beeDebugApi.chequebook.balance() - setChequebookBalance({ - totalBalance: new Token(totalBalance), - availableBalance: new Token(availableBalance), - }) + return { + totalBalance: new Token(totalBalance), + availableBalance: new Token(availableBalance), + } + } - const { balances } = await beeDebugApi.balance.balances() - setPeerBalances(balances.map(({ peer, balance }) => ({ peer, balance: new Token(balance) }))) + // Wrap the balances call to return BZZ values as Token object + const peerBalanceWrapper = async () => { + const { balances } = await beeDebugApi.getAllBalances() - setPeerCheques(await beeDebugApi.chequebook.getLastCheques()) - const { totalReceived, settlements, totalSent } = await beeDebugApi.settlements.getSettlements() - setSettlements({ - totalReceived: new Token(totalReceived), - totalSent: new Token(totalSent), - settlements: settlements.map(({ peer, received, sent }) => ({ - peer, - received: new Token(received), - sent: new Token(sent), - })), - }) + return balances.map(({ peer, balance }) => ({ peer, balance: new Token(balance) })) + } - setLastUpdate(Date.now()) + // Wrap the settlements call to return BZZ values as Token object + const settlementsWrapper = async () => { + const { totalReceived, settlements, totalSent } = await beeDebugApi.getAllSettlements() + + return { + totalReceived: new Token(totalReceived), + totalSent: new Token(totalSent), + settlements: settlements.map(({ peer, received, sent }) => ({ + peer, + received: new Token(received), + sent: new Token(sent), + })), + } + } + + const promises = [ + // API health + beeApi + .isConnected() + .then(setApiHealth) + .catch(() => setApiHealth(false)), + + // Debug API health + beeDebugApi + .getHealth() + .then(setDebugApiHealth) + .catch(() => setDebugApiHealth(null)), + + // Node Addresses + beeDebugApi + .getNodeAddresses() + .then(setNodeAddresses) + .catch(() => setNodeAddresses(null)), + + // Network Topology + beeDebugApi + .getTopology() + .then(setNodeTopology) + .catch(() => setNodeTopology(null)), + + // Peers + beeDebugApi + .getPeers() + .then(setPeers) + .catch(() => setPeers(null)), + + // Chequebook address + beeDebugApi + .getChequebookAddress() + .then(setChequebookAddress) + .catch(() => setChequebookAddress(null)), + + // Cheques + beeDebugApi + .getLastCheques() + .then(setPeerCheques) + .catch(() => setPeerCheques(null)), + + // Chequebook balance + chequeBalanceWrapper() + .then(setChequebookBalance) + .catch(() => setChequebookBalance(null)), + + // Peer balances + peerBalanceWrapper() + .then(setPeerBalances) + .catch(() => setPeerBalances(null)), + + // Settlements + settlementsWrapper() + .then(setSettlements) + .catch(() => setSettlements(null)), + ] + + await Promise.allSettled(promises) } catch (e) { setError(e) } finally { setIsLoading(false) setIsRefreshing(false) + setLastUpdate(Date.now()) } } @@ -204,7 +301,7 @@ export function Provider({ children }: Props): ReactElement { return () => clearInterval(interval) } - }, [frequency]) + }, [frequency, beeDebugApi, beeApi]) return ( void + setDebugApiUrl: (url: string) => void +} + +const initialValues: ContextInterface = { + apiUrl: sessionStorage.getItem('api_host') || process.env.REACT_APP_BEE_HOST || 'http://localhost:1633', + apiDebugUrl: + sessionStorage.getItem('debug_api_host') || process.env.REACT_APP_BEE_DEBUG_HOST || 'http://localhost:1635', + beeApi: null, + beeDebugApi: null, + setApiUrl: (url: string) => {}, // eslint-disable-line + setDebugApiUrl: (url: string) => {}, // eslint-disable-line +} + +export const Context = createContext(initialValues) +export const Consumer = Context.Consumer + +interface Props { + children: ReactChild +} + +export function Provider({ children }: Props): ReactElement { + const [apiUrl, setApiUrl] = useState(initialValues.apiUrl) + const [apiDebugUrl, setDebugApiUrl] = useState(initialValues.apiDebugUrl) + const [beeApi, setBeeApi] = useState(null) + const [beeDebugApi, setBeeDebugApi] = useState(null) + + useEffect(() => { + try { + setBeeApi(new Bee(apiUrl)) + sessionStorage.setItem('api_host', apiUrl) + } catch (e) { + setBeeApi(null) + } + }, [apiUrl]) + + useEffect(() => { + try { + setBeeDebugApi(new BeeDebug(apiDebugUrl)) + sessionStorage.setItem('debug_api_host', apiDebugUrl) + } catch (e) { + setBeeDebugApi(null) + } + }, [apiDebugUrl]) + + return ( + + {children} + + ) +} diff --git a/src/providers/Stamps.tsx b/src/providers/Stamps.tsx index e0b4baf..51296e1 100644 --- a/src/providers/Stamps.tsx +++ b/src/providers/Stamps.tsx @@ -1,6 +1,6 @@ import { PostageBatch } from '@ethersphere/bee-js' -import { createContext, ReactChild, ReactElement, useEffect, useState } from 'react' -import { beeApi } from '../services/bee' +import { createContext, ReactChild, ReactElement, useEffect, useState, useContext } from 'react' +import { Context as SettingsContext } from './Settings' export interface EnrichedPostageBatch extends PostageBatch { usage: number @@ -48,6 +48,7 @@ function enrichStamp(postageBatch: PostageBatch): EnrichedPostageBatch { } export function Provider({ children }: Props): ReactElement { + const { beeApi } = useContext(SettingsContext) const [stamps, setStamps] = useState(initialValues.stamps) const [error, setError] = useState(initialValues.error) const [isLoading, setIsLoading] = useState(initialValues.isLoading) @@ -58,9 +59,11 @@ export function Provider({ children }: Props): ReactElement { // Don't want to refresh when already refreshing if (isLoading) return + if (!beeApi) return + try { setIsLoading(true) - const stamps = await beeApi.stamps.getPostageStamps() + const stamps = await beeApi.getAllPostageBatch() setStamps(stamps.map(enrichStamp)) setLastUpdate(Date.now()) diff --git a/src/services/bee.tsx b/src/services/bee.tsx deleted file mode 100644 index 0fa87fc..0000000 --- a/src/services/bee.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { - Address, - AllSettlements, - BalanceResponse, - Bee, - BeeDebug, - ChequebookAddressResponse, - ChequebookBalanceResponse, - Data, - FileData, - Health, - LastCashoutActionResponse, - LastChequesForPeerResponse, - LastChequesResponse, - NodeAddresses, - Peer, - PingResponse, - PostageBatch, - PostageBatchOptions, - Reference, - Topology, -} from '@ethersphere/bee-js' -import { apiHost, debugApiHost } from '../constants' - -const beeJSClient = () => new Bee(apiHost) - -const beeJSDebugClient = () => new BeeDebug(debugApiHost) - -export const beeApi = { - status: { - health(): Promise { - return beeJSClient().isConnected() - }, - }, - files: { - uploadFile(postageBatchId: Address, file: File): Promise { - return beeJSClient().uploadFile(postageBatchId, file) - }, - downloadFile(hash: string | Reference): Promise> { - return beeJSClient().downloadFile(hash) - }, - }, - stamps: { - getPostageStamps(): Promise { - return beeJSClient().getAllPostageBatch() - }, - buyPostageStamp(amount: bigint, depth: number, options: PostageBatchOptions = {}): Promise
{ - return beeJSClient().createPostageBatch(amount.toString(), depth, options) - }, - }, -} - -export const beeDebugApi = { - status: { - nodeHealth(): Promise { - return beeJSDebugClient().getHealth() - }, - }, - connectivity: { - addresses(): Promise { - return beeJSDebugClient().getNodeAddresses() - }, - listPeers(): Promise { - return beeJSDebugClient().getPeers() - }, - topology(): Promise { - return beeJSDebugClient().getTopology() - }, - ping(peerId: string): Promise { - return beeJSDebugClient().pingPeer(peerId) - }, - }, - balance: { - balances(): Promise { - return beeJSDebugClient().getAllBalances() - }, - }, - chequebook: { - address(): Promise { - return beeJSDebugClient().getChequebookAddress() - }, - balance(): Promise { - return beeJSDebugClient().getChequebookBalance() - }, - getLastCheques(): Promise { - return beeJSDebugClient().getLastCheques() - }, - peerCashout(peerId: string): Promise { - return beeJSDebugClient().cashoutLastCheque(peerId) - }, - getPeerLastCashout(peerId: string): Promise { - return beeJSDebugClient().getLastCashoutAction(peerId) - }, - getPeerLastCheques(peerId: string): Promise { - return beeJSDebugClient().getLastChequesForPeer(peerId) - }, - withdraw(amount: bigint): Promise { - return beeJSDebugClient().withdrawTokens(amount.toString()) - }, - deposit(amount: bigint): Promise { - return beeJSDebugClient().depositTokens(amount.toString()) - }, - }, - settlements: { - getSettlements(): Promise { - return beeJSDebugClient().getAllSettlements() - }, - }, -}