Fix WebSocket events being silently dropped on buffer overflow (#313)

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow `extraBufferCapacity` from 64 to 256
and changes the `onBufferOverflow` policy to `BufferOverflow.DROP_OLDEST`
in `HermesWsClient.kt`. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting `DROP_OLDEST`, `tryEmit()` remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix ktlint formatting issues in HermesWsClient.kt

The formatting changes adhere to ktlint rules required by the CI pipeline:
- Removed a trailing comma in MutableSharedFlow initialization to avoid multiline expression wrapping errors.
- Sorted imports to be in lexicographical order.

These formatting changes fix the failing CI check.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow `extraBufferCapacity` from 64 to 256
and changes the `onBufferOverflow` policy to `BufferOverflow.DROP_OLDEST`
in `HermesWsClient.kt`. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting `DROP_OLDEST`, `tryEmit()` remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow extraBufferCapacity from 64 to 256
and changes the onBufferOverflow policy to BufferOverflow.DROP_OLDEST
in HermesWsClient.kt. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting DROP_OLDEST, tryEmit() remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow extraBufferCapacity from 64 to 256
and changes the onBufferOverflow policy to BufferOverflow.DROP_OLDEST
in HermesWsClient.kt. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting DROP_OLDEST, tryEmit() remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

* Fix WebSocket events being silently dropped on buffer overflow

Increases the MutableSharedFlow extraBufferCapacity from 64 to 256
and changes the onBufferOverflow policy to BufferOverflow.DROP_OLDEST
in HermesWsClient.kt. This fixes an issue where the slow collection
(e.g., due to DB writes) caused the buffer to fill up and silently drop
incoming real-time WebSocket events.

By setting DROP_OLDEST, tryEmit() remains functional without
suspending, allowing real-time events to be retained and prioritized.

Fixes #275

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-06-23 22:13:55 +03:00
1 parent 2c6e437a5b
commit eb71ae3a46
41 files changed
+666 -246

No files matched your search

@@ -57,6 +57,10 @@ object AuthManager {
private val _bottomNavDisplayModeFlow = MutableStateFlow<BottomNavDisplayMode>(BottomNavDisplayMode.ICON_AND_TEXT)
val bottomNavDisplayModeFlow: StateFlow<BottomNavDisplayMode> = _bottomNavDisplayModeFlow.asStateFlow()
private val _tokenFlow = MutableStateFlow<String?>(null)
val tokenFlow: StateFlow<String?> = _tokenFlow.asStateFlow()
private val gson = com.google.gson.Gson()
/**
@@ -84,6 +88,7 @@ object AuthManager {
_useDynamicColorsFlow.value = isUseDynamicColors()
_themePresetFlow.value = getThemePreset()
_bottomNavDisplayModeFlow.value = getBottomNavDisplayMode()
_tokenFlow.value = getToken()
}
}
@@ -103,6 +108,9 @@ object AuthManager {
>() {}.type
gson.fromJson(json, type) ?: emptyList()
} catch (e: Exception) {
if (com.m57.hermescontrol.BuildConfig.DEBUG) {
android.util.Log.w("AuthManager", "Failed to parse connection profiles", e)
}
emptyList()
}
}
@@ -119,6 +127,9 @@ object AuthManager {
token: String?,
) {
requirePrefs().edit().putString("token_$profileId", token).apply()
if (getSelectedProfileId() == profileId) {
_tokenFlow.value = token
}
}
fun getSelectedProfileId(): String? {
@@ -128,6 +139,7 @@ object AuthManager {
fun setSelectedProfileId(id: String?) {
requirePrefs().edit().putString(KEY_SELECTED_PROFILE_ID, id).apply()
_tokenFlow.value = getToken()
}
// ── Token ────────────────────────────────────────────────────────────
@@ -135,8 +147,7 @@ object AuthManager {
fun getToken(): String? {
val selectedId = getSelectedProfileId()
if (selectedId != null) {
val token = getProfileToken(selectedId)
if (token != null) return token
return getProfileToken(selectedId)
}
return requirePrefs().getString(KEY_TOKEN, null)
}
@@ -147,6 +158,7 @@ object AuthManager {
setProfileToken(selectedId, token)
} else {
requirePrefs().edit().putString(KEY_TOKEN, token).apply()
_tokenFlow.value = token
}
}
@@ -12,7 +12,10 @@ import androidx.room.PrimaryKey
* thread. The [timestamp] field preserves original ordering even if Room
* reorders internally.
*/
@Entity(tableName = "chat_messages")
@Entity(
tableName = "chat_messages",
indices = [androidx.room.Index(value = ["session_id", "timestamp"])],
)
data class ChatMessageEntity(
@PrimaryKey
val id: String,
@@ -4,11 +4,13 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.m57.hermescontrol.BuildConfig
@Database(
entities = [ChatMessageEntity::class],
version = 1,
version = 2,
exportSchema = true,
)
abstract class HermesDatabase : RoomDatabase() {
@@ -18,6 +20,16 @@ abstract class HermesDatabase : RoomDatabase() {
@Volatile
private var instance: HermesDatabase? = null
private val MIGRATION_1_2 =
object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"CREATE INDEX IF NOT EXISTS `index_chat_messages_session_id_timestamp` " +
"ON `chat_messages` (`session_id`, `timestamp`)",
)
}
}
fun get(context: Context): HermesDatabase =
instance ?: synchronized(this) {
instance ?: Room
@@ -25,7 +37,8 @@ abstract class HermesDatabase : RoomDatabase() {
context.applicationContext,
HermesDatabase::class.java,
"hermes_control.db",
).apply {
).addMigrations(MIGRATION_1_2)
.apply {
if (BuildConfig.DEBUG) {
fallbackToDestructiveMigration(false)
}