2016-02-24 16:56:39 +01:00

1175 lines
30 KiB
Objective-C

#import "XMPP.h"
#import "XMPPRoom.h"
#import "XMPPIDTracker.h"
#import "XMPPMessage+XEP0045.h"
#import "XMPPLogging.h"
// Log levels: off, error, warn, info, verbose
// Log flags: trace
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
enum XMPPRoomState
{
kXMPPRoomStateNone = 0,
kXMPPRoomStateCreated = 1 << 1,
kXMPPRoomStateJoining = 1 << 3,
kXMPPRoomStateJoined = 1 << 4,
kXMPPRoomStateLeaving = 1 << 5,
};
@interface XMPPRoom ()
// List private methods here
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPRoom
- (id)init
{
// This will cause a crash - it's designed to.
// Only the init methods listed in XMPPRoom.h are supported.
return [self initWithRoomStorage:nil jid:nil dispatchQueue:NULL];
}
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
{
// This will cause a crash - it's designed to.
// Only the init methods listed in XMPPRoom.h are supported.
return [self initWithRoomStorage:nil jid:nil dispatchQueue:queue];
}
- (id)initWithRoomStorage:(id <XMPPRoomStorage>)storage jid:(XMPPJID *)aRoomJID
{
return [self initWithRoomStorage:storage jid:aRoomJID dispatchQueue:NULL];
}
- (id)initWithRoomStorage:(id <XMPPRoomStorage>)storage jid:(XMPPJID *)aRoomJID dispatchQueue:(dispatch_queue_t)queue
{
NSParameterAssert(storage != nil);
NSParameterAssert(aRoomJID != nil);
if ((self = [super initWithDispatchQueue:queue]))
{
if ([storage configureWithParent:self queue:moduleQueue])
{
xmppRoomStorage = storage;
}
else
{
XMPPLogError(@"%@: %@ - Unable to configure storage!", THIS_FILE, THIS_METHOD);
}
roomJID = [aRoomJID bareJID];
}
return self;
}
- (BOOL)activate:(XMPPStream *)aXmppStream
{
if ([super activate:aXmppStream])
{
responseTracker = [[XMPPIDTracker alloc] initWithDispatchQueue:moduleQueue];
return YES;
}
return NO;
}
- (void)deactivate
{
XMPPLogTrace();
dispatch_block_t block = ^{ @autoreleasepool {
if (self.isJoined)
{
[self leaveRoom];
}
[responseTracker removeAllIDs];
responseTracker = nil;
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
[super deactivate];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Internal
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method may optionally be used by XMPPRosterStorage classes (method declared in XMPPRosterPrivate.h)
**/
- (dispatch_queue_t)moduleQueue
{
return moduleQueue;
}
/**
* This method may optionally be used by XMPPRosterStorage classes (method declared in XMPPRosterPrivate.h).
**/
- (GCDMulticastDelegate *)multicastDelegate
{
return multicastDelegate;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Properties
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id <XMPPRoomStorage>)xmppRoomStorage
{
// This variable is readonly - set in init method and never changed.
return xmppRoomStorage;
}
- (XMPPJID *)roomJID
{
// This variable is readonly - set in init method and never changed.
return roomJID;
}
- (XMPPJID *)myRoomJID
{
if (dispatch_get_specific(moduleQueueTag))
{
return myRoomJID;
}
else
{
__block XMPPJID *result;
dispatch_sync(moduleQueue, ^{
result = myRoomJID;
});
return result;
}
}
- (NSString *)myNickname
{
if (dispatch_get_specific(moduleQueueTag))
{
return myNickname;
}
else
{
__block NSString *result;
dispatch_sync(moduleQueue, ^{
result = myNickname;
});
return result;
}
}
- (NSString *)roomSubject
{
if (dispatch_get_specific(moduleQueueTag))
{
return roomSubject;
}
else
{
__block NSString *result;
dispatch_sync(moduleQueue, ^{
result = roomSubject;
});
return result;
}
}
- (BOOL)isJoined
{
__block BOOL result = 0;
dispatch_block_t block = ^{
result = (state & kXMPPRoomStateJoined) ? YES : NO;
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
return result;
}
/*
- (BOOL)isRoomOwner
{
__block BOOL result;
dispatch_block_t block = ^{
id <XMPPRoomOccupant> myOccupant = [xmppRoomStorage occupantForJID:myRoomJID stream:xmppStream];
result = [myOccupant.affiliation isEqualToString:@"owner"];
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_sync(moduleQueue, block);
return result;
}
*/
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Create & Join
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)preJoinWithNickname:(NSString *)nickname
{
if ((state != kXMPPRoomStateNone) && (state != kXMPPRoomStateLeaving))
{
XMPPLogWarn(@"%@[%@] - Cannot create/join room when already creating/joining/joined", THIS_FILE, roomJID);
return NO;
}
myNickname = [nickname copy];
myRoomJID = [XMPPJID jidWithUser:[roomJID user] domain:[roomJID domain] resource:myNickname];
return YES;
}
- (void)joinRoomUsingNickname:(NSString *)desiredNickname history:(NSXMLElement *)history
{
[self joinRoomUsingNickname:desiredNickname history:history password:nil];
}
- (void)joinRoomUsingNickname:(NSString *)desiredNickname history:(NSXMLElement *)history password:(NSString *)passwd
{
dispatch_block_t block = ^{ @autoreleasepool {
XMPPLogTrace2(@"%@[%@] - %@", THIS_FILE, roomJID, THIS_METHOD);
// Check state and update variables
if (![self preJoinWithNickname:desiredNickname])
{
return;
}
// <presence to='darkcave@chat.shakespeare.lit/firstwitch'>
// <x xmlns='http://jabber.org/protocol/muc'/>
// <history/>
// <password>passwd</password>
// </x>
// </presence>
NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:XMPPMUCNamespace];
if (history)
{
[x addChild:history];
}
if (passwd)
{
[x addChild:[NSXMLElement elementWithName:@"password" stringValue:passwd]];
}
XMPPPresence *presence = [XMPPPresence presenceWithType:nil to:myRoomJID];
[presence addChild:x];
[xmppStream sendElement:presence];
state |= kXMPPRoomStateJoining;
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Room Configuration
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)handleConfigurationFormResponse:(XMPPIQ *)iq withInfo:(id <XMPPTrackingInfo>)info
{
XMPPLogTrace();
if ([[iq type] isEqualToString:@"result"])
{
// <iq type='result'
// from='coven@chat.shakespeare.lit'
// id='create1'>
// <query xmlns='http://jabber.org/protocol/muc#owner'>
// <x xmlns='jabber:x:data' type='form'>
// <title>Configuration for "coven" Room</title>
// <field type='hidden'
// var='FORM_TYPE'>
// <value>http://jabber.org/protocol/muc#roomconfig</value>
// </field>
// <field label='Natural-Language Room Name'
// type='text-single'
// var='muc#roomconfig_roomname'/>
// <field label='Enable Public Logging?'
// type='boolean'
// var='muc#roomconfig_enablelogging'>
// <value>0</value>
// </field>
// ...
// </x>
// </query>
// </iq>
NSXMLElement *query = [iq elementForName:@"query" xmlns:XMPPMUCOwnerNamespace];
NSXMLElement *x = [query elementForName:@"x" xmlns:@"jabber:x:data"];
[multicastDelegate xmppRoom:self didFetchConfigurationForm:x];
}
}
- (void)fetchConfigurationForm
{
dispatch_block_t block = ^{ @autoreleasepool {
XMPPLogTrace();
// <iq type='get'
// id='config1'
// to='coven@chat.shakespeare.lit'>
// <query xmlns='http://jabber.org/protocol/muc#owner'/>
// </iq>
NSString *fetchID = [xmppStream generateUUID];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCOwnerNamespace];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:roomJID elementID:fetchID child:query];
[xmppStream sendElement:iq];
[responseTracker addID:fetchID
target:self
selector:@selector(handleConfigurationFormResponse:withInfo:)
timeout:60.0];
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)handleConfigureRoomResponse:(XMPPIQ *)iq withInfo:(id <XMPPTrackingInfo>)info
{
XMPPLogTrace();
if ([[iq type] isEqualToString:@"result"])
{
[multicastDelegate xmppRoom:self didConfigure:iq];
}
else
{
[multicastDelegate xmppRoom:self didNotConfigure:iq];
}
}
- (void)configureRoomUsingOptions:(NSXMLElement *)roomConfigForm
{
dispatch_block_t block = ^{ @autoreleasepool {
XMPPLogTrace();
if (roomConfigForm)
{
// Explicit configuration using given form.
//
// <iq type='set'
// id='create2'
// to='coven@chat.shakespeare.lit'>
// <query xmlns='http://jabber.org/protocol/muc#owner'>
// <x xmlns='jabber:x:data' type='submit'>
// <field var='FORM_TYPE'>
// <value>http://jabber.org/protocol/muc#roomconfig</value>
// </field>
// <field var='muc#roomconfig_roomname'>
// <value>A Dark Cave</value>
// </field>
// <field var='muc#roomconfig_enablelogging'>
// <value>0</value>
// </field>
// ...
// </x>
// </query>
// </iq>
NSXMLElement *x = roomConfigForm;
[x addAttributeWithName:@"type" stringValue:@"submit"];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCOwnerNamespace];
[query addChild:x];
NSString *iqID = [xmppStream generateUUID];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:roomJID elementID:iqID child:query];
[xmppStream sendElement:iq];
[responseTracker addID:iqID
target:self
selector:@selector(handleConfigureRoomResponse:withInfo:)
timeout:60.0];
}
else
{
// Default room configuration (as per server settings).
//
// <iq type='set'
// from='crone1@shakespeare.lit/desktop'
// id='create1'
// to='darkcave@chat.shakespeare.lit'>
// <query xmlns='http://jabber.org/protocol/muc#owner'>
// <x xmlns='jabber:x:data' type='submit'/>
// </query>
// </iq>
NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"];
[x addAttributeWithName:@"type" stringValue:@"submit"];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCOwnerNamespace];
[query addChild:x];
NSString *iqID = [xmppStream generateUUID];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:roomJID elementID:iqID child:query];
[xmppStream sendElement:iq];
[responseTracker addID:iqID
target:self
selector:@selector(handleConfigureRoomResponse:withInfo:)
timeout:60.0];
}
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)changeNickname:(NSString *)newNickname
{
myOldNickname = [myNickname copy];
myNickname = [newNickname copy];
myRoomJID = [XMPPJID jidWithUser:[roomJID user] domain:[roomJID domain] resource:myNickname];
XMPPPresence *presence = [XMPPPresence presenceWithType:nil to:myRoomJID];
[xmppStream sendElement:presence];
}
- (void)changeRoomSubject:(NSString *)newRoomSubject
{
NSXMLElement *subject = [NSXMLElement elementWithName:@"subject" stringValue:newRoomSubject];
XMPPMessage *message = [XMPPMessage message];
[message addAttributeWithName:@"from" stringValue:[myRoomJID full]];
[message addChild:subject];
[self sendMessage:message];
}
- (void)handleFetchBanListResponse:(XMPPIQ *)iq withInfo:(id <XMPPTrackingInfo>)info
{
if ([[iq type] isEqualToString:@"result"])
{
// <iq type='result'
// from='southampton@henryv.shakespeare.lit'
// id='ban2'>
// <query xmlns='http://jabber.org/protocol/muc#admin'>
// <item affiliation='outcast' jid='earlofcambridge@shakespeare.lit'>
// <reason>Treason</reason>
// </item>
// </query>
// </iq>
NSXMLElement *query = [iq elementForName:@"query" xmlns:XMPPMUCAdminNamespace];
NSArray *items = [query elementsForName:@"item"];
[multicastDelegate xmppRoom:self didFetchBanList:items];
}
else
{
[multicastDelegate xmppRoom:self didNotFetchBanList:iq];
}
}
- (void)fetchBanList
{
dispatch_block_t block = ^{ @autoreleasepool {
XMPPLogTrace();
// <iq type='get'
// id='ban2'
// to='southampton@henryv.shakespeare.lit'>
// <query xmlns='http://jabber.org/protocol/muc#admin'>
// <item affiliation='outcast'/>
// </query>
// </iq>
NSString *fetchID = [xmppStream generateUUID];
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"affiliation" stringValue:@"outcast"];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCAdminNamespace];
[query addChild:item];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:roomJID elementID:fetchID child:query];
[xmppStream sendElement:iq];
[responseTracker addID:fetchID
target:self
selector:@selector(handleFetchBanListResponse:withInfo:)
timeout:60.0];
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)handleFetchMembersListResponse:(XMPPIQ *)iq withInfo:(id <XMPPTrackingInfo>)info
{
if ([[iq type] isEqualToString:@"result"])
{
// <iq type='result'
// from='coven@chat.shakespeare.lit'
// id='member3'>
// <query xmlns='http://jabber.org/protocol/muc#admin'>
// <item affiliation='member' jid='hag66@shakespeare.lit' nick='thirdwitch' role='participant'/>
// </query>
// </iq>
NSXMLElement *query = [iq elementForName:@"query" xmlns:XMPPMUCAdminNamespace];
NSArray *items = [query elementsForName:@"item"];
[multicastDelegate xmppRoom:self didFetchMembersList:items];
}
else
{
[multicastDelegate xmppRoom:self didNotFetchMembersList:iq];
}
}
- (void)fetchMembersList
{
dispatch_block_t block = ^{ @autoreleasepool {
XMPPLogTrace();
// <iq type='get'
// id='member3'
// to='coven@chat.shakespeare.lit'>
// <query xmlns='http://jabber.org/protocol/muc#admin'>
// <item affiliation='member'/>
// </query>
// </iq>
NSString *fetchID = [xmppStream generateUUID];
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"affiliation" stringValue:@"member"];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCAdminNamespace];
[query addChild:item];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:roomJID elementID:fetchID child:query];
[xmppStream sendElement:iq];
[responseTracker addID:fetchID
target:self
selector:@selector(handleFetchMembersListResponse:withInfo:)
timeout:60.0];
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)handleFetchModeratorsListResponse:(XMPPIQ *)iq withInfo:(id <XMPPTrackingInfo>)info
{
if ([[iq type] isEqualToString:@"result"])
{
// <iq type='result'
// id='mod3'
// to='crone1@shakespeare.lit/desktop'>
// <query xmlns='http://jabber.org/protocol/muc#admin'>
// <item affiliation='member' jid='hag66@shakespeare.lit/pda' nick='thirdwitch' role='moderator'/>
// </query>
// </iq>
NSXMLElement *query = [iq elementForName:@"query" xmlns:XMPPMUCAdminNamespace];
NSArray *items = [query elementsForName:@"item"];
[multicastDelegate xmppRoom:self didFetchModeratorsList:items];
}
else
{
[multicastDelegate xmppRoom:self didNotFetchModeratorsList:iq];
}
}
- (void)fetchModeratorsList
{
dispatch_block_t block = ^{ @autoreleasepool {
// <iq type='get'
// id='mod3'
// to='coven@chat.shakespeare.lit'>
// <query xmlns='http://jabber.org/protocol/muc#admin'>
// <item role='moderator'/>
// </query>
// </iq>
NSString *fetchID = [xmppStream generateUUID];
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
[item addAttributeWithName:@"role" stringValue:@"moderator"];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCAdminNamespace];
[query addChild:item];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:roomJID elementID:fetchID child:query];
[xmppStream sendElement:iq];
[responseTracker addID:fetchID
target:self
selector:@selector(handleFetchModeratorsListResponse:withInfo:)
timeout:60.0];
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)handleEditRoomPrivilegesResponse:(XMPPIQ *)iq withInfo:(id <XMPPTrackingInfo>)info
{
if ([[iq type] isEqualToString:@"result"])
{
[multicastDelegate xmppRoom:self didEditPrivileges:iq];
}
else
{
[multicastDelegate xmppRoom:self didNotEditPrivileges:iq];
}
}
- (NSString *)editRoomPrivileges:(NSArray *)items
{
NSString *iqID = [xmppStream generateUUID];
dispatch_block_t block = ^{ @autoreleasepool {
XMPPLogTrace();
// <iq type='set'
// id='mod4'
// to='coven@chat.shakespeare.lit'>
// <query xmlns='http://jabber.org/protocol/muc#admin'>
// <item jid='hag66@shakespeare.lit/pda' role='participant'/>
// <item jid='hecate@shakespeare.lit/broom' role='moderator'/>
// </query>
// </iq>
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCAdminNamespace];
for (NSXMLElement *item in items)
{
[query addChild:item];
}
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:roomJID elementID:iqID child:query];
[xmppStream sendElement:iq];
[responseTracker addID:iqID
target:self
selector:@selector(handleEditRoomPrivilegesResponse:withInfo:)
timeout:60.0];
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
return iqID;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Leave & Destroy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)leaveRoom
{
dispatch_block_t block = ^{ @autoreleasepool {
XMPPLogTrace();
// <presence type='unavailable' to='darkcave@chat.shakespeare.lit/thirdwitch'/>
XMPPPresence *presence = [XMPPPresence presence];
[presence addAttributeWithName:@"to" stringValue:[myRoomJID full]];
[presence addAttributeWithName:@"type" stringValue:@"unavailable"];
[xmppStream sendElement:presence];
state &= ~kXMPPRoomStateJoining;
state &= ~kXMPPRoomStateJoined;
state |= kXMPPRoomStateLeaving;
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)handleDestroyRoomResponse:(XMPPIQ *)iq withInfo:(id <XMPPTrackingInfo>)info
{
XMPPLogTrace();
if ([iq isResultIQ])
{
[multicastDelegate xmppRoomDidDestroy:self];
}
else
{
[multicastDelegate xmppRoom:self didFailToDestroy:iq];
}
}
- (void)destroyRoom
{
dispatch_block_t block = ^{ @autoreleasepool {
XMPPLogTrace();
// <iq type="set" to="roomName" id="abc123">
// <query xmlns="http://jabber.org/protocol/muc#owner">
// <destroy/>
// </query>
// </iq>
NSXMLElement *destroy = [NSXMLElement elementWithName:@"destroy"];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:XMPPMUCOwnerNamespace];
[query addChild:destroy];
NSString *iqID = [xmppStream generateUUID];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:roomJID elementID:iqID child:query];
[xmppStream sendElement:iq];
[responseTracker addID:iqID
target:self
selector:@selector(handleDestroyRoomResponse:withInfo:)
timeout:60.0];
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Messages
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)inviteUser:(XMPPJID *)jid withMessage:(NSString *)inviteMessageStr
{
dispatch_block_t block = ^{ @autoreleasepool {
XMPPLogTrace();
// <message to='darkcave@chat.shakespeare.lit'>
// <x xmlns='http://jabber.org/protocol/muc#user'>
// <invite to='hecate@shakespeare.lit'>
// <reason>
// Hey Hecate, this is the place for all good witches!
// </reason>
// </invite>
// </x>
// </message>
NSXMLElement *invite = [NSXMLElement elementWithName:@"invite"];
[invite addAttributeWithName:@"to" stringValue:[jid full]];
if ([inviteMessageStr length] > 0)
{
[invite addChild:[NSXMLElement elementWithName:@"reason" stringValue:inviteMessageStr]];
}
NSXMLElement *x = [NSXMLElement elementWithName:@"x" xmlns:XMPPMUCUserNamespace];
[x addChild:invite];
XMPPMessage *message = [XMPPMessage message];
[message addAttributeWithName:@"to" stringValue:[roomJID full]];
[message addChild:x];
[xmppStream sendElement:message];
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)sendMessage:(XMPPMessage *)message
{
dispatch_block_t block = ^{ @autoreleasepool {
XMPPLogTrace();
[message addAttributeWithName:@"to" stringValue:[roomJID full]];
[message addAttributeWithName:@"type" stringValue:@"groupchat"];
[xmppStream sendElement:message];
}};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)sendMessageWithBody:(NSString *)messageBody
{
if ([messageBody length] == 0) return;
NSXMLElement *body = [NSXMLElement elementWithName:@"body" stringValue:messageBody];
XMPPMessage *message = [XMPPMessage message];
[message addChild:body];
[self sendMessage:message];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XMPPStream Delegate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
// This method is invoked on the moduleQueue.
XMPPLogTrace();
state = kXMPPRoomStateNone;
// Auto-rejoin?
}
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{
NSString *type = [iq type];
if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"])
{
return [responseTracker invokeForID:[iq elementID] withObject:iq];
}
return NO;
}
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
{
// This method is invoked on the moduleQueue.
XMPPJID *from = [presence from];
if (![roomJID isEqualToJID:from options:XMPPJIDCompareBare])
{
return; // Stanza isn't for our room
}
XMPPLogTrace();
[xmppRoomStorage handlePresence:presence room:self];
// My presence:
//
// <presence from='coven@chat.shakespeare.lit/thirdwitch'>
// <x xmlns='http://jabber.org/protocol/muc#user'>
// <item affiliation='member' role='participant'/>
// <status code='110'/>
// <status code='210'/>
// </x>
// </presence>
//
//
// Another's presence:
//
// <presence from='coven@chat.shakespeare.lit/firstwitch'>
// <x xmlns='http://jabber.org/protocol/muc#user'>
// <item affiliation='owner' role='moderator'/>
// </x>
// </presence>
NSXMLElement *x = [presence elementForName:@"x" xmlns:XMPPMUCUserNamespace];
// Process status codes.
//
// 110 - Inform user that presence refers to one of its own room occupants.
// 201 - Inform user that a new room has been created.
// 210 - Inform user that service has assigned or modified occupant's roomnick.
// 303 - Inform all occupants of new room nickname.
BOOL isMyPresence = NO;
BOOL didCreateRoom = NO;
BOOL isNicknameChange = NO;
for (NSXMLElement *status in [x elementsForName:@"status"])
{
switch ([status attributeIntValueForName:@"code"])
{
case 110 : isMyPresence = YES; break;
case 201 : didCreateRoom = YES; break;
case 210 :
case 303 : isNicknameChange = YES; break;
}
}
// Extract presence type
NSString *presenceType = [presence type];
BOOL isAvailable = [presenceType isEqualToString:@"available"];
BOOL isUnavailable = [presenceType isEqualToString:@"unavailable"];
// Server's don't always properly send the statusCodes in every situation.
// So we have some extra checks to ensure the boolean variables are correct.
if (didCreateRoom)
{
isMyPresence = YES;
}
if (!isMyPresence)
{
if ([[from resource] isEqualToString:myNickname])
isMyPresence = YES;
}
if (!isMyPresence && isNicknameChange && myOldNickname)
{
if ([[from resource] isEqualToString:myOldNickname]) {
isMyPresence = YES;
myOldNickname = nil;
}
}
XMPPLogVerbose(@"%@[%@] - isMyPresence = %@", THIS_FILE, roomJID, (isMyPresence ? @"YES" : @"NO"));
XMPPLogVerbose(@"%@[%@] - didCreateRoom = %@", THIS_FILE, roomJID, (didCreateRoom ? @"YES" : @"NO"));
XMPPLogVerbose(@"%@[%@] - isNicknameChange = %@", THIS_FILE, roomJID, (isNicknameChange ? @"YES" : @"NO"));
// Process presence
if (didCreateRoom)
{
state |= kXMPPRoomStateCreated;
[multicastDelegate xmppRoomDidCreate:self];
}
if (isMyPresence)
{
if (isAvailable)
{
myRoomJID = from;
myNickname = [from resource];
if (state & kXMPPRoomStateJoining)
{
state &= ~kXMPPRoomStateJoining;
state |= kXMPPRoomStateJoined;
if ([xmppRoomStorage respondsToSelector:@selector(handleDidJoinRoom:withNickname:)])
[xmppRoomStorage handleDidJoinRoom:self withNickname:myNickname];
[multicastDelegate xmppRoomDidJoin:self];
}
}
else if (isUnavailable && !isNicknameChange)
{
state = kXMPPRoomStateNone;
[responseTracker removeAllIDs];
[xmppRoomStorage handleDidLeaveRoom:self];
[multicastDelegate xmppRoomDidLeave:self];
}
}
else
{
if (isAvailable)
{
[multicastDelegate xmppRoom:self occupantDidJoin:from withPresence:presence];
}
else if (isUnavailable)
{
[multicastDelegate xmppRoom:self occupantDidLeave:from withPresence:presence];
}
}
}
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
// This method is invoked on the moduleQueue.
XMPPJID *from = [message from];
if (![roomJID isEqualToJID:from options:XMPPJIDCompareBare])
{
return; // Stanza isn't for our room
}
XMPPLogTrace();
// Is this a message we need to store (a chat message)?
//
// A message to all recipients MUST be of type groupchat.
// A message to an individual recipient would have a <body/>.
BOOL isChatMessage;
if ([from isFull])
isChatMessage = [message isGroupChatMessageWithBody];
else
isChatMessage = [message isMessageWithBody];
if (isChatMessage)
{
[xmppRoomStorage handleIncomingMessage:message room:self];
[multicastDelegate xmppRoom:self didReceiveMessage:message fromOccupant:from];
}
else if ([message isGroupChatMessageWithSubject])
{
roomSubject = [message subject];
}
else
{
// Todo... Handle other types of messages.
}
}
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message
{
// This method is invoked on the moduleQueue.
XMPPJID *to = [message to];
if (![roomJID isEqualToJID:to options:XMPPJIDCompareBare])
{
return; // Stanza isn't for our room
}
XMPPLogTrace();
// Is this a message we need to store (a chat message)?
//
// A message to all recipients MUST be of type groupchat.
// A message to an individual recipient would have a <body/>.
BOOL isChatMessage;
if ([to isFull])
isChatMessage = [message isGroupChatMessageWithBody];
else
isChatMessage = [message isMessageWithBody];
if (isChatMessage)
{
[xmppRoomStorage handleOutgoingMessage:message room:self];
}
}
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
{
// This method is invoked on the moduleQueue.
XMPPLogTrace();
state = kXMPPRoomStateNone;
[responseTracker removeAllIDs];
[xmppRoomStorage handleDidLeaveRoom:self];
[multicastDelegate xmppRoomDidLeave:self];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Class Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ (NSXMLElement *)itemWithAffiliation:(NSString *)affiliation jid:(XMPPJID *)jid
{
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
if (affiliation)
[item addAttributeWithName:@"affiliation" stringValue:affiliation];
if (jid)
[item addAttributeWithName:@"jid" stringValue:[jid full]];
return item;
}
+ (NSXMLElement *)itemWithRole:(NSString *)role jid:(XMPPJID *)jid
{
NSXMLElement *item = [NSXMLElement elementWithName:@"item"];
if (role)
[item addAttributeWithName:@"role" stringValue:role];
if (jid)
[item addAttributeWithName:@"jid" stringValue:[jid full]];
return item;
}
@end