FUCKING REFACTOR

This commit is contained in:
ivan2282
2025-11-19 18:45:44 +03:00
parent 2d11f1cba2
commit 575c43ce63
41 changed files with 2915 additions and 2910 deletions

View File

@@ -0,0 +1,719 @@
part of 'api_service.dart';
extension ApiServiceConnection on ApiService {
Future<void> _connectWithFallback() async {
_log('Начало подключения...');
while (_currentUrlIndex < _wsUrls.length) {
final currentUrl = _wsUrls[_currentUrlIndex];
final logMessage =
'Попытка ${_currentUrlIndex + 1}/${_wsUrls.length}: $currentUrl';
_log(logMessage);
_connectionLogController.add(logMessage);
try {
await _connectToUrl(currentUrl);
final successMessage = _currentUrlIndex == 0
? 'Подключено к основному серверу'
: 'Подключено через резервный сервер';
_connectionLogController.add('$successMessage');
if (_currentUrlIndex > 0) {
_connectionStatusController.add('Подключено через резервный сервер');
}
return;
} catch (e) {
final errorMessage = '❌ Ошибка: ${e.toString().split(':').first}';
print('Ошибка подключения к $currentUrl: $e');
_connectionLogController.add(errorMessage);
_currentUrlIndex++;
if (_currentUrlIndex < _wsUrls.length) {
await Future.delayed(const Duration(milliseconds: 500));
}
}
}
_log('Все серверы недоступны');
_connectionStatusController.add('Все серверы недоступны');
throw Exception('Не удалось подключиться ни к одному серверу');
}
Future<void> _connectToUrl(String url) async {
_isSessionOnline = false;
_onlineCompleter = Completer<void>();
final bool hadChatsFetched = _chatsFetchedInThisSession;
final bool hasValidToken = authToken != null;
if (!hasValidToken) {
_chatsFetchedInThisSession = false;
} else {
_chatsFetchedInThisSession = hadChatsFetched;
}
_connectionStatusController.add('connecting');
final uri = Uri.parse(url);
print(
'Parsed URI: host=${uri.host}, port=${uri.port}, scheme=${uri.scheme}',
);
final spoofedData = await SpoofingService.getSpoofedSessionData();
final userAgent =
spoofedData?['useragent'] as String? ??
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
final headers = <String, String>{
'Origin': 'https://web.max.ru',
'User-Agent': userAgent,
'Sec-WebSocket-Extensions': 'permessage-deflate',
};
final proxySettings = await ProxyService.instance.loadProxySettings();
if (proxySettings.isEnabled && proxySettings.host.isNotEmpty) {
print(
'Используем ${proxySettings.protocol.name.toUpperCase()} прокси ${proxySettings.host}:${proxySettings.port}',
);
final customHttpClient =
await ProxyService.instance.getHttpClientWithProxy();
_channel = IOWebSocketChannel.connect(
uri,
headers: headers,
customClient: customHttpClient,
);
} else {
print('Подключение без прокси');
_channel = IOWebSocketChannel.connect(uri, headers: headers);
}
await _channel!.ready;
_listen();
await _sendHandshake();
_startPinging();
}
void _handleSessionTerminated() {
print("Сессия была завершена сервером");
_isSessionOnline = false;
_isSessionReady = false;
authToken = null;
clearAllCaches();
_messageController.add({
'type': 'session_terminated',
'message': 'Твоя сессия больше не активна, войди снова',
});
}
void _handleInvalidToken() async {
print("Обработка недействительного токена");
_isSessionOnline = false;
_isSessionReady = false;
authToken = null;
final prefs = await SharedPreferences.getInstance();
await prefs.remove('authToken');
clearAllCaches();
_channel?.sink.close();
_channel = null;
_pingTimer?.cancel();
_messageController.add({
'type': 'invalid_token',
'message': 'Токен недействителен, требуется повторная авторизация',
});
}
Future<void> _sendHandshake() async {
if (_handshakeSent) {
print('Handshake уже отправлен, пропускаем...');
return;
}
print('Отправляем handshake...');
final userAgentPayload = await _buildUserAgentPayload();
final prefs = await SharedPreferences.getInstance();
final deviceId = prefs.getString('spoof_deviceid') ?? generateRandomDeviceId();
if (prefs.getString('spoof_deviceid') == null) {
await prefs.setString('spoof_deviceid', deviceId);
}
final payload = {'deviceId': deviceId, 'userAgent': userAgentPayload};
print('Отправляем handshake с payload: $payload');
_sendMessage(6, payload);
_handshakeSent = true;
print('Handshake отправлен, ожидаем ответ...');
}
void _startPinging() {
_pingTimer?.cancel();
_pingTimer = Timer.periodic(const Duration(seconds: 25), (timer) {
if (_isSessionOnline && _isSessionReady && _isAppInForeground) {
print("Отправляем Ping для поддержания сессии...");
_sendMessage(1, {"interactive": true});
} else {
print("Сессия не готова, пропускаем ping");
}
});
}
Future<void> connect() async {
if (_channel != null && _isSessionOnline) {
print("WebSocket уже подключен, пропускаем подключение");
return;
}
print("Запускаем подключение к WebSocket...");
_isSessionOnline = false;
_isSessionReady = false;
_connectionStatusController.add("connecting");
await _connectWithFallback();
}
Future<void> reconnect() async {
_reconnectAttempts = 0;
_currentUrlIndex = 0;
_connectionStatusController.add("connecting");
await _connectWithFallback();
}
void sendFullJsonRequest(String jsonString) {
if (_channel == null) {
throw Exception('WebSocket is not connected. Connect first.');
}
_log('➡️ SEND (raw): $jsonString');
_channel!.sink.add(jsonString);
}
int sendRawRequest(int opcode, Map<String, dynamic> payload) {
if (_channel == null) {
print('WebSocket не подключен!');
throw Exception('WebSocket is not connected. Connect first.');
}
return _sendMessage(opcode, payload);
}
int sendAndTrackFullJsonRequest(String jsonString) {
if (_channel == null) {
throw Exception('WebSocket is not connected. Connect first.');
}
final message = jsonDecode(jsonString) as Map<String, dynamic>;
final int currentSeq = _seq++;
message['seq'] = currentSeq;
final encodedMessage = jsonEncode(message);
_log('➡️ SEND (custom): $encodedMessage');
print('Отправляем кастомное сообщение (seq: $currentSeq): $encodedMessage');
_channel!.sink.add(encodedMessage);
return currentSeq;
}
int _sendMessage(int opcode, Map<String, dynamic> payload) {
if (_channel == null) {
print('WebSocket не подключен!');
return -1;
}
final message = {
"ver": 11,
"cmd": 0,
"seq": _seq,
"opcode": opcode,
"payload": payload,
};
final encodedMessage = jsonEncode(message);
if (opcode == 1) {
_log('➡️ SEND (ping) seq: $_seq');
} else if (opcode == 18 || opcode == 19) {
Map<String, dynamic> loggablePayload = Map.from(payload);
if (loggablePayload.containsKey('token')) {
String token = loggablePayload['token'] as String;
loggablePayload['token'] = token.length > 8
? '${token.substring(0, 4)}...${token.substring(token.length - 4)}'
: '***';
}
final loggableMessage = {...message, 'payload': loggablePayload};
_log('➡️ SEND: ${jsonEncode(loggableMessage)}');
} else {
_log('➡️ SEND: $encodedMessage');
}
print('Отправляем сообщение (seq: $_seq): $encodedMessage');
_channel!.sink.add(encodedMessage);
return _seq++;
}
void _listen() async {
_streamSubscription?.cancel();
_streamSubscription = _channel?.stream.listen(
(message) {
if (message == null) return;
if (message is String && message.trim().isEmpty) {
return;
}
String loggableMessage = message;
try {
final decoded = jsonDecode(message) as Map<String, dynamic>;
if (decoded['opcode'] == 2) {
loggableMessage = '⬅️ RECV (pong) seq: ${decoded['seq']}';
} else {
Map<String, dynamic> loggableDecoded = Map.from(decoded);
bool wasModified = false;
if (loggableDecoded.containsKey('payload') &&
loggableDecoded['payload'] is Map) {
Map<String, dynamic> payload = Map.from(
loggableDecoded['payload'],
);
if (payload.containsKey('token')) {
String token = payload['token'] as String;
payload['token'] = token.length > 8
? '${token.substring(0, 4)}...${token.substring(token.length - 4)}'
: '***';
loggableDecoded['payload'] = payload;
wasModified = true;
}
}
if (wasModified) {
loggableMessage = '⬅️ RECV: ${jsonEncode(loggableDecoded)}';
} else {
loggableMessage = '⬅️ RECV: $message';
}
}
} catch (_) {
loggableMessage = '⬅️ RECV (raw): $message';
}
_log(loggableMessage);
try {
final decodedMessage =
message is String ? jsonDecode(message) : message;
if (decodedMessage is Map &&
decodedMessage['opcode'] == 97 &&
decodedMessage['cmd'] == 1 &&
decodedMessage['payload'] != null &&
decodedMessage['payload']['token'] != null) {
_handleSessionTerminated();
return;
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 6 &&
decodedMessage['cmd'] == 1) {
print("Handshake успешен. Сессия ONLINE.");
_isSessionOnline = true;
_isSessionReady = false;
_reconnectDelaySeconds = 2;
_connectionStatusController.add("authorizing");
if (_onlineCompleter != null && !_onlineCompleter!.isCompleted) {
_onlineCompleter!.complete();
}
_startPinging();
_processMessageQueue();
}
if (decodedMessage is Map && decodedMessage['cmd'] == 3) {
final error = decodedMessage['payload'];
print('Ошибка сервера: $error');
if (error != null && error['localizedMessage'] != null) {
_errorController.add(error['localizedMessage']);
} else if (error != null && error['message'] != null) {
_errorController.add(error['message']);
}
if (error != null && error['message'] == 'FAIL_WRONG_PASSWORD') {
_errorController.add('FAIL_WRONG_PASSWORD');
}
if (error != null && error['error'] == 'password.invalid') {
_errorController.add('Неверный пароль');
}
if (error != null && error['error'] == 'proto.state') {
print('Ошибка состояния сессии, переподключаемся...');
_chatsFetchedInThisSession = false;
_reconnect();
return;
}
if (error != null && error['error'] == 'login.token') {
print('Токен недействителен, очищаем и завершаем сессию...');
_handleInvalidToken();
return;
}
if (error != null && error['message'] == 'FAIL_WRONG_PASSWORD') {
print('Неверный токен авторизации, очищаем токен...');
_clearAuthToken().then((_) {
_chatsFetchedInThisSession = false;
_messageController.add({
'type': 'invalid_token',
'message':
'Токен авторизации недействителен. Требуется повторная авторизация.',
});
_reconnect();
});
return;
}
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 18 &&
decodedMessage['cmd'] == 1 &&
decodedMessage['payload'] != null) {
final payload = decodedMessage['payload'];
if (payload['passwordChallenge'] != null) {
final challenge = payload['passwordChallenge'];
_currentPasswordTrackId = challenge['trackId'];
_currentPasswordHint = challenge['hint'];
_currentPasswordEmail = challenge['email'];
print(
'Получен запрос на ввод пароля: trackId=${challenge['trackId']}, hint=${challenge['hint']}, email=${challenge['email']}',
);
_messageController.add({
'type': 'password_required',
'trackId': _currentPasswordTrackId,
'hint': _currentPasswordHint,
'email': _currentPasswordEmail,
});
return;
}
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 22 &&
decodedMessage['cmd'] == 1) {
final payload = decodedMessage['payload'];
print('Настройки приватности успешно обновлены: $payload');
_messageController.add({
'type': 'privacy_settings_updated',
'settings': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 116 &&
decodedMessage['cmd'] == 1) {
final payload = decodedMessage['payload'];
print('Пароль успешно установлен: $payload');
_messageController.add({
'type': 'password_set_success',
'payload': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 57 &&
decodedMessage['cmd'] == 1) {
final payload = decodedMessage['payload'];
print('Успешно присоединились к группе: $payload');
_messageController.add({
'type': 'group_join_success',
'payload': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 46 &&
decodedMessage['cmd'] == 1) {
final payload = decodedMessage['payload'];
print('Контакт найден: $payload');
_messageController.add({
'type': 'contact_found',
'payload': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 46 &&
decodedMessage['cmd'] == 3) {
final payload = decodedMessage['payload'];
print('Контакт не найден: $payload');
_messageController.add({
'type': 'contact_not_found',
'payload': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 32 &&
decodedMessage['cmd'] == 1) {
final payload = decodedMessage['payload'];
print('Каналы найдены: $payload');
_messageController.add({
'type': 'channels_found',
'payload': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 32 &&
decodedMessage['cmd'] == 3) {
final payload = decodedMessage['payload'];
print('Каналы не найдены: $payload');
_messageController.add({
'type': 'channels_not_found',
'payload': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 89 &&
decodedMessage['cmd'] == 1) {
final payload = decodedMessage['payload'];
print('Вход в канал успешен: $payload');
_messageController.add({
'type': 'channel_entered',
'payload': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 89 &&
decodedMessage['cmd'] == 3) {
final payload = decodedMessage['payload'];
print('Ошибка входа в канал: $payload');
_messageController.add({
'type': 'channel_error',
'payload': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 57 &&
decodedMessage['cmd'] == 1) {
final payload = decodedMessage['payload'];
print('Подписка на канал успешна: $payload');
_messageController.add({
'type': 'channel_subscribed',
'payload': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 57 &&
decodedMessage['cmd'] == 3) {
final payload = decodedMessage['payload'];
print('Ошибка подписки на канал: $payload');
_messageController.add({
'type': 'channel_error',
'payload': payload,
});
}
if (decodedMessage is Map &&
decodedMessage['opcode'] == 59 &&
decodedMessage['cmd'] == 1) {
final payload = decodedMessage['payload'];
print('Получены участники группы: $payload');
_messageController.add({
'type': 'group_members',
'payload': payload,
});
}
if (decodedMessage is Map<String, dynamic>) {
_messageController.add(decodedMessage);
}
} catch (e) {
print('Невалидное сообщение от сервера, пропускаем: $e');
}
},
onError: (error) {
print('Ошибка WebSocket: $error');
_isSessionOnline = false;
_isSessionReady = false;
_reconnect();
},
onDone: () {
print('WebSocket соединение закрыто. Попытка переподключения...');
_isSessionOnline = false;
_isSessionReady = false;
if (!_isSessionReady) {
_reconnect();
}
},
cancelOnError: true,
);
}
void _reconnect() {
if (_isReconnecting) return;
_isReconnecting = true;
_reconnectAttempts++;
if (_reconnectAttempts > ApiService._maxReconnectAttempts) {
print(
"Превышено максимальное количество попыток переподключения (${ApiService._maxReconnectAttempts}). Останавливаем попытки.",
);
_connectionStatusController.add("disconnected");
_isReconnecting = false;
return;
}
_pingTimer?.cancel();
_reconnectTimer?.cancel();
_isSessionOnline = false;
_isSessionReady = false;
_onlineCompleter = Completer<void>();
_chatsFetchedInThisSession = false;
clearAllCaches();
_currentUrlIndex = 0;
_reconnectDelaySeconds = (_reconnectDelaySeconds * 2).clamp(1, 30);
final jitter = (DateTime.now().millisecondsSinceEpoch % 1000) / 1000.0;
final delay = Duration(seconds: _reconnectDelaySeconds + jitter.round());
_reconnectTimer = Timer(delay, () {
print(
"Переподключаемся после ${delay.inSeconds}s... (попытка $_reconnectAttempts/${ApiService._maxReconnectAttempts})",
);
_isReconnecting = false;
_connectWithFallback();
});
}
void _processMessageQueue() {
if (_messageQueue.isEmpty) return;
print("Отправка ${_messageQueue.length} сообщений из очереди...");
for (var message in _messageQueue) {
_sendMessage(message['opcode'], message['payload']);
}
_messageQueue.clear();
}
void forceReconnect() {
print("Принудительное переподключение...");
_pingTimer?.cancel();
_reconnectTimer?.cancel();
if (_channel != null) {
print("Закрываем существующее соединение...");
_channel!.sink.close(status.goingAway);
_channel = null;
}
_isReconnecting = false;
_reconnectAttempts = 0;
_reconnectDelaySeconds = 2;
_isSessionOnline = false;
_isSessionReady = false;
_chatsFetchedInThisSession = false;
_currentUrlIndex = 0;
_onlineCompleter = Completer<void>();
clearAllCaches();
_messageQueue.clear();
_presenceData.clear();
_connectionStatusController.add("connecting");
_log("Запускаем новую сессию подключения...");
_connectWithFallback();
}
Future<void> performFullReconnection() async {
print("🔄 Начинаем полное переподключение...");
try {
_pingTimer?.cancel();
_reconnectTimer?.cancel();
_streamSubscription?.cancel();
if (_channel != null) {
_channel!.sink.close();
_channel = null;
}
_isReconnecting = false;
_reconnectAttempts = 0;
_reconnectDelaySeconds = 2;
_isSessionOnline = false;
_isSessionReady = false;
_handshakeSent = false;
_chatsFetchedInThisSession = false;
_currentUrlIndex = 0;
_onlineCompleter = Completer<void>();
_seq = 0;
_lastChatsPayload = null;
_lastChatsAt = null;
print(
" Кэш чатов очищен: _lastChatsPayload = $_lastChatsPayload, _chatsFetchedInThisSession = $_chatsFetchedInThisSession",
);
_connectionStatusController.add("disconnected");
await connect();
print(" Полное переподключение завершено");
await Future.delayed(const Duration(milliseconds: 1500));
if (!_reconnectionCompleteController.isClosed) {
print(" Отправляем уведомление о завершении переподключения");
_reconnectionCompleteController.add(null);
}
} catch (e) {
print("Ошибка полного переподключения: $e");
rethrow;
}
}
void disconnect() {
print("Отключаем WebSocket...");
_pingTimer?.cancel();
_reconnectTimer?.cancel();
_streamSubscription?.cancel();
_isSessionOnline = false;
_isSessionReady = false;
_handshakeSent = false;
_onlineCompleter = Completer<void>();
_chatsFetchedInThisSession = false;
_channel?.sink.close(status.goingAway);
_channel = null;
_streamSubscription = null;
_connectionStatusController.add("disconnected");
}
}