base vpn android plugin

This commit is contained in:
2026-03-12 17:37:47 +07:00
parent c765246d79
commit 2f7462e299
28 changed files with 874 additions and 132 deletions
+5 -1
View File
@@ -19,4 +19,8 @@ dist
/android/.tauri
/android/.gradle
/android/build
/android/build
*jniLibs
*uniffi
*rollup.cache
/android/gradle
@@ -15,3 +15,7 @@ thiserror = "2"
[build-dependencies]
tauri-plugin = { version = "2.5.4", features = ["build"] }
[features]
mobile = []
desktop = []
@@ -1,6 +1,7 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("kotlin-parcelize")
}
android {
@@ -10,6 +11,7 @@ android {
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
@@ -18,9 +20,19 @@ android {
kotlinOptions {
jvmTarget = "17"
}
sourceSets {
getByName("main") {
// Добавляем папку с исходным кодом (где лежат твои uniffi .kt файлы)
java.srcDirs("src/main/java")
// Оставляем jniLibs для .so файлов
jniLibs.srcDirs("src/main/jniLibs")
}
}
}
dependencies {
implementation("net.java.dev.jna:jna:5.13.0@aar")
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.0")
implementation(project(":tauri-android"))
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.netrunner.vpn">
<uses-permission android:name="android.permission.BIND_VPN_SERVICE" />
<application>
<service
android:name=".ExampleVpnService"
android:name="app.netrunner.vpn.VpnPlugin"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true">
<intent-filter>
@@ -1,6 +0,0 @@
package app.netrunner.vpn;
import android.net.VpnService;
class VpnEngine : VpnService() {
}
@@ -0,0 +1,192 @@
package app.netrunner.vpn
import android.app.Activity
import android.content.Intent
import app.tauri.plugin.Plugin
import app.tauri.plugin.Invoke
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
import app.tauri.annotation.Permission
import android.Manifest
import app.netrunner.vpn.VpnController
import android.os.ParcelFileDescriptor
import android.content.Context
import uniffi.netrunner_client.SessionManager
import uniffi.netrunner_client.Session
import uniffi.netrunner_client.NoHandle
import android.net.VpnService as AndroidVpnService
import app.netrunner.vpn.TunInterface
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.io.Closeable
@Parcelize
data class TunnelConfig(
val address: String,
val prefix: Int,
val dns: String,
val mtu: Int
) : Parcelable
class TunInterface : Closeable {
private var vpnInterface: ParcelFileDescriptor? = null
fun establish(builder: AndroidVpnService.Builder, config: TunnelConfig): Int {
// Принудительно закрываем текущий, если вдруг вызываем повторно
close()
val descriptor = builder
.addAddress(config.address, config.prefix)
.addRoute("0.0.0.0", 0)
.setMtu(config.mtu)
.establish()
vpnInterface = descriptor
return descriptor?.fd ?: -1
}
override fun close() {
try {
vpnInterface?.close()
} catch (e: Exception) {
e.printStackTrace()
} finally {
vpnInterface = null
}
}
}
class VpnController(private val service: VpnService) {
init {
System.loadLibrary("netrunner_client")
}
private var activeSession: Session? = null
private val tunInterface = TunInterface()
fun startVpn(config: TunnelConfig, ip: String, port: Int) {
val builder = service.Builder()
val fd = tunInterface.establish(builder, config)
if (fd != -1) {
val manager = SessionManager(NoHandle)
android.util.Log.d("VPN_DEBUG", "Starting session with FD: $fd")
activeSession = manager.start("$ip:$port", fd)
} else {
android.util.Log.d("VPN_DEBUG", "Starting session with FD: $fd")
}
}
fun stopVpn() {
activeSession?.stop()
activeSession = null
tunInterface.close()
}
}
class VpnService : AndroidVpnService() {
private var controller: VpnController? = null
override fun onCreate() {
super.onCreate()
android.util.Log.d("VPN_DEBUG", "VpnService: onCreate() executed")
try {
controller = VpnController(this)
android.util.Log.d("VPN_DEBUG", "VpnService: VpnController initialized successfully")
} catch (e: Exception) {
android.util.Log.e("VPN_DEBUG", "VpnService: CRITICAL - VpnController init failed", e)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val action = intent?.action
android.util.Log.d("VPN_DEBUG", "VpnService: onStartCommand() action='$action'")
when (action) {
"START_VPN" -> {
val ip = intent.getStringExtra("ip")
val port = intent.getIntExtra("port", -1)
android.util.Log.d("VPN_DEBUG", "VpnService: Extracting extras -> IP: '$ip', Port: $port")
val config = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra("config", TunnelConfig::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra("config")
}
if (config == null) {
android.util.Log.e("VPN_DEBUG", "VpnService: ERROR - TunnelConfig is NULL! Check if parcelable passed correctly.")
} else {
android.util.Log.d("VPN_DEBUG", "VpnService: Config received: $config. Calling controller.startVpn()...")
controller?.startVpn(config, ip ?: "", port)
}
}
"STOP_VPN" -> {
android.util.Log.d("VPN_DEBUG", "VpnService: Stopping VPN...")
controller?.stopVpn()
stopSelf()
}
else -> {
android.util.Log.w("VPN_DEBUG", "VpnService: Unknown action received: '$action'")
}
}
return START_STICKY
}
override fun onDestroy() {
controller?.stopVpn()
super.onDestroy()
}
}
@InvokeArg
class VpnArgs {
var config: TunnelConfig? = null
var ip: String = ""
var port: Int = 0
}
@TauriPlugin
class VpnPlugin(private val activity: Activity) : Plugin(activity) {
@Command
fun startVpn(invoke: Invoke) {
android.util.Log.d("VPN_DEBUG", "Plugin: startVpn invoked")
val args = invoke.parseArgs(VpnArgs::class.java)
val intent = Intent(activity, VpnService::class.java).apply {
action = "START_VPN"
putExtra("ip", args.ip)
putExtra("port", args.port)
putExtra("config", args.config)
}
android.util.Log.d("VPN_DEBUG", "Plugin: Starting VpnService...")
val result = activity.startService(intent)
if (result == null) {
android.util.Log.e("VPN_DEBUG", "Plugin: startService returned null (Service not found?)")
} else {
android.util.Log.d("VPN_DEBUG", "Plugin: startService triggered successfully")
}
invoke.resolve()
}
@Command
fun stopVpn(invoke: Invoke) {
val intent = Intent(activity, VpnService::class.java).apply {
action = "STOP_VPN"
}
activity.startService(intent)
invoke.resolve()
}
}
+3 -1
View File
@@ -1,4 +1,6 @@
const COMMANDS: &[&str] = &["ping"];
// src-tauri/src/plugins/vpn-plugin/build.rs
const COMMANDS: &[&str] = &["start_tunnel", "stop_tunnel"];
fn main() {
tauri_plugin::Builder::new(COMMANDS)
@@ -1,9 +1,20 @@
import { invoke } from '@tauri-apps/api/core'
import { invoke } from "@tauri-apps/api/core";
export async function ping(value: string): Promise<string | null> {
return await invoke<{value?: string}>('plugin:vpn|ping', {
payload: {
value,
},
}).then((r) => (r.value ? r.value : null));
export interface TunnelConfig {
address: string;
prefix: number;
dns: string;
mtu: number;
}
export async function startVpn(config: TunnelConfig, ip: string, port: number) {
return await invoke("plugin:vpn|start_vpn", {
config,
ip,
port,
});
}
export async function stopVpn() {
return await invoke("plugin:vpn|stop_vpn");
}
@@ -1,13 +0,0 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-ping"
description = "Enables the ping command without any pre-configured scope."
commands.allow = ["ping"]
[[permission]]
identifier = "deny-ping"
description = "Denies the ping command without any pre-configured scope."
commands.deny = ["ping"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-start-tunnel"
description = "Enables the start_tunnel command without any pre-configured scope."
commands.allow = ["start_tunnel"]
[[permission]]
identifier = "deny-start-tunnel"
description = "Denies the start_tunnel command without any pre-configured scope."
commands.deny = ["start_tunnel"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-stop-tunnel"
description = "Enables the stop_tunnel command without any pre-configured scope."
commands.allow = ["stop_tunnel"]
[[permission]]
identifier = "deny-stop-tunnel"
description = "Denies the stop_tunnel command without any pre-configured scope."
commands.deny = ["stop_tunnel"]
@@ -10,12 +10,12 @@
<tr>
<td>
`vpn:allow-ping`
`vpn:allow-start-tunnel`
</td>
<td>
Enables the ping command without any pre-configured scope.
Enables the start_tunnel command without any pre-configured scope.
</td>
</tr>
@@ -23,12 +23,12 @@ Enables the ping command without any pre-configured scope.
<tr>
<td>
`vpn:deny-ping`
`vpn:deny-start-tunnel`
</td>
<td>
Denies the ping command without any pre-configured scope.
Denies the start_tunnel command without any pre-configured scope.
</td>
</tr>
@@ -36,12 +36,38 @@ Denies the ping command without any pre-configured scope.
<tr>
<td>
`vpn:vpn-control`
`vpn:allow-stop-tunnel`
</td>
<td>
Allows access to VPN Service
Enables the stop_tunnel command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpn:deny-stop-tunnel`
</td>
<td>
Denies the stop_tunnel command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpn:allow-vpn-control`
</td>
<td>
Allows Vpn Control
</td>
</tr>
@@ -1,5 +1,6 @@
[[permission]]
identifier = "vpn-control"
description = "Allows access to VPN Service"
name = "allow-vpn-control"
commands = { allow = ["ping", "prepare_vpn", "start_vpn", "stop_vpn"] }
identifier = "allow-vpn-control"
description = "Allows Vpn Control"
[permission.commands]
allow = ["start_vpn", "stop_vpn"]
@@ -295,22 +295,34 @@
"type": "string",
"oneOf": [
{
"description": "Enables the ping command without any pre-configured scope.",
"description": "Enables the start_tunnel command without any pre-configured scope.",
"type": "string",
"const": "allow-ping",
"markdownDescription": "Enables the ping command without any pre-configured scope."
"const": "allow-start-tunnel",
"markdownDescription": "Enables the start_tunnel command without any pre-configured scope."
},
{
"description": "Denies the ping command without any pre-configured scope.",
"description": "Denies the start_tunnel command without any pre-configured scope.",
"type": "string",
"const": "deny-ping",
"markdownDescription": "Denies the ping command without any pre-configured scope."
"const": "deny-start-tunnel",
"markdownDescription": "Denies the start_tunnel command without any pre-configured scope."
},
{
"description": "Allows access to VPN Service",
"description": "Enables the stop_tunnel command without any pre-configured scope.",
"type": "string",
"const": "vpn-control",
"markdownDescription": "Allows access to VPN Service"
"const": "allow-stop-tunnel",
"markdownDescription": "Enables the stop_tunnel command without any pre-configured scope."
},
{
"description": "Denies the stop_tunnel command without any pre-configured scope.",
"type": "string",
"const": "deny-stop-tunnel",
"markdownDescription": "Denies the stop_tunnel command without any pre-configured scope."
},
{
"description": "Allows Vpn Control",
"type": "string",
"const": "allow-vpn-control",
"markdownDescription": "Allows Vpn Control"
}
]
}
+402
View File
@@ -0,0 +1,402 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@tauri-apps/api':
specifier: ^2.0.0
version: 2.10.1
devDependencies:
'@rollup/plugin-typescript':
specifier: ^12.0.0
version: 12.3.0(rollup@4.59.0)(tslib@2.8.1)(typescript@5.9.3)
rollup:
specifier: ^4.9.6
version: 4.59.0
tslib:
specifier: ^2.6.2
version: 2.8.1
typescript:
specifier: ^5.3.3
version: 5.9.3
packages:
'@rollup/plugin-typescript@12.3.0':
resolution: {integrity: sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^2.14.0||^3.0.0||^4.0.0
tslib: '*'
typescript: '>=3.7.0'
peerDependenciesMeta:
rollup:
optional: true
tslib:
optional: true
'@rollup/pluginutils@5.3.0':
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/rollup-android-arm-eabi@4.59.0':
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.59.0':
resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.59.0':
resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.59.0':
resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.59.0':
resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.59.0':
resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.59.0':
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.59.0':
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.59.0':
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.59.0':
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.59.0':
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.59.0':
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.59.0':
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.59.0':
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.59.0':
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.59.0':
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
cpu: [x64]
os: [openbsd]
'@rollup/rollup-openharmony-arm64@4.59.0':
resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==}
cpu: [arm64]
os: [openharmony]
'@rollup/rollup-win32-arm64-msvc@4.59.0':
resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.59.0':
resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-gnu@4.59.0':
resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==}
cpu: [x64]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.59.0':
resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==}
cpu: [x64]
os: [win32]
'@tauri-apps/api@2.10.1':
resolution: {integrity: sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
is-core-module@2.16.1:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
picomatch@4.0.3:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
resolve@1.22.11:
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
engines: {node: '>= 0.4'}
hasBin: true
rollup@4.59.0:
resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
snapshots:
'@rollup/plugin-typescript@12.3.0(rollup@4.59.0)(tslib@2.8.1)(typescript@5.9.3)':
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.59.0)
resolve: 1.22.11
typescript: 5.9.3
optionalDependencies:
rollup: 4.59.0
tslib: 2.8.1
'@rollup/pluginutils@5.3.0(rollup@4.59.0)':
dependencies:
'@types/estree': 1.0.8
estree-walker: 2.0.2
picomatch: 4.0.3
optionalDependencies:
rollup: 4.59.0
'@rollup/rollup-android-arm-eabi@4.59.0':
optional: true
'@rollup/rollup-android-arm64@4.59.0':
optional: true
'@rollup/rollup-darwin-arm64@4.59.0':
optional: true
'@rollup/rollup-darwin-x64@4.59.0':
optional: true
'@rollup/rollup-freebsd-arm64@4.59.0':
optional: true
'@rollup/rollup-freebsd-x64@4.59.0':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-arm64-musl@4.59.0':
optional: true
'@rollup/rollup-linux-loong64-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-loong64-musl@4.59.0':
optional: true
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-ppc64-musl@4.59.0':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.59.0':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-x64-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-x64-musl@4.59.0':
optional: true
'@rollup/rollup-openbsd-x64@4.59.0':
optional: true
'@rollup/rollup-openharmony-arm64@4.59.0':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.59.0':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.59.0':
optional: true
'@rollup/rollup-win32-x64-gnu@4.59.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.59.0':
optional: true
'@tauri-apps/api@2.10.1': {}
'@types/estree@1.0.8': {}
estree-walker@2.0.2: {}
fsevents@2.3.3:
optional: true
function-bind@1.1.2: {}
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
is-core-module@2.16.1:
dependencies:
hasown: 2.0.2
path-parse@1.0.7: {}
picomatch@4.0.3: {}
resolve@1.22.11:
dependencies:
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
rollup@4.59.0:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.59.0
'@rollup/rollup-android-arm64': 4.59.0
'@rollup/rollup-darwin-arm64': 4.59.0
'@rollup/rollup-darwin-x64': 4.59.0
'@rollup/rollup-freebsd-arm64': 4.59.0
'@rollup/rollup-freebsd-x64': 4.59.0
'@rollup/rollup-linux-arm-gnueabihf': 4.59.0
'@rollup/rollup-linux-arm-musleabihf': 4.59.0
'@rollup/rollup-linux-arm64-gnu': 4.59.0
'@rollup/rollup-linux-arm64-musl': 4.59.0
'@rollup/rollup-linux-loong64-gnu': 4.59.0
'@rollup/rollup-linux-loong64-musl': 4.59.0
'@rollup/rollup-linux-ppc64-gnu': 4.59.0
'@rollup/rollup-linux-ppc64-musl': 4.59.0
'@rollup/rollup-linux-riscv64-gnu': 4.59.0
'@rollup/rollup-linux-riscv64-musl': 4.59.0
'@rollup/rollup-linux-s390x-gnu': 4.59.0
'@rollup/rollup-linux-x64-gnu': 4.59.0
'@rollup/rollup-linux-x64-musl': 4.59.0
'@rollup/rollup-openbsd-x64': 4.59.0
'@rollup/rollup-openharmony-arm64': 4.59.0
'@rollup/rollup-win32-arm64-msvc': 4.59.0
'@rollup/rollup-win32-ia32-msvc': 4.59.0
'@rollup/rollup-win32-x64-gnu': 4.59.0
'@rollup/rollup-win32-x64-msvc': 4.59.0
fsevents: 2.3.3
supports-preserve-symlinks-flag@1.0.0: {}
tslib@2.8.1: {}
typescript@5.9.3: {}
@@ -1,13 +1,20 @@
use tauri::{AppHandle, command, Runtime};
use crate::models::*;
use crate::Result;
use crate::VpnExt;
use crate::{models::*, Vpn, VpnInterface};
use tauri::{command, AppHandle, Manager, Runtime};
#[command]
pub(crate) async fn ping<R: Runtime>(
pub(crate) fn start_vpn<R: Runtime>(
app: AppHandle<R>,
payload: PingRequest,
) -> Result<PingResponse> {
app.vpn().ping(payload)
config: TunnelConfig,
ip: String,
port: i32,
) -> crate::Result<()> {
// Получаем состояние Vpn<R> и вызываем метод интерфейса
eprintln!("--- [DEBUG] START_VPN COMMAND CALLED! ---");
app.state::<Vpn<R>>().start_vpn(config, ip, port)
}
#[command]
pub(crate) fn stop_vpn<R: Runtime>(app: AppHandle<R>) -> crate::Result<()> {
// Получаем состояние Vpn<R> и вызываем метод интерфейса
app.state::<Vpn<R>>().stop_vpn()
}
+15 -12
View File
@@ -1,22 +1,25 @@
use crate::models::*;
use crate::VpnInterface;
use serde::de::DeserializeOwned;
use tauri::{plugin::PluginApi, AppHandle, Runtime};
use crate::models::*;
// Инициализация для ПК
pub fn init<R: Runtime, C: DeserializeOwned>(
app: &AppHandle<R>,
_api: PluginApi<R, C>,
app: &AppHandle<R>,
_api: PluginApi<R, C>,
) -> crate::Result<Vpn<R>> {
Ok(Vpn(app.clone()))
Ok(Vpn(app.clone()))
}
/// Access to the vpn APIs.
pub struct Vpn<R: Runtime>(AppHandle<R>);
impl<R: Runtime> Vpn<R> {
pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
Ok(PingResponse {
value: payload.value,
})
}
impl<R: Runtime> VpnInterface<R> for Vpn<R> {
fn start_vpn(&self, _config: TunnelConfig, _ip: String, _port: i32) -> crate::Result<()> {
// Логика для ПК: например, запуск системного процесса
Ok(())
}
fn stop_vpn(&self) -> crate::Result<()> {
Ok(())
}
}
+33 -25
View File
@@ -1,6 +1,7 @@
use std::ops::Deref;
use tauri::{
plugin::{Builder, TauriPlugin},
Manager, Runtime,
plugin::{Builder, TauriPlugin},
Manager, Runtime,
};
pub use models::*;
@@ -16,33 +17,40 @@ mod models;
pub use error::{Error, Result};
#[cfg(desktop)]
use desktop::Vpn;
#[cfg(mobile)]
use mobile::Vpn;
// В lib.rs
pub trait VpnInterface<R: Runtime>: Send + Sync + 'static {
fn start_vpn(&self, config: TunnelConfig, ip: String, port: i32) -> crate::Result<()>;
fn stop_vpn(&self) -> crate::Result<()>;
}
pub struct Vpn<R: Runtime>(Box<dyn VpnInterface<R>>);
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the vpn APIs.
pub trait VpnExt<R: Runtime> {
fn vpn(&self) -> &Vpn<R>;
impl<R: Runtime> Vpn<R> {
pub fn new<I: VpnInterface<R>>(inner: I) -> Self {
Self(Box::new(inner))
}
}
impl<R: Runtime, T: Manager<R>> crate::VpnExt<R> for T {
fn vpn(&self) -> &Vpn<R> {
self.state::<Vpn<R>>().inner()
}
impl<R: Runtime> Deref for Vpn<R> {
type Target = dyn VpnInterface<R>;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
/// Initializes the plugin.
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("vpn")
.invoke_handler(tauri::generate_handler![commands::ping])
.setup(|app, api| {
#[cfg(mobile)]
let vpn = mobile::init(app, api)?;
#[cfg(desktop)]
let vpn = desktop::init(app, api)?;
app.manage(vpn);
Ok(())
})
.build()
Builder::new("vpn")
.invoke_handler(tauri::generate_handler![
commands::start_vpn,
commands::stop_vpn
])
.setup(|app, api| {
#[cfg(mobile)]
let vpn_impl = mobile::init(app, api)?;
#[cfg(desktop)]
let vpn_impl = desktop::init(app, api)?;
app.manage(Vpn::new(vpn_impl));
Ok(())
})
.build()
}
+28 -19
View File
@@ -1,34 +1,43 @@
use crate::models::*;
use crate::VpnInterface;
use serde::de::DeserializeOwned;
use tauri::{
plugin::{PluginApi, PluginHandle},
AppHandle, Runtime,
plugin::{PluginApi, PluginHandle},
AppHandle, Runtime,
};
use crate::models::*;
#[cfg(target_os = "android")]
const PLUGIN_IDENTIFIER: &str = "app.netrunner.vpn";
#[cfg(target_os = "ios")]
tauri::ios_plugin_binding!(init_plugin_vpn);
// initializes the Kotlin or Swift plugin classes
pub fn init<R: Runtime, C: DeserializeOwned>(
_app: &AppHandle<R>,
api: PluginApi<R, C>,
_app: &AppHandle<R>,
api: PluginApi<R, C>,
) -> crate::Result<Vpn<R>> {
#[cfg(target_os = "android")]
let handle = api.register_android_plugin("", "ExamplePlugin")?;
#[cfg(target_os = "ios")]
let handle = api.register_ios_plugin(init_plugin_vpn)?;
Ok(Vpn(handle))
#[cfg(target_os = "android")]
let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "VpnPlugin")?;
#[cfg(target_os = "ios")]
let handle = api.register_ios_plugin(init_plugin_vpn)?;
Ok(Vpn(handle))
}
/// Access to the vpn APIs.
pub struct Vpn<R: Runtime>(PluginHandle<R>);
impl<R: Runtime> Vpn<R> {
pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
self
.0
.run_mobile_plugin("ping", payload)
.map_err(Into::into)
}
impl<R: Runtime> VpnInterface<R> for Vpn<R> {
fn start_vpn(&self, config: TunnelConfig, ip: String, port: i32) -> crate::Result<()> {
self.send("startVpn", StartVpnRequest { config, ip, port })
}
fn stop_vpn(&self) -> crate::Result<()> {
self.send("stopVpn", StopVpnRequest {})
}
}
// Приватный метод для уменьшения дублирования кода
impl<R: Runtime> Vpn<R> {
fn send<S: serde::Serialize>(&self, cmd: &str, payload: S) -> crate::Result<()> {
self.0.run_mobile_plugin(cmd, payload).map_err(Into::into)
}
}
+15 -7
View File
@@ -1,13 +1,21 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TunnelConfig {
pub address: String,
pub prefix: i32, // Соответствует Int в Kotlin
pub dns: String,
pub mtu: i32,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PingRequest {
pub value: Option<String>,
pub struct StartVpnRequest {
pub config: TunnelConfig,
pub ip: String,
pub port: i32,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PingResponse {
pub value: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct StopVpnRequest {}
File diff suppressed because one or more lines are too long