TM-SGNL-iOS/SignalServiceKit/Contacts/UserProfileMerger.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

100 lines
4.3 KiB
Swift

//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import LibSignalClient
class UserProfileMerger: RecipientMergeObserver {
private let userProfileStore: UserProfileStore
private let setProfileKeyShim: (OWSUserProfile, Aes256Key, DBWriteTransaction) -> Void
init(
userProfileStore: UserProfileStore,
setProfileKeyShim: @escaping (OWSUserProfile, Aes256Key, DBWriteTransaction) -> Void
) {
self.userProfileStore = userProfileStore
self.setProfileKeyShim = setProfileKeyShim
}
convenience init(userProfileStore: UserProfileStore) {
self.init(
userProfileStore: userProfileStore,
setProfileKeyShim: { userProfile, profileKey, tx in
userProfile.update(
profileKey: .setTo(profileKey),
userProfileWriter: .localUser,
transaction: SDSDB.shimOnlyBridge(tx),
completion: {
switch userProfile.internalAddress {
case .localUser:
break
case .otherUser(_):
guard let serviceId = userProfile.serviceId else {
return
}
let profileFetcher = SSKEnvironment.shared.profileFetcherRef
_ = profileFetcher.fetchProfileSync(for: serviceId)
}
}
)
}
)
}
func willBreakAssociation(for recipient: SignalRecipient, mightReplaceNonnilPhoneNumber: Bool, tx: DBWriteTransaction) {
mergeUserProfiles(for: recipient, tx: tx)
}
func didLearnAssociation(mergedRecipient: MergedRecipient, tx: DBWriteTransaction) {
if mergedRecipient.isLocalRecipient {
// The local recipient uses a constant key as its identifier in the
// database. However, if you change your own number, you may claim a phone
// number that was connected to some other account, and we want to
// guarantee that that profile is deleted.
fetchAndExpungeUserProfiles(for: mergedRecipient.newRecipient, tx: tx).forEach {
userProfileStore.removeUserProfile($0, tx: tx)
}
} else {
mergeUserProfiles(for: mergedRecipient.newRecipient, tx: tx)
}
}
private func mergeUserProfiles(for recipient: SignalRecipient, tx: DBWriteTransaction) {
let userProfiles = fetchAndExpungeUserProfiles(for: recipient, tx: tx)
guard let userProfileToMergeInto = userProfiles.first else {
return
}
let normalizedAddress = NormalizedDatabaseRecordAddress(
aci: recipient.aci,
phoneNumber: recipient.phoneNumber?.stringValue,
pni: recipient.pni
)
// One of these might not be set, or one of them might have a non-canonical
// representation (eg upper vs. lowercase ServiceId). Make sure both of
// these are updated to reflect that latest normalized value.
userProfileToMergeInto.serviceIdString = normalizedAddress?.serviceId?.serviceIdUppercaseString
userProfileToMergeInto.phoneNumber = normalizedAddress?.phoneNumber
userProfileStore.updateUserProfile(userProfileToMergeInto, tx: tx)
for userProfileToMergeFrom in userProfiles.dropFirst() {
if userProfileToMergeInto.profileKey == nil, let profileKey = userProfileToMergeFrom.profileKey {
setProfileKeyShim(userProfileToMergeInto, profileKey, tx)
}
userProfileStore.removeUserProfile(userProfileToMergeFrom, tx: tx)
}
}
private func fetchAndExpungeUserProfiles(for recipient: SignalRecipient, tx: DBWriteTransaction) -> [OWSUserProfile] {
return UniqueRecipientObjectMerger.fetchAndExpunge(
for: recipient,
serviceIdField: \.serviceIdString,
phoneNumberField: \.phoneNumber,
uniqueIdField: \.uniqueId,
fetchObjectsForServiceId: { userProfileStore.fetchUserProfiles(for: $0, tx: tx) },
fetchObjectsForPhoneNumber: { userProfileStore.fetchUserProfiles(for: $0, tx: tx) },
updateObject: { userProfileStore.updateUserProfile($0, tx: tx) }
)
}
}