Join Room : makes sure we can join by alias

This commit is contained in:
ganfra 2024-07-23 21:17:35 +02:00
parent 1b016946ea
commit 85d6acfc48
16 changed files with 158 additions and 77 deletions

View file

@ -33,6 +33,7 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.room.join.JoinRoom import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.api.notifications.NotificationCleaner
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -107,7 +108,7 @@ class AcceptDeclineInvitePresenter @Inject constructor(
) = launch { ) = launch {
acceptedAction.runUpdatingState { acceptedAction.runUpdatingState {
joinRoom( joinRoom(
roomId = roomId, roomIdOrAlias = roomId.toRoomIdOrAlias(),
serverNames = emptyList(), serverNames = emptyList(),
trigger = JoinedRoom.Trigger.Invite, trigger = JoinedRoom.Trigger.Invite,
) )

View file

@ -23,7 +23,9 @@ import io.element.android.features.invite.api.response.InviteData
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID
@ -178,8 +180,8 @@ class AcceptDeclineInvitePresenterTest {
@Test @Test
fun `present - accepting invite error flow`() = runTest { fun `present - accepting invite error flow`() = runTest {
val joinRoomFailure = lambdaRecorder { roomId: RoomId, _: List<String>, _: JoinedRoom.Trigger -> val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
Result.failure<Unit>(RuntimeException("Failed to join room $roomId")) Result.failure<Unit>(RuntimeException("Failed to join room $roomIdOrAlias"))
} }
val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomFailure) val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomFailure)
presenter.test { presenter.test {
@ -208,7 +210,7 @@ class AcceptDeclineInvitePresenterTest {
assert(joinRoomFailure) assert(joinRoomFailure)
.isCalledOnce() .isCalledOnce()
.with( .with(
value(A_ROOM_ID), value(A_ROOM_ID.toRoomIdOrAlias()),
value(emptyList<String>()), value(emptyList<String>()),
value(JoinedRoom.Trigger.Invite) value(JoinedRoom.Trigger.Invite)
) )
@ -222,7 +224,7 @@ class AcceptDeclineInvitePresenterTest {
val fakeNotificationCleaner = FakeNotificationCleaner( val fakeNotificationCleaner = FakeNotificationCleaner(
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda
) )
val joinRoomSuccess = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger -> val joinRoomSuccess = lambdaRecorder { _: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
Result.success(Unit) Result.success(Unit)
} }
val presenter = createAcceptDeclineInvitePresenter( val presenter = createAcceptDeclineInvitePresenter(
@ -248,7 +250,7 @@ class AcceptDeclineInvitePresenterTest {
assert(joinRoomSuccess) assert(joinRoomSuccess)
.isCalledOnce() .isCalledOnce()
.with( .with(
value(A_ROOM_ID), value(A_ROOM_ID.toRoomIdOrAlias()),
value(emptyList<String>()), value(emptyList<String>()),
value(JoinedRoom.Trigger.Invite) value(JoinedRoom.Trigger.Invite)
) )
@ -271,7 +273,7 @@ class AcceptDeclineInvitePresenterTest {
private fun createAcceptDeclineInvitePresenter( private fun createAcceptDeclineInvitePresenter(
client: MatrixClient = FakeMatrixClient(), client: MatrixClient = FakeMatrixClient(),
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ -> joinRoomLambda: (RoomIdOrAlias, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
Result.success(Unit) Result.success(Unit)
}, },
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(), notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),

View file

@ -96,7 +96,7 @@ class JoinRoomPresenter @AssistedInject constructor(
} }
else -> { else -> {
value = ContentState.Loading(roomIdOrAlias) value = ContentState.Loading(roomIdOrAlias)
val result = matrixClient.getRoomPreviewFromRoomId(roomId, serverNames) val result = matrixClient.getRoomPreview(roomIdOrAlias, serverNames)
value = result.fold( value = result.fold(
onSuccess = { roomPreview -> onSuccess = { roomPreview ->
roomPreview.toContentState() roomPreview.toContentState()
@ -153,7 +153,7 @@ class JoinRoomPresenter @AssistedInject constructor(
private fun CoroutineScope.joinRoom(joinAction: MutableState<AsyncAction<Unit>>) = launch { private fun CoroutineScope.joinRoom(joinAction: MutableState<AsyncAction<Unit>>) = launch {
joinAction.runUpdatingState { joinAction.runUpdatingState {
joinRoom.invoke( joinRoom.invoke(
roomId = roomId, roomIdOrAlias = roomIdOrAlias,
serverNames = serverNames, serverNames = serverNames,
trigger = trigger trigger = trigger
) )

View file

@ -29,6 +29,7 @@ import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomAlias 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.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.CurrentUserMembership
@ -180,7 +181,7 @@ class JoinRoomPresenterTest {
@Test @Test
fun `present - when room is joined with success, all the parameters are provided`() = runTest { fun `present - when room is joined with success, all the parameters are provided`() = runTest {
val aTrigger = JoinedRoom.Trigger.MobilePermalink val aTrigger = JoinedRoom.Trigger.MobilePermalink
val joinRoomLambda = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger -> val joinRoomLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
Result.success(Unit) Result.success(Unit)
} }
val presenter = createJoinRoomPresenter( val presenter = createJoinRoomPresenter(
@ -201,7 +202,7 @@ class JoinRoomPresenterTest {
} }
joinRoomLambda.assertions() joinRoomLambda.assertions()
.isCalledOnce() .isCalledOnce()
.with(value(A_ROOM_ID), value(A_SERVER_LIST), value(aTrigger)) .with(value(A_ROOM_ID.toRoomIdOrAlias()), value(A_SERVER_LIST), value(aTrigger))
} }
} }
@ -366,7 +367,7 @@ class JoinRoomPresenterTest {
@Test @Test
fun `present - when room is not known RoomPreview is loaded`() = runTest { fun `present - when room is not known RoomPreview is loaded`() = runTest {
val client = FakeMatrixClient( val client = FakeMatrixClient(
getRoomPreviewFromRoomIdResult = { _, _ -> getRoomPreviewResult = { _, _ ->
Result.success( Result.success(
RoomPreview( RoomPreview(
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
@ -411,7 +412,7 @@ class JoinRoomPresenterTest {
@Test @Test
fun `present - when room is not known RoomPreview is loaded with error`() = runTest { fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
val client = FakeMatrixClient( val client = FakeMatrixClient(
getRoomPreviewFromRoomIdResult = { _, _ -> getRoomPreviewResult = { _, _ ->
Result.failure(AN_EXCEPTION) Result.failure(AN_EXCEPTION)
} }
) )
@ -449,7 +450,7 @@ class JoinRoomPresenterTest {
@Test @Test
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest { fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
val client = FakeMatrixClient( val client = FakeMatrixClient(
getRoomPreviewFromRoomIdResult = { _, _ -> getRoomPreviewResult = { _, _ ->
Result.failure(Exception("403")) Result.failure(Exception("403"))
} }
) )
@ -474,7 +475,7 @@ class JoinRoomPresenterTest {
serverNames: List<String> = emptyList(), serverNames: List<String> = emptyList(),
trigger: JoinedRoom.Trigger = JoinedRoom.Trigger.Invite, trigger: JoinedRoom.Trigger = JoinedRoom.Trigger.Invite,
matrixClient: MatrixClient = FakeMatrixClient(), matrixClient: MatrixClient = FakeMatrixClient(),
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ -> joinRoomLambda: (RoomIdOrAlias, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
Result.success(Unit) Result.success(Unit)
}, },
knockRoom: KnockRoom = FakeKnockRoom(), knockRoom: KnockRoom = FakeKnockRoom(),

View file

@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.api
import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias 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.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
@ -35,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.room.preview.RoomPreview import io.element.android.libraries.matrix.api.room.preview.RoomPreview
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@ -65,8 +67,8 @@ interface MatrixClient : Closeable {
suspend fun setDisplayName(displayName: String): Result<Unit> suspend fun setDisplayName(displayName: String): Result<Unit>
suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit> suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit>
suspend fun removeAvatar(): Result<Unit> suspend fun removeAvatar(): Result<Unit>
suspend fun joinRoom(roomId: RoomId): Result<Unit> suspend fun joinRoom(roomId: RoomId): Result<RoomSummary?>
suspend fun joinRoomByIdOrAlias(roomId: RoomId, serverNames: List<String>): Result<Unit> suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomSummary?>
suspend fun knockRoom(roomId: RoomId): Result<Unit> suspend fun knockRoom(roomId: RoomId): Result<Unit>
fun syncService(): SyncService fun syncService(): SyncService
fun sessionVerificationService(): SessionVerificationService fun sessionVerificationService(): SessionVerificationService
@ -104,7 +106,6 @@ interface MatrixClient : Closeable {
suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit>
suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>>
suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<ResolvedRoomAlias> suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<ResolvedRoomAlias>
suspend fun getRoomPreviewFromRoomId(roomId: RoomId, serverNames: List<String>): Result<RoomPreview>
/** /**
* Enables or disables the sending queue, according to the given parameter. * Enables or disables the sending queue, according to the given parameter.
@ -132,4 +133,5 @@ interface MatrixClient : Closeable {
* Execute generic GET requests through the SDKs internal HTTP client. * Execute generic GET requests through the SDKs internal HTTP client.
*/ */
suspend fun getUrl(url: String): Result<String> suspend fun getUrl(url: String): Result<String>
suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomPreview>
} }

View file

@ -17,11 +17,11 @@
package io.element.android.libraries.matrix.api.room.join package io.element.android.libraries.matrix.api.room.join
import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
interface JoinRoom { interface JoinRoom {
suspend operator fun invoke( suspend operator fun invoke(
roomId: RoomId, roomIdOrAlias: RoomIdOrAlias,
serverNames: List<String>, serverNames: List<String>,
trigger: JoinedRoom.Trigger, trigger: JoinedRoom.Trigger,
): Result<Unit> ): Result<Unit>

View file

@ -28,6 +28,7 @@ data class RoomSummary(
val roomId: RoomId, val roomId: RoomId,
val name: String?, val name: String?,
val canonicalAlias: RoomAlias?, val canonicalAlias: RoomAlias?,
val alternativeAliases: List<RoomAlias>,
val isDirect: Boolean, val isDirect: Boolean,
val avatarUrl: String?, val avatarUrl: String?,
val lastMessage: RoomMessage?, val lastMessage: RoomMessage?,
@ -44,4 +45,6 @@ data class RoomSummary(
val heroes: List<MatrixUser>, val heroes: List<MatrixUser>,
) { ) {
val lastMessageTimestamp = lastMessage?.originServerTs val lastMessageTimestamp = lastMessage?.originServerTs
val aliases: List<RoomAlias>
get() = listOfNotNull(canonicalAlias) + alternativeAliases
} }

View file

@ -24,7 +24,9 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias 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.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.createroom.RoomPreset import io.element.android.libraries.matrix.api.createroom.RoomPreset
import io.element.android.libraries.matrix.api.createroom.RoomVisibility import io.element.android.libraries.matrix.api.createroom.RoomVisibility
@ -41,6 +43,7 @@ import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.room.preview.RoomPreview import io.element.android.libraries.matrix.api.room.preview.RoomPreview
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
@ -175,7 +178,7 @@ class RustMatrixClient(
val (anonymizedAccessToken, anonymizedRefreshToken) = existingData.anonymizedTokens() val (anonymizedAccessToken, anonymizedRefreshToken) = existingData.anonymizedTokens()
clientLog.d( clientLog.d(
"Removing session data with access token '$anonymizedAccessToken' " + "Removing session data with access token '$anonymizedAccessToken' " +
"and refresh token '$anonymizedRefreshToken'." "and refresh token '$anonymizedRefreshToken'."
) )
if (existingData != null) { if (existingData != null) {
// Set isTokenValid to false // Set isTokenValid to false
@ -320,16 +323,23 @@ class RustMatrixClient(
/** /**
* Wait for the room to be available in the room list. * Wait for the room to be available in the room list.
* @param roomId the room id to wait for * @param roomIdOrAlias the room id or alias to wait for
* @param timeout the timeout to wait for the room to be available * @param timeout the timeout to wait for the room to be available
* @throws TimeoutCancellationException if the room is not available after the timeout * @throws TimeoutCancellationException if the room is not available after the timeout
*/ */
private suspend fun awaitRoom(roomId: RoomId, timeout: Duration) { private suspend fun awaitRoom(roomIdOrAlias: RoomIdOrAlias, timeout: Duration): RoomSummary {
withTimeout(timeout) { val predicate: (List<RoomSummary>) -> Boolean = when (roomIdOrAlias) {
is RoomIdOrAlias.Alias -> { roomSummaries: List<RoomSummary> ->
roomSummaries.flatMap { it.aliases }.contains(roomIdOrAlias.roomAlias)
}
is RoomIdOrAlias.Id -> { roomSummaries: List<RoomSummary> ->
roomSummaries.map { it.roomId }.contains(roomIdOrAlias.roomId)
}
}
return withTimeout(timeout) {
roomListService.allRooms.summaries roomListService.allRooms.summaries
.filter { roomSummaries -> .filter(predicate)
roomSummaries.map { it.roomId }.contains(roomId) .first()
}
.first() .first()
} }
} }
@ -373,7 +383,7 @@ class RustMatrixClient(
val roomId = RoomId(client.createRoom(rustParams)) val roomId = RoomId(client.createRoom(rustParams))
// Wait to receive the room back from the sync but do not returns failure if it fails. // Wait to receive the room back from the sync but do not returns failure if it fails.
try { try {
awaitRoom(roomId, 30.seconds) awaitRoom(roomId.toRoomIdOrAlias(), 30.seconds)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Timeout waiting for the room to be available in the room list") Timber.e(e, "Timeout waiting for the room to be available in the room list")
} }
@ -424,30 +434,29 @@ class RustMatrixClient(
runCatching { client.removeAvatar() } runCatching { client.removeAvatar() }
} }
override suspend fun joinRoom(roomId: RoomId): Result<Unit> = withContext(sessionDispatcher) { override suspend fun joinRoom(roomId: RoomId): Result<RoomSummary?> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.joinRoomById(roomId.value).destroy() client.joinRoomById(roomId.value).destroy()
try { try {
awaitRoom(roomId, 10.seconds) awaitRoom(roomId.toRoomIdOrAlias(), 10.seconds)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Timeout waiting for the room to be available in the room list") Timber.e(e, "Timeout waiting for the room to be available in the room list")
null
} }
} }
} }
override suspend fun joinRoomByIdOrAlias( override suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomSummary?> = withContext(sessionDispatcher) {
roomId: RoomId,
serverNames: List<String>,
): Result<Unit> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.joinRoomByIdOrAlias( client.joinRoomByIdOrAlias(
roomIdOrAlias = roomId.value, roomIdOrAlias = roomIdOrAlias.identifier,
serverNames = serverNames, serverNames = serverNames,
).destroy() ).destroy()
try { try {
awaitRoom(roomId, 10.seconds) awaitRoom(roomIdOrAlias, 10.seconds)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Timeout waiting for the room to be available in the room list") Timber.e(e, "Timeout waiting for the room to be available in the room list")
null
} }
} }
} }
@ -478,12 +487,12 @@ class RustMatrixClient(
} }
} }
override suspend fun getRoomPreviewFromRoomId(roomId: RoomId, serverNames: List<String>): Result<RoomPreview> = withContext(sessionDispatcher) { override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomPreview> = withContext(sessionDispatcher) {
runCatching { runCatching {
client.getRoomPreviewFromRoomId( when (roomIdOrAlias) {
roomId = roomId.value, is RoomIdOrAlias.Alias -> client.getRoomPreviewFromRoomAlias(roomIdOrAlias.roomAlias.value)
viaServers = serverNames, is RoomIdOrAlias.Id -> client.getRoomPreviewFromRoomId(roomIdOrAlias.roomId.value, serverNames)
).let(RoomPreviewMapper::map) }.let(RoomPreviewMapper::map)
} }
} }
@ -581,7 +590,7 @@ class RustMatrixClient(
var room = getRoom(roomId) var room = getRoom(roomId)
if (room == null) { if (room == null) {
emit(Optional.empty()) emit(Optional.empty())
awaitRoom(roomId, INFINITE) awaitRoom(roomId.toRoomIdOrAlias(), INFINITE)
room = getRoom(roomId) room = getRoom(roomId)
} }
room?.use { room?.use {

View file

@ -20,8 +20,9 @@ import com.squareup.anvil.annotations.ContributesBinding
import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.analytics.plan.JoinedRoom
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.core.RoomId import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.room.join.JoinRoom import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.impl.analytics.toAnalyticsJoinedRoom import io.element.android.libraries.matrix.impl.analytics.toAnalyticsJoinedRoom
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import javax.inject.Inject import javax.inject.Inject
@ -32,18 +33,30 @@ class DefaultJoinRoom @Inject constructor(
private val analyticsService: AnalyticsService, private val analyticsService: AnalyticsService,
) : JoinRoom { ) : JoinRoom {
override suspend fun invoke( override suspend fun invoke(
roomId: RoomId, roomIdOrAlias: RoomIdOrAlias,
serverNames: List<String>, serverNames: List<String>,
trigger: JoinedRoom.Trigger, trigger: JoinedRoom.Trigger
): Result<Unit> { ): Result<Unit> {
return if (serverNames.isEmpty()) { return when (roomIdOrAlias) {
client.joinRoom(roomId) is RoomIdOrAlias.Id -> {
} else { if (serverNames.isEmpty()) {
client.joinRoomByIdOrAlias(roomId, serverNames) client.joinRoom(roomIdOrAlias.roomId)
}.onSuccess { } else {
client.getRoom(roomId)?.use { room -> client.joinRoomByIdOrAlias(roomIdOrAlias, serverNames)
analyticsService.capture(room.toAnalyticsJoinedRoom(trigger)) }
} }
is RoomIdOrAlias.Alias -> {
client.joinRoomByIdOrAlias(roomIdOrAlias, serverNames = emptyList())
}
}.onSuccess { roomSummary ->
client.captureJoinedRoomAnalytics(roomSummary, trigger)
}.map { }
}
private suspend fun MatrixClient.captureJoinedRoomAnalytics(roomSummary: RoomSummary?, trigger: JoinedRoom.Trigger) {
if (roomSummary == null) return
getRoom(roomSummary.roomId)?.use { room ->
analyticsService.capture(room.toAnalyticsJoinedRoom(trigger))
} }
} }
} }

View file

@ -38,6 +38,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
roomId = RoomId(roomInfo.id), roomId = RoomId(roomInfo.id),
name = roomInfo.displayName, name = roomInfo.displayName,
canonicalAlias = roomInfo.canonicalAlias?.let(::RoomAlias), canonicalAlias = roomInfo.canonicalAlias?.let(::RoomAlias),
alternativeAliases = roomInfo.alternativeAliases.map(::RoomAlias),
isDirect = roomInfo.isDirect, isDirect = roomInfo.isDirect,
avatarUrl = roomInfo.avatarUrl, avatarUrl = roomInfo.avatarUrl,
numUnreadMentions = roomInfo.numUnreadMentions.toInt(), numUnreadMentions = roomInfo.numUnreadMentions.toInt(),

View file

@ -20,11 +20,15 @@ import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.impl.analytics.toAnalyticsJoinedRoom import io.element.android.libraries.matrix.impl.analytics.toAnalyticsJoinedRoom
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SERVER_LIST import io.element.android.libraries.matrix.test.A_SERVER_LIST
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
@ -33,9 +37,10 @@ import org.junit.Test
class DefaultJoinRoomTest { class DefaultJoinRoomTest {
@Test @Test
fun `when there is no server names, the classic join room API is used`() = runTest { fun `when using roomId and there is no server names, the classic join room API is used`() = runTest {
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(Unit) } val roomSummary = aRoomSummaryFilled()
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomId, _: List<String> -> Result.success(Unit) } val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
val roomResult = FakeMatrixRoom() val roomResult = FakeMatrixRoom()
val aTrigger = JoinedRoom.Trigger.MobilePermalink val aTrigger = JoinedRoom.Trigger.MobilePermalink
val client: MatrixClient = FakeMatrixClient().also { val client: MatrixClient = FakeMatrixClient().also {
@ -51,7 +56,7 @@ class DefaultJoinRoomTest {
client = client, client = client,
analyticsService = analyticsService, analyticsService = analyticsService,
) )
sut.invoke(A_ROOM_ID, emptyList(), aTrigger) sut.invoke(A_ROOM_ID.toRoomIdOrAlias(), emptyList(), aTrigger)
joinRoomByIdOrAliasLambda joinRoomByIdOrAliasLambda
.assertions() .assertions()
.isNeverCalled() .isNeverCalled()
@ -67,9 +72,10 @@ class DefaultJoinRoomTest {
} }
@Test @Test
fun `when server names are available, joinRoomByIdOrAlias API is used`() = runTest { fun `when using roomId and server names are available, joinRoomByIdOrAlias API is used`() = runTest {
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(Unit) } val roomSummary = aRoomSummaryFilled()
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomId, _: List<String> -> Result.success(Unit) } val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
val roomResult = FakeMatrixRoom() val roomResult = FakeMatrixRoom()
val aTrigger = JoinedRoom.Trigger.MobilePermalink val aTrigger = JoinedRoom.Trigger.MobilePermalink
val client: MatrixClient = FakeMatrixClient().also { val client: MatrixClient = FakeMatrixClient().also {
@ -85,12 +91,12 @@ class DefaultJoinRoomTest {
client = client, client = client,
analyticsService = analyticsService, analyticsService = analyticsService,
) )
sut.invoke(A_ROOM_ID, A_SERVER_LIST, aTrigger) sut.invoke(A_ROOM_ID.toRoomIdOrAlias(), A_SERVER_LIST, aTrigger)
joinRoomByIdOrAliasLambda joinRoomByIdOrAliasLambda
.assertions() .assertions()
.isCalledOnce() .isCalledOnce()
.with( .with(
value(A_ROOM_ID), value(A_ROOM_ID.toRoomIdOrAlias()),
value(A_SERVER_LIST) value(A_SERVER_LIST)
) )
joinRoomLambda joinRoomLambda
@ -100,4 +106,40 @@ class DefaultJoinRoomTest {
roomResult.toAnalyticsJoinedRoom(aTrigger) roomResult.toAnalyticsJoinedRoom(aTrigger)
) )
} }
@Test
fun `when using roomAlias, joinRoomByIdOrAlias API is used`() = runTest {
val roomSummary = aRoomSummaryFilled()
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
val roomResult = FakeMatrixRoom()
val aTrigger = JoinedRoom.Trigger.MobilePermalink
val client: MatrixClient = FakeMatrixClient().also {
it.joinRoomLambda = joinRoomLambda
it.joinRoomByIdOrAliasLambda = joinRoomByIdOrAliasLambda
it.givenGetRoomResult(
roomId = A_ROOM_ID,
result = roomResult
)
}
val analyticsService = FakeAnalyticsService()
val sut = DefaultJoinRoom(
client = client,
analyticsService = analyticsService,
)
sut.invoke(A_ROOM_ALIAS.toRoomIdOrAlias(), A_SERVER_LIST, aTrigger)
joinRoomByIdOrAliasLambda
.assertions()
.isCalledOnce()
.with(
value(A_ROOM_ALIAS.toRoomIdOrAlias()),
value(emptyList<String>())
)
joinRoomLambda
.assertions()
.isNeverCalled()
assertThat(analyticsService.capturedEvents).containsExactly(
roomResult.toAnalyticsJoinedRoom(aTrigger)
)
}
} }

View file

@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias 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.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
@ -36,6 +37,7 @@ import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.room.preview.RoomPreview import io.element.android.libraries.matrix.api.room.preview.RoomPreview
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
@ -80,7 +82,7 @@ class FakeMatrixClient(
private val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(), private val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(),
private val accountManagementUrlString: Result<String?> = Result.success(null), private val accountManagementUrlString: Result<String?> = Result.success(null),
private val resolveRoomAliasResult: (RoomAlias) -> Result<ResolvedRoomAlias> = { Result.success(ResolvedRoomAlias(A_ROOM_ID, emptyList())) }, private val resolveRoomAliasResult: (RoomAlias) -> Result<ResolvedRoomAlias> = { Result.success(ResolvedRoomAlias(A_ROOM_ID, emptyList())) },
private val getRoomPreviewFromRoomIdResult: (RoomId, List<String>) -> Result<RoomPreview> = { _, _ -> Result.failure(AN_EXCEPTION) }, private val getRoomPreviewResult: (RoomIdOrAlias, List<String>) -> Result<RoomPreview> = { _, _ -> Result.failure(AN_EXCEPTION) },
private val clearCacheLambda: () -> Unit = { lambdaError() }, private val clearCacheLambda: () -> Unit = { lambdaError() },
private val userIdServerNameLambda: () -> String = { lambdaError() }, private val userIdServerNameLambda: () -> String = { lambdaError() },
private val getUrlLambda: (String) -> Result<String> = { lambdaError() }, private val getUrlLambda: (String) -> Result<String> = { lambdaError() },
@ -108,11 +110,11 @@ class FakeMatrixClient(
private var setDisplayNameResult: Result<Unit> = Result.success(Unit) private var setDisplayNameResult: Result<Unit> = Result.success(Unit)
private var uploadAvatarResult: Result<Unit> = Result.success(Unit) private var uploadAvatarResult: Result<Unit> = Result.success(Unit)
private var removeAvatarResult: Result<Unit> = Result.success(Unit) private var removeAvatarResult: Result<Unit> = Result.success(Unit)
var joinRoomLambda: (RoomId) -> Result<Unit> = { var joinRoomLambda: (RoomId) -> Result<RoomSummary?> = {
Result.success(Unit) Result.success(null)
} }
var joinRoomByIdOrAliasLambda: (RoomId, List<String>) -> Result<Unit> = { _, _ -> var joinRoomByIdOrAliasLambda: (RoomIdOrAlias, List<String>) -> Result<RoomSummary?> = { _, _ ->
Result.success(Unit) Result.success(null)
} }
var knockRoomLambda: (RoomId) -> Result<Unit> = { var knockRoomLambda: (RoomId) -> Result<Unit> = {
Result.success(Unit) Result.success(Unit)
@ -207,10 +209,10 @@ class FakeMatrixClient(
return removeAvatarResult return removeAvatarResult
} }
override suspend fun joinRoom(roomId: RoomId): Result<Unit> = joinRoomLambda(roomId) override suspend fun joinRoom(roomId: RoomId): Result<RoomSummary?> = joinRoomLambda(roomId)
override suspend fun joinRoomByIdOrAlias(roomId: RoomId, serverNames: List<String>): Result<Unit> { override suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomSummary?> {
return joinRoomByIdOrAliasLambda(roomId, serverNames) return joinRoomByIdOrAliasLambda(roomIdOrAlias, serverNames)
} }
override suspend fun knockRoom(roomId: RoomId): Result<Unit> = knockRoomLambda(roomId) override suspend fun knockRoom(roomId: RoomId): Result<Unit> = knockRoomLambda(roomId)
@ -297,8 +299,8 @@ class FakeMatrixClient(
resolveRoomAliasResult(roomAlias) resolveRoomAliasResult(roomAlias)
} }
override suspend fun getRoomPreviewFromRoomId(roomId: RoomId, serverNames: List<String>) = simulateLongTask { override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomPreview> = simulateLongTask {
getRoomPreviewFromRoomIdResult(roomId, serverNames) getRoomPreviewResult(roomIdOrAlias, serverNames)
} }
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> { override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> {

View file

@ -68,6 +68,7 @@ fun aRoomSummary(
notificationMode: RoomNotificationMode? = null, notificationMode: RoomNotificationMode? = null,
inviter: RoomMember? = null, inviter: RoomMember? = null,
canonicalAlias: RoomAlias? = null, canonicalAlias: RoomAlias? = null,
alternativeAliases: List<RoomAlias> = emptyList(),
hasRoomCall: Boolean = false, hasRoomCall: Boolean = false,
isDm: Boolean = false, isDm: Boolean = false,
isFavorite: Boolean = false, isFavorite: Boolean = false,
@ -86,6 +87,7 @@ fun aRoomSummary(
userDefinedNotificationMode = notificationMode, userDefinedNotificationMode = notificationMode,
inviter = inviter, inviter = inviter,
canonicalAlias = canonicalAlias, canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases,
hasRoomCall = hasRoomCall, hasRoomCall = hasRoomCall,
isDm = isDm, isDm = isDm,
isFavorite = isFavorite, isFavorite = isFavorite,

View file

@ -17,18 +17,18 @@
package io.element.android.libraries.matrix.test.room.join package io.element.android.libraries.matrix.test.room.join
import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.room.join.JoinRoom import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.tests.testutils.simulateLongTask import io.element.android.tests.testutils.simulateLongTask
class FakeJoinRoom( class FakeJoinRoom(
var lambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> var lambda: (RoomIdOrAlias, List<String>, JoinedRoom.Trigger) -> Result<Unit>
) : JoinRoom { ) : JoinRoom {
override suspend fun invoke( override suspend fun invoke(
roomId: RoomId, roomIdOrAlias: RoomIdOrAlias,
serverNames: List<String>, serverNames: List<String>,
trigger: JoinedRoom.Trigger, trigger: JoinedRoom.Trigger,
): Result<Unit> = simulateLongTask { ): Result<Unit> = simulateLongTask {
lambda(roomId, serverNames, trigger) lambda(roomIdOrAlias, serverNames, trigger)
} }
} }

View file

@ -38,6 +38,7 @@ fun aRoomSummaryDetails(
roomId: RoomId = RoomId("!room:domain"), roomId: RoomId = RoomId("!room:domain"),
name: String? = "roomName", name: String? = "roomName",
canonicalAlias: RoomAlias? = null, canonicalAlias: RoomAlias? = null,
alternativeAliases: List<RoomAlias> = emptyList(),
isDirect: Boolean = true, isDirect: Boolean = true,
avatarUrl: String? = null, avatarUrl: String? = null,
lastMessage: RoomMessage? = null, lastMessage: RoomMessage? = null,
@ -56,6 +57,7 @@ fun aRoomSummaryDetails(
roomId = roomId, roomId = roomId,
name = name, name = name,
canonicalAlias = canonicalAlias, canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases,
isDirect = isDirect, isDirect = isDirect,
avatarUrl = avatarUrl, avatarUrl = avatarUrl,
lastMessage = lastMessage, lastMessage = lastMessage,

View file

@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.asEventId import io.element.android.libraries.matrix.api.core.asEventId
import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.room.Mention
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_MESSAGE
@ -261,7 +262,7 @@ class NotificationBroadcastReceiverHandlerTest {
@Test @Test
fun `Test join room`() = runTest { fun `Test join room`() = runTest {
val joinRoom = lambdaRecorder<RoomId, Result<Unit>> { _ -> Result.success(Unit) } val joinRoom = lambdaRecorder<RoomId, Result<RoomSummary?>> { _ -> Result.success(null) }
val clearMembershipNotificationForRoomLambda = lambdaRecorder<SessionId, RoomId, Unit> { _, _ -> } val clearMembershipNotificationForRoomLambda = lambdaRecorder<SessionId, RoomId, Unit> { _, _ -> }
val fakeNotificationCleaner = FakeNotificationCleaner( val fakeNotificationCleaner = FakeNotificationCleaner(
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda, clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda,
@ -444,7 +445,7 @@ class NotificationBroadcastReceiverHandlerTest {
private fun TestScope.createNotificationBroadcastReceiverHandler( private fun TestScope.createNotificationBroadcastReceiverHandler(
matrixRoom: FakeMatrixRoom? = FakeMatrixRoom(), matrixRoom: FakeMatrixRoom? = FakeMatrixRoom(),
joinRoom: (RoomId) -> Result<Unit> = { lambdaError() }, joinRoom: (RoomId) -> Result<RoomSummary?> = { lambdaError() },
matrixClient: MatrixClient? = FakeMatrixClient().apply { matrixClient: MatrixClient? = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, matrixRoom) givenGetRoomResult(A_ROOM_ID, matrixRoom)
joinRoomLambda = joinRoom joinRoomLambda = joinRoom