From 789654d54e7a8c80b35934f526e05ee7802c790f Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 20 Feb 2017 22:27:34 +0100 Subject: [PATCH] Updates #785 and #787 Improve upon the previous implementation. If the resource with the highest priority goes offline or becomes unavailable, then the chat status of the contact must fall back to that of the resource with the next highest priority. In your example from #785, if the resource with priority 1 goes offline or becomes unavailable, then in your implementation the chat status would stay at online, although it must actually go to xa. The solution is to update the resources attribute on the contact to not just be an array or strings, but instead to be a map of resources to priorities and statuses and to use that data structure. --- docs/CHANGES.md | 3 +- spec/presence.js | 34 +++++++++++++- src/converse-core.js | 109 +++++++++++++++++-------------------------- 3 files changed, 79 insertions(+), 67 deletions(-) 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}); } } }