Remove verification listeners of logged out sessions

- Clear the listener when RustSessionVerificationService is destroyed.
- IncomingVerificationRequestObserver removes the listener of the clients which are not in the MatrixSessionCache anymore.
- Remove the sessions from the MatrixSessionCache when they are removed from the SessionStore (logout, account deactivation).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benoit Marty
2026-10-01 18:18:19 +02:00
parent ee65fb99ae
commit d68e92a4f0
4 changed files with 47 additions and 2 deletions
@@ -84,6 +84,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.parcelize.Parcelize
@@ -138,6 +139,7 @@ class RootFlowNode(
}
}
observeIncomingVerificationRequests()
observeRemovedSessions()
super.onBuilt()
}
@@ -230,6 +232,22 @@ class RootFlowNode(
}
}
/**
* Remove from the cache the sessions which have been removed from the session store, for instance after a logout.
*/
private fun observeRemovedSessions() {
sessionStore.sessionsFlow()
.map { sessions -> sessions.map { SessionId(it.userId) }.toSet() }
.runningFold(emptySet<SessionId>() to emptySet<SessionId>()) { (_, previous), current -> previous to current }
.onEach { (previous, current) ->
(previous - current).forEach { sessionId ->
Timber.d("Session $sessionId removed, remove it from the cache")
matrixSessionCache.remove(sessionId)
}
}
.launchIn(lifecycleScope)
}
private fun observeIncomingVerificationRequests() {
incomingVerificationRequestObserver.incomingVerificationRequests()
.onEach(::onIncomingVerificationRequest)
@@ -9,6 +9,7 @@ package io.element.android.appnav.verification
import dev.zacsweers.metro.Inject
import io.element.android.appnav.session.MatrixSessionCache
import io.element.android.libraries.core.coroutine.withPreviousValue
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
import io.element.android.libraries.matrix.api.verification.VerificationRequest
@@ -41,8 +42,13 @@ class IncomingVerificationRequestObserver(
*/
fun incomingVerificationRequests(): Flow<IncomingVerificationRequestData> = channelFlow {
launch {
matrixSessionCache.matrixClients.collect { matrixClients ->
matrixClients.forEach { matrixClient ->
matrixSessionCache.matrixClients.withPreviousValue().collect { (previousMatrixClients, matrixClients) ->
val previousMatrixClientSet = previousMatrixClients.orEmpty().toSet()
// Remove the listener of the clients which are not in the cache anymore
(previousMatrixClientSet - matrixClients.toSet()).forEach { matrixClient ->
matrixClient.sessionVerificationService.setListener(null)
}
(matrixClients - previousMatrixClientSet).forEach { matrixClient ->
val sessionId = matrixClient.sessionId
matrixClient.sessionVerificationService.setListener(
object : SessionVerificationServiceListener {
@@ -63,6 +63,26 @@ class IncomingVerificationRequestObserverTest {
assertThat(sessionVerificationService2.listener).isNull()
}
@Test
fun `listener is removed when the session is removed from the cache`() = runTest {
val matrixSessionCache = createMatrixSessionCache()
val observer = IncomingVerificationRequestObserver(
matrixSessionCache = matrixSessionCache,
appForegroundStateService = FakeAppForegroundStateService(),
)
observer.incomingVerificationRequests().test {
matrixSessionCache.getOrRestore(A_SESSION_ID)
matrixSessionCache.getOrRestore(A_SESSION_ID_2)
runCurrent()
assertThat(sessionVerificationService1.listener).isNotNull()
assertThat(sessionVerificationService2.listener).isNotNull()
matrixSessionCache.remove(A_SESSION_ID)
runCurrent()
assertThat(sessionVerificationService1.listener).isNull()
assertThat(sessionVerificationService2.listener).isNotNull()
}
}
@Test
fun `incoming verification request is emitted when the app goes to foreground`() = runTest {
val matrixSessionCache = createMatrixSessionCache()
@@ -245,6 +245,7 @@ class RustSessionVerificationService(
fun destroy() {
Timber.d("Destroying RustSessionVerificationService")
listener = null
verificationStateListenerTaskHandle.cancelAndDestroy()
recoveryStateListenerTaskHandle.cancelAndDestroy()
if (this::verificationController.isInitialized) {