Register for manual message fetching when unable to obtain push tokens
// FREEBIE
This commit is contained in:
parent
df15c904bc
commit
9a7e3cb9d8
|
@ -525,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);
|
||||
|
|
|
@ -46,6 +46,17 @@ class AccountManager: NSObject {
|
|||
self.registerForTextSecure(verificationCode: verificationCode)
|
||||
}.then {
|
||||
self.syncPushTokens()
|
||||
}.recover { (error) -> Promise<Void> 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 {
|
||||
self.completeRegistration()
|
||||
}
|
||||
|
@ -73,21 +84,9 @@ class AccountManager: NSObject {
|
|||
self.textSecureAccountManager.didRegister()
|
||||
}
|
||||
|
||||
// MARK: Push Tokens
|
||||
// MARK: Message Delivery
|
||||
|
||||
func updatePushTokens(pushToken: String, voipToken: String) -> Promise<Void> {
|
||||
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 updateTextSecurePushTokens(pushToken: String, voipToken: String) -> Promise<Void> {
|
||||
return Promise { fulfill, reject in
|
||||
self.textSecureAccountManager.registerForPushNotifications(pushToken:pushToken,
|
||||
voipToken:voipToken,
|
||||
|
@ -96,6 +95,12 @@ class AccountManager: NSObject {
|
|||
}
|
||||
}
|
||||
|
||||
func registerForManualMessageFetching() -> Promise<Void> {
|
||||
return Promise { fulfill, reject in
|
||||
self.textSecureAccountManager.registerForManualMessageFetching(success:fulfill, failure:reject)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Turn Server
|
||||
|
||||
func getTurnServerInfo() -> Promise<TurnServerInfo> {
|
||||
|
|
|
@ -65,7 +65,7 @@ class SyncPushTokensJob: NSObject {
|
|||
return self.recordPushTokensLocally(pushToken:pushToken, voipToken:voipToken)
|
||||
}
|
||||
}.then {
|
||||
Logger.info("\(self.TAG) in \(#function): succeeded")
|
||||
Logger.info("\(self.TAG) completed successfully.")
|
||||
}.catch { error in
|
||||
Logger.error("\(self.TAG) in \(#function): Failed with error: \(error).")
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -6,6 +6,12 @@ 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.
|
||||
*/
|
||||
|
@ -26,15 +32,16 @@ import PushKit
|
|||
super.init()
|
||||
}
|
||||
|
||||
private enum PushRegistrationManagerError: Error {
|
||||
case assertionError(description: String)
|
||||
}
|
||||
private var userNotificationSettingsPromise: Promise<Void>?
|
||||
private var fulfillUserNotificationSettingsPromise: (() -> Void)?
|
||||
|
||||
private var voipRegistry: PKPushRegistry?
|
||||
private var vanillaTokenPromise: Promise<Data>?
|
||||
private var fulfillVanillaTokenPromise: ((Data) -> Void)?
|
||||
private var rejectVanillaTokenPromise: ((Error) -> Void)?
|
||||
|
||||
private var voipRegistry: PKPushRegistry?
|
||||
private var voipTokenPromise: Promise<Data>?
|
||||
private var fulfillVoipTokenPromise: ((Data) -> Void)?
|
||||
private var fulfillRegisterUserNotificationSettingsPromise: (() -> Void)?
|
||||
|
||||
// MARK: Public interface
|
||||
|
||||
|
@ -43,8 +50,7 @@ import PushKit
|
|||
|
||||
return self.registerUserNotificationSettings().then {
|
||||
guard !Platform.isSimulator else {
|
||||
Logger.warn("\(self.TAG) Using fake push tokens for simulator")
|
||||
return Promise(value: (pushToken: "fakePushToken", voipToken: "fakeVoipToken"))
|
||||
throw PushRegistrationError.pushNotSupported(description:"Push not supported on simulators")
|
||||
}
|
||||
|
||||
return self.registerForVanillaPushToken().then { vanillaPushToken in
|
||||
|
@ -62,12 +68,12 @@ import PushKit
|
|||
// we register user notification settings.
|
||||
@objc
|
||||
public func didRegisterUserNotificationSettings() {
|
||||
guard let fulfillRegisterUserNotificationSettingsPromise = self.fulfillRegisterUserNotificationSettingsPromise else {
|
||||
guard let fulfillUserNotificationSettingsPromise = self.fulfillUserNotificationSettingsPromise else {
|
||||
owsFail("\(TAG) promise completion in \(#function) unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
fulfillRegisterUserNotificationSettingsPromise()
|
||||
fulfillUserNotificationSettingsPromise()
|
||||
}
|
||||
|
||||
// MARK: Vanilla push token
|
||||
|
@ -128,13 +134,15 @@ import PushKit
|
|||
private func registerUserNotificationSettings() -> Promise<Void> {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
guard fulfillRegisterUserNotificationSettingsPromise == nil else {
|
||||
guard self.userNotificationSettingsPromise == nil else {
|
||||
let promise = self.userNotificationSettingsPromise!
|
||||
Logger.info("\(TAG) already registered user notification settings")
|
||||
return Promise(value: ())
|
||||
return promise
|
||||
}
|
||||
|
||||
let (promise, fulfill, _) = Promise<Void>.pending()
|
||||
self.fulfillRegisterUserNotificationSettingsPromise = fulfill
|
||||
self.userNotificationSettingsPromise = promise
|
||||
self.fulfillUserNotificationSettingsPromise = fulfill
|
||||
|
||||
Logger.info("\(TAG) registering user notification settings")
|
||||
|
||||
|
@ -143,18 +151,81 @@ import PushKit
|
|||
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<String> {
|
||||
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<Data>.pending()
|
||||
self.vanillaTokenPromise = promise
|
||||
self.fulfillVanillaTokenPromise = fulfill
|
||||
self.rejectVanillaTokenPromise = reject
|
||||
UIApplication.shared.registerForRemoteNotifications()
|
||||
|
||||
return promise.then { (pushTokenData: Data) -> String in
|
||||
let kTimeout: TimeInterval = 10
|
||||
let timeout: Promise<Data> = after(seconds: kTimeout).then { throw PushRegistrationError.timeout }
|
||||
let promiseWithTimeout: Promise<Data> = race(promise, timeout)
|
||||
|
||||
return promiseWithTimeout.recover { error -> Promise<Data> 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()
|
||||
return pushTokenData.hexEncodedString
|
||||
}.always {
|
||||
self.vanillaTokenPromise = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,8 +233,15 @@ import PushKit
|
|||
AssertIsOnMainThread()
|
||||
Logger.info("\(self.TAG) in \(#function)")
|
||||
|
||||
// Voip token not yet registered, assign promise.
|
||||
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<Data>.pending()
|
||||
self.voipTokenPromise = promise
|
||||
self.fulfillVoipTokenPromise = fulfill
|
||||
|
||||
if self.voipRegistry == nil {
|
||||
|
@ -177,10 +255,10 @@ import PushKit
|
|||
|
||||
guard let voipRegistry = self.voipRegistry else {
|
||||
owsFail("\(TAG) failed to initialize voipRegistry in \(#function)")
|
||||
reject(PushRegistrationManagerError.assertionError(description: "\(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
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
@ -194,14 +272,16 @@ import PushKit
|
|||
|
||||
return promise.then { (voipTokenData: Data) -> String in
|
||||
Logger.info("\(self.TAG) successfully registered for voip push notifications")
|
||||
return voipTokenData.hexEncodedString()
|
||||
return voipTokenData.hexEncodedString
|
||||
}.always {
|
||||
self.voipTokenPromise = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We transmit pushToken data as hex encoded string to the server
|
||||
fileprivate extension Data {
|
||||
func hexEncodedString() -> String {
|
||||
var hexEncodedString: String {
|
||||
return map { String(format: "%02hhx", $0) }.joined()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -65,6 +65,9 @@ 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Reference in New Issue