Skip to content

Commit

Permalink
chore: token state
Browse files Browse the repository at this point in the history
chore: add dev ui

chore: ignore empty object error

chore: comment
  • Loading branch information
simonheys committed Jul 8, 2022
1 parent 0025fdb commit 68beaae
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 38 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"scripts": {
"format": "prettier --loglevel warn --write \"**/*.{js,jsx,ts,tsx,css,md,yml,json}\"",
"dev": "lerna run dev --parallel",
"dev:ui": "lerna run dev:ui --parallel",
"clean": "rm -rf packages/extension/dist packages/get-starket/dist",
"build": "lerna run build --stream",
"build:sourcemaps": "GEN_SOURCE_MAPS=true lerna run build",
Expand Down
1 change: 1 addition & 0 deletions packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"build:sourcemaps": "NODE_ENV=production GEN_SOURCE_MAPS=true webpack",
"start": "webpack",
"dev": "webpack --color --watch",
"dev:ui": "SHOW_DEV_UI=true webpack --color --watch",
"lint": "eslint . --cache --ext .ts,.tsx",
"test": "vitest run",
"pretest:e2e": "playwright install chromium && ([ -d dist ] || yarn build)",
Expand Down
2 changes: 2 additions & 0 deletions packages/extension/src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AppRoutes } from "./AppRoutes"
import { ErrorBoundary } from "./components/ErrorBoundary"
import { LoadingScreen } from "./features/actions/LoadingScreen"
import { useExtensionIsInTab } from "./features/browser/tabs"
import DevUI from "./features/dev/DevUI"
import { useTracking } from "./services/analytics"
import { swrCacheProvider } from "./services/swr"
import { GlobalStyle, theme } from "./theme"
Expand All @@ -24,6 +25,7 @@ export const App: FC = () => {
rel="stylesheet"
/>
<GlobalStyle extensionIsInTab={extensionIsInTab} />
{process.env.SHOW_DEV_UI && <DevUI />}
<ErrorBoundary fallback={<AppErrorBoundaryFallback />}>
<Suspense fallback={<LoadingScreen />}>
<AppRoutes />
Expand Down
41 changes: 21 additions & 20 deletions packages/extension/src/ui/features/accountTokens/AccountTokens.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC, Suspense, useEffect, useRef } from "react"
import { FC, useEffect, useRef } from "react"
import { Link, useNavigate } from "react-router-dom"
import styled from "styled-components"
import useSWR from "swr"
Expand All @@ -8,7 +8,6 @@ import {
isDeprecated,
} from "../../../shared/wallet.service"
import { useAppState } from "../../app.state"
import { ErrorBoundary } from "../../components/ErrorBoundary"
import ErrorBoundaryFallbackWithCopyError from "../../components/ErrorBoundaryFallbackWithCopyError"
import { IconButton } from "../../components/IconButton"
import { AddIcon } from "../../components/Icons/MuiIcons"
Expand Down Expand Up @@ -75,7 +74,8 @@ export const AccountTokens: FC<AccountTokensProps> = ({ account }) => {
{ suspense: false },
)

const { isValidating, tokenDetails } = useTokensWithBalance(account)
const { isValidating, error, tokenDetails, tokenDetailsIsInitialising } =
useTokensWithBalance(account)

const { data: needsUpgrade = false, mutate } = useSWR(
[
Expand Down Expand Up @@ -131,30 +131,31 @@ export const AccountTokens: FC<AccountTokensProps> = ({ account }) => {
)}
{showNoBalanceForUpgrade && <UpgradeBanner canNotPay />}
<PendingTransactions account={account} />
<ErrorBoundary
fallback={
<ErrorBoundaryFallbackWithCopyError
message={"Sorry, an error occurred fetching tokens"}
{error ? (
<ErrorBoundaryFallbackWithCopyError
error={error}
message={"Sorry, an error occurred fetching tokens"}
/>
) : (
<>
<TokenList
showTitle={showPendingTransactions}
isValidating={isValidating}
tokenList={tokenDetails}
variant={tokenListVariant}
/>
}
>
<Suspense fallback={<Spinner size={64} style={{ marginTop: 40 }} />}>
<>
<TokenList
showTitle={showPendingTransactions}
variant={tokenListVariant}
isValidating={isValidating}
tokenList={tokenDetails}
/>
{tokenDetailsIsInitialising ? (
<Spinner size={64} style={{ marginTop: 40 }} />
) : (
<TokenWrapper {...makeClickable(() => navigate(routes.newToken()))}>
<AddTokenIconButton size={40}>
<AddIcon />
</AddTokenIconButton>
<TokenTitle>Add token</TokenTitle>
</TokenWrapper>
</>
</Suspense>
</ErrorBoundary>
)}
</>
)}
</Container>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const TokenList: FC<TokenListProps> = ({
navigateToSend = false,
}) => {
if (!tokenList) {
return <></>
return null
}

return (
Expand Down
87 changes: 70 additions & 17 deletions packages/extension/src/ui/features/accountTokens/tokens.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useEffect, useMemo } from "react"
import { number } from "starknet"
import useSWR from "swr"
import create from "zustand"
import shallow from "zustand/shallow"

import { messageStream } from "../../../shared/messages"
import { Token, equalToken } from "../../../shared/token"
Expand Down Expand Up @@ -106,6 +107,7 @@ export const selectTokensByNetwork = (networkId: string) => (state: State) =>

interface UseTokens {
tokenDetails: TokenDetailsWithBalance[]
tokenDetailsIsInitialising: boolean
isValidating: boolean
error?: any
}
Expand All @@ -114,41 +116,82 @@ export const useTokensWithBalance = (
account?: BaseWalletAccount,
): UseTokens => {
const selectedAccount = useAccount(account)
const tokensInNetwork = useTokens(
selectTokensByNetwork(selectedAccount?.networkId ?? ""),
)
const tokenAddresses = useMemo(
() => tokensInNetwork.map((t) => t.address),
[tokensInNetwork],
)

const { data, isValidating, error, mutate } = useSWR(
const tokensInNetworkSelector = useMemo(() => {
return selectTokensByNetwork(selectedAccount?.networkId ?? "")
}, [selectedAccount?.networkId])

const tokenAddressesSelector = useMemo(() => {
return (state: State) => {
const tokensInNetwork = tokensInNetworkSelector(state)
return tokensInNetwork.map((t) => t.address)
}
}, [tokensInNetworkSelector])

// shallow compare objects and arrays
const tokensInNetwork = useTokens(tokensInNetworkSelector, shallow)
const tokenAddresses = useTokens(tokenAddressesSelector, shallow)

const {
data,
isValidating,
error: rawError,
mutate,
} = useSWR(
// skip if no account selected
selectedAccount && [
getAccountIdentifier(selectedAccount),
"accountTokenBalances",
],
async () => {
if (!selectedAccount) {
return {}
return
}

const balances = await fetchAllTokensBalance(
tokenAddresses,
selectedAccount,
)

return balances ?? {}
return balances
},
{
suspense: true,
refreshInterval: 30000,
},
)

// refetch balances on transaction success or token edit (token was added or removed)
/**
* FIXME:
* Investigate what causes the SWR hook above to cache an empty object `error: {}`, usually observed after reloading the extension
*
* This is subsequently retreived by SWR from the cache and causes an immediately defined error if left unchecked
*
* You can verify this by debugging in the `set` method of `swrCacheProvider`, and
* checking for SWR setting a value containing a key of `error` with an empty object {}
*
* As a workaround we check for empty object here and treat as undefined while the hook revalidates properly
*
*/

const error: any = useMemo(() => {
if (!rawError) {
return
}
try {
if (JSON.stringify(rawError) === "{}") {
console.warn("FIXME: Ignoring empty object {} error")
return
}
} catch (e) {
// ignore any stringify errors
}
return rawError
}, [rawError])

const tokenDetailsIsInitialising = !error && !data && isValidating

// refetch balances on transaction success
useEffect(() => {
mutate()
const subscription = messageStream.subscribe(([msg]) => {
if (msg.type === "TRANSACTION_SUCCESS") {
mutate() // refetch balances
Expand All @@ -159,7 +202,12 @@ export const useTokensWithBalance = (
subscription.unsubscribe()
}
}
}, [tokenAddresses.join(":")])
}, [mutate])

// refetch balances on token edit (token was added or removed)
useEffect(() => {
mutate()
}, [mutate, tokenAddresses])

const tokenDetails = useMemo(() => {
return tokensInNetwork
Expand All @@ -170,7 +218,12 @@ export const useTokensWithBalance = (
.filter(
(token) => token.showAlways || (token.balance && token.balance.gt(0)),
)
}, [tokenAddresses, data])

return { tokenDetails, isValidating, error }
}, [tokensInNetwork, data])

return {
tokenDetails,
tokenDetailsIsInitialising,
isValidating,
error,
}
}
46 changes: 46 additions & 0 deletions packages/extension/src/ui/features/dev/DevUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { FC, useCallback } from "react"
import styled from "styled-components"
import browser from "webextension-polyfill"

import { RowCentered } from "../../components/Row"
import { useBackupRequired } from "../recovery/backupDownload.state"

const Container = styled(RowCentered)`
position: fixed;
z-index: 123;
`

const DevButton = styled.div`
background-color: rgba(255, 255, 255, 0.15);
padding: 4px 8px;
cursor: pointer;
border-radius: 500px;
margin-top: 4px;
&:hover {
background-color: rgba(255, 255, 255, 0.25);
}
`

const DevUI: FC = () => {
const reset = useCallback(() => {
// reset cache
const backupState = useBackupRequired.getState()
localStorage.clear()
useBackupRequired.setState(backupState)
}, [])
const onReload = useCallback(() => {
const url = browser.runtime.getURL("index.html")
setTimeout(() => {
// ensure state got persisted before reloading
window.location.href = url
}, 100)
}, [])
return (
<Container gap={"4px"}>
<DevButton onClick={reset}>Reset cache</DevButton>
<DevButton onClick={onReload}>Reload</DevButton>
</Container>
)
}

export default DevUI

0 comments on commit 68beaae

Please sign in to comment.