diff --git a/apps/namadillo/src/App/Common/SyncIndicator.tsx b/apps/namadillo/src/App/Common/SyncIndicator.tsx index e9b659e0e..55877e0c8 100644 --- a/apps/namadillo/src/App/Common/SyncIndicator.tsx +++ b/apps/namadillo/src/App/Common/SyncIndicator.tsx @@ -1,10 +1,12 @@ import { Tooltip } from "@namada/components"; import { syncStatusAtom } from "atoms/syncStatus/atoms"; +import { useChainStatus } from "hooks/useChainStatus"; import { useAtomValue } from "jotai"; import { twMerge } from "tailwind-merge"; export const SyncIndicator = (): JSX.Element => { const syncStatus = useAtomValue(syncStatusAtom); + const { data } = useChainStatus(); return (
@@ -21,7 +23,7 @@ export const SyncIndicator = (): JSX.Element => { "Syncing" : syncStatus.isError ? "Error syncing" - : "Fully synced"} + : `Fully synced: height ${data?.height}, epoch ${data?.epoch}`}
); diff --git a/apps/namadillo/src/hooks/useChainStatus.tsx b/apps/namadillo/src/hooks/useChainStatus.tsx new file mode 100644 index 000000000..54f6dacef --- /dev/null +++ b/apps/namadillo/src/hooks/useChainStatus.tsx @@ -0,0 +1,18 @@ +import { useSSE } from "@namada/hooks"; +import { indexerUrlAtom } from "atoms/settings"; +import { useAtomValue } from "jotai"; + +export type ChainStatus = { + height: number; + epoch: number; +}; + +export const useChainStatus = (): { + data?: ChainStatus; + error?: Event; + closeFn?: () => void; +} => { + const url = useAtomValue(indexerUrlAtom); + + return useSSE(`${url}/api/v1/chain/status`); +}; diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 8a1f36dcd..f36f9fb1c 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -1,6 +1,7 @@ export * from "./useDebounce"; export * from "./useEffectSkipFirstRender"; export * from "./useEvent"; +export * from "./useSSE"; export * from "./useSanitizedLocation"; export * from "./useSanitizedParams"; export * from "./useUntil"; diff --git a/packages/hooks/src/useSSE.ts b/packages/hooks/src/useSSE.ts new file mode 100644 index 000000000..8d8ca35a1 --- /dev/null +++ b/packages/hooks/src/useSSE.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from "react"; + +export function useSSE( + url: string, + options?: EventSourceInit +): { data?: T; error?: Event; closeFn?: () => void } { + const [data, setData] = useState(); + const [error, setError] = useState(); + const [closeFn, setCloseFn] = useState<() => void>(); + + useEffect(() => { + const eventSource = new EventSource(url, options); + setCloseFn(() => eventSource.close.bind(eventSource)); + + eventSource.onmessage = (event) => { + setData((old) => + event.data === JSON.stringify(old) ? old : JSON.parse(event.data) + ); + }; + + eventSource.onerror = (err) => { + setError(err); + eventSource.close(); + }; + + return () => { + eventSource.close(); + }; + }, [url]); + + return { data, error, closeFn }; +}