at main 2.0 kB view raw
1import 'dart:io'; 2 3import 'package:grain/app_logger.dart'; 4 5/// Handles WebSocket connection, reconnection, and message listening. 6class WebSocketService { 7 final String wsUrl; 8 final String? accessToken; 9 final void Function(dynamic message) onMessage; 10 WebSocket? _ws; 11 bool _shouldReconnect = false; 12 int _attempt = 0; 13 static const int _maxRetries = 5; 14 Duration _delay = const Duration(seconds: 2); 15 16 WebSocketService({required this.wsUrl, required this.accessToken, required this.onMessage}); 17 18 Future<void> connect() async { 19 _shouldReconnect = true; 20 _attempt = 0; 21 _delay = const Duration(seconds: 2); 22 await _connectInternal(); 23 } 24 25 Future<void> disconnect() async { 26 _shouldReconnect = false; 27 await _ws?.close(); 28 _ws = null; 29 } 30 31 Future<void> _connectInternal() async { 32 if (accessToken == null) { 33 appLogger.w('No access token for WebSocket connection'); 34 return; 35 } 36 final headers = {'Authorization': 'Bearer $accessToken', 'Content-Type': 'application/json'}; 37 try { 38 appLogger.i('Connecting to WebSocket: $wsUrl (attempt ${_attempt + 1})'); 39 _ws = await WebSocket.connect(wsUrl, headers: headers); 40 _ws!.listen( 41 onMessage, 42 onError: (error) async { 43 appLogger.w('WebSocket error: $error'); 44 await _retry(); 45 }, 46 onDone: () async { 47 appLogger.i('WebSocket connection closed'); 48 await _retry(); 49 }, 50 ); 51 appLogger.i('Connected to WebSocket: $wsUrl'); 52 } catch (e) { 53 appLogger.e('Failed to connect to WebSocket: $e'); 54 await _retry(); 55 } 56 } 57 58 Future<void> _retry() async { 59 if (!_shouldReconnect) return; 60 if (_attempt < _maxRetries) { 61 _attempt++; 62 appLogger.i('Retrying WebSocket connection in ${_delay.inSeconds} seconds...'); 63 await Future.delayed(_delay); 64 _delay *= 2; 65 await _connectInternal(); 66 } else { 67 appLogger.e('Max WebSocket retry attempts reached.'); 68 } 69 } 70}