Adds a subscription to download notifications from random relays in case users are not outbox ready yet.

This commit is contained in:
Vitor Pamplona
2025-07-18 18:33:50 -04:00
parent 19327b682c
commit ee67f2ded9
4 changed files with 132 additions and 4 deletions
@@ -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()
@@ -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<AccountQueryState>,
) : PerUserEoseManager<AccountQueryState>(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?,
@@ -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<AccountQueryState>,
) : PerUserEoseManager<AccountQueryState>(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<RelayBasedFilter>? =
(key.account.followsPerRelay.value.keys - key.account.notificationRelays.flow.value).flatMap {
filterJustTheLatestNotificationsToPubkeyFromRandomRelays(it, user(key).pubkeyHex, since?.get(it)?.time)
}
val userJobMap = mutableMapOf<User, List<Job>>()
@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() }
}
}
@@ -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<RelayBasedFilter> {
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,
),
),
)
}