|
@@ -1,57 +1,61 @@
|
|
import React, { useState, useEffect, useContext, createContext } from 'react';
|
|
import React, { useState, useEffect, useContext, createContext } from 'react';
|
|
import { Message } from 'semantic-ui-react';
|
|
import { Message } from 'semantic-ui-react';
|
|
import axios, { CancelToken } from 'axios';
|
|
import axios, { CancelToken } from 'axios';
|
|
-import { parse as parseUrl } from 'url';
|
|
|
|
|
|
|
|
import { AccountId } from '@polkadot/types/interfaces';
|
|
import { AccountId } from '@polkadot/types/interfaces';
|
|
import { Vec } from '@polkadot/types';
|
|
import { Vec } from '@polkadot/types';
|
|
-import { Url } from '@joystream/types/discovery'
|
|
|
|
|
|
+import { Url } from '@joystream/types/discovery';
|
|
import ApiContext from '@polkadot/react-api/ApiContext';
|
|
import ApiContext from '@polkadot/react-api/ApiContext';
|
|
import { ApiProps } from '@polkadot/react-api/types';
|
|
import { ApiProps } from '@polkadot/react-api/types';
|
|
import { JoyInfo } from '@polkadot/joy-utils/JoyStatus';
|
|
import { JoyInfo } from '@polkadot/joy-utils/JoyStatus';
|
|
|
|
+import { componentName } from '@polkadot/joy-utils/react/helpers';
|
|
|
|
|
|
export type BootstrapNodes = {
|
|
export type BootstrapNodes = {
|
|
- bootstrapNodes?: Url[],
|
|
|
|
|
|
+ bootstrapNodes?: Url[];
|
|
};
|
|
};
|
|
|
|
|
|
export type DiscoveryProvider = {
|
|
export type DiscoveryProvider = {
|
|
- resolveAssetEndpoint: (provider: AccountId, contentId?: string, cancelToken?: CancelToken) => Promise<string>
|
|
|
|
- reportUnreachable: (provider: AccountId) => void
|
|
|
|
|
|
+ resolveAssetEndpoint: (provider: AccountId, contentId?: string, cancelToken?: CancelToken) => Promise<string>;
|
|
|
|
+ reportUnreachable: (provider: AccountId) => void;
|
|
};
|
|
};
|
|
|
|
|
|
export type DiscoveryProviderProps = {
|
|
export type DiscoveryProviderProps = {
|
|
- discoveryProvider: DiscoveryProvider
|
|
|
|
|
|
+ discoveryProvider: DiscoveryProvider;
|
|
};
|
|
};
|
|
|
|
|
|
// return string Url with last `/` removed
|
|
// return string Url with last `/` removed
|
|
-function normalizeUrl(url: string | Url): string {
|
|
|
|
- let st = new String(url)
|
|
|
|
|
|
+function normalizeUrl (url: string | Url): string {
|
|
|
|
+ const st: string = url.toString();
|
|
if (st.endsWith('/')) {
|
|
if (st.endsWith('/')) {
|
|
return st.substring(0, st.length - 1);
|
|
return st.substring(0, st.length - 1);
|
|
}
|
|
}
|
|
- return st.toString()
|
|
|
|
|
|
+ return st.toString();
|
|
}
|
|
}
|
|
|
|
|
|
type ProviderStats = {
|
|
type ProviderStats = {
|
|
- assetApiEndpoint: string,
|
|
|
|
- unreachableReports: number,
|
|
|
|
- resolvedAt: number,
|
|
|
|
|
|
+ assetApiEndpoint: string;
|
|
|
|
+ unreachableReports: number;
|
|
|
|
+ resolvedAt: number;
|
|
}
|
|
}
|
|
|
|
|
|
-function newDiscoveryProvider({ bootstrapNodes }: BootstrapNodes): DiscoveryProvider {
|
|
|
|
- let stats: Map<string, ProviderStats> = new Map();
|
|
|
|
|
|
+function newDiscoveryProvider ({ bootstrapNodes }: BootstrapNodes): DiscoveryProvider {
|
|
|
|
+ const stats: Map<string, ProviderStats> = new Map();
|
|
|
|
|
|
const resolveAssetEndpoint = async (storageProvider: AccountId, contentId?: string, cancelToken?: CancelToken) => {
|
|
const resolveAssetEndpoint = async (storageProvider: AccountId, contentId?: string, cancelToken?: CancelToken) => {
|
|
const providerKey = storageProvider.toString();
|
|
const providerKey = storageProvider.toString();
|
|
|
|
|
|
let stat = stats.get(providerKey);
|
|
let stat = stats.get(providerKey);
|
|
|
|
|
|
- if (!stat || (stat && (Date.now() > (stat.resolvedAt + (10 * 60 * 1000))))) {
|
|
|
|
- for (let n = 0; bootstrapNodes && n < bootstrapNodes.length; n++) {
|
|
|
|
- let discoveryUrl = normalizeUrl(bootstrapNodes[n])
|
|
|
|
|
|
+ if (
|
|
|
|
+ (!stat || (stat && (Date.now() > (stat.resolvedAt + (10 * 60 * 1000))))) &&
|
|
|
|
+ bootstrapNodes
|
|
|
|
+ ) {
|
|
|
|
+ for (let n = 0; n < bootstrapNodes.length; n++) {
|
|
|
|
+ const discoveryUrl = normalizeUrl(bootstrapNodes[n]);
|
|
|
|
|
|
try {
|
|
try {
|
|
- parseUrl(discoveryUrl);
|
|
|
|
|
|
+ // eslint-disable-next-line no-new
|
|
|
|
+ new URL(discoveryUrl);
|
|
} catch (err) {
|
|
} catch (err) {
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
@@ -61,7 +65,7 @@ function newDiscoveryProvider({ bootstrapNodes }: BootstrapNodes): DiscoveryProv
|
|
try {
|
|
try {
|
|
console.log(`Resolving ${providerKey} using ${discoveryUrl}`);
|
|
console.log(`Resolving ${providerKey} using ${discoveryUrl}`);
|
|
|
|
|
|
- const serviceInfo = await axios.get(serviceInfoQuery, { cancelToken }) as any
|
|
|
|
|
|
+ const serviceInfo = await axios.get(serviceInfoQuery, { cancelToken }) as any;
|
|
|
|
|
|
if (!serviceInfo) {
|
|
if (!serviceInfo) {
|
|
continue;
|
|
continue;
|
|
@@ -70,7 +74,7 @@ function newDiscoveryProvider({ bootstrapNodes }: BootstrapNodes): DiscoveryProv
|
|
stats.set(providerKey, {
|
|
stats.set(providerKey, {
|
|
assetApiEndpoint: normalizeUrl(JSON.parse(serviceInfo.data.serialized).asset.endpoint),
|
|
assetApiEndpoint: normalizeUrl(JSON.parse(serviceInfo.data.serialized).asset.endpoint),
|
|
unreachableReports: 0,
|
|
unreachableReports: 0,
|
|
- resolvedAt: Date.now(),
|
|
|
|
|
|
+ resolvedAt: Date.now()
|
|
});
|
|
});
|
|
break;
|
|
break;
|
|
} catch (err) {
|
|
} catch (err) {
|
|
@@ -85,50 +89,50 @@ function newDiscoveryProvider({ bootstrapNodes }: BootstrapNodes): DiscoveryProv
|
|
|
|
|
|
stat = stats.get(providerKey);
|
|
stat = stats.get(providerKey);
|
|
|
|
|
|
- console.log(stat)
|
|
|
|
|
|
+ console.log(stat);
|
|
|
|
|
|
if (stat) {
|
|
if (stat) {
|
|
return `${stat.assetApiEndpoint}/asset/v0/${contentId || ''}`;
|
|
return `${stat.assetApiEndpoint}/asset/v0/${contentId || ''}`;
|
|
}
|
|
}
|
|
|
|
|
|
- throw new Error("Resolving failed.");
|
|
|
|
|
|
+ throw new Error('Resolving failed.');
|
|
};
|
|
};
|
|
|
|
|
|
const reportUnreachable = (provider: AccountId) => {
|
|
const reportUnreachable = (provider: AccountId) => {
|
|
const key = provider.toString();
|
|
const key = provider.toString();
|
|
- let stat = stats.get(key);
|
|
|
|
|
|
+ const stat = stats.get(key);
|
|
if (stat) {
|
|
if (stat) {
|
|
stat.unreachableReports = stat.unreachableReports + 1;
|
|
stat.unreachableReports = stat.unreachableReports + 1;
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
+ };
|
|
|
|
|
|
return { resolveAssetEndpoint, reportUnreachable };
|
|
return { resolveAssetEndpoint, reportUnreachable };
|
|
}
|
|
}
|
|
|
|
|
|
-const DiscoveryProviderContext = createContext<DiscoveryProvider>(undefined as unknown as DiscoveryProvider)
|
|
|
|
|
|
+const DiscoveryProviderContext = createContext<DiscoveryProvider>(undefined as unknown as DiscoveryProvider);
|
|
|
|
|
|
export const DiscoveryProviderProvider = (props: React.PropsWithChildren<{}>) => {
|
|
export const DiscoveryProviderProvider = (props: React.PropsWithChildren<{}>) => {
|
|
const api: ApiProps = useContext(ApiContext);
|
|
const api: ApiProps = useContext(ApiContext);
|
|
- const [provider, setProvider] = useState<DiscoveryProvider | undefined>()
|
|
|
|
- const [loaded, setLoaded] = useState<boolean | undefined>()
|
|
|
|
|
|
+ const [provider, setProvider] = useState<DiscoveryProvider | undefined>();
|
|
|
|
+ const [loaded, setLoaded] = useState<boolean | undefined>();
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
const load = async () => {
|
|
const load = async () => {
|
|
- if (loaded || !api) return
|
|
|
|
|
|
+ if (loaded || !api) return;
|
|
|
|
|
|
- console.log('Discovery Provider: Loading bootstrap node from Substrate...')
|
|
|
|
|
|
+ console.log('Discovery Provider: Loading bootstrap node from Substrate...');
|
|
const bootstrapNodes = await api.api.query.discovery.bootstrapEndpoints() as Vec<Url>;
|
|
const bootstrapNodes = await api.api.query.discovery.bootstrapEndpoints() as Vec<Url>;
|
|
- setProvider(newDiscoveryProvider({ bootstrapNodes }))
|
|
|
|
- setLoaded(true)
|
|
|
|
- console.log('Discovery Provider: Initialized')
|
|
|
|
- }
|
|
|
|
|
|
+ setProvider(newDiscoveryProvider({ bootstrapNodes }));
|
|
|
|
+ setLoaded(true);
|
|
|
|
+ console.log('Discovery Provider: Initialized');
|
|
|
|
+ };
|
|
|
|
|
|
load();
|
|
load();
|
|
- }, [loaded])
|
|
|
|
|
|
+ }, [loaded]);
|
|
|
|
|
|
if (!api || !api.isApiReady) {
|
|
if (!api || !api.isApiReady) {
|
|
// Substrate API is not ready yet.
|
|
// Substrate API is not ready yet.
|
|
- return null
|
|
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
|
|
if (!provider) {
|
|
if (!provider) {
|
|
@@ -139,30 +143,32 @@ export const DiscoveryProviderProvider = (props: React.PropsWithChildren<{}>) =>
|
|
Loading bootstrap nodes... Please wait.
|
|
Loading bootstrap nodes... Please wait.
|
|
</div>
|
|
</div>
|
|
</Message>
|
|
</Message>
|
|
- )
|
|
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
return (
|
|
return (
|
|
<DiscoveryProviderContext.Provider value={provider}>
|
|
<DiscoveryProviderContext.Provider value={provider}>
|
|
{props.children}
|
|
{props.children}
|
|
</DiscoveryProviderContext.Provider>
|
|
</DiscoveryProviderContext.Provider>
|
|
- )
|
|
|
|
-}
|
|
|
|
|
|
+ );
|
|
|
|
+};
|
|
|
|
|
|
export const useDiscoveryProvider = () =>
|
|
export const useDiscoveryProvider = () =>
|
|
- useContext(DiscoveryProviderContext)
|
|
|
|
|
|
+ useContext(DiscoveryProviderContext);
|
|
|
|
|
|
-export function withDiscoveryProvider(Component: React.ComponentType<DiscoveryProviderProps>) {
|
|
|
|
- return (props: React.PropsWithChildren<{}>) => {
|
|
|
|
- const discoveryProvider = useDiscoveryProvider()
|
|
|
|
|
|
+export function withDiscoveryProvider (Component: React.ComponentType<DiscoveryProviderProps>) {
|
|
|
|
+ const ResultComponent: React.FunctionComponent<{}> = (props: React.PropsWithChildren<{}>) => {
|
|
|
|
+ const discoveryProvider = useDiscoveryProvider();
|
|
if (!discoveryProvider) {
|
|
if (!discoveryProvider) {
|
|
- return <JoyInfo title={`Please wait...`}>Loading discovery provider.</JoyInfo>
|
|
|
|
|
|
+ return <JoyInfo title={'Please wait...'}>Loading discovery provider.</JoyInfo>;
|
|
}
|
|
}
|
|
|
|
|
|
return (
|
|
return (
|
|
<Component {...props} discoveryProvider={discoveryProvider}>
|
|
<Component {...props} discoveryProvider={discoveryProvider}>
|
|
{props.children}
|
|
{props.children}
|
|
</Component>
|
|
</Component>
|
|
- )
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+ );
|
|
|
|
+ };
|
|
|
|
+ ResultComponent.displayName = `withDiscoveryProvider(${componentName(Component)})`;
|
|
|
|
+ return ResultComponent;
|
|
|
|
+}
|