Add fallback notifications from UTDs to the push history (#5047)

This commit is contained in:
Jorge Martin Espinosa 2025-07-23 08:55:41 +02:00 committed by GitHub
parent d53457ec66
commit 7c982a30cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 93 additions and 9 deletions

View file

@ -236,7 +236,12 @@ class DefaultNotifiableEventResolver @Inject constructor(
}
NotificationContent.MessageLike.RoomEncrypted -> {
Timber.tag(loggerTag.value).w("Notification with encrypted content -> fallback")
val fallbackNotifiableEvent = fallbackNotificationFactory.create(userId, roomId, eventId)
val fallbackNotifiableEvent = fallbackNotificationFactory.create(
sessionId = userId,
roomId = roomId,
eventId = eventId,
cause = "Unable to decrypt event content",
)
ResolvedPushEvent.Event(fallbackNotifiableEvent)
}
is NotificationContent.MessageLike.RoomRedaction -> {

View file

@ -24,6 +24,7 @@ class FallbackNotificationFactory @Inject constructor(
sessionId: SessionId,
roomId: RoomId,
eventId: EventId,
cause: String?,
): FallbackNotifiableEvent = FallbackNotifiableEvent(
sessionId = sessionId,
roomId = roomId,
@ -34,5 +35,6 @@ class FallbackNotificationFactory @Inject constructor(
isUpdated = false,
timestamp = clock.epochMillis(),
description = stringProvider.getString(R.string.notification_fallback_content),
cause = cause,
)
}

View file

@ -25,4 +25,5 @@ data class FallbackNotifiableEvent(
override val isRedacted: Boolean,
override val isUpdated: Boolean,
val timestamp: Long,
val cause: String?,
) : NotifiableEvent

View file

@ -27,6 +27,7 @@ import io.element.android.libraries.push.impl.notifications.FallbackNotification
import io.element.android.libraries.push.impl.notifications.NotificationEventRequest
import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
@ -90,13 +91,23 @@ class DefaultPushHandler @Inject constructor(
} else {
result.fold(
onSuccess = {
pushHistoryService.onSuccess(
providerInfo = request.providerInfo,
eventId = request.eventId,
roomId = request.roomId,
sessionId = request.sessionId,
comment = "Push handled successfully",
)
if (it is ResolvedPushEvent.Event && it.notifiableEvent is FallbackNotifiableEvent) {
pushHistoryService.onUnableToResolveEvent(
providerInfo = request.providerInfo,
eventId = request.eventId,
roomId = request.roomId,
sessionId = request.sessionId,
reason = it.notifiableEvent.cause.orEmpty(),
)
} else {
pushHistoryService.onSuccess(
providerInfo = request.providerInfo,
eventId = request.eventId,
roomId = request.roomId,
sessionId = request.sessionId,
comment = "Push handled successfully",
)
}
},
onFailure = { exception ->
if (exception is NotificationResolverException.EventFilteredOut) {
@ -140,7 +151,14 @@ class DefaultPushHandler @Inject constructor(
}
else -> {
Timber.tag(loggerTag.value).e(exception, "Failed to resolve push event")
ResolvedPushEvent.Event(fallbackNotificationFactory.create(request.sessionId, request.roomId, request.eventId))
ResolvedPushEvent.Event(
fallbackNotificationFactory.create(
sessionId = request.sessionId,
roomId = request.roomId,
eventId = request.eventId,
cause = exception.message,
)
)
}
}
}.getOrNull() ?: continue

View file

@ -625,6 +625,7 @@ class DefaultNotifiableEventResolverTest {
isRedacted = false,
isUpdated = false,
timestamp = A_FAKE_TIMESTAMP,
cause = "Unable to decrypt event content",
)
)
assertThat(result.getEvent(request)).isEqualTo(Result.success(expectedResult))

View file

@ -71,6 +71,7 @@ class DefaultNotificationCreatorTest {
isRedacted = false,
isUpdated = false,
timestamp = A_FAKE_TIMESTAMP,
cause = null,
)
)
result.commonAssertions(

View file

@ -10,6 +10,7 @@
package io.element.android.libraries.push.impl.push
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.call.api.CallType
import io.element.android.features.call.test.FakeElementCallEntryPoint
import io.element.android.libraries.core.meta.BuildMeta
@ -38,6 +39,7 @@ import io.element.android.libraries.push.impl.notifications.NotificationResolver
import io.element.android.libraries.push.impl.notifications.channels.FakeNotificationChannels
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableCallEvent
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.libraries.push.impl.test.DefaultTestPush
@ -65,6 +67,7 @@ import kotlin.time.Duration.Companion.milliseconds
private const val A_PUSHER_INFO = "info"
@Suppress("LargeClass")
class DefaultPushHandlerTest {
@Test
fun `check handleInvalid behavior`() = runTest {
@ -628,6 +631,59 @@ class DefaultPushHandlerTest {
.isCalledExactly(2)
}
@Test
fun `when receiving a fallback event, we notify the push history service about it not being resolved`() = runTest {
val aNotifiableFallbackEvent = FallbackNotifiableEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
eventId = AN_EVENT_ID,
editedEventId = null,
description = "A fallback notification",
canBeReplaced = false,
isRedacted = false,
isUpdated = false,
timestamp = 0L,
cause = "Unable to decrypt event",
)
val notifiableEventResult =
lambdaRecorder<SessionId, List<NotificationEventRequest>, Result<Map<NotificationEventRequest, Result<ResolvedPushEvent>>>> { _, _ ->
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, A_PUSHER_INFO)
Result.success(mapOf(request to Result.success(ResolvedPushEvent.Event(aNotifiableFallbackEvent))))
}
val onNotifiableEventsReceived = lambdaRecorder<List<NotifiableEvent>, Unit> {}
val incrementPushCounterResult = lambdaRecorder<Unit> {}
var receivedFallbackEvent = false
val onPushReceivedResult =
lambdaRecorder<String, EventId?, RoomId?, SessionId?, Boolean, Boolean, String?, Unit> { _, _, _, _, isResolved, _, comment ->
receivedFallbackEvent = !isResolved && comment == "Unable to resolve event: ${aNotifiableFallbackEvent.cause}"
}
val pushHistoryService = FakePushHistoryService(
onPushReceivedResult = onPushReceivedResult,
)
val aPushData = PushData(
eventId = AN_EVENT_ID,
roomId = A_ROOM_ID,
unread = 0,
clientSecret = A_SECRET,
)
val defaultPushHandler = createDefaultPushHandler(
onNotifiableEventsReceived = onNotifiableEventsReceived,
notifiableEventsResult = notifiableEventResult,
pushClientSecret = FakePushClientSecret(
getUserIdFromSecretResult = { A_USER_ID }
),
incrementPushCounterResult = incrementPushCounterResult,
pushHistoryService = pushHistoryService,
)
defaultPushHandler.handle(aPushData, A_PUSHER_INFO)
advanceTimeBy(300.milliseconds)
onNotifiableEventsReceived.assertions().isCalledOnce()
assertThat(receivedFallbackEvent).isTrue()
}
private fun TestScope.createDefaultPushHandler(
onNotifiableEventsReceived: (List<NotifiableEvent>) -> Unit = { lambdaError() },
onRedactedEventsReceived: (List<ResolvedPushEvent.Redaction>) -> Unit = { lambdaError() },