diff --git a/nym-vpn/ui/README.md b/nym-vpn/ui/README.md index 03456e2a44..b89d289f71 100644 --- a/nym-vpn/ui/README.md +++ b/nym-vpn/ui/README.md @@ -40,9 +40,6 @@ For example on Linux the path would be `~/.config/nym-vpn/config.toml` # path to the env config file if you provide one env_config_file = "$HOME/.config/nym-vpn/qa.env" -# pick a gateway and exit router accordingly to the selected env -entry_gateway = "xxx" -exit_router = "xxx" ``` ## Dev diff --git a/nym-vpn/ui/src-tauri/Cargo.lock b/nym-vpn/ui/src-tauri/Cargo.lock index 719b71f123..cb40fc3e00 100644 --- a/nym-vpn/ui/src-tauri/Cargo.lock +++ b/nym-vpn/ui/src-tauri/Cargo.lock @@ -5267,6 +5267,7 @@ dependencies = [ "nym-task", "nym-validator-client", "nym-wireguard-types", + "rand 0.7.3", "serde", "serde_json", "signature 1.4.0", diff --git a/nym-vpn/ui/src-tauri/src/commands/app_data.rs b/nym-vpn/ui/src-tauri/src/commands/app_data.rs index 9fd33de518..901726d2f4 100644 --- a/nym-vpn/ui/src-tauri/src/commands/app_data.rs +++ b/nym-vpn/ui/src-tauri/src/commands/app_data.rs @@ -1,6 +1,7 @@ use tauri::State; use tracing::{debug, instrument}; +use crate::country::COUNTRIES; use crate::states::app::Country; use crate::{ error::{CmdError, CmdErrorSource}, @@ -12,29 +13,8 @@ use crate::{ #[tauri::command] pub fn get_node_countries() -> Result, CmdError> { debug!("get_node_countries"); - let countries: Vec = vec![ - Country { - name: "United States".to_string(), - code: "US".to_string(), - }, - Country { - name: "France".to_string(), - code: "FR".to_string(), - }, - Country { - name: "Switzerland".to_string(), - code: "CH".to_string(), - }, - Country { - name: "Sweden".to_string(), - code: "SE".to_string(), - }, - Country { - name: "Germany".to_string(), - code: "DE".to_string(), - }, - ]; - Ok(countries) + // TODO fetch the list of countries from some API + Ok(COUNTRIES.clone()) } #[instrument(skip(state))] @@ -97,3 +77,25 @@ pub async fn set_ui_theme( .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; Ok(()) } + +#[instrument(skip(data_state))] +#[tauri::command] +pub async fn set_entry_location_selector( + data_state: State<'_, SharedAppData>, + entry_selector: bool, +) -> Result<(), CmdError> { + debug!("set_entry_location_selector"); + + let mut app_data_store = data_state.lock().await; + let mut app_data = app_data_store + .read() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + app_data.entry_location_selector = Some(entry_selector); + app_data_store.data = app_data; + app_data_store + .write() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + Ok(()) +} diff --git a/nym-vpn/ui/src-tauri/src/commands/connection.rs b/nym-vpn/ui/src-tauri/src/commands/connection.rs index adeb092e4e..1bed47fe88 100644 --- a/nym-vpn/ui/src-tauri/src/commands/connection.rs +++ b/nym-vpn/ui/src-tauri/src/commands/connection.rs @@ -1,4 +1,5 @@ use futures::SinkExt; +use nym_vpn_lib::gateway_client::{EntryPoint, ExitPoint}; use nym_vpn_lib::{NymVpnCtrlMessage, NymVpnHandle}; use tauri::{Manager, State}; use tracing::{debug, error, info, instrument, trace}; @@ -16,6 +17,8 @@ use crate::{ }, }; +const DEFAULT_NODE_LOCATION: &str = "DE"; + #[instrument(skip_all)] #[tauri::command] pub async fn get_connection_state( @@ -67,23 +70,48 @@ pub async fn connect( ) .ok(); - let app_config = config_store.lock().await.read().await.map_err(|e| { - CmdError::new( - CmdErrorSource::InternalError, - format!("failed to read app config: {}", e), - ) - })?; - let mut vpn_config = create_vpn_config(&app_config); - { - let app_state = state.lock().await; - if let VpnMode::TwoHop = app_state.vpn_mode { - info!("2-hop mode enabled"); - vpn_config.enable_two_hop = true; - } else { - info!("5-hop mode enabled"); + let app_state = state.lock().await; + + // get entry node location from app config file + let app_config = config_store + .lock() + .await + .read() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + let entry_location = app_config + .entry_node_location + .clone() + .unwrap_or_else(|| DEFAULT_NODE_LOCATION.to_string()); + // !! release config_store mutex + drop(app_config); + + debug!("using entry node location: {}", entry_location); + let entry_point = EntryPoint::Location(entry_location); + let exit_point = match app_state.exit_node_location { + Some(ref exit_node_location) => { + debug!("exit node location set, using: {}", exit_node_location.code); + ExitPoint::Location(exit_node_location.code.clone()) } + _ => { + debug!( + "exit node location not set, using default: {}", + DEFAULT_NODE_LOCATION + ); + ExitPoint::Location(DEFAULT_NODE_LOCATION.into()) + } + }; + + let mut vpn_config = create_vpn_config(entry_point, exit_point); + if let VpnMode::TwoHop = app_state.vpn_mode { + info!("2-hop mode enabled"); + vpn_config.enable_two_hop = true; + } else { + info!("5-hop mode enabled"); } // vpn_config.disable_routing = true; + // !! release app_state mutex + drop(app_state); // spawn the VPN client and start a new connection let NymVpnHandle { diff --git a/nym-vpn/ui/src-tauri/src/commands/node_location.rs b/nym-vpn/ui/src-tauri/src/commands/node_location.rs index 90d3f9a9f9..9e9eaa84bf 100644 --- a/nym-vpn/ui/src-tauri/src/commands/node_location.rs +++ b/nym-vpn/ui/src-tauri/src/commands/node_location.rs @@ -1,3 +1,4 @@ +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use tauri::State; use tracing::{debug, instrument}; @@ -14,6 +15,11 @@ pub enum NodeType { Exit, } +static DEFAULT_NODE_LOCATION: Lazy = Lazy::new(|| Country { + code: "DE".to_string(), + name: "Germany".to_string(), +}); + #[instrument(skip(app_state, data_state))] #[tauri::command] pub async fn set_node_location( @@ -57,3 +63,10 @@ pub async fn set_node_location( Ok(()) } + +#[instrument] +#[tauri::command] +pub async fn get_default_node_location() -> Result { + debug!("get_default_node_location"); + Ok(DEFAULT_NODE_LOCATION.clone()) +} diff --git a/nym-vpn/ui/src-tauri/src/country.rs b/nym-vpn/ui/src-tauri/src/country.rs new file mode 100644 index 0000000000..5320000c37 --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/country.rs @@ -0,0 +1,25 @@ +use once_cell::sync::Lazy; + +use crate::states::app::Country; + +// TODO use hardcoded country list for now +pub static COUNTRIES: Lazy> = Lazy::new(|| { + vec![ + Country { + name: "Ireland".to_string(), + code: "IE".to_string(), + }, + Country { + name: "Germany".to_string(), + code: "DE".to_string(), + }, + Country { + name: "Japan".to_string(), + code: "JP".to_string(), + }, + Country { + name: "Great Britain".to_string(), + code: "GB".to_string(), + }, + ] +}); diff --git a/nym-vpn/ui/src-tauri/src/fs/config.rs b/nym-vpn/ui/src-tauri/src/fs/config.rs index 72005fe58f..e929a02480 100644 --- a/nym-vpn/ui/src-tauri/src/fs/config.rs +++ b/nym-vpn/ui/src-tauri/src/fs/config.rs @@ -6,8 +6,6 @@ use serde::{Deserialize, Serialize}; pub struct AppConfig { /// Path pointing to an env configuration file describing the network pub env_config_file: Option, - /// Mixnet public ID of the entry gateway - pub entry_gateway: String, - /// Mixnet recipient address - pub exit_router: String, + /// Country code (two letters format, eg. FR) + pub entry_node_location: Option, } diff --git a/nym-vpn/ui/src-tauri/src/fs/data.rs b/nym-vpn/ui/src-tauri/src/fs/data.rs index 0b8bc7a5da..e6b3952316 100644 --- a/nym-vpn/ui/src-tauri/src/fs/data.rs +++ b/nym-vpn/ui/src-tauri/src/fs/data.rs @@ -17,6 +17,7 @@ pub struct AppData { pub monitoring: Option, pub autoconnect: Option, pub killswitch: Option, + pub entry_location_selector: Option, pub ui_theme: Option, pub vpn_mode: Option, pub entry_node: Option, diff --git a/nym-vpn/ui/src-tauri/src/main.rs b/nym-vpn/ui/src-tauri/src/main.rs index 51949f4884..34798e19b5 100644 --- a/nym-vpn/ui/src-tauri/src/main.rs +++ b/nym-vpn/ui/src-tauri/src/main.rs @@ -16,6 +16,7 @@ use nym_vpn_lib::nym_config; use crate::fs::{config::AppConfig, data::AppData, storage::AppStorage}; mod commands; +mod country; mod error; mod fs; mod states; @@ -63,6 +64,8 @@ async fn main() -> Result<()> { &app_config_store.full_path.display() ); + let app_data = app_data_store.read().await?; + debug!("app_data: {app_data:?}"); let app_config = app_config_store.read().await?; debug!("app_config: {app_config:?}"); @@ -88,7 +91,7 @@ async fn main() -> Result<()> { info!("Starting tauri app"); tauri::Builder::default() - .manage(Arc::new(Mutex::new(AppState::default()))) + .manage(Arc::new(Mutex::new(AppState::from(&app_data)))) .manage(Arc::new(Mutex::new(app_data_store))) .manage(Arc::new(Mutex::new(app_config_store))) .setup(|_app| { @@ -104,8 +107,10 @@ async fn main() -> Result<()> { app_data::get_app_data, app_data::set_app_data, app_data::set_ui_theme, + app_data::set_entry_location_selector, app_data::get_node_countries, node_location::set_node_location, + node_location::get_default_node_location, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/nym-vpn/ui/src-tauri/src/states/app.rs b/nym-vpn/ui/src-tauri/src/states/app.rs index 1ef6f1a540..1a54f73e03 100644 --- a/nym-vpn/ui/src-tauri/src/states/app.rs +++ b/nym-vpn/ui/src-tauri/src/states/app.rs @@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use ts_rs::TS; +use crate::fs::data::AppData; + #[derive(Debug, Serialize, Deserialize, Clone, TS)] #[ts(export)] pub struct NodeConfig { @@ -51,6 +53,17 @@ pub struct AppState { pub vpn_ctrl_tx: Option>, } +impl From<&AppData> for AppState { + fn from(app_data: &AppData) -> Self { + AppState { + entry_node_location: app_data.entry_node_location.clone(), + exit_node_location: app_data.exit_node_location.clone(), + vpn_mode: app_data.vpn_mode.clone().unwrap_or_default(), + ..Default::default() + } + } +} + #[derive(Default, Serialize, Deserialize, Debug, Clone, TS)] #[ts(export)] pub struct Country { diff --git a/nym-vpn/ui/src-tauri/src/vpn_client.rs b/nym-vpn/ui/src-tauri/src/vpn_client.rs index ea2eb9073f..1f0aa1c9f5 100644 --- a/nym-vpn/ui/src-tauri/src/vpn_client.rs +++ b/nym-vpn/ui/src-tauri/src/vpn_client.rs @@ -1,9 +1,8 @@ -use crate::fs::config::AppConfig; use crate::states::{app::ConnectionState, SharedAppState}; use anyhow::Result; use futures::channel::oneshot::Receiver as OneshotReceiver; use futures::StreamExt; -use nym_vpn_lib::gateway_client::Config as GatewayClientConfig; +use nym_vpn_lib::gateway_client::{Config as GatewayClientConfig, EntryPoint, ExitPoint}; use nym_vpn_lib::nym_config::OptionalSet; use nym_vpn_lib::{NymVpn, NymVpnExitError, NymVpnExitStatusMessage, StatusReceiver}; use tauri::Manager; @@ -166,8 +165,8 @@ fn setup_gateway_client_config(private_key: Option<&str>) -> GatewayClientConfig } #[instrument(skip_all)] -pub fn create_vpn_config(app_config: &AppConfig) -> NymVpn { - let mut nym_vpn = NymVpn::new(&app_config.entry_gateway, &app_config.exit_router); +pub fn create_vpn_config(entry_point: EntryPoint, exit_point: ExitPoint) -> NymVpn { + let mut nym_vpn = NymVpn::new(entry_point, exit_point); nym_vpn.gateway_config = setup_gateway_client_config(None); nym_vpn } diff --git a/nym-vpn/ui/src-tauri/tauri.conf.json b/nym-vpn/ui/src-tauri/tauri.conf.json index 532d960be3..93898ba01e 100644 --- a/nym-vpn/ui/src-tauri/tauri.conf.json +++ b/nym-vpn/ui/src-tauri/tauri.conf.json @@ -7,7 +7,7 @@ "withGlobalTauri": false }, "package": { - "productName": "nym-vpn-ui", + "productName": "nym-vpn", "version": "0.0.0" }, "tauri": { diff --git a/nym-vpn/ui/src/constants.ts b/nym-vpn/ui/src/constants.ts index 6c9e06ba3b..026e015917 100644 --- a/nym-vpn/ui/src/constants.ts +++ b/nym-vpn/ui/src/constants.ts @@ -1,5 +1,3 @@ -import { Country } from './types'; - export const routes = { root: '/', settings: '/settings', @@ -10,5 +8,4 @@ export const routes = { export const AppName = 'NymVPN'; export const ConnectionEvent = 'connection-state'; export const ProgressEvent = 'connection-progress'; -//putting this here for now until decided how default country is determined -export const QuickConnectCountry: Country = { name: 'Germany', code: 'DE' }; +export const QuickConnectPrefix = 'Fastest'; diff --git a/nym-vpn/ui/src/dev/setup.ts b/nym-vpn/ui/src/dev/setup.ts index 976d4239c3..f07e3b8f18 100644 --- a/nym-vpn/ui/src/dev/setup.ts +++ b/nym-vpn/ui/src/dev/setup.ts @@ -1,7 +1,7 @@ import { mockIPC, mockWindows } from '@tauri-apps/api/mocks'; import { emit } from '@tauri-apps/api/event'; import { AppDataFromBackend, ConnectionState, Country } from '../types'; -import { ConnectionEvent, QuickConnectCountry } from '../constants'; +import { ConnectionEvent } from '../constants'; export function mockTauriIPC() { mockWindows('main'); @@ -56,21 +56,37 @@ export function mockTauriIPC() { ); } + if (cmd === 'get_default_node_location') { + return new Promise((resolve) => + resolve({ + name: 'France', + code: 'FR', + }), + ); + } + if (cmd === 'get_app_data') { return new Promise((resolve) => resolve({ monitoring: false, autoconnect: false, killswitch: false, + entry_location_selector: false, ui_theme: 'Dark', vpn_mode: 'TwoHop', entry_node: { - country: QuickConnectCountry, - id: QuickConnectCountry.code, + country: { + name: 'France', + code: 'FR', + }, + id: 'nodeOne', }, exit_node: { - country: QuickConnectCountry, - id: QuickConnectCountry.code, + country: { + name: 'France', + code: 'FR', + }, + id: 'nodeTwo', }, entry_node_location: null, exit_node_location: null, diff --git a/nym-vpn/ui/src/i18n/en/node-location.json b/nym-vpn/ui/src/i18n/en/node-location.json index cddd35900a..9766e025fb 100644 --- a/nym-vpn/ui/src/i18n/en/node-location.json +++ b/nym-vpn/ui/src/i18n/en/node-location.json @@ -1,7 +1,6 @@ { "loading": "Loading..", - "none-found": "No results found!", + "none-found": "No results found. Please try another search", "selected": "Selected", - "search-country": "Search country", - "quick-connect": "Quick connect" + "search-country": "Search country" } diff --git a/nym-vpn/ui/src/i18n/en/settings.json b/nym-vpn/ui/src/i18n/en/settings.json index 0967ef424b..dd9a88fffe 100644 --- a/nym-vpn/ui/src/i18n/en/settings.json +++ b/nym-vpn/ui/src/i18n/en/settings.json @@ -1 +1,3 @@ -{} +{ + "entry-selector": "Entry location selector" +} diff --git a/nym-vpn/ui/src/pages/home/ConnectionStatus.tsx b/nym-vpn/ui/src/pages/home/ConnectionStatus.tsx index ad9a4acbcb..deb8718fd4 100644 --- a/nym-vpn/ui/src/pages/home/ConnectionStatus.tsx +++ b/nym-vpn/ui/src/pages/home/ConnectionStatus.tsx @@ -66,8 +66,8 @@ function ConnectionStatus() { {getStatusText(state.state)} -
- {state.loading && state.progressMessages.length > 0 && ( +
+ {state.loading && state.progressMessages.length > 0 && !state.error && (

{t( `connection-progress.${ diff --git a/nym-vpn/ui/src/pages/home/Home.tsx b/nym-vpn/ui/src/pages/home/Home.tsx index 843bf5b5a1..64af96a8ec 100644 --- a/nym-vpn/ui/src/pages/home/Home.tsx +++ b/nym-vpn/ui/src/pages/home/Home.tsx @@ -5,14 +5,15 @@ import clsx from 'clsx'; import { Button } from '@mui/base'; import { useNavigate } from 'react-router-dom'; import { useMainDispatch, useMainState } from '../../contexts'; -import { StateDispatch } from '../../types'; -import { QuickConnectCountry, routes } from '../../constants'; +import { CmdError, StateDispatch } from '../../types'; +import { routes } from '../../constants'; import NetworkModeSelect from './NetworkModeSelect'; import ConnectionStatus from './ConnectionStatus'; import HopSelect from './HopSelect'; function Home() { - const { state, loading, exitNodeLocation } = useMainState(); + const { state, loading, exitNodeLocation, defaultNodeLocation } = + useMainState(); const dispatch = useMainDispatch() as StateDispatch; const navigate = useNavigate(); const { t } = useTranslation('home'); @@ -25,9 +26,9 @@ function Home() { console.log('disconnect result'); console.log(result); }) - .catch((e) => { - console.log(e); - dispatch({ type: 'set-error', error: e }); + .catch((e: CmdError) => { + console.warn(`backend error: ${e.source} - ${e.message}`); + dispatch({ type: 'set-error', error: e.message }); }); } else if (state === 'Disconnected') { dispatch({ type: 'connect' }); @@ -36,9 +37,9 @@ function Home() { console.log('connect result'); console.log(result); }) - .catch((e) => { - console.log(e); - dispatch({ type: 'set-error', error: e }); + .catch((e: CmdError) => { + console.warn(`backend error: ${e.source} - ${e.message}`); + dispatch({ type: 'set-error', error: e.message }); }); } }; @@ -73,11 +74,20 @@ function Home() {

- navigate(routes.exitNodeLocation)} - nodeHop="exit" - /> +
+
+ {t('select-node-title')} +
+ { + if (state === 'Disconnected') { + navigate(routes.exitNodeLocation); + } + }} + nodeHop="exit" + /> +
diff --git a/nym-vpn/ui/src/pages/location/QuickConnect.tsx b/nym-vpn/ui/src/pages/location/QuickConnect.tsx index c4e58bf323..07e4b3dbe5 100644 --- a/nym-vpn/ui/src/pages/location/QuickConnect.tsx +++ b/nym-vpn/ui/src/pages/location/QuickConnect.tsx @@ -1,25 +1,26 @@ -import { useTranslation } from 'react-i18next'; -import { QuickConnectCountry } from '../../constants'; +import { useMainState } from '../../contexts'; +import { QuickConnectPrefix } from '../../constants'; + interface QuickConnectProps { onClick: (name: string, code: string) => void; } + export default function QuickConnect({ onClick }: QuickConnectProps) { - const { t } = useTranslation('nodeLocation'); + const { defaultNodeLocation } = useMainState(); + return (
- onClick(QuickConnectCountry.name, QuickConnectCountry.code) + onClick(defaultNodeLocation.name, defaultNodeLocation.code) } - className="flex flex-row w-full py-8 cursor-pointer" + className="flex px-5 flex-row w-full py-8 cursor-pointer" onClick={() => - onClick(QuickConnectCountry.name, QuickConnectCountry.code) + onClick(defaultNodeLocation.name, defaultNodeLocation.code) } > bolt -
{`${t('quick-connect')} (${ - QuickConnectCountry.name - })`}
+
{`${QuickConnectPrefix} ${defaultNodeLocation.name}`}
); } diff --git a/nym-vpn/ui/src/pages/location/SearchBox.tsx b/nym-vpn/ui/src/pages/location/SearchBox.tsx index 60b98ad98a..d506787199 100644 --- a/nym-vpn/ui/src/pages/location/SearchBox.tsx +++ b/nym-vpn/ui/src/pages/location/SearchBox.tsx @@ -12,21 +12,21 @@ export default function SearchBox({ placeholder, }: SearchProps) { return ( -
+
- + search diff --git a/nym-vpn/ui/src/pages/settings/Settings.tsx b/nym-vpn/ui/src/pages/settings/Settings.tsx index 220105a857..862716010f 100644 --- a/nym-vpn/ui/src/pages/settings/Settings.tsx +++ b/nym-vpn/ui/src/pages/settings/Settings.tsx @@ -9,11 +9,13 @@ function Settings() { const state = useMainState(); const dispatch = useMainDispatch() as StateDispatch; - const [enabled, setEnabled] = useState(state.uiTheme === 'Dark'); + const [darkModeEnabled, setDarkModeEnabled] = useState( + state.uiTheme === 'Dark', + ); useEffect(() => { - setEnabled(state.uiTheme === 'Dark'); - }, [state.uiTheme]); + setDarkModeEnabled(state.uiTheme === 'Dark'); + }, [state]); const handleThemeChange = async (darkMode: boolean) => { if (darkMode && state.uiTheme === 'Light') { @@ -29,23 +31,25 @@ function Settings() { }; return ( -
+

Dark Mode

Dark mode diff --git a/nym-vpn/ui/src/state/main.ts b/nym-vpn/ui/src/state/main.ts index c7e498966c..48459804f0 100644 --- a/nym-vpn/ui/src/state/main.ts +++ b/nym-vpn/ui/src/state/main.ts @@ -13,6 +13,7 @@ export type StateAction = | { type: 'set-partial-state'; partialState: Partial } | { type: 'change-connection-state'; state: ConnectionState } | { type: 'set-vpn-mode'; mode: VpnMode } + | { type: 'set-entry-selector'; entrySelector: boolean } | { type: 'set-error'; error: string } | { type: 'reset-error' } | { type: 'new-progress-message'; message: ConnectProgressMsg } @@ -23,17 +24,25 @@ export type StateAction = | { type: 'set-disconnected' } | { type: 'reset' } | { type: 'set-ui-theme'; theme: UiTheme } - | { type: 'set-node-location'; payload: { hop: NodeHop; country: Country } }; + | { type: 'set-countries'; countries: Country[] } + | { type: 'set-node-location'; payload: { hop: NodeHop; country: Country } } + | { type: 'set-default-node-location'; country: Country }; export const initialState: AppState = { state: 'Disconnected', loading: false, - vpnMode: 'Mixnet', + vpnMode: 'TwoHop', + entrySelector: false, tunnel: { name: 'nym', id: 'nym' }, uiTheme: 'Light', progressMessages: [], entryNodeLocation: null, exitNodeLocation: null, + defaultNodeLocation: { + name: 'France', + code: 'FR', + }, + countries: [], }; export function reducer(state: AppState, action: StateAction): AppState { @@ -54,6 +63,16 @@ export function reducer(state: AppState, action: StateAction): AppState { ...state, vpnMode: action.mode, }; + case 'set-entry-selector': + return { + ...state, + entrySelector: action.entrySelector, + }; + case 'set-countries': + return { + ...state, + countries: action.countries, + }; case 'set-partial-state': { return { ...state, ...action.partialState }; } @@ -121,6 +140,11 @@ export function reducer(state: AppState, action: StateAction): AppState { ...state, uiTheme: action.theme, }; + case 'set-default-node-location': + return { + ...state, + defaultNodeLocation: action.country, + }; case 'reset': return initialState; } diff --git a/nym-vpn/ui/src/state/provider.tsx b/nym-vpn/ui/src/state/provider.tsx index d2a281ff5e..9cb376aaf0 100644 --- a/nym-vpn/ui/src/state/provider.tsx +++ b/nym-vpn/ui/src/state/provider.tsx @@ -1,8 +1,12 @@ import React, { useEffect, useReducer } from 'react'; import { invoke } from '@tauri-apps/api'; import { MainDispatchContext, MainStateContext } from '../contexts'; -import { AppDataFromBackend, CmdError, ConnectionState } from '../types'; -import { QuickConnectCountry } from '../constants'; +import { + AppDataFromBackend, + CmdError, + ConnectionState, + Country, +} from '../types'; import { initialState, reducer } from './main'; import { useTauriEvents } from './useTauriEvents'; @@ -26,12 +30,53 @@ export function MainStateProvider({ children }: Props) { return await invoke('get_connection_start_time'); }; - getInitialConnectionState().then((state) => - dispatch({ type: 'change-connection-state', state }), - ); - getSessionStartTime().then((startTime) => - dispatch({ type: 'set-connection-start-time', startTime }), - ); + // init country list + const getCountries = async () => { + return await invoke('get_node_countries'); + }; + + // init default node location + const getDefaultNodeLocation = async () => { + return await invoke('get_default_node_location'); + }; + + getInitialConnectionState() + .then((state) => dispatch({ type: 'change-connection-state', state })) + .catch((e: CmdError) => { + console.warn( + `command [get_connection_state] returned an error: ${e.source} - ${e.message}`, + ); + }); + + getSessionStartTime() + .then((startTime) => + dispatch({ type: 'set-connection-start-time', startTime }), + ) + .catch((e: CmdError) => { + console.warn( + `command [get_connection_start_time] returned an error: ${e.source} - ${e.message}`, + ); + }); + + getCountries() + .then((countries) => { + dispatch({ type: 'set-countries', countries }); + }) + .catch((e: CmdError) => { + console.warn( + `command [get_node_countries] returned an error: ${e.source} - ${e.message}`, + ); + }); + + getDefaultNodeLocation() + .then((country) => { + dispatch({ type: 'set-default-node-location', country }); + }) + .catch((e: CmdError) => { + console.warn( + `command [get_default_node_location] returned an error: ${e.source} - ${e.message}`, + ); + }); }, []); // get saved on disk app data and restore state from it @@ -42,20 +87,28 @@ export function MainStateProvider({ children }: Props) { getAppData() .then((data) => { + console.log('app data read from disk:'); console.log(data); + const partialState: Partial = { + entrySelector: data.entry_location_selector || false, + uiTheme: data.ui_theme || 'Light', + vpnMode: data.vpn_mode || 'TwoHop', + }; + if (data.entry_node_location) { + partialState.entryNodeLocation = data.entry_node_location; + } + if (data.exit_node_location) { + partialState.exitNodeLocation = data.exit_node_location; + } dispatch({ type: 'set-partial-state', - partialState: { - uiTheme: data.ui_theme || 'Light', - vpnMode: data.vpn_mode || 'TwoHop', - entryNodeLocation: data.entry_node_location || QuickConnectCountry, - exitNodeLocation: data.exit_node_location || QuickConnectCountry, - }, + partialState, }); }) - .catch((err: CmdError) => { - // TODO handle error properly - console.log(err); + .catch((e: CmdError) => { + console.warn( + `command [get_app_data] returned an error: ${e.source} - ${e.message}`, + ); }); }, []); diff --git a/nym-vpn/ui/src/types/app-data.ts b/nym-vpn/ui/src/types/app-data.ts index 623904fe11..931c0ead59 100644 --- a/nym-vpn/ui/src/types/app-data.ts +++ b/nym-vpn/ui/src/types/app-data.ts @@ -17,6 +17,7 @@ export interface AppDataFromBackend { monitoring: boolean | null; autoconnect: boolean | null; killswitch: boolean | null; + entry_location_selector: boolean | null; ui_theme: UiTheme | null; vpn_mode: VpnMode | null; entry_node: NodeConfig | null; diff --git a/nym-vpn/ui/src/types/app-state.ts b/nym-vpn/ui/src/types/app-state.ts index 38d0017c5c..bc0dcde5e5 100644 --- a/nym-vpn/ui/src/types/app-state.ts +++ b/nym-vpn/ui/src/types/app-state.ts @@ -26,8 +26,11 @@ export type AppState = { vpnMode: VpnMode; tunnel: TunnelConfig; uiTheme: 'Light' | 'Dark'; + entrySelector: boolean; entryNodeLocation: Country | null; exitNodeLocation: Country | null; + defaultNodeLocation: Country; + countries: Country[]; }; export type ConnectionEventPayload = {