diff --git a/dashboard/src/@core/components/session/SessionGuard.tsx b/dashboard/src/@core/components/session/SessionGuard.tsx index 643debb9bd..387fd24083 100644 --- a/dashboard/src/@core/components/session/SessionGuard.tsx +++ b/dashboard/src/@core/components/session/SessionGuard.tsx @@ -10,6 +10,8 @@ import { DialogContent, DialogContentText, DialogTitle, + Snackbar, + Alert, } from '@mui/material' // ** Next Import @@ -89,13 +91,14 @@ const SessionGuard = (props: SessionGuardProps) => { const auth = useAuth() const router = useRouter() - const { account, requestAuthorize } = useSession() + const { account, requestAuthorize, close, errorMsg } = useSession() const handleAuth = (scope: Array, maxInactiveInterval: number) => { requestAuthorize && requestAuthorize(scope, maxInactiveInterval) } const hanleLogout = () => { + close && close() auth.logout() if (router.asPath !== '/') { @@ -120,6 +123,13 @@ const SessionGuard = (props: SessionGuardProps) => { onReqAuthorize={handleAuth} onLogout={hanleLogout} > + + {errorMsg} +
{children}
diff --git a/dashboard/src/@core/layouts/components/shared-components/UserDropdown.tsx b/dashboard/src/@core/layouts/components/shared-components/UserDropdown.tsx index 0bd8a67ec4..f7a239d891 100644 --- a/dashboard/src/@core/layouts/components/shared-components/UserDropdown.tsx +++ b/dashboard/src/@core/layouts/components/shared-components/UserDropdown.tsx @@ -20,6 +20,7 @@ import Icon from 'src/@core/components/icon' // ** Context import { useAuth } from 'src/hooks/useAuth' +import { useSession } from 'src/hooks/useSessionAccount' // ** Type Imports import { Settings } from 'src/@core/context/settingsContext' @@ -45,6 +46,7 @@ const UserDropdown = (props: Props) => { // ** Hooks const router = useRouter() const { logout } = useAuth() + const { close } = useSession() // ** Vars const { direction } = settings @@ -76,6 +78,7 @@ const UserDropdown = (props: Props) => { } const handleLogout = () => { + close() logout() handleDropdownClose() } diff --git a/dashboard/src/context/session/SessionContext.tsx b/dashboard/src/context/session/SessionContext.tsx index 4ed752ed3c..984fda9ef8 100644 --- a/dashboard/src/context/session/SessionContext.tsx +++ b/dashboard/src/context/session/SessionContext.tsx @@ -24,6 +24,7 @@ import { addressToSeqNumber, parseRoochErrorSubStatus, ErrorCategory, + getErrorCategoryName, } from '@rooch/sdk' // ** React Imports @@ -37,17 +38,21 @@ type Props = { const SessionContext = createContext({ loading: false, account: null, + errorMsg: null, requestAuthorize: undefined, + close: () => {}, }) -const makeSessionAccountStoreKey = (address: string) => { - return `rooch::dashboard::account::${address}::current-session-key` +const makeSessionAccountStoreKey = (chainId: number, address: string) => { + return `rooch::${chainId}::dashboard::account::${address}::current-session-key` } const loadSessionAccountFromSessionStorage = (provider: IProvider, roochAddress: string) => { try { // Get from local storage by key - const secretKey = window.sessionStorage.getItem(makeSessionAccountStoreKey(roochAddress)) + const secretKey = window.sessionStorage.getItem( + makeSessionAccountStoreKey(provider.getChainId(), roochAddress), + ) if (secretKey) { let sk = bcsTypes.fromB64(secretKey) @@ -70,9 +75,12 @@ const loadSessionAccountFromSessionStorage = (provider: IProvider, roochAddress: return null } -const clearSessionAccountInSessionStorage = (roochAddress: string) => { +const clearSessionAccountInSessionStorage = (provider: IProvider, roochAddress: string) => { try { - window.sessionStorage.setItem(makeSessionAccountStoreKey(roochAddress), '') + window.sessionStorage.setItem( + makeSessionAccountStoreKey(provider.getChainId(), roochAddress), + '', + ) } catch (error) { // If error also return initialValue console.log(error) @@ -85,7 +93,8 @@ const SessionProvider = ({ children }: Props) => { const auth = useAuth() const rooch = useRooch() - const [loading, setLoading] = useState(false) + const [loading, setLoading] = useState(false) + const [errorMsg, setErrorMsg] = useState(null) const filterdProvider = useMemo(() => { const sessionKeyInvalidFilterFunc: FilterFunc = async ( @@ -106,7 +115,7 @@ const SessionProvider = ({ children }: Props) => { const defaultAccount = auth.defaultAccount if (defaultAccount) { - clearSessionAccountInSessionStorage(defaultAccount.roochAddress) + clearSessionAccountInSessionStorage(rooch.provider!, defaultAccount.roochAddress) } } @@ -258,7 +267,7 @@ const SessionProvider = ({ children }: Props) => { maxInactiveInterval, ) - const key = makeSessionAccountStoreKey(account.roochAddress) + const key = makeSessionAccountStoreKey(provider.getChainId(), account.roochAddress) window.sessionStorage.setItem(key, pk.export().privateKey) const authorizer = new PrivateKeyAuth(pk) @@ -266,7 +275,17 @@ const SessionProvider = ({ children }: Props) => { } catch (err: any) { console.log(`registerSessionKey error:`, err) - return null + const subStatus = parseRoochErrorSubStatus(err.message) + if (subStatus) { + throw new Error( + 'create session key fail, error category: ' + + getErrorCategoryName(subStatus.category) + + ', reason: ' + + subStatus.reason, + ) + } + + throw new Error('create session key error, reason:' + err.message) } } @@ -282,7 +301,7 @@ const SessionProvider = ({ children }: Props) => { try { await account.registerSessionKey(roochAddress, scope, maxInactiveInterval) - const key = makeSessionAccountStoreKey(account.getAddress()) + const key = makeSessionAccountStoreKey(provider.getChainId(), account.getAddress()) window.sessionStorage.setItem(key, pk.export().privateKey) const authorizer = new PrivateKeyAuth(pk) @@ -290,7 +309,17 @@ const SessionProvider = ({ children }: Props) => { } catch (err: any) { console.log(`registerSessionKey error:`, err) - return null + const subStatus = parseRoochErrorSubStatus(err.message) + if (subStatus) { + throw new Error( + 'create session key fail, error category: ' + + getErrorCategoryName(subStatus.category) + + ', reason: ' + + subStatus.reason, + ) + } + + throw new Error('create session key error, reason:' + err.message) } } @@ -334,15 +363,29 @@ const SessionProvider = ({ children }: Props) => { } } } + } catch (e: any) { + setErrorMsg(e.message) + setTimeout(() => { + setErrorMsg(null) + }, 5000) } finally { setLoading(false) } } + const closeSession = () => { + const defaultAccount = auth.defaultAccount + if (defaultAccount) { + clearSessionAccountInSessionStorage(rooch.provider!, defaultAccount.roochAddress) + } + } + const session = { loading, account: sessionAccount, + errorMsg, requestAuthorize, + close: closeSession, } as Session return {children} diff --git a/dashboard/src/context/session/types.ts b/dashboard/src/context/session/types.ts index 1f1a68043d..efd28c4231 100644 --- a/dashboard/src/context/session/types.ts +++ b/dashboard/src/context/session/types.ts @@ -7,5 +7,7 @@ import { IAccount } from '@rooch/sdk' export interface Session { account: IAccount | null loading: boolean + errorMsg: string | null requestAuthorize?: (scope: Array, maxInactiveInterval: number) => Promise + close: () => void } diff --git a/dashboard/src/store/session/index.ts b/dashboard/src/store/session/index.ts index bbcbd3661c..c562894d9d 100644 --- a/dashboard/src/store/session/index.ts +++ b/dashboard/src/store/session/index.ts @@ -14,6 +14,8 @@ import { ISessionKey, JsonRpcProvider, ListAnnotatedStateResultPageView, + parseRoochErrorSubStatus, + getErrorCategoryName, } from '@rooch/sdk' interface DataParams { @@ -98,7 +100,19 @@ export const fetchData = createAsyncThunk('state/fetchData', async (params: Data } } } catch (e: any) { - params.dispatch(error(e.toString())) + const subStatus = parseRoochErrorSubStatus(e.message) + if (subStatus) { + params.dispatch( + error( + 'list session keys fail, error category: ' + + getErrorCategoryName(subStatus.category) + + ', reason: ' + + subStatus.reason, + ), + ) + } else { + params.dispatch(error(`list session keys fail, reason: ${e.message}`)) + } setTimeout(() => { params.dispatch(error(null)) @@ -117,7 +131,19 @@ export const removeRow = createAsyncThunk('state/removeRow', async (params: Remo params.refresh() } catch (e: any) { - params.dispatch(error(e.toString())) + const subStatus = parseRoochErrorSubStatus(e.message) + if (subStatus) { + params.dispatch( + error( + 'remove session key fail, error category: ' + + getErrorCategoryName(subStatus.category) + + ', reason: ' + + subStatus.reason, + ), + ) + } else { + params.dispatch(error(`remove session key fail, reason: ${e.message}`)) + } setTimeout(() => { params.dispatch(error(null)) diff --git a/dashboard/src/views/feature/SessionKeyList.tsx b/dashboard/src/views/feature/SessionKeyList.tsx index 71f7d36303..147d0ca7df 100644 --- a/dashboard/src/views/feature/SessionKeyList.tsx +++ b/dashboard/src/views/feature/SessionKeyList.tsx @@ -14,6 +14,7 @@ import Card from '@mui/material/Card' import CardHeader from '@mui/material/CardHeader' import CardContent from '@mui/material/CardContent' import Button from '@mui/material/Button' +import Alert from '@mui/material/Alert' import Snackbar from '@mui/material/Snackbar' import Dialog from '@mui/material/Dialog' import DialogActions from '@mui/material/DialogActions' @@ -253,10 +254,11 @@ export default function SessionKeyList() { /> + > + {error} + { describe('parseRoochErrorCode', () => { @@ -41,4 +46,22 @@ describe('err', () => { expect(parseRoochErrorSubStatus(errorMessage)).toBeNull() }) }) + + describe('getErrorCategoryName', () => { + it('should return the correct string representation of the enum', () => { + expect(getErrorCategoryName(ErrorCategory.INVALID_ARGUMENT)).toBe('INVALID_ARGUMENT') + expect(getErrorCategoryName(ErrorCategory.OUT_OF_RANGE)).toBe('OUT_OF_RANGE') + expect(getErrorCategoryName(ErrorCategory.INVALID_STATE)).toBe('INVALID_STATE') + expect(getErrorCategoryName(ErrorCategory.UNAUTHENTICATED)).toBe('UNAUTHENTICATED') + expect(getErrorCategoryName(ErrorCategory.PERMISSION_DENIED)).toBe('PERMISSION_DENIED') + expect(getErrorCategoryName(ErrorCategory.NOT_FOUND)).toBe('NOT_FOUND') + expect(getErrorCategoryName(ErrorCategory.ABORTED)).toBe('ABORTED') + expect(getErrorCategoryName(ErrorCategory.ALREADY_EXISTS)).toBe('ALREADY_EXISTS') + expect(getErrorCategoryName(ErrorCategory.RESOURCE_EXHAUSTED)).toBe('RESOURCE_EXHAUSTED') + expect(getErrorCategoryName(ErrorCategory.CANCELLED)).toBe('CANCELLED') + expect(getErrorCategoryName(ErrorCategory.INTERNAL)).toBe('INTERNAL') + expect(getErrorCategoryName(ErrorCategory.NOT_IMPLEMENTED)).toBe('NOT_IMPLEMENTED') + expect(getErrorCategoryName(ErrorCategory.UNAVAILABLE)).toBe('UNAVAILABLE') + }) + }) }) diff --git a/sdk/typescript/src/utils/error.ts b/sdk/typescript/src/utils/error.ts index 31acee375e..470b85eaf3 100644 --- a/sdk/typescript/src/utils/error.ts +++ b/sdk/typescript/src/utils/error.ts @@ -45,3 +45,8 @@ export function parseRoochErrorSubStatus(errorMessage: string | null): SubStatus reason: errorCode & 0xffff, } } + +// Get the string representation of an enumeration +export function getErrorCategoryName(code: ErrorCategory): string { + return ErrorCategory[code] +}