diff --git a/extensions/whatsapp/src/inbound.media.test.ts b/extensions/whatsapp/src/inbound.media.test.ts index 54cb537ac4a..392551e9703 100644 --- a/extensions/whatsapp/src/inbound.media.test.ts +++ b/extensions/whatsapp/src/inbound.media.test.ts @@ -100,6 +100,7 @@ vi.mock("./session.js", async () => { sendPresenceUpdate: vi.fn().mockResolvedValue(undefined), sendMessage: vi.fn().mockResolvedValue(undefined), readMessages: vi.fn().mockResolvedValue(undefined), + groupFetchAllParticipating: vi.fn().mockResolvedValue({}), updateMediaMessage: vi.fn(), logger: {}, user: { id: "me@s.whatsapp.net" }, diff --git a/extensions/whatsapp/src/inbound/monitor.ts b/extensions/whatsapp/src/inbound/monitor.ts index f30eb0efe88..c539fd5c6c6 100644 --- a/extensions/whatsapp/src/inbound/monitor.ts +++ b/extensions/whatsapp/src/inbound/monitor.ts @@ -531,6 +531,20 @@ export async function monitorWebInbox(options: { handleConnectionUpdate as unknown as (...args: unknown[]) => void, ); + void (async () => { + try { + const groups = await sock.groupFetchAllParticipating(); + if (shouldLogVerbose()) { + logVerbose(`Hydrated ${Object.keys(groups ?? {}).length} participating groups on connect`); + } + } catch (err) { + const error = String(err); + inboundLogger.warn({ error }, "failed hydrating participating groups on connect"); + inboundConsoleLog.warn(`Failed hydrating participating groups on connect: ${error}`); + logVerbose(`Failed to hydrate participating groups on connect: ${error}`); + } + })(); + const sendApi = createWebSendApi({ sock: { sendMessage: (jid: string, content: AnyMessageContent) => sendTrackedMessage(jid, content), diff --git a/extensions/whatsapp/src/monitor-inbox.streams-inbound-messages.test.ts b/extensions/whatsapp/src/monitor-inbox.streams-inbound-messages.test.ts index 60ecc2feb62..63b9f8c81f5 100644 --- a/extensions/whatsapp/src/monitor-inbox.streams-inbound-messages.test.ts +++ b/extensions/whatsapp/src/monitor-inbox.streams-inbound-messages.test.ts @@ -6,6 +6,7 @@ import { InboxOnMessage, buildNotifyMessageUpsert, getAuthDir, + getSock, installWebMonitorInboxUnitTestHooks, startInboxMonitor, waitForMessageCalls, @@ -126,6 +127,54 @@ describe("web monitor inbox", () => { await listener.close(); }); + it("hydrates participating groups once after connect", async () => { + const { listener, sock } = await startInboxMonitor(vi.fn(async () => {}) as InboxOnMessage); + + expect(sock.groupFetchAllParticipating).toHaveBeenCalledTimes(1); + + await listener.close(); + }); + + it("continues when group hydration fails on connect", async () => { + const sock = getSock(); + sock.groupFetchAllParticipating.mockRejectedValueOnce(new Error("no groups")); + + const { listener } = await startInboxMonitor(vi.fn(async () => {}) as InboxOnMessage); + + expect(sock.groupFetchAllParticipating).toHaveBeenCalledTimes(1); + expect(sock.sendPresenceUpdate).toHaveBeenCalledWith("available"); + + await listener.close(); + }); + + it("does not block inbound listeners while group hydration is pending", async () => { + let resolveHydration!: () => void; + const sock = getSock(); + const pendingHydration = new Promise>((resolve) => { + resolveHydration = () => resolve({}); + }); + sock.groupFetchAllParticipating.mockImplementationOnce(() => pendingHydration); + const onMessage = vi.fn(async () => { + return; + }); + + const { listener } = await startInboxMonitor(onMessage as InboxOnMessage); + sock.ev.emit( + "messages.upsert", + buildNotifyMessageUpsert({ + id: nextMessageId("pending-hydration"), + remoteJid: "999@s.whatsapp.net", + text: "ping", + timestamp: 1_700_000_000, + pushName: "Tester", + }), + ); + await waitForMessageCalls(onMessage, 1); + + resolveHydration(); + await listener.close(); + }); + it("deduplicates redelivered messages by id", async () => { const onMessage = vi.fn(async () => { return; diff --git a/extensions/whatsapp/src/monitor-inbox.test-harness.ts b/extensions/whatsapp/src/monitor-inbox.test-harness.ts index e53e69aa2f3..831d6be5fb8 100644 --- a/extensions/whatsapp/src/monitor-inbox.test-harness.ts +++ b/extensions/whatsapp/src/monitor-inbox.test-harness.ts @@ -39,6 +39,7 @@ export type MockSock = { sendPresenceUpdate: AnyMockFn; sendMessage: AnyMockFn; readMessages: AnyMockFn; + groupFetchAllParticipating: AnyMockFn; updateMediaMessage: AnyMockFn; logger: Record; signalRepository: { @@ -65,6 +66,7 @@ function createMockSock(): MockSock { sendPresenceUpdate: createResolvedMock(), sendMessage: createResolvedMock(), readMessages: createResolvedMock(), + groupFetchAllParticipating: vi.fn().mockResolvedValue({}), updateMediaMessage: vi.fn(), logger: {}, signalRepository: { diff --git a/test/mocks/baileys.ts b/test/mocks/baileys.ts index e88dfbcb261..85e7b45b06e 100644 --- a/test/mocks/baileys.ts +++ b/test/mocks/baileys.ts @@ -21,6 +21,7 @@ export type MockBaileysSocket = { sendPresenceUpdate: ReturnType; sendMessage: ReturnType; readMessages: ReturnType; + groupFetchAllParticipating: ReturnType; user?: { id?: string }; }; @@ -138,6 +139,7 @@ export function createMockBaileys(): { sendPresenceUpdate: vi.fn().mockResolvedValue(undefined), sendMessage: vi.fn().mockResolvedValue({ key: { id: "msg123" } }), readMessages: vi.fn().mockResolvedValue(undefined), + groupFetchAllParticipating: vi.fn().mockResolvedValue({}), user: { id: "123@s.whatsapp.net" }, }; setImmediate(() => ev.emit("connection.update", { connection: "open" }));