frontend polish and security review changes

This commit is contained in:
esefxdz committed 2026-08-22 11:34:17 +03:00
1 parent 5642b0b811
commit 71468f7594
13 files changed
+226 -227

No files matched your search

+24
View File
@@ -6,6 +6,7 @@ import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:revoltvpn/logic/crypto_service.dart';
import 'package:revoltvpn/logic/hivemind_service.dart';
import 'package:revoltvpn/logic/app_config.dart';
import 'package:revoltvpn/logic/consent_manager.dart';
class AdManager extends ChangeNotifier {
static const bool adsEnabled = false;
@@ -27,8 +28,29 @@ class AdManager extends ChangeNotifier {
if (adsEnabled) preloadAd();
}
static Future<void>? _sdkInit;
static Future<void> ensureSdkInitialized() {
if (!adsEnabled) return Future.value();
return _sdkInit ??= _initSdk();
}
static Future<void> _initSdk() async {
try {
await ConsentManager.requestConsentIfNeeded()
.timeout(const Duration(seconds: 5));
} catch (e) {
debugPrint('[AdManager] Consent init skipped: $e');
}
try {
await MobileAds.instance.initialize().timeout(const Duration(seconds: 5));
} catch (e) {
debugPrint('[AdManager] MobileAds init skipped: $e');
}
}
Future<bool> preloadAd() async {
if (!adsEnabled) return true;
await ensureSdkInitialized();
if (_isAdLoaded) return true;
if (_isAdLoading) return _loadCompleter?.future ?? Future.value(false);
@@ -101,6 +123,8 @@ class AdManager extends ChangeNotifier {
if (!adsEnabled) return false;
await ensureSdkInitialized();
if (!_isAdLoaded || _rewardedAd == null) {
final loaded = await preloadAd();
if (!loaded || _rewardedAd == null) {
-3
View File
@@ -9,9 +9,6 @@ abstract final class AppConfig {
/// Your server's public IP. The VPN tunnel is pinned to this.
static const String serverIp = '0.0.0.0'; // ◄── REPLACE
/// API base — routed through the tunnel to Flask via Xray "api" outbound.
static const String hivemindApiBase = 'http://10.254.254.1:5000/api';
// ── Bootstrap (first-connect config fetch) ────────────────────────────
/// Hardcoded Reality config. App connects with this, fetches the real
/// per-session VLESS URL through the tunnel, then reconnects.
-81
View File
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
@@ -18,66 +17,6 @@ class HivemindService {
return http.get(uri, headers: {'User-Agent': _ua}).timeout(timeout);
}
static Future<http.Response> directPost(Uri uri, String body, {Duration timeout = const Duration(seconds: 5)}) {
return http.post(uri, headers: {
'Content-Type': 'application/json',
'User-Agent': _ua,
}, body: body).timeout(timeout);
}
static Future<http.Response> proxyGet(Uri uri, {Duration timeout = const Duration(seconds: 5)}) {
return _proxyRequest('GET', uri, timeout: timeout);
}
static Future<http.Response> proxyPost(Uri uri, String body, {Duration timeout = const Duration(seconds: 5)}) {
return _proxyRequest('POST', uri, body: body, timeout: timeout);
}
static Future<http.Response> _proxyRequest(String method, Uri uri,
{String? body, Duration timeout = const Duration(seconds: 5)}) async {
final socket = await Socket.connect('127.0.0.1', 10808, timeout: timeout);
try {
final target = uri.toString();
final headers = <String, String>{
'Host': '${uri.host}:${uri.port}',
'User-Agent': _ua,
'Accept': '*/*',
'Connection': 'close',
};
if (body != null) {
headers['Content-Type'] = 'application/json';
headers['Content-Length'] = body.length.toString();
}
final reqBuf = StringBuffer();
reqBuf.write('$method $target HTTP/1.1\r\n');
headers.forEach((k, v) => reqBuf.write('$k: $v\r\n'));
reqBuf.write('\r\n');
if (body != null) reqBuf.write(body);
socket.write(reqBuf.toString());
await socket.flush();
final response = await socket.fold<String>(
'',
(prev, chunk) => prev + String.fromCharCodes(chunk),
).timeout(timeout);
final parts = response.split('\r\n\r\n');
if (parts.length < 2) throw Exception('Invalid proxy response');
final headerBlock = parts[0];
final respBody = parts.sublist(1).join('\r\n\r\n');
final headerLines = headerBlock.split('\r\n');
final statusParts = headerLines[0].split(' ');
final statusCode = int.parse(statusParts[1]);
return http.Response(respBody, statusCode);
} finally {
socket.destroy();
}
}
static void cancel() {
_currentCallId++;
}
@@ -166,28 +105,8 @@ class HivemindService {
}
}
static Future<bool> verifySession() async {
try {
final deviceId = await CryptoService.getDeviceId();
final url = _tunnelUrl('/session/status?device_id=$deviceId');
final response = await proxyGet(url);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['active'] == true;
}
} catch (_) {}
return false;
}
// Idle sessions are culled server-side by the management loop
// after 2 minutes of no /status polls (user disconnected).
static void disconnectAndCleanup() {}
// ── URL builders ──────────────────────────────────────────────────
static Uri _publicUrl(String path) =>
Uri.parse('${AppConfig.hivemindApiPublic}$path');
static Uri _tunnelUrl(String path) =>
Uri.parse('${AppConfig.hivemindApiBase}$path');
}
+13 -14
View File
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter_vless/flutter_vless.dart';
import 'package:revoltvpn/logic/hivemind_service.dart';
@@ -33,12 +32,24 @@ class VpnConnection extends ChangeNotifier {
late final FlutterVless _vless;
bool _initialized = false;
final Completer<void> _readyCompleter = Completer<void>();
Future<void> get ready => _readyCompleter.future;
VpnConnection() {
_init();
}
Future<void> _init() async {
try {
await _startEngine();
} catch (e) {
debugPrint('[VPN] Engine init failed: $e');
} finally {
if (!_readyCompleter.isCompleted) _readyCompleter.complete();
}
}
Future<void> _startEngine() async {
if (kIsWeb) return;
_vless = FlutterVless(
@@ -177,22 +188,10 @@ class VpnConnection extends ChangeNotifier {
try {
final parsed = FlutterVless.parse(realUrl);
final configJson =
jsonDecode(parsed.getFullConfiguration()) as Map<String, dynamic>;
// Inject HTTP inbound for tunnel-internal API calls.
final inbounds = (configJson['inbounds'] as List?) ?? [];
inbounds.add({
'tag': 'http-proxy',
'port': 10808,
'listen': '127.0.0.1',
'protocol': 'http',
});
configJson['inbounds'] = inbounds;
await _vless.startVless(
remark: parsed.remark.isNotEmpty ? parsed.remark : 'Revolt VPN',
config: jsonEncode(configJson),
config: parsed.getFullConfiguration(),
);
} catch (e) {
debugPrint('[VPN] Tunnel start error: $e');