// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation public protocol SignalRecipientManager { func fetchRecipientIfPhoneNumberVisible( _ phoneNumber: String, tx: DBReadTransaction ) -> SignalRecipient? func modifyAndSave( _ recipient: SignalRecipient, deviceIdsToAdd: [UInt32], deviceIdsToRemove: [UInt32], shouldUpdateStorageService: Bool, tx: DBWriteTransaction ) func markAsUnregisteredAndSave( _ recipient: SignalRecipient, unregisteredAt: UnregisteredAt, shouldUpdateStorageService: Bool, tx: DBWriteTransaction ) } public enum UnregisteredAt { case now case specificTimeFromOtherDevice(UInt64) } extension SignalRecipientManager { public func markAsRegisteredAndSave( _ recipient: SignalRecipient, deviceId: UInt32 = OWSDevice.primaryDeviceId, shouldUpdateStorageService: Bool, tx: DBWriteTransaction ) { modifyAndSave( recipient, deviceIdsToAdd: [deviceId], deviceIdsToRemove: [], shouldUpdateStorageService: shouldUpdateStorageService, tx: tx ) } } public class SignalRecipientManagerImpl: SignalRecipientManager { private let phoneNumberVisibilityFetcher: any PhoneNumberVisibilityFetcher private let recipientDatabaseTable: any RecipientDatabaseTable let storageServiceManager: any StorageServiceManager public init( phoneNumberVisibilityFetcher: any PhoneNumberVisibilityFetcher, recipientDatabaseTable: any RecipientDatabaseTable, storageServiceManager: any StorageServiceManager ) { self.phoneNumberVisibilityFetcher = phoneNumberVisibilityFetcher self.recipientDatabaseTable = recipientDatabaseTable self.storageServiceManager = storageServiceManager } public func fetchRecipientIfPhoneNumberVisible(_ phoneNumber: String, tx: DBReadTransaction) -> SignalRecipient? { let recipient = recipientDatabaseTable.fetchRecipient(phoneNumber: phoneNumber, transaction: tx) guard let recipient else { return nil } guard phoneNumberVisibilityFetcher.isPhoneNumberVisible(for: recipient, tx: tx) else { return nil } return recipient } public func markAsUnregisteredAndSave( _ recipient: SignalRecipient, unregisteredAt: UnregisteredAt, shouldUpdateStorageService: Bool, tx: DBWriteTransaction ) { if case .specificTimeFromOtherDevice(let timestamp) = unregisteredAt { setUnregisteredAtTimestamp(timestamp, for: recipient, shouldUpdateStorageService: shouldUpdateStorageService) } modifyAndSave( recipient, deviceIdsToAdd: [], deviceIdsToRemove: recipient.deviceIds, shouldUpdateStorageService: shouldUpdateStorageService, tx: tx ) } public func modifyAndSave( _ recipient: SignalRecipient, deviceIdsToAdd: [UInt32], deviceIdsToRemove: [UInt32], shouldUpdateStorageService: Bool, tx: DBWriteTransaction ) { var deviceIdsToAdd = deviceIdsToAdd // Always add the primary if any other device is registered. if !deviceIdsToAdd.isEmpty { deviceIdsToAdd.append(OWSDevice.primaryDeviceId) } let oldDeviceIds = Set(recipient.deviceIds) let newDeviceIds = oldDeviceIds.union(deviceIdsToAdd).subtracting(deviceIdsToRemove) if oldDeviceIds == newDeviceIds { return } Logger.info("Updating \(recipient.aci?.logString ?? recipient.pni?.logString ?? "<>")'s devices. Added \(newDeviceIds.subtracting(oldDeviceIds).sorted()). Removed \(oldDeviceIds.subtracting(newDeviceIds).sorted()).") setDeviceIds(newDeviceIds, for: recipient, shouldUpdateStorageService: shouldUpdateStorageService) recipientDatabaseTable.updateRecipient(recipient, transaction: tx) } }