feat(app): OS session-expiry enforcement, session persistence, support sync

Enforce session expiry at the OS level (AlarmManager + handler) so the tunnel dies even if the UI process is killed; persist the session so a START_STICKY restart re-establishes it; sync immediately after a support top-up; slow the session beacon 5s/30s -> 60s/60s; drop the Always-on-VPN deep-link and the legacy 'ass' migration. Docs: CHANGELOG 3.3.4, privacy-policy log disclosure, README.
This commit is contained in:
esefxdz committed 2026-09-02 20:33:33 +03:00
1 parent dfeb7f161c
commit 9ac236a981
15 files changed
+447 -383

No files matched your search

+8 -8
View File
@@ -24,9 +24,9 @@ val dartExecutable = File(
"bin/cache/dart-sdk/bin/dart"
},
)
val flutterVlessPatchScript = File(
val flutterVlessRecoveryPatchScript = File(
projectRootDir,
"tool/patch_flutter_vless_allowed_apps.dart",
"tool/patch_flutter_vless_runtime_recovery.dart",
)
val flutterVlessSecureSocksPatchScript = File(
projectRootDir,
@@ -37,17 +37,17 @@ val flutterVlessSecureSocksScopeFixScript = File(
"tool/patch_flutter_vless_secure_socks_scope_fix.dart",
)
val patchFlutterVlessAllowedApps = tasks.register<Exec>("patchFlutterVlessAllowedApps") {
val patchFlutterVlessRuntimeRecovery = tasks.register<Exec>("patchFlutterVlessRuntimeRecovery") {
group = "build setup"
description = "Patch pinned flutter_vless_android 1.1.5 with ReVolt routing/runtime hardening"
description = "Recover stale Xray core and tun2socks socket before restart"
workingDir(projectRootDir)
doFirst {
if (!dartExecutable.isFile) {
throw GradleException("Flutter Dart executable not found: ${dartExecutable.absolutePath}")
}
if (!flutterVlessPatchScript.isFile) {
throw GradleException("flutter_vless patch script missing: ${flutterVlessPatchScript.absolutePath}")
if (!flutterVlessRecoveryPatchScript.isFile) {
throw GradleException("flutter_vless recovery patch script missing: ${flutterVlessRecoveryPatchScript.absolutePath}")
}
if (!File(projectRootDir, ".dart_tool/package_config.json").isFile) {
throw GradleException("Flutter package metadata missing. Run `flutter pub get` before Android compilation.")
@@ -57,7 +57,7 @@ val patchFlutterVlessAllowedApps = tasks.register<Exec>("patchFlutterVlessAllowe
commandLine(
dartExecutable.absolutePath,
"run",
flutterVlessPatchScript.absolutePath,
flutterVlessRecoveryPatchScript.absolutePath,
)
}
@@ -65,7 +65,7 @@ val patchFlutterVlessSecureSocks = tasks.register<Exec>("patchFlutterVlessSecure
group = "build setup"
description = "Add authenticated ephemeral SOCKS5 and fail-closed process recovery to pinned flutter_vless_android 1.1.5"
workingDir(projectRootDir)
dependsOn(patchFlutterVlessAllowedApps)
dependsOn(patchFlutterVlessRuntimeRecovery)
doFirst {
if (!dartExecutable.isFile) {
+3
View File
@@ -10,6 +10,9 @@
<!-- Doze/App Standby stop background services of non-exempt apps. A VPN
cannot stay up without this; Play allows it for VPN functionality. -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<!-- OS-level session-expiry alarm. Auto-granted on 31-32; on 33+ the daemon
falls back to setAndAllowWhileIdle if exact alarms aren't granted. -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<!-- flutter_vless 1.1.5 declares this legacy permission. ReVolt never needs it. -->
<uses-permission
@@ -61,10 +61,13 @@ class MainActivity : FlutterActivity() {
result.success(isIgnoringBatteryOptimizations())
"requestIgnoreBatteryOptimizations" ->
result.success(requestIgnoreBatteryOptimizations())
// Android's own Always-on VPN is the only thing that keeps a
// tunnel up across swipe-from-recents and reboot. It cannot be
// toggled programmatically, so deep-link the user to it.
"openVpnSettings" -> result.success(openVpnSettings())
"setSessionExpiry" -> {
val expiresAtMs = call.argument<Number>("expiresAtMs")?.toLong() ?: 0L
if (expiresAtMs > 0) {
sendSessionExpiryToDaemon(expiresAtMs)
}
result.success(null)
}
// Is our VPN runtime actually alive? The service lives in
// :RunSoLibXrayDaemon, a separate process, so its statics are
// NOT visible here -- this is the only honest check.
@@ -153,16 +156,14 @@ class MainActivity : FlutterActivity() {
}
}
/** Deep-links to the OS VPN settings, where Always-on VPN lives. */
private fun openVpnSettings(): Boolean {
return try {
startActivity(
Intent(Settings.ACTION_VPN_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
)
true
/** Pushes the session-expiry deadline to the VPN service process. */
private fun sendSessionExpiryToDaemon(expiresAtMs: Long) {
try {
val intent = Intent(this, XrayVPNService::class.java)
intent.putExtra("SESSION_EXPIRES_AT_MS", expiresAtMs)
startService(intent)
} catch (_: Exception) {
false
// Daemon not reachable; its persisted expiry still applies.
}
}