Skip to content

Commit 5eb7192

Browse files
committed
fix(whatsapp): bound group metadata cache expiry
1 parent cbd8049 commit 5eb7192

2 files changed

Lines changed: 51 additions & 5 deletions

File tree

extensions/whatsapp/src/inbound/monitor.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runt
1010
import { formatLocationText } from "openclaw/plugin-sdk/channel-inbound";
1111
import { createInboundDebouncer } from "openclaw/plugin-sdk/channel-inbound-debounce";
1212
import { getChildLogger } from "openclaw/plugin-sdk/logging-core";
13-
import { parseStrictFiniteNumber } from "openclaw/plugin-sdk/number-runtime";
13+
import {
14+
asDateTimestampMs,
15+
parseStrictFiniteNumber,
16+
resolveExpiresAtMsFromDurationMs,
17+
} from "openclaw/plugin-sdk/number-runtime";
1418
import { defaultRuntime } from "openclaw/plugin-sdk/runtime-env";
1519
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
1620
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -80,6 +84,13 @@ type LocalGroupMetadataCacheEntry = WhatsAppGroupMetadataCacheEntry & {
8084
mentionParticipants?: WhatsAppOutboundMentionParticipant[];
8185
};
8286

87+
function resolveGroupMetadataExpiresAt(nowRaw = Date.now()): number | undefined {
88+
const now = asDateTimestampMs(nowRaw);
89+
return now === undefined
90+
? undefined
91+
: resolveExpiresAtMsFromDurationMs(GROUP_META_TTL_MS, { nowMs: now });
92+
}
93+
8394
function parseWhatsAppTimestampSeconds(value: unknown): number | undefined {
8495
if (value == null) {
8596
return undefined;
@@ -96,6 +107,10 @@ function rememberGroupMetadataCacheEntry<T extends WhatsAppGroupMetadataCacheEnt
96107
jid: string,
97108
entry: T,
98109
): void {
110+
if (asDateTimestampMs(entry.expires) === undefined) {
111+
cache.delete(jid);
112+
return;
113+
}
99114
if (cache.has(jid)) {
100115
cache.delete(jid);
101116
}
@@ -118,7 +133,9 @@ function readGroupMetadataCacheEntry<T extends WhatsAppGroupMetadataCacheEntry>(
118133
if (!entry) {
119134
return null;
120135
}
121-
if (entry.expires <= Date.now()) {
136+
const now = asDateTimestampMs(Date.now());
137+
const expires = asDateTimestampMs(entry.expires);
138+
if (now === undefined || expires === undefined || expires <= now) {
122139
cache.delete(jid);
123140
return null;
124141
}
@@ -472,15 +489,15 @@ export async function attachWebInboxToSocket(
472489
subject: meta.subject,
473490
participants,
474491
mentionParticipants,
475-
expires: Date.now() + GROUP_META_TTL_MS,
492+
expires: resolveGroupMetadataExpiresAt() ?? 0,
476493
};
477494
};
478495

479496
const summarizeGroupMetaForReconnectCache = (
480497
meta: GroupMetadata,
481498
): WhatsAppGroupMetadataCacheEntry => ({
482499
subject: meta.subject,
483-
expires: Date.now() + GROUP_META_TTL_MS,
500+
expires: resolveGroupMetadataExpiresAt() ?? Number.NaN,
484501
});
485502

486503
const getGroupMeta = async (jid: string) => {
@@ -511,7 +528,7 @@ export async function attachWebInboxToSocket(
511528
options.verbose,
512529
`Failed to fetch group metadata for ${jid}: ${String(err)}`,
513530
);
514-
return { expires: Date.now() + GROUP_META_TTL_MS };
531+
return { expires: resolveGroupMetadataExpiresAt() ?? 0 };
515532
}
516533
};
517534

extensions/whatsapp/src/monitor-inbox.streams-inbound-messages.test-support.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,35 @@ describe("web monitor inbox", () => {
427427
await listener.close();
428428
});
429429

430+
it("does not keep reconnect group metadata when the expiry would exceed a valid Date", async () => {
431+
const groupMetadataCache: NonNullable<InboxMonitorOptions["groupMetadataCache"]> = new Map();
432+
const dateNow = vi.spyOn(Date, "now").mockReturnValue(8_640_000_000_000_000);
433+
try {
434+
const sock = getSock();
435+
sock.groupFetchAllParticipating.mockResolvedValueOnce({
436+
437+
438+
subject: "Boundary Group",
439+
owner: undefined,
440+
participants: [],
441+
},
442+
});
443+
444+
const { listener } = await startInboxMonitor(vi.fn(async () => {}) as InboxOnMessage, {
445+
groupMetadataCache,
446+
});
447+
448+
await vi.waitFor(() => {
449+
expect(sock.groupFetchAllParticipating).toHaveBeenCalledTimes(1);
450+
});
451+
expect(groupMetadataCache.has("[email protected]")).toBe(false);
452+
453+
await listener.close();
454+
} finally {
455+
dateNow.mockRestore();
456+
}
457+
});
458+
430459
it("does not block inbound listeners while group hydration is pending", async () => {
431460
let resolveHydration!: () => void;
432461
const sock = getSock();

0 commit comments

Comments
 (0)