fix(dm): WebSocket heartbeat와 token 재연결을 보정한다
This commit is contained in:
@@ -48,6 +48,8 @@ class DmChatRoomViewModel(
|
||||
private var currentRealtimeToken: String = ""
|
||||
private var currentRealtimeRoomId: Long = 0L
|
||||
private var reconnectDisposable: Disposable? = null
|
||||
private var heartbeatPingDisposable: Disposable? = null
|
||||
private var heartbeatTimeoutDisposable: Disposable? = null
|
||||
private var localMessageSequence: Long = 0L
|
||||
private var requestSequence: Long = 0L
|
||||
private val pendingRequestLocalIds = mutableMapOf<String, String>()
|
||||
@@ -188,7 +190,16 @@ class DmChatRoomViewModel(
|
||||
|
||||
private fun connectRealtime(token: String) {
|
||||
val roomId = currentRoomId
|
||||
if (roomId <= 0L || isRealtimeConnected && currentRealtimeRoomId == roomId) return
|
||||
if (roomId <= 0L) return
|
||||
if (currentRealtimeToken.isNotEmpty() && currentRealtimeToken != token && currentRealtimeRoomId == roomId) {
|
||||
stopHeartbeat()
|
||||
repository.closeSocket()
|
||||
isRealtimeJoining = false
|
||||
isRealtimeConnected = false
|
||||
currentRealtimeToken = ""
|
||||
currentRealtimeRoomId = 0L
|
||||
}
|
||||
if (isRealtimeConnected && currentRealtimeRoomId == roomId) return
|
||||
if (isRealtimeJoining && currentRealtimeRoomId == roomId) return
|
||||
if (!shouldReconnectRealtime && currentRealtimeToken.isNotEmpty() && currentRealtimeRoomId == roomId) return
|
||||
|
||||
@@ -222,8 +233,12 @@ class DmChatRoomViewModel(
|
||||
fun leaveRealtime() {
|
||||
val roomId = currentRoomId
|
||||
if (roomId <= 0L) return
|
||||
val hasActiveSocket = currentRealtimeRoomId == roomId &&
|
||||
(isRealtimeJoining || isRealtimeConnected || currentRealtimeToken.isNotEmpty())
|
||||
if (!hasActiveSocket) return
|
||||
|
||||
shouldReconnectRealtime = false
|
||||
stopHeartbeat()
|
||||
currentRealtimeToken = ""
|
||||
currentRealtimeRoomId = 0L
|
||||
isRealtimeJoining = false
|
||||
@@ -243,7 +258,7 @@ class DmChatRoomViewModel(
|
||||
reconnectDisposable = reconnectScheduler.scheduleDirect(
|
||||
{
|
||||
scheduleRealtimeCallback {
|
||||
if (shouldReconnectRealtime) connectRealtime(token = token)
|
||||
if (shouldReconnectRealtime) connectRealtime(token = authToken().ifBlank { token })
|
||||
}
|
||||
},
|
||||
RECONNECT_DELAY_MILLIS,
|
||||
@@ -261,6 +276,7 @@ class DmChatRoomViewModel(
|
||||
|
||||
override fun onCleared() {
|
||||
mainHandler.removeCallbacksAndMessages(null)
|
||||
stopHeartbeat()
|
||||
reconnectDisposable?.dispose()
|
||||
reconnectDisposable = null
|
||||
currentRealtimeRoomId = 0L
|
||||
@@ -362,15 +378,65 @@ class DmChatRoomViewModel(
|
||||
DmChatSocketEvent.Joined -> {
|
||||
isRealtimeJoining = false
|
||||
isRealtimeConnected = true
|
||||
startHeartbeat()
|
||||
syncLatestMessagesAfterReconnect(token = token)
|
||||
}
|
||||
is DmChatSocketEvent.Message -> handleRealtimeMessage(event.requestId, event.message)
|
||||
is DmChatSocketEvent.SendAck -> handleSendAck(event.requestId, event.message)
|
||||
is DmChatSocketEvent.Error -> event.requestId?.let { markPendingMessageFailed(it) }
|
||||
DmChatSocketEvent.Pong -> Unit
|
||||
DmChatSocketEvent.Pong -> clearHeartbeatTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startHeartbeat() {
|
||||
stopHeartbeat()
|
||||
heartbeatPingDisposable = reconnectScheduler.schedulePeriodicallyDirect(
|
||||
{
|
||||
scheduleRealtimeCallback {
|
||||
if (!isRealtimeConnected || !shouldReconnectRealtime) return@scheduleRealtimeCallback
|
||||
val latestToken = authToken()
|
||||
if (latestToken.isNotBlank() && latestToken != currentRealtimeToken) {
|
||||
connectRealtime(token = latestToken)
|
||||
return@scheduleRealtimeCallback
|
||||
}
|
||||
if (repository.sendPing()) scheduleHeartbeatTimeout()
|
||||
}
|
||||
},
|
||||
HEARTBEAT_INTERVAL_MILLIS,
|
||||
HEARTBEAT_INTERVAL_MILLIS,
|
||||
TimeUnit.MILLISECONDS
|
||||
).also { compositeDisposable.add(it) }
|
||||
}
|
||||
|
||||
private fun scheduleHeartbeatTimeout() {
|
||||
heartbeatTimeoutDisposable?.dispose()
|
||||
heartbeatTimeoutDisposable = reconnectScheduler.scheduleDirect(
|
||||
{
|
||||
scheduleRealtimeCallback {
|
||||
if (!isRealtimeConnected || !shouldReconnectRealtime) return@scheduleRealtimeCallback
|
||||
isRealtimeJoining = false
|
||||
isRealtimeConnected = false
|
||||
stopHeartbeat()
|
||||
repository.closeSocket()
|
||||
scheduleRealtimeReconnect()
|
||||
}
|
||||
},
|
||||
HEARTBEAT_TIMEOUT_MILLIS,
|
||||
TimeUnit.MILLISECONDS
|
||||
).also { compositeDisposable.add(it) }
|
||||
}
|
||||
|
||||
private fun stopHeartbeat() {
|
||||
heartbeatPingDisposable?.dispose()
|
||||
heartbeatPingDisposable = null
|
||||
clearHeartbeatTimeout()
|
||||
}
|
||||
|
||||
private fun clearHeartbeatTimeout() {
|
||||
heartbeatTimeoutDisposable?.dispose()
|
||||
heartbeatTimeoutDisposable = null
|
||||
}
|
||||
|
||||
private fun handleSendAck(requestId: String, message: DmChatMessageResponse) {
|
||||
val localId = pendingRequestLocalIds.remove(requestId)
|
||||
?: recentFailedRequestLocalIds.remove(requestId)
|
||||
@@ -475,6 +541,8 @@ class DmChatRoomViewModel(
|
||||
private companion object {
|
||||
const val RECONNECT_DELAY_MILLIS = 3_000L
|
||||
const val SEND_ACK_TIMEOUT_MILLIS = 10_000L
|
||||
const val HEARTBEAT_INTERVAL_MILLIS = 30_000L
|
||||
const val HEARTBEAT_TIMEOUT_MILLIS = 10_000L
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user