mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 08:15:02 +00:00
Update Matrix Rust SDK to 26.10.07-nightly (#7880)
* Update Matrix Rust SDK to 26.10.07-nightly * Adapt to the new QR code login task API `generate()` and `scan()` of `GrantLoginWithQrCodeHandler` and `LoginWithQrCodeHandler` now return a task instead of suspending until the flow is over. Wait for the task to complete, and cancel it if the coroutine is cancelled, so that the flow is also stopped on the Rust side. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Add tests for the cancellation of the QR code login tasks Check that cancelling the coroutine of the link new device handlers and of the QR code login cancels the task returned by the SDK, so that the flow is stopped on the Rust side. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Fix import ordering in the link new device handler tests ktlint expects the imports to be sorted in lexicographic order. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Benoit Marty <benoit@matrix.org> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -186,7 +186,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version
|
||||
# it as a plain `require`: without this, a library built against a newer SDK would silently drag this app
|
||||
# onto it. With it, this pin always wins, and a library that needs something newer fails at configuration
|
||||
# time naming both versions instead of at runtime with NoSuchMethodError.
|
||||
matrix_sdk = { module = "org.matrix.rustcomponents:sdk-android", version = { strictly = "26.10.05-nightly" } }
|
||||
matrix_sdk = { module = "org.matrix.rustcomponents:sdk-android", version = { strictly = "26.10.07-nightly" } }
|
||||
rustls_platform_verifier = "org.rustls:rustls-platform-verifier:0.2.0"
|
||||
|
||||
# Others
|
||||
|
||||
+8
-1
@@ -345,7 +345,14 @@ class RustMatrixAuthenticationService(
|
||||
it.scan(
|
||||
qrCodeData = qrCodeData.rustQrCodeData,
|
||||
progressListener = progressListener,
|
||||
)
|
||||
).use { task ->
|
||||
try {
|
||||
task.wait()
|
||||
} catch (e: CancellationException) {
|
||||
task.cancel()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure that the user is not already logged in with the same account
|
||||
ensureNotAlreadyLoggedIn(client)
|
||||
|
||||
+10
-2
@@ -12,6 +12,7 @@ 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.logs.LoggerTags
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -53,8 +54,15 @@ class RustLinkDesktopHandler(
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// We emit Done in case the progress listener was deallocated before scan() sent the Done
|
||||
).use { task ->
|
||||
try {
|
||||
task.wait()
|
||||
} catch (e: CancellationException) {
|
||||
task.cancel()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
// We emit Done in case the progress listener was deallocated before the task sent the Done
|
||||
_linkDesktopStep.emit(LinkDesktopStep.Done)
|
||||
} catch (e: QrCodeDecodeException) {
|
||||
Timber.tag(tag.value).w(e, "Invalid QR code scanned")
|
||||
|
||||
+10
-2
@@ -11,6 +11,7 @@ import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileHandler
|
||||
import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep
|
||||
import io.element.android.libraries.matrix.api.logs.LoggerTags
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -48,8 +49,15 @@ class RustLinkMobileHandler(
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// We emit Done in case the progress listener was deallocated before generate() sent the Done
|
||||
).use { task ->
|
||||
try {
|
||||
task.wait()
|
||||
} catch (e: CancellationException) {
|
||||
task.cancel()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
// We emit Done in case the progress listener was deallocated before the task sent the Done
|
||||
_linkMobileStep.emit(LinkMobileStep.Done)
|
||||
} catch (e: HumanQrGrantLoginException.Expired) {
|
||||
// Note: the SDK does not return this error, but is returning `.NotFound` instead when the QR code expires. We catch both just in case.
|
||||
|
||||
+39
@@ -6,6 +6,8 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.libraries.matrix.impl.auth
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -36,7 +38,11 @@ import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -171,6 +177,39 @@ class RustMatrixAuthenticationServiceTest {
|
||||
closeResult.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loginWithQrCode cancels the login task when it is cancelled`() = runTest {
|
||||
val cancelResult = lambdaRecorder<Unit> {}
|
||||
val sut = createRustMatrixAuthenticationService(
|
||||
clientBuilderProvider = FakeClientBuilderProvider(
|
||||
provideResult = {
|
||||
FakeFfiClientBuilder(
|
||||
buildResult = {
|
||||
FakeFfiClient(
|
||||
newLoginWithQrCodeHandlerResult = {
|
||||
FakeFfiLoginWithQrCodeHandler(
|
||||
scanResult = { awaitCancellation() },
|
||||
cancelResult = cancelResult,
|
||||
)
|
||||
},
|
||||
closeResult = {},
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
val job = backgroundScope.launch {
|
||||
sut.loginWithQrCode(aSdkQrCodeLoginData()) {}
|
||||
}
|
||||
runCurrent()
|
||||
cancelResult.assertions().isNeverCalled()
|
||||
job.cancel()
|
||||
runCurrent()
|
||||
cancelResult.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
private fun aSdkQrCodeLoginData() = SdkQrCodeLoginData(
|
||||
FakeFfiQrCodeData(
|
||||
baseUrlResult = { A_HOMESERVER_URL },
|
||||
|
||||
+12
-4
@@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||
import org.matrix.rustcomponents.sdk.GrantGeneratedQrLoginProgress
|
||||
import org.matrix.rustcomponents.sdk.GrantGeneratedQrLoginProgressListener
|
||||
import org.matrix.rustcomponents.sdk.GrantLoginWithQrCodeHandler
|
||||
import org.matrix.rustcomponents.sdk.GrantLoginWithQrCodeTask
|
||||
import org.matrix.rustcomponents.sdk.GrantQrLoginProgress
|
||||
import org.matrix.rustcomponents.sdk.GrantQrLoginProgressListener
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
@@ -18,21 +19,28 @@ import org.matrix.rustcomponents.sdk.QrCodeData
|
||||
class FakeFfiGrantLoginWithQrCodeHandler(
|
||||
private val generateResult: suspend () -> Unit = {},
|
||||
private val scanResult: suspend (QrCodeData) -> Unit = {},
|
||||
private val cancelResult: () -> Unit = {},
|
||||
) : GrantLoginWithQrCodeHandler(NoHandle) {
|
||||
private var generateProgressListener: GrantGeneratedQrLoginProgressListener? = null
|
||||
private var scanProgressListener: GrantQrLoginProgressListener? = null
|
||||
override suspend fun generate(progressListener: GrantGeneratedQrLoginProgressListener) {
|
||||
override fun generate(progressListener: GrantGeneratedQrLoginProgressListener): GrantLoginWithQrCodeTask {
|
||||
generateProgressListener = progressListener
|
||||
generateResult()
|
||||
return FakeFfiGrantLoginWithQrCodeTask(
|
||||
waitResult = generateResult,
|
||||
cancelResult = cancelResult,
|
||||
)
|
||||
}
|
||||
|
||||
fun emitGenerateProgress(progress: GrantGeneratedQrLoginProgress) {
|
||||
generateProgressListener?.onUpdate(progress)
|
||||
}
|
||||
|
||||
override suspend fun scan(qrCodeData: QrCodeData, progressListener: GrantQrLoginProgressListener) {
|
||||
override fun scan(qrCodeData: QrCodeData, progressListener: GrantQrLoginProgressListener): GrantLoginWithQrCodeTask {
|
||||
scanProgressListener = progressListener
|
||||
scanResult(qrCodeData)
|
||||
return FakeFfiGrantLoginWithQrCodeTask(
|
||||
waitResult = { scanResult(qrCodeData) },
|
||||
cancelResult = cancelResult,
|
||||
)
|
||||
}
|
||||
|
||||
fun emitScanProgress(progress: GrantQrLoginProgress) {
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||
|
||||
import org.matrix.rustcomponents.sdk.GrantLoginWithQrCodeTask
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
|
||||
class FakeFfiGrantLoginWithQrCodeTask(
|
||||
private val waitResult: suspend () -> Unit = {},
|
||||
private val cancelResult: () -> Unit = {},
|
||||
) : GrantLoginWithQrCodeTask(NoHandle) {
|
||||
override suspend fun wait() = waitResult()
|
||||
override fun cancel() = cancelResult()
|
||||
override fun close() = Unit
|
||||
}
|
||||
+12
-4
@@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||
|
||||
import org.matrix.rustcomponents.sdk.GeneratedQrLoginProgressListener
|
||||
import org.matrix.rustcomponents.sdk.LoginWithQrCodeHandler
|
||||
import org.matrix.rustcomponents.sdk.LoginWithQrCodeTask
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
import org.matrix.rustcomponents.sdk.QrCodeData
|
||||
import org.matrix.rustcomponents.sdk.QrLoginProgress
|
||||
@@ -17,16 +18,23 @@ import org.matrix.rustcomponents.sdk.QrLoginProgressListener
|
||||
class FakeFfiLoginWithQrCodeHandler(
|
||||
private val generateResult: suspend () -> Unit = {},
|
||||
private val scanResult: suspend (QrCodeData) -> Unit = {},
|
||||
private val cancelResult: () -> Unit = {},
|
||||
) : LoginWithQrCodeHandler(NoHandle) {
|
||||
private var scanProgressListener: QrLoginProgressListener? = null
|
||||
|
||||
override suspend fun generate(progressListener: GeneratedQrLoginProgressListener) {
|
||||
generateResult()
|
||||
override fun generate(progressListener: GeneratedQrLoginProgressListener): LoginWithQrCodeTask {
|
||||
return FakeFfiLoginWithQrCodeTask(
|
||||
waitResult = generateResult,
|
||||
cancelResult = cancelResult,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun scan(qrCodeData: QrCodeData, progressListener: QrLoginProgressListener) {
|
||||
override fun scan(qrCodeData: QrCodeData, progressListener: QrLoginProgressListener): LoginWithQrCodeTask {
|
||||
scanProgressListener = progressListener
|
||||
scanResult(qrCodeData)
|
||||
return FakeFfiLoginWithQrCodeTask(
|
||||
waitResult = { scanResult(qrCodeData) },
|
||||
cancelResult = cancelResult,
|
||||
)
|
||||
}
|
||||
|
||||
fun emitScanProgress(progress: QrLoginProgress) {
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||
|
||||
import org.matrix.rustcomponents.sdk.LoginWithQrCodeTask
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
|
||||
class FakeFfiLoginWithQrCodeTask(
|
||||
private val waitResult: suspend () -> Unit = {},
|
||||
private val cancelResult: () -> Unit = {},
|
||||
) : LoginWithQrCodeTask(NoHandle) {
|
||||
override suspend fun wait() = waitResult()
|
||||
override fun cancel() = cancelResult()
|
||||
override fun close() = Unit
|
||||
}
|
||||
+22
@@ -17,9 +17,11 @@ import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiContinuati
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiGrantLoginWithQrCodeHandler
|
||||
import io.element.android.libraries.matrix.test.QR_CODE_DATA
|
||||
import io.element.android.tests.testutils.ExpectedResult
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.match
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
@@ -140,6 +142,26 @@ class RustLinkDesktopHandlerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when handleScannedQrCode is cancelled, the task is cancelled`() = runTest {
|
||||
val cancelResult = lambdaRecorder<Unit> { }
|
||||
val handler = FakeFfiGrantLoginWithQrCodeHandler(
|
||||
scanResult = { awaitCancellation() },
|
||||
cancelResult = cancelResult,
|
||||
)
|
||||
val sut = createRustLinkDesktopHandler(
|
||||
handler,
|
||||
)
|
||||
val job = backgroundScope.launch {
|
||||
sut.handleScannedQrCode(QR_CODE_DATA)
|
||||
}
|
||||
runCurrent()
|
||||
cancelResult.assertions().isNeverCalled()
|
||||
job.cancel()
|
||||
runCurrent()
|
||||
cancelResult.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
private fun TestScope.createRustLinkDesktopHandler(
|
||||
handler: FakeFfiGrantLoginWithQrCodeHandler = FakeFfiGrantLoginWithQrCodeHandler(),
|
||||
) = RustLinkDesktopHandler(
|
||||
|
||||
+22
@@ -19,9 +19,11 @@ import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiGrantLogin
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiQrCodeData
|
||||
import io.element.android.libraries.matrix.test.QR_CODE_DATA_RECIPROCATE
|
||||
import io.element.android.tests.testutils.ExpectedResult
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.match
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
@@ -176,6 +178,26 @@ class RustLinkMobileHandlerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when start is cancelled, the task is cancelled`() = runTest {
|
||||
val cancelResult = lambdaRecorder<Unit> { }
|
||||
val handler = FakeFfiGrantLoginWithQrCodeHandler(
|
||||
generateResult = { awaitCancellation() },
|
||||
cancelResult = cancelResult,
|
||||
)
|
||||
val sut = createRustLinkMobileHandler(
|
||||
handler,
|
||||
)
|
||||
val job = backgroundScope.launch {
|
||||
sut.start()
|
||||
}
|
||||
runCurrent()
|
||||
cancelResult.assertions().isNeverCalled()
|
||||
job.cancel()
|
||||
runCurrent()
|
||||
cancelResult.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
private fun TestScope.createRustLinkMobileHandler(
|
||||
handler: FakeFfiGrantLoginWithQrCodeHandler = FakeFfiGrantLoginWithQrCodeHandler(),
|
||||
) = RustLinkMobileHandler(
|
||||
|
||||
Reference in New Issue
Block a user