diff --git a/skills/nordvpn-client/scripts/nordvpn-client.js b/skills/nordvpn-client/scripts/nordvpn-client.js index 376eda3..f515cd2 100644 --- a/skills/nordvpn-client/scripts/nordvpn-client.js +++ b/skills/nordvpn-client/scripts/nordvpn-client.js @@ -349,6 +349,22 @@ function buildLookupResult(address, options = {}) { return [address, 4]; } +function cleanupMacWireguardState(paths = {}) { + const targets = [paths.configPath || WG_CONFIG_PATH, paths.lastConnectionPath || LAST_CONNECTION_PATH]; + let cleaned = false; + for (const target of targets) { + try { + if (target && fs.existsSync(target)) { + fs.unlinkSync(target); + cleaned = true; + } + } catch { + // Ignore cleanup errors; caller will rely on current runtime state. + } + } + return { cleaned }; +} + function fetchJson(url, headers = {}) { return new Promise((resolve) => { const targetUrl = new URL(url); @@ -1092,10 +1108,12 @@ async function disconnectNordvpn(installProbe) { if (installProbe.platform === "darwin" && installProbe.wireguard && installProbe.wireguard.dependenciesReady) { if (!installProbe.wireguard.active) { + const cleaned = cleanupMacWireguardState(); const tailscale = await resumeMacTailscaleIfNeeded(); return { backend: "wireguard", changed: false, + stateCleaned: cleaned.cleaned, tailscaleRestored: tailscale.restored, message: "No active macOS WireGuard NordVPN connection found.", }; @@ -1104,10 +1122,12 @@ async function disconnectNordvpn(installProbe) { if (!down.ok) { throw new Error((down.stderr || down.stdout || down.error).trim() || "wg-quick down failed"); } + const cleaned = cleanupMacWireguardState(); const tailscale = await resumeMacTailscaleIfNeeded(); return { backend: "wireguard", changed: true, + stateCleaned: cleaned.cleaned, tailscaleRestored: tailscale.restored, message: "Disconnected the macOS NordLynx/WireGuard session.", }; diff --git a/skills/nordvpn-client/scripts/nordvpn-client.test.js b/skills/nordvpn-client/scripts/nordvpn-client.test.js index a16b37d..b5a09e4 100644 --- a/skills/nordvpn-client/scripts/nordvpn-client.test.js +++ b/skills/nordvpn-client/scripts/nordvpn-client.test.js @@ -15,6 +15,8 @@ module.exports = { typeof buildWireguardConfig === "function" ? buildWireguardConfig : undefined, buildLookupResult: typeof buildLookupResult === "function" ? buildLookupResult : undefined, + cleanupMacWireguardState: + typeof cleanupMacWireguardState === "function" ? cleanupMacWireguardState : undefined, getMacTailscalePath: typeof getMacTailscalePath === "function" ? getMacTailscalePath : undefined, isMacTailscaleActive: @@ -108,6 +110,26 @@ test("buildMacTailscaleState records whether tailscale was active", () => { ); }); +test("cleanupMacWireguardState removes stale config and last-connection files", () => { + const { cleanupMacWireguardState } = loadInternals(); + assert.equal(typeof cleanupMacWireguardState, "function"); + + const tmpDir = fs.mkdtempSync(path.join(fs.mkdtempSync("/tmp/nordvpn-client-test-"), "state-")); + const configPath = path.join(tmpDir, "nordvpnctl.conf"); + const lastConnectionPath = path.join(tmpDir, "last-connection.json"); + fs.writeFileSync(configPath, "wireguard-config"); + fs.writeFileSync(lastConnectionPath, "{\"country\":\"Germany\"}"); + + const result = cleanupMacWireguardState({ + configPath, + lastConnectionPath, + }); + + assert.equal(result.cleaned, true); + assert.equal(fs.existsSync(configPath), false); + assert.equal(fs.existsSync(lastConnectionPath), false); +}); + test("isMacTailscaleActive treats Running backend as active", () => { const { isMacTailscaleActive } = loadInternals(); assert.equal(typeof isMacTailscaleActive, "function");