Phase 668: composer embed preview and mesh embed guards.

Add prefetchComposerEmbed, clearComposerEmbedPreview, guild resolve rate
limit, DM gossip bandwidth cap, URL-hash dedupe, and domain policy gates.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-06-02 20:34:17 -04:00
co-authored by Cursor
parent 295f233bfb
commit e50d317aa4
2 changed files with 153 additions and 11 deletions
+2
View File
@@ -2,6 +2,8 @@
Application facade: one `PearcordPlatform` class that wires identity, database, guild mesh, DMs, invites, voice, discovery, bots, and dozens of feature modules for the Pearcord desktop sidecar and companion.
**Phase 668 (v0.8.643):** Composer embed preview — `prefetchComposerEmbed`, `clearComposerEmbedPreview`, `composerEmbedPreview` view slice; guild embed resolve rate limit; DM gossip bandwidth cap; URL-hash dedupe; domain block/allowlist; automation embed opt-out; scheduled/webhook embed queue guards. Bundle: `npm run test:ci-phase668`.
**Phase 667 (v0.8.642):** Markdown-aware unfurl — `shouldUnfurlMessageLayout` gates `_queueLinkEmbed`, edit URL change refresh, hub/emoji-only view skip, `markdown-embed suppressed` debug span. Bundle: `npm run test:ci-phase667`.
**Phase 666 (v0.8.641):** Embed parity — `exportEmbedDiagnostics`, `_healStaleEmbedCache`, `_embedPrefetchCoalesce`, `embed.prefetch`/`embed.heal` spans, DM+guild `_queueLinkEmbed`, DM `gossipLinkEmbed`, `retryLinkEmbedForMessage`, `setComposerImeComposing`, conversation-scoped cache, multi-embed view slice, `embed.clear` on edit. Bundle: `npm run test:ci-phase666`.
+151 -11
View File
@@ -260,6 +260,7 @@ const {
extractFirstUrl,
extractUrls,
shouldUnfurlMessageLayout,
isEmbedDomainBlocked,
parsePearcordDeepLink,
ALLOWED_DEEP_LINK_KINDS,
validatePastedNavigationInput,
@@ -583,6 +584,10 @@ class PearcordPlatform extends EventEmitter {
this._embedResolveLatencyByMessage = new Map()
/** Suppress composer-driven embed prefetch during IME composition (Phase 666). */
this._composerImeComposing = false
this._composerEmbedPreview = null
this._embedGossipUrlHashes = new Set()
this._embedResolveRateByGuild = new Map()
this._dmEmbedGossipBytesByConversation = new Map()
}
_getGuildOpenSlowThresholdMs () {
@@ -12472,7 +12477,7 @@ class PearcordPlatform extends EventEmitter {
return this.readAttachmentPreview(hash)
}
async updateGuildSettings ({ name, iconHash, nsfwGateEnabled }) {
async updateGuildSettings ({ name, iconHash, nsfwGateEnabled, embedAllowlistDomains, embedAllowlistOnly }) {
const guildId = this.guild?.guild?.id || null
const prev = this.guild?.guild || null
const span = this.log.time('guild.update', {
@@ -12496,6 +12501,14 @@ class PearcordPlatform extends EventEmitter {
nsfwGateEnabled:
nsfwGateEnabled !== undefined ? !!nsfwGateEnabled : prev.nsfwGateEnabled !== false
}
if (embedAllowlistDomains !== undefined) {
guild.embedAllowlistDomains = Array.isArray(embedAllowlistDomains)
? embedAllowlistDomains.map((d) => String(d || '').trim().toLowerCase()).filter(Boolean).slice(0, 64)
: []
}
if (embedAllowlistOnly !== undefined) {
guild.embedAllowlistOnly = !!embedAllowlistOnly
}
await this.db.insert(COLLECTIONS.GUILDS, guild)
this.guild.guild = guild
await this._audit('guild.update', { guildId: guild.id, targetId: guild.id })
@@ -13000,9 +13013,105 @@ class PearcordPlatform extends EventEmitter {
setComposerImeComposing (active) {
this._composerImeComposing = !!active
if (this._composerImeComposing) this._composerEmbedPreview = null
return { active: this._composerImeComposing }
}
clearComposerEmbedPreview () {
this._composerEmbedPreview = null
return { cleared: true }
}
async prefetchComposerEmbed (url) {
const target = String(url || '').trim()
if (!target || this._composerImeComposing || this._readOnly) {
this._composerEmbedPreview = null
return { row: null, skipped: true }
}
const prefs = this.userSettings ? await this.userSettings.getPrefs() : null
if (prefs && (prefs.showLinkPreviews === false || prefs.linkPreviewsEnabled === false)) {
this._composerEmbedPreview = null
return { row: null, previewsOff: true }
}
if (await this._embedPolicyBlocksUrl(target, prefs)) {
this._composerEmbedPreview = null
return { row: null, blocked: true }
}
const conversationId = this.mode === 'dm' ? this.activeChannelId : null
const acceptLanguage = prefs?.embedResolveLocale || null
const cached = await this.embeds?.get(target, { conversationId }).catch(() => null)
if (cached) {
this._composerEmbedPreview = { url: target, row: cached, fromCache: true }
this.log.debug('embed.prefetch composer cache', {
spanKind: 'embed.prefetch',
url: target,
fromCache: true,
channelId: this.activeChannelId
})
return { row: cached, fromCache: true }
}
const row = await this.embeds
?.resolve(target, { conversationId, acceptLanguage })
.catch(() => null)
this._composerEmbedPreview = row ? { url: target, row, fromCache: false } : null
this.log.debug('embed.prefetch composer', {
spanKind: 'embed.prefetch',
url: target,
fromCache: false,
resolved: !!row,
channelId: this.activeChannelId
})
return { row, fromCache: false }
}
async _embedPolicyBlocksUrl (url, prefs = null) {
if (!url) return true
const p = prefs || (this.userSettings ? await this.userSettings.getPrefs() : null)
const guild = this.guild?.guild || null
return isEmbedDomainBlocked(url, {
blockedDomains: p?.embedBlockedDomains || [],
allowlistOnly: guild?.embedAllowlistOnly === true,
allowedDomains: guild?.embedAllowlistDomains || []
})
}
_checkEmbedResolveRateLimit (guildId) {
const key = guildId || 'dm'
const windowMs = 60_000
const cap = Math.max(4, Number(process.env.PEARCORD_EMBED_RESOLVE_RATE_PER_MIN) || 24)
const now = Date.now()
let bucket = this._embedResolveRateByGuild.get(key)
if (!bucket || now - bucket.resetAt > windowMs) {
bucket = { count: 0, resetAt: now }
}
if (bucket.count >= cap) return false
bucket.count++
this._embedResolveRateByGuild.set(key, bucket)
return true
}
_shouldGossipEmbedUrl (url) {
const crypto = require('hypercore-crypto')
const hash = b4a.toString(crypto.hash(b4a.from(String(url))), 'hex')
if (this._embedGossipUrlHashes.has(hash)) return false
this._embedGossipUrlHashes.add(hash)
if (this._embedGossipUrlHashes.size > 2048) {
const first = this._embedGossipUrlHashes.values().next().value
if (first) this._embedGossipUrlHashes.delete(first)
}
return true
}
_trackDmEmbedGossipBytes (conversationId, row) {
if (!conversationId || !row) return
const cap = Number(process.env.PEARCORD_DM_EMBED_GOSSIP_BYTES_CAP) || 256 * 1024
const est = JSON.stringify(row).length
const prev = this._dmEmbedGossipBytesByConversation.get(conversationId) || 0
if (prev + est > cap) return false
this._dmEmbedGossipBytesByConversation.set(conversationId, prev + est)
return true
}
/** Re-resolve embed for an existing message (read-only fetch; ignores slowmode send gates). */
async retryLinkEmbedForMessage (messageId) {
if (!this.embeds || !messageId) throw new Error('embed retry requires messageId')
@@ -17444,6 +17553,28 @@ class PearcordPlatform extends EventEmitter {
}
return
}
if (await this._embedPolicyBlocksUrl(url, prefs)) {
this.log.debug('embed.domain blocked', { spanKind: 'embed.prefetch', url, messageId: msg.id })
return
}
const guildIdForRate =
msg.guildId || (this.mode === 'dm' ? DM_GUILD_ID : this.guild?.guild?.id) || null
if (!this._checkEmbedResolveRateLimit(guildIdForRate)) {
this.log.debug('embed.rate limited', { spanKind: 'embed.prefetch', url, guildId: guildIdForRate })
return
}
if (plain.length > 8000) {
this.log.debug('embed.payload too large', { spanKind: 'embed.prefetch', messageId: msg.id, len: plain.length })
return
}
const guildIdForHook = msg.guildId || this.guild?.guild?.id || null
if (prefs?.automationEmbedResolveEnabled === false && msg.authorId && guildIdForHook && this.webhooks) {
const hook = await this.webhooks.getById(guildIdForHook, msg.authorId).catch(() => null)
if (hook) {
this.log.debug('embed.automation opt-out', { spanKind: 'embed.prefetch', messageId: msg.id })
return
}
}
if (this._embedPrefetchCoalesce.has(url)) {
return this._embedPrefetchCoalesce.get(url)
}
@@ -17481,9 +17612,11 @@ class PearcordPlatform extends EventEmitter {
const conversationId =
this.mode === 'dm' ? msg.channelId || this.activeChannelId || null : null
const fromCache = !!(await this.embeds.get(url, { conversationId }))
const acceptLanguage = (await this.userSettings?.getPrefs()?.catch(() => null))?.embedResolveLocale || null
const row = await this.embeds.resolveForContent(plain, {
force: !!opts.force,
conversationId
conversationId,
acceptLanguage
})
const latencyMs = Date.now() - started
if (msg.id) this._embedResolveLatencyByMessage.set(msg.id, latencyMs)
@@ -17496,7 +17629,7 @@ class PearcordPlatform extends EventEmitter {
latencyMs
})
let gossipSent = false
if (row && this.guild) {
if (row && this.guild && this._shouldGossipEmbedUrl(row.url || url)) {
const gossipSpan = this.log.time('embed.gossip', {
spanKind: 'embed.gossip',
messageId: msg.id,
@@ -17537,15 +17670,17 @@ class PearcordPlatform extends EventEmitter {
embedType: row?.embedType || null
})
gossipSent = true
} else if (row && this.mode === 'dm' && this.dm?.gossipLinkEmbed) {
} else if (row && this.mode === 'dm' && this.dm?.gossipLinkEmbed && this._shouldGossipEmbedUrl(row.url || url)) {
const conversationId = msg.channelId || this.activeChannelId || null
this.dm.gossipLinkEmbed({
...row,
messageId: msg.id,
channelId: conversationId,
conversationId
})
gossipSent = true
if (this._trackDmEmbedGossipBytes(conversationId, row)) {
this.dm.gossipLinkEmbed({
...row,
messageId: msg.id,
channelId: conversationId,
conversationId
})
gossipSent = true
}
}
let urlHost = null
try {
@@ -20233,6 +20368,9 @@ class PearcordPlatform extends EventEmitter {
await this.selectChannel(prevChannel)
}
if (this.guild) this.guild.gossipMessage(msg)
if (String(content ?? '').length <= 4000) {
this._queueLinkEmbed(msg).catch(() => {})
}
await this._audit('webhook.execute', {
guildId: row.guildId,
targetId: row.id,
@@ -21316,6 +21454,7 @@ class PearcordPlatform extends EventEmitter {
await this.openGroupDM({ channelId: row.channelId })
}
const msg = await this._sendPlainMessage(row.content, {})
if (msg) this._queueLinkEmbed(msg).catch(() => {})
return { msg, error: null }
} catch (error) {
return { msg: null, error }
@@ -24500,6 +24639,7 @@ class PearcordPlatform extends EventEmitter {
discoveryStats,
linkEmbedsByMessage,
embedResolveLatencyByMessage,
composerEmbedPreview: this._composerEmbedPreview || null,
threadsByParent,
threadMessageCounts,
threadLastActivity,