Commit Graph
30 Commits
Author SHA1 Message Date
esefxdzandClaude Opus 5 62e968f133 fix(app): stop probing the control plane while disconnected
Android CI / android (push) Canceled after 0s
VpnConnection._init() started a 30 s /health poll from the constructor
with no lifecycle gating, so it ran for the life of the app process -
backgrounded, and whether or not the user ever connected. The app is
excluded from its own tunnel, so those requests left in the clear. A
periodic request that fires while the user is deliberately not connected
is an avoidable fingerprint, and rule 60 already ruled probes out:
reachability is meant to be passive.

All it bought was the status dot before the first connect, and it got
that wrong in both directions - "Offline" on a transient resolver
failure against a server that was fine.

Reachability now comes from a session sync that succeeded. serverHealthy
is isConnected && hasSyncedOnce, so a green dot means a working session
rather than a check made beforehand; while disconnected the bar reads
"Ready" and claims nothing either way.

checkHealth() goes with it. Nothing calls it, and leaving a probe helper
in place invites its reuse.

Pinned in test/session_lifecycle_regression_test.dart so it cannot come
back as a tidy-up. flutter analyze clean, 10/10 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 21:17:17 +03:00
esefxdzandClaude Opus 5 571c07bca8 feat(session): every reconnect starts a fresh session
Android CI / android (push) Canceled after 0s
SOCKS5 Reconnect re-attached to the session the device already held. It
was the only path where a session began without an ad and without a
nonce binding it to one, and supporting it cost three parameters -
resume, release, resetSupportClaim - plus a carve-out in rule 4 that let
_fetchActiveSession accept whatever nonce the server happened to return.

It also wasn't the kindness it looked like. _handle_main calls
create_or_replace with a fresh full window, so a new session hands the
user *more* time, not less; re-attaching only preserved the remainder of
a session whose proxy had just died.

Reconnect now disconnects and connects again exactly as the button does,
ad included. All four special cases are deleted and the nonce check is
unconditional.

Costs one rewarded ad to repair an orphaned proxy once ads are enabled.
esef's call, and it buys a single way to start a session.

Also fixes the 3.3.7 regression where Reconnect released the session and
then asked for it back: there is no longer a path that does either.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 18:53:42 +03:00
esefxdzandClaude Opus 5 73d76c04f2 docs(changelog): correct 3.3.7, which advertised behaviour it does not have
Android CI / android (push) Canceled after 0s
A Fable review checked every entry against the commits in the release and
found the SOCKS5 Reconnect bullet is false for the shipped binary: tag
v3.3.7 contains e67f0b9 (/session/stop) but not e751a9d (the fix), so in
that build Reconnect ends the session instead of re-attaching. The public
changelog promised the opposite. It now carries a Known issue box naming
the symptom and the workaround.

Also corrected, same review:

- The warning's "now actually arrives when the app is closed" named the
  sleeping-phone case and claimed to fix it. It does not: the backstop is
  Handler.postDelayed on uptimeMillis, which stalls under true suspend,
  and a late delivery is then dropped by the window check - so the
  failure mode there is no warning, not a late one. The evidence was an
  emulator under forced idle, which never suspends the CPU. Dropped the
  Fixed entry (the feature is new in 3.3.7 anyway, so it fixed no
  released bug) and let the honest Added bullet carry it.
- /session/stop fires only from the app's own disconnect button, not on
  expiry, cap, revoke or a kill. Scoped the claim.
- The ad entry moved to Groundwork: ads are off in this build and the
  path was reviewed, never exercised against a real SSV callback.
- "One restart now brings it back" rested on one emulator run killing a
  warm core, and the cold-start exit-23 is unfixed. Softened.
- Core recovery "says why" -> logs why; no user sees it.
- Added the two countdown fixes from d1ea13b and 34722c7, which were
  user-visible and had no entry at all.
- Dropped "the tunnel stopping unexpectedly": that path never reaches
  stopAll, so no alert is posted for it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 18:22:19 +03:00
esefxdzandClaude Opus 5 550402a663 chore(release): 3.3.7
Version and changelog together (rule 24). The in-app updater compares
PackageInfo.version against the GitHub tag, so the tag and this must
agree or an update is offered forever.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 16:46:49 +03:00
esefxdzandClaude Opus 5 437d998663 docs(changelog): record the session-end fixes, and correct a claim
The Unreleased entry said the five-minute warning "still arrives when
the app is closed". Device testing showed it did not: it was scheduled
only on an inexact alarm, which Doze deferred past the end of the
session it was warning about. That is now true rather than aspirational,
and the entry no longer promises an exact number of minutes, because the
warning can legitimately arrive anywhere inside its window.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 16:35:53 +03:00
esefxdzandClaude Opus 5 8ba3b6c543 fix(session): alert on every unsolicited native stop, not just the deadline
Second review pass found that VpnStatus.error is unreachable mid-session:
AppConfigs.V2RAY_STATES has no error state and the plugin's stateName when()
ends in else -> "DISCONNECTED", so Dart's unknown -> error mapping is dead
code on Android. Every native stop therefore arrived as a plain disconnect,
and anything other than the expiry deadline - an Xray or TUN setup failure, a
mid-session fail-closed deadline arm - left the user on their real IP with no
notification and no message. That is the exact hole this feature exists to
close.

stopAll() is the single funnel for every native stop, so the alert moves
there: it fires when the stop interrupted a live tunnel and the user did not
ask for it. isRunning is captured before cleanup() clears it, which keeps a
failed connect silent - it never had a tunnel to interrupt. STOP_SERVICE
(the notification's Disconnect action) and onRevoke pass userRequested: in
the revoke case the user switched VPNs or withdrew consent themselves, and
may well be protected by the app that took over.

Also from the pass:
- re-arming now needs the deadline to move by more than 15 s, so round-trip
  jitter cannot cancel and re-post a warning that is already on screen
- a warning delivered with under a minute left is dropped rather than
  announcing "5 minutes"; the disconnected alert follows anyway
- the _doDisconnect comment no longer claims endedMessage posts the alert
- CHANGELOG: the alert does now cover the tunnel stopping unexpectedly

flutter analyze clean; debug APK builds with the Kotlin recompiled.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 00:44:51 +03:00
esefxdzandClaude Opus 5 d1ea13b01a 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 <[email protected]>
2026-10-17 00:38:08 +03:00
esefxdzandClaude Opus 5 dd1082c8de fix(session): repair the expiry warning and the gaps the review found
Review of 742f376 (Fable). Three defects made the feature not work for the
case it exists for:

- armExpiryWarning() cancelled the alarm before its own early return. Dart
  re-pushes the deadline every 60 s with no dedupe (rule 93's ">5 s" dedupe
  does not exist), so the first sync after the warning came due deleted the
  armed alarm and never re-armed it. Cancel now happens only on the branch
  that re-arms.
- cancelSessionWarningAlarm() built its PendingIntent with
  FLAG_UPDATE_CURRENT, rewriting the live alarm's RUNTIME_TOKEN to "" so a
  delivery in flight was dropped as a stale generation. It now looks the
  existing PendingIntent up with FLAG_NO_CREATE.
- SOCKS5 mode never requested POST_NOTIFICATIONS (requestPermission() is
  TUN-only because it also raises the VPN consent dialog), so on Android 13+
  both alerts were dropped. New plugin method requests it on its own.

Also:
- the native deadline wins the expiry race against the 1 s Dart tick, so the
  commonest session end reached _onVpnConnectionChanged as a bare tunnel
  drop with no message. It now reconciles and sets the expiry text, with
  postAlert false since the native side already posted.
- a resume reconnect no longer resets the support-ad claim: it re-attaches
  to the same server session, where the top-up has already been spent.
- RevoltAlerts sets setOnlyAlertOnce so a replacing post does not re-sound.
- AGENTS.md rule 93 described a setSessionExpiry/SESSION_EXPIRES_AT_MS path
  that no longer exists, dedupe included. Corrected; that stale text is what
  the warning bug was written against.
- CHANGELOG no longer claims the alert covers the tunnel stopping on its own.

flutter analyze clean; debug APK builds.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 00:22:26 +03:00
esefxdzandClaude Opus 5 742f37683e feat(session): warn before a session ends and say when protection stops
Review findings #1-#3 in internal/APP_REVIEW_2026-10-16.md: every session
end dropped the user onto their real IP with no warning, and the app showed
no errors at all.

- RevoltAlerts (vendored runtime) owns both alerts: a high-importance
  channel, ids 2 and 3, posted from whichever process is alive
- XrayVPNService schedules a 5-minute warning off the deadline it already
  holds, best effort (a warning that cannot be armed never stops a tunnel),
  with its own request code so it cannot clobber the expiry alarm
- the warning intent is handled above the command dispatch: without that it
  would fall through to the final else and stop the tunnel 5 minutes early
- SessionTimer._doDisconnect takes endedMessage: set for ends the user did
  not ask for, which both posts the alert and gives the UI a sentence
- the API being unreachable no longer disconnects a working tunnel; it is a
  display state now, with expiry still enforced natively and server-side
- status_bar.dart renders connect errors, connecting progress and the
  session-end reason

flutter analyze clean; debug APK builds (Kotlin compiles).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 00:02:20 +03:00
esefxdzandClaude Opus 5 37753c6396 fix(ads): pass the rewarded ad's nonce through to connect
With ads enabled the SSV nonce and the nonce the connect flow waited for
were minted separately, so the client rejected the very session the ad had
just created and failed with "Session not activated".

- showAd() returns the nonce it put in the SSV custom_data (null on no reward)
- fetchConfigDirectly({adNonce, resume}) replaces skipAdBypass: a real ad
  waits for exactly that nonce and polls ~36 s for Google's callback, the
  bypass path mints its own, and resume re-attaches to the session the
  device already owns
- SOCKS5 "Reconnect" uses resume: true instead of requesting a new session

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-16 23:51:19 +03:00
esefxdzandClaude Opus 5.5 e14e900276 chore(release): prepare 3.3.6
- fix(app): Test Local SOCKS forces a round trip instead of trusting the CONNECT reply
- changelog: notification icon + SOCKS test fixes

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 13:20:52 +03:00
esefxdz 5d2683f354 chore(release): prepare 3.3.5 2026-09-06 12:09:20 +03:00
esefxdz 9ac236a981 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.
2026-09-02 20:33:33 +03:00
esefxdzandClaude Opus 5 dfeb7f161c fix(app): resume the session clock when attaching to a live tunnel
The clock sat at 00:00:00 on a connected tunnel, and the notification
countdown froze with it.

SessionTimer is built lazily, so when the app reattaches to a tunnel that
outlived its UI process, VpnConnection had already reported CONNECTED
before the timer existed. A change listener does not replay events it was
not present for, so the timer never learned it should be running. The
notification text is pushed by that same timer, which is why the countdown
froze at the same moment -- one cause, both symptoms.

The timer now evaluates the current connection state once at construction
rather than waiting for a transition, and is created eagerly alongside
VpnConnection so it is listening from the start.

Resuming also no longer requires adoptedRunningRuntime or a previous
successful sync. Both are false in a freshly started process, which is
exactly the case that needed to work. A connected tunnel with a stopped
clock is now always resumed.

Version 3.3.3+29.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-01 00:39:22 +03:00
esefxdzandClaude Opus 5 186dc906da feat(app): keep the tunnel alive via the OS, drop the workarounds
3.3.1 could display "connected" against a tunnel that was no longer
running, with a frozen countdown and no traffic. Three causes, and the
fix for each removes more code than it adds.

Liveness was read from getConnectedServerDelay(), which measures through
AppConfigs.V2RAY_CONFIG?.LOCAL_SOCKS5_PORT ?: 10807. AppConfigs lives in
the :RunSoLibXrayDaemon process, so from the UI process it is always null
and the port always fell back to 10807 -- accidentally correct while the
SOCKS port was fixed, permanently wrong once 3.3.1 made it ephemeral.
Liveness now comes from the OS: is our service process running, and is a
VPN transport actually present.

The foreground notification (id 1) is owned by XrayVPNService via
startForeground, but MainActivity was posting to the same id from the UI
process. When that process died its notification stayed on screen with
nothing left to update it. MainActivity now refuses to post for a dead
runtime and clears the stale notification instead.

Nothing ever asked Android for permission to stay alive. Adds the
battery-optimisation exemption prompt and a deep link to Always-on VPN --
the two sanctioned mechanisms for background survival, each needing one
user tap.

Removes Standard/Extreme resilience and the runtime-restart machinery:
recovery ran off a disconnect broadcast delivered to the app process, so
it could never fire when Android killed that process. Removes startup
restoration and the config-snapshot rebuild along with it; with the OS
keeping the tunnel up there is nothing to restore.

Version 3.3.2+28.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-01 00:16:05 +03:00
esefxdzandClaude Opus 5 ac52681452 docs(changelog): rewrite 3.3.1 against the shipped tree
The notes described PR #2 as opened, not what is being merged. Removed the
entries for per-app routing, the installed-apps bridge and the app picker
-- all dropped in the debloat -- and moved the rationale to a Removed
section. Replaced the overstated validation claims with measured ones, and
documented the startup-restoration fixes.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-31 23:31:51 +03:00
Naveax da82a833cb docs: document 3.3.1 release changes
Document the client routing, secure SOCKS5, runtime resilience, lifecycle, notification and migration changes so reviewers can compare the 3.3.1 release against 3.3.0 without reconstructing the behavior from implementation commits.
2026-08-31 20:31:35 +03:00
esefxdz 8a89af3c62 docs: mark 3.3.0 as released in CHANGELOG 2026-08-30 15:30:14 +03:00
esefxdzandNaveax 62104d5730 chore: release 3.3.0 (sidebar nav fix, notification countdown, hardening)
Co-authored-by: Naveax <[email protected]>
2026-08-30 14:01:52 +03:00
esefxdz cc819abc41 chore: bump to 3.2.2+25 (discord link fix, apk release) 2026-08-08 14:23:13 +03:00
esefxdz 61c72b2200 feat: first-launch data disclosure dialog (v3.2.1+24) 2026-08-08 13:49:00 +03:00
esefxdz 54f949ca8e v3.2.0 — DPI hardening, notification consolidation, disconnect refactor
User-Agent spoofed to Chrome on Android (was Dart/3.x). Domain renamed to userevolt.app. Branding to Revolt VPN. Removed duplicate notification, patched flutter_vless for custom icon + dark background. Merged 5 disconnect paths into single idempotent gate, fixed stopVless timeout silently lying about state. Removed kDebugMode guard from AdMob bypass.
2026-08-08 13:06:19 +03:00
esefxdz cf50c3a01b chore: bump version to 3.1.0+22 2026-08-06 23:09:56 +03:00
esefxdz 04ec8ac5d1 feat: settings screen, haptic toggle, rain/lightning toggles, review docs 2026-08-06 23:08:26 +03:00
esefxdz 56ea53e73b fix: RenderFlex overflow, circular import, remove timer spinner 2026-08-06 22:09:14 +03:00
esefxdz 4fde4a9217 v3.0.0: domain-free client, bootstrap tunnel, tunnel-internal API 2026-08-02 14:00:38 +03:00
esefxdz 75cf59f22c cleaning up 2026-08-02 00:56:55 +03:00
esefxdz 54609a3119 audit #13: notification fix, sync guard, bypass gate, responsive layout, dynamic version, dart format, clock_display to backups, stats race fix, new locks, nginx swarm block 2026-07-31 22:41:29 +03:00
esefxdz 33522dfeaf changelog: bump to v2.0.7 2026-07-29 23:46:04 +03:00
esefxdz f01a610182 v1.0.7: updater, website button, timer sync indicator, server flow fix
- App: manual update checker (GitHub API, Play Store redirect)
- App: website button in sidebar
- App: timer_box shows Connecting/Syncing spinner during handshake
- App: synced app_config.example.dart with app_config.dart
- Sidebar: 6 rows (added Website, Check for updates)
- Server: add_vless_client now includes flow xtls-rprx-vision
- Deps: added package_info_plus
- Version bump: 1.0.6+10 → 1.0.7+11
- CHANGELOG.md created
2026-07-29 20:37:33 +03:00