Skip to content

Commit b09cab4

Browse files
committed
fix(whatsapp): cap QR login timers
1 parent 7d71c5d commit b09cab4

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

extensions/whatsapp/src/login-qr.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { beforeEach, describe, expect, it, vi } from "vitest";
1+
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
2+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
23
import { startWebLoginWithQr, waitForWebLogin } from "./login-qr.js";
34
import { renderQrPngDataUrl } from "./qr-image.js";
45
import {
@@ -101,6 +102,10 @@ describe("login-qr", () => {
101102
.mockImplementation(async (input) => `data:image/png;base64,encoded:${input}`);
102103
});
103104

105+
afterEach(() => {
106+
vi.useRealTimers();
107+
});
108+
104109
it("restarts login once on status 515 and completes", async () => {
105110
waitForWaConnectionMock
106111
// Baileys v7 wraps the error: { error: BoomError(515) }
@@ -181,6 +186,27 @@ describe("login-qr", () => {
181186
expect(logoutWebMock).toHaveBeenCalledOnce();
182187
});
183188

189+
it("caps oversized wait timeouts to a timer-safe delay", async () => {
190+
const accountId = "oversized-wait-timeout";
191+
waitForWaConnectionMock.mockImplementation(() => new Promise(() => {}));
192+
193+
const start = await startWebLoginWithQr({ timeoutMs: 5000, accountId });
194+
expect(start.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
195+
196+
vi.useFakeTimers();
197+
const resultPromise = waitForWebLogin({
198+
timeoutMs: Number.MAX_SAFE_INTEGER,
199+
currentQrDataUrl: start.qrDataUrl,
200+
accountId,
201+
});
202+
203+
await vi.advanceTimersByTimeAsync(MAX_TIMER_TIMEOUT_MS);
204+
await expect(resultPromise).resolves.toEqual({
205+
connected: false,
206+
message: "Still waiting for the QR scan. Let me know when you’ve scanned it.",
207+
});
208+
});
209+
184210
it("turns unexpected login cleanup failures into a normal login error", async () => {
185211
waitForWaConnectionMock.mockRejectedValueOnce({
186212
output: { statusCode: 401 },

extensions/whatsapp/src/login-qr.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { randomUUID } from "node:crypto";
22
import { logInfo } from "openclaw/plugin-sdk/logging-core";
3+
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
34
import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
45
import { danger, info, success } from "openclaw/plugin-sdk/runtime-env";
56
import { defaultRuntime, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
@@ -62,6 +63,14 @@ const ACTIVE_LOGIN_TTL_MS = 3 * 60_000;
6263
const MAX_QR_RENDER_CHASES = 10;
6364
const activeLogins = new Map<string, ActiveLogin>();
6465

66+
function resolveWhatsAppLoginTimeoutMs(
67+
value: number | undefined,
68+
fallbackMs: number,
69+
minMs: number,
70+
): number {
71+
return resolveTimerTimeoutMs(value, fallbackMs, minMs);
72+
}
73+
6574
function closeSocket(sock: WaSocket) {
6675
closeWaSocket(sock);
6776
}
@@ -344,7 +353,7 @@ export async function startWebLoginWithQr(
344353
() => {
345354
rejectQr?.(new Error("Timed out waiting for WhatsApp QR"));
346355
},
347-
Math.max(opts.timeoutMs ?? 30_000, 5000),
356+
resolveWhatsAppLoginTimeoutMs(opts.timeoutMs, 30_000, 5000),
348357
);
349358

350359
let sock: WaSocket;
@@ -492,7 +501,7 @@ export async function waitForWebLogin(
492501
message: "The login QR expired. Ask me to generate a new one.",
493502
};
494503
}
495-
const timeoutMs = Math.max(opts.timeoutMs ?? 120_000, 1000);
504+
const timeoutMs = resolveWhatsAppLoginTimeoutMs(opts.timeoutMs, 120_000, 1000);
496505
const deadline = Date.now() + timeoutMs;
497506
const currentQrDataUrl = opts.currentQrDataUrl;
498507

0 commit comments

Comments
 (0)