瀏覽代碼

Allow plugins to have optional dependencies.

This change refactors out the plugin code from converse-core into
src/converse-puggable.js

Additionally, plugins now have an optional attribute `optional_dependencies`
which is an array of dependencies which are "nice-to-have" but not essential.

Work has also been done to ensure that a plugins' dependencies are first loaded
before the plugin itself.
JC Brand 9 年之前
父節點
當前提交
62c170273e

+ 2 - 1
converse.js

@@ -56,6 +56,7 @@ require.config({
         "converse-notification":    "src/converse-notification",
         "converse-notification":    "src/converse-notification",
         "converse-otr":             "src/converse-otr",
         "converse-otr":             "src/converse-otr",
         "converse-ping":            "src/converse-ping",
         "converse-ping":            "src/converse-ping",
+        "converse-pluggable":       "src/converse-pluggable",
         "converse-register":        "src/converse-register",
         "converse-register":        "src/converse-register",
         "converse-rosterview":      "src/converse-rosterview",
         "converse-rosterview":      "src/converse-rosterview",
         "converse-templates":       "src/converse-templates",
         "converse-templates":       "src/converse-templates",
@@ -235,11 +236,11 @@ if (typeof define !== 'undefined') {
                                 // translations that you care about.
                                 // translations that you care about.
 
 
         "converse-chatview",    // Renders standalone chat boxes for single user chat
         "converse-chatview",    // Renders standalone chat boxes for single user chat
+        "converse-controlbox",  // The control box
         "converse-mam",         // XEP-0313 Message Archive Management
         "converse-mam",         // XEP-0313 Message Archive Management
         "converse-muc",         // XEP-0045 Multi-user chat
         "converse-muc",         // XEP-0045 Multi-user chat
         "converse-vcard",       // XEP-0054 VCard-temp
         "converse-vcard",       // XEP-0054 VCard-temp
         "converse-otr",         // Off-the-record encryption for one-on-one messages
         "converse-otr",         // Off-the-record encryption for one-on-one messages
-        "converse-controlbox",  // The control box
         "converse-register",    // XEP-0077 In-band registration
         "converse-register",    // XEP-0077 In-band registration
         "converse-ping",        // XEP-0199 XMPP Ping
         "converse-ping",        // XEP-0199 XMPP Ping
         "converse-notification",// HTML5 Notifications
         "converse-notification",// HTML5 Notifications

+ 1 - 0
dev.html

@@ -68,6 +68,7 @@
             play_sounds: true,
             play_sounds: true,
             roster_groups: true,
             roster_groups: true,
             show_controlbox_by_default: true,
             show_controlbox_by_default: true,
+            strict_plugin_dependencies: false,
             chatstate_notification_blacklist: ['mulles@movim.eu'],
             chatstate_notification_blacklist: ['mulles@movim.eu'],
             xhr_user_search: false,
             xhr_user_search: false,
             debug: true
             debug: true

+ 7 - 5
spec/chatbox.js

@@ -406,11 +406,13 @@
             describe("A Chat Message", function () {
             describe("A Chat Message", function () {
 
 
                 beforeEach(function () {
                 beforeEach(function () {
-                    runs(function () {
-                        test_utils.closeAllChatBoxes();
-                    });
-                    waits(250);
-                    runs(function () {});
+                    test_utils.closeAllChatBoxes();
+                    test_utils.removeControlBox();
+                    converse.roster.browserStorage._clear();
+                    test_utils.initConverse();
+                    test_utils.createContacts('current');
+                    test_utils.openControlBox();
+                    test_utils.openContactsPanel();
                 });
                 });
 
 
                 describe("when received from someone else", function () {
                 describe("when received from someone else", function () {

+ 1 - 1
spec/converse.js

@@ -20,7 +20,7 @@
                 var connection = converse.connection;
                 var connection = converse.connection;
                 delete converse.bosh_service_url;
                 delete converse.bosh_service_url;
                 delete converse.connection;
                 delete converse.connection;
-                expect(converse.initConnection.bind({})).toThrow(
+                expect(converse.initConnection.bind(converse)).toThrow(
                     new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both."));
                     new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both."));
                 converse.bosh_service_url = url;
                 converse.bosh_service_url = url;
                 converse.connection = connection;
                 converse.connection = connection;

+ 6 - 3
spec/protocol.js

@@ -17,6 +17,12 @@
     // https://xmpp.org/rfcs/rfc3921.html
     // https://xmpp.org/rfcs/rfc3921.html
 
 
     describe("The Protocol", $.proxy(function (mock, test_utils) {
     describe("The Protocol", $.proxy(function (mock, test_utils) {
+        beforeEach(function () {
+            test_utils.removeControlBox();
+            converse.roster.browserStorage._clear();
+            test_utils.initConverse();
+        });
+
         describe("Integration of Roster Items and Presence Subscriptions", $.proxy(function (mock, test_utils) {
         describe("Integration of Roster Items and Presence Subscriptions", $.proxy(function (mock, test_utils) {
             /* Some level of integration between roster items and presence
             /* Some level of integration between roster items and presence
             * subscriptions is normally expected by an instant messaging user
             * subscriptions is normally expected by an instant messaging user
@@ -48,9 +54,6 @@
             */
             */
             beforeEach(function () {
             beforeEach(function () {
                 test_utils.closeAllChatBoxes();
                 test_utils.closeAllChatBoxes();
-                test_utils.removeControlBox();
-                converse.roster.browserStorage._clear();
-                test_utils.initConverse();
                 test_utils.openControlBox();
                 test_utils.openControlBox();
                 test_utils.openContactsPanel();
                 test_utils.openContactsPanel();
             });
             });

+ 9 - 2
src/converse-api.js

@@ -133,7 +133,13 @@
                 } else if (typeof jids === "string") {
                 } else if (typeof jids === "string") {
                     return converse.wrappedChatBox(converse.chatboxes.getChatBox(jids, true));
                     return converse.wrappedChatBox(converse.chatboxes.getChatBox(jids, true));
                 }
                 }
-                return _.map(jids, _.partial(_.compose(converse.wrappedChatBox, converse.chatboxes.getChatBox.bind(converse.chatboxes)), _, true));
+                return _.map(jids,
+                    _.partial(
+                        _.compose(
+                            converse.wrappedChatBox.bind(converse), converse.chatboxes.getChatBox.bind(converse.chatboxes)
+                        ), _, true
+                    )
+                );
             }
             }
         },
         },
         'tokens': {
         'tokens': {
@@ -181,7 +187,8 @@
         },
         },
         'plugins': {
         'plugins': {
             'add': function (name, plugin) {
             'add': function (name, plugin) {
-                converse.plugins[name] = plugin;
+                plugin.__name__ = name;
+                converse.pluggable.plugins[name] = plugin;
             },
             },
             'remove': function (name) {
             'remove': function (name) {
                 delete converse.plugins[name];
                 delete converse.plugins[name];

+ 1 - 1
src/converse-chatview.js

@@ -24,7 +24,7 @@
     };
     };
 
 
 
 
-    converse_api.plugins.add('chatview', {
+    converse_api.plugins.add('converse-chatview', {
 
 
         overrides: {
         overrides: {
             // Overrides mentioned here will be picked up by converse.js's
             // Overrides mentioned here will be picked up by converse.js's

+ 1 - 1
src/converse-controlbox.js

@@ -27,7 +27,7 @@
         moment = converse_api.env.moment;
         moment = converse_api.env.moment;
 
 
 
 
-    converse_api.plugins.add('controlbox', {
+    converse_api.plugins.add('converse-controlbox', {
 
 
         overrides: {
         overrides: {
             // Overrides mentioned here will be picked up by converse.js's
             // Overrides mentioned here will be picked up by converse.js's

+ 24 - 108
src/converse-core.js

@@ -23,11 +23,12 @@
         "moment_with_locales",
         "moment_with_locales",
         "strophe",
         "strophe",
         "converse-templates",
         "converse-templates",
+        "converse-pluggable",
         "strophe.disco",
         "strophe.disco",
         "backbone.browserStorage",
         "backbone.browserStorage",
         "backbone.overview",
         "backbone.overview",
     ], factory);
     ], factory);
-}(this, function ($, _, dummy, utils, moment, Strophe, templates) {
+}(this, function ($, _, dummy, utils, moment, Strophe, templates, pluggable) {
     /*
     /*
      * Cannot use this due to Safari bug.
      * Cannot use this due to Safari bug.
      * See https://github.com/jcbrand/converse.js/issues/196
      * See https://github.com/jcbrand/converse.js/issues/196
@@ -58,26 +59,31 @@
     var event_context = {};
     var event_context = {};
 
 
     var converse = {
     var converse = {
-        plugins: {},
-        initialized_plugins: [],
         templates: templates,
         templates: templates,
+
         emit: function (evt, data) {
         emit: function (evt, data) {
             $(event_context).trigger(evt, data);
             $(event_context).trigger(evt, data);
         },
         },
+
         once: function (evt, handler) {
         once: function (evt, handler) {
             $(event_context).one(evt, handler);
             $(event_context).one(evt, handler);
         },
         },
+
         on: function (evt, handler) {
         on: function (evt, handler) {
             if (_.contains(['ready', 'initialized'], evt)) {
             if (_.contains(['ready', 'initialized'], evt)) {
                 converse.log('Warning: The "'+evt+'" event has been deprecated and will be removed, please use "connected".');
                 converse.log('Warning: The "'+evt+'" event has been deprecated and will be removed, please use "connected".');
             }
             }
             $(event_context).bind(evt, handler);
             $(event_context).bind(evt, handler);
         },
         },
+
         off: function (evt, handler) {
         off: function (evt, handler) {
             $(event_context).unbind(evt, handler);
             $(event_context).unbind(evt, handler);
         }
         }
     };
     };
 
 
+    // Make converse pluggable
+    pluggable.enable(converse);
+
     // Module-level constants
     // Module-level constants
     converse.STATUS_WEIGHTS = {
     converse.STATUS_WEIGHTS = {
         'offline':      6,
         'offline':      6,
@@ -1770,117 +1776,27 @@
             return this;
             return this;
         };
         };
 
 
-        this.wrappedOverride = function (key, value, super_method) {
-            // We create a partially applied wrapper function, that
-            // makes sure to set the proper super method when the
-            // overriding method is called. This is done to enable
-            // chaining of plugin methods, all the way up to the
-            // original method.
-            this._super[key] = super_method;
-            return value.apply(this, _.rest(arguments, 3));
-        };
-
-        this._overrideAttribute = function (key, plugin) {
-            // See converse.plugins.override
-            var value = plugin.overrides[key];
-            if (typeof value === "function") {
-                var wrapped_function = _.partial(
-                    converse.wrappedOverride.bind(converse),
-                        key, value, converse[key].bind(converse)
-                );
-                converse[key] = wrapped_function;
-            } else {
-                converse[key] = value;
-            }
-        };
-
-        this._extendObject = function (obj, attributes) {
-            // See converse.plugins.extend
-            if (!obj.prototype._super) {
-                obj.prototype._super = {'converse': converse};
-            }
-            _.each(attributes, function (value, key) {
-                if (key === 'events') {
-                    obj.prototype[key] = _.extend(value, obj.prototype[key]);
-                } else if (typeof value === 'function') {
-                    // We create a partially applied wrapper function, that
-                    // makes sure to set the proper super method when the
-                    // overriding method is called. This is done to enable
-                    // chaining of plugin methods, all the way up to the
-                    // original method.
-                    var wrapped_function = _.partial(
-                        converse.wrappedOverride,
-                            key, value, obj.prototype[key]
-                    );
-                    obj.prototype[key] = wrapped_function;
-                } else {
-                    obj.prototype[key] = value;
-                }
-            });
-        };
-
-        this.initializePlugins = function () {
-            if (typeof converse._super === 'undefined') {
-                converse._super = { 'converse': converse };
-            }
-
-            var updateSettings = function (settings) {
-                /* Helper method which gets put on the plugin and allows it to
-                 * add more user-facing config settings to converse.js.
-                 */
-                _.extend(converse.default_settings, settings);
-                _.extend(converse, settings);
-                _.extend(converse, _.pick(converse.user_settings, Object.keys(settings)));
-            };
-
-            _.each(_.keys(this.plugins), function (name) {
-                var plugin = this.plugins[name];
-                plugin.updateSettings = updateSettings;
-
-                if (_.contains(this.initialized_plugins, name)) {
-                    // Don't initialize plugins twice, otherwise we get
-                    // infinite recursion in overridden methods.
-                    return;
-                }
-                plugin.converse = converse;
-                _.each(Object.keys(plugin.overrides || {}), function (key) {
-                    /* We automatically override all methods and Backbone views and
-                     * models that are in the "overrides" namespace.
-                     */
-                    var msg,
-                        override = plugin.overrides[key];
-                    if (typeof override === "object") {
-                        if (typeof converse[key] === 'undefined') {
-                            msg = "Error: Plugin tried to override "+key+" but it's not found.";
-                            if (converse.strict_plugin_dependencies) {
-                                throw msg;
-                            } else {
-                                converse.log(msg);
-                                return;
-                            }
-                        }
-                        this._extendObject(converse[key], override);
-                    } else {
-                        this._overrideAttribute(key, plugin);
-                    }
-                }.bind(this));
-
-                if (typeof plugin.initialize === "function") {
-                    plugin.initialize.bind(plugin)(this);
-                }
-                this.initialized_plugins.push(name);
-            }.bind(this));
-        };
-
         // Initialization
         // Initialization
         // --------------
         // --------------
         // This is the end of the initialize method.
         // This is the end of the initialize method.
         if (settings.connection) {
         if (settings.connection) {
             this.connection = settings.connection;
             this.connection = settings.connection;
         }
         }
-        this.initializePlugins();
-        this._initialize();
-        this.registerGlobalEventHandlers();
+        var updateSettings = function (settings) {
+            /* Helper method which gets put on the plugin and allows it to
+             * add more user-facing config settings to converse.js.
+             */
+            _.extend(converse.default_settings, settings);
+            _.extend(converse, settings);
+            _.extend(converse, _.pick(converse.user_settings, Object.keys(settings)));
+        };
+        converse.pluggable.initializePlugins({
+            'updateSettings': updateSettings,
+            'converse': converse
+        }).then(function () {
+            converse._initialize();
+            converse.registerGlobalEventHandlers();
+        });
     };
     };
     return converse;
     return converse;
 }));
 }));

+ 1 - 1
src/converse-dragresize.js

@@ -19,7 +19,7 @@
     var $ = converse_api.env.jQuery,
     var $ = converse_api.env.jQuery,
         _ = converse_api.env._;
         _ = converse_api.env._;
 
 
-    converse_api.plugins.add('dragresize', {
+    converse_api.plugins.add('converse-dragresize', {
 
 
         overrides: {
         overrides: {
             // Overrides mentioned here will be picked up by converse.js's
             // Overrides mentioned here will be picked up by converse.js's

+ 1 - 1
src/converse-headline.js

@@ -36,7 +36,7 @@
         return true;
         return true;
     };
     };
 
 
-    converse_api.plugins.add('headline', {
+    converse_api.plugins.add('converse-headline', {
 
 
         overrides: {
         overrides: {
             // Overrides mentioned here will be picked up by converse.js's
             // Overrides mentioned here will be picked up by converse.js's

+ 1 - 1
src/converse-mam.js

@@ -32,7 +32,7 @@
     Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
     Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
 
 
 
 
-    converse_api.plugins.add('mam', {
+    converse_api.plugins.add('converse-mam', {
 
 
         overrides: {
         overrides: {
             // Overrides mentioned here will be picked up by converse.js's
             // Overrides mentioned here will be picked up by converse.js's

+ 1 - 1
src/converse-minimize.js

@@ -23,7 +23,7 @@
         utils = converse_api.env.utils,
         utils = converse_api.env.utils,
         __ = utils.__.bind(converse);
         __ = utils.__.bind(converse);
 
 
-    converse_api.plugins.add('minimize', {
+    converse_api.plugins.add('converse-minimize', {
 
 
         overrides: {
         overrides: {
             // Overrides mentioned here will be picked up by converse.js's
             // Overrides mentioned here will be picked up by converse.js's

+ 12 - 3
src/converse-muc.js

@@ -14,8 +14,7 @@
             "converse-core",
             "converse-core",
             "converse-api",
             "converse-api",
             "typeahead",
             "typeahead",
-            "converse-chatview",
-            "converse-controlbox"
+            "converse-chatview"
     ], factory);
     ], factory);
 }(this, function (converse, converse_api) {
 }(this, function (converse, converse_api) {
     "use strict";
     "use strict";
@@ -43,7 +42,17 @@
     Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig");
     Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig");
     Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user");
     Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user");
 
 
-    converse_api.plugins.add('muc', {
+    converse_api.plugins.add('converse-muc', {
+        /* Optional dependencies are require.js dependencies which might be
+         * overridden or relied upon if they exist, but otherwise ignored.
+         *
+         * However, if the setting "strict_plugin_dependencies" is set to true,
+         * then these dependencies will be considered required.
+         *
+         * Optional dependencies will be available in the initialize method as
+         * a the "optional_dependencies" attribute of the plugin.
+         */
+        optional_dependencies: ["converse-controlbox"],
 
 
         overrides: {
         overrides: {
             // Overrides mentioned here will be picked up by converse.js's
             // Overrides mentioned here will be picked up by converse.js's

+ 1 - 1
src/converse-notification.js

@@ -21,7 +21,7 @@
     var supports_html5_notification = "Notification" in window;
     var supports_html5_notification = "Notification" in window;
 
 
 
 
-    converse_api.plugins.add('notification', {
+    converse_api.plugins.add('converse-notification', {
 
 
         initialize: function () {
         initialize: function () {
             /* The initialize function gets called as soon as the plugin is
             /* The initialize function gets called as soon as the plugin is

+ 1 - 1
src/converse-otr.js

@@ -50,7 +50,7 @@
     OTR_CLASS_MAPPING[VERIFIED] = 'verified';
     OTR_CLASS_MAPPING[VERIFIED] = 'verified';
     OTR_CLASS_MAPPING[FINISHED] = 'finished';
     OTR_CLASS_MAPPING[FINISHED] = 'finished';
 
 
-    converse_api.plugins.add('otr', {
+    converse_api.plugins.add('converse-otr', {
 
 
         overrides: {
         overrides: {
             // Overrides mentioned here will be picked up by converse.js's
             // Overrides mentioned here will be picked up by converse.js's

+ 1 - 1
src/converse-ping.js

@@ -22,7 +22,7 @@
     // Other necessary globals
     // Other necessary globals
     var _ = converse_api.env._;
     var _ = converse_api.env._;
     
     
-    converse_api.plugins.add('ping', {
+    converse_api.plugins.add('converse-ping', {
 
 
         initialize: function () {
         initialize: function () {
             /* The initialize function gets called as soon as the plugin is
             /* The initialize function gets called as soon as the plugin is

+ 208 - 0
src/converse-pluggable.js

@@ -0,0 +1,208 @@
+/*
+ *     ____  __                        __    __         _
+ *    / __ \/ /_  __ ___   ___  ____ _/ /_  / /__      (_)____
+ *   / /_/ / / / / / __ \/ __ \/ __/ / __ \/ / _ \    / / ___/
+ *  / ____/ / /_/ / /_/ / /_/ / /_/ / /_/ / /  __/   / (__  )
+ * /_/   /_/\__,_/\__, /\__, /\__/_/_.___/_/\___(_)_/ /____/
+ *               /____//____/                    /___/
+ *
+ */
+(function (root, factory) {
+    define("converse-pluggable", ["jquery", "underscore"], factory);
+}(this, function ($, _) {
+    "use strict";
+
+    function Pluggable (plugged) {
+        this.plugged = plugged;
+        this.plugged._super = {};
+        this.plugins = {};
+        this.initialized_plugins = [];
+    }
+    _.extend(Pluggable.prototype, {
+        wrappedOverride: function (key, value, super_method) {
+            /* We create a partially applied wrapper function, that
+             * makes sure to set the proper super method when the
+             * overriding method is called. This is done to enable
+             * chaining of plugin methods, all the way up to the
+             * original method.
+             */
+            if (typeof super_method === "function") {
+                this._super[key] = super_method.bind(this);
+            }
+            return value.apply(this, _.rest(arguments, 3));
+        },
+
+        _overrideAttribute: function (key, plugin) {
+            /* Overrides an attribute on the original object (the thing being
+             * plugged into).
+             *
+             * If the attribute being overridden is a function, then the original
+             * function will still be available via the _super attribute.
+             *
+             * If the same function is being overridden multiple times, then
+             * the original function will be available at the end of a chain of
+             * functions, starting from the most recent override, all the way
+             * back to the original function, each being referenced by the
+             * previous' _super attribute.
+             *
+             * For example:
+             *
+             * plugin2.MyFunc._super.myFunc => * plugin1.MyFunc._super.myFunc => original.myFunc
+             */
+            var value = plugin.overrides[key];
+            if (typeof value === "function") {
+                var wrapped_function = _.partial(
+                    this.wrappedOverride, key, value, this.plugged[key]
+                );
+                this.plugged[key] = wrapped_function;
+            } else {
+                this.plugged[key] = value;
+            }
+        },
+
+        _extendObject: function (obj, attributes) {
+            if (!obj.prototype._super) {
+                // FIXME: make generic
+                obj.prototype._super = {'converse': this.plugged };
+            }
+            _.each(attributes, function (value, key) {
+                if (key === 'events') {
+                    obj.prototype[key] = _.extend(value, obj.prototype[key]);
+                } else if (typeof value === 'function') {
+                    // We create a partially applied wrapper function, that
+                    // makes sure to set the proper super method when the
+                    // overriding method is called. This is done to enable
+                    // chaining of plugin methods, all the way up to the
+                    // original method.
+                    var wrapped_function = _.partial(
+                        this.wrappedOverride, key, value, obj.prototype[key]
+                    );
+                    obj.prototype[key] = wrapped_function;
+                } else {
+                    obj.prototype[key] = value;
+                }
+            }.bind(this));
+        },
+
+        setOptionalDependencies: function (plugin, dependencies) {
+            plugin.optional_dependencies = dependencies;
+            return plugin;
+        },
+
+        loadOptionalDependencies: function (plugins) {
+            var deferred = new $.Deferred();
+            require(plugins, 
+                function () {
+                    _.each(plugins, function (name) {
+                        var plugin = this.plugins[name];
+                        if (plugin) {
+                            this.initializePlugin(plugin).then(
+                                deferred.resolve.bind(this, plugins)
+                            );
+                        }
+                    }.bind(this));
+                }.bind(this),
+                function () {
+                    if (this.plugged.strict_plugin_dependencies) {
+                        deferred.fail.apply(this, arguments);
+                        this.throwUndefinedDependencyError(arguments[0]);
+                    } else {
+                        deferred.resolve.apply(this, [plugins]);
+                    }
+                }.bind(this));
+            return deferred.promise();
+        },
+
+        throwUndefinedDependencyError: function (msg) {
+            if (this.plugged.strict_plugin_dependencies) {
+                throw msg;
+            } else {
+                console.log(msg);
+                return;
+            }
+        },
+
+        applyOverrides: function (plugin) {
+            _.each(Object.keys(plugin.overrides || {}), function (key) {
+                /* We automatically override all methods and Backbone views and
+                 * models that are in the "overrides" namespace.
+                 */
+                var override = plugin.overrides[key];
+                if (typeof override === "object") {
+                    if (typeof this.plugged[key] === 'undefined') {
+                        this.throwUndefinedDependencyError("Error: Plugin \""+plugin.__name__+"\" tried to override "+key+" but it's not found.");
+                    } else {
+                        this._extendObject(this.plugged[key], override);
+                    }
+                } else {
+                    this._overrideAttribute(key, plugin);
+                }
+            }.bind(this));
+        },
+
+        _initializePlugin: function (plugin) {
+            this.applyOverrides(plugin);
+            if (typeof plugin.initialize === "function") {
+                plugin.initialize.bind(plugin)(this);
+            }
+            this.initialized_plugins.push(plugin.__name__);
+        },
+
+        asyncInitializePlugin: function (plugin) {
+            var deferred = new $.Deferred();
+            this.loadOptionalDependencies(plugin.optional_dependencies).then(
+                _.compose(
+                    deferred.resolve,
+                    this._initializePlugin.bind(this),
+                    _.partial(this.setOptionalDependencies, plugin)
+                ));
+            return deferred.promise();
+        },
+
+        initializePlugin: function (plugin) {
+            var deferred = new $.Deferred();
+            if (_.contains(this.initialized_plugins, plugin.__name__)) {
+                // Don't initialize plugins twice, otherwise we get
+                // infinite recursion in overridden methods.
+                return deferred.resolve().promise();
+            }
+            _.extend(plugin, this.properties);
+            if (plugin.optional_dependencies) {
+                this.asyncInitializePlugin(plugin).then(deferred.resolve);
+            } else {
+                this._initializePlugin(plugin);
+                deferred.resolve();
+            }
+            return deferred.promise();
+        },
+
+        initNextPlugin: function (remaining, deferred) {
+            if (remaining.length === 0) {
+                deferred.resolve();
+                return;
+            }
+            var plugin = remaining.pop();
+            this.initializePlugin(plugin).then(
+                this.initNextPlugin.bind(this, remaining, deferred));
+        },
+
+        initializePlugins: function (properties) {
+            /* The properties variable is an object of attributes and methods
+             * which will be attached to the plugins.
+             */
+            var deferred = new $.Deferred();
+            if (!_.size(this.plugins)) {
+                return deferred.promise();
+            }
+            this.properties = properties;
+            this.initNextPlugin(_.values(this.plugins).reverse(), deferred);
+            return deferred;
+        }
+    });
+    return {
+        'enable': function (object) {
+            /* Call this method to make an object pluggable */
+            return _.extend(object, {'pluggable': new Pluggable(object)});
+        }
+    };
+}));

+ 1 - 1
src/converse-register.js

@@ -40,7 +40,7 @@
     Strophe.Status.CONFLICT        = i + 3;
     Strophe.Status.CONFLICT        = i + 3;
     Strophe.Status.NOTACCEPTABLE   = i + 5;
     Strophe.Status.NOTACCEPTABLE   = i + 5;
 
 
-    converse_api.plugins.add('register', {
+    converse_api.plugins.add('converse-register', {
 
 
         overrides: {
         overrides: {
             // Overrides mentioned here will be picked up by converse.js's
             // Overrides mentioned here will be picked up by converse.js's

+ 1 - 1
src/converse-vcard.js

@@ -20,7 +20,7 @@
         moment = converse_api.env.moment;
         moment = converse_api.env.moment;
 
 
 
 
-    converse_api.plugins.add('vcard', {
+    converse_api.plugins.add('converse-vcard', {
 
 
         overrides: {
         overrides: {
             // Overrides mentioned here will be picked up by converse.js's
             // Overrides mentioned here will be picked up by converse.js's

+ 0 - 1
src/utils.js

@@ -149,7 +149,6 @@
             return str;
             return str;
         },
         },
 
 
-
         isOTRMessage: function (message) {
         isOTRMessage: function (message) {
             var $body = $(message).children('body'),
             var $body = $(message).children('body'),
                 text = ($body.length > 0 ? $body.text() : undefined);
                 text = ($body.length > 0 ? $body.text() : undefined);