Merge pull request #7457 from hayaksi1/fix/1270-verification-request-timeout

Stop waiting on a verification request the other session never accepts
This commit is contained in:
Benoit Marty
2026-08-25 14:22:39 +02:00
committed by GitHub
2 changed files with 37 additions and 4 deletions
@@ -23,9 +23,11 @@ import io.element.android.libraries.matrix.api.verification.VerificationRequest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.timeout
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
import com.freeletics.flowredux.dsl.State as MachineState
@@ -36,6 +38,11 @@ class OutgoingVerificationStateMachine(
) : FlowReduxStateMachine<OutgoingVerificationStateMachine.State, OutgoingVerificationStateMachine.Event>(
initialState = State.Initial,
) {
companion object {
/** Lifetime the Matrix specification gives a verification request; past it the other session can no longer accept ours. */
private val VERIFICATION_REQUEST_TIMEOUT = 10.minutes
}
init {
spec {
inState<State.Initial> {
@@ -44,11 +51,15 @@ class OutgoingVerificationStateMachine(
}
}
inState<State.RequestingVerification> {
onEnterEffect { event ->
when (event.verificationRequest) {
onEnter { state ->
val verificationRequest = state.snapshot.verificationRequest
when (verificationRequest) {
is VerificationRequest.Outgoing.CurrentSession -> sessionVerificationService.requestDeviceVerification()
is VerificationRequest.Outgoing.User -> sessionVerificationService.requestUserVerification(event.verificationRequest.userId)
is VerificationRequest.Outgoing.User -> sessionVerificationService.requestUserVerification(verificationRequest.userId)
}
delay(VERIFICATION_REQUEST_TIMEOUT)
sessionVerificationService.cancelVerification()
state.override { State.Canceled.andLogStateChange() }
}
on<Event.DidAcceptVerificationRequest> { _, state ->
state.override { State.VerificationRequestAccepted.andLogStateChange() }
@@ -31,10 +31,12 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import kotlin.time.Duration.Companion.minutes
@ExperimentalCoroutinesApi
class OutgoingVerificationPresenterTest {
@@ -141,6 +143,26 @@ class OutgoingVerificationPresenterTest {
}
}
@Test
fun `present - A verification request the other session never accepts is canceled`() = runTest {
val cancelVerificationRecorder = lambdaRecorder<Unit> {}
val service = unverifiedSessionService(
requestDeviceVerificationLambda = { },
cancelVerificationLambda = cancelVerificationRecorder,
)
val presenter = createOutgoingVerificationPresenter(service)
presenter.test {
awaitItem().eventSink(OutgoingVerificationViewEvent.RequestVerification)
advanceTimeBy(1.minutes)
assertThat(awaitItem().step).isEqualTo(Step.AwaitingOtherDeviceResponse)
advanceTimeBy(9.minutes)
expectNoEvents()
advanceTimeBy(2.minutes)
assertThat(awaitItem().step).isEqualTo(Step.Canceled)
cancelVerificationRecorder.assertions().isCalledOnce()
}
}
@Test
fun `present - Canceling the flow once it's verifying cancels it`() = runTest {
val service = unverifiedSessionService(
@@ -363,7 +385,7 @@ class OutgoingVerificationPresenterTest {
var state = awaitItem()
assertThat(state.step).isEqualTo(Step.Initial)
state.eventSink(OutgoingVerificationViewEvent.RequestVerification)
testScope.advanceUntilIdle()
testScope.advanceTimeBy(1.minutes)
// Await for other device response:
fakeService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest)
state = awaitItem()