refactor: env provider to use zustand
This commit is contained in:
@@ -17,10 +17,15 @@ import {
|
||||
useEthWithdrawApprovalsManager,
|
||||
} from '@vegaprotocol/web3';
|
||||
import {
|
||||
clients,
|
||||
EnvironmentProvider,
|
||||
envTriggerMapping,
|
||||
Networks,
|
||||
NodeSwitcherDialog,
|
||||
useEnvironment,
|
||||
useEnvironment2,
|
||||
useInitializeEnv,
|
||||
useStatisticsQuery,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { AppLoader, Web3Provider } from '../components/app-loader';
|
||||
import './styles.css';
|
||||
@@ -35,6 +40,8 @@ import { Connectors } from '../lib/vega-connectors';
|
||||
import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { Banner } from '../components/banner';
|
||||
import classNames from 'classnames';
|
||||
import { Dialog } from '@vegaprotocol/ui-toolkit';
|
||||
import { ApolloProvider } from '@apollo/client';
|
||||
|
||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||
|
||||
@@ -117,28 +124,113 @@ const DynamicLoader = dynamic(
|
||||
);
|
||||
|
||||
function VegaTradingApp(props: AppProps) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [open, setOpen] = useState(true);
|
||||
const status = useEnvironment2((store) => store.status);
|
||||
useInitializeEnv();
|
||||
|
||||
// Hash router requires access to the document object. At compile time that doesn't exist
|
||||
// so we need to ensure client side rendering only from this point onwards in
|
||||
// the component tree
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!mounted) {
|
||||
if (status === 'default' || status === 'pending') {
|
||||
return <DynamicLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
<EnvironmentProvider>
|
||||
<AppBody {...props} />
|
||||
</EnvironmentProvider>
|
||||
{/* <AppBody {...props} /> */}
|
||||
<Test />
|
||||
<button onClick={() => setOpen(true)}>Status</button>
|
||||
<NodeSwitcher open={open} setOpen={setOpen} />
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
|
||||
const NodeSwitcher = ({
|
||||
open,
|
||||
setOpen,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: (x: boolean) => void;
|
||||
}) => {
|
||||
const [customUrl, setCustomUrl] = useState('');
|
||||
const { status, nodes, setUrl } = useEnvironment2((store) => ({
|
||||
status: store.status,
|
||||
nodes: store.nodes,
|
||||
setUrl: store.setUrl,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Dialog open={open} onChange={setOpen}>
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left">node</th>
|
||||
<th className="text-right">response time</th>
|
||||
<th className="text-right">block height</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{nodes.map((node) => {
|
||||
const client = clients[node];
|
||||
|
||||
if (!client) return null;
|
||||
|
||||
return (
|
||||
<tr key={node} onClick={() => setUrl(node)}>
|
||||
<ApolloProvider client={client}>
|
||||
<Row url={node} />
|
||||
</ApolloProvider>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
Custom
|
||||
<input
|
||||
value={customUrl}
|
||||
onChange={(e) => setCustomUrl(e.target.value)}
|
||||
/>
|
||||
{/* <button onClick={}>Check</button> */}
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const Row = ({ url }: { url: string }) => {
|
||||
const [time, setTime] = useState<number>();
|
||||
const { data } = useStatisticsQuery({
|
||||
pollInterval: 3000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const requestUrl = new URL(url);
|
||||
const requests = window.performance.getEntriesByName(requestUrl.href);
|
||||
const { duration } =
|
||||
(requests.length && requests[requests.length - 1]) || {};
|
||||
setTime(duration);
|
||||
}, [url]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<td>{url}</td>
|
||||
<td className="text-right">{time ? time.toFixed(2) + 'ms' : 'n/a'}</td>
|
||||
<td className="text-right">{data?.statistics.blockHeight || '-'}</td>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
const env = useEnvironment2((store) => ({
|
||||
url: store.url,
|
||||
configUrl: store.configUrl,
|
||||
nodes: store.nodes,
|
||||
status: store.status,
|
||||
}));
|
||||
return (
|
||||
<div>
|
||||
<pre>{JSON.stringify(env, null, 2)}</pre>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VegaTradingApp;
|
||||
|
||||
const MaybeConnectEagerly = () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './use-environment';
|
||||
export * from './use-environment-2';
|
||||
export * from './use-links';
|
||||
export * from './use-node-health';
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import { LocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useEffect } from 'react';
|
||||
import { create } from 'zustand';
|
||||
import { createClient } from '@vegaprotocol/apollo-client';
|
||||
import type {
|
||||
BlockTimeSubscription,
|
||||
StatisticsQuery,
|
||||
} from '../utils/__generated__/Node';
|
||||
import {
|
||||
BlockTimeDocument,
|
||||
StatisticsDocument,
|
||||
} from '../utils/__generated__/Node';
|
||||
|
||||
type Client = ReturnType<typeof createClient>;
|
||||
type ClientCollection = {
|
||||
[node: string]: Client;
|
||||
};
|
||||
export const clients: ClientCollection = {};
|
||||
|
||||
interface Env {
|
||||
url: string;
|
||||
configUrl: string;
|
||||
nodes: string[];
|
||||
status: 'default' | 'pending' | 'success' | 'failed';
|
||||
}
|
||||
|
||||
interface Actions {
|
||||
setUrl: (url: string) => void;
|
||||
initialize: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useEnvironment2 = create<Env & Actions>((set, get) => ({
|
||||
url: process.env['NX_VEGA_URL'] || '',
|
||||
vegaEnv: process.env['NX_VEGA_ENV'] || '',
|
||||
configUrl: process.env['NX_VEGA_CONFIG_URL'] || '',
|
||||
nodes: [],
|
||||
status: 'default',
|
||||
setUrl: (url) => {
|
||||
set({ url });
|
||||
},
|
||||
initialize: async () => {
|
||||
const state = get();
|
||||
if (state.status === 'pending') return;
|
||||
const storedUrl = LocalStorage.getItem('vega_url');
|
||||
set({ status: 'pending' });
|
||||
const nodes = await fetchConfig(state.configUrl);
|
||||
set({ nodes });
|
||||
|
||||
// create client and store instances
|
||||
nodes.forEach((url) => {
|
||||
clients[url] = createClient({
|
||||
url,
|
||||
cacheConfig: undefined,
|
||||
retry: false,
|
||||
connectToDevTools: false,
|
||||
});
|
||||
});
|
||||
|
||||
// if (storedUrl) {
|
||||
// set({ url: storedUrl, status: 'success' });
|
||||
// } else {
|
||||
const url = await findNode(clients);
|
||||
set({
|
||||
status: url ? 'success' : 'failed',
|
||||
url: url ? url : '',
|
||||
});
|
||||
// }
|
||||
},
|
||||
}));
|
||||
|
||||
export const useInitializeEnv = () => {
|
||||
const { initialize, ...env } = useEnvironment2();
|
||||
|
||||
useEffect(() => {
|
||||
if (env.status === 'default') {
|
||||
initialize();
|
||||
}
|
||||
}, [env.status, initialize]);
|
||||
};
|
||||
|
||||
const fetchConfig = async (url: string): Promise<string[]> => {
|
||||
const res = await fetch(url);
|
||||
const cfg = await res.json();
|
||||
return cfg.hosts;
|
||||
};
|
||||
|
||||
const findNode = (clients: ClientCollection): Promise<string | null> => {
|
||||
const tests = Object.entries(clients).map((args) => testNode(...args));
|
||||
return Promise.race(tests);
|
||||
};
|
||||
|
||||
const testNode = async (
|
||||
url: string,
|
||||
client: Client
|
||||
): Promise<string | null> => {
|
||||
return null;
|
||||
try {
|
||||
const results = await Promise.all([
|
||||
testQuery(client),
|
||||
testSubscription(client),
|
||||
]);
|
||||
if (results[0] && results[1]) {
|
||||
return url;
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.warn(`tests failed for ${url}`);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const testQuery = async (client: Client) => {
|
||||
try {
|
||||
const result = await client.query<StatisticsQuery>({
|
||||
query: StatisticsDocument,
|
||||
});
|
||||
if (!result || result.error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const testSubscription = (client: Client) => {
|
||||
return new Promise((resolve) => {
|
||||
const sub = client
|
||||
.subscribe<BlockTimeSubscription>({
|
||||
query: BlockTimeDocument,
|
||||
errorPolicy: 'all',
|
||||
})
|
||||
.subscribe({
|
||||
next: () => {
|
||||
resolve(true);
|
||||
sub.unsubscribe();
|
||||
},
|
||||
error: () => {
|
||||
resolve(false);
|
||||
sub.unsubscribe();
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user