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]>
This commit is contained in:
esefxdzandClaude Opus 5 committed 2026-10-16 23:51:19 +03:00
1 parent e14e900276
commit 37753c6396
7 files changed
+68 -43

No files matched your search

@@ -67,10 +67,10 @@ class _ConnectionModeTileState extends State<ConnectionModeTile> {
widget.onChanged?.call(next);
}
/// Recovers from an orphaned proxy: tear the runtime down and start a fresh
/// session so a readable set of credentials exists again. No rewarded ad —
/// the user is repairing a connection they already paid for, not opening a
/// new one.
// Recovers from an orphaned proxy: tear the runtime down and re-attach to
// the session this device already holds (connect(resume: true)), so a
// readable set of credentials exists again. No rewarded ad and no new
// session — the user is repairing a connection they already paid for.
Future<void> _reconnect() async {
if (_reconnecting) return;
final vpn = context.read<VpnConnection>();
@@ -80,7 +80,7 @@ class _ConnectionModeTileState extends State<ConnectionModeTile> {
try {
await timer.disconnect();
if (!mounted) return;
if (await vpn.connect()) {
if (await vpn.connect(resume: true)) {
if (!mounted) return;
await timer.start();
}