Skip to content

Commit

Permalink
Network selection (#72)
Browse files Browse the repository at this point in the history
* Network selection

* progress

* format

* ..

* working

* fix style

* lint fixes

* width

* fix

* linting errors

* working but a mess

* cleanup

* more cleanup

* ..

* small fixes

* merge fix

* fix issue
  • Loading branch information
Szegoo authored Apr 23, 2024
1 parent 8329716 commit a2a7f34
Show file tree
Hide file tree
Showing 20 changed files with 239 additions and 98 deletions.
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

0 comments on commit a2a7f34

Please sign in to comment.