taxgilde/lib/view/Mahi_chat/webscoket.dart
2026-04-11 10:21:31 +05:30

297 lines
7.8 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
/// WebSocket service for real-time chat communication
class ChatWebSocketService {
static final ChatWebSocketService _instance =
ChatWebSocketService._internal();
factory ChatWebSocketService() => _instance;
ChatWebSocketService._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<String, dynamic>)? onMessageReceived;
Function()? onConnected;
Function()? onDisconnected;
Function(dynamic error)? onError;
String? _currentChatId;
String? _wsUrl;
/// Check if WebSocket is currently connected
bool get isConnected => _isConnected;
/// Get current chat ID
String? get currentChatId => _currentChatId;
/// Connect to WebSocket for a specific chat
Future<void> connect({
required String chatId,
String baseUrl = "wss://taxglide.amrithaa.net:443/reverb",
String appKey = "2yj0lyzc9ylw2h03ts6i",
}) async {
try {
// Close existing connection if any
await disconnect();
_currentChatId = chatId;
_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();
}
}
/// Establish WebSocket connection
Future<void> _establishConnection() async {
try {
debugPrint("🔌 Connecting to: $_wsUrl");
_channel = WebSocketChannel.connect(Uri.parse(_wsUrl!));
// Listen to messages
_subscription = _channel!.stream.listen(
_handleMessage,
onError: _handleError,
onDone: _handleDisconnection,
cancelOnError: false,
);
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
_isConnected = true;
_reconnectAttempts = 0;
debugPrint("✅ WebSocket connected");
// Subscribe to chat channel
_subscribeToChannel();
// Start ping timer
_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<String, dynamic> message = json.decode(data);
// Handle Pusher ping
if (message['event'] == 'pusher:ping') {
_sendPong();
return;
}
// Handle connection established
if (message['event'] == 'pusher:connection_established') {
debugPrint("🔗 Connection established");
return;
}
// Handle subscription succeeded
if (message['event'] == 'pusher_internal:subscription_succeeded') {
debugPrint("✅ Subscribed to chat.${_currentChatId}");
return;
}
// Handle custom events (your chat messages)
if (message['event'] != null &&
!message['event'].toString().startsWith('pusher')) {
onMessageReceived?.call(message);
}
} catch (e) {
debugPrint("❌ Error parsing message: $e");
onError?.call(e);
}
}
/// Handle WebSocket errors
void _handleError(dynamic error) {
debugPrint("❌ WebSocket error: $error");
_isConnected = false;
onError?.call(error);
if (!_isIntentionalClose) {
_scheduleReconnect();
}
}
/// Handle disconnection
void _handleDisconnection() {
debugPrint("🔌 WebSocket disconnected");
_isConnected = false;
_stopPingTimer();
onDisconnected?.call();
if (!_isIntentionalClose) {
_scheduleReconnect();
}
}
/// Subscribe to chat channel
void _subscribeToChannel() {
if (_currentChatId == null) return;
final subscribeMessage = {
'event': 'pusher:subscribe',
'data': {'channel': 'chat.$_currentChatId'},
};
_sendMessage(subscribeMessage);
debugPrint("📡 Subscribing to chat.$_currentChatId");
}
/// Send pong response to ping
void _sendPong() {
final pongMessage = {'event': 'pusher:pong', 'data': {}};
_sendMessage(pongMessage);
debugPrint("🏓 Pong sent");
}
/// Start periodic ping timer
void _startPingTimer() {
_pingTimer?.cancel();
_pingTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
if (_isConnected) {
// Server will send ping, we just respond with pong
debugPrint("💓 Heartbeat check");
}
});
}
/// Stop ping timer
void _stopPingTimer() {
_pingTimer?.cancel();
_pingTimer = null;
}
/// Schedule reconnection attempt
void _scheduleReconnect() {
if (_isIntentionalClose) return;
if (_reconnectAttempts >= _maxReconnectAttempts) {
debugPrint("❌ Max reconnection attempts reached");
onError?.call(
"Failed to reconnect after $_maxReconnectAttempts attempts",
);
return;
}
_reconnectTimer?.cancel();
_reconnectAttempts++;
debugPrint(
"🔄 Scheduling reconnect attempt $_reconnectAttempts in ${_reconnectDelay.inSeconds}s",
);
_reconnectTimer = Timer(_reconnectDelay, () async {
if (!_isIntentionalClose && _wsUrl != null && _currentChatId != null) {
debugPrint("🔄 Attempting to reconnect...");
try {
await _establishConnection();
} catch (e) {
debugPrint("❌ Reconnect failed: $e");
_scheduleReconnect();
}
}
});
}
/// Send a message through WebSocket
void _sendMessage(Map<String, dynamic> message) {
if (_channel == null || !_isConnected) {
debugPrint("⚠️ Cannot send message: Not connected");
return;
}
try {
final jsonMessage = json.encode(message);
_channel!.sink.add(jsonMessage);
debugPrint("📤 Sent: $jsonMessage");
} catch (e) {
debugPrint("❌ Error sending message: $e");
onError?.call(e);
}
}
/// Send a custom event to the channel
void sendEvent({
required String eventName,
required Map<String, dynamic> data,
}) {
final message = {
'event': eventName,
'data': data,
'channel': 'chat.$_currentChatId',
};
_sendMessage(message);
}
/// Disconnect from WebSocket
Future<void> disconnect() async {
_isIntentionalClose = true;
_reconnectTimer?.cancel();
_stopPingTimer();
await _subscription?.cancel();
await _channel?.sink.close();
_channel = null;
_subscription = null;
_isConnected = false;
_currentChatId = null;
debugPrint("🔌 WebSocket disconnected intentionally");
}
/// Switch to a different chat channel
Future<void> switchChannel(String newChatId) async {
if (newChatId == _currentChatId) return;
debugPrint("🔄 Switching from chat.$_currentChatId to chat.$newChatId");
// Unsubscribe from current channel
if (_currentChatId != null) {
final unsubscribeMessage = {
'event': 'pusher:unsubscribe',
'data': {'channel': 'chat.$_currentChatId'},
};
_sendMessage(unsubscribeMessage);
}
// Update chat ID and subscribe to new channel
_currentChatId = newChatId;
_subscribeToChannel();
}
/// Dispose and cleanup
void dispose() {
disconnect();
}
}