Merge branch 'main' of https://github.com/vitorpamplona/amethyst
This commit is contained in:
@@ -1757,10 +1757,23 @@ class Account(
|
|||||||
|
|
||||||
// Build filter for the member's KeyPackages
|
// Build filter for the member's KeyPackages
|
||||||
val filter = manager.subscriptionManager.keyPackageFilter(memberPubKey)
|
val filter = manager.subscriptionManager.keyPackageFilter(memberPubKey)
|
||||||
val relays = outboxRelays.flow.value
|
|
||||||
|
|
||||||
// Query across outbox relays
|
// KeyPackages are published by the invitee to *their own* outbox
|
||||||
val filterMap = relays.associateWith { listOf(filter) }
|
// (see publishMarmotKeyPackage), so look there first. Union with
|
||||||
|
// our own outbox so we still find packages that ended up on a
|
||||||
|
// shared relay, and as a fallback when we don't know the
|
||||||
|
// invitee's outbox yet.
|
||||||
|
val myOutbox = outboxRelays.flow.value
|
||||||
|
val memberOutbox =
|
||||||
|
cache
|
||||||
|
.getOrCreateUser(memberPubKey)
|
||||||
|
.outboxRelays()
|
||||||
|
?.toSet()
|
||||||
|
.orEmpty()
|
||||||
|
val fetchRelays = memberOutbox + myOutbox
|
||||||
|
|
||||||
|
// Query across the combined relay set
|
||||||
|
val filterMap = fetchRelays.associateWith { listOf(filter) }
|
||||||
|
|
||||||
val event =
|
val event =
|
||||||
client.fetchFirst(
|
client.fetchFirst(
|
||||||
@@ -1784,7 +1797,10 @@ class Account(
|
|||||||
kotlin.io.encoding.Base64
|
kotlin.io.encoding.Base64
|
||||||
.decode(keyPackageBase64)
|
.decode(keyPackageBase64)
|
||||||
val keyPackageEventId = event.id
|
val keyPackageEventId = event.id
|
||||||
val groupRelays = relays.toList()
|
// The relays embedded in the WelcomeEvent tell the new member
|
||||||
|
// where to subscribe for subsequent GroupEvents. Use our own
|
||||||
|
// outbox — that's where we will publish them.
|
||||||
|
val groupRelays = myOutbox.toList()
|
||||||
|
|
||||||
addMarmotGroupMember(
|
addMarmotGroupMember(
|
||||||
nostrGroupId = nostrGroupId,
|
nostrGroupId = nostrGroupId,
|
||||||
@@ -1823,9 +1839,32 @@ class Account(
|
|||||||
// Publish commit first (critical ordering)
|
// Publish commit first (critical ordering)
|
||||||
client.publish(commitEvent.signedEvent, groupRelays.toSet())
|
client.publish(commitEvent.signedEvent, groupRelays.toSet())
|
||||||
|
|
||||||
// Then send Welcome gift wrap to the new member
|
// Then send the Welcome gift wrap to the new member.
|
||||||
|
//
|
||||||
|
// We deliberately do NOT route this through
|
||||||
|
// computeRelayListToBroadcast(): its GiftWrap branch returns an
|
||||||
|
// empty relay set when the recipient has no NIP-17 kind:10050 and
|
||||||
|
// no cached NIP-65 inbox relays, which silently drops the welcome
|
||||||
|
// and leaves the invitee with no way to discover the group.
|
||||||
|
//
|
||||||
|
// Instead, mirror sendNip04PrivateMessage's delivery strategy:
|
||||||
|
// publish to our own outbox (so we keep a copy and the invitee
|
||||||
|
// may find it via a shared relay) unioned with the recipient's
|
||||||
|
// DM inbox relays — User.dmInboxRelays() already falls back
|
||||||
|
// through NIP-17 kind:10050 → NIP-65 read relays, which is where
|
||||||
|
// the invitee's AccountGiftWrapsEoseManager actually listens.
|
||||||
if (welcomeDelivery != null) {
|
if (welcomeDelivery != null) {
|
||||||
val relayList = computeRelayListToBroadcast(welcomeDelivery.giftWrapEvent)
|
val recipientInbox =
|
||||||
|
cache
|
||||||
|
.getOrCreateUser(memberPubKey)
|
||||||
|
.dmInboxRelays()
|
||||||
|
.orEmpty()
|
||||||
|
val relayList = outboxRelays.flow.value + recipientInbox
|
||||||
|
if (relayList.isEmpty()) {
|
||||||
|
Log.w("Marmot") {
|
||||||
|
"addMarmotGroupMember: no relays to deliver welcome gift wrap to ${memberPubKey.take(8)}…"
|
||||||
|
}
|
||||||
|
}
|
||||||
client.publish(welcomeDelivery.giftWrapEvent, relayList)
|
client.publish(welcomeDelivery.giftWrapEvent, relayList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-3
@@ -71,8 +71,10 @@ import kotlinx.coroutines.TimeoutCancellationException
|
|||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
|
||||||
@@ -699,11 +701,32 @@ class EventNotificationConsumer(
|
|||||||
|
|
||||||
if (TimeUtils.now() - event.createdAt > CallManager.MAX_EVENT_AGE_SECONDS) return
|
if (TimeUtils.now() - event.createdAt > CallManager.MAX_EVENT_AGE_SECONDS) return
|
||||||
|
|
||||||
val callerUser = LocalCache.getUserIfExists(event.pubKey)
|
val callerUser = LocalCache.getOrCreateUser(event.pubKey)
|
||||||
val callerName = callerUser?.toBestDisplayName() ?: event.pubKey.take(8) + "..."
|
|
||||||
|
// If the caller's metadata hasn't been loaded yet (e.g. fresh process from
|
||||||
|
// a push notification), briefly subscribe to the user finder so we can
|
||||||
|
// resolve the user's display name instead of showing the raw pubkey.
|
||||||
|
if (callerUser.metadataOrNull()?.bestName() == null) {
|
||||||
|
val authorState = UserFinderQueryState(callerUser, account)
|
||||||
|
try {
|
||||||
|
Amethyst.instance.sources.userFinder
|
||||||
|
.subscribe(authorState)
|
||||||
|
withTimeoutOrNull(5_000L) {
|
||||||
|
callerUser
|
||||||
|
.metadata()
|
||||||
|
.flow
|
||||||
|
.first { it?.info?.bestName() != null }
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Amethyst.instance.sources.userFinder
|
||||||
|
.unsubscribe(authorState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val callerName = callerUser.toBestDisplayName()
|
||||||
|
|
||||||
val callerBitmap =
|
val callerBitmap =
|
||||||
callerUser?.profilePicture()?.let { pictureUrl ->
|
callerUser.profilePicture()?.let { pictureUrl ->
|
||||||
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val request =
|
val request =
|
||||||
|
|||||||
+3
-3
@@ -630,12 +630,12 @@ object NotificationUtils {
|
|||||||
callerPubKey: String,
|
callerPubKey: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
) {
|
) {
|
||||||
val callerUser = LocalCache.getUserIfExists(callerPubKey)
|
val callerUser = LocalCache.getOrCreateUser(callerPubKey)
|
||||||
val callerName = callerUser?.toBestDisplayName() ?: callerPubKey.take(8) + "..."
|
val callerName = callerUser.toBestDisplayName()
|
||||||
val uri = "nostr:${callerPubKey.hexToByteArray().toNpub()}"
|
val uri = "nostr:${callerPubKey.hexToByteArray().toNpub()}"
|
||||||
|
|
||||||
val callerBitmap =
|
val callerBitmap =
|
||||||
callerUser?.profilePicture()?.let { pictureUrl ->
|
callerUser.profilePicture()?.let { pictureUrl ->
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val request =
|
val request =
|
||||||
|
|||||||
Reference in New Issue
Block a user