mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
fix(desktop-cache): scope consumeContactList to active user + fan out to SharedFlow
The old implementation tracked kind-3 replaceability with a single global `lastContactListCreatedAt` scalar and unconditionally overwrote `_followedUsers` on every accepted event. Once *any* subsystem starts fetching other users' kind-3 events (WoT scoring, mutual-follow lookups, etc.), a newer-createdAt kind-3 from a follower silently hijacks the active user's follow-set state, cascading into feed filters, mute logic, and sidebar counts. Fix by tracking newest-per-author (`ConcurrentHashMap<HexKey, Long>`) and guarding writes to `_followedUsers` / `lastContactListEvent` on `event.pubKey == accountPubkey`. `accountPubkey` is bound from `Main.kt` on login. Also expose `contactListEvents: SharedFlow<ContactListEvent>` (buffer 64, DROP_OLDEST) so downstream consumers (the incoming WoT service) can observe every accepted kind-3 without adding a bespoke listener API.
This commit is contained in:
Vendored
+43
-8
@@ -66,6 +66,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@@ -91,6 +92,27 @@ class DesktopLocalCache : ICacheProvider {
|
||||
private val _followedUsers = MutableStateFlow<Set<HexKey>>(emptySet())
|
||||
val followedUsers: StateFlow<Set<HexKey>> = _followedUsers.asStateFlow()
|
||||
|
||||
/**
|
||||
* Active user's pubkey (hex). Set from Main.kt on login. When set, only
|
||||
* kind-3 events from this pubkey update [_followedUsers] and
|
||||
* [lastContactListEvent]. Other users' kind-3 events still flow through
|
||||
* [contactListEvents] for consumers like the WoT service.
|
||||
*/
|
||||
@Volatile
|
||||
var accountPubkey: HexKey? = null
|
||||
|
||||
/**
|
||||
* Fires for every accepted kind-3 event (both the active user's and
|
||||
* other users' — filtered downstream). Buffered so slow consumers don't
|
||||
* block the consume path.
|
||||
*/
|
||||
private val _contactListEvents =
|
||||
MutableSharedFlow<ContactListEvent>(
|
||||
extraBufferCapacity = 64,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
)
|
||||
val contactListEvents: SharedFlow<ContactListEvent> = _contactListEvents.asSharedFlow()
|
||||
|
||||
/** Increments on each metadata update — observe to recompose when user names change. */
|
||||
private val _metadataVersion = MutableStateFlow(0L)
|
||||
val metadataVersion: StateFlow<Long> = _metadataVersion.asStateFlow()
|
||||
@@ -472,19 +494,28 @@ class DesktopLocalCache : ICacheProvider {
|
||||
|
||||
/**
|
||||
* Consumes a kind 3 contact list event (replaceable).
|
||||
* Updates the cached followedUsers set.
|
||||
*
|
||||
* Tracks the newest kind-3 per author (not a single global scalar) so
|
||||
* ingesting other users' follow lists (e.g. for WoT scoring) doesn't
|
||||
* corrupt the active user's state. Only the active user's kind-3 updates
|
||||
* [_followedUsers] / [lastContactListEvent]. Every accepted event fans
|
||||
* out on [_contactListEvents] for downstream consumers.
|
||||
*/
|
||||
private var lastContactListCreatedAt = 0L
|
||||
private val lastContactListByAuthor = ConcurrentHashMap<HexKey, Long>()
|
||||
|
||||
var lastContactListEvent: ContactListEvent? = null
|
||||
private set
|
||||
|
||||
private fun consumeContactList(event: ContactListEvent): Boolean {
|
||||
// Replaceable event — only accept newer contact lists
|
||||
if (event.createdAt <= lastContactListCreatedAt) return false
|
||||
lastContactListCreatedAt = event.createdAt
|
||||
lastContactListEvent = event
|
||||
_followedUsers.value = event.verifiedFollowKeySet()
|
||||
// Replaceable event — only accept newer contact lists per author
|
||||
val prev = lastContactListByAuthor[event.pubKey] ?: 0L
|
||||
if (event.createdAt <= prev) return false
|
||||
lastContactListByAuthor[event.pubKey] = event.createdAt
|
||||
|
||||
if (event.pubKey == accountPubkey) {
|
||||
lastContactListEvent = event
|
||||
_followedUsers.value = event.verifiedFollowKeySet()
|
||||
}
|
||||
|
||||
// Store in addressableNotes too — Kind3FollowListState.getFollowListEvent
|
||||
// reads from getOrCreateAddressableNote(...) and would otherwise see a
|
||||
@@ -493,6 +524,8 @@ class DesktopLocalCache : ICacheProvider {
|
||||
val addressableNote = getOrCreateAddressableNote(event.address())
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
addressableNote.loadEvent(event, author, emptyList())
|
||||
|
||||
_contactListEvents.tryEmit(event)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -786,7 +819,9 @@ class DesktopLocalCache : ICacheProvider {
|
||||
followerCounts.clear()
|
||||
followingCounts.clear()
|
||||
notesByAuthor.clear()
|
||||
lastContactListCreatedAt = 0L
|
||||
lastContactListByAuthor.clear()
|
||||
lastContactListEvent = null
|
||||
accountPubkey = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user