disco: Create new plugin converse-disco.

We can now support feature discovery for multiple entities (although we
currently still only query for the user's own XMPP server).
This commit is contained in:
JC Brand 2017-07-21 12:41:16 +02:00
parent 00484280c2
commit 7850c38faa
12 changed files with 142 additions and 199 deletions

View File

@ -59,7 +59,7 @@
"dot-notation": [ "dot-notation": [
"error", "error",
{ {
"allowKeywords": false "allowKeywords": true
} }
], ],
"eol-last": "error", "eol-last": "error",

View File

@ -2,6 +2,11 @@
## 3.2.0 (Unreleased) ## 3.2.0 (Unreleased)
### New Plugins
- New plugin `converse-disco` which replaces the original support for
[XEP-0030](https://xmpp.org/extensions/xep-0030.html) and which has been
refactored to allow features for multiple entities to be stored. [jcbrand]
### New features and improvements ### New features and improvements
- Add support for Emojis (either native, or via <a href="https://www.emojione.com/">Emojione</a>). [jcbrand] - Add support for Emojis (either native, or via <a href="https://www.emojione.com/">Emojione</a>). [jcbrand]
- Add JID validation in the contact add form. [jcbrand] - Add JID validation in the contact add form. [jcbrand]

View File

@ -53,7 +53,11 @@
describe("A chat state indication", function () { describe("A chat state indication", function () {
it("are sent out when the client becomes or stops being idle", mock.initConverse(function (_converse) { it("are sent out when the client becomes or stops being idle",
mock.initConverseWithPromises(
null, ['discoInitialized'], {},
function (done, _converse) {
spyOn(_converse, 'sendCSI').and.callThrough(); spyOn(_converse, 'sendCSI').and.callThrough();
var sent_stanza; var sent_stanza;
spyOn(_converse.connection, 'send').and.callFake(function (stanza) { spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
@ -61,7 +65,7 @@
}); });
var i = 0; var i = 0;
_converse.idle_seconds = 0; // Usually initialized by registerIntervalHandler _converse.idle_seconds = 0; // Usually initialized by registerIntervalHandler
_converse.features['urn:xmpp:csi:0'] = true; // Mock that the server supports CSI _converse.disco_entities.get(_converse.domain).features['urn:xmpp:csi:0'] = true; // Mock that the server supports CSI
_converse.csi_waiting_time = 3; // The relevant config option _converse.csi_waiting_time = 3; // The relevant config option
while (i <= _converse.csi_waiting_time) { while (i <= _converse.csi_waiting_time) {
@ -78,10 +82,10 @@
expect(sent_stanza.toLocaleString()).toBe( expect(sent_stanza.toLocaleString()).toBe(
"<active xmlns='urn:xmpp:csi:0'/>" "<active xmlns='urn:xmpp:csi:0'/>"
); );
// Reset values // Reset values
_converse.csi_waiting_time = 0; _converse.csi_waiting_time = 0;
_converse.features['urn:xmpp:csi:0'] = false; _converse.disco_entities.get(_converse.domain).features['urn:xmpp:csi:0'] = false;
done();
})); }));
}); });

View File

@ -11,11 +11,16 @@
describe("Service Discovery", function () { describe("Service Discovery", function () {
describe("Whenever converse.js discovers a new server feature", function () { describe("Whenever converse.js discovers a new server feature", function () {
it("emits the serviceDiscovered event", mock.initConverse(function (_converse) { it("emits the serviceDiscovered event",
mock.initConverseWithPromises(
null, ['discoInitialized'], {},
function (done, _converse) {
sinon.spy(_converse, 'emit'); sinon.spy(_converse, 'emit');
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
expect(_converse.emit.called).toBe(true); expect(_converse.emit.called).toBe(true);
expect(_converse.emit.args[0][1].get('var')).toBe(Strophe.NS.MAM); expect(_converse.emit.args[0][1].get('var')).toBe(Strophe.NS.MAM);
done();
})); }));
}); });
}); });

View File

@ -15,20 +15,25 @@
describe("The archive.query API", function () { describe("The archive.query API", function () {
it("can be used to query for all archived messages", mock.initConverse(function (_converse) { it("can be used to query for all archived messages",
mock.initConverseWithPromises(
null, ['discoInitialized'], {},
function (done, _converse) {
var sent_stanza, IQ_id; var sent_stanza, IQ_id;
var sendIQ = _converse.connection.sendIQ; var sendIQ = _converse.connection.sendIQ;
spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) { spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
} }
_converse.api.archive.query(); _converse.api.archive.query();
var queryid = $(sent_stanza.toString()).find('query').attr('queryid'); var queryid = $(sent_stanza.toString()).find('query').attr('queryid');
expect(sent_stanza.toString()).toBe( expect(sent_stanza.toString()).toBe(
"<iq type='set' xmlns='jabber:client' id='"+IQ_id+"'><query xmlns='urn:xmpp:mam:2' queryid='"+queryid+"'/></iq>"); "<iq type='set' xmlns='jabber:client' id='"+IQ_id+"'><query xmlns='urn:xmpp:mam:2' queryid='"+queryid+"'/></iq>");
done();
})); }));
it("can be used to query for all messages to/from a particular JID", mock.initConverse(function (_converse) { it("can be used to query for all messages to/from a particular JID", mock.initConverse(function (_converse) {
@ -38,8 +43,8 @@
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
} }
_converse.api.archive.query({'with':'juliet@capulet.lit'}); _converse.api.archive.query({'with':'juliet@capulet.lit'});
var queryid = $(sent_stanza.toString()).find('query').attr('queryid'); var queryid = $(sent_stanza.toString()).find('query').attr('queryid');
@ -66,8 +71,8 @@
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
} }
var start = '2010-06-07T00:00:00Z'; var start = '2010-06-07T00:00:00Z';
var end = '2010-07-07T13:23:54Z'; var end = '2010-07-07T13:23:54Z';
@ -97,8 +102,8 @@
})); }));
it("throws a TypeError if an invalid date is provided", mock.initConverse(function (_converse) { it("throws a TypeError if an invalid date is provided", mock.initConverse(function (_converse) {
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
} }
expect(_.partial(_converse.api.archive.query, {'start': 'not a real date'})).toThrow( expect(_.partial(_converse.api.archive.query, {'start': 'not a real date'})).toThrow(
new TypeError('archive.query: invalid date provided for: start') new TypeError('archive.query: invalid date provided for: start')
@ -112,8 +117,8 @@
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
} }
var start = '2010-06-07T00:00:00Z'; var start = '2010-06-07T00:00:00Z';
_converse.api.archive.query({'start': start}); _converse.api.archive.query({'start': start});
@ -141,8 +146,8 @@
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
} }
var start = '2010-06-07T00:00:00Z'; var start = '2010-06-07T00:00:00Z';
_converse.api.archive.query({'start': start, 'max':10}); _converse.api.archive.query({'start': start, 'max':10});
@ -173,8 +178,8 @@
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
} }
var start = '2010-06-07T00:00:00Z'; var start = '2010-06-07T00:00:00Z';
_converse.api.archive.query({ _converse.api.archive.query({
@ -210,8 +215,8 @@
sent_stanza = iq; sent_stanza = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
} }
_converse.api.archive.query({'before': '', 'max':10}); _converse.api.archive.query({'before': '', 'max':10});
var queryid = $(sent_stanza.toString()).find('query').attr('queryid'); var queryid = $(sent_stanza.toString()).find('query').attr('queryid');
@ -237,8 +242,8 @@
// and pass it in. However, in the callback method an RSM object is // and pass it in. However, in the callback method an RSM object is
// returned which can be reused for easy paging. This test is // returned which can be reused for easy paging. This test is
// more for that usecase. // more for that usecase.
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
} }
var sent_stanza, IQ_id; var sent_stanza, IQ_id;
var sendIQ = _converse.connection.sendIQ; var sendIQ = _converse.connection.sendIQ;
@ -247,7 +252,7 @@
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
var rsm = new Strophe.RSM({'max': '10'}); var rsm = new Strophe.RSM({'max': '10'});
rsm['with'] = 'romeo@montague.lit'; rsm['with'] = 'romeo@montague.lit'; // eslint-disable-line dot-notation
rsm.start = '2010-06-07T00:00:00Z'; rsm.start = '2010-06-07T00:00:00Z';
_converse.api.archive.query(rsm); _converse.api.archive.query(rsm);
@ -275,8 +280,8 @@
})); }));
it("accepts a callback function, which it passes the messages and a Strophe.RSM object", mock.initConverse(function (_converse) { it("accepts a callback function, which it passes the messages and a Strophe.RSM object", mock.initConverse(function (_converse) {
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain).features.findWhere({'var': Strophe.NS.MAM})) {
_converse.features.create({'var': Strophe.NS.MAM}); _converse.disco_entities.get(_converse.domain).features.create({'var': Strophe.NS.MAM});
} }
var sent_stanza, IQ_id; var sent_stanza, IQ_id;
var sendIQ = _converse.connection.sendIQ; var sendIQ = _converse.connection.sendIQ;
@ -351,7 +356,7 @@
expect(args[0].length).toBe(2); expect(args[0].length).toBe(2);
expect(args[0][0].outerHTML).toBe(msg1.nodeTree.outerHTML); expect(args[0][0].outerHTML).toBe(msg1.nodeTree.outerHTML);
expect(args[0][1].outerHTML).toBe(msg2.nodeTree.outerHTML); expect(args[0][1].outerHTML).toBe(msg2.nodeTree.outerHTML);
expect(args[1]['with']).toBe('romeo@capulet.lit'); expect(args[1]['with']).toBe('romeo@capulet.lit'); // eslint-disable-line dot-notation
expect(args[1].max).toBe('10'); expect(args[1].max).toBe('10');
expect(args[1].count).toBe('16'); expect(args[1].count).toBe('16');
expect(args[1].first).toBe('23452-4534-1'); expect(args[1].first).toBe('23452-4534-1');
@ -376,7 +381,7 @@
'var': Strophe.NS.MAM 'var': Strophe.NS.MAM
}); });
spyOn(feature, 'save').and.callFake(feature.set); // Save will complain about a url not being set spyOn(feature, 'save').and.callFake(feature.set); // Save will complain about a url not being set
_converse.features.onFeatureAdded(feature); _converse.disco_entities.get(_converse.domain).features.onFeatureAdded(feature);
expect(_converse.connection.sendIQ).toHaveBeenCalled(); expect(_converse.connection.sendIQ).toHaveBeenCalled();
expect(sent_stanza.toLocaleString()).toBe( expect(sent_stanza.toLocaleString()).toBe(
@ -430,7 +435,7 @@
.c('never').up(); .c('never').up();
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(feature.save).toHaveBeenCalled(); expect(feature.save).toHaveBeenCalled();
expect(feature.get('preferences')['default']).toBe('never'); expect(feature.get('preferences')['default']).toBe('never'); // eslint-disable-line dot-notation
// Restore // Restore
_converse.message_archiving = 'never'; _converse.message_archiving = 'never';

View File

@ -55,6 +55,7 @@ require.config({
"converse-chatview": "src/converse-chatview", "converse-chatview": "src/converse-chatview",
"converse-controlbox": "src/converse-controlbox", "converse-controlbox": "src/converse-controlbox",
"converse-core": "src/converse-core", "converse-core": "src/converse-core",
"converse-disco": "src/converse-disco",
"converse-dragresize": "src/converse-dragresize", "converse-dragresize": "src/converse-dragresize",
"converse-headline": "src/converse-headline", "converse-headline": "src/converse-headline",
"converse-inverse": "src/converse-inverse", "converse-inverse": "src/converse-inverse",

View File

@ -16,7 +16,6 @@
"strophe", "strophe",
"pluggable", "pluggable",
"backbone.noconflict", "backbone.noconflict",
"strophe.disco",
"backbone.browserStorage", "backbone.browserStorage",
"backbone.overview", "backbone.overview",
], factory); ], factory);
@ -59,6 +58,7 @@
'converse-chatview', 'converse-chatview',
'converse-controlbox', 'converse-controlbox',
'converse-core', 'converse-core',
'converse-disco',
'converse-dragresize', 'converse-dragresize',
'converse-headline', 'converse-headline',
'converse-mam', 'converse-mam',
@ -794,16 +794,11 @@
// If there's no xmppstatus obj, then we were never connected to // If there's no xmppstatus obj, then we were never connected to
// begin with, so we set reconnecting to false. // begin with, so we set reconnecting to false.
reconnecting = _.isUndefined(_converse.xmppstatus) ? false : reconnecting; reconnecting = _.isUndefined(_converse.xmppstatus) ? false : reconnecting;
if (reconnecting) { if (reconnecting) {
_converse.onStatusInitialized(true); _converse.onStatusInitialized(true);
_converse.emit('reconnected'); _converse.emit('reconnected');
} else { } else {
// There might be some open chat boxes. We don't
// know whether these boxes are of the same account or not, so we
// close them now.
_converse.chatboxviews.closeAllChatBoxes(); _converse.chatboxviews.closeAllChatBoxes();
_converse.features = new _converse.Features();
_converse.initStatus() _converse.initStatus()
.then( .then(
_.partial(_converse.onStatusInitialized, false), _.partial(_converse.onStatusInitialized, false),
@ -1866,88 +1861,6 @@
} }
}); });
this.Features = Backbone.Collection.extend({
/* Service Discovery
* -----------------
* This collection stores Feature Models, representing features
* provided by available XMPP entities (e.g. servers)
* See XEP-0030 for more details: http://xmpp.org/extensions/xep-0030.html
* All features are shown here: http://xmpp.org/registrar/disco-features.html
*/
model: Backbone.Model,
initialize () {
this.addClientIdentities().addClientFeatures();
this.browserStorage = new Backbone.BrowserStorage[_converse.storage](
b64_sha1(`converse.features${_converse.bare_jid}`)
);
this.on('add', this.onFeatureAdded, this);
this.fetchFeatures();
},
fetchFeatures () {
if (this.browserStorage.records.length === 0) {
// browserStorage is empty, so we've likely never queried this
// domain for features yet
_converse.connection.disco.info(_converse.domain, null, this.onInfo.bind(this));
_converse.connection.disco.items(_converse.domain, null, this.onItems.bind(this));
} else {
this.fetch({add:true});
}
},
onFeatureAdded (feature) {
_converse.emit('serviceDiscovered', feature);
},
addClientIdentities () {
/* See http://xmpp.org/registrar/disco-categories.html
*/
_converse.connection.disco.addIdentity('client', 'web', 'Converse.js');
return this;
},
addClientFeatures () {
/* The strophe.disco.js plugin keeps a list of features which
* it will advertise to any #info queries made to it.
*
* See: http://xmpp.org/extensions/xep-0030.html#info
*/
_converse.connection.disco.addFeature(Strophe.NS.BOSH);
_converse.connection.disco.addFeature(Strophe.NS.CHATSTATES);
_converse.connection.disco.addFeature(Strophe.NS.DISCO_INFO);
_converse.connection.disco.addFeature(Strophe.NS.ROSTERX); // Limited support
if (_converse.message_carbons) {
_converse.connection.disco.addFeature(Strophe.NS.CARBONS);
}
return this;
},
onItems (stanza) {
_.each(stanza.querySelectorAll('query item'), (item) => {
_converse.connection.disco.info(
item.getAttribute('jid'),
null,
this.onInfo.bind(this));
});
},
onInfo (stanza) {
if ((sizzle('identity[category=server][type=im]', stanza).length === 0) &&
(sizzle('identity[category=conference][type=text]', stanza).length === 0)) {
// This isn't an IM server component
return;
}
_.forEach(stanza.querySelectorAll('feature'), (feature) => {
const namespace = feature.getAttribute('var');
this[namespace] = true;
this.create({
'var': namespace,
'from': stanza.getAttribute('from')
});
});
}
});
this.setUpXMLLogging = function () { this.setUpXMLLogging = function () {
Strophe.log = function (level, msg) { Strophe.log = function (level, msg) {
_converse.log(msg, level); _converse.log(msg, level);
@ -2165,16 +2078,13 @@
/* Remove those views which are only allowed with a valid /* Remove those views which are only allowed with a valid
* connection. * connection.
*/ */
_converse.emit('beforeTearDown');
this.unregisterPresenceHandler(); this.unregisterPresenceHandler();
if (this.roster) { if (this.roster) {
this.roster.off().reset(); // Removes roster contacts this.roster.off().reset(); // Removes roster contacts
} }
this.chatboxes.remove(); // Don't call off(), events won't get re-registered upon reconnect. this.chatboxes.remove(); // Don't call off(), events won't get re-registered upon reconnect.
delete this.chatboxes.browserStorage; delete this.chatboxes.browserStorage;
if (this.features) {
this.features.reset();
this.features.browserStorage._clear();
}
this.session.destroy(); this.session.destroy();
window.removeEventListener('click', _converse.onUserActivity); window.removeEventListener('click', _converse.onUserActivity);
window.removeEventListener('focus', _converse.onUserActivity); window.removeEventListener('focus', _converse.onUserActivity);
@ -2182,6 +2092,7 @@
window.removeEventListener('mousemove', _converse.onUserActivity); window.removeEventListener('mousemove', _converse.onUserActivity);
window.removeEventListener(unloadevent, _converse.onUserActivity); window.removeEventListener(unloadevent, _converse.onUserActivity);
window.clearInterval(_converse.everySecondTrigger); window.clearInterval(_converse.everySecondTrigger);
_converse.emit('afterTearDown');
return this; return this;
}; };

View File

@ -84,7 +84,12 @@
* All features are shown here: http://xmpp.org/registrar/disco-features.html * All features are shown here: http://xmpp.org/registrar/disco-features.html
*/ */
model: Backbone.Model, model: Backbone.Model,
initialize (jid) {
initialize (settings) {
const jid = settings.jid;
if (_.isNil(jid)) {
throw new Error('DiscoEntity must be instantiated with a JID');
}
this.addClientIdentities().addClientFeatures(); this.addClientIdentities().addClientFeatures();
this.browserStorage = new Backbone.BrowserStorage[_converse.storage]( this.browserStorage = new Backbone.BrowserStorage[_converse.storage](
b64_sha1(`converse.features-${jid}`) b64_sha1(`converse.features-${jid}`)
@ -158,9 +163,11 @@
} }
}); });
_converse.api.waitUntil('connected').then(() => { function initializeDisco () {
_converse.disco_entities = new _converse.DiscoEntities(); _converse.disco_entities = new _converse.DiscoEntities();
}); }
_converse.api.listen.on('reconnected', initializeDisco);
_converse.api.listen.on('connected', initializeDisco);
_converse.api.listen.on('beforeTearDown', () => { _converse.api.listen.on('beforeTearDown', () => {
if (_converse.disco_entities) { if (_converse.disco_entities) {

View File

@ -11,6 +11,7 @@
(function (root, factory) { (function (root, factory) {
define(["jquery.noconflict", define(["jquery.noconflict",
"converse-core", "converse-core",
"converse-disco",
"converse-chatview", // Could be made a soft dependency "converse-chatview", // Could be made a soft dependency
"converse-muc", // Could be made a soft dependency "converse-muc", // Could be made a soft dependency
"strophe.rsm" "strophe.rsm"
@ -31,15 +32,6 @@
// relevant objects or classes. // relevant objects or classes.
// //
// New functions which don't exist yet can also be added. // New functions which don't exist yet can also be added.
Features: {
addClientFeatures () {
const { _converse } = this.__super__;
_converse.connection.disco.addFeature(Strophe.NS.MAM);
return this.__super__.addClientFeatures.apply(this, arguments);
}
},
ChatBox: { ChatBox: {
getMessageAttributes ($message, $delay, original_stanza) { getMessageAttributes ($message, $delay, original_stanza) {
const attrs = this.__super__.getMessageAttributes.apply(this, arguments); const attrs = this.__super__.getMessageAttributes.apply(this, arguments);
@ -60,7 +52,8 @@
afterMessagesFetched () { afterMessagesFetched () {
const { _converse } = this.__super__; const { _converse } = this.__super__;
if (this.disable_mam || if (this.disable_mam ||
!_converse.features.findWhere({'var': Strophe.NS.MAM})) { !_converse.disco_entities.get(_converse.domain)
.features.findWhere({'var': Strophe.NS.MAM})) {
return this.__super__.afterMessagesFetched.apply(this, arguments); return this.__super__.afterMessagesFetched.apply(this, arguments);
} }
if (!this.model.get('mam_initialized') && if (!this.model.get('mam_initialized') &&
@ -83,7 +76,9 @@
* box, so that they are displayed inside it. * box, so that they are displayed inside it.
*/ */
const { _converse } = this.__super__; const { _converse } = this.__super__;
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain)
.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.log( _converse.log(
"Attempted to fetch archived messages but this "+ "Attempted to fetch archived messages but this "+
"user's server doesn't support XEP-0313", "user's server doesn't support XEP-0313",
@ -167,7 +162,9 @@
* so that they are displayed inside it. * so that they are displayed inside it.
*/ */
const { _converse } = this.__super__; const { _converse } = this.__super__;
if (!_converse.features.findWhere({'var': Strophe.NS.MAM})) { if (!_converse.disco_entities.get(_converse.domain)
.features.findWhere({'var': Strophe.NS.MAM})) {
_converse.log( _converse.log(
"Attempted to fetch archived messages but this "+ "Attempted to fetch archived messages but this "+
"user's server doesn't support XEP-0313", "user's server doesn't support XEP-0313",
@ -237,12 +234,12 @@
const queryid = _converse.connection.getUniqueId(); const queryid = _converse.connection.getUniqueId();
const attrs = {'type':'set'}; const attrs = {'type':'set'};
if (!_.isUndefined(options) && options.groupchat) { if (!_.isUndefined(options) && options.groupchat) {
if (!options['with']) { if (!options['with']) { // eslint-disable-line dot-notation
throw new Error( throw new Error(
'You need to specify a "with" value containing '+ 'You need to specify a "with" value containing '+
'the chat room JID, when querying groupchat messages.'); 'the chat room JID, when querying groupchat messages.');
} }
attrs.to = options['with']; attrs.to = options['with']; // eslint-disable-line dot-notation
} }
const stanza = $iq(attrs).c('query', {'xmlns':Strophe.NS.MAM, 'queryid':queryid}); const stanza = $iq(attrs).c('query', {'xmlns':Strophe.NS.MAM, 'queryid':queryid});
if (!_.isUndefined(options)) { if (!_.isUndefined(options)) {
@ -250,8 +247,9 @@
.c('field', {'var':'FORM_TYPE', 'type': 'hidden'}) .c('field', {'var':'FORM_TYPE', 'type': 'hidden'})
.c('value').t(Strophe.NS.MAM).up().up(); .c('value').t(Strophe.NS.MAM).up().up();
if (options['with'] && !options.groupchat) { if (options['with'] && !options.groupchat) { // eslint-disable-line dot-notation
stanza.c('field', {'var':'with'}).c('value').t(options['with']).up().up(); stanza.c('field', {'var':'with'}).c('value')
.t(options['with']).up().up(); // eslint-disable-line dot-notation
} }
_.each(['start', 'end'], function (t) { _.each(['start', 'end'], function (t) {
if (options[t]) { if (options[t]) {
@ -352,11 +350,11 @@
} }
}; };
/* Event handlers */
const onFeatureAdded = function (feature) { _converse.on('serviceDiscovered', (feature) => {
const prefs = feature.get('preferences') || {}; const prefs = feature.get('preferences') || {};
if (feature.get('var') === Strophe.NS.MAM && if (feature.get('var') === Strophe.NS.MAM &&
prefs['default'] !== _converse.message_archiving && prefs['default'] !== _converse.message_archiving && // eslint-disable-line dot-notation
!_.isUndefined(_converse.message_archiving) ) { !_.isUndefined(_converse.message_archiving) ) {
// Ask the server for archiving preferences // Ask the server for archiving preferences
_converse.connection.sendIQ( _converse.connection.sendIQ(
@ -365,8 +363,11 @@
_.partial(_converse.onMAMError, feature) _.partial(_converse.onMAMError, feature)
); );
} }
}; });
_converse.on('serviceDiscovered', onFeatureAdded.bind(_converse.features));
_converse.on('addClientFeatures', () => {
_converse.connection.disco.addFeature(Strophe.NS.MAM);
});
} }
}); });
})); }));

View File

@ -32,7 +32,8 @@
"tpl!room_panel", "tpl!room_panel",
"tpl!spinner", "tpl!spinner",
"awesomplete", "awesomplete",
"converse-chatview" "converse-chatview",
"converse-disco"
], factory); ], factory);
}(this, function ( }(this, function (
$, $,
@ -130,19 +131,6 @@
this.__super__._tearDown.call(this, arguments); this.__super__._tearDown.call(this, arguments);
}, },
Features: {
addClientFeatures () {
const { _converse } = this.__super__;
this.__super__.addClientFeatures.apply(this, arguments);
if (_converse.allow_muc_invitations) {
_converse.connection.disco.addFeature('jabber:x:conference'); // Invites
}
if (_converse.allow_muc) {
_converse.connection.disco.addFeature(Strophe.NS.MUC);
}
}
},
ChatBoxes: { ChatBoxes: {
model (attrs, options) { model (attrs, options) {
const { _converse } = this.__super__; const { _converse } = this.__super__;
@ -182,6 +170,33 @@
} }
}, },
featureAdded (feature) {
const { _converse } = this.__super__;
if ((feature.get('var') === Strophe.NS.MUC) && (_converse.allow_muc)) {
this.setMUCDomain(feature.get('from'));
}
},
getMUCDomainFromDisco () {
/* Check whether service discovery for the user's domain
* returned MUC information and use that to automatically
* set the MUC domain for the "Rooms" panel of the
* controlbox.
*/
const { _converse } = this.__super__;
_converse.api.waitUntil('discoInitialized').then(() => {
_converse.api.listen.on('serviceDiscovered', this.featureAdded, this);
// Features could have been added before the controlbox was
// initialized. We're only interested in MUC
const feature = _converse.disco_entities[_converse.domain].features.findWhere({
'var': Strophe.NS.MUC
});
if (feature) {
this.featureAdded(feature);
}
});
},
onConnected () { onConnected () {
const { _converse } = this.__super__; const { _converse } = this.__super__;
this.__super__.onConnected.apply(this, arguments); this.__super__.onConnected.apply(this, arguments);
@ -189,34 +204,20 @@
return; return;
} }
if (_.isUndefined(_converse.muc_domain)) { if (_.isUndefined(_converse.muc_domain)) {
_converse.features.off('add', this.featureAdded, this); this.getMUCDomainFromDisco();
_converse.features.on('add', this.featureAdded, this);
// Features could have been added before the controlbox was
// initialized. We're only interested in MUC
const feature = _converse.features.findWhere({
'var': Strophe.NS.MUC
});
if (feature) {
this.featureAdded(feature);
}
} else { } else {
this.setMUCDomain(_converse.muc_domain); this.setMUCDomain(_converse.muc_domain);
} }
}, },
setMUCDomain (domain) { setMUCDomain (domain) {
const { _converse } = this.__super__;
_converse.muc_domain = domain;
this.roomspanel.model.save({'muc_domain': domain}); this.roomspanel.model.save({'muc_domain': domain});
const $server= this.$el.find('input.new-chatroom-server'); const $server= this.$el.find('input.new-chatroom-server');
if (!$server.is(':focus')) { if (!$server.is(':focus')) {
$server.val(this.roomspanel.model.get('muc_domain')); $server.val(this.roomspanel.model.get('muc_domain'));
} }
},
featureAdded (feature) {
const { _converse } = this.__super__;
if ((feature.get('var') === Strophe.NS.MUC) && (_converse.allow_muc)) {
this.setMUCDomain(feature.get('from'));
}
} }
}, },
@ -1974,7 +1975,7 @@
return false; return false;
} else { } else {
return this.model.messages.where({ return this.model.messages.where({
'sender': this.model.get('nick'), 'sender': 'me',
'message': this.model.getMessageBody(message) 'message': this.model.getMessageBody(message)
}).filter( }).filter(
(msg) => Math.abs(moment(msg.get('time')).diff(moment.unix(ts))) < 2000 (msg) => Math.abs(moment(msg.get('time')).diff(moment.unix(ts))) < 2000
@ -2778,7 +2779,17 @@
} }
}); });
function reconnectToChatRooms () { /* Event handlers */
_converse.on('addClientFeatures', () => {
if (_converse.allow_muc) {
_converse.connection.disco.addFeature(Strophe.NS.MUC);
}
if (_converse.allow_muc_invitations) {
_converse.connection.disco.addFeature('jabber:x:conference'); // Invites
}
});
_converse.on('reconnected', function reconnectToChatRooms () {
/* Upon a reconnection event from converse, join again /* Upon a reconnection event from converse, join again
* all the open chat rooms. * all the open chat rooms.
*/ */
@ -2790,8 +2801,7 @@
view.fetchMessages(); view.fetchMessages();
} }
}); });
} });
_converse.on('reconnected', reconnectToChatRooms);
function disconnectChatRooms () { function disconnectChatRooms () {
/* When disconnecting, or reconnecting, mark all chat rooms as /* When disconnecting, or reconnecting, mark all chat rooms as

View File

@ -34,7 +34,7 @@
// However, some servers don't advertise while still keeping the // However, some servers don't advertise while still keeping the
// connection option due to pings. // connection option due to pings.
// //
// var feature = _converse.features.findWhere({'var': Strophe.NS.PING}); // var feature = _converse.disco_entities[_converse.domain].features.findWhere({'var': Strophe.NS.PING});
_converse.lastStanzaDate = new Date(); _converse.lastStanzaDate = new Date();
if (_.isNil(jid)) { if (_.isNil(jid)) {
jid = Strophe.getDomainFromJid(_converse.bare_jid); jid = Strophe.getDomainFromJid(_converse.bare_jid);

View File

@ -21,16 +21,6 @@
// //
// New functions which don't exist yet can also be added. // New functions which don't exist yet can also be added.
Features: {
addClientFeatures () {
const { _converse } = this.__super__;
this.__super__.addClientFeatures.apply(this, arguments);
if (_converse.use_vcards) {
_converse.connection.disco.addFeature(Strophe.NS.VCARD);
}
}
},
RosterContacts: { RosterContacts: {
createRequestingContact (presence) { createRequestingContact (presence) {
const { _converse } = this.__super__; const { _converse } = this.__super__;
@ -50,7 +40,6 @@
} }
}, },
initialize () { initialize () {
/* The initialize function gets called as soon as the plugin is /* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery. * loaded by converse.js's plugin machinery.
@ -135,6 +124,13 @@
} }
}; };
/* Event handlers */
_converse.on('addClientFeatures', () => {
if (_converse.use_vcards) {
_converse.connection.disco.addFeature(Strophe.NS.VCARD);
}
});
const updateVCardForChatBox = function (chatbox) { const updateVCardForChatBox = function (chatbox) {
if (!_converse.use_vcards) { return; } if (!_converse.use_vcards) { return; }
const jid = chatbox.model.get('jid'), const jid = chatbox.model.get('jid'),
@ -161,7 +157,6 @@
}; };
_converse.on('chatBoxInitialized', updateVCardForChatBox); _converse.on('chatBoxInitialized', updateVCardForChatBox);
const onContactAdd = function (contact) { const onContactAdd = function (contact) {
if (!contact.get('vcard_updated')) { if (!contact.get('vcard_updated')) {
// This will update the vcard, which triggers a change // This will update the vcard, which triggers a change
@ -173,7 +168,7 @@
_converse.roster.on("add", onContactAdd); _converse.roster.on("add", onContactAdd);
}); });
const fetchOwnVCard = function () { _converse.on('statusInitialized', function fetchOwnVCard () {
if (_converse.xmppstatus.get('fullname') === undefined) { if (_converse.xmppstatus.get('fullname') === undefined) {
_converse.getVCard( _converse.getVCard(
null, // No 'to' attr when getting one's own vCard null, // No 'to' attr when getting one's own vCard
@ -182,8 +177,7 @@
} }
); );
} }
}; });
_converse.on('statusInitialized', fetchOwnVCard);
} }
}); });
})); }));