feat(vpn-ui): scaffold rust backend (#4073)
* scaffold app * feat: local storage for app data & config
This commit is contained in:
@@ -25,6 +25,25 @@ yarn
|
||||
yarn dev:app
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
cd src-tauri
|
||||
cargo tauri dev
|
||||
```
|
||||
|
||||
#### Logging
|
||||
|
||||
Rust logging (standard output) is controlled by the `RUST_LOG`
|
||||
env variable
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
cd src-tauri
|
||||
RUST_LOG=trace cargo tauri dev
|
||||
```
|
||||
|
||||
## Dev in the browser
|
||||
|
||||
For convenience and better development experience, we can run the
|
||||
@@ -43,6 +62,22 @@ When creating new tauri command, be sure to add the corresponding
|
||||
mock definition into `nym-vpn/ui/src/dev/tauri-cmd-mocks/` and
|
||||
update `nym-vpn/ui/src/dev/setup.ts` accordingly.
|
||||
|
||||
## Type bindings
|
||||
|
||||
[ts-rs](https://github.com/Aleph-Alpha/ts-rs) can be used to generate
|
||||
TS type definitions from Rust types
|
||||
|
||||
To generate bindings, first
|
||||
[annotate](https://github.com/Aleph-Alpha/ts-rs/blob/main/example/src/lib.rs)
|
||||
Rust types, then run
|
||||
|
||||
```
|
||||
cd src-tauri
|
||||
cargo test
|
||||
```
|
||||
|
||||
Generated TS types will be located in `src-tauri/bindings/`
|
||||
|
||||
## Build
|
||||
|
||||
To build as a **shared library**
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:app": "WEBKIT_DISABLE_COMPOSITING_MODE=1 tauri dev",
|
||||
"dev:app": "RUST_LOG=nymvpn_ui=trace tauri dev",
|
||||
"dev:browser": "vite --mode dev-browser",
|
||||
"build": "tsc && vite build",
|
||||
"build:app": "yarn build && cd src-tauri && cargo build --release --lib --features custom-protocol",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/bindings/
|
||||
|
||||
|
||||
Generated
+67
-11
@@ -2,6 +2,12 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.21.0"
|
||||
@@ -294,8 +300,10 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
@@ -570,6 +578,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "1.0.9"
|
||||
@@ -599,7 +613,7 @@ checksum = "f54cc3e827ee1c3812239a9a41dede7b4d7d5d5464faa32d71bd7cba28ce2cb2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"rustc_version",
|
||||
"toml 0.8.2",
|
||||
"toml 0.8.5",
|
||||
"vswhom",
|
||||
"winreg",
|
||||
]
|
||||
@@ -1651,10 +1665,19 @@ dependencies = [
|
||||
name = "nymvpn-ui"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dotenvy",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"toml 0.8.5",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2323,9 +2346,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
||||
checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -2557,7 +2580,7 @@ dependencies = [
|
||||
"cfg-expr 0.15.5",
|
||||
"heck 0.4.1",
|
||||
"pkg-config",
|
||||
"toml 0.8.2",
|
||||
"toml 0.8.5",
|
||||
"version-compare 0.1.1",
|
||||
]
|
||||
|
||||
@@ -2847,6 +2870,15 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-slice"
|
||||
version = "0.1.1"
|
||||
@@ -2962,21 +2994,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.2"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
|
||||
checksum = "3efaf127c78d5339cc547cce4e4d973bd5e4f56e949a06d091c082ebeef2f800"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.20.2",
|
||||
"toml_edit 0.20.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.3"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -2996,9 +3028,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.20.2"
|
||||
version = "0.20.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
|
||||
checksum = "782bf6c2ddf761c1e7855405e8975472acf76f7f36d0d4328bd3b7a2fae12a85"
|
||||
dependencies = [
|
||||
"indexmap 2.0.2",
|
||||
"serde",
|
||||
@@ -3077,6 +3109,30 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1ff1f8c90369bc172200013ac17ae86e7b5def580687df4e6127883454ff2b0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"thiserror",
|
||||
"ts-rs-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6f41cc0aeb7a4a55730188e147d3795a7349b501f8334697fd37629b896cdc2"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
|
||||
@@ -14,9 +14,18 @@ crate-type = ["cdylib"]
|
||||
tauri-build = { version = "1.5", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "1.5", features = ["shell-open"] }
|
||||
tauri = { version = "1.5.2", features = ["shell-open"] }
|
||||
tokio = { version = "1.33", features = ["rt", "sync", "time", "fs"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3.1", features = ["tracing-log", "env-filter"] }
|
||||
anyhow = "1.0"
|
||||
dotenvy = "0.15.7"
|
||||
thiserror = "1.0"
|
||||
ts-rs = { version = "7.0.0", features = ["chrono-impl"] }
|
||||
once_cell = "1.18.0"
|
||||
toml = "0.8.5"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use tauri::State;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, instrument, trace};
|
||||
|
||||
use crate::{
|
||||
error::CommandError,
|
||||
states::{app::ConnectionState, SharedAppState},
|
||||
};
|
||||
|
||||
#[instrument]
|
||||
#[tauri::command]
|
||||
pub async fn get_connection_state(
|
||||
state: State<'_, SharedAppState>,
|
||||
) -> Result<ConnectionState, CommandError> {
|
||||
debug!("get_connection_state");
|
||||
let app_state = state.lock().await;
|
||||
Ok(app_state.state)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn connect(state: State<'_, SharedAppState>) -> Result<ConnectionState, CommandError> {
|
||||
debug!("connect");
|
||||
let mut app_state = state.lock().await;
|
||||
let ConnectionState::Disconnected = app_state.state else {
|
||||
return Err(CommandError::CallerError(format!(
|
||||
"cannot connect from state {:?}",
|
||||
app_state.state
|
||||
)));
|
||||
};
|
||||
|
||||
// TODO fake some delay to establish connection
|
||||
let app_state_cloned = state.inner().clone();
|
||||
let task = tokio::spawn(async move {
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
trace!("connected");
|
||||
app_state_cloned.lock().await.state = ConnectionState::Connected;
|
||||
});
|
||||
|
||||
let _ = task.await;
|
||||
|
||||
app_state.state = ConnectionState::Connecting;
|
||||
Ok(app_state.state)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
#[tauri::command]
|
||||
pub async fn disconnect(state: State<'_, SharedAppState>) -> Result<ConnectionState, CommandError> {
|
||||
debug!("disconnect");
|
||||
let mut app_state = state.lock().await;
|
||||
let ConnectionState::Connected = app_state.state else {
|
||||
return Err(CommandError::CallerError(format!(
|
||||
"cannot disconnect from state {:?}",
|
||||
app_state.state
|
||||
)));
|
||||
};
|
||||
|
||||
app_state.state = ConnectionState::Disconnecting;
|
||||
Ok(app_state.state)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
pub mod connection;
|
||||
pub mod settings;
|
||||
|
||||
#[instrument]
|
||||
#[tauri::command]
|
||||
pub fn greet(name: &str) -> String {
|
||||
debug!("greet");
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use tauri::State;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use crate::{error::CommandError, fs::data::AppData, states::SharedAppData};
|
||||
|
||||
#[instrument]
|
||||
#[tauri::command]
|
||||
pub async fn save_user_settings(state: State<'_, SharedAppData>) -> Result<(), CommandError> {
|
||||
debug!("save_user_settings");
|
||||
let app_data = state.lock().await;
|
||||
app_data
|
||||
.write()
|
||||
.map_err(|e| CommandError::InternalError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
#[tauri::command]
|
||||
pub async fn set_user_settings(
|
||||
state: State<'_, SharedAppData>,
|
||||
settings: AppData,
|
||||
) -> Result<(), CommandError> {
|
||||
debug!("set_user_settings");
|
||||
let mut app_data = state.lock().await;
|
||||
app_data.data = settings;
|
||||
app_data
|
||||
.write()
|
||||
.map_err(|e| CommandError::InternalError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, Serialize, Deserialize)]
|
||||
pub enum CommandError {
|
||||
#[error("internal error: `{0}`")]
|
||||
InternalError(String),
|
||||
#[error("caller error: `{0}`")]
|
||||
CallerError(String),
|
||||
#[error("unknown error")]
|
||||
Unknown,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AppCache {
|
||||
// TODO
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AppConfig {
|
||||
// TODO
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::states::app::{NodeConfig, PrivacyMode};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AppData {
|
||||
pub monitoring: Option<bool>,
|
||||
pub autoconnect: Option<bool>,
|
||||
pub killswitch: Option<bool>,
|
||||
pub privacy_mode: Option<PrivacyMode>,
|
||||
pub entry_node: Option<NodeConfig>,
|
||||
pub exit_node: Option<NodeConfig>,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AppLog {
|
||||
// TODO
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod data;
|
||||
pub mod log;
|
||||
pub mod storage;
|
||||
@@ -0,0 +1,84 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::{fmt, fs, path::PathBuf, str};
|
||||
use tauri::api::path::data_dir;
|
||||
use tracing::{debug, error, instrument};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppStorage<T>
|
||||
where
|
||||
T: Serialize + DeserializeOwned + Default + fmt::Debug,
|
||||
{
|
||||
pub data: T,
|
||||
pub dir_path: PathBuf,
|
||||
pub filename: String,
|
||||
pub full_path: PathBuf,
|
||||
}
|
||||
|
||||
fn create_directory_path(path: &PathBuf) -> Result<()> {
|
||||
let mut data_dir = data_dir().ok_or(anyhow!(
|
||||
"Failed to retrieve data directory {:?}",
|
||||
path.display()
|
||||
))?;
|
||||
data_dir.push(path);
|
||||
|
||||
fs::create_dir_all(&data_dir).context(format!(
|
||||
"Failed to create data directory {}",
|
||||
data_dir.display()
|
||||
))
|
||||
}
|
||||
|
||||
impl<T> AppStorage<T>
|
||||
where
|
||||
T: Serialize + DeserializeOwned + Default + fmt::Debug,
|
||||
{
|
||||
pub fn new(dir_path: PathBuf, filename: &str, data: Option<T>) -> Self {
|
||||
let mut full_path = dir_path.clone();
|
||||
full_path.push(filename);
|
||||
|
||||
Self {
|
||||
data: data.unwrap_or_default(),
|
||||
dir_path,
|
||||
filename: filename.to_owned(),
|
||||
full_path,
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub fn read(&self) -> Result<T> {
|
||||
// create the full directory path if it is missing
|
||||
create_directory_path(&self.dir_path)?;
|
||||
|
||||
debug!("reading stored data from {}", self.full_path.display());
|
||||
let content = fs::read(&self.full_path).context(format!(
|
||||
"Failed to read data from {}",
|
||||
self.full_path.display()
|
||||
))?;
|
||||
|
||||
toml::from_str::<T>(str::from_utf8(&content)?).map_err(|e| {
|
||||
error!("{e}");
|
||||
anyhow!("{e}")
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub fn write(&self) -> Result<()> {
|
||||
// create the full directory path if it is missing
|
||||
create_directory_path(&self.dir_path)?;
|
||||
|
||||
debug!("writing data to {}", self.full_path.display());
|
||||
let toml = toml::to_string(&self.data)?;
|
||||
fs::write(&self.full_path, toml)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub fn clear(&self) -> Result<()> {
|
||||
// create the full directory path if it is missing
|
||||
create_directory_path(&self.dir_path)?;
|
||||
|
||||
debug!("clearing data {}", self.full_path.display());
|
||||
fs::write(&self.full_path, vec![])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,64 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Result;
|
||||
use tauri::api::path::{config_dir, data_dir};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::info;
|
||||
|
||||
mod commands;
|
||||
mod error;
|
||||
mod fs;
|
||||
mod states;
|
||||
|
||||
use commands::*;
|
||||
use states::app::AppState;
|
||||
|
||||
use crate::fs::config::AppConfig;
|
||||
use crate::fs::data::AppData;
|
||||
use crate::fs::storage::AppStorage;
|
||||
|
||||
const APP_DIR: &str = "nymvpn";
|
||||
const APP_DATA_FILE: &str = "app-data.toml";
|
||||
const APP_CONFIG_FILE: &str = "config.toml";
|
||||
|
||||
fn main() -> Result<()> {
|
||||
dotenvy::dotenv()?;
|
||||
|
||||
// uses RUST_LOG value for logging level
|
||||
// eg. RUST_LOG=tauri=debug,nymvpn_ui=trace
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let mut app_data_path = data_dir().ok_or(anyhow!("Failed to retrieve data directory"))?;
|
||||
app_data_path.push(APP_DIR);
|
||||
let app_data_store = AppStorage::<AppData>::new(app_data_path, APP_DATA_FILE, None);
|
||||
|
||||
let mut app_config_path = config_dir().ok_or(anyhow!("Failed to retrieve config directory"))?;
|
||||
app_config_path.push(APP_DIR);
|
||||
let app_config_store = AppStorage::<AppConfig>::new(app_config_path, APP_CONFIG_FILE, None);
|
||||
|
||||
info!("Starting tauri app");
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.manage(Arc::new(Mutex::new(AppState::default())))
|
||||
.manage(Arc::new(Mutex::new(app_data_store)))
|
||||
.manage(Arc::new(Mutex::new(app_config_store)))
|
||||
.setup(|_app| {
|
||||
info!("app setup");
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
greet,
|
||||
connection::get_connection_state,
|
||||
connection::connect,
|
||||
connection::disconnect,
|
||||
settings::save_user_settings,
|
||||
settings::set_user_settings,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct NodeConfig {
|
||||
pub id: String,
|
||||
pub country: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub enum ConnectionState {
|
||||
Connected,
|
||||
#[default]
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Disconnecting,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, TS, Clone)]
|
||||
#[ts(export)]
|
||||
pub enum PrivacyMode {
|
||||
High,
|
||||
Medium,
|
||||
#[default]
|
||||
Low,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct TunnelConfig {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AppState {
|
||||
pub state: ConnectionState,
|
||||
pub privacy_mode: PrivacyMode,
|
||||
pub entry_node: Option<NodeConfig>,
|
||||
pub exit_node: Option<NodeConfig>,
|
||||
pub tunnel: Option<TunnelConfig>,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::fs::{config::AppConfig, data::AppData, storage::AppStorage};
|
||||
|
||||
pub mod app;
|
||||
|
||||
pub type SharedAppState = Arc<Mutex<app::AppState>>;
|
||||
pub type SharedAppData = Arc<Mutex<AppStorage<AppData>>>;
|
||||
pub type SharedAppConfig = Arc<Mutex<AppStorage<AppConfig>>>;
|
||||
@@ -29,7 +29,7 @@
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "com.tauri.dev",
|
||||
"identifier": "net.nymtech.vpn",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
@@ -46,8 +46,8 @@
|
||||
"fullscreen": false,
|
||||
"resizable": true,
|
||||
"title": "NymVPN",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
"width": 200,
|
||||
"height": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user