瀏覽代碼

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.
JC Brand 7 年之前
父節點
當前提交
38499917a9

+ 1 - 0
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

+ 9 - 4
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 

+ 2 - 2
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
         });

+ 58 - 0
docs/source/developer_api.rst

@@ -437,6 +437,64 @@ The **disco** grouping
 This grouping collects API functions related to `service discovery
 <https://xmpp.org/extensions/xep-0030.html>`_.
 
+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
 ~~~~~~~~~~~
 

+ 113 - 0
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",

+ 1 - 0
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",

+ 34 - 2
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(
+                    "<presence xmlns='jabber:client'>"+
+                        "<priority>0</priority>"+
+                        "<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='QgayPKawpkPSDYmwT/WM94uAlu0='/>"+
+                    "</presence>")
+                done();
+        }));
+
         it("has a given priority", mock.initConverse(function (_converse) {
             var pres = _converse.xmppstatus.constructPresence('online', 'Hello world');
             expect(pres.toLocaleString()).toBe(
                 "<presence xmlns='jabber:client'>"+
                     "<status>Hello world</status>"+
                     "<priority>0</priority>"+
+                    "<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='1J7kq1MEvnB6ea6vKcgCsSE37gw='/>"+
                 "</presence>"
             );
             _converse.priority = 2;
@@ -33,6 +57,7 @@
                     "<show>away</show>"+
                     "<status>Going jogging</status>"+
                     "<priority>2</priority>"+
+                    "<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='1J7kq1MEvnB6ea6vKcgCsSE37gw='/>"+
                 "</presence>"
             );
 
@@ -43,6 +68,7 @@
                     "<show>dnd</show>"+
                     "<status>Doing taxes</status>"+
                     "<priority>0</priority>"+
+                    "<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='1J7kq1MEvnB6ea6vKcgCsSE37gw='/>"+
                 "</presence>"
             );
         }));
@@ -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("<presence xmlns='jabber:client'><status>My custom status</status><priority>0</priority></presence>")
+                    .toBe("<presence xmlns='jabber:client'>"+
+                          "<status>My custom status</status>"+
+                          "<priority>0</priority>"+
+                          "<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='1J7kq1MEvnB6ea6vKcgCsSE37gw='/>"+
+                          "</presence>")
 
                 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("<presence xmlns='jabber:client'><show>dnd</show><status>My custom status</status><priority>0</priority></presence>")
+                    .toBe("<presence xmlns='jabber:client'><show>dnd</show><status>My custom status</status><priority>0</priority>"+
+                          "<c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='https://conversejs.org' ver='1J7kq1MEvnB6ea6vKcgCsSE37gw='/>"+
+                          "</presence>")
                 done();
             });
         }));

+ 1 - 1
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);

+ 1 - 0
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",

+ 63 - 0
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;
+                }
+            }
+        }
+    });
+}));

+ 2 - 2
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', () => {

+ 1 - 1
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 ************************/

+ 1 - 0
src/converse-core.js

@@ -74,6 +74,7 @@
         'converse-bookmarks',
         'converse-chatboxes',
         'converse-chatview',
+        'converse-caps',
         'converse-controlbox',
         'converse-core',
         'converse-disco',

+ 84 - 28
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 <jc@opkode.com>
+// 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<plugin._identities.length; i++) {
+                                    if (plugin._identities[i].category == category &&
+                                        plugin._identities[i].type == type &&
+                                        plugin._identities[i].name == name &&
+                                        plugin._identities[i].lang == lang) {
+                                        return false;
+                                    }
+                                }
+                                plugin._identities.push({category: category, type: type, name: name, lang: lang});
+                            },
+                            /**
+                             * Returns all of the identities registered for this client
+                             * (i.e. instance of Converse.js).
+                             * @function
+                             *
+                             * @example
+                             * const identities = _converse.api.disco.own.identities.get();
+                             */
+                            get () {
+                                return plugin._identities;
+                            }
+                        },
+
+                        'features': {
+                            add (name) {
+                                for (var i=0; i<plugin._features.length; i++) {
+                                    if (plugin._features[i] == name) { return false; }
+                                }
+                                plugin._features.push(name);
+                            },
+                            clear () {
+                                plugin._features = []
+                            },
+                            get () {
+                                return plugin._features;
+                            }
+                        }
+                    },
+
                     'info' (jid, node, callback, errback, timeout) {
                         const attrs = {xmlns: Strophe.NS.DISCO_INFO};
                         if (node) {
@@ -363,25 +438,6 @@
                         }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
                     },
 
-                    'addIdentity' (category, type, name, lang) {
-                        for (var i=0; i<plugin._identities.length; i++) {
-                            if (plugin._identities[i].category == category &&
-                                plugin._identities[i].type == type &&
-                                plugin._identities[i].name == name &&
-                                plugin._identities[i].lang == lang) {
-                                return false;
-                            }
-                        }
-                        plugin._identities.push({category: category, type: type, name: name, lang: lang});
-                    },
-
-                    'addFeature' (name) {
-                        for (var i=0; i<plugin._features.length; i++) {
-                            if (plugin._features[i] == name) { return false; }
-                        }
-                        plugin._features.push(name);
-                    },
-
                     'getIdentity' (category, type, entity_jid) {
                         /* Returns a Promise which resolves with a map indicating
                          * whether an identity with a given type is provided by

+ 1 - 1
src/converse-mam.js

@@ -384,7 +384,7 @@
             });
 
             _converse.on('addClientFeatures', () => {
-                _converse.api.disco.addFeature(Strophe.NS.MAM);
+                _converse.api.disco.own.features.add(Strophe.NS.MAM);
             });
 
             _converse.on('afterMessagesFetched', (chatboxview) => {

+ 3 - 3
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);

+ 1 - 1
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);
             };

+ 1 - 1
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, {

+ 1 - 0
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