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]>
This commit is contained in:
esefxdzandClaude Opus 5 committed 2026-10-17 18:53:42 +03:00
1 parent 73d76c04f2
commit 571c07bca8
7 files changed
+75 -51

No files matched your search

+8 -17
View File
@@ -158,9 +158,7 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver {
}
}
// resetSupportClaim is false when re-attaching to a session already held:
// the top-up is once per server session and this flag is what enforces it.
Future<void> start({bool resetSupportClaim = true}) async {
Future<void> start() async {
_sessionEpoch++;
_remainingSeconds = 0;
_remainingAtLastSync = 0;
@@ -178,12 +176,11 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver {
_lastEndMessage = null;
NotificationService.reset();
if (resetSupportClaim) {
_supportStateEpoch++;
_supportRewardClaimed = false;
_supportRewardStateLoaded = true;
unawaited(_persistSupportRewardState(false));
}
// Every session is new, so the once-per-session top-up resets with it.
_supportStateEpoch++;
_supportRewardClaimed = false;
_supportRewardStateLoaded = true;
unawaited(_persistSupportRewardState(false));
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), _tick);
@@ -240,17 +237,11 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver {
}
}
// release is false when the caller intends to re-attach to the same session,
// as the SOCKS5 Reconnect does: handing it back here would destroy the very
// session the follow-up connect(resume: true) is about to ask for.
Future<void> disconnect({
String reason = 'User requested',
bool release = true,
}) async {
Future<void> disconnect({String reason = 'User requested'}) async {
// Only here, not in _doDisconnect: this is the one path the user actually
// asked for. Unawaited so a slow or unreachable API never holds up the
// teardown.
if (release) unawaited(HivemindService.releaseSession());
unawaited(HivemindService.releaseSession());
await _doDisconnect(reason);
}