This commit is contained in:
2026-03-27 17:34:40 +07:00
parent 46b7f5dfa5
commit 17649bd1c0
15 changed files with 280 additions and 35 deletions
@@ -27,6 +27,10 @@ import uniffi.netrunner_client.SessionManager
import java.io.File
import java.net.URL
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
private const val TAG = "VPN_DEBUG"
@Parcelize
@@ -224,10 +228,10 @@ class TunVpnService : VpnService() {
super.onDestroy()
}
}
@TauriPlugin
class VpnPlugin(private val activity: Activity) : Plugin(activity) {
private var pendingArgs: VpnArgs? = null
private var statsJob: Job? = null // Переменная для управления корутиной статистики
private val statusReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
@@ -246,7 +250,6 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
}
}
private fun updateBlocklistIfNeeded() {
try {
val cachePath = activity.cacheDir.absolutePath
@@ -318,7 +321,6 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
}
} catch (e: Exception) { false }
// Если интерфейс поднят ИЛИ сервис числится в запущенных — значит мы connected
val isRunning = hasTun || isVpnServiceRunning() || TunVpnService.isServiceRunning
return if (isRunning) "connected" else "idle"
}
@@ -363,7 +365,51 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
invoke.resolve(JSObject().apply { put("status", status) })
}
// --- НОВЫЙ БЛОК: УПРАВЛЕНИЕ КОРОТУИНОЙ СТАТИСТИКИ ---
private fun startStatsLoop() {
// Защита от запуска нескольких корутин одновременно
if (statsJob?.isActive == true) return
Log.d(TAG, "Starting traffic stats loop")
statsJob = CoroutineScope(Dispatchers.IO).launch {
while (isActive) {
try {
val stats = SessionManager().getTrafficStats()
val result = JSObject().apply {
put("rxBytes", stats.rxBytes.toLong())
put("txBytes", stats.txBytes.toLong())
put("rxPackets", stats.rxPackets.toLong())
put("txPackets", stats.txPackets.toLong())
}
// Отправляем данные на фронтенд
trigger("traffic-stats", result)
} catch (e: Exception) {
Log.e(TAG, "Stats loop error", e)
}
delay(1000) // Пауза в 1 секунду
}
}
}
private fun stopStatsLoop() {
Log.d(TAG, "Stopping traffic stats loop")
statsJob?.cancel()
statsJob = null
}
// Обновленный метод updateStatus
private fun updateStatus(status: String) {
if (status == "connected") {
startStatsLoop()
} else {
stopStatsLoop()
}
trigger("status-change", JSObject().apply { put("status", status) })
}
@@ -6,6 +6,13 @@ export interface TunnelConfig {
mtu: number;
}
export interface TrafficStatsResponse {
rxBytes: number;
txBytes: number;
rxPackets: number;
txPackets: number;
}
export async function startVpn(config: TunnelConfig, ip: string, port: number) {
return await invoke("plugin:vpn|start_vpn", {
config,
@@ -38,3 +45,12 @@ export const setupVpnStatusListener = async (callback: (p: any) => void) => {
callback(payload);
});
};
export const setupTrafficStatsListener = async (
callback: (stats: TrafficStatsResponse) => void,
) => {
return await addPluginListener("vpn", "traffic-stats", (payload) => {
console.log(">>> [SUCCESS] Пришло через мобильный листенер:", payload);
callback(payload as TrafficStatsResponse);
});
};
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-get-traffic-stats"
description = "Enables the get_traffic_stats command without any pre-configured scope."
commands.allow = ["get_traffic_stats"]
[[permission]]
identifier = "deny-get-traffic-stats"
description = "Denies the get_traffic_stats command without any pre-configured scope."
commands.deny = ["get_traffic_stats"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-traffic-stats"
description = "Enables the traffic_stats command without any pre-configured scope."
commands.allow = ["traffic_stats"]
[[permission]]
identifier = "deny-traffic-stats"
description = "Denies the traffic_stats command without any pre-configured scope."
commands.deny = ["traffic_stats"]
@@ -36,6 +36,32 @@ Denies the check_actual_vpn_status command without any pre-configured scope.
<tr>
<td>
`vpn:allow-get-traffic-stats`
</td>
<td>
Enables the get_traffic_stats command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpn:deny-get-traffic-stats`
</td>
<td>
Denies the get_traffic_stats command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpn:allow-registerListener`
</td>
@@ -114,6 +140,32 @@ Denies the stop_vpn command without any pre-configured scope.
<tr>
<td>
`vpn:allow-traffic-stats`
</td>
<td>
Enables the traffic_stats command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpn:deny-traffic-stats`
</td>
<td>
Denies the traffic_stats command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`vpn:allow-unregisterListener`
</td>
@@ -3,7 +3,7 @@ identifier = "allow-vpn-control"
description = "Allows Vpn Control"
[permission.commands]
allow = ["start_vpn", "stop_vpn", "check_actual_vpn_status", "registerListener", "unregisterListener"]
allow = ["start_vpn", "stop_vpn", "get_traffic_stats", "check_actual_vpn_status", "registerListener", "unregisterListener"]
[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_traffic_stats command without any pre-configured scope.",
"type": "string",
"const": "allow-get-traffic-stats",
"markdownDescription": "Enables the get_traffic_stats command without any pre-configured scope."
},
{
"description": "Denies the get_traffic_stats command without any pre-configured scope.",
"type": "string",
"const": "deny-get-traffic-stats",
"markdownDescription": "Denies the get_traffic_stats command without any pre-configured scope."
},
{
"description": "Enables the registerListener command without any pre-configured scope.",
"type": "string",
@@ -342,6 +354,18 @@
"const": "deny-stop-vpn",
"markdownDescription": "Denies the stop_vpn command without any pre-configured scope."
},
{
"description": "Enables the traffic_stats command without any pre-configured scope.",
"type": "string",
"const": "allow-traffic-stats",
"markdownDescription": "Enables the traffic_stats command without any pre-configured scope."
},
{
"description": "Denies the traffic_stats command without any pre-configured scope.",
"type": "string",
"const": "deny-traffic-stats",
"markdownDescription": "Denies the traffic_stats command without any pre-configured scope."
},
{
"description": "Enables the unregisterListener command without any pre-configured scope.",
"type": "string",
@@ -1,6 +1,6 @@
use std::sync::atomic::Ordering;
use crate::{models::*, Vpn, VpnInterface};
use crate::{models::*, Vpn};
use tauri::{command, AppHandle, Manager, Runtime};
#[command]
+1 -1
View File
@@ -53,7 +53,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.invoke_handler(tauri::generate_handler![
commands::start_vpn,
commands::stop_vpn,
commands::check_actual_vpn_status
commands::check_actual_vpn_status,
])
.setup(|app, api| {
#[cfg(mobile)]
@@ -25,3 +25,12 @@ pub struct StopVpnRequest {}
pub struct StatusResponse {
pub status: String,
}
#[derive(Serialize, Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")] // Чтобы в JS поля были rxBytes, txBytes и т.д.
pub struct TrafficStatsResponse {
pub rx_bytes: u64,
pub tx_bytes: u64,
pub rx_packets: u64,
pub tx_packets: u64,
}