import 'dart:async'; import 'dart:convert'; import 'package:chepuhagram/domain/services/api_service.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/status.dart' as status; import 'package:web_socket_channel/io.dart'; import 'package:chepuhagram/core/constants.dart'; import 'package:flutter/widgets.dart'; class SocketService with WidgetsBindingObserver { static final SocketService _instance = SocketService._internal(); factory SocketService() => _instance; SocketService._internal() { WidgetsBinding.instance.addObserver(this); } WebSocketChannel? _channel; final StreamController> _messageController = StreamController>.broadcast(); // Поток, который будут слушать провайдеры Stream> get messages => _messageController.stream; bool allowConnect = true; // Флаг для контроля подключения @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { allowConnect = true; } else { allowConnect = false; disconnect(); } } Future connect(ApiService apiService) async { final token = await apiService.getAccessToken(); if (_channel != null) return; // Уже подключены if (token == null || token.isEmpty) { print('❌ SocketService.connect: no access token, skipping connect'); return; } if (!allowConnect) return; // Не разрешаем подключение // В FastAPI эндпоинт ожидает токен в URL-параметре final uri = Uri.parse("${AppConstants.wsUrl}/ws?token=$token"); //_channel = WebSocketChannel.connect(uri); _channel = IOWebSocketChannel.connect( uri, connectTimeout: Duration(seconds: 10), ); try { await _channel!.ready; _channel!.stream.listen( (data) { final decoded = jsonDecode(data); print("🚀 СООБЩЕНИЕ ПОЛУЧЕНО ИЗ SINK: $decoded"); _messageController.add(decoded); }, onError: (error) => _reconnect(apiService), onDone: () => _reconnect(apiService), ); } on TimeoutException catch (_) { _channel = null; throw Exception('timeout'); } catch (e) { _channel = null; throw Exception("Ошибка подключения: $e"); } } Future _reconnect(ApiService apiService) async { _channel = null; Future.delayed(const Duration(seconds: 5), () => connect(apiService)); } bool sendMessage(Map data, {int retryCnt = 0}) { const maxRetries = 5; if (_channel == null) { if (retryCnt < maxRetries) { // Schedule retry with exponential backoff Future.delayed(Duration(seconds: 1 << retryCnt), () => sendMessage(data, retryCnt: retryCnt + 1)); } return false; } try { final encodedData = jsonEncode(data); // 1. Проверяем, не закрыт ли sink (у некоторых провайдеров это доступно) _channel!.sink.add(encodedData); // 2. Добавляем принт подтверждения print("🚀 СООБЩЕНИЕ ОТПРАВЛЕНО В SINK: $encodedData"); return true; } catch (e) { print("❌ КРИТИЧЕСКАЯ ОШИБКА ПРИ ОТПРАВКЕ: $e"); return false; } } bool sendReadReceipt(int messageId) { return sendMessage({'type': 'read_receipt', 'message_id': messageId}); } void disconnect() { _channel?.sink.close(status.normalClosure); _channel = null; } }