blog

A badge for linking to your XMPP chat

| categories: xmpp, converse

You've probably noticed the coloured badges at the top of project READMEs on Github and Gitlab.

These badges indicate various metrics about the project's current state and its supporting infrastructure.

For example, the README page for Converse contains the following badge:

Bountysource bounties

It indicates how many open bounties there are for Converse on Bountysource.

There are dozens such badges available nowadays, just take a look at shields.io.

Some projects have badges that link to a chatroom, hosted by Gitter, Slack etc.

Converse, being an XMPP-based project, has its own XMPP chatroom at discuss@conference.conversejs.org.

So I created a badge for its README, which points to that chatroom and also indicates how many users are currently in it.

It looks like this:

Link to the Converse chatroom on inverse.chat

The Markdown syntax for showing the badge looks like this:

[![Link to XMPP chat](https://inverse.chat/badge.svg?room=discuss@conference.conversejs.org)](https://inverse.chat/#converse/room?jid=discuss@conference.conversejs.org)

The target URL for this badge is https://inverse.chat/#converse/room?jid=discuss@conference.conversejs.org, which means that it takes the user to https://inverse.chat and then once the user has logged in, the room discuss@conference.conversejs.org will be opened.

To go to a different room, just specify a different JID (Jabber ID) in the URL parameters.

The image URL is https://inverse.chat/badge.svg?room=discuss@conference.conversejs.org

Here you specify the room JID with the room URL parameter.

The image is an SVG which is generated by a tiny Flask app, which includes a chatbot written with SleekXMPP that checks how many users are in a particular chatroom.

The code is open source, and hosted on Github at jcbrand/xmpp-chat-badge.

If you're using this badge for your project, I'd be happy to hear about it.

You can let me know in the Converse XMPP chatroom at discuss@conference.conversejs.org (or through the web with inverse.chat) or by using this website's contact form.


The 2018 XSF Summit

| categories: xmpp

This year I again attended the XMPP Software Foundation's summit in Brussels. The summit spans two days and is held at Cisco's offices.

It's a rare opportunity to get community members together to discuss and make decisions around protocol extensions (the so-called XEPs). On the second day interesting discussions were had regarding the state of XMPP today, for example how and by whom its used, how to foster and support client development and the merits of federation (i.e. communication between different messaging providers).

We heard that XMPP is heavily used and respected within the Telecoms industry, and that there's a lot of "hidden" use of XMPP that even most of the Summit participants were unaware of.

The distinction was made between XMPP, which represents the protocol, and Jabber®, which represents the ideal of federated instant messaging as used by end users.

Besides what some people may think, XMPP is alive and well, and used extensively in many applications and in various sectors.

Jabber®, on the other hand, not so much. Mindshare has moved largely to siloed (i.e. non-federated and non-standardized) messaging apps like Slack et al.

The Tigase guys Andrzej Wójcik and Daniel Wisnewski gave a demo of the new Internet-of-things module of the Tigase XMPP server which also drew attention at the realtime communications stand at FOSDEM.

Minutes for the XSF Summit

I volunteered to be the minute taker this year, for which I used my favorite notes-taking app: Vimwiki.

With Vimwiki you can generate HTML from your wiki files, so I was able to make the notes available online roughly as I was typing them. which I did by pushing the generated HTML to a git repo and then pulling again via cron on a server and serving with Nginx.

Me writing the notes at the summit.

I N C E P T I O N (an original piece by Guus der Kinderen)

I also wanted to make the minutes available on the XMPP wiki, but Vimwiki's syntax is a little bit different than the Mediawiki syntax.

Pandoc came to the rescue, and with a one-liner I could generate Mediawiki files from all my notes.

So here are the 2018 XSF Summit minutes on the XMPP wiki:

https://wiki.xmpp.org/web/Summit_22#Minutes

Final thoughts

It was fun to meet up again with kindred spirits and to see some familiar and friendly faces from the XMPP world.

This year was also the first time I attended the fancy XSF dinner. I had the sea bass with endive and beurre blanc.

See you folks next year and thanks for all the fish!

The XSF summit dinner.

Converse 3.3 has been released

| categories: xmpp, converse.js

Last night I released version 3.3.0 of Converse.js, and as often happens with big releases, I made a quick bugfix release (3.3.1) today.

The bugfix release turns off some CSS3 animations for new messages which caused degraded performance on Firefox. On Chrome the animations render smoothly, so if you'd like you can still turn them on with the show_message_load_animation config option.

What's in the release?

Maintaining a long-term open source front-end JavaScript library almost feels like a Sisyphean task sometimes. As soon as you've rolled the big stone up the hill, the whole JS ecosystem, best practices and tooling changes and you find yourself at the bottom of the hill again.

This release is therefore heavy on changes under the hood, with the aim of modernizing and improving the quality of the code.

Besides that, I also spent time squashing lots of small usability bugs and on improving performance.

Converse.js now uses a Virtual DOM

Various views, such as the registration form, are now rendered by means of a Virtual DOM. I wrote a new Backbone view called Backbone.VDOMView for this, and blogged about it here: https://opkode.com/blog/backbone-vdomview/

No more jQuery

Looking at the git log, I started rewriting code to not use jQuery in January 2017.

So this change has been a year in the works. I often asked myself whether I should spend time on this and not rather do something else, like adding new features, especially since removing jQuery has taken a lot of time.

However, there were some good reasons, or perhaps motivations, for me to remove jQuery.

Before ES6 promises were available, I used $.Deferred. However, jQuery's deferreds weren't compatible with Promises, so when ES6 Promises came around, I had to rewrite lots of code to use Promises.

I used $.show and $.hide quite a bit, and then it turned out that the way jQuery was doing it (by adding/removing display: none to the DOM element) is not good practice and also very slow.

So I started writing my own utility functions to replace jQuery's.

The last straw for me was when jQuery 3 came out, and half of Converse.js's ~240 tests failed once I plugged it in.

After spending some time trying to figure out what backward incompatible changes they made and how I should update the code, I decided to instead rip jQuery out entirely.

It's still used in the tests, but it's no longer included in any build.

Since removing it, I've noticed a remarkable reduction in time to run the tests.

By looking at how quickly the tests run now, the code seems to run much faster without jQuery.

Less weight

Besides removing jQuery, I also updated Converse.js to load translations at runtime, and only the exact translation JSON file that's needed.

Due to these changes, the unminified built has shrunk from 3.38MB to 2.8MB, and the minified build from 1.66MB to 1.2MB.

And this is while adding the virtual DOM code.

Route to a specific chat room via the URL

It's now possible to directly link to a specific chat room, or to the registration page (instead of the login page) via the URL.

For example, the URL https://inverse.chat/#converse/room?jid=discuss@conference.conversejs.org will take you immediately to the Converse.js "Discuss" chat room, once you've logged in.

What else?

Lots of other bugfixes and improvements have been added in this release. For more details, check the changelog.

Notable absent from this release are some desired features, such as file sharing, message corrections, message receipts and the like.

I would love to add some of these often requested features, however I had to get the house in order so to speak, by modernizing the code and squashing lots and lots of little usability and performance bugs.

That said, Converse.js takes up a LOT of my free time and not a single line of code in this release was paid for.

If you or your company make use of converse.js, please consider sponsoring it on Patreon or Liberapay.

Thanks

Thanks goes out to everyone who's made pull requests and bug reports over the last months.

And thanks also to the folks who hang out in the Converse.js Discusss chat room and who have there provided valuable feedback.


Using a virtual DOM with Backbone views

| categories: virtual-dom, converse, backbone.js

Backbone.js, first released in 2010, provided one-way event binding with unidirectional data-flow and therefore a means to do reactive programming before it was cool.

Backbone views provide a render method, which is supposed to insert HTML into the DOM. How this was actually done was left up to the user as a homework assignment.

Generally, jQuery's html method was used. Like so:

const MyView = Backbone.View.extend({

    initialize () {
        this.model.on('change', this.render, this);
    },

    render () {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

Each view has an associated DOM element, this.$el if you're using jQuery or this.el otherwise. In the above example, the element's contents would be replaced.

What this approach however lacks, is a way to rerender views containing forms without losing the input focus or user-provided form values.

This is because back in 2010, the virtual DOM as popularized by React wasn't yet around.

Additionally, since rendering the HTML and inserting it into the DOM were both done inside the render method, and the implementation details were left up to the user, enough rope was provided for you to hang yourself by putting things inside the render method which shouldn't be there.

Contrast this to a React component's render method, which is a "pure function" that doesn't modify the DOM, and instead simply returns JSX:

class MyComponent extends React.Component {

        render() {
                return <p>Hello World!</p>;
        }
}

The markup is then inserted into the DOM separately with ReactDOM.render

ReactDOM.render(
        <MyComponent />,
        document.getElementById('root')
);

The fact that a component's render method must return JSX, means that they are pure functions without side-effects.

Backbone Views which use a virtual DOM

In Converse we still use Backbone Models and Views, and for the most part, Backbone still works beautifully. It's a small, lightweight library that's fit for purpose and doesn't get in the way.

However, we've been missing out on that sweet sweet virtual DOM.

No more!

To make our Backbone Views more reactive, as is done in React, I wrote Backbone.VDOMView.

This library adds a new view, called Backbone.VDOMView, which uses a Virtual DOM (provided by Snabbdom) to only update those DOM elements (within the view) as necessary.

Instead of writing a render method, for Backbone.VDOMView you need to implement a toHTML method, which is a pure function that simply returns the view's HTML.

Here's what it looks like:

const MyView = Backbone.VDOMView.extend({

    initialize () {
        this.model.on('change', this.render, this);
    },

    toHTML () {
        return this.template(this.model.toJSON());
    }
});

Backbone.VDOMView takes the output of toHTML and converts it into virtual DOM nodes and automatically diffs and patches the DOM.

When the view's associated Backbone.Model (referenced via this.model) changes, we can simply call render again, knowing that only the changed DOM elements will be updated.

We write less to the DOM and we prevent the loss of form input values and loss of element focus.

Include the root element in your HTML

There's one important difference with normal Backbone.Views and that is that toHTML (as opposed to render) must including the view's root element in the returned HTML.

With normal Backbone.View, you don't include the view's root element when you render a template.

React has a similar requirement to this. JSX returned in the render method of a component must have a root node which contains everything else.

jQuery not required

With Backbone, you can use Backbone.NativeView to remove the default dependency on jQuery.

Backbone.VDOMView checks whether you are using Backbone.NativeView and will then subclass it instead of Backbone.View.

Usage in Converse

In release 3.3.0 of Converse I've removed jQuery as dependency and started integrating Backbone.VDOMView.

Various Backbone.View components from Converse have already been migrated over to Backbone.VDOMView.

So far, I'm very happy with the results, and haven't run into any issues.

If you're using Backbone.VDOMView or have any feedback, feel free to leave a comment or contact me here.


Customizing and whitelisting SASL authentication mechanisms in Strophe.js

| categories: xmpp, strophe.js, foss, sasl

Introduction

If you've decided to read this fairly technical blogpost, then you probably have at least a rough idea what SASL is about and why one would want to create custom SASL auth mechanisms or whitelist the supported mechanisms.

I'll therefore provide just a very brief recap of the topics involved:

The Simple Authentication and Security Layer or SASL RFC 4422 is a framework for adding authentication support to connection-based protocols.

It provides an abstraction layer for authentication mechanisms, so that protocols, such as XMPP don't have to deal with the intricacies and complexities of supporting multiple authentication mechanisms.

It therefore makes auth mechanisms pluggable (if they are SASL compatible).

Strophe.js has supported SASL since a long time, but it didn't provide an easy way add custom SASL mechanisms, or to whitelist the mechanisms to be used.

Until now... or rather, since the 1.2.9 release.

Creating a custom SASL auth mechanism

To create a custom SASL authentication mechanism is fairly simple.

You can glean what's required by simply looking at how the default mechanisms are created.

See for example how the SASLPlain mechanism is defined.

And look as the SASLMechanism prototype to see the interface that the mechanism supports.

Perty much it boils down to creating constructor, settings its prototype to an invoked Strophe.SASLMechanism instance, providing its name, a boolean to indicate whether it should proactively respond without an initial server challenge, and an integer value specifying its priority amongst the supported mechanisms.

The default mechanisms and their respective priorities are:

  • EXTERNAL - 60
  • OAUTHBEARER - 50
  • SCRAM-SHA1 - 40
  • DIGEST-MD5 - 30
  • PLAIN - 20
  • ANONYMOUS - 10

Then it's a matter of implementing onChallenge and any of the other methods provided by the SASLMechanism prototype.

onChallenge is called once the server challenges the client to authenticate itself or proactively if the mechanism requires that the client initiates authentication (configured with the isClientFirst parameter of Strophe.SASLMechanism).

So, lets create a fictional auth mechanism called SASL-FOO which works similarly to SASL-PLAIN, except that the password is encrypted with double-encoding ROT13 (hint: this is a joke).

We would then create the authentication mechanism like so:

Strophe.SASLFoo = function() {};
Strophe.SASLFoo.prototype = new Strophe.SASLMechanism("FOO", true, 60);

Strophe.SASLFoo.prototype.onChallenge = function(connection) {
    var auth_str = connection.authzid;
    auth_str = auth_str + "\u0000";
    auth_str = auth_str + connection.authcid;
    auth_str = auth_str + "\u0000";
    auth_str = auth_str + DoubleROT13(connection.pass);
    return utils.utf16to8(auth_str);
};

Whitelisting the supported SASL auth mechanisms

Now with SASL-FOO in hand, we can whitelist the supported authentication mechanisms by specifying a list of mechanisms in the options map passed in when we instantiate a new Strohe.Connection.

var service = 'chat.example.org';
var options = {
    'mechanisms': [
        SASLFoo,
        Strophe.SASLPlain
    ]
};
var conn = new Strophe.Connection(service, options);

Bonus: Whitelisting SASL auth mechanisms in Converse.js

Due to the above changes it'll also be possible to whitelist SASL mechanisms in Converse.js (version 2.0.1 and upwards).

This is done via the connection_options configuration setting:

converse.initialize({

    connection_options: {
        'mechanisms': [
            converse.env.Strophe.SASLMD5,
            converse.env.Strophe.SASLPlain
        ]
    },
});

« Previous Page -- Next Page »