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 7db47d4..045fba7 100644 --- a/app/src/main/java/com/rosetta/messenger/network/Protocol.kt +++ b/app/src/main/java/com/rosetta/messenger/network/Protocol.kt @@ -69,6 +69,9 @@ class Protocol( private var lastPublicKey: String? = null private var lastPrivateHash: String? = null + // Heartbeat + private var heartbeatJob: Job? = null + // Supported packets private val supportedPackets = mapOf( 0x00 to { PacketHandshake() }, @@ -92,6 +95,30 @@ class Protocol( handshakeComplete = true _state.value = ProtocolState.AUTHENTICATED flushPacketQueue() + + // Start heartbeat with interval from server + startHeartbeat(packet.heartbeatInterval) + } + } + } + + /** + * Start heartbeat to keep connection alive + */ + private fun startHeartbeat(intervalSeconds: Int) { + heartbeatJob?.cancel() + + val intervalMs = (intervalSeconds * 1000L) / 2 // Send at half the interval + log("💓 Starting heartbeat with interval: ${intervalSeconds}s (sending every ${intervalMs}ms)") + + heartbeatJob = scope.launch { + while (isActive) { + delay(intervalMs) + if (webSocket?.send("heartbeat") == true) { + log("💓 Heartbeat sent") + } else { + log("💔 Heartbeat failed to send") + } } } } @@ -100,13 +127,19 @@ class Protocol( * Initialize connection to server */ fun connect() { - if (_state.value == ProtocolState.CONNECTING || _state.value == ProtocolState.CONNECTED) { - log("Already connecting or connected") + if (_state.value == ProtocolState.CONNECTING) { + log("Already connecting, skipping...") + return + } + + // Allow reconnection even if connected (for manual reconnect) + if (_state.value == ProtocolState.CONNECTED || _state.value == ProtocolState.AUTHENTICATED) { + log("Already connected/authenticated, skipping...") return } isManuallyClosed = false - reconnectAttempts = 0 // Reset reconnect attempts on new connection + // Don't reset reconnectAttempts here - it's reset on successful connection in onOpen _state.value = ProtocolState.CONNECTING _lastError.value = null @@ -266,6 +299,7 @@ class Protocol( _state.value = ProtocolState.DISCONNECTED handshakeComplete = false handshakeJob?.cancel() + heartbeatJob?.cancel() if (!isManuallyClosed && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { reconnectAttempts++ @@ -302,6 +336,7 @@ class Protocol( log("Disconnecting...") isManuallyClosed = true handshakeJob?.cancel() + heartbeatJob?.cancel() webSocket?.close(1000, "User disconnected") webSocket = null _state.value = ProtocolState.DISCONNECTED @@ -332,6 +367,7 @@ class Protocol( */ fun destroy() { disconnect() + heartbeatJob?.cancel() scope.cancel() } }