From c0d610d9d75e4089f3b5392c40f26c6237d9a6ad Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 17 Jul 2026 09:13:55 -0700 Subject: [PATCH] fix(whatsapp-plugin): connect with current WA Web version and load as bundled plugin WhatsApp rejects handshakes advertising Baileys' baked-in stale protocol version with a 405 close, so the plugin cycled starting->disconnected and never issued a QR. connect() now fetches the current WA Web version per socket build. /status also exposes lastError so this failure mode is diagnosable from the documented troubleshooting surface. The published bundled.js also failed to load entirely ("Dynamic require of 'crypto' is not supported"): plugin bundles are ESM but Baileys is CJS. bundlePluginEntry now injects the same createRequire banner as dist/bin.js. Verified end-to-end against an isolated fn serve: bundled.js loads, status reaches awaiting-qr, /qr serves a scannable QR data URL, /pair-code validates input, /logout clears auth state. Co-Authored-By: Claude Fable 5 --- .changeset/whatsapp-plugin-connects-again.md | 7 +++++++ packages/cli/tsup.config.ts | 7 +++++++ .../src/__tests__/connection.test.ts | 6 ++++-- plugins/fusion-plugin-whatsapp-chat/src/connection.ts | 8 +++++++- plugins/fusion-plugin-whatsapp-chat/src/index.ts | 5 +++++ 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 .changeset/whatsapp-plugin-connects-again.md diff --git a/.changeset/whatsapp-plugin-connects-again.md b/.changeset/whatsapp-plugin-connects-again.md new file mode 100644 index 0000000000..3f24dad34a --- /dev/null +++ b/.changeset/whatsapp-plugin-connects-again.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Fix WhatsApp Chat plugin failing to connect (405 rejection) and its bundled build failing to load. +category: fix +dev: connect() now passes fetchLatestBaileysVersion() to makeWASocket so WhatsApp accepts the handshake; /status exposes lastError; plugin bundled.js builds get the createRequire ESM banner so CJS deps (Baileys) can require node builtins. diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts index ab735e96b1..5af775883c 100644 --- a/packages/cli/tsup.config.ts +++ b/packages/cli/tsup.config.ts @@ -203,6 +203,13 @@ async function bundlePluginEntry({ pluginId, srcDir, destDir, withMcpAsset = fal target: "node22", outfile: join(destDir, "bundled.js"), external: ["@fusion/engine", ...external], + /* + * FNXC:BundledPlugins 2026-07-17-09:20: + * CJS dependencies bundled into ESM output (e.g. Baileys in the WhatsApp plugin) compile to esbuild's __require helper, which throws "Dynamic require of \"crypto\" is not supported" at load time in an ESM module. Inject the same createRequire shim dist/bin.js uses so every bundled.js can require node builtins. + */ + banner: { + js: 'import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);', + }, alias: { "@fusion/plugin-sdk": join(__dirname, "..", "plugin-sdk", "src", "index.ts"), /* diff --git a/plugins/fusion-plugin-whatsapp-chat/src/__tests__/connection.test.ts b/plugins/fusion-plugin-whatsapp-chat/src/__tests__/connection.test.ts index aee2c06aae..c8471e8746 100644 --- a/plugins/fusion-plugin-whatsapp-chat/src/__tests__/connection.test.ts +++ b/plugins/fusion-plugin-whatsapp-chat/src/__tests__/connection.test.ts @@ -24,6 +24,7 @@ const mockState = vi.hoisted(() => { vi.mock("@whiskeysockets/baileys", () => ({ default: mockState.makeWASocket, makeWASocket: mockState.makeWASocket, + fetchLatestBaileysVersion: async () => ({ version: [2, 3000, 0], isLatest: true }), DisconnectReason: { loggedOut: 401 }, BufferJSON: { reviver: undefined, replacer: undefined }, initAuthCreds: () => ({}), @@ -140,11 +141,12 @@ describe("WhatsAppConnection", () => { const connection = new WhatsAppConnection(makeCtx(), "0.1.0", vi.fn().mockResolvedValue("reply"), createInMemoryPersistence()); await connection.start(); await mockState.handlers.get("connection.update")?.({ connection: "close", lastDisconnect: { error: new Error("boom") } }); - vi.advanceTimersByTime(1000); + // connect() awaits fetchLatestBaileysVersion before building the socket, so flush microtasks after the timer fires. + await vi.advanceTimersByTimeAsync(1000); expect(mockState.makeWASocket).toHaveBeenCalledTimes(2); await mockState.handlers.get("connection.update")?.({ connection: "close", lastDisconnect: { error: { output: { statusCode: 401 } } } }); - vi.advanceTimersByTime(1000); + await vi.advanceTimersByTimeAsync(1000); expect(mockState.makeWASocket).toHaveBeenCalledTimes(2); vi.useRealTimers(); }); diff --git a/plugins/fusion-plugin-whatsapp-chat/src/connection.ts b/plugins/fusion-plugin-whatsapp-chat/src/connection.ts index 66914ead48..5b123b6066 100644 --- a/plugins/fusion-plugin-whatsapp-chat/src/connection.ts +++ b/plugins/fusion-plugin-whatsapp-chat/src/connection.ts @@ -1,4 +1,4 @@ -import { DisconnectReason, makeWASocket, type ConnectionState, type WAMessage, type WAMessageContent, type WASocket } from "@whiskeysockets/baileys"; +import { DisconnectReason, fetchLatestBaileysVersion, makeWASocket, type ConnectionState, type WAMessage, type WAMessageContent, type WASocket } from "@whiskeysockets/baileys"; import type { PluginContext } from "@fusion/plugin-sdk"; import pino from "pino"; import qrcode from "qrcode"; @@ -156,7 +156,13 @@ export class WhatsAppConnection { if (!this.authState) this.authState = await createPersistenceAuthState(this.persistence); this.status = { state: "starting" }; + /** + * FNXC:WhatsAppProtocolVersion 2026-07-17-09:15: + * WhatsApp rejects handshakes that advertise a stale WA Web protocol version with a 405 "Connection Failure" close, which left the plugin stuck cycling starting→disconnected and no QR was ever issued. Fetch the current version before each socket build; fetchLatestBaileysVersion falls back to the library's baked-in version on network failure, so this never blocks connecting. + */ + const { version } = await fetchLatestBaileysVersion(); const socket = makeWASocket({ + version, auth: this.authState.state, printQRInTerminal: false, browser: ["Fusion", "Chrome", this.fusionVersion], diff --git a/plugins/fusion-plugin-whatsapp-chat/src/index.ts b/plugins/fusion-plugin-whatsapp-chat/src/index.ts index 8a1a86d0fc..f35d9e0d03 100644 --- a/plugins/fusion-plugin-whatsapp-chat/src/index.ts +++ b/plugins/fusion-plugin-whatsapp-chat/src/index.ts @@ -95,11 +95,16 @@ const routes: PluginRouteDefinition[] = [ const { connection, error } = getConnectionOrResponse(ctx); if (!connection) return error as PluginRouteResponse; const status = connection.getStatus(); + /** + * FNXC:WhatsAppStatusVisibility 2026-07-17-09:15: + * /status must expose lastError: a stale-protocol 405 rejection previously showed only a bare "disconnected" with no way to diagnose it from the API surface the README points troubleshooters at. + */ return { status: 200, body: { status: status.state, jid: status.jid, + lastError: status.lastError, allowedSenders: Array.from(getAllowedSenders(ctx.settings)), }, };