prod works

This commit is contained in:
2026-03-13 14:37:27 +07:00
parent d5acfe7448
commit ca880112d1
7 changed files with 104 additions and 23 deletions
+3 -1
View File
@@ -7,7 +7,9 @@
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"preview": "vite preview", "preview": "vite preview",
"tauri": "tauri" "tauri": "tauri",
"android-logs": "adb logcat -c && adb logcat NETRUNNER_RUST VPN_DEBUG AndroidRuntime:E System.err:V VpnConfigService VpnService *:S",
"android-logs:full": "adb logcat NETRUNNER_RUST VPN_DEBUG AndroidRuntime:E System.err:V *:W"
}, },
"dependencies": { "dependencies": {
"@fontsource-variable/figtree": "^5.2.10", "@fontsource-variable/figtree": "^5.2.10",
@@ -0,0 +1,4 @@
-keep class com.netrunner.vpn.** { *; }
-keepclasseswithmembernames class * {
native <methods>;
}
@@ -2,12 +2,17 @@
package="app.netrunner.vpn"> package="app.netrunner.vpn">
<uses-permission android:name="android.permission.BIND_VPN_SERVICE" /> <uses-permission android:name="android.permission.BIND_VPN_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<application> <application>
<service <service
android:name=".TunVpnService" android:name=".TunVpnService"
android:permission="android.permission.BIND_VPN_SERVICE" android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true"> android:exported="false"
android:foregroundServiceType="specialUse">
<intent-filter> <intent-filter>
<action android:name="android.net.VpnService"/> <action android:name="android.net.VpnService"/>
</intent-filter> </intent-filter>
@@ -50,7 +50,7 @@ class TunVpnService : VpnService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand called action=${intent?.action}") Log.d(TAG, "onStartCommand called action=${intent?.action}")
startForegroundNotification()
when (intent?.action) { when (intent?.action) {
"START_VPN" -> { "START_VPN" -> {
@@ -77,8 +77,7 @@ class TunVpnService : VpnService() {
"STOP_VPN" -> { "STOP_VPN" -> {
Log.d(TAG, "STOP_VPN received") Log.d(TAG, "STOP_VPN received")
stopVpn() stopServiceProperly()
stopSelf()
} }
else -> { else -> {
@@ -89,6 +88,17 @@ class TunVpnService : VpnService() {
return START_STICKY return START_STICKY
} }
private fun stopServiceProperly() {
stopVpn()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
stopForeground(android.app.Service.STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION")
stopForeground(true)
}
stopSelf()
}
private fun startVpn(config: TunnelConfig, ip: String, port: Int) { private fun startVpn(config: TunnelConfig, ip: String, port: Int) {
Log.d(TAG, "startVpn called") Log.d(TAG, "startVpn called")
@@ -158,6 +168,35 @@ class TunVpnService : VpnService() {
vpnInterface = null vpnInterface = null
} }
private fun startForegroundNotification() {
val channelId = "vpn_service_channel"
val channelName = "VPN Service"
val notificationManager = getSystemService(android.app.NotificationManager::class.java)
// Создаем канал для Android 8.0+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
val channel = android.app.NotificationChannel(
channelId, channelName, android.app.NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(channel)
}
val notification = android.app.Notification.Builder(this, channelId)
.setContentTitle("Netrunner VPN")
.setContentText("VPN активен")
.setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) // Используй иконку из ресурсов
.setOngoing(true) // Пользователь не может смахнуть уведомление
.build()
// Запуск Foreground с обязательным указанием типа для Android 14+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(1, notification, android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
startForeground(1, notification)
}
}
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "TunVpnService destroyed") Log.d(TAG, "TunVpnService destroyed")
stopVpn() stopVpn()
@@ -230,8 +269,7 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
} }
private fun startVpnService(args: VpnArgs) { private fun startVpnService(args: VpnArgs) {
Log.d(TAG, "Starting TunVpnService directly")
Log.d(TAG, "Starting TunVpnService")
val intent = Intent(activity, TunVpnService::class.java).apply { val intent = Intent(activity, TunVpnService::class.java).apply {
action = "START_VPN" action = "START_VPN"
@@ -240,15 +278,13 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
putExtra("config", args.config) putExtra("config", args.config)
} }
val result = activity.startService(intent) // Для современных Android (API 26+) используем Foreground, для старых - обычный.
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
if (result == null) { activity.startForegroundService(intent)
Log.e(TAG, "startService returned null")
} else { } else {
Log.d(TAG, "TunVpnService start requested") activity.startService(intent)
} }
} }
@ActivityCallback @ActivityCallback
fun onVpnPermissionResult(invoke: Invoke, result: ActivityResult) { fun onVpnPermissionResult(invoke: Invoke, result: ActivityResult) {
@@ -1,3 +1,5 @@
use std::sync::atomic::Ordering;
use crate::{models::*, Vpn, VpnInterface}; use crate::{models::*, Vpn, VpnInterface};
use tauri::{command, AppHandle, Manager, Runtime}; use tauri::{command, AppHandle, Manager, Runtime};
@@ -8,13 +10,40 @@ pub(crate) fn start_vpn<R: Runtime>(
ip: String, ip: String,
port: i32, port: i32,
) -> crate::Result<()> { ) -> crate::Result<()> {
// Получаем состояние Vpn<R> и вызываем метод интерфейса let vpn = app.state::<Vpn<R>>();
eprintln!("--- [DEBUG] START_VPN COMMAND CALLED! ---");
app.state::<Vpn<R>>().start_vpn(config, ip, port) // Пытаемся захватить флаг
if vpn
.is_running
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_err()
{
eprintln!("[DEBUG] VPN уже запущен, игнорирую вызов.");
return Ok(());
}
eprintln!("--- [DEBUG] STARTING VPN TUNNEL ---");
match vpn.start_vpn(config, ip, port) {
Ok(res) => Ok(res),
Err(e) => {
// При ошибке возвращаем флаг в false
vpn.is_running.store(false, Ordering::SeqCst);
Err(e)
}
}
} }
#[command] #[command]
pub(crate) fn stop_vpn<R: Runtime>(app: AppHandle<R>) -> crate::Result<()> { pub(crate) fn stop_vpn<R: Runtime>(app: AppHandle<R>) -> crate::Result<()> {
// Получаем состояние Vpn<R> и вызываем метод интерфейса let vpn = app.state::<Vpn<R>>();
app.state::<Vpn<R>>().stop_vpn()
// Останавливаем
let result = vpn.stop_vpn();
// Сбрасываем флаг, даже если ошибка при остановке (чтобы не залочить приложение)
vpn.is_running.store(false, Ordering::SeqCst);
eprintln!("--- [DEBUG] STOPPING VPN TUNNEL ---");
result
} }
@@ -15,7 +15,6 @@ pub struct Vpn<R: Runtime>(AppHandle<R>);
impl<R: Runtime> VpnInterface<R> for Vpn<R> { impl<R: Runtime> VpnInterface<R> for Vpn<R> {
fn start_vpn(&self, _config: TunnelConfig, _ip: String, _port: i32) -> crate::Result<()> { fn start_vpn(&self, _config: TunnelConfig, _ip: String, _port: i32) -> crate::Result<()> {
// Логика для ПК: например, запуск системного процесса
Ok(()) Ok(())
} }
+10 -4
View File
@@ -1,4 +1,4 @@
use std::ops::Deref; use std::{ops::Deref, sync::atomic::AtomicBool};
use tauri::{ use tauri::{
plugin::{Builder, TauriPlugin}, plugin::{Builder, TauriPlugin},
Manager, Runtime, Manager, Runtime,
@@ -22,11 +22,17 @@ pub trait VpnInterface<R: Runtime>: Send + Sync + 'static {
fn start_vpn(&self, config: TunnelConfig, ip: String, port: i32) -> crate::Result<()>; fn start_vpn(&self, config: TunnelConfig, ip: String, port: i32) -> crate::Result<()>;
fn stop_vpn(&self) -> crate::Result<()>; fn stop_vpn(&self) -> crate::Result<()>;
} }
pub struct Vpn<R: Runtime>(Box<dyn VpnInterface<R>>); pub struct Vpn<R: Runtime> {
inner: Box<dyn VpnInterface<R>>,
pub is_running: AtomicBool, // Добавляем поле
}
impl<R: Runtime> Vpn<R> { impl<R: Runtime> Vpn<R> {
pub fn new<I: VpnInterface<R>>(inner: I) -> Self { pub fn new<I: VpnInterface<R>>(inner: I) -> Self {
Self(Box::new(inner)) Self {
inner: Box::new(inner),
is_running: AtomicBool::new(false),
}
} }
} }
@@ -34,7 +40,7 @@ impl<R: Runtime> Deref for Vpn<R> {
type Target = dyn VpnInterface<R>; type Target = dyn VpnInterface<R>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&*self.0 &*self.inner // Теперь обращаемся к полю inner
} }
} }