7

My code to create a room:

XMPPRoomCoreDataStorage *rosterstorage = [[XMPPRoomCoreDataStorage alloc] init];
XMPPRoom *xmppRoom = [[XMPPRoom alloc] initWithRoomStorage:rosterstorage jid:[XMPPJID jidWithString:@"groupchat@xmpp.getkismet.com/groupchat"] dispatchQueue:dispatch_get_main_queue()];

[xmppRoom activate:[[self appDelegate] xmppStream]];
if ([xmppRoom preJoinWithNickname:@"nameToCreateRoom"]) 
{
    NSLog(@"room created");
    [xmppRoom joinRoomUsingNickname:self.userName history:nil];
}
[xmppRoom fetchConfigurationForm];
[xmppRoom configureRoomUsingOptions:nil];
[xmppRoom addDelegate:[self appDelegate] delegateQueue:dispatch_get_main_queue()];

Debug:

2012-08-03 07:46:29.204 iPhoneXMPP[9887:fb03] room created
2012-08-03 07:46:29:230 iPhoneXMPP[9887:15003] SEND: <iq type="get" to="groupchat@xmpp.getkismet.com" id="B793062B-0E09-492F-BC0F-703503AAA664"><query xmlns="http://jabber.org/protocol/muc#owner"/></iq>
2012-08-03 07:46:29:237 iPhoneXMPP[9887:15003] SEND: <iq type="set" to="groupchat@xmpp.getkismet.com" id="392D5BFC-707B-4F68-A829-56F949F4E96D"><query xmlns="http://jabber.org/protocol/muc#owner"><x xmlns="jabber:x:data" type="submit"/></query></iq>
2012-08-03 07:46:29:326 iPhoneXMPP[9887:14f03] SEND: <presence to="groupchat@xmpp.getkismet.com"><x xmlns="http://jabber.org/protocol/muc"/><x xmlns="vcard-temp:x:update"><photo>91217a961321f8f6380ea2feefd0632353ad296c</photo></x><c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="http://code.google.com/p/xmppframework" ver="VyOFcFX6+YNmKssVXSBKGFP0BS4="/></presence>
2012-08-03 07:46:29:327 iPhoneXMPP[9887:14f03] RECV: <iq xmlns="jabber:client" from="groupchat@xmpp.getkismet.com" to="lee@xmpp.getkismet.com/41068195801343976386548353" type="error" id="B793062B-0E09-492F-BC0F-703503AAA664"><query xmlns="http://jabber.org/protocol/muc#owner"/><error code="503" type="cancel"><service-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>
2012-08-03 07:46:29:343 iPhoneXMPP[9887:fb03] iPhoneXMPPAppDelegate: xmppStream:didReceiveIQ:
2012-08-03 07:46:29:421 iPhoneXMPP[9887:15003] RECV: <iq xmlns="jabber:client" from="groupchat@xmpp.getkismet.com" to="lee@xmpp.getkismet.com/41068195801343976386548353" type="error" id="392D5BFC-707B-4F68-A829-56F949F4E96D"><query xmlns="http://jabber.org/protocol/muc#owner"><x xmlns="jabber:x:data" type="submit"/></query><error code="503" type="cancel"><service-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>
2012-08-03 07:46:29:440 iPhoneXMPP[9887:fb03] iPhoneXMPPAppDelegate: xmppStream:didReceiveIQ:

I see that it is creating/joining groupchat@xmpp.getkismet.com and not groupchat@xmpp.getkismet.com/groupchat like I specified. I read that this is most likely the problem. However, I have specified for the full jid, so I'm lost.

Thanks in advance to all who help.

user1561639
  • 71
  • 1
  • 3

1 Answers1

26

First, take look here XEP-0045: Multi-User Chat.
As you can see, first you have to discover which capabilities your user (XMPPJID) has on the Jabber server.

To do this, send next command to your Jabber Server:

<iq from='user@jabber.server.com/resource' id='some_expression' to='jabber.server.com' type='get'>
    <query xmlns='http://jabber.org/protocol/disco#items'/>
</iq>

or writen in objective-c using XMPP library functions:

NSError *error = nil;
NSXMLElement *query = [[NSXMLElement alloc] initWithXMLString:@"<query xmlns='http://jabber.org/protocol/disco#items'/>" 
                                                        error:&error];
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" 
                             to:[XMPPJID jidWithString:@"jabber.server.com"] 
                      elementID:[xmppStream generateUUID] child:query];
[xmppStream sendElement:iq];

Now listen response from server in XMPPStream delegate - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq and server response should be something like this:

<iq from='jabber.server.com' id='some_expression' to='user@jabber.server.com/resource' type='result'>
    <query xmlns='http://jabber.org/protocol/disco#items'>
        <item jid='im.jabber.server.com' name='Instant Message Service'/>
        <item jid='conference.jabber.server.com' name='Chatroom Service'/>
    </query>
</iq>

or objective c:

- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{

    if([iq isResultIQ])
    {
        if([iq elementForName:@"query" xmlns:@"http://jabber.org/protocol/disco#items"])
        {
            NSLog(@"Jabber Server's Capabilities: %@", [iq XMLString]);
        }
    }
}

Now for every item returned send IQ to your server for it's properties and figure out which one is type of conference, something like this:

<iq from='user@jabber.server.com/resource' id='some_expression' to='conference.jabber.server.com' type='get'>
    <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>

or in objective c:

- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{

    if([iq isResultIQ])
    {
        if([iq elementForName:@"query" xmlns:@"http://jabber.org/protocol/disco#items"])
        {
            NSXMLElement *query = [iq childElement];
            NSArray *items = [query children];
            for(NSXMLElement *item in items)
            {
                NSError *error = nil;
                NSXMLElement *sendQuery = [[NSXMLElement alloc] initWithXMLString:@"<query xmlns='http://jabber.org/protocol/disco#info'/>" 
                                                                            error:&error];
                XMPPIQ *sendIQ = [XMPPIQ iqWithType:@"get" 
                                                 to:[XMPPJID jidWithString:[item attributeStringValueForName:@"jid"]] 
                                          elementID:[xmppStream generateUUID] 
                                              child:sendQuery];
                [xmppStream sendElement:sendIQ];
            }
        }
    }
}

Listen for responses from server:

<iq from='conference.jabber.server.com' id='some_expression' to='user@jabber.server.com/resource' type='result'>
    <query xmlns='http://jabber.org/protocol/disco#info'>
        <identity category='conference' name='Server Group Chat Service' type='text'/>
        <feature var='http://jabber.org/protocol/muc'/>
    </query>
</iq>

and take group chat domain from identity with category:conference

- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{

    if([iq isResultIQ])
    {
        if([iq elementForName:@"query" xmlns:@"http://jabber.org/protocol/disco#items"])
        {
            ...
        }
        else if([iq elementForName:@"query" xmlns:@"http://jabber.org/protocol/disco#info"])
        {
            NSXMLElement *query = [iq childElement];
            NSXMLElement *identity = [query elementForName:@"identity"];
            if([[identity attributeStringValueForName:@"category"] isEqualToString:@"conference"])
            {
                groupChatDomain = [iq fromStr];
            }
        }
    }
}

Finally, when we got group chat domain we can create chat room something like this:

XMPPJID *chatRoomJID = [XMPPJID jidWithUser:@"chat_room" 
                                     domain:groupChatDomain 
                                   resource:@"user"];
XMPPRoomMemoryStorage *roomMemoryStorage = [[XMPPRoomMemoryStorage alloc] init];
XMPPRoom *xmppRoom = [[XMPPRoom alloc] initWithRoomStorage:roomMemoryStorage
                                                       jid:roomChatJID
                                             dispatchQueue:dispatch_get_main_queue()];
[xmppRoom activate:xmppStream];
[xmppRoom addDelegate:self delegateQueue:dispatch_get_main_queue()];
[xmppRoom joinRoomUsingNickname:user history:nil];

and add <XMPPRoomDelegate> protocol in your view controller and its delegates:

- (void)xmppRoomDidCreate:(XMPPRoom *)sender
- (void)xmppRoomDidDestroy:(XMPPRoom *)sender
- (void)xmppRoom:(XMPPRoom *)sender didConfigure:(XMPPIQ *)iqResult
- (void)xmppRoom:(XMPPRoom *)sender didNotConfigure:(XMPPIQ *)iqResult
- (void)xmppRoomDidJoin:(XMPPRoom *)sender
- (void)xmppRoomDidLeave:(XMPPRoom *)sender
- (void)xmppRoom:(XMPPRoom *)sender occupantDidJoin:(XMPPJID *)occupantJID withPresence:(XMPPPresence *)presence
- (void)xmppRoom:(XMPPRoom *)sender occupantDidLeave:(XMPPJID *)occupantJID withPresence:(XMPPPresence *)presence
- (void)xmppRoom:(XMPPRoom *)sender didReceiveMessage:(XMPPMessage *)message fromOccupant:(XMPPJID *)occupantJID

Note: Before inviting other users to Chat Room, you have to send and confirm room configurations (other users can be invited but messages can not be sent).
So you can do this after Room is created (delegate - (void)xmppRoomDidCreate:(XMPPRoom *)sender is called) or your user has joined (delegate - (void)xmppRoomDidJoin:(XMPPRoom *)sender is called) to Chat Room.

To send and confirm room configuration do one of the following:

- (void)xmppRoomDidCreate:(XMPPRoom *)sender
{
    [sender configureRoomUsingOptions:nil];
}

or

- (void)xmppRoomDidJoin:(XMPPRoom *)sender
{    
    [sender configureRoomUsingOptions:nil];
}

Send nil to accept default options or you can send IQ with syntax as below to your server:

<iq type='set' from='user@jabber.server.com/resource' id='some_expression' to='chat_room@conference.jabber.server.com'>
    <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>My Chat Room</value>
            </field>
              .
              .
              .
        <x>
</query>
</iq>

or objective c code:

NSError *error = nil;
NSXMLElement *query = [[NSXMLElement alloc] initWithXMLString:@"<query xmlns='http://jabber.org/protocol/muc#owner'/>" 
                                                        error:&error];
NSXMLElement *x = [NSXMLElement elementWithName:@"x" 
                                          xmlns:@"jabber:x:data"];
[x addAttributeWithName:@"type" stringValue:@"submit"];
NSXMLElement *field1 = [NSXMLElement elementWithName:@"field"];
[field1 addAttributeWithName:@"var" stringValue:@"FORM_TYPE"];
NSXMLElement *value1 = [NSXMLElement elementWithName:@"value" 
                                         stringValue:@"http://jabber.org/protocol/muc#roomconfig"];
[field1 addChild:value1];

NSXMLElement *field2 = [NSXMLElement elementWithName:@"field"];
[field2 addAttributeWithName:@"var" stringValue:@"muc#roomconfig_roomname"];
NSXMLElement *value2 = [NSXMLElement elementWithName:@"value" 
                                         stringValue:@"My Chat Room"];
[field2 addChild:value2];

//Add other fields you need, just like field1 and field2

[x addChild:field1];
[x addChild:field2];

[query addChild:x];

NSXMLElement *roomOptions = [NSXMLElement elementWithName:@"iq"];
[roomOptions addAttributeWithName:@"type" stringValue:@"set"];
[roomOptions addAttributeWithName:@"id" stringValue:[xmppStream generateUUID];
[roomOptions addAttributeWithName:@"to" stringValue:@"chat_room@conference.jabber.server.com"];

[roomOptions addChild:query];

[sender configureRoomUsingOptions:roomOptions];

and list of all possible Configuration Form fields is here

Sihad Begovic
  • 1,907
  • 1
  • 31
  • 33
  • Thanks Sihad. This is a very detailed and helpful post! – CodeBrew Dec 02 '13 at 04:36
  • Hi Sihad, thanks for the detailed code! i have couple of questions, i have been trying to create a xmpproom but my xmpproom delegates are not firing for some reason, i have my code here http://stackoverflow.com/questions/20765641/ios-xmppframework-creating-xmpproom – DevCali Dec 24 '13 at 20:13
  • 1
    In your view controller .h file did you add , it should be something like this: @interface YourViewController : UIViewController <..., XMPPRoomDelegate> – Sihad Begovic Dec 24 '13 at 23:04
  • Hi , I have successfully implemented the room but somehow have following issues. May be I am looking for similar solution like whatsapp. 1) user don't open the app still how to let them get added to group and send push notification like creator added you to group 2) even when the user don't open app the messages start coming by the time user was added 3) offline messages to come to all users who left the group but still linked to group 4) how to exit group and leave group is same as exit but get member list still gives that user. – codelover Nov 26 '15 at 14:37
  • 5) cannot auto accept user on server for invites as that needs presence available is supposed to be sent but that will flush all his offline messages stored in server loosing his data. Any help is much appreciated on these problems left in my app. – codelover Nov 26 '15 at 14:37
  • I have implemented everything successfully 1) create room 2) configure 3) join room 4) invite 5) send message 6) on going offline and coming back join room automatically using bookmarks logic , planning to try this. – codelover Nov 26 '15 at 14:38
  • Very useful solution. – KSR Feb 17 '17 at 05:54
  • @SihadBegovic... Thanks for this useful solution. At my side, Group has been created but, other members not receiving any indication regarding the new group they need to join. Am I missing something? – Kiran Jasvanee Sep 16 '20 at 04:44