fix: validate whatsapp login timeout

This commit is contained in:
Peter Steinberger
2026-05-28 21:18:24 -04:00
parent c66c404d58
commit c0d525c8a0
2 changed files with 42 additions and 10 deletions

View File

@@ -26,7 +26,7 @@ describe("createWhatsAppLoginTool", () => {
const tool = createWhatsAppLoginTool();
const result = await tool.execute("tool-call-1", {
action: "wait",
timeoutMs: 5000,
timeoutMs: "5000",
accountId,
currentQrDataUrl: "data:image/png;base64,current-qr",
});
@@ -56,6 +56,39 @@ describe("createWhatsAppLoginTool", () => {
});
});
it("passes string timeoutMs through to start actions", async () => {
startWebLoginWithQrMock.mockResolvedValueOnce({
connected: false,
message: "Scan this QR in WhatsApp → Linked Devices.",
qrDataUrl: "data:image/png;base64,current-qr",
});
const tool = createWhatsAppLoginTool();
await tool.execute("tool-call-start", {
action: "start",
timeoutMs: "6000",
accountId: "account-3",
});
expect(startWebLoginWithQrMock).toHaveBeenCalledWith({
accountId: "account-3",
timeoutMs: 6000,
force: false,
});
});
it("rejects fractional timeoutMs before login actions", async () => {
const tool = createWhatsAppLoginTool();
await expect(
tool.execute("tool-call-start", {
action: "start",
timeoutMs: "6000.5",
}),
).rejects.toThrow("timeoutMs must be a positive integer");
expect(startWebLoginWithQrMock).not.toHaveBeenCalled();
});
it("does not retain QR state across tool actions", async () => {
const accountId = "account-2";
startWebLoginWithQrMock.mockResolvedValueOnce({

View File

@@ -1,3 +1,7 @@
import {
optionalPositiveIntegerSchema,
readPositiveIntegerParam,
} from "openclaw/plugin-sdk/channel-actions";
import type { ChannelAgentTool } from "openclaw/plugin-sdk/channel-contract";
import { Type } from "typebox";
import { startWebLoginWithQr, waitForWebLogin } from "../login-qr-api.js";
@@ -20,7 +24,7 @@ export function createWhatsAppLoginTool(): ChannelAgentTool {
type: "string",
enum: ["start", "wait"],
}),
timeoutMs: Type.Optional(Type.Number()),
timeoutMs: optionalPositiveIntegerSchema(),
force: Type.Optional(Type.Boolean()),
accountId: Type.Optional(Type.String()),
currentQrDataUrl: Type.Optional(
@@ -54,13 +58,11 @@ export function createWhatsAppLoginTool(): ChannelAgentTool {
const action = (args as { action?: string })?.action ?? "start";
const accountId = readOptionalString((args as { accountId?: unknown }).accountId);
const timeoutMs = readPositiveIntegerParam(args as Record<string, unknown>, "timeoutMs");
if (action === "wait") {
const result = await waitForWebLogin({
accountId,
timeoutMs:
typeof (args as { timeoutMs?: unknown }).timeoutMs === "number"
? (args as { timeoutMs?: number }).timeoutMs
: undefined,
timeoutMs,
currentQrDataUrl: readOptionalString(
(args as { currentQrDataUrl?: unknown }).currentQrDataUrl,
),
@@ -80,10 +82,7 @@ export function createWhatsAppLoginTool(): ChannelAgentTool {
const result = await startWebLoginWithQr({
accountId,
timeoutMs:
typeof (args as { timeoutMs?: unknown }).timeoutMs === "number"
? (args as { timeoutMs?: number }).timeoutMs
: undefined,
timeoutMs,
force:
typeof (args as { force?: unknown }).force === "boolean"
? (args as { force?: boolean }).force