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:
@@ -22,6 +22,20 @@ function contactKey (ownerId, peerUserId) {
|
|||||||
return { 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 {
|
class PearcordContacts extends EventEmitter {
|
||||||
constructor (opts = {}) {
|
constructor (opts = {}) {
|
||||||
super()
|
super()
|
||||||
@@ -272,7 +286,8 @@ class PearcordContacts extends EventEmitter {
|
|||||||
}
|
}
|
||||||
row.status = CONTACT_STATUS.ACCEPTED
|
row.status = CONTACT_STATUS.ACCEPTED
|
||||||
row.acceptedAt = now()
|
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.store.insert(CONTACTS_COLLECTION, row)
|
||||||
await this._syncFriendTopicLinks()
|
await this._syncFriendTopicLinks()
|
||||||
this.emit('contact', row)
|
this.emit('contact', row)
|
||||||
@@ -300,27 +315,24 @@ class PearcordContacts extends EventEmitter {
|
|||||||
|
|
||||||
ingestFriendPresence (payload) {
|
ingestFriendPresence (payload) {
|
||||||
if (!payload?.userId || payload.userId === this.userId) return null
|
if (!payload?.userId || payload.userId === this.userId) return null
|
||||||
const row = {
|
const row = presenceGossipRow(payload)
|
||||||
userId: payload.userId,
|
|
||||||
status: payload.status || USER_STATUS.ONLINE,
|
|
||||||
customStatus: payload.customStatus || null,
|
|
||||||
activity: normalizeActivity(payload.activity),
|
|
||||||
at: payload.at || now()
|
|
||||||
}
|
|
||||||
this._friendPresence.set(payload.userId, row)
|
this._friendPresence.set(payload.userId, row)
|
||||||
this.emit('friend-presence', 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
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
async broadcastPresence (payload) {
|
async broadcastPresence (payload) {
|
||||||
if (!payload?.userId) return null
|
if (!payload?.userId) return null
|
||||||
this._lastSelfPresence = {
|
this._lastSelfPresence = presenceGossipRow(payload, this.userId)
|
||||||
userId: payload.userId,
|
|
||||||
status: payload.status || USER_STATUS.ONLINE,
|
|
||||||
customStatus: payload.customStatus || null,
|
|
||||||
activity: normalizeActivity(payload.activity),
|
|
||||||
at: payload.at || now()
|
|
||||||
}
|
|
||||||
if (this._meshJoined) {
|
if (this._meshJoined) {
|
||||||
await broadcastContactsGossip(this, RPC.PRESENCE_UPDATE, this._lastSelfPresence)
|
await broadcastContactsGossip(this, RPC.PRESENCE_UPDATE, this._lastSelfPresence)
|
||||||
}
|
}
|
||||||
@@ -342,6 +354,10 @@ class PearcordContacts extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onGossip (method, payload) {
|
_onGossip (method, payload) {
|
||||||
|
if (method === RPC.USER_UPSERT) {
|
||||||
|
if (payload?.id) this.emit('user', payload)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (method === RPC.PRESENCE_UPDATE) {
|
if (method === RPC.PRESENCE_UPDATE) {
|
||||||
this.ingestFriendPresence(payload)
|
this.ingestFriendPresence(payload)
|
||||||
return
|
return
|
||||||
@@ -374,6 +390,10 @@ class PearcordContacts extends EventEmitter {
|
|||||||
this.emit('dm-channel', payload)
|
this.emit('dm-channel', payload)
|
||||||
return 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.PRESENCE_UPDATE) return this.ingestFriendPresence(payload)
|
||||||
if (method === RPC.PROFILE_COSMETIC_UPDATE) {
|
if (method === RPC.PROFILE_COSMETIC_UPDATE) {
|
||||||
this.emit('profile-cosmetic', payload)
|
this.emit('profile-cosmetic', payload)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
const Protomux = require('protomux')
|
const Protomux = require('protomux')
|
||||||
const b4a = require('b4a')
|
const b4a = require('b4a')
|
||||||
const { encodeRpc, decodeRpc, wireSwarmConnection } = require('pearcord-shared')
|
const { decodeRpc, wireSwarmConnection } = require('pearcord-shared')
|
||||||
const { openWireChannel, wireReady } = require('pearcord-drive/mux-wire')
|
const { openWireChannel, broadcastRpcToSessions } = require('pearcord-drive/mux-wire')
|
||||||
|
|
||||||
const CONTACTS_PROTOCOL = 'pearcord-contacts-v1'
|
const CONTACTS_PROTOCOL = 'pearcord-contacts-v1'
|
||||||
|
|
||||||
@@ -26,19 +26,7 @@ function attachContactsMesh (contacts, conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function broadcastContactsGossip (contacts, method, payload) {
|
async function broadcastContactsGossip (contacts, method, payload) {
|
||||||
const buf = encodeRpc(method, b4a.from(JSON.stringify(payload)))
|
await broadcastRpcToSessions(contacts._channels, method, payload, { wireReadyMs: 6000 })
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { attachContactsMesh, broadcastContactsGossip, CONTACTS_PROTOCOL }
|
module.exports = { attachContactsMesh, broadcastContactsGossip, CONTACTS_PROTOCOL }
|
||||||
|
|||||||
Reference in New Issue
Block a user