diff --git a/docs/CHANGES.md b/docs/CHANGES.md
index 73ae9c237..4098ee9e8 100755
--- a/docs/CHANGES.md
+++ b/docs/CHANGES.md
@@ -16,7 +16,6 @@
* Templates are no longer stored as attributes on the `_converse` object.
If you need a particular template, use `require` to load it.
-- Add Presence Priority Handling [w3host]
- The chat room `description` is now shown in the heading, not the `subject`.
[jcbrand]
- Chat room features are shown in the sidebar. [jcbrand]
@@ -53,6 +52,8 @@
- #366 Show the chat room occupant's JID in the tooltip (if you're allowed to see it). [jcbrand]
- #694 The `notification_option` wasn't being used consistently. [jcbrand]
- #770 Allow setting contact attrs on chats.open [Ape]
+- #785 Add presence priority handling [w3host, jcbrand]
+
## 2.0.6 (2017-02-13)
- Escape user-generated input to prevent JS-injection attacks. (Thanks to SamWhited) [jcbrand]
diff --git a/spec/presence.js b/spec/presence.js
index 4709332df..6332e882b 100644
--- a/spec/presence.js
+++ b/spec/presence.js
@@ -1,3 +1,4 @@
+/*jshint sub:true*/
(function (root, factory) {
define([
"jquery",
@@ -17,6 +18,7 @@
test_utils.openControlBox();
test_utils.createContacts(_converse, 'current'); // Create some contacts so that we can test positioning
var contact_jid = mock.cur_names[8].replace(/ /g,'.').toLowerCase() + '@localhost';
+ var contact = _converse.roster.get(contact_jid);
var stanza = $(
''+
'');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
- expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('online');
+ expect(contact.get('chat_status')).toBe('online');
+ expect(_.keys(contact.get('resources')).length).toBe(1);
+ expect(contact.get('resources')['c71f218b-0797-4732-8a88-b42cb1d8557a']['priority']).toBe(1);
+ expect(contact.get('resources')['c71f218b-0797-4732-8a88-b42cb1d8557a']['status']).toBe('online');
stanza = $(
'');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('online');
+ expect(_.keys(contact.get('resources')).length).toBe(2);
+ expect(contact.get('resources')['c71f218b-0797-4732-8a88-b42cb1d8557a']['priority']).toBe(1);
+ expect(contact.get('resources')['c71f218b-0797-4732-8a88-b42cb1d8557a']['status']).toBe('online');
+ expect(contact.get('resources')['androidkhydmcKW']['priority']).toBe(0);
+ expect(contact.get('resources')['androidkhydmcKW']['status']).toBe('xa');
+
+ stanza = $(
+ ''+
+ '');
+ _converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
+ expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('xa');
+ expect(_.keys(contact.get('resources')).length).toBe(1);
+ expect(contact.get('resources')['androidkhydmcKW']['priority']).toBe(0);
+ expect(contact.get('resources')['androidkhydmcKW']['status']).toBe('xa');
+
+ stanza = $(
+ ''+
+ '');
+ _converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
+ expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('offline');
+ expect(_.keys(contact.get('resources')).length).toBe(0);
}));
});
}));
diff --git a/src/converse-core.js b/src/converse-core.js
index 667fa931d..6f6398436 100755
--- a/src/converse-core.js
+++ b/src/converse-core.js
@@ -775,7 +775,7 @@
'fullname': bare_jid,
'chat_status': 'offline',
'user_id': Strophe.getNodeFromJid(jid),
- 'resources': resource ? [resource] : [],
+ 'resources': resource ? {'resource':0} : {},
'groups': [],
'image_type': DEFAULT_IMAGE_TYPE,
'image': DEFAULT_IMAGE,
@@ -855,23 +855,44 @@
return this;
},
+ addResource: function (resource, priority, chat_status) {
+ var resources = this.get('resources');
+ if (!_.isObject(resources)) { resources = {}; }
+ resources[resource] = {
+ 'priority': _.isNaN(Number(priority)) ? 0 : Number(priority),
+ 'status': chat_status
+ };
+ this.set({'resources': resources});
+ return resources;
+ },
+
removeResource: function (resource) {
- var resources = this.get('resources'), idx;
- if (resource) {
- idx = _.indexOf(resources, resource);
- if (idx !== -1) {
- resources.splice(idx, 1);
- this.save({'resources': resources});
+ /* Remove the passed in resource from the contact's resources
+ * map.
+ * Return the amount of resources left over.
+ */
+ var resources = this.get('resources');
+ if (!_.isObject(resources)) {
+ resources = {};
+ } else {
+ delete resources[resource];
+ }
+ this.save({'resources': resources});
+ return _.size(resources);
+ },
+
+ getHighestPriorityStatus: function () {
+ /* Return the chat status assigned to the resource with the
+ * highest priority.
+ */
+ var resources = this.get('resources');
+ if (_.isObject(resources) && _.size(resources)) {
+ var val = _(resources).values().sortBy('priority').get(0);
+ if (!_.isUndefined(val)) {
+ return val.status;
}
}
- else {
- // if there is no resource (resource is null), it probably
- // means that the user is now completely offline. To make sure
- // that there isn't any "ghost" resources left, we empty the array
- this.save({'resources': []});
- return 0;
- }
- return resources.length;
+ return 'offline';
},
removeFromRoster: function (callback) {
@@ -1023,45 +1044,6 @@
return deferred.promise();
},
- addResource: function (bare_jid, resource) {
- var item = this.get(bare_jid),
- resources;
- if (item) {
- resources = item.get('resources');
- if (resources) {
- if (!_.includes(resources, resource)) {
- resources.push(resource);
- item.set({'resources': resources});
- }
- } else {
- item.set({'resources': [resource]});
- }
- }
- },
-
- setPriority: function (bare_jid, resource, priority) {
- var item = this.get(bare_jid),
- stored_priority;
- if (item) {
- stored_priority = item.get('priority');
- if (stored_priority) {
- if (priority >= stored_priority) {
- item.set({'priority': priority});
- item.set({'priority_updated_by': resource});
- return true;
- } else if (resource === item.get('priority_updated_by')) {
- item.set({'priority': priority});
- return true;
- }
- } else {
- item.set({'priority': priority});
- item.set({'priority_updated_by': resource});
- return true;
- }
- }
- return false;
- },
-
subscribeBack: function (bare_jid) {
var contact = this.get(bare_jid);
if (contact instanceof _converse.RosterContact) {
@@ -1237,7 +1219,7 @@
status_message = _.propertyOf(presence.querySelector('status'))('textContent'),
priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0,
contact = this.get(bare_jid);
-
+
if (this.isSelf(bare_jid)) {
if ((_converse.connection.jid !== jid) &&
(presence_type !== 'unavailable') &&
@@ -1267,17 +1249,14 @@
} else if (presence_type === 'subscribe') {
this.handleIncomingSubscription(presence);
} else if (presence_type === 'unavailable' && contact) {
- // update priority to default level
- this.setPriority(bare_jid, resource, 0);
- // Only set the user to offline if there aren't any
- // other resources still available.
- if (contact.removeResource(resource) === 0) {
- contact.save({'chat_status': "offline"});
- }
+ contact.removeResource(resource);
+ contact.save({'chat_status': contact.getHighestPriorityStatus()});
} else if (contact) { // presence_type is undefined
- this.addResource(bare_jid, resource);
- if (this.setPriority(bare_jid, resource, priority)) {
- contact.save({'chat_status': chat_status});
+ var resources = contact.addResource(resource, priority, chat_status);
+ if (priority >= _(resources).values().map('priority').max()) {
+ // Only save if it's the resource with the highest
+ // priority
+ contact.save({'chat_status': chat_status});
}
}
}