fix: hydrate WhatsApp participating groups on connect (#58007) (thanks @neeravmakwana)

* Web: hydrate participating groups on connect

* Web: avoid blocking inbox listeners during group hydration
This commit is contained in:
Neerav Makwana
2026-03-31 00:39:18 -04:00
committed by GitHub
parent 235908c30e
commit 763d5cea44
5 changed files with 68 additions and 0 deletions

View File

@@ -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" },

View File

@@ -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),

View File

@@ -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<Record<string, never>>((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;

View File

@@ -39,6 +39,7 @@ export type MockSock = {
sendPresenceUpdate: AnyMockFn;
sendMessage: AnyMockFn;
readMessages: AnyMockFn;
groupFetchAllParticipating: AnyMockFn;
updateMediaMessage: AnyMockFn;
logger: Record<string, unknown>;
signalRepository: {
@@ -65,6 +66,7 @@ function createMockSock(): MockSock {
sendPresenceUpdate: createResolvedMock(),
sendMessage: createResolvedMock(),
readMessages: createResolvedMock(),
groupFetchAllParticipating: vi.fn().mockResolvedValue({}),
updateMediaMessage: vi.fn(),
logger: {},
signalRepository: {

View File

@@ -21,6 +21,7 @@ export type MockBaileysSocket = {
sendPresenceUpdate: ReturnType<typeof vi.fn>;
sendMessage: ReturnType<typeof vi.fn>;
readMessages: ReturnType<typeof vi.fn>;
groupFetchAllParticipating: ReturnType<typeof vi.fn>;
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" }));