
























@@ -54,6 +54,7 @@ import type { WebInboundMessage, WebListenerCloseReason } from "./types.js";
5454const LOGGED_OUT_STATUS = DisconnectReason?.loggedOut ?? 401;
5555const RECONNECT_IN_PROGRESS_ERROR = "no active socket - reconnection in progress";
5656const GROUP_META_TTL_MS = 5 * 60 * 1000; // 5 minutes
57+const INBOUND_CLOSE_DRAIN_TIMEOUT_MS = 5_000;
5758export const WHATSAPP_GROUP_METADATA_CACHE_MAX_ENTRIES = 500;
58595960type WhatsAppGroupMetadataCacheEntry = {
@@ -223,7 +224,29 @@ export async function attachWebInboxToSocket(
223224const self = selfIdentity.identity;
224225type QueuedInboundMessage = WebInboundMessage & {
225226dedupeKey?: string;
227+debounceKey?: string;
226228};
229+const inboundDebounceMs = Math.max(0, Math.trunc(options.debounceMs ?? 0));
230+const pendingDebounceKeys = new Set<string>();
231+const activeInboundFlushes = new Set<Promise<void>>();
232+const buildInboundDebounceKey = (msg: WebInboundMessage): string | null => {
233+const sender = msg.sender;
234+const senderKey =
235+msg.chatType === "group"
236+ ? (getPrimaryIdentityId(sender ?? null) ??
237+msg.senderJid ??
238+msg.senderE164 ??
239+msg.senderName ??
240+msg.from)
241+ : msg.from;
242+if (!senderKey) {
243+return null;
244+}
245+const conversationKey = msg.chatType === "group" ? msg.chatId : msg.from;
246+return `${msg.accountId}:${conversationKey}:${senderKey}`;
247+};
248+const shouldDebounceInboundMessage = (msg: WebInboundMessage): boolean =>
249+options.shouldDebounce?.(msg) ?? true;
227250228251const finalizeInboundDedupe = async (
229252entries: QueuedInboundMessage[],
@@ -243,57 +266,57 @@ export async function attachWebInboxToSocket(
243266};
244267245268const debouncer = createInboundDebouncer<QueuedInboundMessage>({
246-debounceMs: options.debounceMs ?? 0,
247-buildKey: (msg) => {
248-const sender = msg.sender;
249-const senderKey =
250-msg.chatType === "group"
251- ? (getPrimaryIdentityId(sender ?? null) ??
252-msg.senderJid ??
253-msg.senderE164 ??
254-msg.senderName ??
255-msg.from)
256- : msg.from;
257-if (!senderKey) {
258-return null;
259-}
260-const conversationKey = msg.chatType === "group" ? msg.chatId : msg.from;
261-return `${msg.accountId}:${conversationKey}:${senderKey}`;
262-},
263-shouldDebounce: options.shouldDebounce,
269+debounceMs: inboundDebounceMs,
270+buildKey: (msg) => msg.debounceKey ?? buildInboundDebounceKey(msg),
271+shouldDebounce: shouldDebounceInboundMessage,
264272onFlush: async (entries) => {
265-const last = entries.at(-1);
266-if (!last) {
267-return;
268-}
273+let finishFlush!: () => void;
274+const flushTask = new Promise<void>((resolve) => {
275+finishFlush = resolve;
276+});
277+activeInboundFlushes.add(flushTask);
269278try {
270-if (entries.length === 1) {
271-await options.onMessage(last);
272-await finalizeInboundDedupe(entries);
279+const last = entries.at(-1);
280+if (!last) {
273281return;
274282}
275-const mentioned = new Set<string>();
283+try {
284+if (entries.length === 1) {
285+await options.onMessage(last);
286+await finalizeInboundDedupe(entries);
287+return;
288+}
289+const mentioned = new Set<string>();
290+for (const entry of entries) {
291+for (const jid of entry.mentions ?? entry.mentionedJids ?? []) {
292+mentioned.add(jid);
293+}
294+}
295+const combinedBody = entries
296+.map((entry) => entry.body)
297+.filter(Boolean)
298+.join("\n");
299+const combinedMessage: WebInboundMessage = {
300+ ...last,
301+body: combinedBody,
302+mentions: mentioned.size > 0 ? Array.from(mentioned) : undefined,
303+mentionedJids: mentioned.size > 0 ? Array.from(mentioned) : undefined,
304+isBatched: true,
305+};
306+await options.onMessage(combinedMessage);
307+await finalizeInboundDedupe(entries);
308+} catch (error) {
309+await finalizeInboundDedupe(entries, error);
310+throw error;
311+}
312+} finally {
276313for (const entry of entries) {
277-for (const jid of entry.mentions ?? entry.mentionedJids ?? []) {
278-mentioned.add(jid);
314+if (entry.debounceKey) {
315+pendingDebounceKeys.delete(entry.debounceKey);
279316}
280317}
281-const combinedBody = entries
282-.map((entry) => entry.body)
283-.filter(Boolean)
284-.join("\n");
285-const combinedMessage: WebInboundMessage = {
286- ...last,
287-body: combinedBody,
288-mentions: mentioned.size > 0 ? Array.from(mentioned) : undefined,
289-mentionedJids: mentioned.size > 0 ? Array.from(mentioned) : undefined,
290-isBatched: true,
291-};
292-await options.onMessage(combinedMessage);
293-await finalizeInboundDedupe(entries);
294-} catch (error) {
295-await finalizeInboundDedupe(entries, error);
296-throw error;
318+activeInboundFlushes.delete(flushTask);
319+finishFlush();
297320}
298321},
299322onError: (err) => {
@@ -783,6 +806,13 @@ export async function attachWebInboxToSocket(
783806mediaFileName: enriched.mediaFileName,
784807dedupeKey: inbound.id ? `${options.accountId}:${inbound.remoteJid}:${inbound.id}` : undefined,
785808};
809+const debounceKey = buildInboundDebounceKey(inboundMessage);
810+if (debounceKey) {
811+inboundMessage.debounceKey = debounceKey;
812+if (inboundDebounceMs > 0 && shouldDebounceInboundMessage(inboundMessage)) {
813+pendingDebounceKeys.add(debounceKey);
814+}
815+}
786816if (inboundMessage.id) {
787817cacheInboundMessageMeta(inboundMessage.accountId, inboundMessage.chatId, inboundMessage.id, {
788818participant: inboundMessage.senderJid,
@@ -804,6 +834,7 @@ export async function attachWebInboxToSocket(
804834}
805835};
806836837+const pendingMessageHandlers = new Set<Promise<void>>();
807838const handleMessagesUpsert = async (upsert: { type?: string; messages?: Array<WAMessage> }) => {
808839if (upsert.type !== "notify" && upsert.type !== "append") {
809840return;
@@ -841,6 +872,62 @@ export async function attachWebInboxToSocket(
841872await enqueueInboundMessage(msg, inbound, enriched);
842873}
843874};
875+const handleMessagesUpsertEvent = (upsert: { type?: string; messages?: Array<WAMessage> }) => {
876+const task = handleMessagesUpsert(upsert).catch((err) => {
877+inboundLogger.error({ error: String(err) }, "messages.upsert handler error");
878+inboundConsoleLog.error(`Messages upsert handler error: ${String(err)}`);
879+});
880+pendingMessageHandlers.add(task);
881+void task.finally(() => {
882+pendingMessageHandlers.delete(task);
883+});
884+};
885+const waitForPendingMessageHandlers = async () => {
886+while (pendingMessageHandlers.size > 0) {
887+await Promise.all(Array.from(pendingMessageHandlers));
888+}
889+};
890+const drainDebouncedInboundMessages = async () => {
891+while (pendingDebounceKeys.size > 0 || activeInboundFlushes.size > 0) {
892+const debounceKeys = Array.from(pendingDebounceKeys);
893+if (debounceKeys.length > 0) {
894+await Promise.all(debounceKeys.map((key) => debouncer.flushKey(key)));
895+}
896+897+const flushes = Array.from(activeInboundFlushes);
898+if (flushes.length > 0) {
899+await Promise.allSettled(flushes);
900+}
901+902+await Promise.resolve();
903+}
904+};
905+const drainInboundBeforeSocketClose = async () => {
906+await waitForPendingMessageHandlers();
907+await drainDebouncedInboundMessages();
908+};
909+const drainInboundBeforeSocketCloseWithTimeout = async () => {
910+let timeout: ReturnType<typeof setTimeout> | null = null;
911+try {
912+await Promise.race([
913+drainInboundBeforeSocketClose(),
914+new Promise<void>((_, reject) => {
915+timeout = setTimeout(() => {
916+reject(
917+new Error(
918+`Timed out draining WhatsApp inbound debounce after ${INBOUND_CLOSE_DRAIN_TIMEOUT_MS}ms`,
919+),
920+);
921+}, INBOUND_CLOSE_DRAIN_TIMEOUT_MS);
922+timeout.unref?.();
923+}),
924+]);
925+} finally {
926+if (timeout) {
927+clearTimeout(timeout);
928+}
929+}
930+};
844931const handleConnectionUpdate = (update: Partial<import("baileys").ConnectionState>) => {
845932try {
846933if (update.connection === "close") {
@@ -866,7 +953,7 @@ export async function attachWebInboxToSocket(
866953removeListener?: (event: string, listener: (...args: unknown[]) => void) => void;
867954},
868955"messages.upsert",
869-handleMessagesUpsert as unknown as (...args: unknown[]) => void,
956+handleMessagesUpsertEvent as unknown as (...args: unknown[]) => void,
870957);
871958const detachConnectionUpdate = attachEmitterListener(
872959sock.ev as unknown as {
@@ -930,6 +1017,11 @@ export async function attachWebInboxToSocket(
9301017try {
9311018detachMessagesUpsert();
9321019detachConnectionUpdate();
1020+await drainInboundBeforeSocketCloseWithTimeout();
1021+} catch (err) {
1022+logWhatsAppVerbose(options.verbose, `Inbound close drain failed: ${String(err)}`);
1023+}
1024+try {
9331025closeInboundMonitorSocket(sock);
9341026} catch (err) {
9351027logWhatsAppVerbose(options.verbose, `Socket close failed: ${String(err)}`);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。