Commit 4c3645c5 authored by JC Brand's avatar JC Brand

Merge branch 'master' into converse-omemo

parents e774e9d1 0a00c617
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
- Documentation includes utf-8 charset to make minfied versions compatible across platforms. #1017 - Documentation includes utf-8 charset to make minfied versions compatible across platforms. #1017
- #1026 Typing in MUC shows "Typing from another device" - #1026 Typing in MUC shows "Typing from another device"
- #1039 Multi-option data form elements not shown and saved correctly - #1039 Multi-option data form elements not shown and saved correctly
- #1143 Able to send blank message
### API changes ### API changes
...@@ -39,6 +40,9 @@ ...@@ -39,6 +40,9 @@
- New API method `_converse.api.vcard.update`. - New API method `_converse.api.vcard.update`.
- The `contactStatusChanged` event has been renamed to `contactPresenceChanged` - The `contactStatusChanged` event has been renamed to `contactPresenceChanged`
and a event `presenceChanged` is now also triggered on the contact. and a event `presenceChanged` is now also triggered on the contact.
- `_converse.api.chats.open` and `_converse.api.rooms.open` now returns a
`Presence` which resolves with the `Backbone.Model` representing the chat
object.
## UI changes ## UI changes
......
...@@ -227,6 +227,7 @@ check: eslint ...@@ -227,6 +227,7 @@ check: eslint
html: html:
rm -rf $(BUILDDIR)/html rm -rf $(BUILDDIR)/html
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
make apidoc
@echo @echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
......
This diff is collapsed.
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Converse.js documentation build configuration file, created by # Converse documentation build configuration file, created by
# sphinx-quickstart on Fri Apr 26 20:48:03 2013. # sphinx-quickstart on Fri Apr 26 20:48:03 2013.
# #
# This file is execfile()d with the current directory set to its containing dir. # This file is execfile()d with the current directory set to its containing dir.
...@@ -40,8 +40,8 @@ source_suffix = '.rst' ...@@ -40,8 +40,8 @@ source_suffix = '.rst'
master_doc = 'index' master_doc = 'index'
# General information about the project. # General information about the project.
project = u'Converse.js' project = u'Converse'
copyright = u'2017, JC Brand' copyright = u'2018, JC Brand'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
...@@ -108,7 +108,7 @@ html_logo = "_static/conversejs_small.png" ...@@ -108,7 +108,7 @@ html_logo = "_static/conversejs_small.png"
# theme further. # theme further.
html_theme_options = { html_theme_options = {
# Navigation bar title. (Default: ``project`` value) # Navigation bar title. (Default: ``project`` value)
'navbar_title': "Converse.js", 'navbar_title': "Converse",
# Tab name for entire site. (Default: "Site") # Tab name for entire site. (Default: "Site")
'navbar_site_name': "Table of Contents", 'navbar_site_name': "Table of Contents",
# A list of tuples containing pages or urls to link to. # A list of tuples containing pages or urls to link to.
...@@ -229,7 +229,7 @@ latex_elements = { ...@@ -229,7 +229,7 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'Conversejs.tex', u'Converse.js Documentation', ('index', 'Conversejs.tex', u'Converse Documentation',
u'JC Brand', 'manual'), u'JC Brand', 'manual'),
] ]
...@@ -259,7 +259,7 @@ latex_documents = [ ...@@ -259,7 +259,7 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('index', 'conversejs', u'Converse.js Documentation', ('index', 'conversejs', u'Converse Documentation',
[u'JC Brand'], 1) [u'JC Brand'], 1)
] ]
...@@ -273,8 +273,8 @@ man_pages = [ ...@@ -273,8 +273,8 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'Conversejs', u'Converse.js Documentation', ('index', 'Conversejs', u'Converse Documentation',
u'JC Brand', 'Conversejs', 'Open Source XMPP webchat', u'JC Brand', 'Converse', 'Open Source XMPP webchat',
'Miscellaneous'), 'Miscellaneous'),
] ]
......
This diff is collapsed.
...@@ -8,13 +8,13 @@ ...@@ -8,13 +8,13 @@
Development Development
=========== ===========
Welcome to the developer documentation of converse.js. Read the documentation Welcome to the developer documentation of Converse. Read the documentation
linked to below, if you want to add new features or create your own customized linked to below, if you want to add new features or create your own customized
version of converse.js. version of Converse.
Converse.js itself composed of plugins, and exposes an API with which you can Converse itself composed of plugins, and exposes an API with which you can
create and register your own plugins. This is the recommended way to customize create and register your own plugins. This is the recommended way to customize
or add new functionality to converse.js. or add new functionality to Converse.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
...@@ -22,6 +22,7 @@ or add new functionality to converse.js. ...@@ -22,6 +22,7 @@ or add new functionality to converse.js.
developer_guidelines developer_guidelines
style_guide style_guide
plugin_development plugin_development
api/index
developer_api developer_api
events events
other_frameworks other_frameworks
......
...@@ -8,19 +8,19 @@ ...@@ -8,19 +8,19 @@
Setup and integration Setup and integration
===================== =====================
This page documents what you'll need to do to be able to connect Converse.js with This page documents what you'll need to do to be able to connect Converse with
your own XMPP server and to better integrate it into your website. your own XMPP server and to better integrate it into your website.
At the very least you'll need Converse.js and an :ref:`XMPP server` with At the very least you'll need Converse and an :ref:`XMPP server` with
:ref:`websocket-section` or :ref:`BOSH-section` enabled. That's definitely :ref:`websocket-section` or :ref:`BOSH-section` enabled. That's definitely
enough to simply demo Converse.js or to do development work on it. enough to simply demo Converse or to do development work on it.
However, if you want to more fully integrate it into a website or intranet, However, if you want to more fully integrate it into a website
then you'll likely need to set up more services and components. then you'll likely need to set up more services and components.
The diagram below shows a fairly common setup for a website or intranet: The diagram below shows a fairly common setup for a website or intranet:
* Converse.js runs in the web-browser on the user's device. * Converse runs in the web-browser on the user's device.
* It communicates with the XMPP server via BOSH or websocket which is usually * It communicates with the XMPP server via BOSH or websocket which is usually
reverse-proxied by a web-server in order to overcome cross-site scripting reverse-proxied by a web-server in order to overcome cross-site scripting
...@@ -34,13 +34,12 @@ The diagram below shows a fairly common setup for a website or intranet: ...@@ -34,13 +34,12 @@ The diagram below shows a fairly common setup for a website or intranet:
the XMPP server is configured to use, so that users can log in with those the XMPP server is configured to use, so that users can log in with those
credentials. credentials.
* Usually (but optionally) there is a backend web application which hosts a * Usually (but optionally) there is a backend web application which hosts a
website in which Converse.js appears. website in which Converse appears.
.. figure:: images/diagram.png .. figure:: images/diagram.png
:align: center :align: center
:alt: A diagram of a possible setup, consisting of Converse.js, a web server, a backend web application, an XMPP server, a user directory such as LDAP and an XMPP server. :alt: A diagram of a possible setup, consisting of Converse, a web server, a backend web application, an XMPP server, a user directory such as LDAP and an XMPP server.
This diagram shows the various services in a fairly common setup (image generated with `draw.io <https://draw.io>`_). This diagram shows the various services in a fairly common setup (image generated with `draw.io <https://draw.io>`_).
...@@ -53,11 +52,11 @@ The various components ...@@ -53,11 +52,11 @@ The various components
An XMPP server An XMPP server
============== ==============
*Converse.js* implements `XMPP <http://xmpp.org/about-xmpp/>`_ as its *Converse* uses `XMPP <http://xmpp.org/about-xmpp/>`_ as its
messaging protocol, and therefore needs to connect to an XMPP/Jabber messaging protocol, and therefore needs to connect to an XMPP/Jabber
server (Jabber® is an older and more user-friendly synonym for XMPP). server (Jabber® is an older and more user-friendly synonym for XMPP).
You can connect to public XMPP servers like ``jabber.org`` but if you want to You can connect to public XMPP servers like ``conversejs.org`` but if you want to
have :ref:`session support <session-support>` you'll have to set up your own XMPP server. have :ref:`session support <session-support>` you'll have to set up your own XMPP server.
You can find a list of public XMPP servers/providers on `xmpp.net <https://list.jabber.at>`_ You can find a list of public XMPP servers/providers on `xmpp.net <https://list.jabber.at>`_
...@@ -79,7 +78,7 @@ stanzas to be sent over an HTTP connection. ...@@ -79,7 +78,7 @@ stanzas to be sent over an HTTP connection.
HTTP connections are stateless and usually shortlived. HTTP connections are stateless and usually shortlived.
XMPP connections on the other hand are stateful and usually last much longer. XMPP connections on the other hand are stateful and usually last much longer.
So to enable a web application like *Converse.js* to communicate with an XMPP So to enable a web application like *Converse* to communicate with an XMPP
server, we need a proxy which acts as a bridge between these two protocols. server, we need a proxy which acts as a bridge between these two protocols.
This is the job of a BOSH connection manager. BOSH (Bidirectional-streams Over This is the job of a BOSH connection manager. BOSH (Bidirectional-streams Over
...@@ -87,7 +86,7 @@ Synchronous HTTP) is a protocol for allowing XMPP communication over HTTP. The ...@@ -87,7 +86,7 @@ Synchronous HTTP) is a protocol for allowing XMPP communication over HTTP. The
protocol is defined in `XEP-0206: XMPP Over BOSH <http://xmpp.org/extensions/xep-0206.html>`_. protocol is defined in `XEP-0206: XMPP Over BOSH <http://xmpp.org/extensions/xep-0206.html>`_.
Popular XMPP servers such as `Ejabberd <http://www.ejabberd.im>`_, Popular XMPP servers such as `Ejabberd <http://www.ejabberd.im>`_,
prosody `(mod_bosh) <http://prosody.im/doc/setting_up_bosh>`_ and Prosody `(mod_bosh) <http://prosody.im/doc/setting_up_bosh>`_ and
`OpenFire <http://www.igniterealtime.org/projects/openfire/>`_ all include `OpenFire <http://www.igniterealtime.org/projects/openfire/>`_ all include
their own BOSH connection managers (but you usually have to enable them in the their own BOSH connection managers (but you usually have to enable them in the
configuration). configuration).
...@@ -98,14 +97,15 @@ https://conversejs.org does), then you'll need a standalone connection manager. ...@@ -98,14 +97,15 @@ https://conversejs.org does), then you'll need a standalone connection manager.
For a standalone manager, see for example `Punjab <https://github.com/twonds/punjab>`_ For a standalone manager, see for example `Punjab <https://github.com/twonds/punjab>`_
and `node-xmpp-bosh <https://github.com/dhruvbird/node-xmpp-bosh>`_. and `node-xmpp-bosh <https://github.com/dhruvbird/node-xmpp-bosh>`_.
The demo on the `Converse.js homepage <http://conversejs.org>`_ uses a connection The demo on the `Converse homepage <http://conversejs.org>`_ uses a connection
manager located at https://conversejs.org/http-bind. manager located at https://conversejs.org/http-bind.
This connection manager is available for testing purposes only, please don't This connection manager is available for testing purposes only, please don't
use it in production. 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.js to connect to a BOSH URL. how to configure Converse to connect to a BOSH URL.
.. _`websocket-section`: .. _`websocket-section`:
...@@ -122,7 +122,7 @@ HTTP. Therefore BOSH, which operates over HTTP, doesn't apply to websockets. ...@@ -122,7 +122,7 @@ HTTP. Therefore BOSH, which operates over HTTP, doesn't apply to websockets.
does the node-xmpp-bosh connection manager. does the node-xmpp-bosh connection manager.
Refer to the :ref:`websocket-url` configuration setting for information on how to Refer to the :ref:`websocket-url` configuration setting for information on how to
configure Converse.js to connect to a websocket URL. configure Converse to connect to a websocket URL.
The Webserver The Webserver
============= =============
...@@ -133,7 +133,7 @@ Overcoming cross-domain request restrictions ...@@ -133,7 +133,7 @@ Overcoming cross-domain request restrictions
Lets say your domain is *example.org*, but the domain of your connection Lets say your domain is *example.org*, but the domain of your connection
manager is *example.com*. manager is *example.com*.
HTTP requests are made by *Converse.js* to the BOSH connection manager via HTTP requests are made by *Converse* to the BOSH connection manager via
XmlHttpRequests (XHR). Until recently, it was not possible to make such XmlHttpRequests (XHR). Until recently, it was not possible to make such
requests to a different domain than the one currently being served requests to a different domain than the one currently being served
(to prevent XSS attacks). (to prevent XSS attacks).
...@@ -159,7 +159,7 @@ Examples: ...@@ -159,7 +159,7 @@ Examples:
Assuming your site is accessible on port ``80`` for the domain ``mysite.com`` Assuming your site is accessible on port ``80`` for the domain ``mysite.com``
and your connection manager manager is running at ``someothersite.com/http-bind``. and your connection manager manager is running at ``someothersite.com/http-bind``.
The *bosh_service_url* value you want to give Converse.js to overcome The *bosh_service_url* value you want to give Converse to overcome
the cross-domain restriction is ``mysite.com/http-bind`` and not the cross-domain restriction is ``mysite.com/http-bind`` and not
``someothersite.com/http-bind``. ``someothersite.com/http-bind``.
...@@ -192,6 +192,32 @@ Apache ...@@ -192,6 +192,32 @@ Apache
</VirtualHost> </VirtualHost>
.. note::
If you're getting XML parsing errors for your BOSH endpoint, for
example::
XML Parsing Error: mismatched tag. Expected: </hr>.
Location: https://example.org/http-bind/
Line Number 6, Column 3: bosh-anon:6:3
Also ERROR: request id 12.2 error 504 happened
Then your BOSH proxy is returning an HTML error page (for a 504 error in
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,
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.
To fix this, make sure that the webserver's timeout is slightly higher.
In Nginx you can do this by adding ``proxy_read_timeout 61;``;
From Converse 4.0.0 onwards the default ``wait`` time is set to 59 seconds, to avoid
this problem.
.. _`session-support`: .. _`session-support`:
Single Session Support Single Session Support
...@@ -215,7 +241,7 @@ authenticated BOSH session with the XMPP server or a standalone `BOSH <http://xm ...@@ -215,7 +241,7 @@ authenticated BOSH session with the XMPP server or a standalone `BOSH <http://xm
connection manager. connection manager.
Once authenticated, it receives RID and SID tokens which need to be passed Once authenticated, it receives RID and SID tokens which need to be passed
on to converse.js upon pa. Converse.js will then attach to that same session using on to converse.js upon pa. Converse will then attach to that same session using
those tokens. those tokens.
It's called "prebind" because you bind to the BOSH session beforehand, and then It's called "prebind" because you bind to the BOSH session beforehand, and then
...@@ -250,7 +276,7 @@ converse.js can then attach to. ...@@ -250,7 +276,7 @@ converse.js can then attach to.
A BOSH server acts as a bridge between HTTP, the protocol of the web, and A BOSH server acts as a bridge between HTTP, the protocol of the web, and
XMPP, the instant messaging protocol. XMPP, the instant messaging protocol.
Converse.js can only communicate via HTTP (or websocket, in which case BOSH can't be used). Converse can only communicate via HTTP (or websocket, in which case BOSH can't be used).
It cannot open TCP sockets to communicate to an XMPP server directly. It cannot open TCP sockets to communicate to an XMPP server directly.
So the BOSH server acts as a middle man, translating our HTTP requests into XMPP stanzas and vice versa. So the BOSH server acts as a middle man, translating our HTTP requests into XMPP stanzas and vice versa.
...@@ -303,7 +329,7 @@ authentication to external services. They are listed in the `Prosody community m ...@@ -303,7 +329,7 @@ authentication to external services. They are listed in the `Prosody community m
page <https://modules.prosody.im/>`_. Other XMPP servers have similar plugin modules. page <https://modules.prosody.im/>`_. Other XMPP servers have similar plugin modules.
If your web-application has access to the same credentials, it can send those If your web-application has access to the same credentials, it can send those
credentials to Converse.js so that user's are automatically logged in when the credentials to Converse so that user's are automatically logged in when the
page loads. 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
...@@ -316,7 +342,7 @@ The first option has the drawback that your web-application needs to know the ...@@ -316,7 +342,7 @@ The first option has the drawback that your web-application needs to know the
XMPP credentials of your users and that they need to be stored in the clear. XMPP credentials of your users and that they need to be stored in the clear.
The second option has that same drawback and it also needs to pass those The second option has that same drawback and it also needs to pass those
credentials to Converse.js. credentials to Converse.
To avoid these drawbacks, you can instead let your backend web application To avoid these drawbacks, you can instead let your backend web application
generate temporary authentication tokens which are then sent to the XMPP server generate temporary authentication tokens which are then sent to the XMPP server
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -2410,9 +2410,9 @@ ...@@ -2410,9 +2410,9 @@
"dev": true "dev": true
}, },
"bootstrap.native": { "bootstrap.native": {
"version": "2.0.22", "version": "2.0.23",
"resolved": "https://registry.npmjs.org/bootstrap.native/-/bootstrap.native-2.0.22.tgz", "resolved": "https://registry.npmjs.org/bootstrap.native/-/bootstrap.native-2.0.23.tgz",
"integrity": "sha512-eypi4y1eKJoRt8cTwkZCI3QQ7W04rbv4VU1nBjBshqNXkONI7jO6tG3qZTwq9Zd+gDoeaQASyk6V185y+Y7KHQ==", "integrity": "sha512-bcbVgqIjRkyiHd6DN8Y18BYjJTKvvnN3Msb7Yh6K5vGGsjRT3gV0IFKR3rcEYgJ5Kvg1egL9exIfN/Hvwn4wNA==",
"dev": true "dev": true
}, },
"bourbon": { "bourbon": {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -66,47 +66,50 @@ ...@@ -66,47 +66,50 @@
it("shows the number of unread mentions received", it("shows the number of unread mentions received",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { function (done, _converse) {
test_utils.createContacts(_converse, 'all').openControlBox(); test_utils.createContacts(_converse, 'all').openControlBox();
_converse.emit('rosterContactsFetched');
var contacts_panel = _converse.chatboxviews.get('controlbox').contactspanel; const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, sender_jid); test_utils.openChatBoxFor(_converse, sender_jid);
var chatview = _converse.chatboxviews.get(sender_jid); return test_utils.waitUntil(() => _converse.chatboxes.length).then(() => {
chatview.model.set({'minimized': true});
const chatview = _converse.chatboxviews.get(sender_jid);
expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy(); chatview.model.set({'minimized': true});
expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy();
expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy();
var msg = $msg({ expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy();
from: sender_jid,
to: _converse.connection.jid, var msg = $msg({
type: 'chat', from: sender_jid,
id: (new Date()).getTime() to: _converse.connection.jid,
}).c('body').t('hello').up() type: 'chat',
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); id: (new Date()).getTime()
_converse.chatboxes.onMessage(msg); }).c('body').t('hello').up()
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('1'); .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1'); _converse.chatboxes.onMessage(msg);
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('1');
msg = $msg({ expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('1');
from: sender_jid,
to: _converse.connection.jid, msg = $msg({
type: 'chat', from: sender_jid,
id: (new Date()).getTime() to: _converse.connection.jid,
}).c('body').t('hello again').up() type: 'chat',
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree(); id: (new Date()).getTime()
_converse.chatboxes.onMessage(msg); }).c('body').t('hello again').up()
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('2'); .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('2'); _converse.chatboxes.onMessage(msg);
expect(_converse.chatboxviews.el.querySelector('.restore-chat .message-count').textContent).toBe('2');
chatview.model.set({'minimized': false}); expect(_converse.rosterview.el.querySelector('.msgs-indicator').textContent).toBe('2');
expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy();
expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy(); chatview.model.set({'minimized': false});
done(); expect(_.isNull(_converse.chatboxviews.el.querySelector('.restore-chat .message-count'))).toBeTruthy();
expect(_.isNull(_converse.rosterview.el.querySelector('.msgs-indicator'))).toBeTruthy();
done();
});
})); }));
}); });
......
(function (root, factory) { (function (root, factory) {
define([ define([
"jquery",
"jasmine", "jasmine",
"mock", "mock",
"test-utils"], factory); "test-utils"], factory);
} (this, function ($, jasmine, mock, test_utils) { } (this, function (jasmine, mock, test_utils) {
var b64_sha1 = converse.env.b64_sha1; const b64_sha1 = converse.env.b64_sha1,
var _ = converse.env._; _ = converse.env._,
u = converse.env.utils;
describe("Converse", function() { describe("Converse", function() {
...@@ -274,59 +274,72 @@ ...@@ -274,59 +274,72 @@
describe("The \"chats\" API", function() { describe("The \"chats\" API", function() {
it("has a method 'get' which returns the chatbox model", mock.initConverseWithPromises( it("has a method 'get' which returns the promise that resolves to a chat model", mock.initConverseWithPromises(
null, ['rosterInitialized'], {}, function (done, _converse) { null, ['rosterInitialized', 'chatBoxesInitialized'], {}, function (done, _converse) {
test_utils.openControlBox(); test_utils.openControlBox();
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current', 2);
_converse.emit('rosterContactsFetched');
// Test on chat that doesn't exist. // Test on chat that doesn't exist.
expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy(); expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy();
var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
// Test on chat that's not open // Test on chat that's not open
var box = _converse.api.chats.get(jid); let box = _converse.api.chats.get(jid);
expect(typeof box === 'undefined').toBeTruthy(); expect(typeof box === 'undefined').toBeTruthy();
var chatboxview = _converse.chatboxviews.get(jid); expect(_converse.chatboxes.length).toBe(1);
// Test for single JID
// Test for one JID
test_utils.openChatBoxFor(_converse, jid); test_utils.openChatBoxFor(_converse, jid);
box = _converse.api.chats.get(jid); test_utils.waitUntil(() => _converse.chatboxes.length == 1).then(() => {
box = _converse.api.chats.get(jid);
expect(box instanceof Object).toBeTruthy();
expect(box.get('box_id')).toBe(b64_sha1(jid));
const chatboxview = _converse.chatboxviews.get(jid);
expect(u.isVisible(chatboxview.el)).toBeTruthy();
// Test for multiple JIDs
test_utils.openChatBoxFor(_converse, jid2);
return test_utils.waitUntil(() => _converse.chatboxes.length == 2);
}).then(() => {
const list = _converse.api.chats.get([jid, jid2]);
expect(_.isArray(list)).toBeTruthy();
expect(list[0].get('box_id')).toBe(b64_sha1(jid));
expect(list[1].get('box_id')).toBe(b64_sha1(jid2));
done();
}).catch(_.partial(console.error, _));
}));
it("has a method 'open' which opens and returns a promise that resolves to a chat model", mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesInitialized'], {}, function (done, _converse) {
test_utils.openControlBox();
test_utils.createContacts(_converse, 'current', 2);
_converse.emit('rosterContactsFetched');
const jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
const jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
// Test on chat that doesn't exist.
expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy();
return _converse.api.chats.open(jid).then((box) => {
expect(box instanceof Object).toBeTruthy(); expect(box instanceof Object).toBeTruthy();
expect(box.get('box_id')).toBe(b64_sha1(jid)); expect(box.get('box_id')).toBe(b64_sha1(jid));
chatboxview = _converse.chatboxviews.get(jid); expect(
expect($(chatboxview.el).is(':visible')).toBeTruthy(); _.keys(box),
['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
);
const chatboxview = _converse.chatboxviews.get(jid);
expect(u.isVisible(chatboxview.el)).toBeTruthy();
// Test for multiple JIDs // Test for multiple JIDs
var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost'; return _converse.api.chats.open([jid, jid2]);
test_utils.openChatBoxFor(_converse, jid2); }).then((list) => {
var list = _converse.api.chats.get([jid, jid2]);
expect(_.isArray(list)).toBeTruthy(); expect(_.isArray(list)).toBeTruthy();
expect(list[0].get('box_id')).toBe(b64_sha1(jid)); expect(list[0].get('box_id')).toBe(b64_sha1(jid));
expect(list[1].get('box_id')).toBe(b64_sha1(jid2)); expect(list[1].get('box_id')).toBe(b64_sha1(jid2));
done(); done();
})); });
it("has a method 'open' which opens and returns the chatbox model", mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, function (done, _converse) {
test_utils.openControlBox();
test_utils.createContacts(_converse, 'current');
var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
var chatboxview;
// Test on chat that doesn't exist.
expect(_converse.api.chats.get('non-existing@jabber.org')).toBeFalsy();
var box = _converse.api.chats.open(jid);
expect(box instanceof Object).toBeTruthy();
expect(box.get('box_id')).toBe(b64_sha1(jid));
expect(
_.keys(box),
['close', 'focus', 'get', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
);
chatboxview = _converse.chatboxviews.get(jid);
expect($(chatboxview.el).is(':visible')).toBeTruthy();
// Test for multiple JIDs
var jid2 = mock.cur_names[1].replace(/ /g,'.').toLowerCase() + '@localhost';
var list = _converse.api.chats.open([jid, jid2]);
expect(_.isArray(list)).toBeTruthy();
expect(list[0].get('box_id')).toBe(b64_sha1(jid));
expect(list[1].get('box_id')).toBe(b64_sha1(jid2));
done();
})); }));
}); });
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -92,12 +92,14 @@ ...@@ -92,12 +92,14 @@
it("can be sent without a hint", it("can be sent without a hint",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { function (done, _converse) {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched');
test_utils.openControlBox(); test_utils.openControlBox();
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
// XXX: We need to send a presence from the contact, so that we // XXX: We need to send a presence from the contact, so that we
// have a resource, that resource is then queried to see // have a resource, that resource is then queried to see
...@@ -108,9 +110,9 @@ ...@@ -108,9 +110,9 @@
'to': 'dummy@localhost' 'to': 'dummy@localhost'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
test_utils.openChatBoxFor(_converse, contact_jid); test_utils.openChatBoxFor(_converse, contact_jid)
.then(() => test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]))
test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () { .then(() => {
var view = _converse.chatboxviews.get(contact_jid); var view = _converse.chatboxviews.get(contact_jid);
spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view, 'onMessageSubmitted').and.callThrough();
spyOn(_converse.connection, 'send'); spyOn(_converse.connection, 'send');
...@@ -167,10 +169,12 @@ ...@@ -167,10 +169,12 @@
it("can be sent with a hint", it("can be sent with a hint",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { function (done, _converse) {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched');
test_utils.openControlBox(); test_utils.openControlBox();
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
...@@ -183,9 +187,9 @@ ...@@ -183,9 +187,9 @@
'to': 'dummy@localhost' 'to': 'dummy@localhost'
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
test_utils.openChatBoxFor(_converse, contact_jid); test_utils.openChatBoxFor(_converse, contact_jid)
.then(() => test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]))
test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () { .then(() => {
var view = _converse.chatboxviews.get(contact_jid); var view = _converse.chatboxviews.get(contact_jid);
var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click(); spoiler_toggle.click();
...@@ -206,17 +210,17 @@ ...@@ -206,17 +210,17 @@
expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.onMessageSubmitted).toHaveBeenCalled();
/* Test the XML stanza /* Test the XML stanza
* *
* <message from="dummy@localhost/resource" * <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost" * to="max.frankfurter@localhost"
* type="chat" * type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e" * id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client"> * xmlns="jabber:client">
* <body>This is the spoiler</body> * <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/> * <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler> * <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
* </message>" * </message>"
*/ */
var stanza = _converse.connection.send.calls.argsFor(0)[0].tree(); var stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]'); var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
......
This diff is collapsed.
This diff is collapsed.
...@@ -888,6 +888,9 @@ ...@@ -888,6 +888,9 @@
const textarea = this.el.querySelector('.chat-textarea'), const textarea = this.el.querySelector('.chat-textarea'),
message = textarea.value; message = textarea.value;
if (!message.replace(/\s/g, '').length) {
return;
}
let spoiler_hint; let spoiler_hint;
if (this.model.get('composing_spoiler')) { if (this.model.get('composing_spoiler')) {
const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint'); const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint');
...@@ -901,10 +904,8 @@ ...@@ -901,10 +904,8 @@
event.initEvent('input', true, true); event.initEvent('input', true, true);
textarea.dispatchEvent(event); textarea.dispatchEvent(event);
if (message !== '') { this.onMessageSubmitted(message, spoiler_hint);
this.onMessageSubmitted(message, spoiler_hint); _converse.emit('messageSend', message);
_converse.emit('messageSend', message);
}
this.setChatState(_converse.ACTIVE); this.setChatState(_converse.ACTIVE);
}, },
......
This diff is collapsed.
...@@ -296,6 +296,7 @@ ...@@ -296,6 +296,7 @@
if (from !== null) { if (from !== null) {
iqresult.attrs({'to': from}); iqresult.attrs({'to': from});
} }
iqresult.c('query', attrs);
_.each(plugin._identities, (identity) => { _.each(plugin._identities, (identity) => {
const attrs = { const attrs = {
'category': identity.category, 'category': identity.category,
......
...@@ -30,11 +30,10 @@ ...@@ -30,11 +30,10 @@
if (!_.isArray(_converse.auto_join_rooms) && !_.isArray(_converse.auto_join_private_chats)) { if (!_.isArray(_converse.auto_join_rooms) && !_.isArray(_converse.auto_join_private_chats)) {
throw new Error("converse-embedded: auto_join_rooms must be an Array"); throw new Error("converse-embedded: auto_join_rooms must be an Array");
} }
if (_converse.auto_join_rooms.length !== 1 && _converse.auto_join_private_chats.length !== 1) { if (_converse.auto_join_rooms.length > 1 && _converse.auto_join_private_chats.length > 1) {
throw new Error("converse-embedded: It doesn't make "+ throw new Error("converse-embedded: It doesn't make "+
"sense to have the auto_join_rooms setting to zero or "+ "sense to have the auto_join_rooms setting more then one, "+
"more then one, since only one chat room can be open "+ "since only one chat room can be open at any time.");
"at any time.");
} }
} }
}); });
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment