From aef6bf998bec5cf0f80cf2238c0ac05efad14379 Mon Sep 17 00:00:00 2001 From: nessi Date: Wed, 18 Mar 2026 07:15:54 +0100 Subject: [PATCH] refactor: update bundled binary path and add tunnel status polling with retry logic Change NSIS installer hooks to use bundled/ instead of resources/bundled/ path for tunnel helper executable. Add waitForTunnelStatus helper that polls tunnel status up to 8 times with 500ms intervals to verify expected state after connect/disconnect operations. Update toggle handler to use polling instead of single status check and add error message for failed disconnect operations. --- desktop-client/src-tauri/windows/hooks.nsh | 4 ++-- desktop-client/src/App.tsx | 23 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/desktop-client/src-tauri/windows/hooks.nsh b/desktop-client/src-tauri/windows/hooks.nsh index ff54271..187510d 100644 --- a/desktop-client/src-tauri/windows/hooks.nsh +++ b/desktop-client/src-tauri/windows/hooks.nsh @@ -1,7 +1,7 @@ !macro NSIS_HOOK_POSTINSTALL - nsExec::ExecToLog '"$INSTDIR\resources\bundled\windows-x64\nexavpn-tunnel-helper.exe" install-service' + nsExec::ExecToLog '"$INSTDIR\bundled\windows-x64\nexavpn-tunnel-helper.exe" install-service' !macroend !macro NSIS_HOOK_PREUNINSTALL - nsExec::ExecToLog '"$INSTDIR\resources\bundled\windows-x64\nexavpn-tunnel-helper.exe" uninstall-service' + nsExec::ExecToLog '"$INSTDIR\bundled\windows-x64\nexavpn-tunnel-helper.exe" uninstall-service' !macroend diff --git a/desktop-client/src/App.tsx b/desktop-client/src/App.tsx index d7b0ad8..33c3922 100644 --- a/desktop-client/src/App.tsx +++ b/desktop-client/src/App.tsx @@ -67,6 +67,25 @@ export function App() { const profileLabel = useMemo(() => currentProfileLabel(state), [state]); + async function waitForTunnelStatus(expected: boolean) { + for (let attempt = 0; attempt < 8; attempt += 1) { + try { + const active = await invoke("tunnel_status"); + if (active === expected) { + return active; + } + } catch { + if (!expected) { + return false; + } + } + + await new Promise((resolve) => window.setTimeout(resolve, 500)); + } + + return invoke("tunnel_status").catch(() => false); + } + async function onSubmit(event: FormEvent) { event.preventDefault(); setLoading(true); @@ -102,10 +121,12 @@ export function App() { const command = connected ? "disconnect_tunnel" : "connect_tunnel"; try { await invoke(command); - const active = await invoke("tunnel_status"); + const active = await waitForTunnelStatus(!connected); setConnected(active); if (!connected && !active) { setError("Tunnel was installed, but no active interface could be verified yet."); + } else if (connected && active) { + setError("Tunnel could not be stopped cleanly yet."); } else { setError(null); }