From 5b8472b0b9efee79a7dfc328bc64ba0ec0a06005 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 19:03:55 -0400 Subject: [PATCH] fix(whatsapp): cap credential flush timeout --- .../whatsapp/src/creds-persistence.test.ts | 25 +++++++++++++++++++ extensions/whatsapp/src/creds-persistence.ts | 4 ++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 extensions/whatsapp/src/creds-persistence.test.ts diff --git a/extensions/whatsapp/src/creds-persistence.test.ts b/extensions/whatsapp/src/creds-persistence.test.ts new file mode 100644 index 00000000000..6db058e2456 --- /dev/null +++ b/extensions/whatsapp/src/creds-persistence.test.ts @@ -0,0 +1,25 @@ +import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { enqueueCredsSave, waitForCredsSaveQueueWithTimeout } from "./creds-persistence.js"; + +describe("creds-persistence", () => { + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + it("caps oversized credential flush timeouts before scheduling", async () => { + vi.useFakeTimers(); + const timeoutSpy = vi.spyOn(globalThis, "setTimeout"); + const authDir = "oversized-timeout"; + enqueueCredsSave( + authDir, + () => undefined, + () => undefined, + ); + + await waitForCredsSaveQueueWithTimeout(authDir, Number.MAX_SAFE_INTEGER); + + expect(timeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS); + }); +}); diff --git a/extensions/whatsapp/src/creds-persistence.ts b/extensions/whatsapp/src/creds-persistence.ts index 204af357677..9ad82529022 100644 --- a/extensions/whatsapp/src/creds-persistence.ts +++ b/extensions/whatsapp/src/creds-persistence.ts @@ -1,3 +1,4 @@ +import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime"; import { replaceFileAtomic } from "openclaw/plugin-sdk/security-runtime"; import { assertWebCredsPathRegularFileOrMissing, resolveWebCredsPath } from "./creds-files.js"; @@ -71,11 +72,12 @@ export async function waitForCredsSaveQueueWithTimeout( authDir: string, timeoutMs = CREDS_SAVE_FLUSH_TIMEOUT_MS, ): Promise { + const boundedTimeoutMs = resolveTimerTimeoutMs(timeoutMs, CREDS_SAVE_FLUSH_TIMEOUT_MS, 0); let flushTimeout: ReturnType | undefined; return await Promise.race([ waitForCredsSaveQueue(authDir).then(() => "drained" as const), new Promise((resolve) => { - flushTimeout = setTimeout(() => resolve("timed_out"), timeoutMs); + flushTimeout = setTimeout(() => resolve("timed_out"), boundedTimeoutMs); }), ]).finally(() => { if (flushTimeout) {