fix(lib): add message checks and improve exceptions

This commit is contained in:
ksenia312
2024-02-01 12:06:16 +01:00
parent 093a23145f
commit 5870dc2577
8 changed files with 84 additions and 40 deletions
+25 -11
View File
@@ -53,15 +53,15 @@ class MyApp extends StatelessWidget {
), ),
if (service.currentDeviceInfo != null) if (service.currentDeviceInfo != null)
Text( Text(
'Device Name: ${service.currentDeviceInfo!.displayName}\n' 'Device Name: ${service.currentDeviceInfo!.displayName}'
'${Platform.isIOS ? 'Device ID: ${service.currentDeviceInfo!.id}' : ''}', '${Platform.isIOS ? '\nDevice ID: ${service.currentDeviceInfo!.id}' : ''}',
), ),
if (Platform.isIOS) if (Platform.isIOS)
Text( Text(
'You are ${service.isIOSBrowser ? 'going to find your friend' : 'waiting for another user to connect'}', 'You are ${service.isIOSBrowser ? 'going to find your friend' : 'waiting for another user to connect'}',
), ),
Text( Text(
'Communication channel state: ${service.communicationChannelState.name.toUpperCase()}', 'Communication channel state: ${service.communicationChannelState.previewName}',
) )
], ],
), ),
@@ -172,6 +172,16 @@ enum AppState {
} }
} }
extension on CommunicationChannelState {
String get previewName {
return switch (this) {
CommunicationChannelState.notConnected => 'Not connected',
CommunicationChannelState.loading => 'Connecting',
CommunicationChannelState.connected => 'Connected',
};
}
}
class AppService extends ChangeNotifier { class AppService extends ChangeNotifier {
late final _nearbyService = NearbyService.getInstance() late final _nearbyService = NearbyService.getInstance()
..communicationChannelState.addListener(notifyListeners); ..communicationChannelState.addListener(notifyListeners);
@@ -187,6 +197,12 @@ class AppService extends ChangeNotifier {
StreamSubscription? peersSubscription; StreamSubscription? peersSubscription;
StreamSubscription? connectedDeviceSubscription; StreamSubscription? connectedDeviceSubscription;
@override
void dispose() {
stopListeningAll();
super.dispose();
}
CommunicationChannelState get communicationChannelState { CommunicationChannelState get communicationChannelState {
return _nearbyService.communicationChannelState.value; return _nearbyService.communicationChannelState.value;
} }
@@ -328,7 +344,7 @@ class AppService extends ChangeNotifier {
final wasConnected = connectedDevice?.status.isConnected ?? false; final wasConnected = connectedDevice?.status.isConnected ?? false;
final nowConnected = event?.status.isConnected ?? false; final nowConnected = event?.status.isConnected ?? false;
if (wasConnected && !nowConnected) { if (wasConnected && !nowConnected) {
restart(); stopListeningAll();
return; return;
} }
connectedDevice = event; connectedDevice = event;
@@ -358,7 +374,7 @@ class AppService extends ChangeNotifier {
Future<void> startCommunicationChannel({ Future<void> startCommunicationChannel({
ValueChanged<ReceivedNearbyMessage>? listener, ValueChanged<ReceivedNearbyMessage>? listener,
}) async { }) async {
final eventListener = NearbyServiceStreamListener<ReceivedNearbyMessage>( final eventListener = NearbyServiceStreamListener(
onCreated: (_) { onCreated: (_) {
updateState(AppState.communicationChannelCreated); updateState(AppState.communicationChannelCreated);
}, },
@@ -366,7 +382,7 @@ class AppService extends ChangeNotifier {
listener?.call(event); listener?.call(event);
}, },
onError: (e, [StackTrace? s]) { onError: (e, [StackTrace? s]) {
restart(); stopListeningAll();
}, },
); );
@@ -396,17 +412,15 @@ class AppService extends ChangeNotifier {
print(e); print(e);
} }
} finally { } finally {
await restart(); await stopListeningAll();
} }
notifyListeners(); notifyListeners();
} }
Future<void> restart() async { Future<void> stopListeningAll() async {
await stopListeningConnectedDevice(); await stopListeningConnectedDevice();
await stopListeningPeers(); await stopListeningPeers();
await stopDiscovery(); await stopDiscovery();
await discover();
} }
void updateState(AppState state, {bool shouldNotify = true}) { void updateState(AppState state, {bool shouldNotify = true}) {
@@ -768,7 +782,7 @@ class _ConnectedSocketBodyState extends State<_ConnectedSocketBody> {
if (device == null) { if (device == null) {
return Center( return Center(
child: _ActionButton( child: _ActionButton(
onTap: service.restart, onTap: service.stopListeningAll,
title: 'Restart', title: 'Restart',
), ),
); );
+7
View File
@@ -12,6 +12,13 @@ abstract class NearbyMessage {
final String value; final String value;
///
/// Checks if [value] is not empty
///
bool get isValid {
return value.isNotEmpty;
}
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
@@ -27,6 +27,7 @@ class NearbySocketService {
String? _connectedDeviceId; String? _connectedDeviceId;
WebSocket? _socket; WebSocket? _socket;
HttpServer? _server;
StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription; StreamSubscription<ReceivedNearbyMessage>? _messagesSubscription;
/// ///
@@ -72,21 +73,25 @@ class NearbySocketService {
/// Add [OutgoingNearbyMessage]'s JSON representation to [_socket]. /// Add [OutgoingNearbyMessage]'s JSON representation to [_socket].
/// ///
Future<bool> send(OutgoingNearbyMessage message) async { Future<bool> send(OutgoingNearbyMessage message) async {
if (_socket != null && message.receiver.id == _connectedDeviceId) { if (message.isValid) {
final sender = await _manager.getCurrentDeviceInfo(); if (_socket != null && message.receiver.id == _connectedDeviceId) {
if (sender != null) { final sender = await _manager.getCurrentDeviceInfo();
_socket!.add( if (sender != null) {
jsonEncode( _socket!.add(
{ jsonEncode(
'message': message.value, {
'sender': sender.toJson(), 'message': message.value,
}, 'sender': sender.toJson(),
), },
); ),
);
}
return true;
} }
return true; return false;
} else {
throw NearbyServiceException.invalidMessage(message.value);
} }
return false;
} }
/// ///
@@ -96,8 +101,12 @@ class NearbySocketService {
try { try {
await _messagesSubscription?.cancel(); await _messagesSubscription?.cancel();
_messagesSubscription = null; _messagesSubscription = null;
_socket?.close();
_socket = null; _socket = null;
_server?.close(force: true);
_server = null;
_connectedDeviceId = null; _connectedDeviceId = null;
state.value = CommunicationChannelState.notConnected;
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
@@ -142,11 +151,11 @@ class NearbySocketService {
required int port, required int port,
ValueChanged<HttpRequest>? serverListener, ValueChanged<HttpRequest>? serverListener,
}) async { }) async {
final httpServer = await _network.startServer( _server = await _network.startServer(
ownerIpAddress: info.ownerIpAddress, ownerIpAddress: info.ownerIpAddress,
port: port, port: port,
); );
httpServer?.listen( _server?.listen(
(request) async { (request) async {
serverListener?.call(request); serverListener?.call(request);
final isPing = await _pingManager.checkPing(request); final isPing = await _pingManager.checkPing(request);
@@ -156,7 +165,7 @@ class NearbySocketService {
return; return;
} }
if (request.uri.path == '/ws') { if (request.uri.path == _Urls.ws) {
_socket = await WebSocketTransformer.upgrade(request); _socket = await WebSocketTransformer.upgrade(request);
_createSocketSubscription(socketListener); _createSocketSubscription(socketListener);
} else { } else {
@@ -5,6 +5,10 @@ class _Protocols {
static const ws = 'ws://'; static const ws = 'ws://';
} }
class _Urls {
static const ws = '/ws';
}
class NearbyServiceNetwork { class NearbyServiceNetwork {
final _httpClient = HttpClient(); final _httpClient = HttpClient();
final _random = Random(); final _random = Random();
@@ -20,7 +24,7 @@ class NearbyServiceNetwork {
final response = await request.close(); final response = await request.close();
return response; return response;
} catch (e) { } catch (e) {
Logger.error('Server is unreachable'); Logger.error('Server is unreachable: $e');
return null; return null;
} }
} }
@@ -37,7 +41,7 @@ class NearbyServiceNetwork {
required int port, required int port,
}) async { }) async {
try { try {
final url = 'ws://$ownerIpAddress:$port'; final url = '${_Protocols.ws}$ownerIpAddress:$port';
Logger.debug('Starting server on $url'); Logger.debug('Starting server on $url');
var server = await HttpServer.bind( var server = await HttpServer.bind(
ownerIpAddress, ownerIpAddress,
@@ -47,8 +51,7 @@ class NearbyServiceNetwork {
Logger.info('Server running on $url'); Logger.info('Server running on $url');
return server; return server;
} catch (e) { } catch (e) {
Logger.error('Error starting socket: $e'); throw NearbyServiceException('Error starting socket: $e');
return null;
} }
} }
@@ -58,14 +61,14 @@ class NearbyServiceNetwork {
}) async { }) async {
try { try {
final connectionId = _random.nextInt(1000) + 100; final connectionId = _random.nextInt(1000) + 100;
final url = '${_Protocols.ws}$ownerIpAddress:$port/ws?as=$connectionId'; final url =
'${_Protocols.ws}$ownerIpAddress:$port${_Urls.ws}?as=$connectionId';
Logger.debug('Connecting to $url'); Logger.debug('Connecting to $url');
final socket = await WebSocket.connect(url); final socket = await WebSocket.connect(url);
Logger.info('Connected to $url'); Logger.info('Connected to $url');
return socket; return socket;
} catch (e) { } catch (e) {
Logger.error('Error connecting to server: $e'); throw NearbyServiceException('Error connecting to server: $e');
return null;
} }
} }
} }
@@ -201,6 +201,7 @@ class NearbyIOSService extends NearbyService {
FutureOr<bool> endCommunicationChannel() async { FutureOr<bool> endCommunicationChannel() async {
await _messagesSubscription?.cancel(); await _messagesSubscription?.cancel();
_messagesSubscription = null; _messagesSubscription = null;
_state.value = CommunicationChannelState.notConnected;
Logger.debug('Communication channel was cancelled'); Logger.debug('Communication channel was cancelled');
return true; return true;
} }
@@ -211,7 +212,10 @@ class NearbyIOSService extends NearbyService {
/// ///
@override @override
Future<bool> send(OutgoingNearbyMessage message) { Future<bool> send(OutgoingNearbyMessage message) {
return NearbyServiceIOSPlatform.instance.send(message); if (message.isValid) {
return NearbyServiceIOSPlatform.instance.send(message);
}
throw NearbyServiceException.invalidMessage(message.value);
} }
/// ///
@@ -242,7 +246,7 @@ class NearbyIOSService extends NearbyService {
if (value) { if (value) {
Logger.info(onSuccess); Logger.info(onSuccess);
} else { } else {
Logger.error(onError); throw NearbyServiceException(onError);
} }
} }
@@ -28,7 +28,7 @@ class NearbyCommunicationChannelData {
/// ///
/// Listener for message stream changes. /// Listener for message stream changes.
/// ///
final NearbyServiceStreamListener<ReceivedNearbyMessage> eventListener; final NearbyServiceStreamListener eventListener;
/// ///
/// Android-specific connection data. /// Android-specific connection data.
@@ -1,11 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:nearby_service/nearby_service.dart';
/// ///
/// Stream Subscription Listener. /// Stream Subscription Listener.
/// ///
class NearbyServiceStreamListener<T> { class NearbyServiceStreamListener {
/// ///
/// It is required to pass the [onData] parameter to process the /// It is required to pass the [onData] parameter to process the
/// data that came through the stream. /// data that came through the stream.
@@ -18,8 +19,8 @@ class NearbyServiceStreamListener<T> {
this.cancelOnError, this.cancelOnError,
}); });
final ValueChanged<T> onData; final ValueChanged<ReceivedNearbyMessage> onData;
final ValueChanged<StreamSubscription<T>>? onCreated; final ValueChanged<StreamSubscription<ReceivedNearbyMessage>>? onCreated;
final VoidCallback? onDone; final VoidCallback? onDone;
final void Function(Object, [StackTrace])? onError; final void Function(Object, [StackTrace])? onError;
final bool? cancelOnError; final bool? cancelOnError;
+6
View File
@@ -30,5 +30,11 @@ class NearbyServiceException implements Exception {
); );
} }
factory NearbyServiceException.invalidMessage(String value) {
return NearbyServiceException(
'The message="$value" is not valid',
);
}
final Object? error; final Object? error;
} }