fix(contacts): sync profile fields on presence gossip and USER_UPSERT

Store username/displayName in friend presence, set peerUsername on accept, and use guarded mesh broadcast.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-25 01:18:49 -04:00
co-authored by Cursor
parent 2a8b857162
commit 0a620d5341
2 changed files with 38 additions and 30 deletions
+35 -15
View File
@@ -22,6 +22,20 @@ function contactKey (ownerId, peerUserId) {
return { ownerId, peerUserId }
}
function presenceGossipRow (payload, fallbackUserId) {
return {
userId: payload?.userId || fallbackUserId,
status: payload?.status || USER_STATUS.ONLINE,
customStatus: payload?.customStatus ?? null,
activity: normalizeActivity(payload?.activity),
username: payload?.username || null,
displayName: payload?.displayName || null,
discriminator: payload?.discriminator || null,
avatarHash: payload?.avatarHash || null,
at: payload?.at || now()
}
}
class PearcordContacts extends EventEmitter {
constructor (opts = {}) {
super()
@@ -272,7 +286,8 @@ class PearcordContacts extends EventEmitter {
}
row.status = CONTACT_STATUS.ACCEPTED
row.acceptedAt = now()
row.peerDisplayName = payload.fromDisplayName || row.peerDisplayName
row.peerUsername = payload.fromUsername || row.peerUsername || null
row.peerDisplayName = payload.fromDisplayName || payload.fromUsername || row.peerDisplayName
await this.store.insert(CONTACTS_COLLECTION, row)
await this._syncFriendTopicLinks()
this.emit('contact', row)
@@ -300,27 +315,24 @@ class PearcordContacts extends EventEmitter {
ingestFriendPresence (payload) {
if (!payload?.userId || payload.userId === this.userId) return null
const row = {
userId: payload.userId,
status: payload.status || USER_STATUS.ONLINE,
customStatus: payload.customStatus || null,
activity: normalizeActivity(payload.activity),
at: payload.at || now()
}
const row = presenceGossipRow(payload)
this._friendPresence.set(payload.userId, row)
this.emit('friend-presence', row)
if (row.username || row.displayName) {
this.emit('user', {
id: row.userId,
username: row.username,
displayName: row.displayName,
discriminator: row.discriminator,
avatarHash: row.avatarHash
})
}
return row
}
async broadcastPresence (payload) {
if (!payload?.userId) return null
this._lastSelfPresence = {
userId: payload.userId,
status: payload.status || USER_STATUS.ONLINE,
customStatus: payload.customStatus || null,
activity: normalizeActivity(payload.activity),
at: payload.at || now()
}
this._lastSelfPresence = presenceGossipRow(payload, this.userId)
if (this._meshJoined) {
await broadcastContactsGossip(this, RPC.PRESENCE_UPDATE, this._lastSelfPresence)
}
@@ -342,6 +354,10 @@ class PearcordContacts extends EventEmitter {
}
_onGossip (method, payload) {
if (method === RPC.USER_UPSERT) {
if (payload?.id) this.emit('user', payload)
return
}
if (method === RPC.PRESENCE_UPDATE) {
this.ingestFriendPresence(payload)
return
@@ -374,6 +390,10 @@ class PearcordContacts extends EventEmitter {
this.emit('dm-channel', payload)
return payload
}
if (method === RPC.USER_UPSERT) {
if (payload?.id) this.emit('user', payload)
return payload
}
if (method === RPC.PRESENCE_UPDATE) return this.ingestFriendPresence(payload)
if (method === RPC.PROFILE_COSMETIC_UPDATE) {
this.emit('profile-cosmetic', payload)
+3 -15
View File
@@ -2,8 +2,8 @@
const Protomux = require('protomux')
const b4a = require('b4a')
const { encodeRpc, decodeRpc, wireSwarmConnection } = require('pearcord-shared')
const { openWireChannel, wireReady } = require('pearcord-drive/mux-wire')
const { decodeRpc, wireSwarmConnection } = require('pearcord-shared')
const { openWireChannel, broadcastRpcToSessions } = require('pearcord-drive/mux-wire')
const CONTACTS_PROTOCOL = 'pearcord-contacts-v1'
@@ -26,19 +26,7 @@ function attachContactsMesh (contacts, conn) {
}
async function broadcastContactsGossip (contacts, method, payload) {
const buf = encodeRpc(method, b4a.from(JSON.stringify(payload)))
const sessions = [...contacts._channels.values()]
await Promise.all(
sessions.map(async (session) => {
try {
const ok = await wireReady(session, 6000)
if (!ok) return
session.send(buf)
} catch {
// peer gone
}
})
)
await broadcastRpcToSessions(contacts._channels, method, payload, { wireReadyMs: 6000 })
}
module.exports = { attachContactsMesh, broadcastContactsGossip, CONTACTS_PROTOCOL }