From 0a620d53413e3ebc86179034c55ef3d30c284954 Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 25 May 2026 01:18:49 -0400 Subject: [PATCH] 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 --- index.js | 50 +++++++++++++++++++++++++++++++++++--------------- mesh.js | 18 +++--------------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/index.js b/index.js index 4fa4339..55f05e7 100644 --- a/index.js +++ b/index.js @@ -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) diff --git a/mesh.js b/mesh.js index df43fd9..4e8746a 100644 --- a/mesh.js +++ b/mesh.js @@ -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 }