fix(dm): MESSAGE 선도착 ACK 처리를 보정한다

This commit is contained in:
2026-06-18 22:57:36 +09:00
parent 2c1eb03e5f
commit 482d517145
4 changed files with 141 additions and 21 deletions

View File

@@ -51,6 +51,7 @@ class DmChatRoomViewModel(
private var localMessageSequence: Long = 0L
private var requestSequence: Long = 0L
private val pendingRequestLocalIds = mutableMapOf<String, String>()
private val recentFailedRequestLocalIds = mutableMapOf<String, String>()
private val pendingTimeoutDisposables = mutableMapOf<String, Disposable>()
private val mainHandler = Handler(Looper.getMainLooper())
@@ -137,6 +138,7 @@ class DmChatRoomViewModel(
if (currentRoomId <= 0L) return
val requestId = nextRequestId()
removeRecentFailedRequests(localId)
currentMessages = currentMessages.map {
if (it.localId == localId) it.copy(requestId = requestId, status = DmChatMessageStatus.SENDING) else it
}
@@ -148,9 +150,7 @@ class DmChatRoomViewModel(
}
fun onRealtimeMessage(message: DmChatMessageResponse) {
val item = message.toUiItem() ?: return
currentMessages = currentMessages.mergeByMessageId(listOf(item))
emitContent()
handleRealtimeMessage(requestId = null, message = message)
}
fun syncLatestMessagesAfterReconnect() {
@@ -364,7 +364,7 @@ class DmChatRoomViewModel(
isRealtimeConnected = true
syncLatestMessagesAfterReconnect(token = token)
}
is DmChatSocketEvent.Message -> onRealtimeMessage(event.message)
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
@@ -372,7 +372,9 @@ class DmChatRoomViewModel(
}
private fun handleSendAck(requestId: String, message: DmChatMessageResponse) {
val localId = pendingRequestLocalIds.remove(requestId) ?: return
val localId = pendingRequestLocalIds.remove(requestId)
?: recentFailedRequestLocalIds.remove(requestId)
?: return
pendingTimeoutDisposables.remove(requestId)?.dispose()
val sentItem = message.toUiItem()
if (sentItem == null) {
@@ -380,12 +382,28 @@ class DmChatRoomViewModel(
return
}
removeRecentFailedRequests(localId)
currentMessages = currentMessages.map {
if (it.localId == localId) sentItem.copy(localId = localId) else it
}.deduplicateSentMessage(sentItem.messageId).sortByCreatedAtAndMessageId()
emitContent()
}
private fun removeRecentFailedRequests(localId: String) {
recentFailedRequestLocalIds.entries.removeAll { it.value == localId }
}
private fun handleRealtimeMessage(requestId: String?, message: DmChatMessageResponse) {
if (requestId != null && pendingRequestLocalIds.containsKey(requestId)) {
handleSendAck(requestId, message)
return
}
val item = message.toUiItem() ?: return
currentMessages = currentMessages.mergeByMessageId(listOf(item))
emitContent()
}
private fun List<DmChatMessageUiItem>.deduplicateSentMessage(messageId: Long?): List<DmChatMessageUiItem> {
if (messageId == null) return this
var found = false
@@ -399,6 +417,7 @@ class DmChatRoomViewModel(
private fun markPendingMessageFailed(requestId: String) {
val localId = pendingRequestLocalIds.remove(requestId) ?: return
recentFailedRequestLocalIds[requestId] = localId
pendingTimeoutDisposables.remove(requestId)?.dispose()
markLocalMessageFailed(localId)
}

View File

@@ -26,6 +26,7 @@ data class DmChatSocketSendTextPayload(
@Keep
data class DmChatSocketMessagePayload(
@SerializedName("requestId") val requestId: String?,
@SerializedName("message") val message: DmChatMessageResponse
)
@@ -51,7 +52,10 @@ enum class DmChatSocketClientType(val value: String) {
sealed class DmChatSocketEvent {
data object Joined : DmChatSocketEvent()
data class Message(val message: DmChatMessageResponse) : DmChatSocketEvent()
data class Message(
val requestId: String?,
val message: DmChatMessageResponse
) : DmChatSocketEvent()
data class SendAck(
val requestId: String,
val message: DmChatMessageResponse
@@ -85,7 +89,10 @@ class DmChatSocketParser(private val gson: Gson) {
private fun parseMessage(payload: JsonObject?): DmChatSocketEvent.Message? {
val messagePayload = gson.fromJson(payload, DmChatSocketMessagePayload::class.java) ?: return null
return DmChatSocketEvent.Message(messagePayload.message)
return DmChatSocketEvent.Message(
requestId = messagePayload.requestId,
message = messagePayload.message
)
}
private fun parseSendAck(payload: JsonObject?): DmChatSocketEvent.SendAck? {