refactor(notifications): type notifyGroupMessage as ChatEvent

The previous signature took Event and runtime-checked kind == 9 inside
the notifier. That left the contract implicit: any caller could pass a
reaction or control message and the call would silently no-op.

Type the parameter as ChatEvent so the kind:9 restriction is structural,
and move the narrowing (`is ChatEvent`) to the GroupEventHandler call
site where the inner event is parsed. Reactions, deletions, and other
inner kinds now can't reach the notifier in the first place.

Drops the runtime kind check and the now-stale comment about reactions.
This commit is contained in:
Claude
2026-05-05 15:57:36 +00:00
parent cfde4a5bf7
commit 30d8d1bb6f
3 changed files with 20 additions and 15 deletions
@@ -505,18 +505,17 @@ class EventNotificationConsumer(
* decrypted the outer ChaCha20-Poly1305 layer and verified the inner
* MLS-signed payload.
*
* Only kind:9 chat messages produce a notification — reactions, control
* messages, and deletions stay silent, mirroring how NIP-17 (kind:14)
* is the only DM kind we notify.
* Typed to [ChatEvent] so the caller has to narrow first — reactions,
* control messages, and deletions stay silent at the type level,
* mirroring how NIP-17 (kind:14) is the only DM kind we notify.
*/
suspend fun notifyGroupMessage(
innerEvent: Event,
innerEvent: ChatEvent,
nostrGroupId: String,
account: Account,
) = withWakeLock {
Log.d(TAG, "New Marmot Group Message to Notify")
if (innerEvent.kind != ChatEvent.KIND) return@withWakeLock
if (!notificationManager().areNotificationsEnabled()) return@withWakeLock
if (MainActivity.isResumed) return@withWakeLock
@@ -530,8 +529,9 @@ class EventNotificationConsumer(
val sender = LocalCache.getOrCreateUser(innerEvent.pubKey)
val senderName = sender.toBestDisplayName()
val senderPicture = sender.profilePicture()
// Show the message body when present; reactions/empty payloads fall
// back to a generic prompt so the popup is still actionable.
// Defensive fallback for the rare empty-content ChatEvent so the
// popup is still actionable. Non-chat inner kinds were filtered
// out at the call site by the ChatEvent type narrowing.
val body = innerEvent.content.takeIf { it.isNotBlank() } ?: "New message"
val accountNpub =
@@ -51,6 +51,7 @@ import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallOfferEvent
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CancellationException
@@ -231,7 +232,7 @@ class NotificationDispatcher(
* once the MLS-decrypted inner event has been parsed and indexed.
*/
suspend fun notifyGroupMessage(
innerEvent: Event,
innerEvent: ChatEvent,
nostrGroupId: String,
account: Account,
) {
@@ -49,6 +49,7 @@ import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallIceCandidateEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallOfferEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallRejectEvent
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallRenegotiateEvent
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.sync.Mutex
@@ -665,13 +666,16 @@ class GroupEventHandler(
// notification path can't route them. Fire the popup
// directly here — only on first-time decryption, so
// a relay re-broadcast or persist-replay doesn't
// double-notify. The notifier itself filters by
// inner kind (chat only) and freshness.
Amethyst.instance.notificationDispatcher.notifyGroupMessage(
innerEvent,
result.groupId,
account,
)
// double-notify. Restrict to ChatEvent (kind:9) so
// reactions, deletions, and control messages stay
// silent.
if (innerEvent is ChatEvent) {
Amethyst.instance.notificationDispatcher.notifyGroupMessage(
innerEvent,
result.groupId,
account,
)
}
}
}