Fix: Prevent AuthManager state pollution on connection verification (#348)
* Fix: Prevent AuthManager state pollution on connection verification Modified `ConnectViewModel.connect()` to construct a temporary OkHttpClient and Retrofit builder, isolated from the global `ApiClient` state. The connection verification is executed using this temporary service. Only upon a successful HTTP response does the system persist the configured `host`, `port`, and `token` to the global `AuthManager` and triggers an `ApiClient.rebuild()`. This resolves the issue where a failed connection attempt would overwrite active and verified profiles. Extracted `createTempService` factory to `ApiClient` for robustness and clean tests. Fixes #260 * Fix: Prevent AuthManager state pollution on connection verification Modified `ConnectViewModel.connect()` to construct a temporary OkHttpClient and Retrofit builder, isolated from the global `ApiClient` state. The connection verification is executed using this temporary service. Only upon a successful HTTP response does the system persist the configured `host`, `port`, and `token` to the global `AuthManager` and triggers an `ApiClient.rebuild()`. This resolves the issue where a failed connection attempt would overwrite active and verified profiles. Extracted `createTempService` factory to `ApiClient` for robustness and clean tests. Fixes #260 --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
c21e3e9ed0
commit
2c6e437a5b
42 files changed
+296
-667
No files matched your search
@@ -57,10 +57,6 @@ 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()
|
||||
|
||||
/**
|
||||
@@ -88,7 +84,6 @@ object AuthManager {
|
||||
_useDynamicColorsFlow.value = isUseDynamicColors()
|
||||
_themePresetFlow.value = getThemePreset()
|
||||
_bottomNavDisplayModeFlow.value = getBottomNavDisplayMode()
|
||||
_tokenFlow.value = getToken()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,9 +103,6 @@ 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()
|
||||
}
|
||||
}
|
||||
@@ -127,9 +119,6 @@ object AuthManager {
|
||||
token: String?,
|
||||
) {
|
||||
requirePrefs().edit().putString("token_$profileId", token).apply()
|
||||
if (getSelectedProfileId() == profileId) {
|
||||
_tokenFlow.value = token
|
||||
}
|
||||
}
|
||||
|
||||
fun getSelectedProfileId(): String? {
|
||||
@@ -139,7 +128,6 @@ object AuthManager {
|
||||
|
||||
fun setSelectedProfileId(id: String?) {
|
||||
requirePrefs().edit().putString(KEY_SELECTED_PROFILE_ID, id).apply()
|
||||
_tokenFlow.value = getToken()
|
||||
}
|
||||
|
||||
// ── Token ────────────────────────────────────────────────────────────
|
||||
@@ -147,7 +135,8 @@ object AuthManager {
|
||||
fun getToken(): String? {
|
||||
val selectedId = getSelectedProfileId()
|
||||
if (selectedId != null) {
|
||||
return getProfileToken(selectedId)
|
||||
val token = getProfileToken(selectedId)
|
||||
if (token != null) return token
|
||||
}
|
||||
return requirePrefs().getString(KEY_TOKEN, null)
|
||||
}
|
||||
@@ -158,7 +147,6 @@ object AuthManager {
|
||||
setProfileToken(selectedId, token)
|
||||
} else {
|
||||
requirePrefs().edit().putString(KEY_TOKEN, token).apply()
|
||||
_tokenFlow.value = token
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,7 @@ import androidx.room.PrimaryKey
|
||||
* thread. The [timestamp] field preserves original ordering even if Room
|
||||
* reorders internally.
|
||||
*/
|
||||
@Entity(
|
||||
tableName = "chat_messages",
|
||||
indices = [androidx.room.Index(value = ["session_id", "timestamp"])],
|
||||
)
|
||||
@Entity(tableName = "chat_messages")
|
||||
data class ChatMessageEntity(
|
||||
@PrimaryKey
|
||||
val id: String,
|
||||
|
||||
@@ -4,13 +4,11 @@ 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 = 2,
|
||||
version = 1,
|
||||
exportSchema = true,
|
||||
)
|
||||
abstract class HermesDatabase : RoomDatabase() {
|
||||
@@ -20,16 +18,6 @@ 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
|
||||
@@ -37,8 +25,7 @@ abstract class HermesDatabase : RoomDatabase() {
|
||||
context.applicationContext,
|
||||
HermesDatabase::class.java,
|
||||
"hermes_control.db",
|
||||
).addMigrations(MIGRATION_1_2)
|
||||
.apply {
|
||||
).apply {
|
||||
if (BuildConfig.DEBUG) {
|
||||
fallbackToDestructiveMigration(false)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user