Commit 789654d5 authored by JC Brand's avatar JC Brand

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.
parent 15d2640c
......@@ -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]
......
/*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 = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
......@@ -30,7 +32,10 @@
' <delay xmlns="urn:xmpp:delay" stamp="2017-02-15T20:26:05Z" from="jabbim.hu"/>'+
'</presence>');
_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 = $(
'<presence xmlns="jabber:client"'+
......@@ -46,6 +51,33 @@
'</presence>');
_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 = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/c71f218b-0797-4732-8a88-b42cb1d8557a">'+
'</presence>');
_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 = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/androidkhydmcKW">'+
'</presence>');
_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);
}));
});
}));
......
......@@ -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];
}
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;
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;
}
}
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});
}
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment