Commit 9d77a4ef authored by JC Brand's avatar JC Brand

Fixes #129 Add support for XEP-0156.

Only XML is supported for now.
parent 54e9c51a
...@@ -2,11 +2,18 @@ ...@@ -2,11 +2,18 @@
## 6.0.0 (Unreleased) ## 6.0.0 (Unreleased)
- #129: Add support for XEP-0156: Disovering Alternative XMPP Connection Methods. Only XML is supported for now.
- #1691 Fix `collection.chatbox is undefined` errors - #1691 Fix `collection.chatbox is undefined` errors
- Prevent editing of sent file uploads. - Prevent editing of sent file uploads.
### Breaking changes ### Breaking changes
- In order to add support for XEP-0156, the XMPP connection needs to be created
only once we know the JID of the user that's logging in. This means that the
[connectionInitialized](https://conversejs.org/docs/html/api/-_converse.html#event:connectionInitialized)
event now fires much later than before. Plugins that rely on `connectionInitialized`
being triggered before the user's JID has been provided will need to be updated.
- The following API methods now return promises: - The following API methods now return promises:
* `_converse.api.chats.get` * `_converse.api.chats.get`
* `_converse.api.chats.create` * `_converse.api.chats.create`
......
...@@ -639,6 +639,23 @@ The default chat status that the user wil have. If you for example set this to ...@@ -639,6 +639,23 @@ The default chat status that the user wil have. If you for example set this to
``'chat'``, then Converse will send out a presence stanza with ``"show"`` ``'chat'``, then Converse will send out a presence stanza with ``"show"``
set to ``'chat'`` as soon as you've been logged in. set to ``'chat'`` as soon as you've been logged in.
discover_connection_methods
---------------------------
* Default: ``false``
Use `XEP-0156 <https://xmpp.org/extensions/xep-0156.html>`_ to discover whether
the XMPP host for the current user advertises any Websocket or BOSH connection
URLs that can be used.
If this is set to ``false``, then a `websocket_url`_ or `bosh_service_url`_ need to be
set.
Currently only the XML encoded host-meta resource is supported as shown in
`Example 2 under section 3.3 <https://xmpp.org/extensions/xep-0156.html#httpexamples>`_.
domain_placeholder domain_placeholder
------------------ ------------------
...@@ -647,8 +664,6 @@ domain_placeholder ...@@ -647,8 +664,6 @@ domain_placeholder
The placeholder text shown in the domain input on the registration form. The placeholder text shown in the domain input on the registration form.
emoji_image_path emoji_image_path
---------------- ----------------
...@@ -1624,6 +1639,7 @@ Allows you to show or hide buttons on the chatboxes' toolbars. ...@@ -1624,6 +1639,7 @@ Allows you to show or hide buttons on the chatboxes' toolbars.
.. _`websocket-url`: .. _`websocket-url`:
websocket_url websocket_url
------------- -------------
......
...@@ -404,8 +404,8 @@ ...@@ -404,8 +404,8 @@
it("can be retrieved from the XMPP server", mock.initConverse( it("can be retrieved from the XMPP server", mock.initConverse(
{'connection': ['send']}, ['chatBoxesFetched', 'roomsPanelRendered', 'rosterGroupsFetched'], {}, null, ['chatBoxesFetched', 'roomsPanelRendered', 'rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitUntilDiscoConfirmed( await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid, _converse, _converse.bare_jid,
...@@ -421,25 +421,12 @@ ...@@ -421,25 +421,12 @@
* </pubsub> * </pubsub>
* </iq> * </iq>
*/ */
let IQ_id; const IQ_stanzas = _converse.connection.IQ_stanzas;
const call = await u.waitUntil(() => const sent_stanza = await u.waitUntil(
_.filter( () => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop());
_converse.connection.send.calls.all(),
call => {
const stanza = call.args[0];
if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
return;
}
if (sizzle('items[node="storage:bookmarks"]', stanza).length) {
IQ_id = stanza.getAttribute('id');
return true;
}
}
).pop()
);
expect(Strophe.serialize(call.args[0])).toBe( expect(Strophe.serialize(sent_stanza)).toBe(
`<iq from="romeo@montague.lit/orchard" id="${IQ_id}" type="get" xmlns="jabber:client">`+ `<iq from="romeo@montague.lit/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+ '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<items node="storage:bookmarks"/>'+ '<items node="storage:bookmarks"/>'+
'</pubsub>'+ '</pubsub>'+
...@@ -469,7 +456,7 @@ ...@@ -469,7 +456,7 @@
expect(_converse.bookmarks.models.length).toBe(0); expect(_converse.bookmarks.models.length).toBe(0);
spyOn(_converse.bookmarks, 'onBookmarksReceived').and.callThrough(); spyOn(_converse.bookmarks, 'onBookmarksReceived').and.callThrough();
var stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id}) var stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':sent_stanza.getAttribute('id')})
.c('pubsub', {'xmlns': Strophe.NS.PUBSUB}) .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
.c('items', {'node': 'storage:bookmarks'}) .c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'}) .c('item', {'id': 'current'})
...@@ -495,7 +482,7 @@ ...@@ -495,7 +482,7 @@
describe("The rooms panel", function () { describe("The rooms panel", function () {
it("shows a list of bookmarks", mock.initConverse( it("shows a list of bookmarks", mock.initConverse(
{'connection': ['send']}, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
await test_utils.waitUntilDiscoConfirmed( await test_utils.waitUntilDiscoConfirmed(
...@@ -505,31 +492,19 @@ ...@@ -505,31 +492,19 @@
); );
test_utils.openControlBox(); test_utils.openControlBox();
let IQ_id; const IQ_stanzas = _converse.connection.IQ_stanzas;
const call = await u.waitUntil(() => const sent_stanza = await u.waitUntil(
_.filter( () => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop());
_converse.connection.send.calls.all(),
call => { expect(Strophe.serialize(sent_stanza)).toBe(
const stanza = call.args[0]; `<iq from="romeo@montague.lit/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
return;
}
if (sizzle('items[node="storage:bookmarks"]', stanza).length) {
IQ_id = stanza.getAttribute('id');
return true;
}
}
).pop()
);
expect(Strophe.serialize(call.args[0])).toBe(
`<iq from="romeo@montague.lit/orchard" id="${IQ_id}" type="get" xmlns="jabber:client">`+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+ '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<items node="storage:bookmarks"/>'+ '<items node="storage:bookmarks"/>'+
'</pubsub>'+ '</pubsub>'+
'</iq>' '</iq>'
); );
const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id}) const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':sent_stanza.getAttribute('id')})
.c('pubsub', {'xmlns': Strophe.NS.PUBSUB}) .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
.c('items', {'node': 'storage:bookmarks'}) .c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'}) .c('item', {'id': 'current'})
...@@ -583,7 +558,7 @@ ...@@ -583,7 +558,7 @@
it("remembers the toggle state of the bookmarks list", mock.initConverse( it("remembers the toggle state of the bookmarks list", mock.initConverse(
{'connection': ['send']}, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
async function (done, _converse) { async function (done, _converse) {
test_utils.openControlBox(); test_utils.openControlBox();
...@@ -593,31 +568,19 @@ ...@@ -593,31 +568,19 @@
['http://jabber.org/protocol/pubsub#publish-options'] ['http://jabber.org/protocol/pubsub#publish-options']
); );
let IQ_id; const IQ_stanzas = _converse.connection.IQ_stanzas;
const call = await u.waitUntil(() => const sent_stanza = await u.waitUntil(
_.filter( () => IQ_stanzas.filter(s => sizzle('items[node="storage:bookmarks"]', s).length).pop());
_converse.connection.send.calls.all(),
call => { expect(Strophe.serialize(sent_stanza)).toBe(
const stanza = call.args[0]; `<iq from="romeo@montague.lit/orchard" id="${sent_stanza.getAttribute('id')}" type="get" xmlns="jabber:client">`+
if (!(stanza instanceof Element) || stanza.nodeName !== 'iq') {
return;
}
if (sizzle('items[node="storage:bookmarks"]', stanza).length) {
IQ_id = stanza.getAttribute('id');
return true;
}
}
).pop()
);
expect(Strophe.serialize(call.args[0])).toBe(
`<iq from="romeo@montague.lit/orchard" id="${IQ_id}" type="get" xmlns="jabber:client">`+
'<pubsub xmlns="http://jabber.org/protocol/pubsub">'+ '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
'<items node="storage:bookmarks"/>'+ '<items node="storage:bookmarks"/>'+
'</pubsub>'+ '</pubsub>'+
'</iq>' '</iq>'
); );
const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':IQ_id}) const stanza = $iq({'to': _converse.connection.jid, 'type':'result', 'id':sent_stanza.getAttribute('id')})
.c('pubsub', {'xmlns': Strophe.NS.PUBSUB}) .c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
.c('items', {'node': 'storage:bookmarks'}) .c('items', {'node': 'storage:bookmarks'})
.c('item', {'id': 'current'}) .c('item', {'id': 'current'})
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
it("contains a checkbox to indicate whether the computer is trusted or not", it("contains a checkbox to indicate whether the computer is trusted or not",
mock.initConverse( mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'], null, ['chatBoxesInitialized'],
{ auto_login: false, { auto_login: false,
allow_registration: false }, allow_registration: false },
async function (done, _converse) { async function (done, _converse) {
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
it("checkbox can be set to false by default", it("checkbox can be set to false by default",
mock.initConverse( mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'], null, ['chatBoxesInitialized'],
{ auto_login: false, { auto_login: false,
trusted: false, trusted: false,
allow_registration: false }, allow_registration: false },
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
it("is not available unless allow_registration=true", it("is not available unless allow_registration=true",
mock.initConverse( mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'], null, ['chatBoxesInitialized'],
{ auto_login: false, { auto_login: false,
allow_registration: false }, allow_registration: false },
async function (done, _converse) { async function (done, _converse) {
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
it("can be opened by clicking on the registration tab", it("can be opened by clicking on the registration tab",
mock.initConverse( mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'], null, ['chatBoxesInitialized'],
{ auto_login: false, { auto_login: false,
allow_registration: true }, allow_registration: true },
async function (done, _converse) { async function (done, _converse) {
...@@ -45,18 +45,18 @@ ...@@ -45,18 +45,18 @@
it("allows the user to choose an XMPP provider's domain", it("allows the user to choose an XMPP provider's domain",
mock.initConverse( mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'], null, ['chatBoxesInitialized'],
{ auto_login: false, { auto_login: false,
allow_registration: true }, allow_registration: true },
async function (done, _converse) { async function (done, _converse) {
spyOn(Strophe.Connection.prototype, 'connect');
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel')); await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox(); test_utils.openControlBox();
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
const registerview = cbview.registerpanel; const registerview = cbview.registerpanel;
spyOn(registerview, 'onProviderChosen').and.callThrough(); spyOn(registerview, 'onProviderChosen').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
spyOn(_converse.connection, 'connect');
// Open the register panel // Open the register panel
cbview.el.querySelector('.toggle-register-login').click(); cbview.el.querySelector('.toggle-register-login').click();
...@@ -75,17 +75,18 @@ ...@@ -75,17 +75,18 @@
form.querySelector('input[name=domain]').value = 'conversejs.org'; form.querySelector('input[name=domain]').value = 'conversejs.org';
submit_button.click(); submit_button.click();
expect(registerview.onProviderChosen).toHaveBeenCalled(); expect(registerview.onProviderChosen).toHaveBeenCalled();
expect(_converse.connection.connect).toHaveBeenCalled(); await u.waitUntil(() => _converse.connection.connect.calls.count());
done(); done();
})); }));
it("will render a registration form as received from the XMPP provider", it("will render a registration form as received from the XMPP provider",
mock.initConverse( mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'], null, ['chatBoxesInitialized'],
{ auto_login: false, { auto_login: false,
allow_registration: true }, allow_registration: true },
async function (done, _converse) { async function (done, _converse) {
spyOn(Strophe.Connection.prototype, 'connect');
await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel')); await u.waitUntil(() => _.get(_converse.chatboxviews.get('controlbox'), 'registerpanel'));
test_utils.openControlBox(); test_utils.openControlBox();
const cbview = _converse.chatboxviews.get('controlbox'); const cbview = _converse.chatboxviews.get('controlbox');
...@@ -97,7 +98,6 @@ ...@@ -97,7 +98,6 @@
spyOn(registerview, 'onRegistrationFields').and.callThrough(); spyOn(registerview, 'onRegistrationFields').and.callThrough();
spyOn(registerview, 'renderRegistrationForm').and.callThrough(); spyOn(registerview, 'renderRegistrationForm').and.callThrough();
registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called registerview.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
spyOn(_converse.connection, 'connect').and.callThrough();
expect(registerview._registering).toBeFalsy(); expect(registerview._registering).toBeFalsy();
expect(_converse.connection.connected).toBeFalsy(); expect(_converse.connection.connected).toBeFalsy();
...@@ -105,7 +105,7 @@ ...@@ -105,7 +105,7 @@
registerview.el.querySelector('input[type=submit]').click(); registerview.el.querySelector('input[type=submit]').click();
expect(registerview.onProviderChosen).toHaveBeenCalled(); expect(registerview.onProviderChosen).toHaveBeenCalled();
expect(registerview._registering).toBeTruthy(); expect(registerview._registering).toBeTruthy();
expect(_converse.connection.connect).toHaveBeenCalled(); await u.waitUntil(() => _converse.connection.connect.calls.count());
let stanza = new Strophe.Builder("stream:features", { let stanza = new Strophe.Builder("stream:features", {
'xmlns:stream': "http://etherx.jabber.org/streams", 'xmlns:stream': "http://etherx.jabber.org/streams",
...@@ -137,7 +137,7 @@ ...@@ -137,7 +137,7 @@
it("will set form_type to legacy and submit it as legacy", it("will set form_type to legacy and submit it as legacy",
mock.initConverse( mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'], null, ['chatBoxesInitialized'],
{ auto_login: false, { auto_login: false,
allow_registration: true }, allow_registration: true },
async function (done, _converse) { async function (done, _converse) {
...@@ -194,7 +194,7 @@ ...@@ -194,7 +194,7 @@
it("will set form_type to xform and submit it as xform", it("will set form_type to xform and submit it as xform",
mock.initConverse( mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'], null, ['chatBoxesInitialized'],
{ auto_login: false, { auto_login: false,
allow_registration: true }, allow_registration: true },
async function (done, _converse) { async function (done, _converse) {
...@@ -267,7 +267,7 @@ ...@@ -267,7 +267,7 @@
it("renders the account registration form", it("renders the account registration form",
mock.initConverse( mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'], null, ['chatBoxesInitialized'],
{ auto_login: false, { auto_login: false,
view_mode: 'fullscreen', view_mode: 'fullscreen',
allow_registration: true }, allow_registration: true },
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
it("uses bookmarks to determine groupchat names", it("uses bookmarks to determine groupchat names",
mock.initConverse( mock.initConverse(
{'connection': ['send']}, null,
['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
{'view_mode': 'fullscreen'}, {'view_mode': 'fullscreen'},
async function (done, _converse) { async function (done, _converse) {
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
describe("A groupchat shown in the groupchats list", function () { describe("A groupchat shown in the groupchats list", function () {
it("is highlighted if its currently open", mock.initConverse( it("is highlighted if it's currently open", mock.initConverse(
null, ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'], null, ['rosterGroupsFetched', 'chatBoxesFetched', 'emojisInitialized'],
{ view_mode: 'fullscreen', { view_mode: 'fullscreen',
allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic. allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
...@@ -137,8 +137,6 @@ ...@@ -137,8 +137,6 @@
expect(room_els.length).toBe(1); expect(room_els.length).toBe(1);
item = room_els[0]; item = room_els[0];
expect(item.textContent.trim()).toBe('balcony@chat.shakespeare.lit'); expect(item.textContent.trim()).toBe('balcony@chat.shakespeare.lit');
const conv_el = document.querySelector('#conversejs');
conv_el.parentElement.removeChild(conv_el);
done(); done();
})); }));
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
it("gets enabled with an <enable> stanza and resumed with a <resume> stanza", it("gets enabled with an <enable> stanza and resumed with a <resume> stanza",
mock.initConverse( mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'], null, ['chatBoxesInitialized'],
{ 'auto_login': false, { 'auto_login': false,
'enable_smacks': true, 'enable_smacks': true,
'show_controlbox_by_default': true, 'show_controlbox_by_default': true,
......
...@@ -1124,7 +1124,7 @@ converse.plugins.add('converse-chatview', { ...@@ -1124,7 +1124,7 @@ converse.plugins.add('converse-chatview', {
if (Backbone.history.getFragment() === "converse/chat?jid="+this.model.get('jid')) { if (Backbone.history.getFragment() === "converse/chat?jid="+this.model.get('jid')) {
_converse.router.navigate(''); _converse.router.navigate('');
} }
if (_converse.connection.connected) { if (_converse.api.connection.connected()) {
// Immediately sending the chat state, because the // Immediately sending the chat state, because the
// model is going to be destroyed afterwards. // model is going to be destroyed afterwards.
this.model.setChatState(_converse.INACTIVE); this.model.setChatState(_converse.INACTIVE);
......
...@@ -154,6 +154,7 @@ converse.plugins.add('converse-controlbox', { ...@@ -154,6 +154,7 @@ converse.plugins.add('converse-controlbox', {
_converse.api.promises.add('controlBoxInitialized'); _converse.api.promises.add('controlBoxInitialized');
const addControlBox = () => _converse.chatboxes.add({'id': 'controlbox'}); const addControlBox = () => _converse.chatboxes.add({'id': 'controlbox'});
_converse.ControlBox = _converse.ChatBox.extend({ _converse.ControlBox = _converse.ChatBox.extend({
...@@ -220,9 +221,9 @@ converse.plugins.add('converse-controlbox', { ...@@ -220,9 +221,9 @@ converse.plugins.add('converse-controlbox', {
} else { } else {
this.hide(); this.hide();
} }
if (!_converse.connection.connected ||
!_converse.connection.authenticated || const connection = get(_converse, 'connection', {});
_converse.connection.disconnecting) { if (!connection.connected || !connection.authenticated || connection.disconnecting) {
this.renderLoginPanel(); this.renderLoginPanel();
} else if (this.model.get('connected')) { } else if (this.model.get('connected')) {
this.renderControlBoxPane(); this.renderControlBoxPane();
...@@ -296,7 +297,8 @@ converse.plugins.add('converse-controlbox', { ...@@ -296,7 +297,8 @@ converse.plugins.add('converse-controlbox', {
if (_converse.sticky_controlbox) { if (_converse.sticky_controlbox) {
return; return;
} }
if (_converse.connection.connected && !_converse.connection.disconnecting) { const connection = get(_converse, 'connection', {});
if (connection.connected && !connection.disconnecting) {
this.model.save({'closed': true}); this.model.save({'closed': true});
} else { } else {
this.model.trigger('hide'); this.model.trigger('hide');
...@@ -319,7 +321,8 @@ converse.plugins.add('converse-controlbox', { ...@@ -319,7 +321,8 @@ converse.plugins.add('converse-controlbox', {
} }
u.addClass('hidden', this.el); u.addClass('hidden', this.el);
_converse.api.trigger('chatBoxClosed', this); _converse.api.trigger('chatBoxClosed', this);
if (!_converse.connection.connected) {
if (!_converse.api.connection.connected()) {
_converse.controlboxtoggle.render(); _converse.controlboxtoggle.render();
} }
_converse.controlboxtoggle.show(callback); _converse.controlboxtoggle.show(callback);
...@@ -464,7 +467,7 @@ converse.plugins.add('converse-controlbox', { ...@@ -464,7 +467,7 @@ converse.plugins.add('converse-controlbox', {
if (["converse/login", "converse/register"].includes(Backbone.history.getFragment())) { if (["converse/login", "converse/register"].includes(Backbone.history.getFragment())) {
_converse.router.navigate('', {'replace': true}); _converse.router.navigate('', {'replace': true});
} }
_converse.connection.reset(); _converse.connection && _converse.connection.reset();
_converse.api.user.login(jid, password); _converse.api.user.login(jid, password);
} }
}); });
...@@ -510,7 +513,7 @@ converse.plugins.add('converse-controlbox', { ...@@ -510,7 +513,7 @@ converse.plugins.add('converse-controlbox', {
// artifacts (i.e. on page load the toggle is shown only to then // artifacts (i.e. on page load the toggle is shown only to then
// seconds later be hidden in favor of the controlbox). // seconds later be hidden in favor of the controlbox).
this.el.innerHTML = tpl_controlbox_toggle({ this.el.innerHTML = tpl_controlbox_toggle({
'label_toggle': _converse.connection.connected ? __('Chat Contacts') : __('Toggle chat') 'label_toggle': _converse.api.connection.connected() ? __('Chat Contacts') : __('Toggle chat')
}) })
return this; return this;
}, },
...@@ -529,7 +532,7 @@ converse.plugins.add('converse-controlbox', { ...@@ -529,7 +532,7 @@ converse.plugins.add('converse-controlbox', {
if (!controlbox) { if (!controlbox) {
controlbox = addControlBox(); controlbox = addControlBox();
} }
if (_converse.connection.connected) { if (_converse.api.connection.connected()) {
controlbox.save({'closed': false}); controlbox.save({'closed': false});
} else { } else {
controlbox.trigger('show'); controlbox.trigger('show');
...@@ -540,7 +543,7 @@ converse.plugins.add('converse-controlbox', { ...@@ -540,7 +543,7 @@ converse.plugins.add('converse-controlbox', {
e.preventDefault(); e.preventDefault();
if (u.isVisible(_converse.root.querySelector("#controlbox"))) { if (u.isVisible(_converse.root.querySelector("#controlbox"))) {
const controlbox = _converse.chatboxes.get('controlbox'); const controlbox = _converse.chatboxes.get('controlbox');
if (_converse.connection.connected) { if (_converse.api.connection.connected) {
controlbox.save({closed: true}); controlbox.save({closed: true});
} else { } else {
controlbox.trigger('hide'); controlbox.trigger('hide');
...@@ -582,10 +585,9 @@ converse.plugins.add('converse-controlbox', { ...@@ -582,10 +585,9 @@ converse.plugins.add('converse-controlbox', {
}); });
Promise.all([ _converse.api.waitUntil('chatBoxViewsInitialized')
_converse.api.waitUntil('connectionInitialized'), .then(addControlBox)
_converse.api.waitUntil('chatBoxViewsInitialized') .catch(e => _converse.log(e, Strophe.LogLevel.FATAL));
]).then(addControlBox).catch(e => _converse.log(e, Strophe.LogLevel.FATAL));
_converse.api.listen.on('chatBoxesFetched', () => { _converse.api.listen.on('chatBoxesFetched', () => {
const controlbox = _converse.chatboxes.get('controlbox') || addControlBox(); const controlbox = _converse.chatboxes.get('controlbox') || addControlBox();
......
...@@ -351,7 +351,7 @@ converse.plugins.add('converse-dragresize', { ...@@ -351,7 +351,7 @@ converse.plugins.add('converse-dragresize', {
_converse.resizing.chatbox.width, _converse.resizing.chatbox.width,
_converse.resizing.chatbox.model.get('default_width') _converse.resizing.chatbox.model.get('default_width')
); );
if (_converse.connection.connected) { if (_converse.api.connection.connected()) {
_converse.resizing.chatbox.model.save({'height': height}); _converse.resizing.chatbox.model.save({'height': height});
_converse.resizing.chatbox.model.save({'width': width}); _converse.resizing.chatbox.model.save({'width': width});
} else { } else {
......
...@@ -318,7 +318,7 @@ converse.plugins.add('converse-minimize', { ...@@ -318,7 +318,7 @@ converse.plugins.add('converse-minimize', {
* @param { _converse.ChatBoxView|_converse.ChatRoomView|_converse.ControlBoxView|_converse.HeadlinesBoxView } [newchat] * @param { _converse.ChatBoxView|_converse.ChatRoomView|_converse.ControlBoxView|_converse.HeadlinesBoxView } [newchat]
*/ */
async trimChats (newchat) { async trimChats (newchat) {
if (_converse.no_trimming || !_converse.connection.connected || _converse.view_mode !== 'overlayed') { if (_converse.no_trimming || !_converse.api.connection.connected() || _converse.view_mode !== 'overlayed') {
return; return;
} }
const shown_chats = this.getShownChats(); const shown_chats = this.getShownChats();
...@@ -556,10 +556,7 @@ converse.plugins.add('converse-minimize', { ...@@ -556,10 +556,7 @@ converse.plugins.add('converse-minimize', {
}); });
/************************ BEGIN Event Handlers ************************/ /************************ BEGIN Event Handlers ************************/
Promise.all([ _converse.api.waitUntil('chatBoxViewsInitialized').then(() => {
_converse.api.waitUntil('connectionInitialized'),
_converse.api.waitUntil('chatBoxViewsInitialized')
]).then(() => {
_converse.minimized_chats = new _converse.MinimizedChats({ _converse.minimized_chats = new _converse.MinimizedChats({
model: _converse.chatboxes model: _converse.chatboxes
}); });
......
...@@ -240,8 +240,8 @@ converse.plugins.add('converse-omemo', { ...@@ -240,8 +240,8 @@ converse.plugins.add('converse-omemo', {
/* 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.
*/ */
const { _converse } = this, const { _converse } = this;
{ __ } = _converse; const { __ } = _converse;
_converse.api.settings.update({ _converse.api.settings.update({
'omemo_default': false, 'omemo_default': false,
......
...@@ -175,7 +175,7 @@ converse.plugins.add('converse-register', { ...@@ -175,7 +175,7 @@ converse.plugins.add('converse-register', {
initialize () { initialize () {
this.reset(); this.reset();
this.registerHooks(); _converse.api.listen.on('connectionInitialized', () => this.registerHooks());
}, },
render () { render () {
...@@ -340,7 +340,7 @@ converse.plugins.add('converse-register', { ...@@ -340,7 +340,7 @@ converse.plugins.add('converse-register', {
* @method _converse.RegisterPanel#fetchRegistrationForm * @method _converse.RegisterPanel#fetchRegistrationForm
* @param { String } domain_name - XMPP server domain * @param { String } domain_name - XMPP server domain
*/ */
fetchRegistrationForm (domain_name) { async fetchRegistrationForm (domain_name) {
if (!this.model.get('registration_form_rendered')) { if (!this.model.get('registration_form_rendered')) {
this.renderRegistrationRequest(); this.renderRegistrationRequest();
} }
...@@ -348,7 +348,8 @@ converse.plugins.add('converse-register', { ...@@ -348,7 +348,8 @@ converse.plugins.add('converse-register', {
'domain': Strophe.getDomainFromJid(domain_name), 'domain': Strophe.getDomainFromJid(domain_name),
'_registering': true '_registering': true
}); });
_converse.connection.connect(this.domain, "", this.onConnectStatusChanged.bind(this)); await _converse.initConnection(this.domain);
_converse.connection.connect(this.domain, "", status => this.onConnectStatusChanged(status));
return false; return false;
}, },
......
...@@ -19,6 +19,10 @@ const BOSH_SESSION_ID = 'converse.bosh-session'; ...@@ -19,6 +19,10 @@ const BOSH_SESSION_ID = 'converse.bosh-session';
converse.plugins.add('converse-bosh', { converse.plugins.add('converse-bosh', {
enabled () {
return true;
},
initialize () { initialize () {
const { _converse } = this; const { _converse } = this;
...@@ -35,9 +39,15 @@ converse.plugins.add('converse-bosh', { ...@@ -35,9 +39,15 @@ converse.plugins.add('converse-bosh', {
_converse.bosh_session.browserStorage = new BrowserStorage.session(id); _converse.bosh_session.browserStorage = new BrowserStorage.session(id);
await new Promise(resolve => _converse.bosh_session.fetch({'success': resolve, 'error': resolve})); await new Promise(resolve => _converse.bosh_session.fetch({'success': resolve, 'error': resolve}));
} }
if (_converse.jid && _converse.bosh_session.get('jid') === _converse.jid) { if (_converse.jid) {
_converse.bosh_session.clear({'silent': true }); if (_converse.bosh_session.get('jid') !== _converse.jid) {
_converse.bosh_session.save({'jid': _converse.jid, id}); const jid = await _converse.setUserJID(_converse.jid);
_converse.bosh_session.clear({'silent': true });
_converse.bosh_session.save({jid});
}
} else { // Keepalive
const jid = _converse.bosh_session.get('jid');
jid && await _converse.setUserJID();
} }
return _converse.bosh_session; return _converse.bosh_session;
} }
...@@ -45,17 +55,17 @@ converse.plugins.add('converse-bosh', { ...@@ -45,17 +55,17 @@ converse.plugins.add('converse-bosh', {
_converse.startNewPreboundBOSHSession = function () { _converse.startNewPreboundBOSHSession = function () {
if (!_converse.prebind_url) { if (!_converse.prebind_url) {
throw new Error( throw new Error("startNewPreboundBOSHSession: If you use prebind then you MUST supply a prebind_url");
"attemptPreboundSession: If you use prebind then you MUST supply a prebind_url");
} }
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('GET', _converse.prebind_url, true); xhr.open('GET', _converse.prebind_url, true);
xhr.setRequestHeader('Accept', 'application/json, text/javascript'); xhr.setRequestHeader('Accept', 'application/json, text/javascript');
xhr.onload = function () { xhr.onload = async function () {
if (xhr.status >= 200 && xhr.status < 400) { if (xhr.status >= 200 && xhr.status < 400) {
const data = JSON.parse(xhr.responseText); const data = JSON.parse(xhr.responseText);
const jid = await _converse.setUserJID(data.jid);
_converse.connection.attach( _converse.connection.attach(
data.jid, jid,
data.sid, data.sid,
data.rid, data.rid,
_converse.onConnectStatusChanged _converse.onConnectStatusChanged
...@@ -79,9 +89,6 @@ converse.plugins.add('converse-bosh', { ...@@ -79,9 +89,6 @@ converse.plugins.add('converse-bosh', {
_converse.restoreBOSHSession = async function () { _converse.restoreBOSHSession = async function () {
if (!_converse.api.connection.isType('bosh')) {
return false;
}
const jid = (await initBOSHSession()).get('jid'); const jid = (await initBOSHSession()).get('jid');
if (jid) { if (jid) {
try { try {
...@@ -119,9 +126,7 @@ converse.plugins.add('converse-bosh', { ...@@ -119,9 +126,7 @@ converse.plugins.add('converse-bosh', {
} }
}); });
_converse.api.listen.on('addClientFeatures', _converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(Strophe.NS.BOSH));
() => _converse.api.disco.own.features.add(Strophe.NS.BOSH)
);
/************************ END Event Handlers ************************/ /************************ END Event Handlers ************************/
......
...@@ -850,8 +850,8 @@ converse.plugins.add('converse-chatboxes', { ...@@ -850,8 +850,8 @@ converse.plugins.add('converse-chatboxes', {
'to': this.get('jid'), 'to': this.get('jid'),
'type': 'chat' 'type': 'chat'
}).c(this.get('chat_state'), {'xmlns': Strophe.NS.CHATSTATES}).up() }).c(this.get('chat_state'), {'xmlns': Strophe.NS.CHATSTATES}).up()
.c('no-store', {'xmlns': Strophe.NS.HINTS}).up() .c('no-store', {'xmlns': Strophe.NS.HINTS}).up()
.c('no-permanent-store', {'xmlns': Strophe.NS.HINTS}) .c('no-permanent-store', {'xmlns': Strophe.NS.HINTS})
); );
} }
}, },
......
This diff is collapsed.
...@@ -633,7 +633,7 @@ converse.plugins.add('converse-muc', { ...@@ -633,7 +633,7 @@ converse.plugins.add('converse-muc', {
disco_entity.destroy(); disco_entity.destroy();
} }
} }
if (_converse.connection.connected) { if (_converse.api.connection.connected()) {
this.sendUnavailablePresence(exit_msg); this.sendUnavailablePresence(exit_msg);
} }
u.safeSave(this, {'connection_status': converse.ROOMSTATUS.DISCONNECTED}); u.safeSave(this, {'connection_status': converse.ROOMSTATUS.DISCONNECTED});
......
...@@ -107,6 +107,15 @@ u.isSameBareJID = function (jid1, jid2) { ...@@ -107,6 +107,15 @@ u.isSameBareJID = function (jid1, jid2) {
Strophe.getBareJidFromJid(jid2).toLowerCase(); Strophe.getBareJidFromJid(jid2).toLowerCase();
}; };
u.isSameDomain = function (jid1, jid2) {
if (!_.isString(jid1) || !_.isString(jid2)) {
return false;
}
return Strophe.getDomainFromJid(jid1).toLowerCase() ===
Strophe.getDomainFromJid(jid2).toLowerCase();
};
u.isNewMessage = function (message) { u.isNewMessage = function (message) {
/* Given a stanza, determine whether it's a new /* Given a stanza, determine whether it's a new
* message, i.e. not a MAM archived one. * message, i.e. not a MAM archived one.
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
const Strophe = converse.env.Strophe; const Strophe = converse.env.Strophe;
const dayjs = converse.env.dayjs; const dayjs = converse.env.dayjs;
const $iq = converse.env.$iq; const $iq = converse.env.$iq;
const u = converse.env.utils;
window.libsignal = { window.libsignal = {
'SignalProtocolAddress': function (name, device_id) { 'SignalProtocolAddress': function (name, device_id) {
...@@ -32,7 +31,7 @@ ...@@ -32,7 +31,7 @@
return Promise.resolve(key_and_tag); return Promise.resolve(key_and_tag);
} }
}, },
'SessionBuilder': function (storage, remote_address) { 'SessionBuilder': function (storage, remote_address) { // eslint-disable-line no-unused-vars
this.processPreKey = function () { this.processPreKey = function () {
return Promise.resolve(); return Promise.resolve();
} }
...@@ -116,95 +115,87 @@ ...@@ -116,95 +115,87 @@
'preventDefault': function () {} 'preventDefault': function () {}
}; };
mock.mock_connection = function () { // eslint-disable-line wrap-iife
return function () {
Strophe.Bosh.prototype._processRequest = function () {}; // Don't attempt to send out stanzas
const c = new Strophe.Connection('jasmine tests');
const sendIQ = c.sendIQ;
c.IQ_stanzas = []; const OriginalConnection = Strophe.Connection;
c.IQ_ids = [];
c.sendIQ = function (iq, callback, errback) { function MockConnection (service, options) {
if (!_.isElement(iq)) { OriginalConnection.call(this, service, options);
iq = iq.nodeTree;
} Strophe.Bosh.prototype._processRequest = function () {}; // Don't attempt to send out stanzas
this.IQ_stanzas.push(iq); const sendIQ = this.sendIQ;
const id = sendIQ.bind(this)(iq, callback, errback);
this.IQ_ids.push(id); this.IQ_stanzas = [];
return id; this.IQ_ids = [];
this.sendIQ = function (iq, callback, errback) {
if (!_.isElement(iq)) {
iq = iq.nodeTree;
} }
this.IQ_stanzas.push(iq);
const id = sendIQ.bind(this)(iq, callback, errback);
this.IQ_ids.push(id);
return id;
}
const send = c.send; const send = this.send;
c.sent_stanzas = []; this.sent_stanzas = [];
c.send = function (stanza) { this.send = function (stanza) {
if (_.isElement(stanza)) { if (_.isElement(stanza)) {
this.sent_stanzas.push(stanza); this.sent_stanzas.push(stanza);
} else { } else {
this.sent_stanzas.push(stanza.nodeTree); this.sent_stanzas.push(stanza.nodeTree);
}
return send.apply(this, arguments);
} }
return send.apply(this, arguments);
}
c.features = Strophe.xmlHtmlNode( this.features = Strophe.xmlHtmlNode(
'<stream:features xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client">'+ '<stream:features xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client">'+
'<ver xmlns="urn:xmpp:features:rosterver"/>'+ '<ver xmlns="urn:xmpp:features:rosterver"/>'+
'<csi xmlns="urn:xmpp:csi:0"/>'+ '<csi xmlns="urn:xmpp:csi:0"/>'+
'<c xmlns="http://jabber.org/protocol/caps" ver="UwBpfJpEt3IoLYfWma/o/p3FFRo=" hash="sha-1" node="http://prosody.im"/>'+ '<this xmlns="http://jabber.org/protocol/caps" ver="UwBpfJpEt3IoLYfWma/o/p3FFRo=" hash="sha-1" node="http://prosody.im"/>'+
'<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">'+ '<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">'+
'<required/>'+ '<required/>'+
'</bind>'+ '</bind>'+
`<sm xmlns='urn:xmpp:sm:3'/>`+ `<sm xmlns='urn:xmpp:sm:3'/>`+
'<session xmlns="urn:ietf:params:xml:ns:xmpp-session">'+ '<session xmlns="urn:ietf:params:xml:ns:xmpp-session">'+
'<optional/>'+ '<optional/>'+
'</session>'+ '</session>'+
'</stream:features>').firstChild; '</stream:features>').firstChild;
c._proto._connect = function () { this._proto._connect = () => {
c.connected = true; this.connected = true;
c.mock = true; this.mock = true;
c.jid = 'romeo@montague.lit/orchard'; this.jid = 'romeo@montague.lit/orchard';
c._changeConnectStatus(Strophe.Status.BINDREQUIRED); this._changeConnectStatus(Strophe.Status.BINDREQUIRED);
}; };
c.bind = function () { this.bind = () => {
c.authenticated = true; this.authenticated = true;
this.authenticated = true; this.authenticated = true;
c._changeConnectStatus(Strophe.Status.CONNECTED); this._changeConnectStatus(Strophe.Status.CONNECTED);
}; };
c._proto._disconnect = function () { this._proto._disconnect = () => this._onDisconnectTimeout();
c._onDisconnectTimeout(); this._proto._onDisconnectTimeout = _.noop;
} }
c._proto._onDisconnectTimeout = _.noop; MockConnection.prototype = Object.create(OriginalConnection.prototype);
return c; Strophe.Connection = MockConnection;
};
}();
async function initConverse (settings, spies={}, promises) {
async function initConverse (settings, spies={}) {
window.localStorage.clear(); window.localStorage.clear();
window.sessionStorage.clear(); window.sessionStorage.clear();
const el = document.querySelector('#conversejs');
if (el) {
el.parentElement.removeChild(el);
}
const connection = mock.mock_connection();
if (spies && spies.connection) {
spies.connection.forEach(method => spyOn(connection, method));
}
const _converse = await converse.initialize(Object.assign({ const _converse = await converse.initialize(Object.assign({
'i18n': 'en', 'animate': false,
'auto_subscribe': false, 'auto_subscribe': false,
'play_sounds': false,
'bosh_service_url': 'montague.lit/http-bind', 'bosh_service_url': 'montague.lit/http-bind',
'connection': connection, 'debug': false,
'animate': false, 'i18n': 'en',
'use_emojione': false,
'no_trimming': true, 'no_trimming': true,
'play_sounds': false,
'use_emojione': false,
'view_mode': mock.view_mode, 'view_mode': mock.view_mode,
'debug': false
}, settings || {})); }, settings || {}));
if (spies && spies._converse) { if (spies && spies._converse) {
...@@ -214,7 +205,7 @@ ...@@ -214,7 +205,7 @@
_converse.ChatBoxViews.prototype.trimChat = function () {}; _converse.ChatBoxViews.prototype.trimChat = function () {};
_converse.api.vcard.get = function (model, force) { _converse.api.vcard.get = function (model, force) {
return new Promise((resolve, reject) => { return new Promise(resolve => {
let jid; let jid;
if (_.isString(model)) { if (_.isString(model)) {
jid = model; jid = model;
...@@ -263,9 +254,13 @@ ...@@ -263,9 +254,13 @@
return async done => { return async done => {
const _converse = await initConverse(settings, spies); const _converse = await initConverse(settings, spies);
async function _done () { async function _done () {
await _converse.api.user.logout(); if (_converse.api.connection.connected()) {
await _converse.api.user.logout();
}
const el = document.querySelector('#conversejs'); const el = document.querySelector('#conversejs');
el.parentElement.removeChild(el); if (el) {
el.parentElement.removeChild(el);
}
done(); done();
} }
await Promise.all((promise_names || []).map(_converse.api.waitUntil)); await Promise.all((promise_names || []).map(_converse.api.waitUntil));
......
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