import type { Dispatch, SetStateAction } from 'react'; import { useState, useEffect } from 'react'; import { LocalStorage } from '@vegaprotocol/react-helpers'; import type { Environment, Configuration, ConfigStatus } from '../types'; import { validateConfiguration } from '../utils/validate-configuration'; import { promiseRaceToSuccess } from '../utils/promise-race-success'; export const LOCAL_STORAGE_NETWORK_KEY = 'vegaNetworkConfig'; export type EnvironmentWithOptionalUrl = Partial & Omit; const requestToNode = async (url: string, index: number): Promise => { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed connecting to node: ${url}.`); } return index; }; const getCachedConfig = () => { const value = LocalStorage.getItem(LOCAL_STORAGE_NETWORK_KEY); if (value) { try { const config = JSON.parse(value) as Configuration; const hasError = validateConfiguration(config); if (hasError) { throw new Error('Invalid configuration found in the storage.'); } return config; } catch (err) { LocalStorage.removeItem(LOCAL_STORAGE_NETWORK_KEY); console.warn( 'Malformed data found for network configuration. Removed and continuing...' ); } } return undefined; }; export const useConfig = ( environment: EnvironmentWithOptionalUrl, updateEnvironment: Dispatch> ) => { const [config, setConfig] = useState( getCachedConfig() ); const [status, setStatus] = useState( !environment.VEGA_URL ? 'idle' : 'success' ); useEffect(() => { if (!config && status === 'idle') { (async () => { setStatus('loading-config'); try { const response = await fetch(environment.VEGA_CONFIG_URL ?? ''); const configData: Configuration = await response.json(); if (validateConfiguration(configData)) { setStatus('error-validating-config'); return; } setConfig({ hosts: configData.hosts }); LocalStorage.setItem( LOCAL_STORAGE_NETWORK_KEY, JSON.stringify({ hosts: configData.hosts }) ); } catch (err) { setStatus('error-loading-config'); } })(); } // load config only once per runtime // eslint-disable-next-line react-hooks/exhaustive-deps }, [environment.VEGA_CONFIG_URL, !!config, status, setStatus, setConfig]); useEffect(() => { if ( config && !['loading-node', 'success', 'error-loading-node'].includes(status) ) { (async () => { setStatus('loading-node'); // if there's only one configured node to choose from, set is as the env url if (config.hosts.length === 1) { setStatus('success'); updateEnvironment((prevEnvironment) => ({ ...prevEnvironment, VEGA_URL: config.hosts[0], })); return; } // when there are multiple possible hosts, set the env url to the node which responds first try { const requests = config.hosts.map(requestToNode); const index = await promiseRaceToSuccess(requests); setStatus('success'); updateEnvironment((prevEnvironment) => ({ ...prevEnvironment, VEGA_URL: config.hosts[index], })); } catch (err) { setStatus('error-loading-node'); } })(); } // load config only once per runtime // eslint-disable-next-line react-hooks/exhaustive-deps }, [status, !!config, setStatus, updateEnvironment]); return { status, }; };