Fix room id parsing from notification tags

Thread notification tags have the form "roomId|threadId". clearReadRoomsNotifications
was building a RoomId from the whole tag, which is only validated in debug builds, so
in release an invalid room id reached RoomListService.room(). Add
NotificationCreator.getMessageNotificationRoomId(), the inverse of messageTag(), which
only keeps the part before '|' and checks it is a valid room id.

Also make RoomListServiceInterface.roomOrNull take a RoomId instead of a String.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benoit Marty
2026-10-15 12:40:26 +02:00
parent e1b9ba21dc
commit 9ac71fd35d
9 changed files with 106 additions and 17 deletions
@@ -820,7 +820,7 @@ class RustMatrixClient(
override suspend fun getRoomInfo(roomId: RoomId): Result<RoomInfo?> = withContext(sessionDispatcher) {
runCatchingExceptions {
innerRoomListService.roomOrNull(roomId.value)?.use { room ->
innerRoomListService.roomOrNull(roomId)?.use { room ->
roomInfoMapper.map(room.roomInfo())
}
}
@@ -828,7 +828,7 @@ class RustMatrixClient(
override fun getRoomInfoFlow(roomId: RoomId): Flow<Optional<RoomInfo>> {
return mxCallbackFlow {
val roomNotFound = innerRoomListService.roomOrNull(roomId.value).use { it == null }
val roomNotFound = innerRoomListService.roomOrNull(roomId).use { it == null }
if (roomNotFound) {
channel.send(Optional.empty())
}
@@ -49,7 +49,7 @@ class RoomContentForwarder(
val message = content.content.kind as? MsgLikeKind.Message ?: throw ForwardEventException(toRoomIds)
contentWithoutRelationFromMessage(message.content)
}
val targetRooms = toRoomIds.toSet().mapNotNull { roomId -> roomListService.roomOrNull(roomId.value) }
val targetRooms = toRoomIds.toSet().mapNotNull { roomId -> roomListService.roomOrNull(roomId) }
val failedForwardingTo = mutableSetOf<RoomId>()
targetRooms.parallelMap { room ->
room.use { targetRoom ->
@@ -187,11 +187,11 @@ class RustRoomFactory(
* Get the Rust room for a room, retrying after the room list is loaded if necessary.
*/
private suspend fun awaitRoomInRoomList(roomId: RoomId): Room? {
var sdkRoom = innerRoomListService.roomOrNull(roomId.value)
var sdkRoom = innerRoomListService.roomOrNull(roomId)
if (sdkRoom == null) {
// ... otherwise, lets wait for the SS to load all rooms and check again.
roomListService.allRooms.awaitLoaded()
sdkRoom = innerRoomListService.roomOrNull(roomId.value)
sdkRoom = innerRoomListService.roomOrNull(roomId)
}
if (sdkRoom == null) {
@@ -9,6 +9,7 @@
package io.element.android.libraries.matrix.impl.roomlist
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import kotlinx.coroutines.channels.Channel
@@ -109,10 +110,10 @@ internal fun RoomListServiceInterface.syncIndicator(): Flow<RoomListServiceSyncI
)
}.buffer(Channel.UNLIMITED)
internal fun RoomListServiceInterface.roomOrNull(roomId: String): Room? {
internal fun RoomListServiceInterface.roomOrNull(roomId: RoomId): Room? {
return tryOrNull(
onException = { Timber.e(it, "Failed finding room with id=$roomId.") }
) {
room(roomId)
room(roomId.value)
}
}
@@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.roomlist
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -43,7 +44,7 @@ class RoomSummaryListProcessor(
suspend fun rebuildRoomSummaries() {
updateRoomSummaries(emptyList()) {
forEachIndexed { i, summary ->
val result = buildRoomSummaryForIdentifier(summary.roomId.value)
val result = buildRoomSummaryForIdentifier(summary.roomId)
if (result != null) {
this[i] = result
}
@@ -103,8 +104,8 @@ class RoomSummaryListProcessor(
return entry.use { roomSummaryFactory.create(room = it) }
}
private suspend fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary? {
return roomListService.roomOrNull(identifier)?.let { room ->
private suspend fun buildRoomSummaryForIdentifier(roomId: RoomId): RoomSummary? {
return roomListService.roomOrNull(roomId)?.let { room ->
buildSummaryForRoomListEntry(room)
}
}