fix(zalo): cap api request timeouts

This commit is contained in:
Peter Steinberger
2026-05-29 16:19:10 -04:00
parent eb7e237151
commit f66d14def5
2 changed files with 44 additions and 4 deletions

View File

@@ -1,6 +1,8 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const resolvePinnedHostnameWithPolicyMock = vi.fn();
const { resolvePinnedHostnameWithPolicyMock } = vi.hoisted(() => ({
resolvePinnedHostnameWithPolicyMock: vi.fn(),
}));
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
resolvePinnedHostnameWithPolicy: (...args: unknown[]) =>
@@ -9,6 +11,8 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
import { deleteWebhook, getWebhookInfo, sendChatAction, sendPhoto, type ZaloFetch } from "./api.js";
const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
function createOkFetcher() {
return vi.fn<ZaloFetch>(async () => new Response(JSON.stringify({ ok: true, result: {} })));
}
@@ -90,6 +94,38 @@ describe("Zalo API request methods", () => {
}
});
it("caps oversized sendChatAction timeouts before scheduling the timer", async () => {
const setTimeoutMock = vi
.spyOn(globalThis, "setTimeout")
.mockImplementation((() => 1) as typeof setTimeout);
const clearTimeoutMock = vi
.spyOn(globalThis, "clearTimeout")
.mockImplementation(() => undefined);
try {
const fetcher = vi.fn<ZaloFetch>(
async () =>
({
json: async () => ({ ok: true, result: {} }),
}) as Response,
);
await sendChatAction(
"test-token",
{
chat_id: "chat-123",
action: "typing",
},
fetcher,
MAX_TIMER_TIMEOUT_MS + 1_000_000,
);
expect(setTimeoutMock).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS);
} finally {
setTimeoutMock.mockRestore();
clearTimeoutMock.mockRestore();
}
});
it("validates outbound photo URLs against the SSRF guard before posting", async () => {
const fetcher = createOkFetcher();

View File

@@ -3,6 +3,7 @@
* @see https://bot.zaloplatforms.com/docs
*/
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
import { resolvePinnedHostnameWithPolicy, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
const ZALO_API_BASE = "https://bot-api.zaloplatforms.com";
@@ -112,9 +113,12 @@ export async function callZaloApi<T = unknown>(
): Promise<ZaloApiResponse<T>> {
const url = `${ZALO_API_BASE}/bot${token}/${method}`;
const controller = new AbortController();
const timeoutId = options?.timeoutMs
? setTimeout(() => controller.abort(), options.timeoutMs)
: undefined;
const requestTimeoutMs =
options?.timeoutMs === undefined ? undefined : resolveTimerTimeoutMs(options.timeoutMs, 1);
const timeoutId =
requestTimeoutMs === undefined
? undefined
: setTimeout(() => controller.abort(), requestTimeoutMs);
const fetcher = options?.fetch ?? fetch;
try {