Otalk IM

A customer of mine asked for a WebRTC based, self hosted solution. So I started investigating and stumbled upon the great Otalk IM Client. Unfortunately it has not been maintained for a while. I decided to go with it nonetheless. I updated dependencies as required to make them work with latest stable NPM (3.x) and Node.js (6.x) as applicable and fixed bugs all along the way. Check out my fork if you are interested! Feedback would be very welcome. I think it makes for a very good starting point if you were about to create your own WebRTC solution. Or maybe even just a regular chat client.

CORS and basic authentication with cowboy and XmlHttpRequest

So this seems to be anything but straight forward so I document here for the sake of it.

Note that

  • you may not require an Authorization header for the preflight OPTIONS request,
  • you may not set Access-Control-Allow-Origin to the wildcard in that case but to the originating domain specifically,
  • of course Access-Control-Allow-Credentials must be set to true,
  • and last but not least you must advertise the content-type and authorization (for Firefox) header with Access-Control-Allow-Headers (if you want to have some content POST’ed to you),
  • all those headers better be sent with every response.

So in my REST resource I did:

rest_init(Req0, State) ->
    case cowboy_req:header(<<"origin">>, Req0) of
        {undefined, Req1} -> {ok, Req1, State};
        {Origin, Req1}    ->
            %% set CORS headers
            Req2 = cowboy_req:set_resp_header(<<"access-control-allow-origin">>,
                                              Origin, Req1),
            Req3 = cowboy_req:set_resp_header(<<"access-control-allow-methods">>,
                                              <<"POST, OPTIONS">>, Req2),
            Req4 = cowboy_req:set_resp_header(
                     <<"access-control-allow-credentials">>, <<"true">>, Req3),
            Req5 = cowboy_req:set_resp_header(
                     <<"access-control-allow-headers">>, <<"authorization,content-type">>, Req4),
            {ok, Req5, State}
    end.

allowed_methods(Req, State) -> {[<<"OPTIONS">>, <<"POST">>], Req, State}.

is_authorized(Req, State) ->
    case cowboy_req:method(Req) of
        {<<"OPTIONS">>, Req0} -> {true, Req0, State};
        {_Any,          Req0} ->
            case cowboy_req:parse_header(<<"authorization">>, Req0) of
                {ok, {<<"basic">>, {AppId, Token}}, Req1} ->
                    case is_token_valid(AppId, Token) of
                        true ->
                            {true, Req1, AppId};
                        false -> {{false, <<"Basic realm=\"myrealm\"">>}, Req1, State}
                    end;
                {ok, undefined, Req1} ->
                    {{false, <<"Basic realm=\"myrealm\"">>}, Req1, State}
            end
    end.

As for the AJAX request you need to set withCredentials to true (doesn’t work with Firefox, do it manually):

    var r = new XMLHttpRequest();
    r.open("POST", api_uri, true);
    r.setRequestHeader("Authorization", "Basic " + btoa(app_id + ":" + app_token));
    r.setRequestHeader("Content-type", "application/json");
    r.send(JSON.stringify(msg));

I’ve tested with Safari, Chrome and Firefox.

Update: So apparently to make that work with Firefox you have to add authorization explicitly to Access-Control-Allow-Headers. And as for the XmlHttpRequest you have to set the Authorization manually as shown above. Found at http://stackoverflow.com/questions/27001894/firefox-does-not-allow-cors-request-even-when-the-server-returns-the-appropriate

jwchat.org Updates

I have been working a lot on my XMPP service at jwchat.org recently. The idea was to bring you more security, privacy and modern features. In detail what has changed:

I hope you’ll enjoy the extended and improved service.

If you want to have your own XMPP (Jabber) server hosted, feel free to contact me!

Migrate ejabberd DB schema (mysql) from 2.1.x

Hiya! Long time no see. So I was upgrading my beloved ejabberd a while back and when I looked closer I found that most of the new functionality just wouldn’t work. Mostly because my ejabberd’s DB schema dating back to some 2.1.x install didn’t fit anymore. So but how to upgrade? After playing weird ideas back and forth I came across a script called ‘php-mysql-diff‘. According to its description it sounded very promising to what I wanted to achieve. Just that it wouldn’t work right of the box. After tearing it apart and putting it back together again I found that this was because my dump files as created by mysqldump sport a ‘COLLATE’ keyword which wasn’t supported by the built-in regexp used by its parser. After I fixed this I got the desired result, a migration script that I could use to upgrade my schemas. Hooray!


# Disable Foreign Keys Check
SET FOREIGN_KEY_CHECKS = 0;
SET SQL_MODE = '';

# Deleted Tables

# Changed Tables

-- changed table `last`

ALTER TABLE `last`
CHANGE COLUMN `username` `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL FIRST,
CHANGE COLUMN `seconds` `seconds` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `username`,
CHANGE COLUMN `state` `state` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `seconds`;

-- changed table `privacy_default_list`

ALTER TABLE `privacy_default_list`
CHANGE COLUMN `username` `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL FIRST,
CHANGE COLUMN `name` `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `username`;

-- changed table `privacy_list`

ALTER TABLE `privacy_list`
CHANGE COLUMN `username` `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL FIRST,
CHANGE COLUMN `name` `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `username`,
ADD COLUMN `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `id`;

-- changed table `privacy_list_data`

ALTER TABLE `privacy_list_data`
CHANGE COLUMN `t` `t` char(1) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `id`,
CHANGE COLUMN `value` `value` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `t`,
CHANGE COLUMN `action` `action` char(1) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `value`,
ADD KEY `i_privacy_list_data_id` (`id`);

-- changed table `private_storage`

ALTER TABLE `private_storage`
CHANGE COLUMN `username` `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL FIRST,
CHANGE COLUMN `namespace` `namespace` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `username`,
CHANGE COLUMN `data` `data` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `namespace`,
ADD COLUMN `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `data`;

-- changed table `rostergroups`

ALTER TABLE `rostergroups`
CHANGE COLUMN `username` `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL FIRST,
CHANGE COLUMN `jid` `jid` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `username`,
CHANGE COLUMN `grp` `grp` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `jid`;

-- changed table `rosterusers`

ALTER TABLE `rosterusers`
CHANGE COLUMN `username` `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL FIRST,
CHANGE COLUMN `jid` `jid` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `username`,
CHANGE COLUMN `nick` `nick` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `jid`,
CHANGE COLUMN `subscription` `subscription` char(1) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `nick`,
CHANGE COLUMN `ask` `ask` char(1) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `subscription`,
CHANGE COLUMN `askmessage` `askmessage` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `ask`,
CHANGE COLUMN `server` `server` char(1) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `askmessage`,
CHANGE COLUMN `subscribe` `subscribe` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `server`,
CHANGE COLUMN `type` `type` text COLLATE utf8mb4_unicode_ci AFTER `subscribe`,
ADD COLUMN `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `type`;

-- changed table `spool`

ALTER TABLE `spool`
CHANGE COLUMN `username` `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL FIRST,
CHANGE COLUMN `xml` `xml` blob NOT NULL AFTER `username`,
ADD COLUMN `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `seq`,
ADD KEY `i_spool_created_at` (`created_at`) USING BTREE;

-- changed table `users`

ALTER TABLE `users`
CHANGE COLUMN `username` `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL FIRST,
CHANGE COLUMN `password` `password` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `username`,
ADD COLUMN `serverkey` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' AFTER `password`,
ADD COLUMN `salt` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' AFTER `serverkey`,
ADD COLUMN `iterationcount` int(11) NOT NULL DEFAULT '0' AFTER `salt`,
ADD COLUMN `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `iterationcount`;

-- changed table `vcard`

ALTER TABLE `vcard`
CHANGE COLUMN `username` `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL FIRST,
CHANGE COLUMN `vcard` `vcard` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL AFTER `username`,
ADD COLUMN `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `vcard`;

-- changed table `vcard_search`

ALTER TABLE `vcard_search`
CHANGE COLUMN `username` `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL FIRST,
CHANGE COLUMN `lusername` `lusername` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `username`,
CHANGE COLUMN `fn` `fn` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `lusername`,
CHANGE COLUMN `lfn` `lfn` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `fn`,
CHANGE COLUMN `family` `family` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `lfn`,
CHANGE COLUMN `lfamily` `lfamily` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `family`,
CHANGE COLUMN `given` `given` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `lfamily`,
CHANGE COLUMN `lgiven` `lgiven` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `given`,
CHANGE COLUMN `middle` `middle` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `lgiven`,
CHANGE COLUMN `lmiddle` `lmiddle` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `middle`,
CHANGE COLUMN `nickname` `nickname` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `lmiddle`,
CHANGE COLUMN `lnickname` `lnickname` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `nickname`,
CHANGE COLUMN `bday` `bday` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `lnickname`,
CHANGE COLUMN `lbday` `lbday` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `bday`,
CHANGE COLUMN `ctry` `ctry` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `lbday`,
CHANGE COLUMN `lctry` `lctry` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `ctry`,
CHANGE COLUMN `locality` `locality` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `lctry`,
CHANGE COLUMN `llocality` `llocality` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `locality`,
CHANGE COLUMN `email` `email` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `llocality`,
CHANGE COLUMN `lemail` `lemail` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `email`,
CHANGE COLUMN `orgname` `orgname` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `lemail`,
CHANGE COLUMN `lorgname` `lorgname` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `orgname`,
CHANGE COLUMN `orgunit` `orgunit` text COLLATE utf8mb4_unicode_ci NOT NULL AFTER `lorgname`,
CHANGE COLUMN `lorgunit` `lorgunit` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL AFTER `orgunit`;

# New Tables

-- new table `archive`

CREATE TABLE `archive` (
`username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`timestamp` bigint(20) unsigned NOT NULL,
`peer` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`bare_peer` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`xml` text COLLATE utf8mb4_unicode_ci NOT NULL,
`txt` text COLLATE utf8mb4_unicode_ci,
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`kind` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`nick` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY `i_bare_peer` (`bare_peer`) USING BTREE,
KEY `i_peer` (`peer`) USING BTREE,
FULLTEXT KEY `i_text` (`txt`),
KEY `i_timestamp` (`timestamp`) USING BTREE,
KEY `i_username` (`username`) USING BTREE,
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `archive_prefs`

CREATE TABLE `archive_prefs` (
`username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`def` text COLLATE utf8mb4_unicode_ci NOT NULL,
`always` text COLLATE utf8mb4_unicode_ci NOT NULL,
`never` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `caps_features`

CREATE TABLE `caps_features` (
`node` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`subnode` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`feature` text COLLATE utf8mb4_unicode_ci,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY `i_caps_features_node_subnode` (`node`(75),`subnode`(75))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `irc_custom`

CREATE TABLE `irc_custom` (
`jid` text COLLATE utf8mb4_unicode_ci NOT NULL,
`host` text COLLATE utf8mb4_unicode_ci NOT NULL,
`data` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `i_irc_custom_jid_host` (`jid`(75),`host`(75)) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `motd`

CREATE TABLE `motd` (
`username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`xml` text COLLATE utf8mb4_unicode_ci,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `muc_registered`

CREATE TABLE `muc_registered` (
`jid` text COLLATE utf8mb4_unicode_ci NOT NULL,
`host` text COLLATE utf8mb4_unicode_ci NOT NULL,
`nick` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `i_muc_registered_jid_host` (`jid`(75),`host`(75)) USING BTREE,
KEY `i_muc_registered_nick` (`nick`(75)) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `muc_room`

CREATE TABLE `muc_room` (
`name` text COLLATE utf8mb4_unicode_ci NOT NULL,
`host` text COLLATE utf8mb4_unicode_ci NOT NULL,
`opts` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `i_muc_room_name_host` (`name`(75),`host`(75)) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `pubsub_item`

CREATE TABLE `pubsub_item` (
`nodeid` bigint(20) DEFAULT NULL,
`itemid` text COLLATE utf8mb4_unicode_ci,
`publisher` text COLLATE utf8mb4_unicode_ci,
`creation` text COLLATE utf8mb4_unicode_ci,
`modification` text COLLATE utf8mb4_unicode_ci,
`payload` text COLLATE utf8mb4_unicode_ci,
KEY `i_pubsub_item_itemid` (`itemid`(36)),
UNIQUE KEY `i_pubsub_item_tuple` (`nodeid`,`itemid`(36)),
CONSTRAINT `pubsub_item_ibfk_1` FOREIGN KEY (`nodeid`) REFERENCES `pubsub_node` (`nodeid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `pubsub_node`

CREATE TABLE `pubsub_node` (
`host` text COLLATE utf8mb4_unicode_ci,
`node` text COLLATE utf8mb4_unicode_ci,
`parent` text COLLATE utf8mb4_unicode_ci,
`type` text COLLATE utf8mb4_unicode_ci,
`nodeid` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nodeid`),
KEY `i_pubsub_node_parent` (`parent`(120)),
UNIQUE KEY `i_pubsub_node_tuple` (`host`(20),`node`(120))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `pubsub_node_option`

CREATE TABLE `pubsub_node_option` (
`nodeid` bigint(20) DEFAULT NULL,
`name` text COLLATE utf8mb4_unicode_ci,
`val` text COLLATE utf8mb4_unicode_ci,
KEY `i_pubsub_node_option_nodeid` (`nodeid`),
CONSTRAINT `pubsub_node_option_ibfk_1` FOREIGN KEY (`nodeid`) REFERENCES `pubsub_node` (`nodeid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `pubsub_node_owner`

CREATE TABLE `pubsub_node_owner` (
`nodeid` bigint(20) DEFAULT NULL,
`owner` text COLLATE utf8mb4_unicode_ci,
KEY `i_pubsub_node_owner_nodeid` (`nodeid`),
CONSTRAINT `pubsub_node_owner_ibfk_1` FOREIGN KEY (`nodeid`) REFERENCES `pubsub_node` (`nodeid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `pubsub_state`

CREATE TABLE `pubsub_state` (
`nodeid` bigint(20) DEFAULT NULL,
`jid` text COLLATE utf8mb4_unicode_ci,
`affiliation` char(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`subscriptions` text COLLATE utf8mb4_unicode_ci,
`stateid` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`stateid`),
KEY `i_pubsub_state_jid` (`jid`(60)),
UNIQUE KEY `i_pubsub_state_tuple` (`nodeid`,`jid`(60)),
CONSTRAINT `pubsub_state_ibfk_1` FOREIGN KEY (`nodeid`) REFERENCES `pubsub_node` (`nodeid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `pubsub_subscription_opt`

CREATE TABLE `pubsub_subscription_opt` (
`subid` text COLLATE utf8mb4_unicode_ci,
`opt_name` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`opt_value` text COLLATE utf8mb4_unicode_ci,
UNIQUE KEY `i_pubsub_subscription_opt` (`subid`(32),`opt_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `roster_version`

CREATE TABLE `roster_version` (
`username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`version` text COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `sm`

CREATE TABLE `sm` (
`usec` bigint(20) NOT NULL,
`pid` text COLLATE utf8mb4_unicode_ci NOT NULL,
`node` text COLLATE utf8mb4_unicode_ci NOT NULL,
`username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`resource` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`priority` text COLLATE utf8mb4_unicode_ci NOT NULL,
`info` text COLLATE utf8mb4_unicode_ci NOT NULL,
KEY `i_node` (`node`(75)),
UNIQUE KEY `i_sid` (`usec`,`pid`(75)),
KEY `i_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `sr_group`

CREATE TABLE `sr_group` (
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`opts` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `sr_user`

CREATE TABLE `sr_user` (
`jid` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`grp` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY `i_sr_user_grp` (`grp`),
KEY `i_sr_user_jid` (`jid`),
UNIQUE KEY `i_sr_user_jid_group` (`jid`(75),`grp`(75))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- new table `vcard_xupdate`

CREATE TABLE `vcard_xupdate` (
`username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`hash` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

# Disable Foreign Keys Check
SET FOREIGN_KEY_CHECKS = 1;

For convenience you can also download the file created: ejabberd_migrate2.sql.

PS: The fixed version of php-mysql-diff can be found at https://github.com/sstrigler/php-mysql-diff, a PR has been submitted.

Update: First version was working with an outdated schema for the users table. This is fixed at ejabberd_migrate2.sql now. Apparently my ejabberd-16.01 with shipped with an incorrect schema. :-/

Random Rant on Ubuntu/Linux

Dear Ubuntu or whom it might concern,

I understand or lets say, I’ve learned after a fresh install your default email client is thunderbird. Lets rather call it a strong suggestion instead of default cause default would mean you could seamlessly switch away from it. Which you can’t. Anyway. At the same time I discovered that thunderbird does not support any calenders or management of such and as such it does not support the calendar widget in your taskabar. Something I really like and want to see on my desktop. You know, I like it when my computer reminds me of things I could easily forget otherwise. Ok. I switched to evolution because of that. And also it’s kind of advertised as a possible replacement of thunderbird. And also I’ve been using it. Years ago. My calendar works now. Thanks for that. But evolution is not able to set Emails to read when they come with an attachment. Or sth evolutions thinks is an attachment. Lets not get too much into details here. How hard can it be to deploy a decent solution here? You know, we’re talking about fundamental things here. But you don’t even get the WindowManager right. Or at least stable. So why am I even complaining?

WebRTC based Jingle for JSJaC

Thanks to Valérian Saliou from jappix.org there’s now an implementation of Jingle for JSJaC. Jingle is a protocol that allows audio and video calls on top of XMPP. The implementation makes use of WebRTC as the underlying transport. And as such by definition it’s the hottest shit. The project is called JSJaCJingle.js and can be found over at github.

Cookie monster’s eating all of my Websockets

Lately I came across to playing around with XMPP Over Websockets. The objective was to get something like page transitions working for Websockets as well as they do with BOSH. With page transitions I mean being able to integrate some XMPP client into a web site that still has the old habit of loading new pages every time new content needs to be displayed. You know this thing with hyperlinks, what we did when we were young. Anyway, in such a case a XMPP client must be able to store it’s state upon unloading a page and restore it once the new page is being loaded. This also applies to the underlying XMPP stream the client is attached to.

The current proposal of XMPP Over Websockets does not support this scenario as the websocket is within a 1:1 relationship to the c2s session (aka the XMPP stream). So upon unloading a page the websocket would be closed and as such the c2s session would have to be terminated as well. So I introduced the notion of a websocket session that manages the underlying XMPP stream and allows websockets being attached to it. The idea was to use session IDs that are being stored at a cookie being sent of the websocket.

Now unfortunately this didn’t work well. As of time of this writing (Feb, 2013) only latest versions of Firefox and Chrome support cookies at the websockets level. E.g. Safari doesn’t handle them at all. Second both are unable to expose this cookie to the JavaScript API. While getting the success case working fine with this approach for the mentioned browsers you fail to being able to handle edge cases like sessions that ran into a timeout and having session IDs which are invalid. Bummer!

So probably we’ll come up with a solution that deals with those situations at the stream level itself using XEP-0198 – Stream Management. To make this work with XMPP Over Websockets there will be slight changes to the draft that allow to not close the stream immediately after closing the websockets but uses some predefined timeout instead.

Introducing Tumblikes

Tumblikes is a webapp based on node.js that allows you to download all your liked posts at Tumblr. It’s useful for backup or offline access. The project is opensource (AGPL) and hosted at Github. For convenience there’s an npm package as well. So you might just want to

$ npm install tumblikes

If you just want to use it, there’s an instance up and running for you at http://tumblikes.x-berg.de. Enjoy!

JSJaC v1.4

Good news folks: JSJaC v1.4 has just been released. With tons of updates and bugfixes for recent versions of browsers it also comes along with support for Facebook authentication, CORS and websockets. Documentation has been switched to JSDoc3 and improved a lot. Thanks a lot to all the contributors (check github to find out more)!

Downloads available:

That said, use git! 😉

bosh speed tests

Now with the newly announced node-xmpp-bosh (NXB) my first tests left me with the impression that this thing is pretty speedy to what I am used to. So I decided to do some quick benchmarks on different implementations of BOSH. I did these tests on my desktop (some intel core 2 duo with 2,4 GHz and 4GB of RAM).

Login-Times (in ms):

Native NXB
Ejabberd 2.1.6 593 (process_delay: 100ms)
417 (process_delay 10ms)
204
Tigase 4.3.1 145 222
Prosody 0.7.0 111 170

So what we can see here is that NXB might be a feasible improvement over ejabberd’s native BOSH interface while tigase and prosody do fine in both cases.
Would be nice of course to also test with some decent load and see how the configurations behave with different loads put on them. But that’s out of scope of my quick tests for now, sorry.

Update: Ejabberd honors a ‘process_delay’ parameter which makes the BOSH CM wait for this amount of 100ms for incoming packets from the server in order to reduce the overall number of roundtrips between client and BOSH CM. This results in an overall improved client experience. Although it might be a bit slower at login times.
By default this value is set to 100ms so I did my tests again with ‘process_delay’ set to 10ms. A value of 1ms or 0ms made ejabberd fail to work properly unfortunately so I can’t provide any further numbers. But as the overall number of request roundtrips for a login is 4, the maximum number of time saved for the login would be 40ms in best case when only dealing with the ‘process_delay’ parameter.