feat(contacts): broadcast and ingest friend presence on contacts mesh
Add PRESENCE_UPDATE gossip on pearcord:contacts:{userId} so accepted
friends see online/idle/dnd/offline without joining each other's guilds.
- ingestFriendPresence map with TTL-friendly updates
- broadcastPresence on setPresence and mesh rejoin (_lastSelfPresence)
- getFriendPresenceMap for platform listContacts enrichment
- simulateGossip handles PRESENCE_UPDATE for CI smokes
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -5,7 +5,7 @@ const path = require('bare-path')
|
||||
const EventEmitter = require('bare-events')
|
||||
const b4a = require('b4a')
|
||||
const { JsonStore } = require('pearcord-db/store-json')
|
||||
const { RPC, contactsTopic, topicToBuffer, id, now } = require('pearcord-shared')
|
||||
const { RPC, contactsTopic, topicToBuffer, id, now, USER_STATUS } = require('pearcord-shared')
|
||||
const { attachContactsMesh, broadcastContactsGossip } = require('./mesh')
|
||||
|
||||
const CONTACTS_COLLECTION = '@pearcord/contacts'
|
||||
@@ -34,6 +34,8 @@ class PearcordContacts extends EventEmitter {
|
||||
this._channels = new Map()
|
||||
this._meshJoined = false
|
||||
this._ephemeralTopics = new Set()
|
||||
this._friendPresence = new Map()
|
||||
this._lastSelfPresence = null
|
||||
}
|
||||
|
||||
async ready () {
|
||||
@@ -283,7 +285,46 @@ class PearcordContacts extends EventEmitter {
|
||||
return null
|
||||
}
|
||||
|
||||
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,
|
||||
at: payload.at || now()
|
||||
}
|
||||
this._friendPresence.set(payload.userId, row)
|
||||
this.emit('friend-presence', row)
|
||||
return row
|
||||
}
|
||||
|
||||
broadcastPresence (payload) {
|
||||
if (!payload?.userId) return null
|
||||
this._lastSelfPresence = {
|
||||
userId: payload.userId,
|
||||
status: payload.status || USER_STATUS.ONLINE,
|
||||
customStatus: payload.customStatus || null,
|
||||
at: payload.at || now()
|
||||
}
|
||||
if (this._meshJoined) {
|
||||
broadcastContactsGossip(this, RPC.PRESENCE_UPDATE, this._lastSelfPresence)
|
||||
}
|
||||
return this._lastSelfPresence
|
||||
}
|
||||
|
||||
getFriendPresenceMap () {
|
||||
const out = {}
|
||||
for (const [uid, row] of this._friendPresence) {
|
||||
out[uid] = row
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
_onGossip (method, payload) {
|
||||
if (method === RPC.PRESENCE_UPDATE) {
|
||||
this.ingestFriendPresence(payload)
|
||||
return
|
||||
}
|
||||
if (method === RPC.CONTACT_REQUEST) {
|
||||
this.ingestRequest(payload).catch(() => {})
|
||||
} else if (method === RPC.CONTACT_ACCEPT) {
|
||||
@@ -300,6 +341,7 @@ class PearcordContacts extends EventEmitter {
|
||||
}
|
||||
|
||||
async simulateGossip (method, payload) {
|
||||
if (method === RPC.PRESENCE_UPDATE) return this.ingestFriendPresence(payload)
|
||||
if (method === RPC.CONTACT_REQUEST) return this.ingestRequest(payload)
|
||||
if (method === RPC.CONTACT_ACCEPT) return this.ingestAccept(payload)
|
||||
if (method === RPC.CONTACT_DECLINE) return this.ingestDecline(payload)
|
||||
@@ -366,6 +408,9 @@ class PearcordContacts extends EventEmitter {
|
||||
})
|
||||
await this._flushSwarm(3500)
|
||||
this._meshJoined = true
|
||||
if (this._lastSelfPresence) {
|
||||
broadcastContactsGossip(this, RPC.PRESENCE_UPDATE, this._lastSelfPresence)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user