From a7976c7cf3acacd6e14a578f09a34e9fd7613d20 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Mon, 12 Jan 2026 04:31:42 +0500 Subject: [PATCH] feat: Enhance packet handling logging to notify number of waiters and catch errors --- .../com/rosetta/messenger/network/Protocol.kt | 106 +++++++++++------- .../messenger/ui/chats/ChatsListScreen.kt | 101 ++++++++++++----- 2 files changed, 137 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/network/Protocol.kt b/app/src/main/java/com/rosetta/messenger/network/Protocol.kt index 78dcf8a..3aa618b 100644 --- a/app/src/main/java/com/rosetta/messenger/network/Protocol.kt +++ b/app/src/main/java/com/rosetta/messenger/network/Protocol.kt @@ -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)") - - 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() - } + log("🔄 Reconnecting in ${delay}ms (attempt $reconnectAttempts)") + + scope.launch { + delay(delay) + if (!isManuallyClosed) { + connect() } } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt index 365ab27..d0e0700 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt @@ -190,6 +190,9 @@ fun ChatsListScreen( var showDevConsole by remember { mutableStateOf(false) } 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() } @@ -225,13 +228,59 @@ 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 + } + ) } }