#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 )storage jid:(XMPPJID *)aRoomJID { return [self initWithRoomStorage:storage jid:aRoomJID dispatchQueue:NULL]; } - (id)initWithRoomStorage:(id )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 { // 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 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; } // // // // passwd // // 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 )info { XMPPLogTrace(); if ([[iq type] isEqualToString:@"result"]) { // // // // Configuration for "coven" Room // // http://jabber.org/protocol/muc#roomconfig // // // // 0 // // ... // // // 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(); // // // 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 )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. // // // // // // http://jabber.org/protocol/muc#roomconfig // // // A Dark Cave // // // 0 // // ... // // // 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). // // // // // // 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 )info { if ([[iq type] isEqualToString:@"result"]) { // // // // Treason // // // 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(); // // // // // 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 )info { if ([[iq type] isEqualToString:@"result"]) { // // // // // 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(); // // // // // 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 )info { if ([[iq type] isEqualToString:@"result"]) { // // // // // 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 { // // // // // 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 )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(); // // // // // // 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(); // 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 )info { XMPPLogTrace(); if ([iq isResultIQ]) { [multicastDelegate xmppRoomDidDestroy:self]; } else { [multicastDelegate xmppRoom:self didFailToDestroy:iq]; } } - (void)destroyRoom { dispatch_block_t block = ^{ @autoreleasepool { XMPPLogTrace(); // // // // // 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(); // // // // // Hey Hecate, this is the place for all good witches! // // // // 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: // // // // // // // // // // // Another's 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 . 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 . 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