feat: add single instance enforcement with TCP socket lock

Add single instance check using TCP listener on 127.0.0.1:53190 to prevent multiple application instances. Move AppState initialization into setup closure to include single_instance_lock field. Remove window close prevention and focus restoration handlers. Make main window non-resizable and non-maximizable.
This commit is contained in:
2026-03-18 07:06:20 +01:00
parent 31369a7743
commit d72a32cce1
2 changed files with 14 additions and 18 deletions

View File

@@ -1,6 +1,6 @@
mod tunnel_manager;
use std::{fs, path::PathBuf, sync::Mutex};
use std::{fs, net::TcpListener, path::PathBuf, sync::Mutex};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use rand_core::OsRng;
@@ -15,9 +15,11 @@ use x25519_dalek::{PublicKey, StaticSecret};
const PROFILE_NAME: &str = "NexaVPN";
const MAIN_WINDOW_LABEL: &str = "main";
const SINGLE_INSTANCE_ADDR: &str = "127.0.0.1:53190";
struct AppState {
session: Mutex<Option<SessionState>>,
single_instance_lock: TcpListener,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -362,12 +364,6 @@ fn ensure_app_dir(app: &AppHandle) -> Result<PathBuf, String> {
Ok(dir)
}
fn restore_window(window: &Window) {
let _ = window.show();
let _ = window.unminimize();
let _ = window.set_focus();
}
fn restore_webview_window(window: &WebviewWindow) {
let _ = window.show();
let _ = window.unminimize();
@@ -382,6 +378,9 @@ fn hide_main_window(window: &Window) {
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let single_instance_lock = TcpListener::bind(SINGLE_INSTANCE_ADDR)
.map_err(|_| format!("{} is already running.", app.package_info().name))?;
let open_item = MenuItemBuilder::with_id("open", "Open NexaVPN").build(app)?;
let quit_item = MenuItemBuilder::with_id("quit", "Quit NexaVPN").build(app)?;
let menu = MenuBuilder::new(app).items(&[&open_item, &quit_item]).build()?;
@@ -418,24 +417,20 @@ pub fn run() {
})
.build(app)?;
app.manage(AppState {
session: Mutex::new(None),
single_instance_lock,
});
Ok(())
})
.manage(AppState {
session: Mutex::new(None),
})
.on_window_event(|window, event| match event {
WindowEvent::CloseRequested { api, .. } => {
api.prevent_close();
hide_main_window(window);
}
WindowEvent::CloseRequested { .. } => {}
WindowEvent::Resized(_) => {
if window.is_minimized().unwrap_or(false) {
hide_main_window(window);
}
}
WindowEvent::Focused(true) => {
restore_window(window);
}
_ => {}
})
.invoke_handler(tauri::generate_handler![load_state, clear_session, enroll_device, sync_profile, connect_tunnel, disconnect_tunnel, tunnel_status])

View File

@@ -15,7 +15,8 @@
"title": "NexaVPN",
"width": 1120,
"height": 760,
"resizable": true
"resizable": false,
"maximizable": false
}
],
"security": {