Merge branch 'develop' into feature/valere/message_shields
This commit is contained in:
@@ -23,10 +23,12 @@ import io.element.android.libraries.matrix.impl.certificates.UserCertificatesPro
|
||||
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
|
||||
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
|
||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.ClientBuilder
|
||||
import org.matrix.rustcomponents.sdk.Session
|
||||
@@ -46,9 +48,18 @@ class RustMatrixClientFactory @Inject constructor(
|
||||
private val proxyProvider: ProxyProvider,
|
||||
private val clock: SystemClock,
|
||||
private val utdTracker: UtdTracker,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) {
|
||||
suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) {
|
||||
val client = getBaseClientBuilder(sessionData.sessionPath, sessionData.passphrase)
|
||||
val client = getBaseClientBuilder(
|
||||
sessionPath = sessionData.sessionPath,
|
||||
passphrase = sessionData.passphrase,
|
||||
slidingSync = if (appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first()) {
|
||||
ClientBuilderSlidingSync.Simplified
|
||||
} else {
|
||||
ClientBuilderSlidingSync.Restored
|
||||
},
|
||||
)
|
||||
.homeserverUrl(sessionData.homeserverUrl)
|
||||
.username(sessionData.userId)
|
||||
.use { it.build() }
|
||||
@@ -79,6 +90,7 @@ class RustMatrixClientFactory @Inject constructor(
|
||||
sessionPath: String,
|
||||
passphrase: String?,
|
||||
slidingSyncProxy: String? = null,
|
||||
slidingSync: ClientBuilderSlidingSync,
|
||||
): ClientBuilder {
|
||||
return ClientBuilder()
|
||||
.sessionPath(sessionPath)
|
||||
@@ -88,6 +100,13 @@ class RustMatrixClientFactory @Inject constructor(
|
||||
.addRootCertificates(userCertificatesProvider.provides())
|
||||
.autoEnableBackups(true)
|
||||
.autoEnableCrossSigning(true)
|
||||
.run {
|
||||
when (slidingSync) {
|
||||
ClientBuilderSlidingSync.Restored -> this
|
||||
ClientBuilderSlidingSync.Discovered -> requiresSlidingSync()
|
||||
ClientBuilderSlidingSync.Simplified -> simplifiedSlidingSync(true)
|
||||
}
|
||||
}
|
||||
.run {
|
||||
// Workaround for non-nullable proxy parameter in the SDK, since each call to the ClientBuilder returns a new reference we need to keep
|
||||
proxyProvider.provides()?.let { proxy(it) } ?: this
|
||||
@@ -95,6 +114,17 @@ class RustMatrixClientFactory @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
enum class ClientBuilderSlidingSync {
|
||||
// The proxy will be supplied when restoring the Session.
|
||||
Restored,
|
||||
|
||||
// A proxy must be discovered whilst building the session.
|
||||
Discovered,
|
||||
|
||||
// Use Simplified Sliding Sync (discovery isn't a thing yet).
|
||||
Simplified,
|
||||
}
|
||||
|
||||
private fun SessionData.toSession() = Session(
|
||||
accessToken = accessToken,
|
||||
refreshToken = refreshToken,
|
||||
|
||||
@@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync
|
||||
import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData
|
||||
@@ -59,7 +60,7 @@ import javax.inject.Inject
|
||||
@ContributesBinding(AppScope::class)
|
||||
@SingleIn(AppScope::class)
|
||||
class RustMatrixAuthenticationService @Inject constructor(
|
||||
baseDirectory: File,
|
||||
private val baseDirectory: File,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
private val sessionStore: SessionStore,
|
||||
private val rustMatrixClientFactory: RustMatrixClientFactory,
|
||||
@@ -69,10 +70,19 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
// Passphrase which will be used for new sessions. Existing sessions will use the passphrase
|
||||
// stored in the SessionData.
|
||||
private val pendingPassphrase = getDatabasePassphrase()
|
||||
private val sessionPath = File(baseDirectory, UUID.randomUUID().toString()).absolutePath
|
||||
|
||||
// Need to keep a copy of the current session path to eventually delete it.
|
||||
// Ideally it would be possible to get the sessionPath from the Client to avoid doing this.
|
||||
private var sessionPath: File? = null
|
||||
private var currentClient: Client? = null
|
||||
private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
|
||||
|
||||
private fun rotateSessionPath(): File {
|
||||
sessionPath?.deleteRecursively()
|
||||
return File(baseDirectory, UUID.randomUUID().toString())
|
||||
.also { sessionPath = it }
|
||||
}
|
||||
|
||||
override fun loggedInStateFlow(): Flow<LoggedInState> {
|
||||
return sessionStore.isLoggedIn()
|
||||
}
|
||||
@@ -116,8 +126,9 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
|
||||
override suspend fun setHomeserver(homeserver: String): Result<Unit> =
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val emptySessionPath = rotateSessionPath()
|
||||
runCatching {
|
||||
val client = getBaseClientBuilder()
|
||||
val client = getBaseClientBuilder(emptySessionPath)
|
||||
.serverNameOrHomeserverUrl(homeserver)
|
||||
.build()
|
||||
currentClient = client
|
||||
@@ -134,13 +145,14 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
val client = currentClient ?: error("You need to call `setHomeserver()` first")
|
||||
val currentSessionPath = sessionPath ?: error("You need to call `setHomeserver()` first")
|
||||
client.login(username, password, "Element X Android", null)
|
||||
val sessionData = client.session()
|
||||
.toSessionData(
|
||||
isTokenValid = true,
|
||||
loginType = LoginType.PASSWORD,
|
||||
passphrase = pendingPassphrase,
|
||||
sessionPath = sessionPath,
|
||||
sessionPath = currentSessionPath.absolutePath,
|
||||
)
|
||||
clear()
|
||||
sessionStore.storeData(sessionData)
|
||||
@@ -184,13 +196,14 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
val client = currentClient ?: error("You need to call `setHomeserver()` first")
|
||||
val currentSessionPath = sessionPath ?: error("You need to call `setHomeserver()` first")
|
||||
val urlForOidcLogin = pendingOidcAuthorizationData ?: error("You need to call `getOidcUrl()` first")
|
||||
client.loginWithOidcCallback(urlForOidcLogin, callbackUrl)
|
||||
val sessionData = client.session().toSessionData(
|
||||
isTokenValid = true,
|
||||
loginType = LoginType.OIDC,
|
||||
passphrase = pendingPassphrase,
|
||||
sessionPath = sessionPath,
|
||||
sessionPath = currentSessionPath.absolutePath,
|
||||
)
|
||||
clear()
|
||||
pendingOidcAuthorizationData?.close()
|
||||
@@ -205,11 +218,13 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
|
||||
override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) =
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val emptySessionPath = rotateSessionPath()
|
||||
runCatching {
|
||||
val client = rustMatrixClientFactory.getBaseClientBuilder(
|
||||
sessionPath = sessionPath,
|
||||
sessionPath = emptySessionPath.absolutePath,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
|
||||
slidingSync = ClientBuilderSlidingSync.Discovered,
|
||||
)
|
||||
.buildWithQrCode(
|
||||
qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData,
|
||||
@@ -227,7 +242,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
isTokenValid = true,
|
||||
loginType = LoginType.QR,
|
||||
passphrase = pendingPassphrase,
|
||||
sessionPath = sessionPath,
|
||||
sessionPath = emptySessionPath.absolutePath,
|
||||
)
|
||||
sessionStore.storeData(sessionData)
|
||||
SessionId(sessionData.userId)
|
||||
@@ -244,15 +259,17 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
}
|
||||
Timber.e(throwable, "Failed to login with QR code")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBaseClientBuilder() = rustMatrixClientFactory
|
||||
private fun getBaseClientBuilder(
|
||||
sessionPath: File,
|
||||
) = rustMatrixClientFactory
|
||||
.getBaseClientBuilder(
|
||||
sessionPath = sessionPath,
|
||||
sessionPath = sessionPath.absolutePath,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
|
||||
slidingSync = ClientBuilderSlidingSync.Discovered,
|
||||
)
|
||||
.requiresSlidingSync()
|
||||
|
||||
private fun clear() {
|
||||
currentClient?.close()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
@@ -58,7 +59,8 @@ class MatrixRoomInfoMapper {
|
||||
userDefinedNotificationMode = it.userDefinedNotificationMode?.map(),
|
||||
hasRoomCall = it.hasRoomCall,
|
||||
activeRoomCallParticipants = it.activeRoomCallParticipants.toImmutableList(),
|
||||
heroes = it.elementHeroes().toImmutableList()
|
||||
heroes = it.elementHeroes().toImmutableList(),
|
||||
pinnedEventIds = it.pinnedEventIds.map(::EventId).toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,23 +51,15 @@ class RoomSyncSubscriber(
|
||||
includeHeroes = false,
|
||||
)
|
||||
|
||||
suspend fun subscribe(roomId: RoomId) = mutex.withLock {
|
||||
withContext(dispatchers.io) {
|
||||
try {
|
||||
subscribeToRoom(roomId)
|
||||
} catch (exception: Exception) {
|
||||
Timber.e("Failed to subscribe to room $roomId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun batchSubscribe(roomIds: List<RoomId>) = mutex.withLock {
|
||||
withContext(dispatchers.io) {
|
||||
for (roomId in roomIds) {
|
||||
suspend fun subscribe(roomId: RoomId) {
|
||||
mutex.withLock {
|
||||
withContext(dispatchers.io) {
|
||||
try {
|
||||
subscribeToRoom(roomId)
|
||||
} catch (cancellationException: CancellationException) {
|
||||
throw cancellationException
|
||||
if (!isSubscribedTo(roomId)) {
|
||||
Timber.d("Subscribing to room $roomId}")
|
||||
roomListService.subscribeToRooms(listOf(roomId.value), settings)
|
||||
}
|
||||
subscribedRoomIds.add(roomId)
|
||||
} catch (exception: Exception) {
|
||||
Timber.e("Failed to subscribe to room $roomId")
|
||||
}
|
||||
@@ -75,14 +67,21 @@ class RoomSyncSubscriber(
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToRoom(roomId: RoomId) {
|
||||
if (!isSubscribedTo(roomId)) {
|
||||
Timber.d("Subscribing to room $roomId}")
|
||||
roomListService.room(roomId.value).use { roomListItem ->
|
||||
roomListItem.subscribe(settings)
|
||||
suspend fun batchSubscribe(roomIds: List<RoomId>) = mutex.withLock {
|
||||
withContext(dispatchers.io) {
|
||||
try {
|
||||
val roomIdsToSubscribeTo = roomIds.filterNot { isSubscribedTo(it) }
|
||||
if (roomIdsToSubscribeTo.isNotEmpty()) {
|
||||
Timber.d("Subscribing to rooms: $roomIds")
|
||||
roomListService.subscribeToRooms(roomIdsToSubscribeTo.map { it.value }, settings)
|
||||
subscribedRoomIds.addAll(roomIds)
|
||||
}
|
||||
} catch (cancellationException: CancellationException) {
|
||||
throw cancellationException
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception, "Failed to subscribe to rooms: $roomIds")
|
||||
}
|
||||
}
|
||||
subscribedRoomIds.add(roomId)
|
||||
}
|
||||
|
||||
fun isSubscribedTo(roomId: RoomId): Boolean {
|
||||
|
||||
@@ -192,6 +192,21 @@ class RustMatrixRoom(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun pinnedEventsTimeline(): Result<Timeline> {
|
||||
return runCatching {
|
||||
innerRoom.pinnedEventsTimeline(
|
||||
internalIdPrefix = "pinned_events",
|
||||
maxEventsToLoad = 100u,
|
||||
).let { inner ->
|
||||
createTimeline(inner, isLive = false)
|
||||
}
|
||||
}.onFailure {
|
||||
if (it is CancellationException) {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
roomCoroutineScope.cancel()
|
||||
liveTimeline.close()
|
||||
@@ -403,6 +418,12 @@ class RustMatrixRoom(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> {
|
||||
return runCatching {
|
||||
innerRoom.canUserPinUnpin(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendImage(
|
||||
file: File,
|
||||
thumbnailFile: File?,
|
||||
|
||||
@@ -525,6 +525,18 @@ class RustTimeline(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun pinEvent(eventId: EventId): Result<Boolean> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.pinEvent(eventId = eventId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unpinEvent(eventId: EventId): Result<Boolean> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.unpinEvent(eventId = eventId.value)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> {
|
||||
return runCatching {
|
||||
inner.fetchDetailsForEvent(eventId.value)
|
||||
|
||||
@@ -47,6 +47,7 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap
|
||||
eventId = it.eventId()?.let(::EventId),
|
||||
transactionId = it.transactionId()?.let(::TransactionId),
|
||||
isEditable = it.isEditable(),
|
||||
canBeRepliedTo = it.canBeRepliedTo(),
|
||||
isLocal = it.isLocal(),
|
||||
isOwn = it.isOwn(),
|
||||
isRemote = it.isRemote(),
|
||||
|
||||
@@ -40,6 +40,7 @@ import kotlinx.collections.immutable.toImmutableMap
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import uniffi.matrix_sdk_ui.RoomPinnedEventsChange
|
||||
import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage
|
||||
import org.matrix.rustcomponents.sdk.MembershipChange as RustMembershipChange
|
||||
import org.matrix.rustcomponents.sdk.OtherState as RustOtherState
|
||||
@@ -176,7 +177,7 @@ private fun RustOtherState.map(): OtherState {
|
||||
RustOtherState.RoomHistoryVisibility -> OtherState.RoomHistoryVisibility
|
||||
RustOtherState.RoomJoinRules -> OtherState.RoomJoinRules
|
||||
is RustOtherState.RoomName -> OtherState.RoomName(name)
|
||||
RustOtherState.RoomPinnedEvents -> OtherState.RoomPinnedEvents
|
||||
is RustOtherState.RoomPinnedEvents -> OtherState.RoomPinnedEvents(change.map())
|
||||
is RustOtherState.RoomPowerLevels -> OtherState.RoomUserPowerLevels(users)
|
||||
RustOtherState.RoomServerAcl -> OtherState.RoomServerAcl
|
||||
is RustOtherState.RoomThirdPartyInvite -> OtherState.RoomThirdPartyInvite(displayName)
|
||||
@@ -187,6 +188,14 @@ private fun RustOtherState.map(): OtherState {
|
||||
}
|
||||
}
|
||||
|
||||
private fun RoomPinnedEventsChange.map(): OtherState.RoomPinnedEvents.Change {
|
||||
return when (this) {
|
||||
RoomPinnedEventsChange.ADDED -> OtherState.RoomPinnedEvents.Change.ADDED
|
||||
RoomPinnedEventsChange.REMOVED -> OtherState.RoomPinnedEvents.Change.REMOVED
|
||||
RoomPinnedEventsChange.CHANGED -> OtherState.RoomPinnedEvents.Change.CHANGED
|
||||
}
|
||||
}
|
||||
|
||||
private fun RustEncryptedMessage.map(): UnableToDecryptContent.Data {
|
||||
return when (this) {
|
||||
is RustEncryptedMessage.MegolmV1AesSha2 -> UnableToDecryptContent.Data.MegolmV1AesSha2(sessionId, cause.map())
|
||||
|
||||
@@ -189,6 +189,8 @@ class RoomSummaryListProcessorTest {
|
||||
override fun syncIndicator(delayBeforeShowingInMs: UInt, delayBeforeHidingInMs: UInt, listener: RoomListServiceSyncIndicatorListener): TaskHandle {
|
||||
return TaskHandle(Pointer.NULL)
|
||||
}
|
||||
|
||||
override fun subscribeToRooms(roomIds: List<String>, settings: RoomSubscription?) = Unit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +223,7 @@ private fun aRustRoomInfo(
|
||||
numUnreadMessages: ULong = 0uL,
|
||||
numUnreadNotifications: ULong = 0uL,
|
||||
numUnreadMentions: ULong = 0uL,
|
||||
pinnedEventIds: List<String> = listOf(),
|
||||
) = RoomInfo(
|
||||
id = id,
|
||||
displayName = displayName,
|
||||
@@ -249,7 +252,8 @@ private fun aRustRoomInfo(
|
||||
isMarkedUnread = isMarkedUnread,
|
||||
numUnreadMessages = numUnreadMessages,
|
||||
numUnreadNotifications = numUnreadNotifications,
|
||||
numUnreadMentions = numUnreadMentions
|
||||
numUnreadMentions = numUnreadMentions,
|
||||
pinnedEventIds = pinnedEventIds,
|
||||
)
|
||||
|
||||
class FakeRoomListItem(
|
||||
@@ -268,6 +272,4 @@ class FakeRoomListItem(
|
||||
override suspend fun latestEvent(): EventTimelineItem? {
|
||||
return latestEvent
|
||||
}
|
||||
|
||||
override fun subscribe(settings: RoomSubscription?) = Unit
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user