Commit 7b11d855 authored by JC Brand's avatar JC Brand

Add support for XEP-0198 Stream Management

- New plugin `converse-smacks`
- New config option `enable_smacks`
- Rename session cache id from `converse.bosh-session` to `converse.session`
- Refactor logout and login as consistently used api methods
- Refactor session cache to store per JID

Fixes #316
parent a46ee4df
......@@ -4,7 +4,7 @@ cache:
directories:
- node_modules
addons:
chrome: unstable
chrome: stable
node_js:
- "10"
install: make stamp-npm
......
......@@ -15,15 +15,15 @@
- Message deduplication bugfixes and improvements
- Continuously retry (in 2s intervals) to fetch login credentials (via [credentials_url](https://conversejs.org/docs/html/configuration.html#credentials-url)) in case of failure
- Replace `moment` with [DayJS](https://github.com/iamkun/dayjs).
- New API method [\_converse.api.disco.features.get](https://conversejs.org/docs/html/api/-_converse.api.disco.features.html#.get)
- New config setting [muc_show_join_leave_status](https://conversejs.org/docs/html/configuration.html#muc-show-join-leave-status)
- New config option [enable_smacks](https://conversejs.org/docs/html/configuration.html#enable-smacks).
- New config option [muc_show_join_leave_status](https://conversejs.org/docs/html/configuration.html#muc-show-join-leave-status)
- New config option [singleton](https://conversejs.org/docs/html/configuration.html#singleton).
By setting this option to `false` and `view_mode` to `'embedded'`, it's now possible to
"embed" the full app and not just a single chat. To embed just a single chat, it's now
necessary to explicitly set `singleton` to `true`.
- New event: `chatBoxBlurred`.
- New event: [chatBoxBlurred](https://conversejs.org/docs/html/api/-_converse.html#event:chatBoxBlurred)
- New event: [chatReconnected](https://conversejs.org/docs/html/api/-_converse.html#event:chatReconnected)
- #316: Add support for XEP-0198 Stream Management
- #1296: `embedded` view mode shows `chatbox-navback` arrow in header
- #1465: When highlighting a roster contact, they're incorrectly shown as online
- #1532: Converse reloads on enter pressed in the filter box
......@@ -34,14 +34,14 @@
- #1576: Converse gets stuck with spinner when logging out with `auto_login` set to `true`
- #1586: Not possible to kick someone with a space in their nickname
- **Breaking changes**:
### Breaking changes
- Rename `muc_disable_moderator_commands` to [muc_disable_slash_commands](https://conversejs.org/docs/html/configuration.html#muc-disable-slash-commands).
- `_converse.api.archive.query` now returns a Promise instead of accepting a callback functions.
- `_converse.api.disco.supports` now returns a Promise which resolves to a Boolean instead of an Array.
- The `forward_messages` config option (which was set to `false` by default) has been removed.
Use [message_carbons](https://conversejs.org/docs/html/configuration.html#message-carbons) instead.
### API changes
- `_converse.chats.open` and `_converse.rooms.open` now take a `force`
......@@ -51,6 +51,7 @@
- `_converse.api.emit` has been removed in favor of [\_converse.api.trigger](https://conversejs.org/docs/html/api/-_converse.api.html#.trigger)
- `_converse.updateSettings` has been removed in favor of [\_converse.api.settings.update](https://conversejs.org/docs/html/api/-_converse.api.settings.html#.update)
- `_converse.api.roster.get` now returns a promise.
- New API method [\_converse.api.disco.features.get](https://conversejs.org/docs/html/api/-_converse.api.disco.features.html#.get)
## 4.2.0 (2019-04-04)
......
......@@ -25,14 +25,15 @@
// 'prosody@conference.prosody.im',
// 'jdev@conference.jabber.org'
// ],
// websocket_url: 'ws://chat.example.org:5280/xmpp-websocket',
// bosh_service_url: 'http://chat.example.org:5280/http-bind/',
websocket_url: 'wss://conversejs.org/xmpp-websocket',
bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
view_mode: 'fullscreen',
notify_all_room_messages: [
'discuss@conference.conversejs.org'
],
enable_smacks: true,
muc_respect_autojoin: false,
// bosh_service_url: 'http://chat.example.org:5280/http-bind/',
bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
message_archiving: 'always',
debug: true
});
......
......@@ -635,6 +635,15 @@ The app servers are specified with the `push_app_servers`_ option.
Registering a push app server against a MUC domain is not (yet) standardized
and this feature should be considered experimental.
enable_smacks
-------------
* Default: ``false``
Determines whether `XEP-0198 Stream Management <https://xmpp.org/extensions/xep-0198.html>`_
support is turned on or not.
expose_rid_and_sid
------------------
......@@ -1376,6 +1385,16 @@ want to embed a chat into the page.
Alternatively you could use it with `view_mode`_ set to ``overlayed`` to create
a single helpdesk-type chat.
smacks_max_unacked_stanzas
--------------------------
* Default: ``5``
This setting relates to `XEP-0198 <https://xmpp.org/extensions/xep-0198.html>`_
and determines the number of stanzas to be sent before Converse will ask the
server for acknowledgement of those stanzas.
sounds_path
-----------
......
......@@ -71,8 +71,8 @@ and a list of servers that you can set up yourself on `xmpp.org <https://xmpp.or
.. _`BOSH-section`:
BOSH
====
BOSH (XMPP-over-HTTP)
=====================
Web-browsers do not allow the persistent, direct TCP socket connections used by
desktop XMPP clients to communicate with XMPP servers.
......@@ -113,26 +113,8 @@ use it in production.
Refer to the :ref:`bosh-service-url` configuration setting for information on
how to configure Converse to connect to a BOSH URL.
.. _`websocket-section`:
Websocket
=========
Websockets provide an alternative means of connection to an XMPP server from
your browser.
Websockets provide long-lived, bidirectional connections which do not rely on
HTTP. Therefore BOSH, which operates over HTTP, doesn't apply to websockets.
`Prosody <http://prosody.im>`_ (from version 0.10) and `Ejabberd <http://www.ejabberd.im>`_ support websocket connections, as
does the node-xmpp-bosh connection manager.
Refer to the :ref:`websocket-url` configuration setting for information on how to
configure Converse to connect to a websocket URL.
The Webserver
=============
Configuring your webserver for BOSH
-----------------------------------
Lets say the domain under which you host Converse is *example.org:80*,
but the domain of your connection manager or the domain of
......@@ -149,7 +131,7 @@ There are two ways in which you can solve this problem.
.. _CORS:
1. Cross-Origin Resource Sharing (CORS)
---------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CORS is a technique for overcoming browser restrictions related to the
`same-origin security policy <https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy>`_.
......@@ -158,8 +140,8 @@ CORS is enabled by adding an ``Access-Control-Allow-Origin`` header. Where this
is configured depends on what webserver is used for your file upload server.
2. Reverse-proxy
----------------
2. Reverse-proxy
~~~~~~~~~~~~~~~~
Another possible solution is to add a reverse proxy to a webserver such as Nginx or Apache to ensure that
all services you use are hosted under the same domain name and port.
......@@ -177,7 +159,7 @@ the cross-domain restriction is ``mysite.com/http-bind`` and not
Your ``nginx`` or ``apache`` configuration will look as follows:
Nginx
~~~~~
^^^^^
.. code-block:: nginx
......@@ -202,7 +184,7 @@ Nginx
}
Apache
~~~~~~
^^^^^^
.. code-block:: apache
......@@ -227,7 +209,7 @@ Apache
the above example).
This might be because your webserver and BOSH proxy have the same timeout
for BOSH requests. Because the webserver receives the request slightly earlier,
for BOSH requests. Because the webserver receives the request slightly earlier,
it gives up a few microseconds before the XMPP server’s empty result and thus returns a
504 error page containing HTML to browser, which then gets parsed as if its
XML.
......@@ -239,6 +221,70 @@ Apache
this problem.
.. _`websocket-section`:
Websocket
=========
Websockets provide an alternative means of connection to an XMPP server from
your browser.
Websockets provide long-lived, bidirectional connections which do not rely on
HTTP. Therefore BOSH, which operates over HTTP, doesn't apply to websockets.
`Prosody <http://prosody.im>`_ (from version 0.10) and `Ejabberd <http://www.ejabberd.im>`_ support websocket connections, as
does the node-xmpp-bosh connection manager.
Refer to the :ref:`websocket-url` configuration setting for information on how to
configure Converse to connect to a websocket URL.
Reverse-proxy for a websocket connection
----------------------------------------
Assuming your website is accessible on port ``443`` on the domain ``mysite.com``
and your XMPP server's websocket server is running at ``localhost:5280/xmpp-websocket``.
You can then set up your webserver as an SSL enabled reverse proxy in front of
your websocket endpoint.
The :ref:`websocket-url` value you'll want to pass in to ``converse.initialize`` is ``wss://mysite.com/xmpp-websocket``.
Your ``nginx`` will look as follows:
.. code-block:: nginx
http {
server {
listen 443
server_name mysite.com;
ssl on;
ssl_certificate /path/to/fullchain.pem; # Properly set the path here
ssl_certificate_key /path/to/privkey.pem; # Properly set the path here
location = / {
root /path/to/converse.js/; # Properly set the path here
index index.html;
}
location /xmpp-websocket {
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:5280;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
# CORS
location ~ .(ttf|ttc|otf|eot|woff|woff2|font.css|css|js)$ {
add_header Access-Control-Allow-Origin "*"; # Decide here whether you want to allow all or only a particular domain
root /path/to/converse.js/; # Properly set the path here
}
}
}
.. _`session-support`:
Single Session Support
......@@ -353,7 +399,7 @@ If your web-application has access to the same credentials, it can send those
credentials to Converse so that user's are automatically logged in when the
page loads.
This is can be done by setting :ref:`auto_login` to true and configuring the
This is can be done by setting :ref:`auto_login` to true and configuring the
the :ref:`credentials_url` setting.
Option 3). Temporary authentication tokens
......
......@@ -13702,8 +13702,8 @@
}
},
"strophe.js": {
"version": "github:strophe/strophejs#44da5faca8baa61c691739d63af8b1dea1d2436c",
"from": "github:strophe/strophejs#44da5faca8baa61c691739d63af8b1dea1d2436c"
"version": "github:strophe/strophejs#f52f26e8cc23f738b7b39180a7ee4511ccd41526",
"from": "github:strophe/strophejs#f52f26e8cc23f738b7b39180a7ee4511ccd41526"
},
"style-loader": {
"version": "0.23.1",
......
......@@ -32,7 +32,7 @@
delete _converse.jid;
_converse.keepalive = true;
_converse.authentication = "prebind";
expect(_converse.logIn.bind(_converse)).toThrow(
expect(_converse.api.user.login.bind(_converse)).toThrow(
new Error(
"restoreBOSHSession: tried to restore a \"keepalive\" session "+
"but we don't have the JID for the user!"));
......@@ -47,7 +47,7 @@
delete _converse.jid;
_converse.keepalive = false;
_converse.authentication = "prebind";
expect(_converse.logIn.bind(_converse)).toThrow(
expect(_converse.api.user.login.bind(_converse)).toThrow(
new Error("attemptPreboundSession: If you use prebind and not keepalive, then you MUST supply JID, RID and SID values or a prebind_url."));
_converse.bosh_service_url = undefined;
_converse.jid = jid;
......
......@@ -9,37 +9,33 @@
null, ['connectionInitialized', 'chatBoxesInitialized'],
{ auto_login: false,
allow_registration: false },
function (done, _converse) {
async function (done, _converse) {
test_utils.waitUntil(() => _converse.chatboxviews.get('controlbox'))
.then(function () {
var cbview = _converse.chatboxviews.get('controlbox');
test_utils.openControlBox();
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1);
test_utils.openControlBox();
const cbview = await test_utils.waitUntil(() => _converse.chatboxviews.get('controlbox'));
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
expect(checkboxes.length).toBe(1);
const checkbox = checkboxes[0];
const label = cbview.el.querySelector(`label[for="${checkbox.getAttribute('id')}"]`);
expect(label.textContent).toBe('This is a trusted device');
expect(checkbox.checked).toBe(true);
cbview.el.querySelector('input[name="jid"]').value = 'dummy@localhost';
cbview.el.querySelector('input[name="password"]').value = 'secret';
const checkbox = checkboxes[0];
const label = cbview.el.querySelector(`label[for="${checkbox.getAttribute('id')}"]`);
expect(label.textContent).toBe('This is a trusted device');
expect(checkbox.checked).toBe(true);
spyOn(cbview.loginpanel, 'connect');
cbview.delegateEvents();
cbview.el.querySelector('input[name="jid"]').value = 'dummy@localhost';
cbview.el.querySelector('input[name="password"]').value = 'secret';
expect(_converse.config.get('storage')).toBe('local');
cbview.el.querySelector('input[type="submit"]').click();
expect(_converse.config.get('storage')).toBe('local');
expect(cbview.loginpanel.connect).toHaveBeenCalled();
spyOn(cbview.loginpanel, 'connect');
cbview.delegateEvents();
expect(_converse.config.get('storage')).toBe('local');
cbview.el.querySelector('input[type="submit"]').click();
expect(_converse.config.get('storage')).toBe('local');
expect(cbview.loginpanel.connect).toHaveBeenCalled();
checkbox.click();
cbview.el.querySelector('input[type="submit"]').click();
expect(_converse.config.get('storage')).toBe('session');
done();
});
checkbox.click();
cbview.el.querySelector('input[type="submit"]').click();
expect(_converse.config.get('storage')).toBe('session');
done();
}));
it("checkbox can be set to false by default",
......
......@@ -273,7 +273,7 @@
'name': 'Nicky'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
// Check that the IQ set was acknowledged.
expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
expect(Strophe.serialize(sent_stanza)).toBe( // Strophe adds the xmlns attr (although not in spec)
`<iq from="dummy@localhost/resource" id="${IQ_id}" type="result" xmlns="jabber:client"/>`
);
expect(_converse.roster.updateContact).toHaveBeenCalled();
......
......@@ -5,6 +5,8 @@
const $iq = converse.env.$iq;
const Strophe = converse.env.Strophe;
const _ = converse.env._;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
describe("XEP-0357 Push Notifications", function () {
......@@ -56,31 +58,52 @@
}]
}, async function (done, _converse) {
const IQ_stanzas = _converse.connection.IQ_stanzas,
room_jid = 'coven@chat.shakespeare.lit';
expect(_converse.session.get('push_enabled')).toBeFalsy();
test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'oldhag');
const IQ_stanzas = _converse.connection.IQ_stanzas;
const room_jid = 'coven@chat.shakespeare.lit';
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.push_app_servers[0].jid,
[{'category': 'pubsub', 'type':'push'}],
['urn:xmpp:push:0'], [], 'info');
await test_utils.waitUntilDiscoConfirmed(
_converse, _converse.bare_jid, [],
['urn:xmpp:push:0']);
let iq = await test_utils.waitUntil(() => _.filter(
IQ_stanzas,
iq => sizzle(`iq[type="set"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
).pop());
expect(Strophe.serialize(iq)).toBe(
`<iq id="${iq.getAttribute('id')}" type="set" xmlns="jabber:client">`+
`<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>`+
`</iq>`
);
const result = u.toStanza(`<iq type="result" id="${iq.getAttribute('id')}" to="dummy@localhost" />`);
_converse.connection._dataRecv(test_utils.createRequest(result));
await test_utils.waitUntil(() => _converse.session.get('push_enabled'));
expect(_converse.session.get('push_enabled').length).toBe(1);
expect(_.includes(_converse.session.get('push_enabled'), 'dummy@localhost')).toBe(true);
test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'oldhag');
await test_utils.waitUntilDiscoConfirmed(
_converse, 'chat.shakespeare.lit',
[{'category': 'account', 'type':'registered'}],
['urn:xmpp:push:0'], [], 'info');
const stanza = await test_utils.waitUntil(
() => _.filter(IQ_stanzas, (iq) => iq.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]')).pop()
);
expect(Strophe.serialize(stanza)).toEqual(
`<iq id="${stanza.getAttribute('id')}" to="chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
iq = await test_utils.waitUntil(() => _.filter(
IQ_stanzas,
iq => sizzle(`iq[type="set"][to="chat.shakespeare.lit"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
).pop());
expect(Strophe.serialize(iq)).toEqual(
`<iq id="${iq.getAttribute('id')}" to="chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
'<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>'+
'</iq>'
);
_converse.connection._dataRecv(test_utils.createRequest($iq({
'to': _converse.connection.jid,
'type': 'result',
'id': stanza.getAttribute('id')
'id': iq.getAttribute('id')
})));
await test_utils.waitUntil(() => _.includes(_converse.session.get('push_enabled'), 'chat.shakespeare.lit'));
done();
......
(function (root, factory) {
define(["jasmine", "mock", "test-utils"], factory);
} (this, function (jasmine, mock, test_utils) {
"use strict";
const $iq = converse.env.$iq;
const Strophe = converse.env.Strophe;
const sizzle = converse.env.sizzle;
const u = converse.env.utils;
describe("XEP-0198 Stream Management", function () {
it("gets enabled with an <enable> stanza and resumed with a <resume> stanza",
mock.initConverse(
null, ['connectionInitialized', 'chatBoxesInitialized'],
{ 'auto_login': false,
'enable_smacks': true,
'show_controlbox_by_default': true,
'smacks_max_unacked_stanzas': 2
},
async function (done, _converse) {
const view = _converse.chatboxviews.get('controlbox');
spyOn(view, 'renderControlBoxPane').and.callThrough();
_converse.api.user.login('dummy@localhost', 'secret');
const sent_stanzas = _converse.connection.sent_stanzas;
let stanza = await test_utils.waitUntil(() =>
sent_stanzas.filter(s => (s.tagName === 'enable')).pop());
expect(_converse.session.get('smacks_enabled')).toBe(false);
expect(Strophe.serialize(stanza)).toEqual('<enable resume="true" xmlns="urn:xmpp:sm:3"/>');
let result = u.toStanza(`<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true"/>`);
_converse.connection._dataRecv(test_utils.createRequest(result));
expect(_converse.session.get('smacks_enabled')).toBe(true);
await test_utils.waitUntil(() => view.renderControlBoxPane.calls.count());
let IQ_stanzas = _converse.connection.IQ_stanzas;
await test_utils.waitUntil(() => IQ_stanzas.length === 4);
let iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe(
`<iq from="dummy@localhost/resource" id="${iq.getAttribute('id')}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe(
`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe(
`<iq from="dummy@localhost/resource" id="${iq.getAttribute('id')}" to="localhost" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
const disco_iq = IQ_stanzas.pop();
expect(Strophe.serialize(disco_iq)).toBe(
`<iq from="dummy@localhost" id="${disco_iq.getAttribute('id')}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub></iq>`);
expect(sent_stanzas.filter(s => (s.nodeName === 'r')).length).toBe(2);
expect(_converse.session.get('unacked_stanzas').length).toBe(4);
// test handling of acks
let ack = u.toStanza(`<a xmlns="urn:xmpp:sm:3" h="1"/>`);
_converse.connection._dataRecv(test_utils.createRequest(ack));
expect(_converse.session.get('unacked_stanzas').length).toBe(3);
// test handling of ack requests
let r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
_converse.connection._dataRecv(test_utils.createRequest(r));
ack = await test_utils.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a')).pop());
expect(Strophe.serialize(ack)).toBe('<a h="0" xmlns="urn:xmpp:sm:3"/>');
const disco_result = $iq({
'type': 'result',
'from': 'localhost',
'to': 'dummy@localhost/resource',
'id': disco_iq.getAttribute('id'),
}).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
.c('identity', {
'category': 'server',
'type': 'im'
}).up()
.c('feature', {'var': 'http://jabber.org/protocol/disco#info'}).up()
.c('feature', {'var': 'http://jabber.org/protocol/disco#items'});
_converse.connection._dataRecv(test_utils.createRequest(disco_result));
ack = u.toStanza(`<a xmlns="urn:xmpp:sm:3" h="2"/>`);
_converse.connection._dataRecv(test_utils.createRequest(ack));
expect(_converse.session.get('unacked_stanzas').length).toBe(2);
r = u.toStanza(`<r xmlns="urn:xmpp:sm:3"/>`);
_converse.connection._dataRecv(test_utils.createRequest(r));
ack = await test_utils.waitUntil(() => sent_stanzas.filter(s => (s.nodeName === 'a' && s.getAttribute('h') === '1')).pop());
expect(Strophe.serialize(ack)).toBe('<a h="1" xmlns="urn:xmpp:sm:3"/>');
// test session resumption
_converse.connection.IQ_stanzas = [];
IQ_stanzas = _converse.connection.IQ_stanzas;
_converse.api.connection.reconnect();
stanza = await test_utils.waitUntil(() =>
sent_stanzas.filter(s => (s.tagName === 'resume')).pop());
expect(Strophe.serialize(stanza)).toEqual('<resume h="2" previd="some-long-sm-id" xmlns="urn:xmpp:sm:3"/>');
result = u.toStanza(`<resumed xmlns="urn:xmpp:sm:3" h="another-sequence-number" previd="some-long-sm-id"/>`);
_converse.connection._dataRecv(test_utils.createRequest(result));
// Another <enable> stanza doesn't get sent out
expect(sizzle('enable', sent_stanzas).length).toBe(0);
expect(_converse.session.get('smacks_enabled')).toBe(true);
await test_utils.waitUntil(() => IQ_stanzas.length === 2);
// Test that unacked stanzas get resent out
iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe(
`<iq from="dummy@localhost/resource" id="${iq.getAttribute('id')}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
`<query xmlns="http://jabber.org/protocol/disco#info"/></iq>`);
iq = IQ_stanzas.pop();
expect(Strophe.serialize(iq)).toBe(
`<iq id="${iq.getAttribute('id')}" type="get" xmlns="jabber:client"><query xmlns="jabber:iq:roster"/></iq>`);
done();
}));
});
}));
......@@ -117,7 +117,7 @@
});
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
/* Test the XML stanza
/* Test the XML stanza
*
* <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost"
......@@ -194,7 +194,7 @@
});
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
/* Test the XML stanza
/* Test the XML stanza
*
* <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost"
......
......@@ -438,8 +438,7 @@ converse.plugins.add('converse-controlbox', {
*/
if (ev && ev.preventDefault) { ev.preventDefault(); }
if (_converse.authentication === _converse.ANONYMOUS) {
this.connect(_converse.jid, null);
return;
return this.connect(_converse.jid, null);
}
if (!this.validate()) { return; }
......@@ -467,24 +466,16 @@ converse.plugins.add('converse-controlbox', {
} else if (_converse.default_domain && !_.includes(jid, '@')) {
jid = jid + '@' + _converse.default_domain;
}
this.connect(jid, form_data.get('password'));
this.connect(jid, form_data.get('password'));
},
connect (jid, password) {
if (jid) {
const resource = Strophe.getResourceFromJid(jid);
if (!resource) {
jid = jid.toLowerCase() + _converse.generateResource();
} else {
jid = Strophe.getBareJidFromJid(jid).toLowerCase()+'/'+resource;
}
}
if (_.includes(["converse/login", "converse/register"],
Backbone.history.getFragment())) {
_converse.router.navigate('', {'replace': true});
}
_converse.connection.reset();
_converse.connection.connect(jid, password, _converse.onConnectStatusChanged);
_converse.api.user.login(jid, password);
}
});
......
......@@ -285,7 +285,7 @@ converse.plugins.add('converse-profile', {
ev.preventDefault();
const result = confirm(__("Are you sure you want to log out?"));
if (result === true) {
_converse.logOut();
_converse.api.user.logout();
}
},
......
......@@ -106,9 +106,9 @@ converse.plugins.add('converse-push', {
}
const enabled_services = _.reject(_converse.push_app_servers, 'disable');
const disabled_services = _.filter(_converse.push_app_servers, 'disable');
const enabled = _.map(enabled_services, _.partial(enablePushAppServer, domain));
const disabled = _.map(disabled_services, _.partial(disablePushAppServer, domain));
try {
const enabled = _.map(enabled_services, _.partial(enablePushAppServer, domain));
const disabled = _.map(disabled_services, _.partial(disablePushAppServer, domain));
await Promise.all(enabled.concat(disabled));
} catch (e) {
_converse.log('Could not enable or disable push App Server', Strophe.LogLevel.ERROR);
......@@ -118,7 +118,6 @@ converse.plugins.add('converse-push', {
}
_converse.session.save('push_enabled', push_enabled);
}
_converse.api.listen.on('statusInitialized', () => enablePush());
function onChatBoxAdded (model) {
......
This diff is collapsed.
......@@ -22,6 +22,7 @@ converse.plugins.add('converse-disco', {
// Promises exposed by this plugin
_converse.api.promises.add('discoInitialized');
_converse.api.promises.add('streamFeaturesAdded');
/**
......@@ -260,32 +261,33 @@ converse.plugins.add('converse-disco', {
}
function initStreamFeatures () {
_converse.stream_features = new Backbone.Collection();
_converse.stream_features.browserStorage = new BrowserStorage.session(
`converse.stream-features-${_converse.bare_jid}`
);
_converse.stream_features.fetch({
success (collection) {
if (collection.length === 0 && _converse.connection.features) {
_.forEach(
_converse.connection.features.childNodes,
(feature) => {
_converse.stream_features.create({
'name': feature.nodeName,
'xmlns': feature.getAttribute('xmlns')
const bare_jid = Strophe.getBareJidFromJid(_converse.jid);
const id = `converse.stream-features-${bare_jid}`;
if (!_converse.stream_features || _converse.stream_features.browserStorage.id !== id) {
_converse.stream_features = new Backbone.Collection();
_converse.stream_features.browserStorage = new BrowserStorage.session(id);
_converse.stream_features.fetch({
success (collection) {
if (collection.length === 0 && _converse.connection.features) {
Array.from(_converse.connection.features.childNodes)
.forEach(feature => {
_converse.stream_features.create({
'name': feature.nodeName,
'xmlns': feature.getAttribute('xmlns')
});
});
});
}
/**
* Triggered as soon as Converse has processed the stream features as advertised by
* the server. If you want to check whether a stream feature is supported before
* proceeding, then you'll first want to wait for this event.
* @event _converse#streamFeaturesAdded
* @example _converse.api.listen.on('streamFeaturesAdded', () => { ... });
*/
_converse.api.trigger('streamFeaturesAdded');
}
}
});
/**
* Triggered as soon as Converse has processed the stream features as advertised by
* the server. If you want to check whether a stream feature is supported before
* proceeding, then you'll first want to wait for this event.
* @event _converse#streamFeaturesAdded
* @example _converse.api.listen.on('streamFeaturesAdded', () => { ... });
*/
_converse.api.trigger('streamFeaturesAdded');
});
}
}
async function initializeDisco () {
......@@ -313,7 +315,9 @@ converse.plugins.add('converse-disco', {
_converse.api.trigger('discoInitialized');
}
_converse.api.listen.on('setUserJID', initStreamFeatures);
_converse.api.listen.on('userSessionInitialized', initStreamFeatures);
_converse.api.listen.on('beforeResourceBinding', initStreamFeatures);
_converse.api.listen.on('reconnected', initializeDisco);
_converse.api.listen.on('connected', initializeDisco);
......@@ -326,6 +330,10 @@ converse.plugins.add('converse-disco', {
_converse.disco_entities.reset();
_converse.disco_entities.browserStorage._clear();
}
if (_converse.stream_features) {
_converse.stream_features.reset();
_converse.stream_features.browserStorage._clear();
}
});
const plugin = this;
......@@ -386,7 +394,8 @@ converse.plugins.add('converse-disco', {
* @param {String} xmlns The XML namespace
* @example _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver')
*/
'getFeature': function (name, xmlns) {
'getFeature': async function (name, xmlns) {
await _converse.api.waitUntil('streamFeaturesAdded');
if (_.isNil(name) || _.isNil(xmlns)) {
throw new Error("name and xmlns need to be provided when calling disco.stream.getFeature");
}
......
This diff is collapsed.
......@@ -55,7 +55,7 @@ converse.plugins.add('converse-vcard', {
model: _converse.VCard,
initialize () {
this.on('add', (vcard) => _converse.api.vcard.update(vcard));
this.on('add', vcard => _converse.api.vcard.update(vcard));
}
});
......@@ -125,19 +125,17 @@ converse.plugins.add('converse-vcard', {
_converse.vcards.browserStorage = new BrowserStorage[_converse.config.get('storage')](id);
_converse.vcards.fetch();
}
_converse.api.listen.on('setUserJID', _converse.initVCardCollection);
_converse.api.listen.on('afterResourceBinding', _converse.initVCardCollection);
_converse.api.listen.on('statusInitialized', () => {
const vcards = _converse.vcards;
const jid = _converse.xmppstatus.get('jid');
const jid = _converse.session.get('bare_jid');
_converse.xmppstatus.vcard = vcards.findWhere({'jid': jid}) || vcards.create({'jid': jid});
});
_converse.api.listen.on('addClientFeatures', () => {
_converse.api.disco.own.features.add(Strophe.NS.VCARD);
});
_converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(Strophe.NS.VCARD));
/************************ BEGIN API ************************/
Object.assign(_converse.api, {
......@@ -191,7 +189,7 @@ converse.plugins.add('converse-vcard', {
* );
* });
*/
'get' (model, force) {
get (model, force) {
if (_.isString(model)) {
return getVCard(_converse, model);
} else if (force ||
......@@ -224,7 +222,7 @@ converse.plugins.add('converse-vcard', {
* _converse.api.vcard.update(chatbox);
* });
*/
'update' (model, force) {
update (model, force) {
return this.get(model, force)
.then(vcard => {
delete vcard['stanza']
......
......@@ -12,6 +12,7 @@ import "./converse-ping"; // XEP-0199 XMPP Ping
import "./converse-pubsub"; // XEP-0199 XMPP Ping
import "./converse-roster"; // Contacts Roster
import "./converse-rsm"; // XEP-0059 Result Set management
import "./converse-smacks"; // XEP-0198 Stream Management
import "./converse-vcard"; // XEP-0054 VCard-temp
/* END: Removable components */
......
......@@ -29,7 +29,7 @@
"jed": "1.1.1",
"lodash": "^4.17.11",
"pluggable.js": "2.0.1",
"strophe.js": "strophe/strophejs#44da5faca8baa61c691739d63af8b1dea1d2436c",
"strophe.js": "strophe/strophejs#f52f26e8cc23f738b7b39180a7ee4511ccd41526",
"twemoji": "^11.0.1",
"urijs": "^1.19.1"
}
......
......@@ -21,6 +21,18 @@ import sizzle from "sizzle";
*/
const u = {};
u.isTagEqual = function (stanza, name) {
if (stanza.nodeTree) {
return u.isTagEqual(stanza.nodeTree, name);
} else if (!(stanza instanceof Element)) {
throw Error(
"isTagEqual called with value which isn't "+
"an element or Strophe.Builder instance");
} else {
return Strophe.isTagEqual(stanza, name);
}
}
u.toStanza = function (string) {
return Strophe.xmlHtmlNode(string).firstElementChild;
}
......
......@@ -145,16 +145,22 @@
'<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">'+
'<required/>'+
'</bind>'+
`<sm xmlns='urn:xmpp:sm:3'/>`+
'<session xmlns="urn:ietf:params:xml:ns:xmpp-session">'+
'<optional/>'+
'</session>'+
'</stream:features>').firstChild;
c._proto._connect = function () {
c.authenticated = true;
c.connected = true;
c.mock = true;
c.jid = 'dummy@localhost/resource';
c._changeConnectStatus(Strophe.Status.BINDREQUIRED);
};
c.bind = function () {
c.authenticated = true;
this.authenticated = true;
c._changeConnectStatus(Strophe.Status.CONNECTED);
};
......@@ -180,7 +186,7 @@
_.forEach(spies.connection, method => spyOn(connection, method));
}
const _converse = await converse.initialize(_.extend({
const _converse = await converse.initialize(Object.assign({
'i18n': 'en',
'auto_subscribe': false,
'play_sounds': false,
......@@ -232,10 +238,8 @@
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
};
if (_.get(settings, 'auto_login') !== false) {
_converse.api.user.login({
'jid': 'dummy@localhost',
'password': 'secret'
});
_converse.api.user.login('dummy@localhost', 'secret');
await _converse.api.waitUntil('afterResourceBinding');
}
window.converse_disable_effects = true;
return _converse;
......
......@@ -44,6 +44,7 @@ var specs = [
"spec/protocol",
"spec/presence",
"spec/eventemitter",
"spec/smacks",
"spec/ping",
"spec/push",
"spec/xmppstatus",
......
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