надеюсь будет работать сукi

This commit is contained in:
Jganenokk
2026-07-05 23:43:15 +07:00
parent 7ce7cf7a1d
commit 815e41648a
10 changed files with 419 additions and 117 deletions
+162 -70
View File
@@ -70,10 +70,16 @@ class Api {
StreamSubscription<SocketState>? _socketStateSubscription;
Timer? _pingTimer;
Timer? _reconnectTimer;
Timer? _connectWatchdog;
int _connectGen = 0;
int _reconnectAttempts = 0;
bool _autoReconnect = false;
int _sessionEpoch = 0;
static const Duration _connectWatchdogTimeout = Duration(seconds: 75);
static const Duration _shouldArmTimeout = Duration(seconds: 5);
static const Duration _endpointTimeout = Duration(seconds: 5);
int get sessionEpoch => _sessionEpoch;
/// Залипает на время сессии: VPN-путь не сработал — идём мимо туннеля.
@@ -83,87 +89,168 @@ class Api {
/// Подключается к серверу, шлёт хэндшейк, запускает пинг.
Future<void> connect() async {
if (_sessionState != SessionState.disconnected) return;
// Ставим автоматический реконнект и статус подключения
_autoReconnect = true;
_setSessionState(SessionState.connecting);
_dataSubscription = _connection.dataStream.listen(_onDataReceived);
_socketStateSubscription = _connection.stateStream.listen((socketState) {
if (socketState == SocketState.disconnected &&
_sessionState != SessionState.disconnected) {
_onDisconnected();
}
});
final bypassArmed = await VpnBypassService.instance.shouldArm();
if (!bypassArmed) _bypassActive = false;
final useBypass = _bypassActive && bypassArmed;
// Попытку через VPN ограничиваем по времени, чтобы быстро понять,
// что туннель не пропускает, и переключиться на обход.
final attemptTimeout = bypassArmed && !useBypass
? const Duration(seconds: 8)
: null;
try {
final endpoint = await ServerConfig.loadEndpoint();
await _connection.connect(
endpoint.host,
endpoint.port,
bypassVpn: useBypass,
timeout: attemptTimeout,
);
} catch (e) {
await _handleConnectFailure(
e,
phase: 'Не удалось подключиться',
bypassArmed: bypassArmed,
useBypass: useBypass,
bypassWhy: 'подключение не удалось',
);
if (_sessionState != SessionState.disconnected) {
logger.i('connect пропущен: состояние ${_sessionState.name}');
return;
}
_setSessionState(SessionState.connected);
_reconnectAttempts = 0;
_autoReconnect = true;
final gen = ++_connectGen;
_setSessionState(SessionState.connecting);
logger.i('connect: старт (поколение $gen)');
_armConnectWatchdog(gen);
try {
final response = await sendHandshake();
if (response.isOk) {
_callsSeed = response.payload['callsSeed'] as int?;
_registrationCountries = _parseRegistrationCountries(response.payload);
_sessionState = SessionState.online;
_sessionEpoch++;
_startPinging();
logger.i('Сессия онлайн, хэндшейк ок');
if (_onReconnectCallback != null) {
try {
await _onReconnectCallback!();
} catch (e) {
logger.w('Авто-логин при хэндшейке не удался: $e');
}
_dataSubscription = _connection.dataStream.listen(_onDataReceived);
_socketStateSubscription = _connection.stateStream.listen((socketState) {
if (socketState == SocketState.disconnected &&
_sessionState != SessionState.disconnected) {
_onDisconnected();
}
if (_sessionState == SessionState.online) {
_stateController.add(SessionState.online);
_handshakeSuccessController.add(
response.payload['device_name'] as String? ?? 'Unknown',
});
bool bypassArmed;
try {
bypassArmed = await VpnBypassService.instance.shouldArm().timeout(
_shouldArmTimeout,
);
} catch (e) {
logger.w('connect: shouldArm завис/упал ($e) — без обхода VPN');
bypassArmed = false;
}
if (gen != _connectGen) return;
if (!bypassArmed) _bypassActive = false;
final useBypass = _bypassActive && bypassArmed;
final attemptTimeout = bypassArmed && !useBypass
? const Duration(seconds: 8)
: null;
({String host, int port}) endpoint;
try {
endpoint = await ServerConfig.loadEndpoint().timeout(_endpointTimeout);
} catch (e) {
logger.w('connect: loadEndpoint завис/упал ($e) — дефолтный endpoint');
endpoint = (
host: ServerConfig.defaultHost,
port: ServerConfig.defaultPort,
);
}
if (gen != _connectGen) return;
logger.i(
'connect: endpoint ${endpoint.host}:${endpoint.port}, bypass=$useBypass',
);
try {
await _connection.connect(
endpoint.host,
endpoint.port,
bypassVpn: useBypass,
timeout: attemptTimeout,
);
} catch (e) {
if (gen != _connectGen) return;
await _handleConnectFailure(
e,
phase: 'Не удалось подключиться',
bypassArmed: bypassArmed,
useBypass: useBypass,
bypassWhy: 'подключение не удалось',
);
return;
}
if (gen != _connectGen) return;
_setSessionState(SessionState.connected);
_reconnectAttempts = 0;
try {
logger.i('connect: сокет готов, отправляю хэндшейк');
final response = await sendHandshake();
if (gen != _connectGen) return;
if (response.isOk) {
_callsSeed = response.payload['callsSeed'] as int?;
_registrationCountries = _parseRegistrationCountries(
response.payload,
);
_sessionState = SessionState.online;
_sessionEpoch++;
_cancelConnectWatchdog();
_startPinging();
logger.i('Сессия онлайн, хэндшейк ок');
if (_onReconnectCallback != null) {
try {
await _onReconnectCallback!();
} catch (e) {
logger.w('Авто-логин при хэндшейке не удался: $e');
}
}
if (_sessionState == SessionState.online) {
_stateController.add(SessionState.online);
_handshakeSuccessController.add(
response.payload['device_name'] as String? ?? 'Unknown',
);
}
} else {
logger.e('Хэндшейк отклонён: ${response.payload}');
await _handleConnectFailure(
StateError('хэндшейк отклонён сервером'),
phase: 'Хэндшейк отклонён',
bypassArmed: bypassArmed,
useBypass: useBypass,
bypassWhy: 'хэндшейк отклонён',
disconnectSocket: true,
);
}
} else {
logger.e('Хэндшейк отклонён: ${response.payload}');
} catch (e) {
if (gen != _connectGen) return;
await _handleConnectFailure(
e,
phase: 'Ошибка хэндшейка',
bypassArmed: bypassArmed,
useBypass: useBypass,
bypassWhy: 'хэндшейк не прошёл',
disconnectSocket: true,
);
}
} catch (e) {
await _handleConnectFailure(
e,
phase: 'Ошибка хэндшейка',
bypassArmed: bypassArmed,
useBypass: useBypass,
bypassWhy: 'хэндшейк не прошёл',
disconnectSocket: true,
);
} catch (e, st) {
logger.e('connect: непредвиденная ошибка: $e\n$st');
if (gen == _connectGen) await _resetStuckConnect(gen);
}
}
void _armConnectWatchdog(int gen) {
_connectWatchdog?.cancel();
_connectWatchdog = Timer(_connectWatchdogTimeout, () {
if (gen != _connectGen) return;
if (_sessionState == SessionState.online ||
_sessionState == SessionState.disconnected) {
return;
}
logger.e(
'connect: watchdog ${_connectWatchdogTimeout.inSeconds}с — застряли в '
'${_sessionState.name}, принудительный сброс',
);
unawaited(_resetStuckConnect(gen));
});
}
void _cancelConnectWatchdog() {
_connectWatchdog?.cancel();
_connectWatchdog = null;
}
Future<void> _resetStuckConnect(int gen) async {
if (gen != _connectGen) return;
_connectGen++;
_cancelConnectWatchdog();
_cleanup();
try {
await _connection.disconnect();
} catch (_) {}
_setSessionState(SessionState.disconnected);
if (_autoReconnect) _scheduleReconnect();
}
Future<void> _handleConnectFailure(
Object error, {
required String phase,
@@ -173,6 +260,7 @@ class Api {
bool disconnectSocket = false,
}) async {
logger.e('$phase: $error');
_cancelConnectWatchdog();
if (_sessionState != SessionState.disconnected) {
_cleanup();
if (disconnectSocket) await _connection.disconnect();
@@ -193,6 +281,7 @@ class Api {
Future<void> disconnect() async {
_autoReconnect = false;
_bypassActive = false;
_connectGen++;
_reconnectTimer?.cancel();
_cleanup();
await _connection.disconnect();
@@ -441,6 +530,7 @@ class Api {
}
void _onDisconnected() {
_connectGen++;
_cleanup();
_setSessionState(SessionState.disconnected);
if (_autoReconnect) _scheduleReconnect();
@@ -463,6 +553,7 @@ class Api {
}
Future<void> _forceReconnect() async {
_connectGen++;
_cleanup();
await _connection.disconnect();
_reconnectAttempts = 0;
@@ -472,6 +563,7 @@ class Api {
}
void _cleanup() {
_cancelConnectWatchdog();
_pingTimer?.cancel();
_dataSubscription?.cancel();
_socketStateSubscription?.cancel();