mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'charlesmchen/firstSendVsLinkedDevices'
This commit is contained in:
commit
64938b7824
|
@ -9,6 +9,7 @@
|
|||
#import "OWSProfileManager.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <SignalServiceKit/ECKeyPair+OWSPrivateKey.h>
|
||||
#import <SignalServiceKit/OWSDevice.h>
|
||||
#import <SignalServiceKit/OWSDeviceProvisioner.h>
|
||||
#import <SignalServiceKit/OWSIdentityManager.h>
|
||||
#import <SignalServiceKit/OWSReadReceiptManager.h>
|
||||
|
@ -18,9 +19,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@interface OWSLinkDeviceViewController ()
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UIView *qrScanningView;
|
||||
@property (strong, nonatomic) IBOutlet UILabel *scanningInstructionsLabel;
|
||||
@property (strong, nonatomic) OWSQRCodeScanningViewController *qrScanningController;
|
||||
@property (nonatomic) YapDatabaseConnection *dbConnection;
|
||||
@property (nonatomic) IBOutlet UIView *qrScanningView;
|
||||
@property (nonatomic) IBOutlet UILabel *scanningInstructionsLabel;
|
||||
@property (nonatomic) OWSQRCodeScanningViewController *qrScanningController;
|
||||
@property (nonatomic, readonly) OWSReadReceiptManager *readReceiptManager;
|
||||
|
||||
@end
|
||||
|
@ -31,6 +33,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.dbConnection = [[TSStorageManager sharedManager] newDatabaseConnection];
|
||||
|
||||
// HACK to get full width preview layer
|
||||
CGRect oldFrame = self.qrScanningView.frame;
|
||||
self.qrScanningView.frame = CGRectMake(
|
||||
|
@ -142,6 +146,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (void)provisionWithParser:(OWSDeviceProvisioningURLParser *)parser
|
||||
{
|
||||
// Optimistically set this flag.
|
||||
[OWSDeviceManager.sharedManager setMayHaveLinkedDevices:YES dbConnection:self.dbConnection];
|
||||
|
||||
ECKeyPair *_Nullable identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair];
|
||||
OWSAssert(identityKeyPair);
|
||||
NSData *myPublicKey = identityKeyPair.publicKey;
|
||||
|
|
|
@ -20,10 +20,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@interface OWSLinkedDevicesTableViewController ()
|
||||
|
||||
@property YapDatabaseConnection *dbConnection;
|
||||
@property YapDatabaseViewMappings *deviceMappings;
|
||||
@property NSTimer *pollingRefreshTimer;
|
||||
@property BOOL isExpectingMoreDevices;
|
||||
@property (nonatomic) YapDatabaseConnection *dbConnection;
|
||||
@property (nonatomic) YapDatabaseViewMappings *deviceMappings;
|
||||
@property (nonatomic) NSTimer *pollingRefreshTimer;
|
||||
@property (nonatomic) BOOL isExpectingMoreDevices;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -131,6 +131,14 @@ int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1;
|
|||
__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.
|
||||
[OWSDevice setMayHaveLinkedDevices:YES
|
||||
dbConnection:[[TSStorageManager sharedManager] newDatabaseConnection]];
|
||||
}
|
||||
|
||||
if (devices.count > [OWSDevice numberOfKeysInCollection]) {
|
||||
// Got our new device, we can stop refreshing.
|
||||
wself.isExpectingMoreDevices = NO;
|
||||
|
@ -243,7 +251,6 @@ int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1;
|
|||
return (NSInteger)[self.deviceMappings numberOfItemsInSection:(NSUInteger)section];
|
||||
case OWSLinkedDevicesTableViewControllerSectionAddDevice:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
DDLogError(@"Unknown section: %ld", (long)section);
|
||||
return 0;
|
||||
|
|
|
@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)removeDevices:(NSSet *)set;
|
||||
|
||||
@property (nonatomic, nullable) NSString *relay;
|
||||
@property (nonatomic, retain) NSMutableOrderedSet *devices;
|
||||
@property (nonatomic) NSMutableOrderedSet *devices;
|
||||
|
||||
- (BOOL)supportsVoice;
|
||||
// This property indicates support for both WebRTC audio and video calls.
|
||||
|
|
|
@ -23,7 +23,23 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return self;
|
||||
}
|
||||
|
||||
_devices = [NSMutableOrderedSet orderedSetWithObject:[NSNumber numberWithInt:1]];
|
||||
OWSAssert([TSAccountManager localNumber].length > 0);
|
||||
if ([[TSAccountManager localNumber] isEqualToString:textSecureIdentifier]) {
|
||||
// Default to no devices.
|
||||
//
|
||||
// This instance represents our own account and is used for sending
|
||||
// sync message to linked devices. We shouldn't have any linked devices
|
||||
// yet when we create the "self" SignalRecipient, and we don't need to
|
||||
// send sync messages to the primary - we ARE the primary.
|
||||
_devices = [NSMutableOrderedSet new];
|
||||
} else {
|
||||
// Default to sending to just primary device.
|
||||
//
|
||||
// OWSMessageSender will correct this if it is wrong the next time
|
||||
// we send a message to this recipient.
|
||||
_devices = [NSMutableOrderedSet orderedSetWithObject:@(1)];
|
||||
}
|
||||
|
||||
_relay = [relay isEqualToString:@""] ? nil : relay;
|
||||
|
||||
return self;
|
||||
|
|
|
@ -9,12 +9,25 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
extern uint32_t const OWSDevicePrimaryDeviceId;
|
||||
|
||||
@interface OWSDeviceManager : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
- (BOOL)mayHaveLinkedDevices:(YapDatabaseConnection *)dbConnection;
|
||||
- (void)setMayHaveLinkedDevices:(BOOL)value dbConnection:(YapDatabaseConnection *)dbConnection;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSDevice : TSYapDatabaseObject <MTLJSONSerializing>
|
||||
|
||||
@property (nonatomic, readonly) NSInteger deviceId;
|
||||
@property (nullable, readonly) NSString *name;
|
||||
@property (readonly) NSDate *createdAt;
|
||||
@property (readonly) NSDate *lastSeenAt;
|
||||
@property (nonatomic, readonly, nullable) NSString *name;
|
||||
@property (nonatomic, readonly) NSDate *createdAt;
|
||||
@property (nonatomic, readonly) NSDate *lastSeenAt;
|
||||
|
||||
+ (instancetype)deviceFromJSONDictionary:(NSDictionary *)deviceAttributes error:(NSError **)error;
|
||||
|
||||
|
|
|
@ -5,26 +5,90 @@
|
|||
#import "OWSDevice.h"
|
||||
#import "NSDate+OWS.h"
|
||||
#import "OWSError.h"
|
||||
#import "TSStorageManager.h"
|
||||
#import "YapDatabaseConnection.h"
|
||||
#import "YapDatabaseTransaction.h"
|
||||
#import <Mantle/MTLValueTransformer.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static MTLValueTransformer *_millisecondTimestampToDateTransformer;
|
||||
uint32_t const OWSDevicePrimaryDeviceId = 1;
|
||||
NSString *const kTSStorageManager_OWSDeviceCollection = @"kTSStorageManager_OWSDeviceCollection";
|
||||
NSString *const kTSStorageManager_MayHaveLinkedDevices = @"kTSStorageManager_MayHaveLinkedDevices";
|
||||
|
||||
@interface OWSDevice ()
|
||||
@interface OWSDeviceManager ()
|
||||
|
||||
@property NSString *name;
|
||||
@property NSDate *createdAt;
|
||||
@property NSDate *lastSeenAt;
|
||||
@property (atomic, nullable) NSNumber *mayHaveLinkedDevicesCached;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OWSDevice
|
||||
#pragma mark -
|
||||
|
||||
@synthesize name = _name;
|
||||
@implementation OWSDeviceManager
|
||||
|
||||
+ (instancetype)sharedManager
|
||||
{
|
||||
static OWSDeviceManager *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[self alloc] initDefault];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (instancetype)initDefault
|
||||
{
|
||||
return [super init];
|
||||
}
|
||||
|
||||
- (BOOL)mayHaveLinkedDevices:(YapDatabaseConnection *)dbConnection
|
||||
{
|
||||
OWSAssert(dbConnection);
|
||||
|
||||
@synchronized(self)
|
||||
{
|
||||
if (!self.mayHaveLinkedDevicesCached) {
|
||||
self.mayHaveLinkedDevicesCached = @([dbConnection boolForKey:kTSStorageManager_MayHaveLinkedDevices
|
||||
inCollection:kTSStorageManager_OWSDeviceCollection
|
||||
defaultValue:YES]);
|
||||
}
|
||||
|
||||
return [self.mayHaveLinkedDevicesCached boolValue];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMayHaveLinkedDevices:(BOOL)value dbConnection:(YapDatabaseConnection *)dbConnection
|
||||
{
|
||||
OWSAssert(dbConnection);
|
||||
|
||||
@synchronized(self)
|
||||
{
|
||||
self.mayHaveLinkedDevicesCached = @(value);
|
||||
|
||||
[dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
[transaction setObject:@(value)
|
||||
forKey:kTSStorageManager_MayHaveLinkedDevices
|
||||
inCollection:kTSStorageManager_OWSDeviceCollection];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSDevice ()
|
||||
|
||||
@property (nonatomic) NSInteger deviceId;
|
||||
@property (nonatomic, nullable) NSString *name;
|
||||
@property (nonatomic) NSDate *createdAt;
|
||||
@property (nonatomic) NSDate *lastSeenAt;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSDevice
|
||||
|
||||
+ (instancetype)deviceFromJSONDictionary:(NSDictionary *)deviceAttributes error:(NSError **)error
|
||||
{
|
||||
|
@ -76,38 +140,39 @@ uint32_t const OWSDevicePrimaryDeviceId = 1;
|
|||
|
||||
+ (MTLValueTransformer *)millisecondTimestampToDateTransformer
|
||||
{
|
||||
if (!_millisecondTimestampToDateTransformer) {
|
||||
_millisecondTimestampToDateTransformer =
|
||||
[MTLValueTransformer transformerUsingForwardBlock:^id(id value, BOOL *success, NSError **error) {
|
||||
if ([value isKindOfClass:[NSNumber class]]) {
|
||||
NSNumber *number = (NSNumber *)value;
|
||||
NSDate *result = [NSDate ows_dateWithMillisecondsSince1970:[number longLongValue]];
|
||||
static MTLValueTransformer *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [MTLValueTransformer transformerUsingForwardBlock:^id(id value, BOOL *success, NSError **error) {
|
||||
if ([value isKindOfClass:[NSNumber class]]) {
|
||||
NSNumber *number = (NSNumber *)value;
|
||||
NSDate *result = [NSDate ows_dateWithMillisecondsSince1970:[number longLongValue]];
|
||||
if (result) {
|
||||
*success = YES;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
*success = NO;
|
||||
DDLogError(@"%@ unable to decode date from %@", self.logTag, value);
|
||||
*error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecodeJson, @"Unable to decode date from %@");
|
||||
return nil;
|
||||
}
|
||||
reverseBlock:^id(id value, BOOL *success, NSError **error) {
|
||||
if ([value isKindOfClass:[NSDate class]]) {
|
||||
NSDate *date = (NSDate *)value;
|
||||
NSNumber *result = [NSNumber numberWithLongLong:[NSDate ows_millisecondsSince1970ForDate:date]];
|
||||
if (result) {
|
||||
*success = YES;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
DDLogError(@"%@ unable to encode date from %@", self.logTag, value);
|
||||
*error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToEncodeJson, @"Unable to encode date");
|
||||
*success = NO;
|
||||
DDLogError(@"%@ unable to decode date from %@", self.logTag, value);
|
||||
*error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecodeJson, @"Unable to decode date from %@");
|
||||
return nil;
|
||||
}
|
||||
reverseBlock:^id(id value, BOOL *success, NSError **error) {
|
||||
if ([value isKindOfClass:[NSDate class]]) {
|
||||
NSDate *date = (NSDate *)value;
|
||||
NSNumber *result = [NSNumber numberWithLongLong:[NSDate ows_millisecondsSince1970ForDate:date]];
|
||||
if (result) {
|
||||
*success = YES;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
DDLogError(@"%@ unable to encode date from %@", self.logTag, value);
|
||||
*error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToEncodeJson, @"Unable to encode date");
|
||||
*success = NO;
|
||||
return nil;
|
||||
}];
|
||||
}
|
||||
return _millisecondTimestampToDateTransformer;
|
||||
}];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (uint32_t)currentDeviceId
|
||||
|
|
|
@ -954,14 +954,29 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|||
|
||||
NSString *localNumber = [TSAccountManager localNumber];
|
||||
if ([localNumber isEqualToString:recipient.uniqueId]) {
|
||||
__block BOOL hasSecondaryDevices;
|
||||
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
hasSecondaryDevices = [OWSDevice hasSecondaryDevicesWithTransaction:transaction];
|
||||
}];
|
||||
OWSAssert([message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
||||
// Messages send to the "local number" should be sync messages.
|
||||
//
|
||||
// We can skip sending sync messages if we know that we have no linked
|
||||
// devices. However, we need to be sure to handle the case where the
|
||||
// linked device list has just changed.
|
||||
//
|
||||
// The linked device list is reflected in two separate pieces of state:
|
||||
//
|
||||
// * OWSDevice's state is updated when you link or unlink a device.
|
||||
// * SignalRecipient's state is updated by 409 "Mismatched devices"
|
||||
// responses from the service.
|
||||
//
|
||||
// If _both_ of these pieces of state agree that there are no linked
|
||||
// devices, then can safely skip sending sync message.
|
||||
|
||||
// 1. Check OWSDevice's state.
|
||||
BOOL mayHaveLinkedDevices = [OWSDeviceManager.sharedManager mayHaveLinkedDevices:self.dbConnection];
|
||||
|
||||
// 2. Check SignalRecipient's state.
|
||||
BOOL hasDeviceMessages = deviceMessages.count > 0;
|
||||
|
||||
if (!hasSecondaryDevices && !hasDeviceMessages) {
|
||||
if (!mayHaveLinkedDevices && !hasDeviceMessages) {
|
||||
DDLogInfo(@"%@ Ignoring sync message without secondary devices: %@", self.logTag, [message class]);
|
||||
OWSAssert([message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
||||
|
||||
|
@ -972,10 +987,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|||
});
|
||||
|
||||
return;
|
||||
} else if (hasSecondaryDevices) {
|
||||
} else if (mayHaveLinkedDevices) {
|
||||
// We may have just linked a new secondary device which is not yet reflected in
|
||||
// the SignalRecipient that corresponds to ourself. Proceed. Client should learn
|
||||
// of new secondary devices when this message send fails.
|
||||
// of new secondary devices via 409 "Mismatched devices" response.
|
||||
DDLogWarn(@"%@ sync message has no device messages but account has secondary devices.", self.logTag);
|
||||
} else if (hasDeviceMessages) {
|
||||
OWSFail(@"%@ sync message has device messages for unknown secondary devices.", self.logTag);
|
||||
|
@ -1095,6 +1110,13 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|||
NSArray *extraDevices = [dictionary objectForKey:@"extraDevices"];
|
||||
NSArray *missingDevices = [dictionary objectForKey:@"missingDevices"];
|
||||
|
||||
if (missingDevices.count > 0) {
|
||||
NSString *localNumber = [TSAccountManager localNumber];
|
||||
if ([localNumber isEqualToString:recipient.uniqueId]) {
|
||||
[OWSDeviceManager.sharedManager setMayHaveLinkedDevices:YES dbConnection:self.dbConnection];
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async([OWSDispatch sessionStoreQueue], ^{
|
||||
if (extraDevices.count < 1 && missingDevices.count < 1) {
|
||||
OWSProdFail([OWSAnalyticsEvents messageSenderErrorNoMissingOrExtraDevices]);
|
||||
|
|
|
@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (BOOL)hasObjectForKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(BOOL)defaultValue;
|
||||
- (int)intForKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
- (nullable NSDate *)dateForKey:(NSString *)key inCollection:(NSString *)collection;
|
||||
|
|
|
@ -52,8 +52,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection
|
||||
{
|
||||
NSNumber *_Nullable number = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]];
|
||||
return [number boolValue];
|
||||
return [self boolForKey:key inCollection:collection defaultValue:NO];
|
||||
}
|
||||
|
||||
- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(BOOL)defaultValue
|
||||
{
|
||||
NSNumber *_Nullable value = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]];
|
||||
return value ? [value boolValue] : defaultValue;
|
||||
}
|
||||
|
||||
- (nullable NSData *)dataForKey:(NSString *)key inCollection:(NSString *)collection
|
||||
|
|
Loading…
Reference in a new issue