refactor: update message ID handling to use GUIDs instead of UUIDs for consistency

This commit is contained in:
Tyler Yust 2026-01-24 13:18:58 -08:00 committed by Peter Steinberger
parent fd36e05bf3
commit e516e923ff
2 changed files with 23 additions and 20 deletions

View File

@ -1985,7 +1985,7 @@ describe("BlueBubbles webhook monitor", () => {
handle: { address: "+15551234567" }, handle: { address: "+15551234567" },
isGroup: false, isGroup: false,
isFromMe: false, isFromMe: false,
guid: "msg-uuid-12345", guid: "p:1/msg-uuid-12345",
chatGuid: "iMessage;-;+15551234567", chatGuid: "iMessage;-;+15551234567",
date: Date.now(), date: Date.now(),
}, },
@ -2001,7 +2001,7 @@ describe("BlueBubbles webhook monitor", () => {
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0]; const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0][0];
// MessageSid should be short ID "1" instead of full UUID // MessageSid should be short ID "1" instead of full UUID
expect(callArgs.ctx.MessageSid).toBe("1"); expect(callArgs.ctx.MessageSid).toBe("1");
expect(callArgs.ctx.MessageSidFull).toBe("msg-uuid-12345"); expect(callArgs.ctx.MessageSidFull).toBe("p:1/msg-uuid-12345");
}); });
it("resolves short ID back to UUID", async () => { it("resolves short ID back to UUID", async () => {
@ -2025,7 +2025,7 @@ describe("BlueBubbles webhook monitor", () => {
handle: { address: "+15551234567" }, handle: { address: "+15551234567" },
isGroup: false, isGroup: false,
isFromMe: false, isFromMe: false,
guid: "msg-uuid-12345", guid: "p:1/msg-uuid-12345",
chatGuid: "iMessage;-;+15551234567", chatGuid: "iMessage;-;+15551234567",
date: Date.now(), date: Date.now(),
}, },
@ -2038,7 +2038,7 @@ describe("BlueBubbles webhook monitor", () => {
await flushAsync(); await flushAsync();
// The short ID "1" should resolve back to the full UUID // The short ID "1" should resolve back to the full UUID
expect(resolveBlueBubblesMessageId("1")).toBe("msg-uuid-12345"); expect(resolveBlueBubblesMessageId("1")).toBe("p:1/msg-uuid-12345");
}); });
it("returns UUID unchanged when not in cache", () => { it("returns UUID unchanged when not in cache", () => {

View File

@ -55,7 +55,7 @@ type BlueBubblesReplyCacheEntry = {
// Best-effort cache for resolving reply context when BlueBubbles webhooks omit sender/body. // Best-effort cache for resolving reply context when BlueBubbles webhooks omit sender/body.
const blueBubblesReplyCacheByMessageId = new Map<string, BlueBubblesReplyCacheEntry>(); const blueBubblesReplyCacheByMessageId = new Map<string, BlueBubblesReplyCacheEntry>();
// Bidirectional maps for short ID ↔ UUID resolution (token savings optimization) // Bidirectional maps for short ID ↔ message GUID resolution (token savings optimization)
const blueBubblesShortIdToUuid = new Map<string, string>(); const blueBubblesShortIdToUuid = new Map<string, string>();
const blueBubblesUuidToShortId = new Map<string, string>(); const blueBubblesUuidToShortId = new Map<string, string>();
let blueBubblesShortIdCounter = 0; let blueBubblesShortIdCounter = 0;
@ -85,18 +85,18 @@ function rememberBlueBubblesReplyCache(
if (!rawMessageId) { if (!rawMessageId) {
return { ...entry, shortId: "" }; return { ...entry, shortId: "" };
} }
// Normalize to strip "p:N/" prefix for consistent lookups // Normalize to strip "p:N/" prefix for consistent cache lookups
const messageId = normalizeMessageIdForCache(rawMessageId); const messageId = normalizeMessageIdForCache(rawMessageId);
// Check if we already have a short ID for this UUID // Check if we already have a short ID for this GUID (keep "p:N/" prefix)
let shortId = blueBubblesUuidToShortId.get(messageId); let shortId = blueBubblesUuidToShortId.get(rawMessageId);
if (!shortId) { if (!shortId) {
shortId = generateShortId(); shortId = generateShortId();
blueBubblesShortIdToUuid.set(shortId, messageId); blueBubblesShortIdToUuid.set(shortId, rawMessageId);
blueBubblesUuidToShortId.set(messageId, shortId); blueBubblesUuidToShortId.set(rawMessageId, shortId);
} }
const fullEntry: BlueBubblesReplyCacheEntry = { ...entry, shortId }; const fullEntry: BlueBubblesReplyCacheEntry = { ...entry, messageId: rawMessageId, shortId };
// Refresh insertion order. // Refresh insertion order.
blueBubblesReplyCacheByMessageId.delete(messageId); blueBubblesReplyCacheByMessageId.delete(messageId);
@ -110,7 +110,7 @@ function rememberBlueBubblesReplyCache(
// Clean up short ID mappings for expired entries // Clean up short ID mappings for expired entries
if (value.shortId) { if (value.shortId) {
blueBubblesShortIdToUuid.delete(value.shortId); blueBubblesShortIdToUuid.delete(value.shortId);
blueBubblesUuidToShortId.delete(key); blueBubblesUuidToShortId.delete(value.messageId.trim());
} }
continue; continue;
} }
@ -124,7 +124,7 @@ function rememberBlueBubblesReplyCache(
// Clean up short ID mappings for evicted entries // Clean up short ID mappings for evicted entries
if (oldEntry?.shortId) { if (oldEntry?.shortId) {
blueBubblesShortIdToUuid.delete(oldEntry.shortId); blueBubblesShortIdToUuid.delete(oldEntry.shortId);
blueBubblesUuidToShortId.delete(oldest); blueBubblesUuidToShortId.delete(oldEntry.messageId.trim());
} }
} }
@ -132,8 +132,8 @@ function rememberBlueBubblesReplyCache(
} }
/** /**
* Resolves a short message ID (e.g., "1", "2") to a full BlueBubbles UUID. * Resolves a short message ID (e.g., "1", "2") to a full BlueBubbles GUID.
* Returns the input unchanged if it's already a UUID or not found in the mapping. * Returns the input unchanged if it's already a GUID or not found in the mapping.
*/ */
export function resolveBlueBubblesMessageId( export function resolveBlueBubblesMessageId(
shortOrUuid: string, shortOrUuid: string,
@ -169,12 +169,15 @@ export function _resetBlueBubblesShortIdState(): void {
} }
/** /**
* Gets the short ID for a UUID, if one exists. * Gets the short ID for a message GUID, if one exists.
* Normalizes the UUID by stripping "p:N/" prefix before lookup.
*/ */
function getShortIdForUuid(uuid: string): string | undefined { function getShortIdForUuid(uuid: string): string | undefined {
const normalized = normalizeMessageIdForCache(uuid); const trimmed = uuid.trim();
return blueBubblesUuidToShortId.get(normalized); if (!trimmed) return undefined;
const direct = blueBubblesUuidToShortId.get(trimmed);
if (direct) return direct;
const normalized = normalizeMessageIdForCache(trimmed);
return normalized === trimmed ? undefined : blueBubblesUuidToShortId.get(normalized);
} }
function resolveReplyContextFromCache(params: { function resolveReplyContextFromCache(params: {
@ -1616,7 +1619,7 @@ async function processMessage(
replyToId = tapbackContext.replyToId; replyToId = tapbackContext.replyToId;
} }
if (replyToId && (!replyToBody || !replyToSender)) { if (replyToId) {
const cached = resolveReplyContextFromCache({ const cached = resolveReplyContextFromCache({
accountId: account.accountId, accountId: account.accountId,
replyToId, replyToId,