telegram free connection
This commit is contained in:
@@ -2,8 +2,6 @@ package app.netrunner.vpn
|
||||
|
||||
import android.app.*
|
||||
import android.content.*
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.VpnService
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
@@ -42,14 +40,14 @@ data class TunnelConfig @JsonCreator constructor(
|
||||
@JsonProperty("mtu") val mtu: Int,
|
||||
@JsonProperty("killswitchEnabled") val killswitchEnabled: Boolean,
|
||||
@JsonProperty("excludedApps") val excludedApps: List<String>,
|
||||
@JsonProperty("excludedDomains") val excludedDomains: List<String>
|
||||
@JsonProperty("excludedDomains") val excludedDomains: List<String>,
|
||||
// Новое поле для режима песочницы
|
||||
@JsonProperty("allowedApps") val allowedApps: List<String>? = null
|
||||
) : Parcelable
|
||||
|
||||
class TunVpnService : VpnService() {
|
||||
private var activeSession: Session? = null
|
||||
private var vpnInterface: ParcelFileDescriptor? = null
|
||||
|
||||
// ДОБАВЛЕНО: Корутина для статистики живет теперь здесь
|
||||
private var statsJob: Job? = null
|
||||
|
||||
companion object {
|
||||
@@ -81,25 +79,41 @@ class TunVpnService : VpnService() {
|
||||
val builder = Builder().addAddress(config.address, config.prefix)
|
||||
.addDnsServer(config.dns).addRoute("0.0.0.0", 0).setMtu(config.mtu)
|
||||
|
||||
for (appPkg in config.excludedApps) {
|
||||
if (appPkg.isNotBlank()) {
|
||||
try {
|
||||
builder.addDisallowedApplication(appPkg.trim())
|
||||
Log.d(TAG, "Excluded app from VPN tunnel: $appPkg")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to exclude app $appPkg (possibly not installed)", e)
|
||||
val allowed = config.allowedApps ?: emptyList()
|
||||
|
||||
// ЛОГИКА ФИЛЬТРАЦИИ
|
||||
if (allowed.isNotEmpty()) {
|
||||
// Режим песочницы (Allow-list) - только для Telegram
|
||||
for (appPkg in allowed) {
|
||||
if (appPkg.isNotBlank()) {
|
||||
try {
|
||||
builder.addAllowedApplication(appPkg.trim())
|
||||
Log.d(TAG, "Allowed app in VPN tunnel: $appPkg")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to allow app $appPkg (possibly not installed)", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Обычный режим (Exclude-list)
|
||||
for (appPkg in config.excludedApps) {
|
||||
if (appPkg.isNotBlank()) {
|
||||
try {
|
||||
builder.addDisallowedApplication(appPkg.trim())
|
||||
Log.d(TAG, "Excluded app from VPN tunnel: $appPkg")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to exclude app $appPkg (possibly not installed)", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
try { builder.addDisallowedApplication(packageName) } catch (e: Exception) { Log.e(TAG, "Exclude fail") }
|
||||
}
|
||||
|
||||
try { builder.addDisallowedApplication(packageName) } catch (e: Exception) { Log.e(TAG, "Exclude fail") }
|
||||
|
||||
vpnInterface = builder.establish() ?: return
|
||||
val fd = vpnInterface!!.detachFd()
|
||||
|
||||
try {
|
||||
val manager = SessionManager()
|
||||
// Передаем параметры в ядро Rust
|
||||
activeSession = manager.spawnSession(
|
||||
"$ip:$port",
|
||||
fd,
|
||||
@@ -110,7 +124,6 @@ class TunVpnService : VpnService() {
|
||||
)
|
||||
triggerVibration(true)
|
||||
sendStatusBroadcast("connected")
|
||||
|
||||
startStatsLoop()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Rust fail", e)
|
||||
@@ -119,8 +132,7 @@ class TunVpnService : VpnService() {
|
||||
}
|
||||
|
||||
private fun stopServiceProperly() {
|
||||
stopStatsLoop() // ДОБАВЛЕНО: Останавливаем цикл при дисконнекте
|
||||
|
||||
stopStatsLoop()
|
||||
activeSession?.stop()
|
||||
activeSession = null
|
||||
try { vpnInterface?.close() } catch (e: Exception) { }
|
||||
@@ -131,10 +143,8 @@ class TunVpnService : VpnService() {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
// --- НОВЫЙ БЛОК КОРУТИН ВНУТРИ СЕРВИСА ---
|
||||
private fun startStatsLoop() {
|
||||
if (statsJob?.isActive == true) return
|
||||
|
||||
Log.d(TAG, "Starting traffic stats loop IN SERVICE")
|
||||
statsJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
while (isActive) {
|
||||
@@ -165,10 +175,9 @@ class TunVpnService : VpnService() {
|
||||
putExtra("txBytes", txBytes)
|
||||
putExtra("rxPackets", rxPackets)
|
||||
putExtra("txPackets", txPackets)
|
||||
setPackage(packageName) // Безопасность: шлем только в свое приложение
|
||||
setPackage(packageName)
|
||||
})
|
||||
}
|
||||
// -----------------------------------------
|
||||
|
||||
private fun sendStatusBroadcast(status: String) {
|
||||
sendBroadcast(Intent("com.netrunner.vpn.STATUS_UPDATE").apply {
|
||||
@@ -217,6 +226,7 @@ class TunVpnService : VpnService() {
|
||||
Notification.Builder(this)
|
||||
}
|
||||
|
||||
// ВОЗВРАЩЕНО ТВОЕ КАСТОМНОЕ УВЕДОМЛЕНИЕ!
|
||||
val vpnBlack = 0xFF121212.toInt()
|
||||
|
||||
val notification = builder
|
||||
@@ -248,30 +258,30 @@ class TunVpnService : VpnService() {
|
||||
getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
}
|
||||
|
||||
if (!vibrator.hasVibrator()) return
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val fixedDelay = 30L
|
||||
val stepCount = 23
|
||||
val timings = LongArray(stepCount * 2)
|
||||
val amplitudes = IntArray(stepCount * 2)
|
||||
val timings: LongArray
|
||||
val amplitudes: IntArray
|
||||
|
||||
for (i in 0 until stepCount) {
|
||||
val progress = i.toFloat() / (stepCount - 1)
|
||||
val activeTime = if (isAscending) (10 + (progress * 50)).toLong() else (60 - (progress * 50)).toLong()
|
||||
|
||||
timings[i * 2] = fixedDelay
|
||||
timings[i * 2 + 1] = activeTime
|
||||
amplitudes[i * 2] = 0
|
||||
amplitudes[i * 2 + 1] = 255
|
||||
if (isAscending) {
|
||||
timings = longArrayOf(0, 30, 40, 40, 30, 50, 20, 70)
|
||||
amplitudes = intArrayOf(0, 40, 0, 80, 0, 150, 0, 255)
|
||||
} else {
|
||||
timings = longArrayOf(0, 50, 30, 30, 40, 20)
|
||||
amplitudes = intArrayOf(0, 255, 0, 100, 0, 30)
|
||||
}
|
||||
vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, -1))
|
||||
|
||||
val effect = VibrationEffect.createWaveform(timings, amplitudes, -1)
|
||||
vibrator.vibrate(effect)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
vibrator.vibrate(1500)
|
||||
vibrator.vibrate(if (isAscending) 1200L else 300L)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
stopStatsLoop() // На всякий случай
|
||||
stopStatsLoop()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -280,7 +290,6 @@ class TunVpnService : VpnService() {
|
||||
class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
private var pendingArgs: VpnArgs? = null
|
||||
|
||||
// ДОБАВЛЕНО: Теперь плагин слушает ДВА типа бродкастов (статус и трафик)
|
||||
private val broadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
@@ -294,7 +303,6 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
put("rxPackets", intent.getLongExtra("rxPackets", 0L))
|
||||
put("txPackets", intent.getLongExtra("txPackets", 0L))
|
||||
}
|
||||
// Пересылаем в UI
|
||||
trigger("traffic-stats", result)
|
||||
}
|
||||
}
|
||||
@@ -302,7 +310,7 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
}
|
||||
|
||||
private fun requestNotificationPermission() {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 33) {
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
val permission = "android.permission.POST_NOTIFICATIONS"
|
||||
if (androidx.core.content.ContextCompat.checkSelfPermission(activity, permission) !=
|
||||
android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
||||
@@ -311,6 +319,7 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
}
|
||||
}
|
||||
|
||||
// ВОЗВРАЩЕНО ТВОЕ ЛОГИРОВАНИЕ СКАЧИВАНИЯ БЛОКЛИСТА
|
||||
private fun updateBlocklistIfNeeded() {
|
||||
try {
|
||||
val cachePath = activity.cacheDir.absolutePath
|
||||
@@ -341,7 +350,6 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
|
||||
requestNotificationPermission()
|
||||
|
||||
// ДОБАВЛЕНО: Регистрируем фильтр на оба действия
|
||||
val filter = IntentFilter().apply {
|
||||
addAction("com.netrunner.vpn.STATUS_UPDATE")
|
||||
addAction("com.netrunner.vpn.TRAFFIC_STATS_UPDATE")
|
||||
@@ -356,7 +364,7 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
CoroutineScope(Dispatchers.IO).launch { updateBlocklistIfNeeded() }
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
kotlinx.coroutines.delay(500)
|
||||
delay(500)
|
||||
val currentStatus = getHardwareVpnStatus()
|
||||
updateStatus(currentStatus)
|
||||
Log.d(TAG, "Plugin reloaded. Current status: $currentStatus")
|
||||
@@ -397,7 +405,10 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
|
||||
private fun startVpnService(args: VpnArgs) {
|
||||
val intent = Intent(activity, TunVpnService::class.java).apply {
|
||||
action = "START_VPN"; putExtra("ip", args.ip); putExtra("port", args.port); putExtra("config", args.config)
|
||||
action = "START_VPN"
|
||||
putExtra("ip", args.ip)
|
||||
putExtra("port", args.port)
|
||||
putExtra("config", args.config)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) activity.startForegroundService(intent)
|
||||
else activity.startService(intent)
|
||||
@@ -422,45 +433,54 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
invoke.resolve(JSObject().apply { put("status", status) })
|
||||
}
|
||||
|
||||
// ВОЗВРАЩЕНО ТВОЕ ЛОГИРОВАНИЕ ПРИ ПОИСКЕ ПРИЛОЖЕНИЙ
|
||||
@Command
|
||||
fun getInstalledApps(invoke: Invoke) {
|
||||
val pm = activity.packageManager
|
||||
// Используем флаг GET_META_DATA для более полной информации
|
||||
val packages = pm.getInstalledPackages(0)
|
||||
val jsArray = JSArray()
|
||||
|
||||
for (pkg in packages) {
|
||||
val info = pkg.applicationInfo ?: continue
|
||||
|
||||
// Условие 1: Это не наше собственное приложение
|
||||
if (pkg.packageName == activity.packageName) continue
|
||||
|
||||
// Условие 2: Это пользовательское приложение ИЛИ оно имеет иконку в лаунчере
|
||||
val isSystem = (info.flags and android.content.pm.ApplicationInfo.FLAG_SYSTEM) != 0
|
||||
val hasLauncher = pm.getLaunchIntentForPackage(pkg.packageName) != null
|
||||
|
||||
// Если это не системное приложение (установлено юзером) ИЛИ у него есть лаунчер
|
||||
if (!isSystem || hasLauncher) {
|
||||
val appName = info.loadLabel(pm).toString()
|
||||
|
||||
val obj = JSObject()
|
||||
obj.put("appName", appName)
|
||||
obj.put("packageName", pkg.packageName)
|
||||
|
||||
jsArray.put(obj)
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Found ${jsArray.length()} apps for exclusion list")
|
||||
|
||||
val result = JSObject()
|
||||
result.put("apps", jsArray)
|
||||
invoke.resolve(result)
|
||||
}
|
||||
|
||||
// НОВАЯ ФУНКЦИЯ ДЛЯ ЗАПУСКА ТЕЛЕГИ ИЗ REACT
|
||||
@Command
|
||||
fun launchApp(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(LaunchAppArgs::class.java)
|
||||
val launchIntent = activity.packageManager.getLaunchIntentForPackage(args.packageName)
|
||||
if (launchIntent != null) {
|
||||
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
activity.startActivity(launchIntent)
|
||||
invoke.resolve()
|
||||
} else {
|
||||
invoke.reject("App not found")
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateStatus(status: String) {
|
||||
trigger("status-change", JSObject().apply { put("status", status) })
|
||||
}
|
||||
|
||||
@InvokeArg class VpnArgs { var config: TunnelConfig? = null; var ip: String = ""; var port: Int = 0 }
|
||||
@InvokeArg class LaunchAppArgs { var packageName: String = "" }
|
||||
}
|
||||
@@ -4,6 +4,10 @@ const COMMANDS: &[&str] = &[
|
||||
"check_actual_vpn_status",
|
||||
"registerListener",
|
||||
"unregisterListener",
|
||||
"get_installed_apps", // <--- ДОБАВЛЕНО
|
||||
"launch_app", // <--- ДОБАВЛЕНО
|
||||
"get_traffic_stats", // <--- ДОБАВЛЕНО
|
||||
"traffic_stats", // <--- На всякий случай (было в старых логах)
|
||||
];
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { addPluginListener, invoke } from "@tauri-apps/api/core";
|
||||
|
||||
export interface TunnelConfig {
|
||||
address: string;
|
||||
prefix: number;
|
||||
@@ -7,6 +8,7 @@ export interface TunnelConfig {
|
||||
killswitchEnabled: boolean;
|
||||
excludedApps: string[];
|
||||
excludedDomains: string[];
|
||||
allowedApps?: string[];
|
||||
}
|
||||
|
||||
export interface TrafficStatsResponse {
|
||||
@@ -22,11 +24,7 @@ export interface AppInfo {
|
||||
}
|
||||
|
||||
export async function startVpn(config: TunnelConfig, ip: string, port: number) {
|
||||
return await invoke("plugin:vpn|start_vpn", {
|
||||
config,
|
||||
ip,
|
||||
port,
|
||||
});
|
||||
return await invoke("plugin:vpn|start_vpn", { config, ip, port });
|
||||
}
|
||||
|
||||
export async function stopVpn() {
|
||||
@@ -35,21 +33,17 @@ export async function stopVpn() {
|
||||
|
||||
export async function checkActualVpnStatus(): Promise<"connected" | "idle"> {
|
||||
try {
|
||||
console.log("Actual vpn status called");
|
||||
const result = await invoke<{ status: string }>(
|
||||
"plugin:vpn|check_actual_vpn_status",
|
||||
);
|
||||
console.log(result);
|
||||
return result.status as "connected" | "idle";
|
||||
} catch (e) {
|
||||
console.error("Failed to check VPN status", e);
|
||||
return "idle";
|
||||
}
|
||||
}
|
||||
|
||||
export const setupVpnStatusListener = async (callback: (p: any) => void) => {
|
||||
return await addPluginListener("vpn", "status-change", (payload) => {
|
||||
console.log(">>> [SUCCESS] Пришло через мобильный листенер:", payload);
|
||||
callback(payload);
|
||||
});
|
||||
};
|
||||
@@ -58,12 +52,15 @@ export const setupTrafficStatsListener = async (
|
||||
callback: (stats: TrafficStatsResponse) => void,
|
||||
) => {
|
||||
return await addPluginListener("vpn", "traffic-stats", (payload) => {
|
||||
console.log(">>> [SUCCESS] Пришло через мобильный листенер:", payload);
|
||||
callback(payload as TrafficStatsResponse);
|
||||
});
|
||||
};
|
||||
|
||||
export async function getInstalledApps(): Promise<AppInfo[]> {
|
||||
// Rust теперь сам распаковывает "apps" и отдает нам готовый массив
|
||||
return await invoke<AppInfo[]>("plugin:vpn|get_installed_apps");
|
||||
}
|
||||
|
||||
// НОВАЯ ФУНКЦИЯ ДЛЯ ЗАПУСКА ТЕЛЕГИ
|
||||
export async function launchApp(packageName: string): Promise<void> {
|
||||
return await invoke("plugin:vpn|launch_app", { packageName });
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-get-installed-apps"
|
||||
description = "Enables the get_installed_apps command without any pre-configured scope."
|
||||
commands.allow = ["get_installed_apps"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-get-installed-apps"
|
||||
description = "Denies the get_installed_apps command without any pre-configured scope."
|
||||
commands.deny = ["get_installed_apps"]
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-launch-app"
|
||||
description = "Enables the launch_app command without any pre-configured scope."
|
||||
commands.allow = ["launch_app"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-launch-app"
|
||||
description = "Denies the launch_app command without any pre-configured scope."
|
||||
commands.deny = ["launch_app"]
|
||||
@@ -36,6 +36,32 @@ Denies the check_actual_vpn_status command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vpn:allow-get-installed-apps`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the get_installed_apps command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vpn:deny-get-installed-apps`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the get_installed_apps command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vpn:allow-get-traffic-stats`
|
||||
|
||||
</td>
|
||||
@@ -62,6 +88,32 @@ Denies the get_traffic_stats command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vpn:allow-launch-app`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the launch_app command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vpn:deny-launch-app`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the launch_app command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vpn:allow-registerListener`
|
||||
|
||||
</td>
|
||||
|
||||
@@ -3,7 +3,7 @@ identifier = "allow-vpn-control"
|
||||
description = "Allows Vpn Control"
|
||||
|
||||
[permission.commands]
|
||||
allow = ["start_vpn", "stop_vpn", "get_traffic_stats", "check_actual_vpn_status", "registerListener", "unregisterListener", "get_installed_apps"]
|
||||
allow = ["start_vpn", "stop_vpn", "get_traffic_stats", "check_actual_vpn_status", "registerListener", "unregisterListener", "get_installed_apps", "launch_app"]
|
||||
|
||||
[permission.events]
|
||||
allow = ["status-change"]
|
||||
@@ -306,6 +306,18 @@
|
||||
"const": "deny-check-actual-vpn-status",
|
||||
"markdownDescription": "Denies the check_actual_vpn_status command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_installed_apps command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-get-installed-apps",
|
||||
"markdownDescription": "Enables the get_installed_apps command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_installed_apps command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-get-installed-apps",
|
||||
"markdownDescription": "Denies the get_installed_apps command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_traffic_stats command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -318,6 +330,18 @@
|
||||
"const": "deny-get-traffic-stats",
|
||||
"markdownDescription": "Denies the get_traffic_stats command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the launch_app command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-launch-app",
|
||||
"markdownDescription": "Enables the launch_app command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the launch_app command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-launch-app",
|
||||
"markdownDescription": "Denies the launch_app command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the registerListener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::{models::*, Vpn};
|
||||
use std::sync::atomic::Ordering;
|
||||
use tauri::{command, AppHandle, Manager, Runtime};
|
||||
|
||||
#[command]
|
||||
@@ -11,23 +10,16 @@ pub(crate) fn start_vpn<R: Runtime>(
|
||||
port: i32,
|
||||
) -> crate::Result<()> {
|
||||
let vpn = app.state::<Vpn<R>>();
|
||||
|
||||
// Пытаемся захватить флаг
|
||||
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)
|
||||
}
|
||||
@@ -37,14 +29,8 @@ pub(crate) fn start_vpn<R: Runtime>(
|
||||
#[command]
|
||||
pub(crate) fn stop_vpn<R: Runtime>(app: AppHandle<R>) -> crate::Result<()> {
|
||||
let vpn = app.state::<Vpn<R>>();
|
||||
|
||||
// Останавливаем
|
||||
let result = vpn.stop_vpn();
|
||||
|
||||
// Сбрасываем флаг, даже если ошибка при остановке (чтобы не залочить приложение)
|
||||
vpn.is_running.store(false, Ordering::SeqCst);
|
||||
|
||||
eprintln!("--- [DEBUG] STOPPING VPN TUNNEL ---");
|
||||
result
|
||||
}
|
||||
|
||||
@@ -52,10 +38,7 @@ pub(crate) fn stop_vpn<R: Runtime>(app: AppHandle<R>) -> crate::Result<()> {
|
||||
pub(crate) fn check_actual_vpn_status<R: tauri::Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> crate::Result<StatusResponse> {
|
||||
// StatusResponse должен быть в твоих моделях
|
||||
let vpn = app.state::<Vpn<R>>();
|
||||
|
||||
// Вызываем метод из vpn_impl (в mobile это уйдет в Kotlin)
|
||||
vpn.check_actual_vpn_status()
|
||||
}
|
||||
|
||||
@@ -63,3 +46,8 @@ pub(crate) fn check_actual_vpn_status<R: tauri::Runtime>(
|
||||
pub(crate) fn get_installed_apps<R: Runtime>(app: AppHandle<R>) -> crate::Result<Vec<AppInfo>> {
|
||||
app.state::<Vpn<R>>().get_installed_apps()
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) fn launch_app<R: Runtime>(app: AppHandle<R>, package_name: String) -> crate::Result<()> {
|
||||
app.state::<Vpn<R>>().launch_app(package_name)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ impl<R: Runtime> Clone 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<()> {
|
||||
let _ = self.app.emit(
|
||||
"status-change",
|
||||
StatusResponse {
|
||||
@@ -132,4 +132,8 @@ impl<R: Runtime> VpnInterface<R> for Vpn<R> {
|
||||
fn get_installed_apps(&self) -> crate::Result<Vec<AppInfo>> {
|
||||
Ok(vec![]) // На десктопе пока отдаем пустоту
|
||||
}
|
||||
|
||||
fn launch_app(&self, _package_name: String) -> crate::Result<()> {
|
||||
Ok(()) // На десктопе мы блокируем это через UI, так что просто Ok
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ pub trait VpnInterface<R: Runtime>: Send + Sync + 'static {
|
||||
fn stop_vpn(&self) -> crate::Result<()>;
|
||||
fn check_actual_vpn_status(&self) -> crate::Result<StatusResponse>;
|
||||
fn get_installed_apps(&self) -> crate::Result<Vec<AppInfo>>;
|
||||
fn launch_app(&self, package_name: String) -> crate::Result<()>;
|
||||
}
|
||||
pub struct Vpn<R: Runtime> {
|
||||
inner: Box<dyn VpnInterface<R>>,
|
||||
@@ -52,7 +53,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
commands::start_vpn,
|
||||
commands::stop_vpn,
|
||||
commands::check_actual_vpn_status,
|
||||
commands::get_installed_apps, // <--- НОВОЕ
|
||||
commands::get_installed_apps,
|
||||
commands::launch_app, // <--- НОВОЕ
|
||||
])
|
||||
.setup(|app, api| {
|
||||
#[cfg(mobile)]
|
||||
|
||||
@@ -35,41 +35,31 @@ impl<R: Runtime> VpnInterface<R> for Vpn<R> {
|
||||
}
|
||||
|
||||
fn check_actual_vpn_status(&self) -> crate::Result<StatusResponse> {
|
||||
println!(">>> [RUST PLUGIN] Calling Kotlin: checkActualVpnStatus");
|
||||
self.0
|
||||
.run_mobile_plugin::<StatusResponse>("checkActualVpnStatus", ())
|
||||
.map(|res| {
|
||||
println!(">>> [RUST PLUGIN] Success from Kotlin: {:?}", res);
|
||||
res
|
||||
})
|
||||
.map_err(|e| {
|
||||
println!(">>> [RUST PLUGIN] Critical Error calling Kotlin: {:?}", e);
|
||||
let err_msg = format!("{:?}", e);
|
||||
if err_msg.contains("CommandNotFound") {
|
||||
println!(
|
||||
"!!! [RUST PLUGIN] ДИАГНОЗ: Метод checkActualVpnStatus не найден в Kotlin."
|
||||
);
|
||||
}
|
||||
|
||||
crate::Error::from(e)
|
||||
})
|
||||
.map_err(crate::Error::from)
|
||||
}
|
||||
|
||||
fn get_installed_apps(&self) -> crate::Result<Vec<AppInfo>> {
|
||||
// Создаем временную структуру для распаковки JSObject, который шлет Kotlin
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Wrapper {
|
||||
apps: Vec<AppInfo>,
|
||||
}
|
||||
|
||||
let wrapper: Wrapper = self
|
||||
.0
|
||||
.run_mobile_plugin("getInstalledApps", ())
|
||||
.map_err(crate::error::Error::from)?;
|
||||
|
||||
// Отдаем наружу (в React) чистый массив
|
||||
Ok(wrapper.apps)
|
||||
}
|
||||
|
||||
fn launch_app(&self, package_name: String) -> crate::Result<()> {
|
||||
#[derive(serde::Serialize)]
|
||||
struct Payload {
|
||||
#[serde(rename = "packageName")]
|
||||
package_name: String,
|
||||
}
|
||||
self.send("launchApp", Payload { package_name })
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Vpn<R> {
|
||||
|
||||
@@ -7,10 +7,11 @@ pub struct TunnelConfig {
|
||||
pub prefix: i32,
|
||||
pub dns: String,
|
||||
pub mtu: i32,
|
||||
// Новые параметры
|
||||
pub killswitch_enabled: bool,
|
||||
pub excluded_apps: Vec<String>,
|
||||
pub excluded_domains: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub allowed_apps: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
@@ -31,7 +32,7 @@ pub struct StatusResponse {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")] // Чтобы в JS поля были rxBytes, txBytes и т.д.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TrafficStatsResponse {
|
||||
pub rx_bytes: u64,
|
||||
pub tx_bytes: u64,
|
||||
|
||||
Reference in New Issue
Block a user