| const REPLY_CACHE_MAX = 2000; |
| const REPLY_CACHE_TTL_MS = 6 * 60 * 60 * 1000; |
|
|
| type BlueBubblesReplyCacheEntry = { |
| accountId: string; |
| messageId: string; |
| shortId: string; |
| chatGuid?: string; |
| chatIdentifier?: string; |
| chatId?: number; |
| senderLabel?: string; |
| body?: string; |
| timestamp: number; |
| }; |
|
|
| |
| const blueBubblesReplyCacheByMessageId = new Map<string, BlueBubblesReplyCacheEntry>(); |
|
|
| |
| const blueBubblesShortIdToUuid = new Map<string, string>(); |
| const blueBubblesUuidToShortId = new Map<string, string>(); |
| let blueBubblesShortIdCounter = 0; |
|
|
| function trimOrUndefined(value?: string | null): string | undefined { |
| const trimmed = value?.trim(); |
| return trimmed ? trimmed : undefined; |
| } |
|
|
| function generateShortId(): string { |
| blueBubblesShortIdCounter += 1; |
| return String(blueBubblesShortIdCounter); |
| } |
|
|
| export function rememberBlueBubblesReplyCache( |
| entry: Omit<BlueBubblesReplyCacheEntry, "shortId">, |
| ): BlueBubblesReplyCacheEntry { |
| const messageId = entry.messageId.trim(); |
| if (!messageId) { |
| return { ...entry, shortId: "" }; |
| } |
|
|
| |
| let shortId = blueBubblesUuidToShortId.get(messageId); |
| if (!shortId) { |
| shortId = generateShortId(); |
| blueBubblesShortIdToUuid.set(shortId, messageId); |
| blueBubblesUuidToShortId.set(messageId, shortId); |
| } |
|
|
| const fullEntry: BlueBubblesReplyCacheEntry = { ...entry, messageId, shortId }; |
|
|
| |
| blueBubblesReplyCacheByMessageId.delete(messageId); |
| blueBubblesReplyCacheByMessageId.set(messageId, fullEntry); |
|
|
| |
| const cutoff = Date.now() - REPLY_CACHE_TTL_MS; |
| for (const [key, value] of blueBubblesReplyCacheByMessageId) { |
| if (value.timestamp < cutoff) { |
| blueBubblesReplyCacheByMessageId.delete(key); |
| |
| if (value.shortId) { |
| blueBubblesShortIdToUuid.delete(value.shortId); |
| blueBubblesUuidToShortId.delete(key); |
| } |
| continue; |
| } |
| break; |
| } |
| while (blueBubblesReplyCacheByMessageId.size > REPLY_CACHE_MAX) { |
| const oldest = blueBubblesReplyCacheByMessageId.keys().next().value as string | undefined; |
| if (!oldest) { |
| break; |
| } |
| const oldEntry = blueBubblesReplyCacheByMessageId.get(oldest); |
| blueBubblesReplyCacheByMessageId.delete(oldest); |
| |
| if (oldEntry?.shortId) { |
| blueBubblesShortIdToUuid.delete(oldEntry.shortId); |
| blueBubblesUuidToShortId.delete(oldest); |
| } |
| } |
|
|
| return fullEntry; |
| } |
|
|
| |
| |
| |
| |
| export function resolveBlueBubblesMessageId( |
| shortOrUuid: string, |
| opts?: { requireKnownShortId?: boolean }, |
| ): string { |
| const trimmed = shortOrUuid.trim(); |
| if (!trimmed) { |
| return trimmed; |
| } |
|
|
| |
| if (/^\d+$/.test(trimmed)) { |
| const uuid = blueBubblesShortIdToUuid.get(trimmed); |
| if (uuid) { |
| return uuid; |
| } |
| if (opts?.requireKnownShortId) { |
| throw new Error( |
| `BlueBubbles short message id "${trimmed}" is no longer available. Use MessageSidFull.`, |
| ); |
| } |
| } |
|
|
| |
| return trimmed; |
| } |
|
|
| |
| |
| |
| |
| export function _resetBlueBubblesShortIdState(): void { |
| blueBubblesShortIdToUuid.clear(); |
| blueBubblesUuidToShortId.clear(); |
| blueBubblesReplyCacheByMessageId.clear(); |
| blueBubblesShortIdCounter = 0; |
| } |
|
|
| |
| |
| |
| export function getShortIdForUuid(uuid: string): string | undefined { |
| return blueBubblesUuidToShortId.get(uuid.trim()); |
| } |
|
|
| export function resolveReplyContextFromCache(params: { |
| accountId: string; |
| replyToId: string; |
| chatGuid?: string; |
| chatIdentifier?: string; |
| chatId?: number; |
| }): BlueBubblesReplyCacheEntry | null { |
| const replyToId = params.replyToId.trim(); |
| if (!replyToId) { |
| return null; |
| } |
|
|
| const cached = blueBubblesReplyCacheByMessageId.get(replyToId); |
| if (!cached) { |
| return null; |
| } |
| if (cached.accountId !== params.accountId) { |
| return null; |
| } |
|
|
| const cutoff = Date.now() - REPLY_CACHE_TTL_MS; |
| if (cached.timestamp < cutoff) { |
| blueBubblesReplyCacheByMessageId.delete(replyToId); |
| return null; |
| } |
|
|
| const chatGuid = trimOrUndefined(params.chatGuid); |
| const chatIdentifier = trimOrUndefined(params.chatIdentifier); |
| const cachedChatGuid = trimOrUndefined(cached.chatGuid); |
| const cachedChatIdentifier = trimOrUndefined(cached.chatIdentifier); |
| const chatId = typeof params.chatId === "number" ? params.chatId : undefined; |
| const cachedChatId = typeof cached.chatId === "number" ? cached.chatId : undefined; |
|
|
| |
| if (chatGuid && cachedChatGuid && chatGuid !== cachedChatGuid) { |
| return null; |
| } |
| if ( |
| !chatGuid && |
| chatIdentifier && |
| cachedChatIdentifier && |
| chatIdentifier !== cachedChatIdentifier |
| ) { |
| return null; |
| } |
| if (!chatGuid && !chatIdentifier && chatId && cachedChatId && chatId !== cachedChatId) { |
| return null; |
| } |
|
|
| return cached; |
| } |
|
|