feat: Enhance packet handling logging to notify number of waiters and catch errors

This commit is contained in:
k1ngsterr1
2026-01-12 04:31:42 +05:00
parent e04010d720
commit a7976c7cf3
2 changed files with 137 additions and 70 deletions

View File

@@ -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()
}
}
}

View File

@@ -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
}
)
}
}