diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 124cb7267..3b8f1042b 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -156,6 +156,8 @@ 452ECA4E1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; }; 4531C9C41DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */; }; 45387B041E36D650005D00B3 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 45387B031E36D650005D00B3 /* OWS102MoveLoggingPreferenceToUserDefaults.m */; }; + 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4539B5851F79348F007141FF /* PushRegistrationManager.swift */; }; + 4539B5871F79348F007141FF /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4539B5851F79348F007141FF /* PushRegistrationManager.swift */; }; 453D28BA1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */; }; 453D28BB1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */; }; 4542F0941EB9372700C7EE92 /* SystemContactsFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542F0931EB9372700C7EE92 /* SystemContactsFetcher.swift */; }; @@ -303,7 +305,6 @@ B6258B331C29E2E60014138E /* NotificationsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B6258B321C29E2E60014138E /* NotificationsManager.m */; }; B625CD561ABB589C00E8B23C /* NewMessage.aifc in Resources */ = {isa = PBXBuildFile; fileRef = B625CD551ABB589C00E8B23C /* NewMessage.aifc */; }; B62D53F71A23CCAD009AAF82 /* TSMessageAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = B62D53F61A23CCAD009AAF82 /* TSMessageAdapter.m */; }; - B62F5E101C2980B4000D370C /* NSData+ows_StripToken.m in Sources */ = {isa = PBXBuildFile; fileRef = B62F5E0F1C2980B4000D370C /* NSData+ows_StripToken.m */; }; B633C5861A1D190B0059AC12 /* call@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C5041A1D190B0059AC12 /* call@2x.png */; }; B633C58D1A1D190B0059AC12 /* contact_default_feed.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C50B1A1D190B0059AC12 /* contact_default_feed.png */; }; B633C59D1A1D190B0059AC12 /* endcall@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C51B1A1D190B0059AC12 /* endcall@2x.png */; }; @@ -625,6 +626,7 @@ 4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQMessagesCollectionViewCell+OWS.m"; sourceTree = ""; }; 45387B021E36D650005D00B3 /* OWS102MoveLoggingPreferenceToUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWS102MoveLoggingPreferenceToUserDefaults.h; path = Migrations/OWS102MoveLoggingPreferenceToUserDefaults.h; sourceTree = ""; }; 45387B031E36D650005D00B3 /* OWS102MoveLoggingPreferenceToUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWS102MoveLoggingPreferenceToUserDefaults.m; path = Migrations/OWS102MoveLoggingPreferenceToUserDefaults.m; sourceTree = ""; }; + 4539B5851F79348F007141FF /* PushRegistrationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushRegistrationManager.swift; sourceTree = ""; }; 453CC0361D08E1A60040EBA3 /* sn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sn; path = translations/sn.lproj/Localizable.strings; sourceTree = ""; }; 453D28B81D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessagesBubblesSizeCalculator.h; sourceTree = ""; }; 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = OWSMessagesBubblesSizeCalculator.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -786,8 +788,6 @@ B625CD551ABB589C00E8B23C /* NewMessage.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = NewMessage.aifc; sourceTree = ""; }; B62D53F51A23CCAD009AAF82 /* TSMessageAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSMessageAdapter.h; sourceTree = ""; }; B62D53F61A23CCAD009AAF82 /* TSMessageAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSMessageAdapter.m; sourceTree = ""; }; - B62F5E0E1C2980B4000D370C /* NSData+ows_StripToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+ows_StripToken.h"; sourceTree = ""; }; - B62F5E0F1C2980B4000D370C /* NSData+ows_StripToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+ows_StripToken.m"; sourceTree = ""; }; B633C5041A1D190B0059AC12 /* call@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "call@2x.png"; sourceTree = ""; }; B633C50B1A1D190B0059AC12 /* contact_default_feed.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = contact_default_feed.png; sourceTree = ""; }; B633C51B1A1D190B0059AC12 /* endcall@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "endcall@2x.png"; sourceTree = ""; }; @@ -1415,6 +1415,7 @@ B6B1013B196D213F007E3930 /* SignalKeyingStorage.m */, B60C16631988999D00E97A6C /* VersionMigrations.h */, B60C16641988999D00E97A6C /* VersionMigrations.m */, + 4539B5851F79348F007141FF /* PushRegistrationManager.swift */, ); path = environment; sourceTree = ""; @@ -1446,8 +1447,6 @@ 455AC69A1F4F79E500134004 /* ImageCache.swift */, 34CA1C231F706B5400E51C51 /* NSAttributedString+OWS.h */, 34CA1C241F706B5400E51C51 /* NSAttributedString+OWS.m */, - B62F5E0E1C2980B4000D370C /* NSData+ows_StripToken.h */, - B62F5E0F1C2980B4000D370C /* NSData+ows_StripToken.m */, 76EB04EC18170B33006006FC /* NumberUtil.h */, 76EB04ED18170B33006006FC /* NumberUtil.m */, 76EB04EE18170B33006006FC /* Operation.h */, @@ -2351,6 +2350,7 @@ 7038632718F70C0700D4A43F /* CryptoTools.m in Sources */, 45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */, 34B3F8911E8DF1710035BE1A /* ShowGroupMembersViewController.m in Sources */, + 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */, 45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */, 452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */, 45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */, @@ -2390,7 +2390,6 @@ 34B3F87E1E8DF1700035BE1A /* InboxTableViewCell.m in Sources */, 34B3F8731E8DF1700035BE1A /* AttachmentApprovalViewController.swift in Sources */, B6B1013C196D213F007E3930 /* SignalKeyingStorage.m in Sources */, - B62F5E101C2980B4000D370C /* NSData+ows_StripToken.m in Sources */, 34D5CC961EA6AFAD005515DB /* OWSContactsSyncing.m in Sources */, 45E2E9201E153B3D00457AA0 /* Strings.swift in Sources */, 34B3F88B1E8DF1700035BE1A /* OWSLinkedDevicesTableViewController.m in Sources */, @@ -2448,6 +2447,7 @@ 45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */, 34B3F8991E8DF1B90035BE1A /* TSMessageAdapterTest.m in Sources */, 456F6E231E24133500FD2210 /* Platform.swift in Sources */, + 4539B5871F79348F007141FF /* PushRegistrationManager.swift in Sources */, 4504493A1F45EE7D002D1ADA /* NSString+OWS.m in Sources */, 45843D201D2236B30013E85A /* OWSContactsSearcher.m in Sources */, 45AE48521E0732D6004D96C2 /* TurnServerInfo.swift in Sources */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 77bd15809..98ce74c6f 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -124,7 +124,6 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; [self setupEnvironment]; [UIUtil applySignalAppearence]; - [[PushManager sharedManager] registerPushKitNotificationFuture]; if (getenv("runningTests_dontStartApp")) { return YES; @@ -272,7 +271,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { DDLogInfo(@"%@ registered vanilla push token: %@", self.tag, deviceToken); - [PushManager.sharedManager.pushNotificationFutureSource trySetResult:deviceToken]; + [PushRegistrationManager.sharedManager didReceiveVanillaPushToken:deviceToken]; } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error @@ -280,10 +279,10 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; DDLogError(@"%@ failed to register vanilla push token with error: %@", self.tag, error); #ifdef DEBUG DDLogWarn(@"%@ We're in debug mode. Faking success for remote registration with a fake push identifier", self.tag); - [PushManager.sharedManager.pushNotificationFutureSource trySetResult:[[NSMutableData dataWithLength:32] copy]]; + [PushRegistrationManager.sharedManager didReceiveVanillaPushToken:[[NSMutableData dataWithLength:32] copy]]; #else OWSProdError([OWSAnalyticsEvents appDelegateErrorFailedToRegisterForRemoteNotifications]); - [PushManager.sharedManager.pushNotificationFutureSource trySetFailure:error]; + [PushRegistrationManager.sharedManager didFailToReceiveVanillaPushTokenWithError:error]; #endif } @@ -291,7 +290,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { DDLogInfo(@"%@ registered user notification settings", self.tag); - [PushManager.sharedManager.userNotificationFutureSource trySetResult:notificationSettings]; + [PushRegistrationManager.sharedManager didRegisterUserNotificationSettings]; } - (BOOL)application:(UIApplication *)application @@ -526,7 +525,20 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; [[Environment getCurrent].contactsManager fetchSystemContactsIfAlreadyAuthorized]; // This will fetch new messages, if we're using domain fronting. [[PushManager sharedManager] applicationDidBecomeActive]; + + if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) { + DDLogInfo( + @"%@ Retrying to register for remote notifications since user hasn't registered yet.", self.tag); + // Push tokens don't normally change while the app is launched, so checking once during launch is + // usually sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications" and disabled + // "Background App Refresh" will not be able to obtain an APN token. Enabling those settings does not + // restart the app, so we check every activation for users who haven't yet registered. + __unused AnyPromise *promise = + [OWSSyncPushTokensJob runWithAccountManager:[Environment getCurrent].accountManager + preferences:[Environment preferences]]; + } }); + } DDLogInfo(@"%@ applicationDidBecomeActive completed.", self.tag); @@ -813,7 +825,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; - (void)databaseViewRegistrationComplete { - DDLogInfo(@"databaseViewRegistrationComplete"); + DDLogInfo(@"%@ databaseViewRegistrationComplete", self.tag); if ([TSAccountManager isRegistered]) { DDLogInfo(@"localNumber: %@", [TSAccountManager localNumber]); @@ -824,9 +836,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; [[Environment getCurrent].messageFetcherJob runAsync]; // This should happen at any launch, background or foreground. - __unused AnyPromise *promise = [OWSSyncPushTokensJob runWithPushManager:[PushManager sharedManager] - accountManager:[Environment getCurrent].accountManager - preferences:[Environment preferences]]; + __unused AnyPromise *promise = [OWSSyncPushTokensJob runWithAccountManager:[Environment getCurrent].accountManager + preferences:[Environment preferences]]; } [DeviceSleepManager.sharedInstance removeBlockWithBlockObject:self]; @@ -873,7 +884,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; - (void)ensureRootViewController { - DDLogInfo(@"ensureRootViewController"); + DDLogInfo(@"%@ ensureRootViewController", self.tag); if ([TSDatabaseView hasPendingViewRegistrations] || self.hasInitialRootViewController) { return; diff --git a/Signal/src/Models/AccountManager.swift b/Signal/src/Models/AccountManager.swift index 42bc66b70..9b9ff9e43 100644 --- a/Signal/src/Models/AccountManager.swift +++ b/Signal/src/Models/AccountManager.swift @@ -11,6 +11,7 @@ import PromiseKit */ class AccountManager: NSObject { let TAG = "[AccountManager]" + let textSecureAccountManager: TSAccountManager let networkManager: TSNetworkManager let preferences: OWSPreferences @@ -33,24 +34,33 @@ class AccountManager: NSObject { } func register(verificationCode: String) -> Promise { - let registrationPromise = firstly { - Promise { fulfill, reject in - if verificationCode.characters.count == 0 { - let error = OWSErrorWithCodeDescription(.userError, - NSLocalizedString("REGISTRATION_ERROR_BLANK_VERIFICATION_CODE", - comment: "alert body during registration")) - reject(error) - } - fulfill() - } - }.then { - Logger.debug("\(self.TAG) verification code looks well formed.") - return self.registerForTextSecure(verificationCode: verificationCode) + guard verificationCode.characters.count > 0 else { + let error = OWSErrorWithCodeDescription(.userError, + NSLocalizedString("REGISTRATION_ERROR_BLANK_VERIFICATION_CODE", + comment: "alert body during registration")) + return Promise(error: error) + } + + Logger.debug("\(self.TAG) registering with signal server") + let registrationPromise: Promise = firstly { + self.registerForTextSecure(verificationCode: verificationCode) }.then { - return SyncPushTokensJob.run(pushManager: self.pushManager, accountManager: self, preferences: self.preferences) + self.syncPushTokens() + }.recover { (error) -> Promise in + switch error { + case PushRegistrationError.pushNotSupported(let description): + // This can happen with: + // - simulators, none of which support receiving push notifications + // - on iOS11 devices which have disabled "Allow Notifications" and disabled "Enable Background Refresh" in the system settings. + Logger.info("\(self.TAG) Recovered push registration error. Registering for manual message fetcher because push not supported: \(description)") + return self.registerForManualMessageFetching() + default: + throw error + } }.then { - Logger.debug("\(self.TAG) successfully registered for TextSecure") + self.completeRegistration() } + registrationPromise.retainUntilComplete() return registrationPromise @@ -64,21 +74,19 @@ class AccountManager: NSObject { } } - // MARK: Push Tokens + private func syncPushTokens() -> Promise { + Logger.info("\(self.TAG) in \(#function)") + return SyncPushTokensJob.run(accountManager: self, preferences: self.preferences) + } - func updatePushTokens(pushToken: String, voipToken: String) -> Promise { - return firstly { - return self.updateTextSecurePushTokens(pushToken: pushToken, voipToken: voipToken) - }.then { - Logger.info("\(self.TAG) Successfully updated text secure push tokens.") - // TODO code cleanup - convert to `return Promise(value: nil)` and test - return Promise { fulfill, _ in - fulfill() - } - } + private func completeRegistration() { + Logger.info("\(self.TAG) in \(#function)") + self.textSecureAccountManager.didRegister() } - private func updateTextSecurePushTokens(pushToken: String, voipToken: String) -> Promise { + // MARK: Message Delivery + + func updatePushTokens(pushToken: String, voipToken: String) -> Promise { return Promise { fulfill, reject in self.textSecureAccountManager.registerForPushNotifications(pushToken:pushToken, voipToken:voipToken, @@ -87,6 +95,12 @@ class AccountManager: NSObject { } } + func registerForManualMessageFetching() -> Promise { + return Promise { fulfill, reject in + self.textSecureAccountManager.registerForManualMessageFetching(success:fulfill, failure:reject) + } + } + // MARK: Turn Server func getTurnServerInfo() -> Promise { diff --git a/Signal/src/Models/SyncPushTokensJob.swift b/Signal/src/Models/SyncPushTokensJob.swift index dbba29646..9eadb7699 100644 --- a/Signal/src/Models/SyncPushTokensJob.swift +++ b/Signal/src/Models/SyncPushTokensJob.swift @@ -2,25 +2,28 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // -import Foundation import PromiseKit @objc(OWSSyncPushTokensJob) class SyncPushTokensJob: NSObject { let TAG = "[SyncPushTokensJob]" - let pushManager: PushManager + + // MARK: Dependencies let accountManager: AccountManager let preferences: OWSPreferences + var pushRegistrationManager: PushRegistrationManager { + return PushRegistrationManager.shared + } + var uploadOnlyIfStale = true - required init(pushManager: PushManager, accountManager: AccountManager, preferences: OWSPreferences) { - self.pushManager = pushManager + required init(accountManager: AccountManager, preferences: OWSPreferences) { self.accountManager = accountManager self.preferences = preferences } - class func run(pushManager: PushManager, accountManager: AccountManager, preferences: OWSPreferences) -> Promise { - let job = self.init(pushManager: pushManager, accountManager: accountManager, preferences: preferences) + class func run(accountManager: AccountManager, preferences: OWSPreferences) -> Promise { + let job = self.init(accountManager: accountManager, preferences: preferences) return job.run() } @@ -28,42 +31,43 @@ class SyncPushTokensJob: NSObject { Logger.info("\(TAG) Starting.") let runPromise: Promise = DispatchQueue.main.promise { - // Required to potentially prompt user for notifications settings - // before `requestPushTokens` will return. - self.pushManager.validateUserNotificationSettings() + // HACK: no-op dispatch to work around a bug in PromiseKit/Swift which won't compile + // when dispatching complex Promise types. We should eventually be able to delete the + // following two lines, skipping this no-op dispatch. + return }.then { - self.requestPushTokens() + return self.pushRegistrationManager.requestPushTokens() }.then { (pushToken: String, voipToken: String) in + Logger.info("\(self.TAG) finished: requesting push tokens") var shouldUploadTokens = false if self.preferences.getPushToken() != pushToken || self.preferences.getVoipToken() != voipToken { Logger.debug("\(self.TAG) Push tokens changed.") shouldUploadTokens = true } else if !self.uploadOnlyIfStale { - Logger.debug("\(self.TAG) Uploading even though tokens didn't change.") + Logger.debug("\(self.TAG) Forced uploading, even though tokens didn't change.") shouldUploadTokens = true } - Logger.warn("\(self.TAG) lastAppVersion: \(AppVersion.instance().lastAppVersion), currentAppVersion: \(AppVersion.instance().currentAppVersion)") if AppVersion.instance().lastAppVersion != AppVersion.instance().currentAppVersion { - Logger.debug("\(self.TAG) Fresh install or app upgrade.") + Logger.info("\(self.TAG) Uploading due to fresh install or app upgrade.") shouldUploadTokens = true } guard shouldUploadTokens else { - Logger.warn("\(self.TAG) Skipping push token upload. pushToken: \(pushToken), voipToken: \(voipToken)") + Logger.info("\(self.TAG) No reason to upload pushToken: \(pushToken), voipToken: \(voipToken)") return Promise(value: ()) } - Logger.warn("\(self.TAG) Sending new tokens to account servers. pushToken: \(pushToken), voipToken: \(voipToken)") - + Logger.warn("\(self.TAG) uploading tokens to account servers. pushToken: \(pushToken), voipToken: \(voipToken)") return self.accountManager.updatePushTokens(pushToken:pushToken, voipToken:voipToken).then { - return self.recordNewPushTokens(pushToken:pushToken, voipToken:voipToken) - }.then { - Logger.debug("\(self.TAG) Successfully ran syncPushTokensJob.") - }.catch { error in - Logger.error("\(self.TAG) Failed to run syncPushTokensJob with error: \(error).") + Logger.info("\(self.TAG) successfully updated push tokens on server") + return self.recordPushTokensLocally(pushToken:pushToken, voipToken:voipToken) } + }.then { + Logger.info("\(self.TAG) completed successfully.") + }.catch { error in + Logger.error("\(self.TAG) in \(#function): Failed with error: \(error).") } runPromise.retainUntilComplete() @@ -73,8 +77,8 @@ class SyncPushTokensJob: NSObject { // MARK - objc wrappers, since objc can't use swift parameterized types - @objc class func run(pushManager: PushManager, accountManager: AccountManager, preferences: OWSPreferences) -> AnyPromise { - let promise: Promise = self.run(pushManager: pushManager, accountManager: accountManager, preferences: preferences) + @objc class func run(accountManager: AccountManager, preferences: OWSPreferences) -> AnyPromise { + let promise: Promise = self.run(accountManager: accountManager, preferences: preferences) return AnyPromise(promise) } @@ -83,21 +87,8 @@ class SyncPushTokensJob: NSObject { return AnyPromise(promise) } - // MARK - private helpers - - private func requestPushTokens() -> Promise<(pushToken: String, voipToken: String)> { - return Promise { fulfill, reject in - self.pushManager.requestPushToken( - success: { (pushToken: String, voipToken: String) in - fulfill((pushToken:pushToken, voipToken:voipToken)) - }, - failure: reject - ) - } - } - - private func recordNewPushTokens(pushToken: String, voipToken: String) -> Promise { - Logger.warn("\(TAG) Recording new push tokens. pushToken: \(pushToken), voipToken: \(voipToken)") + private func recordPushTokensLocally(pushToken: String, voipToken: String) -> Promise { + Logger.warn("\(TAG) Recording push tokens locally. pushToken: \(pushToken), voipToken: \(voipToken)") if (pushToken != self.preferences.getPushToken()) { Logger.info("\(TAG) Recording new plain push token") diff --git a/Signal/src/ViewControllers/AdvancedSettingsTableViewController.m b/Signal/src/ViewControllers/AdvancedSettingsTableViewController.m index 1975d6d40..4c75c7ee0 100644 --- a/Signal/src/ViewControllers/AdvancedSettingsTableViewController.m +++ b/Signal/src/ViewControllers/AdvancedSettingsTableViewController.m @@ -241,9 +241,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)syncPushTokens { OWSSyncPushTokensJob *job = - [[OWSSyncPushTokensJob alloc] initWithPushManager:[PushManager sharedManager] - accountManager:[Environment getCurrent].accountManager - preferences:[Environment preferences]]; + [[OWSSyncPushTokensJob alloc] initWithAccountManager:[Environment getCurrent].accountManager + preferences:[Environment preferences]]; job.uploadOnlyIfStale = NO; [job run] .then(^{ diff --git a/Signal/src/ViewControllers/HomeViewController.h b/Signal/src/ViewControllers/HomeViewController.h index 223006547..2c32f0c9c 100644 --- a/Signal/src/ViewControllers/HomeViewController.h +++ b/Signal/src/ViewControllers/HomeViewController.h @@ -9,9 +9,6 @@ @interface HomeViewController : OWSViewController -// TODO: Remove this property. -@property (nonatomic) BOOL newlyRegisteredUser; - - (void)presentThread:(TSThread *)thread keyboardOnViewAppearing:(BOOL)keyboardOnViewAppearing callOnViewAppearing:(BOOL)callOnViewAppearing; diff --git a/Signal/src/ViewControllers/HomeViewController.m b/Signal/src/ViewControllers/HomeViewController.m index cb30b3f68..f30e923aa 100644 --- a/Signal/src/ViewControllers/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeViewController.m @@ -23,8 +23,6 @@ #import #import #import -#import -#import #import #import #import @@ -490,13 +488,10 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; { [super viewDidAppear:animated]; - if (self.newlyRegisteredUser) { - [self markAllUpgradeExperiencesAsSeen]; - } else if (!self.viewHasEverAppeared) { + if (!self.viewHasEverAppeared) { + self.viewHasEverAppeared = YES; [self displayAnyUnseenUpgradeExperience]; } - - self.viewHasEverAppeared = YES; } #pragma mark - startup diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index cf6fa708a..ac001fe53 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -437,7 +437,6 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat - (void)showHomeView { HomeViewController *homeView = [HomeViewController new]; - homeView.newlyRegisteredUser = YES; SignalsNavigationController *navigationController = [[SignalsNavigationController alloc] initWithRootViewController:homeView]; AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m index 34701d367..75d187caa 100644 --- a/Signal/src/contact/OWSContactsManager.m +++ b/Signal/src/contact/OWSContactsManager.m @@ -27,8 +27,6 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont @interface OWSContactsManager () -@property (atomic) id addressBookReference; -@property (atomic) TOCFuture *futureAddressBook; @property (nonatomic) BOOL isContactsUpdateInFlight; // This reflects the contents of the device phone book and includes // contacts that do not correspond to any signal account. diff --git a/Signal/src/environment/Migrations/OWS103EnableVideoCalling.m b/Signal/src/environment/Migrations/OWS103EnableVideoCalling.m index d0f5911f0..7411c19fd 100644 --- a/Signal/src/environment/Migrations/OWS103EnableVideoCalling.m +++ b/Signal/src/environment/Migrations/OWS103EnableVideoCalling.m @@ -23,7 +23,7 @@ static NSString *const OWS103EnableVideoCallingMigrationId = @"103"; DDLogWarn(@"%@ running migration...", self.tag); if ([TSAccountManager isRegistered]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - TSUpdateAttributesRequest *request = [[TSUpdateAttributesRequest alloc] initWithUpdatedAttributesWithVoice]; + TSUpdateAttributesRequest *request = [[TSUpdateAttributesRequest alloc] initWithManualMessageFetching:NO]; [[TSNetworkManager sharedManager] makeRequest:request success:^(NSURLSessionDataTask *task, id responseObject) { DDLogInfo(@"%@ successfully ran", self.tag); diff --git a/Signal/src/environment/PushRegistrationManager.swift b/Signal/src/environment/PushRegistrationManager.swift new file mode 100644 index 000000000..99542b84c --- /dev/null +++ b/Signal/src/environment/PushRegistrationManager.swift @@ -0,0 +1,287 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import Foundation +import PromiseKit +import PushKit + +public enum PushRegistrationError: Error { + case assertionError(description: String) + case pushNotSupported(description: String) + case timeout +} + +/** + * Singleton used to integrate with push notification services - registration and routing received remote notifications. + */ +@objc class PushRegistrationManager: NSObject, PKPushRegistryDelegate { + + let TAG = "[PushRegistrationManager]" + + // MARK - Dependencies + private var pushManager: PushManager { + return PushManager.shared() + } + + // MARK - Singleton class + + @objc(sharedManager) + static let shared = PushRegistrationManager() + private override init() { + super.init() + } + + private var userNotificationSettingsPromise: Promise? + private var fulfillUserNotificationSettingsPromise: (() -> Void)? + + private var vanillaTokenPromise: Promise? + private var fulfillVanillaTokenPromise: ((Data) -> Void)? + private var rejectVanillaTokenPromise: ((Error) -> Void)? + + private var voipRegistry: PKPushRegistry? + private var voipTokenPromise: Promise? + private var fulfillVoipTokenPromise: ((Data) -> Void)? + + // MARK: Public interface + + public func requestPushTokens() -> Promise<(pushToken: String, voipToken: String)> { + Logger.info("\(self.TAG) in \(#function)") + + return self.registerUserNotificationSettings().then { + guard !Platform.isSimulator else { + throw PushRegistrationError.pushNotSupported(description:"Push not supported on simulators") + } + + return self.registerForVanillaPushToken().then { vanillaPushToken in + self.registerForVoipPushToken().then { voipPushToken in + (pushToken: vanillaPushToken, voipToken: voipPushToken) + } + } + } + } + + // Notification registration is confirmed via AppDelegate + // Before this occurs, it is not safe to assume push token requests will be acknowledged. + // + // e.g. in the case that Background Fetch is disabled, token requests will be ignored until + // we register user notification settings. + @objc + public func didRegisterUserNotificationSettings() { + guard let fulfillUserNotificationSettingsPromise = self.fulfillUserNotificationSettingsPromise else { + owsFail("\(TAG) promise completion in \(#function) unexpectedly nil") + return + } + + fulfillUserNotificationSettingsPromise() + } + + // MARK: Vanilla push token + + // Vanilla push token is obtained from the system via AppDelegate + @objc + public func didReceiveVanillaPushToken(_ tokenData: Data) { + guard let fulfillVanillaTokenPromise = self.fulfillVanillaTokenPromise else { + owsFail("\(TAG) promise completion in \(#function) unexpectedly nil") + return + } + + fulfillVanillaTokenPromise(tokenData) + } + + // Vanilla push token is obtained from the system via AppDelegate + @objc + public func didFailToReceiveVanillaPushToken(error: Error) { + guard let rejectVanillaTokenPromise = self.rejectVanillaTokenPromise else { + owsFail("\(TAG) promise completion in \(#function) unexpectedly nil") + return + } + + rejectVanillaTokenPromise(error) + } + + // MARK: PKPushRegistryDelegate - voIP Push Token + + public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) { + Logger.info("\(self.TAG) in \(#function)") + assert(type == .voIP) + self.pushManager.application(UIApplication.shared, didReceiveRemoteNotification: payload.dictionaryPayload) + } + + public func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, forType type: PKPushType) { + Logger.info("\(self.TAG) in \(#function)") + assert(type == .voIP) + assert(credentials.type == .voIP) + guard let fulfillVoipTokenPromise = self.fulfillVoipTokenPromise else { + owsFail("\(TAG) fulfillVoipTokenPromise was unexpectedly nil") + return + } + + fulfillVoipTokenPromise(credentials.token) + } + + public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenForType type: PKPushType) { + // It's not clear when this would happen. We've never previously handled it, but we should at + // least start learning if it happens. + owsFail("\(TAG) in \(#function)") + } + + // MARK: helpers + + // User notification settings must be registered *before* AppDelegate will + // return any requested push tokens. We don't consider the notifications settings registration + // *complete* until AppDelegate#didRegisterUserNotificationSettings is called. + private func registerUserNotificationSettings() -> Promise { + AssertIsOnMainThread() + + guard self.userNotificationSettingsPromise == nil else { + let promise = self.userNotificationSettingsPromise! + Logger.info("\(TAG) already registered user notification settings") + return promise + } + + let (promise, fulfill, _) = Promise.pending() + self.userNotificationSettingsPromise = promise + self.fulfillUserNotificationSettingsPromise = fulfill + + Logger.info("\(TAG) registering user notification settings") + + UIApplication.shared.registerUserNotificationSettings(self.pushManager.userNotificationSettings) + + return promise + } + + /** + * work around for iOS11 bug, wherein for users who have disabled notifications + * and background fetch, the AppDelegate will neither succeed nor fail at registering + * for a vanilla push token. + */ + private var isSusceptibleToFailedPushRegistration: Bool { + // Only affects iOS11 users + guard #available(iOS 11.0, *) else { + return false + } + + // Only affects users who have disabled both: background refresh *and* notifications + guard UIApplication.shared.backgroundRefreshStatus == .denied else { + return false + } + + guard let notificationSettings = UIApplication.shared.currentUserNotificationSettings else { + return false + } + + guard notificationSettings.types == [] else { + return false + } + + return true + } + + private func registerForVanillaPushToken() -> Promise { + Logger.info("\(self.TAG) in \(#function)") + AssertIsOnMainThread() + + guard self.vanillaTokenPromise == nil else { + let promise = vanillaTokenPromise! + assert(promise.isPending) + Logger.info("\(TAG) alreay pending promise for vanilla push token") + return promise.then { $0.hexEncodedString } + } + + // No pending vanilla token yet. Create a new promise + let (promise, fulfill, reject) = Promise.pending() + self.vanillaTokenPromise = promise + self.fulfillVanillaTokenPromise = fulfill + self.rejectVanillaTokenPromise = reject + UIApplication.shared.registerForRemoteNotifications() + + let kTimeout: TimeInterval = 10 + let timeout: Promise = after(seconds: kTimeout).then { throw PushRegistrationError.timeout } + let promiseWithTimeout: Promise = race(promise, timeout) + + return promiseWithTimeout.recover { error -> Promise in + switch error { + case PushRegistrationError.timeout: + if self.isSusceptibleToFailedPushRegistration { + // If we've timed out on a device known to be susceptible to failures, quit trying + // so the user doesn't remain indefinitely hung for no good reason. + throw PushRegistrationError.pushNotSupported(description: "Device configuration disallows push notifications") + } else { + // Sometimes registration can just take a while. + // If we're not on a device known to be susceptible to push registration failure, + // just return the original promise. + return promise + } + default: + throw error + } + }.then { (pushTokenData: Data) -> String in + if self.isSusceptibleToFailedPushRegistration { + // Sentinal in case this bug is fixed. + owsFail("Device was unexpectedly able to complete push registration even though it was susceptible to failure.") + } + + Logger.info("\(self.TAG) successfully registered for vanilla push notifications") + return pushTokenData.hexEncodedString + }.always { + self.vanillaTokenPromise = nil + } + } + + private func registerForVoipPushToken() -> Promise { + AssertIsOnMainThread() + Logger.info("\(self.TAG) in \(#function)") + + guard self.voipTokenPromise == nil else { + let promise = self.voipTokenPromise! + assert(promise.isPending) + return promise.then { $0.hexEncodedString } + } + + // No pending voip token yet. Create a new promise + let (promise, fulfill, reject) = Promise.pending() + self.voipTokenPromise = promise + self.fulfillVoipTokenPromise = fulfill + + if self.voipRegistry == nil { + // We don't create the voip registry in init, because it immediately requests the voip token, + // potentially before we're ready to handle it. + let voipRegistry = PKPushRegistry(queue: nil) + self.voipRegistry = voipRegistry + voipRegistry.desiredPushTypes = [.voIP] + voipRegistry.delegate = self + } + + guard let voipRegistry = self.voipRegistry else { + owsFail("\(TAG) failed to initialize voipRegistry in \(#function)") + reject(PushRegistrationError.assertionError(description: "\(TAG) failed to initialize voipRegistry in \(#function)")) + return promise.then { _ in + // coerce expected type of returned promise - we don't really care about the value, + // since this promise has been rejected. In practice this shouldn't happen + String() + } + } + + // If we've already completed registering for a voip token, resolve it immediately, + // rather than waiting for the delegate method to be called. + if let voipTokenData = voipRegistry.pushToken(forType: .voIP) { + Logger.info("\(self.TAG) using pre-registered voIP token") + fulfill(voipTokenData) + } + + return promise.then { (voipTokenData: Data) -> String in + Logger.info("\(self.TAG) successfully registered for voip push notifications") + return voipTokenData.hexEncodedString + }.always { + self.voipTokenPromise = nil + } + } +} + +// We transmit pushToken data as hex encoded string to the server +fileprivate extension Data { + var hexEncodedString: String { + return map { String(format: "%02hhx", $0) }.joined() + } +} diff --git a/Signal/src/environment/VersionMigrations.m b/Signal/src/environment/VersionMigrations.m index 1122688a9..fceae5a70 100644 --- a/Signal/src/environment/VersionMigrations.m +++ b/Signal/src/environment/VersionMigrations.m @@ -142,7 +142,7 @@ __block BOOL success; - TSUpdateAttributesRequest *request = [[TSUpdateAttributesRequest alloc] initWithUpdatedAttributesWithVoice]; + TSUpdateAttributesRequest *request = [[TSUpdateAttributesRequest alloc] initWithManualMessageFetching:NO]; [[TSNetworkManager sharedManager] makeRequest:request success:^(NSURLSessionDataTask *task, id responseObject) { success = YES; diff --git a/Signal/src/network/PushManager.h b/Signal/src/network/PushManager.h index 07bda7c21..4a92c3d7e 100644 --- a/Signal/src/network/PushManager.h +++ b/Signal/src/network/PushManager.h @@ -2,10 +2,6 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // -#import -#import -#import - NS_ASSUME_NONNULL_BEGIN @class UILocalNotification; @@ -37,38 +33,19 @@ typedef void (^failedPushRegistrationBlock)(NSError *error); typedef void (^pushTokensSuccessBlock)(NSString *pushToken, NSString *voipToken); /** - * The Push Manager is responsible for registering the device for Signal push notifications. + * The Push Manager is responsible for handling received push notifications. */ - -@interface PushManager : NSObject +@interface PushManager : NSObject - (instancetype)init NS_UNAVAILABLE; + (PushManager *)sharedManager; /** - * Returns the Push Notification Token of this device - * - * @param success Completion block that is passed the token as a parameter - * @param failure Failure block, executed when failed to get push token - */ -- (void)requestPushTokenWithSuccess:(pushTokensSuccessBlock)success failure:(void (^)(NSError *))failure; - -/** - * Registers for Users Notifications. By doing this on launch, we are sure that the correct categories of user - * notifications is registered. - */ -- (void)validateUserNotificationSettings; - -/** - * The pushNotification and userNotificationFutureSource are accessed by the App Delegate after requested permissions. + * Settings required for the notification categories we use. */ -@property (nullable, atomic, readwrite, strong) TOCFutureSource *pushNotificationFutureSource; -@property (nullable, atomic, readwrite, strong) TOCFutureSource *userNotificationFutureSource; -@property (nullable, atomic, readwrite, strong) TOCFutureSource *pushKitNotificationFutureSource; +@property (nonatomic, readonly) UIUserNotificationSettings *userNotificationSettings; -- (TOCFuture *)registerPushKitNotificationFuture; -- (BOOL)supportsVOIPPush; // If checkForCancel is set, the notification will be delayed for // a moment. If a relevant cancel notification is received in that window, // the notification will not be displayed. diff --git a/Signal/src/network/PushManager.m b/Signal/src/network/PushManager.m index 9a6be0e29..309599b2d 100644 --- a/Signal/src/network/PushManager.m +++ b/Signal/src/network/PushManager.m @@ -4,7 +4,6 @@ #import "PushManager.h" #import "AppDelegate.h" -#import "NSData+ows_StripToken.h" #import "OWSContactsManager.h" #import "Signal-Swift.h" #import "ThreadUtil.h" @@ -30,7 +29,6 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe @interface PushManager () -@property (nonatomic) TOCFutureSource *registerWithServerFutureSource; @property (nonatomic) NSMutableArray *currentNotifications; @property (nonatomic) UIBackgroundTaskIdentifier callBackgroundTask; @property (nonatomic, readonly) OWSMessageSender *messageSender; @@ -72,6 +70,7 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe _messageSender = messageSender; _messageFetcherJob = messageFetcherJob; _callBackgroundTask = UIBackgroundTaskInvalid; + // TODO: consolidate notification tracking with NotificationsManager, which also maintains a list of notifications. _currentNotifications = [NSMutableArray array]; OWSSingletonAssert(); @@ -98,8 +97,9 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe #pragma mark Manage Incoming Push -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { - DDLogInfo(@"received: %s", __PRETTY_FUNCTION__); +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo +{ + DDLogInfo(@"%@ received remote notification", self.tag); [self.messageFetcherJob runAsync]; } @@ -113,19 +113,23 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe * "content-available:1" pushes if there is no "voip" token registered * */ - - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo - fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - DDLogInfo(@"received: %s", __PRETTY_FUNCTION__); + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler +{ + DDLogInfo(@"%@ received content-available push", self.tag); + + // If we want to re-introduce silent pushes we can remove this assert. + OWSFail(@"Unexpected content-available push."); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ completionHandler(UIBackgroundFetchResultNewData); }); } -- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { - DDLogInfo(@"received: %s", __PRETTY_FUNCTION__); +- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification +{ + DDLogInfo(@"%@ launched from local notification", self.tag); NSString *_Nullable threadId = notification.userInfo[Signal_Thread_UserInfo_Key]; @@ -139,8 +143,9 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe - (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification - completionHandler:(void (^)())completionHandler { - DDLogInfo(@"received: %s", __PRETTY_FUNCTION__); + completionHandler:(void (^)())completionHandler +{ + DDLogInfo(@"%@ in %s", self.tag, __FUNCTION__); [self application:application handleActionWithIdentifier:identifier @@ -264,82 +269,6 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe }]; } -#pragma mark PushKit - -- (void)pushRegistry:(PKPushRegistry *)registry - didUpdatePushCredentials:(PKPushCredentials *)credentials - forType:(NSString *)type { - [[PushManager sharedManager].pushKitNotificationFutureSource trySetResult:[credentials.token ows_tripToken]]; -} - -- (void)pushRegistry:(PKPushRegistry *)registry - didReceiveIncomingPushWithPayload:(PKPushPayload *)payload - forType:(NSString *)type { - - DDLogInfo(@"received: %s", __PRETTY_FUNCTION__); - - [self application:[UIApplication sharedApplication] didReceiveRemoteNotification:payload.dictionaryPayload]; -} - -- (TOCFuture *)registerPushKitNotificationFuture { - if ([self supportsVOIPPush]) { - self.pushKitNotificationFutureSource = [TOCFutureSource new]; - PKPushRegistry *voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; - voipRegistry.delegate = self; - voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; - return self.pushKitNotificationFutureSource.future; - } else { - TOCFutureSource *futureSource = [TOCFutureSource new]; - [futureSource trySetResult:nil]; - return futureSource.future; - } -} - -- (BOOL)supportsVOIPPush { - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(8, 2)) { - return YES; - } else { - return NO; - } -} - -#pragma mark Register device for Push Notification locally - -- (TOCFuture *)registerPushNotificationFuture { - self.pushNotificationFutureSource = [TOCFutureSource new]; - [UIApplication.sharedApplication registerForRemoteNotifications]; - return self.pushNotificationFutureSource.future; -} - -- (void)requestPushTokenWithSuccess:(pushTokensSuccessBlock)success failure:(failedPushRegistrationBlock)failure { - AssertIsOnMainThread(); - - if (!self.wantRemoteNotifications) { - DDLogWarn(@"%@ Using fake push tokens", self.tag); - success(@"fakePushToken", @"fakeVoipToken"); - return; - } - - TOCFuture *requestPushTokenFuture = [self registerPushNotificationFuture]; - - [requestPushTokenFuture thenDo:^(NSData *pushTokenData) { - NSString *pushToken = [pushTokenData ows_tripToken]; - TOCFuture *pushKit = [self registerPushKitNotificationFuture]; - - [pushKit thenDo:^(NSString *voipToken) { - success(pushToken, voipToken); - }]; - - [pushKit catchDo:^(NSError *error) { - failure(error); - }]; - }]; - - [requestPushTokenFuture catchDo:^(NSError *error) { - failure(error); - }]; -} - - (UIUserNotificationCategory *)fullNewMessageNotificationCategory { UIMutableUserNotificationAction *action_markRead = [self markAsReadAction]; @@ -464,20 +393,13 @@ NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId = @"PushManager #pragma mark Util -- (BOOL)wantRemoteNotifications { -#if TARGET_IPHONE_SIMULATOR - return NO; -#else - return YES; -#endif -} - - (int)allNotificationTypes { return UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge; } -- (void)validateUserNotificationSettings +- (UIUserNotificationSettings *)userNotificationSettings { + DDLogDebug(@"%@ registering user notification settings", self.tag); UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationType)[self allNotificationTypes] categories:[NSSet setWithObjects:[self fullNewMessageNotificationCategory], @@ -487,7 +409,7 @@ NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId = @"PushManager [self signalMissedCallWithNoLongerVerifiedIdentityChangeCategory], nil]]; - [UIApplication.sharedApplication registerUserNotificationSettings:settings]; + return settings; } - (BOOL)applicationIsActive { @@ -500,6 +422,7 @@ NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId = @"PushManager return NO; } +// TODO: consolidate notification tracking with NotificationsManager, which also maintains a list of notifications. - (void)presentNotification:(UILocalNotification *)notification checkForCancel:(BOOL)checkForCancel { dispatch_async(dispatch_get_main_queue(), ^{ @@ -517,6 +440,7 @@ NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId = @"PushManager }); } +// TODO: consolidate notification tracking with NotificationsManager, which also maintains a list of notifications. - (void)cancelNotificationsWithThreadId:(NSString *)threadId { dispatch_async(dispatch_get_main_queue(), ^{ diff --git a/Signal/src/util/NSData+ows_StripToken.h b/Signal/src/util/NSData+ows_StripToken.h deleted file mode 100644 index d07ff7818..000000000 --- a/Signal/src/util/NSData+ows_StripToken.h +++ /dev/null @@ -1,9 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -@interface NSData (ows_StripToken) - -- (NSString *)ows_tripToken; - -@end diff --git a/Signal/src/util/NSData+ows_StripToken.m b/Signal/src/util/NSData+ows_StripToken.m deleted file mode 100644 index c61ce6918..000000000 --- a/Signal/src/util/NSData+ows_StripToken.m +++ /dev/null @@ -1,20 +0,0 @@ -// -// NSData+ows_StripToken.m -// Signal -// -// Created by Frederic Jacobs on 14/04/15. -// Copyright (c) 2015 Open Whisper Systems. All rights reserved. -// - -#import "NSData+ows_StripToken.h" - -@implementation NSData (ows_StripToken) - -- (NSString *)ows_tripToken { - return [[[NSString stringWithFormat:@"%@", self] - stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] - stringByReplacingOccurrencesOfString:@" " - withString:@""]; -} - -@end diff --git a/SignalServiceKit/src/Account/TSAccountManager.h b/SignalServiceKit/src/Account/TSAccountManager.h index 2e53e96cc..ae7e7ed92 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.h +++ b/SignalServiceKit/src/Account/TSAccountManager.h @@ -65,6 +65,15 @@ extern NSString *const kNSNotificationName_LocalNumberDidChange; success:(void (^)())successBlock failure:(void (^)(NSError *error))failureBlock; +- (void)registerForManualMessageFetchingWithSuccess:(void (^)())successBlock + failure:(void (^)(NSError *error))failureBlock; + +// Called once registration is complete - meaning the following have succeeded: +// - obtained signal server credentials +// - uploaded pre-keys +// - uploaded push tokens +- (void)didRegister; + #if TARGET_OS_IPHONE /** diff --git a/SignalServiceKit/src/Account/TSAccountManager.m b/SignalServiceKit/src/Account/TSAccountManager.m index a0b97d3d4..dc5b61604 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.m +++ b/SignalServiceKit/src/Account/TSAccountManager.m @@ -274,6 +274,20 @@ NSString *const TSAccountManager_LocalRegistrationIdKey = @"TSStorageLocalRegist [self registerWithPhoneNumber:number success:successBlock failure:failureBlock smsVerification:NO]; } +- (void)registerForManualMessageFetchingWithSuccess:(void (^)())successBlock + failure:(void (^)(NSError *error))failureBlock +{ + TSUpdateAttributesRequest *request = [[TSUpdateAttributesRequest alloc] initWithManualMessageFetching:YES]; + [self.networkManager makeRequest:request + success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { + DDLogInfo(@"%@ updated server with account attributes to enableManualFetching", self.tag); + successBlock(); + } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) { + DDLogInfo(@"%@ failed to updat server with account attributes with error: %@", self.tag, error); + failureBlock(error); + }]; +} + - (void)verifyAccountWithCode:(NSString *)verificationCode success:(void (^)())successBlock failure:(void (^)(NSError *error))failureBlock @@ -290,11 +304,6 @@ NSString *const TSAccountManager_LocalRegistrationIdKey = @"TSStorageLocalRegist forNumber:phoneNumber signalingKey:signalingKey authKey:authToken]; - void (^completedRegistrationBlock)() = ^{ - [self didRegister]; - [TSSocketManager requestSocketOpen]; - successBlock(); - }; [self.networkManager makeRequest:request success:^(NSURLSessionDataTask *task, id responseObject) { @@ -304,9 +313,10 @@ NSString *const TSAccountManager_LocalRegistrationIdKey = @"TSStorageLocalRegist switch (statuscode) { case 200: case 204: { + DDLogInfo(@"%@ Verification code accepted.", self.tag); [TSStorageManager storeServerToken:authToken signalingKey:signalingKey]; [TSPreKeyManager registerPreKeysWithMode:RefreshPreKeysMode_SignedAndOneTime - success:completedRegistrationBlock + success:successBlock failure:failureBlock]; break; } diff --git a/SignalServiceKit/src/Account/TSAttributes.h b/SignalServiceKit/src/Account/TSAttributes.h index d19ab8b17..20f9d8d2f 100644 --- a/SignalServiceKit/src/Account/TSAttributes.h +++ b/SignalServiceKit/src/Account/TSAttributes.h @@ -2,11 +2,16 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // +NS_ASSUME_NONNULL_BEGIN + @interface TSAttributes : NSObject -+ (NSDictionary *)attributesFromStorageWithVoiceSupport; ++ (NSDictionary *)attributesFromStorageWithManualMessageFetching:(BOOL)isEnabled; + (NSDictionary *)attributesWithSignalingKey:(NSString *)signalingKey - serverAuthToken:(NSString *)authToken; + serverAuthToken:(NSString *)authToken + manualMessageFetching:(BOOL)isEnabled; @end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Account/TSAttributes.m b/SignalServiceKit/src/Account/TSAttributes.m index 7528dfe6a..ad1053d4a 100644 --- a/SignalServiceKit/src/Account/TSAttributes.m +++ b/SignalServiceKit/src/Account/TSAttributes.m @@ -6,23 +6,31 @@ #import "TSAccountManager.h" #import "TSStorageManager+keyingMaterial.h" +NS_ASSUME_NONNULL_BEGIN + @implementation TSAttributes -+ (NSDictionary *)attributesFromStorageWithVoiceSupport { ++ (NSDictionary *)attributesFromStorageWithManualMessageFetching:(BOOL)isEnabled +{ return [self attributesWithSignalingKey:[TSStorageManager signalingKey] - serverAuthToken:[TSStorageManager serverAuthToken]]; + serverAuthToken:[TSStorageManager serverAuthToken] + manualMessageFetching:isEnabled]; } + (NSDictionary *)attributesWithSignalingKey:(NSString *)signalingKey serverAuthToken:(NSString *)authToken + manualMessageFetching:(BOOL)isEnabled { return @{ @"signalingKey" : signalingKey, @"AuthKey" : authToken, @"voice" : @(YES), // all Signal-iOS clients support voice @"video" : @(YES), // all Signal-iOS clients support WebRTC-based voice and video calls. + @"fetchesMessages" : @(isEnabled), // devices that don't support push must tell the server they fetch messages manually @"registrationId" : [NSString stringWithFormat:@"%i", [TSAccountManager getOrGenerateRegistrationId]] }; } @end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/Requests/TSUpdateAttributesRequest.h b/SignalServiceKit/src/Network/API/Requests/TSUpdateAttributesRequest.h index 1347ef864..f59f95577 100644 --- a/SignalServiceKit/src/Network/API/Requests/TSUpdateAttributesRequest.h +++ b/SignalServiceKit/src/Network/API/Requests/TSUpdateAttributesRequest.h @@ -1,15 +1,11 @@ // -// TSUpdateAttributesRequest.h -// Signal -// -// Created by Frederic Jacobs on 22/08/15. -// Copyright (c) 2015 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "TSRequest.h" @interface TSUpdateAttributesRequest : TSRequest -- (instancetype)initWithUpdatedAttributesWithVoice; +- (instancetype)initWithManualMessageFetching:(BOOL)isEnabled; @end diff --git a/SignalServiceKit/src/Network/API/Requests/TSUpdateAttributesRequest.m b/SignalServiceKit/src/Network/API/Requests/TSUpdateAttributesRequest.m index c27b33457..caf6c4dd3 100644 --- a/SignalServiceKit/src/Network/API/Requests/TSUpdateAttributesRequest.m +++ b/SignalServiceKit/src/Network/API/Requests/TSUpdateAttributesRequest.m @@ -1,27 +1,28 @@ // -// TSUpdateAttributesRequest.m -// Signal -// -// Created by Frederic Jacobs on 22/08/15. -// Copyright (c) 2015 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "TSAttributes.h" #import "TSConstants.h" #import "TSUpdateAttributesRequest.h" +NS_ASSUME_NONNULL_BEGIN + @implementation TSUpdateAttributesRequest -- (instancetype)initWithUpdatedAttributesWithVoice { +- (instancetype)initWithManualMessageFetching:(BOOL)enableManualMessageFetching +{ NSString *endPoint = [textSecureAccountsAPI stringByAppendingString:textSecureAttributesAPI]; - self = [super initWithURL:[NSURL URLWithString:endPoint]]; + self = [super initWithURL:[NSURL URLWithString:endPoint]]; if (self) { [self setHTTPMethod:@"PUT"]; - [self.parameters addEntriesFromDictionary:[TSAttributes attributesFromStorageWithVoiceSupport]]; + [self.parameters addEntriesFromDictionary:[TSAttributes attributesFromStorageWithManualMessageFetching:enableManualMessageFetching]]; } - + return self; } @end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/Requests/TSVerifyCodeRequest.m b/SignalServiceKit/src/Network/API/Requests/TSVerifyCodeRequest.m index 5c67b2df0..ed7e6bdfa 100644 --- a/SignalServiceKit/src/Network/API/Requests/TSVerifyCodeRequest.m +++ b/SignalServiceKit/src/Network/API/Requests/TSVerifyCodeRequest.m @@ -1,9 +1,5 @@ // -// TSRegisterWithTokenRequest.m -// TextSecureKit -// -// Created by Frederic Jacobs on 14/11/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "TSAccountManager.h" @@ -22,7 +18,7 @@ stringWithFormat:@"%@/code/%@", textSecureAccountsAPI, verificationCode]]]; NSDictionary *attributes = - [TSAttributes attributesWithSignalingKey:signalingKey serverAuthToken:authKey]; + [TSAttributes attributesWithSignalingKey:signalingKey serverAuthToken:authKey manualMessageFetching:NO]; _numberToValidate = phoneNumber; [self.parameters addEntriesFromDictionary:attributes]; diff --git a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m index 27150ae44..d835fda2c 100644 --- a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m +++ b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m @@ -164,7 +164,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ self.state = SocketManagerStateOpen; return; case SR_CONNECTING: - DDLogVerbose(@"WebSocket is already connecting"); + DDLogVerbose(@"%@ WebSocket is already connecting", self.tag); self.state = SocketManagerStateConnecting; return; default: @@ -172,7 +172,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ } } - DDLogWarn(@"Creating new websocket"); + DDLogWarn(@"%@ Creating new websocket", self.tag); // If socket is not already open or connecting, connect now. // @@ -373,7 +373,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ - (void)processWebSocketRequestMessage:(WebSocketRequestMessage *)message { OWSAssert([NSThread isMainThread]); - DDLogInfo(@"Got message with verb: %@ and path: %@", message.verb, message.path); + DDLogInfo(@"%@ Got message with verb: %@ and path: %@", self.tag, message.verb, message.path); // If we receive a message over the socket while the app is in the background, // prolong how long the socket stays open. @@ -385,7 +385,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ [Cryptography decryptAppleMessagePayload:message.body withSignalingKey:TSStorageManager.signalingKey]; if (!decryptedPayload) { - DDLogWarn(@"Failed to decrypt incoming payload or bad HMAC"); + DDLogWarn(@"%@ Failed to decrypt incoming payload or bad HMAC", self.tag); [self sendWebSocketMessageAcknowledgement:message]; return; } @@ -396,7 +396,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ [self sendWebSocketMessageAcknowledgement:message]; } else { - DDLogWarn(@"Unsupported WebSocket Request"); + DDLogWarn(@"%@ Unsupported WebSocket Request", self.tag); [self sendWebSocketMessageAcknowledgement:message]; } diff --git a/SignalServiceKit/src/Util/AppVersion.m b/SignalServiceKit/src/Util/AppVersion.m index de7f6c6fb..f24887185 100755 --- a/SignalServiceKit/src/Util/AppVersion.m +++ b/SignalServiceKit/src/Util/AppVersion.m @@ -55,15 +55,15 @@ NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion = @"kNSUserDefault forKey:kNSUserDefaults_LastAppVersion]; [[NSUserDefaults standardUserDefaults] synchronize]; - DDLogInfo(@"firstAppVersion: %@", self.firstAppVersion); - DDLogInfo(@"lastAppVersion: %@", self.lastAppVersion); - DDLogInfo(@"currentAppVersion: %@", self.currentAppVersion); - DDLogInfo(@"lastCompletedLaunchAppVersion: %@", self.lastCompletedLaunchAppVersion); + DDLogInfo(@"%@ firstAppVersion: %@", self.tag, self.firstAppVersion); + DDLogInfo(@"%@ lastAppVersion: %@", self.tag, self.lastAppVersion); + DDLogInfo(@"%@ currentAppVersion: %@", self.tag, self.currentAppVersion); + DDLogInfo(@"%@ lastCompletedLaunchAppVersion: %@", self.tag, self.lastCompletedLaunchAppVersion); } - (void)appLaunchDidComplete { - DDLogInfo(@"appLaunchDidComplete"); + DDLogInfo(@"%@ appLaunchDidComplete", self.tag); self.lastCompletedLaunchAppVersion = self.currentAppVersion; @@ -78,4 +78,16 @@ NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion = @"kNSUserDefault return self.firstAppVersion != nil; } +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + @end