Merge pull request #3108 from nymtech/feature/nym-connect-select-sp
Feature/nym connect select sp
This commit is contained in:
@@ -6,12 +6,13 @@ import { CustomTitleBar } from './CustomTitleBar';
|
||||
|
||||
export const AppWindowFrame: FCWithChildren = ({ children }) => {
|
||||
const location = useLocation();
|
||||
const { userDefinedGateway, setUserDefinedGateway } = useClientContext();
|
||||
const { userDefinedGateway, setUserDefinedGateway, userDefinedSPAddress, setUserDefinedSPAddress } =
|
||||
useClientContext();
|
||||
|
||||
// defined functions to be used when moving away from pages
|
||||
const onBack = () => {
|
||||
switch (location.pathname) {
|
||||
case '/menu/settings':
|
||||
case '/menu/settings/gateway':
|
||||
return () => {
|
||||
// when the user moves away from the settings page and the gateway is not valid
|
||||
// set isActive to false
|
||||
@@ -19,6 +20,14 @@ export const AppWindowFrame: FCWithChildren = ({ children }) => {
|
||||
setUserDefinedGateway((current) => ({ ...current, isActive: false }));
|
||||
}
|
||||
};
|
||||
case '/menu/settings/service-provider':
|
||||
return () => {
|
||||
// when the user moves away from the settings page and the sp is not valid
|
||||
// set isActive to false
|
||||
if (!userDefinedSPAddress?.address) {
|
||||
setUserDefinedSPAddress((current) => ({ ...current, isActive: false }));
|
||||
}
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ import { invoke } from '@tauri-apps/api';
|
||||
import { Error } from 'src/types/error';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { useEvents } from 'src/hooks/events';
|
||||
import { UserDefinedGateway } from 'src/types/gateway';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { UserDefinedGateway, UserDefinedSPAddress } from 'src/types/service-provider';
|
||||
import { getItemFromStorage, setItemInStorage } from 'src/utils';
|
||||
import { ConnectionStatusKind, GatewayPerformance } from '../types';
|
||||
import { ConnectionStatsItem } from '../components/ConnectionStats';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
|
||||
const FORAGE_KEY = 'nym-connect-user-gateway';
|
||||
const FORAGE_GATEWAY_KEY = 'nym-connect-user-gateway';
|
||||
const FORAGE_SP_KEY = 'nym-connect-user-sp';
|
||||
|
||||
type ModeType = 'light' | 'dark';
|
||||
|
||||
@@ -25,6 +26,7 @@ export type TClientContext = {
|
||||
selectedProvider?: ServiceProvider;
|
||||
showInfoModal: boolean;
|
||||
userDefinedGateway?: UserDefinedGateway;
|
||||
userDefinedSPAddress: UserDefinedSPAddress;
|
||||
serviceProviders?: ServiceProvider[];
|
||||
setMode: (mode: ModeType) => void;
|
||||
clearError: () => void;
|
||||
@@ -32,10 +34,11 @@ export type TClientContext = {
|
||||
setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void;
|
||||
setConnectedSince: (connectedSince: DateTime | undefined) => void;
|
||||
setShowInfoModal: (show: boolean) => void;
|
||||
setRandomSerivceProvider: () => void;
|
||||
setSerivceProvider: () => void;
|
||||
startConnecting: () => Promise<void>;
|
||||
startDisconnecting: () => Promise<void>;
|
||||
setUserDefinedGateway: React.Dispatch<React.SetStateAction<UserDefinedGateway>>;
|
||||
setUserDefinedSPAddress: React.Dispatch<React.SetStateAction<UserDefinedSPAddress>>;
|
||||
};
|
||||
|
||||
export const ClientContext = createContext({} as TClientContext);
|
||||
@@ -51,43 +54,39 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
const [appVersion, setAppVersion] = useState<string>();
|
||||
const [gatewayPerformance, setGatewayPerformance] = useState<GatewayPerformance>('Good');
|
||||
const [showInfoModal, setShowInfoModal] = useState(false);
|
||||
const [userDefinedGateway, setUserDefinedGateway] = useState<UserDefinedGateway>({ isActive: false, gateway: '' });
|
||||
const [userDefinedGateway, setUserDefinedGateway] = useState<UserDefinedGateway>({
|
||||
isActive: false,
|
||||
gateway: undefined,
|
||||
});
|
||||
const [userDefinedSPAddress, setUserDefinedSPAddress] = useState<UserDefinedSPAddress>({
|
||||
isActive: false,
|
||||
address: undefined,
|
||||
});
|
||||
|
||||
const getAppVersion = async () => {
|
||||
const version = await getVersion();
|
||||
return version;
|
||||
};
|
||||
|
||||
const setUserGatewayInStorage = async (gateway: UserDefinedGateway) => {
|
||||
try {
|
||||
await forage.setItem({
|
||||
key: FORAGE_KEY,
|
||||
value: gateway,
|
||||
} as any)();
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
useEffect(() => {
|
||||
setItemInStorage({ key: FORAGE_GATEWAY_KEY, value: userDefinedGateway });
|
||||
}, [userDefinedGateway]);
|
||||
|
||||
const getUserGatewayFromStorage = async (): Promise<UserDefinedGateway | undefined> => {
|
||||
try {
|
||||
const gatewayFromStorage = await forage.getItem({ key: FORAGE_KEY })();
|
||||
return gatewayFromStorage;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
useEffect(() => {
|
||||
setItemInStorage({ key: FORAGE_SP_KEY, value: userDefinedSPAddress });
|
||||
}, [userDefinedSPAddress]);
|
||||
|
||||
const initialiseApp = async () => {
|
||||
const services = await invoke('get_services');
|
||||
const AppVersion = await getAppVersion();
|
||||
const storedUserDefinedGateway = await getUserGatewayFromStorage();
|
||||
const storedUserDefinedGateway = await getItemFromStorage({ key: FORAGE_GATEWAY_KEY });
|
||||
const storedUserDefinedSP = await getItemFromStorage({ key: FORAGE_SP_KEY });
|
||||
|
||||
setAppVersion(AppVersion);
|
||||
setServiceProviders(services as ServiceProvider[]);
|
||||
|
||||
if (storedUserDefinedGateway) setUserDefinedGateway(storedUserDefinedGateway);
|
||||
if (storedUserDefinedSP) setUserDefinedSPAddress(storedUserDefinedSP);
|
||||
};
|
||||
|
||||
useEvents({
|
||||
@@ -126,27 +125,37 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
}, []);
|
||||
|
||||
const shouldUseUserGateway = !!userDefinedGateway.gateway && userDefinedGateway.isActive;
|
||||
const shouldUseUserSP = !!userDefinedSPAddress.address && userDefinedSPAddress.isActive;
|
||||
|
||||
const setServiceProvider = async (newServiceProvider: ServiceProvider) => {
|
||||
await invoke('set_gateway', {
|
||||
gateway: newServiceProvider.gateway,
|
||||
gateway: shouldUseUserGateway ? userDefinedGateway.gateway : newServiceProvider.gateway,
|
||||
});
|
||||
await invoke('set_service_provider', {
|
||||
serviceProvider: shouldUseUserSP ? userDefinedSPAddress.address : newServiceProvider.address,
|
||||
});
|
||||
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
|
||||
};
|
||||
|
||||
const getRandomSPFromList = (services: ServiceProvider[]) => {
|
||||
const randomSelection = services[Math.floor(Math.random() * services.length)];
|
||||
|
||||
if (shouldUseUserGateway) return { ...randomSelection, gateway: userDefinedGateway.gateway } as ServiceProvider;
|
||||
return randomSelection;
|
||||
};
|
||||
|
||||
const setRandomSerivceProvider = async () => {
|
||||
const buildServiceProvider = async (serviceProvider: ServiceProvider) => {
|
||||
const sp = { ...serviceProvider };
|
||||
|
||||
if (shouldUseUserGateway) sp.gateway = userDefinedGateway.gateway as string;
|
||||
if (shouldUseUserSP) sp.address = userDefinedSPAddress.address as string;
|
||||
|
||||
return sp;
|
||||
};
|
||||
|
||||
const setSerivceProvider = async () => {
|
||||
if (serviceProviders) {
|
||||
const randomServiceProvider = getRandomSPFromList(serviceProviders);
|
||||
await setServiceProvider(randomServiceProvider);
|
||||
await setUserGatewayInStorage(userDefinedGateway);
|
||||
setSelectedProvider(randomServiceProvider);
|
||||
const withUserDefinitions = await buildServiceProvider(randomServiceProvider);
|
||||
await setServiceProvider(withUserDefinitions);
|
||||
setSelectedProvider(withUserDefinitions);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@@ -169,13 +178,15 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
serviceProviders,
|
||||
connectedSince,
|
||||
setConnectedSince,
|
||||
setRandomSerivceProvider,
|
||||
setSerivceProvider,
|
||||
startConnecting,
|
||||
startDisconnecting,
|
||||
gatewayPerformance,
|
||||
setShowInfoModal,
|
||||
userDefinedSPAddress,
|
||||
userDefinedGateway,
|
||||
setUserDefinedGateway,
|
||||
setUserDefinedSPAddress,
|
||||
}),
|
||||
[
|
||||
mode,
|
||||
@@ -190,6 +201,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
gatewayPerformance,
|
||||
selectedProvider,
|
||||
userDefinedGateway,
|
||||
userDefinedSPAddress,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const mockValues: TClientContext = {
|
||||
gatewayPerformance: 'Good',
|
||||
showInfoModal: false,
|
||||
userDefinedGateway: { isActive: false, gateway: '' },
|
||||
userDefinedSPAddress: { isActive: false, address: '' },
|
||||
setShowInfoModal: () => {},
|
||||
setMode: () => {},
|
||||
clearError: () => {},
|
||||
@@ -18,8 +19,9 @@ const mockValues: TClientContext = {
|
||||
setConnectionStatus: () => {},
|
||||
startConnecting: async () => {},
|
||||
startDisconnecting: async () => {},
|
||||
setRandomSerivceProvider: () => {},
|
||||
setSerivceProvider: () => {},
|
||||
setUserDefinedGateway: () => {},
|
||||
setUserDefinedSPAddress: () => {},
|
||||
};
|
||||
|
||||
export const MockProvider: FCWithChildren<{
|
||||
|
||||
@@ -28,7 +28,7 @@ export const ConnectionPage = () => {
|
||||
// eslint-disable-next-line default-case
|
||||
switch (currentStatus) {
|
||||
case 'disconnected':
|
||||
await context.setRandomSerivceProvider();
|
||||
await context.setSerivceProvider();
|
||||
await context.startConnecting();
|
||||
context.setConnectedSince(DateTime.now());
|
||||
context.setShowInfoModal(true);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ChangeEvent, useState } from 'react';
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
@@ -15,18 +15,26 @@ import { ConnectionStatusKind } from 'src/types';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
|
||||
export const ServiceProviderSettings = () => {
|
||||
const [spAddress, setSPAddress] = useState<string>();
|
||||
const [isOn, setIsOn] = useState(false);
|
||||
|
||||
const { connectionStatus, serviceProviders } = useClientContext();
|
||||
const { connectionStatus, serviceProviders, userDefinedSPAddress, setUserDefinedSPAddress } = useClientContext();
|
||||
|
||||
const toggleOnOff = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!isOn) setSPAddress(undefined);
|
||||
setIsOn(e.target.checked);
|
||||
setUserDefinedSPAddress((current) => ({ ...current, isActive: e.target.checked }));
|
||||
};
|
||||
|
||||
const handleSelectFromList = (value: string | null) => {
|
||||
setSPAddress(value ?? undefined);
|
||||
setUserDefinedSPAddress((current) => ({ ...current, address: value ?? undefined }));
|
||||
};
|
||||
|
||||
const getSPDescription = (spAddress: string) => {
|
||||
const match = serviceProviders?.find((sp) => sp.address === spAddress);
|
||||
|
||||
if (match) return match.description;
|
||||
|
||||
return 'The service provider specified is not in our known list.';
|
||||
};
|
||||
|
||||
const validateInput = (value: string) => {
|
||||
setUserDefinedSPAddress((current) => ({ ...current, address: !value.length ? undefined : value }));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -43,40 +51,43 @@ export const ServiceProviderSettings = () => {
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={isOn}
|
||||
checked={userDefinedSPAddress.isActive}
|
||||
onChange={toggleOnOff}
|
||||
disabled={connectionStatus === ConnectionStatusKind.connected}
|
||||
size="small"
|
||||
sx={{ ml: 1 }}
|
||||
/>
|
||||
}
|
||||
label={isOn ? 'On' : 'Off'}
|
||||
label={userDefinedSPAddress.isActive ? 'On' : 'Off'}
|
||||
/>
|
||||
{connectionStatus === ConnectionStatusKind.connected && (
|
||||
<FormHelperText sx={{ m: 0, my: 1 }}>This setting is disabled during an active connection</FormHelperText>
|
||||
)}
|
||||
{isOn && serviceProviders && (
|
||||
{userDefinedSPAddress.isActive && serviceProviders && (
|
||||
<Autocomplete
|
||||
clearOnEscape
|
||||
disabled={connectionStatus === 'connected'}
|
||||
sx={{ mt: 1 }}
|
||||
options={serviceProviders.map((sp) => `${sp.address.substring(0, 20)}...`)}
|
||||
options={serviceProviders.map((sp) => sp.address)}
|
||||
freeSolo
|
||||
value={userDefinedSPAddress.address || ''}
|
||||
onChange={(e, value) => handleSelectFromList(value)}
|
||||
value={spAddress}
|
||||
size="small"
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
autoFocus
|
||||
{...params}
|
||||
value={spAddress}
|
||||
onChange={(e) => console.log(e.target.value)}
|
||||
placeholder="Service provider"
|
||||
onChange={(e) => validateInput(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
ListboxProps={{ style: { background: 'unset', fontSize: '14px' } }}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
{userDefinedSPAddress.address && userDefinedSPAddress.isActive && (
|
||||
<Typography sx={{ mt: 1 }}>{getSPDescription(userDefinedSPAddress.address)}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<AppVersion />
|
||||
</Stack>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface UserDefinedGateway {
|
||||
isActive: boolean;
|
||||
gateway?: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface UserDefinedGateway {
|
||||
isActive: boolean;
|
||||
gateway?: string;
|
||||
}
|
||||
|
||||
export interface UserDefinedSPAddress {
|
||||
isActive: boolean;
|
||||
address?: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface StorageKeyValue<T> {
|
||||
key: string;
|
||||
value: T;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { EventName, listen, UnlistenFn, EventCallback } from '@tauri-apps/api/event';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { StorageKeyValue } from 'src/types/storage';
|
||||
|
||||
export const useTauriEvents = <T>(event: EventName, handler: EventCallback<T>) => {
|
||||
const unlisten = useRef<UnlistenFn>();
|
||||
@@ -22,3 +24,25 @@ export const useTauriEvents = <T>(event: EventName, handler: EventCallback<T>) =
|
||||
export const toggleLogViewer = async () => {
|
||||
await invoke('help_log_toggle_window');
|
||||
};
|
||||
|
||||
export async function setItemInStorage<T>({ key, value }: StorageKeyValue<T>) {
|
||||
try {
|
||||
await forage.setItem({
|
||||
key,
|
||||
value,
|
||||
} as any)();
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const getItemFromStorage = async ({ key }: Pick<StorageKeyValue<undefined>, 'key'>) => {
|
||||
try {
|
||||
const gatewayFromStorage = await forage.getItem({ key })();
|
||||
return gatewayFromStorage;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user