The CAXL Book :: Chapter 4 - Contacts and Presence

4.0 Overview

Now that you know how to log in, you'll certainly be interested in using your session to interact with other people and entities over the XMPP network. In general, the people you want to chat with can be found in your contact list or "roster." In XMPP, the roster also includes the subscription states for your contacts, which enables you to receive information about their network availability or "presence."

The roster contains contacts you are connected with on a semi-permanent basis, and roster-based subscriptions last until the contact is removed from the roster or the subscription is cancelled. However, sometimes you want to interact with people temporarily on an ad-hoc basis. If you are using Cisco WebEx or Cisco Unified Presence, the QuickContacts feature gives you access to presence information without needing to establish a long-lived subscription.

4.1 Roster Management Using RosterController

Once a client has logged in, typically the next thing it does is retrieve the roster. Because the core XMPP specifications recommend retrieving the roster before sending initial presence, we cover presence in the next section.

The roster is just a list of JabberIDs, containing one bare JID for each contact (e.g., "bob@example.com"). The roster also includes information about the presence subscription state for each contact (e.g., "both" for a bidirectional presence subscription), a user-friendly handle (e.g., "Big Bob"), and the roster groups to which a contact belongs (e.g., "Bigwigs").

As you might recall from Chapter 2, CAXL includes a construct for lists of JIDs, called the EntitySet. Thus it comes as no surprise that the roster is represented as an EntitySet in CAXL.

To access the roster, you first instantiate a client and create a roster controller, then connect to the server:

        client = new jabberwerx.Client();
        roster = new jabberwerx.RosterController(client);
        client.connect(bareJID, password, arg);
      

On connect, the client retrieves the roster in the background on your behalf, and the client's EntitySet caches all of the roster contacts (as we'll see, it also caches other sorts of entities, such as QuickContacts and multi-user chat rooms). This makes it easy for your code to access the contacts. One simple way to do so is to walk through the EntitySet using the each() method and then pull out all of the entities that match the jabberwerx.Contact type, as shown in the following example:

        client.entitySet.each(function(entity) {
            //check the entities type, allow only jabberwerx.Contact
            if (entity instanceof jabberwerx.Contact) {
                // do something, like show the contact in a user interface
            }
        });
      

The roster is not a completely static thing: sometimes you will want to add, modify, or delete contacts. The RosterController class provides two methods for these actions: updateContact() and deleteContact().

The deleteContact() method is the simpler of the two: its primary argument is just the JID of the contact you want to delete (it can also take a callback).

        deleteContact(jid);
      

The updateContact() method is a bit more complex, because it allows arguments for the contact's nickname and the roster group or groups to which the contact belongs, in addition to the JID and a callback.

        updateContact(jid,nickname,groups);
      

Note that if the contact does not already exist in the EntitySet, the updateContact() method will add the contact, whereas if the contact already exists then the updateContact() method will modify various information about the contact (e.g., the nickname or groups). Although you can use the entityCreated event to be informed when this specific contact has been added to the roster, that will be only a one-time event. A more sustainable approach is to register the entityCreated, entityUpdated, and entityDestroyed events with the client's EntitySet, then filter out all entities except instances of jabberwerx.Contact. This way you will be informed whenever anything changes with all of your roster contacts, which is important because XMPP allows you to simultaneously log into the same account with multiple devices, and one of those other devices could modify the roster during your session. Registering for all contact-related events enables you to keep in sync with the roster as it is stored on the server.

4.2 Presence

In XMPP, presence and rosters are closely intertwined (read RFC 6121 for all the details). Typically — that is, not including special cases like QuickContacts and directed presence — it is the contacts in your roster to which you send presence and from which you receive presence.

In the following sections, we cover sending your own presence, receiving presence from others, and managing presence subscriptions.

4.2.1 Sending Presence

To send outbound presence to the contacts in your roster, usually you will use the sendPresence() method on the jabberwerx.Client object. This method takes two arguments:

Here is an example:

          client.sendPresence('away','In a WebEx Meeting');
        

When you set presence, your XMPP server will return your own presence to you, which you will learn about if you bind the resourcePresenceChanged event (described in the next section) to the client.connectedUser object (which represents connection information about your account, i.e. your bare JID). This also enables you to listen for presence changes related to other devices that are logged into your same account.

4.2.2 Receiving Presence

The contacts in your roster are continually interacting with the XMPP server: logging in, logging out, sending notifications about taking phone calls, joining voice conferences, being in meetings, running errands, etc. These changes in ability or willingness to communicate are represented in XMPP as inbound presence notifications that you will receive from your contacts.

Because these presence notifications are really a kind of "push" mechanism, they are perfectly suited to listening for events. There are two events in particular that you'll want to use: primaryPresenceChanged and resourcePresenceChanged, both of which are associated with the jabberwerx.Entity class.

Normally, you will bind one of these events on each entity in the EntitySet and define a callback method that is invoked when you are notified about an incoming presence notification for that contact:

          client.entitySet.event("entityCreated", (function(evt) {
              var contact = evt.source;
              contact.event("primaryPresenceChanged", (function(evt) {
                  _handlePresence(evt.data.stanza);
              });
          });
          _handlePresence: function(presence) {
              var show = presence.getShow();
              var status = presence.getStatus();
              var priority = String(presence.getPriority());
              var fromJID = presence.getFromJID();
              var type = presence.getType();
          }
        

As you can see, when getting presence information you can pull out the show value via getShow(), the status message via getStatus(), the priority via getPriority(), or all three. The jabberwerx.Presence object also inherits various methods from jabberwerx.Stanza, such as getFromJID() to determine the sender and getType() to determine (for presence) whether this event indicates that the sender is available or unavailable. Note that in XMPP a presence stanza with no 'type' value is assumed to be available!

4.2.3 Managing Presence Subscriptions

In XMPP, you can't see just anyone's presence information, and random people can't see your presence either. Instead, presence requires authorization: you need to approve of my request to see when you are online, and I need to approve such a request from you. These approvals happen though a kind of two-way "handshake" based on long-lived subscriptions.

The CAXL RosterController provides methods for managing presence subscriptions. These methods abstract away the messy details of the XMPP presence subscription protocol, which has a somewhat complex state chart. Instead, RosterController gives you two simple methods for adding and removing contacts, the very same updateContact() and deleteContact() methods that we've already discussed in the first section of this chapter. These methods generate all the necessary subscription-related stanzas for you.

However, RosterController also gives you access to some of the lower-layer stanzas. For example, you can explicitly call the subscribe() and unsubscribe() methods to send XMPP presence stanzas of type "subscribe" and "unsubscribe" respectively (see RFC 6121 for a complete definition of these actions).

You also require mechanisms for approving and denying incoming subscription requests. Here you'll need to listen for the relevant event, which is subscriptionReceived:

          roster.event("subscriptionReceived"), (function(evt) {
              // Handle the incoming subscription request, probably by showing a dialogue box.
          });
        

Depending on user input, you will call acceptSubscription() or denySubscription(), which send presence stanzas of type "subscribed" and "unsubscribed" respectively (again, see RFC 6121 for details). You can do the same thing with the unsubscriptionReceived event (e.g., to remove contacts who unsubscribe from your presence).

At this point, you might be wondering: do I really want to bother the user about every incoming subscription request? Probably not. For that reason, CAXL enables you to establish general policies for handling subscription-related stanzas from other entities; this is done by setting various flags in the RosterController.

For instance, if the RosterController.autoaccept_in_domain configuration option is set to true, then the client will automatically accept subscription requests when the domain name of the contact's JID matches the domain name of the user. This setting can be very useful in certain scenarios (e.g., enterprise deployments).

          roster = new jabberwerx.RosterController(client);
          roster.autoaccept_in_domain = true;
        

Similarly, if the RosterController.autoaccept configuration option is set to true (which is the default), then the client will automatically accept some subscription requests according to the policies established by one of the following constants:

The following code snippet shows an example of setting a policy of automatically accepting subscription requests from people in our domain, but never automatically accepting subscription requests from outside our domain.

          roster = new jabberwerx.RosterController(client);
          roster.autoaccept_in_domain = true;
          roster.autoaccept = true; // the default, so actually not necessary to set it here
          roster.AUTOACCEPT_NEVER = true;
        

Finally, CAXL includes an autoremove configuration option similar to autoaccept (although without all the additional policies). This option, which defaults to true, removes contacts from the roster if they send a presence stanza of type "unsubscribe", thus helping to prevent "ghost" contacts in the roster who have an XMPP subscription state of "none".

4.3 QuickContacts

The core XMPP model for presence subscriptions is to add contacts to your roster and establish long-lived subscriptions. Cisco WebEx and Cisco Unified Presence also support a temporary subscription ("tempsub") model for use when a long-lived subscription is not needed or appropriate. CAXL provides access to these temporary subscriptions through the QuickContactController class.

To access QuickContacts, you first instantiate a client and create a QuickContact controller, then connect to the server. Note that usually you will also create a roster controller to handle various edge cases (e.g., if you already have a long-lived presence subscription to a contact then you can't to add that person as a QuickContact, as described below).

        client = new jabberwerx.Client();
        var quickcontacts = new jabberwerx.QuickContactController(client);
        var roster = new jabberwerx.RosterController(client);
        client.connect(bareJID, password, arg);
      

The QuickContact feature is implemented using directed presence (see Section 4.6 of RFC 6121) and entity capabilities (see XEP-0115). However, all you need to do is call the subscribe() method to add someone to your QuickContacts (note that it is safest to do this inside a try block in case the JID is invalid):

        function mysubscribe(jid) {
            try {
               quickcontacts.subscribe(jid);
            } catch (ex) {
               alert("Exception thrown by subscribe: " + ex.message)
            }
         mysubscribe("someone@example.com");
      

The contact is now available in the client's general EntitySet:

        var myquickcontact = client.EntitySet.entity("someone@example.com");
      

To track changes to this contact's presence, we can bind the primaryPresenceChanged event to the entity:

        myquickcontact.event("primaryPresenceChanged").bind(function(evt) {
          // do something here, e.g. update the user interface
        }
      

To put an end to a temporary subscription, you can either remove the QuickContact from the EntitySet or unsubscribe:

        myquickcontact.remove(); // the next line does the same thing!
        // quickcontacts.unsubscribe("somejid@example.com");
      

The QuickContactsController also includes two convenience functions for subscribing to and unsubscribing from multiple JIDs at the same time: subscribeAll() and unsubscribeAll(). Both of these helper functions take a list of JIDs as their only argument.

When using the QuickContacts feature, it is important to understand how it interacts with the core XMPP roster. In particular, because QuickContacts supplement roster-based contacts, various QuickContactController methods take account of existing roster contacts:

© 2012 Cisco Systems, Inc. All rights reserved.