Commit 3cbc99a3 authored by JC Brand's avatar JC Brand

Roster fixes related to reconnecting

Avoid `An 'url' property must be specified` error by properly clearing
presence data upon teardown and then resetting the browserStorage upon
reconnection.

Store contact resources in a Backbone collection
parent 4e9e532a
...@@ -49505,13 +49505,12 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins ...@@ -49505,13 +49505,12 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins
} }
const contact_jid = this.model.get('jid'); const contact_jid = this.model.get('jid');
const resources = this.model.presence.get('resources');
if (_.isEmpty(resources)) { if (this.model.presence.resources.length === 0) {
return; return;
} }
const results = await Promise.all(_.map(_.keys(resources), resource => _converse.api.disco.supports(Strophe.NS.SPOILER, `${contact_jid}/${resource}`))); const results = await Promise.all(this.model.presence.resources.map(res => _converse.api.disco.supports(Strophe.NS.SPOILER, `${contact_jid}/${res.get('name')}`)));
if (_.filter(results, 'length').length) { if (_.filter(results, 'length').length) {
const html = templates_spoiler_button_html__WEBPACK_IMPORTED_MODULE_16___default()(this.model.toJSON()); const html = templates_spoiler_button_html__WEBPACK_IMPORTED_MODULE_16___default()(this.model.toJSON());
...@@ -67777,7 +67776,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins ...@@ -67777,7 +67776,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
_converse.registerPresenceHandler = function () { _converse.registerPresenceHandler = function () {
_converse.unregisterPresenceHandler(); _converse.unregisterPresenceHandler();
_converse.presence_ref = _converse.connection.addHandler(function (presence) { _converse.presence_ref = _converse.connection.addHandler(presence => {
_converse.roster.presenceHandler(presence); _converse.roster.presenceHandler(presence);
return true; return true;
...@@ -67843,12 +67842,35 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins ...@@ -67843,12 +67842,35 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
} }
}; };
const Resource = Backbone.Model.extend({
'idAttribute': 'name'
});
const Resources = Backbone.Collection.extend({
'model': Resource
});
_converse.Presence = Backbone.Model.extend({ _converse.Presence = Backbone.Model.extend({
defaults() { defaults: {
return { 'show': 'offline'
'show': 'offline', },
'resources': {}
}; initialize() {
this.resources = new Resources();
const id = `converse.identities-${this.get('jid')}`;
this.resources.browserStorage = new Backbone.BrowserStorage.session(id);
this.resources.on('update', this.onResourcesChanged, this);
this.resources.on('change', this.onResourcesChanged, this);
},
onResourcesChanged() {
const hpr = this.getHighestPriorityResource();
const show = _.get(hpr, 'attributes.show', 'offline');
if (this.get('show') !== show) {
this.save({
'show': show
});
}
}, },
getHighestPriorityResource() { getHighestPriorityResource() {
...@@ -67857,15 +67879,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins ...@@ -67857,15 +67879,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
* If multiple resources have the same priority, take the * If multiple resources have the same priority, take the
* latest one. * latest one.
*/ */
const resources = this.get('resources'); return this.resources.sortBy(r => `${r.get('priority')}-${r.get('timestamp')}`).reverse()[0];
if (_.isObject(resources) && _.size(resources)) {
const val = _.flow(_.values, _.partial(_.sortBy, _, ['priority', 'timestamp']), _.reverse)(resources)[0];
if (!_.isUndefined(val)) {
return val;
}
}
}, },
addResource(presence) { addResource(presence) {
...@@ -67875,52 +67889,35 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins ...@@ -67875,52 +67889,35 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
* Also updates the presence if the resource has higher priority (and is newer). * Also updates the presence if the resource has higher priority (and is newer).
*/ */
const jid = presence.getAttribute('from'), const jid = presence.getAttribute('from'),
show = _.propertyOf(presence.querySelector('show'))('textContent') || 'online', name = Strophe.getResourceFromJid(jid),
resource = Strophe.getResourceFromJid(jid),
delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop(), delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop(),
timestamp = _.isNil(delay) ? moment().format() : moment(delay.getAttribute('stamp')).format(); priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0,
let priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0; resource = this.resources.get(name),
priority = _.isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10); settings = {
const resources = _.isObject(this.get('resources')) ? this.get('resources') : {}; 'name': name,
resources[resource] = { 'priority': _.isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10),
'name': resource, 'show': _.propertyOf(presence.querySelector('show'))('textContent') || 'online',
'priority': priority, 'timestamp': _.isNil(delay) ? moment().format() : moment(delay.getAttribute('stamp')).format()
'show': show,
'timestamp': timestamp
};
const changed = {
'resources': resources
}; };
const hpr = this.getHighestPriorityResource();
if (priority == hpr.priority && timestamp == hpr.timestamp) { if (resource) {
// Only set the "global" presence if this is the newest resource resource.save(settings);
// with the highest priority } else {
changed.show = show; this.resources.create(settings);
} }
this.save(changed);
return resources;
}, },
removeResource(resource) { removeResource(name) {
/* Remove the passed in resource from the resources map. /* Remove the passed in resource from the resources map.
* *
* Also redetermines the presence given that there's one less * Also redetermines the presence given that there's one less
* resource. * resource.
*/ */
let resources = this.get('resources'); const resource = this.resources.get(name);
if (!_.isObject(resources)) { if (resource) {
resources = {}; resource.destroy();
} else {
delete resources[resource];
} }
this.save({
'resources': resources,
'show': _.propertyOf(this.getHighestPriorityResource())('show') || 'offline'
});
} }
}); });
...@@ -68686,8 +68683,16 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins ...@@ -68686,8 +68683,16 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
_converse.api.listen.on('afterTearDown', () => { _converse.api.listen.on('afterTearDown', () => {
if (_converse.presences) { if (_converse.presences) {
_converse.presences.off().reset(); // Remove presences _converse.presences.each(p => {
p.resources.each(r => r.destroy({
'silent': true
}));
p.save({
'show': 'offline'
}, {
'silent': true
});
});
} }
}); });
...@@ -68700,10 +68705,11 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins ...@@ -68700,10 +68705,11 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
_converse.api.listen.on('statusInitialized', reconnecting => { _converse.api.listen.on('statusInitialized', reconnecting => {
if (!reconnecting) { if (!reconnecting) {
_converse.presences = new _converse.Presences(); _converse.presences = new _converse.Presences();
}
_converse.presences.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.presences-${_converse.bare_jid}`)); _converse.presences.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.presences-${_converse.bare_jid}`));
_converse.presences.fetch(); _converse.presences.fetch();
}
_converse.emit('presencesInitialized', reconnecting); _converse.emit('presencesInitialized', reconnecting);
}); });
...@@ -68723,9 +68729,9 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins ...@@ -68723,9 +68729,9 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
_converse.roster.onConnected(); _converse.roster.onConnected();
_converse.populateRoster(reconnecting);
_converse.registerPresenceHandler(); _converse.registerPresenceHandler();
_converse.populateRoster(reconnecting);
}); });
/************************ API ************************/ /************************ API ************************/
// API methods only available to plugins // API methods only available to plugins
This diff is collapsed.
...@@ -431,13 +431,14 @@ converse.plugins.add('converse-chatview', { ...@@ -431,13 +431,14 @@ converse.plugins.add('converse-chatview', {
return; return;
} }
const contact_jid = this.model.get('jid'); const contact_jid = this.model.get('jid');
const resources = this.model.presence.get('resources'); if (this.model.presence.resources.length === 0) {
if (_.isEmpty(resources)) {
return; return;
} }
const results = await Promise.all(_.map(_.keys(resources), const results = await Promise.all(
resource => _converse.api.disco.supports(Strophe.NS.SPOILER, `${contact_jid}/${resource}`) this.model.presence.resources.map(
)); res => _converse.api.disco.supports(Strophe.NS.SPOILER, `${contact_jid}/${res.get('name')}`)
)
);
if (_.filter(results, 'length').length) { if (_.filter(results, 'length').length) {
const html = tpl_spoiler_button(this.model.toJSON()); const html = tpl_spoiler_button(this.model.toJSON());
if (_converse.visible_toolbar_buttons.emoji) { if (_converse.visible_toolbar_buttons.emoji) {
......
...@@ -38,8 +38,7 @@ converse.plugins.add('converse-roster', { ...@@ -38,8 +38,7 @@ converse.plugins.add('converse-roster', {
_converse.registerPresenceHandler = function () { _converse.registerPresenceHandler = function () {
_converse.unregisterPresenceHandler(); _converse.unregisterPresenceHandler();
_converse.presence_ref = _converse.connection.addHandler( _converse.presence_ref = _converse.connection.addHandler(presence => {
function (presence) {
_converse.roster.presenceHandler(presence); _converse.roster.presenceHandler(presence);
return true; return true;
}, null, 'presence', null); }, null, 'presence', null);
...@@ -102,12 +101,28 @@ converse.plugins.add('converse-roster', { ...@@ -102,12 +101,28 @@ converse.plugins.add('converse-roster', {
} }
}; };
const Resource = Backbone.Model.extend({'idAttribute': 'name'});
const Resources = Backbone.Collection.extend({'model': Resource});
_converse.Presence = Backbone.Model.extend({ _converse.Presence = Backbone.Model.extend({
defaults () { defaults: {
return { 'show': 'offline'
'show': 'offline', },
'resources': {}
initialize () {
this.resources = new Resources();
const id = `converse.identities-${this.get('jid')}`;
this.resources.browserStorage = new Backbone.BrowserStorage.session(id);
this.resources.on('update', this.onResourcesChanged, this);
this.resources.on('change', this.onResourcesChanged, this);
},
onResourcesChanged () {
const hpr = this.getHighestPriorityResource();
const show = _.get(hpr, 'attributes.show', 'offline');
if (this.get('show') !== show) {
this.save({'show': show});
} }
}, },
...@@ -117,17 +132,7 @@ converse.plugins.add('converse-roster', { ...@@ -117,17 +132,7 @@ converse.plugins.add('converse-roster', {
* If multiple resources have the same priority, take the * If multiple resources have the same priority, take the
* latest one. * latest one.
*/ */
const resources = this.get('resources'); return this.resources.sortBy(r => `${r.get('priority')}-${r.get('timestamp')}`).reverse()[0];
if (_.isObject(resources) && _.size(resources)) {
const val = _.flow(
_.values,
_.partial(_.sortBy, _, ['priority', 'timestamp']),
_.reverse
)(resources)[0];
if (!_.isUndefined(val)) {
return val;
}
}
}, },
addResource (presence) { addResource (presence) {
...@@ -137,52 +142,35 @@ converse.plugins.add('converse-roster', { ...@@ -137,52 +142,35 @@ converse.plugins.add('converse-roster', {
* Also updates the presence if the resource has higher priority (and is newer). * Also updates the presence if the resource has higher priority (and is newer).
*/ */
const jid = presence.getAttribute('from'), const jid = presence.getAttribute('from'),
show = _.propertyOf(presence.querySelector('show'))('textContent') || 'online', name = Strophe.getResourceFromJid(jid),
resource = Strophe.getResourceFromJid(jid),
delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop(), delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, presence).pop(),
timestamp = _.isNil(delay) ? moment().format() : moment(delay.getAttribute('stamp')).format(); priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0,
resource = this.resources.get(name),
let priority = _.propertyOf(presence.querySelector('priority'))('textContent') || 0; settings = {
priority = _.isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10); 'name': name,
'priority': _.isNaN(parseInt(priority, 10)) ? 0 : parseInt(priority, 10),
const resources = _.isObject(this.get('resources')) ? this.get('resources') : {}; 'show': _.propertyOf(presence.querySelector('show'))('textContent') || 'online',
resources[resource] = { 'timestamp': _.isNil(delay) ? moment().format() : moment(delay.getAttribute('stamp')).format()
'name': resource,
'priority': priority,
'show': show,
'timestamp': timestamp
}; };
const changed = {'resources': resources}; if (resource) {
const hpr = this.getHighestPriorityResource(); resource.save(settings);
if (priority == hpr.priority && timestamp == hpr.timestamp) { } else {
// Only set the "global" presence if this is the newest resource this.resources.create(settings);
// with the highest priority
changed.show = show;
} }
this.save(changed);
return resources;
}, },
removeResource (resource) { removeResource (name) {
/* Remove the passed in resource from the resources map. /* Remove the passed in resource from the resources map.
* *
* Also redetermines the presence given that there's one less * Also redetermines the presence given that there's one less
* resource. * resource.
*/ */
let resources = this.get('resources'); const resource = this.resources.get(name);
if (!_.isObject(resources)) { if (resource) {
resources = {}; resource.destroy();
} else { }
delete resources[resource];
} }
this.save({
'resources': resources,
'show': _.propertyOf(
this.getHighestPriorityResource())('show') || 'offline'
});
},
}); });
...@@ -847,7 +835,10 @@ converse.plugins.add('converse-roster', { ...@@ -847,7 +835,10 @@ converse.plugins.add('converse-roster', {
_converse.api.listen.on('afterTearDown', () => { _converse.api.listen.on('afterTearDown', () => {
if (_converse.presences) { if (_converse.presences) {
_converse.presences.off().reset(); // Remove presences _converse.presences.each(p => {
p.resources.each(r => r.destroy({'silent': true}));
p.save({'show': 'offline'}, {'silent': true})
});
} }
}); });
...@@ -860,10 +851,10 @@ converse.plugins.add('converse-roster', { ...@@ -860,10 +851,10 @@ converse.plugins.add('converse-roster', {
_converse.api.listen.on('statusInitialized', (reconnecting) => { _converse.api.listen.on('statusInitialized', (reconnecting) => {
if (!reconnecting) { if (!reconnecting) {
_converse.presences = new _converse.Presences(); _converse.presences = new _converse.Presences();
}
_converse.presences.browserStorage = _converse.presences.browserStorage =
new Backbone.BrowserStorage.session(b64_sha1(`converse.presences-${_converse.bare_jid}`)); new Backbone.BrowserStorage.session(b64_sha1(`converse.presences-${_converse.bare_jid}`));
_converse.presences.fetch(); _converse.presences.fetch();
}
_converse.emit('presencesInitialized', reconnecting); _converse.emit('presencesInitialized', reconnecting);
}); });
...@@ -879,8 +870,8 @@ converse.plugins.add('converse-roster', { ...@@ -879,8 +870,8 @@ converse.plugins.add('converse-roster', {
_converse.initRoster(); _converse.initRoster();
} }
_converse.roster.onConnected(); _converse.roster.onConnected();
_converse.populateRoster(reconnecting);
_converse.registerPresenceHandler(); _converse.registerPresenceHandler();
_converse.populateRoster(reconnecting);
}); });
......
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