fix: handle Long protobuf timestamps in append recency check

Baileys can deliver messageTimestamp as a Long object (protobufjs).
Use Number(msgTsRaw) instead of typeof check to correctly convert
both number and Long values.  Add regression test for Long-like
timestamps.
This commit is contained in:
ted 2026-03-10 17:33:17 -07:00 committed by scoootscooob
parent d7ac16788e
commit 5a4563ed90
2 changed files with 156 additions and 1 deletions

View File

@ -413,7 +413,13 @@ export async function monitorWebInbox(options: {
// If this is history/offline catch-up, mark read above but skip auto-reply.
if (upsert.type === "append") {
continue;
const APPEND_RECENT_GRACE_MS = 60_000;
const msgTsRaw = msg.messageTimestamp;
const msgTsNum = msgTsRaw != null ? Number(msgTsRaw) : NaN;
const msgTsMs = Number.isFinite(msgTsNum) ? msgTsNum * 1000 : 0;
if (msgTsMs < connectedAtMs - APPEND_RECENT_GRACE_MS) {
continue;
}
}
const enriched = await enrichInboundMessage(msg);

View File

@ -0,0 +1,149 @@
import "./monitor-inbox.test-harness.js";
import { describe, expect, it, vi } from "vitest";
import { monitorWebInbox } from "./inbound.js";
import {
DEFAULT_ACCOUNT_ID,
getAuthDir,
getSock,
installWebMonitorInboxUnitTestHooks,
} from "./monitor-inbox.test-harness.js";
describe("append upsert handling (#20952)", () => {
installWebMonitorInboxUnitTestHooks();
type InboxOnMessage = NonNullable<Parameters<typeof monitorWebInbox>[0]["onMessage"]>;
async function tick() {
await new Promise((resolve) => setImmediate(resolve));
}
async function startInboxMonitor(onMessage: InboxOnMessage) {
const listener = await monitorWebInbox({
verbose: false,
onMessage,
accountId: DEFAULT_ACCOUNT_ID,
authDir: getAuthDir(),
});
return { listener, sock: getSock() };
}
it("processes recent append messages (within 60s of connect)", async () => {
const onMessage = vi.fn(async () => {});
const { listener, sock } = await startInboxMonitor(onMessage);
// Timestamp ~5 seconds ago — recent, should be processed.
const recentTs = Math.floor(Date.now() / 1000) - 5;
sock.ev.emit("messages.upsert", {
type: "append",
messages: [
{
key: { id: "recent-1", fromMe: false, remoteJid: "120363@g.us" },
message: { conversation: "hello from group" },
messageTimestamp: recentTs,
pushName: "Tester",
},
],
});
await tick();
expect(onMessage).toHaveBeenCalledTimes(1);
await listener.close();
});
it("skips stale append messages (older than 60s before connect)", async () => {
const onMessage = vi.fn(async () => {});
const { listener, sock } = await startInboxMonitor(onMessage);
// Timestamp 5 minutes ago — stale history sync, should be skipped.
const staleTs = Math.floor(Date.now() / 1000) - 300;
sock.ev.emit("messages.upsert", {
type: "append",
messages: [
{
key: { id: "stale-1", fromMe: false, remoteJid: "120363@g.us" },
message: { conversation: "old history sync" },
messageTimestamp: staleTs,
pushName: "OldTester",
},
],
});
await tick();
expect(onMessage).not.toHaveBeenCalled();
await listener.close();
});
it("skips append messages with NaN/non-finite timestamps", async () => {
const onMessage = vi.fn(async () => {});
const { listener, sock } = await startInboxMonitor(onMessage);
// NaN timestamp should be treated as 0 (stale) and skipped.
sock.ev.emit("messages.upsert", {
type: "append",
messages: [
{
key: { id: "nan-1", fromMe: false, remoteJid: "120363@g.us" },
message: { conversation: "bad timestamp" },
messageTimestamp: NaN,
pushName: "BadTs",
},
],
});
await tick();
expect(onMessage).not.toHaveBeenCalled();
await listener.close();
});
it("handles Long-like protobuf timestamps correctly", async () => {
const onMessage = vi.fn(async () => {});
const { listener, sock } = await startInboxMonitor(onMessage);
// Baileys can deliver messageTimestamp as a Long object (from protobufjs).
// Number(longObj) calls valueOf() and returns the numeric value.
const recentTs = Math.floor(Date.now() / 1000) - 5;
const longLike = { low: recentTs, high: 0, unsigned: true, valueOf: () => recentTs };
sock.ev.emit("messages.upsert", {
type: "append",
messages: [
{
key: { id: "long-1", fromMe: false, remoteJid: "120363@g.us" },
message: { conversation: "long timestamp" },
messageTimestamp: longLike,
pushName: "LongTs",
},
],
});
await tick();
expect(onMessage).toHaveBeenCalledTimes(1);
await listener.close();
});
it("always processes notify messages regardless of timestamp", async () => {
const onMessage = vi.fn(async () => {});
const { listener, sock } = await startInboxMonitor(onMessage);
// Very old timestamp but type=notify — should always be processed.
const oldTs = Math.floor(Date.now() / 1000) - 86400;
sock.ev.emit("messages.upsert", {
type: "notify",
messages: [
{
key: { id: "notify-1", fromMe: false, remoteJid: "999@s.whatsapp.net" },
message: { conversation: "normal message" },
messageTimestamp: oldTs,
pushName: "User",
},
],
});
await tick();
expect(onMessage).toHaveBeenCalledTimes(1);
await listener.close();
});
});