Antidote/local_pod_repo/objcTox/Classes/Private/Manager/Submanagers/OCTSubmanagerChatsImpl.m

511 lines
22 KiB
Objective-C

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#import "OCTSubmanagerChatsImpl.h"
#import "OCTTox.h"
#import "OCTRealmManager.h"
#import "OCTMessageAbstract.h"
#import "OCTMessageText.h"
#import "OCTChat.h"
#import "OCTLogging.h"
#import "OCTSendMessageOperation.h"
#import "OCTTox+Private.h"
#import "OCTToxOptions+Private.h"
//#import "Firebase.h"
@interface OCTSubmanagerChatsImpl ()
@property (strong, nonatomic, readonly) NSOperationQueue *sendMessageQueue;
@end
@implementation OCTSubmanagerChatsImpl
@synthesize dataSource = _dataSource;
- (instancetype)init
{
self = [super init];
if (! self) {
return nil;
}
_sendMessageQueue = [NSOperationQueue new];
_sendMessageQueue.maxConcurrentOperationCount = 1;
return self;
}
- (void)dealloc
{
[self.dataSource.managerGetNotificationCenter removeObserver:self];
}
- (void)configure
{
[self.dataSource.managerGetNotificationCenter addObserver:self
selector:@selector(friendConnectionStatusChangeNotification:)
name:kOCTFriendConnectionStatusChangeNotification
object:nil];
}
#pragma mark - Public
- (OCTChat *)getOrCreateChatWithFriend:(OCTFriend *)friend
{
return [[self.dataSource managerGetRealmManager] getOrCreateChatWithFriend:friend];
}
- (void)removeMessages:(NSArray<OCTMessageAbstract *> *)messages
{
[[self.dataSource managerGetRealmManager] removeMessages:messages];
[self.dataSource.managerGetNotificationCenter postNotificationName:kOCTScheduleFileTransferCleanupNotification object:nil];
}
- (void)removeAllMessagesInChat:(OCTChat *)chat removeChat:(BOOL)removeChat
{
[[self.dataSource managerGetRealmManager] removeAllMessagesInChat:chat removeChat:removeChat];
[self.dataSource.managerGetNotificationCenter postNotificationName:kOCTScheduleFileTransferCleanupNotification object:nil];
}
- (void)sendOwnPush
{
}
- (void)sendMessagePushToChat:(OCTChat *)chat
{
NSParameterAssert(chat);
NSLog(@"PUSH:sendMessagePushToChat");
__weak OCTSubmanagerChatsImpl *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager];
OCTFriend *friend = [chat.friends firstObject];
__block NSString *friend_pushToken = friend.pushToken;
if (friend_pushToken == nil)
{
NSLog(@"sendMessagePushToChat:Friend has No Pushtoken");
}
else
{
// HINT: only select outgoing messages (senderUniqueIdentifier == NULL)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.isDelivered == 0 AND messageText.sentPush == 0 AND senderUniqueIdentifier == nil", chat.uniqueIdentifier];
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
OCTMessageAbstract *message_found = [results firstObject];
if (message_found) {
[realmManager updateObject:message_found withBlock:^(OCTMessageAbstract *theMessage) {
theMessage.messageText.sentPush = YES;
}];
triggerPush(friend_pushToken, message_found.messageText.msgv3HashHex, strongSelf, chat);
}
}
});
}
triggerPush(NSString *used_pushToken,
NSString *msgv3HashHex,
OCTSubmanagerChatsImpl *strongSelf,
OCTChat *chat)
{
// HINT: call push token (URL) here
// best in a background thread
//
NSLog(@"PUSH:triggerPush");
if ((used_pushToken != nil) && (used_pushToken.length > 5)) {
// check push url starts with allowed values
if (
([used_pushToken hasPrefix:@"https://tox.zoff.xyz/toxfcm/fcm.php?id="])
||
([used_pushToken hasPrefix:@"https://gotify1.unifiedpush.org/UP?token="])
||
([used_pushToken hasPrefix:@"https://ntfy.sh/"])
) {
NSString *strong_pushToken = used_pushToken;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// NSString *strong_pushToken = weak_pushToken;
int PUSH_URL_TRIGGER_AGAIN_MAX_COUNT = 8;
int PUSH_URL_TRIGGER_AGAIN_SECONDS = 21;
for (int i=0; i<(PUSH_URL_TRIGGER_AGAIN_MAX_COUNT + 1); i++)
{
if (chat == nil) {
__block UIApplicationState as = UIApplicationStateBackground;
dispatch_sync(dispatch_get_main_queue(), ^{
as =[[UIApplication sharedApplication] applicationState];
});
if (as == UIApplicationStateActive) {
NSLog(@"PUSH:fg->break:1");
break;
}
}
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:strong_pushToken]];
NSString *userUpdate = [NSString stringWithFormat:@"&text=1", nil];
[urlRequest setHTTPMethod:@"POST"];
NSData *data1 = [userUpdate dataUsingEncoding:NSUTF8StringEncoding];
[urlRequest setHTTPBody:data1];
[urlRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[urlRequest setTimeoutInterval:10]; // HINT: 10 seconds
NSString *userAgent = @"Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0";
[urlRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"];
[urlRequest setValue:@"no-cache, no-store, must-revalidate" forHTTPHeaderField:@"Cache-Control"];
[urlRequest setValue:@"no-cache" forHTTPHeaderField:@"Pragma"];
[urlRequest setValue:@"0" forHTTPHeaderField:@"Expires"];
// NSLog(@"PUSH:for msgv3HashHex=%@", msgv3HashHex);
// NSLog(@"PUSH:for friend.pushToken=%@", strong_pushToken);
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ((httpResponse.statusCode < 300) && (httpResponse.statusCode > 199)) {
NSLog(@"calling PUSH URL:CALL:SUCCESS");
}
else {
NSLog(@"calling PUSH URL:-ERROR:01-");
}
}];
NSLog(@"calling PUSH URL:CALL:start");
[dataTask resume];
if (i < PUSH_URL_TRIGGER_AGAIN_MAX_COUNT)
{
NSLog(@"calling PUSH URL:WAIT:start");
[NSThread sleepForTimeInterval:PUSH_URL_TRIGGER_AGAIN_SECONDS];
NSLog(@"calling PUSH URL:WAIT:done");
}
if (chat == nil) {
__block UIApplicationState as = UIApplicationStateBackground;
dispatch_sync(dispatch_get_main_queue(), ^{
as =[[UIApplication sharedApplication] applicationState];
});
if (as == UIApplicationStateActive) {
NSLog(@"PUSH:fg->break:2");
break;
}
}
if (msgv3HashHex != nil)
{
OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager];
__block BOOL msgIsDelivered = NO;
NSLog(@"calling PUSH URL:DB check:start");
dispatch_sync(dispatch_get_main_queue(), ^{
// HINT: only select outgoing messages (senderUniqueIdentifier == NULL)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier == nil",
chat.uniqueIdentifier, msgv3HashHex];
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
OCTMessageAbstract *message_found = [results firstObject];
if (message_found) {
if (message_found.messageText) {
msgIsDelivered = message_found.messageText.isDelivered;
}
}
NSLog(@"calling PUSH URL:DB check:end_real");
});
NSLog(@"calling PUSH URL:DB check:end");
if (msgIsDelivered == YES) {
// OCTLogInfo(@"PUSH:for msgv3HashHex isDelivered=YES");
NSLog(@"PUSH:for msgv3HashHex isDelivered=YES");
break;
}
}
}
});
}
else {
NSLog(@"unsafe PUSH URL not allowed:-ERROR:02-");
}
}
}
- (void)sendMessageToChat:(OCTChat *)chat
text:(NSString *)text
type:(OCTToxMessageType)type
successBlock:(void (^)(OCTMessageAbstract *message))userSuccessBlock
failureBlock:(void (^)(NSError *error))userFailureBlock
{
NSParameterAssert(chat);
NSParameterAssert(text);
OCTFriend *friend = [chat.friends firstObject];
uint8_t *message_v3_hash_bin = calloc(1, TOX_MSGV3_MSGID_LENGTH);
uint8_t *message_v3_hash_hexstr = calloc(1, (TOX_MSGV3_MSGID_LENGTH * 2) + 1);
NSString *msgv3HashHex = nil;
UInt32 msgv3tssec = 0;
// if ((message_v3_hash_bin) && (message_v3_hash_hexstr))
// {
// tox_messagev3_get_new_message_id(message_v3_hash_bin);
// bin_to_hex((const char *)message_v3_hash_bin, (size_t)TOX_MSGV3_MSGID_LENGTH, message_v3_hash_hexstr);
//
// msgv3HashHex = [[NSString alloc] initWithBytes:message_v3_hash_hexstr length:(TOX_MSGV3_MSGID_LENGTH * 2) encoding:NSUTF8StringEncoding];
//
// // HINT: set sent timestamp to now() as unixtimestamp value
// msgv3tssec = [[NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970]] integerValue];
//
// free(message_v3_hash_bin);
// free(message_v3_hash_hexstr);
// }
__weak OCTSubmanagerChatsImpl *weakSelf = self;
OCTSendMessageOperationSuccessBlock successBlock = ^(OCTToxMessageId messageId) {
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
BOOL sent_push = NO;
if (messageId == -1) {
if ((friend.pushToken != nil) && (friend.pushToken.length > 5)) {
// check push url starts with allowed values
if (
([friend.pushToken hasPrefix:@"https://tox.zoff.xyz/toxfcm/fcm.php?id="])
||
([friend.pushToken hasPrefix:@"https://gotify1.unifiedpush.org/UP?token="])
||
([friend.pushToken hasPrefix:@"https://ntfy.sh/"])
) {
sent_push = YES;
}
}
}
OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager];
OCTMessageAbstract *message = [realmManager addMessageWithText:text type:type chat:chat sender:nil messageId:messageId msgv3HashHex:msgv3HashHex sentPush:sent_push tssent:msgv3tssec tsrcvd:0];
if (userSuccessBlock) {
userSuccessBlock(message);
}
};
OCTSendMessageOperationFailureBlock failureBlock = ^(NSError *error) {
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
if ((error.code == OCTToxErrorFriendSendMessageFriendNotConnected) &&
[strongSelf.dataSource managerUseFauxOfflineMessaging]) {
NSString *friend_pushToken = friend.pushToken;
triggerPush(friend_pushToken, msgv3HashHex, strongSelf, chat);
successBlock(-1);
return;
}
if (userFailureBlock) {
userFailureBlock(error);
}
};
OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox]
friendNumber:friend.friendNumber
messageType:type
message:text
msgv3HashHex:msgv3HashHex
msgv3tssec:msgv3tssec
successBlock:successBlock
failureBlock:failureBlock];
[self.sendMessageQueue addOperation:operation];
}
- (BOOL)setIsTyping:(BOOL)isTyping inChat:(OCTChat *)chat error:(NSError **)error
{
NSParameterAssert(chat);
OCTFriend *friend = [chat.friends firstObject];
OCTTox *tox = [self.dataSource managerGetTox];
return [tox setUserIsTyping:isTyping forFriendNumber:friend.friendNumber error:error];
}
#pragma mark - NSNotification
- (void)friendConnectionStatusChangeNotification:(NSNotification *)notification
{
OCTFriend *friend = notification.object;
if (! friend) {
OCTLogWarn(@"no friend received in notification %@, exiting", notification);
return;
}
if (friend.isConnected) {
[self resendUndeliveredMessagesToFriend:friend];
}
}
#pragma mark - Private
- (void)resendUndeliveredMessagesToFriend:(OCTFriend *)friend
{
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@"
@" AND senderUniqueIdentifier == nil"
@" AND messageText.isDelivered == NO",
chat.uniqueIdentifier];
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
for (OCTMessageAbstract *message in results) {
OCTLogInfo(@"Resending message to friend %@", friend);
__weak OCTSubmanagerChatsImpl *weakSelf = self;
OCTSendMessageOperationSuccessBlock successBlock = ^(OCTToxMessageId messageId) {
__strong OCTSubmanagerChatsImpl *strongSelf = weakSelf;
OCTRealmManager *realmManager = [strongSelf.dataSource managerGetRealmManager];
[realmManager updateObject:message withBlock:^(OCTMessageAbstract *theMessage) {
theMessage.messageText.messageId = messageId;
}];
};
OCTSendMessageOperationFailureBlock failureBlock = ^(NSError *error) {
OCTLogWarn(@"Cannot resend message to friend %@, error %@", friend, error);
};
OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox]
friendNumber:friend.friendNumber
messageType:message.messageText.type
message:message.messageText.text
msgv3HashHex:message.messageText.msgv3HashHex
msgv3tssec:message.tssent
successBlock:successBlock
failureBlock:failureBlock];
[self.sendMessageQueue addOperation:operation];
}
}
#pragma mark - OCTToxDelegate
/*
* send mesgV3 high level ACK message.
*/
- (void)tox:(OCTTox *)tox sendFriendHighlevelACK:(NSString *)message
friendNumber:(OCTToxFriendNumber)friendNumber
msgv3HashHex:(NSString *)msgv3HashHex
sendTimestamp:(uint32_t)sendTimestamp
{
OCTSendMessageOperation *operation = [[OCTSendMessageOperation alloc] initWithTox:[self.dataSource managerGetTox]
friendNumber:friendNumber
messageType:OCTToxMessageTypeHighlevelack
message:message
msgv3HashHex:msgv3HashHex
msgv3tssec:sendTimestamp
successBlock:nil
failureBlock:nil];
[self.sendMessageQueue addOperation:operation];
}
/*
* Process incoming text message from friend.
*/
- (void)tox:(OCTTox *)tox friendMessage:(NSString *)message
type:(OCTToxMessageType)type
friendNumber:(OCTToxFriendNumber)friendNumber
msgv3HashHex:(NSString *)msgv3HashHex
sendTimestamp:(uint32_t)sendTimestamp
{
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
if (msgv3HashHex != nil)
{
// HINT: check for double message, but only select incoming messages (senderUniqueIdentifier != NULL)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier != nil",
chat.uniqueIdentifier, msgv3HashHex];
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
OCTMessageAbstract *message_found = [results firstObject];
if (message_found) {
OCTLogInfo(@"friendMessage ignoring double message i %@", chat.uniqueIdentifier);
OCTLogInfo(@"friendMessage ignoring double message f %@", friend);
return;
}
}
[realmManager addMessageWithText:message type:type chat:chat sender:friend messageId:0 msgv3HashHex:msgv3HashHex sentPush:NO tssent:sendTimestamp tsrcvd:0];
}
- (void)tox:(OCTTox *)tox friendHighLevelACK:(NSString *)message
friendNumber:(OCTToxFriendNumber)friendNumber
msgv3HashHex:(NSString *)msgv3HashHex
sendTimestamp:(uint32_t)sendTimestamp
{
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
// HINT: only select outgoing messages
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.msgv3HashHex == %@ AND senderUniqueIdentifier == nil",
chat.uniqueIdentifier, msgv3HashHex];
// HINT: we still sort and use only 1 result row, just in case more than 1 row is returned.
// but if more than 1 row is returned that would actually be an error.
// we use the newest message with this Hash
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
results = [results sortedResultsUsingKeyPath:@"dateInterval" ascending:YES];
OCTMessageAbstract *message_found = [results firstObject];
if (! message_found) {
return;
}
OCTLogInfo(@"friendHighLevelACK recevied from friend %@", friend);
[realmManager updateObject:message_found withBlock:^(OCTMessageAbstract *theMessage) {
theMessage.messageText.isDelivered = YES;
}];
}
- (void)tox:(OCTTox *)tox messageDelivered:(OCTToxMessageId)messageId friendNumber:(OCTToxFriendNumber)friendNumber
{
OCTRealmManager *realmManager = [self.dataSource managerGetRealmManager];
NSString *publicKey = [[self.dataSource managerGetTox] publicKeyFromFriendNumber:friendNumber error:nil];
OCTFriend *friend = [realmManager friendWithPublicKey:publicKey];
OCTChat *chat = [realmManager getOrCreateChatWithFriend:friend];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"chatUniqueIdentifier == %@ AND messageText.messageId == %d",
chat.uniqueIdentifier, messageId];
// messageId is reset on every launch, so we want to update delivered status on latest message.
RLMResults *results = [realmManager objectsWithClass:[OCTMessageAbstract class] predicate:predicate];
results = [results sortedResultsUsingKeyPath:@"dateInterval" ascending:NO];
OCTMessageAbstract *message = [results firstObject];
if (! message) {
return;
}
[realmManager updateObject:message withBlock:^(OCTMessageAbstract *theMessage) {
theMessage.messageText.isDelivered = YES;
}];
}
@end