import 'dart:async'; import 'dart:convert'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'api.dart'; /// Thin wrapper over the raw `ws` chat gateway. The NestJS `WsAdapter` /// expects `{"event": ..., "data": ...}` frames in both directions. class ChatSocket { ChatSocket(this._uri); final Uri _uri; WebSocketChannel? _channel; final _messages = StreamController.broadcast(); final _status = StreamController.broadcast(); /// Incoming `chat:message` frames. Stream get messages => _messages.stream; /// "connected" / "closed" / "error: ..." for a small status line. Stream get status => _status.stream; void connect(String channelId) { _channel = WebSocketChannel.connect(_uri); _channel!.stream.listen( (raw) { _status.add('connected'); try { final frame = jsonDecode(raw as String) as Map; if (frame['event'] == 'chat:message') { _messages.add(ChatMessage.fromJson(frame['data'] as Map)); } } catch (_) { // ignore frames we don't model } }, onError: (Object e) => _status.add('error: $e'), onDone: () => _status.add('closed'), ); _send('chat:join', {'channelId': channelId}); } void sendMessage(String channelId, String body) { _send('chat:send', {'channelId': channelId, 'body': body}); } void _send(String event, Map data) { _channel?.sink.add(jsonEncode({'event': event, 'data': data})); } void dispose() { _channel?.sink.close(); _messages.close(); _status.close(); } }