Join Room : makes sure we can join by alias

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

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

View file

@ -20,8 +20,9 @@ import com.squareup.anvil.annotations.ContributesBinding
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.libraries.di.SessionScope
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.roomlist.RoomSummary
import io.element.android.libraries.matrix.impl.analytics.toAnalyticsJoinedRoom
import io.element.android.services.analytics.api.AnalyticsService
import javax.inject.Inject
@ -32,18 +33,30 @@ class DefaultJoinRoom @Inject constructor(
private val analyticsService: AnalyticsService,
) : JoinRoom {
override suspend fun invoke(
roomId: RoomId,
roomIdOrAlias: RoomIdOrAlias,
serverNames: List<String>,
trigger: JoinedRoom.Trigger,
trigger: JoinedRoom.Trigger
): Result<Unit> {
return if (serverNames.isEmpty()) {
client.joinRoom(roomId)
} else {
client.joinRoomByIdOrAlias(roomId, serverNames)
}.onSuccess {
client.getRoom(roomId)?.use { room ->
analyticsService.capture(room.toAnalyticsJoinedRoom(trigger))
return when (roomIdOrAlias) {
is RoomIdOrAlias.Id -> {
if (serverNames.isEmpty()) {
client.joinRoom(roomIdOrAlias.roomId)
} else {
client.joinRoomByIdOrAlias(roomIdOrAlias, serverNames)
}
}
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),
name = roomInfo.displayName,
canonicalAlias = roomInfo.canonicalAlias?.let(::RoomAlias),
alternativeAliases = roomInfo.alternativeAliases.map(::RoomAlias),
isDirect = roomInfo.isDirect,
avatarUrl = roomInfo.avatarUrl,
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 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.core.toRoomIdOrAlias
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_SERVER_LIST
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.aRoomSummaryFilled
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
@ -33,9 +37,10 @@ import org.junit.Test
class DefaultJoinRoomTest {
@Test
fun `when there is no server names, the classic join room API is used`() = runTest {
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(Unit) }
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomId, _: List<String> -> Result.success(Unit) }
fun `when using roomId and there is no server names, the classic join room 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 {
@ -51,7 +56,7 @@ class DefaultJoinRoomTest {
client = client,
analyticsService = analyticsService,
)
sut.invoke(A_ROOM_ID, emptyList(), aTrigger)
sut.invoke(A_ROOM_ID.toRoomIdOrAlias(), emptyList(), aTrigger)
joinRoomByIdOrAliasLambda
.assertions()
.isNeverCalled()
@ -67,9 +72,10 @@ class DefaultJoinRoomTest {
}
@Test
fun `when server names are available, joinRoomByIdOrAlias API is used`() = runTest {
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(Unit) }
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomId, _: List<String> -> Result.success(Unit) }
fun `when using roomId and server names are available, 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 {
@ -85,12 +91,12 @@ class DefaultJoinRoomTest {
client = client,
analyticsService = analyticsService,
)
sut.invoke(A_ROOM_ID, A_SERVER_LIST, aTrigger)
sut.invoke(A_ROOM_ID.toRoomIdOrAlias(), A_SERVER_LIST, aTrigger)
joinRoomByIdOrAliasLambda
.assertions()
.isCalledOnce()
.with(
value(A_ROOM_ID),
value(A_ROOM_ID.toRoomIdOrAlias()),
value(A_SERVER_LIST)
)
joinRoomLambda
@ -100,4 +106,40 @@ class DefaultJoinRoomTest {
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)
)
}
}