Writing a plugin

Introduction

Converse.js is exposes a plugin architecture which allows developers to modify and extend its functionality.

Specifically, plugins enable developers to extend and override existing objects, functions and Backbone models and views that make up Converse.js, and also give them the ability to write new models and views.

Various core features of Converse.js, such as Message Archive Management and Group chats are implemented as plugins, thereby showing their power and flexibility.

Converse.js uses pluggable.js as its plugin architecture.

To more deeply understand how this plugin architecture works, please read the pluggable.js documentation and to understand its inner workins, please refer to the annotated source code.

Note

Trying out a plugin in JSFiddle

Because Converse.js consists only of JavaScript, HTML and CSS (with no backend code required like PHP, Python or Ruby) it runs fine in JSFiddle.

Here’s a Fiddle with a Converse.js plugin that calls alert once it gets initialized and also when a chat message gets rendered: https://jsfiddle.net/4drfaok0/15/

Note

Generating a plugin with Yeoman

The rest of this document explains how to write a plugin for Converse.js and ends with a documented example of a plugin.

There is a Yeoman code generator, called generator-conversejs, which you can use to generate plugin scaffolding/boilerplate, which you can use as a starting point and basis for writing your plugin.

Please refer to the generator-conversejs README for information on how to use it.

Registering a plugin

Plugins need to be registered (and whitelisted) before they can be loaded and initialized.

You register a converse.js plugin by calling converse.plugins.add.

The plugin itself is a JavaScript object which usually has at least an initialize method, which gets called at the end of the converse.initialize method which is the top-level method that gets called by the website to configure and initialize Converse.js itself.

Here’s an example code snippet:

converse.plugins.add('myplugin', {

    initialize: function () {
        // This method gets called once converse.initialize has been called
        // and the plugin itself has been loaded.

        // Inside this method, you have access to the closured
        // _converse object as an attribute on "this".
        // E.g. this._converse
    },
});

Note

It’s important that converse.plugins.add is called before converse.initialize is called. Otherwise the plugin will never get registered and never get called.

Whitelisting of plugins

As of converse.js 3.0.0 and higher, plugins need to be whitelisted before they can be used. This is because plugins have access to a powerful API. For example, they can read all messages and send messages on the user’s behalf.

To avoid malicious plugins being registered (i.e. by malware infected advertising networks) we now require whitelisting.

To whitelist a plugin simply means to specify whitelisted_plugins when you call converse.initialize.

Security and access to the inner workings

The globally available converse object, which exposes the API methods, such as initialize and plugins.add, is a wrapper that encloses and protects a sensitive inner object, named _converse (not the underscore prefix).

This inner _converse object contains all the Backbone models and views, as well as various other attributes and functions.

Within a plugin, you will have access to this internal “closured” _converse object, which is normally not exposed in the global variable scope.

The inner _converse object is made private in order to safely hide and encapsulate sensitive information and methods which should not be exposed to any 3rd-party scripts that might be running in the same page.

Loading a plugin module

Converse.js uses the UMD (Universal Modules Definition) as its module syntax. This makes modules loadable via require.js, webpack or other module loaders, but also includable as old-school <script> tags in your HTML.

Here’s an example of the plugin shown above wrapped inside a UMD module:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as a module called "myplugin"
        define("myplugin", ["converse"], factory);
    } else {
        // Browser globals. If you're not using a module loader such as require.js,
        // then this line below executes. Make sure that your plugin's <script> tag
        // appears after the one from converse.js.
        factory(converse);
    }
}(this, function (converse) {

    converse.plugins.add('myplugin', {

        initialize: function () {
            // This method gets called once converse.initialize has been called
            // and the plugin itself has been loaded.

            // Inside this method, you have access to the closured
            // _converse object as an attribute on "this".
            // E.g. this._converse
        },
    });

});

Accessing 3rd party libraries

Immediately inside the module shown above you can access 3rd party libraries (such moment, underscore and jQuery) via the converse.env map.

The code for it would look something like this:

// Commonly used utilities and variables can be found under the "env"
// namespace of the "converse" global.
var Strophe = converse.env.Strophe,
    $iq = converse.env.$iq,
    $msg = converse.env.$msg,
    $pres = converse.env.$pres,
    $build = converse.env.$build,
    b64_sha1 = converse.env.b64_sha1;
    $ = converse.env.jQuery,
    _ = converse.env._,
    moment = converse.env.moment;

These dependencies are closured so that they don’t pollute the global namespace, that’s why you need to access them in such a way inside the module.

Overrides

Plugins can override core code or code from other plugins. Refer to the full example at the bottom for code details.

Use the overrides functionality with caution. It basically resorts to monkey patching which pollutes the call stack and can make your code fragile and prone to bugs when Converse.js gets updated. Too much use of overrides is therefore a “code smell” which should ideally be avoided.

A better approach is to listen to the events emitted by Converse.js, and to add your code in event handlers. This is however not always possible, in which case the overrides are a powerful tool.

Optional plugin dependencies

When using overrides, the code that you want to override (which is either in converse-core or in other plugins), needs to be loaded already by the type the overrides object is being parsed.

So it’s important to include overridden plugins in the AMD define statement at the top of the plugin module.

However, sometimes you want to override parts of another plugin if it exists, but you don’t want anything to break if it doesn’t exist (for example when using a custom build which excludes that plugin). An example is the converse-dragresize plugin, which will add drag-resize handles to the headlines box (which shows messages of type headline) but doesn’t care if that particular plugin isn’t actually loaded.

In this case, you can’t specify the plugin as a dependency in the define statement at the top of the plugin, since it might not always be available, which would cause require.js to throw an error.

To resolve this problem we have the optional_dependencies Array attribute. With this you can specify those dependencies which need to be loaded before your plugin, if they exist. If they don’t exist, they won’t be ignored.

If the setting strict_plugin_dependencies is set to true, an error will be raised if the plugin is not found, thereby making them non-optional.

Extending converse.js’s configuration settings

Converse.js comes with various :ref:`configuration-settings`_ that can be used to modify its functionality and behavior.

All configuration settings have default values which can be overridden when converse.initialize (see :ref:`initialize`_) gets called.

Plugins often need their own additional configuration settings and you can add these settings with the _converse.api.settings.update method (see :ref:`settings-update`_).

Exposing promises

Converse.js has a waitUntil API method (see :ref:`waituntil-grouping`_) which allows you to wait for various promises to resolve before executing a piece of code.

You can add new promises for your plugin by calling _converse.api.promises.add (see :ref:`promises-grouping`_).

Generally, your plugin will then also be responsible for making sure these promises are resolved. You do this by calling _converse.api.emit, which not only resolves the plugin but will also emit an event with the same name.

Dealing with asynchronicity

Due to the asynchronous nature of XMPP, many subroutines in Converse.js execute at different times and not necessarily in the same order.

In many cases, when you want to execute a piece of code in a plugin, you first want to make sure that the supporting data-structures that your code might rely on have been created and populated with data.

There are two ways of waiting for the right time before executing your code. You can either listen for certain events, or you can wait for promises to resolve.

For example, in the Bookmarks plugin (in src/converse-bookmarks.js), before bookmarks can be fetched and shown to the user, we first have to wait until the “Rooms” panel of the ControlBox has been rendered and inserted into the DOM. Otherwise we have no place to show the bookmarks yet.

Therefore, there are the following lines of code in the initialize method of converse-bookmarks.js:

Promise.all([
    _converse.api.waitUntil('chatBoxesFetched'),
    _converse.api.waitUntil('roomsPanelRendered')
]).then(initBookmarks);

What this means, is that the plugin will wait until the chatBoxesFetched and roomsPanelRendered promises have been resolved before it calls the initBookmarks method (which is defined inside the plugin).

This way, we know that we have everything in place and set up correctly before fetching the bookmarks.

As another example, there is also the following code in the initialize method of the plugin:

_converse.on('chatBoxOpened', function renderMinimizeButton (view) {
    // Inserts a "minimize" button in the chatview's header

    // Implementation code removed for brevity
    // ...
});

In this case, the plugin waits for the chatBoxOpened event, before it then calls renderMinimizeButton, which adds a new button to the chat box (which enables you to minimize it).

Finding the right promises and/or events to listen to, can be a bit challenging, and sometimes it might be necessary to create new events or promises.

Please refer to the Events and promises section of the documentation for an overview of what’s available to you. If you need new events or promises, then please open an issue or make a pull request on Github

A full example plugin

Below follows a documented example of a plugin. This is the same code that gets generated by generator-conversejs.

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as a module called "myplugin"
        define("<%= name %>", ["converse"], factory);
    } else {
        // Browser globals. If you're not using a module loader such as require.js,
        // then this line below executes. Make sure that your plugin's <script> tag
        // appears after the one from converse.js.
        factory(converse);
    }
}(this, function (converse) {

    // Commonly used utilities and variables can be found under the "env"
    // namespace of the "converse" global.
    var Strophe = converse.env.Strophe,
        $iq = converse.env.$iq,
        $msg = converse.env.$msg,
        $pres = converse.env.$pres,
        $build = converse.env.$build,
        b64_sha1 = converse.env.b64_sha1;
        $ = converse.env.jQuery,
        _ = converse.env._,
        moment = converse.env.moment;

    // The following line registers your plugin.
    converse.plugins.add("<%= name %>", {

        /* Optional dependencies are other plugins which might be
           * overridden or relied upon, and therefore need to be loaded before
           * this plugin. They are called "optional" because they might not be
           * available, in which case any overrides applicable to them will be
           * ignored.
           *
           * NB: These plugins need to have already been loaded via require.js.
           *
           * It's possible to make optional dependencies non-optional.
           * If the setting "strict_plugin_dependencies" is set to true,
           * an error will be raised if the plugin is not found.
           */
        'optional_dependencies': [],

        /* Converse.js's plugin mechanism will call the initialize
         * method on any plugin (if it exists) as soon as the plugin has
         * been loaded.
         */
        'initialize': function () {
            /* Inside this method, you have access to the private
             * `_converse` object.
             */
            var _converse = this._converse;
            _converse.log("The <%= name %> plugin is being initialized");

            /* From the `_converse` object you can get any configuration
             * options that the user might have passed in via
             * `converse.initialize`. These values are stored in the
             * "user_settings" attribute.
             *
             * You can also specify new configuration settings for this
             * plugin, or override the default values of existing
             * configuration settings. This is done like so:
            */
            _converse.api.settings.update({
                'initialize_message': 'Initializing <%= name %>!'
            });

            /* The user can then pass in values for the configuration
             * settings when `converse.initialize` gets called.
             * For example:
             *
             *      converse.initialize({
             *           "initialize_message": "My plugin has been initialized"
             *      });
             *
             * And the configuration setting is then available via the
             * `user_settings` attribute:
             */
            alert(this._converse.user_settings.initialize_message);

            /* Besides `_converse.api.settings.update`, there is also a
             * `_converse.api.promises.add` method, which allows you to
             * add new promises that your plugin is obligated to fulfill.
             *
             * This method takes a string or a list of strings which
             * represent the promise names:
             *
             *      _converse.api.promises.add('myPromise');
             *
             * Your plugin should then, when appropriate, resolve the
             * promise by calling `_converse.api.emit`, which will also
             * emit an event with the same name as the promise.
             * For example:
             *
             *      _converse.api.emit('operationCompleted');
             *
             * Other plugins can then either listen for the event
             * `operationCompleted` like so:
             *
             *      _converse.api.listen.on('operationCompleted', function { ... });
             *
             * or they can wait for the promise to be fulfilled like so:
             *
             *      _converse.api.waitUntil('operationCompleted', function { ... });
             */
        },

        /* If you want to override some function or a Backbone model or
         * view defined elsewhere in converse.js, then you do that under
         * the "overrides" namespace.
         */
        'overrides': {
            /* For example, the private *_converse* object has a
             * method "onConnected". You can override that method as follows:
             */
            'onConnected': function () {
                // Overrides the onConnected method in converse.js

                // Top-level functions in "overrides" are bound to the
                // inner "_converse" object.
                var _converse = this;

                // Your custom code can come here ...

                // You can access the original function being overridden
                // via the __super__ attribute.
                // Make sure to pass on the arguments supplied to this
                // function and also to apply the proper "this" object.
                _converse.__super__.onConnected.apply(this, arguments);

                // Your custom code can come here ...
            },

            /* Override converse.js's XMPPStatus Backbone model so that we can override the
             * function that sends out the presence stanza.
             */
            'XMPPStatus': {
                'sendPresence': function (type, status_message, jid) {
                    // The "_converse" object is available via the __super__
                    // attribute.
                    var _converse = this.__super__._converse;

                    // Custom code can come here ...

                    // You can call the original overridden method, by
                    // accessing it via the __super__ attribute.
                    // When calling it, you need to apply the proper
                    // context as reference by the "this" variable.
                    this.__super__.sendPresence.apply(this, arguments);

                    // Custom code can come here ...
                }
            }
        }
    });
}));