When logging out from Pin code screen, logout from all the sessions.
This commit is contained in:
@@ -174,7 +174,7 @@ class PinUnlockPresenter(
|
||||
|
||||
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||
suspend {
|
||||
logoutUseCase.logout(ignoreSdkError = true)
|
||||
logoutUseCase.logoutAll(ignoreSdkError = true)
|
||||
}.runCatchingUpdatingState(signOutAction)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,12 @@
|
||||
package io.element.android.features.logout.api
|
||||
|
||||
/**
|
||||
* Used to trigger a log out of the current user from any part of the app.
|
||||
* Used to trigger a log out of the current user(s) from any part of the app.
|
||||
*/
|
||||
interface LogoutUseCase {
|
||||
/**
|
||||
* Log out the current user and then perform any needed cleanup tasks.
|
||||
* Log out the current user(s) and then perform any needed cleanup tasks.
|
||||
* @param ignoreSdkError if true, the SDK error will be ignored and the user will be logged out anyway.
|
||||
*/
|
||||
suspend fun logout(ignoreSdkError: Boolean)
|
||||
|
||||
interface Factory {
|
||||
fun create(sessionId: String): LogoutUseCase
|
||||
}
|
||||
suspend fun logoutAll(ignoreSdkError: Boolean)
|
||||
}
|
||||
|
||||
@@ -39,4 +39,5 @@ dependencies {
|
||||
testCommonDependencies(libs, true)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
testImplementation(projects.libraries.sessionStorage.test)
|
||||
}
|
||||
|
||||
@@ -12,22 +12,31 @@ import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.logout.api.LogoutUseCase
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import timber.log.Timber
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
@Inject
|
||||
class DefaultLogoutUseCase(
|
||||
private val authenticationService: MatrixAuthenticationService,
|
||||
private val sessionStore: SessionStore,
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
) : LogoutUseCase {
|
||||
override suspend fun logout(ignoreSdkError: Boolean) {
|
||||
val currentSession = authenticationService.getLatestSessionId()
|
||||
if (currentSession != null) {
|
||||
matrixClientProvider.getOrRestore(currentSession)
|
||||
.getOrThrow()
|
||||
.logout(userInitiated = true, ignoreSdkError = true)
|
||||
} else {
|
||||
error("No session to sign out")
|
||||
}
|
||||
override suspend fun logoutAll(ignoreSdkError: Boolean) {
|
||||
sessionStore.getAllSessions()
|
||||
.map { sessionData ->
|
||||
SessionId(sessionData.userId)
|
||||
}
|
||||
.forEach { sessionId ->
|
||||
Timber.d("Logging out sessionId: $sessionId")
|
||||
matrixClientProvider.getOrRestore(sessionId).fold(
|
||||
onSuccess = { client ->
|
||||
client.logout(userInitiated = true, ignoreSdkError = ignoreSdkError)
|
||||
},
|
||||
onFailure = { error ->
|
||||
Timber.e(error, "Failed to get or restore MatrixClient for sessionId: $sessionId")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.features.logout.impl
|
||||
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultLogoutUseCaseTest {
|
||||
@Test
|
||||
fun `test logout from one session`() = runTest {
|
||||
val logoutLambda1 = lambdaRecorder<Boolean, Boolean, Unit> { _, _ -> }
|
||||
val client1 = FakeMatrixClient(A_USER_ID).apply {
|
||||
logoutLambda = logoutLambda1
|
||||
}
|
||||
val sut = DefaultLogoutUseCase(
|
||||
sessionStore = InMemorySessionStore(
|
||||
initialList = listOf(
|
||||
aSessionData(sessionId = A_USER_ID.value),
|
||||
)
|
||||
),
|
||||
matrixClientProvider = FakeMatrixClientProvider(
|
||||
getClient = { sessionId ->
|
||||
when (sessionId) {
|
||||
A_USER_ID -> Result.success(client1)
|
||||
else -> error("Unexpected sessionId")
|
||||
}
|
||||
}
|
||||
),
|
||||
)
|
||||
sut.logoutAll(ignoreSdkError = true)
|
||||
logoutLambda1.assertions().isCalledOnce().with(value(true), value(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test logout from several sessions`() = runTest {
|
||||
val logoutLambda1 = lambdaRecorder<Boolean, Boolean, Unit> { _, _ -> }
|
||||
val logoutLambda2 = lambdaRecorder<Boolean, Boolean, Unit> { _, _ -> }
|
||||
val client1 = FakeMatrixClient(A_USER_ID).apply {
|
||||
logoutLambda = logoutLambda1
|
||||
}
|
||||
val client2 = FakeMatrixClient(A_USER_ID_2).apply {
|
||||
logoutLambda = logoutLambda2
|
||||
}
|
||||
val sut = DefaultLogoutUseCase(
|
||||
sessionStore = InMemorySessionStore(
|
||||
initialList = listOf(
|
||||
aSessionData(sessionId = A_USER_ID.value),
|
||||
aSessionData(sessionId = A_USER_ID_2.value),
|
||||
)
|
||||
),
|
||||
matrixClientProvider = FakeMatrixClientProvider(
|
||||
getClient = { sessionId ->
|
||||
when (sessionId) {
|
||||
A_USER_ID -> Result.success(client1)
|
||||
A_USER_ID_2 -> Result.success(client2)
|
||||
else -> error("Unexpected sessionId")
|
||||
}
|
||||
}
|
||||
),
|
||||
)
|
||||
sut.logoutAll(ignoreSdkError = true)
|
||||
logoutLambda1.assertions().isCalledOnce().with(value(true), value(true))
|
||||
logoutLambda2.assertions().isCalledOnce().with(value(true), value(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test logout session not found is ignored`() = runTest {
|
||||
val sut = DefaultLogoutUseCase(
|
||||
sessionStore = InMemorySessionStore(
|
||||
initialList = listOf(
|
||||
aSessionData(sessionId = A_USER_ID.value),
|
||||
)
|
||||
),
|
||||
matrixClientProvider = FakeMatrixClientProvider(
|
||||
getClient = { sessionId ->
|
||||
when (sessionId) {
|
||||
A_USER_ID -> Result.failure(Exception("Session not found"))
|
||||
else -> error("Unexpected sessionId")
|
||||
}
|
||||
}
|
||||
),
|
||||
)
|
||||
sut.logoutAll(ignoreSdkError = true)
|
||||
// No error
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test logout no sessions`() = runTest {
|
||||
val sut = DefaultLogoutUseCase(
|
||||
sessionStore = InMemorySessionStore(
|
||||
initialList = emptyList()
|
||||
),
|
||||
matrixClientProvider = FakeMatrixClientProvider(
|
||||
getClient = { sessionId ->
|
||||
when (sessionId) {
|
||||
else -> error("Unexpected sessionId")
|
||||
}
|
||||
}
|
||||
),
|
||||
)
|
||||
sut.logoutAll(ignoreSdkError = true)
|
||||
// No error
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import io.element.android.tests.testutils.simulateLongTask
|
||||
class FakeLogoutUseCase(
|
||||
var logoutLambda: (Boolean) -> Unit = { lambdaError() }
|
||||
) : LogoutUseCase {
|
||||
override suspend fun logout(ignoreSdkError: Boolean) = simulateLongTask {
|
||||
override suspend fun logoutAll(ignoreSdkError: Boolean) = simulateLongTask {
|
||||
logoutLambda(ignoreSdkError)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user