reality configured

This commit is contained in:
esefxdz committed 2026-07-27 23:10:11 +03:00
1 parent efcfe6e706
commit 2086f9ded2
11 files changed
+205 -209

No files matched your search

+15 -8
View File
@@ -3,22 +3,29 @@
// ─────────────────────────────────────────────────────────────────────────────
// Rename this file to `app_config.dart` and fill in your real values.
// Do not commit your real `app_config.dart` to GitHub!
//
// Most Reality tunnel fields (vless_ip, vless_port, reality_pbk, reality_sid,
// reality_sni, reality_fp) come from the server's /session/status response.
// The constants below are fallbacks — the server is the source of truth.
// =============================================================================
abstract final class AppConfig {
AppConfig._();
// ── Server ─────────────────────────────────────────────────────────────
static const String serverDomain = 'yourdomain.com'; // REPLACE
static const String vlessPath = '/tunnel'; // REPLACE if different
// ── Server (API) ──────────────────────────────────────────────────────
static const String serverDomain = 'yourdomain.com'; // ◄── REPLACE
static String get hivemindApiBase => 'https://$serverDomain/api';
// ── VLESS ──────────────────────────────────────────────────────────────
static const int serverPort = 443;
static const String vlessSecurity = 'tls';
static const String vlessType = 'ws';
// ── Reality (VPN tunnel) ──────────────────────────────────────────────
// These are fallbacks — normally overridden by /session/status response.
static const String realitySni = 'www.microsoft.com';
static const String realityFp = 'chrome';
// These are authoritative — never sent by the server.
static const String vlessSecurity = 'reality';
static const String vlessType = 'tcp';
static const String vlessFlow = 'xtls-rprx-vision';
// ── AdMob ──────────────────────────────────────────────────────────────
static const String adUnitId = 'ca-app-pub-0000000000000000/0000000000'; // REPLACE
+26 -4
View File
@@ -61,11 +61,23 @@ class HivemindService {
debugPrint('[HivemindService] Nonce mismatch — retrying…');
} else if (data['active'] == true && data['vless_uuid'] != null) {
final vlessUuid = data['vless_uuid'];
final vlessPath = data['vless_path'] ?? AppConfig.vlessPath;
final vlessUrl = 'vless://$vlessUuid@${AppConfig.serverDomain}:${AppConfig.serverPort}'
'?path=${Uri.encodeComponent(vlessPath)}'
'&security=${AppConfig.vlessSecurity}'
// Reality tunnel fields — server is source of truth, AppConfig is fallback.
final vlessIp = data['vless_ip'] ?? AppConfig.serverDomain;
final vlessPort = data['vless_port'] ?? 8443;
final pbk = data['reality_pbk'] ?? '';
final sid = data['reality_sid'] ?? '';
final sni = data['reality_sni'] ?? AppConfig.realitySni;
final fp = data['reality_fp'] ?? AppConfig.realityFp;
final vlessUrl = 'vless://$vlessUuid@$vlessIp:$vlessPort'
'?security=${AppConfig.vlessSecurity}'
'&type=${AppConfig.vlessType}'
'&flow=${AppConfig.vlessFlow}'
'&pbk=${Uri.encodeComponent(pbk)}'
'&sni=${Uri.encodeComponent(sni)}'
'&sid=${Uri.encodeComponent(sid)}'
'&fp=${Uri.encodeComponent(fp)}'
'#ReVoltVPN';
_expectedNonce = null;
return vlessUrl;
@@ -82,6 +94,16 @@ class HivemindService {
throw Exception('Session not activated. Server callback may have timed out.');
}
static Future<bool> checkHealth() async {
try {
final url = Uri.parse('${AppConfig.hivemindApiBase}/health');
final response = await http.get(url).timeout(const Duration(seconds: 3));
return response.statusCode == 200;
} catch (_) {
return false;
}
}
static Future<bool> verifySession() async {
try {
final deviceId = await CryptoService.getDeviceId();
+17 -40
View File
@@ -14,7 +14,6 @@ class SessionTimer extends ChangeNotifier {
final VpnConnection vpnConnection;
int _remainingSeconds = 0;
int _quotaBytes = 0;
int _usedBytes = 0;
bool _hasSyncedOnce = false;
@@ -22,9 +21,10 @@ class SessionTimer extends ChangeNotifier {
bool _isDisconnecting = false;
bool _userInitiatedStop = false;
String? _currentPath;
bool _isThrottled = false;
bool _pathSwitchInProgress = false;
int? _currentPort;
bool _portSwitchInProgress = false;
static const int _maxConsecutiveFailures = 3;
@@ -48,9 +48,7 @@ class SessionTimer extends ChangeNotifier {
bool get isExpired => _remainingSeconds <= 0 && !isRunning;
bool get hasSyncedOnce => _hasSyncedOnce;
int get quotaBytes => _quotaBytes;
int get usedBytes => _usedBytes;
int get remainingBytes => _quotaBytes > _usedBytes ? _quotaBytes - _usedBytes : 0;
double get currentSpeedKBps => _currentSpeedKBps;
String get formatted {
@@ -60,27 +58,6 @@ class SessionTimer extends ChangeNotifier {
return '$h:$m:$s';
}
String get formattedDataRemaining {
if (_quotaBytes == 0) return '0.00 GB';
final gb = remainingBytes / (1024 * 1024 * 1024);
return '${gb.toStringAsFixed(2)} GB';
}
String get formattedDataUsed {
if (_usedBytes == 0) return '0.00 MB';
if (_usedBytes > 1024 * 1024 * 1024) {
final gb = _usedBytes / (1024 * 1024 * 1024);
return '${gb.toStringAsFixed(2)} GB';
}
final mb = _usedBytes / (1024 * 1024);
return '${mb.toStringAsFixed(2)} MB';
}
double get progress {
if (_quotaBytes == 0) return 0.0;
return (_quotaBytes - _usedBytes) / _quotaBytes;
}
// Lifecycle
/// Fires whenever VpnConnection state changes.
@@ -113,7 +90,6 @@ class SessionTimer extends ChangeNotifier {
Future<void> start(String adType) async {
_remainingSeconds = 0;
_quotaBytes = 0;
_usedBytes = 0;
_lastUsedBytes = 0;
_currentSpeedKBps = 0.0;
@@ -123,6 +99,8 @@ class SessionTimer extends ChangeNotifier {
_offlineSeconds = 0;
_isDisconnecting = false;
_userInitiatedStop = false;
_currentPort = null;
_portSwitchInProgress = false;
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), _tick);
@@ -227,7 +205,6 @@ class SessionTimer extends ChangeNotifier {
}
_remainingSeconds = data['expires_in_seconds'] ?? _remainingSeconds;
_quotaBytes = data['quota_bytes'] ?? _quotaBytes;
_usedBytes = data['used_bytes'] ?? _usedBytes;
@@ -240,27 +217,27 @@ class SessionTimer extends ChangeNotifier {
_lastUsedBytes = _usedBytes;
// ── Detect path changes (throttle engage / disengage) ──────
final serverPath = data['vless_path'] as String?;
if (serverPath != null &&
_currentPath != null &&
serverPath != _currentPath &&
!_pathSwitchInProgress) {
debugPrint('[Timer] Path changed $_currentPath → $serverPath — reconnecting…');
_currentPath = serverPath;
// ── Detect port change (throttle engage / disengage) ──────
final serverPort = data['vless_port'];
if (serverPort != null &&
_currentPort != null &&
serverPort != _currentPort &&
!_portSwitchInProgress) {
debugPrint('[Timer] Port changed $_currentPort → $serverPort — reconnecting…');
_currentPort = serverPort;
_isThrottled = data['is_throttled'] ?? false;
_pathSwitchInProgress = true;
_portSwitchInProgress = true;
_timer?.cancel();
_isDisconnecting = true; // block _stopInternal during relocation
_isDisconnecting = true;
await vpnConnection.disconnect(skipCleanup: true);
await vpnConnection.connect(skipAdBypass: true, quickReconnect: true);
_resumeTicking();
_pathSwitchInProgress = false;
_portSwitchInProgress = false;
return;
}
_currentPath = serverPath;
_currentPort = serverPort;
_isThrottled = data['is_throttled'] ?? false;
_consecutiveFailures = 0;
+17 -3
View File
@@ -26,6 +26,11 @@ class VpnConnection extends ChangeNotifier {
bool _isStartupRestoration = false;
bool get isStartupRestoration => _isStartupRestoration;
bool _serverReachable = false;
bool get serverReachable => _serverReachable;
Timer? _healthTimer;
late final FlutterVless _vless;
bool _initialized = false;
@@ -53,6 +58,10 @@ class VpnConnection extends ChangeNotifier {
debugPrint('[VPN] VLESS init error (expected on emulator): $e');
}
// Periodic server health check
_checkHealth();
_healthTimer = Timer.periodic(const Duration(seconds: 30), (_) => _checkHealth());
// Check if VPN was already running (app killed and reopened)
try {
final coreVersion = await _vless.getCoreVersion();
@@ -62,9 +71,6 @@ class VpnConnection extends ChangeNotifier {
if (delay > 0) {
_isStartupRestoration = true;
_setStatus(VpnStatus.connected, 'Secured');
Future.delayed(const Duration(seconds: 5), () {
_isStartupRestoration = false;
});
}
} catch (_) {}
}
@@ -75,12 +81,14 @@ class VpnConnection extends ChangeNotifier {
_setStatus(VpnStatus.connected, 'Secured');
break;
case VlessConnectionState.disconnected:
_isStartupRestoration = false;
_setStatus(VpnStatus.disconnected, 'Tap to connect');
break;
case VlessConnectionState.connecting:
_setStatus(VpnStatus.connecting, 'Establishing tunnel…');
break;
case VlessConnectionState.disconnecting:
_isStartupRestoration = false;
_setStatus(VpnStatus.disconnecting, 'Tearing down…');
break;
case VlessConnectionState.unknown:
@@ -234,8 +242,14 @@ class VpnConnection extends ChangeNotifier {
notifyListeners();
}
Future<void> _checkHealth() async {
_serverReachable = await HivemindService.checkHealth();
notifyListeners();
}
@override
void dispose() {
_healthTimer?.cancel();
if (_status == VpnStatus.connected || _status == VpnStatus.connecting) {
try {
_vless.stopVless();