mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Updated the config message generation for GRDB Migrated more preferences into GRDB Added paging to the MediaTileViewController and sorted out the various animations/transitions Fixed an issue where the 'recipientState' for the 'baseQuery' on the ConversationCell.ViewModel wasn't grouping correctly Fixed an issue where the MediaZoomAnimationController could fail if the contextual info wasn't available Fixed an issue where the MediaZoomAnimationController bounce looked odd when returning to the detail screen from the tile screen Fixed an issue where the MediaZoomAnimationController didn't work for videos Fixed a bug where the YDB to GRDB migration wasn't properly handling video files Fixed a number of minor UI bugs with the GalleryRailView Deleted a bunch of legacy code
500 lines
18 KiB
Objective-C
500 lines
18 KiB
Objective-C
//
|
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
#import "TSAccountManager.h"
|
|
#import "AppContext.h"
|
|
#import "AppReadiness.h"
|
|
#import "NSNotificationCenter+OWS.h"
|
|
#import "SSKEnvironment.h"
|
|
#import "YapDatabaseConnection+OWS.h"
|
|
#import "YapDatabaseTransaction+OWS.h"
|
|
#import <PromiseKit/AnyPromise.h>
|
|
#import <Reachability/Reachability.h>
|
|
#import <SignalCoreKit/NSData+OWS.h>
|
|
#import <SignalCoreKit/Randomness.h>
|
|
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
|
|
#import <YapDatabase/YapDatabase.h>
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
NSString *const TSRegistrationErrorDomain = @"TSRegistrationErrorDomain";
|
|
NSString *const TSRegistrationErrorUserInfoHTTPStatus = @"TSHTTPStatus";
|
|
NSString *const RegistrationStateDidChangeNotification = @"RegistrationStateDidChangeNotification";
|
|
NSString *const kNSNotificationName_LocalNumberDidChange = @"kNSNotificationName_LocalNumberDidChange";
|
|
|
|
NSString *const TSAccountManager_RegisteredNumberKey = @"TSStorageRegisteredNumberKey";
|
|
NSString *const TSAccountManager_IsDeregisteredKey = @"TSAccountManager_IsDeregisteredKey";
|
|
NSString *const TSAccountManager_ReregisteringPhoneNumberKey = @"TSAccountManager_ReregisteringPhoneNumberKey";
|
|
NSString *const TSAccountManager_LocalRegistrationIdKey = @"TSStorageLocalRegistrationId";
|
|
NSString *const TSAccountManager_HasPendingRestoreDecisionKey = @"TSAccountManager_HasPendingRestoreDecisionKey";
|
|
|
|
NSString *const TSAccountManager_UserAccountCollection = @"TSStorageUserAccountCollection";
|
|
NSString *const TSAccountManager_ServerAuthToken = @"TSStorageServerAuthToken";
|
|
NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignalingKey";
|
|
NSString *const TSAccountManager_ManualMessageFetchKey = @"TSAccountManager_ManualMessageFetchKey";
|
|
NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountManager_NeedsAccountAttributesUpdateKey";
|
|
|
|
@interface TSAccountManager ()
|
|
|
|
@property (atomic, readonly) BOOL isRegistered;
|
|
|
|
@property (nonatomic, nullable) NSString *cachedLocalNumber;
|
|
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
|
|
|
|
@property (nonatomic, nullable) NSNumber *cachedIsDeregistered;
|
|
|
|
@property (nonatomic) Reachability *reachability;
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@implementation TSAccountManager
|
|
|
|
@synthesize isRegistered = _isRegistered;
|
|
|
|
- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage
|
|
{
|
|
self = [super init];
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
|
|
_dbConnection = [primaryStorage newDatabaseConnection];
|
|
self.reachability = [Reachability reachabilityForInternetConnection];
|
|
|
|
if (!CurrentAppContext().isMainApp) {
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(yapDatabaseModifiedExternally:)
|
|
name:YapDatabaseModifiedExternallyNotification
|
|
object:nil];
|
|
}
|
|
|
|
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
|
|
[[self updateAccountAttributesIfNecessary] retainUntilComplete];
|
|
}];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(reachabilityChanged)
|
|
name:kReachabilityChangedNotification
|
|
object:nil];
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
+ (instancetype)sharedInstance
|
|
{
|
|
return SSKEnvironment.shared.tsAccountManager;
|
|
}
|
|
|
|
#pragma mark - Dependencies
|
|
|
|
- (id<ProfileManagerProtocol>)profileManager {
|
|
return SSKEnvironment.shared.profileManager;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)setPhoneNumberAwaitingVerification:(NSString *_Nullable)phoneNumberAwaitingVerification
|
|
{
|
|
_phoneNumberAwaitingVerification = phoneNumberAwaitingVerification;
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_LocalNumberDidChange
|
|
object:nil
|
|
userInfo:nil];
|
|
}
|
|
|
|
- (OWSRegistrationState)registrationState
|
|
{
|
|
if (!self.isRegistered) {
|
|
return OWSRegistrationState_Unregistered;
|
|
} else if (self.isDeregistered) {
|
|
if (self.isReregistering) {
|
|
return OWSRegistrationState_Reregistering;
|
|
} else {
|
|
return OWSRegistrationState_Deregistered;
|
|
}
|
|
} else if (self.isDeregistered) {
|
|
return OWSRegistrationState_PendingBackupRestore;
|
|
} else {
|
|
return OWSRegistrationState_Registered;
|
|
}
|
|
}
|
|
|
|
- (BOOL)isRegistered
|
|
{
|
|
@synchronized (self) {
|
|
if (_isRegistered) {
|
|
return YES;
|
|
} else {
|
|
// Cache this once it's true since it's called alot, involves a dbLookup, and once set - it doesn't change.
|
|
_isRegistered = [self storedLocalNumber] != nil;
|
|
}
|
|
return _isRegistered;
|
|
}
|
|
}
|
|
|
|
- (BOOL)isRegisteredAndReady
|
|
{
|
|
return self.registrationState == OWSRegistrationState_Registered;
|
|
}
|
|
|
|
- (void)didRegister
|
|
{
|
|
NSString *phoneNumber = self.phoneNumberAwaitingVerification;
|
|
|
|
[self storeLocalNumber:phoneNumber];
|
|
|
|
// Warm these cached values.
|
|
[self isRegistered];
|
|
[self localNumber];
|
|
[self isDeregistered];
|
|
|
|
[self postRegistrationStateDidChangeNotification];
|
|
}
|
|
|
|
+ (nullable NSString *)localNumber
|
|
{
|
|
return [[self sharedInstance] localNumber];
|
|
}
|
|
|
|
- (nullable NSString *)localNumber
|
|
{
|
|
NSString *awaitingVerif = self.phoneNumberAwaitingVerification;
|
|
if (awaitingVerif) {
|
|
return awaitingVerif;
|
|
}
|
|
|
|
// Cache this since we access this a lot, and once set it will not change.
|
|
@synchronized(self)
|
|
{
|
|
if (self.cachedLocalNumber == nil) {
|
|
self.cachedLocalNumber = self.storedLocalNumber;
|
|
}
|
|
}
|
|
|
|
return self.cachedLocalNumber;
|
|
}
|
|
|
|
- (nullable NSString *)storedLocalNumber
|
|
{
|
|
@synchronized (self) {
|
|
return [self.dbConnection stringForKey:TSAccountManager_RegisteredNumberKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
}
|
|
}
|
|
|
|
- (nullable NSString *)storedOrCachedLocalNumber:(YapDatabaseReadTransaction *)transaction
|
|
{
|
|
@synchronized(self) {
|
|
if (self.cachedLocalNumber) {
|
|
return self.cachedLocalNumber;
|
|
}
|
|
}
|
|
|
|
return [transaction stringForKey:TSAccountManager_RegisteredNumberKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
}
|
|
|
|
- (void)storeLocalNumber:(NSString *)localNumber
|
|
{
|
|
@synchronized (self) {
|
|
[self.dbConnection setObject:localNumber
|
|
forKey:TSAccountManager_RegisteredNumberKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
|
|
[self.dbConnection removeObjectForKey:TSAccountManager_ReregisteringPhoneNumberKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
|
|
self.phoneNumberAwaitingVerification = nil;
|
|
|
|
self.cachedLocalNumber = localNumber;
|
|
}
|
|
}
|
|
|
|
+ (uint32_t)getOrGenerateRegistrationId:(YapDatabaseReadWriteTransaction *)transaction
|
|
{
|
|
return [[self sharedInstance] getOrGenerateRegistrationId:transaction];
|
|
}
|
|
|
|
- (uint32_t)getOrGenerateRegistrationId
|
|
{
|
|
__block uint32_t result;
|
|
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
|
result = [self getOrGenerateRegistrationId:transaction];
|
|
}];
|
|
return result;
|
|
}
|
|
|
|
- (uint32_t)getOrGenerateRegistrationId:(YapDatabaseReadWriteTransaction *)transaction
|
|
{
|
|
// Unlike other methods in this class, there's no need for a `@synchronized` block
|
|
// here, since we're already in a write transaction, and all writes occur on a serial queue.
|
|
//
|
|
// Since other code in this class which uses @synchronized(self) also needs to open write
|
|
// transaction, using @synchronized(self) here, inside of a WriteTransaction risks deadlock.
|
|
uint32_t registrationID = [[transaction objectForKey:TSAccountManager_LocalRegistrationIdKey
|
|
inCollection:TSAccountManager_UserAccountCollection] unsignedIntValue];
|
|
|
|
if (registrationID == 0) {
|
|
registrationID = (uint32_t)arc4random_uniform(16380) + 1;
|
|
|
|
[transaction setObject:[NSNumber numberWithUnsignedInteger:registrationID]
|
|
forKey:TSAccountManager_LocalRegistrationIdKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
}
|
|
return registrationID;
|
|
}
|
|
|
|
- (void)registerForPushNotificationsWithPushToken:(NSString *)pushToken
|
|
voipToken:(NSString *)voipToken
|
|
isForcedUpdate:(BOOL)isForcedUpdate
|
|
success:(void (^)(void))successHandler
|
|
failure:(void (^)(NSError *))failureHandler
|
|
{
|
|
[self registerForPushNotificationsWithPushToken:pushToken
|
|
voipToken:voipToken
|
|
isForcedUpdate:isForcedUpdate
|
|
success:successHandler
|
|
failure:failureHandler
|
|
remainingRetries:3];
|
|
}
|
|
|
|
- (void)registerForPushNotificationsWithPushToken:(NSString *)pushToken
|
|
voipToken:(NSString *)voipToken
|
|
isForcedUpdate:(BOOL)isForcedUpdate
|
|
success:(void (^)(void))successHandler
|
|
failure:(void (^)(NSError *))failureHandler
|
|
remainingRetries:(int)remainingRetries
|
|
{
|
|
BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
|
|
NSData *pushTokenAsData = [NSData dataFromHexString:pushToken];
|
|
AnyPromise *promise = isUsingFullAPNs ? [LKPushNotificationAPI registerWithToken:pushTokenAsData hexEncodedPublicKey:self.localNumber isForcedUpdate:isForcedUpdate]
|
|
: [LKPushNotificationAPI unregisterToken:pushTokenAsData];
|
|
promise
|
|
.then(^() {
|
|
successHandler();
|
|
})
|
|
.catch(^(NSError *error) {
|
|
if (remainingRetries > 0) {
|
|
[self registerForPushNotificationsWithPushToken:pushToken voipToken:voipToken isForcedUpdate:isForcedUpdate success:successHandler failure:failureHandler
|
|
remainingRetries:remainingRetries - 1];
|
|
} else {
|
|
failureHandler(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)rerequestSMSWithCaptchaToken:(nullable NSString *)captchaToken
|
|
success:(void (^)(void))successBlock
|
|
failure:(void (^)(NSError *error))failureBlock
|
|
{
|
|
// TODO: Can we remove phoneNumberAwaitingVerification?
|
|
NSString *number = self.phoneNumberAwaitingVerification;
|
|
|
|
[self registerWithPhoneNumber:number
|
|
captchaToken:captchaToken
|
|
success:successBlock
|
|
failure:failureBlock
|
|
smsVerification:YES];
|
|
}
|
|
|
|
- (void)rerequestVoiceWithCaptchaToken:(nullable NSString *)captchaToken
|
|
success:(void (^)(void))successBlock
|
|
failure:(void (^)(NSError *error))failureBlock
|
|
{
|
|
NSString *number = self.phoneNumberAwaitingVerification;
|
|
|
|
[self registerWithPhoneNumber:number
|
|
captchaToken:captchaToken
|
|
success:successBlock
|
|
failure:failureBlock
|
|
smsVerification:NO];
|
|
}
|
|
|
|
#pragma mark Server keying material
|
|
|
|
+ (NSString *)generateNewAccountAuthenticationToken {
|
|
NSData *authToken = [Randomness generateRandomBytes:16];
|
|
NSString *authTokenPrint = [[NSData dataWithData:authToken] hexadecimalString];
|
|
return authTokenPrint;
|
|
}
|
|
|
|
+ (nullable NSString *)signalingKey
|
|
{
|
|
return [[self sharedInstance] signalingKey];
|
|
}
|
|
|
|
- (nullable NSString *)signalingKey
|
|
{
|
|
return [self.dbConnection stringForKey:TSAccountManager_ServerSignalingKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
}
|
|
|
|
+ (nullable NSString *)serverAuthToken
|
|
{
|
|
return [[self sharedInstance] serverAuthToken];
|
|
}
|
|
|
|
- (nullable NSString *)serverAuthToken
|
|
{
|
|
return [self.dbConnection stringForKey:TSAccountManager_ServerAuthToken
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
}
|
|
|
|
- (void)storeServerAuthToken:(NSString *)authToken
|
|
{
|
|
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
[transaction setObject:authToken
|
|
forKey:TSAccountManager_ServerAuthToken
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
}];
|
|
}
|
|
|
|
- (void)yapDatabaseModifiedExternally:(NSNotification *)notification
|
|
{
|
|
// Any database write by the main app might reflect a deregistration,
|
|
// so clear the cached "is registered" state. This will significantly
|
|
// erode the value of this cache in the SAE.
|
|
@synchronized(self)
|
|
{
|
|
_isRegistered = NO;
|
|
}
|
|
}
|
|
|
|
#pragma mark - De-Registration
|
|
|
|
- (BOOL)isDeregistered
|
|
{
|
|
// Cache this since we access this a lot, and once set it will not change.
|
|
@synchronized(self) {
|
|
if (self.cachedIsDeregistered == nil) {
|
|
self.cachedIsDeregistered = @([self.dbConnection boolForKey:TSAccountManager_IsDeregisteredKey
|
|
inCollection:TSAccountManager_UserAccountCollection
|
|
defaultValue:NO]);
|
|
}
|
|
|
|
return self.cachedIsDeregistered.boolValue;
|
|
}
|
|
}
|
|
|
|
- (void)setIsDeregistered:(BOOL)isDeregistered
|
|
{
|
|
@synchronized(self) {
|
|
if (self.cachedIsDeregistered && self.cachedIsDeregistered.boolValue == isDeregistered) {
|
|
return;
|
|
}
|
|
|
|
self.cachedIsDeregistered = @(isDeregistered);
|
|
}
|
|
|
|
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
[transaction setObject:@(isDeregistered)
|
|
forKey:TSAccountManager_IsDeregisteredKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
}];
|
|
|
|
[self postRegistrationStateDidChangeNotification];
|
|
}
|
|
|
|
- (nullable NSString *)reregisterationPhoneNumber
|
|
{
|
|
NSString *_Nullable result = [self.dbConnection stringForKey:TSAccountManager_ReregisteringPhoneNumberKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
return result;
|
|
}
|
|
|
|
- (BOOL)isReregistering
|
|
{
|
|
return nil !=
|
|
[self.dbConnection stringForKey:TSAccountManager_ReregisteringPhoneNumberKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
}
|
|
|
|
- (BOOL)isManualMessageFetchEnabled
|
|
{
|
|
return [self.dbConnection boolForKey:TSAccountManager_ManualMessageFetchKey
|
|
inCollection:TSAccountManager_UserAccountCollection
|
|
defaultValue:NO];
|
|
}
|
|
|
|
- (AnyPromise *)setIsManualMessageFetchEnabled:(BOOL)value {
|
|
[self.dbConnection setBool:value
|
|
forKey:TSAccountManager_ManualMessageFetchKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
|
|
// Try to update the account attributes to reflect this change.
|
|
return [self updateAccountAttributes];
|
|
}
|
|
|
|
- (void)registerForTestsWithLocalNumber:(NSString *)localNumber
|
|
{
|
|
[self storeLocalNumber:localNumber];
|
|
}
|
|
|
|
#pragma mark - Account Attributes
|
|
|
|
- (AnyPromise *)updateAccountAttributes {
|
|
// Enqueue a "account attribute update", recording the "request time".
|
|
[self.dbConnection setObject:[NSDate new]
|
|
forKey:TSAccountManager_NeedsAccountAttributesUpdateKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
|
|
return [self updateAccountAttributesIfNecessary];
|
|
}
|
|
|
|
- (AnyPromise *)updateAccountAttributesIfNecessary {
|
|
if (!self.isRegistered) {
|
|
return [AnyPromise promiseWithValue:@(1)];
|
|
}
|
|
|
|
return [AnyPromise promiseWithValue:@(1)];
|
|
|
|
NSDate *_Nullable updateRequestDate =
|
|
[self.dbConnection objectForKey:TSAccountManager_NeedsAccountAttributesUpdateKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
if (!updateRequestDate) {
|
|
return [AnyPromise promiseWithValue:@(1)];
|
|
}
|
|
AnyPromise *promise = [self performUpdateAccountAttributes];
|
|
promise = promise.then(^(id value) {
|
|
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
// Clear the update request unless a new update has been requested
|
|
// while this update was in flight.
|
|
NSDate *_Nullable latestUpdateRequestDate =
|
|
[transaction objectForKey:TSAccountManager_NeedsAccountAttributesUpdateKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
if (latestUpdateRequestDate && [latestUpdateRequestDate isEqual:updateRequestDate]) {
|
|
[transaction removeObjectForKey:TSAccountManager_NeedsAccountAttributesUpdateKey
|
|
inCollection:TSAccountManager_UserAccountCollection];
|
|
}
|
|
}];
|
|
});
|
|
return promise;
|
|
}
|
|
|
|
- (void)reachabilityChanged {
|
|
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
|
|
[[self updateAccountAttributesIfNecessary] retainUntilComplete];
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Notifications
|
|
|
|
- (void)postRegistrationStateDidChangeNotification
|
|
{
|
|
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:RegistrationStateDidChangeNotification
|
|
object:nil
|
|
userInfo:nil];
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|