Grain flutter app
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}