diff --git a/src/web/session.test.ts b/src/web/session.test.ts index ae012b916..e595742ba 100644 --- a/src/web/session.test.ts +++ b/src/web/session.test.ts @@ -9,9 +9,8 @@ import { resetLoadConfigMock, } from "./test-helpers.js"; -const { createWaSocket, logWebSelfId, waitForWaConnection } = await import( - "./session.js" -); +const { createWaSocket, formatError, logWebSelfId, waitForWaConnection } = + await import("./session.js"); describe("web session", () => { beforeEach(() => { @@ -89,4 +88,23 @@ describe("web session", () => { existsSpy.mockRestore(); readSpy.mockRestore(); }); + + it("formatError prints Boom-like payload message", () => { + const err = { + error: { + isBoom: true, + output: { + statusCode: 408, + payload: { + statusCode: 408, + error: "Request Time-out", + message: "QR refs attempts ended", + }, + }, + }, + }; + expect(formatError(err)).toContain("status=408"); + expect(formatError(err)).toContain("Request Time-out"); + expect(formatError(err)).toContain("QR refs attempts ended"); + }); }); diff --git a/src/web/session.ts b/src/web/session.ts index aed5ef05d..35b5a392b 100644 --- a/src/web/session.ts +++ b/src/web/session.ts @@ -186,14 +186,88 @@ export function getStatusCode(err: unknown) { ); } +function safeStringify(value: unknown, limit = 800): string { + try { + const seen = new WeakSet(); + const raw = JSON.stringify( + value, + (_key, v) => { + if (typeof v === "bigint") return v.toString(); + if (typeof v === "function") return `[Function ${(v as Function).name || "anonymous"}]`; + if (typeof v === "object" && v) { + if (seen.has(v)) return "[Circular]"; + seen.add(v); + } + return v; + }, + 2, + ); + if (!raw) return String(value); + return raw.length > limit ? `${raw.slice(0, limit)}…` : raw; + } catch { + return String(value); + } +} + +function extractBoomDetails(err: unknown): { + statusCode?: number; + error?: string; + message?: string; +} | null { + if (!err || typeof err !== "object") return null; + const output = (err as { output?: unknown })?.output as + | { statusCode?: unknown; payload?: unknown } + | undefined; + if (!output || typeof output !== "object") return null; + const payload = (output as { payload?: unknown }).payload as + | { error?: unknown; message?: unknown; statusCode?: unknown } + | undefined; + const statusCode = + typeof (output as { statusCode?: unknown }).statusCode === "number" + ? ((output as { statusCode?: unknown }).statusCode as number) + : typeof payload?.statusCode === "number" + ? (payload.statusCode as number) + : undefined; + const error = typeof payload?.error === "string" ? payload.error : undefined; + const message = + typeof payload?.message === "string" ? payload.message : undefined; + if (!statusCode && !error && !message) return null; + return { statusCode, error, message }; +} + export function formatError(err: unknown): string { if (err instanceof Error) return err.message; if (typeof err === "string") return err; - const status = getStatusCode(err); + if (!err || typeof err !== "object") return String(err); + + // Baileys frequently wraps errors under `error` with a Boom-like shape. + const boom = + extractBoomDetails(err) ?? + extractBoomDetails((err as { error?: unknown })?.error) ?? + extractBoomDetails((err as { lastDisconnect?: { error?: unknown } })?.lastDisconnect?.error); + + const status = boom?.statusCode ?? getStatusCode(err); const code = (err as { code?: unknown })?.code; - if (status || code) - return `status=${status ?? "unknown"} code=${code ?? "unknown"}`; - return String(err); + + const messageCandidates = [ + boom?.message, + typeof (err as { message?: unknown })?.message === "string" + ? ((err as { message?: unknown }).message as string) + : undefined, + typeof (err as { error?: { message?: unknown } })?.error?.message === "string" + ? ((err as { error?: { message?: unknown } }).error?.message as string) + : undefined, + ].filter((v): v is string => Boolean(v && v.trim().length > 0)); + const message = messageCandidates[0]; + + const pieces: string[] = []; + if (typeof status === "number") pieces.push(`status=${status}`); + if (boom?.error) pieces.push(boom.error); + if (message) pieces.push(message); + if (code !== undefined && code !== null) pieces.push(`code=${String(code)}`); + + if (pieces.length > 0) return pieces.join(" "); + return safeStringify(err); } export async function webAuthExists() {