WhatsApp: guard main DM last-route to single owner
This commit is contained in:
committed by
Peter Steinberger
parent
f534ea9906
commit
ab0b2c21f3
@@ -344,4 +344,76 @@ describe("web processMessage inbound contract", () => {
|
||||
|
||||
expect(updateLastRouteMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not update main last route for non-owner sender when main DM scope is pinned", async () => {
|
||||
const updateLastRouteMock = vi.mocked(updateLastRouteInBackground);
|
||||
updateLastRouteMock.mockClear();
|
||||
|
||||
const args = makeProcessMessageArgs({
|
||||
routeSessionKey: "agent:main:main",
|
||||
groupHistoryKey: "+3000",
|
||||
cfg: {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
},
|
||||
messages: {},
|
||||
session: { store: sessionStorePath, dmScope: "main" },
|
||||
} as unknown as ReturnType<typeof import("../../../config/config.js").loadConfig>,
|
||||
msg: {
|
||||
id: "msg-last-route-3",
|
||||
from: "+3000",
|
||||
to: "+2000",
|
||||
chatType: "direct",
|
||||
body: "hello",
|
||||
senderE164: "+3000",
|
||||
},
|
||||
});
|
||||
args.route = {
|
||||
...args.route,
|
||||
sessionKey: "agent:main:main",
|
||||
mainSessionKey: "agent:main:main",
|
||||
};
|
||||
|
||||
await processMessage(args);
|
||||
|
||||
expect(updateLastRouteMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates main last route for owner sender when main DM scope is pinned", async () => {
|
||||
const updateLastRouteMock = vi.mocked(updateLastRouteInBackground);
|
||||
updateLastRouteMock.mockClear();
|
||||
|
||||
const args = makeProcessMessageArgs({
|
||||
routeSessionKey: "agent:main:main",
|
||||
groupHistoryKey: "+1000",
|
||||
cfg: {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
},
|
||||
messages: {},
|
||||
session: { store: sessionStorePath, dmScope: "main" },
|
||||
} as unknown as ReturnType<typeof import("../../../config/config.js").loadConfig>,
|
||||
msg: {
|
||||
id: "msg-last-route-4",
|
||||
from: "+1000",
|
||||
to: "+2000",
|
||||
chatType: "direct",
|
||||
body: "hello",
|
||||
senderE164: "+1000",
|
||||
},
|
||||
});
|
||||
args.route = {
|
||||
...args.route,
|
||||
sessionKey: "agent:main:main",
|
||||
mainSessionKey: "agent:main:main",
|
||||
};
|
||||
|
||||
await processMessage(args);
|
||||
|
||||
expect(updateLastRouteMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,6 +107,28 @@ async function resolveWhatsAppCommandAuthorized(params: {
|
||||
return access.commandAuthorized;
|
||||
}
|
||||
|
||||
function resolvePinnedMainDmRecipient(params: {
|
||||
cfg: ReturnType<typeof loadConfig>;
|
||||
msg: WebInboundMsg;
|
||||
}): string | null {
|
||||
if ((params.cfg.session?.dmScope ?? "main") !== "main") {
|
||||
return null;
|
||||
}
|
||||
const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.msg.accountId });
|
||||
const rawAllowFrom = account.allowFrom ?? [];
|
||||
if (rawAllowFrom.includes("*")) {
|
||||
return null;
|
||||
}
|
||||
const normalizedOwners = Array.from(
|
||||
new Set(
|
||||
rawAllowFrom
|
||||
.map((entry) => normalizeE164(String(entry)))
|
||||
.filter((entry): entry is string => Boolean(entry)),
|
||||
),
|
||||
);
|
||||
return normalizedOwners.length === 1 ? normalizedOwners[0] : null;
|
||||
}
|
||||
|
||||
export async function processMessage(params: {
|
||||
cfg: ReturnType<typeof loadConfig>;
|
||||
msg: WebInboundMsg;
|
||||
@@ -320,7 +342,17 @@ export async function processMessage(params: {
|
||||
// Only update main session's lastRoute when DM actually IS the main session.
|
||||
// When dmScope="per-channel-peer", the DM uses an isolated sessionKey,
|
||||
// and updating mainSessionKey would corrupt routing for the session owner.
|
||||
if (dmRouteTarget && params.route.sessionKey === params.route.mainSessionKey) {
|
||||
const pinnedMainDmRecipient = resolvePinnedMainDmRecipient({
|
||||
cfg: params.cfg,
|
||||
msg: params.msg,
|
||||
});
|
||||
const shouldUpdateMainLastRoute =
|
||||
!pinnedMainDmRecipient || pinnedMainDmRecipient === dmRouteTarget;
|
||||
if (
|
||||
dmRouteTarget &&
|
||||
params.route.sessionKey === params.route.mainSessionKey &&
|
||||
shouldUpdateMainLastRoute
|
||||
) {
|
||||
updateLastRouteInBackground({
|
||||
cfg: params.cfg,
|
||||
backgroundTasks: params.backgroundTasks,
|
||||
@@ -332,6 +364,14 @@ export async function processMessage(params: {
|
||||
ctx: ctxPayload,
|
||||
warn: params.replyLogger.warn.bind(params.replyLogger),
|
||||
});
|
||||
} else if (
|
||||
dmRouteTarget &&
|
||||
params.route.sessionKey === params.route.mainSessionKey &&
|
||||
pinnedMainDmRecipient
|
||||
) {
|
||||
logVerbose(
|
||||
`Skipping main-session last route update for ${dmRouteTarget} (pinned owner ${pinnedMainDmRecipient})`,
|
||||
);
|
||||
}
|
||||
|
||||
const metaTask = recordSessionMetaFromInbound({
|
||||
|
||||
Reference in New Issue
Block a user