From d1ea13b01a7a4f9660e7823fbb3c9e241a1ff334 Mon Sep 17 00:00:00 2001 From: esefxdz Date: Sat, 17 Oct 2026 00:38:08 +0300 Subject: [PATCH] fix(session): clamp the countdown to wall clock, correct two doc claims Second review pass (Fable) cleared all six fixes; these are its leftovers. - _reconcileElapsedTime() runs off the same suspend-blind Stopwatch, so after a long device sleep the countdown over-reports - and with the API unreachable nothing corrects it, which is precisely the state the app now stays in instead of disconnecting. It now takes the lesser of the Stopwatch value and the wall-clock deadline; the minimum, so a clock set backwards cannot extend a session. - CHANGELOG claimed the alert covers "the tunnel reporting an error". It cannot: AppConfigs.V2RAY_STATES has no error state and the plugin's stateName when() ends in else -> "DISCONNECTED", so VpnStatus.error is unreachable from the status stream on Android. - AGENTS.md file map still listed a setSessionExpiry bridge in power_settings.dart; no such symbol exists. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 4 ++-- lib/logic/session_timer.dart | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 370e878..ece4ba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,8 @@ Versions follow [semver](https://semver.org/): MAJOR.MINOR.PATCH off the same deadline it already enforces, so the warning still arrives when the app is closed, and a support top-up moves it with the deadline. - **"Disconnected — not protected" notification** when a session ends without - you asking: the 2-hour expiry, the data limit, the server ending the session, - or the tunnel reporting an error. + you asking: the 2-hour expiry, the data limit, or the server ending the + session. - **SOCKS5 mode now asks for the notification permission.** It never did, because it never raises the VPN consent dialog, so on Android 13+ these alerts would have been dropped before they were shown. diff --git a/lib/logic/session_timer.dart b/lib/logic/session_timer.dart index fb59bf4..9e8b138 100644 --- a/lib/logic/session_timer.dart +++ b/lib/logic/session_timer.dart @@ -426,6 +426,20 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver { final elapsed = clock.elapsed.inSeconds; _remainingSeconds = (_remainingAtLastSync - elapsed).clamp(0, 1 << 31).toInt(); + + // The Stopwatch is CLOCK_MONOTONIC and stops while the device is + // suspended, so after a long sleep it over-reports - and with the server + // unreachable no sync arrives to correct it, which is exactly the state + // the app now stays in rather than disconnecting. Take whichever is + // smaller, never the wall clock alone: a clock set backwards must not be + // able to extend a session. + final deadline = _deadlineAt; + if (deadline != null) { + final byWallClock = deadline.difference(DateTime.now()).inSeconds; + if (byWallClock < _remainingSeconds) { + _remainingSeconds = byWallClock.clamp(0, 1 << 31).toInt(); + } + } } @override