prod works
This commit is contained in:
+3
-1
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user