diff --git a/Signal/src/Models/AccountManager.swift b/Signal/src/Models/AccountManager.swift index 8f57c38d79..112a567512 100644 --- a/Signal/src/Models/AccountManager.swift +++ b/Signal/src/Models/AccountManager.swift @@ -174,8 +174,19 @@ public class AccountManager: NSObject { throw error } } - }.done { (_) -> Void in + }.done { self.completeRegistration() + }.then { _ -> Promise in + BenchEventStart(title: "waiting for initial storage service restore", eventId: "initial-storage-service-restore") + return StorageServiceManager.shared.restoreOrCreateManifestIfNecessary().asVoid().ensure { + BenchEventComplete(eventId: "initial-storage-service-restore") + }.timeout(seconds: 60) + }.then { _ -> Promise in + // In the case that we restored our profile from a previous registration, + // re-upload it so that the user does not need to refill in all the details. + // Right now the avatar will always be lost since we do not store avatars in + // the storage service. + self.profileManager.reuploadLocalProfilePromise() } registrationPromise.retainUntilComplete() @@ -236,15 +247,32 @@ public class AccountManager: NSObject { } }.then { self.deviceService.updateCapabilities() - }.then { _ -> Promise in + }.done { self.completeRegistration() + }.then { _ -> Promise in + BenchEventStart(title: "waiting for initial storage service restore", eventId: "initial-storage-service-restore") + + self.databaseStorage.asyncWrite { transaction in + OWSSyncManager.shared().sendKeysSyncRequestMessage(transaction: transaction) + } + + let storageServiceRestorePromise = firstly { + NotificationCenter.default.observe(once: .OWSSyncManagerKeysSyncDidComplete).asVoid() + }.then { + StorageServiceManager.shared.restoreOrCreateManifestIfNecessary().asVoid() + }.ensure { + BenchEventComplete(eventId: "initial-storage-service-restore") + }.timeout(seconds: 60) // we wait a bit for the initial syncs to come in before proceeding to the inbox // because we want to present the inbox already populated with groups and contacts, // rather than have the trickle in moments later. + // TODO: Eventually, we can rely entirely on the storage service and will no longer + // need to do any initial sync beyond the "keys" sync. For now, we try and do both + // operations in parallel. BenchEventStart(title: "waiting for initial contact and group sync", eventId: "initial-contact-sync") - return firstly { + let initialSyncMessagePromise = firstly { OWSSyncManager.shared().sendInitialSyncRequestsAwaitingCreatedThreadOrdering(timeoutSeconds: 60) }.done(on: .global() ) { orderedThreadIds in Logger.debug("orderedThreadIds: \(orderedThreadIds)") @@ -265,6 +293,8 @@ public class AccountManager: NSObject { }.ensure { BenchEventComplete(eventId: "initial-contact-sync") } + + return when(fulfilled: [storageServiceRestorePromise, initialSyncMessagePromise]) } } diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index 57ff540ae3..3caa6ad271 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -483,6 +483,12 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat case ProfileViewMode_Registration: self.navigationItem.hidesBackButton = YES; self.navigationItem.rightBarButtonItem = nil; + + // During registration, if you have a name pre-populatd we want + // to enable the save button even if you haven't edited anything. + if (self.givenNameTextField.text.length > 0) { + forceSaveButtonEnabled = YES; + } break; case ProfileViewMode_ExperienceUpgrade: self.navigationItem.rightBarButtonItem = nil; diff --git a/SignalMessaging/Storage Service/StorageServiceManager.swift b/SignalMessaging/Storage Service/StorageServiceManager.swift index c546bbebb4..d480ab68a1 100644 --- a/SignalMessaging/Storage Service/StorageServiceManager.swift +++ b/SignalMessaging/Storage Service/StorageServiceManager.swift @@ -1,14 +1,15 @@ // -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // import Foundation +import PromiseKit @objc(OWSStorageServiceManager) -class StorageServiceManager: NSObject, StorageServiceManagerProtocol { +public class StorageServiceManager: NSObject, StorageServiceManagerProtocol { @objc - static let shared = StorageServiceManager() + public static let shared = StorageServiceManager() var tsAccountManager: TSAccountManager { return SSKEnvironment.shared.tsAccountManager @@ -18,35 +19,24 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol { super.init() AppReadiness.runNowOrWhenAppDidBecomeReady { - self.registrationStateDidChange() - - NotificationCenter.default.addObserver( - self, - selector: #selector(self.registrationStateDidChange), - name: .RegistrationStateDidChange, - object: nil - ) - NotificationCenter.default.addObserver( self, selector: #selector(self.willResignActive), name: .OWSApplicationWillResignActive, object: nil ) + + guard self.tsAccountManager.isRegisteredAndReady else { return } + + // Schedule a restore. This will do nothing unless we've never + // registered a manifest before. + self.restoreOrCreateManifestIfNecessary() + + // If we have any pending changes since we last launch, back them up now. + self.backupPendingChanges() } } - @objc private func registrationStateDidChange() { - guard self.tsAccountManager.isRegisteredAndReady else { return } - - // Schedule a restore. This will do nothing unless we've never - // registered a manifest before. - restoreOrCreateManifestIfNecessary() - - // If we have any pending changes since we last launch, back them up now. - backupPendingChanges() - } - @objc private func willResignActive() { // If we have any pending changes, start a back up immediately // to try and make sure the service doesn't get stale. If for @@ -58,7 +48,7 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol { // MARK: - @objc - func recordPendingDeletions(deletedIds: [AccountId]) { + public func recordPendingDeletions(deletedIds: [AccountId]) { let operation = StorageServiceOperation.recordPendingDeletions(deletedIds) StorageServiceOperation.operationQueue.addOperation(operation) @@ -68,7 +58,7 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol { } @objc - func recordPendingDeletions(deletedAddresses: [SignalServiceAddress]) { + public func recordPendingDeletions(deletedAddresses: [SignalServiceAddress]) { let operation = StorageServiceOperation.recordPendingDeletions(deletedAddresses) StorageServiceOperation.operationQueue.addOperation(operation) @@ -78,7 +68,7 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol { } @objc - func recordPendingUpdates(updatedIds: [AccountId]) { + public func recordPendingUpdates(updatedIds: [AccountId]) { let operation = StorageServiceOperation.recordPendingUpdates(updatedIds) StorageServiceOperation.operationQueue.addOperation(operation) @@ -88,7 +78,7 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol { } @objc - func recordPendingUpdates(updatedAddresses: [SignalServiceAddress]) { + public func recordPendingUpdates(updatedAddresses: [SignalServiceAddress]) { let operation = StorageServiceOperation.recordPendingUpdates(updatedAddresses) StorageServiceOperation.operationQueue.addOperation(operation) @@ -98,15 +88,17 @@ class StorageServiceManager: NSObject, StorageServiceManagerProtocol { } @objc - func backupPendingChanges() { + public func backupPendingChanges() { let operation = StorageServiceOperation(mode: .backup) StorageServiceOperation.operationQueue.addOperation(operation) } @objc - func restoreOrCreateManifestIfNecessary() { + @discardableResult + public func restoreOrCreateManifestIfNecessary() -> AnyPromise { let operation = StorageServiceOperation(mode: .restoreOrCreate) StorageServiceOperation.operationQueue.addOperation(operation) + return AnyPromise(operation.promise) } // MARK: - Backup Scheduling @@ -183,14 +175,28 @@ class StorageServiceOperation: OWSOperation { } private let mode: Mode + let promise: Promise + private let resolver: Resolver + fileprivate init(mode: Mode) { self.mode = mode + (self.promise, self.resolver) = Promise.pending() super.init() self.remainingRetries = 4 } // MARK: - Run + override func didSucceed() { + super.didSucceed() + resolver.fulfill(()) + } + + override func didFail(error: Error) { + super.didFail(error: error) + resolver.reject(error) + } + // Called every retry, this is where the bulk of the operation's work should go. override public func run() { Logger.info("\(mode)") diff --git a/SignalMessaging/Storage Service/StorageServiceProto+Sync.swift b/SignalMessaging/Storage Service/StorageServiceProto+Sync.swift index e30f611ba5..6c1549f3a1 100644 --- a/SignalMessaging/Storage Service/StorageServiceProto+Sync.swift +++ b/SignalMessaging/Storage Service/StorageServiceProto+Sync.swift @@ -148,6 +148,12 @@ extension StorageServiceProtoContactRecord { mergeState = .needsUpdate(recipient.accountId) } + // The only thing we currently want to preserve for the local user is profile + // information. Everything else doesn't make sense to store / update and can + // lead to us being in a weird state such as thinking our own safety number changed. + // If more data needs to be restored for the local user it should be done above this line. + guard !address.isLocalAddress else { return mergeState } + // If our local identity differs from the service, use the service's value. if let identityKey = identity?.key, let identityState = identity?.state?.verificationState, localIdentityKey != identityKey || localIdentityState != identityState { diff --git a/SignalMessaging/contacts/OWSSyncManager.h b/SignalMessaging/contacts/OWSSyncManager.h index ca291a702e..1bbd5e5103 100644 --- a/SignalMessaging/contacts/OWSSyncManager.h +++ b/SignalMessaging/contacts/OWSSyncManager.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // #import @@ -7,6 +7,7 @@ NS_ASSUME_NONNULL_BEGIN extern NSString *const OWSSyncManagerConfigurationSyncDidCompleteNotification; +extern NSString *const OWSSyncManagerKeysSyncDidCompleteNotification; @class AnyPromise; @class OWSContactsManager; diff --git a/SignalMessaging/contacts/OWSSyncManager.m b/SignalMessaging/contacts/OWSSyncManager.m index cba2c5911d..a69949e20b 100644 --- a/SignalMessaging/contacts/OWSSyncManager.m +++ b/SignalMessaging/contacts/OWSSyncManager.m @@ -32,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN NSString *const OWSSyncManagerConfigurationSyncDidCompleteNotification = @"OWSSyncManagerConfigurationSyncDidCompleteNotification"; +NSString *const OWSSyncManagerKeysSyncDidCompleteNotification = @"OWSSyncManagerKeysSyncDidCompleteNotification"; NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManagerLastMessageKey"; diff --git a/SignalMessaging/contacts/OWSSyncManager.swift b/SignalMessaging/contacts/OWSSyncManager.swift index ca08cfcb1c..ff896574dd 100644 --- a/SignalMessaging/contacts/OWSSyncManager.swift +++ b/SignalMessaging/contacts/OWSSyncManager.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // import Foundation @@ -39,7 +39,8 @@ extension OWSSyncManager: SyncManagerProtocolSwift { NotificationCenter.default.observe(once: .IncomingContactSyncDidComplete).asVoid(), NotificationCenter.default.observe(once: .IncomingGroupSyncDidComplete).asVoid(), NotificationCenter.default.observe(once: .OWSSyncManagerConfigurationSyncDidComplete).asVoid(), - NotificationCenter.default.observe(once: .OWSBlockingManagerBlockedSyncDidComplete).asVoid() + NotificationCenter.default.observe(once: .OWSBlockingManagerBlockedSyncDidComplete).asVoid(), + NotificationCenter.default.observe(once: .OWSSyncManagerKeysSyncDidComplete).asVoid() ]) } @@ -74,6 +75,10 @@ extension OWSSyncManager: SyncManagerProtocolSwift { } KeyBackupService.storeSyncedKey(type: .storageService, data: syncMessage.storageService, transaction: transaction) + + transaction.addAsyncCompletion { + NotificationCenter.default.postNotificationNameAsync(.OWSSyncManagerKeysSyncDidComplete, object: nil) + } } @objc diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index f28c2c4ce9..fe3b904d41 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -1050,6 +1050,13 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; return; } + // We also keep track of our local profile key under a special hard coded address, + // update it accordingly. This should generally only happen if we're restoring + // our profile data from the storage service. + if (address.isLocalAddress) { + [self setLocalProfileKey:profileKey transaction:transaction]; + } + OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForAddress:address transaction:transaction]; OWSAssertDebug(userProfile); @@ -1082,6 +1089,13 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; OWSUserProfile *userProfile = [OWSUserProfile getOrBuildUserProfileForAddress:address transaction:transaction]; [userProfile updateWithGivenName:givenName familyName:familyName transaction:transaction completion:nil]; + + if (address.isLocalAddress) { + [self.localUserProfile updateWithGivenName:givenName + familyName:familyName + transaction:transaction + completion:nil]; + } } - (nullable NSData *)profileKeyDataForAddress:(SignalServiceAddress *)address diff --git a/SignalServiceKit/src/TestUtils/FakeStorageServiceManager.swift b/SignalServiceKit/src/TestUtils/FakeStorageServiceManager.swift index 1fc7a5c7d7..3ca74a2424 100644 --- a/SignalServiceKit/src/TestUtils/FakeStorageServiceManager.swift +++ b/SignalServiceKit/src/TestUtils/FakeStorageServiceManager.swift @@ -1,8 +1,9 @@ // -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // import Foundation +import PromiseKit @objc(OWSFakeStorageServiceManager) class FakeStorageServiceManager: NSObject, StorageServiceManagerProtocol { @@ -13,5 +14,5 @@ class FakeStorageServiceManager: NSObject, StorageServiceManagerProtocol { func recordPendingUpdates(updatedAddresses: [SignalServiceAddress]) {} func backupPendingChanges() {} - func restoreOrCreateManifestIfNecessary() {} + func restoreOrCreateManifestIfNecessary() -> AnyPromise { AnyPromise(Promise.value(())) } } diff --git a/SignalServiceKit/src/Util/KeyBackupService.swift b/SignalServiceKit/src/Util/KeyBackupService.swift index 8061409b1a..0172543d0b 100644 --- a/SignalServiceKit/src/Util/KeyBackupService.swift +++ b/SignalServiceKit/src/Util/KeyBackupService.swift @@ -293,10 +293,7 @@ public class KeyBackupService: NSObject { default: // Most keys derive directly from the master key. // Only a few exceptions derive from another derived key. - guard let masterKey = cacheQueue.sync(execute: { cachedMasterKey }) else { - owsFailDebug("missing master key") - return nil - } + guard let masterKey = cacheQueue.sync(execute: { cachedMasterKey }) else { return nil } return masterKey } } @@ -543,15 +540,13 @@ public class KeyBackupService: NSObject { } // Only continue if we didn't previously have a master key or our master key has changed - guard masterKey != previousMasterKey else { return } + guard masterKey != previousMasterKey, tsAccountManager.isRegisteredAndReady else { return } // Trigger a re-creation of the storage manifest, our keys have changed storageServiceManager.restoreOrCreateManifestIfNecessary() - if tsAccountManager.isRegisteredAndReady { - // Sync our new keys with linked devices. - syncManager.sendKeysSyncMessage() - } + // Sync our new keys with linked devices. + syncManager.sendKeysSyncMessage() } public static func storeSyncedKey(type: DerivedKey, data: Data?, transaction: SDSAnyWriteTransaction) { diff --git a/SignalServiceKit/src/Util/OWSUserProfile.m b/SignalServiceKit/src/Util/OWSUserProfile.m index ad1cb36356..1a31ee5dbb 100644 --- a/SignalServiceKit/src/Util/OWSUserProfile.m +++ b/SignalServiceKit/src/Util/OWSUserProfile.m @@ -324,11 +324,15 @@ NSUInteger const kUserProfileSchemaVersion = 1; return; } - // Profile changes, record updates with storage service - [self.storageServiceManager recordPendingUpdatesWithUpdatedAddresses:@[ self.address ]]; - BOOL isLocalUserProfile = [self.address.phoneNumber isEqualToString:kLocalProfileUniqueId]; + // Profile changes, record updates with storage service + if (self.tsAccountManager.isRegisteredAndReady) { + [self.storageServiceManager + recordPendingUpdatesWithUpdatedAddresses:@[ isLocalUserProfile ? self.tsAccountManager.localAddress + : self.address ]]; + } + [transaction addAsyncCompletionWithQueue:dispatch_get_main_queue() block:^{ diff --git a/SignalServiceKit/src/Util/StorageService.swift b/SignalServiceKit/src/Util/StorageService.swift index 3d9584cd7d..ab8217888f 100644 --- a/SignalServiceKit/src/Util/StorageService.swift +++ b/SignalServiceKit/src/Util/StorageService.swift @@ -14,7 +14,9 @@ public protocol StorageServiceManagerProtocol { func recordPendingUpdates(updatedAddresses: [SignalServiceAddress]) func backupPendingChanges() - func restoreOrCreateManifestIfNecessary() + + @discardableResult + func restoreOrCreateManifestIfNecessary() -> AnyPromise } public struct StorageService { @@ -306,7 +308,6 @@ public struct StorageService { case 404: status = .notFound default: - owsFailDebug("invalid response \(response.statusCode)") if response.statusCode >= 500 { // This is a server error, retry return resolver.reject(StorageError.retryableAssertion)