web: reuse active listener for sends

main
Peter Steinberger 2025-12-09 20:33:50 +00:00
parent f34b238713
commit 5a8d18edf3
3 changed files with 59 additions and 15 deletions

View File

@ -0,0 +1,20 @@
export type ActiveWebListener = {
sendMessage: (
to: string,
text: string,
mediaBuffer?: Buffer,
mediaType?: string,
) => Promise<{ messageId: string }>;
sendComposingTo: (to: string) => Promise<void>;
close?: () => Promise<void>;
};
let currentListener: ActiveWebListener | null = null;
export function setActiveWebListener(listener: ActiveWebListener | null) {
currentListener = listener;
}
export function getActiveWebListener(): ActiveWebListener | null {
return currentListener;
}

View File

@ -21,6 +21,7 @@ import { jidToE164, normalizeE164 } from "../utils.js";
import { monitorWebInbox } from "./inbound.js"; import { monitorWebInbox } from "./inbound.js";
import { loadWebMedia } from "./media.js"; import { loadWebMedia } from "./media.js";
import { sendMessageWhatsApp } from "./outbound.js"; import { sendMessageWhatsApp } from "./outbound.js";
import { setActiveWebListener } from "./active-listener.js";
import { import {
computeBackoff, computeBackoff,
newConnectionId, newConnectionId,
@ -1016,7 +1017,10 @@ export async function monitorWebProvider(
`WhatsApp gateway connected${selfE164 ? ` as ${selfE164}` : ""}.`, `WhatsApp gateway connected${selfE164 ? ` as ${selfE164}` : ""}.`,
); );
setActiveWebListener(listener);
const closeListener = async () => { const closeListener = async () => {
setActiveWebListener(null);
if (heartbeat) clearInterval(heartbeat); if (heartbeat) clearInterval(heartbeat);
if (replyHeartbeatTimer) clearInterval(replyHeartbeatTimer); if (replyHeartbeatTimer) clearInterval(replyHeartbeatTimer);
if (watchdogTimer) clearInterval(watchdogTimer); if (watchdogTimer) clearInterval(watchdogTimer);

View File

@ -7,6 +7,7 @@ import { logInfo } from "../logger.js";
import { getChildLogger } from "../logging.js"; import { getChildLogger } from "../logging.js";
import { toWhatsappJid } from "../utils.js"; import { toWhatsappJid } from "../utils.js";
import { loadWebMedia } from "./media.js"; import { loadWebMedia } from "./media.js";
import { getActiveWebListener } from "./active-listener.js";
import { createWaSocket, waitForWaConnection } from "./session.js"; import { createWaSocket, waitForWaConnection } from "./session.js";
export async function sendMessageWhatsApp( export async function sendMessageWhatsApp(
@ -15,22 +16,25 @@ export async function sendMessageWhatsApp(
options: { verbose: boolean; mediaUrl?: string }, options: { verbose: boolean; mediaUrl?: string },
): Promise<{ messageId: string; toJid: string }> { ): Promise<{ messageId: string; toJid: string }> {
const correlationId = randomUUID(); const correlationId = randomUUID();
const sock = await createWaSocket(false, options.verbose); const active = getActiveWebListener();
const usingActive = Boolean(active);
const sock = usingActive ? null : await createWaSocket(false, options.verbose);
const logger = getChildLogger({ const logger = getChildLogger({
module: "web-outbound", module: "web-outbound",
correlationId, correlationId,
to, to,
}); });
try { try {
logInfo("🔌 Connecting to WhatsApp Web…");
logger.info("connecting to whatsapp web");
await waitForWaConnection(sock);
// waitForWaConnection sets up listeners and error handling; keep the presence update safe.
const jid = toWhatsappJid(to); const jid = toWhatsappJid(to);
try { if (!usingActive) {
await sock.sendPresenceUpdate("composing", jid); logInfo("🔌 Connecting to WhatsApp Web…");
} catch (err) { logger.info("connecting to whatsapp web");
logVerbose(`Presence update skipped: ${String(err)}`); await waitForWaConnection(sock!);
try {
await sock!.sendPresenceUpdate("composing", jid);
} catch (err) {
logVerbose(`Presence update skipped: ${String(err)}`);
}
} }
let payload: AnyMessageContent = { text: body }; let payload: AnyMessageContent = { text: body };
if (options.mediaUrl) { if (options.mediaUrl) {
@ -76,18 +80,34 @@ export async function sendMessageWhatsApp(
{ jid, hasMedia: Boolean(options.mediaUrl) }, { jid, hasMedia: Boolean(options.mediaUrl) },
"sending message", "sending message",
); );
const result = await sock.sendMessage(jid, payload); const result = usingActive
const messageId = result?.key?.id ?? "unknown"; ? await (async () => {
let mediaBuffer: Buffer | undefined;
let mediaType: string | undefined;
if (options.mediaUrl) {
const media = await loadWebMedia(options.mediaUrl);
mediaBuffer = media.buffer;
mediaType = media.contentType;
}
await active!.sendComposingTo(to);
return active!.sendMessage(to, body, mediaBuffer, mediaType);
})()
: await sock!.sendMessage(jid, payload);
const messageId = usingActive
? (result as { messageId?: string })?.messageId ?? "unknown"
: (result as any)?.key?.id ?? "unknown";
logInfo( logInfo(
`✅ Sent via web session. Message ID: ${messageId} -> ${jid}${options.mediaUrl ? " (media)" : ""}`, `✅ Sent via web session. Message ID: ${messageId} -> ${jid}${options.mediaUrl ? " (media)" : ""}`,
); );
logger.info({ jid, messageId }, "sent message"); logger.info({ jid, messageId }, "sent message");
return { messageId, toJid: jid }; return { messageId, toJid: jid };
} finally { } finally {
try { if (!usingActive) {
sock.ws?.close(); try {
} catch (err) { sock?.ws?.close();
logVerbose(`Socket close failed: ${String(err)}`); } catch (err) {
logVerbose(`Socket close failed: ${String(err)}`);
}
} }
} }
} }