mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(feishu): bound streaming token expiry
This commit is contained in:
@@ -342,6 +342,65 @@ describe("FeishuStreamingSession", () => {
|
||||
"Final replace failed: Error: Replace card content failed with HTTP 500",
|
||||
);
|
||||
});
|
||||
|
||||
it("bounds streaming token cache lifetime when token expiry overflows", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-05-29T12:00:00.000Z"));
|
||||
const release = vi.fn(async () => {});
|
||||
const authTokens: string[] = [];
|
||||
fetchWithSsrFGuardMock.mockImplementation(
|
||||
async ({ url }: { url: string; init?: { body?: string } }) => {
|
||||
if (url.includes("/auth/")) {
|
||||
const token = `token-${authTokens.length + 1}`;
|
||||
authTokens.push(token);
|
||||
return {
|
||||
response: {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
code: 0,
|
||||
msg: "ok",
|
||||
tenant_access_token: token,
|
||||
expire: Number.MAX_SAFE_INTEGER,
|
||||
}),
|
||||
},
|
||||
release,
|
||||
};
|
||||
}
|
||||
return {
|
||||
response: {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
code: 0,
|
||||
msg: "ok",
|
||||
data: { card_id: `card-${authTokens.length}` },
|
||||
}),
|
||||
},
|
||||
release,
|
||||
};
|
||||
},
|
||||
);
|
||||
const client = {
|
||||
im: {
|
||||
message: {
|
||||
create: vi.fn(async () => ({ code: 0, msg: "ok", data: { message_id: "om_1" } })),
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
|
||||
await new FeishuStreamingSession(client, {
|
||||
appId: "app_unsafe_token_expiry",
|
||||
appSecret: "secret",
|
||||
}).start("chat_id", "open_id");
|
||||
expect(authTokens).toEqual(["token-1"]);
|
||||
|
||||
vi.setSystemTime(Date.now() + 7200 * 1000 - 60_000 + 1);
|
||||
await new FeishuStreamingSession(client, {
|
||||
appId: "app_unsafe_token_expiry",
|
||||
appSecret: "secret",
|
||||
}).start("chat_id", "open_id");
|
||||
|
||||
expect(authTokens).toEqual(["token-1", "token-2"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mergeStreamingText", () => {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import type { Client } from "@larksuiteoapi/node-sdk";
|
||||
import { resolveExpiresAtMsFromDurationSeconds } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { getFeishuUserAgent } from "./client.js";
|
||||
import { resolveFeishuCardTemplate, type CardHeaderConfig } from "./send.js";
|
||||
@@ -42,10 +43,21 @@ type StreamingStartOptions = {
|
||||
|
||||
const STREAMING_UPDATE_THROTTLE_MS = 160;
|
||||
const STREAMING_SIGNIFICANT_DELTA_CHARS = 18;
|
||||
const FEISHU_STREAMING_TOKEN_DEFAULT_LIFETIME_SECONDS = 7200;
|
||||
|
||||
// Token cache (keyed by domain + appId)
|
||||
const tokenCache = new Map<string, { token: string; expiresAt: number }>();
|
||||
|
||||
function resolveStreamingTokenExpiresAt(value: unknown): number {
|
||||
if (typeof value === "number" && Number.isFinite(value) && value <= 0) {
|
||||
return Date.now();
|
||||
}
|
||||
return (
|
||||
resolveExpiresAtMsFromDurationSeconds(value) ??
|
||||
Date.now() + FEISHU_STREAMING_TOKEN_DEFAULT_LIFETIME_SECONDS * 1000
|
||||
);
|
||||
}
|
||||
|
||||
function resolveApiBase(domain?: FeishuDomain): string {
|
||||
if (domain === "lark") {
|
||||
return "https://open.larksuite.com/open-apis";
|
||||
@@ -103,7 +115,7 @@ async function getToken(creds: Credentials): Promise<string> {
|
||||
}
|
||||
tokenCache.set(key, {
|
||||
token: data.tenant_access_token,
|
||||
expiresAt: Date.now() + (data.expire ?? 7200) * 1000,
|
||||
expiresAt: resolveStreamingTokenExpiresAt(data.expire),
|
||||
});
|
||||
return data.tenant_access_token;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user