import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; /// Common WebSocket for user-based communication (not chatId-based) class CommonWebSocketService { static final CommonWebSocketService _instance = CommonWebSocketService._internal(); factory CommonWebSocketService() => _instance; CommonWebSocketService._internal(); WebSocketChannel? _channel; StreamSubscription? _subscription; Timer? _pingTimer; Timer? _reconnectTimer; bool _isConnected = false; bool _isIntentionalClose = false; int _reconnectAttempts = 0; static const int _maxReconnectAttempts = 5; static const Duration _reconnectDelay = Duration(seconds: 3); // Callbacks Function(Map)? onMessageReceived; Function()? onConnected; Function()? onDisconnected; Function(dynamic error)? onError; String? _userId; String? _wsUrl; /// Check connection status bool get isConnected => _isConnected; /// Current user ID String? get currentUserId => _userId; /// Connect WS using LOCAL USER ID Future connect({ required String userId, String baseUrl = "wss://taxglide.amrithaa.net:443/reverb", String appKey = "2yj0lyzc9ylw2h03ts6i", }) async { try { await disconnect(); _userId = userId; _wsUrl = "$baseUrl/app/$appKey?protocol=7&client=js&version=1.0"; _isIntentionalClose = false; _reconnectAttempts = 0; await _establishConnection(); } catch (e) { debugPrint("❌ Connection error: $e"); onError?.call(e); _scheduleReconnect(); } } Future _establishConnection() async { try { debugPrint("🔌 Connecting to: $_wsUrl"); _channel = WebSocketChannel.connect(Uri.parse(_wsUrl!)); _subscription = _channel!.stream.listen( _handleMessage, onError: _handleError, onDone: _handleDisconnection, cancelOnError: false, ); await Future.delayed(const Duration(milliseconds: 500)); _isConnected = true; _reconnectAttempts = 0; debugPrint("✅ WebSocket connected"); _subscribeToUserChannel(); _startPingTimer(); onConnected?.call(); } catch (e) { debugPrint("❌ Failed to establish connection: $e"); _isConnected = false; rethrow; } } /// Handle incoming messages void _handleMessage(dynamic data) { try { debugPrint("📨 Received: $data"); final Map message = json.decode(data); if (message['event'] == 'pusher:ping') { _sendPong(); return; } if (message['event'] == 'pusher:connection_established') { debugPrint("🔗 Pusher connection established"); return; } if (message['event'] == 'pusher_internal:subscription_succeeded') { debugPrint("✅ User channel subscribed"); return; } if (message['event'] != null && !message['event'].toString().startsWith('pusher')) { onMessageReceived?.call(message); } } catch (e) { debugPrint("❌ Error parsing message: $e"); onError?.call(e); } } void _handleError(dynamic error) { debugPrint("❌ WebSocket error: $error"); _isConnected = false; onError?.call(error); if (!_isIntentionalClose) { _scheduleReconnect(); } } void _handleDisconnection() { debugPrint("🔌 Disconnected"); _isConnected = false; _stopPingTimer(); onDisconnected?.call(); if (!_isIntentionalClose) { _scheduleReconnect(); } } /// Subscribe to USER CHANNEL void _subscribeToUserChannel() { if (_userId == null) return; final subscribeMessage = { 'event': 'pusher:subscribe', 'data': {'channel': 'user-chat-notification.$_userId'}, }; _sendMessage(subscribeMessage); debugPrint("📡 Subscribing to user.$_userId"); } void _sendPong() { final pong = {'event': 'pusher:pong', 'data': {}}; _sendMessage(pong); } void _startPingTimer() { _pingTimer?.cancel(); _pingTimer = Timer.periodic(const Duration(seconds: 30), (timer) { if (_isConnected) debugPrint("💓 WS heartbeat"); }); } void _stopPingTimer() { _pingTimer?.cancel(); _pingTimer = null; } void _scheduleReconnect() { if (_isIntentionalClose) return; if (_reconnectAttempts >= _maxReconnectAttempts) { onError?.call( "Failed to reconnect after $_maxReconnectAttempts attempts", ); return; } _reconnectTimer?.cancel(); _reconnectAttempts++; debugPrint( "🔄 Retry $_reconnectAttempts in ${_reconnectDelay.inSeconds}s...", ); _reconnectTimer = Timer(_reconnectDelay, () async { if (!_isIntentionalClose && _wsUrl != null && _userId != null) { await _establishConnection(); } }); } /// Send message void _sendMessage(Map message) { if (!_isConnected || _channel == null) { debugPrint("⚠️ Cannot send: Not connected"); return; } try { final jsonMsg = json.encode(message); _channel!.sink.add(jsonMsg); debugPrint("📤 Sent: $jsonMsg"); } catch (e) { debugPrint("❌ Send error: $e"); } } /// Send event to backend void sendEvent({ required String eventName, required Map data, }) { final msg = { 'event': eventName, 'data': data, 'channel': 'user-chat-notification.$_userId', }; _sendMessage(msg); } Future disconnect() async { _isIntentionalClose = true; _reconnectTimer?.cancel(); _stopPingTimer(); await _subscription?.cancel(); await _channel?.sink.close(); _channel = null; _subscription = null; _isConnected = false; _userId = null; debugPrint("🔌 WS disconnected intentionally"); } }