mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(msteams): bound team id cache expiry
This commit is contained in:
@@ -77,6 +77,35 @@ describe("resolveTeamGroupId", () => {
|
||||
expect(fetchGraphJson).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not cache team ids when the expiry would exceed a valid Date", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date(8_640_000_000_000_000));
|
||||
try {
|
||||
vi.mocked(fetchGraphJson).mockResolvedValue({ id: "group-guid-boundary" } as never);
|
||||
|
||||
await resolveTeamGroupId("tok", "team-boundary");
|
||||
await resolveTeamGroupId("tok", "team-boundary");
|
||||
|
||||
expect(fetchGraphJson).toHaveBeenCalledTimes(2);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("evicts cached team ids when the current clock is invalid", async () => {
|
||||
vi.mocked(fetchGraphJson).mockResolvedValue({ id: "group-guid-invalid-clock" } as never);
|
||||
|
||||
await resolveTeamGroupId("tok", "team-invalid-clock");
|
||||
const dateNow = vi.spyOn(Date, "now").mockReturnValue(Number.NaN);
|
||||
try {
|
||||
await resolveTeamGroupId("tok", "team-invalid-clock");
|
||||
} finally {
|
||||
dateNow.mockRestore();
|
||||
}
|
||||
|
||||
expect(fetchGraphJson).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("falls back to conversationTeamId when Graph returns no id", async () => {
|
||||
vi.mocked(fetchGraphJson).mockResolvedValueOnce({} as never);
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
asDateTimestampMs,
|
||||
resolveExpiresAtMsFromDurationMs,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { fetchGraphJson, type GraphResponse } from "./graph.js";
|
||||
|
||||
export type GraphThreadMessage = {
|
||||
@@ -14,6 +18,13 @@ export type GraphThreadMessage = {
|
||||
const teamGroupIdCache = new Map<string, { groupId: string; expiresAt: number }>();
|
||||
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
||||
|
||||
function resolveTeamGroupIdCacheExpiresAt(nowRaw = Date.now()): number | undefined {
|
||||
const now = asDateTimestampMs(nowRaw);
|
||||
return now === undefined
|
||||
? undefined
|
||||
: resolveExpiresAtMsFromDurationMs(CACHE_TTL_MS, { nowMs: now });
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip HTML tags from Teams message content, preserving @mention display names.
|
||||
* Teams wraps mentions in <at>Name</at> tags.
|
||||
@@ -44,8 +55,13 @@ export async function resolveTeamGroupId(
|
||||
conversationTeamId: string,
|
||||
): Promise<string> {
|
||||
const cached = teamGroupIdCache.get(conversationTeamId);
|
||||
if (cached && cached.expiresAt > Date.now()) {
|
||||
return cached.groupId;
|
||||
if (cached) {
|
||||
const now = asDateTimestampMs(Date.now());
|
||||
const expiresAt = asDateTimestampMs(cached.expiresAt);
|
||||
if (now !== undefined && expiresAt !== undefined && expiresAt > now) {
|
||||
return cached.groupId;
|
||||
}
|
||||
teamGroupIdCache.delete(conversationTeamId);
|
||||
}
|
||||
|
||||
// The team ID in channelData is typically the group ID itself for standard teams.
|
||||
@@ -59,10 +75,13 @@ export async function resolveTeamGroupId(
|
||||
// Only cache when the Graph lookup succeeds — caching a fallback raw ID
|
||||
// can cause silent failures for the entire TTL if the ID is not a valid
|
||||
// Graph team GUID (e.g. Bot Framework conversation key).
|
||||
teamGroupIdCache.set(conversationTeamId, {
|
||||
groupId,
|
||||
expiresAt: Date.now() + CACHE_TTL_MS,
|
||||
});
|
||||
const expiresAt = resolveTeamGroupIdCacheExpiresAt();
|
||||
if (expiresAt !== undefined) {
|
||||
teamGroupIdCache.set(conversationTeamId, {
|
||||
groupId,
|
||||
expiresAt,
|
||||
});
|
||||
}
|
||||
|
||||
return groupId;
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user