Support for XEP-0203 delayed delivery of presence stanzas

This commit is contained in:
JC Brand 2017-02-21 23:25:19 +01:00
parent 40605607e6
commit 17e0e70e4e
3 changed files with 200 additions and 185 deletions

View File

@ -16,6 +16,7 @@
* Templates are no longer stored as attributes on the `_converse` object.
If you need a particular template, use `require` to load it.
- Better support for delayed delivery of presence stanzas (XEP-0203). [jcbrand]
- The chat room `description` is now shown in the heading, not the `subject`.
[jcbrand]
- Chat room features are shown in the sidebar. [jcbrand]

View File

@ -21,174 +21,170 @@
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;
runs(function () {
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' from="'+contact_jid+'/priority-1-resource">'+
' <priority>1</priority>'+
' <c xmlns="http://jabber.org/protocol/caps" hash="sha-1" ext="voice-v1 camera-v1 video-v1"'+
' ver="AcN1/PEN8nq7AHD+9jpxMV4U6YM=" node="http://pidgin.im/"/>'+
' <x xmlns="vcard-temp:x:update">'+
' <photo>ce51d94f7f22b87a21274abb93710b9eb7cc1c65</photo>'+
' </x>'+
' <delay xmlns="urn:xmpp:delay" stamp="2017-02-15T20:26:05Z" from="jabbim.hu"/>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(contact.get('chat_status')).toBe('online');
expect(_.keys(contact.get('resources')).length).toBe(1);
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
var stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' from="'+contact_jid+'/priority-1-resource">'+
' <priority>1</priority>'+
' <c xmlns="http://jabber.org/protocol/caps" hash="sha-1" ext="voice-v1 camera-v1 video-v1"'+
' ver="AcN1/PEN8nq7AHD+9jpxMV4U6YM=" node="http://pidgin.im/"/>'+
' <x xmlns="vcard-temp:x:update">'+
' <photo>ce51d94f7f22b87a21274abb93710b9eb7cc1c65</photo>'+
' </x>'+
' <delay xmlns="urn:xmpp:delay" stamp="2017-02-15T20:26:05Z" from="'+contact_jid+'/priority-1-resource"/>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(contact.get('chat_status')).toBe('online');
expect(_.keys(contact.get('resources')).length).toBe(1);
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' from="'+contact_jid+'/priority-0-resource">'+
' <status/>'+
' <priority>0</priority>'+
' <show>xa</show>'+
' <c xmlns="http://jabber.org/protocol/caps" ver="GyIX/Kpa4ScVmsZCxRBboJlLAYU=" hash="sha-1"'+
' node="http://www.igniterealtime.org/projects/smack/"/>'+
' <delay xmlns="urn:xmpp:delay" stamp="2017-02-15T17:02:24Z" from="jabbim.hu"/>'+
'</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')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' from="'+contact_jid+'/priority-0-resource">'+
' <status/>'+
' <priority>0</priority>'+
' <show>xa</show>'+
' <c xmlns="http://jabber.org/protocol/caps" ver="GyIX/Kpa4ScVmsZCxRBboJlLAYU=" hash="sha-1"'+
' node="http://www.igniterealtime.org/projects/smack/"/>'+
' <delay xmlns="urn:xmpp:delay" stamp="2017-02-15T17:02:24Z" from="'+contact_jid+'/priority-0-resource"/>'+
'</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')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' from="'+contact_jid+'/priority-2-resource">'+
' <priority>2</priority>'+
' <show>dnd</show>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('dnd');
expect(_.keys(contact.get('resources')).length).toBe(3);
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
expect(contact.get('resources')['priority-2-resource']['priority']).toBe(2);
expect(contact.get('resources')['priority-2-resource']['status']).toBe('dnd');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' from="'+contact_jid+'/priority-2-resource">'+
' <priority>2</priority>'+
' <show>dnd</show>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('dnd');
expect(_.keys(contact.get('resources')).length).toBe(3);
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
expect(contact.get('resources')['priority-2-resource']['priority']).toBe(2);
expect(contact.get('resources')['priority-2-resource']['status']).toBe('dnd');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' from="'+contact_jid+'/priority-3-resource">'+
' <priority>3</priority>'+
' <show>away</show>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('away');
expect(_.keys(contact.get('resources')).length).toBe(4);
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
expect(contact.get('resources')['priority-2-resource']['priority']).toBe(2);
expect(contact.get('resources')['priority-2-resource']['status']).toBe('dnd');
expect(contact.get('resources')['priority-3-resource']['priority']).toBe(3);
expect(contact.get('resources')['priority-3-resource']['status']).toBe('away');
});
waits(1000); // XXX: Bit of a hack. With jasmine 2 we can mock the date instead.
runs(function () {
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' from="'+contact_jid+'/newer-priority-1-resource">'+
' <priority>1</priority>'+
' <show>dnd</show>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('away');
expect(_.keys(contact.get('resources')).length).toBe(5);
expect(contact.get('resources')['newer-priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['newer-priority-1-resource']['status']).toBe('dnd');
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
expect(contact.get('resources')['priority-2-resource']['priority']).toBe(2);
expect(contact.get('resources')['priority-2-resource']['status']).toBe('dnd');
expect(contact.get('resources')['priority-3-resource']['priority']).toBe(3);
expect(contact.get('resources')['priority-3-resource']['status']).toBe('away');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' from="'+contact_jid+'/priority-3-resource">'+
' <priority>3</priority>'+
' <show>away</show>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('away');
expect(_.keys(contact.get('resources')).length).toBe(4);
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
expect(contact.get('resources')['priority-2-resource']['priority']).toBe(2);
expect(contact.get('resources')['priority-2-resource']['status']).toBe('dnd');
expect(contact.get('resources')['priority-3-resource']['priority']).toBe(3);
expect(contact.get('resources')['priority-3-resource']['status']).toBe('away');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-3-resource">'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('dnd');
expect(_.keys(contact.get('resources')).length).toBe(4);
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
expect(contact.get('resources')['priority-2-resource']['priority']).toBe(2);
expect(contact.get('resources')['priority-2-resource']['status']).toBe('dnd');
expect(contact.get('resources')['newer-priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['newer-priority-1-resource']['status']).toBe('dnd');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' from="'+contact_jid+'/older-priority-1-resource">'+
' <priority>1</priority>'+
' <show>dnd</show>'+
' <delay xmlns="urn:xmpp:delay" stamp="2017-02-15T15:02:24Z" from="'+contact_jid+'/older-priority-1-resource"/>'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('away');
expect(_.keys(contact.get('resources')).length).toBe(5);
expect(contact.get('resources')['older-priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['older-priority-1-resource']['status']).toBe('dnd');
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
expect(contact.get('resources')['priority-2-resource']['priority']).toBe(2);
expect(contact.get('resources')['priority-2-resource']['status']).toBe('dnd');
expect(contact.get('resources')['priority-3-resource']['priority']).toBe(3);
expect(contact.get('resources')['priority-3-resource']['status']).toBe('away');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-2-resource">'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('dnd');
expect(_.keys(contact.get('resources')).length).toBe(3);
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
expect(contact.get('resources')['newer-priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['newer-priority-1-resource']['status']).toBe('dnd');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-3-resource">'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('dnd');
expect(_.keys(contact.get('resources')).length).toBe(4);
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
expect(contact.get('resources')['priority-2-resource']['priority']).toBe(2);
expect(contact.get('resources')['priority-2-resource']['status']).toBe('dnd');
expect(contact.get('resources')['older-priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['older-priority-1-resource']['status']).toBe('dnd');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-1-resource">'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('dnd');
expect(_.keys(contact.get('resources')).length).toBe(2);
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['newer-priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['newer-priority-1-resource']['status']).toBe('dnd');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-2-resource">'+
'</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(3);
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['priority-1-resource']['status']).toBe('online');
expect(contact.get('resources')['older-priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['older-priority-1-resource']['status']).toBe('dnd');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/newer-priority-1-resource">'+
'</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')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-1-resource">'+
'</presence>');
_converse.connection._dataRecv(test_utils.createRequest(stanza[0]));
expect(_converse.roster.get(contact_jid).get('chat_status')).toBe('dnd');
expect(_.keys(contact.get('resources')).length).toBe(2);
expect(contact.get('resources')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
expect(contact.get('resources')['older-priority-1-resource']['priority']).toBe(1);
expect(contact.get('resources')['older-priority-1-resource']['status']).toBe('dnd');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-0-resource">'+
'</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);
});
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/older-priority-1-resource">'+
'</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')['priority-0-resource']['priority']).toBe(0);
expect(contact.get('resources')['priority-0-resource']['status']).toBe('xa');
stanza = $(
'<presence xmlns="jabber:client"'+
' to="dummy@localhost/converse.js-21770972"'+
' type="unavailable"'+
' from="'+contact_jid+'/priority-0-resource">'+
'</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);
}));
});
}));

View File

@ -177,6 +177,7 @@
Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
Strophe.addNamespace('HINTS', 'urn:xmpp:hints');
Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
Strophe.addNamespace('DELAY', 'urn:xmpp:delay');
// Instance level constants
this.TIMEOUTS = { // Set as module attr so that we can override in tests.
@ -855,22 +856,44 @@
return this;
},
addResource: function (resource, priority, chat_status) {
var resources = this.get('resources');
if (!_.isObject(resources)) { resources = {}; }
addResource: function (presence) {
/* Adds a new resource and it's associated attributes as taken
* from the passed in presence stanza.
*
* Also updates the contact's chat_status if the presence has
* higher priority (and is newer).
*/
var jid = presence.getAttribute('from'),
chat_status = _.propertyOf(presence.querySelector('show'))('textContent') || 'online',
resource = Strophe.getResourceFromJid(jid),
priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0,
delay = presence.querySelector('delay[xmlns="'+Strophe.NS.DELAY+'"]'),
timestamp = _.isNull(delay) ? moment().format() : moment(delay.getAttribute('stamp')).format();
priority = _.isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10);
var resources = _.isObject(this.get('resources')) ? this.get('resources') : {};
resources[resource] = {
'priority': _.isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10),
'priority': priority,
'status': chat_status,
'timestamp': moment().format()
'timestamp': timestamp
};
this.set({'resources': resources});
var changed = {'resources': resources};
var hpr = this.getHighestPriorityResource();
if (priority == hpr.priority && timestamp == hpr.timestamp) {
// Only set the chat status if this is the newest resource
// with the highest priority
changed.chat_status = chat_status;
}
this.save(changed);
return resources;
},
removeResource: function (resource) {
/* Remove the passed in resource from the contact's resources
* map.
* Return the amount of resources left over.
/* Remove the passed in resource from the contact's resources map.
*
* Also recomputes the chat_status given that there's one less
* resource.
*/
var resources = this.get('resources');
if (!_.isObject(resources)) {
@ -878,13 +901,15 @@
} else {
delete resources[resource];
}
this.save({'resources': resources});
return _.size(resources);
this.save({
'resources': resources,
'chat_status': _.propertyOf(
this.getHighestPriorityResource())('status') || 'offline'
});
},
getHighestPriorityStatus: function () {
/* Return the chat status assigned to the resource with the
* highest priority.
getHighestPriorityResource: function () {
/* Return the resource with the highest priority.
*
* If multiple resources have the same priority, take the
* newest one.
@ -897,10 +922,9 @@
_.reverse
)(resources)[0];
if (!_.isUndefined(val)) {
return val.status;
return val;
}
}
return 'offline';
},
removeFromRoster: function (callback) {
@ -1225,7 +1249,6 @@
resource = Strophe.getResourceFromJid(jid),
chat_status = _.propertyOf(presence.querySelector('show'))('textContent') || 'online',
status_message = _.propertyOf(presence.querySelector('status'))('textContent'),
priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0,
contact = this.get(bare_jid);
if (this.isSelf(bare_jid)) {
@ -1258,14 +1281,9 @@
this.handleIncomingSubscription(presence);
} else if (presence_type === 'unavailable' && contact) {
contact.removeResource(resource);
contact.save({'chat_status': contact.getHighestPriorityStatus()});
} else if (contact) { // presence_type is undefined
var resources = contact.addResource(resource, priority, chat_status);
if (priority >= _.flow(_.values, _.partial(_.map, _, 'priority'), _.max)(resources)) {
// Only save if it's the resource with the highest
// priority
contact.save({'chat_status': chat_status});
}
} else if (contact) {
// presence_type is undefined
contact.addResource(presence);
}
}
});