library api_service; import 'dart:async'; import 'dart:convert'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/services.dart'; import 'package:gwid/models/contact.dart'; import 'package:gwid/models/message.dart'; import 'package:gwid/models/profile.dart'; import 'package:gwid/proxy_service.dart'; import 'package:gwid/services/account_manager.dart'; import 'package:gwid/spoofing_service.dart'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart'; import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/status.dart' as status; part 'api_service_connection.dart'; part 'api_service_auth.dart'; part 'api_service_contacts.dart'; part 'api_service_chats.dart'; part 'api_service_media.dart'; part 'api_service_privacy.dart'; class ApiService { ApiService._privateConstructor(); static final ApiService instance = ApiService._privateConstructor(); int? _userId; late int _sessionId; int _actionId = 1; bool _isColdStartSent = false; late int _lastActionTime; bool _isAppInForeground = true; final List _wsUrls = ['wss://ws-api.oneme.ru:443/websocket']; int _currentUrlIndex = 0; List get wsUrls => _wsUrls; int get currentUrlIndex => _currentUrlIndex; IOWebSocketChannel? _channel; StreamSubscription? _streamSubscription; Timer? _pingTimer; int _seq = 0; final StreamController _contactUpdatesController = StreamController.broadcast(); Stream get contactUpdates => _contactUpdatesController.stream; final StreamController _errorController = StreamController.broadcast(); Stream get errorStream => _errorController.stream; final _reconnectionCompleteController = StreamController.broadcast(); Stream get reconnectionComplete => _reconnectionCompleteController.stream; final Map _presenceData = {}; String? authToken; String? userId; String? get token => authToken; String? _currentPasswordTrackId; String? _currentPasswordHint; String? _currentPasswordEmail; bool _isSessionOnline = false; bool _handshakeSent = false; Completer? _onlineCompleter; final List> _messageQueue = []; final Map> _messageCache = {}; final Map _contactCache = {}; DateTime? _lastContactsUpdate; static const Duration _contactCacheExpiry = Duration( minutes: 5, ); bool _isLoadingBlockedContacts = false; bool _isSessionReady = false; final _messageController = StreamController>.broadcast(); Stream> get messages => _messageController.stream; final _connectionStatusController = StreamController.broadcast(); Stream get connectionStatus => _connectionStatusController.stream; final _connectionLogController = StreamController.broadcast(); Stream get connectionLog => _connectionLogController.stream; final List _connectionLogCache = []; List get connectionLogCache => _connectionLogCache; bool get isOnline => _isSessionOnline; Future waitUntilOnline() async { if (_isSessionOnline && _isSessionReady) return; _onlineCompleter ??= Completer(); return _onlineCompleter!.future; } bool get isActuallyConnected { try { if (_channel == null || !_isSessionOnline) { return false; } return true; } catch (e) { print("🔴 Ошибка при проверке состояния канала: $e"); return false; } } Completer>? _inflightChatsCompleter; Map? _lastChatsPayload; DateTime? _lastChatsAt; final Duration _chatsCacheTtl = const Duration(seconds: 5); bool _chatsFetchedInThisSession = false; Map? get lastChatsPayload => _lastChatsPayload; int _reconnectDelaySeconds = 2; int _reconnectAttempts = 0; static const int _maxReconnectAttempts = 10; Timer? _reconnectTimer; bool _isReconnecting = false; void _log(String message) { print(message); _connectionLogCache.add(message); if (!_connectionLogController.isClosed) { _connectionLogController.add(message); } } void _emitLocal(Map frame) { try { _messageController.add(frame); } catch (_) {} } String generateRandomDeviceId() { return const Uuid().v4(); } Future> _buildUserAgentPayload() async { final spoofedData = await SpoofingService.getSpoofedSessionData(); if (spoofedData != null) { print( '--- [_buildUserAgentPayload] Используются подменённые данные сессии ---', ); final String finalDeviceId; final String? idFromSpoofing = spoofedData['device_id'] as String?; if (idFromSpoofing != null && idFromSpoofing.isNotEmpty) { finalDeviceId = idFromSpoofing; print('Используется deviceId из сессии: $finalDeviceId'); } else { finalDeviceId = generateRandomDeviceId(); print('device_id не найден в кэше, сгенерирован новый: $finalDeviceId'); } return { 'deviceType': spoofedData['device_type'] as String? ?? 'IOS', 'locale': spoofedData['locale'] as String? ?? 'ru', 'deviceLocale': spoofedData['locale'] as String? ?? 'ru', 'osVersion': spoofedData['os_version'] as String? ?? 'iOS 17.5.1', 'deviceName': spoofedData['device_name'] as String? ?? 'iPhone', 'headerUserAgent': spoofedData['user_agent'] as String? ?? 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1', 'appVersion': spoofedData['app_version'] as String? ?? '25.10.10', 'screen': spoofedData['screen'] as String? ?? '1170x2532 3.0x', 'timezone': spoofedData['timezone'] as String? ?? 'Europe/Moscow', }; } else { print( '--- [_buildUserAgentPayload] Используются псевдо-случайные данные ---', ); return { 'deviceType': 'WEB', 'locale': 'ru', 'deviceLocale': 'ru', 'osVersion': 'Windows', 'deviceName': 'Chrome', 'headerUserAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'appVersion': '25.10.10', 'screen': '1920x1080 1.0x', 'timezone': 'Europe/Moscow', }; } } void setAppInForeground(bool isForeground) { _isAppInForeground = isForeground; } Future getClipboardData() async { final data = await Clipboard.getData(Clipboard.kTextPlain); return data?.text; } void dispose() { _pingTimer?.cancel(); _channel?.sink.close(status.goingAway); _reconnectionCompleteController.close(); _messageController.close(); } }