// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation /// These extensions allow callers to use RecipientHidingManager /// via SignalServiceAddress, temporarily, while we still have /// callsites using SignalServiceAddress. /// The actual root identifier used by RecipientHidingManager is /// SignalRecipient (well, its row id), and all these methods just do /// a lookup of SignalRecipient by address. /// Eventually, all callsites should be explicit about the recipient /// identifier they have (whether a SignalRecipient or some as /// yet undefined combo of {ACI} or {e164 + PNI}. /// At that point, this extension should be deleted. extension RecipientHidingManager { // MARK: Read /// Returns set of ``SignalServiceAddress``es corresponding with /// all hidden recipients. /// /// - Parameter tx: The transaction to use for database operations. public func hiddenAddresses(tx: DBReadTransaction) -> Set { return Set(hiddenRecipients(tx: tx).compactMap { (recipient: SignalRecipient) -> SignalServiceAddress? in let address = recipient.address guard address.isValid else { return nil } return address }) } /// Whether a service address corresponds with a hidden recipient. /// /// - Parameter address: The service address corresponding with /// the ``SignalRecipient``. /// - Parameter tx: The transaction to use for database operations. /// /// - Returns: True if the address is hidden. public func isHiddenAddress(_ address: SignalServiceAddress, tx: DBReadTransaction) -> Bool { guard let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: tx)?.aciAddress, !localAddress.isEqualToAddress(address) else { return false } guard let recipient = recipient(from: address, tx: tx) else { return false } return isHiddenRecipient(recipient, tx: tx) } // MARK: Write /// Inserts hidden-recipient state for the given `SignalServiceAddress`. /// /// - Parameter inKnownMessageRequestState /// Whether we know immediately that this hidden recipient's chat should be /// in a message-request state. /// - Parameter wasLocallyInitiated: Whether the user initiated /// the hide on this device (true) or a linked device (false). public func addHiddenRecipient( _ address: SignalServiceAddress, inKnownMessageRequestState: Bool, wasLocallyInitiated: Bool, tx: DBWriteTransaction ) throws { guard let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: tx)?.aciAddress, !localAddress.isEqualToAddress(address) else { throw RecipientHidingError.cannotHideLocalAddress } let recipientFetcher = DependenciesBridge.shared.recipientFetcher let recipient = try { () throws -> SignalRecipient in if let serviceId = address.serviceId { return recipientFetcher.fetchOrCreate(serviceId: serviceId, tx: tx) } if let phoneNumber = address.e164 { return recipientFetcher.fetchOrCreate(phoneNumber: phoneNumber, tx: tx) } throw RecipientHidingError.invalidRecipientAddress(address) }() try addHiddenRecipient( recipient, inKnownMessageRequestState: inKnownMessageRequestState, wasLocallyInitiated: wasLocallyInitiated, tx: tx ) } /// Removes a recipient from the hidden recipient table. /// /// - Parameter address: The service address corresponding with /// the ``SignalRecipient``. /// - Parameter wasLocallyInitiated: Whether the user initiated /// the hide on this device (true) or a linked device (false). /// - Parameter tx: The transaction to use for database operations. public func removeHiddenRecipient( _ address: SignalServiceAddress, wasLocallyInitiated: Bool, tx: DBWriteTransaction ) throws { guard let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: tx)?.aciAddress, !localAddress.isEqualToAddress(address) else { owsFailDebug("Cannot unhide the local address") return } if let recipient = recipient(from: address, tx: tx) { try removeHiddenRecipient(recipient, wasLocallyInitiated: wasLocallyInitiated, tx: tx) } } /// Returns the id for a recipient, if the recipient exists. /// /// - Parameter address: The service address corresponding with /// the ``SignalRecipient``. /// - Parameter tx: The transaction to use for database operations. /// /// - Returns: The ``SignalRecipient``. private func recipient(from address: SignalServiceAddress, tx: DBReadTransaction) -> SignalRecipient? { return DependenciesBridge.shared.recipientDatabaseTable .fetchRecipient(address: address, tx: tx) } }