238 lines
7.8 KiB
Swift
238 lines
7.8 KiB
Swift
//
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import LibSignalClient
|
|
import SignalCoreKit
|
|
|
|
@objc
|
|
public class LegacyChangePhoneNumber: NSObject {
|
|
|
|
private let changePhoneNumberPniManager: ChangePhoneNumberPniManager
|
|
|
|
/// Records change-number operations that were started (by this, or
|
|
/// potentially a prior launch of the app) but not yet known to have
|
|
/// completed.
|
|
///
|
|
/// If, on a launch, we find an incomplete change-number operation in here,
|
|
/// we will attempt to recover it.
|
|
private let incompleteChangeTokenStore = IncompleteChangeTokenStore()
|
|
|
|
@objc
|
|
public override init() {
|
|
self.changePhoneNumberPniManager = DependenciesBridge.shared.changePhoneNumberPniManager
|
|
|
|
super.init()
|
|
|
|
SwiftSingletons.register(self)
|
|
|
|
AppReadiness.runNowOrWhenAppDidBecomeReadyAsync {
|
|
self.recoverInterruptedChangeNumberIfNecessary()
|
|
}
|
|
}
|
|
|
|
/// Parameters representing a successfully-completed change.
|
|
public struct SuccessfulChangeParams {
|
|
/// Our account's new E164 on the service.
|
|
let newServiceE164: E164
|
|
/// Our account's ACI on the service.
|
|
let serviceAci: Aci
|
|
/// Our account's PNI on the service.
|
|
let servicePni: Pni
|
|
|
|
public init(
|
|
newServiceE164: E164,
|
|
serviceAci: Aci,
|
|
servicePni: Pni
|
|
) {
|
|
self.newServiceE164 = newServiceE164
|
|
self.serviceAci = serviceAci
|
|
self.servicePni = servicePni
|
|
}
|
|
}
|
|
|
|
/// Mark the given change as complete, optionally including additional data
|
|
/// if the change was completed successfully.
|
|
///
|
|
/// - Parameter changeToken
|
|
/// The change to mark as complete.
|
|
/// - Parameter successfulChange
|
|
/// If the change was successful, contains parameters representing our new
|
|
/// state post-change. `nil` if the change was unsuccessful.
|
|
public func changeDidComplete(
|
|
changeToken: ChangeToken,
|
|
successfulChangeParams: SuccessfulChangeParams?,
|
|
transaction: SDSAnyWriteTransaction
|
|
) {
|
|
owsAssertDebug(CurrentAppContext().isMainApp)
|
|
|
|
if let successfulChangeParams {
|
|
do {
|
|
try updateLocalPhoneNumber(
|
|
forServiceAci: successfulChangeParams.serviceAci,
|
|
servicePni: successfulChangeParams.servicePni,
|
|
serviceE164: successfulChangeParams.newServiceE164,
|
|
transaction: transaction
|
|
)
|
|
} catch {
|
|
Logger.error("Failed to update local state while completing change!")
|
|
}
|
|
}
|
|
|
|
incompleteChangeTokenStore.clear(
|
|
changeToken: changeToken,
|
|
transaction: transaction
|
|
)
|
|
}
|
|
|
|
/// Take steps to recover from an interrupted change-number operation,
|
|
/// indicated by the presence of the given change token.
|
|
private func recoverIncompleteChangeToken(
|
|
changeToken: ChangeToken
|
|
) -> Promise<Void> {
|
|
firstly { () -> Promise<WhoAmIRequestFactory.Responses.WhoAmI> in
|
|
self.accountServiceClient.getAccountWhoAmI()
|
|
}.done(on: DispatchQueue.global()) { whoAmIResponse in
|
|
self.databaseStorage.write { transaction in
|
|
let successfulChangeParams = SuccessfulChangeParams(
|
|
newServiceE164: whoAmIResponse.e164,
|
|
serviceAci: whoAmIResponse.aci,
|
|
servicePni: whoAmIResponse.pni
|
|
)
|
|
|
|
self.changeDidComplete(
|
|
changeToken: changeToken,
|
|
successfulChangeParams: successfulChangeParams,
|
|
transaction: transaction
|
|
)
|
|
}
|
|
}.recover(on: DispatchQueue.global()) { error in
|
|
if error.isNetworkFailureOrTimeout {
|
|
// If there was a network error, we can't confirm anything.
|
|
throw error
|
|
}
|
|
|
|
self.databaseStorage.write { transaction in
|
|
self.changeDidComplete(
|
|
changeToken: changeToken,
|
|
successfulChangeParams: nil,
|
|
transaction: transaction
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func recoverInterruptedChangeNumberIfNecessary() {
|
|
guard AppReadiness.isAppReady else {
|
|
owsFailDebug("isAppReady.")
|
|
return
|
|
}
|
|
|
|
if DependenciesBridge.shared.appExpiry.isExpired {
|
|
owsFailDebug("appExpiry.")
|
|
return
|
|
}
|
|
|
|
guard
|
|
CurrentAppContext().isMainApp,
|
|
DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered
|
|
else {
|
|
return
|
|
}
|
|
|
|
let incompleteChangeToken: ChangeToken? = self.databaseStorage.read { transaction in
|
|
incompleteChangeTokenStore.existingToken(
|
|
transaction: transaction
|
|
)
|
|
}
|
|
|
|
guard let incompleteChangeToken else {
|
|
return
|
|
}
|
|
|
|
firstly { () -> Promise<Void> in
|
|
recoverIncompleteChangeToken(
|
|
changeToken: incompleteChangeToken
|
|
)
|
|
}.done(on: DispatchQueue.global()) {
|
|
Logger.info("Recovered incomplete change token!")
|
|
}.catch(on: DispatchQueue.global()) { error in
|
|
Logger.info("Failed to recover incomplete change token: \(error)")
|
|
}
|
|
}
|
|
|
|
// MARK: - Update the local phone number
|
|
|
|
/// Update local state concerning the phone number, based on values fetched
|
|
/// from the service.
|
|
///
|
|
/// - Returns
|
|
/// The persisted local phone number.
|
|
@discardableResult
|
|
private func updateLocalPhoneNumber(
|
|
forServiceAci serviceAci: Aci,
|
|
servicePni: Pni,
|
|
serviceE164: E164,
|
|
transaction: SDSAnyWriteTransaction
|
|
) throws -> E164 {
|
|
guard
|
|
let localIdentifiers = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: transaction.asV2Read),
|
|
let localE164 = E164(localIdentifiers.phoneNumber)
|
|
else {
|
|
throw OWSAssertionError("Missing or invalid local parameters!")
|
|
}
|
|
|
|
let localAci = localIdentifiers.aci
|
|
let localPni = localIdentifiers.pni
|
|
|
|
guard serviceAci == localAci else {
|
|
throw OWSAssertionError("Service ACI \(serviceAci) unexpectedly did not match local ACI!")
|
|
}
|
|
|
|
Logger.info(
|
|
"""
|
|
localAci: \(localAci),
|
|
localPni: \(localPni?.logString ?? "nil"),
|
|
localE164: \(localE164),
|
|
serviceAci: \(serviceAci),
|
|
servicePni: \(servicePni),
|
|
serviceE164: \(serviceE164)")
|
|
"""
|
|
)
|
|
|
|
let recipientMerger = DependenciesBridge.shared.recipientMerger
|
|
let localRecipient = recipientMerger.applyMergeForLocalAccount(
|
|
aci: serviceAci,
|
|
phoneNumber: serviceE164,
|
|
pni: servicePni,
|
|
tx: transaction.asV2Write
|
|
)
|
|
let recipientManager = DependenciesBridge.shared.recipientManager
|
|
recipientManager.markAsRegisteredAndSave(localRecipient, shouldUpdateStorageService: false, tx: transaction.asV2Write)
|
|
|
|
if
|
|
serviceE164 != localE164
|
|
|| servicePni != localPni
|
|
{
|
|
Logger.info(
|
|
"Recording new phone number: \(serviceE164), PNI: \(servicePni)"
|
|
)
|
|
|
|
DependenciesBridge.shared.registrationStateChangeManager.didUpdateLocalPhoneNumber(
|
|
serviceE164,
|
|
aci: serviceAci,
|
|
pni: servicePni,
|
|
tx: transaction.asV2Write
|
|
)
|
|
|
|
self.storageServiceManager.recordPendingLocalAccountUpdates()
|
|
|
|
return serviceE164
|
|
} else {
|
|
return localE164
|
|
}
|
|
}
|
|
}
|