feat: sync and update with all changes from fork (#720)
* feat: sync and update with all changes from fork * refactor: extract clipboard copy logic into custom hook * fix: correct spelling of DEFAULT_REFRESH_FREQUENCY_MS in Stamps and WalletBalance providers * refactor(ui-tests): replace fixed sleeps with condition-based waits * fix: handle null values for size and granteeCount in infoGroups * fix(lint): add newline at end of file in useClipboardCopy hook * fix(ui-tests): page.goto URL * refactor: update import paths for useClipboardCopy --------- Co-authored-by: Ferenc Sárai <sarai.ferenc@gmail.com>
This commit is contained in:
+120
-77
@@ -13,8 +13,20 @@ import {
|
||||
Topology,
|
||||
WalletBalance,
|
||||
} from '@ethersphere/bee-js'
|
||||
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
createContext,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
import { useLatestBeeRelease } from '../hooks/apiHooks'
|
||||
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
|
||||
const LAUNCH_GRACE_PERIOD = 15_000
|
||||
@@ -92,8 +104,8 @@ const initialValues: ContextInterface = {
|
||||
latestBeeRelease: null,
|
||||
isLoading: true,
|
||||
lastUpdate: null,
|
||||
start: () => {}, // eslint-disable-line
|
||||
stop: () => {}, // eslint-disable-line
|
||||
start: () => {},
|
||||
stop: () => {},
|
||||
refresh: () => Promise.reject(),
|
||||
}
|
||||
|
||||
@@ -101,7 +113,7 @@ export const Context = createContext<ContextInterface>(initialValues)
|
||||
export const Consumer = Context.Consumer
|
||||
|
||||
interface Props {
|
||||
children: ReactChild
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
function getStatus(
|
||||
@@ -162,12 +174,9 @@ function determineOverallStatus(status: Status, startedAt: number): CheckState {
|
||||
// This does not need to be exposed and works much better as variable than state variable which may trigger some unnecessary re-renders
|
||||
let isRefreshing = false
|
||||
|
||||
interface Props {
|
||||
children: ReactChild
|
||||
}
|
||||
|
||||
export function Provider({ children }: Props): ReactElement {
|
||||
const { beeApi } = useContext(SettingsContext)
|
||||
|
||||
const [beeVersion, setBeeVersion] = useState<string | null>(null)
|
||||
const [apiHealth, setApiHealth] = useState<boolean>(false)
|
||||
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
|
||||
@@ -182,42 +191,18 @@ export function Provider({ children }: Props): ReactElement {
|
||||
const [settlements, setSettlements] = useState<AllSettlements | null>(null)
|
||||
const [chainState, setChainState] = useState<ChainState | null>(null)
|
||||
const [walletBalance, setWalletBalance] = useState<WalletBalance | null>(null)
|
||||
const [startedAt] = useState(Date.now())
|
||||
const [startedAt] = useState(() => Date.now())
|
||||
|
||||
const { latestBeeRelease } = useLatestBeeRelease()
|
||||
|
||||
const [error, setError] = useState<Error | null>(initialValues.error)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(initialValues.isLoading)
|
||||
const [lastUpdate, setLastUpdate] = useState<number | null>(initialValues.lastUpdate)
|
||||
const [frequency, setFrequency] = useState<number | null>(30000)
|
||||
const [frequency, setFrequency] = useState<number | null>(REFRESH_WHEN_OK)
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true)
|
||||
const frequencyRef = useRef<number | null>(frequency)
|
||||
|
||||
setApiHealth(false)
|
||||
|
||||
if (beeApi !== null) refresh()
|
||||
}, [beeApi]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true)
|
||||
setNodeAddresses(null)
|
||||
setNodeTopology(null)
|
||||
setNodeInfo(null)
|
||||
setPeers(null)
|
||||
setChequebookAddress(null)
|
||||
setChequebookBalance(null)
|
||||
setPeerBalances(null)
|
||||
setPeerCheques(null)
|
||||
setSettlements(null)
|
||||
setChainState(null)
|
||||
|
||||
if (beeApi !== null) {
|
||||
refresh()
|
||||
}
|
||||
}, [beeApi]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const refresh = async () => {
|
||||
const refresh = useCallback(async () => {
|
||||
// Don't want to refresh when already refreshing
|
||||
if (isRefreshing) {
|
||||
return
|
||||
@@ -325,23 +310,59 @@ export function Provider({ children }: Props): ReactElement {
|
||||
setIsLoading(false)
|
||||
isRefreshing = false
|
||||
setLastUpdate(Date.now())
|
||||
}
|
||||
}, [beeApi])
|
||||
|
||||
const start = (freq = REFRESH_WHEN_OK) => {
|
||||
refresh()
|
||||
setFrequency(freq)
|
||||
}
|
||||
const stop = () => setFrequency(null)
|
||||
const start = useCallback(
|
||||
(freq = REFRESH_WHEN_OK) => {
|
||||
refresh()
|
||||
setFrequency(freq)
|
||||
},
|
||||
[refresh],
|
||||
)
|
||||
const stop = useCallback(() => setFrequency(null), [])
|
||||
|
||||
const status = getStatus(nodeInfo, apiHealth, topology, chequebookAddress, chequebookBalance, error, startedAt)
|
||||
const status = useMemo(
|
||||
() => getStatus(nodeInfo, apiHealth, topology, chequebookAddress, chequebookBalance, error, startedAt),
|
||||
[nodeInfo, apiHealth, topology, chequebookAddress, chequebookBalance, error, startedAt],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let newFrequency = REFRESH_WHEN_OK
|
||||
const setStates = () => {
|
||||
setIsLoading(true)
|
||||
setApiHealth(false)
|
||||
setNodeAddresses(null)
|
||||
setNodeTopology(null)
|
||||
setNodeInfo(null)
|
||||
setPeers(null)
|
||||
setChequebookAddress(null)
|
||||
setChequebookBalance(null)
|
||||
setPeerBalances(null)
|
||||
setPeerCheques(null)
|
||||
setSettlements(null)
|
||||
setChainState(null)
|
||||
|
||||
if (status.all !== 'OK') newFrequency = REFRESH_WHEN_ERROR
|
||||
if (beeApi !== null) {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
if (newFrequency !== frequency) setFrequency(newFrequency)
|
||||
}, [status.all, frequency])
|
||||
setStates()
|
||||
}, [beeApi, refresh])
|
||||
|
||||
useEffect(() => {
|
||||
frequencyRef.current = frequency
|
||||
}, [frequency])
|
||||
|
||||
useEffect(() => {
|
||||
const newFrequency = status.all !== CheckState.OK ? REFRESH_WHEN_ERROR : REFRESH_WHEN_OK
|
||||
const setFrequencyState = () => {
|
||||
if (newFrequency !== frequencyRef.current) {
|
||||
setFrequency(newFrequency)
|
||||
}
|
||||
}
|
||||
|
||||
setFrequencyState()
|
||||
}, [status.all])
|
||||
|
||||
// Start the update loop
|
||||
useEffect(() => {
|
||||
@@ -351,36 +372,58 @@ export function Provider({ children }: Props): ReactElement {
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
}, [frequency, beeApi]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, [frequency, beeApi, refresh])
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
beeVersion,
|
||||
status,
|
||||
error,
|
||||
apiHealth,
|
||||
nodeAddresses,
|
||||
nodeInfo,
|
||||
topology,
|
||||
chequebookAddress,
|
||||
peers,
|
||||
chequebookBalance,
|
||||
stake,
|
||||
peerBalances,
|
||||
peerCheques,
|
||||
settlements,
|
||||
chainState,
|
||||
walletBalance,
|
||||
latestBeeRelease,
|
||||
isLoading,
|
||||
lastUpdate,
|
||||
start,
|
||||
stop,
|
||||
refresh,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
beeVersion,
|
||||
status,
|
||||
error,
|
||||
apiHealth,
|
||||
nodeAddresses,
|
||||
nodeInfo,
|
||||
topology,
|
||||
chequebookAddress,
|
||||
peers,
|
||||
chequebookBalance,
|
||||
stake,
|
||||
peerBalances,
|
||||
peerCheques,
|
||||
settlements,
|
||||
chainState,
|
||||
walletBalance,
|
||||
latestBeeRelease,
|
||||
isLoading,
|
||||
lastUpdate,
|
||||
start,
|
||||
stop,
|
||||
refresh,
|
||||
}),
|
||||
[
|
||||
beeVersion,
|
||||
status,
|
||||
error,
|
||||
apiHealth,
|
||||
nodeAddresses,
|
||||
nodeInfo,
|
||||
topology,
|
||||
chequebookAddress,
|
||||
peers,
|
||||
chequebookBalance,
|
||||
stake,
|
||||
peerBalances,
|
||||
peerCheques,
|
||||
settlements,
|
||||
chainState,
|
||||
walletBalance,
|
||||
latestBeeRelease,
|
||||
isLoading,
|
||||
lastUpdate,
|
||||
start,
|
||||
stop,
|
||||
refresh,
|
||||
],
|
||||
)
|
||||
|
||||
return <Context.Provider value={contextValue}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
+11
-6
@@ -1,6 +1,11 @@
|
||||
import { createContext, ReactChild, ReactElement, useEffect, useState } from 'react'
|
||||
import { createContext, ReactElement, ReactNode, useEffect, useState } from 'react'
|
||||
|
||||
export type IdentityType = 'V3' | 'PRIVATE_KEY'
|
||||
import { LocalStorageKeys } from '../utils/localStorage'
|
||||
|
||||
export enum IdentityType {
|
||||
V3 = 'V3',
|
||||
PrivateKey = 'PRIVATE_KEY',
|
||||
}
|
||||
|
||||
export interface Identity {
|
||||
uuid: string
|
||||
@@ -18,14 +23,14 @@ interface ContextInterface {
|
||||
|
||||
const initialValues: ContextInterface = {
|
||||
identities: [],
|
||||
setIdentities: () => {}, // eslint-disable-line
|
||||
setIdentities: () => {},
|
||||
}
|
||||
|
||||
export const Context = createContext<ContextInterface>(initialValues)
|
||||
export const Consumer = Context.Consumer
|
||||
|
||||
interface Props {
|
||||
children: ReactChild
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function Provider({ children }: Props): ReactElement {
|
||||
@@ -33,11 +38,11 @@ export function Provider({ children }: Props): ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
setIdentities(JSON.parse(localStorage.getItem('feeds') || '[]'))
|
||||
setIdentities(JSON.parse(localStorage.getItem(LocalStorageKeys.feeds) || '[]'))
|
||||
} catch {
|
||||
setIdentities([])
|
||||
}
|
||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, [setIdentities])
|
||||
|
||||
return <Context.Provider value={{ identities, setIdentities }}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { createContext, ReactElement, ReactNode, useEffect, useState } from 'react'
|
||||
|
||||
import { createContext, ReactChild, ReactElement, useState, useEffect } from 'react'
|
||||
import { PREVIEW_DIMENSIONS } from '../constants'
|
||||
import { getMetadata } from '../utils/file'
|
||||
import { resize } from '../utils/image'
|
||||
import { PREVIEW_DIMENSIONS } from '../constants'
|
||||
|
||||
export type UploadOrigin = { origin: 'UPLOAD' | 'FEED'; uuid?: string }
|
||||
import { FileOrigin } from '@/pages/files/FileNavigation'
|
||||
|
||||
export const defaultUploadOrigin: UploadOrigin = { origin: 'UPLOAD' }
|
||||
export type UploadOrigin = { origin: FileOrigin.Upload | FileOrigin.Feed; uuid?: string }
|
||||
|
||||
export const defaultUploadOrigin: UploadOrigin = { origin: FileOrigin.Upload }
|
||||
|
||||
interface ContextInterface {
|
||||
files: FilePath[]
|
||||
@@ -30,7 +31,7 @@ export const Context = createContext<ContextInterface>(initialValues)
|
||||
export const Consumer = Context.Consumer
|
||||
|
||||
interface Props {
|
||||
children: ReactChild
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function Provider({ children }: Props): ReactElement {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { createContext, useCallback, useContext, useState, ReactNode, useEffect } from 'react'
|
||||
import { Bee, PostageBatch } from '@ethersphere/bee-js'
|
||||
import type { FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { FileManagerBase, FileManagerEvents } from '@solarpunkltd/file-manager-lib'
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
import { DriveInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { getSignerPk } from '../modules/filemanager/utils/common'
|
||||
import { DriveInfo, FileManagerBase, FileManagerEvents } from '@solarpunkltd/file-manager-lib'
|
||||
import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import { FILE_MANAGER_EVENTS } from '../modules/filemanager/constants/common'
|
||||
import { getUsableStamps } from '../modules/filemanager/utils/bee'
|
||||
import { getSignerPk } from '../modules/filemanager/utils/common'
|
||||
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
|
||||
interface ContextInterface {
|
||||
fm: FileManagerBase | null
|
||||
@@ -39,13 +40,15 @@ const initialValues: ContextInterface = {
|
||||
initializationError: false,
|
||||
showError: false,
|
||||
shallReset: false,
|
||||
setCurrentDrive: () => {}, // eslint-disable-line
|
||||
setCurrentStamp: () => {}, // eslint-disable-line
|
||||
resync: async () => {}, // eslint-disable-line
|
||||
init: async () => null, // eslint-disable-line
|
||||
setShowError: () => {}, // eslint-disable-line
|
||||
syncDrives: async () => {}, // eslint-disable-line
|
||||
refreshStamp: async () => undefined, // eslint-disable-line
|
||||
setCurrentDrive: () => {},
|
||||
setCurrentStamp: () => {},
|
||||
resync: async () => {},
|
||||
// eslint-disable-next-line require-await
|
||||
init: async () => null,
|
||||
setShowError: () => {},
|
||||
syncDrives: async () => {},
|
||||
// eslint-disable-next-line require-await
|
||||
refreshStamp: async () => undefined,
|
||||
}
|
||||
|
||||
export const Context = createContext<ContextInterface>(initialValues)
|
||||
@@ -82,6 +85,8 @@ const findDrives = (
|
||||
}
|
||||
|
||||
export function Provider({ children }: Props) {
|
||||
const initInProgressRef = useRef(false)
|
||||
|
||||
const { apiUrl, beeApi } = useContext(SettingsContext)
|
||||
|
||||
const [fm, setFm] = useState<FileManagerBase | null>(null)
|
||||
@@ -193,22 +198,30 @@ export function Provider({ children }: Props) {
|
||||
}
|
||||
}, [fm, syncDrives])
|
||||
|
||||
// no useCallback is needed because it caches the stamp
|
||||
const refreshStamp = async (batchId: string): Promise<PostageBatch | undefined> => {
|
||||
const usableStamps = await getUsableStamps(beeApi)
|
||||
const refreshedStamp = usableStamps.find(s => s.batchID.toString() === batchId)
|
||||
const refreshStamp = useCallback(
|
||||
async (batchId: string): Promise<PostageBatch | undefined> => {
|
||||
const usableStamps = await getUsableStamps(beeApi)
|
||||
const refreshedStamp = usableStamps.find(s => s.batchID.toString() === batchId)
|
||||
|
||||
if (currentStamp && currentStamp.batchID.toString() === batchId && refreshedStamp) {
|
||||
setCurrentStamp(refreshedStamp)
|
||||
}
|
||||
setCurrentStamp(prev => {
|
||||
if (prev && prev.batchID.toString() === batchId && refreshedStamp) {
|
||||
return refreshedStamp
|
||||
}
|
||||
|
||||
return refreshedStamp
|
||||
}
|
||||
return prev
|
||||
})
|
||||
|
||||
return refreshedStamp
|
||||
},
|
||||
[beeApi],
|
||||
)
|
||||
|
||||
const init = useCallback(async (): Promise<FileManagerBase | null> => {
|
||||
const pk = getSignerPk()
|
||||
|
||||
if (!apiUrl || !pk) return null
|
||||
if (!apiUrl || !pk || initInProgressRef.current) return null
|
||||
|
||||
initInProgressRef.current = true
|
||||
|
||||
setFm(null)
|
||||
setFiles([])
|
||||
@@ -235,7 +248,6 @@ export function Provider({ children }: Props) {
|
||||
}
|
||||
|
||||
setFm(manager)
|
||||
syncDrives(manager)
|
||||
syncFiles(manager)
|
||||
}
|
||||
}
|
||||
@@ -290,8 +302,10 @@ export function Provider({ children }: Props) {
|
||||
await manager.initialize()
|
||||
|
||||
return manager
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return null
|
||||
} finally {
|
||||
initInProgressRef.current = false
|
||||
}
|
||||
}, [apiUrl, syncDrives, syncFiles])
|
||||
|
||||
@@ -305,18 +319,17 @@ export function Provider({ children }: Props) {
|
||||
const refreshedDrive = manager.driveList.find(d => d.id.toString() === prevDriveId)
|
||||
setCurrentDrive(refreshedDrive)
|
||||
|
||||
const isValidCurrentStamp = (await getUsableStamps(beeApi)).find(
|
||||
s => s.batchID.toString() === prevStamp?.batchID.toString(),
|
||||
)
|
||||
const uStamps: PostageBatch[] = await getUsableStamps(beeApi)
|
||||
const isValidCurrentStamp = uStamps.find(s => s.batchID.toString() === prevStamp?.batchID.toString())
|
||||
|
||||
setCurrentStamp(isValidCurrentStamp)
|
||||
}
|
||||
}, [currentDrive?.id, currentStamp, init, setCurrentDrive, setCurrentStamp, beeApi])
|
||||
}, [currentDrive?.id, currentStamp, init, beeApi])
|
||||
|
||||
useEffect(() => {
|
||||
const pk = getSignerPk()
|
||||
|
||||
if (!pk || fm) return
|
||||
if (!pk || fm || initInProgressRef.current) return
|
||||
|
||||
const initFromLocalState = async () => {
|
||||
await init()
|
||||
@@ -325,29 +338,52 @@ export function Provider({ children }: Props) {
|
||||
initFromLocalState()
|
||||
}, [fm, init])
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
fm,
|
||||
files,
|
||||
currentDrive,
|
||||
currentStamp,
|
||||
drives,
|
||||
expiredDrives,
|
||||
adminDrive,
|
||||
initializationError,
|
||||
showError,
|
||||
shallReset,
|
||||
setCurrentDrive,
|
||||
setCurrentStamp,
|
||||
resync,
|
||||
init,
|
||||
setShowError,
|
||||
syncDrives: syncDrivesPublic,
|
||||
refreshStamp,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
useEffect(() => {
|
||||
if (fm && drives.length === 0 && !adminDrive) {
|
||||
syncDrives(fm)
|
||||
}
|
||||
}, [fm, drives.length, adminDrive, syncDrives])
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
fm,
|
||||
files,
|
||||
currentDrive,
|
||||
currentStamp,
|
||||
drives,
|
||||
expiredDrives,
|
||||
adminDrive,
|
||||
initializationError,
|
||||
showError,
|
||||
shallReset,
|
||||
setCurrentDrive,
|
||||
setCurrentStamp,
|
||||
resync,
|
||||
init,
|
||||
setShowError,
|
||||
syncDrives: syncDrivesPublic,
|
||||
refreshStamp,
|
||||
}),
|
||||
[
|
||||
fm,
|
||||
files,
|
||||
currentDrive,
|
||||
currentStamp,
|
||||
drives,
|
||||
expiredDrives,
|
||||
adminDrive,
|
||||
initializationError,
|
||||
showError,
|
||||
shallReset,
|
||||
setCurrentDrive,
|
||||
setCurrentStamp,
|
||||
resync,
|
||||
init,
|
||||
setShowError,
|
||||
syncDrivesPublic,
|
||||
refreshStamp,
|
||||
],
|
||||
)
|
||||
|
||||
return <Context.Provider value={contextValue}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
+10
-17
@@ -1,4 +1,4 @@
|
||||
import { createContext, ReactChild, ReactElement, useState, useEffect } from 'react'
|
||||
import { createContext, ReactElement, ReactNode, useState } from 'react'
|
||||
|
||||
// These need to be numeric values as they are used as indexes in the TabsContainer
|
||||
export enum Platforms {
|
||||
@@ -21,14 +21,14 @@ interface ContextInterface {
|
||||
|
||||
const initialValues: ContextInterface = {
|
||||
platform: SupportedPlatforms.macOS,
|
||||
setPlatform: () => {}, // eslint-disable-line
|
||||
setPlatform: () => {},
|
||||
}
|
||||
|
||||
export const Context = createContext<ContextInterface>(initialValues)
|
||||
export const Consumer = Context.Consumer
|
||||
|
||||
interface Props {
|
||||
children: ReactChild
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
function isSupportedPlatform(platform: unknown): platform is SupportedPlatforms {
|
||||
@@ -37,20 +37,16 @@ function isSupportedPlatform(platform: unknown): platform is SupportedPlatforms
|
||||
|
||||
function getOS(): Platforms | null {
|
||||
const userAgent = window.navigator.userAgent
|
||||
const platform = window.navigator.platform
|
||||
const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K']
|
||||
const windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']
|
||||
const iosPlatforms = ['iPhone', 'iPad', 'iPod']
|
||||
|
||||
if (macosPlatforms.includes(platform)) return Platforms.macOS
|
||||
if (/Macintosh|MacIntel|MacPPC|Mac68K/.test(userAgent)) return Platforms.macOS
|
||||
|
||||
if (iosPlatforms.includes(platform)) return Platforms.iOS
|
||||
if (/iPhone|iPad|iPod/.test(userAgent)) return Platforms.iOS
|
||||
|
||||
if (windowsPlatforms.includes(platform)) return Platforms.Windows
|
||||
if (/Win32|Win64|Windows|WinCE/.test(userAgent)) return Platforms.Windows
|
||||
|
||||
if (/Android/.test(userAgent)) return Platforms.Android
|
||||
|
||||
if (/Linux/.test(platform)) return Platforms.Linux
|
||||
if (/Linux/.test(userAgent)) return Platforms.Linux
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -92,14 +88,11 @@ export const cacheClearUrls = {
|
||||
}
|
||||
|
||||
export function Provider({ children }: Props): ReactElement {
|
||||
const [platform, setPlatform] = useState<SupportedPlatforms>(SupportedPlatforms.Linux)
|
||||
|
||||
// This is in useEffect as it really just needs to run once and not on each re-render
|
||||
useEffect(() => {
|
||||
const [platform, setPlatform] = useState<SupportedPlatforms>(() => {
|
||||
const os = getOS()
|
||||
|
||||
setPlatform(isSupportedPlatform(os) ? os : SupportedPlatforms.Linux)
|
||||
}, [])
|
||||
return isSupportedPlatform(os) ? os : SupportedPlatforms.Linux
|
||||
})
|
||||
|
||||
return <Context.Provider value={{ platform, setPlatform }}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
+67
-54
@@ -1,12 +1,11 @@
|
||||
import { Bee } from '@ethersphere/bee-js'
|
||||
import { providers } from 'ethers'
|
||||
import { ReactElement, ReactNode, createContext, useEffect, useState } from 'react'
|
||||
import { JsonRpcProvider } from 'ethers'
|
||||
import { createContext, ReactElement, ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { DEFAULT_BEE_API_HOST, DEFAULT_RPC_URL } from '../constants'
|
||||
import { useGetBeeConfig } from '../hooks/apiHooks'
|
||||
|
||||
const LocalStorageKeys = {
|
||||
providerUrl: 'json-rpc-provider',
|
||||
}
|
||||
import { newGnosisProvider } from '../utils/chain'
|
||||
import { LocalStorageKeys } from '../utils/localStorage'
|
||||
|
||||
interface ContextInterface {
|
||||
apiUrl: string
|
||||
@@ -16,7 +15,7 @@ interface ContextInterface {
|
||||
isDesktop: boolean
|
||||
desktopUrl: string
|
||||
rpcProviderUrl: string
|
||||
rpcProvider: providers.JsonRpcProvider | null
|
||||
rpcProvider: JsonRpcProvider | null
|
||||
cors: string | null
|
||||
dataDir: string | null
|
||||
ensResolver: string | null
|
||||
@@ -29,12 +28,12 @@ interface ContextInterface {
|
||||
const initialValues: ContextInterface = {
|
||||
beeApi: null,
|
||||
apiUrl: DEFAULT_BEE_API_HOST,
|
||||
setApiUrl: () => {}, // eslint-disable-line
|
||||
setApiUrl: () => {},
|
||||
lockedApiSettings: false,
|
||||
isDesktop: false,
|
||||
desktopApiKey: '',
|
||||
desktopUrl: window.location.origin,
|
||||
setAndPersistJsonRpcProvider: async () => {}, // eslint-disable-line
|
||||
setAndPersistJsonRpcProvider: async () => {},
|
||||
rpcProviderUrl: '',
|
||||
rpcProvider: null,
|
||||
cors: null,
|
||||
@@ -66,12 +65,13 @@ export function Provider({ children, ...propsSettings }: Props): ReactElement {
|
||||
localStorage.getItem(LocalStorageKeys.providerUrl) || propsSettings.defaultRpcUrl || DEFAULT_RPC_URL
|
||||
|
||||
const [apiUrl, setApiUrl] = useState<string>(
|
||||
localStorage.getItem('api_host') ?? propsSettings.beeApiUrl ?? initialValues.apiUrl,
|
||||
localStorage.getItem(LocalStorageKeys.apiHost) ?? propsSettings.beeApiUrl ?? initialValues.apiUrl,
|
||||
)
|
||||
const [beeApi, setBeeApi] = useState<Bee | null>(null)
|
||||
const [desktopApiKey, setDesktopApiKey] = useState<string>(initialValues.desktopApiKey)
|
||||
const [rpcProviderUrl, setRpcProviderUrl] = useState(propsProviderUrl)
|
||||
const [rpcProvider, setRpcProvider] = useState(new providers.JsonRpcProvider(propsProviderUrl))
|
||||
const [rpcProvider, setRpcProvider] = useState(newGnosisProvider(propsProviderUrl))
|
||||
|
||||
const { config, isLoading, error } = useGetBeeConfig(desktopUrl)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -79,56 +79,80 @@ export function Provider({ children, ...propsSettings }: Props): ReactElement {
|
||||
const newApiKey = urlSearchParams.get('v')
|
||||
|
||||
if (newApiKey) {
|
||||
localStorage.setItem('apiKey', newApiKey)
|
||||
localStorage.setItem(LocalStorageKeys.apiKey, newApiKey)
|
||||
window.location.search = ''
|
||||
setDesktopApiKey(newApiKey)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const url = makeHttpUrl(localStorage.getItem('api_host') ?? config?.['api-addr'] ?? apiUrl)
|
||||
try {
|
||||
setBeeApi(new Bee(url))
|
||||
} catch (e) {
|
||||
setBeeApi(null)
|
||||
const url = makeHttpUrl(localStorage.getItem(LocalStorageKeys.apiHost) ?? config?.['api-addr'] ?? apiUrl)
|
||||
|
||||
const setBeeApiState = () => {
|
||||
try {
|
||||
setBeeApi(new Bee(url))
|
||||
} catch {
|
||||
setBeeApi(null)
|
||||
}
|
||||
}
|
||||
|
||||
setBeeApiState()
|
||||
}, [config, apiUrl])
|
||||
|
||||
const updateApiUrl = (url: string) => {
|
||||
const updateApiUrl = useCallback((url: string) => {
|
||||
const userProvidedUrl = makeHttpUrl(url)
|
||||
|
||||
try {
|
||||
setBeeApi(new Bee(userProvidedUrl))
|
||||
localStorage.setItem('api_host', userProvidedUrl)
|
||||
localStorage.setItem(LocalStorageKeys.apiHost, userProvidedUrl)
|
||||
setApiUrl(userProvidedUrl)
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
setBeeApi(null)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
apiUrl,
|
||||
beeApi,
|
||||
setApiUrl: updateApiUrl,
|
||||
lockedApiSettings: Boolean(propsSettings.lockedApiSettings),
|
||||
desktopApiKey,
|
||||
isDesktop,
|
||||
desktopUrl,
|
||||
rpcProvider,
|
||||
rpcProviderUrl,
|
||||
cors: config?.['cors-allowed-origins'] ?? null,
|
||||
dataDir: config?.['data-dir'] ?? null,
|
||||
ensResolver: config?.['resolver-options'] ?? null,
|
||||
setAndPersistJsonRpcProvider: setAndPersistJsonRpcProviderClosure(setRpcProviderUrl, setRpcProvider),
|
||||
isLoading,
|
||||
error,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
const setAndPersistJsonRpcProvider = useCallback((providerUrl: string) => {
|
||||
localStorage.setItem(LocalStorageKeys.providerUrl, providerUrl)
|
||||
setRpcProviderUrl(providerUrl)
|
||||
setRpcProvider(newGnosisProvider(providerUrl))
|
||||
}, [])
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
apiUrl,
|
||||
beeApi,
|
||||
setApiUrl: updateApiUrl,
|
||||
lockedApiSettings: Boolean(propsSettings.lockedApiSettings),
|
||||
desktopApiKey,
|
||||
isDesktop,
|
||||
desktopUrl,
|
||||
rpcProvider,
|
||||
rpcProviderUrl,
|
||||
cors: config?.['cors-allowed-origins'] ?? null,
|
||||
dataDir: config?.['data-dir'] ?? null,
|
||||
ensResolver: config?.['resolver-options'] ?? null,
|
||||
setAndPersistJsonRpcProvider,
|
||||
isLoading,
|
||||
error,
|
||||
}),
|
||||
[
|
||||
apiUrl,
|
||||
beeApi,
|
||||
updateApiUrl,
|
||||
propsSettings.lockedApiSettings,
|
||||
desktopApiKey,
|
||||
isDesktop,
|
||||
desktopUrl,
|
||||
rpcProvider,
|
||||
rpcProviderUrl,
|
||||
config,
|
||||
setAndPersistJsonRpcProvider,
|
||||
isLoading,
|
||||
error,
|
||||
],
|
||||
)
|
||||
|
||||
return <Context.Provider value={contextValue}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
function makeHttpUrl(string: string): string {
|
||||
@@ -138,14 +162,3 @@ function makeHttpUrl(string: string): string {
|
||||
|
||||
return string
|
||||
}
|
||||
|
||||
function setAndPersistJsonRpcProviderClosure(
|
||||
setProviderUrl: (url: string) => void,
|
||||
setProvider: (prov: providers.JsonRpcProvider) => void,
|
||||
) {
|
||||
return (providerUrl: string) => {
|
||||
localStorage.setItem(LocalStorageKeys.providerUrl, providerUrl)
|
||||
setProviderUrl(providerUrl)
|
||||
setProvider(new providers.JsonRpcProvider(providerUrl))
|
||||
}
|
||||
}
|
||||
|
||||
+23
-16
@@ -1,7 +1,10 @@
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import { createContext, ReactElement, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
|
||||
const DEFAULT_REFRESH_REQUENCY_MS = 30_000
|
||||
|
||||
export interface EnrichedPostageBatch extends PostageBatch {
|
||||
usage: number
|
||||
usageText: string
|
||||
@@ -22,8 +25,8 @@ const initialValues: ContextInterface = {
|
||||
error: null,
|
||||
isLoading: false,
|
||||
lastUpdate: null,
|
||||
start: () => {}, // eslint-disable-line
|
||||
stop: () => {}, // eslint-disable-line
|
||||
start: () => {},
|
||||
stop: () => {},
|
||||
refresh: () => Promise.reject(),
|
||||
}
|
||||
|
||||
@@ -31,7 +34,7 @@ export const Context = createContext<ContextInterface>(initialValues)
|
||||
export const Consumer = Context.Consumer
|
||||
|
||||
interface Props {
|
||||
children: ReactChild
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function enrichStamp(postageBatch: PostageBatch): EnrichedPostageBatch {
|
||||
@@ -55,18 +58,20 @@ export function Provider({ children }: Props): ReactElement {
|
||||
const [lastUpdate, setLastUpdate] = useState<number | null>(initialValues.lastUpdate)
|
||||
const [frequency, setFrequency] = useState<number | null>(null)
|
||||
|
||||
const refresh = async () => {
|
||||
if (isLoading) {
|
||||
return
|
||||
}
|
||||
const isLoadingRef = useRef<boolean>(isLoading)
|
||||
|
||||
if (!beeApi) {
|
||||
useEffect(() => {
|
||||
isLoadingRef.current = isLoading
|
||||
}, [isLoading])
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (isLoadingRef.current || !beeApi) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const stamps = await beeApi.getAllPostageBatch()
|
||||
const stamps = await beeApi.getPostageBatches()
|
||||
|
||||
setStamps(stamps.map(enrichStamp))
|
||||
setLastUpdate(Date.now())
|
||||
@@ -76,22 +81,24 @@ export function Provider({ children }: Props): ReactElement {
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
}, [beeApi])
|
||||
|
||||
const start = (freq = 30000) => setFrequency(freq)
|
||||
const start = (freq = DEFAULT_REFRESH_REQUENCY_MS) => setFrequency(freq)
|
||||
const stop = () => setFrequency(null)
|
||||
|
||||
// Start the update loop
|
||||
useEffect(() => {
|
||||
refresh()
|
||||
if (beeApi) {
|
||||
refresh()
|
||||
}
|
||||
}, [beeApi, refresh])
|
||||
|
||||
// Start autorefresh only if the frequency is set
|
||||
useEffect(() => {
|
||||
if (frequency) {
|
||||
const interval = setInterval(refresh, frequency)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
}, [frequency]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, [frequency, refresh])
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ stamps, error, isLoading, lastUpdate, start, stop, refresh }}>
|
||||
|
||||
+13
-13
@@ -1,12 +1,9 @@
|
||||
import { Wallet } from 'ethers'
|
||||
import { createContext, ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
import { createContext, ReactElement, useCallback, useContext, useEffect, useState } from 'react'
|
||||
|
||||
const LocalStorageKeys = {
|
||||
depositWallet: 'deposit-wallet',
|
||||
giftWallets: 'gift-wallets',
|
||||
invitation: 'invitation',
|
||||
}
|
||||
import { LocalStorageKeys } from '../utils/localStorage'
|
||||
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
|
||||
interface ContextInterface {
|
||||
giftWallets: Wallet[]
|
||||
@@ -15,7 +12,7 @@ interface ContextInterface {
|
||||
|
||||
const initialValues: ContextInterface = {
|
||||
giftWallets: [],
|
||||
addGiftWallet: () => {}, // eslint-disable-line
|
||||
addGiftWallet: () => {},
|
||||
}
|
||||
|
||||
export const Context = createContext<ContextInterface>(initialValues)
|
||||
@@ -39,11 +36,14 @@ export function Provider({ children }: Props): ReactElement {
|
||||
}
|
||||
}, [rpcProvider])
|
||||
|
||||
function addGiftWallet(wallet: Wallet) {
|
||||
const newArray = [...giftWallets, wallet]
|
||||
localStorage.setItem(LocalStorageKeys.giftWallets, JSON.stringify(newArray.map(x => x.privateKey)))
|
||||
setGiftWallets(newArray)
|
||||
}
|
||||
const addGiftWallet = useCallback((wallet: Wallet) => {
|
||||
setGiftWallets(prev => {
|
||||
const newArray = [...prev, wallet]
|
||||
localStorage.setItem(LocalStorageKeys.giftWallets, JSON.stringify(newArray.map(x => x.privateKey)))
|
||||
|
||||
return newArray
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
createContext,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
import { WalletAddress } from '../utils/wallet'
|
||||
|
||||
import { Context as BeeContext } from './Bee'
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
|
||||
@@ -18,8 +30,8 @@ const initialValues: ContextInterface = {
|
||||
error: null,
|
||||
isLoading: false,
|
||||
lastUpdate: null,
|
||||
start: () => {}, // eslint-disable-line
|
||||
stop: () => {}, // eslint-disable-line
|
||||
start: () => {},
|
||||
stop: () => {},
|
||||
refresh: () => Promise.reject(),
|
||||
}
|
||||
|
||||
@@ -27,18 +39,31 @@ export const Context = createContext<ContextInterface>(initialValues)
|
||||
export const Consumer = Context.Consumer
|
||||
|
||||
interface Props {
|
||||
children: ReactChild
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const DEFAULT_REFRESH_REQUENCY_MS = 30_000
|
||||
|
||||
export function Provider({ children }: Props): ReactElement {
|
||||
const { rpcProvider } = useContext(SettingsContext)
|
||||
const { nodeAddresses } = useContext(BeeContext)
|
||||
const [balance, setBalance] = useState<WalletAddress | null>(initialValues.balance)
|
||||
const [error, setError] = useState<Error | null>(initialValues.error)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(initialValues.isLoading)
|
||||
|
||||
const balanceRef = useRef<WalletAddress | null>(balance)
|
||||
const isLoadingRef = useRef<boolean>(isLoading)
|
||||
const [lastUpdate, setLastUpdate] = useState<number | null>(initialValues.lastUpdate)
|
||||
const [frequency, setFrequency] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
balanceRef.current = balance
|
||||
}, [balance])
|
||||
|
||||
useEffect(() => {
|
||||
isLoadingRef.current = isLoading
|
||||
}, [isLoading])
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeAddresses?.ethereum && rpcProvider) {
|
||||
WalletAddress.make(nodeAddresses.ethereum.toHex(), rpcProvider).then(setBalance)
|
||||
@@ -47,26 +72,26 @@ export function Provider({ children }: Props): ReactElement {
|
||||
}
|
||||
}, [nodeAddresses, rpcProvider])
|
||||
|
||||
const refresh = async () => {
|
||||
const refresh = useCallback(async () => {
|
||||
// Don't want to refresh when already refreshing
|
||||
if (isLoading) return
|
||||
if (isLoadingRef.current) return
|
||||
|
||||
if (!balance) return
|
||||
if (!balanceRef.current) return
|
||||
|
||||
try {
|
||||
setIsLoading(true)
|
||||
|
||||
setBalance(await balance.refresh())
|
||||
setBalance(await balanceRef.current.refresh())
|
||||
setLastUpdate(Date.now())
|
||||
} catch (e) {
|
||||
setError(e as Error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const start = (freq = 30000) => setFrequency(freq)
|
||||
const stop = () => setFrequency(null)
|
||||
const start = useCallback((freq = DEFAULT_REFRESH_REQUENCY_MS) => setFrequency(freq), [])
|
||||
const stop = useCallback(() => setFrequency(null), [])
|
||||
|
||||
// Start the update loop
|
||||
useEffect(() => {
|
||||
@@ -78,11 +103,20 @@ export function Provider({ children }: Props): ReactElement {
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
}, [frequency]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, [frequency, refresh])
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ balance, error, isLoading, lastUpdate, start, stop, refresh }}>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
balance,
|
||||
error,
|
||||
isLoading,
|
||||
lastUpdate,
|
||||
start,
|
||||
stop,
|
||||
refresh,
|
||||
}),
|
||||
[balance, error, isLoading, lastUpdate, start, stop, refresh],
|
||||
)
|
||||
|
||||
return <Context.Provider value={contextValue}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user