Compare commits
No commits in common. "master" and "new-window-v2" have entirely different histories.
master
...
new-window
|
|
@ -30,4 +30,3 @@ nextSteps.txt
|
|||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
resources
|
||||
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
|
|
@ -13,7 +13,7 @@
|
|||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"transpile:electron": "tsc --project src/electron/tsconfig.json",
|
||||
"dist:mac": "npm run transpile:electron && npm run build && electron-builder --mac --universal",
|
||||
"dist:mac": "npm run transpile:electron && npm run build && electron-builder --mac --x64 --arm64",
|
||||
"dist:win": "npm run transpile:electron && npm run build && electron-builder --win --x64",
|
||||
"dist:linux": "npm run transpile:electron && npm run build && electron-builder --linux --x64",
|
||||
"test:e2e": "playwright test",
|
||||
|
|
@ -82,10 +82,7 @@
|
|||
],
|
||||
"mac": {
|
||||
"target": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": "universal"
|
||||
}
|
||||
"dmg"
|
||||
],
|
||||
"icon": "build/icon.icns",
|
||||
"extendInfo": {
|
||||
|
|
@ -129,13 +126,13 @@
|
|||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"daisyui": "^5.4.3",
|
||||
"electron": "^39.2.5",
|
||||
"electron": "^32.1.2",
|
||||
"electron-builder": "^25.0.5",
|
||||
"electron-rebuild": "^3.2.9",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rcedit": "^5.0.2",
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^7.2.6",
|
||||
"vitest": "^4.0.15"
|
||||
"vite": "^5.4.7",
|
||||
"vitest": "^2.1.1"
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,6 @@
|
|||
[Interface]
|
||||
PrivateKey = gPqf7ScXv2xtLkZz5a8qa7RNJ7uU5TQUo/rTfcprMWE=
|
||||
Address = 10.0.0.172/32
|
||||
PrivateKey = wHjS7rJYnAGhlvj+XYLefAzki1uRM2xz95IxRML2q24=
|
||||
Address = 10.0.0.169/32
|
||||
DNS = 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// electron/api/axiosClient.ts
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
import { getToken } from '../utils/tokenHelper.js';
|
||||
import chalk from "chalk";
|
||||
import { sendTokenExpired } from '../ipc/ipcHandler.js';
|
||||
|
|
@ -8,40 +8,61 @@ import { getBaseURL } from './baseURL.js';
|
|||
|
||||
const TOKEN_KEY = 'ACCESS_TOKEN';
|
||||
|
||||
const Base_URL = await getBaseURL()
|
||||
// const Base_URL = await getBaseURL()
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
|
||||
console.log('Base_URL$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' ,Base_URL)
|
||||
// console.log('Base_URL$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' ,Base_URL)
|
||||
|
||||
const api: AxiosInstance = axios.create({
|
||||
// baseURL: 'https://portal-sf.docva.com/api/v1/va', // 🔁 your backend
|
||||
baseURL: `${Base_URL}/api/v1/va`,
|
||||
// baseURL: 'http://dreadingly-unfighting-pandora.ngrok-free.dev/api/v1/va', // 🔁 your backend
|
||||
// baseURL: `${Base_URL}/api/v1/va`,
|
||||
httpsAgent
|
||||
// timeout: 10000,
|
||||
});
|
||||
|
||||
|
||||
export function syncAxiosBaseURL() {
|
||||
const baseURL = getBaseURL();
|
||||
api.defaults.baseURL = `${baseURL}/api/v1/va`;
|
||||
|
||||
console.log('✅ Axios baseURL synced →', api.defaults.baseURL);
|
||||
}
|
||||
|
||||
// console.log("Creating Axios Instance", axios.VERSION);
|
||||
// console.log("Axios .get reference:", api.get);
|
||||
// console.log("Axios .request reference:", api.request);
|
||||
|
||||
// --- Request Interceptor ---
|
||||
// api.interceptors.request.use(
|
||||
// async (config: AxiosRequestConfig): Promise<any> => {
|
||||
// const token = await getToken(TOKEN_KEY);
|
||||
// config.headers = config.headers || {}
|
||||
|
||||
// if (token) {
|
||||
// config.headers['token'] = token;
|
||||
// config.headers['ngrok-skip-browser-warning'] = 'true';
|
||||
// }
|
||||
|
||||
// return config;
|
||||
// },
|
||||
// (error) => Promise.reject(error)
|
||||
// );
|
||||
|
||||
api.interceptors.request.use(
|
||||
async (config: AxiosRequestConfig): Promise<any> => {
|
||||
const token = await getToken(TOKEN_KEY);
|
||||
config.headers = config.headers || {}
|
||||
async (config: InternalAxiosRequestConfig) => {
|
||||
const token = await getToken(TOKEN_KEY);
|
||||
|
||||
if (token) {
|
||||
config.headers['token'] = token;
|
||||
config.headers['ngrok-skip-browser-warning'] = 'true';
|
||||
}
|
||||
if (token) {
|
||||
config.headers.token = token;
|
||||
config.headers['ngrok-skip-browser-warning'] = 'true';
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
return config;
|
||||
}
|
||||
);
|
||||
|
||||
// --- Response Interceptor ---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// let CURRENT_BASE_URL = "https://portal-sf.docva.com/api/v1/va";
|
||||
let CURRENT_BASE_URL = "https://portal-sf.docva.com";
|
||||
import { syncAxiosBaseURL } from "./axiosClient.js";
|
||||
|
||||
// let CURRENT_BASE_URL = "http://dreadingly-unfighting-pandora.ngrok-free.dev/api/v1/va";
|
||||
let CURRENT_BASE_URL = "http://dreadingly-unfighting-pandora.ngrok-free.dev";
|
||||
|
||||
export const getBaseURL = () => CURRENT_BASE_URL;
|
||||
export const setBaseURL = (url: string) => {
|
||||
CURRENT_BASE_URL = url;
|
||||
syncAxiosBaseURL();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ export const registerIPCHandlers = (mainWindow: BrowserWindow) => {
|
|||
// UPDATE Base URL
|
||||
ipcMain.handle("UPDATE_BASE_URL", (event, url: string) => {
|
||||
setBaseURL(url);
|
||||
api.defaults.baseURL = `${url}/api/v1/va`;
|
||||
console.log("Base URL updated to:", api.defaults.baseURL);
|
||||
return {
|
||||
updated: true,
|
||||
baseURL: url
|
||||
|
|
|
|||
|
|
@ -178,12 +178,6 @@ app.on("ready", async () => {
|
|||
// HANDLE CLOSE EVENTS
|
||||
// ------------------------------------
|
||||
// handleCloseEvents(win);
|
||||
mainWindow.on("close", () => {
|
||||
if(mainWindow !== null) {
|
||||
mainWindow.webContents.executeJavaScript(`window.__allowClose__ = true`);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -204,7 +198,6 @@ app.on('browser-window-focus', () => {
|
|||
// });
|
||||
|
||||
|
||||
|
||||
// ---------------------------
|
||||
// CUSTOM CLOSE BEHAVIOR
|
||||
// ---------------------------
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function ipcMainHandle<Key extends keyof EventPayloadMapping>(
|
|||
handler: () => EventPayloadMapping[Key]
|
||||
) {
|
||||
ipcMain.handle(key, (event) => {
|
||||
validateEventFrame(event.senderFrame!);
|
||||
validateEventFrame(event.senderFrame);
|
||||
return handler();
|
||||
});
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ export function ipcMainOn<Key extends keyof EventPayloadMapping>(
|
|||
handler: (payload: EventPayloadMapping[Key]) => void
|
||||
) {
|
||||
ipcMain.on(key, (event, payload) => {
|
||||
validateEventFrame(event.senderFrame!);
|
||||
validateEventFrame(event.senderFrame);
|
||||
return handler(payload);
|
||||
});
|
||||
}
|
||||
|
|
@ -34,16 +34,11 @@ export function ipcWebContentsSend<Key extends keyof EventPayloadMapping>(
|
|||
webContents.send(key, payload);
|
||||
}
|
||||
|
||||
export function validateEventFrame(frame: WebFrameMain | null): asserts frame is WebFrameMain {
|
||||
if (!frame) throw new Error('Missing frame');
|
||||
|
||||
const url = frame.url;
|
||||
|
||||
if (isDev() && new URL(url).host === 'localhost:5123') {
|
||||
export function validateEventFrame(frame: WebFrameMain) {
|
||||
if (isDev() && new URL(frame.url).host === 'localhost:5123') {
|
||||
return;
|
||||
}
|
||||
if (url !== pathToFileURL(getUIPath()).toString()) {
|
||||
if (frame.url !== pathToFileURL(getUIPath()).toString()) {
|
||||
throw new Error('Malicious event');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,62 +68,12 @@ function getWireGuardInstallerPath(): string {
|
|||
// throw new Error("Unsupported OS");
|
||||
// }
|
||||
|
||||
// export async function getConfigPath(): Promise<string> {
|
||||
// if (process.platform !== "win32") {
|
||||
// throw new Error("Only Windows supported for dynamic WireGuard config");
|
||||
// }
|
||||
// console.log("Calling http://35.95.209.161/api/v1/vpn/connect")
|
||||
// // 1️⃣ Call API to get dynamic config
|
||||
// let response = null;
|
||||
// try {
|
||||
// response = await axios.post(
|
||||
// "http://35.95.209.161/api/v1/vpn/connect",
|
||||
// {},
|
||||
// { timeout: 15000 }
|
||||
// );
|
||||
// } catch (err: any) {
|
||||
// console.error("VPN API error:", {
|
||||
// message: err.message,
|
||||
// code: err.code,
|
||||
// response: err.response?.data,
|
||||
// });
|
||||
// throw err;
|
||||
// }
|
||||
// console.log("Called http://35.95.209.161/api/v1/vpn/connect", JSON.stringify(response.data));
|
||||
|
||||
// if (!response.data?.success || !response.data.clientConf) {
|
||||
// throw new Error("Invalid VPN config response");
|
||||
// }
|
||||
|
||||
// const clientConf: string = response.data.clientConf;
|
||||
|
||||
// // 2️⃣ Decide base directory (same idea as your existing logic)
|
||||
// const base = isDev()
|
||||
// ? path.join(process.cwd(), "resources", "wireguard", "windows")
|
||||
// : path.join(process.resourcesPath, "wireguard", "windows");
|
||||
|
||||
// // Ensure directory exists
|
||||
// fs.mkdirSync(base, { recursive: true });
|
||||
|
||||
// // 3️⃣ Create temp / runtime conf file
|
||||
// // IMPORTANT: name must stay stable (WireGuard service name depends on it)
|
||||
// const confPath = path.join(base, "wg0.conf");
|
||||
|
||||
// // 4️⃣ Write config to file
|
||||
// fs.writeFileSync(confPath, clientConf, {
|
||||
// encoding: "utf-8",
|
||||
// mode: 0o600, // secure permissions
|
||||
// });
|
||||
|
||||
// console.log("VPN config written to:", confPath);
|
||||
// // 5️⃣ Return path (same contract as before)
|
||||
// return confPath;
|
||||
// }
|
||||
|
||||
export async function getConfigPath(): Promise<string> {
|
||||
console.log("Calling API: http://35.95.209.161/api/v1/vpn/connect");
|
||||
|
||||
// Fetch dynamic config from server (works for both Windows & macOS)
|
||||
if (process.platform !== "win32") {
|
||||
throw new Error("Only Windows supported for dynamic WireGuard config");
|
||||
}
|
||||
console.log("Calling http://35.95.209.161/api/v1/vpn/connect")
|
||||
// 1️⃣ Call API to get dynamic config
|
||||
let response = null;
|
||||
try {
|
||||
response = await axios.post(
|
||||
|
|
@ -139,6 +89,7 @@ export async function getConfigPath(): Promise<string> {
|
|||
});
|
||||
throw err;
|
||||
}
|
||||
console.log("Called http://35.95.209.161/api/v1/vpn/connect", JSON.stringify(response.data));
|
||||
|
||||
if (!response.data?.success || !response.data.clientConf) {
|
||||
throw new Error("Invalid VPN config response");
|
||||
|
|
@ -146,48 +97,29 @@ export async function getConfigPath(): Promise<string> {
|
|||
|
||||
const clientConf: string = response.data.clientConf;
|
||||
|
||||
// ---------- PLATFORM SPECIFIC OUTPUT DIRECTORY ----------
|
||||
let base = "";
|
||||
|
||||
if (process.platform === "win32") {
|
||||
base = isDev()
|
||||
? path.join(process.cwd(), "resources", "wireguard", "windows")
|
||||
: path.join(process.resourcesPath, "wireguard", "windows");
|
||||
}
|
||||
|
||||
// if (process.platform === "darwin") {
|
||||
// base = isDev()
|
||||
// ? path.join(process.cwd(), "resources", "wireguard", "macos")
|
||||
// : path.join(process.resourcesPath, "wireguard", "macos");
|
||||
// }
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
const base = "/usr/local/etc/wireguard";
|
||||
fs.mkdirSync(base, { recursive: true });
|
||||
|
||||
const confPath = path.join(base, "wg0.conf");
|
||||
fs.writeFileSync(confPath, clientConf, { encoding: "utf8", mode: 0o600 });
|
||||
|
||||
return confPath;
|
||||
}
|
||||
|
||||
|
||||
if (!base) throw new Error("Unsupported OS");
|
||||
// 2️⃣ Decide base directory (same idea as your existing logic)
|
||||
const base = isDev()
|
||||
? path.join(process.cwd(), "resources", "wireguard", "windows")
|
||||
: path.join(process.resourcesPath, "wireguard", "windows");
|
||||
|
||||
// Ensure directory exists
|
||||
fs.mkdirSync(base, { recursive: true });
|
||||
|
||||
// 3️⃣ Create temp / runtime conf file
|
||||
// IMPORTANT: name must stay stable (WireGuard service name depends on it)
|
||||
const confPath = path.join(base, "wg0.conf");
|
||||
|
||||
// 4️⃣ Write config to file
|
||||
fs.writeFileSync(confPath, clientConf, {
|
||||
encoding: "utf-8",
|
||||
mode: 0o600,
|
||||
mode: 0o600, // secure permissions
|
||||
});
|
||||
|
||||
console.log("Dynamic VPN config saved:", confPath);
|
||||
console.log("VPN config written to:", confPath);
|
||||
// 5️⃣ Return path (same contract as before)
|
||||
return confPath;
|
||||
}
|
||||
|
||||
|
||||
// Detect installed WireGuard.exe on Windows
|
||||
function findWireGuardCLI(): string | null {
|
||||
const possiblePaths = [
|
||||
|
|
@ -264,80 +196,6 @@ async function ensureWireGuardInstalled(): Promise<string> {
|
|||
// });
|
||||
// }
|
||||
|
||||
//
|
||||
// ------------------ HEALTH + STATUS HELPERS ------------------
|
||||
//
|
||||
|
||||
// 1️⃣ Check if wg0 interface exists (works for mac + windows)
|
||||
async function isTunnelActive(): Promise<boolean> {
|
||||
console.log("🔍 Checking if VPN tunnel is active...");
|
||||
|
||||
try {
|
||||
// ---------------------- macOS ----------------------
|
||||
if (process.platform === "darwin") {
|
||||
console.log("🖥 Platform: macOS — detecting WireGuard via utun interface...");
|
||||
|
||||
const netOutput = await execPromise(`ifconfig`);
|
||||
console.log("📡 ifconfig output length:", netOutput.length);
|
||||
|
||||
const utunMatch = netOutput.match(/utun\d+/);
|
||||
console.log("🔎 utun interface detected:", utunMatch);
|
||||
|
||||
if (utunMatch) {
|
||||
console.log("✅ VPN tunnel ACTIVE (utun interface exists)");
|
||||
return true;
|
||||
} else {
|
||||
console.log("❌ VPN tunnel NOT active (no utun interface found)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- Windows ----------------------
|
||||
if (process.platform === "win32") {
|
||||
console.log("🖥 Platform: Windows — detecting WireGuard via wg show...");
|
||||
|
||||
const wgOutput = await execPromise(`wg show 2>&1`);
|
||||
console.log("📡 wg show output length:", wgOutput.length);
|
||||
|
||||
const interfaceFound = wgOutput.includes("interface: wg0");
|
||||
console.log("🔎 wg0 interface detected:", interfaceFound);
|
||||
|
||||
if (interfaceFound) {
|
||||
console.log("✅ VPN tunnel ACTIVE (wg0 found)");
|
||||
return true;
|
||||
} else {
|
||||
console.log("❌ VPN tunnel NOT active (wg0 not found)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- Unsupported OS ----------------------
|
||||
console.log("⚠️ Unsupported OS platform — cannot detect VPN state");
|
||||
return false;
|
||||
|
||||
} catch (err: any) {
|
||||
console.log("❌ isTunnelActive() failed:", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 2️⃣ Check endpoint health
|
||||
async function isTunnelHealthy(): Promise<boolean> {
|
||||
try {
|
||||
const response = await axios.get("http://10.0.0.1/health", { timeout: 2000 });
|
||||
return !!response.data?.success;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 3️⃣ Download config only if needed
|
||||
async function ensureConfig(platform: string): Promise<string> {
|
||||
return await getConfigPath(); // uses dynamic logic already
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function connectWindows(): Promise<string> {
|
||||
|
||||
|
|
@ -390,234 +248,37 @@ async function disconnectWindows(): Promise<string> {
|
|||
}
|
||||
|
||||
|
||||
async function ensureHomebrew(): Promise<void> {
|
||||
try {
|
||||
await execPromise("brew --version");
|
||||
return; // brew already installed
|
||||
} catch (_) {
|
||||
console.log("Homebrew missing. Installing...");
|
||||
|
||||
await execPromise(
|
||||
'/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
|
||||
);
|
||||
|
||||
// Update shell env — macOS uses zprofile for system shells
|
||||
await execPromise(`echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile`);
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureWireGuardMac(): Promise<void> {
|
||||
try {
|
||||
await execPromise("which wg");
|
||||
return; // already installed
|
||||
} catch (_) {
|
||||
console.log("Installing Homebrew + wireguard-tools...");
|
||||
|
||||
// Install Homebrew if missing
|
||||
try {
|
||||
await execPromise("brew --version");
|
||||
} catch {
|
||||
console.log("Homebrew missing. Installing...");
|
||||
await execPromise(
|
||||
'/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
|
||||
);
|
||||
|
||||
// Load Homebrew env for next commands
|
||||
await execPromise(`echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile`);
|
||||
await execPromise(`eval "$(/opt/homebrew/bin/brew shellenv)"`);
|
||||
}
|
||||
|
||||
// Install CLI WireGuard tools
|
||||
await execPromise("brew install wireguard-tools");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// --------------- MAC CONNECT/DISCONNECT ---------------
|
||||
// ------------------ MACOS CONNECT/DISCONNECT ------------------
|
||||
//
|
||||
|
||||
// async function connectMac(): Promise<string> {
|
||||
// const conf = getConfigPath();
|
||||
|
||||
// // Wrap command in login shell so Homebrew paths are available
|
||||
// const cmd = `/bin/bash -l -c "/opt/homebrew/bin/wg-quick up '${conf}'"`;
|
||||
|
||||
// const script = `
|
||||
// do shell script "${cmd}" with administrator privileges
|
||||
// `;
|
||||
// const escapedScript = script.replace(/\n/g, " ");
|
||||
|
||||
// return execPromise(`osascript -e '${escapedScript}'`)
|
||||
// .then(() => "macOS VPN connected (wg-quick)")
|
||||
// .catch(err => {
|
||||
// throw new Error("macOS VPN connect failed: " + err);
|
||||
// });
|
||||
// }
|
||||
|
||||
// async function connectMac(): Promise<string> {
|
||||
// try {
|
||||
// const conf = await getConfigPath();
|
||||
// console.log("Configuration Path:", conf); // Debug the path
|
||||
|
||||
// const cmd = `/bin/bash -l -c "/opt/homebrew/bin/wg-quick up '${conf}'"`;
|
||||
|
||||
// const script = `
|
||||
// do shell script "${cmd}" with administrator privileges
|
||||
// `;
|
||||
// const escapedScript = script.replace(/\n/g, " ");
|
||||
|
||||
// await execPromise(`osascript -e '${escapedScript}'`);
|
||||
|
||||
// return "macOS VPN connected (wg-quick)";
|
||||
// } catch (err) {
|
||||
// console.error("macOS VPN connect failed:", err);
|
||||
// throw new Error("macOS VPN connect failed: " + err);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// async function connectMac(): Promise<string> {
|
||||
// await ensureWireGuardMac();
|
||||
|
||||
// const conf = await getConfigPath();
|
||||
|
||||
// const cmd = `/bin/bash -c "sudo /opt/homebrew/bin/wg-quick up '${conf}'"`;
|
||||
|
||||
// try {
|
||||
// await execPromise(cmd);
|
||||
// return "macOS VPN connected";
|
||||
// } catch (err: any) {
|
||||
// throw new Error("macOS VPN connect failed: " + err.message);
|
||||
// }
|
||||
// }
|
||||
async function connectMac(): Promise<string> {
|
||||
await ensureWireGuardMac();
|
||||
|
||||
const conf = await getConfigPath();
|
||||
|
||||
// Detect the correct wg-quick binary
|
||||
let wgPath = (await execPromise(`which wg-quick`)).trim();
|
||||
|
||||
const cmd = `do shell script "${wgPath} up '${conf}'" with administrator privileges`;
|
||||
|
||||
try {
|
||||
const result = await execPromise(`osascript -e '${cmd}'`);
|
||||
console.log("macOS wg-quick result:", result);
|
||||
return "macOS VPN connected";
|
||||
} catch (err: any) {
|
||||
console.error("macOS VPN connect failed:", err);
|
||||
throw new Error("macOS VPN connect failed: " + err);
|
||||
}
|
||||
function connectMac(): Promise<string> {
|
||||
const conf = getConfigPath();
|
||||
return execPromise(`sudo wg-quick up "${conf}"`).then(() => "macOS VPN connected");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// function disconnectMac(): Promise<string> {
|
||||
// const cmd = `/bin/bash -l -c "/opt/homebrew/bin/wg-quick down wg0"`;
|
||||
|
||||
// const script = `
|
||||
// do shell script "${cmd}" with administrator privileges
|
||||
// `;
|
||||
// const escapedScript = script.replace(/\n/g, " ");
|
||||
|
||||
// return execPromise(`osascript -e '${escapedScript}'`)
|
||||
// .then(() => "macOS VPN disconnected")
|
||||
// .catch(err => {
|
||||
// throw new Error("macOS VPN disconnect failed: " + err);
|
||||
// });
|
||||
// }
|
||||
|
||||
// function disconnectMac(): Promise<string> {
|
||||
// const cmd = `/bin/bash -c "sudo /opt/homebrew/bin/wg-quick down wg0"`;
|
||||
|
||||
// return execPromise(cmd)
|
||||
// .then(() => "macOS VPN disconnected")
|
||||
// .catch(err => {
|
||||
// throw new Error("macOS VPN disconnect failed: " + err.message);
|
||||
// });
|
||||
// }
|
||||
|
||||
|
||||
|
||||
async function disconnectMac(): Promise<string> {
|
||||
let wgPath = (await execPromise(`which wg-quick`)).trim();
|
||||
|
||||
const cmd = `do shell script "${wgPath} down wg0" with administrator privileges`;
|
||||
|
||||
try {
|
||||
await execPromise(`osascript -e '${cmd}'`);
|
||||
return "macOS VPN disconnected";
|
||||
} catch (err: any) {
|
||||
throw new Error("macOS VPN disconnect failed: " + err);
|
||||
}
|
||||
function disconnectMac(): Promise<string> {
|
||||
return execPromise(`sudo wg-quick down wg0`).then(() => "macOS VPN disconnected");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
// ------------------ PUBLIC API ------------------
|
||||
//
|
||||
|
||||
export async function startVPN(): Promise<string> {
|
||||
export function startVPN(): Promise<string> {
|
||||
const platform = os.platform();
|
||||
|
||||
if (platform !== "win32" && platform !== "darwin") {
|
||||
throw new Error("Unsupported OS");
|
||||
}
|
||||
if (platform === "win32") return connectWindows();
|
||||
if (platform === "darwin") return connectMac();
|
||||
|
||||
// STEP 1: tunnel active?
|
||||
console.log("Start VPN Called for pltf :", platform)
|
||||
const active = await isTunnelActive();
|
||||
console.log("VPN Active Status :", active)
|
||||
|
||||
if (active) {
|
||||
console.log("VPN already active — checking health...");
|
||||
|
||||
// STEP 2: healthy?
|
||||
if (await isTunnelHealthy()) {
|
||||
console.log("VPN healthy — skip reconnect");
|
||||
return "VPN already connected and healthy";
|
||||
}
|
||||
|
||||
console.log("VPN active but unhealthy — reconnecting...");
|
||||
await stopVPN(); // gracefully disconnect
|
||||
}
|
||||
|
||||
// STEP 3: ensure config before connect
|
||||
await ensureConfig(platform);
|
||||
|
||||
// STEP 4: connect
|
||||
if (platform === "win32") {
|
||||
return connectWindows();
|
||||
}
|
||||
|
||||
if (platform === "darwin") {
|
||||
return connectMac();
|
||||
}
|
||||
|
||||
// STEP 5: unreachable fallback to satisfy TS
|
||||
throw new Error("Unexpected platform state");
|
||||
return Promise.reject("Unsupported OS");
|
||||
}
|
||||
|
||||
|
||||
export async function stopVPN(): Promise<string> {
|
||||
export function stopVPN(): Promise<string> {
|
||||
const platform = os.platform();
|
||||
|
||||
try {
|
||||
if (platform === "win32") return await disconnectWindows();
|
||||
if (platform === "darwin") return await disconnectMac();
|
||||
} catch {
|
||||
return "VPN not running";
|
||||
}
|
||||
if (platform === "win32") return disconnectWindows();
|
||||
if (platform === "darwin") return disconnectMac();
|
||||
|
||||
throw new Error("Unsupported OS");
|
||||
return Promise.reject("Unsupported OS");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,6 @@ import { Provider } from "react-redux";
|
|||
import { store } from "./store";
|
||||
import { ToastProvider } from './helper/ToastContext';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__allowClose__: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// renderer only
|
||||
window.addEventListener("online", (e) => {
|
||||
console.log("Network online (ignored)");
|
||||
|
|
@ -25,15 +18,7 @@ window.addEventListener("offline", (e) => {
|
|||
});
|
||||
|
||||
// Extra safety
|
||||
window.__allowClose__ = false;
|
||||
|
||||
window.addEventListener("beforeunload", (e) => {
|
||||
if (!window.__allowClose__) {
|
||||
e.returnValue = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
window.onbeforeunload = () => false;
|
||||
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ const Login = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="min-h-screen flex items-center justify-center bg-linear-to-br from-primary/10 via-base-200 to-secondary/10 p-4">
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-primary/10 via-base-200 to-secondary/10 p-4">
|
||||
<div className="card w-full max-w-md shadow-2xl bg-base-100 backdrop-blur-sm">
|
||||
<div className="card-body">
|
||||
<div className="flex justify-center items-center mb-4">
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ const VpnGuard: React.FC<Props> = ({ children }) => {
|
|||
console.log("connectVPN result → ", res);
|
||||
|
||||
// Only proceed if VPN actually connected
|
||||
if (res === "Windows VPN connected silently" || res === "VPN already connected and healthy") {
|
||||
if (res === "Windows VPN connected silently") {
|
||||
|
||||
const vpnRes = await window.electron.updateBaseURL(VPN_API);
|
||||
console.log("vpnRes", vpnRes);
|
||||
|
|
@ -169,14 +169,14 @@ const VpnGuard: React.FC<Props> = ({ children }) => {
|
|||
</button>
|
||||
|
||||
{/* Disconnect VPN (Disabled when NOT connected) */}
|
||||
{/* <button
|
||||
<button
|
||||
className="btn btn-error btn-wide gap-2 mt-3"
|
||||
onClick={disconnectVPN}
|
||||
// disabled={!vpnConnected || checking} // Disable if not connected
|
||||
>
|
||||
<Power className="w-4 h-4" />
|
||||
Disconnect VPN
|
||||
</button> */}
|
||||
</button>
|
||||
|
||||
<div className="divider"></div>
|
||||
<p className="text-xs opacity-60">
|
||||
|
|
|
|||
Loading…
Reference in New Issue