From ee67f2ded99d1e2384c7ac9f6cba214cc151fa4a Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 18 Jul 2025 18:33:50 -0400 Subject: [PATCH] Adds a subscription to download notifications from random relays in case users are not outbox ready yet. --- .../account/AccountFilterAssembler.kt | 6 +- ...otificationsEoseFromInboxRelaysManager.kt} | 7 +- ...otificationsEoseFromRandomRelaysManager.kt | 80 +++++++++++++++++++ .../FilterNotificationsToPubkey.kt | 43 +++++++++- 4 files changed, 132 insertions(+), 4 deletions(-) rename amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/{AccountNotificationsEoseManager.kt => AccountNotificationsEoseFromInboxRelaysManager.kt} (92%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromRandomRelaysManager.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/AccountFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/AccountFilterAssembler.kt index 68fea5dac8..6795ee287f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/AccountFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/AccountFilterAssembler.kt @@ -23,7 +23,8 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.metadata.AccountMetadataEoseManager -import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.nip01Notifications.AccountNotificationsEoseManager +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.nip01Notifications.AccountNotificationsEoseFromInboxRelaysManager +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.nip01Notifications.AccountNotificationsEoseFromRandomRelaysManager import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.nip59GiftWraps.AccountGiftWrapsEoseManager import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient @@ -44,7 +45,8 @@ class AccountFilterAssembler( listOf( AccountMetadataEoseManager(client, ::allKeys), AccountGiftWrapsEoseManager(client, ::allKeys), - AccountNotificationsEoseManager(client, ::allKeys), + AccountNotificationsEoseFromInboxRelaysManager(client, ::allKeys), + AccountNotificationsEoseFromRandomRelaysManager(client, ::allKeys), ) override fun invalidateKeys() = invalidateFilters() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromInboxRelaysManager.kt similarity index 92% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseManager.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromInboxRelaysManager.kt index 331080ef65..d4f2ff05ab 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromInboxRelaysManager.kt @@ -33,12 +33,17 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -class AccountNotificationsEoseManager( +class AccountNotificationsEoseFromInboxRelaysManager( client: NostrClient, allKeys: () -> Set, ) : PerUserEoseManager(client, allKeys) { override fun user(query: AccountQueryState) = query.account.userProfile() + /** + * Downloads most notifications from the user's own inbox relays. + * But also connects to all the follows relays to check for new notifications that are not in the user's + * own inbox. + */ override fun updateFilter( key: AccountQueryState, since: SincePerRelayMap?, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromRandomRelaysManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromRandomRelaysManager.kt new file mode 100644 index 0000000000..cada0012fa --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/AccountNotificationsEoseFromRandomRelaysManager.kt @@ -0,0 +1,80 @@ +/** + * 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.relayClient.reqCommand.account.nip01Notifications + +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserEoseManager +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountQueryState +import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap +import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient +import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter +import com.vitorpamplona.quartz.nip01Core.relay.client.subscriptions.Subscription +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class AccountNotificationsEoseFromRandomRelaysManager( + client: NostrClient, + allKeys: () -> Set, +) : PerUserEoseManager(client, allKeys) { + override fun user(query: AccountQueryState) = query.account.userProfile() + + /** + * Downloads most notifications from the user's own inbox relays. + * But also connects to all the follows relays to check for new notifications that are not in the user's + * own inbox. + */ + override fun updateFilter( + key: AccountQueryState, + since: SincePerRelayMap?, + ): List? = + (key.account.followsPerRelay.value.keys - key.account.notificationRelays.flow.value).flatMap { + filterJustTheLatestNotificationsToPubkeyFromRandomRelays(it, user(key).pubkeyHex, since?.get(it)?.time) + } + + val userJobMap = mutableMapOf>() + + @OptIn(FlowPreview::class) + override fun newSub(key: AccountQueryState): Subscription { + val user = user(key) + userJobMap[user]?.forEach { it.cancel() } + userJobMap[user] = + listOf( + key.account.scope.launch(Dispatchers.Default) { + key.account.followsPerRelay.collectLatest { + invalidateFilters() + } + }, + ) + + return super.newSub(key) + } + + override fun endSub( + key: User, + subId: String, + ) { + super.endSub(key, subId) + userJobMap[key]?.forEach { it.cancel() } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/FilterNotificationsToPubkey.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/FilterNotificationsToPubkey.kt index 8a7ba5e3ee..16614cbb68 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/FilterNotificationsToPubkey.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/account/nip01Notifications/FilterNotificationsToPubkey.kt @@ -100,7 +100,7 @@ fun filterNotificationsToPubkey( Filter( kinds = NotificationsPerKeyKinds2, tags = mapOf("p" to listOf(pubkey)), - limit = 400, + limit = 50, since = since, ), ), @@ -116,3 +116,44 @@ fun filterNotificationsToPubkey( ), ) } + +fun filterJustTheLatestNotificationsToPubkeyFromRandomRelays( + relay: NormalizedRelayUrl, + pubkey: HexKey?, + since: Long?, +): List { + if (pubkey == null || pubkey.isEmpty()) return emptyList() + + return listOf( + RelayBasedFilter( + relay = relay, + filter = + Filter( + kinds = NotificationsPerKeyKinds, + tags = mapOf("p" to listOf(pubkey)), + limit = 10, + since = since, + ), + ), + RelayBasedFilter( + relay = relay, + filter = + Filter( + kinds = NotificationsPerKeyKinds2, + tags = mapOf("p" to listOf(pubkey)), + limit = 10, + since = since, + ), + ), + RelayBasedFilter( + relay = relay, + filter = + Filter( + kinds = NotificationsPerKeyKinds3, + tags = mapOf("p" to listOf(pubkey)), + limit = 10, + since = since, + ), + ), + ) +}