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}); } } }