feat: Enhance packet handling logging to notify number of waiters and catch errors
This commit is contained in:
@@ -104,6 +104,7 @@ class Protocol(
|
||||
|
||||
/**
|
||||
* Start heartbeat to keep connection alive
|
||||
* Как в Архиве - отправляем text "heartbeat"
|
||||
*/
|
||||
private fun startHeartbeat(intervalSeconds: Int) {
|
||||
heartbeatJob?.cancel()
|
||||
@@ -114,10 +115,14 @@ class Protocol(
|
||||
heartbeatJob = scope.launch {
|
||||
while (isActive) {
|
||||
delay(intervalMs)
|
||||
if (webSocket?.send("heartbeat") == true) {
|
||||
log("💓 Heartbeat sent")
|
||||
} else {
|
||||
log("💔 Heartbeat failed to send")
|
||||
try {
|
||||
if (webSocket?.send("heartbeat") == true) {
|
||||
log("💓 Heartbeat sent")
|
||||
} else {
|
||||
log("💔 Heartbeat failed - socket closed or null")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log("💔 Heartbeat error: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,14 +137,18 @@ class Protocol(
|
||||
return
|
||||
}
|
||||
|
||||
// Allow reconnection even if connected (for manual reconnect)
|
||||
if (_state.value == ProtocolState.CONNECTED || _state.value == ProtocolState.AUTHENTICATED) {
|
||||
log("Already connected/authenticated, skipping...")
|
||||
return
|
||||
// Закрываем старый сокет если есть (как в Архиве)
|
||||
webSocket?.let { oldSocket ->
|
||||
try {
|
||||
oldSocket.close(1000, "Reconnecting")
|
||||
log("🔄 Closed old WebSocket before reconnect")
|
||||
} catch (e: Exception) {
|
||||
log("⚠️ Error closing old socket: ${e.message}")
|
||||
}
|
||||
}
|
||||
webSocket = null
|
||||
|
||||
isManuallyClosed = false
|
||||
// Don't reset reconnectAttempts here - it's reset on successful connection in onOpen
|
||||
_state.value = ProtocolState.CONNECTING
|
||||
_lastError.value = null
|
||||
|
||||
@@ -164,6 +173,7 @@ class Protocol(
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
|
||||
log("📥 onMessage called - ${bytes.size} bytes")
|
||||
handleMessage(bytes.toByteArray())
|
||||
}
|
||||
|
||||
@@ -172,16 +182,23 @@ class Protocol(
|
||||
}
|
||||
|
||||
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
|
||||
log("⚠️ WebSocket closing: $code - $reason")
|
||||
log("⚠️ WebSocket closing: code=$code reason='$reason'")
|
||||
log("⚠️ Stack trace at closing:")
|
||||
Thread.currentThread().stackTrace.take(10).forEach {
|
||||
log(" at $it")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
||||
log("❌ WebSocket closed: $code - $reason")
|
||||
log("❌ WebSocket closed: code=$code reason='$reason'")
|
||||
log("❌ State was: ${_state.value}")
|
||||
handleDisconnect()
|
||||
}
|
||||
|
||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||
log("❌ WebSocket error: ${t.message}")
|
||||
log("❌ WebSocket failure: ${t.message}")
|
||||
log("❌ Response: ${response?.code} ${response?.message}")
|
||||
t.printStackTrace()
|
||||
_lastError.value = t.message
|
||||
handleDisconnect()
|
||||
}
|
||||
@@ -251,7 +268,23 @@ class Protocol(
|
||||
val hexDump = data.take(50).joinToString(" ") { String.format("%02X", it) }
|
||||
log(" Hex: $hexDump${if (data.size > 50) "..." else ""}")
|
||||
|
||||
webSocket?.send(ByteString.of(*data))
|
||||
val socket = webSocket
|
||||
if (socket == null) {
|
||||
log("❌ WebSocket is null, cannot send packet ${packet.getPacketId()}")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val sent = socket.send(ByteString.of(*data))
|
||||
if (sent) {
|
||||
log("✅ Packet ${packet.getPacketId()} sent successfully")
|
||||
} else {
|
||||
log("❌ Failed to send packet ${packet.getPacketId()} - send() returned false")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log("❌ Exception sending packet ${packet.getPacketId()}: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun flushPacketQueue() {
|
||||
@@ -282,11 +315,15 @@ class Protocol(
|
||||
packet.receive(stream)
|
||||
|
||||
// Notify waiters
|
||||
val waitersCount = packetWaiters[packetId]?.size ?: 0
|
||||
log("📥 Notifying $waitersCount waiter(s) for packet $packetId")
|
||||
|
||||
packetWaiters[packetId]?.forEach { callback ->
|
||||
try {
|
||||
callback(packet)
|
||||
} catch (e: Exception) {
|
||||
log("❌ Error in packet handler: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -301,37 +338,26 @@ class Protocol(
|
||||
handshakeComplete = false
|
||||
handshakeJob?.cancel()
|
||||
heartbeatJob?.cancel()
|
||||
webSocket = null // Обнуляем сокет чтобы connect() мог создать новый
|
||||
|
||||
// Автоматический reconnect (как в Архиве)
|
||||
// Автоматический reconnect (упрощённая логика как в Архиве)
|
||||
if (!isManuallyClosed) {
|
||||
// Логируем потерю соединения
|
||||
if (previousState == ProtocolState.AUTHENTICATED || previousState == ProtocolState.CONNECTED) {
|
||||
log("🔄 Connection lost from $previousState, will reconnect...")
|
||||
reconnectAttempts = 0 // Сбрасываем счётчик при неожиданной потере
|
||||
log("🔄 Connection lost from $previousState, reconnecting...")
|
||||
reconnectAttempts++
|
||||
|
||||
val delay = if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
||||
RECONNECT_INTERVAL
|
||||
} else {
|
||||
// После максимума попыток - ждём дольше
|
||||
reconnectAttempts = 0
|
||||
30000L
|
||||
}
|
||||
|
||||
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
||||
reconnectAttempts++
|
||||
val delay = RECONNECT_INTERVAL * minOf(reconnectAttempts, 3) // Exponential backoff до 15 секунд
|
||||
log("🔄 Reconnecting in ${delay}ms (attempt $reconnectAttempts/$MAX_RECONNECT_ATTEMPTS)")
|
||||
log("🔄 Reconnecting in ${delay}ms (attempt $reconnectAttempts)")
|
||||
|
||||
scope.launch {
|
||||
delay(delay)
|
||||
if (!isManuallyClosed) {
|
||||
connect()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log("❌ Max reconnect attempts reached, resetting counter...")
|
||||
// Сбрасываем счетчик и пробуем снова через большой интервал
|
||||
reconnectAttempts = 0
|
||||
scope.launch {
|
||||
delay(30000) // 30 секунд перед следующей серией попыток
|
||||
if (!isManuallyClosed) {
|
||||
log("🔄 Restarting reconnection attempts...")
|
||||
connect()
|
||||
}
|
||||
scope.launch {
|
||||
delay(delay)
|
||||
if (!isManuallyClosed) {
|
||||
connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,9 @@ fun ChatsListScreen(
|
||||
var titleClickCount by remember { mutableStateOf(0) }
|
||||
var lastClickTime by remember { mutableStateOf(0L) }
|
||||
|
||||
// Status dialog state
|
||||
var showStatusDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// Search state - используем ViewModel для поиска пользователей
|
||||
val searchViewModel = remember { SearchUsersViewModel() }
|
||||
val searchQuery by searchViewModel.searchQuery.collectAsState()
|
||||
@@ -226,12 +229,58 @@ fun ChatsListScreen(
|
||||
}
|
||||
*/
|
||||
|
||||
// Status dialog
|
||||
if (showStatusDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showStatusDialog = false },
|
||||
title = { Text("Connection Status", fontWeight = FontWeight.Bold) },
|
||||
text = {
|
||||
Column {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
when (protocolState) {
|
||||
ProtocolState.AUTHENTICATED -> Color(0xFF4CAF50)
|
||||
ProtocolState.CONNECTING, ProtocolState.CONNECTED, ProtocolState.HANDSHAKING -> Color(0xFFFFC107)
|
||||
ProtocolState.DISCONNECTED -> Color(0xFFF44336)
|
||||
}
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
text = when (protocolState) {
|
||||
ProtocolState.DISCONNECTED -> "Disconnected"
|
||||
ProtocolState.CONNECTING -> "Connecting..."
|
||||
ProtocolState.CONNECTED -> "Connected"
|
||||
ProtocolState.HANDSHAKING -> "Authenticating..."
|
||||
ProtocolState.AUTHENTICATED -> "Authenticated"
|
||||
},
|
||||
fontSize = 16.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(onClick = { showStatusDialog = false }) {
|
||||
Text("OK")
|
||||
}
|
||||
},
|
||||
containerColor = if (isDarkTheme) Color(0xFF212121) else Color.White
|
||||
)
|
||||
}
|
||||
|
||||
// Simple background
|
||||
Box(modifier = Modifier.fillMaxSize().background(backgroundColor)) {
|
||||
ModalNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
ModalDrawerSheet(drawerContainerColor = drawerBackgroundColor) {
|
||||
ModalDrawerSheet(
|
||||
drawerContainerColor = drawerBackgroundColor,
|
||||
modifier = Modifier.width(280.dp)
|
||||
) {
|
||||
// Drawer Header
|
||||
Column(
|
||||
modifier =
|
||||
@@ -319,7 +368,7 @@ fun ChatsListScreen(
|
||||
selected = false,
|
||||
colors = drawerItemColors,
|
||||
onClick = {
|
||||
scope.launch { drawerState.close() }
|
||||
// Don't close drawer when toggling theme
|
||||
onToggleTheme()
|
||||
}
|
||||
)
|
||||
@@ -410,7 +459,7 @@ fun ChatsListScreen(
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
// Title - Triple click to open dev console (stays in place, just fades)
|
||||
if (titleAlpha > 0.01f) {
|
||||
Column(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
val currentTime =
|
||||
@@ -435,7 +484,8 @@ fun ChatsListScreen(
|
||||
lastClickTime =
|
||||
currentTime
|
||||
}
|
||||
.graphicsLayer { alpha = titleAlpha }
|
||||
.graphicsLayer { alpha = titleAlpha },
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
"Rosetta",
|
||||
@@ -443,32 +493,23 @@ fun ChatsListScreen(
|
||||
fontSize = 20.sp,
|
||||
color = textColor
|
||||
)
|
||||
if (protocolState !=
|
||||
ProtocolState.AUTHENTICATED
|
||||
) {
|
||||
Text(
|
||||
text =
|
||||
when (protocolState) {
|
||||
ProtocolState
|
||||
.DISCONNECTED ->
|
||||
"Connecting..."
|
||||
ProtocolState
|
||||
.CONNECTING ->
|
||||
"Connecting..."
|
||||
ProtocolState
|
||||
.CONNECTED ->
|
||||
"Authenticating..."
|
||||
ProtocolState
|
||||
.HANDSHAKING ->
|
||||
"Authenticating..."
|
||||
ProtocolState
|
||||
.AUTHENTICATED ->
|
||||
""
|
||||
},
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
// Status indicator dot
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(10.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
when (protocolState) {
|
||||
ProtocolState.AUTHENTICATED -> Color(0xFF4CAF50) // Green
|
||||
ProtocolState.CONNECTING, ProtocolState.CONNECTED, ProtocolState.HANDSHAKING -> Color(0xFFFFC107) // Yellow
|
||||
ProtocolState.DISCONNECTED -> Color(0xFFF44336) // Red
|
||||
}
|
||||
)
|
||||
.clickable {
|
||||
showStatusDialog = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user