feat: Implement heartbeat mechanism to maintain WebSocket connection

This commit is contained in:
k1ngsterr1
2026-01-11 16:54:03 +05:00
parent 161a4fe61b
commit 304b57af30

View File

@@ -69,6 +69,9 @@ class Protocol(
private var lastPublicKey: String? = null private var lastPublicKey: String? = null
private var lastPrivateHash: String? = null private var lastPrivateHash: String? = null
// Heartbeat
private var heartbeatJob: Job? = null
// Supported packets // Supported packets
private val supportedPackets = mapOf( private val supportedPackets = mapOf(
0x00 to { PacketHandshake() }, 0x00 to { PacketHandshake() },
@@ -92,6 +95,30 @@ class Protocol(
handshakeComplete = true handshakeComplete = true
_state.value = ProtocolState.AUTHENTICATED _state.value = ProtocolState.AUTHENTICATED
flushPacketQueue() 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 * Initialize connection to server
*/ */
fun connect() { fun connect() {
if (_state.value == ProtocolState.CONNECTING || _state.value == ProtocolState.CONNECTED) { if (_state.value == ProtocolState.CONNECTING) {
log("Already connecting or connected") 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 return
} }
isManuallyClosed = false 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 _state.value = ProtocolState.CONNECTING
_lastError.value = null _lastError.value = null
@@ -266,6 +299,7 @@ class Protocol(
_state.value = ProtocolState.DISCONNECTED _state.value = ProtocolState.DISCONNECTED
handshakeComplete = false handshakeComplete = false
handshakeJob?.cancel() handshakeJob?.cancel()
heartbeatJob?.cancel()
if (!isManuallyClosed && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { if (!isManuallyClosed && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
reconnectAttempts++ reconnectAttempts++
@@ -302,6 +336,7 @@ class Protocol(
log("Disconnecting...") log("Disconnecting...")
isManuallyClosed = true isManuallyClosed = true
handshakeJob?.cancel() handshakeJob?.cancel()
heartbeatJob?.cancel()
webSocket?.close(1000, "User disconnected") webSocket?.close(1000, "User disconnected")
webSocket = null webSocket = null
_state.value = ProtocolState.DISCONNECTED _state.value = ProtocolState.DISCONNECTED
@@ -332,6 +367,7 @@ class Protocol(
*/ */
fun destroy() { fun destroy() {
disconnect() disconnect()
heartbeatJob?.cancel()
scope.cancel() scope.cancel()
} }
} }