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:
|
||||
- node_modules
|
||||
addons:
|
||||
chrome: unstable
|
||||
chrome: stable
|
||||
node_js:
|
||||
- "10"
|
||||
install: make stamp-npm
|
||||
|
11
CHANGES.md
11
CHANGES.md
@ -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)
|
||||
|
||||
|
7
dev.html
7
dev.html
@ -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
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -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);
|
||||
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';
|
||||
cbview.el.querySelector('input[name="jid"]').value = 'dummy@localhost';
|
||||
cbview.el.querySelector('input[name="password"]').value = 'secret';
|
||||
|
||||
spyOn(cbview.loginpanel, 'connect');
|
||||
cbview.delegateEvents();
|
||||
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();
|
||||
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();
|
||||
|
45
spec/push.js
45
spec/push.js
@ -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();
|
||||
|
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();
|
||||
}));
|
||||
});
|
||||
}));
|
@ -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) {
|
||||
|
@ -102,6 +102,7 @@ _converse.core_plugins = [
|
||||
'converse-pubsub',
|
||||
'converse-roster',
|
||||
'converse-rsm',
|
||||
'converse-smacks',
|
||||
'converse-vcard'
|
||||
];
|
||||
|
||||
@ -190,7 +191,7 @@ _converse.CHATROOMS_TYPE = 'chatroom';
|
||||
_converse.HEADLINES_TYPE = 'headline';
|
||||
_converse.CONTROLBOX_TYPE = 'controlbox';
|
||||
|
||||
_converse.default_connection_options = {};
|
||||
_converse.default_connection_options = {'explicitResourceBinding': true};
|
||||
|
||||
// Default configuration values
|
||||
// ----------------------------
|
||||
@ -304,8 +305,9 @@ _converse.__ = function (str) {
|
||||
const __ = _converse.__;
|
||||
|
||||
const PROMISES = [
|
||||
'initialized',
|
||||
'afterResourceBinding',
|
||||
'connectionInitialized',
|
||||
'initialized',
|
||||
'pluginsInitialized',
|
||||
'statusInitialized'
|
||||
];
|
||||
@ -405,6 +407,34 @@ function initClientConfig () {
|
||||
_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 () {
|
||||
/* 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';
|
||||
_converse.session = new Backbone.Model({id});
|
||||
_converse.session.browserStorage = new BrowserStorage.session(id);
|
||||
_converse.bosh_session = new Backbone.Model({id});
|
||||
_converse.bosh_session.browserStorage = new BrowserStorage.session(id);
|
||||
try {
|
||||
await new Promise((success, error) => _converse.session.fetch({success, error}));
|
||||
if (_converse.jid && !u.isSameBareJID(_converse.session.get('jid'), _converse.jid)) {
|
||||
_converse.session.clear({'silent': true});
|
||||
_converse.session.save({'jid': _converse.jid, id});
|
||||
await new Promise((success, error) => _converse.bosh_session.fetch({success, error}));
|
||||
if (_converse.jid && !u.isSameBareJID(_converse.bosh_session.get('jid'), _converse.jid)) {
|
||||
_converse.bosh_session.clear({'silent': true});
|
||||
_converse.bosh_session.save({'jid': _converse.jid, id});
|
||||
}
|
||||
} catch (e) {
|
||||
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
|
||||
* persistent object which stores session information in the browser storage.
|
||||
* @event _converse#sessionInitialized
|
||||
* @event _converse#BOSHSessionInitialized
|
||||
* @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();
|
||||
initPlugins();
|
||||
_converse.initConnection();
|
||||
await initSession();
|
||||
_converse.logIn();
|
||||
await initBOSHSession();
|
||||
_converse.api.user.login();
|
||||
_converse.registerGlobalEventHandlers();
|
||||
if (!Backbone.history.started) {
|
||||
Backbone.history.start();
|
||||
@ -758,7 +836,7 @@ _converse.initialize = async function (settings, callback) {
|
||||
|
||||
_converse.connection.reconnecting = true;
|
||||
_converse.tearDown();
|
||||
_converse.logIn(null, true);
|
||||
_converse.api.user.login(null, null, true);
|
||||
}, 2000);
|
||||
|
||||
|
||||
@ -773,7 +851,7 @@ _converse.initialize = async function (settings, callback) {
|
||||
delete _converse.connection.reconnecting;
|
||||
_converse.connection.reset();
|
||||
_converse.tearDown();
|
||||
_converse.clearSession();
|
||||
clearSession();
|
||||
/**
|
||||
* Triggered after converse.js has disconnected from the XMPP server.
|
||||
* @event _converse#disconnected
|
||||
@ -840,7 +918,7 @@ _converse.initialize = async function (settings, callback) {
|
||||
_converse.setDisconnectionCause();
|
||||
if (_converse.connection.reconnecting) {
|
||||
_converse.log(status === Strophe.Status.CONNECTED ? 'Reconnected' : 'Reattached');
|
||||
_converse.onConnected(true);
|
||||
onConnected(true);
|
||||
} else {
|
||||
_converse.log(status === Strophe.Status.CONNECTED ? 'Connected' : 'Attached');
|
||||
if (_converse.connection.restored) {
|
||||
@ -848,7 +926,7 @@ _converse.initialize = async function (settings, callback) {
|
||||
// we're restoring an existing session.
|
||||
_converse.send_initial_presence = false;
|
||||
}
|
||||
_converse.onConnected();
|
||||
onConnected();
|
||||
}
|
||||
} else if (status === Strophe.Status.DISCONNECTED) {
|
||||
_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) {
|
||||
// XXX: eventually we should be able to just use
|
||||
// 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
|
||||
* 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;
|
||||
}
|
||||
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 () {
|
||||
/**
|
||||
* 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();
|
||||
};
|
||||
|
||||
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({
|
||||
defaults: {
|
||||
'connection_status': Strophe.Status.DISCONNECTED,
|
||||
@ -1130,12 +1145,8 @@ _converse.initialize = async function (settings, callback) {
|
||||
|
||||
|
||||
this.XMPPStatus = Backbone.Model.extend({
|
||||
|
||||
defaults () {
|
||||
return {
|
||||
"jid": _converse.bare_jid,
|
||||
"status": _converse.default_state
|
||||
}
|
||||
defaults: {
|
||||
"status": _converse.default_state
|
||||
},
|
||||
|
||||
initialize () {
|
||||
@ -1237,7 +1248,7 @@ _converse.initialize = async function (settings, callback) {
|
||||
return false;
|
||||
}
|
||||
/* Tries to restore a cached BOSH session. */
|
||||
const jid = _converse.session.get('jid');
|
||||
const jid = _converse.bosh_session.get('jid');
|
||||
if (!jid) {
|
||||
const msg = "restoreBOSHSession: tried to restore a \"keepalive\" session "+
|
||||
"but we don't have the JID for the user!";
|
||||
@ -1256,7 +1267,7 @@ _converse.initialize = async function (settings, callback) {
|
||||
_converse.log(
|
||||
"Could not restore session for jid: "+
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1323,11 +1334,6 @@ _converse.initialize = async function (settings, callback) {
|
||||
};
|
||||
|
||||
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.jid) {
|
||||
throw new Error("Config Error: when using anonymous login " +
|
||||
@ -1350,12 +1356,6 @@ _converse.initialize = async function (settings, callback) {
|
||||
_converse.api.connection.disconnect();
|
||||
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) {
|
||||
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 () {
|
||||
_converse.api.trigger('beforeTearDown');
|
||||
if (!_.isUndefined(_converse.session)) {
|
||||
_converse.session.destroy();
|
||||
if (!_.isUndefined(_converse.bosh_session)) {
|
||||
_converse.bosh_session.destroy();
|
||||
}
|
||||
window.removeEventListener('click', _converse.onUserActivity);
|
||||
window.removeEventListener('focus', _converse.onUserActivity);
|
||||
@ -1451,7 +1441,7 @@ _converse.api = {
|
||||
_converse.connection.disconnect();
|
||||
} else {
|
||||
_converse.tearDown();
|
||||
_converse.clearSession();
|
||||
clearSession();
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -1473,7 +1463,7 @@ _converse.api = {
|
||||
/* Event emitter and promise resolver */
|
||||
const args = Array.from(arguments);
|
||||
const options = args.pop();
|
||||
if (options.synchronous) {
|
||||
if (options && options.synchronous) {
|
||||
const events = _converse._events[name] || [];
|
||||
await Promise.all(events.map(e => e.callback.call(e.ctx, args)));
|
||||
} else {
|
||||
@ -1507,31 +1497,59 @@ _converse.api = {
|
||||
* to log the user in by calling the `prebind_url` or `credentials_url` depending
|
||||
* on whether prebinding is used or not.
|
||||
*
|
||||
* Otherwise the user will be shown a login form.
|
||||
*
|
||||
* @method _converse.api.user.login
|
||||
* @param {object} [credentials] An object with the credentials.
|
||||
* @param {string} [jid]
|
||||
* @param {string} [password]
|
||||
* @param {boolean} [reconnecting]
|
||||
* @example
|
||||
* converse.plugins.add('myplugin', {
|
||||
* initialize: function () {
|
||||
*
|
||||
* this._converse.api.user.login({
|
||||
* 'jid': 'dummy@example.com',
|
||||
* 'password': 'secret'
|
||||
* });
|
||||
*
|
||||
* this._converse.api.user.login('dummy@example.com', 'secret');
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
'login' (credentials) {
|
||||
_converse.logIn(credentials);
|
||||
login (jid, password, reconnecting) {
|
||||
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.
|
||||
*
|
||||
* @method _converse.api.user.logout
|
||||
* @example _converse.api.user.logout();
|
||||
*/
|
||||
'logout' () {
|
||||
_converse.logOut();
|
||||
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*.
|
||||
@ -1841,9 +1859,16 @@ _converse.api = {
|
||||
* });
|
||||
* _converse.api.send(msg);
|
||||
*/
|
||||
'send' (stanza) {
|
||||
_converse.connection.send(stanza);
|
||||
_converse.api.trigger('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.api.trigger('send', stanza);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1852,7 +1877,7 @@ _converse.api = {
|
||||
* @returns {Promise} A promise which resolves when we receive a `result` stanza
|
||||
* or is rejected when we receive an `error` stanza.
|
||||
*/
|
||||
'sendIQ' (stanza, timeout) {
|
||||
sendIQ (stanza, timeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
_converse.connection.sendIQ(stanza, resolve, reject, timeout || _converse.IQ_TIMEOUT);
|
||||
_converse.api.trigger('send', stanza);
|
||||
|
@ -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");
|
||||
}
|
||||
|
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,
|
||||
|
||||
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",
|
||||
|
Loading…
Reference in New Issue
Block a user