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.
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.
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.
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:
show — This is the value of the XMPP <show/> element, which has four pre-defined values: "away" (the user is away or idle for a short time), "xa" (the user is away for an extended period), "dnd" (the user is busy and does not wish to be disturbed), and "chat" (the user is actively interested in chatting — many clients do not expose this option to the user).
status — This is a user-provided string that provides a longer description about the user's availability state, such as "In a meeting" or "On the phone".
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.
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.
fullJID
(i.e., jabberwerx.JID
) and a presence
(i.e., jabberwerx.Presence
) for the new primary resource. If there is no primary resource (usually because there are no online resources for the bare JID, i.e., because the contact went offline), the presence
is <null>
and the fullJID
is the contact's bare JIDfullJID
is the localpart@domainpart/resourcepart whose availability changed, and the presence
is the presence stanza that triggered the change. Note that the presence stanza might be of type "unavailable", indicating that the resource has gone offline.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!
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:
RosterController.AUTOACCEPT_ALWAYS
tells the client to automatically accept all subscription requests. This is usually not a great idea!RosterController.AUTOACCEPT_IN_ROSTER
tells the client to automatically accept subscription requests if the sender is in the user's roster (e.g., because the user previously sent an outbound subscription request to the contact). This is the default when autoaccept
is set to true.RosterController.AUTOACCEPT_NEVER
tells the client to never automatically accept subscription requests (essentially equivalent to setting the RosterController.autoaccept
configuration option to false).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".
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:
QuickContactController.subscribe()
method on a JID that is in your roster and you receive presence from that JID (because the subscription is of type "to" or "both), the QuickContactController will deny the request with an exception. It is up to the developer using the library to check if the JID is in the roster with a subscription of "to" or "both" before attempting to create a temporary subscription to that JID using QuickContactController.subscribe()
.QuickContactController.subscribe()
method on a JID that is in your roster but you don't receive presence from that JID (i.e., because the JID is an "observer" with a subscription state of "from"), no exception occurs and the existing contact is temporarily updated to indicate that there is also a temporary presence subscription. However, this does not modify the long-lived subscription state, which is still "from".QuickContactController.unsubscribe()
method on an "observer" JID (as defined above), the existing roster contact is updated to indicate that there is no longer a temporary presence subscription. Here again, the long-lived subscription state is not modified.RosterController.subscribe()
method. Note that all temporary subscription data is maintained until a long-lived subscription is actually established.© 2012 Cisco Systems, Inc. All rights reserved.