stabilize connection state of the ghost connect

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-04-09 14:30:55 +03:00
parent fe71618369
commit c2d81bc229
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
3 changed files with 52 additions and 41 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "ghost-dao-interface", "name": "ghost-dao-interface",
"private": true, "private": true,
"version": "0.7.9", "version": "0.7.10",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -92,7 +92,7 @@ function SelectNetwork({ chainId, wrongNetworkToastId, setWrongNetworkToastId, s
return ( return (
<MenuItem key={chain.name} value={chain.id}> <MenuItem key={chain.name} value={chain.id}>
<Box gap="10px" display="flex" flexDirection="row" alignItems="center"> <Box gap="10px" display="flex" flexDirection="row" alignItems="center">
<SvgIcon component={parseKnownToken(chain?.nativeCurrency?.symbol)} inheritViewBox /> <SvgIcon sx={{ width: 22 }} component={parseKnownToken(chain?.nativeCurrency?.symbol)} inheritViewBox />
{!small && <Typography>{chain.name}</Typography>} {!small && <Typography>{chain.name}</Typography>}
</Box> </Box>
</MenuItem> </MenuItem>

View File

@ -1,14 +1,19 @@
import { createContext, useEffect, useContext, useState, useMemo, useCallback } from "react" import { createContext, useEffect, useContext, useState, useMemo, useCallback, useRef } from "react"
import { Unstable } from "@substrate/connect-discovery" import { Unstable } from "@substrate/connect-discovery"
import { createClient } from "@polkadot-api/substrate-client" import { createClient } from "@polkadot-api/substrate-client"
import { getObservableClient } from "@polkadot-api/observable-client" import { getObservableClient } from "@polkadot-api/observable-client"
import useSWR from "swr" import useSWR from "swr"
const MAX_BLOCK_TIMEOUT = 15000
const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3" const DEFAULT_CHAIN_ID = "0x475e48fab52f3d0587b6b03101d224560c549e984d1dee197b7d8b55830e7da3"
const UnstableProvider = createContext(null) const UnstableProvider = createContext(null)
export const useUnstableProvider = () => useContext(UnstableProvider) export const useUnstableProvider = () => useContext(UnstableProvider)
export const UnstableProviderProvider = ({ children }) => { export const UnstableProviderProvider = ({ children }) => {
const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID);
const [isConnected, setIsConnected] = useState(false);
const [reconnectTicket, setReconnectTicket] = useState(0);
const { data: providerDetails } = useSWR("getGhostProviders", () => const { data: providerDetails } = useSWR("getGhostProviders", () =>
Unstable.getSubstrateConnectExtensionProviders() Unstable.getSubstrateConnectExtensionProviders()
); );
@ -19,60 +24,66 @@ export const UnstableProviderProvider = ({ children }) => {
() => providerDetail ? providerDetail.provider : null () => providerDetail ? providerDetail.provider : null
); );
const [chainId, setChainId] = useState(DEFAULT_CHAIN_ID);
const [isConnected, setIsConnected] = useState(false);
const [reconnectTicket, setReconnectTicket] = useState(0);
const reconnect = () => setReconnectTicket(prev => prev + 1);
const createConnectWithStatus = (originalConnect, onConnect) => {
return (observer) => {
onConnect(true);
return originalConnect(observer);
};
};
const client = useMemo(() => { const client = useMemo(() => {
if (!provider || !chainId) return undefined; if (!provider || !chainId) return undefined;
const chain = provider.getChains()[chainId]; const chain = provider.getChains()[chainId];
if (!chain) return undefined; if (!chain) return undefined;
const wrappedConnect = createConnectWithStatus(chain.connect, setIsConnected);
return createClient(wrappedConnect); return createClient(chain.connect)
}, [provider, chainId, reconnectTicket]); }, [provider, chainId, reconnectTicket]);
const observableClient = useMemo(() => { const observableClient = useMemo(() => client ? getObservableClient(client) : undefined, [client]);
return client ? getObservableClient(client) : undefined; const chainHead$ = useMemo(() => observableClient?.chainHead$(), [observableClient]);
}, [client]);
const chainHead$ = useMemo(() => { const lastBlockNumber = useRef(0);
return observableClient ? observableClient.chainHead$() : undefined;
}, [observableClient]);
useEffect(() => { useEffect(() => {
if (!chainHead$) return; if (!chainHead$) return;
const sub = chainHead$.runtime$.subscribe({
next: () => setIsConnected(true), lastBlockNumber.current = 0;
let timeoutId;
const sub = chainHead$.bestBlocks$.subscribe({
next: (blocks) => {
const currentHeight = blocks.at(0)?.number ?? -1;
if (currentHeight > lastBlockNumber.current) {
lastBlockNumber.current = currentHeight;
setIsConnected(true);
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
setIsConnected(false);
setReconnectTicket(t => t + 1);
}, MAX_BLOCK_TIMEOUT);
}
},
error: (err) => { error: (err) => {
setIsConnected(false); setIsConnected(false);
setTimeout(reconnect, 3000); setTimeout(() => setReconnectTicket(t => t + 1), 1000);
} }
}); });
return () => sub.unsubscribe();
}, [chainHead$, reconnect]); return () => {
sub.unsubscribe();
clearTimeout(timeoutId);
};
}, [chainHead$]);
const value = useMemo(() => ({
isConnected,
providerDetails,
providerDetail,
connectProviderDetail: setProviderDetail,
chainId,
client,
setChainId,
chainHead$
}), [isConnected, providerDetails, providerDetail, chainId, client, chainHead$]);
return ( return (
<UnstableProvider.Provider <UnstableProvider.Provider value={value}>
value={{
isConnected,
providerDetails,
providerDetail,
connectProviderDetail: setProviderDetail,
provider,
chainId,
client,
setChainId,
chainHead$
}}
>
{children} {children}
</UnstableProvider.Provider> </UnstableProvider.Provider>
); );