mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-11 07:22:55 +00:00
fix(minimax): clamp oauth poll interval
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { loginMiniMaxPortalOAuth, normalizeOAuthExpires } from "./oauth.js";
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
@@ -54,4 +57,152 @@ describe("loginMiniMaxPortalOAuth", () => {
|
||||
).rejects.toThrow("invalid expired_in");
|
||||
expect(note).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("caps oversized authorization poll intervals before scheduling", async () => {
|
||||
vi.useFakeTimers();
|
||||
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
|
||||
let callCount = 0;
|
||||
const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||
callCount += 1;
|
||||
const body =
|
||||
init?.body instanceof URLSearchParams
|
||||
? init.body
|
||||
: new URLSearchParams(typeof init?.body === "string" ? init.body : "");
|
||||
if (callCount === 1) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
user_code: "CODE",
|
||||
verification_uri: "https://example.com/device",
|
||||
expired_in: Date.now() + MAX_TIMER_TIMEOUT_MS + 10_000,
|
||||
interval: Number.MAX_SAFE_INTEGER,
|
||||
state: body.get("state"),
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify(
|
||||
callCount === 2
|
||||
? { status: "pending" }
|
||||
: {
|
||||
status: "success",
|
||||
access_token: "access",
|
||||
refresh_token: "refresh",
|
||||
expired_in: 3600,
|
||||
},
|
||||
),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const result = loginMiniMaxPortalOAuth({
|
||||
openUrl: vi.fn(async () => undefined),
|
||||
note: vi.fn(async () => undefined),
|
||||
progress: { update: vi.fn(), stop: vi.fn() },
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(MAX_TIMER_TIMEOUT_MS);
|
||||
await expect(result).resolves.toMatchObject({ access: "access", refresh: "refresh" });
|
||||
});
|
||||
|
||||
it("does not sleep past the authorization expiry deadline", async () => {
|
||||
vi.useFakeTimers();
|
||||
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
|
||||
let callCount = 0;
|
||||
const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||
callCount += 1;
|
||||
const body =
|
||||
init?.body instanceof URLSearchParams
|
||||
? init.body
|
||||
: new URLSearchParams(typeof init?.body === "string" ? init.body : "");
|
||||
if (callCount === 1) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
user_code: "CODE",
|
||||
verification_uri: "https://example.com/device",
|
||||
expired_in: Date.now() + 10_000,
|
||||
interval: Number.MAX_SAFE_INTEGER,
|
||||
state: body.get("state"),
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
return new Response(JSON.stringify({ status: "pending" }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const result = loginMiniMaxPortalOAuth({
|
||||
openUrl: vi.fn(async () => undefined),
|
||||
note: vi.fn(async () => undefined),
|
||||
progress: { update: vi.fn(), stop: vi.fn() },
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 10_000);
|
||||
});
|
||||
|
||||
const rejection = expect(result).rejects.toThrow("timed out");
|
||||
await vi.advanceTimersByTimeAsync(10_000);
|
||||
await rejection;
|
||||
});
|
||||
|
||||
it("keeps the default poll delay for zero authorization intervals", async () => {
|
||||
vi.useFakeTimers();
|
||||
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
|
||||
let callCount = 0;
|
||||
const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||
callCount += 1;
|
||||
const body =
|
||||
init?.body instanceof URLSearchParams
|
||||
? init.body
|
||||
: new URLSearchParams(typeof init?.body === "string" ? init.body : "");
|
||||
if (callCount === 1) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
user_code: "CODE",
|
||||
verification_uri: "https://example.com/device",
|
||||
expired_in: Date.now() + 10_000,
|
||||
interval: 0,
|
||||
state: body.get("state"),
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify(
|
||||
callCount === 2
|
||||
? { status: "pending" }
|
||||
: {
|
||||
status: "success",
|
||||
access_token: "access",
|
||||
refresh_token: "refresh",
|
||||
expired_in: 3600,
|
||||
},
|
||||
),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const result = loginMiniMaxPortalOAuth({
|
||||
openUrl: vi.fn(async () => undefined),
|
||||
note: vi.fn(async () => undefined),
|
||||
progress: { update: vi.fn(), stop: vi.fn() },
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 2_000);
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(2_000);
|
||||
await expect(result).resolves.toMatchObject({ access: "access", refresh: "refresh" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
MAX_DATE_TIMESTAMP_MS,
|
||||
asSafeIntegerInRange,
|
||||
resolveExpiresAtMsFromDurationOrEpoch,
|
||||
resolvePositiveTimerTimeoutMs,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { generatePkceVerifierChallenge, toFormUrlEncoded } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { ensureGlobalUndiciEnvProxyDispatcher } from "openclaw/plugin-sdk/runtime-env";
|
||||
@@ -259,7 +260,7 @@ export async function loginMiniMaxPortalOAuth(params: {
|
||||
// Fall back to manual copy/paste if browser open fails.
|
||||
}
|
||||
|
||||
let pollIntervalMs = oauth.interval ? oauth.interval : 2000;
|
||||
let pollIntervalMs = resolvePositiveTimerTimeoutMs(oauth.interval, 2000);
|
||||
// The authorization endpoint returns an absolute millisecond deadline.
|
||||
const expireTimeMs = oauth.expired_in;
|
||||
|
||||
@@ -279,7 +280,11 @@ export async function loginMiniMaxPortalOAuth(params: {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
||||
const remainingMs = Math.max(0, expireTimeMs - Date.now());
|
||||
if (remainingMs <= 0) {
|
||||
break;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, Math.min(pollIntervalMs, remainingMs)));
|
||||
pollIntervalMs = Math.max(pollIntervalMs, 2000);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user