import 'dart:async'; import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:talker/talker.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../constants/api_constants.dart'; import '../constants/app_constants.dart'; import '../logging/app_logger.dart'; import '../storage/secure_storage.dart'; part 'socket_manager.g.dart'; @Riverpod(keepAlive: true) SocketManager socketManager(Ref ref) { final talker = ref.read(appLoggerProvider); final secureStorage = ref.read(secureStorageProvider); final manager = SocketManager(talker: talker, secureStorage: secureStorage); ref.onDispose(manager.dispose); return manager; } class SocketManager { SocketManager({ required this.talker, required this.secureStorage, }); final Talker talker; final SecureStorage secureStorage; WebSocketChannel? _channel; Timer? _reconnectTimer; final _messageController = StreamController>.broadcast(); bool _isConnected = false; int _reconnectAttempts = 0; static const int _maxReconnectAttempts = 5; Stream> get messageStream => _messageController.stream; bool get isConnected => _isConnected; Future connect({String? path}) async { try { final token = await secureStorage.read(key: AppConstants.accessTokenKey); final wsUrl = '${ApiConstants.wsUrl}${path ?? ''}'; final uri = Uri.parse( token != null ? '$wsUrl?token=$token' : wsUrl, ); _channel = WebSocketChannel.connect(uri); await _channel!.ready; _isConnected = true; _reconnectAttempts = 0; talker.info('WebSocket connected: $wsUrl'); _channel!.stream.listen( (data) { try { final decoded = jsonDecode(data as String) as Map; _messageController.add(decoded); } catch (e) { talker.error('WebSocket message parse error', e); } }, onError: (Object error) { talker.error('WebSocket error', error); _handleDisconnect(); }, onDone: () { talker.warning('WebSocket disconnected'); _handleDisconnect(); }, ); } catch (e) { talker.error('WebSocket connection failed', e); _handleDisconnect(); } } void _handleDisconnect() { _isConnected = false; if (_reconnectAttempts < _maxReconnectAttempts) { final delay = Duration(seconds: _reconnectAttempts * 2 + 1); _reconnectTimer = Timer(delay, () { _reconnectAttempts++; talker.info('WebSocket reconnect attempt $_reconnectAttempts'); connect(); }); } } void send(Map data) { if (_isConnected && _channel != null) { _channel!.sink.add(jsonEncode(data)); } else { talker.warning('WebSocket not connected, message not sent'); } } void dispose() { _reconnectTimer?.cancel(); _channel?.sink.close(); _messageController.close(); _isConnected = false; } }