fix: implement safe map casting for socket events and add support for conversation deletion updates
This commit is contained in:
@@ -37,6 +37,7 @@ class SocketService {
|
||||
final _connectionRequestController = StreamController<Map<String, dynamic>>.broadcast();
|
||||
final _connectionResponseController = StreamController<Map<String, dynamic>>.broadcast();
|
||||
final _deliveredController = StreamController<Map<String, dynamic>>.broadcast();
|
||||
final _conversationDeletedController = StreamController<Map<String, dynamic>>.broadcast();
|
||||
|
||||
Stream<ChatMessage> get onNewMessage => _messageController.stream;
|
||||
Stream<Map<String, dynamic>> get onTypingStart => _typingStartController.stream;
|
||||
@@ -50,8 +51,18 @@ class SocketService {
|
||||
Stream<Map<String, dynamic>> get onConnectionRequest => _connectionRequestController.stream;
|
||||
Stream<Map<String, dynamic>> get onConnectionResponse => _connectionResponseController.stream;
|
||||
Stream<Map<String, dynamic>> get onMessageDelivered => _deliveredController.stream;
|
||||
Stream<Map<String, dynamic>> get onConversationDeleted => _conversationDeletedController.stream;
|
||||
bool get isConnected => _isConnected;
|
||||
|
||||
/// Safely cast socket.io data to Map<String, dynamic>.
|
||||
/// socket_io_client in Dart sometimes delivers maps as Map<dynamic, dynamic>
|
||||
/// which fails a direct `as Map<String, dynamic>` cast and silently drops events.
|
||||
static Map<String, dynamic> _toStringMap(dynamic data) {
|
||||
if (data is Map<String, dynamic>) return data;
|
||||
if (data is Map) return Map<String, dynamic>.from(data);
|
||||
return <String, dynamic>{};
|
||||
}
|
||||
|
||||
Future<void> connect() async {
|
||||
final token = await SecureStorage.getAccessToken();
|
||||
if (token == null) {
|
||||
@@ -153,7 +164,9 @@ class SocketService {
|
||||
// Listen for events
|
||||
_socket!.on('new_message', (data) {
|
||||
try {
|
||||
final message = ChatMessage.fromJson(data as Map<String, dynamic>);
|
||||
// socket.io in Dart may deliver data as Map<dynamic, dynamic> — cast safely
|
||||
final json = _toStringMap(data);
|
||||
final message = ChatMessage.fromJson(json);
|
||||
// Auto-ack delivery so sender gets real-time double-tick
|
||||
try {
|
||||
_socket?.emit('message_received', {
|
||||
@@ -162,31 +175,31 @@ class SocketService {
|
||||
});
|
||||
} catch (_) {}
|
||||
_messageController.add(message);
|
||||
} catch (e) {
|
||||
_log.e('Error parsing new_message: $e');
|
||||
} catch (e, stack) {
|
||||
_log.e('Error parsing new_message: $e\nRaw data: $data', error: e, stackTrace: stack);
|
||||
}
|
||||
});
|
||||
|
||||
_socket!.on('typing_start', (data) {
|
||||
_typingStartController.add(Map<String, dynamic>.from(data as Map));
|
||||
_typingStartController.add(_toStringMap(data));
|
||||
});
|
||||
|
||||
_socket!.on('typing_stop', (data) {
|
||||
_typingStopController.add(Map<String, dynamic>.from(data as Map));
|
||||
_typingStopController.add(_toStringMap(data));
|
||||
});
|
||||
|
||||
_socket!.on('user_status_change', (data) {
|
||||
_statusController.add(Map<String, dynamic>.from(data as Map));
|
||||
_statusController.add(_toStringMap(data));
|
||||
});
|
||||
|
||||
_socket!.on('messages_read', (data) {
|
||||
_readController.add(Map<String, dynamic>.from(data as Map));
|
||||
_readController.add(_toStringMap(data));
|
||||
});
|
||||
|
||||
// Support chat events
|
||||
_socket!.on('support_new_message', (data) {
|
||||
try {
|
||||
final message = SupportMessage.fromJson(data as Map<String, dynamic>);
|
||||
final message = SupportMessage.fromJson(_toStringMap(data));
|
||||
_supportMessageController.add(message);
|
||||
} catch (e) {
|
||||
_log.e('Error parsing support_new_message: $e');
|
||||
@@ -194,24 +207,29 @@ class SocketService {
|
||||
});
|
||||
|
||||
_socket!.on('support_typing_start', (data) {
|
||||
_supportTypingStartController.add(Map<String, dynamic>.from(data as Map));
|
||||
_supportTypingStartController.add(_toStringMap(data));
|
||||
});
|
||||
|
||||
_socket!.on('support_typing_stop', (data) {
|
||||
_supportTypingStopController.add(Map<String, dynamic>.from(data as Map));
|
||||
_supportTypingStopController.add(_toStringMap(data));
|
||||
});
|
||||
|
||||
// Connection request events (real-time updates)
|
||||
_socket!.on('connection_request', (data) {
|
||||
_connectionRequestController.add(Map<String, dynamic>.from(data as Map));
|
||||
_connectionRequestController.add(_toStringMap(data));
|
||||
});
|
||||
|
||||
_socket!.on('connection_response', (data) {
|
||||
_connectionResponseController.add(Map<String, dynamic>.from(data as Map));
|
||||
_connectionResponseController.add(_toStringMap(data));
|
||||
});
|
||||
|
||||
_socket!.on('message_delivered', (data) {
|
||||
_deliveredController.add(Map<String, dynamic>.from(data as Map));
|
||||
_deliveredController.add(_toStringMap(data));
|
||||
});
|
||||
|
||||
// Conversation deleted by the other party — remove from local state
|
||||
_socket!.on('conversation_deleted', (data) {
|
||||
_conversationDeletedController.add(_toStringMap(data));
|
||||
});
|
||||
|
||||
_socket!.connect();
|
||||
@@ -351,5 +369,6 @@ class SocketService {
|
||||
_connectionRequestController.close();
|
||||
_connectionResponseController.close();
|
||||
_deliveredController.close();
|
||||
_conversationDeletedController.close();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user