import type { ChequebookBalance, Balance, Settlements } from '../types' 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, ChequebookAddressResponse, LastChequesResponse, Health, Peer, Topology, } from '@ethersphere/bee-js' import { useLatestBeeRelease } from '../hooks/apiHooks' interface Status { all: boolean version: boolean blockchainConnection: boolean debugApiConnection: boolean apiConnection: boolean topology: boolean chequebook: boolean } interface ContextInterface { status: Status latestPublishedVersion?: string latestUserVersion?: string latestUserVersionExact?: string isLatestBeeVersion: boolean latestBeeVersionUrl: string error: Error | null apiHealth: boolean debugApiHealth: Health | null nodeAddresses: NodeAddresses | null topology: Topology | null chequebookAddress: ChequebookAddressResponse | null peers: Peer[] | null chequebookBalance: ChequebookBalance | null peerBalances: Balance[] | null peerCheques: LastChequesResponse | null settlements: Settlements | null latestBeeRelease: LatestBeeRelease | null isLoading: boolean isRefreshing: boolean lastUpdate: number | null start: (frequency?: number) => void stop: () => void refresh: () => Promise } const initialValues: ContextInterface = { status: { all: false, version: false, blockchainConnection: false, debugApiConnection: false, apiConnection: false, topology: false, chequebook: false, }, latestPublishedVersion: undefined, latestUserVersion: undefined, latestUserVersionExact: undefined, isLatestBeeVersion: false, latestBeeVersionUrl: 'https://github.com/ethersphere/bee/releases/latest', error: null, apiHealth: false, debugApiHealth: null, nodeAddresses: null, topology: null, chequebookAddress: null, peers: null, chequebookBalance: null, peerBalances: null, peerCheques: null, settlements: null, latestBeeRelease: null, isLoading: true, isRefreshing: false, lastUpdate: null, start: () => {}, // eslint-disable-line stop: () => {}, // eslint-disable-line refresh: () => Promise.reject(), } export const Context = createContext(initialValues) export const Consumer = Context.Consumer interface Props { children: ReactChild } function getStatus( latestBeeRelease: LatestBeeRelease | null, debugApiHealth: Health | null, nodeAddresses: NodeAddresses | null, apiHealth: boolean, topology: Topology | null, chequebookAddress: ChequebookAddressResponse | null, chequebookBalance: ChequebookBalance | null, error: Error | null, ): Status { const status = { version: Boolean( debugApiHealth && semver.satisfies(debugApiHealth.version, engines.bee, { includePrerelease: true, }), ), blockchainConnection: Boolean(nodeAddresses?.ethereum), debugApiConnection: Boolean(debugApiHealth?.status === 'ok'), apiConnection: apiHealth, topology: Boolean(topology?.connected && topology?.connected > 0), chequebook: Boolean(chequebookAddress?.chequebookAddress) && chequebookBalance !== null && chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0), } return { ...status, all: !error && Object.values(status).every(v => v) } } 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) const [topology, setNodeTopology] = useState(null) const [chequebookAddress, setChequebookAddress] = useState(null) const [peers, setPeers] = useState(null) const [chequebookBalance, setChequebookBalance] = useState(null) const [peerBalances, setPeerBalances] = useState(null) const [peerCheques, setPeerCheques] = useState(null) const [settlements, setSettlements] = useState(null) const { latestBeeRelease } = useLatestBeeRelease() const [error, setError] = useState(initialValues.error) const [isLoading, setIsLoading] = useState(initialValues.isLoading) const [isRefreshing, setIsRefreshing] = useState(initialValues.isRefreshing) const [lastUpdate, setLastUpdate] = useState(initialValues.lastUpdate) const [frequency, setFrequency] = useState(30000) const latestPublishedVersion = semver.coerce(latestBeeRelease?.name)?.version 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) // Wrap the chequebook balance call to return BZZ values as Token object const chequeBalanceWrapper = async () => { const { totalBalance, availableBalance } = await beeDebugApi.getChequebookBalance() return { totalBalance: new Token(totalBalance), availableBalance: new Token(availableBalance), } } // Wrap the balances call to return BZZ values as Token object const peerBalanceWrapper = async () => { const { balances } = await beeDebugApi.getAllBalances() return balances.map(({ peer, balance }) => ({ peer, balance: new Token(balance) })) } // 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()) } } const start = (freq = 30000) => setFrequency(freq) const stop = () => setFrequency(null) // Start the update loop useEffect(() => { refresh() // Start autorefresh only if the frequency is set if (frequency) { const interval = setInterval(refresh, frequency) return () => clearInterval(interval) } }, [frequency, beeDebugApi, beeApi]) return ( {children} ) }