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 |