plugin fixes
This commit is contained in:
@@ -44,6 +44,9 @@ data class TunnelConfig @JsonCreator constructor(
|
||||
class TunVpnService : VpnService() {
|
||||
private var activeSession: Session? = null
|
||||
private var vpnInterface: ParcelFileDescriptor? = null
|
||||
|
||||
// ДОБАВЛЕНО: Корутина для статистики живет теперь здесь
|
||||
private var statsJob: Job? = null
|
||||
|
||||
companion object {
|
||||
const val ACTION_STOP_VPN = "app.netrunner.vpn.STOP_FROM_NOTIFICATION"
|
||||
@@ -84,6 +87,9 @@ class TunVpnService : VpnService() {
|
||||
activeSession = manager.spawnSession("$ip:$port", fd, cacheDir.absolutePath)
|
||||
triggerVibration(true)
|
||||
sendStatusBroadcast("connected")
|
||||
|
||||
// ДОБАВЛЕНО: Запускаем цикл статистики при успешном коннекте
|
||||
startStatsLoop()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Rust fail", e)
|
||||
stopServiceProperly()
|
||||
@@ -91,6 +97,8 @@ class TunVpnService : VpnService() {
|
||||
}
|
||||
|
||||
private fun stopServiceProperly() {
|
||||
stopStatsLoop() // ДОБАВЛЕНО: Останавливаем цикл при дисконнекте
|
||||
|
||||
activeSession?.stop()
|
||||
activeSession = null
|
||||
try { vpnInterface?.close() } catch (e: Exception) { }
|
||||
@@ -101,6 +109,45 @@ 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) {
|
||||
try {
|
||||
val stats = SessionManager().getTrafficStats()
|
||||
sendStatsBroadcast(
|
||||
stats.rxBytes.toLong(),
|
||||
stats.txBytes.toLong(),
|
||||
stats.rxPackets.toLong(),
|
||||
stats.txPackets.toLong()
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Stats loop error", e)
|
||||
}
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopStatsLoop() {
|
||||
statsJob?.cancel()
|
||||
statsJob = null
|
||||
}
|
||||
|
||||
private fun sendStatsBroadcast(rxBytes: Long, txBytes: Long, rxPackets: Long, txPackets: Long) {
|
||||
sendBroadcast(Intent("com.netrunner.vpn.TRAFFIC_STATS_UPDATE").apply {
|
||||
putExtra("rxBytes", rxBytes)
|
||||
putExtra("txBytes", txBytes)
|
||||
putExtra("rxPackets", rxPackets)
|
||||
putExtra("txPackets", txPackets)
|
||||
setPackage(packageName) // Безопасность: шлем только в свое приложение
|
||||
})
|
||||
}
|
||||
// -----------------------------------------
|
||||
|
||||
private fun sendStatusBroadcast(status: String) {
|
||||
sendBroadcast(Intent("com.netrunner.vpn.STATUS_UPDATE").apply {
|
||||
putExtra("status", status)
|
||||
@@ -132,23 +179,13 @@ class TunVpnService : VpnService() {
|
||||
|
||||
val stopIcon = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
android.graphics.drawable.Icon.createWithResource(this, android.R.drawable.ic_menu_close_clear_cancel)
|
||||
} else {
|
||||
null // Для совсем древних версий можно передать null или оставить старый метод
|
||||
}
|
||||
} else null
|
||||
|
||||
val stopAction = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && stopIcon != null) {
|
||||
Notification.Action.Builder(
|
||||
stopIcon,
|
||||
"Отключить",
|
||||
stopPI
|
||||
).build()
|
||||
Notification.Action.Builder(stopIcon, "Отключить", stopPI).build()
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
Notification.Action(
|
||||
android.R.drawable.ic_menu_close_clear_cancel,
|
||||
"Отключить",
|
||||
stopPI
|
||||
)
|
||||
Notification.Action(android.R.drawable.ic_menu_close_clear_cancel, "Отключить", stopPI)
|
||||
}
|
||||
|
||||
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -163,16 +200,12 @@ class TunVpnService : VpnService() {
|
||||
val notification = builder
|
||||
.setContentTitle("Netrunner VPN")
|
||||
.setContentText("Защита активна")
|
||||
|
||||
.setSmallIcon(android.R.drawable.ic_lock_lock)
|
||||
.setContentIntent(contentPI)
|
||||
.setOngoing(true)
|
||||
.addAction(stopAction)
|
||||
|
||||
|
||||
.setColor(vpnBlack)
|
||||
.setColorized(true)
|
||||
|
||||
.setVisibility(Notification.VISIBILITY_PUBLIC)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.build()
|
||||
@@ -195,28 +228,19 @@ class TunVpnService : VpnService() {
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, -1))
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -225,17 +249,33 @@ class TunVpnService : VpnService() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
stopStatsLoop() // На всякий случай
|
||||
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() {
|
||||
// ДОБАВЛЕНО: Теперь плагин слушает ДВА типа бродкастов (статус и трафик)
|
||||
private val broadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
intent?.getStringExtra("status")?.let { updateStatus(it) }
|
||||
when (intent?.action) {
|
||||
"com.netrunner.vpn.STATUS_UPDATE" -> {
|
||||
intent.getStringExtra("status")?.let { updateStatus(it) }
|
||||
}
|
||||
"com.netrunner.vpn.TRAFFIC_STATS_UPDATE" -> {
|
||||
val result = JSObject().apply {
|
||||
put("rxBytes", intent.getLongExtra("rxBytes", 0L))
|
||||
put("txBytes", intent.getLongExtra("txBytes", 0L))
|
||||
put("rxPackets", intent.getLongExtra("rxPackets", 0L))
|
||||
put("txPackets", intent.getLongExtra("txPackets", 0L))
|
||||
}
|
||||
// Пересылаем в UI
|
||||
trigger("traffic-stats", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +284,6 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
val permission = "android.permission.POST_NOTIFICATIONS"
|
||||
if (androidx.core.content.ContextCompat.checkSelfPermission(activity, permission) !=
|
||||
android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
androidx.core.app.ActivityCompat.requestPermissions(activity, arrayOf(permission), 101)
|
||||
}
|
||||
}
|
||||
@@ -254,9 +293,7 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
try {
|
||||
val cachePath = activity.cacheDir.absolutePath
|
||||
val hostsFile = File(cachePath, "hosts_cache.txt")
|
||||
|
||||
val isFresh = hostsFile.exists() &&
|
||||
(System.currentTimeMillis() - hostsFile.lastModified() < 24 * 60 * 60 * 1000)
|
||||
val isFresh = hostsFile.exists() && (System.currentTimeMillis() - hostsFile.lastModified() < 24 * 60 * 60 * 1000)
|
||||
|
||||
if (isFresh) {
|
||||
Log.d(TAG, "DNS: Blocklist is fresh, skipping download")
|
||||
@@ -264,18 +301,12 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
}
|
||||
|
||||
Log.d(TAG, "DNS: Starting blocklist download via Kotlin...")
|
||||
|
||||
// Качаем
|
||||
val url = URL("https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts")
|
||||
val connection = url.openConnection()
|
||||
connection.connectTimeout = 15000
|
||||
connection.readTimeout = 15000
|
||||
|
||||
val bytes = connection.getInputStream().use { it.readBytes() }
|
||||
|
||||
// Сохраняем
|
||||
hostsFile.writeBytes(bytes)
|
||||
|
||||
Log.d(TAG, "DNS: Blocklist updated! Size: ${bytes.size} bytes")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "DNS: Failed to download blocklist: ${e.message}")
|
||||
@@ -287,16 +318,20 @@ class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
try { System.loadLibrary("netrunner_client") } catch (e: Exception) { Log.e(TAG, "Lib fail") }
|
||||
|
||||
requestNotificationPermission()
|
||||
val filter = IntentFilter("com.netrunner.vpn.STATUS_UPDATE")
|
||||
|
||||
// ДОБАВЛЕНО: Регистрируем фильтр на оба действия
|
||||
val filter = IntentFilter().apply {
|
||||
addAction("com.netrunner.vpn.STATUS_UPDATE")
|
||||
addAction("com.netrunner.vpn.TRAFFIC_STATS_UPDATE")
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
activity.registerReceiver(statusReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
||||
activity.registerReceiver(broadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
||||
} else {
|
||||
activity.registerReceiver(statusReceiver, filter)
|
||||
activity.registerReceiver(broadcastReceiver, filter)
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
updateBlocklistIfNeeded()
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch { updateBlocklistIfNeeded() }
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
kotlinx.coroutines.delay(500)
|
||||
@@ -365,51 +400,7 @@ 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) })
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user