diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index 3903dccef..e40adf983 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -49,7 +49,6 @@ import com.vitorpamplona.amethyst.ui.MainActivity import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.experimental.notifications.wake.WakeUpEvent -import com.vitorpamplona.quartz.marmot.WelcomeResult import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray @@ -179,7 +178,8 @@ class EventNotificationConsumer( is ReactionEvent -> notify(event, account) is LiveChessGameAcceptEvent -> notifyChessEvent(event, account, R.string.app_notification_chess_challenge_accepted) is LiveChessMoveEvent -> notifyChessEvent(event, account, R.string.app_notification_chess_your_turn) - is WelcomeEvent -> notify(event, account) + // WelcomeEvent is dispatched directly from processMarmotWelcomeFlow + // (no `p` tag, so tag-based matching doesn't work). } } @@ -371,38 +371,28 @@ class EventNotificationConsumer( } } - private suspend fun notify( + /** + * Welcomes have no `p` tag, so [consumeFromCache]'s tag-based account match + * can't route them. They are instead dispatched here directly by + * [com.vitorpamplona.amethyst.ui.screen.loggedIn.processMarmotWelcomeFlow] + * after [MarmotManager.processWelcome] joins the group — which is also the + * only place we reliably know which account the invite was for. + */ + suspend fun notifyWelcome( event: WelcomeEvent, account: Account, - ) { + ) = withWakeLock { Log.d(TAG, "New Marmot Welcome to Notify") + if (!notificationManager().areNotificationsEnabled()) return@withWakeLock + if (MainActivity.isResumed) return@withWakeLock + // old event being re-broadcast - if (event.createdAt < TimeUtils.fifteenMinutesAgo()) return + if (event.createdAt < TimeUtils.fifteenMinutesAgo()) return@withWakeLock // a welcome we ourselves emitted - if (event.pubKey == account.signer.pubKey) return + if (event.pubKey == account.signer.pubKey) return@withWakeLock - val nostrGroupId = event.nostrGroupId() ?: return - val manager = account.marmotManager ?: return - - // Best-effort: process the welcome here so the chatroom is hydrated - // before composing the notification body. The push-notification - // background path does NOT go through Account.eventProcessor, so - // without this the invitee would only join the group later, when - // they next open the app and the relay subscription redelivers. - if (!manager.isMember(nostrGroupId)) { - try { - val result = manager.processWelcome(event, nostrGroupId) - if (result is WelcomeResult.Joined) { - val chatroom = account.marmotGroupList.getOrCreateGroup(result.nostrGroupId) - manager.syncMetadataTo(result.nostrGroupId, chatroom) - account.marmotGroupList.notifyGroupChanged(result.nostrGroupId) - } - } catch (e: Exception) { - if (e is CancellationException) throw e - Log.w(TAG) { "Failed to process Marmot Welcome from notification path: ${e.message}" } - } - } + val nostrGroupId = event.nostrGroupId() ?: return@withWakeLock val chatroom = account.marmotGroupList.getOrCreateGroup(nostrGroupId) val groupName = chatroom.displayName.value?.takeIf { it.isNotBlank() } ?: "a private group" diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt index 0788e20e7..f4ff5f82d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.service.notifications import android.content.Context +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.quartz.experimental.notifications.wake.WakeUpEvent import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent @@ -63,6 +64,11 @@ class NotificationDispatcher( // listed here — by the time we care, Account.newNotesPreProcessor has // already unwrapped them and inserted the inner payload into LocalCache, // which fires the observer a second time on the inner event. + // + // WelcomeEvent (kind:444) is also excluded: it has no `p` tag, so + // consumeFromCache can't route it. It's delivered directly via + // [notifyWelcome] from processMarmotWelcomeFlow, which does know the + // recipient account. private val NOTIFICATION_KINDS = listOf( // Direct-arrival @@ -75,7 +81,6 @@ class NotificationDispatcher( // Unwrapped from GiftWrap → Seal ChatMessageEvent.KIND, ChatMessageEncryptedFileHeaderEvent.KIND, - WelcomeEvent.KIND, // Unwrapped from EphemeralGiftWrap CallOfferEvent.KIND, ) @@ -106,4 +111,22 @@ class NotificationDispatcher( job?.cancel() job = null } + + /** + * Direct-invocation entry point for [WelcomeEvent]. Bypasses the + * cache-observer path because Welcomes have no `p` tag for account + * routing. Called from processMarmotWelcomeFlow once MLS group join + * succeeds — at which point we know which account the invite was for. + */ + suspend fun notifyWelcome( + event: WelcomeEvent, + account: Account, + ) { + try { + consumer.notifyWelcome(event, account) + } catch (e: Exception) { + if (e is CancellationException) throw e + Log.e(TAG, "Failed to dispatch Welcome notification ${event.id}", e) + } + } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt index 943e2d2d7..6cbd30046 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn +import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.call.CallManager import com.vitorpamplona.amethyst.commons.model.privateChats.ChatroomList import com.vitorpamplona.amethyst.model.Account @@ -397,6 +398,11 @@ private suspend fun processMarmotWelcomeFlow( if (result.needsKeyPackageRotation) { account.publishMarmotKeyPackages() } + + // Fire the "You've been added to " notification. Welcomes + // have no `p` tag, so the cache-observer path can't route them; + // this is the single point where we know the recipient account. + Amethyst.instance.notificationDispatcher.notifyWelcome(innerEvent, account) } is WelcomeResult.AlreadyJoined -> {