100 Commits
Author SHA1 Message Date
esefxdzandClaude Opus 5 f28379f73f Merge branch 'docs/privacy-policy-accuracy'
Android CI / android (push) Canceled after 0s
Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-18 13:47:36 +03:00
esefxdzandClaude Opus 5 8260c7cc0a docs(privacy): correct the policy the app actually links to
The sidebar opens this file, so it is the copy users read, and it had
drifted from the service it describes.

- the device ID is stored on the phone and reused between sessions; the
  policy implied it was per-session
- "we do not sell or share any data with third parties" sat in the same
  document as the AdMob section that explains Google collects data for
  ad delivery; narrowed to what is true
- dropped the service name and journalctl invocation from the log
  paragraph; operational detail nobody reading a privacy policy needs

Now byte-identical to the copy on the website.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-18 13:41:57 +03:00
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 e751a9d6e4 fix(session): don't release the session on a SOCKS5 Reconnect
Android CI / android (push) Canceled after 0s
Regression from e67f0b9, shipped in 3.3.7.

Reconnect repairs an orphaned proxy by tearing the runtime down and
re-attaching to the session the device already holds. It does that
through SessionTimer.disconnect(), which e67f0b9 taught to hand the
session back to the server - so it destroyed the very session the
following connect(resume: true) then asked for. Unawaited, so it raced:
the resume either found the session already gone or removed moments
later, surfacing as "Session not activated".

disconnect() now takes release, and the Reconnect path passes false.
Every other caller still hands the session back.

Narrow in practice: SOCKS5 mode only, only the Settings Reconnect
button, and the main-screen connect was unaffected because it mints a
new session anyway.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 17:17:29 +03:00
esefxdz c8617835b1 Merge branch 'fix/device-test-findings'
Android CI / android (push) Canceled after 0s
2026-10-17 16:47:29 +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 52e8cd8ca1 test: pin the session-lifecycle fixes against silent regression
Source assertions, in the style of the existing release-verification
test. Every behaviour here was found broken on a device and fixed today;
none can be covered by an ordinary Dart test, because they live in the
vendored Android runtime or need a live tunnel. They all regress
silently, and each one looks like tidying when it does - inlining a
CountDownTimer, dropping a Handler callback, restoring a friendlier
notification string.

Checked that they fail without the fixes, not just that they pass with
them: restoring the "disconnects in 5 minutes" wording and moving
releaseSession() out of disconnect() both turn them red.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 16:37:41 +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 e67f0b93ee feat(session): hand the session back when the user disconnects
The client never called /session/stop - the string appears nowhere in
lib/ or local_packages/, on this branch or on main - while the server has
implemented the route all along (hivemind/api.py:237) and AGENTS.md rule
33 documents its ordering. Across a full device test session, two user
disconnects and several expiries, the control plane logged zero calls.

So a disconnect left the VLESS client provisioned in the Reality inbound
and the session dict in memory and sessions.json until its window ran
out. Anyone holding that UUID could keep tunnelling for the remainder.
Users were not locked out of their quota - _handle_main replaces the
session with a fresh full-length one on the next connect - so this is
hygiene rather than a user-facing bug.

Only SessionTimer.disconnect() calls it, which is reached only from the
connect button. Expiry, the hard cap and a server-ended session are
already gone server-side, and the route takes nothing but a device_id as
proof of ownership, so no error path may reach it. Unawaited and capped
at 3 s, so an unreachable API cannot delay the teardown, and every
failure is swallowed: the session expires on its own regardless.

Verified against a mock control plane:

  user disconnect  POST /api/session/stop -> session removed
  expiry           no call; alert still posted

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 16:31:51 +03:00
esefxdzandClaude Opus 5 00b84dde61 fix(session): stop the expiry warning promising a time and an ad it cannot honour
The warning said "Revolt VPN disconnects in 5 minutes" / "Watch an ad to
extend your session." Both can be wrong.

handleSessionWarningAlarm accepts the warning anywhere between
WARNING_FLOOR_MS and WARNING_LEAD_MS + WARNING_SLACK_MS remaining, so it
can say "5 minutes" with just over one left. And the ad is a dead end in
a release build: AdManager.showAd('support') is still kDebugMode-gated
(rule 87), so the support button does nothing there - and nothing at all
in a build shipped with ads off.

Say neither. The comment records what has to change for the number and
the ad to come back.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 16:26:41 +03:00
esefxdzandClaude Opus 5 296b087db6 fix(session): take down the countdown notification when the tunnel stops
Disconnecting the VPN from Android's own settings left "Revolt VPN
00:28:13" in the shade, frozen, for as long as the phone stayed up - a
notification claiming a protected session over a tunnel that was gone.

updateTimer() re-posts notification id 1 as ongoing and non-dismissible
from the UI process, every tick. The service cancels its own foreground
copy when it stops, but a tick landing just after that re-creates it as
an ordinary notification nobody owns, and nothing ever cancelled it:
NotificationService.reset() only clears the dedupe cache. AGENTS.md
rule 31 describes the cancel this restores.

_doDisconnect now cancels it, after vpnConnection.disconnect() so the
service is already down - cancelling while it is still in the foreground
would be swallowed by the platform.

Verified on an emulator, revoking via Settings -> VPN -> Disconnect:

  before  countdown stranded at 00:28:13 indefinitely
  after   no live notifications

User disconnect still posts no alert, and expiry still posts "Revolt VPN
disconnected" with the countdown gone.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 16:26:41 +03:00
esefxdzandClaude Opus 5 eab43b0420 fix(session): deliver the expiry warning when the app is closed
The five-minute warning did not arrive in the one case it exists for.

The deadline is enforced by two things - a main-looper Handler and an
alarm - while the warning had only the alarm, and that alarm is inexact
on purpose so the app can stay off SCHEDULE_EXACT_ALARM. Doze defers an
inexact alarm freely, and with the app closed it deferred this one past
the end of the session it was warning about.

Measured on an emulator, app swiped from recents and its process killed,
screen off, deviceidle forced to IDLE, 7-minute session:

  before  warning never fired; request code 1002 never delivered and
          handleSessionWarningAlarm never ran. Expiry was still punctual
          (16:00:55 against a 16:00:54 deadline) because the deadline has
          its Handler.
  after   warning posted as the session crossed 300 s remaining, app
          still closed and device still IDLE; expiry unchanged.

So give the warning the same backstop that already makes the deadline
reliable. postDelayed runs on uptimeMillis, so a genuinely suspended
device still fires it late - handleSessionWarningAlarm then drops it on
the existing window check instead of claiming "5 minutes" with seconds
left. The alarm stays for the case where the process was restarted and
no Handler callback survives.

Note this does not make the warning punctual under true suspend; only
SCHEDULE_EXACT_ALARM would, and that permission is deliberately not
declared.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 16:14:48 +03:00
esefxdzandClaude Opus 5 e7ef251882 fix(runtime): keep Xray's startup output so a failed core can be diagnosed
The monitor thread read the core's merged stdout/stderr and dropped
every line:

    process.inputStream.bufferedReader().use { reader ->
        reader.forEachLine { _ -> }
    }

so the only trace of a core that died at startup was "Xray process
exited with code 23", with nothing to say why. That exit is real and
intermittent - it showed up on both a handset and an emulator - and
was undiagnosable in the field.

Keep at most 40 lines, and only from the core's first 10 seconds, then
log them when the exit code is non-zero. A core up longer than that may
be carrying traffic and its log can name destinations, which must never
reach the device log (rule 95), so past the window the buffer is
dropped rather than kept.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 15:37:29 +03:00
esefxdzandClaude Opus 5 4e5c7a02db fix(runtime): make Xray core recovery work, and stop it leaking a core per try
Core recovery could never succeed, and every attempt left a live
libxray.so behind.

startCore() calls startTimer(), which builds a CountDownTimer and so a
Handler on the calling thread. handleXrayCoreExit() runs recovery on a
bare Thread, so the constructor threw "Can't create handler inside
thread ... that has not called Looper.prepare()" and took the whole
restart down with it. The catch then deleted the ephemeral config and
returned false without killing the process pb.start() had already
started, so each retry stranded another core.

Measured on an emulator by killing the live core:

  before  1 -> 5 -> 6 -> 8 processes over 120 s, never recovers
  after   "Xray core recovered without dropping TUN" on attempt 1,
          exactly 1 process

startTimer now posts to the main looper, re-checking RUNTIME_TOKEN
inside the post so a stop that lands while it is queued cannot leave a
timer running for a dead generation. stopTimer posts too, so the
countDownTimer field is only touched from one thread. The catch clears
xrayProcess before destroying it, so the monitor thread's
`xrayProcess === process` guard fails and recovery is not re-entered
for a core being killed here.

Pre-existing on main; reachable whenever the core exits.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 15:37:29 +03:00
esefxdzandClaude Opus 5 30d96a4049 style: cut the comments down
Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 00:50:36 +03:00
esefxdzandClaude Opus 5 66789d49c1 revert: restore pubspec.lock to main
Builds run with Flutter 3.47.2 rewrote the lockfile, downgrading five
transitive packages and the Dart SDK constraint. Unrelated to this branch's
work and swept in by git add -A.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 00:47:23 +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 34722c751b fix(session): recognise expiry after device sleep by wall clock
_reconcileElapsedTime() runs off a Dart Stopwatch, which is CLOCK_MONOTONIC
and does not advance while the device is suspended - exactly the state an
RTC_WAKEUP expiry alarm fires in. The "did this drop happen at the deadline"
test therefore came out false after any sleep, and the screen-off expiry (the
common one) still showed no reason in the UI.

Each successful sync now records an absolute DateTime deadline, and the
tunnel-dropped branch compares against that instead.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-17 00:25:17 +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 6d8a923475 notification fixed 2026-09-06 13:07:37 +03:00
esefxdz 4718a5912a build(android): pin Windows aapt2 checksum for local release builds 2026-09-06 12:37:20 +03:00
esefxdz 5d2683f354 chore(release): prepare 3.3.5 2026-09-06 12:09:20 +03:00
esefxdz 08b21dc5d9 docs(contributing): drop server/xray/nginx scopes 2026-09-06 12:09:20 +03:00
esefxdz bbc83ea425 chore(app): lint cleanups (const constructors, local function) 2026-09-06 12:09:16 +03:00
esefxdzandClaude Opus 5 9d67679fa8 fix(app): do not throw from ConnectionSettings.initialize
The migration write is best-effort again. `_mode` is already the safe TUN
default in memory by the time it runs, so a failed write leaves nothing
insecure -- it only means the migration is retried on the next launch.

Throwing there was a startup brick rather than a fail-closed guard.
`initialize()` is awaited from `main()` at lib/main.dart:19, before
`runApp()`, with no try/catch and no `runZonedGuarded`; `FlutterError.onError`
and `PlatformDispatcher.onError` are installed later and would not catch it
either. The branch also runs on every fresh install, since `storedMode` is null
and `null != 'tun'`. A `setString` returning false would therefore have meant a
black screen with no error UI, repeating every launch, in exchange for no
security benefit.

Also restores two comments this PR removed from code it does not otherwise
touch -- the `_reconnect` note on why the repair path shows no rewarded ad, and
the explanation of how an orphaned proxy outlives the app process -- and drops
a stray 'ass' from the legacy-values comment.

The `setMode` half of the PR is unchanged: persisting before mutating memory
and surfacing failures to the UI is the right behaviour.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019HT2x2JvJNg7XNsJubfHFf
2026-09-06 11:51:52 +03:00
esefxdzandClaude Opus 5 bd41a3c693 refactor(app): drop Dart wrappers whose native handlers no longer exist
#17 removed the `setSessionExpiry` and `isVpnRuntimeAlive` MethodChannel
handlers from MainActivity, and #18 removed their last callers. The Dart
wrappers stayed behind.

Both swallow their errors, so a future caller would not fail loudly — it would
get a silent no-op from `setSessionExpiry` and a permanent
`processAlive: false` from `runtimeState()`, which previously read as "the
tunnel is dead". Deleting them keeps that trap out of the codebase.

`VpnRuntimeState` goes with them; runtime liveness now arrives through the
native status stream, which is generation-scoped. The battery-optimisation
methods are untouched and still used by background_reliability.dart.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019HT2x2JvJNg7XNsJubfHFf
2026-09-06 11:28:52 +03:00
esefxdzandClaude Opus 5 061bd41920 Merge pull request #19 from Naveax/pr/add-android-supply-chain-ci
build: add reproducible Android release verification

Resolved a conflict in android/app/build.gradle.kts. #19 was written against
the pre-#10 file and re-added the pub-cache patch tasks that #10 deleted; only
`sha256Hex()` was kept from that block, since `verifyReleaseAppConfig` needs it.

Also restored `import java.io.File`, which #10 removed along with the patch
tasks. #19's `sha256Hex(file: File)` names the type in its signature, and
Kotlin does not auto-import `java.io.File`, so the release build would have
failed at Gradle configuration without it.

Verified after resolution: no orphaned references to `projectRootDir`,
`localProperties`, `flutterSdkPath` or `patchFlutterVless`; both tests in
test/release_verification_regression_test.dart pass; `flutter analyze` reports
no errors or warnings.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019HT2x2JvJNg7XNsJubfHFf
2026-09-06 11:27:28 +03:00
esefxdzandClaude Opus 5 e818d54b0e build(config): delete orphaned assets instead of narrowing the manifest
#16 replaced the `assets/` wildcard with three explicit paths. That is correct
today but makes the failure mode for a forgotten registration an `Unable to load
asset` exception at runtime rather than at build time, which neither
`flutter analyze` nor the build would catch.

Restore the wildcard and delete the three files it was working around instead.
All are unreferenced in tracked source: `logo.png` has no callers and no
`flutter_launcher_icons` config, while `notification_icon.png` and
`notification_banner.png` are stale copies of the real Android drawables in
`android/app/src/main/res/`, which the notification reaches via
`R.drawable.notification_status_icon` and never through the Flutter bundle.

Same APK-size win, no hand-maintained list, and the dead files leave the repo.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019HT2x2JvJNg7XNsJubfHFf
2026-09-06 11:20:08 +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
esefxdzandClaude Opus 5 ebaa2fc516 chore(test): remove the regression test suite
Removed at the maintainer's request. The four tests covered the auto->tun
and ass->proxy preference migrations, secure SOCKS session generation, and
the patched Xray SOCKS authentication schema.

Note that nothing now guards the build-time patchers against schema drift;
their fail-closed anchors are the only remaining check.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-31 23:31:51 +03:00
esefxdzandClaude Opus 5 6fcde9dd7b refactor(app): debloat 3.3.1 and fix startup restoration
Maintainer changes on top of PR #2 (Naveax). Reduces the release to two
deliberate connection modes and resolves the issues found in review:

- Lock-screen privacy: move VISIBILITY_PRIVATE and a generic publicVersion
  into MainActivity.updateVpnNotification. The PR's fix lived in an
  unreferenced class and never reached the live notification.
- Haptics: restore the default to off, and reduce HapticSettings to a
  single success() on connect/disconnect via the native channel.
- SOCKS5: make it a real proxyOnly local-proxy mode rather than a TUN
  connection under a different label. TUN stays the default.
- Health probe: restore the idle-only guard so /health is no longer polled
  every 30s while connected.
- Recovery: add a _connectionEpoch counter so connect() aborts an
  in-flight Extreme recovery.
- Startup restoration: rebuild the recovery snapshot from the still-active
  session after an app-process restart (TUN), and surface an honest
  orphaned state with a Reconnect action in proxy mode.

Drops per-app routing entirely. It only ever functioned in TUN --
addDisallowedApplication lives inside the VPN-interface setup that
proxyOnly skips -- and its allowlist half had no Dart caller.

Also removes flutter_local_notifications, the global tap-haptic surface,
and unused runtime telemetry.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-31 23:31:51 +03:00
esefxdz 371db8da9f docs: make app_config.example github owner/repo placeholders unambiguous 2026-08-30 15:43:39 +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 4eb1fca666 Merge branch 'main' of https://github.com/esefxdz/ReVoltVPN 2026-08-22 11:34:39 +03:00
esefxdz 71468f7594 frontend polish and security review changes 2026-08-22 11:34:17 +03:00
esefxdz 5642b0b811 Merge branch 'main' of https://github.com/esefxdz/ReVoltVPN 2026-08-08 14:32:40 +03:00
esefxdz 096fbf5a97 docs: update privacy policy for V3 model and in-app disclosure 2026-08-08 14:31:54 +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 d696bda63f discord link fixed 2026-08-08 14:21:53 +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 143f5f4eff Merge branch 'main' of https://github.com/esefxdz/ReVoltVPN 2026-08-06 21:48:18 +03:00
esefxdz abcb41fb06 splash screen and status bar color polish 2026-08-06 21:47:58 +03:00
esefxdz 2d0628c5bb docs: update SERVER_DEPLOYMENT.md for V2 domain-free architecture 2026-08-02 15:38:42 +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 c1ec77a075 http conversion through guides 2026-08-01 23:55:54 +03:00
esefxdz 1b8a67cecb less client things removal 2026-08-01 22:10:48 +03:00
esefxdz c5148e138d Merge branch 'main' of https://github.com/esefxdz/ReVoltVPN 2026-08-01 20:58:54 +03:00
esefxdz 832c2d0a2d privacy policy changed and gitignore updated 2026-08-01 20:58:36 +03:00
esefxdz fd6b358929 clean up .gitignore: organize sections, add backups/ docs/ patterns, remove tracked server+backup files from repo 2026-07-31 22:53:50 +03:00
esefxdz 4555911ee8 fix compilation: revert AppConfig.enableAdBypass to kDebugMode, fix const Padding with dynamic version 2026-07-31 22:47:51 +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 e1c5d5b106 rain and lightning effects addded. 2026-07-30 23:54:02 +03:00
esefxdz adf6de5dc0 refactor(server): split hivemind monolith into modular package
- server_hivemind_5_0.py: 744-line monolith → 19-line entry point shim
- hivemind/ package (8 files, gitignored): config, sessions, xray,
  admob, management, api, main, README
- Encapsulated locks in classes (SessionStore, XrayManager,
  AdMobVerifier) — fixes 3 critical race conditions
- Added localhost-gated /api/swarm and /api/log with log ring buffer
- swarm_watch.py: Rich TUI monitor for local server dashboards
- Backed up original monolith to backups/hivemind_backup_2.py
2026-07-30 17:40:07 +03:00
esefxdz 81d0387612 update readme 2026-07-29 23:50:48 +03:00
esefxdz 33522dfeaf changelog: bump to v2.0.7 2026-07-29 23:46:04 +03:00
esefxdz f8abda9323 v2.0.7: security hardening, config sync, cleanup
- 6 new security audits (technical_debt_8 through 12)
- SNI rotation pool per session (5 sites)
- vless_ip pinned to hardcoded server IP
- TLS cert validation restored (domain-based)
- Swarm monitoring removed from public domain
- Xray routing blocks abuse ports (SMTP, NetBIOS, SSDP)
- flow: xtls-rprx-vision in add_vless_client
- TimerBox shows Connecting/Syncing spinner
- Outbound port blocking in Xray config
- Cleaned up comments, synced config templates
2026-07-29 23:43:47 +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
esefxdz d6c56e8345 paladinvpn replacement added 2026-07-28 21:58:02 +03:00
esefxdz 96584082cf changed licensing 2026-07-28 21:37:59 +03:00
esefxdz 66e89d8d28 contributing guide added 2026-07-28 21:35:44 +03:00
esefxdz c47dfb471e delete paladin stuff 2026-07-28 20:56:08 +03:00
esefxdz 7bac95e45a change screenshots 2026-07-28 20:55:39 +03:00
esefxdz 345fb0e316 update readme.md 2026-07-28 20:52:46 +03:00
esefxdz 3afebd66fb resolve merge: keep nginx_hivemind_reality.conf with swarm auth + rate limit 2026-07-28 20:49:19 +03:00
esefxdz 0e17163f83 update gitignore 2026-07-28 20:47:00 +03:00
esefxdz 156d9f49b2 tiny fixes and cosmetic 2026-07-28 20:34:02 +03:00
esefxdz a659e3bb2e tiniest of fixes 2026-07-27 23:20:18 +03:00
esefxdz 2086f9ded2 reality configured 2026-07-27 23:10:11 +03:00
esefxdz efcfe6e706 Two-box throttle system + support top-up
- Server: two Xray inbounds (vless-normal / vless-throttled), 4GB threshold,
  management loop moves users between boxes instead of hard-killing.
  /session/status returns dynamic vless_path + is_throttled.
  Support ad callback (ad_type: support) extends session +30min +2GB.
  No hard quota kill — only 60-min expiry removes sessions.

- App: session_timer detects vless_path change in 5s poll, triggers
  silent reconnect (skipCleanup, skipAdBypass, quickReconnect).
  New support_button.dart for top-up rewarded ads.
  hivemind_service uses server vless_path instead of hardcoded constant.
  vpn_connection: skipAdBypass, quickReconnect, skipCleanup params.

- Config: Xray two-inbound template, nginx /tunnel-throttled with
  limit_rate 187k (1.5 Mbps). Updated AGENTS.md + SERVER_SETUP.md.
2026-07-27 20:15:25 +03:00
esefxdz 6825a264a5 notification icon added 2026-07-26 17:16:35 +03:00
esefxdz 6d89a376bb cleanup unused image 2026-07-26 12:18:06 +03:00
esefxdz 35bfeb61c4 polish: strip verbose comments, simplify codebase 2026-07-26 11:30:59 +03:00
esefxdz a32252c56d deleting old safety checks and connect polish 2026-07-26 10:41:30 +03:00
esefxdz a863656758 initial commit 2026-07-26 10:30:44 +03:00
esefxdz d106356ca6 v2.0.0: VLESS cleanup — remove bonus ads, throttling, fix Xray compliance, rebrand colors 2026-07-25 00:34:14 +03:00
esefxdz 10a1450292 fix: stale timer flash on reconnect — reset _hasSyncedOnce in _stopInternal 2026-07-24 22:36:08 +03:00
esefxdz 6d58aca01b Polish: fix outdated AWG/WireGuard comments, update README & privacy policy for team 2026-07-24 22:26:46 +03:00
esefxdz c2acb667d8 Remove XRAY_CONFIG_REFERENCE.md — server-side docs don't belong in client repo 2026-07-24 21:03:38 +03:00
esefxdz 3531671d3d Rebrand: PaladinVPN -> ReVoltVPN — all references, package names, Android namespace 2026-07-24 21:03:18 +03:00
esefxdz aaf9887f49 v1.0.0: AWG -> VLESS migration — ReVoltVPN debut 2026-07-24 20:56:13 +03:00
esefxdz b679ba4d8f tiny fixes 2026-07-18 19:36:02 +03:00
esefxdz 0943506404 fix: session nonce to prevent stale session reuse, reorganize hivemind, clean AdMob bypass toggle 2026-07-18 13:02:04 +03:00
esefxdz 08f08fbf3e fix: notification countdown now ticks locally every second instead of gated behind 5s server poll; bump to 1.0.5+9 2026-07-17 13:17:47 +03:00
esefxdz 259db2f386 v1.0.5: dual-stack IPv6, pure AmneziaWG, scrub leaks, verifySession fix 2026-07-16 20:02:37 +03:00
esefxdz d6bdef4d89 version bump 1.0.3 2026-07-16 13:41:32 +03:00