mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 08:15:02 +00:00
Give up on a verification request the other session never accepts
The outgoing verification flow had no client side timeout: if the other session never answered, the screen waited on "Waiting to accept request" for as long as the user left it open, with no indication that the request could no longer succeed. Bound the wait to the ten minutes the Matrix specification gives a verification request. Past that the other session can no longer accept it, so cancel the request and move to the canceled state, which already offers to start again.
This commit is contained in:
+14
-3
@@ -23,9 +23,11 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
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() }
|
||||
|
||||
+23
-3
@@ -6,8 +6,6 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:Suppress("UnusedImports")
|
||||
|
||||
package io.element.android.features.verifysession.impl.outgoing
|
||||
|
||||
import app.cash.turbine.ReceiveTurbine
|
||||
@@ -30,10 +28,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 {
|
||||
@@ -140,6 +140,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(OutgoingVerificationViewEvents.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(
|
||||
@@ -283,7 +303,7 @@ class OutgoingVerificationPresenterTest {
|
||||
var state = awaitItem()
|
||||
assertThat(state.step).isEqualTo(Step.Initial)
|
||||
state.eventSink(OutgoingVerificationViewEvents.RequestVerification)
|
||||
testScope.advanceUntilIdle()
|
||||
testScope.advanceTimeBy(1.minutes)
|
||||
// Await for other device response:
|
||||
fakeService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest)
|
||||
state = awaitItem()
|
||||
|
||||
Reference in New Issue
Block a user