import 'dart:async'; import 'dart:convert'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../config.dart'; import '../models/models.dart'; /// Conversation-scoped WebSocket relay (`GET /ws/conversations/:id`). class ConversationSocket { ConversationSocket(this.conversationId); final int conversationId; WebSocketChannel? _channel; final _controller = StreamController>.broadcast(); Stream> get events => _controller.stream; void connect() { final uri = Uri.parse('${AppConfig.wsBase()}/ws/conversations/$conversationId'); _channel = WebSocketChannel.connect(uri); _channel!.stream.listen( (raw) { if (raw is! String) return; final decoded = jsonDecode(raw); if (decoded is Map) { _controller.add(decoded); } }, onError: _controller.addError, onDone: () {}, ); } ChatMessage? parseMessageEvent(Map event) { final type = event['type'] as String?; if (type != null && type != 'message') return null; // Backend may wrap payload or send the message object directly. final payload = event['message'] as Map? ?? event; if (payload['id'] == null || payload['text'] == null) return null; return ChatMessage.fromJson(payload); } int? parseRetractionId(Map event) { if (event['type'] != 'retraction') return null; return event['id'] as int?; } Future dispose() async { await _channel?.sink.close(); await _controller.close(); } }