|
@@ -0,0 +1,211 @@
|
|
|
+// Converse.js
|
|
|
+// http://conversejs.org
|
|
|
+//
|
|
|
+// Copyright (c) 2013-2018, the Converse.js developers
|
|
|
+// Licensed under the Mozilla Public License (MPLv2)
|
|
|
+
|
|
|
+/*global libsignal */
|
|
|
+(function (root, factory) {
|
|
|
+ define([
|
|
|
+ "converse-core",
|
|
|
+ "tpl!toolbar_omemo"
|
|
|
+ ], factory);
|
|
|
+}(this, function (converse, tpl_toolbar_omemo) {
|
|
|
+
|
|
|
+ const { Backbone, Promise, Strophe, sizzle, $build, _, b64_sha1 } = converse.env;
|
|
|
+
|
|
|
+ Strophe.addNamespace('OMEMO', "eu.siacs.conversations.axolotl");
|
|
|
+ Strophe.addNamespace('OMEMO_DEVICELIST', Strophe.NS.OMEMO+".devicelist");
|
|
|
+ Strophe.addNamespace('OMEMO_VERIFICATION', Strophe.NS.OMEMO+".verification");
|
|
|
+ Strophe.addNamespace('OMEMO_WHITELISTED', Strophe.NS.OMEMO+".whitelisted");
|
|
|
+
|
|
|
+ const UNDECIDED = 0;
|
|
|
+ const TRUSTED = 1;
|
|
|
+ const UNTRUSTED = -1;
|
|
|
+
|
|
|
+ converse.plugins.add('converse-omemo', {
|
|
|
+
|
|
|
+ enabled (_converse) {
|
|
|
+ return !_.isNil(window.libsignal);
|
|
|
+ },
|
|
|
+
|
|
|
+ overrides: {
|
|
|
+ ChatBoxView: {
|
|
|
+
|
|
|
+ addOMEMOToolbarButton (options) {
|
|
|
+ const { _converse } = this.__super__,
|
|
|
+ { __ } = _converse,
|
|
|
+ data = this.model.toJSON();
|
|
|
+ this.el.querySelector('.chat-toolbar').insertAdjacentHTML(
|
|
|
+ 'beforeend',
|
|
|
+ tpl_toolbar_omemo({'__': __}));
|
|
|
+ },
|
|
|
+
|
|
|
+ renderToolbar (toolbar, options) {
|
|
|
+ const result = this.__super__.renderToolbar.apply(this, arguments);
|
|
|
+ this.addOMEMOToolbarButton(options);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ initialize () {
|
|
|
+ /* The initialize function gets called as soon as the plugin is
|
|
|
+ * loaded by Converse.js's plugin machinery.
|
|
|
+ */
|
|
|
+ const { _converse } = this;
|
|
|
+
|
|
|
+ _converse.OMEMOSession = Backbone.Model.extend({
|
|
|
+
|
|
|
+ initialize () {
|
|
|
+ this.keyhelper = libsignal.KeyHelper;
|
|
|
+ },
|
|
|
+
|
|
|
+ fetchSession () {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ this.fetch({
|
|
|
+ 'success': () => {
|
|
|
+ if (!_converse.omemo_session.get('registration_id')) {
|
|
|
+ this.keyhelper.generateIdentityKeyPair().then(function (keypair) {
|
|
|
+ _converse.omemo_session.set({
|
|
|
+ 'registration_id': this.keyhelper.generateRegistrationId(),
|
|
|
+ 'pub_key': keypair.pubKey,
|
|
|
+ 'priv_key': keypair.privKey
|
|
|
+ });
|
|
|
+ resolve();
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ resolve();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ _converse.Device = Backbone.Model.extend({
|
|
|
+ defaults: {
|
|
|
+ 'active': true,
|
|
|
+ 'trusted': UNDECIDED
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ _converse.Devices = Backbone.Collection.extend({
|
|
|
+ model: _converse.Device,
|
|
|
+ });
|
|
|
+
|
|
|
+ _converse.DeviceList = Backbone.Model.extend({
|
|
|
+ idAttribute: 'jid',
|
|
|
+
|
|
|
+ initialize () {
|
|
|
+ this.devices = new _converse.Devices();
|
|
|
+ },
|
|
|
+
|
|
|
+ fetchDevices () {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ this.devices.fetch({
|
|
|
+ success (collection) {
|
|
|
+ if (collection.length === 0) {
|
|
|
+ this.fetchDevicesFromServer().then(resolve).catch(reject);
|
|
|
+ } else {
|
|
|
+ resolve();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ fetchDevicesFromServer () {
|
|
|
+ // TODO: send IQ stanza to get device list.
|
|
|
+ return Promise.resolve([]);
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ _converse.DeviceLists = Backbone.Collection.extend({
|
|
|
+ model: _converse.DeviceList,
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ function publishBundle () {
|
|
|
+ // TODO: publish bundle information (public key and pre keys)
|
|
|
+ // Keep the used device id consistant. You have to republish
|
|
|
+ // this because you don't know if the server was restarted or might have
|
|
|
+ // otherwise lost the information.
|
|
|
+ return Promise.resolve();
|
|
|
+ }
|
|
|
+
|
|
|
+ function fetchDeviceLists () {
|
|
|
+ return new Promise((resolve, reject) => _converse.devicelists.fetch({'success': resolve}));
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateOwnDeviceList () {
|
|
|
+ /* If our own device is not on the list, add it.
|
|
|
+ * Also, deduplicate devices if necessary.
|
|
|
+ */
|
|
|
+ // TODO:
|
|
|
+ const devicelist = _converse.devicelists.get(_converse.bare_jid);
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateDevicesFromStanza (stanza) {
|
|
|
+ const device_ids = _.map(
|
|
|
+ sizzle(`items[node="${Strophe.NS.OMEMO_DEVICELIST}"] item[xmlns="${Strophe.NS.OMEMO}"] device`, stanza),
|
|
|
+ (device) => device.getAttribute('id'));
|
|
|
+
|
|
|
+ const removed_ids = _.difference(_converse.devices.pluck('id'), device_ids);
|
|
|
+ _.forEach(removed_ids, (removed_id) => _converse.devices.get(removed_id).set('active', false));
|
|
|
+
|
|
|
+ _.forEach(device_ids, (device_id) => {
|
|
|
+ const dev = _converse.devices.get(device_id);
|
|
|
+ if (dev) {
|
|
|
+ dev.save({'active': true});
|
|
|
+ } else {
|
|
|
+ _converse.devices.create({'id': device_id})
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function registerPEPPushHandler () {
|
|
|
+ // Add a handler for devices pushed from other connected clients
|
|
|
+ _converse.connection.addHandler((message) => {
|
|
|
+ if (message.querySelector('event[xmlns="'+Strophe.NS.PUBSUB+'#event"]')) {
|
|
|
+ _converse.bookmarks.updateDevicesFromStanza(message);
|
|
|
+ }
|
|
|
+ }, null, 'message', 'headline', null, _converse.bare_jid);
|
|
|
+ }
|
|
|
+
|
|
|
+ function initOMEMO () {
|
|
|
+ /* Publish our bundle and then fetch our own device list.
|
|
|
+ * If our device list does not contain this device's id, publish the
|
|
|
+ * device list with the id added. Also deduplicate device ids in the list.
|
|
|
+ */
|
|
|
+ publishBundle()
|
|
|
+ .then(() => fetchDeviceLists())
|
|
|
+ .then(() => _converse.devicelists.get(_converse.bare_jid).fetchDevices())
|
|
|
+ .then(updateOwnDeviceList)
|
|
|
+ .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
|
|
|
+ }
|
|
|
+
|
|
|
+ function onStatusInitialized () {
|
|
|
+ _converse.devicelists = new _converse.DeviceLists();
|
|
|
+ _converse.devicelists.browserStorage = new Backbone.BrowserStorage.session(
|
|
|
+ b64_sha1(`converse.devicelists-${_converse.bare_jid}`)
|
|
|
+ );
|
|
|
+
|
|
|
+ _converse.omemo_session = new Backbone.Model();
|
|
|
+ _converse.omemo_session.browserStorage = new Backbone.BrowserStorage.session(
|
|
|
+ b64_sha1(`converse.omemosession-${_converse.bare_jid}`)
|
|
|
+ );
|
|
|
+ _converse.omemo_session.fetchSession()
|
|
|
+ .then(initOMEMO)
|
|
|
+ .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
|
|
|
+ }
|
|
|
+
|
|
|
+ _converse.api.listen.on('statusInitialized', onStatusInitialized);
|
|
|
+ _converse.api.listen.on('connected', registerPEPPushHandler);
|
|
|
+ _converse.api.listen.on('afterTearDown', () => _converse.devices.reset());
|
|
|
+ _converse.api.listen.on('addClientFeatures',
|
|
|
+ () => _converse.api.disco.own.features.add(Strophe.NS.OMEMO_DEVICELIST+"notify"));
|
|
|
+ }
|
|
|
+ });
|
|
|
+}));
|