diff --git a/plinth/modules/xmpp/static/jsxc-plinth.css b/plinth/modules/xmpp/static/jsxc-plinth.css
new file mode 100644
index 000000000..15d16d376
--- /dev/null
+++ b/plinth/modules/xmpp/static/jsxc-plinth.css
@@ -0,0 +1,124 @@
+/*
+#
+# This file is part of Plinth.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+# This file based on example code from Javascript XMPP Client which is
+# licensed as follows.
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2014 Klaus Herberth
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+*/
+
+/* Elements to hide on page load */
+.logout {
+ display: none;
+}
+
+#content form .alert {
+ display: none;
+}
+
+/* Server connection status message */
+#server-flash {
+ margin: 0;
+ margin-top: 10px;
+}
+
+#server-flash:before {
+ content: "\e031";
+ position: relative;
+ top: 1px;
+ padding-right: 5px;
+ display: inline-block;
+ font-family: 'Glyphicons Halflings';
+ font-style: normal;
+ font-weight: 400;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+}
+
+#server-flash.success:before {
+ content: "\e089";
+ color: green;
+}
+
+#server-flash.fail:before {
+ content: "\e088";
+ color: red;
+}
+
+/* Colorbox popups */
+#cboxOverlay {
+ width: 100%;
+ height: 100%;
+ position: fixed;
+ left: 0;
+ top: 0;
+ background-color: grey;
+}
+
+#colorbox {
+ border-radius: 10px;
+ background: white;
+}
+
+#cboxClose {
+ position: absolute;
+ top: 10px;
+ right: 3px;
+ cursor: pointer;
+ font-size: 0;
+ border: 0;
+ background: white;
+ height: 26px;
+}
+
+#cboxClose:after {
+ content: '\e014';
+ font-family: 'Glyphicons Halflings';
+ font-style: normal;
+ font-weight: 400;
+ font-size: 14px;
+ background: white none repeat scroll 0% 0%;
+ border: 1px solid rgb(173, 173, 173);
+ border-radius: 4px;
+ padding: 4px 6px;
+}
+
+/* Don't show options to cancel, depend on close button */
+#jsxc_dialog .jsxc_cancel {
+ display: none;
+}
diff --git a/plinth/modules/xmpp/static/jsxc-plinth.js b/plinth/modules/xmpp/static/jsxc-plinth.js
new file mode 100644
index 000000000..17e69d759
--- /dev/null
+++ b/plinth/modules/xmpp/static/jsxc-plinth.js
@@ -0,0 +1,263 @@
+/*
+#
+# This file is part of Plinth.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+# This file based on example code from Javascript XMPP Client which is
+# licensed as follows.
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2014 Klaus Herberth
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+*/
+
+$(function() {
+ var settings = {
+ url: '/http-bind/',
+ domain: plinth_settings.domainname
+ };
+
+ jsxc.init({
+ loginForm: {
+ form: '#jsxc-login-form',
+ jid: '#jsxc-username',
+ pass: '#jsxc-password'
+ },
+ checkFlash: false,
+ rosterAppend: 'body',
+ root: plinth_settings.jsxc_root,
+ displayRosterMinimized: function() {
+ return true;
+ },
+ otr: {
+ debug: true,
+ SEND_WHITESPACE_TAG: true,
+ WHITESPACE_START_AKE: true
+ },
+ loadSettings: function(username, password) {
+ return {
+ xmpp: {
+ url: settings.url,
+ domain: settings.domain,
+ resource: 'jsxc',
+ overwrite: true,
+ onlogin: true
+ }
+ };
+ }
+ });
+
+ // Form elements which needs to be enabled/disabled
+ var formElements = $('#jsxc-login-form').find('input');
+
+ // Click on logout button to logout
+ $('.logout').on('click', function() {
+ jsxc.triggeredFromElement = true;
+ return jsxc.xmpp.logout();
+ });
+
+ var logged_in_state = function() {
+ formElements.prop('disabled', true);
+ $('.submit').hide();
+ $('.logout').show();
+ }
+ var logged_out_state = function() {
+ formElements.prop('disabled', false);
+ $('.submit').show();
+ $('.logout').hide();
+ }
+
+ $(document).on('close.dialog.jsxc', function() {
+ jsxc.debug('Event triggered close.dialog.jsxc');
+ });
+
+ $(document).on('connecting.jsxc', function() {
+ jsxc.debug('Event triggered connecting.jsxc');
+ formElements.prop('disabled', true);
+ });
+
+ $(document).on('restoreCompleted.jsxc', function() {
+ jsxc.debug('Event triggered restoreCompleted.jsxc');
+ logged_in_state();
+ });
+
+ $(document).on('connected.jsxc', function() {
+ jsxc.debug('Event triggered connected.jsxc');
+ logged_in_state();
+ });
+
+ $(document).on('authfail.jsxc', function() {
+ jsxc.debug('Event triggered authfail.jsxc');
+ logged_out_state();
+ $('#jsxc-login-form').find('.submit').button('reset');
+ });
+
+ $(document).on('attached.jsxc', function() {
+ jsxc.debug('Event triggered attached.jsxc');
+ logged_in_state();
+ });
+
+ $(document).on('disconnected.jsxc', function() {
+ $('#jsxc-login-form').find('button').button('reset');
+ logged_out_state();
+ });
+
+ // Load xmpp domain from storage
+ if (typeof localStorage.getItem('xmpp-domain') === 'string') {
+ $('#xmpp-domain').val(localStorage.getItem('xmpp-domain'));
+ } else {
+ $('#xmpp-domain').val(settings.domain);
+ }
+
+ // Check bosh url, if input changed
+ $('#xmpp-domain').on('input', function(){
+ var self = $(this);
+
+ var timeout = self.data('timeout');
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+
+ var domain = $('#xmpp-domain').val();
+ if (!domain) {
+ // we need domain to test BOSH server
+ return;
+ }
+
+ localStorage.setItem('xmpp-domain', domain);
+ settings.domain = domain;
+
+ $('#server-flash').removeClass('success fail').text('Testing...');
+
+ // test only every 2 seconds
+ timeout = setTimeout(function() {
+ testBoshServer(settings.url, $('#xmpp-domain').val(), function(result) {
+ $('#server-flash').removeClass('success fail').addClass(result.status).html(result.msg);
+ });
+ }, 2000);
+
+ self.data('timeout', timeout);
+ });
+
+ // check initial bosh url
+ $('#xmpp-domain').trigger('input');
+});
+
+/**
+* Test if bosh server is up and running.
+*
+* @param {string} url BOSH url
+* @param {string} domain host domain for BOSH server
+* @param {Function} cb called if test is done
+*/
+function testBoshServer(url, domain, cb) {
+ var rid = jsxc.storage.getItem('rid') || '123456';
+
+ function fail(m) {
+ var msg = 'BOSH server NOT reachable or misconfigured.';
+
+ if (typeof m === 'string') {
+ msg += '
' + m;
+ }
+
+ cb({
+ status: 'fail',
+ msg: msg
+ });
+ }
+
+ $.ajax({
+ type: 'POST',
+ url: url,
+ data: "",
+ global: false,
+ dataType: 'xml'
+ }).done(function(stanza) {
+ if (typeof stanza === 'string') {
+ // shouldn't be needed anymore, because of dataType
+ stanza = $.parseXML(stanza);
+ }
+
+ var body = $(stanza).find('body[xmlns="http://jabber.org/protocol/httpbind"]');
+ var condition = (body) ? body.attr('condition') : null;
+ var type = (body) ? body.attr('type') : null;
+
+ // we got a valid xml response, but we have test for errors
+
+ if (body.length > 0 && type !== 'terminate') {
+ cb({
+ status: 'success',
+ msg: 'BOSH Server reachable.'
+ });
+ } else {
+ if (condition === 'internal-server-error') {
+ fail('Internal server error: ' + body.text());
+ } else if (condition === 'host-unknown') {
+ if (url) {
+ fail('Host unknown: ' + domain + ' is unknown to your XMPP server.');
+ } else {
+ fail('Host unknown: Please provide a XMPP domain.');
+ }
+ } else {
+ fail(condition);
+ }
+ }
+ }).fail(function(xhr, textStatus) {
+ // no valid xml, not found or csp issue
+
+ var fullurl;
+ if (url.match(/^https?:\/\//)) {
+ fullurl = url;
+ } else {
+ fullurl = window.location.protocol + '//' + window.location.host;
+ if (url.match(/^\//)) {
+ fullurl += url;
+ } else {
+ fullurl += window.location.pathname.replace(/[^/]+$/, "") + url;
+ }
+ }
+
+ if(xhr.status === 0) {
+ // cross-side
+ fail('Cross domain request was not possible.');
+ } else if (xhr.status === 404) {
+ // not found
+ fail('Your server responded with "404 Not Found". Please check if your BOSH server is running and reachable via ' + fullurl + '.');
+ } else if (textStatus === 'parsererror') {
+ fail('Invalid XML received. Maybe ' + fullurl + ' was redirected. You should use an absolute url.');
+ } else {
+ fail(xhr.status + ' ' + xhr.statusText);
+ }
+ });
+}
diff --git a/plinth/modules/xmpp/static/libjs-jsxc/img b/plinth/modules/xmpp/static/libjs-jsxc/img
new file mode 120000
index 000000000..7e588436e
--- /dev/null
+++ b/plinth/modules/xmpp/static/libjs-jsxc/img
@@ -0,0 +1 @@
+/usr/share/libjs-jsxc/img/
\ No newline at end of file
diff --git a/plinth/modules/xmpp/static/libjs-jsxc/jsxc.css b/plinth/modules/xmpp/static/libjs-jsxc/jsxc.css
new file mode 120000
index 000000000..d1147ba42
--- /dev/null
+++ b/plinth/modules/xmpp/static/libjs-jsxc/jsxc.css
@@ -0,0 +1 @@
+/usr/share/libjs-jsxc/jsxc.css
\ No newline at end of file
diff --git a/plinth/modules/xmpp/static/libjs-jsxc/jsxc.webrtc.css b/plinth/modules/xmpp/static/libjs-jsxc/jsxc.webrtc.css
new file mode 120000
index 000000000..b61f5cfbf
--- /dev/null
+++ b/plinth/modules/xmpp/static/libjs-jsxc/jsxc.webrtc.css
@@ -0,0 +1 @@
+/usr/share/libjs-jsxc/jsxc.webrtc.css
\ No newline at end of file
diff --git a/plinth/modules/xmpp/static/libjs-jsxc/lib b/plinth/modules/xmpp/static/libjs-jsxc/lib
new file mode 120000
index 000000000..6197f27cb
--- /dev/null
+++ b/plinth/modules/xmpp/static/libjs-jsxc/lib
@@ -0,0 +1 @@
+/usr/share/javascript/jsxc/lib
\ No newline at end of file
diff --git a/plinth/modules/xmpp/static/libjs-jsxc/sound b/plinth/modules/xmpp/static/libjs-jsxc/sound
new file mode 120000
index 000000000..c61291cd7
--- /dev/null
+++ b/plinth/modules/xmpp/static/libjs-jsxc/sound
@@ -0,0 +1 @@
+/usr/share/libjs-jsxc/sound/
\ No newline at end of file
diff --git a/plinth/modules/xmpp/templates/jsxc.html b/plinth/modules/xmpp/templates/jsxc.html
new file mode 100644
index 000000000..778570d0c
--- /dev/null
+++ b/plinth/modules/xmpp/templates/jsxc.html
@@ -0,0 +1,138 @@
+{% comment %}
+#
+# This file is part of Plinth.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+# This file based on example code from Javascript XMPP Client which is
+# licensed as follows.
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2014 Klaus Herberth
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+{% endcomment %}
+
+{% load static %}
+{% load i18n %}
+
+
+
+
+ Jabber Chat (JSXC)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+