feat(android): persist client_id for play-event reporting

Adds a stable client_id column to auth_session for the upcoming
PlayEventsReporter. Lazily generated on first read by the reporter;
deliberately survives sign-out since it's a device install identity,
not a session value. DB version bump 3→4 (destructive migration per
the pre-release policy).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 14:01:21 -04:00
parent 3f00006b74
commit 415200d8f0
4 changed files with 24 additions and 1 deletions
@@ -43,6 +43,9 @@ class AuthStore @Inject constructor(
private val themeModeState = MutableStateFlow<String?>(null) private val themeModeState = MutableStateFlow<String?>(null)
val themeMode: StateFlow<String?> = themeModeState.asStateFlow() val themeMode: StateFlow<String?> = themeModeState.asStateFlow()
private val clientIdState = MutableStateFlow<String?>(null)
val clientId: StateFlow<String?> = clientIdState.asStateFlow()
init { init {
scope.launch { scope.launch {
dao.observe().collect { row -> dao.observe().collect { row ->
@@ -50,6 +53,7 @@ class AuthStore @Inject constructor(
baseUrlState.value = row?.baseUrl ?: DEFAULT_BASE_URL baseUrlState.value = row?.baseUrl ?: DEFAULT_BASE_URL
userJsonState.value = row?.userJson userJsonState.value = row?.userJson
themeModeState.value = row?.themeMode themeModeState.value = row?.themeMode
clientIdState.value = row?.clientId
} }
} }
} }
@@ -74,6 +78,11 @@ class AuthStore @Inject constructor(
scope.launch { persistThemeMode(value) } scope.launch { persistThemeMode(value) }
} }
fun setClientId(value: String?) {
clientIdState.value = value
scope.launch { persistClientId(value) }
}
private suspend fun persistCookie(value: String?) { private suspend fun persistCookie(value: String?) {
if (dao.get() == null) { if (dao.get() == null) {
dao.upsert(currentEntity().copy(sessionCookie = value)) dao.upsert(currentEntity().copy(sessionCookie = value))
@@ -106,12 +115,21 @@ class AuthStore @Inject constructor(
} }
} }
private suspend fun persistClientId(value: String?) {
if (dao.get() == null) {
dao.upsert(currentEntity().copy(clientId = value))
} else {
dao.setClientId(value)
}
}
private fun currentEntity(): AuthSessionEntity = AuthSessionEntity( private fun currentEntity(): AuthSessionEntity = AuthSessionEntity(
id = ROW_ID, id = ROW_ID,
sessionCookie = sessionCookieState.value, sessionCookie = sessionCookieState.value,
baseUrl = baseUrlState.value, baseUrl = baseUrlState.value,
userJson = userJsonState.value, userJson = userJsonState.value,
themeMode = themeModeState.value, themeMode = themeModeState.value,
clientId = clientIdState.value,
) )
companion object { companion object {
@@ -59,7 +59,7 @@ import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity
CachedHomeIndexEntity::class, CachedHomeIndexEntity::class,
AuthSessionEntity::class, AuthSessionEntity::class,
], ],
version = 3, version = 4,
exportSchema = true, exportSchema = true,
) )
@TypeConverters(MinstrelTypeConverters::class) @TypeConverters(MinstrelTypeConverters::class)
@@ -34,4 +34,8 @@ interface AuthSessionDao {
/** Partial update: change only the theme override ("light"/"dark"/null=system). */ /** Partial update: change only the theme override ("light"/"dark"/null=system). */
@Query("UPDATE auth_session SET themeMode = :themeMode WHERE id = 0") @Query("UPDATE auth_session SET themeMode = :themeMode WHERE id = 0")
suspend fun setThemeMode(themeMode: String?) suspend fun setThemeMode(themeMode: String?)
/** Partial update: change only the stable client install id. */
@Query("UPDATE auth_session SET clientId = :clientId WHERE id = 0")
suspend fun setClientId(clientId: String?)
} }
@@ -28,4 +28,5 @@ data class AuthSessionEntity(
val baseUrl: String, val baseUrl: String,
val userJson: String? = null, val userJson: String? = null,
val themeMode: String? = null, val themeMode: String? = null,
val clientId: String? = null,
) )