Statistics
| Branch: | Tag: | Revision:

chatsecureios / ChatSecure / Classes / Controllers / XMPP / OTRXMPPManager.m @ 8358d691

History | View | Annotate | Download (60.5 KB)

1
//
2
//  OTRXMPPManager.m
3
//  Off the Record
4
//
5
//  Created by Chris Ballinger on 9/7/11.
6
//  Copyright (c) 2011 Chris Ballinger. All rights reserved.
7
//
8
//  This file is part of ChatSecure.
9
//
10
//  ChatSecure is free software: you can redistribute it and/or modify
11
//  it under the terms of the GNU General Public License as published by
12
//  the Free Software Foundation, either version 3 of the License, or
13
//  (at your option) any later version.
14
//
15
//  ChatSecure is distributed in the hope that it will be useful,
16
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
//  GNU General Public License for more details.
19
//
20
//  You should have received a copy of the GNU General Public License
21
//  along with ChatSecure.  If not, see <http://www.gnu.org/licenses/>.
22

    
23
#import "OTRXMPPManager.h"
24

    
25
@import CocoaAsyncSocket;
26
@import XMPPFramework;
27
#import "OTRYapDatabaseRosterStorage.h"
28

    
29
#import "OTRLog.h"
30

    
31
#import <CFNetwork/CFNetwork.h>
32

    
33
#import "OTRSettingsManager.h"
34
#import "OTRConstants.h"
35
#import "OTRProtocolManager.h"
36
#include <stdlib.h>
37
#import "OTRConstants.h"
38
#import "OTRUtilities.h"
39

    
40
#import "OTRDatabaseManager.h"
41
@import YapDatabase;
42
#import "OTRXMPPBuddy.h"
43
#import "OTRXMPPAccount.h"
44
#import "OTRIncomingMessage.h"
45
#import "OTROutgoingMessage.h"
46
#import "OTRAccount.h"
47
#import "OTRXMPPPresenceSubscriptionRequest.h"
48
#import "OTRvCardYapDatabaseStorage.h"
49
#import "OTRNotificationController.h"
50
#import "OTRStreamManagementYapStorage.h"
51
@import OTRKit;
52
#import "OTRXMPPRoomManager.h"
53
#import "OTRXMPPBuddyTimers.h"
54
#import "OTRXMPPError.h"
55
#import "OTRXMPPManager_Private.h"
56
#import "OTRBuddyCache.h"
57
#import "UIImage+ChatSecure.h"
58
#import "XMPPPushModule.h"
59
#import "OTRXMPPTorManager.h"
60
#import "OTRTorManager.h"
61
@import OTRAssets;
62

    
63
NSString *const OTRXMPPRegisterSucceededNotificationName = @"OTRXMPPRegisterSucceededNotificationName";
64
NSString *const OTRXMPPRegisterFailedNotificationName    = @"OTRXMPPRegisterFailedNotificationName";
65

    
66
NSTimeInterval const kOTRChatStatePausedTimeout   = 5;
67
NSTimeInterval const kOTRChatStateInactiveTimeout = 120;
68

    
69
NSString *const OTRXMPPLoginStatusNotificationName = @"OTRXMPPLoginStatusNotificationName";
70

    
71
NSString *const OTRXMPPOldLoginStatusKey = @"OTRXMPPOldLoginStatusKey";
72
NSString *const OTRXMPPNewLoginStatusKey = @"OTRXMPPNewLoginStatusKey";
73
NSString *const OTRXMPPLoginErrorKey = @"OTRXMPPLoginErrorKey";
74

    
75
/** For XEP-0352 CSI Client State Indication */
76
typedef NS_ENUM(NSInteger, XMPPClientState) {
77
    XMPPClientStateInactive,
78
    XMPPClientStateActive,
79
};
80

    
81
@implementation OTRXMPPManager
82

    
83
- (instancetype)init
84
{
85
    if (self = [super init]) {
86
        NSString * queueLabel = [NSString stringWithFormat:@"%@.work.%@",[self class],self];
87
        _workQueue = dispatch_queue_create([queueLabel UTF8String], 0);
88
        self.connectionStatus = OTRProtocolConnectionStatusDisconnected;
89
        _buddyTimers = [NSMutableDictionary dictionary];
90
        _databaseConnection = [OTRDatabaseManager sharedInstance].readWriteDatabaseConnection;
91
    }
92
    return self;
93
}
94

    
95
- (instancetype) initWithAccount:(OTRAccount *)newAccount {
96
    if(self = [self init])
97
    {
98
        NSAssert([newAccount isKindOfClass:[OTRXMPPAccount class]], @"Must have XMPP account");
99
        self.isRegisteringNewAccount = NO;
100
        _account = (OTRXMPPAccount *)newAccount;
101
        
102
        // Setup the XMPP stream
103
        [self setupStream];        
104
    }
105
    
106
    return self;
107
}
108

    
109
- (void)dealloc
110
{
111
	[self teardownStream];
112
}
113

    
114
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
115
#pragma mark Private
116
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
117

    
118
- (OTRXMPPStream*) newStream {
119
    return [[OTRXMPPStream alloc] init];
120
}
121

    
122
- (void)setupStream
123
{
124
	NSAssert(_xmppStream == nil, @"Method setupStream invoked multiple times");
125
    
126
	_xmppStream = [self newStream];
127

    
128
    //Used to fetch correct account from XMPPStream in delegate methods especailly
129
    self.xmppStream.tag = self.account.uniqueId;
130
    self.xmppStream.startTLSPolicy = XMPPStreamStartTLSPolicyRequired;
131
    
132
    [self.certificatePinningModule activate:self.xmppStream];
133
    
134
    _deliveryReceipts = [[XMPPMessageDeliveryReceipts alloc] init];
135
    // We want to check if OTR messages can be decrypted
136
    self.deliveryReceipts.autoSendMessageDeliveryReceipts = NO;
137
    self.deliveryReceipts.autoSendMessageDeliveryRequests = YES;
138
    [self.deliveryReceipts activate:self.xmppStream];
139
	
140
	// Setup reconnect
141
	// 
142
	// The XMPPReconnect module monitors for "accidental disconnections" and
143
	// automatically reconnects the stream for you.
144
	// There's a bunch more information in the XMPPReconnect header file.
145
	
146
	_xmppReconnect = [[XMPPReconnect alloc] init];
147
	
148
	// Setup roster
149
	// 
150
	// The XMPPRoster handles the xmpp protocol stuff related to the roster.
151
	// The storage for the roster is abstracted.
152
	// So you can use any storage mechanism you want.
153
	// You can store it all in memory, or use core data and store it on disk, or use core data with an in-memory store,
154
	// or setup your own using raw SQLite, or create your own storage mechanism.
155
	// You can do it however you like! It's your application.
156
	// But you do need to provide the roster with some storage facility.
157
    
158
    //DDLogInfo(@"Unique Identifier: %@",self.account.uniqueIdentifier);
159
	
160
    _xmppRosterStorage = [[OTRYapDatabaseRosterStorage alloc] init];
161
	
162
	_xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:self.xmppRosterStorage];
163
	
164
	self.xmppRoster.autoFetchRoster = YES;
165
    self.xmppRoster.autoClearAllUsersAndResources = NO;
166
	self.xmppRoster.autoAcceptKnownPresenceSubscriptionRequests = YES;
167
	
168
	// Setup vCard support
169
	// 
170
	// The vCard Avatar module works in conjuction with the standard vCard Temp module to download user avatars.
171
	// The XMPPRoster will automatically integrate with XMPPvCardAvatarModule to cache roster photos in the roster.
172
	
173
    OTRvCardYapDatabaseStorage * vCardStorage  = [[OTRvCardYapDatabaseStorage alloc] init];
174
	_xmppvCardTempModule = [[XMPPvCardTempModule alloc] initWithvCardStorage:vCardStorage];
175
	
176
	_xmppvCardAvatarModule = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:self.xmppvCardTempModule];
177
	
178
	// Setup capabilities
179
	// 
180
	// The XMPPCapabilities module handles all the complex hashing of the caps protocol (XEP-0115).
181
	// Basically, when other clients broadcast their presence on the network
182
	// they include information about what capabilities their client supports (audio, video, file transfer, etc).
183
	// But as you can imagine, this list starts to get pretty big.
184
	// This is where the hashing stuff comes into play.
185
	// Most people running the same version of the same client are going to have the same list of capabilities.
186
	// So the protocol defines a standardized way to hash the list of capabilities.
187
	// Clients then broadcast the tiny hash instead of the big list.
188
	// The XMPPCapabilities protocol automatically handles figuring out what these hashes mean,
189
	// and also persistently storing the hashes so lookups aren't needed in the future.
190
	// 
191
	// Similarly to the roster, the storage of the module is abstracted.
192
	// You are strongly encouraged to persist caps information across sessions.
193
	// 
194
	// The XMPPCapabilitiesCoreDataStorage is an ideal solution.
195
	// It can also be shared amongst multiple streams to further reduce hash lookups.
196
    
197
    _serverCapabilities = [[OTRServerCapabilities alloc] init];
198
    [self.serverCapabilities activate:self.xmppStream];
199
    
200
    // Add push registration module
201
    _xmppPushModule = [[XMPPPushModule alloc] init];
202
    [self.xmppPushModule activate:self.xmppStream];
203
    [self.xmppPushModule addDelegate:self delegateQueue:self.workQueue];
204
    
205
    _xmppCapabilitiesStorage = [[XMPPCapabilitiesCoreDataStorage alloc] initWithInMemoryStore];
206
    _xmppCapabilities = [[XMPPCapabilities alloc] initWithCapabilitiesStorage:self.xmppCapabilitiesStorage];
207
    
208
    self.xmppCapabilities.autoFetchHashedCapabilities = YES;
209
    self.xmppCapabilities.autoFetchNonHashedCapabilities = YES;
210
    self.xmppCapabilities.autoFetchMyServerCapabilities = YES;
211
    
212
	// Activate xmpp modules
213
    
214
	[self.xmppReconnect         activate:self.xmppStream];
215
	[self.xmppRoster            activate:self.xmppStream];
216
	[self.xmppvCardTempModule   activate:self.xmppStream];
217
	[self.xmppvCardAvatarModule activate:self.xmppStream];
218
	[self.xmppCapabilities      activate:self.xmppStream];
219
    
220
    _stanzaIdModule = [[XMPPStanzaIdModule alloc] init];
221
    [self.stanzaIdModule activate:self.xmppStream];
222
    
223
	// Add ourself as a delegate to anything we may be interested in
224
    
225
	[self.xmppStream addDelegate:self delegateQueue:self.workQueue];
226
	[self.xmppRoster addDelegate:self delegateQueue:self.workQueue];
227
    [self.xmppCapabilities addDelegate:self delegateQueue:self.workQueue];
228
    [self.xmppvCardTempModule addDelegate:self delegateQueue:self.workQueue];
229
    
230
    // File Transfer
231
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
232
    if ([self isKindOfClass:[OTRXMPPTorManager class]]) {
233
        CPAProxyManager *tor = [OTRTorManager sharedInstance].torManager;
234
        NSString *proxyHost = tor.SOCKSHost;
235
        NSUInteger proxyPort = tor.SOCKSPort;
236
        NSDictionary *proxyDict = @{
237
                                    (NSString *)kCFStreamPropertySOCKSProxyHost : proxyHost,
238
                                    (NSString *)kCFStreamPropertySOCKSProxyPort : @(proxyPort)
239
                                    };
240
        sessionConfiguration.connectionProxyDictionary = proxyDict;
241
    }
242
    _fileTransferManager = [[FileTransferManager alloc] initWithConnection:self.databaseConnection serverCapabilities:self.serverCapabilities sessionConfiguration:sessionConfiguration];
243
    
244
    // Message storage
245
    _messageStorage = [[MessageStorage alloc] initWithConnection:self.databaseConnection capabilities:self.xmppCapabilities fileTransfer:self.fileTransferManager dispatchQueue:nil];
246
    [self.messageStorage activate:self.xmppStream];
247
    
248
    //Stream Management
249
    _streamManagementDelegate = [[OTRStreamManagementDelegate alloc] initWithDatabaseConnection:self.databaseConnection];
250
    
251
    //OTRStreamManagementYapStorage *streamManagementStorage = [[OTRStreamManagementYapStorage alloc] initWithDatabaseConnection:self.databaseConnection];
252
    XMPPStreamManagementMemoryStorage *memoryStorage = [[XMPPStreamManagementMemoryStorage alloc] init];
253
    _streamManagement = [[XMPPStreamManagement alloc] initWithStorage:memoryStorage];
254
    [self.streamManagement addDelegate:self.streamManagementDelegate delegateQueue:self.workQueue];
255
    [self.streamManagement automaticallyRequestAcksAfterStanzaCount:10 orTimeout:5];
256
    [self.streamManagement automaticallySendAcksAfterStanzaCount:30 orTimeout:5];
257
    self.streamManagement.autoResume = YES;
258
    [self.streamManagement activate:self.xmppStream];
259
    
260
    //MUC
261
    _roomManager = [[OTRXMPPRoomManager alloc] initWithDatabaseConnection:[OTRDatabaseManager sharedInstance].readWriteDatabaseConnection capabilities:self.xmppCapabilities dispatchQueue:nil];
262
    [self.roomManager activate:self.xmppStream];
263
    
264
    //Buddy Manager (for deleting)
265
    _xmppBuddyManager = [[OTRXMPPBuddyManager alloc] init];
266
    self.xmppBuddyManager.databaseConnection = [OTRDatabaseManager sharedInstance].longLivedReadOnlyConnection;
267
    self.xmppBuddyManager.protocol = self;
268
    [self.xmppBuddyManager activate:self.xmppStream];
269
    
270
    //Message Queue Module
271
    MessageQueueHandler *queueHandler = [OTRDatabaseManager sharedInstance].messageQueueHandler;
272
    _messageStatusModule = [[OTRXMPPMessageStatusModule alloc] initWithDatabaseConnection:self.databaseConnection delegate:queueHandler];
273
    [self.messageStatusModule activate:self.xmppStream];
274
    
275
    //OMEMO
276
    if ([[OTRAppDelegate appDelegate].theme enableOMEMO]) {
277
        self.omemoSignalCoordinator = [[OTROMEMOSignalCoordinator alloc] initWithAccountYapKey:self.account.uniqueId databaseConnection:self.databaseConnection messageStorage:self.messageStorage error:nil];
278
        _omemoModule = [[OMEMOModule alloc] initWithOMEMOStorage:self.omemoSignalCoordinator xmlNamespace:OMEMOModuleNamespaceConversationsLegacy];
279
        [self.omemoModule addDelegate:self.omemoSignalCoordinator delegateQueue:self.workQueue];
280
        [self.omemoModule activate:self.xmppStream];
281
    }
282
    
283
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pushAccountChanged:) name:OTRPushAccountDeviceChanged object:[OTRProtocolManager sharedInstance].pushController];
284
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pushAccountChanged:) name:OTRPushAccountTokensChanged object:[OTRProtocolManager sharedInstance].pushController];
285
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(buddyPendingApprovalStateChanged:) name:OTRBuddyPendingApprovalDidChangeNotification object:self.xmppRosterStorage];
286
    
287
    _serverCheck = [[ServerCheck alloc] initWithXmpp:self push:[OTRProtocolManager sharedInstance].pushController];
288
}
289

    
290
- (void)teardownStream
291
{
292
    [[NSNotificationCenter defaultCenter] removeObserver:self name:OTRBuddyPendingApprovalDidChangeNotification object:self.xmppRosterStorage];
293

    
294
    [_xmppStream removeDelegate:self];
295
    [_xmppRoster removeDelegate:self];
296
    [_xmppCapabilities removeDelegate:self];
297
    [_xmppvCardTempModule removeDelegate:self];
298
    [_xmppPushModule removeDelegate:self];
299

    
300
    [_xmppPushModule deactivate];
301
    [_xmppReconnect         deactivate];
302
    [_xmppRoster            deactivate];
303
    [_xmppvCardTempModule   deactivate];
304
    [_xmppvCardAvatarModule deactivate];
305
    [_xmppCapabilities      deactivate];
306
    [_streamManagement      deactivate];
307
    [_messageStorage        deactivate];
308
    [_certificatePinningModule deactivate];
309
    [_deliveryReceipts deactivate];
310
    [_streamManagement deactivate];
311
    [_roomManager deactivate];
312
    [_xmppBuddyManager deactivate];
313
    [_messageStatusModule deactivate];
314
    [_omemoModule deactivate];
315
    [_serverCapabilities deactivate];
316
    _serverCheck = nil;
317
    _fileTransferManager = nil;
318

    
319
    [_xmppStream disconnect];
320
}
321

    
322
- (void) addIdleDate:(NSDate*)date toPresence:(XMPPPresence*)presence {
323
    // Don't leak any extra info over Tor or non-autologin accounts
324
    if (self.account.accountType == OTRAccountTypeXMPPTor ||
325
        !self.account.autologin) {
326
        return;
327
    }
328
    NSString *nowString = [date xmppDateTimeString];
329
    if (nowString) {
330
        /*
331
         <presence from='juliet@capulet.com/balcony'>
332
         <show>away</show>
333
         <idle xmlns='urn:xmpp:idle:1' since='1969-07-21T02:56:15Z'/>
334
         </presence>
335
         */
336
        // https://xmpp.org/extensions/xep-0319.html
337
        NSXMLElement *idle = [NSXMLElement elementWithName:@"idle" xmlns:@"urn:xmpp:idle:1"];
338
        [idle addAttributeWithName:@"since" stringValue:nowString];
339
        [presence addChild:idle];
340
    }
341
}
342

    
343
/** Sends "away" presence with last idle time */
344
- (void) goAway {
345
    // Don't leak any extra info over Tor
346
    if (self.account.accountType == OTRAccountTypeXMPPTor) {
347
        return;
348
    }
349
    XMPPPresence *presence = [XMPPPresence presence];
350
    NSXMLElement *show = [NSXMLElement elementWithName:@"show" stringValue:@"away"];
351
    [presence addChild:show];
352
    NSDate *idleDate = [OTRProtocolManager sharedInstance].lastInteractionDate;
353
    [self addIdleDate:idleDate toPresence:presence];
354
    [self.xmppStream sendElement:presence];
355
    
356
    [self updateClientState:XMPPClientStateInactive];
357
}
358

    
359
- (void)goActive {
360
    XMPPPresence *presence = [XMPPPresence presence]; // type="available" is implicit
361
    [self.xmppStream sendElement:presence];
362
    
363
    [self updateClientState:XMPPClientStateActive];
364
}
365

    
366
/** This will choose "active" or "away" based on UIApplication applicationState */
367
- (void)goOnline
368
{
369
    dispatch_async(dispatch_get_main_queue(), ^{
370
        self->_lastConnectionError = nil;
371
        if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
372
            [self goActive];
373
        } else {
374
            [self goAway];
375
        }
376
    });
377
}
378

    
379
- (void)goOffline
380
{
381
	XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
382
    NSDate *idleDate = [OTRProtocolManager sharedInstance].lastInteractionDate;
383
    [self addIdleDate:idleDate toPresence:presence]; // I don't think this does anything
384
	[self.xmppStream sendElement:presence];
385
    
386
    [self updateClientState:XMPPClientStateInactive];
387
}
388

    
389

    
390

    
391
/** XEP-0352 Client State Indication */
392
- (void) updateClientState:(XMPPClientState)clientState {
393
    NSXMLElement *csiFeature = [self.serverCapabilities.streamFeatures elementForName:@"csi" xmlns:@"urn:xmpp:csi:0"];
394
    if (!csiFeature) {
395
        return;
396
    }
397
    
398
    NSXMLElement *csi = nil;
399
    switch (clientState) {
400
        case XMPPClientStateActive:
401
            csi = [NSXMLElement indicateActiveElement];
402
            break;
403
        case XMPPClientStateInactive:
404
            csi = [NSXMLElement indicateInactiveElement];
405
            break;
406
    }
407
    [self.xmppStream sendElement:csi];
408
}
409

    
410
- (NSString *)accountDomainWithError:(id)error;
411
{
412
    return self.account.domain;
413
}
414

    
415
- (void)didRegisterNewAccountWithStream:(XMPPStream *)stream
416
{
417
    self.isRegisteringNewAccount = NO;
418
    [self authenticateWithStream:stream];
419
    [[NSNotificationCenter defaultCenter] postNotificationName:OTRXMPPRegisterSucceededNotificationName object:self];
420
}
421
- (void)failedToRegisterNewAccount:(NSError *)error
422
{
423
    if (error) {
424
        [[NSNotificationCenter defaultCenter]
425
         postNotificationName:OTRXMPPRegisterFailedNotificationName object:self userInfo:@{kOTRNotificationErrorKey:error}];
426
    }
427
    else {
428
        [[NSNotificationCenter defaultCenter]
429
         postNotificationName:OTRXMPPRegisterFailedNotificationName object:self];
430
    }
431
}
432

    
433

    
434
- (void)authenticateWithStream:(XMPPStream *)stream {
435
    NSError * error = nil;
436
    BOOL status = YES;
437
    if ([stream supportsXOAuth2GoogleAuthentication] && self.account.accountType == OTRAccountTypeGoogleTalk) {
438
        status = [stream authenticateWithGoogleAccessToken:self.account.password error:&error];
439
    }
440
    else {
441
        status = [stream authenticateWithPassword:self.account.password error:&error];
442
    }
443
}
444

    
445
///////////////////////////////
446
#pragma mark Capabilities Collected
447
////////////////////////////////////////////
448

    
449
- (NSArray *)myFeaturesForXMPPCapabilities:(XMPPCapabilities *)sender
450
{
451
    return @[@"http://jabber.org/protocol/chatstates"];
452
}
453

    
454
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
455
#pragma mark Connect/disconnect
456
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
457

    
458
- (BOOL)startConnection
459
{
460
    self.connectionStatus = OTRProtocolConnectionStatusConnecting;
461
    
462
    XMPPJID *jid = [XMPPJID jidWithString:self.account.username resource:self.account.resource];
463
    
464
    if (![jid.domain isEqualToString:self.xmppStream.myJID.domain]) {
465
        [self.xmppStream disconnect];
466
    }
467
    self.xmppStream.myJID = jid;
468
	if (![self.xmppStream isDisconnected]) {
469
        [self authenticateWithStream:self.xmppStream];
470
		return YES;
471
	}
472
    
473
	//
474
	// If you don't want to use the Settings view to set the JID, 
475
	// uncomment the section below to hard code a JID and password.
476
	//
477
	// Replace me with the proper JID and password:
478
	//	myJID = @"user@gmail.com/xmppframework";
479
	//	myPassword = @"";
480
    
481
	
482
    
483
    
484
    NSError * error = nil;
485
    NSString * domainString = [self accountDomainWithError:error];
486
    if (error) {
487
        [self failedToConnect:error];
488
        return NO;
489
    }
490
    if ([domainString length]) {
491
        [self.xmppStream setHostName:domainString];
492
    }
493
    
494
    [self.xmppStream setHostPort:self.account.port];
495
	
496
    
497
	error = nil;
498
	if (![self.xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&error])
499
	{
500
		[self failedToConnect:error];
501
        
502
		DDLogError(@"Error connecting: %@", error);
503
        
504
		return NO;
505
	}
506
    
507
	return YES;
508
}
509

    
510
- (void) disconnectSocketOnly:(BOOL)socketOnly {
511
    DDLogVerbose(@"%@: %@ %d", THIS_FILE, THIS_METHOD, socketOnly);
512
    if (socketOnly) {
513
        if (self.connectionStatus == OTRProtocolConnectionStatusConnecting || self.connectionStatus == OTRProtocolConnectionStatusConnected) {
514
            self.connectionStatus = OTRProtocolConnectionStatusDisconnecting;
515
            [self goAway];
516
        }
517
        return;
518
    }
519
    
520
    [self goOffline];
521
    [self.xmppStream disconnectAfterSending];
522

    
523
    if([OTRSettingsManager boolForOTRSettingKey:kOTRSettingKeyDeleteOnDisconnect])
524
    {
525
        [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
526
            [OTRBaseMessage deleteAllMessagesForAccountId:self.account.uniqueId transaction:transaction];
527
        }];
528
    }
529
}
530

    
531
- (void)disconnect
532
{
533
    [self disconnectSocketOnly:NO];
534
}
535

    
536
- (BOOL)startRegisteringNewAccount
537
{
538
    self.isRegisteringNewAccount = YES;
539
    if (self.xmppStream.isConnected) {
540
        [self.xmppStream disconnect];
541
        return NO;
542
    }
543
    
544
    return [self startConnection];
545
}
546

    
547
- (BOOL)continueRegisteringNewAccount
548
{
549
    NSError * error = nil;
550
    if ([self.xmppStream supportsInBandRegistration]) {
551
        [self.xmppStream registerWithPassword:self.account.password error:&error];
552
        if (error) {
553
            [self failedToRegisterNewAccount:error];
554
            return NO;
555
        }
556
    } else {
557
        error = [NSError errorWithDomain:OTRXMPPErrorDomain code:OTRXMPPErrorCodeUnsupportedAction userInfo:nil];
558
        [self failedToRegisterNewAccount:error];
559
        return NO;
560
    }
561
    return YES;
562
}
563

    
564
#pragma mark Public Methods
565

    
566
/** Will send a probe to fetch last seen */
567
- (void) sendPresenceProbeForBuddy:(OTRXMPPBuddy*)buddy {
568
    NSParameterAssert(buddy);
569
    if (!buddy) { return; }
570
    XMPPJID *jid = buddy.bareJID;
571
    if (!jid) { return; }
572
    
573
    // We can't probe presence if we are still pending approval, so resend the request.
574
    if (buddy.pendingApproval) {
575
        [self.xmppRoster subscribePresenceToUser:jid];
576
        return;
577
    }
578
    
579
    // https://xmpp.org/extensions/xep-0318.html
580
    // <presence from='juliet@capulet.com/balcony' to='romeo@montague.com' type='probe' />
581
    XMPPPresence *probe = [XMPPPresence presenceWithType:@"probe" to:jid];
582
    if (!probe) { return; }
583
    [self.xmppStream sendElement:probe];
584
}
585

    
586

    
587
/** Enqueues a message to be sent by message queue */
588
- (void) enqueueMessage:(id<OTRMessageProtocol>)message {
589
    NSParameterAssert(message);
590
    if (!message) { return; }
591
    [self enqueueMessages:@[message]];
592
}
593

    
594
/** Enqueues an array of messages to be sent by message queue */
595
- (void) enqueueMessages:(NSArray<id<OTRMessageProtocol>>*)messages {
596
    NSParameterAssert(messages);
597
    if (!messages.count) {
598
        return;
599
    }
600
    [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
601
        [messages enumerateObjectsUsingBlock:^(id<OTRMessageProtocol> _Nonnull message, NSUInteger idx, BOOL * _Nonnull stop) {
602
            if (message.isMessageIncoming) {
603
                // We cannot send incoming messages
604
                DDLogError(@"Cannot send incoming message: %@", message);
605
                return;
606
            }
607
            //2. Create send message task
608
            OTRYapMessageSendAction *sendingAction = [OTRYapMessageSendAction sendActionForMessage:message date:message.messageDate];
609
            //3. save both to database
610
            [message saveWithTransaction:transaction];
611
            [sendingAction saveWithTransaction:transaction];
612
            //Update thread
613
            id<OTRThreadOwner> thread = [message threadOwnerWithTransaction:transaction];
614
            if (!thread) {
615
                return;
616
            }
617
            thread.currentMessageText = nil;
618
            thread.lastMessageIdentifier = message.uniqueId;
619
            [thread saveWithTransaction:transaction];
620
        }];
621
    }];
622
}
623

    
624
- (void)setAvatar:(UIImage *)avatarImage completion:(void (^)(BOOL success))completion
625
{
626
    if (!avatarImage) {
627
        completion(NO);
628
        return;
629
    }
630
    
631
    __block UIImage *newImage = avatarImage;
632
    
633
    
634
    dispatch_async(self.workQueue, ^{
635
        
636
        //Square crop & Resize image
637
        newImage = [UIImage otr_prepareForAvatarUpload:newImage maxSize:120.0];
638
        //jpeg compression
639
        NSData *data = UIImageJPEGRepresentation(newImage, 0.6);
640
        
641
        //Save new avatar right away to update UI
642
        
643
        [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
644
            OTRXMPPAccount *account = [[OTRXMPPAccount fetchObjectWithUniqueID:self.account.uniqueId transaction:transaction] copy];
645
            account.avatarData = data;
646
            [account saveWithTransaction:transaction];
647
        }];
648
        
649
        self.changeAvatar = [[OTRXMPPChangeAvatar alloc] initWithPhotoData:data
650
                                                       xmppvCardTempModule:self.xmppvCardTempModule];
651
        
652
        __weak typeof(self) weakSelf = self;
653
        [self.changeAvatar updatePhoto:^(BOOL success) {
654
            typeof(weakSelf) strongSelf = weakSelf;
655
            if (completion) {
656
                completion(success);
657
            }
658
            strongSelf.changeAvatar = nil;
659
        }];
660
    });
661
    
662
}
663

    
664
// Currently the only way to force a vCard update is to update the avatar. We want to do this in a non-destructive way, so that this method can be used many times without the avatar being distorted. The idea is to manipulate the bottom rightmost pixel value, setting it to the neighboring pixel value with the blue component alternating between +-1.
665
- (void)forcevCardUpdateWithCompletion:(void (^)(BOOL success))completion {
666
    UIImage *image = [self.account avatarImage];
667
    if (image != nil) {
668
        CGFloat width = image.size.width;
669
        CGFloat height = image.size.height;
670
        if (width <= 0 || height <= 0) {
671
            return;
672
        }
673
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
674
        
675
        size_t bitsPerComponent = 8;
676
        size_t bytesPerPixel    = 4;
677
        size_t bytesPerRow      = (width * bitsPerComponent * bytesPerPixel + 7) / 8;
678
        size_t dataSize         = bytesPerRow * height;
679
        
680
        unsigned char *data = malloc(dataSize);
681
        memset(data, 0, dataSize);
682
        
683
        CGContextRef context = CGBitmapContextCreate(data, width, height,
684
                                                     bitsPerComponent,
685
                                                     bytesPerRow, colorSpace,
686
                                                     kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big);
687
        
688
        
689
        CGColorSpaceRelease(colorSpace);
690
        
691
        CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
692
        
693
        // Get pixel color for neighboring pixel
694
        int offset = (width - 2) * bytesPerRow + (height - 1) * bytesPerPixel;
695
        int alpha =  data[offset];
696
        int red = data[offset+1];
697
        int green = data[offset+2];
698
        int blue = data[offset+3];
699

    
700
        // Manipulate the bottom right pixel
701
        offset = (width - 1) * bytesPerRow + (height - 1) * bytesPerPixel;
702
        int currentBlue = data[offset+3];
703

    
704
        data[offset] = alpha;
705
        data[offset+1] = red;
706
        data[offset+2] = green;
707
        if (currentBlue != blue) {
708
            // Not equal, just use the blue value for the neighboring pixel
709
            data[offset+3] = blue;
710
        } else {
711
            if (blue == 255) {
712
                data[offset+3] = blue - 1;
713
            } else {
714
                data[offset+3] = blue + 1;
715
            }
716
        }
717
        
718
        CGImageRef imgRef = CGBitmapContextCreateImage(context);
719
        UIImage* img = [UIImage imageWithCGImage:imgRef];
720
        CGImageRelease(imgRef);
721
        
722
        // When finished, release the context
723
        CGContextRelease(context);
724
        if (data) { free(data); }
725
        
726
        [self setAvatar:img completion:completion];
727
    }
728
}
729

    
730
- (void)changePassword:(NSString *)newPassword completion:(void (^)(BOOL,NSError*))completion {
731
    if (!completion) {
732
        return;
733
    }
734
    
735
    if (!self.xmppStream.isAuthenticated || [newPassword length] == 0) {
736
        completion(NO,nil);
737
    }
738
    
739
    self.changePasswordManager = [[OTRXMPPChangePasswordManager alloc] initWithNewPassword:newPassword xmppStream:self.xmppStream completion:^(BOOL success, NSError * _Nullable error) {
740
        
741
        if (success) {
742
            self.account.password = newPassword;
743
        }
744
        self.changePasswordManager = nil;
745
        completion(success,error);
746
    }];
747
#pragma clang diagnostic push
748
#pragma clang diagnostic ignored "-Wunused-value"
749
    [self.changePasswordManager changePassword];
750
#pragma clang diagnostic pop
751
}
752

    
753
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
754
#pragma mark XMPPStream Delegate
755
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
756

    
757
- (XMPPMessage *)xmppStream:(XMPPStream *)sender willSendMessage:(XMPPMessage *)message {
758
    NSString *body = [message body];
759
    if (body.length && ![body containsString:@" "]) {
760
        NSURL *url = [NSURL URLWithString:body];
761
        NSString *extension = url.pathExtension;
762
        NSString *scheme = url.scheme;
763
        if (scheme.length &&
764
            extension.length &&
765
            ([scheme isEqualToString:@"https"])) {
766
            // this means we're probably sending a file so mark it as OOB data
767
            /*
768
             <x xmlns="jabber:x:oob">
769
               <url>https://xmpp.example.com/upload/6c44c22c-70c6-4375-8b2d-1992a0f9dc18/8CleEoqhTjiMcny0PqoZyg.jpg</url>
770
             </x>
771
             */
772
            NSXMLElement *oob = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:oob"];
773
            NSXMLElement *urlElement = [NSXMLElement elementWithName:@"url" stringValue:body];
774
            [oob addChild:urlElement];
775
            [message addChild:oob];
776
        }
777
    }
778
    return message;
779
}
780

    
781
- (void)xmppStreamDidChangeMyJID:(XMPPStream *)stream
782
{
783
    if (![[stream.myJID bare] isEqualToString:self.account.username] || ![[stream.myJID resource] isEqualToString:self.account.resource])
784
    {
785
        self.account.username = [stream.myJID bare];
786
        self.account.resource = [stream.myJID resource];
787
        [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
788
            [self.account saveWithTransaction:transaction];
789
        }];
790
    }
791
}
792

    
793
- (void)xmppStream:(XMPPStream *)sender socketDidConnect:(GCDAsyncSocket *)socket 
794
{
795
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
796
    [self changeLoginStatus:OTRLoginStatusConnected error:nil];
797
}
798

    
799
- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings
800
{
801
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
802
    
803
    settings[GCDAsyncSocketSSLProtocolVersionMin] = @(kTLSProtocol1);
804
    settings[GCDAsyncSocketSSLCipherSuites] = [OTRUtilities cipherSuites];
805
    settings[GCDAsyncSocketManuallyEvaluateTrust] = @(YES);
806
    
807
    [self changeLoginStatus:OTRLoginStatusSecuring error:nil];
808
}
809

    
810
- (void)xmppStreamDidSecure:(XMPPStream *)sender
811
{
812
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
813
    
814
    [self changeLoginStatus:OTRLoginStatusSecured error:nil];
815
}
816

    
817
- (void)xmppStreamDidConnect:(XMPPStream *)sender
818
{
819
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
820
    
821
    if (self.isRegisteringNewAccount) {
822
        [self continueRegisteringNewAccount];
823
    }
824
    else{
825
        [self authenticateWithStream:sender];
826
    }
827
    
828
    [self changeLoginStatus:OTRLoginStatusAuthenticating error:nil];
829
}
830

    
831
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
832
{
833
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, error);
834
    
835
    self.connectionStatus = OTRProtocolConnectionStatusDisconnected;
836
    
837
    [self changeLoginStatus:OTRLoginStatusDisconnected error:error];
838
    
839
    if (self.loginStatus == OTRLoginStatusDisconnected)
840
    {
841
        DDLogError(@"Unable to connect to server. Check xmppStream.hostName");
842
        
843
        [self failedToConnect:error];
844
    }
845
    
846
    //Reset buddy info to offline
847
    __block NSArray<OTRXMPPBuddy*> *allBuddies = nil;
848
    [self.databaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
849
        allBuddies = [self.account allBuddiesWithTransaction:transaction];
850
    } completionBlock:^{
851
        // We don't need to save in here because we're using OTRBuddyCache in memory storage
852
        if (!self.streamManagementDelegate.streamManagementEnabled) {
853
            [OTRBuddyCache.shared purgeAllPropertiesForBuddies:allBuddies];
854
        }
855
    }];
856
}
857

    
858
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
859
{
860
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
861
    if ([sender supportsStreamManagement] && ![self.streamManagement didResume]) {
862
        [self.streamManagement enableStreamManagementWithResumption:YES maxTimeout:0];
863
    }
864
    
865
    self.connectionStatus = OTRProtocolConnectionStatusConnected;
866
    NSString *accountKey = self.account.uniqueId;
867
    NSString *accountCollection = [[self.account class] collection];
868
    NSDictionary *userInfo = nil;
869
    if(accountKey && accountCollection) {
870
        userInfo = @{kOTRNotificationAccountUniqueIdKey:accountKey,kOTRNotificationAccountCollectionKey:accountCollection};
871
    }
872
    [[NSNotificationCenter defaultCenter]
873
     postNotificationName:kOTRProtocolLoginSuccess
874
     object:self userInfo:userInfo];
875
    
876
    [self changeLoginStatus:OTRLoginStatusAuthenticated error:nil];
877
    
878
    [self goOnline];
879
    
880
    
881
    
882
    // Fetch latest vCard from server so we can update nickname
883
    //[self.xmppvCardTempModule fetchvCardTempForJID:self.JID ignoreStorage:YES];
884
    
885
    //NSDate *lastInteraction = OTRProtocolManager.shared.lastInteractionDate;
886
    [self.databaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
887
        OTRXMPPAccount *account = [self.account refetchWithTransaction:transaction];
888
        [self.messageStorage.archiving fetchHistoryWithArchiveJID:nil userJID:nil since:account.lastHistoryFetchDate];
889
    }];
890
    //[self.messageStorage.archiving fetchHistoryWithArchiveJID:nil userJID:nil since:lastInteraction];
891
    
892
    //[self.messageStorage.archiving retrieveMessageArchiveWithFields:nil withResultSet:nil];
893
}
894

    
895
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error
896
{
897
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
898
    self.connectionStatus = OTRProtocolConnectionStatusDisconnected;
899
    NSError *err = [OTRXMPPError errorForXMLElement:error];
900
    [self failedToConnect:err];
901
    
902
    [self changeLoginStatus:OTRLoginStatusSecured error:err];
903
}
904

    
905
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
906
{
907
	DDLogVerbose(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, iq);
908
	return NO;
909
}
910

    
911
- (void)xmppStreamDidRegister:(XMPPStream *)sender {
912
    [self didRegisterNewAccountWithStream:sender];
913
}
914

    
915
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(NSXMLElement *)xmlError {
916
    
917
    self.isRegisteringNewAccount = NO;
918
    NSError * error = [OTRXMPPError errorForXMLElement:xmlError];
919
    [self failedToRegisterNewAccount:error];
920
    
921
    [self changeLoginStatus:OTRLoginStatusSecured error:error];
922
}
923

    
924
-(nullable OTRXMPPBuddy *)buddyWithMessage:(XMPPMessage *)message transaction:(YapDatabaseReadTransaction *)transaction
925
{
926
    XMPPJID *from = message.from;
927
    if (!from) { return nil; }
928
    OTRXMPPBuddy *buddy = [OTRXMPPBuddy fetchBuddyWithJid:from accountUniqueId:self.account.uniqueId transaction:transaction];
929
    return buddy;
930
}
931

    
932

    
933
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)xmppMessage
934
{
935
	DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, xmppMessage);
936
}
937

    
938
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
939
{
940
	DDLogVerbose(@"%@: %@\n%@", THIS_FILE, THIS_METHOD, presence.prettyXMLString);
941
    
942
    
943
}
944

    
945
- (void)xmppStream:(XMPPStream *)sender didReceiveError:(id)error
946
{
947
	DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, error);
948
}
949

    
950
- (void)xmppStream:(XMPPStream *)sender didFailToSendIQ:(XMPPIQ *)iq error:(NSError *)error
951
{
952
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, iq, error);
953
}
954

    
955
- (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error
956
{
957
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, message, error);
958
    [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
959
        [transaction enumerateMessagesWithElementId:message.elementID originId:message.originId stanzaId:nil block:^(id<OTRMessageProtocol> _Nonnull databaseMessage, BOOL * _Null_unspecified stop) {
960
            if ([databaseMessage isKindOfClass:[OTRBaseMessage class]]) {
961
                ((OTRBaseMessage *)databaseMessage).error = error;
962
                [(OTRBaseMessage *)databaseMessage saveWithTransaction:transaction];
963
                *stop = YES;
964
            }
965
        }];
966
    }];
967
}
968
- (void)xmppStream:(XMPPStream *)sender didFailToSendPresence:(XMPPPresence *)presence error:(NSError *)error
969
{
970
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, presence, error);
971
    [self finishDisconnectingIfNeeded];
972
}
973

    
974
- (void)xmppStream:(XMPPStream *)sender didSendPresence:(XMPPPresence *)presence
975
{
976
    [self finishDisconnectingIfNeeded];
977
}
978

    
979
- (void)finishDisconnectingIfNeeded {
980
    if (self.connectionStatus == OTRProtocolConnectionStatusDisconnecting) {
981
        [self.xmppStream disconnect];
982
        self.connectionStatus =  OTRProtocolConnectionStatusDisconnected;
983
    }
984
}
985

    
986
#pragma mark XMPPvCardTempModuleDelegate
987

    
988
- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule
989
        didReceivevCardTemp:(XMPPvCardTemp *)vCardTemp
990
                     forJID:(XMPPJID *)jid {
991
    DDLogVerbose(@"%@: %@ %@ %@ %@", THIS_FILE, THIS_METHOD, vCardTempModule, vCardTemp, jid);
992
    
993
    // update my vCard to local nickname setting
994
    // currently this will clobber whatever you have on the server
995
    if ([self.xmppStream.myJID isEqualToJID:jid options:XMPPJIDCompareBare]) {
996
        if (self.account.displayName.length &&
997
            vCardTemp.nickname.length &&
998
            ![vCardTemp.nickname isEqualToString:self.account.displayName]) {
999
            vCardTemp.nickname = self.account.displayName;
1000
            [vCardTempModule updateMyvCardTemp:vCardTemp];
1001
        } else if (vCardTemp.nickname.length) {
1002
            [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
1003
                NSString *collection = [self.account.class collection];
1004
                NSString *key = self.account.uniqueId;
1005
                OTRXMPPAccount *account = [[transaction objectForKey:key inCollection:collection] copy];
1006
                account.displayName = vCardTemp.nickname;
1007
                [transaction setObject:account forKey:key inCollection:collection];
1008
            }];
1009
        }
1010
    } else {
1011
        // this is someone elses vCard
1012
        DDLogVerbose(@"%@: other's vCard %@ %@ %@ %@", THIS_FILE, THIS_METHOD, vCardTempModule, vCardTemp, jid);
1013
    }
1014
}
1015

    
1016
- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule
1017
   failedToFetchvCardForJID:(XMPPJID *)jid
1018
                      error:(NSXMLElement*)error {
1019
    DDLogVerbose(@"%@: %@ %@ %@ %@", THIS_FILE, THIS_METHOD, vCardTempModule, jid, error);
1020
    
1021
    // update my vCard to local nickname setting
1022
    if ([self.xmppStream.myJID isEqualToJID:jid options:XMPPJIDCompareBare] &&
1023
        self.account.displayName.length) {
1024
        XMPPvCardTemp *vCardTemp = [XMPPvCardTemp vCardTemp];
1025
        vCardTemp.nickname = self.account.displayName;
1026
        [vCardTempModule updateMyvCardTemp:vCardTemp];
1027
    }
1028
}
1029

    
1030
- (void)xmppvCardTempModuleDidUpdateMyvCard:(XMPPvCardTempModule *)vCardTempModule {
1031
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, vCardTempModule);
1032
}
1033

    
1034
- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule failedToUpdateMyvCard:(NSXMLElement *)error {
1035
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, vCardTempModule, error);
1036
}
1037

    
1038

    
1039
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1040
#pragma mark XMPPRosterDelegate
1041
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1042

    
1043
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(NSXMLElement *)item {
1044
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, item);
1045

    
1046
    // Because XMPP sucks, there's no way to know if a vCard has changed without fetching all of them again
1047
    // To preserve user mobile data, just fetch each vCard once, only if it's never been fetched
1048
    // Otherwise you'll only receive vCard updates if someone updates their avatar
1049
    NSString *jidStr = [item attributeStringValueForName:@"jid"];
1050
    XMPPJID *jid = [[XMPPJID jidWithString:jidStr] bareJID];
1051
    __block OTRXMPPBuddy *buddy = nil;
1052
    [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
1053
        buddy = [OTRXMPPBuddy fetchBuddyWithJid:jid accountUniqueId:self.account.uniqueId transaction:transaction];
1054
    } completionQueue:self.workQueue completionBlock:^{
1055
        if (!buddy) { return; }
1056
        XMPPvCardTemp *vCard = [self.xmppvCardTempModule vCardTempForJID:jid shouldFetch:YES];
1057
        if (!vCard) { return; }
1058
        [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
1059
            buddy = [buddy refetchWithTransaction:transaction];
1060
            if (!buddy) { return; }
1061
            buddy.vCardTemp = vCard;
1062
            [buddy saveWithTransaction:transaction];
1063
        }];
1064
    }];
1065
}
1066

    
1067
-(void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence
1068
{
1069
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, presence);
1070
    
1071
	NSString *jidStrBare = [presence fromStr];
1072
    
1073
    [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1074
        OTRXMPPPresenceSubscriptionRequest *request = [OTRXMPPPresenceSubscriptionRequest fetchPresenceSubscriptionRequestWithJID:jidStrBare accontUniqueId:self.account.uniqueId transaction:transaction];
1075
        if (!request) {
1076
            request = [[OTRXMPPPresenceSubscriptionRequest alloc] init];
1077
            [[UIApplication sharedApplication] showLocalNotificationForSubscriptionRequestFrom:jidStrBare];
1078
        }
1079
        
1080
        request.jid = jidStrBare;
1081
        request.accountUniqueId = self.account.uniqueId;
1082
        
1083
        [request saveWithTransaction:transaction];
1084
    }];
1085
}
1086

    
1087
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterPush:(XMPPIQ *)iq
1088
{
1089
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, iq);
1090
    //verry unclear what this delegate call is supposed to do with jabber.ccc.de it seems to have all the subscription=both,none and jid
1091
    /*
1092
    if ([iq isSetIQ] && [[[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"subscription"] isEqualToString:@"from"]) {
1093
        NSString *jidString = [[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"jid"];
1094
        
1095
        [self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1096
            OTRXMPPPresenceSubscriptionRequest *request = [OTRXMPPPresenceSubscriptionRequest fetchPresenceSubscriptionRequestWithJID:jidString accontUniqueId:self.account.uniqueId transaction:transaction];
1097
            if (!request) {
1098
                request = [[OTRXMPPPresenceSubscriptionRequest alloc] init];
1099
            }
1100
            
1101
            request.jid = jidString;
1102
            request.accountUniqueId = self.account.uniqueId;
1103
            
1104
            [transaction setObject:request forKey:request.uniqueId inCollection:[OTRXMPPPresenceSubscriptionRequest collection]];
1105
        }];
1106
    }
1107
    else if ([iq isSetIQ] && [[[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"subscription"] isEqualToString:@"none"])
1108
    {
1109
        [self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1110
            NSString *jidString = [[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"jid"];
1111
            
1112
            OTRXMPPBuddy *buddy = [[OTRXMPPBuddy fetchBuddyWithUsername:jidString withAccountUniqueId:self.account.uniqueId transaction:transaction] copy];
1113
            buddy.pendingApproval = YES;
1114
            [buddy saveWithTransaction:transaction];
1115
        }];
1116
    }
1117
    
1118
    */
1119
    
1120
    
1121
}
1122

    
1123
#pragma mark XMPPPushDelegate
1124

    
1125
- (void) pushAccountChanged:(NSNotification*)notif {
1126
    __block OTRXMPPAccount *account = nil;
1127
    [self.databaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
1128
        NSString *collection = [self.account.class collection];
1129
        NSString *key = self.account.uniqueId;
1130
        account = [transaction objectForKey:key inCollection:collection];
1131
    }];
1132
    if (!account.pushPubsubEndpoint) { return; }
1133
    XMPPJID *serverJID = [XMPPJID jidWithUser:nil domain:account.pushPubsubEndpoint resource:nil];
1134
    if (!serverJID) { return; }
1135
    XMPPPushStatus status = [self.xmppPushModule registrationStatusForServerJID:serverJID];
1136
    if (status != XMPPPushStatusRegistered &&
1137
        status != XMPPPushStatusRegistering) {
1138
        [self.xmppPushModule refresh];
1139
    }
1140
}
1141

    
1142
- (void)pushModule:(XMPPPushModule*)module readyWithCapabilities:(NSXMLElement *)caps jid:(XMPPJID *)jid {
1143
    // Enable XEP-0357 push bridge if server supports it
1144
    // ..but don't register for Tor accounts
1145
    if (self.account.accountType == OTRAccountTypeXMPPTor) {
1146
        return;
1147
    }
1148
    BOOL hasPushAccount = [[OTRProtocolManager sharedInstance].pushController.pushStorage hasPushAccount];
1149
    if (!hasPushAccount) {
1150
        return;
1151
    }
1152
    [[OTRProtocolManager sharedInstance].pushController getPubsubEndpoint:^(NSString * _Nullable endpoint, NSError * _Nullable error) {
1153
        if (endpoint) {
1154
            [[OTRProtocolManager sharedInstance].pushController getNewPushToken:nil completion:^(TokenContainer * _Nullable token, NSError * _Nullable error) {
1155
                if (token) {
1156
                    [self enablePushWithToken:token endpoint:endpoint];
1157
                } else if (error) {
1158
                    DDLogError(@"fetch token error: %@", error);
1159
                }
1160
            }];
1161
        } else if (error) {
1162
            DDLogError(@"357 pubsub Error: %@", error);
1163
        }
1164
    }];
1165
}
1166

    
1167
- (void) enablePushWithToken:(TokenContainer*)token endpoint:(NSString*)endpoint {
1168
    __block OTRXMPPAccount *account = nil;
1169
    [self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
1170
        NSString *collection = [self.account.class collection];
1171
        NSString *key = self.account.uniqueId;
1172
        account = [[transaction objectForKey:key inCollection:collection] copy];
1173
        account.pushPubsubEndpoint = endpoint;
1174
        if (!account.pushPubsubNode.length) {
1175
            account.pushPubsubNode = [[NSUUID UUID] UUIDString];
1176
        }
1177
        [transaction setObject:account forKey:key inCollection:collection];
1178
    }];
1179
    XMPPJID *nodeJID = [XMPPJID jidWithString:endpoint];
1180
    NSString *tokenString = token.pushToken.tokenString;
1181
    if (tokenString.length > 0) {
1182
        NSString *pushEndpointURLString = [[OTRProtocolManager sharedInstance].pushController getMessagesEndpoint].absoluteString;
1183
        NSMutableDictionary *options = [NSMutableDictionary dictionary];
1184
        [options setObject:tokenString forKey:@"token"];
1185
        if (pushEndpointURLString) {
1186
            [options setObject:pushEndpointURLString forKey:@"endpoint"];
1187
        }
1188
        XMPPPushOptions *pushOptions = [[XMPPPushOptions alloc] initWithServerJID:nodeJID node:account.pushPubsubNode formOptions:options];
1189
        [self.xmppPushModule registerForPushWithOptions:pushOptions elementId:nil];
1190
    }
1191
}
1192

    
1193
- (void)pushModule:(XMPPPushModule*)module
1194
didRegisterWithResponseIq:(XMPPIQ*)responseIq
1195
        outgoingIq:(XMPPIQ*)outgoingIq {
1196
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, responseIq, outgoingIq);
1197
}
1198

    
1199
- (void)pushModule:(XMPPPushModule*)module
1200
failedToRegisterWithErrorIq:(nullable XMPPIQ*)errorIq
1201
        outgoingIq:(XMPPIQ*)outgoingIq {
1202
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, errorIq, outgoingIq);
1203
}
1204

    
1205
- (void)pushModule:(XMPPPushModule*)module
1206
disabledPushForServerJID:(XMPPJID*)serverJID
1207
              node:(nullable NSString*)node
1208
        responseIq:(XMPPIQ*)responseIq
1209
        outgoingIq:(XMPPIQ*)outgoingIq {
1210
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, responseIq, outgoingIq);
1211
}
1212

    
1213
- (void)pushModule:(XMPPPushModule*)module
1214
failedToDisablePushWithErrorIq:(nullable XMPPIQ*)errorIq
1215
         serverJID:(XMPPJID*)serverJID
1216
              node:(nullable NSString*)node
1217
        outgoingIq:(XMPPIQ*)outgoingIq {
1218
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, errorIq, outgoingIq);
1219
}
1220

    
1221

    
1222
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1223
#pragma mark OTRProtocol
1224
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1225

    
1226
- (void) sendMessage:(OTROutgoingMessage*)message
1227
{
1228
    NSParameterAssert(message);
1229
    NSString *text = message.text;
1230
    if (!text.length) {
1231
        return;
1232
    }
1233
    __block OTRXMPPBuddy *buddy = nil;
1234
    [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
1235
        buddy = (OTRXMPPBuddy *)[message threadOwnerWithTransaction:transaction];
1236
    }];
1237
    if (!buddy || ![buddy isKindOfClass:[OTRXMPPBuddy class]]) {
1238
        return;
1239
    }
1240
    [self invalidatePausedChatStateTimerForBuddyUniqueId:buddy.uniqueId];
1241
    
1242
    NSString * messageID = message.messageId;
1243
    XMPPMessage * xmppMessage = [XMPPMessage messageWithType:@"chat" to:buddy.bareJID elementID:messageID];
1244
    [xmppMessage addBody:text];
1245
    
1246
    [xmppMessage addActiveChatState];
1247
    
1248
    if ([OTRKit stringStartsWithOTRPrefix:text]) {
1249
        [xmppMessage addPrivateMessageCarbons];
1250
        [xmppMessage addStorageHint:XMPPMessageStorageNoCopy];
1251
        [xmppMessage addStorageHint:XMPPMessageStorageNoPermanentStore];
1252
    }
1253
    
1254
    [self.xmppStream sendElement:xmppMessage];
1255
}
1256

    
1257
- (NSString*) type {
1258
    return kOTRProtocolTypeXMPP;
1259
}
1260

    
1261
- (void) connectUserInitiated:(BOOL)userInitiated
1262
{
1263
    self.userInitiatedConnection = userInitiated;
1264
    // Don't issue a reconnect if we're already connected and authenticated
1265
    if ([self.xmppStream isConnected] && [self.xmppStream isAuthenticated]) {
1266
        [self goOnline];
1267
        return;
1268
    }
1269
    [self startConnection];
1270
    if (self.userInitiatedConnection) {
1271
        [[OTRNotificationController sharedInstance] showAccountConnectingNotificationWithAccountName:self.account.username];
1272
    }
1273
}
1274

    
1275
-(void)connect
1276
{
1277
    [self connectUserInitiated:NO];
1278
}
1279

    
1280
-(void)sendChatState:(OTRChatState)chatState withBuddyID:(NSString *)buddyUniqueId
1281
{
1282
    dispatch_async(self.workQueue, ^{
1283
        
1284
        __block OTRXMPPBuddy *buddy = nil;
1285
        [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
1286
            buddy = [OTRXMPPBuddy fetchObjectWithUniqueID:buddyUniqueId transaction:transaction];
1287
        }];
1288
        if (!buddy) { return; }
1289
        
1290
        if (buddy.lastSentChatState == chatState) {
1291
            return;
1292
        }
1293
        
1294
        XMPPMessage * xMessage = [[XMPPMessage alloc] initWithType:@"chat" to:[XMPPJID jidWithString:buddy.username]];
1295
        BOOL shouldSend = YES;
1296
        
1297
        if (chatState == OTRChatStateActive) {
1298
            //Timers
1299
            dispatch_async(dispatch_get_main_queue(), ^{
1300
                [[self pausedChatStateTimerForBuddyObjectID:buddyUniqueId] invalidate];
1301
                [self restartInactiveChatStateTimerForBuddyObjectID:buddyUniqueId];
1302
            });
1303
            
1304
            [xMessage addActiveChatState];
1305
        }
1306
        else if (chatState == OTRChatStateComposing)
1307
        {
1308
            if(buddy.lastSentChatState !=OTRChatStateComposing)
1309
                [xMessage addComposingChatState];
1310
            else
1311
                shouldSend = NO;
1312
            
1313
            //Timers
1314
            dispatch_async(dispatch_get_main_queue(), ^{
1315
                [self restartPausedChatStateTimerForBuddyObjectID:buddy.uniqueId];
1316
                [[self inactiveChatStateTimerForBuddyObjectID:buddy.uniqueId] invalidate];
1317
            });
1318
        }
1319
        else if(chatState == OTRChatStateInactive)
1320
        {
1321
            if(buddy.lastSentChatState != OTRChatStateInactive)
1322
                [xMessage addInactiveChatState];
1323
            else
1324
                shouldSend = NO;
1325
        }
1326
        else if (chatState == OTRChatStatePaused)
1327
        {
1328
            [xMessage addPausedChatState];
1329
        }
1330
        else if (chatState == OTRChatStateGone)
1331
        {
1332
            [xMessage addGoneChatState];
1333
        }
1334
        else
1335
        {
1336
            shouldSend = NO;
1337
        }
1338
        
1339
        if(shouldSend)
1340
        {
1341
            [OTRBuddyCache.shared setLastSentChatState:chatState forBuddy:buddy];
1342
            [self.xmppStream sendElement:xMessage];
1343
        }
1344
    });
1345
}
1346

    
1347
- (void) addBuddies:(NSArray<OTRXMPPBuddy*> *)buddies {
1348
    NSParameterAssert(buddies != nil);
1349
    if (!buddies.count) { return; }
1350
    [[OTRDatabaseManager sharedInstance].readWriteDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1351
        [buddies enumerateObjectsUsingBlock:^(OTRXMPPBuddy * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
1352
            [obj saveWithTransaction:transaction];
1353
            OTRYapAddBuddyAction *addBuddyAction = [[OTRYapAddBuddyAction alloc] init];
1354
            addBuddyAction.buddyKey = obj.uniqueId;
1355
            [addBuddyAction saveWithTransaction:transaction];
1356
        }];
1357
    }];
1358
}
1359

    
1360
- (void) addBuddy:(OTRXMPPBuddy *)newBuddy
1361
{
1362
    NSParameterAssert(newBuddy != nil);
1363
    if (!newBuddy) { return; }
1364
    [self addBuddies:@[newBuddy]];
1365
}
1366

    
1367
- (void) setDisplayName:(NSString *) newDisplayName forBuddy:(OTRXMPPBuddy *)buddy
1368
{
1369
    XMPPJID * jid = [XMPPJID jidWithString:buddy.username];
1370
    [self.xmppRoster setNickname:newDisplayName forUser:jid];
1371
    
1372
}
1373
-(void)removeBuddies:(NSArray *)buddies
1374
{
1375
    [[OTRDatabaseManager sharedInstance].readWriteDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1376
        
1377
        // Add actions to the queue
1378
        for (OTRXMPPBuddy *buddy in buddies){
1379
            OTRYapRemoveBuddyAction *removeBuddyAction = [[OTRYapRemoveBuddyAction alloc] init];
1380
            removeBuddyAction.buddyKey = buddy.uniqueId;
1381
            removeBuddyAction.buddyJid = buddy.username;
1382
            removeBuddyAction.accountKey = buddy.accountUniqueId;
1383
            [removeBuddyAction saveWithTransaction:transaction];
1384
        }
1385
        
1386
        [transaction removeObjectsForKeys:[buddies valueForKey:NSStringFromSelector(@selector(uniqueId))] inCollection:[OTRXMPPBuddy collection]];
1387
    }];
1388

    
1389

    
1390

    
1391
}
1392
-(void)blockBuddies:(NSArray *)buddies
1393
{
1394
    for (OTRXMPPBuddy *buddy in buddies){
1395
        XMPPJID * jid = [XMPPJID jidWithString:buddy.username];
1396
        [self.xmppRoster revokePresencePermissionFromUser:jid];
1397
    }
1398
}
1399

    
1400
//Chat State
1401

    
1402
-(OTRXMPPBuddyTimers *)buddyTimersForBuddyObjectID:(NSString *)
1403
managedBuddyObjectID
1404
{
1405
    OTRXMPPBuddyTimers * timers = [self.buddyTimers objectForKey:managedBuddyObjectID];
1406
    return timers;
1407
}
1408

    
1409
-(NSTimer *)inactiveChatStateTimerForBuddyObjectID:(NSString *)
1410
managedBuddyObjectID
1411
{
1412
   return [self buddyTimersForBuddyObjectID:managedBuddyObjectID].inactiveChatStateTimer;
1413
    
1414
}
1415
-(NSTimer *)pausedChatStateTimerForBuddyObjectID:(NSString *)
1416
managedBuddyObjectID
1417
{
1418
    return [self buddyTimersForBuddyObjectID:managedBuddyObjectID].pausedChatStateTimer;
1419
}
1420

    
1421
-(void)restartPausedChatStateTimerForBuddyObjectID:(NSString *)managedBuddyObjectID
1422
{
1423
    dispatch_async(dispatch_get_main_queue(), ^{
1424
        OTRXMPPBuddyTimers * timer = [self.buddyTimers objectForKey:managedBuddyObjectID];
1425
        if(!timer)
1426
        {
1427
            timer = [[OTRXMPPBuddyTimers alloc] init];
1428
        }
1429
        [timer.pausedChatStateTimer invalidate];
1430
        timer.pausedChatStateTimer = [NSTimer scheduledTimerWithTimeInterval:kOTRChatStatePausedTimeout target:self selector:@selector(sendPausedChatState:) userInfo:managedBuddyObjectID repeats:NO];
1431
        [self.buddyTimers setObject:timer forKey:managedBuddyObjectID];
1432
    });
1433
    
1434
}
1435
-(void)restartInactiveChatStateTimerForBuddyObjectID:(NSString *)managedBuddyObjectID
1436
{
1437
    dispatch_async(dispatch_get_main_queue(), ^{
1438
        OTRXMPPBuddyTimers * timer = [self.buddyTimers objectForKey:managedBuddyObjectID];
1439
        if(!timer)
1440
        {
1441
            timer = [[OTRXMPPBuddyTimers alloc] init];
1442
        }
1443
        [timer.inactiveChatStateTimer invalidate];
1444
        timer.inactiveChatStateTimer = [NSTimer scheduledTimerWithTimeInterval:kOTRChatStateInactiveTimeout target:self selector:@selector(sendInactiveChatState:) userInfo:managedBuddyObjectID repeats:NO];
1445
        [self.buddyTimers setObject:timer forKey:managedBuddyObjectID];
1446
    });
1447
}
1448
-(void)sendPausedChatState:(NSTimer *)timer
1449
{
1450
    NSString * managedBuddyObjectID= (NSString *)timer.userInfo;
1451
    dispatch_async(dispatch_get_main_queue(), ^{
1452
        [timer invalidate];
1453
    });
1454
    [self sendChatState:OTRChatStatePaused withBuddyID:managedBuddyObjectID];
1455
}
1456
-(void)sendInactiveChatState:(NSTimer *)timer
1457
{
1458
    NSString *managedBuddyObjectID= (NSString *)timer.userInfo;
1459
    dispatch_async(dispatch_get_main_queue(), ^{
1460
        [timer invalidate];
1461
    });
1462
    
1463
    [self sendChatState:OTRChatStateInactive withBuddyID:managedBuddyObjectID];
1464
}
1465

    
1466
- (void)invalidatePausedChatStateTimerForBuddyUniqueId:(NSString *)buddyUniqueId
1467
{
1468
    [[self pausedChatStateTimerForBuddyObjectID:buddyUniqueId] invalidate];
1469
}
1470

    
1471
- (void)failedToConnect:(NSError *)error
1472
{
1473
    __weak typeof(self)weakSelf = self;
1474
    dispatch_async(dispatch_get_main_queue(), ^{
1475
        __strong typeof(weakSelf)strongSelf = weakSelf;
1476
        strongSelf->_lastConnectionError = error;
1477
        
1478
        NSMutableDictionary *userInfo = [@{kOTRProtocolLoginUserInitiated : @(self.userInitiatedConnection)} mutableCopy];
1479
        if (error) {
1480
            [userInfo setObject:error forKey:kOTRNotificationErrorKey];
1481
        }
1482
        
1483
        [[NSNotificationCenter defaultCenter] postNotificationName:kOTRProtocolLoginFail object:self userInfo:userInfo];
1484
        //Only user initiated on the first time any subsequent attempts will not be from user
1485
        strongSelf.userInitiatedConnection = NO;
1486
    });
1487
}
1488

    
1489
- (OTRCertificatePinning *)certificatePinningModule
1490
{
1491
    if(!_certificatePinningModule){
1492
        _certificatePinningModule = [[OTRCertificatePinning alloc] init];
1493
        _certificatePinningModule.delegate = self;
1494
    }
1495
    return _certificatePinningModule;
1496
}
1497

    
1498
- (void)newTrust:(SecTrustRef)trust withHostName:(NSString *)hostname systemTrustResult:(SecTrustResultType)trustResultType
1499
{
1500
    NSData * certifcateData = [OTRCertificatePinning dataForCertificate:[OTRCertificatePinning certForTrust:trust]];
1501
    DDLogVerbose(@"New trustResultType: %d certLength: %d", (int)trustResultType, (int)certifcateData.length);
1502
    NSError *error = [OTRXMPPError errorForTrustResult:trustResultType withCertData:certifcateData hostname:hostname];
1503
    dispatch_async(dispatch_get_main_queue(), ^{
1504
        [self failedToConnect:error];
1505
    });
1506
    
1507
    [self changeLoginStatus:OTRLoginStatusDisconnected error:error];
1508
}
1509

    
1510
- (void)changeLoginStatus:(OTRLoginStatus)status error:(NSError *)error
1511
{
1512
    OTRLoginStatus oldStatus = self.loginStatus;
1513
    OTRLoginStatus newStatus = status;
1514
    self.loginStatus = status;
1515
    
1516
    NSMutableDictionary *userInfo = [@{OTRXMPPOldLoginStatusKey: @(oldStatus), OTRXMPPNewLoginStatusKey: @(newStatus)} mutableCopy];
1517
    
1518
    if (error) {
1519
        userInfo[OTRXMPPLoginErrorKey] = error;
1520
    }
1521
    
1522
    dispatch_async(dispatch_get_main_queue(), ^{
1523
        [[NSNotificationCenter defaultCenter] postNotificationName:OTRXMPPLoginStatusNotificationName object:self userInfo:userInfo];
1524
    });
1525
}
1526

    
1527
// Delivery receipts
1528
- (void) sendDeliveryReceiptForMessage:(OTRIncomingMessage*)message {
1529
    [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
1530
        OTRBuddy *buddy = [OTRBuddy fetchObjectWithUniqueID:message.buddyUniqueId transaction:transaction];        
1531
        XMPPMessage *tempMessage = [XMPPMessage messageWithType:@"chat" elementID:message.messageId];
1532
        [tempMessage addAttributeWithName:@"from" stringValue:buddy.username];
1533
        XMPPMessage *receiptMessage = [tempMessage generateReceiptResponse];
1534
        [self.xmppStream sendElement:receiptMessage];
1535
    }];
1536
}
1537

    
1538
// A new buddy has approved us, show a local notification
1539
- (void) buddyPendingApprovalStateChanged:(NSNotification*)notif {
1540
    if (notif != nil && notif.userInfo != nil) {
1541
        OTRBuddy *buddy = [notif.userInfo objectForKey:@"buddy"];
1542
        if (buddy != nil) {
1543
            [[UIApplication sharedApplication] showLocalNotificationForApprovedBuddy:buddy];
1544
        }
1545
    }
1546
}
1547

    
1548
@end