From eaa546a8b31cf33ff9080349839d9ef56ff7e52e Mon Sep 17 00:00:00 2001 From: Tseka Luk Date: Sat, 14 Feb 2026 01:06:41 +0800 Subject: [PATCH] fix(whatsapp): preserve outbound document filenames --- src/web/active-listener.ts | 1 + src/web/inbound/send-api.test.ts | 49 ++++++++++++++++++++++++++++++++ src/web/inbound/send-api.ts | 3 +- src/web/outbound.test.ts | 4 ++- src/web/outbound.ts | 5 +++- 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/web/inbound/send-api.test.ts diff --git a/src/web/active-listener.ts b/src/web/active-listener.ts index 81170d3084f..0cb48ab405e 100644 --- a/src/web/active-listener.ts +++ b/src/web/active-listener.ts @@ -5,6 +5,7 @@ import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js"; export type ActiveWebSendOptions = { gifPlayback?: boolean; accountId?: string; + fileName?: string; }; export type ActiveWebListener = { diff --git a/src/web/inbound/send-api.test.ts b/src/web/inbound/send-api.test.ts new file mode 100644 index 00000000000..5e7f7c17e2d --- /dev/null +++ b/src/web/inbound/send-api.test.ts @@ -0,0 +1,49 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const recordChannelActivity = vi.fn(); +vi.mock("../../infra/channel-activity.js", () => ({ + recordChannelActivity: (...args: unknown[]) => recordChannelActivity(...args), +})); + +import { createWebSendApi } from "./send-api.js"; + +describe("createWebSendApi", () => { + const sendMessage = vi.fn(async () => ({ key: { id: "msg-1" } })); + const sendPresenceUpdate = vi.fn(async () => {}); + const api = createWebSendApi({ + sock: { sendMessage, sendPresenceUpdate }, + defaultAccountId: "main", + }); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("uses sendOptions fileName for outbound documents", async () => { + const payload = Buffer.from("pdf"); + await api.sendMessage("+1555", "doc", payload, "application/pdf", { fileName: "invoice.pdf" }); + expect(sendMessage).toHaveBeenCalledWith( + "1555@s.whatsapp.net", + expect.objectContaining({ + document: payload, + fileName: "invoice.pdf", + caption: "doc", + mimetype: "application/pdf", + }), + ); + }); + + it("falls back to default document filename when fileName is absent", async () => { + const payload = Buffer.from("pdf"); + await api.sendMessage("+1555", "doc", payload, "application/pdf"); + expect(sendMessage).toHaveBeenCalledWith( + "1555@s.whatsapp.net", + expect.objectContaining({ + document: payload, + fileName: "file", + caption: "doc", + mimetype: "application/pdf", + }), + ); + }); +}); diff --git a/src/web/inbound/send-api.ts b/src/web/inbound/send-api.ts index 7deb9540dbd..0517dc226ae 100644 --- a/src/web/inbound/send-api.ts +++ b/src/web/inbound/send-api.ts @@ -38,9 +38,10 @@ export function createWebSendApi(params: { ...(gifPlayback ? { gifPlayback: true } : {}), }; } else { + const fileName = sendOptions?.fileName?.trim() || "file"; payload = { document: mediaBuffer, - fileName: "file", + fileName, caption: text || undefined, mimetype: mediaType, }; diff --git a/src/web/outbound.test.ts b/src/web/outbound.test.ts index 1d9fef7d0ab..9f6fdd901b8 100644 --- a/src/web/outbound.test.ts +++ b/src/web/outbound.test.ts @@ -130,7 +130,9 @@ describe("web outbound", () => { verbose: false, mediaUrl: "/tmp/file.pdf", }); - expect(sendMessage).toHaveBeenLastCalledWith("+1555", "doc", buf, "application/pdf"); + expect(sendMessage).toHaveBeenLastCalledWith("+1555", "doc", buf, "application/pdf", { + fileName: "file.pdf", + }); }); it("sends polls via active listener", async () => { diff --git a/src/web/outbound.ts b/src/web/outbound.ts index 08a0e363419..e09981541d1 100644 --- a/src/web/outbound.ts +++ b/src/web/outbound.ts @@ -45,6 +45,7 @@ export async function sendMessageWhatsApp( const jid = toWhatsappJid(to); let mediaBuffer: Buffer | undefined; let mediaType: string | undefined; + let documentFileName: string | undefined; if (options.mediaUrl) { const media = await loadWebMedia(options.mediaUrl); const caption = text || undefined; @@ -62,6 +63,7 @@ export async function sendMessageWhatsApp( text = caption ?? ""; } else { text = caption ?? ""; + documentFileName = media.fileName; } } outboundLog.info(`Sending message -> ${jid}${options.mediaUrl ? " (media)" : ""}`); @@ -70,9 +72,10 @@ export async function sendMessageWhatsApp( const hasExplicitAccountId = Boolean(options.accountId?.trim()); const accountId = hasExplicitAccountId ? resolvedAccountId : undefined; const sendOptions: ActiveWebSendOptions | undefined = - options.gifPlayback || accountId + options.gifPlayback || accountId || documentFileName ? { ...(options.gifPlayback ? { gifPlayback: true } : {}), + ...(documentFileName ? { fileName: documentFileName } : {}), accountId, } : undefined;