import 'dart:async'; import 'dart:convert'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../config.dart'; enum WsStatus { connecting, connected, disconnected } /// کلاینت WebSocket بازی: اتصال با توکن، ارسال/دریافت JSON و اتصال مجدد خودکار. class WsClient { final String token; WebSocketChannel? _channel; StreamSubscription? _sub; Timer? _reconnectTimer; bool _disposed = false; int _attempt = 0; final _messages = StreamController>.broadcast(); final _status = StreamController.broadcast(); WsClient(this.token); /// پیام‌های دیکدشده‌ی سرور. Stream> get messages => _messages.stream; /// وضعیت اتصال (برای نمایش «در حال اتصال مجدد»). Stream get status => _status.stream; void connect() { if (_disposed) return; _status.add(WsStatus.connecting); try { final ch = WebSocketChannel.connect(Uri.parse(AppConfig.wsUrl(token))); _channel = ch; _sub = ch.stream.listen( _onData, onError: (_) => _scheduleReconnect(), onDone: _scheduleReconnect, cancelOnError: true, ); _attempt = 0; _status.add(WsStatus.connected); } catch (_) { _scheduleReconnect(); } } void _onData(dynamic raw) { try { final msg = jsonDecode(raw as String); if (msg is Map) _messages.add(msg); } catch (_) {/* پیام نامعتبر نادیده گرفته می‌شود */} } void send(Map msg) { _channel?.sink.add(jsonEncode(msg)); } void _scheduleReconnect() { if (_disposed) return; _status.add(WsStatus.disconnected); _sub?.cancel(); _sub = null; _channel = null; _reconnectTimer?.cancel(); // backoff تا حداکثر ۵ ثانیه؛ سرور با همان توکن بازیکن را به میز برمی‌گرداند. final delayMs = (500 * (1 << _attempt)).clamp(500, 5000); _attempt = (_attempt + 1).clamp(0, 4); _reconnectTimer = Timer(Duration(milliseconds: delayMs), connect); } void dispose() { _disposed = true; _reconnectTimer?.cancel(); _sub?.cancel(); _channel?.sink.close(); _messages.close(); _status.close(); } }