Merge remote-tracking branch 'origin/develop' into feature/bma/mutliAccountNotification

This commit is contained in:
Benoit Marty 2025-11-04 16:20:42 +01:00
commit e96cd9e28f
421 changed files with 4365 additions and 4190 deletions

View file

@ -0,0 +1,19 @@
/*
* 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.
*/
package io.element.android.libraries.matrix.api.auth
/**
* Checks the homeserver's compatibility with Element X.
*/
interface HomeServerLoginCompatibilityChecker {
/**
* Performs the compatibility check given the homeserver's [url].
* @return a `true` value if the homeserver is compatible, `false` if not, or a failure result if the check unexpectedly failed.
*/
suspend fun check(url: String): Result<Boolean>
}

View file

@ -33,6 +33,7 @@ import org.matrix.rustcomponents.sdk.RequestConfig
import org.matrix.rustcomponents.sdk.Session
import org.matrix.rustcomponents.sdk.SlidingSyncVersion
import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder
import org.matrix.rustcomponents.sdk.SqliteStoreBuilder
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import uniffi.matrix_sdk_crypto.CollectStrategy
@ -105,12 +106,13 @@ class RustMatrixClientFactory(
slidingSyncType: ClientBuilderSlidingSync,
): ClientBuilder {
return clientBuilderProvider.provide()
.sessionPaths(
dataPath = sessionPaths.fileDirectory.absolutePath,
cachePath = sessionPaths.cacheDirectory.absolutePath,
.sqliteStore(
SqliteStoreBuilder(
dataPath = sessionPaths.fileDirectory.absolutePath,
cachePath = sessionPaths.cacheDirectory.absolutePath,
).passphrase(passphrase)
)
.setSessionDelegate(sessionDelegate)
.sessionPassphrase(passphrase)
.userAgent(userAgentProvider.provide())
.addRootCertificates(userCertificatesProvider.provides())
.autoEnableBackups(true)

View file

@ -0,0 +1,36 @@
/*
* 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.
*/
package io.element.android.libraries.matrix.impl.auth
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
import io.element.android.libraries.matrix.impl.ClientBuilderProvider
import timber.log.Timber
@ContributesBinding(AppScope::class)
@Inject
class RustHomeServerLoginCompatibilityChecker(
private val clientBuilderProvider: ClientBuilderProvider,
) : HomeServerLoginCompatibilityChecker {
override suspend fun check(url: String): Result<Boolean> = runCatchingExceptions {
clientBuilderProvider.provide()
.inMemoryStore()
.serverNameOrHomeserverUrl(url)
.build()
.use {
it.homeserverLoginDetails()
}
.use {
Timber.d("Homeserver $url | OIDC: ${it.supportsOidcLogin()} | Password: ${it.supportsPasswordLogin()} | SSO: ${it.supportsSsoLogin()}")
it.supportsOidcLogin() || it.supportsPasswordLogin()
}
}
}

View file

@ -285,7 +285,6 @@ class RustMatrixAuthenticationService(
runCatchingExceptions {
val client = makeQrCodeLoginClient(
sessionPaths = emptySessionPaths,
passphrase = pendingPassphrase,
qrCodeData = sdkQrCodeLoginData,
)
client.loginWithQrCode(
@ -344,7 +343,6 @@ class RustMatrixAuthenticationService(
private suspend fun makeQrCodeLoginClient(
sessionPaths: SessionPaths,
passphrase: String?,
qrCodeData: QrCodeData,
): Client {
Timber.d("Creating client for QR Code login with simplified sliding sync")
@ -354,7 +352,6 @@ class RustMatrixAuthenticationService(
passphrase = pendingPassphrase,
slidingSyncType = ClientBuilderSlidingSync.Discovered,
)
.sessionPassphrase(passphrase)
.serverNameOrHomeserverUrl(qrCodeData.serverName()!!)
.build()
}

View file

@ -39,6 +39,7 @@ class NotificationMapper(
isDirect = item.roomInfo.isDirect,
activeMembersCount = item.roomInfo.joinedMembersCount.toInt(),
)
val timestamp = item.timestamp() ?: clock.epochMillis()
NotificationData(
sessionId = sessionId,
eventId = eventId,
@ -53,8 +54,8 @@ class NotificationMapper(
isDm = isDm,
isEncrypted = item.roomInfo.isEncrypted.orFalse(),
isNoisy = item.isNoisy.orFalse(),
timestamp = item.timestamp() ?: clock.epochMillis(),
content = item.event.use { notificationContentMapper.map(it) }.getOrThrow(),
timestamp = timestamp,
content = notificationContentMapper.map(item.event).getOrThrow(),
hasMention = item.hasMention.orFalse(),
)
}

View file

@ -25,8 +25,9 @@ class TimelineEventToNotificationContentMapper {
fun map(timelineEvent: TimelineEvent): Result<NotificationContent> {
return runCatchingExceptions {
timelineEvent.use {
val senderId = UserId(timelineEvent.senderId())
timelineEvent.eventType().use { eventType ->
eventType.toContent(senderId = UserId(timelineEvent.senderId()))
eventType.toContent(senderId = senderId)
}
}
}

View file

@ -0,0 +1,54 @@
/*
* 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.
*/
package io.element.android.libraries.matrix.impl.auth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.impl.FakeClientBuilderProvider
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClientBuilder
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiHomeserverLoginDetails
import kotlinx.coroutines.test.runTest
import org.junit.Ignore
import org.junit.Test
@Ignore("JNA direct mapping has broken unit tests with FFI fakes")
class RustHomeserverLoginCompatibilityCheckerTest {
@Test
fun `check - is valid if it supports OIDC login`() = runTest {
val sut = createChecker { FakeFfiHomeserverLoginDetails(supportsOidcLogin = true) }
assertThat(sut.check("https://matrix.host.org").getOrNull()).isTrue()
}
@Test
fun `check - is valid if it supports password login`() = runTest {
val sut = createChecker { FakeFfiHomeserverLoginDetails(supportsPasswordLogin = true) }
assertThat(sut.check("https://matrix.host.org").getOrNull()).isTrue()
}
@Test
fun `check - is not valid if it only supports SSO login`() = runTest {
val sut = createChecker { FakeFfiHomeserverLoginDetails(supportsSsoLogin = true) }
assertThat(sut.check("https://matrix.host.org").getOrNull()).isFalse()
}
@Test
fun `check - is not valid if fetching the data fails`() = runTest {
val sut = createChecker { error("Unexpected error!") }
assertThat(sut.check("https://matrix.host.org").isFailure).isTrue()
}
private fun createChecker(
result: () -> FakeFfiHomeserverLoginDetails,
) = RustHomeServerLoginCompatibilityChecker(
clientBuilderProvider = FakeClientBuilderProvider {
FakeFfiClientBuilder {
FakeFfiClient(homeserverLoginDetailsResult = result)
}
}
)
}

View file

@ -13,6 +13,7 @@ import org.matrix.rustcomponents.sdk.ClientSessionDelegate
import org.matrix.rustcomponents.sdk.NoHandle
import org.matrix.rustcomponents.sdk.RequestConfig
import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder
import org.matrix.rustcomponents.sdk.SqliteStoreBuilder
import uniffi.matrix_sdk.BackupDownloadStrategy
import uniffi.matrix_sdk_crypto.CollectStrategy
import uniffi.matrix_sdk_crypto.DecryptionSettings
@ -29,7 +30,6 @@ class FakeFfiClientBuilder(
override fun decryptionSettings(decryptionSettings: DecryptionSettings): ClientBuilder = this
override fun disableSslVerification() = this
override fun homeserverUrl(url: String) = this
override fun sessionPassphrase(passphrase: String?) = this
override fun proxy(url: String) = this
override fun requestConfig(config: RequestConfig) = this
override fun roomKeyRecipientStrategy(strategy: CollectStrategy) = this
@ -42,5 +42,6 @@ class FakeFfiClientBuilder(
override fun username(username: String) = this
override fun enableShareHistoryOnInvite(enableShareHistoryOnInvite: Boolean): ClientBuilder = this
override fun threadsEnabled(enabled: Boolean, threadSubscriptions: Boolean): ClientBuilder = this
override fun sqliteStore(config: SqliteStoreBuilder): ClientBuilder = this
override suspend fun build() = buildResult()
}

View file

@ -12,10 +12,12 @@ import org.matrix.rustcomponents.sdk.NoHandle
class FakeFfiHomeserverLoginDetails(
private val url: String = "https://example.org",
private val supportsPasswordLogin: Boolean = true,
private val supportsOidcLogin: Boolean = false
private val supportsPasswordLogin: Boolean = false,
private val supportsOidcLogin: Boolean = false,
private val supportsSsoLogin: Boolean = false,
) : HomeserverLoginDetails(NoHandle) {
override fun url(): String = url
override fun supportsOidcLogin(): Boolean = supportsOidcLogin
override fun supportsPasswordLogin(): Boolean = supportsPasswordLogin
override fun supportsSsoLogin(): Boolean = supportsSsoLogin
}

View file

@ -0,0 +1,18 @@
/*
* 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.
*/
package io.element.android.libraries.matrix.test.auth
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
class FakeHomeServerLoginCompatibilityChecker(
private val checkResult: (String) -> Result<Boolean>,
) : HomeServerLoginCompatibilityChecker {
override suspend fun check(url: String): Result<Boolean> {
return checkResult(url)
}
}