refactor(notifications): observe unwrapped payloads instead of wrappers
Rely on the existing Account → DecryptAndIndexProcessor flow to unwrap GiftWrap → Seal → inner payload, and observe the final inner kinds instead of duplicating the unwrap logic in EventNotificationConsumer. The only wrap left to handle in the notification path is the outermost server-added GiftWrap from FCM / UnifiedPush, whose recipient tag the push server strips. - Let LocalCache store kind:444 WelcomeEvent (consumeRegularEvent). The Seal handler used to early-return on Welcomes because kind:444 had no cache handler; now it does, so Welcome lands in cache and still routes to MLS processing. - DecryptAndIndexProcessor: cache the inner event first, then branch into processMarmotWelcomeFlow vs eventProcessor.consumeEvent — both paths now emit to the observer. - NotificationDispatcher observes the final payload kinds (ChatMessage 14, ChatMessageEncryptedFileHeader 15, CallOffer 25050, Welcome 444) in addition to the direct-arrival kinds. Wrappers (1059/21059/13) are no longer in the filter. - EventNotificationConsumer drops unwrapFully entirely; consumeFromCache now just matches the account by `p` tag on an already-unwrapped event. - New PushWrapDecryptor helper probes each saved account signer to decrypt the outer server wrap and feeds the inner GiftWrap to LocalCache — where the per-account EventProcessor takes over. - FCM (PushNotificationReceiverService) and UnifiedPush (PushMessageReceiver) call PushWrapDecryptor instead of LocalCache.justConsume directly.
This commit is contained in:
+5
-4
@@ -26,7 +26,6 @@ import android.util.LruCache
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
@@ -74,11 +73,13 @@ class PushMessageReceiver : MessagingReceiver() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun receiveIfNew(event: GiftWrapEvent) {
|
private suspend fun receiveIfNew(event: GiftWrapEvent) {
|
||||||
if (eventCache.get(event.id) == null) {
|
if (eventCache.get(event.id) == null) {
|
||||||
eventCache.put(event.id, event.id)
|
eventCache.put(event.id, event.id)
|
||||||
// Feeds the shared cache; NotificationDispatcher observes and dispatches.
|
// The push server re-wraps the real GiftWrap and strips the p tag;
|
||||||
LocalCache.justConsume(event, null, false)
|
// unwrap the outer layer, feed the inner into LocalCache, and let
|
||||||
|
// the usual Account → EventProcessor chain take over.
|
||||||
|
PushWrapDecryptor.unwrapAndFeed(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryE
|
|||||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||||
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageEvent
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageEvent
|
||||||
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
|
||||||
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.GroupEvent
|
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.GroupEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||||
@@ -2808,6 +2809,7 @@ object LocalCache : ILocalCache, ICacheProvider {
|
|||||||
is VoiceReplyEvent -> consumeRegularEvent(event, relay, wasVerified)
|
is VoiceReplyEvent -> consumeRegularEvent(event, relay, wasVerified)
|
||||||
is WakeUpEvent -> consumeRegularEvent(event, relay, wasVerified)
|
is WakeUpEvent -> consumeRegularEvent(event, relay, wasVerified)
|
||||||
is WebBookmarkEvent -> consumeBaseReplaceable(event, relay, wasVerified)
|
is WebBookmarkEvent -> consumeBaseReplaceable(event, relay, wasVerified)
|
||||||
|
is WelcomeEvent -> consumeRegularEvent(event, relay, wasVerified)
|
||||||
is WikiNoteEvent -> consume(event, relay, wasVerified)
|
is WikiNoteEvent -> consume(event, relay, wasVerified)
|
||||||
is PaymentTargetsEvent -> consume(event, relay, wasVerified)
|
is PaymentTargetsEvent -> consume(event, relay, wasVerified)
|
||||||
else -> Log.w("Event Not Supported") { "From ${relay?.url}: ${event.toJson()}" }.let { false }
|
else -> Log.w("Event Not Supported") { "From ${relay?.url}: ${event.toJson()}" }.let { false }
|
||||||
|
|||||||
+16
-72
@@ -64,8 +64,6 @@ import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
|
|||||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
|
||||||
import com.vitorpamplona.quartz.nip64Chess.baseEvent.BaseChessEvent
|
import com.vitorpamplona.quartz.nip64Chess.baseEvent.BaseChessEvent
|
||||||
import com.vitorpamplona.quartz.nip64Chess.challenge.accept.LiveChessGameAcceptEvent
|
import com.vitorpamplona.quartz.nip64Chess.challenge.accept.LiveChessGameAcceptEvent
|
||||||
import com.vitorpamplona.quartz.nip64Chess.move.LiveChessMoveEvent
|
import com.vitorpamplona.quartz.nip64Chess.move.LiveChessMoveEvent
|
||||||
@@ -117,15 +115,14 @@ class EventNotificationConsumer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point for events arriving into [LocalCache] from any source (FCM push,
|
* Entry point for notification-relevant events arriving into [LocalCache]
|
||||||
* UnifiedPush, Pokey, active relay subscriptions, NotificationRelayService).
|
* from any source (FCM push, UnifiedPush, Pokey, active relay subscriptions,
|
||||||
* The caller is the [NotificationDispatcher] which observes [LocalCache] for
|
* NotificationRelayService). The [NotificationDispatcher] only invokes this
|
||||||
* notification-relevant kinds and guarantees per-event first-delivery semantics
|
* after [Account.newNotesPreProcessor] has fully unwrapped wraps and seals,
|
||||||
* (the [LocalCache] observer only fires on new insertions), so no hasConsumed
|
* so this method receives the final inner payload directly.
|
||||||
* check is needed here.
|
|
||||||
*
|
*
|
||||||
* Iterates the logged-in accounts, decrypting wraps with each signer until one
|
* Matches the event to a logged-in account by its `p` tags and dispatches
|
||||||
* matches (wraps don't carry a recipient hint).
|
* to [dispatchForAccount].
|
||||||
*/
|
*/
|
||||||
suspend fun consumeFromCache(event: Event) =
|
suspend fun consumeFromCache(event: Event) =
|
||||||
withWakeLock {
|
withWakeLock {
|
||||||
@@ -133,40 +130,27 @@ class EventNotificationConsumer(
|
|||||||
|
|
||||||
if (!notificationManager().areNotificationsEnabled()) return@withWakeLock
|
if (!notificationManager().areNotificationsEnabled()) return@withWakeLock
|
||||||
|
|
||||||
|
val taggedNpubs =
|
||||||
|
event
|
||||||
|
.taggedUserIds()
|
||||||
|
.mapTo(mutableSetOf()) { LocalCache.getOrCreateUser(it).pubkeyNpub() }
|
||||||
|
if (taggedNpubs.isEmpty()) return@withWakeLock
|
||||||
|
|
||||||
LocalPreferences.allSavedAccounts().forEach { savedAccount ->
|
LocalPreferences.allSavedAccounts().forEach { savedAccount ->
|
||||||
if (!savedAccount.hasPrivKey && !savedAccount.loggedInWithExternalSigner) return@forEach
|
if (!savedAccount.hasPrivKey && !savedAccount.loggedInWithExternalSigner) return@forEach
|
||||||
|
if (savedAccount.npub !in taggedNpubs) return@forEach
|
||||||
// For unwrapped events with `p` tags, only try accounts that are tagged.
|
|
||||||
if (event !is GiftWrapEvent) {
|
|
||||||
val taggedNpubs = event.taggedUserIds().mapTo(mutableSetOf()) { LocalCache.getOrCreateUser(it).pubkeyNpub() }
|
|
||||||
if (taggedNpubs.isNotEmpty() && savedAccount.npub !in taggedNpubs) return@forEach
|
|
||||||
}
|
|
||||||
|
|
||||||
val accountSettings = LocalPreferences.loadAccountConfigFromEncryptedStorage(savedAccount.npub) ?: return@forEach
|
val accountSettings = LocalPreferences.loadAccountConfigFromEncryptedStorage(savedAccount.npub) ?: return@forEach
|
||||||
try {
|
try {
|
||||||
val account = Amethyst.instance.accountsCache.loadAccount(accountSettings)
|
val account = Amethyst.instance.accountsCache.loadAccount(accountSettings)
|
||||||
if (consumeForAccount(event, account)) return@withWakeLock
|
dispatchForAccount(event, account)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
Log.d(TAG) { "Message was not for user ${savedAccount.npub}: ${e.message}" }
|
Log.d(TAG) { "Failed to dispatch ${event.kind} ${event.id} for ${savedAccount.npub}: ${e.message}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to process [event] for [account]. Unwraps gift wraps and seals as needed,
|
|
||||||
* updating [LocalCache] so the UI can surface the content later. Returns true if
|
|
||||||
* the event matched this account (i.e. decryption succeeded or tagging matched).
|
|
||||||
*/
|
|
||||||
private suspend fun consumeForAccount(
|
|
||||||
event: Event,
|
|
||||||
account: Account,
|
|
||||||
): Boolean {
|
|
||||||
val inner = unwrapFully(event, account.signer) ?: return false
|
|
||||||
dispatchForAccount(inner, account)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun dispatchForAccount(
|
private suspend fun dispatchForAccount(
|
||||||
event: Event,
|
event: Event,
|
||||||
account: Account,
|
account: Account,
|
||||||
@@ -240,46 +224,6 @@ class EventNotificationConsumer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fully unwraps gift wraps and seals so the inner notification-relevant event
|
|
||||||
* can be dispatched. Updates [LocalCache] so the inner events are available to
|
|
||||||
* the UI and clears the outer encrypted payloads to save memory. Returns null
|
|
||||||
* if the signer cannot decrypt the wrap (i.e. this event isn't for this account).
|
|
||||||
*
|
|
||||||
* Idempotent: safe to call whether or not the outer event is already in cache,
|
|
||||||
* since [NotificationDispatcher] invokes this after [LocalCache] has already
|
|
||||||
* stored the wrapper.
|
|
||||||
*/
|
|
||||||
private suspend fun unwrapFully(
|
|
||||||
event: Event,
|
|
||||||
signer: NostrSigner,
|
|
||||||
): Event? =
|
|
||||||
try {
|
|
||||||
when (event) {
|
|
||||||
// EphemeralGiftWrapEvent inherits GiftWrapEvent; both handled here.
|
|
||||||
is GiftWrapEvent -> {
|
|
||||||
val inner = event.unwrapThrowing(signer)
|
|
||||||
LocalCache.getOrCreateNote(event.id).event = event.copyNoContent()
|
|
||||||
unwrapFully(inner, signer)
|
|
||||||
}
|
|
||||||
|
|
||||||
is SealedRumorEvent -> {
|
|
||||||
val inner = event.unsealThrowing(signer)
|
|
||||||
LocalCache.getOrCreateNote(event.id).event = event.copyNoContent()
|
|
||||||
// seal payload is not verifiable via signature
|
|
||||||
LocalCache.justConsume(inner, null, true)
|
|
||||||
inner
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (e is CancellationException) throw e
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun notify(
|
private suspend fun notify(
|
||||||
event: ChatMessageEncryptedFileHeaderEvent,
|
event: ChatMessageEncryptedFileHeaderEvent,
|
||||||
account: Account,
|
account: Account,
|
||||||
|
|||||||
+16
-8
@@ -23,15 +23,17 @@ package com.vitorpamplona.amethyst.service.notifications
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.quartz.experimental.notifications.wake.WakeUpEvent
|
import com.vitorpamplona.quartz.experimental.notifications.wake.WakeUpEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||||
|
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
||||||
|
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.EphemeralGiftWrapEvent
|
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
|
||||||
import com.vitorpamplona.quartz.nip64Chess.challenge.accept.LiveChessGameAcceptEvent
|
import com.vitorpamplona.quartz.nip64Chess.challenge.accept.LiveChessGameAcceptEvent
|
||||||
import com.vitorpamplona.quartz.nip64Chess.move.LiveChessMoveEvent
|
import com.vitorpamplona.quartz.nip64Chess.move.LiveChessMoveEvent
|
||||||
|
import com.vitorpamplona.quartz.nipACWebRtcCalls.events.CallOfferEvent
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -56,20 +58,26 @@ class NotificationDispatcher(
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "NotificationDispatcher"
|
private const val TAG = "NotificationDispatcher"
|
||||||
|
|
||||||
// Kinds that can trigger notifications. GiftWrap and EphemeralGiftWrap carry
|
// The dispatcher observes the *final* notification payload kinds.
|
||||||
// most encrypted payloads (DMs, Chess, CallOffer, Welcome); the rest arrive
|
// GiftWrap/EphemeralGiftWrap (1059/21059) and SealedRumor (13) are NOT
|
||||||
// unwrapped. SealedRumor (kind 13) is intentionally omitted — it only exists
|
// listed here — by the time we care, Account.newNotesPreProcessor has
|
||||||
// inside a GiftWrap, and the wrapper drives the unwrap.
|
// already unwrapped them and inserted the inner payload into LocalCache,
|
||||||
|
// which fires the observer a second time on the inner event.
|
||||||
private val NOTIFICATION_KINDS =
|
private val NOTIFICATION_KINDS =
|
||||||
listOf(
|
listOf(
|
||||||
GiftWrapEvent.KIND,
|
// Direct-arrival
|
||||||
EphemeralGiftWrapEvent.KIND,
|
|
||||||
PrivateDmEvent.KIND,
|
PrivateDmEvent.KIND,
|
||||||
LnZapEvent.KIND,
|
LnZapEvent.KIND,
|
||||||
ReactionEvent.KIND,
|
ReactionEvent.KIND,
|
||||||
LiveChessGameAcceptEvent.KIND,
|
LiveChessGameAcceptEvent.KIND,
|
||||||
LiveChessMoveEvent.KIND,
|
LiveChessMoveEvent.KIND,
|
||||||
WakeUpEvent.KIND,
|
WakeUpEvent.KIND,
|
||||||
|
// Unwrapped from GiftWrap → Seal
|
||||||
|
ChatMessageEvent.KIND,
|
||||||
|
ChatMessageEncryptedFileHeaderEvent.KIND,
|
||||||
|
WelcomeEvent.KIND,
|
||||||
|
// Unwrapped from EphemeralGiftWrap
|
||||||
|
CallOfferEvent.KIND,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.service.notifications
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles GiftWraps that arrive via FCM / UnifiedPush. The push server
|
||||||
|
* re-wraps the real NIP-59 GiftWrap inside its own outer GiftWrap and strips
|
||||||
|
* the recipient `p` tag, so neither the relay's [CacheClientConnector] nor
|
||||||
|
* the account's [GiftWrapEventHandler] can identify which account owns it.
|
||||||
|
* We have to probe every saved account's signer until one decrypts.
|
||||||
|
*
|
||||||
|
* Once decrypted, the inner event is fed into [LocalCache]. From there the
|
||||||
|
* normal `Account.newNotesPreProcessor` → `GiftWrapEventHandler` →
|
||||||
|
* `SealedRumorEventHandler` chain unwraps any remaining layers, and
|
||||||
|
* [NotificationDispatcher] picks up the final payload and notifies.
|
||||||
|
*/
|
||||||
|
object PushWrapDecryptor {
|
||||||
|
private const val TAG = "PushWrapDecryptor"
|
||||||
|
|
||||||
|
suspend fun unwrapAndFeed(outerWrap: GiftWrapEvent) {
|
||||||
|
LocalPreferences.allSavedAccounts().forEach { savedAccount ->
|
||||||
|
if (!savedAccount.hasPrivKey && !savedAccount.loggedInWithExternalSigner) return@forEach
|
||||||
|
|
||||||
|
val accountSettings = LocalPreferences.loadAccountConfigFromEncryptedStorage(savedAccount.npub) ?: return@forEach
|
||||||
|
try {
|
||||||
|
val account = Amethyst.instance.accountsCache.loadAccount(accountSettings)
|
||||||
|
val inner = outerWrap.unwrapThrowing(account.signer)
|
||||||
|
LocalCache.justConsume(inner, null, false)
|
||||||
|
return
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (e is CancellationException) throw e
|
||||||
|
Log.d(TAG) { "Push wrap not for ${savedAccount.npub}: ${e.message}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.w(TAG) { "Push wrap ${outerWrap.id} did not decrypt for any saved account" }
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-27
@@ -311,28 +311,21 @@ class GiftWrapEventHandler(
|
|||||||
|
|
||||||
eventNote.event = event.copyNoContent()
|
eventNote.event = event.copyNoContent()
|
||||||
|
|
||||||
// Check if the unwrapped event is a Marmot WelcomeEvent (kind:444)
|
|
||||||
if (MarmotInboundProcessor.isWelcomeEvent(innerGift)) {
|
|
||||||
Log.d("MarmotDbg") {
|
|
||||||
"GiftWrapEventHandler: detected Marmot WelcomeEvent — routing to processMarmotWelcome"
|
|
||||||
}
|
|
||||||
processMarmotWelcome(innerGift, eventNote, publicNote)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cache.justConsume(innerGift, null, false)) {
|
if (cache.justConsume(innerGift, null, false)) {
|
||||||
cache.copyRelaysFromTo(publicNote, innerGift)
|
cache.copyRelaysFromTo(publicNote, innerGift)
|
||||||
val innerGiftNote = cache.getOrCreateNote(innerGift.id)
|
val innerGiftNote = cache.getOrCreateNote(innerGift.id)
|
||||||
eventProcessor.consumeEvent(innerGift, innerGiftNote, publicNote)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun processMarmotWelcome(
|
// Marmot Welcomes need MLS processing in addition to being cached,
|
||||||
innerEvent: Event,
|
// but they do not route through the normal eventProcessor consumer.
|
||||||
eventNote: Note,
|
if (MarmotInboundProcessor.isWelcomeEvent(innerGift)) {
|
||||||
publicNote: Note,
|
Log.d("MarmotDbg") {
|
||||||
) {
|
"GiftWrapEventHandler: detected Marmot WelcomeEvent — routing to processMarmotWelcomeFlow"
|
||||||
processMarmotWelcomeFlow(innerEvent, account)
|
}
|
||||||
|
processMarmotWelcomeFlow(innerGift, account)
|
||||||
|
} else {
|
||||||
|
eventProcessor.consumeEvent(innerGift, innerGiftNote, publicNote)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun processExistingGiftWrap(
|
private suspend fun processExistingGiftWrap(
|
||||||
@@ -472,23 +465,23 @@ class SealedRumorEventHandler(
|
|||||||
|
|
||||||
eventNote.event = event.copyNoContent()
|
eventNote.event = event.copyNoContent()
|
||||||
|
|
||||||
|
cache.justConsume(innerRumor, null, true)
|
||||||
|
cache.copyRelaysFromTo(publicNote, innerRumor)
|
||||||
|
|
||||||
|
val innerRumorNote = cache.getOrCreateNote(innerRumor.id)
|
||||||
|
|
||||||
// Marmot Welcome: GiftWrap → Seal → WelcomeEvent. The Seal handler
|
// Marmot Welcome: GiftWrap → Seal → WelcomeEvent. The Seal handler
|
||||||
// is the actual point at which we see the kind:444 inner. Route it
|
// is the actual point at which we see the kind:444 inner. Route it
|
||||||
// straight to the shared flow — there's no normal LocalCache event
|
// to the MLS flow for group joining in addition to caching — there's
|
||||||
// handler for kind:444, so otherwise it would be silently dropped.
|
// no normal eventProcessor consumer for kind:444.
|
||||||
if (MarmotInboundProcessor.isWelcomeEvent(innerRumor)) {
|
if (MarmotInboundProcessor.isWelcomeEvent(innerRumor)) {
|
||||||
Log.d("MarmotDbg") {
|
Log.d("MarmotDbg") {
|
||||||
"SealedRumorEventHandler: detected Marmot WelcomeEvent inside seal — routing to processMarmotWelcomeFlow"
|
"SealedRumorEventHandler: detected Marmot WelcomeEvent inside seal — routing to processMarmotWelcomeFlow"
|
||||||
}
|
}
|
||||||
processMarmotWelcomeFlow(innerRumor, account)
|
processMarmotWelcomeFlow(innerRumor, account)
|
||||||
return
|
} else {
|
||||||
|
eventProcessor.consumeEvent(innerRumor, innerRumorNote, publicNote)
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.justConsume(innerRumor, null, true)
|
|
||||||
cache.copyRelaysFromTo(publicNote, innerRumor)
|
|
||||||
|
|
||||||
val innerRumorNote = cache.getOrCreateNote(innerRumor.id)
|
|
||||||
eventProcessor.consumeEvent(innerRumor, innerRumorNote, publicNote)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun processExistingSealedRumor(
|
private suspend fun processExistingSealedRumor(
|
||||||
|
|||||||
+5
-4
@@ -27,7 +27,6 @@ import com.google.firebase.messaging.FirebaseMessagingService
|
|||||||
import com.google.firebase.messaging.RemoteMessage
|
import com.google.firebase.messaging.RemoteMessage
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
@@ -65,11 +64,13 @@ class PushNotificationReceiverService : FirebaseMessagingService() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun receiveIfNew(event: GiftWrapEvent) {
|
private suspend fun receiveIfNew(event: GiftWrapEvent) {
|
||||||
if (eventCache.get(event.id) == null) {
|
if (eventCache.get(event.id) == null) {
|
||||||
eventCache.put(event.id, event.id)
|
eventCache.put(event.id, event.id)
|
||||||
// Feeds the shared cache; NotificationDispatcher observes and dispatches.
|
// The push server re-wraps the real GiftWrap and strips the p tag;
|
||||||
LocalCache.justConsume(event, null, false)
|
// unwrap the outer layer, feed the inner into LocalCache, and let
|
||||||
|
// the usual Account → EventProcessor chain take over.
|
||||||
|
PushWrapDecryptor.unwrapAndFeed(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user