mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			296 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Objective-C
		
	
			
		
		
	
	
			296 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Objective-C
		
	
//
 | 
						|
//  Copyright (c) 2017 Open Whisper Systems. All rights reserved.
 | 
						|
//
 | 
						|
 | 
						|
#import "OWSBlockingManager.h"
 | 
						|
#import "OWSBlockedPhoneNumbersMessage.h"
 | 
						|
#import "OWSMessageSender.h"
 | 
						|
#import "TSStorageManager.h"
 | 
						|
#import "TextSecureKitEnv.h"
 | 
						|
 | 
						|
NS_ASSUME_NONNULL_BEGIN
 | 
						|
 | 
						|
NSString *const kNSNotificationName_BlockedPhoneNumbersDidChange = @"kNSNotificationName_BlockedPhoneNumbersDidChange";
 | 
						|
NSString *const kOWSBlockingManager_BlockedPhoneNumbersCollection = @"kOWSBlockingManager_BlockedPhoneNumbersCollection";
 | 
						|
// This key is used to persist the current "blocked phone numbers" state.
 | 
						|
NSString *const kOWSBlockingManager_BlockedPhoneNumbersKey = @"kOWSBlockingManager_BlockedPhoneNumbersKey";
 | 
						|
// This key is used to persist the most recently synced "blocked phone numbers" state.
 | 
						|
NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockingManager_SyncedBlockedPhoneNumbersKey";
 | 
						|
 | 
						|
@interface OWSBlockingManager ()
 | 
						|
 | 
						|
@property (nonatomic, readonly) TSStorageManager *storageManager;
 | 
						|
@property (nonatomic, readonly) OWSMessageSender *messageSender;
 | 
						|
 | 
						|
// We don't store the phone numbers as instances of PhoneNumber to avoid
 | 
						|
// consistency issues between clients, but these should all be valid e164
 | 
						|
// phone numbers.
 | 
						|
@property (atomic, readonly) NSMutableSet<NSString *> *blockedPhoneNumberSet;
 | 
						|
 | 
						|
@end
 | 
						|
 | 
						|
#pragma mark -
 | 
						|
 | 
						|
@implementation OWSBlockingManager
 | 
						|
 | 
						|
+ (instancetype)sharedManager
 | 
						|
{
 | 
						|
    static OWSBlockingManager *sharedMyManager = nil;
 | 
						|
    static dispatch_once_t onceToken;
 | 
						|
    dispatch_once(&onceToken, ^{
 | 
						|
        sharedMyManager = [[self alloc] initDefault];
 | 
						|
    });
 | 
						|
    return sharedMyManager;
 | 
						|
}
 | 
						|
 | 
						|
- (instancetype)initDefault
 | 
						|
{
 | 
						|
    TSStorageManager *storageManager = [TSStorageManager sharedManager];
 | 
						|
    OWSMessageSender *messageSender = [TextSecureKitEnv sharedEnv].messageSender;
 | 
						|
 | 
						|
    return [self initWithStorageManager:storageManager messageSender:messageSender];
 | 
						|
}
 | 
						|
 | 
						|
- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager messageSender:(OWSMessageSender *)messageSender
 | 
						|
{
 | 
						|
    self = [super init];
 | 
						|
 | 
						|
    if (!self) {
 | 
						|
        return self;
 | 
						|
    }
 | 
						|
 | 
						|
    OWSAssert(storageManager);
 | 
						|
    OWSAssert(messageSender);
 | 
						|
 | 
						|
    _storageManager = storageManager;
 | 
						|
    _messageSender = messageSender;
 | 
						|
 | 
						|
    OWSSingletonAssert();
 | 
						|
 | 
						|
    // Register this manager with the message sender.
 | 
						|
    // This is a circular dependency.
 | 
						|
    [messageSender setBlockingManager:self];
 | 
						|
 | 
						|
    return self;
 | 
						|
}
 | 
						|
 | 
						|
- (void)dealloc
 | 
						|
{
 | 
						|
    [[NSNotificationCenter defaultCenter] removeObserver:self];
 | 
						|
}
 | 
						|
 | 
						|
- (void)observeNotifications
 | 
						|
{
 | 
						|
    [[NSNotificationCenter defaultCenter] addObserver:self
 | 
						|
                                             selector:@selector(applicationDidBecomeActive:)
 | 
						|
                                                 name:UIApplicationDidBecomeActiveNotification
 | 
						|
                                               object:nil];
 | 
						|
}
 | 
						|
 | 
						|
- (void)addBlockedPhoneNumber:(NSString *)phoneNumber
 | 
						|
{
 | 
						|
    OWSAssert(phoneNumber.length > 0);
 | 
						|
 | 
						|
    DDLogInfo(@"%@ addBlockedPhoneNumber: %@", self.tag, phoneNumber);
 | 
						|
 | 
						|
    @synchronized(self)
 | 
						|
    {
 | 
						|
        [self ensureLazyInitialization];
 | 
						|
 | 
						|
        if ([_blockedPhoneNumberSet containsObject:phoneNumber]) {
 | 
						|
            // Ignore redundant changes.
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        [_blockedPhoneNumberSet addObject:phoneNumber];
 | 
						|
    }
 | 
						|
 | 
						|
    [self handleUpdate];
 | 
						|
}
 | 
						|
 | 
						|
- (void)removeBlockedPhoneNumber:(NSString *)phoneNumber
 | 
						|
{
 | 
						|
    OWSAssert(phoneNumber.length > 0);
 | 
						|
 | 
						|
    DDLogInfo(@"%@ removeBlockedPhoneNumber: %@", self.tag, phoneNumber);
 | 
						|
 | 
						|
    @synchronized(self)
 | 
						|
    {
 | 
						|
        [self ensureLazyInitialization];
 | 
						|
 | 
						|
        if (![_blockedPhoneNumberSet containsObject:phoneNumber]) {
 | 
						|
            // Ignore redundant changes.
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        [_blockedPhoneNumberSet removeObject:phoneNumber];
 | 
						|
    }
 | 
						|
 | 
						|
    [self handleUpdate];
 | 
						|
}
 | 
						|
 | 
						|
- (void)setBlockedPhoneNumbers:(NSArray<NSString *> *)blockedPhoneNumbers sendSyncMessage:(BOOL)sendSyncMessage
 | 
						|
{
 | 
						|
    OWSAssert(blockedPhoneNumbers != nil);
 | 
						|
 | 
						|
    DDLogInfo(@"%@ setBlockedPhoneNumbers: %d", self.tag, (int)blockedPhoneNumbers.count);
 | 
						|
 | 
						|
    @synchronized(self)
 | 
						|
    {
 | 
						|
        [self ensureLazyInitialization];
 | 
						|
 | 
						|
        NSSet *newSet = [NSSet setWithArray:blockedPhoneNumbers];
 | 
						|
        if ([_blockedPhoneNumberSet isEqualToSet:newSet]) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        _blockedPhoneNumberSet = [newSet mutableCopy];
 | 
						|
    }
 | 
						|
 | 
						|
    [self handleUpdate:sendSyncMessage];
 | 
						|
}
 | 
						|
 | 
						|
- (NSArray<NSString *> *)blockedPhoneNumbers
 | 
						|
{
 | 
						|
    @synchronized(self)
 | 
						|
    {
 | 
						|
        [self ensureLazyInitialization];
 | 
						|
 | 
						|
        return [_blockedPhoneNumberSet.allObjects sortedArrayUsingSelector:@selector(compare:)];
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// This should be called every time the block list changes.
 | 
						|
 | 
						|
- (void)handleUpdate
 | 
						|
{
 | 
						|
    // By default, always send a sync message when the block list changes.
 | 
						|
    [self handleUpdate:YES];
 | 
						|
}
 | 
						|
 | 
						|
- (void)handleUpdate:(BOOL)sendSyncMessage
 | 
						|
{
 | 
						|
    NSArray<NSString *> *blockedPhoneNumbers = [self blockedPhoneNumbers];
 | 
						|
 | 
						|
    [_storageManager setObject:blockedPhoneNumbers
 | 
						|
                        forKey:kOWSBlockingManager_BlockedPhoneNumbersKey
 | 
						|
                  inCollection:kOWSBlockingManager_BlockedPhoneNumbersCollection];
 | 
						|
 | 
						|
    dispatch_async(dispatch_get_main_queue(), ^{
 | 
						|
        if (sendSyncMessage) {
 | 
						|
            [self sendBlockedPhoneNumbersMessage:blockedPhoneNumbers];
 | 
						|
        } else {
 | 
						|
            // If this update came from an incoming block list sync message,
 | 
						|
            // update the "synced blocked phone numbers" state immediately,
 | 
						|
            // since we're now in sync.
 | 
						|
            //
 | 
						|
            // There could be data loss if both clients modify the block list
 | 
						|
            // at the same time, but:
 | 
						|
            //
 | 
						|
            // a) Block list changes will be rare.
 | 
						|
            // b) Conflicting block list changes will be even rarer.
 | 
						|
            // c) It's unlikely a user will make conflicting changes on two
 | 
						|
            //    devices around the same time.
 | 
						|
            // d) There isn't a good way to avoid this.
 | 
						|
            [self saveSyncedBlockedPhoneNumbers:blockedPhoneNumbers];
 | 
						|
        }
 | 
						|
 | 
						|
        [[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_BlockedPhoneNumbersDidChange
 | 
						|
                                                            object:nil
 | 
						|
                                                          userInfo:nil];
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
// This method should only be called from within a synchronized block.
 | 
						|
- (void)ensureLazyInitialization
 | 
						|
{
 | 
						|
    if (_blockedPhoneNumberSet) {
 | 
						|
        // _blockedPhoneNumberSet has already been loaded, abort.
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    NSArray<NSString *> *blockedPhoneNumbers =
 | 
						|
        [_storageManager objectForKey:kOWSBlockingManager_BlockedPhoneNumbersKey
 | 
						|
                         inCollection:kOWSBlockingManager_BlockedPhoneNumbersCollection];
 | 
						|
    _blockedPhoneNumberSet = [[NSMutableSet alloc] initWithArray:(blockedPhoneNumbers ?: [NSArray new])];
 | 
						|
 | 
						|
    [self syncBlockedPhoneNumbersIfNecessary];
 | 
						|
    [self observeNotifications];
 | 
						|
}
 | 
						|
 | 
						|
// This method should only be called from within a synchronized block.
 | 
						|
- (void)syncBlockedPhoneNumbersIfNecessary
 | 
						|
{
 | 
						|
    OWSAssert(_blockedPhoneNumberSet);
 | 
						|
 | 
						|
    // If we haven't yet successfully synced the current "blocked phone numbers" changes,
 | 
						|
    // try again to sync now.
 | 
						|
    NSArray<NSString *> *syncedBlockedPhoneNumbers =
 | 
						|
        [_storageManager objectForKey:kOWSBlockingManager_SyncedBlockedPhoneNumbersKey
 | 
						|
                         inCollection:kOWSBlockingManager_BlockedPhoneNumbersCollection];
 | 
						|
    NSSet *syncedBlockedPhoneNumberSet = [[NSSet alloc] initWithArray:(syncedBlockedPhoneNumbers ?: [NSArray new])];
 | 
						|
    if (![_blockedPhoneNumberSet isEqualToSet:syncedBlockedPhoneNumberSet]) {
 | 
						|
        DDLogInfo(@"%@ retrying sync of blocked phone numbers", self.tag);
 | 
						|
        dispatch_async(dispatch_get_main_queue(), ^{
 | 
						|
            [self sendBlockedPhoneNumbersMessage:self.blockedPhoneNumbers];
 | 
						|
        });
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
- (void)sendBlockedPhoneNumbersMessage:(NSArray<NSString *> *)blockedPhoneNumbers
 | 
						|
{
 | 
						|
    OWSAssert(blockedPhoneNumbers);
 | 
						|
 | 
						|
    OWSBlockedPhoneNumbersMessage *message =
 | 
						|
        [[OWSBlockedPhoneNumbersMessage alloc] initWithPhoneNumbers:blockedPhoneNumbers];
 | 
						|
 | 
						|
    [self.messageSender sendMessage:message
 | 
						|
        success:^{
 | 
						|
            DDLogInfo(@"%@ Successfully sent blocked phone numbers sync message", self.tag);
 | 
						|
 | 
						|
            // Record the last set of "blocked phone numbers" which we successfully synced.
 | 
						|
            [self saveSyncedBlockedPhoneNumbers:blockedPhoneNumbers];
 | 
						|
        }
 | 
						|
        failure:^(NSError *error) {
 | 
						|
            DDLogError(@"%@ Failed to send blocked phone numbers sync message with error: %@", self.tag, error);
 | 
						|
        }];
 | 
						|
}
 | 
						|
 | 
						|
- (void)saveSyncedBlockedPhoneNumbers:(NSArray<NSString *> *)blockedPhoneNumbers
 | 
						|
{
 | 
						|
    OWSAssert(blockedPhoneNumbers);
 | 
						|
 | 
						|
    // Record the last set of "blocked phone numbers" which we successfully synced.
 | 
						|
    [_storageManager setObject:blockedPhoneNumbers
 | 
						|
                        forKey:kOWSBlockingManager_SyncedBlockedPhoneNumbersKey
 | 
						|
                  inCollection:kOWSBlockingManager_BlockedPhoneNumbersCollection];
 | 
						|
}
 | 
						|
 | 
						|
#pragma mark - Notifications
 | 
						|
 | 
						|
- (void)applicationDidBecomeActive:(NSNotification *)notification
 | 
						|
{
 | 
						|
    OWSAssert([NSThread isMainThread]);
 | 
						|
 | 
						|
    @synchronized(self)
 | 
						|
    {
 | 
						|
        [self syncBlockedPhoneNumbersIfNecessary];
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#pragma mark - Logging
 | 
						|
 | 
						|
+ (NSString *)tag
 | 
						|
{
 | 
						|
    return [NSString stringWithFormat:@"[%@]", self.class];
 | 
						|
}
 | 
						|
 | 
						|
- (NSString *)tag
 | 
						|
{
 | 
						|
    return self.class.tag;
 | 
						|
}
 | 
						|
 | 
						|
@end
 | 
						|
 | 
						|
NS_ASSUME_NONNULL_END
 |