feat: Implement heartbeat mechanism to maintain WebSocket connection
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user