From 38499917a9e19832da8bc93d376d798750e081b6 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Fri, 11 May 2018 13:31:48 +0200 Subject: [PATCH] updates #194 Include entity capabilities hash in outgoing presences Also, started some work on using jsdoc for rendering API documentation. Ideally that would go into a separate commit but that would take ages to untangle. --- CHANGES.md | 1 + Makefile | 13 ++-- dev.html | 4 +- docs/source/developer_api.rst | 58 +++++++++++++++++ package-lock.json | 113 ++++++++++++++++++++++++++++++++++ package.json | 1 + spec/presence.js | 36 ++++++++++- spec/xmppstatus.js | 2 +- src/config.js | 1 + src/converse-caps.js | 63 +++++++++++++++++++ src/converse-chatboxes.js | 4 +- src/converse-chatview.js | 2 +- src/converse-core.js | 1 + src/converse-disco.js | 112 ++++++++++++++++++++++++--------- src/converse-mam.js | 2 +- src/converse-muc.js | 6 +- src/converse-ping.js | 2 +- src/converse-vcard.js | 2 +- src/converse.js | 1 + 19 files changed, 378 insertions(+), 46 deletions(-) create mode 100644 src/converse-caps.js diff --git a/CHANGES.md b/CHANGES.md index 5c9ea730a..f7949244c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ## New Features - #161 XEP-0363: HTTP File Upload +- #194 Include entity capabilities in outgoing presence stanzas - #337 API call to update a VCard - #1094 Show room members who aren't currently online - It's now also possible to edit your VCard via the UI diff --git a/Makefile b/Makefile index f25ee2099..b99af5ee4 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,7 @@ # You can set these variables from the command line. -UGLIFYJS ?= node_modules/.bin/uglifyjs BABEL ?= node_modules/.bin/babel -BOURBON = ./node_modules/bourbon/app/assets/stylesheets/ BOOTSTRAP = ./node_modules/ +BOURBON = ./node_modules/bourbon/app/assets/stylesheets/ BUILDDIR = ./docs BUNDLE ?= ./.bundle/bin/bundle CHROMIUM ?= ./node_modules/.bin/run-headless-chromium @@ -11,14 +10,16 @@ ESLINT ?= ./node_modules/.bin/eslint HTTPSERVE ?= ./node_modules/.bin/http-server HTTPSERVE_PORT ?= 8000 INKSCAPE ?= inkscape +JSDOC ?= ./node_modules/.bin/jsdoc +OXIPNG ?= oxipng PAPER = PO2JSON ?= ./node_modules/.bin/po2json RJS ?= ./node_modules/.bin/r.js SASS ?= ./.bundle/bin/sass -SPHINXBUILD ?= ./bin/sphinx-build SED ?= sed +SPHINXBUILD ?= ./bin/sphinx-build SPHINXOPTS = -OXIPNG ?= oxipng +UGLIFYJS ?= node_modules/.bin/uglifyjs # In the case user wishes to use RVM @@ -235,3 +236,7 @@ html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: apidoc +apidoc: + $(JSDOC) -d docs/html/api src/converse-disco.js diff --git a/dev.html b/dev.html index 49405a304..368a5915a 100644 --- a/dev.html +++ b/dev.html @@ -37,8 +37,8 @@ notify_all_room_messages: [ 'discuss@conference.conversejs.org' ], - // 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 + 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 }); diff --git a/docs/source/developer_api.rst b/docs/source/developer_api.rst index 8c1d9841f..69eb7f0c7 100644 --- a/docs/source/developer_api.rst +++ b/docs/source/developer_api.rst @@ -437,6 +437,64 @@ The **disco** grouping This grouping collects API functions related to `service discovery `_. +The **disco.own** grouping +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The **disco.own.features** grouping +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +add +*** + +Paramters: + +* (String) name + +get +*** + +Returns all of the identities registered for this client (i.e. instance of Converse.js). + +.. code-block:: javascript + + const identities = _converse.api.disco.own.identities.get(); + + +The **disco.own.identities** grouping +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +add +*** + +Paramters: + +* (String) category +* (String) type +* (String) name +* (String) lang + +Lets you add new identities for this client (i.e. instance of Converse.js). + +.. code-block:: javascript + + _converse.api.disco.own.identities.add('client', 'web', 'Converse.js'); + + +get +*** + +Returns all of the identities registered for this client (i.e. instance of Converse.js). + +.. code-block:: javascript + + const identities = _converse.api.disco.own.identities.get(); + +clear +***** + +Clears all previously set identities. + + getIdentity ~~~~~~~~~~~ diff --git a/package-lock.json b/package-lock.json index 92d48c063..17f7f81b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -907,6 +907,12 @@ "dev": true, "optional": true }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, "bootstrap": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.0.0.tgz", @@ -1052,6 +1058,15 @@ "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", "dev": true }, + "catharsis": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz", + "integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=", + "dev": true, + "requires": { + "underscore-contrib": "0.3.0" + } + }, "center-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", @@ -3562,6 +3577,43 @@ "esprima": "4.0.0" } }, + "js2xmlparser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz", + "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", + "dev": true, + "requires": { + "xmlcreate": "1.0.2" + } + }, + "jsdoc": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz", + "integrity": "sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==", + "dev": true, + "requires": { + "babylon": "7.0.0-beta.19", + "bluebird": "3.5.1", + "catharsis": "0.8.9", + "escape-string-regexp": "1.0.5", + "js2xmlparser": "3.0.0", + "klaw": "2.0.0", + "marked": "0.3.19", + "mkdirp": "0.5.1", + "requizzle": "0.2.1", + "strip-json-comments": "2.0.1", + "taffydb": "2.6.2", + "underscore": "1.8.3" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.19", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz", + "integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==", + "dev": true + } + } + }, "jsesc": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", @@ -3631,6 +3683,15 @@ "is-buffer": "1.1.6" } }, + "klaw": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz", + "integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, "latest-version": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-2.0.0.tgz", @@ -3768,6 +3829,12 @@ "yallist": "2.1.2" } }, + "marked": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "dev": true + }, "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", @@ -8604,6 +8671,23 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "requizzle": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", + "integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=", + "dev": true, + "requires": { + "underscore": "1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + } + } + }, "resolve": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", @@ -9402,6 +9486,12 @@ } } }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, "tempfile": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-1.1.1.tgz", @@ -9547,6 +9637,23 @@ "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", "dev": true }, + "underscore-contrib": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/underscore-contrib/-/underscore-contrib-0.3.0.tgz", + "integrity": "sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=", + "dev": true, + "requires": { + "underscore": "1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + } + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.3.tgz", @@ -9871,6 +9978,12 @@ "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", "dev": true }, + "xmlcreate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", + "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=", + "dev": true + }, "xss": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/xss/-/xss-0.3.7.tgz", diff --git a/package.json b/package.json index 270b938db..093f6be7a 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "jasmine-core": "2.6.4", "jed": "1.1.1", "jquery": "3.2.1", + "jsdoc": "^3.5.5", "jshint": "^2.9.4", "lodash": "4.17.4", "lodash-template-loader": "^2.0.0", diff --git a/spec/presence.js b/spec/presence.js index 9e7fd681c..352075f6f 100644 --- a/spec/presence.js +++ b/spec/presence.js @@ -18,12 +18,36 @@ describe("A sent presence stanza", function () { + it("includes a entity capabilities node", + mock.initConverseWithPromises( + null, ['rosterGroupsFetched'], {}, + function (done, _converse) { + + _converse.api.disco.own.identities.clear(); + _converse.api.disco.own.features.clear(); + + _converse.api.disco.own.identities.add("client", "pc", "Exodus 0.9.1"); + _converse.api.disco.own.features.add("http://jabber.org/protocol/caps"); + _converse.api.disco.own.features.add("http://jabber.org/protocol/disco#info"); + _converse.api.disco.own.features.add("http://jabber.org/protocol/disco#items"); + _converse.api.disco.own.features.add("http://jabber.org/protocol/muc"); + + const presence = _converse.xmppstatus.constructPresence(); + expect(presence.toLocaleString()).toBe( + ""+ + "0"+ + ""+ + "") + done(); + })); + it("has a given priority", mock.initConverse(function (_converse) { var pres = _converse.xmppstatus.constructPresence('online', 'Hello world'); expect(pres.toLocaleString()).toBe( ""+ "Hello world"+ "0"+ + ""+ "" ); _converse.priority = 2; @@ -33,6 +57,7 @@ "away"+ "Going jogging"+ "2"+ + ""+ "" ); @@ -43,6 +68,7 @@ "dnd"+ "Doing taxes"+ "0"+ + ""+ "" ); })); @@ -68,7 +94,11 @@ modal.el.querySelector('[type="submit"]').click(); expect(view.model.sendPresence).toHaveBeenCalled(); expect(_converse.connection.send.calls.mostRecent().args[0].toLocaleString()) - .toBe("My custom status0") + .toBe(""+ + "My custom status"+ + "0"+ + ""+ + "") return test_utils.waitUntil(function () { return modal.el.getAttribute('aria-hidden') === "true"; @@ -82,7 +112,9 @@ modal.el.querySelector('label[for="radio-busy"]').click(); // Change status to "dnd" modal.el.querySelector('[type="submit"]').click(); expect(_converse.connection.send.calls.mostRecent().args[0].toLocaleString()) - .toBe("dndMy custom status0") + .toBe("dndMy custom status0"+ + ""+ + "") done(); }); })); diff --git a/spec/xmppstatus.js b/spec/xmppstatus.js index 0c5014d86..43888d223 100644 --- a/spec/xmppstatus.js +++ b/spec/xmppstatus.js @@ -10,7 +10,7 @@ _converse.api.user.status.message.set("I'm also happy!"); expect(_converse.connection.send).toHaveBeenCalled(); var $stanza = $(_converse.connection.send.calls.argsFor(0)[0].tree()); - expect($stanza.children().length).toBe(2); + expect($stanza.children().length).toBe(3); expect($stanza.children('status').length).toBe(1); expect($stanza.children('status').text()).toBe("I'm also happy!"); expect($stanza.children('show').length).toBe(0); diff --git a/src/config.js b/src/config.js index b9da8d352..cb5995441 100644 --- a/src/config.js +++ b/src/config.js @@ -71,6 +71,7 @@ require.config({ "converse-bookmarks": "src/converse-bookmarks", "converse-chatboxes": "src/converse-chatboxes", + "converse-caps": "src/converse-caps", "converse-chatview": "src/converse-chatview", "converse-controlbox": "src/converse-controlbox", "converse-core": "src/converse-core", diff --git a/src/converse-caps.js b/src/converse-caps.js new file mode 100644 index 000000000..9bf5cd3d4 --- /dev/null +++ b/src/converse-caps.js @@ -0,0 +1,63 @@ +// Converse.js +// http://conversejs.org +// +// Copyright (c) 2013-2018, the Converse.js developers +// Licensed under the Mozilla Public License (MPLv2) + +(function (root, factory) { + define(["converse-core"], factory); +}(this, function (converse) { + + const { Strophe, $build, _, b64_sha1 } = converse.env; + + Strophe.addNamespace('CAPS', "http://jabber.org/protocol/caps"); + + function propertySort (array, property) { + return array.sort((a, b) => { return a[property] > b[property] ? -1 : 1 }); + } + + function generateVerificationString (_converse) { + const identities = _converse.api.disco.own.identities.get(), + features = _converse.api.disco.own.features.get(); + + if (identities.length > 1) { + propertySort(identities, "category"); + propertySort(identities, "type"); + propertySort(identities, "lang"); + } + + let S = _.reduce( + identities, + (result, id) => `${result}${id.category}/${id.type}/${_.get(id, 'lang', '')}/${id.name}<`, + ""); + + features.sort(); + S = _.reduce(features, (result, feature) => `${result}${feature}<`, S); + return b64_sha1(S); + } + + function createCapsNode (_converse) { + return $build("c", { + 'xmlns': Strophe.NS.CAPS, + 'hash': "sha-1", + 'node': "https://conversejs.org", + 'ver': generateVerificationString(_converse) + }).nodeTree; + } + + converse.plugins.add('converse-caps', { + + overrides: { + // Overrides mentioned here will be picked up by converse.js's + // plugin architecture they will replace existing methods on the + // relevant objects or classes. + XMPPStatus: { + constructPresence () { + const presence = this.__super__.constructPresence.apply(this, arguments); + presence.root().cnode(createCapsNode(this.__super__._converse)); + return presence; + } + } + } + }); +})); diff --git a/src/converse-chatboxes.js b/src/converse-chatboxes.js index 26ef35e27..ca8f00f3d 100644 --- a/src/converse-chatboxes.js +++ b/src/converse-chatboxes.js @@ -797,8 +797,8 @@ _converse.on('addClientFeatures', () => { - _converse.api.disco.addFeature(Strophe.NS.HTTPUPLOAD); - _converse.api.disco.addFeature(Strophe.NS.OUTOFBAND); + _converse.api.disco.own.features.add(Strophe.NS.HTTPUPLOAD); + _converse.api.disco.own.features.add(Strophe.NS.OUTOFBAND); }); _converse.api.listen.on('pluginsInitialized', () => { diff --git a/src/converse-chatview.js b/src/converse-chatview.js index 5795a5514..f262a9811 100644 --- a/src/converse-chatview.js +++ b/src/converse-chatview.js @@ -1147,7 +1147,7 @@ _converse.on('connected', () => { // Advertise that we support XEP-0382 Message Spoilers - _converse.api.disco.addFeature(Strophe.NS.SPOILER); + _converse.api.disco.own.features.add(Strophe.NS.SPOILER); }); /************************ BEGIN API ************************/ diff --git a/src/converse-core.js b/src/converse-core.js index e22a5d277..f6f4d8ecb 100644 --- a/src/converse-core.js +++ b/src/converse-core.js @@ -74,6 +74,7 @@ 'converse-bookmarks', 'converse-chatboxes', 'converse-chatview', + 'converse-caps', 'converse-controlbox', 'converse-core', 'converse-disco', diff --git a/src/converse-disco.js b/src/converse-disco.js index 352903712..67a35b816 100644 --- a/src/converse-disco.js +++ b/src/converse-disco.js @@ -1,9 +1,8 @@ -// Converse.js (A browser based XMPP chat client) +// Converse.js // http://conversejs.org // -// Copyright (c) 2012-2017, Jan-Carel Brand +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) -// /* This is a Converse.js plugin which add support for XEP-0030: Service Discovery */ @@ -205,14 +204,14 @@ function addClientFeatures () { // See http://xmpp.org/registrar/disco-categories.html - _converse.api.disco.addIdentity('client', 'web', 'Converse.js'); + _converse.api.disco.own.identities.add('client', 'web', 'Converse.js'); - _converse.api.disco.addFeature(Strophe.NS.BOSH); - _converse.api.disco.addFeature(Strophe.NS.CHATSTATES); - _converse.api.disco.addFeature(Strophe.NS.DISCO_INFO); - _converse.api.disco.addFeature(Strophe.NS.ROSTERX); // Limited support + _converse.api.disco.own.features.add(Strophe.NS.BOSH); + _converse.api.disco.own.features.add(Strophe.NS.CHATSTATES); + _converse.api.disco.own.features.add(Strophe.NS.DISCO_INFO); + _converse.api.disco.own.features.add(Strophe.NS.ROSTERX); // Limited support if (_converse.message_carbons) { - _converse.api.disco.addFeature(Strophe.NS.CARBONS); + _converse.api.disco.own.features.add(Strophe.NS.CARBONS); } _converse.emit('addClientFeatures'); return this; @@ -287,7 +286,83 @@ /* We extend the default converse.js API to add methods specific to service discovery */ _.extend(_converse.api, { + /** + * The service discovery API + * @namespace + */ 'disco': { + /** + * The "own" grouping + * @namespace + */ + 'own': { + /** + * The "identities" grouping + * @namespace + */ + 'identities': { + /** + * Clears all previously registered identities. + * @function + * + * @example + * _converse.api.disco.own.identities.clear(); + */ + clear () { + plugin._identities = [] + }, + /** + * Lets you add new identities for this client (i.e. instance of Converse.js) + * @function + * + * @param {String} category - server, client, gateway, directory, etc. + * @param {String} type - phone, pc, web, etc. + * @param {String} name - "Converse.js" + * @param {String} lang - en, el, de, etc. + * + * @example + * _converse.api.disco.own.identities.clear(); + */ + add (category, type, name, lang) { + for (var i=0; i { - _converse.api.disco.addFeature(Strophe.NS.MAM); + _converse.api.disco.own.features.add(Strophe.NS.MAM); }); _converse.on('afterMessagesFetched', (chatboxview) => { diff --git a/src/converse-muc.js b/src/converse-muc.js index 89c500012..d424a4d25 100644 --- a/src/converse-muc.js +++ b/src/converse-muc.js @@ -1,7 +1,7 @@ // Converse.js // http://conversejs.org // -// Copyright (c) 2012-2018, the Converse.js developers +// Copyright (c) 2013-2018, the Converse.js developers // Licensed under the Mozilla Public License (MPLv2) (function (root, factory) { @@ -1176,10 +1176,10 @@ /************************ BEGIN Event Handlers ************************/ _converse.on('addClientFeatures', () => { if (_converse.allow_muc) { - _converse.api.disco.addFeature(Strophe.NS.MUC); + _converse.api.disco.own.features.add(Strophe.NS.MUC); } if (_converse.allow_muc_invitations) { - _converse.api.disco.addFeature('jabber:x:conference'); // Invites + _converse.api.disco.own.features.add('jabber:x:conference'); // Invites } }); _converse.on('chatBoxesFetched', autoJoinRooms); diff --git a/src/converse-ping.js b/src/converse-ping.js index 92296e7ba..9f1fe930a 100644 --- a/src/converse-ping.js +++ b/src/converse-ping.js @@ -57,7 +57,7 @@ _converse.registerPongHandler = function () { if (!_.isUndefined(_converse.connection.disco)) { - _converse.api.disco.addFeature(Strophe.NS.PING); + _converse.api.disco.own.features.add(Strophe.NS.PING); } _converse.connection.ping.addPingHandler(_converse.pong); }; diff --git a/src/converse-vcard.js b/src/converse-vcard.js index dcac405dc..92a25b051 100644 --- a/src/converse-vcard.js +++ b/src/converse-vcard.js @@ -108,7 +108,7 @@ _converse.on('addClientFeatures', () => { - _converse.api.disco.addFeature(Strophe.NS.VCARD); + _converse.api.disco.own.features.add(Strophe.NS.VCARD); }); _.extend(_converse.api, { diff --git a/src/converse.js b/src/converse.js index 20c997798..a924027a4 100644 --- a/src/converse.js +++ b/src/converse.js @@ -8,6 +8,7 @@ if (typeof define !== 'undefined') { * Any of the following components may be removed if they're not needed. */ "converse-bookmarks", // XEP-0048 Bookmarks + "converse-caps", "converse-chatview", // Renders standalone chat boxes for single user chat "converse-controlbox", // The control box "converse-dragresize", // Allows chat boxes to be resized by dragging them