1+ import 'dart:async' ;
12import 'dart:convert' ;
23
34import 'package:flutter/foundation.dart' ;
45import 'package:hushnet_frontend/services/key_provider.dart' ;
56import 'package:hushnet_frontend/services/secure_storage_service.dart' ;
67import 'package:dio/dio.dart' ;
78import 'package:logging/logging.dart' ;
9+ import 'package:web_socket_channel/web_socket_channel.dart' ;
10+ import 'package:web_socket_channel/status.dart' as status;
811
912class NodeService {
1013 static final NodeService _instance = NodeService ._internal ();
@@ -16,6 +19,12 @@ class NodeService {
1619 final log = Logger ('NodeService' );
1720 final KeyProvider _keyProvider = KeyProvider ();
1821
22+ WebSocketChannel ? _channel;
23+ StreamSubscription ? _subscription;
24+ String ? _connectedUserId;
25+ final _controller = StreamController <Map <String , dynamic >>.broadcast ();
26+
27+ Stream <Map <String , dynamic >> get stream => _controller.stream;
1928
2029
2130
@@ -72,6 +81,7 @@ class NodeService {
7281 await _storage.write ('username' , username);
7382 await _storage.write ('user_id' , userId);
7483 await _storage.write ('node_url' , nodeUrl);
84+ await connectWebSocket (nodeUrl, userId);
7585 return username;
7686 } else {
7787 log.severe ('Login failed: ${response .statusCode } ${response .data }' );
@@ -117,7 +127,8 @@ class NodeService {
117127 final deviceKey = data['device_key' ];
118128 await _storage.write ('device_id' , deviceId);
119129 await _storage.write ('device_key' , deviceKey);
120- stepNotifier? .value = 8 ;
130+ await connectWebSocket (nodeUrl, userId);
131+ stepNotifier? .value = 8 ;
121132
122133 return true ;
123134 } else {
@@ -140,5 +151,69 @@ class NodeService {
140151 Future <String ?> getCurrentDeviceId () async {
141152 return await _storage.read ('device_id' );
142153 }
154+ Future <void > connectWebSocket (String nodeUrl, String userId) async {
155+ if (_connectedUserId == userId && _channel != null ) {
156+ debugPrint ("🔁 WebSocket already connected for $userId " );
157+ return ;
158+ }
159+
160+ var clean = nodeUrl.trim ().replaceAll ('#' , '' );
161+ final parsed = Uri .parse (clean);
162+
163+ final scheme = (parsed.scheme == 'https' || parsed.scheme == 'wss' )
164+ ? 'wss'
165+ : 'ws' ;
166+
167+ final host = parsed.host;
168+ final port = parsed.hasPort ? ':${parsed .port }' : '' ;
169+
170+ final wsUrl = Uri .parse ("$scheme ://$host $port /ws/$userId " );
171+ debugPrint ("Connecting WS to $wsUrl with $userId " );
172+
173+ try {
174+ _channel = WebSocketChannel .connect (wsUrl);
175+ debugPrint ("WS connected for $userId " );
176+ _connectedUserId = userId;
177+
178+ _subscription = _channel! .stream.listen (
179+ (event) {
180+ try {
181+ final decoded = jsonDecode (event);
182+ _controller.add (decoded);
183+ debugPrint ("WS event: $decoded " );
184+ } catch (e) {
185+ debugPrint ("Invalid WS payload: $e " );
186+ }
187+ },
188+ onError: (err) {
189+ debugPrint ("WS error: $err " );
190+ _retry (nodeUrl, userId);
191+ },
192+ onDone: () {
193+ debugPrint ("WS closed for $userId " );
194+ _retry (nodeUrl, userId);
195+ },
196+ );
197+ } catch (e) {
198+ debugPrint ("Failed to connect WebSocket: $e " );
199+ _retry (nodeUrl, userId);
200+ }
201+ }
202+
203+ void _retry (String nodeUrl, String userId) {
204+ Future .delayed (const Duration (seconds: 3 ), () {
205+ if (_connectedUserId == userId) {
206+ connectWebSocket (nodeUrl, userId);
207+ }
208+ });
209+ }
210+
211+ void disconnectWebSocket () {
212+ //TODO : Implement disconnect logic
213+ debugPrint ("Closing WS connection..." );
214+ _subscription? .cancel ();
215+ _channel? .sink.close (status.normalClosure);
216+ _connectedUserId = null ;
217+ }
143218
144219}
0 commit comments