fix(whatsapp): prevent merged lid conversations from returning

This commit is contained in:
wmq
2026-08-03 01:35:44 +08:00
parent 9017c26f5b
commit 48d719e520
4 changed files with 59 additions and 5 deletions
+19 -4
View File
@@ -33,6 +33,7 @@ import { isTimestampOlderThan, webhookEventClaimDecision } from './reliability.j
import {
canonicalWhatsAppJid,
isWeakWhatsAppDisplayName,
resolvedWhatsAppJid,
reusableWhatsAppConversations,
whatsappConversationActivityAt,
whatsappCustomerStableId,
@@ -3231,7 +3232,9 @@ async function upsertWhatsAppMessage(tenantId, channel, rawMessage, options = {}
if (!rawJid || !rawMessage.id) return null
const aliasCacheKey = `${channel.gatewayAccountId}:${rawJid}`
const cachedJid = whatsappCanonicalJidCache.get(aliasCacheKey)
const preliminaryJid = cachedJid || rawJid
const rawCanonicalJid = canonicalWhatsAppJid(rawJid)
const hasCanonicalAlias = Boolean(cachedJid && cachedJid !== rawCanonicalJid)
const preliminaryJid = hasCanonicalAlias ? cachedJid : rawCanonicalJid
const rawConversationId = `${channel.id}:${rawJid}`
const preliminaryConversationId = `${channel.id}:${preliminaryJid}`
const preliminaryCustomer = await resolveWhatsAppCustomer(
@@ -3242,7 +3245,7 @@ async function upsertWhatsAppMessage(tenantId, channel, rawMessage, options = {}
)
const needsIdentity = Boolean(
options.identity
|| (rawJid.endsWith('@lid') && !cachedJid)
|| (rawJid.endsWith('@lid') && !hasCanonicalAlias)
|| !preliminaryCustomer
|| isWeakWhatsAppDisplayName(preliminaryCustomer?.name, rawJid),
)
@@ -3256,8 +3259,15 @@ async function upsertWhatsAppMessage(tenantId, channel, rawMessage, options = {}
avatarUrl: preliminaryCustomer.avatarUrl,
chatType: preliminaryCustomer.whatsappChatType,
})
const jid = cachedJid || canonicalWhatsAppJid(rawJid, identity || {})
whatsappCanonicalJidCache.set(aliasCacheKey, jid)
const jid = resolvedWhatsAppJid(rawJid, identity || {}, cachedJid)
// A LID is a temporary alias, not a stable customer identity. Never cache an
// unresolved LID as canonical; otherwise a later sync can recreate a merged
// duplicate conversation even after the gateway learns its phone JID.
if (jid !== rawCanonicalJid || !rawJid.endsWith('@lid')) {
whatsappCanonicalJidCache.set(aliasCacheKey, jid)
} else {
whatsappCanonicalJidCache.delete(aliasCacheKey)
}
const conversationId = `${channel.id}:${jid}`
if (
await getResource(tenantId, 'conversation-tombstones', conversationId)
@@ -3478,6 +3488,11 @@ async function mergeLegacyWhatsAppConversations(tenantId, channel, jid, aliasJid
await deleteResource(tenantId, `messages:${source.id}`, message.id)
}
await deleteResource(tenantId, 'conversations', source.id)
broadcast(tenantId, {
type: 'conversation.deleted',
conversationId: source.id,
mergedIntoConversationId: targetId,
})
}
await patchResource(tenantId, 'conversations', targetId, {
customerId,
+9
View File
@@ -13,6 +13,15 @@ export function canonicalWhatsAppJid(jid, identity = {}) {
return phone ? `${phone}@s.whatsapp.net` : raw
}
export function resolvedWhatsAppJid(jid, identity = {}, cachedJid = '') {
const fallback = canonicalWhatsAppJid(jid)
const resolved = canonicalWhatsAppJid(jid, identity)
if (resolved && resolved !== fallback) return resolved
const cached = canonicalWhatsAppJid(cachedJid)
return cached && cached !== fallback ? cached : fallback
}
export function whatsappCustomerStableId(jid) {
const normalized = canonicalWhatsAppJid(jid)
const hash = crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 24)