docs.ejabberd.im

MucSub: Multi-User Chat Subscriptions


Motivation

In XMPP, Multi-User Chat rooms design rely on presence. To participate in a MUC room, you need to send a presence to the room. When you get disconnected, you leave the room and stopped being part of the room. User involvement in MUC rooms is not permanent.

This is an issue with the rise of mobile applications. Chatting with friends in a room is a big part of messaging usage on mobile. However, to save battery life, mobile devices will freeze mobile XMPP application after a while when they get to background. It means that the connection is lost and that the session is usually terminated.

Some workaround have been used to try letting user keep on receiving messages from MUC room when the app is in background. The most common one is to keep the session open for a while until a timeout happens. This is the approach promoted on mobile by XEP-0198 - Stream Management. When messages are received and no TCP/IP connection is attached, server usually fallback sending the message to the user using mobile push notification service to warn the user that a message has been received.

This approach has many drawbacks:

  1. It is not permanent. The idea of having the session kept into memory for a while is interesting but it is just a long timeout. After that timeout, the session is closed and the user will leave the room. No message will be received anymore.
  2. It does not play well with normal server / cluster operations. If you restart the service where the user session is kept, it will disappear. You can dump them to disk and recreate them on start, but it means that if the node crashes, your session will be lost and user will stop receiving messages.
  3. It does not change the fundamental nature of MUC chat room. They are still presence-based. It means that if you need to restart the MUC service, or if it crashes, presence are lost. For connected clients, they are expected to join the MUC room again. However, for mobile clients, it cannot happens until user reopens the app. Moreover, it means that on new session start, user client is expected to join all the MUC rooms they want to keep track of on connect.

This specification tries to solve those issues by keeping most of the logic of the MUC room intact. There is attempt to rewrite XMPP Multi-User chat rooms by splitting presence from ability to receive and send messages (XEP-0369: Mediated Information eXchange (MIX)). However, the features covered by the MUC protocol are quite comprehensive and the MIX protocol is not yet ready to cover all the MUC use cases yet. The goal is to produce an intermediate state that is compliant with MUC and leverage most of the MUC features, while adding the most basic feature possible to implement the MUC/Sub extension.

This specifications tries to merge ideas to produce a MUC extension that make MUC rooms mobile clients friendly.

To play well with mobile, MUC room need to support the following features:

  • Add the ability to send and receive messages to a room without having to send presence to the room. More generally allow other type of interaction with the room (like configuration changes for example or kick and ban). We will leverage existing publish and subscribe approach.
  • Add the ability to resync the client for missed messages on reconnect. We will leverage Message Archive Management service for MUC.
  • Finally, ensure that a server can implement push notification service to ensure alerting of offline users when MUC messages are received.

The goal is to learn from real life working implementation to help feeding MIX with feedback from the field, without having to reinvent a complete new protocol.

General principle

The core idea is to expose MUC rooms as PubSub nodes and to introduce the concept of MUC rooms subscribers.

A user affiliated to a MUC room should be able to subscribe to MUC node events and have them routed to their JID, even if they are not a participant in the room. It means that a user can receive messages without having to send presence to the room. In that sense, "joining the room" in XEP-0045 becomes more "Being available in the MUC room".

Discovering support

Discovering support on MUC service

You can check if MUC/Sub feature is available on MUC service by sending Disco Info IQ:

<iq from='hag66@shakespeare.example/pda'
    to='muc.shakespeare.example'
    type='get'
    id='ik3vs715'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>

MUC service will show a feature of type 'urn:xmpp:mucsub:0' to the response if the feature is supported and enabled:

<iq from="muc.shakespeare.example"
    to="hag66@shakespeare.example/pda"
    type="result"
    id="ik3vs715">
  <query xmlns="http://jabber.org/protocol/disco#info">
    <identity category="conference"
              type="text"
              name="Chatrooms" />
    ...
    <feature var="urn:xmpp:mucsub:0" />
    ...
  </query>
</iq>

Discovering support on a specific MUC

A user can discover support for MUC/Sub feature on a MUC room as follow:

<iq from='hag66@shakespeare.example/pda'
    to='coven@muc.shakespeare.example'
    type='get'
    id='ik3vs715'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>

A conference MUST add 'urn:xmpp:mucsub:0' to the response if the feature is supported and enabled:

<iq from='coven@muc.shakespeare.example'
    to='hag66@shakespeare.example/pda'
    type='result'
    id='ik3vs715'>
  <query xmlns='http://jabber.org/protocol/disco#info'>
    <identity category='conference'
              name='A Dark Cave'
              type='text' />
    <feature var='http://jabber.org/protocol/muc' />
    ...
    <feature var='urn:xmpp:mucsub:0' />
    ...
  </query>
</iq>

Option MUC room support for subscriptions

Even if MUC room supports MUC/Sub feature, it MAY be explicitly enabled or disabled thanks to a new configuration option:

  • Allow subscription: Users can subscribe to MUC/Sub events.

Subscriber role

Until a subscriber is not joined a conference (see Joining a MUC Room), a subscriber role MUST be 'none'. When a subscriber is joined a conference its role is changed according to XEP-0045 rules, that is, it becomes either 'visitor', 'participant' or 'moderator'.

Subscribing to MUC/Sub events

User can subscribe to the following events, by subscribing to specific nodes:

  • urn:xmpp:mucsub:nodes:presence
  • urn:xmpp:mucsub:nodes:messages
  • urn:xmpp:mucsub:nodes:affiliations
  • urn:xmpp:mucsub:nodes:subscribers
  • urn:xmpp:mucsub:nodes:config
  • urn:xmpp:mucsub:nodes:subject
  • urn:xmpp:mucsub:nodes:system

Example: User Subscribes to MUC/Sub events

<iq from='hag66@shakespeare.example'
    to='coven@muc.shakespeare.example'
    type='set'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7'>
  <subscribe xmlns='urn:xmpp:mucsub:0'
             nick='mynick'
             password='roompassword'>
    <event node='urn:xmpp:mucsub:nodes:messages' />
    <event node='urn:xmpp:mucsub:nodes:affiliations' />
    <event node='urn:xmpp:mucsub:nodes:subject' />
    <event node='urn:xmpp:mucsub:nodes:config' />
  </subscribe>
</iq>

If user is allowed to subscribe, server replies with success. The password attribute can be provided when subscribing to a password-protected room.

Example: Server replies with success

<iq from='coven@muc.shakespeare.example'
    to='hag66@shakespeare.example'
    type='result'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7'>
  <subscribe xmlns='urn:xmpp:mucsub:0'>
    <event node='urn:xmpp:mucsub:nodes:messages' />
    <event node='urn:xmpp:mucsub:nodes:affiliations' />
    <event node='urn:xmpp:mucsub:nodes:subject' />
    <event node='urn:xmpp:mucsub:nodes:config' />
  </subscribe>
</iq>

Subscription is associated with a nick. It will implicitly register the nick. Server should otherwise make sure that subscription match the user registered nickname in that room. In order to change the nick and/or subscription nodes, the same request MUST be sent with a different nick or nodes information.

Example: User changes subscription data

<iq from='hag66@shakespeare.example'
    to='coven@muc.shakespeare.example'
    type='set'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7'>
  <subscribe xmlns='urn:xmpp:mucsub:0'
             nick='newnick'>
    <event node='urn:xmpp:mucsub:nodes:messages' />
    <event node='urn:xmpp:mucsub:nodes:presence' />
  </subscribe>
</iq>

A room moderator can subscribe another user to MUC Room events by providing the user JID as an attribute in the <subscribe/> element.

Example: Room moderator subscribes another user

<iq from='king@shakespeare.example'
    to='coven@muc.shakespeare.example'
    type='set'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7'>
  <subscribe xmlns='urn:xmpp:mucsub:0'
             jid='hag66@shakespeare.example'
             nick='mynick'
             password='roompassword'>
    <event node='urn:xmpp:mucsub:nodes:messages' />
    <event node='urn:xmpp:mucsub:nodes:affiliations' />
    <event node='urn:xmpp:mucsub:nodes:subject' />
    <event node='urn:xmpp:mucsub:nodes:config' />
  </subscribe>
</iq>

Unsubscribing from a MUC Room

At any time a user can unsubscribe from MUC Room events.

Example: User unsubscribes from a MUC Room

<iq from='hag66@shakespeare.example'
    to='coven@muc.shakespeare.example'
    type='set'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7'>
  <unsubscribe xmlns='urn:xmpp:mucsub:0' />
</iq>

Example: A MUC Room responds to unsubscribe request

<iq from='hag66@shakespeare.example'
    to='coven@muc.shakespeare.example'
    type='result'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7' />

A room moderator can unsubscribe another room user from MUC Room events by providing the user JID as an attribute in the <unsubscribe/> element.

Example: Room moderator unsubscribes another room user

<iq from='king@shakespeare.example'
    to='coven@muc.shakespeare.example'
    type='set'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7'>
  <unsubscribe xmlns='urn:xmpp:mucsub:0'
               jid='hag66@shakespeare.example'/>
</iq>

Subscriber actions

If not stated otherwise in this document, a subscriber MUST perform any actions in the conference as described in XEP-0045. For example, it MUST send messages to all occupants according to 7.4 Sending a Message to All Occupants, it MUST configure a conference according to 10.2 Subsequent Room Configuration and so on.

Here are a few examples:

Sending a message

Sending a message is like sending a standard groupchat message in MUC room:

<message from="hag66@shakespeare.example"
         to="coven@muc.shakespeare.example"
         type="groupchat">
  <body>Test</body>
</message>

No need to join it after you connect. As a subscriber, you can send messages at any time.

Joining a MUC Room

If a user wants to be present in the room, they just have to join the room as defined in XEP-0045.

A subscriber MAY decide to join a conference (in the XEP-0045 sense). In this case a conference MUST behave as described in XEP-0045 7.2 Entering a Room. A conference MUST process events as described under XEP-0045 7.1 Order of Events except it MUST not send room history. When a subscriber is joined, a conference MUST stop sending subscription events and MUST switch to a regular groupchat protocol (as described in XEP-0045) until a subscriber leaves.

Receiving events

Here is as an example message received by a subscriber when a message is posted to a MUC room when subscriber is subscribed to node urn:xmpp:mucsub:nodes:messages:

<message from="coven@muc.shakespeare.example"
         to="hag66@shakespeare.example/pda">
  <event xmlns="http://jabber.org/protocol/pubsub#event">
    <items node="urn:xmpp:mucsub:nodes:messages">
      <item id="18277869892147515942">
        <message from="coven@muc.shakespeare.example/secondwitch"
                 to="hag66@shakespeare.example/pda"
                 type="groupchat"
                 xmlns="jabber:client">
          <archived xmlns="urn:xmpp:mam:tmp"
                    by="muc.shakespeare.example"
                    id="1467896732929849" />
          <stanza-id xmlns="urn:xmpp:sid:0"
                     by="muc.shakespeare.example"
                     id="1467896732929849" />
          <body>Hello from the MUC room !</body>
        </message>
      </item>
    </items>
  </event>
</message>

Presence changes in the MUC room are received wrapped in the same way by subscribers which subscribed to node urn:xmpp:mucsub:nodes:presence:

<message from="coven@muc.shakespeare.example"
         to="hag66@shakespeare.example/pda">
  <event xmlns="http://jabber.org/protocol/pubsub#event">
    <items node="urn:xmpp:mucsub:nodes:presences">
      <item id="8170705750417052518">
        <presence xmlns="jabber:client"
                  from="coven@muc.shakespeare.example/secondwitch"
                  type="unavailable"
                  to="hag66@shakespeare.example/pda">
          <x xmlns="http://jabber.org/protocol/muc#user">
            <item affiliation="none"
                  role="none" />
          </x>
        </presence>
      </item>
    </items>
  </event>
</message>

If subscriber is subscribed to node urn:xmpp:mucsub:nodes:subscribers, message will ne sent for every mucsub subscription change. When a user becomes a subscriber:

<message from="coven@muc.shakespeare.example"
         to="hag66@shakespeare.example/pda">
   <event xmlns="http://jabber.org/protocol/pubsub#event">
     <items node="urn:xmpp:mucsub:nodes:subscribers">
       <item id="17895981155977588737">
         <subscribe xmlns="urn:xmpp:mucsub:0" jid="bob@server.com" 
nick="bob"/>
       </item>
     </items>
   </event>
</message>

When a user lost its subscription:

<message from="coven@muc.shakespeare.example"
         to="hag66@shakespeare.example/pda">
   <event xmlns="http://jabber.org/protocol/pubsub#event">
     <items node="urn:xmpp:mucsub:nodes:subscribers">
       <item id="10776102417321261057">
         <unsubscribe xmlns="urn:xmpp:mucsub:0" jid="bob@server.com" 
nick="bob"/>
       </item>
     </items>
   </event>
</message>

Note: Sometimes jid in subscribe/unsubscribe event may be missing if room is set to anonymous and user is not moderator.

List of subscriptions

A user can query the MUC service to get their list of subscriptions.

Example: User asks for subscriptions list

<iq from='hag66@shakespeare.example'
    to='muc.shakespeare.example'
    type='get'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7'>
  <subscriptions xmlns='urn:xmpp:mucsub:0' />
</iq>

Example: Server replies with subscriptions list

<iq from='muc.shakespeare.example'
    to='hag66@shakespeare.example'
    type='result'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7'>
  <subscriptions xmlns='urn:xmpp:mucsub:0'>
    <subscription jid='coven@muc.shakespeare.example'>
      <event node='urn:xmpp:mucsub:nodes:messages'/>
      <event node='urn:xmpp:mucsub:nodes:affiliations'/>
      <event node='urn:xmpp:mucsub:nodes:subject'/>
      <event node='urn:xmpp:mucsub:nodes:config'/>
    </subscription>
    <subscription jid='chat@muc.shakespeare.example'>
      <event node='urn:xmpp:mucsub:nodes:messages'/>
    </subscription>
  </subscriptions>
</iq>

A room moderator can get the list of subscribers by sending <subscriptions/> request directly to the room JID.

Example: Moderator asks for subscribers list

<iq from='hag66@shakespeare.example'
    to='coven@muc.shakespeare.example'
    type='get'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7'>
  <subscriptions xmlns='urn:xmpp:mucsub:0' />
</iq>

Example: Server replies with subscribers list

<iq from='coven@muc.shakespeare.example'
    to='hag66@shakespeare.example'
    type='result'
    id='E6E10350-76CF-40C6-B91B-1EA08C332FC7'>
  <subscriptions xmlns='urn:xmpp:mucsub:0'>
    <subscription jid='juliet@shakespeare.example'>
      <event node='urn:xmpp:mucsub:nodes:messages'/>
      <event node='urn:xmpp:mucsub:nodes:affiliations'/>
    </subscription>
    <subscription jid='romeo@shakespeare.example'>
      <event node='urn:xmpp:mucsub:nodes:messages'/>
    </subscription>
  </subscriptions>
</iq>

Compliance with existing MUC clients

MUC/Sub approach is compliant with existing MUC service and MUC clients. MUC clients compliant with XEP-0045 will receive messages posted by subscribers. They may not see the user's presence, but it should not be an issue for most clients. Most clients already support receiving messages from users that are not currently in the MUC room through history retrieval.

This approach should also help most clients to support better integration with third-party services posting to MUC room through API (as )

However, a server could choose to send presence on behalf of subscribers when a user joins the room (in the XEP-0045 sense) so that the subscriber will be shown in MUC roster of legacy clients.

Synchronization of MUC messages: Leveraging MAM support

To be friendly with mobile, the MAM service should allow a user to connect and easily resync their history for all MUC subscriptions.

Thanks to ability to get the list of all the existing subscription, client can get a starting point to interact with MAM service to resync history and get the messages that were missed while the user was offline.

If you subscribe to MucSub, MAM will add the message to your own user JID on new messages. You will simply be able to query them using your own JID from the standard MAM service.

It means, you can get all new MUC message in subscribed room thanks to MucSub, with a single query. For example, if you ask for all messages sent since a specific date, the result will contain both normal chat and MucSub messages.

You would only need to query MUC for MAM for rooms for which you do not use MucSub as with MucSub you will be "delivered" each message (in that case, each message is added your MAM archive).

Push support compliance

Subscriptions are compliant with push mechanism. It is supported out of the box when using ProcessOne p1:push implementation (deployed on ejabberd SaaS for example).

More generally, it is straightforward to handle them through ejabberd developer API to implement custom mechanisms.

Subscriptions are delivered to online users. If the user has no active session, the server can choose to broadcast to the user through a push notification.

When a session is opened, if the server detects that the user has not been recently active, or for any other reason, the server can still forward the message to a push notification service to warn the user that new messages are available in a MUC room.