fix(whatsapp): prevent merged lid conversations from returning
This commit is contained in:
+19
-4
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -3,6 +3,7 @@ import test from 'node:test'
|
||||
import {
|
||||
canonicalWhatsAppJid,
|
||||
isWeakWhatsAppDisplayName,
|
||||
resolvedWhatsAppJid,
|
||||
reusableWhatsAppConversations,
|
||||
whatsappCustomerStableId,
|
||||
} from '../src/whatsapp-identity.js'
|
||||
@@ -16,6 +17,14 @@ test('WhatsApp LID resolves to the same canonical phone identity', () => {
|
||||
assert.equal(canonicalWhatsAppJid('987654321@lid', { canonicalJid: '15551234567:0@s.whatsapp.net' }), phoneJid)
|
||||
})
|
||||
|
||||
test('a newly resolved phone identity overrides a stale cached LID', () => {
|
||||
const lid = '987654321@lid'
|
||||
const phoneJid = '15551234567@s.whatsapp.net'
|
||||
assert.equal(resolvedWhatsAppJid(lid, {}, lid), lid)
|
||||
assert.equal(resolvedWhatsAppJid(lid, { phone: '+1 555 123 4567' }, lid), phoneJid)
|
||||
assert.equal(resolvedWhatsAppJid(lid, {}, phoneJid), phoneJid)
|
||||
})
|
||||
|
||||
test('WhatsApp customer id remains stable after gateway reauthorization', () => {
|
||||
assert.equal(
|
||||
whatsappCustomerStableId('15551234567@s.whatsapp.net'),
|
||||
|
||||
@@ -300,6 +300,27 @@ export function InboxPage() {
|
||||
backfill?: import('@/types').AiBackfillStatus
|
||||
}
|
||||
try { event = JSON.parse(String(raw.data)) } catch { return }
|
||||
if (event.type === 'conversation.deleted' && event.conversationId) {
|
||||
queryClient.removeQueries({ queryKey: ['conversation', event.conversationId] })
|
||||
queryClient.removeQueries({ queryKey: ['messages', event.conversationId] })
|
||||
queryClient.setQueriesData<{ list: ConversationListItem[]; total: number }>(
|
||||
{ queryKey: ['conversations'] },
|
||||
(current) => current
|
||||
? {
|
||||
...current,
|
||||
list: current.list.filter((item) => item.id !== event.conversationId),
|
||||
total: Math.max(0, current.total - (current.list.some((item) => item.id === event.conversationId) ? 1 : 0)),
|
||||
}
|
||||
: current,
|
||||
)
|
||||
if (convId === event.conversationId) {
|
||||
setSearchParams((prev) => {
|
||||
prev.delete('c')
|
||||
return prev
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
const activeConversationCache = queryClient.getQueryData<{ list: ConversationListItem[] }>(
|
||||
activeConversationQueryKeyRef.current,
|
||||
)
|
||||
@@ -396,7 +417,7 @@ export function InboxPage() {
|
||||
if (reconnectTimer) clearTimeout(reconnectTimer)
|
||||
socket?.close()
|
||||
}
|
||||
}, [token, queryClient])
|
||||
}, [token, queryClient, convId, setSearchParams])
|
||||
|
||||
const setConv = useCallback(
|
||||
(id: string) => {
|
||||
|
||||
Reference in New Issue
Block a user