Skip to content

Commit acab932

Browse files
committed
Merge pull request robbiehanson#445 from jonstaff/master
Updated XMPPMUC to include functionality for discovering rooms and services (issue robbiehanson#444)
2 parents cfb0e4d + e3db746 commit acab932

File tree

2 files changed

+292
-6
lines changed

2 files changed

+292
-6
lines changed

Extensions/XEP-0045/XMPPMUC.h

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#define _XMPP_MUC_H
66

7+
@class XMPPIDTracker;
8+
79
/**
810
* The XMPPMUC module, combined with XMPPRoom and associated storage classes,
911
* provides an implementation of XEP-0045 Multi-User Chat.
@@ -17,7 +19,6 @@
1719
* and provides an efficient query to see if a presence or message element is targeted at a room.
1820
* - It listens for MUC room invitations sent from other users.
1921
**/
20-
2122
@interface XMPPMUC : XMPPModule
2223
{
2324
/* Inherited from XMPPModule:
@@ -28,6 +29,8 @@
2829
*/
2930

3031
NSMutableSet *rooms;
32+
33+
XMPPIDTracker *xmppIDTracker;
3134
}
3235

3336
/* Inherited from XMPPModule:
@@ -47,6 +50,27 @@
4750
- (BOOL)isMUCRoomPresence:(XMPPPresence *)presence;
4851
- (BOOL)isMUCRoomMessage:(XMPPMessage *)message;
4952

53+
/**
54+
* This method will attempt to discover existing services for the domain found in xmppStream.myJID.
55+
*
56+
* @see xmppMUC:didDiscoverServices:
57+
* @see xmppMUCFailedToDiscoverServices:withError:
58+
*/
59+
- (void)discoverServices;
60+
61+
/**
62+
* This method will attempt to discover existing rooms (that are not hidden) for a given service.
63+
*
64+
* @see xmppMUC:didDiscoverRooms:forServiceNamed:
65+
* @see xmppMUC:failedToDiscoverRoomsForServiceNamed:withError:
66+
*
67+
* @param serviceName The name of the service for which to discover rooms. Normally in the form
68+
* of "chat.shakespeare.lit".
69+
*
70+
* @return NO if a serviceName is not provided, otherwise YES
71+
*/
72+
- (BOOL)discoverRoomsForServiceNamed:(NSString *)serviceName;
73+
5074
@end
5175

5276
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -56,7 +80,52 @@
5680
@protocol XMPPMUCDelegate
5781
@optional
5882

59-
- (void)xmppMUC:(XMPPMUC *)sender roomJID:(XMPPJID *) roomJID didReceiveInvitation:(XMPPMessage *)message;
60-
- (void)xmppMUC:(XMPPMUC *)sender roomJID:(XMPPJID *) roomJID didReceiveInvitationDecline:(XMPPMessage *)message;
83+
- (void)xmppMUC:(XMPPMUC *)sender roomJID:(XMPPJID *)roomJID didReceiveInvitation:(XMPPMessage *)message;
84+
- (void)xmppMUC:(XMPPMUC *)sender roomJID:(XMPPJID *)roomJID didReceiveInvitationDecline:(XMPPMessage *)message;
85+
86+
/**
87+
* Implement this method when calling [mucInstance discoverServices]. It will be invoked if the request
88+
* for discovering services is successfully executed and receives a successful response.
89+
*
90+
* @param sender XMPPMUC object invoking this delegate method.
91+
* @param services An array of NSXMLElements in the form shown below. You will need to extract the data you
92+
* wish to use.
93+
*
94+
* <item jid='chat.shakespeare.lit' name='Chatroom Service'/>
95+
*/
96+
- (void)xmppMUC:(XMPPMUC *)sender didDiscoverServices:(NSArray *)services;
97+
98+
/**
99+
* Implement this method when calling [mucInstanse discoverServices]. It will be invoked if the request
100+
* for discovering services is unsuccessfully executed or receives an unsuccessful response.
101+
*
102+
* @param sender XMPPMUC object invoking this delegate method.
103+
* @param error NSError containing more details of the failure.
104+
*/
105+
- (void)xmppMUCFailedToDiscoverServices:(XMPPMUC *)sender withError:(NSError *)error;
106+
107+
/**
108+
* Implement this method when calling [mucInstance discoverRoomsForServiceNamed:]. It will be invoked if
109+
* the request for discovering rooms is successfully executed and receives a successful response.
110+
*
111+
* @param sender XMPPMUC object invoking this delegate method.
112+
* @param rooms An array of NSXMLElements in the form shown below. You will need to extract the data you
113+
* wish to use.
114+
*
115+
* <item jid='[email protected]' name='The Palace'/>
116+
*
117+
* @param serviceName The name of the service for which rooms were discovered.
118+
*/
119+
- (void)xmppMUC:(XMPPMUC *)sender didDiscoverRooms:(NSArray *)rooms forServiceNamed:(NSString *)serviceName;
120+
121+
/**
122+
* Implement this method when calling [mucInstance discoverRoomsForServiceNamed:]. It will be invoked if
123+
* the request for discovering rooms is unsuccessfully executed or receives an unsuccessful response.
124+
*
125+
* @param sender XMPPMUC object invoking this delegate method.
126+
* @param serviceName The name of the service for which rooms were attempted to be discovered.
127+
* @param error NSError containing more details of the failure.
128+
*/
129+
- (void)xmppMUC:(XMPPMUC *)sender failedToDiscoverRoomsForServiceNamed:(NSString *)serviceName withError:(NSError *)error;
61130

62131
@end

Extensions/XEP-0045/XMPPMUC.m

Lines changed: 220 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
11
#import "XMPPMUC.h"
22
#import "XMPPFramework.h"
3+
#import "XMPPLogging.h"
4+
#import "XMPPIDTracker.h"
35

6+
#if DEBUG
7+
static const int xmppLogLevel = XMPP_LOG_LEVEL_VERBOSE;
8+
#else
9+
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
10+
#endif
11+
12+
NSString *const XMPPDiscoverItemsNamespace = @"http://jabber.org/protocol/disco#items";
13+
NSString *const XMPPMUCErrorDomain = @"XMPPMUCErrorDomain";
14+
15+
@interface XMPPMUC()
16+
{
17+
BOOL hasRequestedServices;
18+
BOOL hasRequestedRooms;
19+
}
20+
21+
@end
422

523
@implementation XMPPMUC
624

725
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
826
{
9-
if ((self = [super initWithDispatchQueue:queue]))
10-
{
27+
if ((self = [super initWithDispatchQueue:queue])) {
1128
rooms = [[NSMutableSet alloc] init];
1229
}
1330
return self;
@@ -17,8 +34,15 @@ - (BOOL)activate:(XMPPStream *)aXmppStream
1734
{
1835
if ([super activate:aXmppStream])
1936
{
37+
XMPPLogVerbose(@"%@: Activated", THIS_FILE);
38+
39+
xmppIDTracker = [[XMPPIDTracker alloc] initWithStream:xmppStream
40+
dispatchQueue:moduleQueue];
41+
2042
#ifdef _XMPP_CAPABILITIES_H
21-
[xmppStream autoAddDelegate:self delegateQueue:moduleQueue toModulesOfClass:[XMPPCapabilities class]];
43+
[xmppStream autoAddDelegate:self
44+
delegateQueue:moduleQueue
45+
toModulesOfClass:[XMPPCapabilities class]];
2246
#endif
2347
return YES;
2448
}
@@ -28,6 +52,18 @@ - (BOOL)activate:(XMPPStream *)aXmppStream
2852

2953
- (void)deactivate
3054
{
55+
XMPPLogTrace();
56+
57+
dispatch_block_t block = ^{ @autoreleasepool {
58+
[xmppIDTracker removeAllIDs];
59+
xmppIDTracker = nil;
60+
}};
61+
62+
if (dispatch_get_specific(moduleQueueTag))
63+
block();
64+
else
65+
dispatch_sync(moduleQueue, block);
66+
3167
#ifdef _XMPP_CAPABILITIES_H
3268
[xmppStream removeAutoDelegate:self delegateQueue:moduleQueue fromModulesOfClass:[XMPPCapabilities class]];
3369
#endif
@@ -73,6 +109,175 @@ - (BOOL)isMUCRoomMessage:(XMPPMessage *)message
73109
return [self isMUCRoomElement:message];
74110
}
75111

112+
/**
113+
* This method provides functionality of XEP-0045 6.1 Discovering a MUC Service.
114+
*
115+
* @link {http://xmpp.org/extensions/xep-0045.html#disco-service}
116+
*
117+
* Example 1. Entity Queries Server for Associated Services
118+
*
119+
* <iq from='[email protected]/pda'
120+
* id='h7ns81g'
121+
* to='shakespeare.lit'
122+
* type='get'>
123+
* <query xmlns='http://jabber.org/protocol/disco#items'/>
124+
* </iq>
125+
*/
126+
- (void)discoverServices
127+
{
128+
// This is a public method, so it may be invoked on any thread/queue.
129+
130+
dispatch_block_t block = ^{ @autoreleasepool {
131+
if (hasRequestedServices) return; // We've already requested services
132+
133+
NSString *toStr = xmppStream.myJID.domain;
134+
NSXMLElement *query = [NSXMLElement elementWithName:@"query"
135+
xmlns:XMPPDiscoverItemsNamespace];
136+
XMPPIQ *iq = [XMPPIQ iqWithType:@"get"
137+
to:[XMPPJID jidWithString:toStr]
138+
elementID:[xmppStream generateUUID]
139+
child:query];
140+
141+
[xmppIDTracker addElement:iq
142+
target:self
143+
selector:@selector(handleDiscoverServicesQueryIQ:withInfo:)
144+
timeout:60];
145+
146+
[xmppStream sendElement:iq];
147+
hasRequestedServices = YES;
148+
}};
149+
150+
if (dispatch_get_specific(moduleQueueTag))
151+
block();
152+
else
153+
dispatch_async(moduleQueue, block);
154+
}
155+
156+
/**
157+
* This method provides functionality of XEP-0045 6.3 Discovering Rooms
158+
*
159+
* @link {http://xmpp.org/extensions/xep-0045.html#disco-rooms}
160+
*
161+
* Example 5. Entity Queries Chat Service for Rooms
162+
*
163+
* <iq from='[email protected]/pda'
164+
* id='zb8q41f4'
165+
* to='chat.shakespeare.lit'
166+
* type='get'>
167+
* <query xmlns='http://jabber.org/protocol/disco#items'/>
168+
* </iq>
169+
*/
170+
- (BOOL)discoverRoomsForServiceNamed:(NSString *)serviceName
171+
{
172+
// This is a public method, so it may be invoked on any thread/queue.
173+
174+
if (serviceName.length < 2)
175+
return NO;
176+
177+
dispatch_block_t block = ^{ @autoreleasepool {
178+
if (hasRequestedRooms) return; // We've already requested rooms
179+
180+
NSXMLElement *query = [NSXMLElement elementWithName:@"query"
181+
xmlns:XMPPDiscoverItemsNamespace];
182+
XMPPIQ *iq = [XMPPIQ iqWithType:@"get"
183+
to:[XMPPJID jidWithString:serviceName]
184+
elementID:[xmppStream generateUUID]
185+
child:query];
186+
187+
[xmppIDTracker addElement:iq
188+
target:self
189+
selector:@selector(handleDiscoverRoomsQueryIQ:withInfo:)
190+
timeout:60];
191+
192+
[xmppStream sendElement:iq];
193+
hasRequestedRooms = YES;
194+
}};
195+
196+
if (dispatch_get_specific(moduleQueueTag))
197+
block();
198+
else
199+
dispatch_async(moduleQueue, block);
200+
201+
return YES;
202+
}
203+
204+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
205+
#pragma mark XMPPIDTracker
206+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
207+
208+
/**
209+
* This method handles the response received (or not received) after calling discoverServices.
210+
*/
211+
- (void)handleDiscoverServicesQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info
212+
{
213+
dispatch_block_t block = ^{ @autoreleasepool {
214+
NSXMLElement *errorElem = [iq elementForName:@"error"];
215+
216+
if (errorElem) {
217+
NSString *errMsg = [errorElem.children componentsJoinedByString:@", "];
218+
NSDictionary *dict = @{NSLocalizedDescriptionKey : errMsg};
219+
NSError *error = [NSError errorWithDomain:XMPPMUCErrorDomain
220+
code:[errorElem attributeIntegerValueForName:@"code"
221+
withDefaultValue:0]
222+
userInfo:dict];
223+
224+
[multicastDelegate xmppMUCFailedToDiscoverServices:self
225+
withError:error];
226+
return;
227+
}
228+
229+
NSXMLElement *query = [iq elementForName:@"query"
230+
xmlns:XMPPDiscoverItemsNamespace];
231+
232+
NSArray *items = [query elementsForName:@"item"];
233+
[multicastDelegate xmppMUC:self didDiscoverServices:items];
234+
hasRequestedServices = NO; // Set this back to NO to allow for future requests
235+
}};
236+
237+
if (dispatch_get_specific(moduleQueueTag))
238+
block();
239+
else
240+
dispatch_async(moduleQueue, block);
241+
}
242+
243+
/**
244+
* This method handles the response received (or not received) after calling discoverRoomsForServiceNamed:.
245+
*/
246+
- (void)handleDiscoverRoomsQueryIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)info
247+
{
248+
dispatch_block_t block = ^{ @autoreleasepool {
249+
NSXMLElement *errorElem = [iq elementForName:@"error"];
250+
NSString *serviceName = [iq attributeStringValueForName:@"from" withDefaultValue:@""];
251+
252+
if (errorElem) {
253+
NSString *errMsg = [errorElem.children componentsJoinedByString:@", "];
254+
NSDictionary *dict = @{NSLocalizedDescriptionKey : errMsg};
255+
NSError *error = [NSError errorWithDomain:XMPPMUCErrorDomain
256+
code:[errorElem attributeIntegerValueForName:@"code"
257+
withDefaultValue:0]
258+
userInfo:dict];
259+
[multicastDelegate xmppMUC:self
260+
failedToDiscoverRoomsForServiceNamed:serviceName
261+
withError:error];
262+
return;
263+
}
264+
265+
NSXMLElement *query = [iq elementForName:@"query"
266+
xmlns:XMPPDiscoverItemsNamespace];
267+
268+
NSArray *items = [query elementsForName:@"item"];
269+
[multicastDelegate xmppMUC:self
270+
didDiscoverRooms:items
271+
forServiceNamed:serviceName];
272+
hasRequestedRooms = NO; // Set this back to NO to allow for future requests
273+
}};
274+
275+
if (dispatch_get_specific(moduleQueueTag))
276+
block();
277+
else
278+
dispatch_async(moduleQueue, block);
279+
}
280+
76281
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
77282
#pragma mark XMPPStream Delegate
78283
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -182,6 +387,18 @@ - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
182387
}
183388
}
184389

390+
- (BOOL)xmppStream:(XMPPStream *)stream didReceiveIQ:(XMPPIQ *)iq
391+
{
392+
NSString *type = [iq type];
393+
394+
if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"]) {
395+
return [xmppIDTracker invokeForElement:iq withObject:iq];
396+
}
397+
398+
return NO;
399+
}
400+
401+
185402
#ifdef _XMPP_CAPABILITIES_H
186403
/**
187404
* If an XMPPCapabilites instance is used we want to advertise our support for MUC.

0 commit comments

Comments
 (0)