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
This commit is contained in:
parent
a46ee4dfe1
commit
7b11d85503
@ -4,7 +4,7 @@ cache:
|
|||||||
directories:
|
directories:
|
||||||
- node_modules
|
- node_modules
|
||||||
addons:
|
addons:
|
||||||
chrome: unstable
|
chrome: stable
|
||||||
node_js:
|
node_js:
|
||||||
- "10"
|
- "10"
|
||||||
install: make stamp-npm
|
install: make stamp-npm
|
||||||
|
11
CHANGES.md
11
CHANGES.md
@ -15,15 +15,15 @@
|
|||||||
- Message deduplication bugfixes and improvements
|
- 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
|
- 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).
|
- 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 option [enable_smacks](https://conversejs.org/docs/html/configuration.html#enable-smacks).
|
||||||
- New config setting [muc_show_join_leave_status](https://conversejs.org/docs/html/configuration.html#muc-show-join-leave-status)
|
- 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).
|
- 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
|
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
|
"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`.
|
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: [chatBoxBlurred](https://conversejs.org/docs/html/api/-_converse.html#event:chatBoxBlurred)
|
||||||
- New event: [chatReconnected](https://conversejs.org/docs/html/api/-_converse.html#event:chatReconnected)
|
- 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
|
- #1296: `embedded` view mode shows `chatbox-navback` arrow in header
|
||||||
- #1465: When highlighting a roster contact, they're incorrectly shown as online
|
- #1465: When highlighting a roster contact, they're incorrectly shown as online
|
||||||
- #1532: Converse reloads on enter pressed in the filter box
|
- #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`
|
- #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
|
- #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).
|
- 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.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.
|
- `_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.
|
- 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.
|
Use [message_carbons](https://conversejs.org/docs/html/configuration.html#message-carbons) instead.
|
||||||
|
|
||||||
|
|
||||||
### API changes
|
### API changes
|
||||||
|
|
||||||
- `_converse.chats.open` and `_converse.rooms.open` now take a `force`
|
- `_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.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.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.
|
- `_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)
|
## 4.2.0 (2019-04-04)
|
||||||
|
|
||||||
|
7
dev.html
7
dev.html
@ -25,14 +25,15 @@
|
|||||||
// 'prosody@conference.prosody.im',
|
// 'prosody@conference.prosody.im',
|
||||||
// 'jdev@conference.jabber.org'
|
// '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',
|
view_mode: 'fullscreen',
|
||||||
notify_all_room_messages: [
|
notify_all_room_messages: [
|
||||||
'discuss@conference.conversejs.org'
|
'discuss@conference.conversejs.org'
|
||||||
],
|
],
|
||||||
|
enable_smacks: true,
|
||||||
muc_respect_autojoin: false,
|
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',
|
message_archiving: 'always',
|
||||||
debug: true
|
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
|
Registering a push app server against a MUC domain is not (yet) standardized
|
||||||
and this feature should be considered experimental.
|
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
|
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
|
Alternatively you could use it with `view_mode`_ set to ``overlayed`` to create
|
||||||
a single helpdesk-type chat.
|
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
|
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-section`:
|
||||||
|
|
||||||
BOSH
|
BOSH (XMPP-over-HTTP)
|
||||||
====
|
=====================
|
||||||
|
|
||||||
Web-browsers do not allow the persistent, direct TCP socket connections used by
|
Web-browsers do not allow the persistent, direct TCP socket connections used by
|
||||||
desktop XMPP clients to communicate with XMPP servers.
|
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
|
Refer to the :ref:`bosh-service-url` configuration setting for information on
|
||||||
how to configure Converse to connect to a BOSH URL.
|
how to configure Converse to connect to a BOSH URL.
|
||||||
|
|
||||||
|
Configuring your webserver for BOSH
|
||||||
.. _`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
|
|
||||||
=============
|
|
||||||
|
|
||||||
Lets say the domain under which you host Converse is *example.org:80*,
|
Lets say the domain under which you host Converse is *example.org:80*,
|
||||||
but the domain of your connection manager or the domain of
|
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:
|
.. _CORS:
|
||||||
|
|
||||||
1. Cross-Origin Resource Sharing (CORS)
|
1. Cross-Origin Resource Sharing (CORS)
|
||||||
---------------------------------------
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
CORS is a technique for overcoming browser restrictions related to the
|
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>`_.
|
`same-origin security policy <https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy>`_.
|
||||||
@ -159,7 +141,7 @@ 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
|
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.
|
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:
|
Your ``nginx`` or ``apache`` configuration will look as follows:
|
||||||
|
|
||||||
Nginx
|
Nginx
|
||||||
~~~~~
|
^^^^^
|
||||||
|
|
||||||
.. code-block:: nginx
|
.. code-block:: nginx
|
||||||
|
|
||||||
@ -202,7 +184,7 @@ Nginx
|
|||||||
}
|
}
|
||||||
|
|
||||||
Apache
|
Apache
|
||||||
~~~~~~
|
^^^^^^
|
||||||
|
|
||||||
.. code-block:: apache
|
.. code-block:: apache
|
||||||
|
|
||||||
@ -239,6 +221,70 @@ Apache
|
|||||||
this problem.
|
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`:
|
.. _`session-support`:
|
||||||
|
|
||||||
Single Session Support
|
Single Session Support
|
||||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -13702,8 +13702,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"strophe.js": {
|
"strophe.js": {
|
||||||
"version": "github:strophe/strophejs#44da5faca8baa61c691739d63af8b1dea1d2436c",
|
"version": "github:strophe/strophejs#f52f26e8cc23f738b7b39180a7ee4511ccd41526",
|
||||||
"from": "github:strophe/strophejs#44da5faca8baa61c691739d63af8b1dea1d2436c"
|
"from": "github:strophe/strophejs#f52f26e8cc23f738b7b39180a7ee4511ccd41526"
|
||||||
},
|
},
|
||||||
"style-loader": {
|
"style-loader": {
|
||||||
"version": "0.23.1",
|
"version": "0.23.1",
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
delete _converse.jid;
|
delete _converse.jid;
|
||||||
_converse.keepalive = true;
|
_converse.keepalive = true;
|
||||||
_converse.authentication = "prebind";
|
_converse.authentication = "prebind";
|
||||||
expect(_converse.logIn.bind(_converse)).toThrow(
|
expect(_converse.api.user.login.bind(_converse)).toThrow(
|
||||||
new Error(
|
new Error(
|
||||||
"restoreBOSHSession: tried to restore a \"keepalive\" session "+
|
"restoreBOSHSession: tried to restore a \"keepalive\" session "+
|
||||||
"but we don't have the JID for the user!"));
|
"but we don't have the JID for the user!"));
|
||||||
@ -47,7 +47,7 @@
|
|||||||
delete _converse.jid;
|
delete _converse.jid;
|
||||||
_converse.keepalive = false;
|
_converse.keepalive = false;
|
||||||
_converse.authentication = "prebind";
|
_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."));
|
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.bosh_service_url = undefined;
|
||||||
_converse.jid = jid;
|
_converse.jid = jid;
|
||||||
|
@ -9,12 +9,10 @@
|
|||||||
null, ['connectionInitialized', 'chatBoxesInitialized'],
|
null, ['connectionInitialized', 'chatBoxesInitialized'],
|
||||||
{ auto_login: false,
|
{ auto_login: false,
|
||||||
allow_registration: 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();
|
test_utils.openControlBox();
|
||||||
|
const cbview = await test_utils.waitUntil(() => _converse.chatboxviews.get('controlbox'));
|
||||||
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
|
const checkboxes = cbview.el.querySelectorAll('input[type="checkbox"]');
|
||||||
expect(checkboxes.length).toBe(1);
|
expect(checkboxes.length).toBe(1);
|
||||||
|
|
||||||
@ -34,12 +32,10 @@
|
|||||||
expect(_converse.config.get('storage')).toBe('local');
|
expect(_converse.config.get('storage')).toBe('local');
|
||||||
expect(cbview.loginpanel.connect).toHaveBeenCalled();
|
expect(cbview.loginpanel.connect).toHaveBeenCalled();
|
||||||
|
|
||||||
|
|
||||||
checkbox.click();
|
checkbox.click();
|
||||||
cbview.el.querySelector('input[type="submit"]').click();
|
cbview.el.querySelector('input[type="submit"]').click();
|
||||||
expect(_converse.config.get('storage')).toBe('session');
|
expect(_converse.config.get('storage')).toBe('session');
|
||||||
done();
|
done();
|
||||||
});
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it("checkbox can be set to false by default",
|
it("checkbox can be set to false by default",
|
||||||
|
@ -273,7 +273,7 @@
|
|||||||
'name': 'Nicky'});
|
'name': 'Nicky'});
|
||||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||||
// Check that the IQ set was acknowledged.
|
// 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"/>`
|
`<iq from="dummy@localhost/resource" id="${IQ_id}" type="result" xmlns="jabber:client"/>`
|
||||||
);
|
);
|
||||||
expect(_converse.roster.updateContact).toHaveBeenCalled();
|
expect(_converse.roster.updateContact).toHaveBeenCalled();
|
||||||
|
45
spec/push.js
45
spec/push.js
@ -5,6 +5,8 @@
|
|||||||
const $iq = converse.env.$iq;
|
const $iq = converse.env.$iq;
|
||||||
const Strophe = converse.env.Strophe;
|
const Strophe = converse.env.Strophe;
|
||||||
const _ = converse.env._;
|
const _ = converse.env._;
|
||||||
|
const sizzle = converse.env.sizzle;
|
||||||
|
const u = converse.env.utils;
|
||||||
|
|
||||||
describe("XEP-0357 Push Notifications", function () {
|
describe("XEP-0357 Push Notifications", function () {
|
||||||
|
|
||||||
@ -56,31 +58,52 @@
|
|||||||
}]
|
}]
|
||||||
}, async function (done, _converse) {
|
}, async function (done, _converse) {
|
||||||
|
|
||||||
const IQ_stanzas = _converse.connection.IQ_stanzas,
|
const IQ_stanzas = _converse.connection.IQ_stanzas;
|
||||||
room_jid = 'coven@chat.shakespeare.lit';
|
const room_jid = 'coven@chat.shakespeare.lit';
|
||||||
expect(_converse.session.get('push_enabled')).toBeFalsy();
|
|
||||||
|
|
||||||
test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'oldhag');
|
|
||||||
await test_utils.waitUntilDiscoConfirmed(
|
await test_utils.waitUntilDiscoConfirmed(
|
||||||
_converse, _converse.push_app_servers[0].jid,
|
_converse, _converse.push_app_servers[0].jid,
|
||||||
[{'category': 'pubsub', 'type':'push'}],
|
[{'category': 'pubsub', 'type':'push'}],
|
||||||
['urn:xmpp:push:0'], [], 'info');
|
['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(
|
await test_utils.waitUntilDiscoConfirmed(
|
||||||
_converse, 'chat.shakespeare.lit',
|
_converse, 'chat.shakespeare.lit',
|
||||||
[{'category': 'account', 'type':'registered'}],
|
[{'category': 'account', 'type':'registered'}],
|
||||||
['urn:xmpp:push:0'], [], 'info');
|
['urn:xmpp:push:0'], [], 'info');
|
||||||
const stanza = await test_utils.waitUntil(
|
iq = await test_utils.waitUntil(() => _.filter(
|
||||||
() => _.filter(IQ_stanzas, (iq) => iq.querySelector('iq[type="set"] enable[xmlns="urn:xmpp:push:0"]')).pop()
|
IQ_stanzas,
|
||||||
);
|
iq => sizzle(`iq[type="set"][to="chat.shakespeare.lit"] enable[xmlns="${Strophe.NS.PUSH}"]`, iq).length
|
||||||
expect(Strophe.serialize(stanza)).toEqual(
|
).pop());
|
||||||
`<iq id="${stanza.getAttribute('id')}" to="chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
|
|
||||||
|
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"/>'+
|
'<enable jid="push-5@client.example" node="yxs32uqsflafdk3iuqo" xmlns="urn:xmpp:push:0"/>'+
|
||||||
'</iq>'
|
'</iq>'
|
||||||
);
|
);
|
||||||
_converse.connection._dataRecv(test_utils.createRequest($iq({
|
_converse.connection._dataRecv(test_utils.createRequest($iq({
|
||||||
'to': _converse.connection.jid,
|
'to': _converse.connection.jid,
|
||||||
'type': 'result',
|
'type': 'result',
|
||||||
'id': stanza.getAttribute('id')
|
'id': iq.getAttribute('id')
|
||||||
})));
|
})));
|
||||||
await test_utils.waitUntil(() => _.includes(_converse.session.get('push_enabled'), 'chat.shakespeare.lit'));
|
await test_utils.waitUntil(() => _.includes(_converse.session.get('push_enabled'), 'chat.shakespeare.lit'));
|
||||||
done();
|
done();
|
||||||
|
128
spec/smacks.js
Normal file
128
spec/smacks.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
(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();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}));
|
@ -438,8 +438,7 @@ converse.plugins.add('converse-controlbox', {
|
|||||||
*/
|
*/
|
||||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||||
if (_converse.authentication === _converse.ANONYMOUS) {
|
if (_converse.authentication === _converse.ANONYMOUS) {
|
||||||
this.connect(_converse.jid, null);
|
return this.connect(_converse.jid, null);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (!this.validate()) { return; }
|
if (!this.validate()) { return; }
|
||||||
|
|
||||||
@ -471,20 +470,12 @@ converse.plugins.add('converse-controlbox', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
connect (jid, 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"],
|
if (_.includes(["converse/login", "converse/register"],
|
||||||
Backbone.history.getFragment())) {
|
Backbone.history.getFragment())) {
|
||||||
_converse.router.navigate('', {'replace': true});
|
_converse.router.navigate('', {'replace': true});
|
||||||
}
|
}
|
||||||
_converse.connection.reset();
|
_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();
|
ev.preventDefault();
|
||||||
const result = confirm(__("Are you sure you want to log out?"));
|
const result = confirm(__("Are you sure you want to log out?"));
|
||||||
if (result === true) {
|
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 enabled_services = _.reject(_converse.push_app_servers, 'disable');
|
||||||
const disabled_services = _.filter(_converse.push_app_servers, 'disable');
|
const disabled_services = _.filter(_converse.push_app_servers, 'disable');
|
||||||
try {
|
|
||||||
const enabled = _.map(enabled_services, _.partial(enablePushAppServer, domain));
|
const enabled = _.map(enabled_services, _.partial(enablePushAppServer, domain));
|
||||||
const disabled = _.map(disabled_services, _.partial(disablePushAppServer, domain));
|
const disabled = _.map(disabled_services, _.partial(disablePushAppServer, domain));
|
||||||
|
try {
|
||||||
await Promise.all(enabled.concat(disabled));
|
await Promise.all(enabled.concat(disabled));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_converse.log('Could not enable or disable push App Server', Strophe.LogLevel.ERROR);
|
_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.session.save('push_enabled', push_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
_converse.api.listen.on('statusInitialized', () => enablePush());
|
_converse.api.listen.on('statusInitialized', () => enablePush());
|
||||||
|
|
||||||
function onChatBoxAdded (model) {
|
function onChatBoxAdded (model) {
|
||||||
|
@ -102,6 +102,7 @@ _converse.core_plugins = [
|
|||||||
'converse-pubsub',
|
'converse-pubsub',
|
||||||
'converse-roster',
|
'converse-roster',
|
||||||
'converse-rsm',
|
'converse-rsm',
|
||||||
|
'converse-smacks',
|
||||||
'converse-vcard'
|
'converse-vcard'
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -190,7 +191,7 @@ _converse.CHATROOMS_TYPE = 'chatroom';
|
|||||||
_converse.HEADLINES_TYPE = 'headline';
|
_converse.HEADLINES_TYPE = 'headline';
|
||||||
_converse.CONTROLBOX_TYPE = 'controlbox';
|
_converse.CONTROLBOX_TYPE = 'controlbox';
|
||||||
|
|
||||||
_converse.default_connection_options = {};
|
_converse.default_connection_options = {'explicitResourceBinding': true};
|
||||||
|
|
||||||
// Default configuration values
|
// Default configuration values
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
@ -304,8 +305,9 @@ _converse.__ = function (str) {
|
|||||||
const __ = _converse.__;
|
const __ = _converse.__;
|
||||||
|
|
||||||
const PROMISES = [
|
const PROMISES = [
|
||||||
'initialized',
|
'afterResourceBinding',
|
||||||
'connectionInitialized',
|
'connectionInitialized',
|
||||||
|
'initialized',
|
||||||
'pluginsInitialized',
|
'pluginsInitialized',
|
||||||
'statusInitialized'
|
'statusInitialized'
|
||||||
];
|
];
|
||||||
@ -405,6 +407,34 @@ function initClientConfig () {
|
|||||||
_converse.api.trigger('clientConfigInitialized');
|
_converse.api.trigger('clientConfigInitialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function clearSession () {
|
||||||
|
if (!_.isUndefined(_converse.bosh_session)) {
|
||||||
|
_converse.bosh_session.destroy();
|
||||||
|
delete _converse.bosh_session;
|
||||||
|
}
|
||||||
|
if (!_.isUndefined(_converse.session)) {
|
||||||
|
_converse.session.destroy();
|
||||||
|
delete _converse.session;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Refactor so that we don't clear
|
||||||
|
if (!_converse.config.get('trusted') || isTestEnv()) {
|
||||||
|
window.localStorage.clear();
|
||||||
|
window.sessionStorage.clear();
|
||||||
|
} else {
|
||||||
|
_.get(_converse, 'bosh_session.browserStorage', {'_clear': _.noop})._clear();
|
||||||
|
_.get(_converse, 'session.browserStorage', {'_clear': _.noop})._clear();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Triggered once the session information has been cleared,
|
||||||
|
* for example when the user has logged out or when Converse has
|
||||||
|
* disconnected for some other reason.
|
||||||
|
* @event _converse#clearSession
|
||||||
|
*/
|
||||||
|
_converse.api.trigger('clearSession');
|
||||||
|
}
|
||||||
|
|
||||||
_converse.initConnection = function () {
|
_converse.initConnection = function () {
|
||||||
/* Creates a new Strophe.Connection instance if we don't already have one.
|
/* Creates a new Strophe.Connection instance if we don't already have one.
|
||||||
*/
|
*/
|
||||||
@ -437,28 +467,76 @@ _converse.initConnection = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function initSession () {
|
async function initBOSHSession () {
|
||||||
const id = 'converse.bosh-session';
|
const id = 'converse.bosh-session';
|
||||||
_converse.session = new Backbone.Model({id});
|
_converse.bosh_session = new Backbone.Model({id});
|
||||||
_converse.session.browserStorage = new BrowserStorage.session(id);
|
_converse.bosh_session.browserStorage = new BrowserStorage.session(id);
|
||||||
try {
|
try {
|
||||||
await new Promise((success, error) => _converse.session.fetch({success, error}));
|
await new Promise((success, error) => _converse.bosh_session.fetch({success, error}));
|
||||||
if (_converse.jid && !u.isSameBareJID(_converse.session.get('jid'), _converse.jid)) {
|
if (_converse.jid && !u.isSameBareJID(_converse.bosh_session.get('jid'), _converse.jid)) {
|
||||||
_converse.session.clear({'silent': true});
|
_converse.bosh_session.clear({'silent': true});
|
||||||
_converse.session.save({'jid': _converse.jid, id});
|
_converse.bosh_session.save({'jid': _converse.jid, id});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (_converse.jid) {
|
if (_converse.jid) {
|
||||||
_converse.session.save({'jid': _converse.jid});
|
_converse.bosh_session.save({'jid': _converse.jid});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Triggered once the session has been initialized. The session is a
|
* Triggered once the session has been initialized. The session is a
|
||||||
* persistent object which stores session information in the browser storage.
|
* persistent object which stores session information in the browser storage.
|
||||||
* @event _converse#sessionInitialized
|
* @event _converse#BOSHSessionInitialized
|
||||||
* @memberOf _converse
|
* @memberOf _converse
|
||||||
*/
|
*/
|
||||||
_converse.api.trigger('sessionInitialized');
|
_converse.api.trigger('BOSHSessionInitialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initUserSession (jid) {
|
||||||
|
const bare_jid = Strophe.getBareJidFromJid(jid);
|
||||||
|
const id = `converse.session-${bare_jid}`;
|
||||||
|
if (!_converse.session || _converse.session.get('id') !== id) {
|
||||||
|
_converse.session = new Backbone.Model({id});
|
||||||
|
_converse.session.browserStorage = new BrowserStorage.session(id);
|
||||||
|
await new Promise(r => _converse.session.fetch({'success': r, 'error': r}));
|
||||||
|
/**
|
||||||
|
* Triggered once the user's session has been initialized. The session is a
|
||||||
|
* cache which stores information about the user's current session.
|
||||||
|
* @event _converse#userSessionInitialized
|
||||||
|
* @memberOf _converse
|
||||||
|
*/
|
||||||
|
_converse.api.trigger('userSessionInitialized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUserJID (jid) {
|
||||||
|
initUserSession(jid);
|
||||||
|
_converse.jid = jid;
|
||||||
|
_converse.bare_jid = Strophe.getBareJidFromJid(jid);
|
||||||
|
_converse.resource = Strophe.getResourceFromJid(jid);
|
||||||
|
_converse.domain = Strophe.getDomainFromJid(jid);
|
||||||
|
_converse.session.save({
|
||||||
|
'jid': jid,
|
||||||
|
'bare_jid': _converse.bare_jid,
|
||||||
|
'resource': _converse.resource,
|
||||||
|
'domain': _converse.domain
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function onConnected (reconnecting) {
|
||||||
|
/* Called as soon as a new connection has been established, either
|
||||||
|
* by logging in or by attaching to an existing BOSH session.
|
||||||
|
*/
|
||||||
|
_converse.connection.flush(); // Solves problem of returned PubSub BOSH response not received by browser
|
||||||
|
setUserJID(_converse.connection.jid);
|
||||||
|
/**
|
||||||
|
* Synchronous event triggered after we've sent an IQ to bind the
|
||||||
|
* user's JID resource for this session.
|
||||||
|
* @event _converse#afterResourceBinding
|
||||||
|
*/
|
||||||
|
await _converse.api.trigger('afterResourceBinding', {'synchronous': true});
|
||||||
|
_converse.enableCarbons();
|
||||||
|
_converse.initStatus(reconnecting)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -483,8 +561,8 @@ async function finishInitialization () {
|
|||||||
initClientConfig();
|
initClientConfig();
|
||||||
initPlugins();
|
initPlugins();
|
||||||
_converse.initConnection();
|
_converse.initConnection();
|
||||||
await initSession();
|
await initBOSHSession();
|
||||||
_converse.logIn();
|
_converse.api.user.login();
|
||||||
_converse.registerGlobalEventHandlers();
|
_converse.registerGlobalEventHandlers();
|
||||||
if (!Backbone.history.started) {
|
if (!Backbone.history.started) {
|
||||||
Backbone.history.start();
|
Backbone.history.start();
|
||||||
@ -758,7 +836,7 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
|
|
||||||
_converse.connection.reconnecting = true;
|
_converse.connection.reconnecting = true;
|
||||||
_converse.tearDown();
|
_converse.tearDown();
|
||||||
_converse.logIn(null, true);
|
_converse.api.user.login(null, null, true);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
|
|
||||||
@ -773,7 +851,7 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
delete _converse.connection.reconnecting;
|
delete _converse.connection.reconnecting;
|
||||||
_converse.connection.reset();
|
_converse.connection.reset();
|
||||||
_converse.tearDown();
|
_converse.tearDown();
|
||||||
_converse.clearSession();
|
clearSession();
|
||||||
/**
|
/**
|
||||||
* Triggered after converse.js has disconnected from the XMPP server.
|
* Triggered after converse.js has disconnected from the XMPP server.
|
||||||
* @event _converse#disconnected
|
* @event _converse#disconnected
|
||||||
@ -840,7 +918,7 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
_converse.setDisconnectionCause();
|
_converse.setDisconnectionCause();
|
||||||
if (_converse.connection.reconnecting) {
|
if (_converse.connection.reconnecting) {
|
||||||
_converse.log(status === Strophe.Status.CONNECTED ? 'Reconnected' : 'Reattached');
|
_converse.log(status === Strophe.Status.CONNECTED ? 'Reconnected' : 'Reattached');
|
||||||
_converse.onConnected(true);
|
onConnected(true);
|
||||||
} else {
|
} else {
|
||||||
_converse.log(status === Strophe.Status.CONNECTED ? 'Connected' : 'Attached');
|
_converse.log(status === Strophe.Status.CONNECTED ? 'Connected' : 'Attached');
|
||||||
if (_converse.connection.restored) {
|
if (_converse.connection.restored) {
|
||||||
@ -848,7 +926,7 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
// we're restoring an existing session.
|
// we're restoring an existing session.
|
||||||
_converse.send_initial_presence = false;
|
_converse.send_initial_presence = false;
|
||||||
}
|
}
|
||||||
_converse.onConnected();
|
onConnected();
|
||||||
}
|
}
|
||||||
} else if (status === Strophe.Status.DISCONNECTED) {
|
} else if (status === Strophe.Status.DISCONNECTED) {
|
||||||
_converse.setDisconnectionCause(status, message);
|
_converse.setDisconnectionCause(status, message);
|
||||||
@ -928,39 +1006,6 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.clearSession = function () {
|
|
||||||
if (!_converse.config.get('trusted') || isTestEnv()) {
|
|
||||||
window.localStorage.clear();
|
|
||||||
window.sessionStorage.clear();
|
|
||||||
} else {
|
|
||||||
_.get(_converse, 'session.browserStorage', {'_clear': _.noop})._clear();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Triggered once the session information has been cleared,
|
|
||||||
* for example when the user has logged out or when Converse has
|
|
||||||
* disconnected for some other reason.
|
|
||||||
* @event _converse#clearSession
|
|
||||||
*/
|
|
||||||
_converse.api.trigger('clearSession');
|
|
||||||
};
|
|
||||||
|
|
||||||
this.logOut = function () {
|
|
||||||
_converse.clearSession();
|
|
||||||
_converse.setDisconnectionCause(_converse.LOGOUT, undefined, true);
|
|
||||||
if (!_.isUndefined(_converse.connection)) {
|
|
||||||
_converse.connection.disconnect();
|
|
||||||
} else {
|
|
||||||
_converse.tearDown();
|
|
||||||
}
|
|
||||||
// Recreate all the promises
|
|
||||||
Object.keys(_converse.promises).forEach(addPromise);
|
|
||||||
/**
|
|
||||||
* Triggered once the user has logged out.
|
|
||||||
* @event _converse#logout
|
|
||||||
*/
|
|
||||||
_converse.api.trigger('logout');
|
|
||||||
};
|
|
||||||
|
|
||||||
this.saveWindowState = function (ev) {
|
this.saveWindowState = function (ev) {
|
||||||
// XXX: eventually we should be able to just use
|
// XXX: eventually we should be able to just use
|
||||||
// document.visibilityState (when we drop support for older
|
// document.visibilityState (when we drop support for older
|
||||||
@ -1013,7 +1058,7 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
/* Ask the XMPP server to enable Message Carbons
|
/* Ask the XMPP server to enable Message Carbons
|
||||||
* See XEP-0280 https://xmpp.org/extensions/xep-0280.html#enabling
|
* See XEP-0280 https://xmpp.org/extensions/xep-0280.html#enabling
|
||||||
*/
|
*/
|
||||||
if (!this.message_carbons || this.session.get('carbons_enabled')) {
|
if (!this.message_carbons || !this.session || !this.session.get('carbons_enabled')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const carbons_iq = new Strophe.Builder('iq', {
|
const carbons_iq = new Strophe.Builder('iq', {
|
||||||
@ -1076,25 +1121,6 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setUserJID = function () {
|
|
||||||
_converse.jid = _converse.connection.jid;
|
|
||||||
_converse.bare_jid = Strophe.getBareJidFromJid(_converse.connection.jid);
|
|
||||||
_converse.resource = Strophe.getResourceFromJid(_converse.connection.jid);
|
|
||||||
_converse.domain = Strophe.getDomainFromJid(_converse.connection.jid);
|
|
||||||
_converse.session.save({
|
|
||||||
'jid': _converse.connection.jid,
|
|
||||||
'bare_jid': Strophe.getBareJidFromJid(_converse.connection.jid),
|
|
||||||
'resource': Strophe.getResourceFromJid(_converse.connection.jid),
|
|
||||||
'domain': Strophe.getDomainFromJid(_converse.connection.jid)
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* Triggered once we have the user's full JID and it's been save in the
|
|
||||||
* session.
|
|
||||||
* @event _converse#setUserJID
|
|
||||||
*/
|
|
||||||
_converse.api.trigger('setUserJID');
|
|
||||||
};
|
|
||||||
|
|
||||||
this.bindResource = async function () {
|
this.bindResource = async function () {
|
||||||
/**
|
/**
|
||||||
* Synchronous event triggered before we send an IQ to bind the user's
|
* Synchronous event triggered before we send an IQ to bind the user's
|
||||||
@ -1105,17 +1131,6 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
_converse.connection.bind();
|
_converse.connection.bind();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onConnected = function (reconnecting) {
|
|
||||||
/* Called as soon as a new connection has been established, either
|
|
||||||
* by logging in or by attaching to an existing BOSH session.
|
|
||||||
*/
|
|
||||||
_converse.connection.flush(); // Solves problem of returned PubSub BOSH response not received by browser
|
|
||||||
_converse.setUserJID();
|
|
||||||
_converse.enableCarbons();
|
|
||||||
_converse.initStatus(reconnecting)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
this.ConnectionFeedback = Backbone.Model.extend({
|
this.ConnectionFeedback = Backbone.Model.extend({
|
||||||
defaults: {
|
defaults: {
|
||||||
'connection_status': Strophe.Status.DISCONNECTED,
|
'connection_status': Strophe.Status.DISCONNECTED,
|
||||||
@ -1130,12 +1145,8 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
|
|
||||||
|
|
||||||
this.XMPPStatus = Backbone.Model.extend({
|
this.XMPPStatus = Backbone.Model.extend({
|
||||||
|
defaults: {
|
||||||
defaults () {
|
|
||||||
return {
|
|
||||||
"jid": _converse.bare_jid,
|
|
||||||
"status": _converse.default_state
|
"status": _converse.default_state
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize () {
|
initialize () {
|
||||||
@ -1237,7 +1248,7 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
/* Tries to restore a cached BOSH session. */
|
/* Tries to restore a cached BOSH session. */
|
||||||
const jid = _converse.session.get('jid');
|
const jid = _converse.bosh_session.get('jid');
|
||||||
if (!jid) {
|
if (!jid) {
|
||||||
const msg = "restoreBOSHSession: tried to restore a \"keepalive\" session "+
|
const msg = "restoreBOSHSession: tried to restore a \"keepalive\" session "+
|
||||||
"but we don't have the JID for the user!";
|
"but we don't have the JID for the user!";
|
||||||
@ -1256,7 +1267,7 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
_converse.log(
|
_converse.log(
|
||||||
"Could not restore session for jid: "+
|
"Could not restore session for jid: "+
|
||||||
jid+" Error message: "+e.message, Strophe.LogLevel.WARN);
|
jid+" Error message: "+e.message, Strophe.LogLevel.WARN);
|
||||||
this.clearSession(); // We want to clear presences (see #555)
|
clearSession(); // We want to clear presences (see #555)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1323,11 +1334,6 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.autoLogin = function (credentials) {
|
this.autoLogin = function (credentials) {
|
||||||
if (credentials) {
|
|
||||||
// If passed in, the credentials come from credentials_url,
|
|
||||||
// so we set them on the converse object.
|
|
||||||
this.jid = credentials.jid;
|
|
||||||
}
|
|
||||||
if (this.authentication === _converse.ANONYMOUS || this.authentication === _converse.EXTERNAL) {
|
if (this.authentication === _converse.ANONYMOUS || this.authentication === _converse.EXTERNAL) {
|
||||||
if (!this.jid) {
|
if (!this.jid) {
|
||||||
throw new Error("Config Error: when using anonymous login " +
|
throw new Error("Config Error: when using anonymous login " +
|
||||||
@ -1350,12 +1356,6 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
_converse.api.connection.disconnect();
|
_converse.api.connection.disconnect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const resource = Strophe.getResourceFromJid(this.jid);
|
|
||||||
if (!resource) {
|
|
||||||
this.jid = this.jid.toLowerCase() + _converse.generateResource();
|
|
||||||
} else {
|
|
||||||
this.jid = Strophe.getBareJidFromJid(this.jid).toLowerCase()+'/'+resource;
|
|
||||||
}
|
|
||||||
if (!this.connection.reconnecting) {
|
if (!this.connection.reconnecting) {
|
||||||
this.connection.reset();
|
this.connection.reset();
|
||||||
}
|
}
|
||||||
@ -1363,20 +1363,10 @@ _converse.initialize = async function (settings, callback) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logIn = function (credentials, reconnecting) {
|
|
||||||
// We now try to resume or automatically set up a new session.
|
|
||||||
// Otherwise the user will be shown a login form.
|
|
||||||
if (this.authentication === _converse.PREBIND) {
|
|
||||||
this.attemptPreboundSession(reconnecting);
|
|
||||||
} else {
|
|
||||||
this.attemptNonPreboundSession(credentials, reconnecting);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.tearDown = function () {
|
this.tearDown = function () {
|
||||||
_converse.api.trigger('beforeTearDown');
|
_converse.api.trigger('beforeTearDown');
|
||||||
if (!_.isUndefined(_converse.session)) {
|
if (!_.isUndefined(_converse.bosh_session)) {
|
||||||
_converse.session.destroy();
|
_converse.bosh_session.destroy();
|
||||||
}
|
}
|
||||||
window.removeEventListener('click', _converse.onUserActivity);
|
window.removeEventListener('click', _converse.onUserActivity);
|
||||||
window.removeEventListener('focus', _converse.onUserActivity);
|
window.removeEventListener('focus', _converse.onUserActivity);
|
||||||
@ -1451,7 +1441,7 @@ _converse.api = {
|
|||||||
_converse.connection.disconnect();
|
_converse.connection.disconnect();
|
||||||
} else {
|
} else {
|
||||||
_converse.tearDown();
|
_converse.tearDown();
|
||||||
_converse.clearSession();
|
clearSession();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1473,7 +1463,7 @@ _converse.api = {
|
|||||||
/* Event emitter and promise resolver */
|
/* Event emitter and promise resolver */
|
||||||
const args = Array.from(arguments);
|
const args = Array.from(arguments);
|
||||||
const options = args.pop();
|
const options = args.pop();
|
||||||
if (options.synchronous) {
|
if (options && options.synchronous) {
|
||||||
const events = _converse._events[name] || [];
|
const events = _converse._events[name] || [];
|
||||||
await Promise.all(events.map(e => e.callback.call(e.ctx, args)));
|
await Promise.all(events.map(e => e.callback.call(e.ctx, args)));
|
||||||
} else {
|
} else {
|
||||||
@ -1507,31 +1497,59 @@ _converse.api = {
|
|||||||
* to log the user in by calling the `prebind_url` or `credentials_url` depending
|
* to log the user in by calling the `prebind_url` or `credentials_url` depending
|
||||||
* on whether prebinding is used or not.
|
* on whether prebinding is used or not.
|
||||||
*
|
*
|
||||||
|
* Otherwise the user will be shown a login form.
|
||||||
|
*
|
||||||
* @method _converse.api.user.login
|
* @method _converse.api.user.login
|
||||||
* @param {object} [credentials] An object with the credentials.
|
* @param {string} [jid]
|
||||||
|
* @param {string} [password]
|
||||||
|
* @param {boolean} [reconnecting]
|
||||||
* @example
|
* @example
|
||||||
* converse.plugins.add('myplugin', {
|
* converse.plugins.add('myplugin', {
|
||||||
* initialize: function () {
|
* initialize: function () {
|
||||||
*
|
* this._converse.api.user.login('dummy@example.com', 'secret');
|
||||||
* this._converse.api.user.login({
|
|
||||||
* 'jid': 'dummy@example.com',
|
|
||||||
* 'password': 'secret'
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
'login' (credentials) {
|
login (jid, password, reconnecting) {
|
||||||
_converse.logIn(credentials);
|
if (_converse.authentication === _converse.PREBIND) {
|
||||||
|
_converse.attemptPreboundSession(reconnecting);
|
||||||
|
} else {
|
||||||
|
let credentials;
|
||||||
|
if (jid) {
|
||||||
|
const resource = Strophe.getResourceFromJid(jid);
|
||||||
|
if (!resource) {
|
||||||
|
jid = jid.toLowerCase() + _converse.generateResource();
|
||||||
|
} else {
|
||||||
|
jid = Strophe.getBareJidFromJid(jid).toLowerCase()+'/'+resource;
|
||||||
|
}
|
||||||
|
setUserJID(jid);
|
||||||
|
credentials = {'jid': jid, 'password': password};
|
||||||
|
}
|
||||||
|
_converse.attemptNonPreboundSession(credentials, reconnecting);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs the user out of the current XMPP session.
|
* Logs the user out of the current XMPP session.
|
||||||
*
|
*
|
||||||
* @method _converse.api.user.logout
|
* @method _converse.api.user.logout
|
||||||
* @example _converse.api.user.logout();
|
* @example _converse.api.user.logout();
|
||||||
*/
|
*/
|
||||||
'logout' () {
|
logout () {
|
||||||
_converse.logOut();
|
clearSession();
|
||||||
|
_converse.setDisconnectionCause(_converse.LOGOUT, undefined, true);
|
||||||
|
if (!_.isUndefined(_converse.connection)) {
|
||||||
|
_converse.connection.disconnect();
|
||||||
|
} else {
|
||||||
|
_converse.tearDown();
|
||||||
|
}
|
||||||
|
// Recreate all the promises
|
||||||
|
Object.keys(_converse.promises).forEach(addPromise);
|
||||||
|
/**
|
||||||
|
* Triggered once the user has logged out.
|
||||||
|
* @event _converse#logout
|
||||||
|
*/
|
||||||
|
_converse.api.trigger('logout');
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Set and get the user's chat status, also called their *availability*.
|
* Set and get the user's chat status, also called their *availability*.
|
||||||
@ -1841,9 +1859,16 @@ _converse.api = {
|
|||||||
* });
|
* });
|
||||||
* _converse.api.send(msg);
|
* _converse.api.send(msg);
|
||||||
*/
|
*/
|
||||||
'send' (stanza) {
|
send (stanza) {
|
||||||
|
if (_.isString(stanza)) {
|
||||||
|
stanza = u.toStanza(stanza);
|
||||||
|
}
|
||||||
|
if (stanza.tagName === 'iq') {
|
||||||
|
return _converse.api.sendIQ(stanza);
|
||||||
|
} else {
|
||||||
_converse.connection.send(stanza);
|
_converse.connection.send(stanza);
|
||||||
_converse.api.trigger('send', stanza);
|
_converse.api.trigger('send', stanza);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1852,7 +1877,7 @@ _converse.api = {
|
|||||||
* @returns {Promise} A promise which resolves when we receive a `result` stanza
|
* @returns {Promise} A promise which resolves when we receive a `result` stanza
|
||||||
* or is rejected when we receive an `error` stanza.
|
* or is rejected when we receive an `error` stanza.
|
||||||
*/
|
*/
|
||||||
'sendIQ' (stanza, timeout) {
|
sendIQ (stanza, timeout) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
_converse.connection.sendIQ(stanza, resolve, reject, timeout || _converse.IQ_TIMEOUT);
|
_converse.connection.sendIQ(stanza, resolve, reject, timeout || _converse.IQ_TIMEOUT);
|
||||||
_converse.api.trigger('send', stanza);
|
_converse.api.trigger('send', stanza);
|
||||||
|
@ -22,6 +22,7 @@ converse.plugins.add('converse-disco', {
|
|||||||
|
|
||||||
// Promises exposed by this plugin
|
// Promises exposed by this plugin
|
||||||
_converse.api.promises.add('discoInitialized');
|
_converse.api.promises.add('discoInitialized');
|
||||||
|
_converse.api.promises.add('streamFeaturesAdded');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -260,24 +261,22 @@ converse.plugins.add('converse-disco', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initStreamFeatures () {
|
function initStreamFeatures () {
|
||||||
|
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 = new Backbone.Collection();
|
||||||
_converse.stream_features.browserStorage = new BrowserStorage.session(
|
_converse.stream_features.browserStorage = new BrowserStorage.session(id);
|
||||||
`converse.stream-features-${_converse.bare_jid}`
|
|
||||||
);
|
|
||||||
_converse.stream_features.fetch({
|
_converse.stream_features.fetch({
|
||||||
success (collection) {
|
success (collection) {
|
||||||
if (collection.length === 0 && _converse.connection.features) {
|
if (collection.length === 0 && _converse.connection.features) {
|
||||||
_.forEach(
|
Array.from(_converse.connection.features.childNodes)
|
||||||
_converse.connection.features.childNodes,
|
.forEach(feature => {
|
||||||
(feature) => {
|
|
||||||
_converse.stream_features.create({
|
_converse.stream_features.create({
|
||||||
'name': feature.nodeName,
|
'name': feature.nodeName,
|
||||||
'xmlns': feature.getAttribute('xmlns')
|
'xmlns': feature.getAttribute('xmlns')
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
/**
|
/**
|
||||||
* Triggered as soon as Converse has processed the stream features as advertised by
|
* 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
|
* the server. If you want to check whether a stream feature is supported before
|
||||||
@ -287,6 +286,9 @@ converse.plugins.add('converse-disco', {
|
|||||||
*/
|
*/
|
||||||
_converse.api.trigger('streamFeaturesAdded');
|
_converse.api.trigger('streamFeaturesAdded');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function initializeDisco () {
|
async function initializeDisco () {
|
||||||
addClientFeatures();
|
addClientFeatures();
|
||||||
@ -313,7 +315,9 @@ converse.plugins.add('converse-disco', {
|
|||||||
_converse.api.trigger('discoInitialized');
|
_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('reconnected', initializeDisco);
|
||||||
_converse.api.listen.on('connected', initializeDisco);
|
_converse.api.listen.on('connected', initializeDisco);
|
||||||
|
|
||||||
@ -326,6 +330,10 @@ converse.plugins.add('converse-disco', {
|
|||||||
_converse.disco_entities.reset();
|
_converse.disco_entities.reset();
|
||||||
_converse.disco_entities.browserStorage._clear();
|
_converse.disco_entities.browserStorage._clear();
|
||||||
}
|
}
|
||||||
|
if (_converse.stream_features) {
|
||||||
|
_converse.stream_features.reset();
|
||||||
|
_converse.stream_features.browserStorage._clear();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const plugin = this;
|
const plugin = this;
|
||||||
@ -386,7 +394,8 @@ converse.plugins.add('converse-disco', {
|
|||||||
* @param {String} xmlns The XML namespace
|
* @param {String} xmlns The XML namespace
|
||||||
* @example _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver')
|
* @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)) {
|
if (_.isNil(name) || _.isNil(xmlns)) {
|
||||||
throw new Error("name and xmlns need to be provided when calling disco.stream.getFeature");
|
throw new Error("name and xmlns need to be provided when calling disco.stream.getFeature");
|
||||||
}
|
}
|
||||||
|
242
src/headless/converse-smacks.js
Normal file
242
src/headless/converse-smacks.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
// Converse.js
|
||||||
|
// http://conversejs.org
|
||||||
|
//
|
||||||
|
// Copyright (c) The Converse.js developers
|
||||||
|
// Licensed under the Mozilla Public License (MPLv2)
|
||||||
|
|
||||||
|
/* This is a Converse.js plugin which add support for XEP-0198: Stream Management */
|
||||||
|
|
||||||
|
import converse from "./converse-core";
|
||||||
|
|
||||||
|
const { Strophe, $build, _ } = converse.env;
|
||||||
|
const u = converse.env.utils;
|
||||||
|
|
||||||
|
Strophe.addNamespace('SM', 'urn:xmpp:sm:3');
|
||||||
|
|
||||||
|
|
||||||
|
converse.plugins.add('converse-smacks', {
|
||||||
|
|
||||||
|
initialize () {
|
||||||
|
const { _converse } = this;
|
||||||
|
|
||||||
|
// Configuration values for this plugin
|
||||||
|
// ====================================
|
||||||
|
// Refer to docs/source/configuration.rst for explanations of these
|
||||||
|
// configuration settings.
|
||||||
|
_converse.api.settings.update({
|
||||||
|
'enable_smacks': false,
|
||||||
|
'smacks_max_unacked_stanzas': 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
function isStreamManagementSupported () {
|
||||||
|
return _converse.api.disco.stream.getFeature('sm', Strophe.NS.SM);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAck (el) {
|
||||||
|
if (!_converse.session.get('smacks_enabled')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const handled = parseInt(el.getAttribute('h'), 10);
|
||||||
|
const last_known_handled = _converse.session.get('num_stanzas_handled_by_server');
|
||||||
|
const delta = handled - last_known_handled;
|
||||||
|
|
||||||
|
if (delta < 0) {
|
||||||
|
const err_msg = `New reported stanza count lower than previous. `+
|
||||||
|
`New: ${handled} - Previous: ${last_known_handled}`
|
||||||
|
_converse.log(err_msg, Strophe.LogLevel.ERROR);
|
||||||
|
}
|
||||||
|
const unacked_stanzas = _converse.session.get('unacked_stanzas');
|
||||||
|
if (delta > unacked_stanzas.length) {
|
||||||
|
const err_msg =
|
||||||
|
`Higher reported acknowledge count than unacknowledged stanzas. `+
|
||||||
|
`Reported Acknowledged Count: ${delta} -`+
|
||||||
|
`Unacknowledged Stanza Count: ${unacked_stanzas.length} -`+
|
||||||
|
`New: ${handled} - Previous: ${last_known_handled}`
|
||||||
|
_converse.log(err_msg, Strophe.LogLevel.ERROR);
|
||||||
|
}
|
||||||
|
_converse.session.save({
|
||||||
|
'num_stanzas_handled_by_server': handled,
|
||||||
|
'num_stanzas_since_last_ack': 0,
|
||||||
|
'unacked_stanzas': unacked_stanzas.slice(delta)
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendAck() {
|
||||||
|
if (_converse.session.get('smacks_enabled')) {
|
||||||
|
const h = _converse.session.get('num_stanzas_handled');
|
||||||
|
const stanza = u.toStanza(`<a xmlns="${Strophe.NS.SM}" h="${h}"/>`);
|
||||||
|
_converse.api.send(stanza);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stanzaHandler (el) {
|
||||||
|
if (_converse.session.get('smacks_enabled')) {
|
||||||
|
if (u.isTagEqual(el, 'iq') || u.isTagEqual(el, 'presence') || u.isTagEqual(el, 'message')) {
|
||||||
|
const h = _converse.session.get('num_stanzas_handled');
|
||||||
|
_converse.session.save('num_stanzas_handled', h+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSessionData () {
|
||||||
|
_converse.session.save({
|
||||||
|
'smacks_enabled': false,
|
||||||
|
'num_stanzas_handled': 0,
|
||||||
|
'num_stanzas_handled_by_server': 0,
|
||||||
|
'num_stanzas_since_last_ack': 0,
|
||||||
|
'unacked_stanzas': []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSessionData (el) {
|
||||||
|
const data = {'smacks_enabled': true};
|
||||||
|
if (['1', 'true'].includes(el.getAttribute('resume'))) {
|
||||||
|
data['smacks_stream_id'] = el.getAttribute('id');
|
||||||
|
}
|
||||||
|
_converse.session.save(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFailedStanza (el) {
|
||||||
|
if (el.querySelector('item-not-found')) {
|
||||||
|
// Stream resumption must happen before resource binding but
|
||||||
|
// enabling a new stream must happen after resource binding.
|
||||||
|
// Since resumption failed, we simply continue.
|
||||||
|
//
|
||||||
|
// After resource binding, sendEnableStanza will be called
|
||||||
|
// based on the afterResourceBinding event.
|
||||||
|
_converse.log('Could not resume previous SMACKS session, session id not found. '+
|
||||||
|
'A new session will be established.', Strophe.LogLevel.WARN);
|
||||||
|
} else {
|
||||||
|
_converse.log('Failed to enable stream management', Strophe.LogLevel.ERROR);
|
||||||
|
_converse.log(el.outerHTML, Strophe.LogLevel.ERROR);
|
||||||
|
}
|
||||||
|
clearSessionData();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resendUnackedStanzas () {
|
||||||
|
const stanzas = _converse.session.get('unacked_stanzas');
|
||||||
|
// We clear the unacked_stanzas array because it'll get populated
|
||||||
|
// again in `onStanzaSent`
|
||||||
|
_converse.session.save('unacked_stanzas', []);
|
||||||
|
|
||||||
|
// XXX: Currently we're resending *all* unacked stanzas, including
|
||||||
|
// IQ[type="get"] stanzas that longer have handlers (because the
|
||||||
|
// page reloaded or we reconnected, causing removal of handlers).
|
||||||
|
//
|
||||||
|
// *Side-note:* Is it necessary to clear handlers upon reconnection?
|
||||||
|
//
|
||||||
|
// I've considered not resending those stanzas, but then keeping
|
||||||
|
// track of what's been sent and ack'd and their order gets
|
||||||
|
// prohibitively complex.
|
||||||
|
//
|
||||||
|
// It's unclear how much of a problem this poses.
|
||||||
|
//
|
||||||
|
// Two possible solutions are running @converse/headless as a
|
||||||
|
// service worker or handling IQ[type="result"] stanzas
|
||||||
|
// differently, more like push stanzas, so that they don't need
|
||||||
|
// explicit handlers.
|
||||||
|
stanzas.forEach(s => _converse.api.send(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onResumedStanza (el, resolve) {
|
||||||
|
saveSessionData(el);
|
||||||
|
handleAck(el);
|
||||||
|
resendUnackedStanzas();
|
||||||
|
_converse.connection.do_bind = false; // No need to bind our resource anymore
|
||||||
|
_converse.connection.authenticated = true;
|
||||||
|
_converse.connection._changeConnectStatus(Strophe.Status.CONNECTED, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendResumeStanza () {
|
||||||
|
const promise = u.getResolveablePromise();
|
||||||
|
_converse.connection._addSysHandler(_.flow(onResumedStanza, promise.resolve), Strophe.NS.SM, 'resumed');
|
||||||
|
_converse.connection._addSysHandler(_.flow(onFailedStanza, promise.resolve), Strophe.NS.SM, 'failed');
|
||||||
|
|
||||||
|
const previous_id = _converse.session.get('smacks_stream_id');
|
||||||
|
const h = _converse.session.get('num_stanzas_handled_by_server');
|
||||||
|
const stanza = u.toStanza(`<resume xmlns="${Strophe.NS.SM}" h="${h}" previd="${previous_id}"/>`);
|
||||||
|
_converse.api.send(stanza);
|
||||||
|
_converse.connection.flush();
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendEnableStanza () {
|
||||||
|
if (!_converse.enable_smacks || _converse.session.get('smacks_enabled')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (await isStreamManagementSupported()) {
|
||||||
|
const promise = u.getResolveablePromise();
|
||||||
|
_converse.connection._addSysHandler(_.flow(saveSessionData, promise.resolve), Strophe.NS.SM, 'enabled');
|
||||||
|
_converse.connection._addSysHandler(_.flow(onFailedStanza, promise.resolve), Strophe.NS.SM, 'failed');
|
||||||
|
|
||||||
|
const stanza = u.toStanza(`<enable xmlns="${Strophe.NS.SM}" resume="true"/>`);
|
||||||
|
_converse.api.send(stanza);
|
||||||
|
_converse.connection.flush();
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function enableStreamManagement () {
|
||||||
|
if (!_converse.enable_smacks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(await isStreamManagementSupported())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_converse.connection.addHandler(stanzaHandler);
|
||||||
|
_converse.connection.addHandler(sendAck, Strophe.NS.SM, 'r');
|
||||||
|
_converse.connection.addHandler(handleAck, Strophe.NS.SM, 'a');
|
||||||
|
|
||||||
|
if (_converse.connection._proto instanceof Strophe.Bosh &&
|
||||||
|
_converse.connfeedback.get('connection_status') === Strophe.Status.ATTACHED) {
|
||||||
|
// No need to continue further when we have an existing BOSH session,
|
||||||
|
// since our existing session still exists server-side.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_converse.session.get('smacks_stream_id')) {
|
||||||
|
await sendResumeStanza();
|
||||||
|
} else {
|
||||||
|
clearSessionData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStanzaSent (stanza) {
|
||||||
|
if (!_converse.session) {
|
||||||
|
_converse.log('No _converse.session!', Strophe.LogLevel.WARN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_converse.session.get('smacks_enabled')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (u.isTagEqual(stanza, 'iq') ||
|
||||||
|
u.isTagEqual(stanza, 'presence') ||
|
||||||
|
u.isTagEqual(stanza, 'message')) {
|
||||||
|
|
||||||
|
const stanza_string = Strophe.serialize(stanza);
|
||||||
|
_converse.session.save(
|
||||||
|
'unacked_stanzas',
|
||||||
|
_converse.session.get('unacked_stanzas').concat([stanza_string])
|
||||||
|
);
|
||||||
|
const max_unacked = _converse.smacks_max_unacked_stanzas;
|
||||||
|
if (max_unacked > 0) {
|
||||||
|
const num = _converse.session.get('num_stanzas_since_last_ack') + 1;
|
||||||
|
if (num % max_unacked === 0) {
|
||||||
|
// Request confirmation of sent stanzas
|
||||||
|
_converse.api.send(u.toStanza(`<r xmlns="${Strophe.NS.SM}"/>`));
|
||||||
|
}
|
||||||
|
_converse.session.save({'num_stanzas_since_last_ack': num});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_converse.api.listen.on('beforeResourceBinding', enableStreamManagement);
|
||||||
|
_converse.api.listen.on('afterResourceBinding', sendEnableStanza);
|
||||||
|
_converse.api.listen.on('send', onStanzaSent);
|
||||||
|
}
|
||||||
|
});
|
@ -55,7 +55,7 @@ converse.plugins.add('converse-vcard', {
|
|||||||
model: _converse.VCard,
|
model: _converse.VCard,
|
||||||
|
|
||||||
initialize () {
|
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.browserStorage = new BrowserStorage[_converse.config.get('storage')](id);
|
||||||
_converse.vcards.fetch();
|
_converse.vcards.fetch();
|
||||||
}
|
}
|
||||||
_converse.api.listen.on('setUserJID', _converse.initVCardCollection);
|
_converse.api.listen.on('afterResourceBinding', _converse.initVCardCollection);
|
||||||
|
|
||||||
|
|
||||||
_converse.api.listen.on('statusInitialized', () => {
|
_converse.api.listen.on('statusInitialized', () => {
|
||||||
const vcards = _converse.vcards;
|
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.xmppstatus.vcard = vcards.findWhere({'jid': jid}) || vcards.create({'jid': jid});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
_converse.api.listen.on('addClientFeatures', () => {
|
_converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(Strophe.NS.VCARD));
|
||||||
_converse.api.disco.own.features.add(Strophe.NS.VCARD);
|
|
||||||
});
|
|
||||||
|
|
||||||
/************************ BEGIN API ************************/
|
/************************ BEGIN API ************************/
|
||||||
Object.assign(_converse.api, {
|
Object.assign(_converse.api, {
|
||||||
@ -191,7 +189,7 @@ converse.plugins.add('converse-vcard', {
|
|||||||
* );
|
* );
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
'get' (model, force) {
|
get (model, force) {
|
||||||
if (_.isString(model)) {
|
if (_.isString(model)) {
|
||||||
return getVCard(_converse, model);
|
return getVCard(_converse, model);
|
||||||
} else if (force ||
|
} else if (force ||
|
||||||
@ -224,7 +222,7 @@ converse.plugins.add('converse-vcard', {
|
|||||||
* _converse.api.vcard.update(chatbox);
|
* _converse.api.vcard.update(chatbox);
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
'update' (model, force) {
|
update (model, force) {
|
||||||
return this.get(model, force)
|
return this.get(model, force)
|
||||||
.then(vcard => {
|
.then(vcard => {
|
||||||
delete vcard['stanza']
|
delete vcard['stanza']
|
||||||
|
@ -12,6 +12,7 @@ import "./converse-ping"; // XEP-0199 XMPP Ping
|
|||||||
import "./converse-pubsub"; // XEP-0199 XMPP Ping
|
import "./converse-pubsub"; // XEP-0199 XMPP Ping
|
||||||
import "./converse-roster"; // Contacts Roster
|
import "./converse-roster"; // Contacts Roster
|
||||||
import "./converse-rsm"; // XEP-0059 Result Set management
|
import "./converse-rsm"; // XEP-0059 Result Set management
|
||||||
|
import "./converse-smacks"; // XEP-0198 Stream Management
|
||||||
import "./converse-vcard"; // XEP-0054 VCard-temp
|
import "./converse-vcard"; // XEP-0054 VCard-temp
|
||||||
/* END: Removable components */
|
/* END: Removable components */
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
"jed": "1.1.1",
|
"jed": "1.1.1",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"pluggable.js": "2.0.1",
|
"pluggable.js": "2.0.1",
|
||||||
"strophe.js": "strophe/strophejs#44da5faca8baa61c691739d63af8b1dea1d2436c",
|
"strophe.js": "strophe/strophejs#f52f26e8cc23f738b7b39180a7ee4511ccd41526",
|
||||||
"twemoji": "^11.0.1",
|
"twemoji": "^11.0.1",
|
||||||
"urijs": "^1.19.1"
|
"urijs": "^1.19.1"
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,18 @@ import sizzle from "sizzle";
|
|||||||
*/
|
*/
|
||||||
const u = {};
|
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) {
|
u.toStanza = function (string) {
|
||||||
return Strophe.xmlHtmlNode(string).firstElementChild;
|
return Strophe.xmlHtmlNode(string).firstElementChild;
|
||||||
}
|
}
|
||||||
|
@ -145,16 +145,22 @@
|
|||||||
'<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'/>`+
|
||||||
'<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 () {
|
c._proto._connect = function () {
|
||||||
c.authenticated = true;
|
|
||||||
c.connected = true;
|
c.connected = true;
|
||||||
c.mock = true;
|
c.mock = true;
|
||||||
c.jid = 'dummy@localhost/resource';
|
c.jid = 'dummy@localhost/resource';
|
||||||
|
c._changeConnectStatus(Strophe.Status.BINDREQUIRED);
|
||||||
|
};
|
||||||
|
|
||||||
|
c.bind = function () {
|
||||||
|
c.authenticated = true;
|
||||||
|
this.authenticated = true;
|
||||||
c._changeConnectStatus(Strophe.Status.CONNECTED);
|
c._changeConnectStatus(Strophe.Status.CONNECTED);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -180,7 +186,7 @@
|
|||||||
_.forEach(spies.connection, method => spyOn(connection, method));
|
_.forEach(spies.connection, method => spyOn(connection, method));
|
||||||
}
|
}
|
||||||
|
|
||||||
const _converse = await converse.initialize(_.extend({
|
const _converse = await converse.initialize(Object.assign({
|
||||||
'i18n': 'en',
|
'i18n': 'en',
|
||||||
'auto_subscribe': false,
|
'auto_subscribe': false,
|
||||||
'play_sounds': false,
|
'play_sounds': false,
|
||||||
@ -232,10 +238,8 @@
|
|||||||
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
|
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
|
||||||
};
|
};
|
||||||
if (_.get(settings, 'auto_login') !== false) {
|
if (_.get(settings, 'auto_login') !== false) {
|
||||||
_converse.api.user.login({
|
_converse.api.user.login('dummy@localhost', 'secret');
|
||||||
'jid': 'dummy@localhost',
|
await _converse.api.waitUntil('afterResourceBinding');
|
||||||
'password': 'secret'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
window.converse_disable_effects = true;
|
window.converse_disable_effects = true;
|
||||||
return _converse;
|
return _converse;
|
||||||
|
@ -44,6 +44,7 @@ var specs = [
|
|||||||
"spec/protocol",
|
"spec/protocol",
|
||||||
"spec/presence",
|
"spec/presence",
|
||||||
"spec/eventemitter",
|
"spec/eventemitter",
|
||||||
|
"spec/smacks",
|
||||||
"spec/ping",
|
"spec/ping",
|
||||||
"spec/push",
|
"spec/push",
|
||||||
"spec/xmppstatus",
|
"spec/xmppstatus",
|
||||||
|
Loading…
Reference in New Issue
Block a user