fix(whatsapp): preserve BR variants across self-chat and heartbeat

This commit is contained in:
Thiago Roque Ragazzo 2026-03-20 13:54:23 -03:00
parent 43ae3bc489
commit 5247c29331
6 changed files with 60 additions and 10 deletions

View File

@ -510,5 +510,10 @@ describe("web processMessage inbound context", () => {
await processMessage(args);
expect(updateLastRouteMock).toHaveBeenCalledTimes(1);
expect(updateLastRouteMock).toHaveBeenCalledWith(
expect.objectContaining({
to: "+553598627740",
}),
);
});
});

View File

@ -344,17 +344,18 @@ export async function processMessage(params: {
cfg: params.cfg,
msg: params.msg,
});
const shouldUpdateMainLastRoute =
!pinnedMainDmRecipient ||
(dmRouteTarget
? areEquivalentWhatsAppDirectTargets(pinnedMainDmRecipient, dmRouteTarget)
: false);
const canonicalMainLastRouteTarget = pinnedMainDmRecipient
? dmRouteTarget && areEquivalentWhatsAppDirectTargets(pinnedMainDmRecipient, dmRouteTarget)
? pinnedMainDmRecipient
: null
: dmRouteTarget;
const shouldUpdateMainLastRoute = Boolean(canonicalMainLastRouteTarget);
const inboundLastRouteSessionKey = resolveInboundLastRouteSessionKey({
route: params.route,
sessionKey: params.route.sessionKey,
});
if (
dmRouteTarget &&
canonicalMainLastRouteTarget &&
inboundLastRouteSessionKey === params.route.mainSessionKey &&
shouldUpdateMainLastRoute
) {
@ -364,7 +365,7 @@ export async function processMessage(params: {
storeAgentId: params.route.agentId,
sessionKey: params.route.mainSessionKey,
channel: "whatsapp",
to: dmRouteTarget,
to: canonicalMainLastRouteTarget,
accountId: params.route.accountId,
ctx: ctxPayload,
warn: params.replyLogger.warn.bind(params.replyLogger),

View File

@ -158,6 +158,33 @@ describe("WhatsApp dmPolicy precedence", () => {
expect(sendMessageMock).not.toHaveBeenCalled();
});
it("treats BR ninth-digit variants as the same self-chat phone", async () => {
setAccessControlTestConfig({
channels: {
whatsapp: {
dmPolicy: "pairing",
allowFrom: ["+5511988887777"],
},
},
});
const result = await checkInboundAccessControl({
accountId: "default",
from: "+5535998627740",
selfE164: "+553598627740",
senderE164: "+5535998627740",
group: false,
pushName: "Owner",
isFromMe: false,
sock: { sendMessage: sendMessageMock },
remoteJid: "5535998627740@s.whatsapp.net",
});
expect(result.allowed).toBe(true);
expect(upsertPairingRequestMock).not.toHaveBeenCalled();
expect(sendMessageMock).not.toHaveBeenCalled();
});
it("allows BR contacts when allowFrom and sender differ only by the ninth digit", async () => {
setAccessControlTestConfig({
channels: {

View File

@ -73,7 +73,9 @@ export async function checkInboundAccessControl(params: {
const dmAllowFrom = configuredAllowFrom.length > 0 ? configuredAllowFrom : defaultAllowFrom;
const groupAllowFrom =
account.groupAllowFrom ?? (configuredAllowFrom.length > 0 ? configuredAllowFrom : undefined);
const isSamePhone = params.from === params.selfE164;
const isSamePhone = params.selfE164
? areEquivalentWhatsAppDirectTargets(params.from, params.selfE164)
: false;
const isSelfChat = account.selfChatMode ?? isSelfChatMode(params.selfE164, configuredAllowFrom);
const pairingGraceMs =
typeof params.pairingGraceMs === "number" && params.pairingGraceMs > 0

View File

@ -66,6 +66,17 @@ describe("resolveWhatsAppHeartbeatRecipients", () => {
expect(result).toEqual({ recipients: ["+15550000001"], source: "session-single" });
});
it("treats BR ninth-digit variants as authorized session recipients", () => {
setSessionStore({
a: { lastChannel: "whatsapp", lastTo: "+5535998627740", updatedAt: 2, sessionId: "a" },
});
setAllowFromStore(["+553598627740"]);
const result = resolveWith();
expect(result).toEqual({ recipients: ["+5535998627740"], source: "session-single" });
});
it("falls back to allowFrom when no session recipient is authorized", () => {
setSingleUnauthorizedSessionWithAllowFrom();

View File

@ -3,6 +3,7 @@ import { loadSessionStore, resolveStorePath } from "../../config/sessions.js";
import { readChannelAllowFromStoreSync } from "../../pairing/pairing-store.js";
import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js";
import { normalizeE164 } from "../../utils.js";
import { areEquivalentWhatsAppDirectTargets } from "../../whatsapp/normalize.js";
import { normalizeChatChannelId } from "../registry.js";
type HeartbeatRecipientsResult = { recipients: string[]; source: string };
@ -72,10 +73,13 @@ export function resolveWhatsAppHeartbeatRecipients(
}
if (allowFrom.length > 0) {
const allowSet = new Set(allowFrom);
const authorizedSessionRecipients = sessionRecipients
.map((entry) => entry.to)
.filter((recipient) => allowSet.has(recipient));
.filter((recipient) =>
allowFrom.some((allowedRecipient) =>
areEquivalentWhatsAppDirectTargets(allowedRecipient, recipient),
),
);
if (authorizedSessionRecipients.length === 1) {
return { recipients: [authorizedSessionRecipients[0]], source: "session-single" };
}