diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 7376bee1b..6a4135142 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -1041,7 +1041,7 @@ static NSTimeInterval launchStartedAt; [AppVersion.sharedInstance mainAppLaunchDidComplete]; - [Environment.shared.contactsManager loadSignalAccountsFromCache]; + [Environment.shared.contactsManager setup]; [Environment.shared.contactsManager startObserving]; // If there were any messages in our local queue which we hadn't yet processed. diff --git a/Signal/src/Models/AccountManager.swift b/Signal/src/Models/AccountManager.swift index 7f134613e..dede8708f 100644 --- a/Signal/src/Models/AccountManager.swift +++ b/Signal/src/Models/AccountManager.swift @@ -114,10 +114,7 @@ public class AccountManager: NSObject { } func enableManualMessageFetching() -> Promise { - tsAccountManager.setIsManualMessageFetchEnabled(true) - - // Try to update the account attributes to reflect this change. - return SignalServiceRestClient().updateAcountAttributes() + return tsAccountManager.setIsManualMessageFetchEnabled(true).asPromise().asVoid() } // MARK: Turn Server diff --git a/SignalMessaging/contacts/OWSContactsManager.h b/SignalMessaging/contacts/OWSContactsManager.h index cf4bede12..a03c06d00 100644 --- a/SignalMessaging/contacts/OWSContactsManager.h +++ b/SignalMessaging/contacts/OWSContactsManager.h @@ -44,7 +44,7 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification; - (SignalAccount *)fetchOrBuildSignalAccountForRecipientId:(NSString *)recipientId; - (BOOL)hasSignalAccountForRecipientId:(NSString *)recipientId; -- (void)loadSignalAccountsFromCache; +- (void)setup; #pragma mark - System Contact Fetching @@ -55,6 +55,8 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification; @property (nonatomic, readonly) BOOL supportsContactEditing; +@property (atomic, readonly) BOOL isSetup; + // Request systems contacts and start syncing changes. The user will see an alert // if they haven't previously. - (void)requestSystemContactsOnce; diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index c6ac01f1e..8ef395490 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -48,6 +48,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan @property (nonatomic, readonly) YapDatabaseConnection *dbWriteConnection; @property (nonatomic, readonly) NSCache *cnContactCache; @property (nonatomic, readonly) NSCache *cnContactAvatarCache; +@property (atomic) BOOL isSetup; @end @@ -84,8 +85,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan return self; } -- (void)loadSignalAccountsFromCache -{ +- (void)setup { __block NSMutableArray *signalAccounts; [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { NSUInteger signalAccountCount = [SignalAccount numberOfKeysInCollectionWithTransaction:transaction]; @@ -571,6 +571,8 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan self.signalAccounts = [signalAccounts copy]; [self.profileManager setContactRecipientIds:signalAccountMap.allKeys]; + self.isSetup = YES; + [[NSNotificationCenter defaultCenter] postNotificationNameAsync:OWSContactsManagerSignalAccountsDidChangeNotification object:nil]; diff --git a/SignalMessaging/contacts/OWSContactsSyncing.m b/SignalMessaging/contacts/OWSContactsSyncing.m index 9a9237cb3..6bb842bd5 100644 --- a/SignalMessaging/contacts/OWSContactsSyncing.m +++ b/SignalMessaging/contacts/OWSContactsSyncing.m @@ -79,6 +79,10 @@ NSString *const kOWSPrimaryStorageOWSContactsSyncingLastMessageKey selector:@selector(signalAccountsDidChange:) name:OWSContactsManagerSignalAccountsDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(profileKeyDidChange:) + name:kNSNotificationName_ProfileKeyDidChange + object:nil]; return self; } @@ -95,6 +99,12 @@ NSString *const kOWSPrimaryStorageOWSContactsSyncingLastMessageKey [self sendSyncContactsMessageIfPossible]; } +- (void)profileKeyDidChange:(id)notification { + OWSAssertIsOnMainThread(); + + [self sendSyncContactsMessageIfPossible]; +} + - (YapDatabaseConnection *)editingDatabaseConnection { return OWSPrimaryStorage.sharedManager.dbReadWriteConnection; @@ -172,9 +182,9 @@ NSString *const kOWSPrimaryStorageOWSContactsSyncingLastMessageKey - (void)sendSyncContactsMessageIfPossible { OWSAssertIsOnMainThread(); - if (self.contactsManager.signalAccounts.count == 0) { - // Don't bother if the contacts manager has no contacts, - // e.g. if the contacts manager hasn't finished setup. + + if (!self.contactsManager.isSetup) { + // Don't bother if the contacts manager hasn't finished setup. return; } diff --git a/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift b/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift index 3c5180db7..96dec7ff8 100644 --- a/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift +++ b/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift @@ -8,6 +8,12 @@ import SignalServiceKit @objc public class OWS111UDAttributesMigration: OWSDatabaseMigration { + // MARK: - Dependencies + + private var tsAccountManager: TSAccountManager { + return TSAccountManager.sharedInstance() + } + // MARK: - // increment a similar constant for each migration. @@ -29,13 +35,10 @@ public class OWS111UDAttributesMigration: OWSDatabaseMigration { } private func doMigration(completion: @escaping OWSDatabaseMigrationCompletion) { - return SignalServiceRestClient().updateAcountAttributes().then(execute: { _ in - self.dbReadWriteConnection().readWrite { transaction in - self.save(with: transaction) - } - }) - .always { - completion() - }.retainUntilComplete() + tsAccountManager.updateAccountAttributes() + + self.dbReadWriteConnection().readWrite { transaction in + self.save(with: transaction) + } } } diff --git a/SignalMessaging/profiles/OWSProfileManager.h b/SignalMessaging/profiles/OWSProfileManager.h index 533b36b38..e0846acf6 100644 --- a/SignalMessaging/profiles/OWSProfileManager.h +++ b/SignalMessaging/profiles/OWSProfileManager.h @@ -7,6 +7,7 @@ NS_ASSUME_NONNULL_BEGIN extern NSString *const kNSNotificationName_ProfileWhitelistDidChange; +extern NSString *const kNSNotificationName_ProfileKeyDidChange; extern const NSUInteger kOWSProfileManager_NameDataLength; extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter; diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index bc7386884..3b94b2b26 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -39,6 +39,8 @@ NSString *const kNSNotificationName_ProfileWhitelistDidChange = @"kNSNotificatio NSString *const kOWSProfileManager_UserWhitelistCollection = @"kOWSProfileManager_UserWhitelistCollection"; NSString *const kOWSProfileManager_GroupWhitelistCollection = @"kOWSProfileManager_GroupWhitelistCollection"; +NSString *const kNSNotificationName_ProfileKeyDidChange = @"kNSNotificationName_ProfileKeyDidChange"; + // The max bytes for a user's profile name, encoded in UTF8. // Before encrypting and submitting we NULL pad the name data to this length. const NSUInteger kOWSProfileManager_NameDataLength = 26; @@ -96,6 +98,8 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); [self rotateLocalProfileKeyIfNecessary]; }]; + [self observeNotifications]; + return self; } @@ -621,8 +625,20 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); // No need to rotate the profile key. return success(); } + [self rotateProfileKeyWithIntersectingRecipientIds:intersectingRecipientIds + intersectingGroupIds:intersectingGroupIds + success:success + failure:failure]; + }); +} +- (void)rotateProfileKeyWithIntersectingRecipientIds:(NSSet *)intersectingRecipientIds + intersectingGroupIds:(NSSet *)intersectingGroupIds + success:(dispatch_block_t)success + failure:(ProfileManagerFailureBlock)failure { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Rotate the profile key + OWSLogInfo(@"Rotating the profile key."); // Make copies of the current local profile state. OWSUserProfile *localUserProfile = self.localUserProfile; @@ -709,7 +725,18 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); return @(1); }); + // Update account attributes. + // + // This may fail. promise = promise.then(^(id value) { + return [self.tsAccountManager updateAccountAttributes]; + }); + + promise = promise.then(^(id value) { + [[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_ProfileKeyDidChange + object:nil + userInfo:nil]; + success(); }); promise = promise.catch(^(NSError *error) { diff --git a/SignalServiceKit/src/Account/TSAccountManager.h b/SignalServiceKit/src/Account/TSAccountManager.h index 3de78891b..523cd4919 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.h +++ b/SignalServiceKit/src/Account/TSAccountManager.h @@ -12,6 +12,7 @@ extern NSString *const RegistrationStateDidChangeNotification; extern NSString *const DeregistrationStateDidChangeNotification; extern NSString *const kNSNotificationName_LocalNumberDidChange; +@class AnyPromise; @class OWSPrimaryStorage; @class TSNetworkManager; @class YapDatabaseReadWriteTransaction; @@ -135,12 +136,14 @@ extern NSString *const kNSNotificationName_LocalNumberDidChange; #pragma mark - Manual Message Fetch - (BOOL)isManualMessageFetchEnabled; -- (void)setIsManualMessageFetchEnabled:(BOOL)value; +- (AnyPromise *)setIsManualMessageFetchEnabled:(BOOL)value; #ifdef DEBUG - (void)registerForTestsWithLocalNumber:(NSString *)localNumber; #endif +- (AnyPromise *)updateAccountAttributes; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Account/TSAccountManager.m b/SignalServiceKit/src/Account/TSAccountManager.m index bbc48effd..3cb85c82e 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.m +++ b/SignalServiceKit/src/Account/TSAccountManager.m @@ -4,6 +4,7 @@ #import "TSAccountManager.h" #import "AppContext.h" +#import "AppReadiness.h" #import "NSNotificationCenter+OWS.h" #import "NSURLSessionDataTask+StatusCode.h" #import "OWSError.h" @@ -14,8 +15,11 @@ #import "TSPreKeyManager.h" #import "YapDatabaseConnection+OWS.h" #import "YapDatabaseTransaction+OWS.h" +#import +#import #import #import +#import #import NS_ASSUME_NONNULL_BEGIN @@ -35,6 +39,7 @@ NSString *const TSAccountManager_UserAccountCollection = @"TSStorageUserAccountC NSString *const TSAccountManager_ServerAuthToken = @"TSStorageServerAuthToken"; NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignalingKey"; NSString *const TSAccountManager_ManualMessageFetchKey = @"TSAccountManager_ManualMessageFetchKey"; +NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountManager_NeedsAccountAttributesUpdateKey"; @interface TSAccountManager () @@ -50,6 +55,8 @@ NSString *const TSAccountManager_ManualMessageFetchKey = @"TSAccountManager_Manu @property (nonatomic, nullable) NSNumber *cachedIsDeregistered; +@property (nonatomic) Reachability *reachability; + @end #pragma mark - @@ -66,6 +73,7 @@ NSString *const TSAccountManager_ManualMessageFetchKey = @"TSAccountManager_Manu } _dbConnection = [primaryStorage newDatabaseConnection]; + self.reachability = [Reachability reachabilityForInternetConnection]; OWSSingletonAssert(); @@ -76,6 +84,15 @@ NSString *const TSAccountManager_ManualMessageFetchKey = @"TSAccountManager_Manu object:nil]; } + [AppReadiness runNowOrWhenAppIsReady:^{ + [self updateAccountAttributesIfNecessary]; + }]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reachabilityChanged) + name:kReachabilityChangedNotification + object:nil]; + return self; } @@ -596,11 +613,13 @@ NSString *const TSAccountManager_ManualMessageFetchKey = @"TSAccountManager_Manu defaultValue:NO]; } -- (void)setIsManualMessageFetchEnabled:(BOOL)value -{ +- (AnyPromise *)setIsManualMessageFetchEnabled:(BOOL)value { [self.dbConnection setBool:value forKey:TSAccountManager_ManualMessageFetchKey inCollection:TSAccountManager_UserAccountCollection]; + + // Try to update the account attributes to reflect this change. + return [self updateAccountAttributes]; } - (void)registerForTestsWithLocalNumber:(NSString *)localNumber @@ -610,6 +629,51 @@ NSString *const TSAccountManager_ManualMessageFetchKey = @"TSAccountManager_Manu [self storeLocalNumber:localNumber]; } +#pragma mark - Account Attributes + +- (AnyPromise *)updateAccountAttributes { + // Enqueue a "account attribute update", recording the "request time". + [self.dbConnection setObject:[NSDate new] + forKey:TSAccountManager_NeedsAccountAttributesUpdateKey + inCollection:TSAccountManager_UserAccountCollection]; + + return [self updateAccountAttributesIfNecessary]; +} + +- (AnyPromise *)updateAccountAttributesIfNecessary { + NSDate *_Nullable updateRequestDate = + [self.dbConnection objectForKey:TSAccountManager_NeedsAccountAttributesUpdateKey + inCollection:TSAccountManager_UserAccountCollection]; + if (!updateRequestDate) { + return [AnyPromise promiseWithValue:@(1)]; + } + + AnyPromise *promise = [[SignalServiceRestClient new] updateAccountAttributesObjC]; + promise = promise.then(^(id value) { + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + // Clear the update request unless a new update has been requested + // while this update was in flight. + NSDate *_Nullable latestUpdateRequestDate = + [transaction objectForKey:TSAccountManager_NeedsAccountAttributesUpdateKey + inCollection:TSAccountManager_UserAccountCollection]; + if (latestUpdateRequestDate && [latestUpdateRequestDate isEqual:updateRequestDate]) { + [transaction removeObjectForKey:TSAccountManager_NeedsAccountAttributesUpdateKey + inCollection:TSAccountManager_UserAccountCollection]; + } + }]; + }); + [promise retainUntilComplete]; + return promise; +} + +- (void)reachabilityChanged { + OWSAssertIsOnMainThread(); + + [AppReadiness runNowOrWhenAppIsReady:^{ + [self updateAccountAttributesIfNecessary]; + }]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift index 825a1c7f3..840ffefc3 100644 --- a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift +++ b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift @@ -316,5 +316,8 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { @objc public func setShouldAllowUnrestrictedAccessLocal(_ value: Bool) { dbConnection.setBool(value, forKey: kUDUnrestrictedAccessKey, inCollection: kUDCollection) + + // Try to update the account attributes to reflect this change. + tsAccountManager.updateAccountAttributes() } } diff --git a/SignalServiceKit/src/Network/SignalServiceClient.swift b/SignalServiceKit/src/Network/SignalServiceClient.swift index ecfc9c20b..f1808cc26 100644 --- a/SignalServiceKit/src/Network/SignalServiceClient.swift +++ b/SignalServiceKit/src/Network/SignalServiceClient.swift @@ -8,12 +8,17 @@ import SignalMetadataKit public typealias RecipientIdentifier = String -public protocol SignalServiceClient { +@objc +public protocol SignalServiceClientObjC { + @objc func updateAccountAttributesObjC() -> AnyPromise +} + +public protocol SignalServiceClient: SignalServiceClientObjC { func getAvailablePreKeys() -> Promise func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise func setCurrentSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise func requestUDSenderCertificate() -> Promise - func updateAcountAttributes() -> Promise + func updateAccountAttributes() -> Promise func retrieveProfile(recipientId: RecipientIdentifier, unidentifiedAccess: SSKUnidentifiedAccess?) -> Promise } @@ -77,7 +82,12 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient { return try parser.requiredBase64EncodedData(key: "certificate") } - public func updateAcountAttributes() -> Promise { + @objc + public func updateAccountAttributesObjC() -> AnyPromise { + return AnyPromise(updateAccountAttributes()) + } + + public func updateAccountAttributes() -> Promise { let request = OWSRequestFactory.updateAttributesRequest() let promise: Promise = networkManager.makePromise(request: request) .then(execute: { (_, _) in @@ -90,7 +100,7 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient { public func retrieveProfile(recipientId: RecipientIdentifier, unidentifiedAccess: SSKUnidentifiedAccess?) -> Promise { let request = OWSRequestFactory.getProfileRequest(recipientId: recipientId, unidentifiedAccess: unidentifiedAccess) - return networkManager.makePromise(request: request).then { (task: URLSessionDataTask, responseObject: Any?) in + return networkManager.makePromise(request: request).then { (_: URLSessionDataTask, responseObject: Any?) in return try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject) } } diff --git a/SignalServiceKit/src/Util/OWS2FAManager.m b/SignalServiceKit/src/Util/OWS2FAManager.m index d7b88f497..9ec720325 100644 --- a/SignalServiceKit/src/Util/OWS2FAManager.m +++ b/SignalServiceKit/src/Util/OWS2FAManager.m @@ -7,6 +7,7 @@ #import "OWSPrimaryStorage.h" #import "OWSRequestFactory.h" #import "SSKEnvironment.h" +#import "TSAccountManager.h" #import "TSNetworkManager.h" #import "YapDatabaseConnection+OWS.h" @@ -64,6 +65,10 @@ const NSUInteger kDaySecs = kHourSecs * 24; return SSKEnvironment.shared.networkManager; } +- (TSAccountManager *)tsAccountManager { + return TSAccountManager.sharedInstance; +} + #pragma mark - - (nullable NSString *)pinCode @@ -83,6 +88,8 @@ const NSUInteger kDaySecs = kHourSecs * 24; [[NSNotificationCenter defaultCenter] postNotificationNameAsync:NSNotificationName_2FAStateDidChange object:nil userInfo:nil]; + + [self.tsAccountManager updateAccountAttributes]; } - (void)mark2FAAsEnabledWithPin:(NSString *)pin @@ -97,6 +104,8 @@ const NSUInteger kDaySecs = kHourSecs * 24; [[NSNotificationCenter defaultCenter] postNotificationNameAsync:NSNotificationName_2FAStateDidChange object:nil userInfo:nil]; + + [self.tsAccountManager updateAccountAttributes]; } - (void)requestEnable2FAWithPin:(NSString *)pin diff --git a/SignalShareExtension/ShareViewController.swift b/SignalShareExtension/ShareViewController.swift index c00d594d1..f353a373d 100644 --- a/SignalShareExtension/ShareViewController.swift +++ b/SignalShareExtension/ShareViewController.swift @@ -265,7 +265,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed AppVersion.sharedInstance().saeLaunchDidComplete() - Environment.shared.contactsManager.loadSignalAccountsFromCache() + Environment.shared.contactsManager.setup() Environment.shared.contactsManager.startObserving() ensureRootViewController()