From 2715a4b8b211eb960c5ef8c5dd7c44a53b2cb9d6 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 7 Mar 2026 08:20:44 -0800 Subject: [PATCH] iMessage: hash full self-chat text --- src/imessage/monitor/self-chat-cache.test.ts | 12 ++++++++---- src/imessage/monitor/self-chat-cache.ts | 11 +---------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/imessage/monitor/self-chat-cache.test.ts b/src/imessage/monitor/self-chat-cache.test.ts index 50a829c7d8d..cf3a245ba30 100644 --- a/src/imessage/monitor/self-chat-cache.test.ts +++ b/src/imessage/monitor/self-chat-cache.test.ts @@ -58,15 +58,19 @@ describe("createSelfChatCache", () => { expect(cache.has({ ...directLookup, text: "message-512", createdAt: 512 })).toBe(true); }); - it("handles long texts without requiring the full body in the cache key", () => { + it("does not collide long texts that differ only in the middle", () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-03-07T00:00:00Z")); const cache = createSelfChatCache(); - const longText = `${"a".repeat(800)}middle${"b".repeat(800)}`; + const prefix = "a".repeat(256); + const suffix = "b".repeat(256); + const longTextA = `${prefix}${"x".repeat(300)}${suffix}`; + const longTextB = `${prefix}${"y".repeat(300)}${suffix}`; - cache.remember({ ...directLookup, text: longText, createdAt: 123 }); + cache.remember({ ...directLookup, text: longTextA, createdAt: 123 }); - expect(cache.has({ ...directLookup, text: longText, createdAt: 123 })).toBe(true); + expect(cache.has({ ...directLookup, text: longTextA, createdAt: 123 })).toBe(true); + expect(cache.has({ ...directLookup, text: longTextB, createdAt: 123 })).toBe(false); }); }); diff --git a/src/imessage/monitor/self-chat-cache.ts b/src/imessage/monitor/self-chat-cache.ts index d9884a26a76..a2c4c31ccd9 100644 --- a/src/imessage/monitor/self-chat-cache.ts +++ b/src/imessage/monitor/self-chat-cache.ts @@ -21,8 +21,6 @@ export type SelfChatCache = { const SELF_CHAT_TTL_MS = 10_000; const MAX_SELF_CHAT_CACHE_ENTRIES = 512; const CLEANUP_MIN_INTERVAL_MS = 1_000; -const DIGEST_TEXT_HEAD_CHARS = 256; -const DIGEST_TEXT_TAIL_CHARS = 256; function normalizeText(text: string | undefined): string | null { if (!text) { @@ -36,15 +34,8 @@ function isUsableTimestamp(createdAt: number | undefined): createdAt is number { return typeof createdAt === "number" && Number.isFinite(createdAt); } -function buildDigestSource(text: string): string { - if (text.length <= DIGEST_TEXT_HEAD_CHARS + DIGEST_TEXT_TAIL_CHARS) { - return text; - } - return `${text.slice(0, DIGEST_TEXT_HEAD_CHARS)}\u0000${text.length}\u0000${text.slice(-DIGEST_TEXT_TAIL_CHARS)}`; -} - function digestText(text: string): string { - return createHash("sha256").update(buildDigestSource(text)).digest("hex"); + return createHash("sha256").update(text).digest("hex"); } function buildScope(parts: SelfChatCacheKeyParts): string {