mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Merge pull request #7338 from element-hq/feature/bma/linkNewDeviceTimeout
[Link new device] Add timeout
This commit is contained in:
+17
-5
@@ -12,10 +12,12 @@ import dev.zacsweers.metro.SingleIn
|
|||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.linknewdevice.ErrorType
|
||||||
import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopHandler
|
import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopHandler
|
||||||
import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopStep
|
import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopStep
|
||||||
import io.element.android.libraries.matrix.api.logs.LoggerTags
|
import io.element.android.libraries.matrix.api.logs.LoggerTags
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -23,6 +25,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
private val loggerTag = LoggerTag("LinkNewDesktopHandler", LoggerTags.linkNewDevice)
|
private val loggerTag = LoggerTag("LinkNewDesktopHandler", LoggerTags.linkNewDevice)
|
||||||
|
|
||||||
@@ -40,25 +43,34 @@ class LinkNewDesktopHandler(
|
|||||||
get() = linkDesktopStepFlow.asStateFlow()
|
get() = linkDesktopStepFlow.asStateFlow()
|
||||||
|
|
||||||
private var currentJob: Job? = null
|
private var currentJob: Job? = null
|
||||||
|
private var timerJob: Job? = null
|
||||||
private var handler: LinkDesktopHandler? = null
|
private var handler: LinkDesktopHandler? = null
|
||||||
|
|
||||||
fun createNewHandler() {
|
private fun resetJobs() {
|
||||||
currentJob?.cancel()
|
currentJob?.cancel()
|
||||||
currentJob = null
|
currentJob = null
|
||||||
|
timerJob?.cancel()
|
||||||
|
timerJob = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNewHandler() {
|
||||||
|
resetJobs()
|
||||||
handler = matrixClient.createLinkDesktopHandler().getOrNull()
|
handler = matrixClient.createLinkDesktopHandler().getOrNull()
|
||||||
|
timerJob = sessionScope.launch {
|
||||||
|
delay(2.minutes)
|
||||||
|
linkDesktopStepFlow.emit(LinkDesktopStep.Error(ErrorType.Expired("Scanning QrCode took too long.")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
currentJob?.cancel()
|
resetJobs()
|
||||||
currentJob = null
|
|
||||||
sessionScope.launch {
|
sessionScope.launch {
|
||||||
linkDesktopStepFlow.emit(LinkDesktopStep.Uninitialized)
|
linkDesktopStepFlow.emit(LinkDesktopStep.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onScannedCode(data: ByteArray) {
|
fun onScannedCode(data: ByteArray) {
|
||||||
currentJob?.cancel()
|
resetJobs()
|
||||||
currentJob = null
|
|
||||||
val currentHandler = handler
|
val currentHandler = handler
|
||||||
if (currentHandler == null) {
|
if (currentHandler == null) {
|
||||||
Timber.tag(loggerTag.value).e("onScannedCode: Handler is not initialized. Call createNewHandler() first.")
|
Timber.tag(loggerTag.value).e("onScannedCode: Handler is not initialized. Call createNewHandler() first.")
|
||||||
|
|||||||
+8
@@ -20,6 +20,13 @@ import io.element.android.libraries.architecture.callback
|
|||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max number of QrCode rotation.
|
||||||
|
* When QrCode is rotated this number of time, a TimeOut error is emitted.
|
||||||
|
* Set to 0 to disable QrCode rotation.
|
||||||
|
*/
|
||||||
|
private const val MAX_QR_CODE_ROTATION = 0
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class ShowQrCodeNode(
|
class ShowQrCodeNode(
|
||||||
@@ -39,6 +46,7 @@ class ShowQrCodeNode(
|
|||||||
private val callback: Callback = callback()
|
private val callback: Callback = callback()
|
||||||
private val showQrCodePresenter: ShowQrCodePresenter = showQrCodePresenterFactory.create(
|
private val showQrCodePresenter: ShowQrCodePresenter = showQrCodePresenterFactory.create(
|
||||||
initialData = inputs.data,
|
initialData = inputs.data,
|
||||||
|
maxQrCodeRotation = MAX_QR_CODE_ROTATION,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+8
-7
@@ -26,24 +26,29 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
private val tag = LoggerTag("ShowQrCodePresenter", LoggerTags.linkNewDevice)
|
private val tag = LoggerTag("ShowQrCodePresenter", LoggerTags.linkNewDevice)
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class ShowQrCodePresenter(
|
class ShowQrCodePresenter(
|
||||||
@Assisted private val initialData: String,
|
@Assisted private val initialData: String,
|
||||||
|
@Assisted private val maxQrCodeRotation: Int,
|
||||||
private val linkNewMobileHandler: LinkNewMobileHandler,
|
private val linkNewMobileHandler: LinkNewMobileHandler,
|
||||||
) : Presenter<ShowQrCodeState> {
|
) : Presenter<ShowQrCodeState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(initialData: String): ShowQrCodePresenter
|
fun create(
|
||||||
|
initialData: String,
|
||||||
|
maxQrCodeRotation: Int,
|
||||||
|
): ShowQrCodePresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
private var loadingJob: Job? = null
|
private var loadingJob: Job? = null
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ShowQrCodeState {
|
override fun present(): ShowQrCodeState {
|
||||||
var qrCodeRotationCounter by remember { mutableIntStateOf(MAX_QR_CODE_ROTATION) }
|
var qrCodeRotationCounter by remember { mutableIntStateOf(maxQrCodeRotation) }
|
||||||
val state by produceState(
|
val state by produceState(
|
||||||
initialValue = ShowQrCodeState(
|
initialValue = ShowQrCodeState(
|
||||||
data = AsyncData.Success(initialData),
|
data = AsyncData.Success(initialData),
|
||||||
@@ -63,7 +68,7 @@ class ShowQrCodePresenter(
|
|||||||
linkNewMobileHandler.rotateQrCode()
|
linkNewMobileHandler.rotateQrCode()
|
||||||
// Ensure that outdated data is not rendered too long while rotating QR code
|
// Ensure that outdated data is not rendered too long while rotating QR code
|
||||||
loadingJob = launch {
|
loadingJob = launch {
|
||||||
delay(1000)
|
delay(1.seconds)
|
||||||
value = ShowQrCodeState(
|
value = ShowQrCodeState(
|
||||||
data = AsyncData.Loading(),
|
data = AsyncData.Loading(),
|
||||||
)
|
)
|
||||||
@@ -80,8 +85,4 @@ class ShowQrCodePresenter(
|
|||||||
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val MAX_QR_CODE_ROTATION = 10
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+26
@@ -91,10 +91,36 @@ class ShowQrCodePresenterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - when handler emits QrRotating, the presenter does not emit anything when rotation is disabled`() = runTest {
|
||||||
|
val linkMobileHandler = FakeLinkMobileHandler(
|
||||||
|
startResult = {},
|
||||||
|
)
|
||||||
|
val matrixClient = FakeMatrixClient(
|
||||||
|
sessionCoroutineScope = backgroundScope,
|
||||||
|
createLinkMobileHandlerResult = { Result.success(linkMobileHandler) },
|
||||||
|
)
|
||||||
|
val linkNewMobileHandler = LinkNewMobileHandler(matrixClient)
|
||||||
|
linkNewMobileHandler.createAndStartNewHandler()
|
||||||
|
createPresenter(
|
||||||
|
linkNewMobileHandler = linkNewMobileHandler,
|
||||||
|
maxQrCodeRotation = 0,
|
||||||
|
).test {
|
||||||
|
awaitItem()
|
||||||
|
linkMobileHandler.emitStep(
|
||||||
|
LinkMobileStep.QrRotating
|
||||||
|
)
|
||||||
|
runCurrent()
|
||||||
|
expectNoEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createPresenter(
|
private fun createPresenter(
|
||||||
linkNewMobileHandler: LinkNewMobileHandler = LinkNewMobileHandler(FakeMatrixClient()),
|
linkNewMobileHandler: LinkNewMobileHandler = LinkNewMobileHandler(FakeMatrixClient()),
|
||||||
|
maxQrCodeRotation: Int = 10,
|
||||||
) = ShowQrCodePresenter(
|
) = ShowQrCodePresenter(
|
||||||
initialData = "DATA",
|
initialData = "DATA",
|
||||||
|
maxQrCodeRotation = maxQrCodeRotation,
|
||||||
linkNewMobileHandler = linkNewMobileHandler,
|
linkNewMobileHandler = linkNewMobileHandler,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -59,7 +59,7 @@ class DefaultBiometricAuthentication(
|
|||||||
private var cryptoObject: CryptoObject? = null
|
private var cryptoObject: CryptoObject? = null
|
||||||
|
|
||||||
override suspend fun setup() {
|
override suspend fun setup() {
|
||||||
if (cryptoObject != null) return
|
Timber.d("Setup biometric authentication")
|
||||||
try {
|
try {
|
||||||
val secretKey = ensureKey()
|
val secretKey = ensureKey()
|
||||||
val cipher = encryptionDecryptionService.createEncryptionCipher(secretKey)
|
val cipher = encryptionDecryptionService.createEncryptionCipher(secretKey)
|
||||||
@@ -126,6 +126,9 @@ private class AuthenticationCallback(
|
|||||||
if (this == null) return false
|
if (this == null) return false
|
||||||
return runCatchingExceptions {
|
return runCatchingExceptions {
|
||||||
doFinal("biometric_challenge".toByteArray())
|
doFinal("biometric_challenge".toByteArray())
|
||||||
}.isSuccess
|
}.onFailure {
|
||||||
|
Timber.e(it, "Failed to validate cipher")
|
||||||
|
}
|
||||||
|
.isSuccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user