Fix edge cases around UD v. linked devices.

pull/1/head
Matthew Chen 7 years ago
parent 24e3dbbe46
commit d656ae1017

@ -76,6 +76,69 @@ static NSTimeInterval launchStartedAt;
@synthesize window = _window;
#pragma mark - Dependencies
- (OWSProfileManager *)profileManager
{
return [OWSProfileManager sharedManager];
}
- (OWSReadReceiptManager *)readReceiptManager
{
return [OWSReadReceiptManager sharedManager];
}
- (id<OWSUDManager>)udManager
{
OWSAssertDebug(SSKEnvironment.shared.udManager);
return SSKEnvironment.shared.udManager;
}
- (OWSPrimaryStorage *)primaryStorage
{
OWSAssertDebug(SSKEnvironment.shared.primaryStorage);
return SSKEnvironment.shared.primaryStorage;
}
- (PushRegistrationManager *)pushRegistrationManager
{
OWSAssertDebug(AppEnvironment.shared.pushRegistrationManager);
return AppEnvironment.shared.pushRegistrationManager;
}
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
- (OWSDisappearingMessagesJob *)disappearingMessagesJob
{
OWSAssertDebug(SSKEnvironment.shared.disappearingMessagesJob);
return SSKEnvironment.shared.disappearingMessagesJob;
}
- (TSSocketManager *)socketManager
{
OWSAssertDebug(SSKEnvironment.shared.socketManager);
return SSKEnvironment.shared.socketManager;
}
- (OWSMessageManager *)messageManager
{
OWSAssertDebug(SSKEnvironment.shared.messageManager);
return SSKEnvironment.shared.messageManager;
}
#pragma mark -
- (void)applicationDidEnterBackground:(UIApplication *)application {
OWSLogWarn(@"applicationDidEnterBackground.");
@ -425,7 +488,7 @@ static NSTimeInterval launchStartedAt;
}
OWSLogInfo(@"registered vanilla push token: %@", deviceToken);
[PushRegistrationManager.shared didReceiveVanillaPushToken:deviceToken];
[self.pushRegistrationManager didReceiveVanillaPushToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
@ -440,10 +503,10 @@ static NSTimeInterval launchStartedAt;
OWSLogError(@"failed to register vanilla push token with error: %@", error);
#ifdef DEBUG
OWSLogWarn(@"We're in debug mode. Faking success for remote registration with a fake push identifier");
[PushRegistrationManager.shared didReceiveVanillaPushToken:[[NSMutableData dataWithLength:32] copy]];
[self.pushRegistrationManager didReceiveVanillaPushToken:[[NSMutableData dataWithLength:32] copy]];
#else
OWSProdError([OWSAnalyticsEvents appDelegateErrorFailedToRegisterForRemoteNotifications]);
[PushRegistrationManager.shared didFailToReceiveVanillaPushTokenWithError:error];
[self.pushRegistrationManager didFailToReceiveVanillaPushTokenWithError:error];
#endif
}
@ -458,7 +521,7 @@ static NSTimeInterval launchStartedAt;
}
OWSLogInfo(@"registered user notification settings");
[PushRegistrationManager.shared didRegisterUserNotificationSettings];
[self.pushRegistrationManager didRegisterUserNotificationSettings];
}
- (BOOL)application:(UIApplication *)application
@ -482,7 +545,7 @@ static NSTimeInterval launchStartedAt;
}
if ([url.scheme isEqualToString:kURLSchemeSGNLKey]) {
if ([url.host hasPrefix:kURLHostVerifyPrefix] && ![TSAccountManager isRegistered]) {
if ([url.host hasPrefix:kURLHostVerifyPrefix] && ![self.tsAccountManager isRegistered]) {
id signupController = SignalApp.sharedApp.signUpFlowNavigationController;
if ([signupController isKindOfClass:[OWSNavigationController class]]) {
OWSNavigationController *navController = (OWSNavigationController *)signupController;
@ -542,7 +605,7 @@ static NSTimeInterval launchStartedAt;
- (void)enableBackgroundRefreshIfNecessary
{
[AppReadiness runNowOrWhenAppIsReady:^{
if (OWS2FAManager.sharedManager.is2FAEnabled && [TSAccountManager isRegistered]) {
if (OWS2FAManager.sharedManager.is2FAEnabled && [self.tsAccountManager isRegistered]) {
// Ping server once a day to keep-alive 2FA clients.
const NSTimeInterval kBackgroundRefreshInterval = 24 * 60 * 60;
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:kBackgroundRefreshInterval];
@ -566,26 +629,25 @@ static NSTimeInterval launchStartedAt;
dispatch_once(&onceToken, ^{
RTCInitializeSSL();
if ([TSAccountManager isRegistered]) {
if ([self.tsAccountManager isRegistered]) {
// At this point, potentially lengthy DB locking migrations could be running.
// Avoid blocking app launch by putting all further possible DB access in async block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OWSLogInfo(@"running post launch block for registered user: %@", [TSAccountManager localNumber]);
OWSLogInfo(@"running post launch block for registered user: %@", [self.tsAccountManager localNumber]);
// Clean up any messages that expired since last launch immediately
// and continue cleaning in the background.
[[OWSDisappearingMessagesJob sharedJob] startIfNecessary];
[self.disappearingMessagesJob startIfNecessary];
[self enableBackgroundRefreshIfNecessary];
// Mark all "attempting out" messages as "unsent", i.e. any messages that were not successfully
// sent before the app exited should be marked as failures.
[[[OWSFailedMessagesJob alloc] initWithPrimaryStorage:[OWSPrimaryStorage sharedManager]] run];
[[[OWSFailedMessagesJob alloc] initWithPrimaryStorage:self.primaryStorage] run];
// Mark all "incomplete" calls as missed, e.g. any incoming or outgoing calls that were not
// connected, failed or hung up before the app existed should be marked as missed.
[[[OWSIncompleteCallsJob alloc] initWithPrimaryStorage:[OWSPrimaryStorage sharedManager]] run];
[[[OWSFailedAttachmentDownloadsJob alloc] initWithPrimaryStorage:[OWSPrimaryStorage sharedManager]]
run];
[[[OWSIncompleteCallsJob alloc] initWithPrimaryStorage:self.primaryStorage] run];
[[[OWSFailedAttachmentDownloadsJob alloc] initWithPrimaryStorage:self.primaryStorage] run];
});
} else {
OWSLogInfo(@"running post launch block for unregistered user.");
@ -593,7 +655,7 @@ static NSTimeInterval launchStartedAt;
// Unregistered user should have no unread messages. e.g. if you delete your account.
[SignalApp clearAllNotifications];
[TSSocketManager.shared requestSocketOpen];
[self.socketManager requestSocketOpen];
UITapGestureRecognizer *gesture =
[[UITapGestureRecognizer alloc] initWithTarget:[Pastelog class] action:@selector(submitLogs)];
@ -603,11 +665,11 @@ static NSTimeInterval launchStartedAt;
}); // end dispatchOnce for first time we become active
// Every time we become active...
if ([TSAccountManager isRegistered]) {
if ([self.tsAccountManager isRegistered]) {
// At this point, potentially lengthy DB locking migrations could be running.
// Avoid blocking app launch by putting all further possible DB access in async block
dispatch_async(dispatch_get_main_queue(), ^{
[TSSocketManager.shared requestSocketOpen];
[self.socketManager requestSocketOpen];
[Environment.shared.contactsManager fetchSystemContactsOnceIfAlreadyAuthorized];
// This will fetch new messages, if we're using domain fronting.
[[PushManager sharedManager] applicationDidBecomeActive];
@ -674,7 +736,7 @@ static NSTimeInterval launchStartedAt;
}
[AppReadiness runNowOrWhenAppIsReady:^{
if (![TSAccountManager isRegistered]) {
if (![self.tsAccountManager isRegistered]) {
UIAlertController *controller =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"REGISTER_CONTACTS_WELCOME", nil)
message:NSLocalizedString(@"REGISTRATION_RESTRICTED_MESSAGE", nil)
@ -747,7 +809,7 @@ static NSTimeInterval launchStartedAt;
[AppReadiness runNowOrWhenAppIsReady:^{
NSString *_Nullable phoneNumber = handle;
if ([handle hasPrefix:CallKitCallManager.kAnonymousCallHandlePrefix]) {
phoneNumber = [[OWSPrimaryStorage sharedManager] phoneNumberForCallKitId:handle];
phoneNumber = [self.primaryStorage phoneNumberForCallKitId:handle];
if (phoneNumber.length < 1) {
OWSLogWarn(@"ignoring attempt to initiate video call to unknown anonymous signal user.");
return;
@ -804,7 +866,7 @@ static NSTimeInterval launchStartedAt;
[AppReadiness runNowOrWhenAppIsReady:^{
NSString *_Nullable phoneNumber = handle;
if ([handle hasPrefix:CallKitCallManager.kAnonymousCallHandlePrefix]) {
phoneNumber = [[OWSPrimaryStorage sharedManager] phoneNumberForCallKitId:handle];
phoneNumber = [self.primaryStorage phoneNumberForCallKitId:handle];
if (phoneNumber.length < 1) {
OWSLogWarn(@"ignoring attempt to initiate audio call to unknown anonymous signal user.");
return;
@ -1010,8 +1072,8 @@ static NSTimeInterval launchStartedAt;
OWSLogInfo(@"checkIfAppIsReady");
// TODO: Once "app ready" logic is moved into AppSetup, move this line there.
[[OWSProfileManager sharedManager] ensureLocalProfileCached];
[self.profileManager ensureLocalProfileCached];
// Note that this does much more than set a flag;
// it will also run all deferred blocks.
[AppReadiness setAppIsReady];
@ -1021,8 +1083,8 @@ static NSTimeInterval launchStartedAt;
return;
}
if ([TSAccountManager isRegistered]) {
OWSLogInfo(@"localNumber: %@", [TSAccountManager localNumber]);
if ([self.tsAccountManager isRegistered]) {
OWSLogVerbose(@"localNumber: %@", [self.tsAccountManager localNumber]);
// Fetch messages as soon as possible after launching. In particular, when
// launching from the background, without this, we end up waiting some extra
@ -1047,7 +1109,7 @@ static NSTimeInterval launchStartedAt;
[SSKEnvironment.shared.batchMessageProcessor handleAnyUnprocessedEnvelopesAsync];
if (!Environment.shared.preferences.hasGeneratedThumbnails) {
[OWSPrimaryStorage.sharedManager.newDatabaseConnection
[self.primaryStorage.newDatabaseConnection
asyncReadWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[TSAttachmentStream enumerateCollectionObjectsUsingBlock:^(id _Nonnull obj, BOOL *_Nonnull stop){
// no-op. It's sufficient to initWithCoder: each object.
@ -1069,8 +1131,8 @@ static NSTimeInterval launchStartedAt;
[OWSOrphanDataCleaner auditOnLaunchIfNecessary];
#endif
[OWSProfileManager.sharedManager fetchLocalUsersProfile];
[[OWSReadReceiptManager sharedManager] prepareCachedValues];
[self.profileManager fetchLocalUsersProfile];
[self.readReceiptManager prepareCachedValues];
// Disable the SAE until the main app has successfully completed launch process
// at least once in the post-SAE world.
@ -1080,14 +1142,14 @@ static NSTimeInterval launchStartedAt;
[OWSBackup.sharedManager setup];
[SSKEnvironment.shared.messageManager startObserving];
[self.messageManager startObserving];
#ifdef DEBUG
// Resume lazy restore.
[OWSBackupLazyRestoreJob runAsync];
#endif
[SSKEnvironment.shared.udManager setup];
[self.udManager setup];
}
- (void)registrationStateDidChange
@ -1098,20 +1160,20 @@ static NSTimeInterval launchStartedAt;
[self enableBackgroundRefreshIfNecessary];
if ([TSAccountManager isRegistered]) {
OWSLogInfo(@"localNumber: %@", [TSAccountManager localNumber]);
if ([self.tsAccountManager isRegistered]) {
OWSLogInfo(@"localNumber: %@", [self.tsAccountManager localNumber]);
[[OWSPrimaryStorage sharedManager].newDatabaseConnection
[self.primaryStorage.newDatabaseConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[ExperienceUpgradeFinder.sharedManager markAllAsSeenWithTransaction:transaction];
}];
// Start running the disappearing messages job in case the newly registered user
// enables this feature
[[OWSDisappearingMessagesJob sharedJob] startIfNecessary];
[[OWSProfileManager sharedManager] ensureLocalProfileCached];
[self.disappearingMessagesJob startIfNecessary];
[self.profileManager ensureLocalProfileCached];
// For non-legacy users, read receipts are on by default.
[OWSReadReceiptManager.sharedManager setAreReadReceiptsEnabled:YES];
[self.readReceiptManager setAreReadReceiptsEnabled:YES];
}
}
@ -1134,7 +1196,7 @@ static NSTimeInterval launchStartedAt;
NSTimeInterval startupDuration = CACurrentMediaTime() - launchStartedAt;
OWSLogInfo(@"Presenting app %.2f seconds after launch started.", startupDuration);
if ([TSAccountManager isRegistered]) {
if ([self.tsAccountManager isRegistered]) {
HomeViewController *homeView = [HomeViewController new];
SignalsNavigationController *navigationController =
[[SignalsNavigationController alloc] initWithRootViewController:homeView];

@ -68,6 +68,18 @@ int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1;
selector:@selector(yapDatabaseModifiedExternally:)
name:YapDatabaseModifiedExternallyNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceListUpdateSucceeded:)
name:NSNotificationName_DeviceListUpdateSucceeded
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceListUpdateFailed:)
name:NSNotificationName_DeviceListUpdateFailed
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceListUpdateModifiedDeviceList:)
name:NSNotificationName_DeviceListUpdateModifiedDeviceList
object:nil];
self.refreshControl = [UIRefreshControl new];
[self.refreshControl addTarget:self action:@selector(refreshDevices) forControlEvents:UIControlEventValueChanged];
@ -141,61 +153,54 @@ int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1;
- (void)refreshDevices
{
__weak typeof(self) wself = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[OWSDevicesService new]
getDevicesWithSuccess:^(NSArray<OWSDevice *> *devices) {
// If we have more than one device; we may have a linked device.
if (devices.count > 1) {
// Setting this flag here shouldn't be necessary, but we do so
// because the "cost" is low and it will improve robustness.
[OWSDeviceManager.sharedManager setMayHaveLinkedDevices];
}
if (devices.count > [OWSDevice numberOfKeysInCollection]) {
// Got our new device, we can stop refreshing.
wself.isExpectingMoreDevices = NO;
[wself.pollingRefreshTimer invalidate];
dispatch_async(dispatch_get_main_queue(), ^{
wself.refreshControl.attributedTitle = nil;
});
}
[OWSDevice replaceAll:devices];
if (!self.isExpectingMoreDevices) {
dispatch_async(dispatch_get_main_queue(), ^{
[wself.refreshControl endRefreshing];
});
}
}
failure:^(NSError *error) {
OWSLogError(@"Failed to fetch devices in linkedDevices controller with error: %@", error);
NSString *alertTitle = NSLocalizedString(
@"DEVICE_LIST_UPDATE_FAILED_TITLE", @"Alert title that can occur when viewing device manager.");
UIAlertController *alertController =
[UIAlertController alertControllerWithTitle:alertTitle
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *retryAction = [UIAlertAction actionWithTitle:[CommonStrings retryButton]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[wself refreshDevices];
}];
[alertController addAction:retryAction];
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:CommonStrings.dismissButton
style:UIAlertActionStyleCancel
handler:nil];
[alertController addAction:dismissAction];
dispatch_async(dispatch_get_main_queue(), ^{
[wself.refreshControl endRefreshing];
[wself presentViewController:alertController animated:YES completion:nil];
});
}];
[OWSDevicesService refreshDevices];
}
- (void)deviceListUpdateSucceeded:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
[self.refreshControl endRefreshing];
}
- (void)deviceListUpdateFailed:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
NSError *error = notification.object;
OWSAssertDebug(error);
NSString *alertTitle = NSLocalizedString(
@"DEVICE_LIST_UPDATE_FAILED_TITLE", @"Alert title that can occur when viewing device manager.");
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:alertTitle
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *retryAction = [UIAlertAction actionWithTitle:[CommonStrings retryButton]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self refreshDevices];
}];
[alertController addAction:retryAction];
UIAlertAction *dismissAction =
[UIAlertAction actionWithTitle:CommonStrings.dismissButton style:UIAlertActionStyleCancel handler:nil];
[alertController addAction:dismissAction];
[self.refreshControl endRefreshing];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)deviceListUpdateModifiedDeviceList:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
// Got our new device, we can stop refreshing.
self.isExpectingMoreDevices = NO;
[self.pollingRefreshTimer invalidate];
dispatch_async(dispatch_get_main_queue(), ^{
self.refreshControl.attributedTitle = nil;
});
}
@ -397,29 +402,29 @@ int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1;
- (void)unlinkDevice:(OWSDevice *)device success:(void (^)(void))successCallback
{
[[OWSDevicesService new] unlinkDevice:device
success:successCallback
failure:^(NSError *error) {
NSString *title = NSLocalizedString(
@"UNLINKING_FAILED_ALERT_TITLE", @"Alert title when unlinking device fails");
UIAlertController *alertController =
[UIAlertController alertControllerWithTitle:title
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *retryAction =
[UIAlertAction actionWithTitle:[CommonStrings retryButton]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *aaction) {
[self unlinkDevice:device success:successCallback];
}];
[alertController addAction:retryAction];
[alertController addAction:[OWSAlerts cancelAction]];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:alertController animated:YES completion:nil];
});
}];
[OWSDevicesService unlinkDevice:device
success:successCallback
failure:^(NSError *error) {
NSString *title = NSLocalizedString(
@"UNLINKING_FAILED_ALERT_TITLE", @"Alert title when unlinking device fails");
UIAlertController *alertController =
[UIAlertController alertControllerWithTitle:title
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *retryAction =
[UIAlertAction actionWithTitle:[CommonStrings retryButton]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *aaction) {
[self unlinkDevice:device success:successCallback];
}];
[alertController addAction:retryAction];
[alertController addAction:[OWSAlerts cancelAction]];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:alertController animated:YES completion:nil];
});
}];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(nullable id)sender

@ -142,7 +142,8 @@ public class ProfileFetcherJob: NSObject {
}, websocketFailureBlock: {
// Do nothing
}, recipientId: recipientId,
unidentifiedAccess: unidentifiedAccess)
unidentifiedAccess: unidentifiedAccess,
canFailoverUDAuth: true)
return requestMaker.makeRequest()
.map { (result: RequestMakerResult) -> SignalServiceProfile in
try SignalServiceProfile(recipientId: recipientId, responseObject: result.responseObject)

@ -35,13 +35,17 @@ extern uint32_t const OWSDevicePrimaryDeviceId;
+ (nullable instancetype)deviceFromJSONDictionary:(NSDictionary *)deviceAttributes error:(NSError **)error;
+ (NSArray<OWSDevice *> *)currentDevicesWithTransaction:(YapDatabaseReadTransaction *)transaction;
/**
* Set local database of devices to `devices`.
*
* This will create missing devices, update existing devices, and delete stale devices.
* @param devices Removes any existing devices, replacing them with `devices`
*
* Returns YET if any devices were added or removed.
*/
+ (void)replaceAll:(NSArray<OWSDevice *> *)devices;
+ (BOOL)replaceAll:(NSArray<OWSDevice *> *)devices;
/**
* The id of the device currently running this application

@ -171,21 +171,37 @@ NSString *const kOWSPrimaryStorage_MayHaveLinkedDevices = @"kTSStorageManager_Ma
return self.millisecondTimestampToDateTransformer;
}
+ (void)replaceAll:(NSArray<OWSDevice *> *)currentDevices
+ (NSArray<OWSDevice *> *)currentDevicesWithTransaction:(YapDatabaseReadTransaction *)transaction
{
BOOL didChange = NO;
OWSAssertDebug(transaction);
NSMutableArray<OWSDevice *> *result = [NSMutableArray new];
[transaction enumerateKeysAndObjectsInCollection:OWSDevice.collection
usingBlock:^(NSString *key, OWSDevice *object, BOOL *stop) {
if (![object isKindOfClass:[OWSDevice class]]) {
OWSFailDebug(@"Unexpected object in collection: %@", object.class);
return;
}
[result addObject:object];
}];
return result;
}
+ (BOOL)replaceAll:(NSArray<OWSDevice *> *)currentDevices
{
BOOL didAddOrRemove = NO;
NSMutableArray<OWSDevice *> *existingDevices = [[self allObjectsInCollection] mutableCopy];
for (OWSDevice *currentDevice in currentDevices) {
NSUInteger existingDeviceIndex = [existingDevices indexOfObject:currentDevice];
if (existingDeviceIndex == NSNotFound) {
// New Device
OWSLogInfo(@"Adding device: %@", currentDevice);
[currentDevice save];
didChange = YES;
didAddOrRemove = YES;
} else {
OWSDevice *existingDevice = existingDevices[existingDeviceIndex];
if ([existingDevice updateAttributesWithDevice:currentDevice]) {
[existingDevice save];
didChange = YES;
}
[existingDevices removeObjectAtIndex:existingDeviceIndex];
}
@ -193,11 +209,12 @@ NSString *const kOWSPrimaryStorage_MayHaveLinkedDevices = @"kTSStorageManager_Ma
// Since we removed existing devices as we went, only stale devices remain
for (OWSDevice *staleDevice in existingDevices) {
OWSLogVerbose(@"Removing device: %@", staleDevice);
[staleDevice remove];
didChange = YES;
didAddOrRemove = YES;
}
if (didChange) {
if (didAddOrRemove) {
dispatch_async(dispatch_get_main_queue(), ^{
// Device changes can affect the UD access mode for a recipient,
// so we need to:
@ -208,6 +225,9 @@ NSString *const kOWSPrimaryStorage_MayHaveLinkedDevices = @"kTSStorageManager_Ma
recipientId:self.tsAccountManager.localNumber];
[self.profileManager fetchLocalUsersProfile];
});
return YES;
} else {
return NO;
}
}

@ -14,6 +14,7 @@
#import "OWSCallMessageHandler.h"
#import "OWSContact.h"
#import "OWSDevice.h"
#import "OWSDevicesService.h"
#import "OWSDisappearingConfigurationUpdateInfoMessage.h"
#import "OWSDisappearingMessagesConfiguration.h"
#import "OWSDisappearingMessagesJob.h"
@ -47,6 +48,7 @@
#import <SignalCoreKit/Cryptography.h>
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalCoreKit/NSString+SSK.h>
#import <SignalServiceKit/SignalRecipient.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <YapDatabase/YapDatabase.h>
@ -89,6 +91,8 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
#pragma mark - Dependencies
- (id<OWSCallMessageHandler>)callMessageHandler
{
OWSAssertDebug(SSKEnvironment.shared.callMessageHandler);
@ -145,6 +149,13 @@ NS_ASSUME_NONNULL_BEGIN
return SSKEnvironment.shared.syncManager;
}
- (TSAccountManager *)tsAccountManager
{
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
return SSKEnvironment.shared.tsAccountManager;
}
#pragma mark -
- (void)startObserving
@ -235,6 +246,8 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertDebug(![self isEnvelopeSenderBlocked:envelope]);
[self checkForUnknownLinkedDevice:envelope transaction:transaction];
switch (envelope.type) {
case SSKProtoEnvelopeTypeCiphertext:
case SSKProtoEnvelopeTypePrekeyBundle:
@ -471,7 +484,7 @@ NS_ASSUME_NONNULL_BEGIN
if (groupThread) {
if (dataMessage.group.type != SSKProtoGroupContextTypeUpdate) {
if (![groupThread.groupModel.groupMemberIds containsObject:[TSAccountManager localNumber]]) {
if (![groupThread.groupModel.groupMemberIds containsObject:self.tsAccountManager.localNumber]) {
OWSLogInfo(@"Ignoring messages for left group.");
return;
}
@ -749,7 +762,7 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
NSString *localNumber = [TSAccountManager localNumber];
NSString *localNumber = self.tsAccountManager.localNumber;
if (![localNumber isEqualToString:envelope.source]) {
// Sync messages should only come from linked devices.
OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorSyncMessageFromUnknownSource], envelope);
@ -1062,7 +1075,7 @@ NS_ASSUME_NONNULL_BEGIN
}
// Ensure we are in the group.
NSString *localNumber = [TSAccountManager localNumber];
NSString *localNumber = self.tsAccountManager.localNumber;
if (![gThread.groupModel.groupMemberIds containsObject:localNumber]) {
OWSLogWarn(@"Ignoring 'Request Group Info' message for group we no longer belong to.");
return;
@ -1298,7 +1311,7 @@ NS_ASSUME_NONNULL_BEGIN
[incomingMessage saveWithTransaction:transaction];
// Any messages sent from the current user - from this device or another - should be automatically marked as read.
if ([envelope.source isEqualToString:TSAccountManager.localNumber]) {
if ([envelope.source isEqualToString:self.tsAccountManager.localNumber]) {
// Don't send a read receipt for messages sent by ourselves.
[incomingMessage markAsReadAtTimestamp:envelope.timestamp sendReadReceipt:NO transaction:transaction];
}
@ -1424,6 +1437,48 @@ NS_ASSUME_NONNULL_BEGIN
}
}
#pragma mark -
- (void)checkForUnknownLinkedDevice:(SSKProtoEnvelope *)envelope
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(envelope);
OWSAssertDebug(transaction);
NSString *localNumber = self.tsAccountManager.localNumber;
if (![localNumber isEqualToString:envelope.source]) {
return;
}
NSMutableSet<NSNumber *> *deviceIdSet = [NSMutableSet new];
for (OWSDevice *device in [OWSDevice currentDevicesWithTransaction:transaction]) {
[deviceIdSet addObject:@(device.deviceId)];
}
SignalRecipient *_Nullable recipient =
[SignalRecipient registeredRecipientForRecipientId:localNumber transaction:transaction];
if (!recipient) {
OWSFailDebug(@"No local SignalRecipient.");
} else {
BOOL isRecipientDevice = [recipient.devices containsObject:@(envelope.sourceDevice)];
if (!isRecipientDevice) {
OWSLogInfo(@"Message received from unknown linked device; adding to local SignalRecipient: %lu.",
(unsigned long) envelope.sourceDevice);
[recipient updateRegisteredRecipientWithDevicesToAdd:@[ @(envelope.sourceDevice) ]
devicesToRemove:nil
transaction:transaction];
}
}
BOOL isInDeviceList = [deviceIdSet containsObject:@(envelope.sourceDevice)];
if (!isInDeviceList) {
OWSLogInfo(@"Message received from unknown linked device; refreshing device list: %lu.",
(unsigned long) envelope.sourceDevice);
[OWSDevicesService refreshDevices];
}
}
@end
NS_ASSUME_NONNULL_END

@ -933,6 +933,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// Consume an attempt.
messageSend.remainingAttempts = messageSend.remainingAttempts - 1;
if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]
&& ![message isKindOfClass:[OWSOutgoingSentMessageTranscript class]]) {
[messageSend disableUD];
}
NSError *deviceMessagesError;
NSArray<NSDictionary *> *_Nullable deviceMessages =
[self deviceMessagesForMessageSendSafe:messageSend error:&deviceMessagesError];
@ -1028,11 +1033,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
OWSLogWarn(@"Sending a message with no device messages.");
}
if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]
&& ![message isKindOfClass:[OWSOutgoingSentMessageTranscript class]]) {
[messageSend disableUD];
}
// NOTE: canFailoverUDAuth is NO because UD-auth and Non-UD-auth requests
// use different device lists.
OWSRequestMaker *requestMaker = [[OWSRequestMaker alloc]
initWithRequestFactoryBlock:^(SSKUnidentifiedAccess *_Nullable unidentifiedAccess) {
return [OWSRequestFactory submitMessageRequestWithRecipient:recipient.recipientId
@ -1047,7 +1049,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
messageSend.hasWebsocketSendFailed = YES;
}
recipientId:recipient.recipientId
unidentifiedAccess:messageSend.unidentifiedAccess];
unidentifiedAccess:messageSend.unidentifiedAccess
canFailoverUDAuth:NO];
[[requestMaker makeRequestObjc]
.then(^(OWSRequestMakerResult *result) {
dispatch_async([OWSDispatch sendingQueue], ^{
@ -1059,7 +1062,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
dispatch_async([OWSDispatch sendingQueue], ^{
NSUInteger statusCode = 0;
NSData *_Nullable responseData = nil;
if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) {
if ([error.domain isEqualToString:@"SignalServiceKit.RequestMakerUDAuthError"]) {
// Try again.
OWSLogInfo(@"UD request auth failed; failing over to non-UD request.");
[error setIsRetryable:YES];
} else if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) {
statusCode = error.code;
NSError *_Nullable underlyingError = error.userInfo[NSUnderlyingErrorKey];
@ -1508,7 +1515,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
messageSend.hasWebsocketSendFailed = YES;
}
recipientId:recipientId
unidentifiedAccess:messageSend.unidentifiedAccess];
unidentifiedAccess:messageSend.unidentifiedAccess
canFailoverUDAuth:YES];
[[requestMaker makeRequestObjc]
.then(^(OWSRequestMakerResult *result) {
// We _do not_ want to dispatch to the sendingQueue here; we're

@ -5,6 +5,11 @@
import Foundation
import PromiseKit
@objc
public enum RequestMakerUDAuthError: Int, Error {
case udAuthFailure
}
public enum RequestMakerError: Error {
case websocketRequestError(statusCode : Int, responseData : Data?, underlyingError : Error)
}
@ -40,18 +45,21 @@ public class RequestMaker: NSObject {
private let websocketFailureBlock: WebsocketFailureBlock
private let recipientId: String
private let unidentifiedAccess: SSKUnidentifiedAccess?
private let canFailoverUDAuth: Bool
@objc
public init(requestFactoryBlock : @escaping RequestFactoryBlock,
udAuthFailureBlock : @escaping UDAuthFailureBlock,
websocketFailureBlock : @escaping WebsocketFailureBlock,
recipientId: String,
unidentifiedAccess: SSKUnidentifiedAccess?) {
unidentifiedAccess: SSKUnidentifiedAccess?,
canFailoverUDAuth: Bool) {
self.requestFactoryBlock = requestFactoryBlock
self.udAuthFailureBlock = udAuthFailureBlock
self.websocketFailureBlock = websocketFailureBlock
self.recipientId = recipientId
self.unidentifiedAccess = unidentifiedAccess
self.canFailoverUDAuth = canFailoverUDAuth
}
// MARK: - Dependencies
@ -115,8 +123,13 @@ public class RequestMaker: NSObject {
// failure), mark recipient as _not_ in UD mode, then retry.
self.udManager.setUnidentifiedAccessMode(.disabled, recipientId: self.recipientId)
self.udAuthFailureBlock()
Logger.info("UD websocket request failed; failing over to non-UD websocket request.")
return self.makeRequestInternal(skipUD: true, skipWebsocket: skipWebsocket)
if self.canFailoverUDAuth {
Logger.info("UD websocket request auth failed; failing over to non-UD websocket request.")
return self.makeRequestInternal(skipUD: true, skipWebsocket: skipWebsocket)
} else {
Logger.info("UD websocket request auth failed; aborting.")
throw RequestMakerUDAuthError.udAuthFailure
}
}
break
default:
@ -141,8 +154,13 @@ public class RequestMaker: NSObject {
// failure), mark recipient as _not_ in UD mode, then retry.
self.udManager.setUnidentifiedAccessMode(.disabled, recipientId: self.recipientId)
self.udAuthFailureBlock()
Logger.info("UD REST request failed; failing over to non-UD REST request.")
return self.makeRequestInternal(skipUD: true, skipWebsocket: skipWebsocket)
if self.canFailoverUDAuth {
Logger.info("UD REST request auth failed; failing over to non-UD REST request.")
return self.makeRequestInternal(skipUD: true, skipWebsocket: skipWebsocket)
} else {
Logger.info("UD REST request auth failed; aborting.")
throw RequestMakerUDAuthError.udAuthFailure
}
}
break
default:

@ -1,17 +1,20 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
extern NSString *const NSNotificationName_DeviceListUpdateSucceeded;
extern NSString *const NSNotificationName_DeviceListUpdateFailed;
extern NSString *const NSNotificationName_DeviceListUpdateModifiedDeviceList;
@class OWSDevice;
@interface OWSDevicesService : NSObject
- (void)getDevicesWithSuccess:(void (^)(NSArray<OWSDevice *> *))successCallback
failure:(void (^)(NSError *))failureCallback;
+ (void)refreshDevices;
- (void)unlinkDevice:(OWSDevice *)device
+ (void)unlinkDevice:(OWSDevice *)device
success:(void (^)(void))successCallback
failure:(void (^)(NSError *))failureCallback;

@ -3,6 +3,7 @@
//
#import "OWSDevicesService.h"
#import "NSNotificationCenter+OWS.h"
#import "OWSDevice.h"
#import "OWSError.h"
#import "OWSRequestFactory.h"
@ -11,9 +12,47 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const NSNotificationName_DeviceListUpdateSucceeded = @"NSNotificationName_DeviceListUpdateSucceeded";
NSString *const NSNotificationName_DeviceListUpdateFailed = @"NSNotificationName_DeviceListUpdateFailed";
NSString *const NSNotificationName_DeviceListUpdateModifiedDeviceList
= @"NSNotificationName_DeviceListUpdateModifiedDeviceList";
@implementation OWSDevicesService
- (void)getDevicesWithSuccess:(void (^)(NSArray<OWSDevice *> *))successCallback
+ (void)refreshDevices
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self
getDevicesWithSuccess:^(NSArray<OWSDevice *> *devices) {
// If we have more than one device; we may have a linked device.
if (devices.count > 1) {
// Setting this flag here shouldn't be necessary, but we do so
// because the "cost" is low and it will improve robustness.
[OWSDeviceManager.sharedManager setMayHaveLinkedDevices];
}
BOOL didAddOrRemove = [OWSDevice replaceAll:devices];
[NSNotificationCenter.defaultCenter
postNotificationNameAsync:NSNotificationName_DeviceListUpdateSucceeded
object:nil];
if (didAddOrRemove) {
[NSNotificationCenter.defaultCenter
postNotificationNameAsync:NSNotificationName_DeviceListUpdateModifiedDeviceList
object:nil];
}
}
failure:^(NSError *error) {
OWSLogError(@"Request device list failed with error: %@", error);
[NSNotificationCenter.defaultCenter postNotificationNameAsync:NSNotificationName_DeviceListUpdateFailed
object:error];
}];
});
}
+ (void)getDevicesWithSuccess:(void (^)(NSArray<OWSDevice *> *))successCallback
failure:(void (^)(NSError *))failureCallback
{
TSRequest *request = [OWSRequestFactory getDevicesRequest];
@ -39,7 +78,7 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
- (void)unlinkDevice:(OWSDevice *)device
+ (void)unlinkDevice:(OWSDevice *)device
success:(void (^)(void))successCallback
failure:(void (^)(NSError *))failureCallback
{
@ -59,7 +98,7 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
- (NSArray<OWSDevice *> *)parseResponse:(id)responseObject
+ (NSArray<OWSDevice *> *)parseResponse:(id)responseObject
{
if (![responseObject isKindOfClass:[NSDictionary class]]) {
OWSLogError(@"Device response was not a dictionary.");

Loading…
Cancel
Save