diff --git a/package.json b/package.json index bc7d20f..c2f8023 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-table": "^8.21.3", - "@tauri-apps/api": "~2.10.1", + "@tauri-apps/api": "~2.11.0", "@tauri-apps/plugin-deep-link": "^2.4.7", "@tauri-apps/plugin-dialog": "^2.7.0", "@tauri-apps/plugin-fs": "~2.5.0", @@ -75,7 +75,7 @@ "devDependencies": { "@biomejs/biome": "2.4.10", "@tailwindcss/postcss": "^4.2.2", - "@tauri-apps/cli": "~2.10.1", + "@tauri-apps/cli": "~2.11.0", "@types/color": "^4.2.1", "@types/node": "^25.5.2", "@types/react": "^19.2.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8183978..20e60a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,8 +54,8 @@ importers: specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tauri-apps/api': - specifier: ~2.10.1 - version: 2.10.1 + specifier: ~2.11.0 + version: 2.11.0 '@tauri-apps/plugin-deep-link': specifier: ^2.4.7 version: 2.4.7 @@ -139,8 +139,8 @@ importers: specifier: ^4.2.2 version: 4.2.2 '@tauri-apps/cli': - specifier: ~2.10.1 - version: 2.10.1 + specifier: ~2.11.0 + version: 2.11.0 '@types/color': specifier: ^4.2.1 version: 4.2.1 @@ -2678,82 +2678,82 @@ packages: resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} - '@tauri-apps/api@2.10.1': - resolution: {integrity: sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==} + '@tauri-apps/api@2.11.0': + resolution: {integrity: sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==} - '@tauri-apps/cli-darwin-arm64@2.10.1': - resolution: {integrity: sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==} + '@tauri-apps/cli-darwin-arm64@2.11.0': + resolution: {integrity: sha512-UfMeDNlgIP252rm/KSTuu8yHatPua5TjtUEUf+jyIzVwBNcIl7Ywkdpfj+e5jVVg3EfCTp+4gwuL1dNpgF8clg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tauri-apps/cli-darwin-x64@2.10.1': - resolution: {integrity: sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==} + '@tauri-apps/cli-darwin-x64@2.11.0': + resolution: {integrity: sha512-lY1+aPlgyMN7vgjtCdQ3+WODfZkebAcxnrCrO0HjqDpKSXieDkrJbimqeaoM4RwhTSrCLRHfVYiYrfE5E131tg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tauri-apps/cli-linux-arm-gnueabihf@2.10.1': - resolution: {integrity: sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==} + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.0': + resolution: {integrity: sha512-5uCP0AusgN3NrKC8EpkuJwjek1k8pEffBdugJSpXPey/QGbPEb8vZ542n/giJ2mZPjMSllDkdhG2QIDpBY4PpQ==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tauri-apps/cli-linux-arm64-gnu@2.10.1': - resolution: {integrity: sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==} + '@tauri-apps/cli-linux-arm64-gnu@2.11.0': + resolution: {integrity: sha512-loDPqtRHMSbIcrH2VBd4GgHoQlF7jJnrZj7MxA2lj1cixS/jEgMAPFqj83U6Wvjete4HfYplbE/gCpSFifA9jw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@tauri-apps/cli-linux-arm64-musl@2.10.1': - resolution: {integrity: sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==} + '@tauri-apps/cli-linux-arm64-musl@2.11.0': + resolution: {integrity: sha512-DtSE8ZBlB9H+L+eHkfZ3myt00EVEyAB3e41juEHoE2qT88fgVlJvyrwa9SZYc/xTwCS9TnmK+R84tpg+ZsAg7Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@tauri-apps/cli-linux-riscv64-gnu@2.10.1': - resolution: {integrity: sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==} + '@tauri-apps/cli-linux-riscv64-gnu@2.11.0': + resolution: {integrity: sha512-5QdgS4LD+kntClI1aj2JmwjW38LosNXxwCe8viIHEwqYIWuMPdNEIau6/cLogI38Yzx9DnfCPRfEWLyI+5li8Q==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] libc: [glibc] - '@tauri-apps/cli-linux-x64-gnu@2.10.1': - resolution: {integrity: sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==} + '@tauri-apps/cli-linux-x64-gnu@2.11.0': + resolution: {integrity: sha512-5UynPXo3Zq9khjVdAbD+YogeLltdVUeOah2ioSIM3tu6H7wY9vMy6rgGJhv9r5R8ZXmk9GttMippdqYJWrnLnA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@tauri-apps/cli-linux-x64-musl@2.10.1': - resolution: {integrity: sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==} + '@tauri-apps/cli-linux-x64-musl@2.11.0': + resolution: {integrity: sha512-CNz7fHbApz1Zyhhq73jtGn9JqgNEV/lIWnTnUo6h6ujw+mHsTmkLszvJSM8W6JBaDjNpTTFr/RSNoVL5FMwcTg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@tauri-apps/cli-win32-arm64-msvc@2.10.1': - resolution: {integrity: sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==} + '@tauri-apps/cli-win32-arm64-msvc@2.11.0': + resolution: {integrity: sha512-K+br+VXZ+Xx0n/9FdWohpW5Ugq+2FQUpJScqcPl1hTxXfh3fgjYgt4qA2NgrjlJo+zZPNrmUMl+NLvm0ufEqBQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tauri-apps/cli-win32-ia32-msvc@2.10.1': - resolution: {integrity: sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==} + '@tauri-apps/cli-win32-ia32-msvc@2.11.0': + resolution: {integrity: sha512-OFV+s3MLZnd75zl0ZAFU5riMpGK4waUEA8ZDuijDsnkU0btz/gHhqh5jVlOn8thyvgdtT3Xyoxqo099MMifH3g==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@tauri-apps/cli-win32-x64-msvc@2.10.1': - resolution: {integrity: sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==} + '@tauri-apps/cli-win32-x64-msvc@2.11.0': + resolution: {integrity: sha512-AeDTWBd2cOZ6TX133BWsoo+LutG9o0JRcgjMsIfLE13ZugpgCMv/2dJbUiBGeRvbPOGin5A3aYmsArPVV6ZSHQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tauri-apps/cli@2.10.1': - resolution: {integrity: sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==} + '@tauri-apps/cli@2.11.0': + resolution: {integrity: sha512-W5Wbuqsb2pHFPTj4TaRNKTj5rwXhDShPiLSY9T18y4ouSR/NNCptAEFxFsBtyNRgL6Vs1a/q9LzfqqYzEwC+Jw==} engines: {node: '>= 10'} hasBin: true @@ -8343,74 +8343,74 @@ snapshots: '@tanstack/table-core@8.21.3': {} - '@tauri-apps/api@2.10.1': {} + '@tauri-apps/api@2.11.0': {} - '@tauri-apps/cli-darwin-arm64@2.10.1': + '@tauri-apps/cli-darwin-arm64@2.11.0': optional: true - '@tauri-apps/cli-darwin-x64@2.10.1': + '@tauri-apps/cli-darwin-x64@2.11.0': optional: true - '@tauri-apps/cli-linux-arm-gnueabihf@2.10.1': + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.0': optional: true - '@tauri-apps/cli-linux-arm64-gnu@2.10.1': + '@tauri-apps/cli-linux-arm64-gnu@2.11.0': optional: true - '@tauri-apps/cli-linux-arm64-musl@2.10.1': + '@tauri-apps/cli-linux-arm64-musl@2.11.0': optional: true - '@tauri-apps/cli-linux-riscv64-gnu@2.10.1': + '@tauri-apps/cli-linux-riscv64-gnu@2.11.0': optional: true - '@tauri-apps/cli-linux-x64-gnu@2.10.1': + '@tauri-apps/cli-linux-x64-gnu@2.11.0': optional: true - '@tauri-apps/cli-linux-x64-musl@2.10.1': + '@tauri-apps/cli-linux-x64-musl@2.11.0': optional: true - '@tauri-apps/cli-win32-arm64-msvc@2.10.1': + '@tauri-apps/cli-win32-arm64-msvc@2.11.0': optional: true - '@tauri-apps/cli-win32-ia32-msvc@2.10.1': + '@tauri-apps/cli-win32-ia32-msvc@2.11.0': optional: true - '@tauri-apps/cli-win32-x64-msvc@2.10.1': + '@tauri-apps/cli-win32-x64-msvc@2.11.0': optional: true - '@tauri-apps/cli@2.10.1': + '@tauri-apps/cli@2.11.0': optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.10.1 - '@tauri-apps/cli-darwin-x64': 2.10.1 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.10.1 - '@tauri-apps/cli-linux-arm64-gnu': 2.10.1 - '@tauri-apps/cli-linux-arm64-musl': 2.10.1 - '@tauri-apps/cli-linux-riscv64-gnu': 2.10.1 - '@tauri-apps/cli-linux-x64-gnu': 2.10.1 - '@tauri-apps/cli-linux-x64-musl': 2.10.1 - '@tauri-apps/cli-win32-arm64-msvc': 2.10.1 - '@tauri-apps/cli-win32-ia32-msvc': 2.10.1 - '@tauri-apps/cli-win32-x64-msvc': 2.10.1 + '@tauri-apps/cli-darwin-arm64': 2.11.0 + '@tauri-apps/cli-darwin-x64': 2.11.0 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.11.0 + '@tauri-apps/cli-linux-arm64-gnu': 2.11.0 + '@tauri-apps/cli-linux-arm64-musl': 2.11.0 + '@tauri-apps/cli-linux-riscv64-gnu': 2.11.0 + '@tauri-apps/cli-linux-x64-gnu': 2.11.0 + '@tauri-apps/cli-linux-x64-musl': 2.11.0 + '@tauri-apps/cli-win32-arm64-msvc': 2.11.0 + '@tauri-apps/cli-win32-ia32-msvc': 2.11.0 + '@tauri-apps/cli-win32-x64-msvc': 2.11.0 '@tauri-apps/plugin-deep-link@2.4.7': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.0 '@tauri-apps/plugin-dialog@2.7.0': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.0 '@tauri-apps/plugin-fs@2.5.0': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.0 '@tauri-apps/plugin-log@2.8.0': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.0 '@tauri-apps/plugin-opener@2.5.3': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.0 '@tokenizer/inflate@0.4.1': dependencies: @@ -11037,7 +11037,7 @@ snapshots: tauri-plugin-macos-permissions-api@2.3.0: dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.0 terser-webpack-plugin@5.4.0(webpack@5.105.4): dependencies: diff --git a/src-tauri/src/api_server.rs b/src-tauri/src/api_server.rs index 3d005fe..a80b0fb 100644 --- a/src-tauri/src/api_server.rs +++ b/src-tauri/src/api_server.rs @@ -41,6 +41,7 @@ pub struct ApiProfile { pub tags: Vec, pub is_running: bool, pub proxy_bypass_rules: Vec, + pub vpn_id: Option, } #[derive(Debug, Serialize, Deserialize, ToSchema)] @@ -60,6 +61,7 @@ pub struct CreateProfileRequest { pub browser: String, pub version: String, pub proxy_id: Option, + pub vpn_id: Option, pub launch_hook: Option, pub release_type: Option, #[schema(value_type = Object)] @@ -76,6 +78,7 @@ pub struct UpdateProfileRequest { pub browser: Option, pub version: Option, pub proxy_id: Option, + pub vpn_id: Option, pub launch_hook: Option, pub release_type: Option, #[schema(value_type = Object)] @@ -140,6 +143,16 @@ struct ApiVpnResponse { last_used: Option, } +#[derive(Debug, Serialize, ToSchema)] +struct ApiVpnExportResponse { + id: String, + name: String, + /// Always "WireGuard" + vpn_type: String, + /// Raw `.conf` file content (decrypted) + config_data: String, +} + #[derive(Debug, Deserialize, ToSchema)] struct ImportVpnRequest { /// Raw WireGuard `.conf` file content @@ -357,6 +370,7 @@ impl ApiServer { .routes(routes!(get_proxy, update_proxy, delete_proxy)) .routes(routes!(get_vpns, create_vpn)) .routes(routes!(import_vpn)) + .routes(routes!(export_vpn)) .routes(routes!(get_vpn, update_vpn, delete_vpn)) .routes(routes!(get_extensions)) .routes(routes!(delete_extension_api)) @@ -542,6 +556,7 @@ async fn get_profiles() -> Result, StatusCode> { tags: profile.tags.clone(), is_running: profile.process_id.is_some(), // Simple check based on process_id proxy_bypass_rules: profile.proxy_bypass_rules.clone(), + vpn_id: profile.vpn_id.clone(), }) .collect(); @@ -598,6 +613,7 @@ async fn get_profile( tags: profile.tags.clone(), is_running: profile.process_id.is_some(), // Simple check based on process_id proxy_bypass_rules: profile.proxy_bypass_rules.clone(), + vpn_id: profile.vpn_id.clone(), }, })) } else { @@ -652,7 +668,7 @@ async fn create_profile( &request.version, request.release_type.as_deref().unwrap_or("stable"), request.proxy_id.clone(), - None, // vpn_id + request.vpn_id.clone(), camoufox_config, wayfern_config, request.group_id.clone(), @@ -700,6 +716,7 @@ async fn create_profile( tags: profile.tags, is_running: false, proxy_bypass_rules: profile.proxy_bypass_rules, + vpn_id: profile.vpn_id, }, })) } @@ -733,6 +750,12 @@ async fn update_profile( ) -> Result, StatusCode> { let profile_manager = ProfileManager::instance(); + if request.proxy_id.as_deref().is_some_and(|s| !s.is_empty()) + && request.vpn_id.as_deref().is_some_and(|s| !s.is_empty()) + { + return Err(StatusCode::BAD_REQUEST); + } + // Update profile fields if let Some(new_name) = request.name { if profile_manager @@ -762,6 +785,21 @@ async fn update_profile( } } + if let Some(vpn_id) = request.vpn_id { + let normalized = if vpn_id.is_empty() { + None + } else { + Some(vpn_id) + }; + if profile_manager + .update_profile_vpn(state.app_handle.clone(), &id, normalized) + .await + .is_err() + { + return Err(StatusCode::BAD_REQUEST); + } + } + if let Some(launch_hook) = request.launch_hook { let normalized = if launch_hook.trim().is_empty() { None @@ -1308,6 +1346,37 @@ async fn get_vpn( .ok_or(StatusCode::NOT_FOUND) } +#[utoipa::path( + get, + path = "/v1/vpns/{id}/export", + params(("id" = String, Path, description = "VPN configuration ID")), + responses( + (status = 200, description = "Decrypted VPN configuration", body = ApiVpnExportResponse), + (status = 401, description = "Unauthorized"), + (status = 404, description = "VPN configuration not found"), + (status = 500, description = "Internal server error") + ), + security(("bearer_auth" = [])), + tag = "vpns" +)] +async fn export_vpn( + Path(id): Path, + State(_state): State, +) -> Result, StatusCode> { + let storage = crate::vpn::VPN_STORAGE + .lock() + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + match storage.load_config(&id) { + Ok(config) => Ok(Json(ApiVpnExportResponse { + id: config.id, + name: config.name, + vpn_type: config.vpn_type.to_string(), + config_data: config.config_data, + })), + Err(_) => Err(StatusCode::NOT_FOUND), + } +} + #[utoipa::path( post, path = "/v1/vpns/import",