import Foundation import Postbox /// Which message a delivered notification stands for, keyed by the `UNNotificationRequest` /// identifier the system assigned to it. /// /// The notification service extension records this for every message notification it /// handles. Siri's announce flow (AirPods, CarPlay) later asks the intents extension for the /// messages behind a notification through `INSearchForMessagesIntent.notificationIdentifiers`, /// and this is the only link between that identifier and a `MessageId`: the identifier is /// generated by the system per delivery and never appears in the push payload. /// /// The links live in one item-cache row as a bounded, newest-last list. Request identifiers /// are per-delivery UUIDs that never repeat, so a row per link would grow for the life of the /// account (the item cache has no eviction), while Siri only ever asks about a notification /// that is still recent. private struct NotificationRequestMessageIdLink: Codable { var requestIdentifier: String var peerId: Int64 var namespace: Int32 var id: Int32 } private struct NotificationRequestMessageIdLinks: Codable { var links: [NotificationRequestMessageIdLink] } /// How many recent notification → message links are kept per account. public let _internal_notificationRequestMessageIdsLimit: Int = 256 private let notificationRequestMessageIdsEntryId = ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.notificationRequestMessageIds, key: ValueBoxKey("links")) private func loadLinks(transaction: Transaction) -> [NotificationRequestMessageIdLink] { return transaction.retrieveItemCacheEntry(id: notificationRequestMessageIdsEntryId)?.get(NotificationRequestMessageIdLinks.self)?.links ?? [] } public func _internal_setNotificationRequestMessageId(transaction: Transaction, requestIdentifier: String, messageId: MessageId) { if requestIdentifier.isEmpty { return } var links = loadLinks(transaction: transaction) links.removeAll(where: { $0.requestIdentifier == requestIdentifier }) links.append(NotificationRequestMessageIdLink(requestIdentifier: requestIdentifier, peerId: messageId.peerId.toInt64(), namespace: messageId.namespace, id: messageId.id)) if links.count > _internal_notificationRequestMessageIdsLimit { links.removeFirst(links.count - _internal_notificationRequestMessageIdsLimit) } guard let entry = CodableEntry(NotificationRequestMessageIdLinks(links: links)) else { return } transaction.putItemCacheEntry(id: notificationRequestMessageIdsEntryId, entry: entry) } public func _internal_getNotificationRequestMessageId(transaction: Transaction, requestIdentifier: String) -> MessageId? { if requestIdentifier.isEmpty { return nil } guard let link = loadLinks(transaction: transaction).last(where: { $0.requestIdentifier == requestIdentifier }) else { return nil } return MessageId(peerId: PeerId(link.peerId), namespace: link.namespace, id: link.id) }