Skip to content

Commit 9d77a4e

Browse files
committed
Fixes #129 Add support for XEP-0156.
Only XML is supported for now.
1 parent 54e9c51 commit 9d77a4e

19 files changed

+335
-263
lines changed

CHANGES.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@
22

33
## 6.0.0 (Unreleased)
44

5+
- #129: Add support for XEP-0156: Disovering Alternative XMPP Connection Methods. Only XML is supported for now.
56
- #1691 Fix `collection.chatbox is undefined` errors
67
- Prevent editing of sent file uploads.
78

89
### Breaking changes
910

11+
- In order to add support for XEP-0156, the XMPP connection needs to be created
12+
only once we know the JID of the user that's logging in. This means that the
13+
[connectionInitialized](https://conversejs.org/docs/html/api/-_converse.html#event:connectionInitialized)
14+
event now fires much later than before. Plugins that rely on `connectionInitialized`
15+
being triggered before the user's JID has been provided will need to be updated.
16+
1017
- The following API methods now return promises:
1118
* `_converse.api.chats.get`
1219
* `_converse.api.chats.create`

docs/source/configuration.rst

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,23 @@ The default chat status that the user wil have. If you for example set this to
639639
``'chat'``, then Converse will send out a presence stanza with ``"show"``
640640
set to ``'chat'`` as soon as you've been logged in.
641641

642+
643+
discover_connection_methods
644+
---------------------------
645+
646+
* Default: ``false``
647+
648+
Use `XEP-0156 <https://xmpp.org/extensions/xep-0156.html>`_ to discover whether
649+
the XMPP host for the current user advertises any Websocket or BOSH connection
650+
URLs that can be used.
651+
652+
If this is set to ``false``, then a `websocket_url`_ or `bosh_service_url`_ need to be
653+
set.
654+
655+
Currently only the XML encoded host-meta resource is supported as shown in
656+
`Example 2 under section 3.3 <https://xmpp.org/extensions/xep-0156.html#httpexamples>`_.
657+
658+
642659
domain_placeholder
643660
------------------
644661

@@ -647,8 +664,6 @@ domain_placeholder
647664
The placeholder text shown in the domain input on the registration form.
648665

649666

650-
651-
652667
emoji_image_path
653668
----------------
654669

@@ -1624,6 +1639,7 @@ Allows you to show or hide buttons on the chatboxes' toolbars.
16241639

16251640
.. _`websocket-url`:
16261641

1642+
16271643
websocket_url
16281644
-------------
16291645

spec/bookmarks.js

Lines changed: 24 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,8 @@
404404

405405

406406
it("can be retrieved from the XMPP server", mock.initConverse(
407-
{'connection': ['send']}, ['chatBoxesFetched', 'roomsPanelRendered', 'rosterGroupsFetched'], {},
408-
async function (done, _converse) {
407+
null, ['chatBoxesFetched', 'roomsPanelRendered', 'rosterGroupsFetched'], {},
408+
async function (done, _converse) {
409409

410410
await test_utils.waitUntilDiscoConfirmed(
411411
_converse, _converse.bare_jid,
@@ -421,25 +421,12 @@
421421
* </pubsub>
422422
* </iq>
423423
*/
424-
let IQ_id;
425-
const call = await u.waitUntil(() =>
426-
_.filter(
427-
_converse.connection.send.calls.all(),
428-
call => {
429-
const stanza = call.args[0];
430-
if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
431-
return;
432-
}
433-
if (sizzle('items[node="storage:bookmarks"]', stanza).length) {
434-
IQ_id = stanza.getAttribute('id');
435-
return true;
436-
}
437-
}
438-
).pop()
439-
);
424+
const IQ_stanzas = _converse.connection.IQ_stanzas;
425+
const sent_stanza = await u.waitUntil(
426+
() => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop());
440427

441-
expect(Strophe.serialize(call.args[0])).toBe(
442-
`<iq from="[email protected]/orchard" id="${IQ_id}" type="get" xmlns="jabber:client">`+
428+
expect(Strophe.serialize(sent_stanza)).toBe(
429+
`<iq from="[email protected]/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
443430
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
444431
'<items node="storage:bookmarks"/>'+
445432
'</pubsub>'+
@@ -469,7 +456,7 @@
469456
expect(_converse.bookmarks.models.length).toBe(0);
470457

471458
spyOn(_converse.bookmarks, 'onBookmarksReceived').and.callThrough();
472-
var stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
459+
var stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':sent_stanza.getAttribute('id')})
473460
.c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
474461
.c('items', {'node': 'storage:bookmarks'})
475462
.c('item', {'id': 'current'})
@@ -495,7 +482,7 @@
495482
describe("The rooms panel", function () {
496483

497484
it("shows a list of bookmarks", mock.initConverse(
498-
{'connection': ['send']}, ['rosterGroupsFetched'], {},
485+
null, ['rosterGroupsFetched'], {},
499486
async function (done, _converse) {
500487

501488
await test_utils.waitUntilDiscoConfirmed(
@@ -505,31 +492,19 @@
505492
);
506493
test_utils.openControlBox();
507494

508-
let IQ_id;
509-
const call = await u.waitUntil(() =>
510-
_.filter(
511-
_converse.connection.send.calls.all(),
512-
call => {
513-
const stanza = call.args[0];
514-
if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
515-
return;
516-
}
517-
if (sizzle('items[node="storage:bookmarks"]', stanza).length) {
518-
IQ_id = stanza.getAttribute('id');
519-
return true;
520-
}
521-
}
522-
).pop()
523-
);
524-
expect(Strophe.serialize(call.args[0])).toBe(
525-
`<iq from="[email protected]/orchard" id="${IQ_id}" type="get" xmlns="jabber:client">`+
495+
const IQ_stanzas = _converse.connection.IQ_stanzas;
496+
const sent_stanza = await u.waitUntil(
497+
() => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop());
498+
499+
expect(Strophe.serialize(sent_stanza)).toBe(
500+
`<iq from="[email protected]/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
526501
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
527502
'<items node="storage:bookmarks"/>'+
528503
'</pubsub>'+
529504
'</iq>'
530505
);
531506

532-
const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
507+
const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':sent_stanza.getAttribute('id')})
533508
.c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
534509
.c('items', {'node': 'storage:bookmarks'})
535510
.c('item', {'id': 'current'})
@@ -583,7 +558,7 @@
583558

584559

585560
it("remembers the toggle state of the bookmarks list", mock.initConverse(
586-
{'connection': ['send']}, ['rosterGroupsFetched'], {},
561+
null, ['rosterGroupsFetched'], {},
587562
async function (done, _converse) {
588563

589564
test_utils.openControlBox();
@@ -593,31 +568,19 @@
593568
['http://jabber.org/protocol/pubsub#publish-options']
594569
);
595570

596-
let IQ_id;
597-
const call = await u.waitUntil(() =>
598-
_.filter(
599-
_converse.connection.send.calls.all(),
600-
call => {
601-
const stanza = call.args[0];
602-
if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
603-
return;
604-
}
605-
if (sizzle('items[node="storage:bookmarks"]', stanza).length) {
606-
IQ_id = stanza.getAttribute('id');
607-
return true;
608-
}
609-
}
610-
).pop()
611-
);
612-
expect(Strophe.serialize(call.args[0])).toBe(
613-
`<iq from="[email protected]/orchard" id="${IQ_id}" type="get" xmlns="jabber:client">`+
571+
const IQ_stanzas = _converse.connection.IQ_stanzas;
572+
const sent_stanza = await u.waitUntil(
573+
() => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop());
574+
575+
expect(Strophe.serialize(sent_stanza)).toBe(
576+
`<iq from="[email protected]/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
614577
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
615578
'<items node="storage:bookmarks"/>'+
616579
'</pubsub>'+
617580
'</iq>'
618581
);
619582

620-
const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id})
583+
const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':sent_stanza.getAttribute('id')})
621584
.c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
622585
.c('items', {'node': 'storage:bookmarks'})
623586
.c('item', {'id': 'current'})

spec/login.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
it("contains a checkbox to indicate whether the computer is trusted or not",
1010
mock.initConverse(
11-
null, ['connectionInitialized', 'chatBoxesInitialized'],
11+
null, ['chatBoxesInitialized'],
1212
{ auto_login: false,
1313
allow_registration: false },
1414
async function (done, _converse) {
@@ -42,7 +42,7 @@
4242

4343
it("checkbox can be set to false by default",
4444
mock.initConverse(
45-
null, ['connectionInitialized', 'chatBoxesInitialized'],
45+
null, ['chatBoxesInitialized'],
4646
{ auto_login: false,
4747
trusted: false,
4848
allow_registration: false },

spec/register.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
it("is not available unless allow_registration=true",
1212
mock.initConverse(
13-
null, ['connectionInitialized', 'chatBoxesInitialized'],
13+
null, ['chatBoxesInitialized'],
1414
{ auto_login: false,
1515
allow_registration: false },
1616
async function (done, _converse) {
@@ -24,7 +24,7 @@
2424

2525
it("can be opened by clicking on the registration tab",
2626
mock.initConverse(
27-
null, ['connectionInitialized', 'chatBoxesInitialized'],
27+
null, ['chatBoxesInitialized'],
2828
{ auto_login: false,
2929
allow_registration: true },
3030
async function (done, _converse) {
@@ -45,18 +45,18 @@
4545

4646
it("allows the user to choose an XMPP provider's domain",
4747
mock.initConverse(
48-
null, ['connectionInitialized', 'chatBoxesInitialized'],
48+
null, ['chatBoxesInitialized'],
4949
{ auto_login: false,
5050
allow_registration: true },
5151
async function (done, _converse) {
5252

53+
spyOn(Strophe.Connection.prototype, 'connect');
5354
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
5455
test_utils.openControlBox();
5556
const cbview = _converse.chatboxviews.get('controlbox');
5657
const registerview = cbview.registerpanel;
5758
spyOn(registerview, 'onProviderChosen').and.callThrough();
5859
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
59-
spyOn(_converse.connection, 'connect');
6060

6161
// Open the register panel
6262
cbview.el.querySelector('.toggle-register-login').click();
@@ -75,17 +75,18 @@
7575
form.querySelector('input[name=domain]').value = 'conversejs.org';
7676
submit_button.click();
7777
expect(registerview.onProviderChosen).toHaveBeenCalled();
78-
expect(_converse.connection.connect).toHaveBeenCalled();
78+
await u.waitUntil(() => _converse.connection.connect.calls.count());
7979
done();
8080
}));
8181

8282
it("will render a registration form as received from the XMPP provider",
8383
mock.initConverse(
84-
null, ['connectionInitialized', 'chatBoxesInitialized'],
84+
null, ['chatBoxesInitialized'],
8585
{ auto_login: false,
8686
allow_registration: true },
8787
async function (done, _converse) {
8888

89+
spyOn(Strophe.Connection.prototype, 'connect');
8990
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
9091
test_utils.openControlBox();
9192
const cbview = _converse.chatboxviews.get('controlbox');
@@ -97,15 +98,14 @@
9798
spyOn(registerview, 'onRegistrationFields').and.callThrough();
9899
spyOn(registerview, 'renderRegistrationForm').and.callThrough();
99100
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
100-
spyOn(_converse.connection, 'connect').and.callThrough();
101101

102102
expect(registerview._registering).toBeFalsy();
103103
expect(_converse.connection.connected).toBeFalsy();
104104
registerview.el.querySelector('input[name=domain]').value = 'conversejs.org';
105105
registerview.el.querySelector('input[type=submit]').click();
106106
expect(registerview.onProviderChosen).toHaveBeenCalled();
107107
expect(registerview._registering).toBeTruthy();
108-
expect(_converse.connection.connect).toHaveBeenCalled();
108+
await u.waitUntil(() => _converse.connection.connect.calls.count());
109109

110110
let stanza = new Strophe.Builder("stream:features", {
111111
'xmlns:stream': "http://etherx.jabber.org/streams",
@@ -137,7 +137,7 @@
137137

138138
it("will set form_type to legacy and submit it as legacy",
139139
mock.initConverse(
140-
null, ['connectionInitialized', 'chatBoxesInitialized'],
140+
null, ['chatBoxesInitialized'],
141141
{ auto_login: false,
142142
allow_registration: true },
143143
async function (done, _converse) {
@@ -194,7 +194,7 @@
194194

195195
it("will set form_type to xform and submit it as xform",
196196
mock.initConverse(
197-
null, ['connectionInitialized', 'chatBoxesInitialized'],
197+
null, ['chatBoxesInitialized'],
198198
{ auto_login: false,
199199
allow_registration: true },
200200
async function (done, _converse) {
@@ -267,7 +267,7 @@
267267

268268
it("renders the account registration form",
269269
mock.initConverse(
270-
null, ['connectionInitialized', 'chatBoxesInitialized'],
270+
null, ['chatBoxesInitialized'],
271271
{ auto_login: false,
272272
view_mode: 'fullscreen',
273273
allow_registration: true },

spec/roomslist.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353

5454
it("uses bookmarks to determine groupchat names",
5555
mock.initConverse(
56-
{'connection': ['send']},
56+
null,
5757
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
5858
{'view_mode': 'fullscreen'},
5959
async function (done, _converse) {
@@ -113,7 +113,7 @@
113113

114114
describe("A groupchat shown in the groupchats list", function () {
115115

116-
it("is highlighted if its currently open", mock.initConverse(
116+
it("is highlighted if it's currently open", mock.initConverse(
117117
null, ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
118118
{ view_mode: 'fullscreen',
119119
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
@@ -137,8 +137,6 @@
137137
expect(room_els.length).toBe(1);
138138
item = room_els[0];
139139
expect(item.textContent.trim()).toBe('[email protected]');
140-
const conv_el = document.querySelector('#conversejs');
141-
conv_el.parentElement.removeChild(conv_el);
142140
done();
143141
}));
144142

spec/smacks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
it("gets enabled with an <enable> stanza and resumed with a <resume> stanza",
1313
mock.initConverse(
14-
null, ['connectionInitialized', 'chatBoxesInitialized'],
14+
null, ['chatBoxesInitialized'],
1515
{ 'auto_login': false,
1616
'enable_smacks': true,
1717
'show_controlbox_by_default': true,

src/converse-chatview.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1124,7 +1124,7 @@ converse.plugins.add('converse-chatview', {
11241124
if (Backbone.history.getFragment() === "converse/chat?jid="+this.model.get('jid')) {
11251125
_converse.router.navigate('');
11261126
}
1127-
if (_converse.connection.connected) {
1127+
if (_converse.api.connection.connected()) {
11281128
// Immediately sending the chat state, because the
11291129
// model is going to be destroyed afterwards.
11301130
this.model.setChatState(_converse.INACTIVE);

0 commit comments

Comments
 (0)