Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Network selection #72

Merged
merged 20 commits into from
Apr 23, 2024
6 changes: 4 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
WS_CORETIME_CHAIN="WSS endpoint of the coretime chain"
WS_RELAY_CHAIN="WSS endpoint of the coretime relay chain"
WS_ROCOCO_CORETIME_CHAIN="WSS endpoint of the coretime chain"
WS_KUSAMA_CORETIME_CHAIN="WSS endpoint of the coretime chain"
WS_ROCOCO_RELAY_CHAIN="WSS endpoint of the coretime relay chain"
WS_KUSAMA_RELAY_CHAIN="WSS endpoint of the coretime relay chain"
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ WORKDIR /corehub
COPY . .

# Set the necessary environment variables
ENV WS_CORETIME_CHAIN="ws://127.0.0.1:9910"
ENV WS_RELAY_CHAIN="ws://127.0.0.1:9900"
ENV WS_ROCOCO_CORETIME_CHAIN="ws://127.0.0.1:9910"
ENV WS_ROCOCO_RELAY_CHAIN="ws://127.0.0.1:9900"

RUN apk add --no-cache libc6-compat

Expand Down
6 changes: 4 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ const nextConfig = {
domains: ['github.com'],
},
env: {
WS_CORETIME_CHAIN: process.env.WS_CORETIME_CHAIN || '',
WS_RELAY_CHAIN: process.env.WS_RELAY_CHAIN,
WS_ROCOCO_CORETIME_CHAIN: process.env.WS_ROCOCO_CORETIME_CHAIN || '',
WS_KUSAMA_CORETIME_CHAIN: process.env.WS_KUSAMA_CORETIME_CHAIN || '',
WS_ROCOCO_RELAY_CHAIN: process.env.WS_ROCOCO_RELAY_CHAIN,
WS_KUSAMA_RELAY_CHAIN: process.env.WS_KUSAMA_RELAY_CHAIN,
},
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/Elements/FeatureCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const FeatureCard = ({
enabled,
href,
}: FeatureCardProps) => {
const { push } = useRouter();
const { push, query } = useRouter();
const theme = useTheme();

return (
Expand All @@ -40,7 +40,7 @@ export const FeatureCard = ({
</Typography>
<CardActions>
<Button
onClick={() => enabled && push(href)}
onClick={() => enabled && push({ pathname: href, query })}
size='small'
variant='text'
disabled={!enabled}
Expand Down
35 changes: 35 additions & 0 deletions src/components/Elements/NetworkSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { FormControl, InputLabel, MenuItem, Select } from '@mui/material';
import { useRouter } from 'next/router';

const RelaySelect = () => {
const router = useRouter();
const { network } = router.query;

const handleChange = (e: any) => {
router.push(
{
pathname: router.pathname,
query: { ...router.query, network: e.target.value },
},
undefined,
{ shallow: false }
);
};

return (
<FormControl sx={{ m: 2, minWidth: 150 }} fullWidth>
<InputLabel>Network</InputLabel>
<Select
id='network-select'
value={network ? network : 'rococo'}
label='Relay chain'
onChange={handleChange}
>
<MenuItem value='rococo'>Rococo</MenuItem>
<MenuItem value='kusama'>Kusama</MenuItem>
</Select>
</FormControl>
);
};

export default RelaySelect;
12 changes: 1 addition & 11 deletions src/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import {
useTheme,
} from '@mui/material';
import { useInkathon } from '@scio-labs/use-inkathon';
import React, { useEffect, useState } from 'react';

import { useCoretimeApi, useRelayApi } from '@/contexts/apis';
import React, { useState } from 'react';

import styles from './index.module.scss';
import { WalletModal } from '../Modals/WalletConnect';
Expand All @@ -24,14 +22,6 @@ export const Header = () => {
const [accountsOpen, openAccounts] = useState(false);
const [walletModalOpen, openWalletModal] = useState(false);

const { connectRelay } = useRelayApi();
const { connectCoretime } = useCoretimeApi();

useEffect(() => {
connectRelay();
connectCoretime();
}, [connectRelay, connectCoretime]);

const onDisconnect = () => {
openAccounts(false);
disconnect && disconnect();
Expand Down
7 changes: 6 additions & 1 deletion src/components/Sidebar/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
height: 3rem;
}

.logo > img {
.logo>img {
width: 100%;
height: 100%;
object-fit: contain;
Expand All @@ -36,6 +36,11 @@
gap: 0.5rem;
}

.networkSelector {
display: flex;
padding: .5rem;
}

.menuIcon {
width: 1.5rem;
}
Expand Down
8 changes: 6 additions & 2 deletions src/components/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useCoretimeApi, useRelayApi } from '@/contexts/apis';

import styles from './index.module.scss';
import { StatusIndicator } from '../Elements';
import NetworkSelect from '../Elements/NetworkSelect';

interface MenuItemProps {
label: string;
Expand All @@ -23,7 +24,7 @@ interface MenuItemProps {
}

const MenuItem = ({ label, enabled, route, icon }: MenuItemProps) => {
const { pathname, push } = useRouter();
const { pathname, push, query } = useRouter();
const isActive = pathname === route;
const theme = useTheme();

Expand Down Expand Up @@ -52,7 +53,7 @@ const MenuItem = ({ label, enabled, route, icon }: MenuItemProps) => {
opacity: 0.8,
},
}}
onClick={() => enabled && route && push(route)}
onClick={() => enabled && route && push({ pathname: route, query })}
>
<span className={styles.menuIcon}>{{ ...icon }}</span>
<span
Expand Down Expand Up @@ -168,6 +169,9 @@ export const Sidebar = () => {
<StatusIndicator state={coretimeApiState} label='Coretime chain' />
</div>
</Box>
<div className={styles.networkSelector}>
<NetworkSelect />
</div>
</div>
);
};
33 changes: 24 additions & 9 deletions src/contexts/apis/CoretimeApi/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useRouter } from 'next/router';
import React, { useContext, useEffect, useReducer } from 'react';

import { ApiState } from '@/contexts/apis/types';
import { useToast } from '@/contexts/toast';

import { connect, disconnect, initialState, reducer } from '../common';
import { WS_CORETIME_CHAIN } from '../consts';
import { WS_KUSAMA_CORETIME_CHAIN, WS_ROCOCO_CORETIME_CHAIN } from '../consts';

const types = {
CoreIndex: 'u32',
Expand All @@ -24,9 +25,6 @@ const types = {

const defaultValue = {
state: { ...initialState },
connectCoretime: (): void => {
/** */
},
disconnectCoretime: (): void => {
/** */
},
Expand All @@ -38,6 +36,9 @@ const CoretimeApiContextProvider = (props: any) => {
const [state, dispatch] = useReducer(reducer, initialState);
const { toastError, toastSuccess } = useToast();

const router = useRouter();
const { network } = router.query;

useEffect(() => {
state.apiError && toastError(`Failed to connect to Coretime chain`);
}, [state.apiError, toastError]);
Expand All @@ -47,15 +48,29 @@ const CoretimeApiContextProvider = (props: any) => {
toastSuccess('Successfully connected to the Coretime chain');
}, [state.apiState, toastSuccess]);

const connectCoretime = () =>
connect(state, WS_CORETIME_CHAIN, dispatch, types);
const getUrl = (network: any): string => {
if (!network || network == 'rococo') {
return WS_ROCOCO_CORETIME_CHAIN;
} else if (network == 'kusama') {
return WS_KUSAMA_CORETIME_CHAIN;
} else {
/* eslint-disable no-console */
console.error(`Network: ${network} not recognized`);
// Default to rococo.
return WS_ROCOCO_CORETIME_CHAIN;
}
};

const disconnectCoretime = () => disconnect(state);

useEffect(() => {
if (state.socket == getUrl(network)) return;
const updateNetwork = network != '' && state.socket != getUrl(network);
connect(state, getUrl(network), dispatch, updateNetwork, types);
}, [network, state]);

return (
<CoretimeApiContext.Provider
value={{ state, connectCoretime, disconnectCoretime }}
>
<CoretimeApiContext.Provider value={{ state, disconnectCoretime }}>
{props.children}
</CoretimeApiContext.Provider>
);
Expand Down
33 changes: 25 additions & 8 deletions src/contexts/apis/RelayApi/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useRouter } from 'next/router';
import React, { useContext, useEffect, useReducer, useState } from 'react';

import { parseHNString } from '@/utils/functions';
Expand All @@ -7,13 +8,10 @@ import { useToast } from '@/contexts/toast';
import { ParaId } from '@/models';

import { connect, disconnect, initialState, reducer } from '../common';
import { WS_RELAY_CHAIN } from '../consts';
import { WS_KUSAMA_RELAY_CHAIN, WS_ROCOCO_RELAY_CHAIN } from '../consts';

const defaultValue = {
state: initialState,
connectRelay: (): void => {
/** */
},
disconnectRelay: (): void => {
/** */
},
Expand All @@ -27,6 +25,9 @@ const RelayApiContextProvider = (props: any) => {
const { toastError, toastSuccess } = useToast();
const [paraIds, setParaIds] = useState<ParaId[]>([]);

const router = useRouter();
const { network } = router.query;

useEffect(() => {
state.apiError && toastError(`Failed to connect to relay chain`);
}, [state.apiError, toastError]);
Expand All @@ -36,9 +37,27 @@ const RelayApiContextProvider = (props: any) => {
toastSuccess('Successfully connected to the relay chain');
}, [state.apiState, toastSuccess]);

const connectRelay = () => connect(state, WS_RELAY_CHAIN, dispatch);
const disconnectRelay = () => disconnect(state);

const getUrl = (network: any): string => {
if (!network || network == 'rococo') {
return WS_ROCOCO_RELAY_CHAIN;
} else if (network == 'kusama') {
return WS_KUSAMA_RELAY_CHAIN;
} else {
/* eslint-disable no-console */
console.error(`Network: ${network} not recognized`);
// Default to rococo.
return WS_ROCOCO_RELAY_CHAIN;
}
};

useEffect(() => {
if (state.socket == getUrl(network)) return;
const updateNetwork = network != '' && state.socket != getUrl(network);
connect(state, getUrl(network), dispatch, updateNetwork);
}, [network, state]);

useEffect(() => {
const { api, apiState } = state;
if (!api || apiState !== ApiState.READY) return;
Expand All @@ -54,9 +73,7 @@ const RelayApiContextProvider = (props: any) => {
}, [state]);

return (
<RelayApiContext.Provider
value={{ state, connectRelay, disconnectRelay, paraIds }}
>
<RelayApiContext.Provider value={{ state, disconnectRelay, paraIds }}>
{props.children}
</RelayApiContext.Provider>
);
Expand Down
27 changes: 18 additions & 9 deletions src/contexts/apis/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ApiState } from './types';
export type State = {
jsonrpc: Record<string, Record<string, DefinitionRpcExt>>;
api: ApiPromise | null;
socket: string;
apiError: any;
apiState: ApiState;
symbol: string;
Expand All @@ -19,6 +20,7 @@ export const initialState: State = {
// These are the states
jsonrpc: { ...jsonrpc },
api: null,
socket: '',
apiError: null,
apiState: ApiState.DISCONNECTED,
symbol: '',
Expand All @@ -30,9 +32,18 @@ export const initialState: State = {
export const reducer = (state: any, action: any) => {
switch (action.type) {
case 'CONNECT_INIT':
return { ...state, apiState: ApiState.CONNECT_INIT };
return {
...state,
socket: action.socket,
apiState: ApiState.CONNECT_INIT,
};
case 'CONNECT':
return { ...state, api: action.payload, apiState: ApiState.CONNECTING };
return {
...state,
socket: action.socket,
api: action.payload,
apiState: ApiState.CONNECTING,
};
case 'CONNECT_SUCCESS':
return { ...state, apiState: ApiState.READY };
case 'CONNECT_ERROR':
Expand All @@ -46,29 +57,27 @@ export const reducer = (state: any, action: any) => {
}
};

///
// Connecting to the Substrate node

export const connect = (
state: any,
socket: string,
dispatch: any,
newSocket: boolean,
types?: any
) => {
const { apiState, jsonrpc } = state;
// We only want this function to be performed once
if (apiState !== ApiState.DISCONNECTED) return;

if (!socket) return;

dispatch({ type: 'CONNECT_INIT' });
// We only want this function to be performed once
if (apiState !== ApiState.DISCONNECTED && !newSocket) return;

const provider = new WsProvider(socket);
const _api = new ApiPromise({ provider, rpc: jsonrpc, types });
dispatch({ type: 'CONNECT_INIT', socket });

// Set listeners for disconnection and reconnection event.
_api.on('connected', () => {
dispatch({ type: 'CONNECT', payload: _api });
dispatch({ type: 'CONNECT', payload: _api, socket });
// `ready` event is not emitted upon reconnection and is checked explicitly here.
_api.isReady.then(() => {
dispatch({ type: 'CONNECT_SUCCESS' });
Expand Down
8 changes: 6 additions & 2 deletions src/contexts/apis/consts.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export const WS_RELAY_CHAIN = process.env.WS_RELAY_CHAIN ?? '';
export const WS_CORETIME_CHAIN = process.env.WS_CORETIME_CHAIN ?? '';
export const WS_ROCOCO_RELAY_CHAIN = process.env.WS_ROCOCO_RELAY_CHAIN ?? '';
export const WS_KUSAMA_RELAY_CHAIN = process.env.WS_KUSAMA_RELAY_CHAIN ?? '';
export const WS_ROCOCO_CORETIME_CHAIN =
process.env.WS_ROCOCO_CORETIME_CHAIN ?? '';
export const WS_KUSAMA_CORETIME_CHAIN =
process.env.WS_KUSAMA_CORETIME_CHAIN ?? '';
Loading
Loading