123 lines
3.7 KiB
TypeScript
123 lines
3.7 KiB
TypeScript
|
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<Environment> &
|
||
|
Omit<Environment, 'VEGA_URL'>;
|
||
|
|
||
|
const requestToNode = async (url: string, index: number): Promise<number> => {
|
||
|
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<SetStateAction<Environment>>
|
||
|
) => {
|
||
|
const [config, setConfig] = useState<Configuration | undefined>(
|
||
|
getCachedConfig()
|
||
|
);
|
||
|
const [status, setStatus] = useState<ConfigStatus>(
|
||
|
!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,
|
||
|
};
|
||
|
};
|